Browse Source

LOD generation has been wassembled! (a little borked for now though)

ssao
A.Olokhtonov 10 months ago
parent
commit
1960bdebe9
  1. 6
      README.md
  2. 18
      client/aux.js
  3. 15
      client/bvh.js
  4. 8
      client/client_recv.js
  5. 1
      client/index.html
  6. 35
      client/index.js
  7. 69
      client/speed.js
  8. 28
      client/wasm/lod.c
  9. BIN
      client/wasm/lod.wasm
  10. 39
      client/webgl_geometry.js
  11. 6
      client/webgl_listeners.js

6
README.md

@ -3,7 +3,9 @@ Release:
+ Benchmark harness + 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) + 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) + 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?) - Z-prepass fringe bug (also, when do we enable the prepass?)
- Textured quads (pictures, code already written in older version) - Textured quads (pictures, code already written in older version)
- Resize and move pictures (draw handles) - Resize and move pictures (draw handles)
@ -34,7 +36,7 @@ Release:
- Line drawing - Line drawing
- Undo/redo - Undo/redo
* Polish * Polish
* Use typedvector where appropriate + Use typedvector where appropriate
- Show what's happening while the desk is loading (downloading, processing, uploading to gpu) - 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") - Settings panel for config values (including the setting for "offline mode")
- Set up VAOs - Set up VAOs

18
client/aux.js

