From 1960bdebe9b615c7d1420571f8a810c8d99f37f5 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Sat, 13 Jan 2024 04:18:57 +0300 Subject: [PATCH] LOD generation has been wassembled! (a little borked for now though) --- README.md | 6 ++-- client/aux.js | 18 +++++----- client/bvh.js | 15 +++------ client/client_recv.js | 8 ++++- client/index.html | 1 + client/index.js | 35 +++++++------------ client/speed.js | 69 +++++++++++++++++++++++++++++--------- client/wasm/lod.c | 28 +++++++--------- client/wasm/lod.wasm | Bin 541 -> 1272 bytes client/webgl_geometry.js | 39 ++++++++------------- client/webgl_listeners.js | 6 ++++ 11 files changed, 125 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 0b51884..814ab54 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ Release: + 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) - * Webassembly for core LOD generation + + Webassembly for core LOD generation + - Webassembly for final buffers + - Do not copy memory from wasm and back - Z-prepass fringe bug (also, when do we enable the prepass?) - Textured quads (pictures, code already written in older version) - Resize and move pictures (draw handles) @@ -34,7 +36,7 @@ Release: - Line drawing - Undo/redo * Polish - * Use typedvector where appropriate + + Use typedvector where appropriate - Show what's happening while the desk is loading (downloading, processing, uploading to gpu) - Settings panel for config values (including the setting for "offline mode") - Set up VAOs diff --git a/client/aux.js b/client/aux.js index 7b3b837..81c39db 100644 --- a/client/aux.js +++ b/client/aux.js @@ -125,21 +125,21 @@ function tv_data(tv) { return tv.data.subarray(0, tv.size); } +function tv_bytes(tv) { + return new Uint8Array(tv.data.buffer, 0, tv.size * tv.data.BYTES_PER_ELEMENT); +} + 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; + const new_data = new tv.class_name(capacity); + new_data.set(tv_data(tv)); + tv.capacity = capacity; + tv.data = new_data; } - - return tv; } function tv_ensure_by(tv, by) { - return tv_ensure(tv, round_to_pow2(tv.size + by, 4096)); + tv_ensure(tv, round_to_pow2(tv.size + by, 4096)); } function tv_add(tv, item) { diff --git a/client/bvh.js b/client/bvh.js index e16f7de..f1b34c7 100644 --- a/client/bvh.js +++ b/client/bvh.js @@ -172,8 +172,7 @@ function bvh_intersect_quad(bvh, quad, result_buffer) { } if (node.is_leaf) { - result_buffer.data[result_buffer.count] = node.stroke_index; - result_buffer.count += 1; + tv_add(result_buffer, node.stroke_index); } else { tv_add(bvh.traverse_stack, node.child1); tv_add(bvh.traverse_stack, node.child2); @@ -186,14 +185,10 @@ function bvh_clip(state, context) { return; } - if (context.clipped_indices.cap < state.stroke_count) { - context.clipped_indices.cap = round_to_pow2(state.stroke_count, 4096); - context.clipped_indices.data = new Uint32Array(context.clipped_indices.cap); - } - - state.bvh.traverse_stack = tv_ensure(state.bvh.traverse_stack, round_to_pow2(state.stroke_count, 4096)); + tv_ensure(context.clipped_indices, round_to_pow2(state.stroke_count, 4096)) + tv_ensure(state.bvh.traverse_stack, round_to_pow2(state.stroke_count, 4096)); - context.clipped_indices.count = 0; + tv_clear(context.clipped_indices); const screen_topleft = screen_to_canvas(state, {'x': 0, 'y': 0}); const screen_bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height}); @@ -209,7 +204,7 @@ function bvh_clip(state, context) { bvh_intersect_quad(state.bvh, screen, context.clipped_indices); - new Uint32Array(context.clipped_indices.data.buffer, 0, context.clipped_indices.count).sort(); // we need to draw back to front still! + tv_data(context.clipped_indices).sort(); // we need to draw back to front still! } function bvh_construct_rec(bvh, vertical, strokes) { diff --git a/client/client_recv.js b/client/client_recv.js index e1fa77d..10d2d40 100644 --- a/client/client_recv.js +++ b/client/client_recv.js @@ -101,8 +101,14 @@ function des_event(d, state = null) { const coords = des_f32array(d, point_count * 2); - state.coordinates = tv_ensure_by(state.coordinates, coords.length); + tv_ensure_by(state.coordinates, coords.length); + tv_ensure_by(state.coords_from, 1); + tv_ensure_by(state.coords_to, 1); + + tv_add(state.coords_from, state.coordinates.size); + tv_add(state.coords_to, state.coordinates.size + point_count * 2); + // TODO: remove, this is duplicate data event.coords_from = state.coordinates.size; event.coords_to = state.coordinates.size + point_count * 2; diff --git a/client/index.html b/client/index.html index 2bbdaf0..e9268ec 100644 --- a/client/index.html +++ b/client/index.html @@ -39,6 +39,7 @@ +
diff --git a/client/index.js b/client/index.js index eecd128..dc44cd5 100644 --- a/client/index.js +++ b/client/index.js @@ -129,7 +129,7 @@ function start_spinner(state) { } } -function main() { +async function main() { const state = { 'online': false, 'me': null, @@ -179,18 +179,10 @@ function main() { 'coordinates': tv_create(Float32Array, 4096), 'line_threshold': tv_create(Float32Array, 4096), - - 'segments_from': { - 'data': null, - 'count': 0, - 'cap': 0, - }, - - 'segments': { - 'data': null, - 'count': 0, - 'cap': 0, - }, + 'coords_from': tv_create(Uint32Array, 4096), + 'coords_to': tv_create(Uint32Array, 4096), + 'segments_from': tv_create(Uint32Array, 4096), + 'segments': tv_create(Uint32Array, 4096), 'bvh': { 'nodes': [], @@ -220,6 +212,7 @@ function main() { 'debug': { 'red': false, 'do_prepass': true, + 'use_wasm': false, 'limit_from': false, 'limit_to': false, 'render_from': 0, @@ -230,6 +223,9 @@ function main() { 'stats': {}, 'following_player': null, + + + 'wasm': {}, }; const context = { @@ -244,12 +240,7 @@ function main() { 'dynamic_serializer': serializer_create(config.initial_dynamic_bytes), 'dynamic_index_serializer': serializer_create(config.initial_dynamic_bytes), - // TODO: i seem to have a lot of these, maybe make a few utility functions? similar to serializer, but for pure typedarray - 'clipped_indices': { - 'data': null, - 'count': 0, - 'cap': 0, - }, + 'clipped_indices': tv_create(Uint32Array, 4096), 'instance_data_points': tv_create(Float32Array, 4096), 'instance_data_ids': tv_create(Uint32Array, 4096), @@ -277,16 +268,16 @@ function main() { state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0; + await init_wasm(state); + init_webgl(state, context); init_listeners(state, context); init_tools(state); + ws_connect(state, context, true); schedule_draw(state, context); state.timers.offline_toast = setTimeout(() => ui_offline(), config.initial_offline_timeout); - - - test_wasm(); } diff --git a/client/speed.js b/client/speed.js index 9b0740c..c5a6abc 100644 --- a/client/speed.js +++ b/client/speed.js @@ -1,18 +1,7 @@ -async function test_wasm() { - const memory = new WebAssembly.Memory({ initial: 10, maximum: 100 }); +async function init_wasm(state) { const results = await WebAssembly.instantiateStreaming(fetch('wasm/lod.wasm')); - const numbers_offset = results.instance.exports.alloc(40); - - const numbers = new Uint32Array(results.instance.exports.memory.buffer, numbers_offset, 10); - - for (let i = 0; i < 10; ++i) { - numbers[i] = i; - } - - console.log(numbers); - - const sum = results.instance.exports.sum(numbers_offset, 10); - console.log(sum); + state.wasm.exports = results.instance.exports; + state.wasm.exports.memory.grow(1024); } function rdp_find_max(state, zoom, coords_from, start, end) { @@ -55,20 +44,64 @@ function rdp_find_max(state, zoom, coords_from, start, end) { return result; } +function do_lod_wasm(state, context) { + const clipped_indices = state.wasm.exports.alloc(context.clipped_indices.size * 4); + const stroke_coords_from = state.wasm.exports.alloc(state.coords_from.size * 4); + const stroke_coords_to = state.wasm.exports.alloc(state.coords_to.size * 4); + const line_threshold = state.wasm.exports.alloc(state.line_threshold.size * 4); + const segments_from = state.wasm.exports.alloc((context.clipped_indices.size + 1) * 4); + const segments = state.wasm.exports.alloc(state.segments.capacity * 4); + const coordinates = state.wasm.exports.alloc(state.coordinates.size * 4); + + const mem = new Uint8Array(state.wasm.exports.memory.buffer); + + mem.set(tv_bytes(context.clipped_indices), clipped_indices); + mem.set(tv_bytes(state.coords_from), stroke_coords_from); + mem.set(tv_bytes(state.coords_to), stroke_coords_to); + mem.set(tv_bytes(state.line_threshold), line_threshold); + mem.set(tv_bytes(state.coordinates), coordinates); + + const segment_count = state.wasm.exports.do_lod( + clipped_indices, context.clipped_indices.size, state.canvas.zoom, + stroke_coords_from, stroke_coords_to, + line_threshold, coordinates, + segments_from, segments + ); + + // copy result back + const wasm_segments = new Uint32Array(state.wasm.exports.memory.buffer, segments, segment_count); + const wasm_segments_from = new Uint32Array(state.wasm.exports.memory.buffer, segments_from, context.clipped_indices.size); + + state.segments.data.set(wasm_segments); + state.segments.size = segment_count; + + state.segments_from.data.set(wasm_segments_from); + state.segments_from.size = context.clipped_indices.size + 1; + + state.wasm.exports.total_free(); + + + return segment_count; +} + function do_lod(state, context) { + if (state.debug.use_wasm) { + return do_lod_wasm(state, context); + } + const zoom = state.canvas.zoom; const segments_data = state.segments.data; let segments_head = 0; - for (let i = 0; i < context.clipped_indices.count; ++i) { + for (let i = 0; i < context.clipped_indices.size; ++i) { const stroke_index = context.clipped_indices.data[i]; const stroke = state.events[stroke_index]; const point_count = (stroke.coords_to - stroke.coords_from) / 2; const coords_from = stroke.coords_from; if (point_count > state.rdp_traverse_stack.length) { - //console.count('allocate') + //console.size('allocate') state.rdp_traverse_stack = new Uint32Array(round_to_pow2(point_count, 4096)); } @@ -127,5 +160,9 @@ function do_lod(state, context) { } } + state.segments_from.data[context.clipped_indices.size] = segments_head; + state.segments_from.size = context.clipped_indices.size + 1; + state.segments.size = segments_head; + return segments_head; } diff --git a/client/wasm/lod.c b/client/wasm/lod.c index db1c3c9..d911079 100644 --- a/client/wasm/lod.c +++ b/client/wasm/lod.c @@ -1,6 +1,12 @@ extern char __heap_base; static int allocated; +void +total_free(void) +{ + allocated = 0; +} + void * alloc(int size) { @@ -9,19 +15,6 @@ alloc(int size) return(result); } -int -sum(int *numbers, int count) -{ - int result = 0; - - for (int i = 0; i < count; ++i) { - result += numbers[i]; - } - - return(result); -} - -#if 0 static int rdp_find_max(float *coordinates, float zoom, int coords_from, int segment_start, int segment_end) @@ -67,8 +60,12 @@ do_lod(int *clipped_indices, int clipped_count, float zoom, float *line_threshold, float *coordinates, int *segments_from, int *segments) { + if (clipped_count == 0) { + return(0); + } + int segments_head = 0; - int stack[4096]; + int stack[4096]; // TODO: what's a reasonable max size for this? for (int i = 0; i < clipped_count; ++i) { int stroke_index = clipped_indices[i]; @@ -131,7 +128,8 @@ do_lod(int *clipped_indices, int clipped_count, float zoom, line_threshold[stroke_index] = zoom; } } + + segments_from[clipped_count] = segments_head; return(segments_head); } -#endif \ No newline at end of file diff --git a/client/wasm/lod.wasm b/client/wasm/lod.wasm index e9e084ae24d32198bde8f0b38b2229797a887b24..9c0c4f3afbec8bd83913d27dbc601ef795928d7e 100755 GIT binary patch literal 1272 zcmZuw&2HO95T4zYD3R2U<)n@hC(dx~gWJ?;(}U8UVx}!x^epcT|wNP|Cw)QAT1UG0K|qm1UM9xs1E(AIzm1;If zD4R-v^qvHjh~K?!gcW?e>NXQ^?Dw7fI=K_n?gCa4Ve4BB4vr3EN~_rsKJB zq#yj{H6(;xi0L0L)_fskFvknws`Ll*wi^i(CzTl5bq?|i$a194r8 z6YcLpci+tDEuCveuDBt)L~8fchx>N5U?A|Qunf53H<2u zBU(d+?lr&`_A(4c60nT{sbpLN;2I82V!4QV#1vVyZUZ;TO5gZGg1(9J;Kwf8tg{XF z;)<#R$`Jjh@K^!*mZv`($#yKG4GeL>n$fLTMMKJM$m!fhKhr_{GShcBuT2}pC)s(X z@3OkS$BB!dJ_)tS>8;zO@NP7;px`Fax=*>j<^uKB&t%|)=;2l@N*rX?UqIOti}bHq zJuc|}g~mUS&Luf%WUSS5w~k0e{P%xq{UO?>CtW*ui+m`mCm`ejB8;o3r}nXrjM!`I z;Qj^fT;ev`n9;@M``@+3&H<9UgWvSpCV|VI>49X^CSSOPeOn0?Ouc3Zx%8__?qH8= zzvnvS<88)HFpE=mLiY3YUpoEAmcN#v1uTefUTF+tFN=n_!3b`Igf%>)VAq|bh5-pB z$E7a+%3Z#J@>?2khk8IGMPPT4yAp;pK!TCAGeq6z>CN2iVi3Z!#xh+P@m1r0R$F|s z+0rexy4vK{x`6)24L!||m!(<3H~#4=pPo(3st_G|PE1jl>E8I2==@;D$LSI$^K>~2 yAB`S<{cwc4kG}gHzU-IjYG%shxk<~jl_}(d{^HDT@19%u;z7GOo-Oyx%kn?n<_~iK delta 215 zcmXAj!D_-l7=-8Fe@l@lp_*B00jjKg$mJG^YFUjf0_*Ym)V2eZkrO6hQh{eik6AE$&=#P_!RmbUrY^{&ImsO_5LNw2Ty_A)jA diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 9970f2d..0b252bd 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -38,31 +38,19 @@ function geometry_prepare_stroke(state) { function geometry_write_instances(state, context) { - 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.size / 2) { - state.segments.cap = round_to_pow2(state.coordinates.size, 4096); - state.segments.data = new Uint32Array(state.segments.cap); - } + tv_ensure(state.segments_from, round_to_pow2(context.clipped_indices.size + 1, 4096)); + tv_ensure(state.segments, round_to_pow2(state.coordinates.size / 2, 4096)); - state.segments_from.count = 0; - state.segments.count = 0; + tv_clear(state.segments_from); + tv_clear(state.segments); state.stats.rdp_max_count = 0; state.stats.rdp_segments = 0; const segment_count = do_lod(state, context); - state.segments.count = segment_count; - 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_ensure(context.instance_data_points, state.segments.size * 2); + tv_ensure(context.instance_data_ids, state.segments.size); tv_clear(context.instance_data_points); tv_clear(context.instance_data_ids); @@ -72,10 +60,11 @@ function geometry_write_instances(state, context) { const segments = state.segments.data; const coords = state.coordinates.data; const events = state.events; - - for (let i = 0; i < state.segments_from.count - 1; ++i) { + + // TODO: move this loop to WASM + for (let i = 0; i < state.segments_from.size - 1; ++i) { const stroke_index = clipped[i]; - const coords_from = events[stroke_index].coords_from; + const coords_from = state.events[stroke_index].coords_from; const from = segments_from[i]; const to = segments_from[i + 1]; @@ -100,7 +89,7 @@ function geometry_write_instances(state, context) { if (config.debug_print) console.debug('instances:', state.segments.count, 'rdp max:', state.stats.rdp_max_count, 'rdp segments:', state.stats.rdp_segments); - return state.segments.count; + return state.segments.size; } function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) { @@ -111,7 +100,7 @@ function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = fa context.stroke_data = ser_ensure_by(context.stroke_data, config.bytes_per_stroke); - state.line_threshold = tv_ensure(state.line_threshold, round_to_pow2(state.stroke_count, 4096)); + tv_ensure(state.line_threshold, round_to_pow2(state.stroke_count, 4096)); tv_add(state.line_threshold, -1); const color_u32 = stroke.color; @@ -159,8 +148,8 @@ function recompute_dynamic_data(state, context) { } } - context.dynamic_instance_data = tv_ensure(context.dynamic_instance_points, round_to_pow2(total_points * 2, 4096)); - context.dynamic_instance_ids = tv_ensure(context.dynamic_instance_ids, round_to_pow2(total_points, 4096)); + tv_ensure(context.dynamic_instance_points, round_to_pow2(total_points * 2, 4096)); + tv_ensure(context.dynamic_instance_ids, round_to_pow2(total_points, 4096)); tv_clear(context.dynamic_instance_points); tv_clear(context.dynamic_instance_ids); diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index f89e8d7..65b3d4f 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -26,6 +26,7 @@ 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-use-wasm').checked = state.debug.use_wasm; document.getElementById('debug-red').addEventListener('change', (e) => { state.debug.red = e.target.checked; @@ -37,6 +38,11 @@ function debug_panel_init(state, context) { schedule_draw(state, context); }); + document.getElementById('debug-use-wasm').addEventListener('change', (e) => { + state.debug.use_wasm = e.target.checked; + schedule_draw(state, context); + }); + document.getElementById('debug-limit-from').addEventListener('change', (e) => { state.debug.limit_from = e.target.checked; schedule_draw(state, context);