|
|
|
@ -1,77 +1,118 @@
@@ -1,77 +1,118 @@
|
|
|
|
|
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, |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tv_add(state.wasm.buffers['coords_from'].tv, 0); |
|
|
|
|
state.wasm.buffers['coords_from'].used = 4; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
function wasm_ensure_by(state, nstrokes, ncoords) { |
|
|
|
|
const buffers = state.wasm.buffers; |
|
|
|
|
|
|
|
|
|
let result = -1; |
|
|
|
|
let max_dist = 0; |
|
|
|
|
const old_coords_from_offset = buffers['coords_from'].offset; |
|
|
|
|
const old_line_threshold_offset = buffers['line_threshold'].offset; |
|
|
|
|
|
|
|
|
|
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 old_size_coords = state.wasm.coords_bytes; |
|
|
|
|
const old_size_strokes = state.wasm.stroke_bytes; |
|
|
|
|
|
|
|
|
|
const dx = bx - ax; |
|
|
|
|
const dy = by - ay; |
|
|
|
|
let realloc = false; |
|
|
|
|
|
|
|
|
|
const dist_ab = Math.sqrt(dx * dx + dy * dy); |
|
|
|
|
const dir_nx = dy / dist_ab; |
|
|
|
|
const dir_ny = -dx / dist_ab; |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
if (buffers['coords_from'].used + nstrokes * 4 > state.wasm.stroke_bytes) { |
|
|
|
|
state.wasm.stroke_bytes += round_to_pow2(nstrokes, 4096 * 16); |
|
|
|
|
realloc = true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const dist = Math.abs(apx * dir_nx + apy * dir_ny); |
|
|
|
|
if (realloc) { |
|
|
|
|
// TODO: we do memory.grow() somewhere here if needed
|
|
|
|
|
|
|
|
|
|
if (dist > EPS && dist > max_dist) { |
|
|
|
|
result = i; |
|
|
|
|
max_dist = dist; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
state.wasm.exports.free_static(); |
|
|
|
|
|
|
|
|
|
state.stats.rdp_max_count++; |
|
|
|
|
state.stats.rdp_segments += end - start - 1; |
|
|
|
|
const mem = state.wasm.exports.memory.buffer; |
|
|
|
|
const memv = new Uint8Array(mem); |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
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['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['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; |
|
|
|
|
|
|
|
|
|
const tmp = new Uint8Array(state.wasm.stroke_bytes); // TODO: needed?
|
|
|
|
|
|
|
|
|
|
// First we move the line_threshold, only then coords_from (otherwise we will overwrite)
|
|
|
|
|
tmp.set(new Uint8Array(mem, old_line_threshold_offset, old_size_strokes)); |
|
|
|
|
memv.set(tmp, buffers['coordinates'].offset + state.wasm.coords_bytes + state.wasm.stroke_bytes); |
|
|
|
|
|
|
|
|
|
tmp.set(new Uint8Array(mem, old_coords_from_offset, old_size_strokes)); |
|
|
|
|
memv.set(tmp, buffers['coordinates'].offset + state.wasm.coords_bytes); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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 buffers = state.wasm.buffers; |
|
|
|
|
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 |
|
|
|
|
buffers['coords_from'].offset, |
|
|
|
|
buffers['line_threshold'].offset, |
|
|
|
|
buffers['coordinates'].offset, |
|
|
|
|
buffers['coordinates'].used / 4, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 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); |
|
|
|
|
// 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); |
|
|
|
|
|
|
|
|
|
context.instance_data_points.data = wasm_points; |
|
|
|
|
context.instance_data_points.size = segment_count * 2; |
|
|
|
@ -166,43 +207,3 @@ function do_lod(state, context) {
@@ -166,43 +207,3 @@ function do_lod(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)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|