diff --git a/.gitignore b/.gitignore
index 9029ada..661a671 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ server/images
doca.txt
data/
client/*.dot
+server/points.txt
diff --git a/README.md b/README.md
index c943d7f..aa8d7b8 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,48 @@
-test
+Release:
+ * Engine
+ + Benchmark harness
+ + Reuse points, pack "nodraw" in high bit of stroke id (probably have at least one more bit, so up to 4 flag configurations)
+ - Draw dynamic data (strokes in progress)
+ - Textured quads (pictures, code already written in older version)
+ - Resize and move pictures (draw handles)
+ - Z-prepass fringe bug (also, when do we enable the prepass?)
+ - Restore ability to limit event range
+ - Only upload stroke data to texture as it arrives (texSubImage2D)
+ - Listeners/events
+ - Investigate skipped inputs on mobile (panning, zooming)
+ - Save events to indexeddb (as some kind of a blob), restore on reconnect and page reload
+ - Separate events and other data clearly (events are self-contained, other data is temporal/non-vital)
+ - Do NOT use session id as player id LUL
+ - Local prediction for tools!
+ - Drag with mouse button 3
+ - Missing features I do not consider bonus
+ - Eraser
+ - Line drawing
+ - Player screens/pointers
+ - Follow player (like Ligma)
+ - Color picker (or at the very least an Open Color color pallete)
+ - Undo/redo
+ - Dynamic svg cursor to represent the brush
+ - Polish
+ - Show what's happening while the desk is loading (downloading, processing, uploading to gpu)
+ - Settings panel (including the setting for "offline mode")
+ - Presentation / "marketing"
+ - Title
+ - Icon
+ - Product page (github readme, demo videos)
-# test
+Bonus:
+ - Handle pressure
+ - Add pressure data to quads
+ - Draw capsules instead of segments
+ - Adjust curve simplification to include pressure info
+ - Curve modification
+ - Select curves (with a lasso?)
+ - Move whole curve
+ - Move single point
+ - Move multiple points
+ - Customizable background
+ - Color, textures, procedural
-a
-- b
-- c
-
-*d*
-
-**e**
+Bonus-bonus:
+ - Actually infinite canvas (replace floats with something, some kind of fixed point scheme? chunks? multilevel scheme?)
diff --git a/client/aux.js b/client/aux.js
index aab8c57..faa0fa5 100644
--- a/client/aux.js
+++ b/client/aux.js
@@ -101,4 +101,45 @@ function find_image(state, image_id) {
return event;
}
}
-}
\ No newline at end of file
+}
+
+// TODO: move these to a file? TypedVector
+function tv_create(class_name, capacity) {
+ return {
+ 'class_name': class_name,
+ 'data': new class_name(capacity),
+ 'capacity': capacity,
+ 'size': 0,
+ };
+}
+
+function tv_data(tv) {
+ return tv.data.subarray(0, tv.size);
+}
+
+function tv_ensure(tv, capacity) {
+ if (tv.capacity < capacity) {
+ const new_tv = tv_create(tv.class_name, capacity);
+
+ new_tv.data.set(tv_data(tv));
+ new_tv.size = tv.size;
+
+ return new_tv;
+ }
+
+ return tv;
+}
+
+function tv_ensure_by(tv, by) {
+ return tv_ensure(tv, tv.capacity + by);
+}
+
+function tv_add(tv, item) {
+ tv.data[tv.size++] = item;
+}
+
+function tv_clear(tv) {
+ tv.size = 0;
+}
+
+
diff --git a/client/index.html b/client/index.html
index c8c94f7..341d140 100644
--- a/client/index.html
+++ b/client/index.html
@@ -41,7 +41,6 @@
Simple shader
Depth prepass
- Draw BVH
Limit events from
@@ -52,6 +51,8 @@
Limit events to
+
+ Benchmark
diff --git a/client/index.js b/client/index.js
index f18ef44..ba26382 100644
--- a/client/index.js
+++ b/client/index.js
@@ -1,9 +1,3 @@
-// NEXT: pan with m3, place dot, cursor size and color, YELLOW and gray, show user cursor, background styles,
-// view desks, undo, eraser, ruler, images (movable), quadtree for clipping, f5 without redownload, progress bar
-//
-// use returning ID on insert intead of (COLLIDING!) rand_32
-// look into using u64 for point coordinates? fix bad drawings on zoomout
-
document.addEventListener('DOMContentLoaded', main);
const config = {
@@ -24,14 +18,16 @@ const config = {
initial_offline_timeout: 1000,
default_color: 0x00,
default_width: 8,
- bytes_per_instance: 4 * 4 + 4, // axy, bxy, stroke_id
- bytes_per_stroke: 3 + 1, // r, g, b, width
+ bytes_per_instance: 4 * 2 + 4, // axy, stroke_id
+ bytes_per_stroke: 2 * 3 + 2, // r, g, b, width
initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096,
- tile_size: 16,
- clip_zoom_threshold: 0.00003,
stroke_texture_size: 1024,
- rdp_cache_threshold: 100,
+ benchmark: {
+ zoom: 0.035,
+ offset: { x: 900, y: 400 },
+ frames: 500,
+ },
};
const EVENT = Object.freeze({
@@ -178,6 +174,18 @@ function main() {
'count': 0,
},
+ 'segments_from': {
+ 'data': null,
+ 'count': 0,
+ 'cap': 0,
+ },
+
+ 'segments': {
+ 'data': null,
+ 'count': 0,
+ 'cap': 0,
+ },
+
'bvh': {
'nodes': [],
'root': null,
@@ -209,7 +217,6 @@ function main() {
'limit_to': false,
'render_from': 0,
'render_to': 0,
- 'draw_bvh': false,
},
'rdp_cache': {},
@@ -236,8 +243,9 @@ function main() {
'cap': 0,
},
- 'instance_data': serializer_create(config.initial_static_bytes),
-
+ 'instance_data_points': tv_create(Float32Array, 4096),
+ 'instance_data_ids': tv_create(Uint32Array, 4096),
+
'lods': [],
'stroke_data': serializer_create(config.initial_static_bytes),
diff --git a/client/math.js b/client/math.js
index 1960d2f..8d44689 100644
--- a/client/math.js
+++ b/client/math.js
@@ -9,18 +9,10 @@ function screen_to_canvas(state, p) {
return {'x': xc, 'y': yc};
}
-
+/*
function rdp_find_max(state, zoom, stroke, start, end) {
- // Finds a point from the range [start, end) with the maximum distance from the line (start--end)
+ // Finds a point from the range [start, end) with the maximum distance from the line (start--end) that is also further than EPS
const EPS = 1.0 / zoom;
- let cache_key = null;
-
- if (end - start > config.rdp_cache_threshold) {
- cache_key = stroke.index + '-' + zoom + '-' + start + '-' + end;
- if (cache_key in state.rdp_cache) {
- return state.rdp_cache[cache_key];
- }
- }
let result = -1;
let max_dist = 0;
@@ -54,14 +46,10 @@ function rdp_find_max(state, zoom, stroke, start, end) {
state.stats.rdp_max_count++;
state.stats.rdp_segments += end - start - 1;
-
- if (end - start > config.rdp_cache_threshold) {
- state.rdp_cache[cache_key] = result;
- }
return result;
}
-
+*/
function process_rdp_indices_r(state, zoom, mask, stroke, start, end) {
// Looks like the recursive implementation spends most of its time in the function call overhead
// Let's try to use an explicit stack instead to give the js engine more room to play with
@@ -127,14 +115,13 @@ function process_stroke(state, zoom, stroke) {
return 2;
}
- // const result0 = process_ewmv(points);
- const result1 = process_rdp_indices(state, zoom, stroke, true);
+ const npoints = process_rdp_indices(state, zoom, stroke, true);
- if (result1 === 2 && zoom > stroke.turns_into_straight_line_zoom) {
+ if (npoints === 2 && zoom > stroke.turns_into_straight_line_zoom) {
stroke.turns_into_straight_line_zoom = zoom;
}
- return result1;
+ return npoints;
}
function strokes_intersect_line(state, a, b) {
diff --git a/client/webgl_draw.js b/client/webgl_draw.js
index ad082e2..31a8937 100644
--- a/client/webgl_draw.js
+++ b/client/webgl_draw.js
@@ -66,19 +66,18 @@ function draw(state, context) {
gl.clearDepth(0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
-
locations = context.locations['sdf'].main;
buffers = context.buffers['sdf'];
gl.useProgram(context.programs['sdf'].main);
-
bvh_clip(state, context);
const segment_count = geometry_write_instances(state, context);
- // TODO: maybe have a pool of buffers (pow2?) and select an appropriate one
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
- gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(context.instance_data.buffer, 0, segment_count * config.bytes_per_instance), gl.DYNAMIC_DRAW);
+ gl.bufferData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4, gl.STREAM_DRAW);
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.instance_data_points));
+ gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4, tv_data(context.instance_data_ids));
gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
@@ -88,13 +87,17 @@ function draw(state, context) {
gl.uniform1i(locations['u_stroke_data'], 0);
gl.uniform1i(locations['u_stroke_texture_size'], config.stroke_texture_size);
- gl.enableVertexAttribArray(locations['a_ab']);
+ gl.enableVertexAttribArray(locations['a_a']);
+ gl.enableVertexAttribArray(locations['a_b']);
gl.enableVertexAttribArray(locations['a_stroke_id']);
+
+ // Points (a, b) and stroke ids are not stored in separate cpu buffers so that points can be resued
+ gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
+ gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
+ gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4);
- gl.vertexAttribPointer(locations['a_ab'], 4, gl.FLOAT, false, config.bytes_per_instance, 0);
- gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_instance, 4 * 4);
-
- gl.vertexAttribDivisor(locations['a_ab'], 1);
+ gl.vertexAttribDivisor(locations['a_a'], 1);
+ gl.vertexAttribDivisor(locations['a_b'], 1);
gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
@@ -109,69 +112,6 @@ function draw(state, context) {
Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y})
Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000} `;
-
- /*
- const after_clip = performance.now();
-
- gl.bindBuffer(gl.ARRAY_BUFFER, lod.data_buffer);
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, lod.index_buffer);
-
- upload_if_needed(gl, gl.ARRAY_BUFFER, lod.vertices);
- upload_if_needed(gl, gl.ELEMENT_ARRAY_BUFFER, lod.indices);
-
-
- if (index_count > 0) {
- // DEPTH PREPASS
- if (state.debug.do_prepass) {
- gl.drawBuffers([gl.NONE]);
-
- locations = context.locations['sdf'].opaque;
-
- gl.useProgram(context.programs['sdf'].opaque);
-
- gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
- gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
- gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
- gl.uniform1i(locations['u_stroke_count'], state.stroke_count);
-
- gl.enableVertexAttribArray(locations['a_pos']);
- gl.enableVertexAttribArray(locations['a_line']);
- gl.enableVertexAttribArray(locations['a_stroke_id']);
-
- gl.vertexAttribPointer(locations['a_pos'], 3, gl.FLOAT, false, config.bytes_per_point, 0);
- gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3);
- gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4);
-
- gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0);
- }
-
- // MAIN PASS
- gl.drawBuffers([gl.BACK]);
-
- locations = context.locations['sdf'].main;
-
- gl.useProgram(context.programs['sdf'].main);
-
- gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
- gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
- gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
- gl.uniform1i(locations['u_stroke_count'], state.stroke_count);
- gl.uniform1i(locations['u_debug_mode'], state.debug.red);
-
- gl.enableVertexAttribArray(locations['a_pos']);
- gl.enableVertexAttribArray(locations['a_line']);
- gl.enableVertexAttribArray(locations['a_color']);
- gl.enableVertexAttribArray(locations['a_stroke_id']);
-
- gl.vertexAttribPointer(locations['a_pos'], 3, gl.FLOAT, false, config.bytes_per_point, 0);
- gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3);
- gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 4 * 3 + 4 * 4);
- gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4);
-
- //index_buffer.reverse();
- gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0);
- }
-*/
/*
// Dynamic data (stroke previews that are currently in progress)
const dynamic_points = context.dynamic_serializer.offset / config.bytes_per_point;
@@ -234,46 +174,6 @@ function draw(state, context) {
gl.drawElements(gl.TRIANGLES, dynamic_indices.length, gl.UNSIGNED_INT, 0);
}
*/
-
- if (state.debug.draw_bvh) {
- const points = new Float32Array(state.bvh.nodes.length * 6 * 2);
-
- for (let i = 0; i < state.bvh.nodes.length; ++i) {
- const box = state.bvh.nodes[i].bbox;
-
- points[i * 12 + 0] = box.x1;
- points[i * 12 + 1] = box.y1;
- points[i * 12 + 2] = box.x2;
- points[i * 12 + 3] = box.y1;
- points[i * 12 + 4] = box.x1;
- points[i * 12 + 5] = box.y2;
-
- points[i * 12 + 6] = box.x2;
- points[i * 12 + 7] = box.y2;
- points[i * 12 + 8] = box.x1;
- points[i * 12 + 9] = box.y2;
- points[i * 12 + 10] = box.x2;
- points[i * 12 + 11] = box.y1;
- }
-
- locations = context.locations['debug'];
- buffers = context.buffers['debug'];
-
- gl.useProgram(context.programs['debug']);
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed']);
-
- gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
- gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
- gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
-
- gl.enableVertexAttribArray(locations['a_pos']);
- gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 8, 0);
-
- gl.clear(gl.DEPTH_BUFFER_BIT);
- gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
- gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);
- }
-
if (context.gpu_timer_ext) {
gl.endQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT);
@@ -308,4 +208,11 @@ function draw(state, context) {
document.querySelector('.debug-timings .cpu').innerHTML = 'Last CPU Frametime: ' + Math.round((cpu_after - cpu_before) * 100) / 100 + 'ms';
+
+ if (state.debug.benchmark_mode) {
+ const redraw = state.debug.on_benchmark();
+ if (redraw) {
+ schedule_draw(state, context);
+ }
+ }
}
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index 8965913..20c2dd6 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -36,68 +36,148 @@ function geometry_prepare_stroke(state) {
};
}
+function rdp_find_max(state, zoom, stroke, start, end) {
+ // Finds a point from the range [start, end) with the maximum distance from the line (start--end) that is also further than EPS
+ const EPS = 1.0 / zoom;
+
+ let result = -1;
+ let max_dist = 0;
+
+ const ax = state.coordinates.data[stroke.coords_from + start * 2 + 0];
+ const ay = state.coordinates.data[stroke.coords_from + start * 2 + 1];
+ const bx = state.coordinates.data[stroke.coords_from + end * 2 + 0];
+ const by = state.coordinates.data[stroke.coords_from + end * 2 + 1];
+
+ const dx = bx - ax;
+ const dy = by - ay;
+
+ const dist_ab = Math.sqrt(dx * dx + dy * dy);
+ const dir_nx = dy / dist_ab;
+ const dir_ny = -dx / dist_ab;
+
+ for (let i = start + 1; i < end; ++i) {
+ const px = state.coordinates.data[stroke.coords_from + i * 2 + 0];
+ const py = state.coordinates.data[stroke.coords_from + i * 2 + 1];
+
+ const apx = px - ax;
+ const apy = py - ay;
+
+ const dist = Math.abs(apx * dir_nx + apy * dir_ny);
+
+ if (dist > EPS && dist > max_dist) {
+ result = i;
+ max_dist = dist;
+ }
+ }
+
+ state.stats.rdp_max_count++;
+ state.stats.rdp_segments += end - start - 1;
+
+ return result;
+}
+
function geometry_write_instances(state, context) {
- context.instance_data = ser_ensure(context.instance_data, state.coordinates.count / 2 * config.bytes_per_instance);
- ser_clear(context.instance_data);
+ if (state.segments_from.cap < context.clipped_indices.count + 1) {
+ state.segments_from.cap = round_to_pow2(context.clipped_indices.count + 1, 4096);
+ state.segments_from.data = new Uint32Array(state.segments_from.cap);
+ }
+
+ if (state.segments.cap < state.coordinates.count / 2) {
+ state.segments.cap = round_to_pow2(state.coordinates.count, 4096);
+ state.segments.data = new Uint32Array(state.segments.cap);
+ }
+
+ state.segments_from.count = 0;
+ state.segments.count = 0;
state.stats.rdp_max_count = 0;
state.stats.rdp_segments = 0;
- let segment_count = 0;
-
let fast_path_count = 0;
let slow_path_count = 0;
+ const stack = [];
+
for (let i = 0; i < context.clipped_indices.count; ++i) {
const stroke_index = context.clipped_indices.data[i];
const stroke = state.events[stroke_index];
- const lod_indices_count = process_stroke(state, state.canvas.zoom, stroke);
-
- segment_count += lod_indices_count - 1;
-
- if (lod_indices_count === 2) {
- fast_path_count++;
- // Fast path
- const ax = state.coordinates.data[stroke.coords_from + 0];
- const ay = state.coordinates.data[stroke.coords_from + 1];
- const bx = state.coordinates.data[stroke.coords_to - 2];
- const by = state.coordinates.data[stroke.coords_to - 1];
-
- ser_f32(context.instance_data, ax);
- ser_f32(context.instance_data, ay);
- ser_f32(context.instance_data, bx);
- ser_f32(context.instance_data, by);
- ser_u32(context.instance_data, stroke_index);
+ const point_count = (stroke.coords_to - stroke.coords_from) / 2;
+
+ // Basic CSR crap
+ state.segments_from.data[i] = state.segments.count;
+
+ if (state.canvas.zoom <= stroke.turns_into_straight_line_zoom) {
+ state.segments.data[state.segments.count++] = 0;
+ state.segments.data[state.segments.count++] = point_count - 1;
} else {
- slow_path_count++;
- let base_this = 0;
- let base_next = 0;
-
- for (let j = 0; j < lod_indices_count - 1; ++j) {
- while (state.rdp_mask[base_this] == 0) base_this++;
- base_next = base_this + 1;
- while (state.rdp_mask[base_next] == 0) base_next++;
-
- const ax = state.coordinates.data[stroke.coords_from + base_this * 2 + 0];
- const ay = state.coordinates.data[stroke.coords_from + base_this * 2 + 1];
- const bx = state.coordinates.data[stroke.coords_from + base_next * 2 + 0];
- const by = state.coordinates.data[stroke.coords_from + base_next * 2 + 1];
-
- ser_f32(context.instance_data, ax);
- ser_f32(context.instance_data, ay);
- ser_f32(context.instance_data, bx);
- ser_f32(context.instance_data, by);
- ser_u32(context.instance_data, stroke_index);
-
- base_this = base_next;
+ let segment_count = 2;
+
+ state.segments.data[state.segments.count++] = 0;
+
+ stack.length = 0;
+ stack.push({'type': 0, 'start': 0, 'end': point_count - 1});
+
+ while (stack.length > 0) {
+ const entry = stack.pop();
+
+ if (entry.type === 1) {
+ state.segments.data[state.segments.count++] = entry.value;
+ } else {
+ const max = rdp_find_max(state, state.canvas.zoom, stroke, entry.start, entry.end);
+ if (max !== -1) {
+ segment_count += 1;
+ stack.push({'type': 0, 'start': max, 'end': entry.end});
+ stack.push({'type': 1, 'value': max});
+ stack.push({'type': 0, 'start': entry.start, 'end': max});
+ }
+ }
+ }
+
+ state.segments.data[state.segments.count++] = point_count - 1;
+
+ if (segment_count === 2 && state.canvas.zoom > stroke.turns_into_straight_line_zoom) {
+ stroke.turns_into_straight_line_zoom = state.canvas.zoom;
+ }
+ }
+ }
+
+ state.segments_from.data[context.clipped_indices.count] = state.segments.count;
+ state.segments_from.count = context.clipped_indices.count + 1;
+
+ context.instance_data_points = tv_ensure(context.instance_data_points, state.segments.count * 2);
+ context.instance_data_ids = tv_ensure(context.instance_data_ids, state.segments.count);
+
+ tv_clear(context.instance_data_points);
+ tv_clear(context.instance_data_ids);
+
+ for (let i = 0; i < state.segments_from.count - 1; ++i) {
+ const stroke_index = context.clipped_indices.data[i];
+ const stroke = state.events[stroke_index];
+ const from = state.segments_from.data[i];
+ const to = state.segments_from.data[i + 1];
+
+ for (let j = from; j < to; ++j) {
+ const base_this = state.segments.data[j];
+
+ const ax = state.coordinates.data[stroke.coords_from + base_this * 2 + 0];
+ const ay = state.coordinates.data[stroke.coords_from + base_this * 2 + 1];
+
+ tv_add(context.instance_data_points, ax);
+ tv_add(context.instance_data_points, ay);
+
+ // Pack 1 into highest bit of stroke_index if we should not draw a segemtn from this
+ // point to the next one
+ if (j != to - 1) {
+ tv_add(context.instance_data_ids, stroke_index);
+ } else {
+ tv_add(context.instance_data_ids, stroke_index | (1 << 31));
}
}
}
- console.debug('fast:', fast_path_count, 'slow:', slow_path_count);
- console.debug('rdp max:', state.stats.rdp_max_count, 'rdp segments:', state.stats.rdp_segments);
+ if (config.debug_print) console.debug('instances:', state.segments.count, 'rdp max:', state.stats.rdp_max_count, 'rdp segments:', state.stats.rdp_segments);
- return segment_count;
+ return state.segments.count;
}
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index ce9c254..3cc8cad 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -26,12 +26,6 @@ function debug_panel_init(state, context) {
document.getElementById('debug-do-prepass').checked = state.debug.do_prepass;
document.getElementById('debug-limit-from').checked = state.debug.limit_from;
document.getElementById('debug-limit-to').checked = state.debug.limit_to;
- document.getElementById('debug-draw-bvh').checked = state.debug.draw_bvh;
-
- document.getElementById('debug-draw-bvh').addEventListener('change', (e) => {
- state.debug.draw_bvh = e.target.checked;
- schedule_draw(state, context);
- });
document.getElementById('debug-red').addEventListener('change', (e) => {
state.debug.red = e.target.checked;
@@ -62,6 +56,40 @@ function debug_panel_init(state, context) {
state.debug.render_to = parseInt(e.target.value);
schedule_draw(state, context);
});
+
+ document.getElementById('debug-begin-benchmark').addEventListener('click', (e) => {
+ state.canvas.zoom = config.benchmark.zoom;
+ state.canvas.offset.x = config.benchmark.offset.x;
+ state.canvas.offset.y = config.benchmark.offset.y;
+
+ state.debug.benchmark_mode = true;
+
+ const origin_x = state.canvas.offset.x;
+ const origin_y = state.canvas.offset.y;
+ const original_button_text = e.target.innerText;
+
+ let frame = 0;
+
+ state.debug.on_benchmark = () => {
+ if (frame >= config.benchmark.frames) {
+ state.debug.benchmark_mode = false;
+ e.target.disabled = false;
+ e.target.innerText = original_button_text;
+ return false;
+ }
+
+ state.canvas.offset.x = origin_x + Math.round(100 * Math.cos(frame / 360));
+ state.canvas.offset.y = origin_y + Math.round(100 * Math.sin(frame / 360));
+ frame += 1;
+
+ return true;
+ }
+
+ e.target.disabled = true;
+ e.target.innerText = 'Benchmark in progress...';
+
+ schedule_draw(state, context);
+ });
}
function cancel(e) {
diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js
index 2a65f0b..1520e9e 100644
--- a/client/webgl_shaders.js
+++ b/client/webgl_shaders.js
@@ -102,7 +102,8 @@ const nop_fs_src = `#version 300 es
`;
const sdf_vs_src = `#version 300 es
- in vec4 a_ab; // original points
+ in vec2 a_a; // point from
+ in vec2 a_b; // point to
in float a_radius;
in int a_stroke_id;
@@ -130,10 +131,7 @@ const sdf_vs_src = `#version 300 es
uvec4 stroke_data = texelFetch(u_stroke_data, ivec2(stroke_data_x, stroke_data_y), 0);
float radius = float(stroke_data.w);
- vec2 a = a_ab.xy;
- vec2 b = a_ab.zw;
-
- vec2 line_dir = normalize(b - a);
+ vec2 line_dir = normalize(a_b - a_a);
vec2 up_dir = vec2(line_dir.y, -line_dir.x);
vec2 pixel = vec2(2.0) / u_res * apron;
float rscale = apron / u_scale.x;
@@ -145,19 +143,19 @@ const sdf_vs_src = `#version 300 es
if (vertex_index == 0) {
// "top left" aka "p1"
- origin = a;
+ origin = a_a;
outwards = up_dir - line_dir;
} else if (vertex_index == 1 || vertex_index == 5) {
// "top right" aka "p2"
- origin = b;
+ origin = a_b;
outwards = up_dir + line_dir;
} else if (vertex_index == 2 || vertex_index == 4) {
// "bottom left" aka "p3"
- origin = a;
+ origin = a_a;
outwards = -up_dir - line_dir;
} else {
// "bottom right" aka "p4"
- origin = b;
+ origin = a_b;
outwards = -up_dir + line_dir;
}
@@ -167,10 +165,14 @@ const sdf_vs_src = `#version 300 es
screen02.y = 2.0 - screen02.y;
- v_line = vec4(a, b);
+ v_line = vec4(a_a, a_b);
v_thickness = radius;
v_color = vec3(stroke_data.xyz) / 255.0;
+ if (a_stroke_id >> 31 != 0) {
+ screen02 += vec2(100.0); // shift offscreen
+ }
+
gl_Position = vec4(screen02 - 1.0, (float(a_stroke_id) / float(u_stroke_count)) * 2.0 - 1.0, 1);
}
`;
@@ -307,7 +309,8 @@ function init_webgl(state, context) {
},
'main': {
- 'a_ab': gl.getAttribLocation(context.programs['sdf'].main, 'a_ab'),
+ 'a_a': gl.getAttribLocation(context.programs['sdf'].main, 'a_a'),
+ 'a_b': gl.getAttribLocation(context.programs['sdf'].main, 'a_b'),
'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].main, 'a_stroke_id'),
'u_res': gl.getUniformLocation(context.programs['sdf'].main, 'u_res'),