From 5871833cd1b6a6beddcab6996c3c12a6f5f5b750 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Sat, 25 May 2024 01:00:14 +0300 Subject: [PATCH] (Probably) fix the nasty WASM mis-allocations --- client/client_recv.js | 3 +- client/index.html | 1 - client/index.js | 1 + client/speed.js | 83 +++++++++++++++++++++++-------------------- client/webgl_draw.js | 1 + 5 files changed, 48 insertions(+), 41 deletions(-) diff --git a/client/client_recv.js b/client/client_recv.js index 848a69a..8f4e672 100644 --- a/client/client_recv.js +++ b/client/client_recv.js @@ -361,6 +361,8 @@ function handle_event(state, context, event, options = {}) { } case EVENT.IMAGE: { + geometry_add_dummy_stroke(context); + try { (async () => { const url = config.image_url + event.image_id; @@ -372,7 +374,6 @@ function handle_event(state, context, event, options = {}) { event.width = bitmap.width; event.height = bitmap.height; - geometry_add_dummy_stroke(context); add_image(context, event.image_id, bitmap, p); // God knows when this will actually complete (it loads the image from the server) diff --git a/client/index.html b/client/index.html index e8c381d..7a4b49c 100644 --- a/client/index.html +++ b/client/index.html @@ -9,7 +9,6 @@ - diff --git a/client/index.js b/client/index.js index 308c48d..84fcea9 100644 --- a/client/index.js +++ b/client/index.js @@ -23,6 +23,7 @@ const config = { bytes_per_stroke: 2 * 3 + 2, // r, g, b, width initial_static_bytes: 4096 * 16, initial_dynamic_bytes: 4096, + initial_wasm_bytes: 4096, stroke_texture_size: 1024, // means no more than 1024^2 = 1M strokes in total (this is a LOT. HMH blackboard has like 80K) dynamic_stroke_texture_size: 128, // means no more than 128^2 = 16K dynamic strokes at once bvh_fullnode_depth: 5, diff --git a/client/speed.js b/client/speed.js index eda7432..0db8319 100644 --- a/client/speed.js +++ b/client/speed.js @@ -33,7 +33,7 @@ function workers_thread_message(workers, message, thread_field=null) { async function init_wasm(state) { const memory = new WebAssembly.Memory({ - initial: 32, // 2MiB, 1MiB of which is stack + initial: 16384, // F U maximum: 16384, // 1GiB shared: true, }); @@ -61,37 +61,41 @@ async function init_wasm(state) { 'memory': memory, }, 'thread_id'); - state.wasm.stroke_bytes = 4096; - state.wasm.coords_bytes = 4096; + const initial = config.initial_wasm_bytes; + state.wasm.buffers = { 'xs': { - 'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes / 2), - 'used': 0 + 'offset': state.wasm.exports.alloc_static(initial), + 'used': 0, + 'cap': initial }, 'ys': { - 'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes / 2), - 'used': 0 + 'offset': state.wasm.exports.alloc_static(initial), + 'used': 0, + 'cap': initial }, 'coords_from': { - 'offset': state.wasm.exports.alloc_static(state.wasm.stroke_bytes), + 'offset': state.wasm.exports.alloc_static(initial), 'used': 0, + 'cap': initial }, 'pressures': { - 'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes / 8), - 'used': 0 + 'offset': state.wasm.exports.alloc_static(initial), + 'used': 0, + 'cap': initial }, }; const mem = state.wasm.memory.buffer; - state.wasm.buffers['xs'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 8, + state.wasm.buffers['xs'].tv = tv_create_on(Float32Array, initial / 4, mem, state.wasm.buffers['xs'].offset); - state.wasm.buffers['ys'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 8, + state.wasm.buffers['ys'].tv = tv_create_on(Float32Array, initial / 4, mem, state.wasm.buffers['ys'].offset); - state.wasm.buffers['coords_from'].tv = tv_create_on(Uint32Array, state.wasm.stroke_bytes / 4, - mem, state.wasm.buffers['coords_from'].offset); - state.wasm.buffers['pressures'].tv = tv_create_on(Uint8Array, state.wasm.coords_bytes / 8, + state.wasm.buffers['pressures'].tv = tv_create_on(Uint8Array, initial, mem, state.wasm.buffers['pressures'].offset); + state.wasm.buffers['coords_from'].tv = tv_create_on(Uint32Array, initial / 4, + mem, state.wasm.buffers['coords_from'].offset); tv_add(state.wasm.buffers['coords_from'].tv, 0); state.wasm.buffers['coords_from'].used = 4; @@ -100,60 +104,61 @@ async function init_wasm(state) { function wasm_ensure_by(state, nstrokes, ncoords) { const buffers = state.wasm.buffers; + const old_ys_offset = buffers['ys'].offset; const old_coords_from_offset = buffers['coords_from'].offset; const old_pressures_offset = buffers['pressures'].offset; - const old_size_coords = state.wasm.coords_bytes; - const old_size_strokes = state.wasm.stroke_bytes; - let realloc = false; + let coords_bytes = buffers['xs'].cap; + let stroke_bytes = buffers['coords_from'].cap; - if (buffers['xs'].used + ncoords * 4 > state.wasm.coords_bytes / 2) { - state.wasm.coords_bytes += round_to_pow2(ncoords * 4, 4096 * 16); // 1 wasm page (although it doesn't matter here) + if (buffers['xs'].used + ncoords * 4 > buffers['xs'].cap) { + coords_bytes = round_to_pow2(buffers['xs'].cap + ncoords * 4, 4096 * 16); // 1 wasm page (although it doesn't matter here) realloc = true; } - if (buffers['coords_from'].used + nstrokes * 4 > state.wasm.stroke_bytes / 2) { - state.wasm.stroke_bytes += round_to_pow2(nstrokes * 4, 4096 * 16); + if (buffers['coords_from'].used + nstrokes * 4 > buffers['coords_from'].cap) { + stroke_bytes = round_to_pow2(buffers['coords_from'].cap + nstrokes * 4, 4096 * 16); realloc = true; } if (realloc) { - const current_pages = Math.ceil(state.wasm.memory.buffer.byteLength / (4096 * 16)); - const need_pages = 2 * Math.ceil((state.wasm.coords_bytes * 3 + state.wasm.stroke_bytes * 2) / (4096 * 16)); // TODO: figure out actual memory requirements - const grow_by = Math.max(1, need_pages - current_pages); - // const grow_by = 16; + if (config.debug_print) console.debug('WASM static data re-layout'); - state.wasm.memory.grow(grow_by); state.wasm.exports.free_static(); const mem = state.wasm.memory.buffer; const memv = new Uint8Array(mem); - buffers['xs'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes / 2); - buffers['ys'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes / 2); - buffers['coords_from'].offset = state.wasm.exports.alloc_static(state.wasm.stroke_bytes); - buffers['pressures'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes / 8); + buffers['xs'].offset = state.wasm.exports.alloc_static(coords_bytes); + buffers['ys'].offset = state.wasm.exports.alloc_static(coords_bytes); + buffers['pressures'].offset = state.wasm.exports.alloc_static(coords_bytes); + buffers['coords_from'].offset = state.wasm.exports.alloc_static(stroke_bytes); - buffers['xs'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 8, mem, buffers['xs'].offset); - buffers['ys'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 8, mem, buffers['ys'].offset); - buffers['coords_from'].tv = tv_create_on(Uint32Array, state.wasm.stroke_bytes / 4, mem, buffers['coords_from'].offset); - buffers['pressures'].tv = tv_create_on(Uint8Array, state.wasm.coords_bytes / 8, mem, buffers['pressures'].offset); + buffers['xs'].tv = tv_create_on(Float32Array, coords_bytes / 4, mem, buffers['xs'].offset); + buffers['ys'].tv = tv_create_on(Float32Array, coords_bytes / 4, mem, buffers['ys'].offset); + buffers['pressures'].tv = tv_create_on(Uint8Array, coords_bytes / 8, mem, buffers['pressures'].offset); + buffers['coords_from'].tv = tv_create_on(Uint32Array, stroke_bytes / 4, mem, buffers['coords_from'].offset); // TODO: this should have been automatic maybe? buffers['xs'].tv.size = buffers['xs'].used / 4; buffers['ys'].tv.size = buffers['ys'].used / 4; - buffers['coords_from'].tv.size = buffers['coords_from'].used / 4; buffers['pressures'].tv.size = buffers['pressures'].used; + buffers['coords_from'].tv.size = buffers['coords_from'].used / 4; + buffers['xs'].cap = buffers['ys'].cap = buffers['pressures'].cap = coords_bytes; + buffers['coords_from'].cap = stroke_bytes; - const tmp = new Uint8Array(Math.max(state.wasm.coords_bytes, state.wasm.stroke_bytes)); // TODO: needed? + const tmp = new Uint8Array(Math.max(coords_bytes, stroke_bytes)); // Copy from back to front (otherwise we will overwrite) + tmp.set(new Uint8Array(mem, old_coords_from_offset, buffers['coords_from'].used)); + memv.set(new Uint8Array(tmp.buffer, 0, buffers['coords_from'].used), buffers['coords_from'].offset); + tmp.set(new Uint8Array(mem, old_pressures_offset, buffers['pressures'].used)); memv.set(new Uint8Array(tmp.buffer, 0, buffers['pressures'].used), buffers['pressures'].offset); - tmp.set(new Uint8Array(mem, old_coords_from_offset, old_size_strokes)); - memv.set(new Uint8Array(tmp.buffer, 0, old_size_strokes), buffers['coords_from'].offset); + tmp.set(new Uint8Array(mem, old_ys_offset, buffers['ys'].used)); + memv.set(new Uint8Array(tmp.buffer, 0, buffers['ys'].used), buffers['ys'].offset); } } diff --git a/client/webgl_draw.js b/client/webgl_draw.js index 34493d8..75ffea7 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -348,6 +348,7 @@ async function draw(state, context, animate, ts) { } document.getElementById('debug-stats').innerHTML = ` + Strokes onscreen: ${context.clipped_indices.size} 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}