|
|
|
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;
|
|
|
|
}
|