diff --git a/client/bvh.js b/client/bvh.js
index 45496c4..546f10f 100644
--- a/client/bvh.js
+++ b/client/bvh.js
@@ -175,7 +175,7 @@ function bvh_intersect_quad(bvh, quad, result_buffer) {
if (node.is_fullnode) {
if (quad_fully_inside(quad, node.bbox)) {
- tv_append(result_buffer, node.stroke_indices.data);
+ tv_append(result_buffer, tv_data(node.stroke_indices));
continue;
}
}
diff --git a/client/client_recv.js b/client/client_recv.js
index 24ae3f6..aabeda8 100644
--- a/client/client_recv.js
+++ b/client/client_recv.js
@@ -112,7 +112,6 @@ function des_event(d, state = null) {
// TODO: remove, this is duplicate data
-
event.stroke_id = stroke_id;
event.color = color;
@@ -242,6 +241,10 @@ function handle_event(state, context, event, options = {}) {
case EVENT.STROKE: {
const point_count = event.coords.length / 2;
+ if (point_count === 0) {
+ break;
+ }
+
wasm_ensure_by(state, 1, event.coords.length);
const pressures = state.wasm.buffers['pressures'];
diff --git a/client/index.html b/client/index.html
index 2bbdaf0..dacf28e 100644
--- a/client/index.html
+++ b/client/index.html
@@ -7,23 +7,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/speed.js b/client/speed.js
index 37dcf75..0eabc76 100644
--- a/client/speed.js
+++ b/client/speed.js
@@ -200,6 +200,7 @@ async function do_lod(state, context) {
}
await workers_messages(state.wasm.workers, jobs);
+
const result_offset = state.wasm.exports.merge_results(
result_counts,
result_buffers,
diff --git a/client/webgl_draw.js b/client/webgl_draw.js
index 78e79a4..35185a3 100644
--- a/client/webgl_draw.js
+++ b/client/webgl_draw.js
@@ -78,155 +78,158 @@ function draw_html(state) {
async function draw(state, context) {
const cpu_before = performance.now();
+ try {
+ const gl = context.gl;
+ const width = window.innerWidth;
+ const height = window.innerHeight;
- const gl = context.gl;
- const width = window.innerWidth;
- const height = window.innerHeight;
-
- locations = context.locations['sdf'].main;
- buffers = context.buffers['sdf'];
+ locations = context.locations['sdf'].main;
+ buffers = context.buffers['sdf'];
- bvh_clip(state, context);
-
- const segment_count = await geometry_write_instances(state, context);
- const dynamic_segment_count = context.dynamic_segment_count;
- const dynamic_stroke_count = context.dynamic_stroke_count;
+ bvh_clip(state, context);
- let query = null;
+ const segment_count = await geometry_write_instances(state, context);
+ const dynamic_segment_count = context.dynamic_segment_count;
+ const dynamic_stroke_count = context.dynamic_stroke_count;
- if (context.gpu_timer_ext !== null) {
- query = gl.createQuery();
- gl.beginQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT, query);
- }
-
- // Only clear once we have the data, this might not always be on the same frame?
- gl.viewport(0, 0, context.canvas.width, context.canvas.height);
- gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1);
- gl.clearDepth(0.0);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
-
- gl.useProgram(context.programs['sdf'].main);
-
- // "Static" data upload
- if (segment_count > 0) {
- const total_static_size = context.instance_data_points.size * 4 +
- context.instance_data_ids.size * 4 +
- context.instance_data_pressures.size;
-
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
- gl.bufferData(gl.ARRAY_BUFFER, total_static_size, 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.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4,
- tv_data(context.instance_data_pressures));
- gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
- upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size);
-
- 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.events.length);
- gl.uniform1i(locations['u_debug_mode'], state.debug.red);
- gl.uniform1i(locations['u_stroke_data'], 0);
- gl.uniform1i(locations['u_stroke_texture_size'], config.stroke_texture_size);
-
- gl.enableVertexAttribArray(locations['a_a']);
- gl.enableVertexAttribArray(locations['a_b']);
- gl.enableVertexAttribArray(locations['a_stroke_id']);
- gl.enableVertexAttribArray(locations['a_pressure']);
-
- // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
- 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_pressure'], 2, gl.UNSIGNED_BYTE, true, 1, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4);
-
- gl.vertexAttribDivisor(locations['a_a'], 1);
- gl.vertexAttribDivisor(locations['a_b'], 1);
- gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
- gl.vertexAttribDivisor(locations['a_pressure'], 1);
-
- // Static draw (everything already bound)
- gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count);
- }
+ let query = null;
- // Dynamic strokes should be drawn above static strokes
- gl.clear(gl.DEPTH_BUFFER_BIT);
-
- // Dynamic draw (strokes currently being drawn)
- if (dynamic_segment_count > 0) {
- gl.uniform1i(locations['u_stroke_count'], dynamic_stroke_count);
- gl.uniform1i(locations['u_stroke_data'], 0);
- gl.uniform1i(locations['u_stroke_texture_size'], config.dynamic_stroke_texture_size);
-
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_dynamic_instance']);
-
- // Dynamic data upload
- const total_dynamic_size =
- context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4 +
- context.dynamic_instance_pressure.size;
-
- gl.bufferData(gl.ARRAY_BUFFER, total_dynamic_size, gl.STREAM_DRAW);
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.dynamic_instance_points));
- gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4, tv_data(context.dynamic_instance_ids));
- gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4,
- tv_data(context.dynamic_instance_pressure));
- gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']);
- upload_square_rgba16ui_texture(gl, context.dynamic_stroke_data, config.dynamic_stroke_texture_size);
-
- gl.enableVertexAttribArray(locations['a_a']);
- gl.enableVertexAttribArray(locations['a_b']);
- gl.enableVertexAttribArray(locations['a_stroke_id']);
- gl.enableVertexAttribArray(locations['a_pressure']);
-
- // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
- 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.dynamic_instance_points.size * 4);
- gl.vertexAttribPointer(locations['a_pressure'], 2, gl.UNSIGNED_BYTE, true, 1, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4);
-
- gl.vertexAttribDivisor(locations['a_a'], 1);
- gl.vertexAttribDivisor(locations['a_b'], 1);
- gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
- gl.vertexAttribDivisor(locations['a_pressure'], 1);
-
- gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dynamic_segment_count);
- }
+ if (context.gpu_timer_ext !== null) {
+ query = gl.createQuery();
+ gl.beginQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT, query);
+ }
+
+ // Only clear once we have the data, this might not always be on the same frame?
+ gl.viewport(0, 0, context.canvas.width, context.canvas.height);
+ gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1);
+ gl.clearDepth(0.0);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+ gl.useProgram(context.programs['sdf'].main);
+
+ // "Static" data upload
+ if (segment_count > 0) {
+ const total_static_size = context.instance_data_points.size * 4 +
+ context.instance_data_ids.size * 4 +
+ context.instance_data_pressures.size;
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
+ gl.bufferData(gl.ARRAY_BUFFER, total_static_size, 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.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4,
+ tv_data(context.instance_data_pressures));
+ gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
+ upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size);
+
+ 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.events.length);
+ gl.uniform1i(locations['u_debug_mode'], state.debug.red);
+ gl.uniform1i(locations['u_stroke_data'], 0);
+ gl.uniform1i(locations['u_stroke_texture_size'], config.stroke_texture_size);
+
+ gl.enableVertexAttribArray(locations['a_a']);
+ gl.enableVertexAttribArray(locations['a_b']);
+ gl.enableVertexAttribArray(locations['a_stroke_id']);
+ gl.enableVertexAttribArray(locations['a_pressure']);
+
+ // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
+ 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_pressure'], 2, gl.UNSIGNED_BYTE, true, 1, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4);
+
+ gl.vertexAttribDivisor(locations['a_a'], 1);
+ gl.vertexAttribDivisor(locations['a_b'], 1);
+ gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
+ gl.vertexAttribDivisor(locations['a_pressure'], 1);
+
+ // Static draw (everything already bound)
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count);
+ }
- document.getElementById('debug-stats').innerHTML = `
+ // Dynamic strokes should be drawn above static strokes
+ gl.clear(gl.DEPTH_BUFFER_BIT);
+
+ // Dynamic draw (strokes currently being drawn)
+ if (dynamic_segment_count > 0) {
+ gl.uniform1i(locations['u_stroke_count'], dynamic_stroke_count);
+ gl.uniform1i(locations['u_stroke_data'], 0);
+ gl.uniform1i(locations['u_stroke_texture_size'], config.dynamic_stroke_texture_size);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_dynamic_instance']);
+
+ // Dynamic data upload
+ const total_dynamic_size =
+ context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4 +
+ context.dynamic_instance_pressure.size;
+
+ gl.bufferData(gl.ARRAY_BUFFER, total_dynamic_size, gl.STREAM_DRAW);
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.dynamic_instance_points));
+ gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4, tv_data(context.dynamic_instance_ids));
+ gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4,
+ tv_data(context.dynamic_instance_pressure));
+ gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']);
+ upload_square_rgba16ui_texture(gl, context.dynamic_stroke_data, config.dynamic_stroke_texture_size);
+
+ gl.enableVertexAttribArray(locations['a_a']);
+ gl.enableVertexAttribArray(locations['a_b']);
+ gl.enableVertexAttribArray(locations['a_stroke_id']);
+ gl.enableVertexAttribArray(locations['a_pressure']);
+
+ // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
+ 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.dynamic_instance_points.size * 4);
+ gl.vertexAttribPointer(locations['a_pressure'], 2, gl.UNSIGNED_BYTE, true, 1, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4);
+
+ gl.vertexAttribDivisor(locations['a_a'], 1);
+ gl.vertexAttribDivisor(locations['a_b'], 1);
+ gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
+ gl.vertexAttribDivisor(locations['a_pressure'], 1);
+
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dynamic_segment_count);
+ }
+
+ document.getElementById('debug-stats').innerHTML = `
Segments onscreen: ${segment_count}
Canvas offset: (${Math.round(state.canvas.offset.x * 100) / 100}, ${Math.round(state.canvas.offset.y * 100) / 100})
Canvas zoom level: ${state.canvas.zoom_level}
Canvas zoom: ${Math.round(state.canvas.zoom * 100) / 100}`;
- if (context.gpu_timer_ext) {
- gl.endQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT);
-
- const next_tick = () => {
- if (query) {
- // At some point in the future, after returning control to the browser
- const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
- const disjoint = gl.getParameter(context.gpu_timer_ext.GPU_DISJOINT_EXT);
-
- if (available && !disjoint) {
- // See how much time the rendering of the object took in nanoseconds.
- const timeElapsed = gl.getQueryParameter(query, gl.QUERY_RESULT);
- //console.debug(timeElapsed / 1000000);
- document.querySelector('.debug-timings .gpu').innerHTML = 'Last GPU Frametime: ' + Math.round(timeElapsed / 10000) / 100 + 'ms';
- }
-
- if (available || disjoint) {
- // Clean up the query object.
- gl.deleteQuery(query);
- // Don't re-enter this polling loop.
- query = null;
- } else if (!available) {
- setTimeout(next_tick, 0);
+ if (context.gpu_timer_ext) {
+ gl.endQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT);
+
+ const next_tick = () => {
+ if (query) {
+ // At some point in the future, after returning control to the browser
+ const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
+ const disjoint = gl.getParameter(context.gpu_timer_ext.GPU_DISJOINT_EXT);
+
+ if (available && !disjoint) {
+ // See how much time the rendering of the object took in nanoseconds.
+ const timeElapsed = gl.getQueryParameter(query, gl.QUERY_RESULT);
+ //console.debug(timeElapsed / 1000000);
+ document.querySelector('.debug-timings .gpu').innerHTML = 'Last GPU Frametime: ' + Math.round(timeElapsed / 10000) / 100 + 'ms';
+ }
+
+ if (available || disjoint) {
+ // Clean up the query object.
+ gl.deleteQuery(query);
+ // Don't re-enter this polling loop.
+ query = null;
+ } else if (!available) {
+ setTimeout(next_tick, 0);
+ }
}
}
- }
- setTimeout(next_tick, 0);
+ setTimeout(next_tick, 0);
+ }
+ } catch (e) {
+ console.error('Draw failed:', e);
}
const cpu_after = performance.now();