async function init_wasm(state) { const results = await WebAssembly.instantiateStreaming(fetch('wasm/lod.wasm')); state.wasm.exports = results.instance.exports; state.wasm.exports.memory.grow(4096); state.wasm.stroke_bytes = 4096; state.wasm.coords_bytes = 4096; state.wasm.buffers = { 'coordinates': { 'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes), 'used': 0 }, 'coords_from': { 'offset': state.wasm.exports.alloc_static(state.wasm.stroke_bytes), 'used': 0, }, 'line_threshold': { 'offset': state.wasm.exports.alloc_static(state.wasm.stroke_bytes), 'used': 0, }, 'pressures': { 'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes / 8), 'used': 0 }, }; const mem = state.wasm.exports.memory.buffer; state.wasm.buffers['coordinates'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 4, mem, state.wasm.buffers['coordinates'].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['line_threshold'].tv = tv_create_on(Float32Array, state.wasm.stroke_bytes / 4, mem, state.wasm.buffers['line_threshold'].offset); state.wasm.buffers['pressures'].tv = tv_create_on(Uint8Array, state.wasm.coords_bytes / 8, mem, state.wasm.buffers['pressures'].offset); tv_add(state.wasm.buffers['coords_from'].tv, 0); state.wasm.buffers['coords_from'].used = 4; } function wasm_ensure_by(state, nstrokes, ncoords) { const buffers = state.wasm.buffers; const old_coords_from_offset = buffers['coords_from'].offset; const old_line_threshold_offset = buffers['line_threshold'].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; if (buffers['coordinates'].used + ncoords * 4 > state.wasm.coords_bytes) { state.wasm.coords_bytes += round_to_pow2(ncoords, 4096 * 16); // 1 wasm page (although it doesn't matter here) realloc = true; } if (buffers['coords_from'].used + nstrokes * 4 > state.wasm.stroke_bytes) { state.wasm.stroke_bytes += round_to_pow2(nstrokes, 4096 * 16); realloc = true; } if (realloc) { // TODO: we do memory.grow() somewhere here if needed state.wasm.exports.free_static(); const mem = state.wasm.exports.memory.buffer; const memv = new Uint8Array(mem); buffers['coordinates'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes); buffers['coords_from'].offset = state.wasm.exports.alloc_static(state.wasm.stroke_bytes); buffers['line_threshold'].offset = state.wasm.exports.alloc_static(state.wasm.stroke_bytes); buffers['pressures'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes / 8); buffers['coordinates'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 4, mem, buffers['coordinates'].offset); buffers['coords_from'].tv = tv_create_on(Uint32Array, state.wasm.stroke_bytes / 4, mem, buffers['coords_from'].offset); buffers['line_threshold'].tv = tv_create_on(Float32Array, state.wasm.stroke_bytes / 4, mem, buffers['line_threshold'].offset); buffers['pressures'].tv = tv_create_on(Uint8Array, state.wasm.coords_bytes / 8, mem, buffers['pressures'].offset); buffers['coordinates'].tv.size = buffers['coordinates'].used / 4; buffers['coords_from'].tv.size = buffers['coords_from'].used / 4; buffers['line_threshold'].tv.size = buffers['line_threshold'].used / 4; buffers['pressures'].tv.size = buffers['pressures'].used; const tmp = new Uint8Array(Math.max(state.wasm.coords_bytes / 8, state.wasm.stroke_bytes)); // TODO: needed? // Copy from back to front (otherwise we will overwrite) 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_line_threshold_offset, old_size_strokes)); memv.set(new Uint8Array(tmp.buffer, 0, old_size_strokes), buffers['line_threshold'].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); } } function do_lod_wasm(state, context) { state.wasm.exports.free_dynamic(); const clipped_indices = state.wasm.exports.alloc_dynamic(context.clipped_indices.size * 4); const mem = new Uint8Array(state.wasm.exports.memory.buffer); // Dynamic input data that should (by design) never be too big mem.set(tv_bytes(context.clipped_indices), clipped_indices); const buffers = state.wasm.buffers; const segment_count = state.wasm.exports.do_lod( clipped_indices, context.clipped_indices.size, state.canvas.zoom, buffers['coords_from'].offset, buffers['line_threshold'].offset, buffers['coordinates'].offset, buffers['pressures'].offset, buffers['coordinates'].used / 4, ); // Use results without copying from WASM memory const result_offset = clipped_indices + context.clipped_indices.size * 4 + (context.clipped_indices.size + 1) * 4 + buffers['coordinates'].used / 2; const wasm_points = new Float32Array(state.wasm.exports.memory.buffer, result_offset, segment_count * 2); const wasm_ids = new Uint32Array(state.wasm.exports.memory.buffer, result_offset + segment_count * 2 * 4, segment_count); const wasm_pressures = new Uint8Array(state.wasm.exports.memory.buffer, result_offset + segment_count * 2 * 4 + segment_count * 4, segment_count); context.instance_data_points.data = wasm_points; context.instance_data_points.size = segment_count * 2; context.instance_data_ids.data = wasm_ids; context.instance_data_ids.size = segment_count; context.instance_data_pressures.data = wasm_pressures; context.instance_data_pressures.size = segment_count; 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.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.size('allocate') state.rdp_traverse_stack = new Uint32Array(round_to_pow2(point_count, 4096)); } const stack = state.rdp_traverse_stack; // Basic CSR crap state.segments_from.data[i] = segments_head; if (state.canvas.zoom <= state.line_threshold.data[stroke_index]) { segments_data[segments_head++] = 0; segments_data[segments_head++] = point_count - 1; } else { let segment_count = 2; segments_data[segments_head++] = 0; let head = 0; // Using stack.push() allocates even if the stack is pre-allocated! stack[head++] = 0; stack[head++] = 0; stack[head++] = point_count - 1; while (head > 0) { const end = stack[--head]; const value = start = stack[--head]; const type = stack[--head]; if (type === 1) { segments_data[segments_head++] = value; } else { const max = rdp_find_max(state, zoom, coords_from, start, end); if (max !== -1) { segment_count += 1; stack[head++] = 0; stack[head++] = max; stack[head++] = end; stack[head++] = 1; stack[head++] = max; stack[head++] = -1; stack[head++] = 0; stack[head++] = start; stack[head++] = max; } } } segments_data[segments_head++] = point_count - 1; if (segment_count === 2 && state.canvas.zoom > state.line_threshold.data[stroke_index]) { state.line_threshold.data[stroke_index] = state.canvas.zoom; } } } state.segments_from.data[context.clipped_indices.size] = segments_head; state.segments_from.size = context.clipped_indices.size + 1; state.segments.size = segments_head; write_coordinates(state, context); return segments_head; }