|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
state.wasm.exports.total_free();
|
|
|
|
|
|
|
|
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_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);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|