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();