diff --git a/README.md b/README.md
index 0b51884..814ab54 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,9 @@ Release:
+ Benchmark harness
+ Reuse points, pack "nodraw" in high bit of stroke id (probably have at least one more bit, so up to 4 flag configurations)
+ Draw dynamic data (strokes in progress)
- * Webassembly for core LOD generation
+ + Webassembly for core LOD generation
+ - Webassembly for final buffers
+ - Do not copy memory from wasm and back
- Z-prepass fringe bug (also, when do we enable the prepass?)
- Textured quads (pictures, code already written in older version)
- Resize and move pictures (draw handles)
@@ -34,7 +36,7 @@ Release:
- Line drawing
- Undo/redo
* Polish
- * Use typedvector where appropriate
+ + Use typedvector where appropriate
- Show what's happening while the desk is loading (downloading, processing, uploading to gpu)
- Settings panel for config values (including the setting for "offline mode")
- Set up VAOs
diff --git a/client/aux.js b/client/aux.js
index 7b3b837..81c39db 100644
--- a/client/aux.js
+++ b/client/aux.js
@@ -125,21 +125,21 @@ function tv_data(tv) {
return tv.data.subarray(0, tv.size);
}
+function tv_bytes(tv) {
+ return new Uint8Array(tv.data.buffer, 0, tv.size * tv.data.BYTES_PER_ELEMENT);
+}
+
function tv_ensure(tv, capacity) {
if (tv.capacity < capacity) {
- const new_tv = tv_create(tv.class_name, capacity);
-
- new_tv.data.set(tv_data(tv));
- new_tv.size = tv.size;
-
- return new_tv;
+ const new_data = new tv.class_name(capacity);
+ new_data.set(tv_data(tv));
+ tv.capacity = capacity;
+ tv.data = new_data;
}
-
- return tv;
}
function tv_ensure_by(tv, by) {
- return tv_ensure(tv, round_to_pow2(tv.size + by, 4096));
+ tv_ensure(tv, round_to_pow2(tv.size + by, 4096));
}
function tv_add(tv, item) {
diff --git a/client/bvh.js b/client/bvh.js
index e16f7de..f1b34c7 100644
--- a/client/bvh.js
+++ b/client/bvh.js
@@ -172,8 +172,7 @@ function bvh_intersect_quad(bvh, quad, result_buffer) {
}
if (node.is_leaf) {
- result_buffer.data[result_buffer.count] = node.stroke_index;
- result_buffer.count += 1;
+ tv_add(result_buffer, node.stroke_index);
} else {
tv_add(bvh.traverse_stack, node.child1);
tv_add(bvh.traverse_stack, node.child2);
@@ -186,14 +185,10 @@ function bvh_clip(state, context) {
return;
}
- if (context.clipped_indices.cap < state.stroke_count) {
- context.clipped_indices.cap = round_to_pow2(state.stroke_count, 4096);
- context.clipped_indices.data = new Uint32Array(context.clipped_indices.cap);
- }
-
- state.bvh.traverse_stack = tv_ensure(state.bvh.traverse_stack, round_to_pow2(state.stroke_count, 4096));
+ tv_ensure(context.clipped_indices, round_to_pow2(state.stroke_count, 4096))
+ tv_ensure(state.bvh.traverse_stack, round_to_pow2(state.stroke_count, 4096));
- context.clipped_indices.count = 0;
+ tv_clear(context.clipped_indices);
const screen_topleft = screen_to_canvas(state, {'x': 0, 'y': 0});
const screen_bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height});
@@ -209,7 +204,7 @@ function bvh_clip(state, context) {
bvh_intersect_quad(state.bvh, screen, context.clipped_indices);
- new Uint32Array(context.clipped_indices.data.buffer, 0, context.clipped_indices.count).sort(); // we need to draw back to front still!
+ tv_data(context.clipped_indices).sort(); // we need to draw back to front still!
}
function bvh_construct_rec(bvh, vertical, strokes) {
diff --git a/client/client_recv.js b/client/client_recv.js
index e1fa77d..10d2d40 100644
--- a/client/client_recv.js
+++ b/client/client_recv.js
@@ -101,8 +101,14 @@ function des_event(d, state = null) {
const coords = des_f32array(d, point_count * 2);
- state.coordinates = tv_ensure_by(state.coordinates, coords.length);
+ tv_ensure_by(state.coordinates, coords.length);
+ tv_ensure_by(state.coords_from, 1);
+ tv_ensure_by(state.coords_to, 1);
+
+ tv_add(state.coords_from, state.coordinates.size);
+ tv_add(state.coords_to, state.coordinates.size + point_count * 2);
+ // TODO: remove, this is duplicate data
event.coords_from = state.coordinates.size;
event.coords_to = state.coordinates.size + point_count * 2;
diff --git a/client/index.html b/client/index.html
index 2bbdaf0..e9268ec 100644
--- a/client/index.html
+++ b/client/index.html
@@ -39,6 +39,7 @@
+
diff --git a/client/index.js b/client/index.js
index eecd128..dc44cd5 100644
--- a/client/index.js
+++ b/client/index.js
@@ -129,7 +129,7 @@ function start_spinner(state) {
}
}
-function main() {
+async function main() {
const state = {
'online': false,
'me': null,
@@ -179,18 +179,10 @@ function main() {
'coordinates': tv_create(Float32Array, 4096),
'line_threshold': tv_create(Float32Array, 4096),
-
- 'segments_from': {
- 'data': null,
- 'count': 0,
- 'cap': 0,
- },
-
- 'segments': {
- 'data': null,
- 'count': 0,
- 'cap': 0,
- },
+ 'coords_from': tv_create(Uint32Array, 4096),
+ 'coords_to': tv_create(Uint32Array, 4096),
+ 'segments_from': tv_create(Uint32Array, 4096),
+ 'segments': tv_create(Uint32Array, 4096),
'bvh': {
'nodes': [],
@@ -220,6 +212,7 @@ function main() {
'debug': {
'red': false,
'do_prepass': true,
+ 'use_wasm': false,
'limit_from': false,
'limit_to': false,
'render_from': 0,
@@ -230,6 +223,9 @@ function main() {
'stats': {},
'following_player': null,
+
+
+ 'wasm': {},
};
const context = {
@@ -244,12 +240,7 @@ function main() {
'dynamic_serializer': serializer_create(config.initial_dynamic_bytes),
'dynamic_index_serializer': serializer_create(config.initial_dynamic_bytes),
- // TODO: i seem to have a lot of these, maybe make a few utility functions? similar to serializer, but for pure typedarray
- 'clipped_indices': {
- 'data': null,
- 'count': 0,
- 'cap': 0,
- },
+ 'clipped_indices': tv_create(Uint32Array, 4096),
'instance_data_points': tv_create(Float32Array, 4096),
'instance_data_ids': tv_create(Uint32Array, 4096),
@@ -277,16 +268,16 @@ function main() {
state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0;
+ await init_wasm(state);
+
init_webgl(state, context);
init_listeners(state, context);
init_tools(state);
+
ws_connect(state, context, true);
schedule_draw(state, context);
state.timers.offline_toast = setTimeout(() => ui_offline(), config.initial_offline_timeout);
-
-
- test_wasm();
}
diff --git a/client/speed.js b/client/speed.js
index 9b0740c..c5a6abc 100644
--- a/client/speed.js
+++ b/client/speed.js
@@ -1,18 +1,7 @@
-async function test_wasm() {
- const memory = new WebAssembly.Memory({ initial: 10, maximum: 100 });
+async function init_wasm(state) {
const results = await WebAssembly.instantiateStreaming(fetch('wasm/lod.wasm'));
- const numbers_offset = results.instance.exports.alloc(40);
-
- const numbers = new Uint32Array(results.instance.exports.memory.buffer, numbers_offset, 10);
-
- for (let i = 0; i < 10; ++i) {
- numbers[i] = i;
- }
-
- console.log(numbers);
-
- const sum = results.instance.exports.sum(numbers_offset, 10);
- console.log(sum);
+ state.wasm.exports = results.instance.exports;
+ state.wasm.exports.memory.grow(1024);
}
function rdp_find_max(state, zoom, coords_from, start, end) {
@@ -55,20 +44,64 @@ function rdp_find_max(state, zoom, coords_from, start, end) {
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);
+
+ 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;
+
+ 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.count; ++i) {
+ 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.count('allocate')
+ //console.size('allocate')
state.rdp_traverse_stack = new Uint32Array(round_to_pow2(point_count, 4096));
}
@@ -127,5 +160,9 @@ function do_lod(state, context) {
}
}
+ state.segments_from.data[context.clipped_indices.size] = segments_head;
+ state.segments_from.size = context.clipped_indices.size + 1;
+ state.segments.size = segments_head;
+
return segments_head;
}
diff --git a/client/wasm/lod.c b/client/wasm/lod.c
index db1c3c9..d911079 100644
--- a/client/wasm/lod.c
+++ b/client/wasm/lod.c
@@ -1,6 +1,12 @@
extern char __heap_base;
static int allocated;
+void
+total_free(void)
+{
+ allocated = 0;
+}
+
void *
alloc(int size)
{
@@ -9,19 +15,6 @@ alloc(int size)
return(result);
}
-int
-sum(int *numbers, int count)
-{
- int result = 0;
-
- for (int i = 0; i < count; ++i) {
- result += numbers[i];
- }
-
- return(result);
-}
-
-#if 0
static int
rdp_find_max(float *coordinates, float zoom, int coords_from,
int segment_start, int segment_end)
@@ -67,8 +60,12 @@ do_lod(int *clipped_indices, int clipped_count, float zoom,
float *line_threshold, float *coordinates,
int *segments_from, int *segments)
{
+ if (clipped_count == 0) {
+ return(0);
+ }
+
int segments_head = 0;
- int stack[4096];
+ int stack[4096]; // TODO: what's a reasonable max size for this?
for (int i = 0; i < clipped_count; ++i) {
int stroke_index = clipped_indices[i];
@@ -131,7 +128,8 @@ do_lod(int *clipped_indices, int clipped_count, float zoom,
line_threshold[stroke_index] = zoom;
}
}
+
+ segments_from[clipped_count] = segments_head;
return(segments_head);
}
-#endif
\ No newline at end of file
diff --git a/client/wasm/lod.wasm b/client/wasm/lod.wasm
index e9e084a..9c0c4f3 100755
Binary files a/client/wasm/lod.wasm and b/client/wasm/lod.wasm differ
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index 9970f2d..0b252bd 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -38,31 +38,19 @@ function geometry_prepare_stroke(state) {
function geometry_write_instances(state, context) {
- if (state.segments_from.cap < context.clipped_indices.count + 1) {
- state.segments_from.cap = round_to_pow2(context.clipped_indices.count + 1, 4096);
- state.segments_from.data = new Uint32Array(state.segments_from.cap);
- }
-
- if (state.segments.cap < state.coordinates.size / 2) {
- state.segments.cap = round_to_pow2(state.coordinates.size, 4096);
- state.segments.data = new Uint32Array(state.segments.cap);
- }
+ tv_ensure(state.segments_from, round_to_pow2(context.clipped_indices.size + 1, 4096));
+ tv_ensure(state.segments, round_to_pow2(state.coordinates.size / 2, 4096));
- state.segments_from.count = 0;
- state.segments.count = 0;
+ tv_clear(state.segments_from);
+ tv_clear(state.segments);
state.stats.rdp_max_count = 0;
state.stats.rdp_segments = 0;
const segment_count = do_lod(state, context);
- state.segments.count = segment_count;
- state.segments_from.data[context.clipped_indices.count] = state.segments.count;
- state.segments_from.count = context.clipped_indices.count + 1;
-
- context.instance_data_points = tv_ensure(context.instance_data_points, state.segments.count * 2);
- context.instance_data_ids = tv_ensure(context.instance_data_ids, state.segments.count);
-
+ 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);
@@ -72,10 +60,11 @@ function geometry_write_instances(state, context) {
const segments = state.segments.data;
const coords = state.coordinates.data;
const events = state.events;
-
- for (let i = 0; i < state.segments_from.count - 1; ++i) {
+
+ // 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 = events[stroke_index].coords_from;
+ const coords_from = state.events[stroke_index].coords_from;
const from = segments_from[i];
const to = segments_from[i + 1];
@@ -100,7 +89,7 @@ function geometry_write_instances(state, context) {
if (config.debug_print) console.debug('instances:', state.segments.count, 'rdp max:', state.stats.rdp_max_count, 'rdp segments:', state.stats.rdp_segments);
- return state.segments.count;
+ return state.segments.size;
}
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {
@@ -111,7 +100,7 @@ function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = fa
context.stroke_data = ser_ensure_by(context.stroke_data, config.bytes_per_stroke);
- state.line_threshold = tv_ensure(state.line_threshold, round_to_pow2(state.stroke_count, 4096));
+ tv_ensure(state.line_threshold, round_to_pow2(state.stroke_count, 4096));
tv_add(state.line_threshold, -1);
const color_u32 = stroke.color;
@@ -159,8 +148,8 @@ function recompute_dynamic_data(state, context) {
}
}
- context.dynamic_instance_data = tv_ensure(context.dynamic_instance_points, round_to_pow2(total_points * 2, 4096));
- context.dynamic_instance_ids = tv_ensure(context.dynamic_instance_ids, round_to_pow2(total_points, 4096));
+ tv_ensure(context.dynamic_instance_points, round_to_pow2(total_points * 2, 4096));
+ tv_ensure(context.dynamic_instance_ids, round_to_pow2(total_points, 4096));
tv_clear(context.dynamic_instance_points);
tv_clear(context.dynamic_instance_ids);
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index f89e8d7..65b3d4f 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -26,6 +26,7 @@ function debug_panel_init(state, context) {
document.getElementById('debug-do-prepass').checked = state.debug.do_prepass;
document.getElementById('debug-limit-from').checked = state.debug.limit_from;
document.getElementById('debug-limit-to').checked = state.debug.limit_to;
+ document.getElementById('debug-use-wasm').checked = state.debug.use_wasm;
document.getElementById('debug-red').addEventListener('change', (e) => {
state.debug.red = e.target.checked;
@@ -37,6 +38,11 @@ function debug_panel_init(state, context) {
schedule_draw(state, context);
});
+ document.getElementById('debug-use-wasm').addEventListener('change', (e) => {
+ state.debug.use_wasm = e.target.checked;
+ schedule_draw(state, context);
+ });
+
document.getElementById('debug-limit-from').addEventListener('change', (e) => {
state.debug.limit_from = e.target.checked;
schedule_draw(state, context);