@ -125,21 +125,21 @@ function tv_data(tv) {
return tv.data.subarray(0, tv.size); 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) { function tv_ensure(tv, capacity) {
if (tv.capacity < capacity) { if (tv.capacity < capacity) {
const new_tv = tv_create(tv.class_name, capacity); const new_data = new tv.class_name(capacity);
new_data.set(tv_data(tv));
new_tv.data.set(tv_data(tv)); tv.capacity = capacity;
new_tv.size = tv.size; tv.data = new_data;
return new_tv;
} }
return tv;
} }
function tv_ensure_by(tv, by) { 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) { function tv_add(tv, item) {

15
client/bvh.js

@ -172,8 +172,7 @@ function bvh_intersect_quad(bvh, quad, result_buffer) {
} }
if (node.is_leaf) { if (node.is_leaf) {
result_buffer.data[result_buffer.count] = node.stroke_index; tv_add(result_buffer, node.stroke_index);
result_buffer.count += 1;
} else { } else {
tv_add(bvh.traverse_stack, node.child1); tv_add(bvh.traverse_stack, node.child1);
tv_add(bvh.traverse_stack, node.child2); tv_add(bvh.traverse_stack, node.child2);
@ -186,14 +185,10 @@ function bvh_clip(state, context) {
return; return;
} }
if (context.clipped_indices.cap < state.stroke_count) { tv_ensure(context.clipped_indices, round_to_pow2(state.stroke_count, 4096))
context.clipped_indices.cap = round_to_pow2(state.stroke_count, 4096); tv_ensure(state.bvh.traverse_stack, 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));
context.clipped_indices.count = 0; tv_clear(context.clipped_indices);
const screen_topleft = screen_to_canvas(state, {'x': 0, 'y': 0}); 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}); 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); 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) { function bvh_construct_rec(bvh, vertical, strokes) {

8
client/client_recv.js

@ -101,8 +101,14 @@ function des_event(d, state = null) {
const coords = des_f32array(d, point_count * 2); 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_from = state.coordinates.size;
event.coords_to = state.coordinates.size + point_count * 2; event.coords_to = state.coordinates.size + point_count * 2;

1
client/index.html

@ -39,6 +39,7 @@
<label><input type="checkbox" id="debug-red">Simple shader</label> <label><input type="checkbox" id="debug-red">Simple shader</label>
<label><input type="checkbox" id="debug-do-prepass">Depth prepass</label> <label><input type="checkbox" id="debug-do-prepass">Depth prepass</label>
<label><input type="checkbox" id="debug-use-wasm">Use WASM</label>
<div class="flexcol"> <div class="flexcol">
<label><input type="checkbox" id="debug-limit-from">Limit events from</label> <label><input type="checkbox" id="debug-limit-from">Limit events from</label>

35
client/index.js

@ -129,7 +129,7 @@ function start_spinner(state) {
} }
} }
function main() { async function main() {
const state = { const state = {
'online': false, 'online': false,
'me': null, 'me': null,
@ -179,18 +179,10 @@ function main() {
'coordinates': tv_create(Float32Array, 4096), 'coordinates': tv_create(Float32Array, 4096),
'line_threshold': tv_create(Float32Array, 4096), 'line_threshold': tv_create(Float32Array, 4096),
'coords_from': tv_create(Uint32Array, 4096),
'segments_from': { 'coords_to': tv_create(Uint32Array, 4096),
'data': null, 'segments_from': tv_create(Uint32Array, 4096),
'count': 0, 'segments': tv_create(Uint32Array, 4096),
'cap': 0,
},
'segments': {
'data': null,
'count': 0,
'cap': 0,
},
'bvh': { 'bvh': {
'nodes': [], 'nodes': [],
@ -220,6 +212,7 @@ function main() {
'debug': { 'debug': {
'red': false, 'red': false,
'do_prepass': true, 'do_prepass': true,
'use_wasm': false,
'limit_from': false, 'limit_from': false,
'limit_to': false, 'limit_to': false,
'render_from': 0, 'render_from': 0,
@ -230,6 +223,9 @@ function main() {
'stats': {}, 'stats': {},
'following_player': null, 'following_player': null,
'wasm': {},
}; };
const context = { const context = {
@ -244,12 +240,7 @@ function main() {
'dynamic_serializer': serializer_create(config.initial_dynamic_bytes), 'dynamic_serializer': serializer_create(config.initial_dynamic_bytes),
'dynamic_index_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': tv_create(Uint32Array, 4096),
'clipped_indices': {
'data': null,
'count': 0,
'cap': 0,
},
'instance_data_points': tv_create(Float32Array, 4096), 'instance_data_points': tv_create(Float32Array, 4096),
'instance_data_ids': tv_create(Uint32Array, 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; state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0;
await init_wasm(state);
init_webgl(state, context); init_webgl(state, context);
init_listeners(state, context); init_listeners(state, context);
init_tools(state); init_tools(state);
ws_connect(state, context, true); ws_connect(state, context, true);
schedule_draw(state, context); schedule_draw(state, context);
state.timers.offline_toast = setTimeout(() => ui_offline(), config.initial_offline_timeout); state.timers.offline_toast = setTimeout(() => ui_offline(), config.initial_offline_timeout);
test_wasm();
} }

69
client/speed.js

@ -1,18 +1,7 @@
async function test_wasm() { async function init_wasm(state) {
const memory = new WebAssembly.Memory({ initial: 10, maximum: 100 });
const results = await WebAssembly.instantiateStreaming(fetch('wasm/lod.wasm')); const results = await WebAssembly.instantiateStreaming(fetch('wasm/lod.wasm'));
const numbers_offset = results.instance.exports.alloc(40); state.wasm.exports = results.instance.exports;
state.wasm.exports.memory.grow(1024);
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);
} }
function rdp_find_max(state, zoom, coords_from, start, end) { 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; 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) { function do_lod(state, context) {
if (state.debug.use_wasm) {
return do_lod_wasm(state, context);
}
const zoom = state.canvas.zoom; const zoom = state.canvas.zoom;
const segments_data = state.segments.data; const segments_data = state.segments.data;
let segments_head = 0; 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_index = context.clipped_indices.data[i];
const stroke = state.events[stroke_index]; const stroke = state.events[stroke_index];
const point_count = (stroke.coords_to - stroke.coords_from) / 2; const point_count = (stroke.coords_to - stroke.coords_from) / 2;
const coords_from = stroke.coords_from; const coords_from = stroke.coords_from;
if (point_count > state.rdp_traverse_stack.length) { 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)); 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; return segments_head;
} }

28
client/wasm/lod.c

@ -1,6 +1,12 @@
extern char __heap_base; extern char __heap_base;
static int allocated; static int allocated;
void
total_free(void)
{
allocated = 0;
}
void * void *
alloc(int size) alloc(int size)
{ {
@ -9,19 +15,6 @@ alloc(int size)
return(result); 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 static int
rdp_find_max(float *coordinates, float zoom, int coords_from, rdp_find_max(float *coordinates, float zoom, int coords_from,
int segment_start, int segment_end) 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, float *line_threshold, float *coordinates,
int *segments_from, int *segments) int *segments_from, int *segments)
{ {
if (clipped_count == 0) {
return(0);
}
int segments_head = 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) { for (int i = 0; i < clipped_count; ++i) {
int stroke_index = clipped_indices[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; line_threshold[stroke_index] = zoom;
} }
} }
segments_from[clipped_count] = segments_head;
return(segments_head); return(segments_head);
} }
#endif

BIN
client/wasm/lod.wasm

Binary file not shown.

39
client/webgl_geometry.js

@ -38,31 +38,19 @@ function geometry_prepare_stroke(state) {
function geometry_write_instances(state, context) { function geometry_write_instances(state, context) {
if (state.segments_from.cap < context.clipped_indices.count + 1) { tv_ensure(state.segments_from, round_to_pow2(context.clipped_indices.size + 1, 4096));
state.segments_from.cap = round_to_pow2(context.clipped_indices.count + 1, 4096); tv_ensure(state.segments, round_to_pow2(state.coordinates.size / 2, 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);
}
state.segments_from.count = 0; tv_clear(state.segments_from);
state.segments.count = 0; tv_clear(state.segments);
state.stats.rdp_max_count = 0; state.stats.rdp_max_count = 0;
state.stats.rdp_segments = 0; state.stats.rdp_segments = 0;
const segment_count = do_lod(state, context); const segment_count = do_lod(state, context);
state.segments.count = segment_count; tv_ensure(context.instance_data_points, state.segments.size * 2);
state.segments_from.data[context.clipped_indices.count] = state.segments.count; tv_ensure(context.instance_data_ids, state.segments.size);
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_clear(context.instance_data_points); tv_clear(context.instance_data_points);
tv_clear(context.instance_data_ids); tv_clear(context.instance_data_ids);
@ -72,10 +60,11 @@ function geometry_write_instances(state, context) {
const segments = state.segments.data; const segments = state.segments.data;
const coords = state.coordinates.data; const coords = state.coordinates.data;
const events = state.events; 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 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 from = segments_from[i];
const to = segments_from[i + 1]; 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); 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) { 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); 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); tv_add(state.line_threshold, -1);
const color_u32 = stroke.color; 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)); 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_ids, round_to_pow2(total_points, 4096));
tv_clear(context.dynamic_instance_points); tv_clear(context.dynamic_instance_points);
tv_clear(context.dynamic_instance_ids); tv_clear(context.dynamic_instance_ids);

6
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-do-prepass').checked = state.debug.do_prepass;
document.getElementById('debug-limit-from').checked = state.debug.limit_from; document.getElementById('debug-limit-from').checked = state.debug.limit_from;
document.getElementById('debug-limit-to').checked = state.debug.limit_to; 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) => { document.getElementById('debug-red').addEventListener('change', (e) => {
state.debug.red = e.target.checked; state.debug.red = e.target.checked;
@ -37,6 +38,11 @@ function debug_panel_init(state, context) {
schedule_draw(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) => { document.getElementById('debug-limit-from').addEventListener('change', (e) => {
state.debug.limit_from = e.target.checked; state.debug.limit_from = e.target.checked;
schedule_draw(state, context); schedule_draw(state, context);

Loading…
Cancel
Save