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(1024); } function rdp_find_max(state, zoom, coords_from, start, end) { // Finds a point from the range [start, end) with the maximum distance from the line (start--end) that is also further than EPS const EPS = 1.0 / zoom; let result = -1; let max_dist = 0; const ax = state.coordinates.data[coords_from + start * 2 + 0]; const ay = state.coordinates.data[coords_from + start * 2 + 1]; const bx = state.coordinates.data[coords_from + end * 2 + 0]; const by = state.coordinates.data[coords_from + end * 2 + 1]; const dx = bx - ax; const dy = by - ay; const dist_ab = Math.sqrt(dx * dx + dy * dy); const dir_nx = dy / dist_ab; const dir_ny = -dx / dist_ab; for (let i = start + 1; i < end; ++i) { const px = state.coordinates.data[coords_from + i * 2 + 0]; const py = state.coordinates.data[coords_from + i * 2 + 1]; const apx = px - ax; const apy = py - ay; const dist = Math.abs(apx * dir_nx + apy * dir_ny); if (dist > EPS && dist > max_dist) { result = i; max_dist = dist; } } state.stats.rdp_max_count++; state.stats.rdp_segments += end - start - 1; 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 + 1); const wasm_points = new Float32Array(state.wasm.exports.memory.buffer, coordinates + state.coordinates.size * 4, segment_count * 2); const wasm_ids = new Uint32Array(state.wasm.exports.memory.buffer, coordinates + (state.coordinates.size + segment_count * 2) * 4, segment_count); 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; tv_ensure(context.instance_data_points, segment_count * 2); tv_ensure(context.instance_data_ids, segment_count); context.instance_data_points.data.set(wasm_points); context.instance_data_points.size = segment_count * 2; context.instance_data_ids.data.set(wasm_ids); context.instance_data_ids.size = segment_count; 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.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; } function write_coordinates(state, context) { 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); const clipped = context.clipped_indices.data; const segments_from = state.segments_from.data; const segments = state.segments.data; const coords = state.coordinates.data; const events = state.events; // 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 = state.events[stroke_index].coords_from; const from = segments_from[i]; const to = segments_from[i + 1]; for (let j = from; j < to; ++j) { const base_this = segments[j]; const ax = coords[coords_from + base_this * 2 + 0]; const ay = coords[coords_from + base_this * 2 + 1]; tv_add(context.instance_data_points, ax); tv_add(context.instance_data_points, ay); // Pack 1 into highest bit of stroke_index if we should not draw a segemtn from this // point to the next one if (j != to - 1) { tv_add(context.instance_data_ids, stroke_index); } else { tv_add(context.instance_data_ids, stroke_index | (1 << 31)); } } } }