From 0c215796942fd20acb4ec6cff630f5dc37d4a662 Mon Sep 17 00:00:00 2001 From: Aleksey Olokhtonov Date: Sat, 19 Oct 2024 20:39:26 +0300 Subject: [PATCH] Boudning boxes debug draw. Fix missing mipmap warning. Fix dynamic stroke not drawing on empty canvas --- README.txt | 7 +++++ client/config.js | 6 ++-- client/speed.js | 5 ++-- client/webgl_draw.js | 31 ++++++++++++++++++-- client/webgl_geometry.js | 1 + client/webgl_listeners.js | 4 ++- client/webgl_shaders.js | 62 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 107 insertions(+), 9 deletions(-) diff --git a/README.txt b/README.txt index 333fa36..6191c85 100644 --- a/README.txt +++ b/README.txt @@ -11,6 +11,7 @@ Release: + 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?) + - Frame-independent lerp where applicable + Bugs + GC stalls!!! + Stroke previews get connected when drawn without panning on touch devices @@ -19,7 +20,9 @@ Release: + Undo history of moving and scaling images seems messed up - Nothing get's drawn if we enable snapping and draw a curve where first and last point match - Weird clipping on HMH desk full zoomout after running "benchmark" + - Stuck in color picker mode when mouse leaves screen - Debug + * Debug view for BVH - Restore ability to limit event range * Listeners/events/multiplayer + Fix multiplayer LUL @@ -78,6 +81,10 @@ Bonus: + Migrate old non-pressure desks - Check out e.pressure on touch devices - Send pressure in PREDRAW event + - Stroke smoothing + https://github.com/xournalpp/xournalpp/issues/2320 + https://www.digital-epigraphy.com/tutorials/the-most-useful-new-features-of-photoshop-cc-using-brush-stroke-smoothing-for-digital-inking + https://stackoverflow.com/questions/20618804/how-to-smooth-a-curve-for-a-dataset - Curve modification - Select curves (with a lasso?) - Move whole curve diff --git a/client/config.js b/client/config.js index 680bb0d..cb381ff 100644 --- a/client/config.js +++ b/client/config.js @@ -8,7 +8,7 @@ const config = { second_finger_timeout: 500, buffer_first_touchmoves: 5, debug_print: false, - draw_bvh: true, + draw_bvh: false, zoom_delta: 0.05, min_zoom_level: -250, max_zoom_level: 100, @@ -28,8 +28,8 @@ const config = { pattern_fadeout_max: 0.75, min_pressure: 50, benchmark: { - zoom_level: -75, - offset: { x: 425, y: -1195 }, + zoom_level: -18, + offset: { x: 654, y: 372 }, frames: 500, }, }; diff --git a/client/speed.js b/client/speed.js index bd97e8c..bcb6f7d 100644 --- a/client/speed.js +++ b/client/speed.js @@ -175,9 +175,8 @@ async function do_lod(state, context) { // Dynamic input data that should (by design) never be too big mem.set(tv_bytes(context.clipped_indices), clipped_indices); - // TODO: this is a very naive and dumb way of distributing work. Better way - // would be to distrubute strokes based on total point count, so that - // each worker gets approximately the same amout of _points_ + // NOTE: this static partitioning scheme turned out to be "good enough" (i.e., trying + // to allocate approximately the same amount of points per job wasn't any faster) const indices_per_thread = Math.floor(context.clipped_indices.size / state.wasm.workers.length); const offsets = { 'coords_from': buffers['coords_from'].offset, diff --git a/client/webgl_draw.js b/client/webgl_draw.js index 8c97abb..80eb66e 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -317,6 +317,7 @@ async function draw(state, context, animate, ts) { // Dynamic strokes should be drawn above static strokes gl.clear(gl.DEPTH_BUFFER_BIT); + gl.useProgram(pr.program); gl.uniform1i(pr.locations['u_stroke_count'], dynamic_stroke_count); gl.uniform1i(pr.locations['u_stroke_data'], 0); @@ -422,17 +423,43 @@ async function draw(state, context, animate, ts) { } if (config.draw_bvh) { + const pr = programs['iquad']; const bboxes = tv_create(Float32Array, context.clipped_indices.size * 4); // Debug BVH viz for (let i = 0; i < context.clipped_indices.size; ++i) { const stroke_id = context.clipped_indices.data[i]; const stroke = state.events[stroke_id]; - const stroke_bbox = state.bvh.nodes[stroke.bvh_node].bbox; tv_add(bboxes, stroke.bbox.x1); - tv_add(bboxes, stroke.bbox.x2); tv_add(bboxes, stroke.bbox.y1); + tv_add(bboxes, stroke.bbox.x2); tv_add(bboxes, stroke.bbox.y2); } + + const quad_count = bboxes.size / 4; + + gl.useProgram(pr.program); + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_iquads']); + gl.bufferData(gl.ARRAY_BUFFER, tv_data(bboxes), gl.STREAM_DRAW); + + gl.uniform2f(pr.locations['u_res'], context.canvas.width, context.canvas.height); + gl.uniform2f(pr.locations['u_scale'], state.canvas.zoom, state.canvas.zoom); + gl.uniform2f(pr.locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); + + gl.enableVertexAttribArray(pr.locations['a_topleft']); + gl.enableVertexAttribArray(pr.locations['a_bottomright']); + + gl.vertexAttribPointer(pr.locations['a_topleft'], 2, gl.FLOAT, false, 4 * 4, 0); + gl.vertexAttribPointer(pr.locations['a_bottomright'], 2, gl.FLOAT, false, 4 * 4, 2 * 4); + + gl.vertexAttribDivisor(pr.locations['a_topleft'], 1); + gl.vertexAttribDivisor(pr.locations['a_bottomright'], 1); + + // Static draw (everything already bound) + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, quad_count); + + gl.vertexAttribDivisor(pr.locations['a_topleft'], 0); + gl.vertexAttribDivisor(pr.locations['a_bottomright'], 0); } document.getElementById('debug-stats').innerHTML = ` diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 0235652..43d9b99 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -229,6 +229,7 @@ function add_image(context, image_id, bitmap, p, width, height) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.generateMipmap(gl.TEXTURE_2D); } } diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index 225c002..5131b5b 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -43,6 +43,7 @@ function debug_panel_init(state, context) { document.getElementById('draw-bvh').addEventListener('change', (e) => { config.draw_bvh = e.target.checked; + schedule_draw(state, context); }); document.getElementById('debug-begin-benchmark').addEventListener('click', (e) => { @@ -51,7 +52,8 @@ function debug_panel_init(state, context) { state.canvas.offset.y = config.benchmark.offset.y; const dz = (state.canvas.zoom_level > 0 ? config.zoom_delta : -config.zoom_delta); - state.canvas.zoom = Math.pow(1.0 + dz, Math.abs(state.canvas.zoom_level)) + state.canvas.target_zoom = Math.pow(1.0 + dz, Math.abs(state.canvas.zoom_level)) + state.canvas.zoom = state.canvas.target_zoom; state.debug.benchmark_mode = true; diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index f4e4e9f..f63b274 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -270,6 +270,63 @@ const dots_fs_src = `#version 300 es } `; + +// +const iquad_vs_src = `#version 300 es + in vec2 a_topleft; // per-instance + in vec2 a_bottomright; // per-instance + + uniform vec2 u_scale; + uniform vec2 u_res; + uniform vec2 u_translation; + + out vec3 v_color; + + void main() { + vec2 pos; + + int vertex_index = gl_VertexID % 6; + + if (vertex_index == 0) { + // top left + pos = a_topleft; + } else if (vertex_index == 1 || vertex_index == 5) { + // top right + pos = vec2(a_bottomright.x, a_topleft.y); + } else if (vertex_index == 2 || vertex_index == 4) { + // bottom left + pos = vec2(a_topleft.x, a_bottomright.y); + } else { + // bottom right + pos = a_bottomright; + } + + v_color = vec3( + float(int(a_topleft.x) * 908125 % 255) / 255.0, + float(int(a_topleft.y) * 257722 % 255) / 255.0, + float(int(a_bottomright.y) * 826586 % 255) / 255.0 + ); + + vec2 screen01 = (pos * u_scale + u_translation) / u_res; + vec2 screen02 = screen01 * 2.0; + screen02.y = 2.0 - screen02.y; + vec2 screen11 = screen02 - 1.0; + gl_Position = vec4(screen11, 0.0, 1.0); + } +`; + +const iquad_fs_src = `#version 300 es + precision highp float; + + layout(location = 0) out vec4 FragColor; + + in vec3 v_color; + + void main() { + FragColor = vec4(v_color, 0.5); + } +`; + function init_webgl(state, context) { context.canvas = document.querySelector('#c'); context.gl = context.canvas.getContext('webgl2', { @@ -303,11 +360,15 @@ function init_webgl(state, context) { const grid_vs = create_shader(gl, gl.VERTEX_SHADER, grid_vs_src); + const iquad_vs = create_shader(gl, gl.VERTEX_SHADER, iquad_vs_src); + const iquad_fs = create_shader(gl, gl.FRAGMENT_SHADER, iquad_fs_src); + context.programs = { 'image': create_program(gl, quad_vs, quad_fs), 'main': create_program(gl, sdf_vs, sdf_fs), 'dots': create_program(gl, dots_vs, dots_fs), 'grid': create_program(gl, grid_vs, dots_fs), + 'iquad': create_program(gl, iquad_vs, iquad_fs), }; context.buffers = { @@ -318,6 +379,7 @@ function init_webgl(state, context) { 'b_instance_grid': gl.createBuffer(), 'b_dot': gl.createBuffer(), 'b_hud': gl.createBuffer(), + 'b_iquads': gl.createBuffer(), }; context.textures = {