Browse Source

(Probably) fix the nasty WASM mis-allocations

ssao
A.Olokhtonov 6 months ago
parent
commit
5871833cd1
  1. 3
      client/client_recv.js
  2. 1
      client/index.html
  3. 1
      client/index.js
  4. 83
      client/speed.js
  5. 1
      client/webgl_draw.js

3
client/client_recv.js

@ -361,6 +361,8 @@ function handle_event(state, context, event, options = {}) {
} }
case EVENT.IMAGE: { case EVENT.IMAGE: {
geometry_add_dummy_stroke(context);
try { try {
(async () => { (async () => {
const url = config.image_url + event.image_id; const url = config.image_url + event.image_id;
@ -372,7 +374,6 @@ function handle_event(state, context, event, options = {}) {
event.width = bitmap.width; event.width = bitmap.width;
event.height = bitmap.height; event.height = bitmap.height;
geometry_add_dummy_stroke(context);
add_image(context, event.image_id, bitmap, p); add_image(context, event.image_id, bitmap, p);
// God knows when this will actually complete (it loads the image from the server) // God knows when this will actually complete (it loads the image from the server)

1
client/index.html

@ -9,7 +9,6 @@
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon"> <link rel="shortcut icon" href="icons/favicon.svg" id="favicon">
<link rel="stylesheet" type="text/css" href="default.css"> <link rel="stylesheet" type="text/css" href="default.css">
<link rel="preload" href="icons/cursor.svg" as="image" type="image/svg+xml" />
<link rel="preload" href="icons/picker.svg" as="image" type="image/svg+xml" /> <link rel="preload" href="icons/picker.svg" as="image" type="image/svg+xml" />
<script type="text/javascript" src="aux.js"></script> <script type="text/javascript" src="aux.js"></script>

1
client/index.js

@ -23,6 +23,7 @@ const config = {
bytes_per_stroke: 2 * 3 + 2, // r, g, b, width bytes_per_stroke: 2 * 3 + 2, // r, g, b, width
initial_static_bytes: 4096 * 16, initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096, 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) 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 dynamic_stroke_texture_size: 128, // means no more than 128^2 = 16K dynamic strokes at once
bvh_fullnode_depth: 5, bvh_fullnode_depth: 5,

83
client/speed.js

@ -33,7 +33,7 @@ function workers_thread_message(workers, message, thread_field=null) {
async function init_wasm(state) { async function init_wasm(state) {
const memory = new WebAssembly.Memory({ const memory = new WebAssembly.Memory({
initial: 32, // 2MiB, 1MiB of which is stack initial: 16384, // F U
maximum: 16384, // 1GiB maximum: 16384, // 1GiB
shared: true, shared: true,
}); });
@ -61,37 +61,41 @@ async function init_wasm(state) {
'memory': memory, 'memory': memory,
}, 'thread_id'); }, 'thread_id');
state.wasm.stroke_bytes = 4096; const initial = config.initial_wasm_bytes;
state.wasm.coords_bytes = 4096;
state.wasm.buffers = { state.wasm.buffers = {
'xs': { 'xs': {
'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes / 2), 'offset': state.wasm.exports.alloc_static(initial),
'used': 0 'used': 0,
'cap': initial
}, },
'ys': { 'ys': {
'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes / 2), 'offset': state.wasm.exports.alloc_static(initial),
'used': 0 'used': 0,
'cap': initial
}, },
'coords_from': { 'coords_from': {
'offset': state.wasm.exports.alloc_static(state.wasm.stroke_bytes), 'offset': state.wasm.exports.alloc_static(initial),
'used': 0, 'used': 0,
'cap': initial
}, },
'pressures': { 'pressures': {
'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes / 8), 'offset': state.wasm.exports.alloc_static(initial),
'used': 0 'used': 0,
'cap': initial
}, },
}; };
const mem = state.wasm.memory.buffer; 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); 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); mem, state.wasm.buffers['ys'].offset);
state.wasm.buffers['coords_from'].tv = tv_create_on(Uint32Array, state.wasm.stroke_bytes / 4, state.wasm.buffers['pressures'].tv = tv_create_on(Uint8Array, initial,
mem, state.wasm.buffers['coords_from'].offset);
state.wasm.buffers['pressures'].tv = tv_create_on(Uint8Array, state.wasm.coords_bytes / 8,
mem, state.wasm.buffers['pressures'].offset); 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); tv_add(state.wasm.buffers['coords_from'].tv, 0);
state.wasm.buffers['coords_from'].used = 4; state.wasm.buffers['coords_from'].used = 4;
@ -100,60 +104,61 @@ async function init_wasm(state) {
function wasm_ensure_by(state, nstrokes, ncoords) { function wasm_ensure_by(state, nstrokes, ncoords) {
const buffers = state.wasm.buffers; const buffers = state.wasm.buffers;
const old_ys_offset = buffers['ys'].offset;
const old_coords_from_offset = buffers['coords_from'].offset; const old_coords_from_offset = buffers['coords_from'].offset;
const old_pressures_offset = buffers['pressures'].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 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) { if (buffers['xs'].used + ncoords * 4 > buffers['xs'].cap) {
state.wasm.coords_bytes += round_to_pow2(ncoords * 4, 4096 * 16); // 1 wasm page (although it doesn't matter here) coords_bytes = round_to_pow2(buffers['xs'].cap + ncoords * 4, 4096 * 16); // 1 wasm page (although it doesn't matter here)
realloc = true; realloc = true;
} }
if (buffers['coords_from'].used + nstrokes * 4 > state.wasm.stroke_bytes / 2) { if (buffers['coords_from'].used + nstrokes * 4 > buffers['coords_from'].cap) {
state.wasm.stroke_bytes += round_to_pow2(nstrokes * 4, 4096 * 16); stroke_bytes = round_to_pow2(buffers['coords_from'].cap + nstrokes * 4, 4096 * 16);
realloc = true; realloc = true;
} }
if (realloc) { if (realloc) {
const current_pages = Math.ceil(state.wasm.memory.buffer.byteLength / (4096 * 16)); if (config.debug_print) console.debug('WASM static data re-layout');
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;
state.wasm.memory.grow(grow_by);
state.wasm.exports.free_static(); state.wasm.exports.free_static();
const mem = state.wasm.memory.buffer; const mem = state.wasm.memory.buffer;
const memv = new Uint8Array(mem); const memv = new Uint8Array(mem);
buffers['xs'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes / 2); buffers['xs'].offset = state.wasm.exports.alloc_static(coords_bytes);
buffers['ys'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes / 2); buffers['ys'].offset = state.wasm.exports.alloc_static(coords_bytes);
buffers['coords_from'].offset = state.wasm.exports.alloc_static(state.wasm.stroke_bytes); buffers['pressures'].offset = state.wasm.exports.alloc_static(coords_bytes);
buffers['pressures'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes / 8); 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['xs'].tv = tv_create_on(Float32Array, coords_bytes / 4, mem, buffers['xs'].offset);
buffers['ys'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 8, mem, buffers['ys'].offset); buffers['ys'].tv = tv_create_on(Float32Array, coords_bytes / 4, 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, coords_bytes / 8, mem, buffers['pressures'].offset);
buffers['pressures'].tv = tv_create_on(Uint8Array, state.wasm.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? // TODO: this should have been automatic maybe?
buffers['xs'].tv.size = buffers['xs'].used / 4; buffers['xs'].tv.size = buffers['xs'].used / 4;
buffers['ys'].tv.size = buffers['ys'].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['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) // 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)); tmp.set(new Uint8Array(mem, old_pressures_offset, buffers['pressures'].used));
memv.set(new Uint8Array(tmp.buffer, 0, buffers['pressures'].used), buffers['pressures'].offset); 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)); tmp.set(new Uint8Array(mem, old_ys_offset, buffers['ys'].used));
memv.set(new Uint8Array(tmp.buffer, 0, old_size_strokes), buffers['coords_from'].offset); memv.set(new Uint8Array(tmp.buffer, 0, buffers['ys'].used), buffers['ys'].offset);
} }
} }

1
client/webgl_draw.js

@ -348,6 +348,7 @@ async function draw(state, context, animate, ts) {
} }
document.getElementById('debug-stats').innerHTML = ` document.getElementById('debug-stats').innerHTML = `
<span>Strokes onscreen: ${context.clipped_indices.size}</span>
<span>Segments onscreen: ${segment_count}</span> <span>Segments onscreen: ${segment_count}</span>
<span>Canvas offset: (${Math.round(state.canvas.offset.x * 100) / 100}, ${Math.round(state.canvas.offset.y * 100) / 100})</span> <span>Canvas offset: (${Math.round(state.canvas.offset.x * 100) / 100}, ${Math.round(state.canvas.offset.y * 100) / 100})</span>
<span>Canvas zoom level: ${state.canvas.zoom_level}</span> <span>Canvas zoom level: ${state.canvas.zoom_level}</span>

Loading…
Cancel
Save