From 777772530f9a5bf83ef35348d359315759d9b377 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Thu, 21 Nov 2024 23:49:06 +0300 Subject: [PATCH] Batching is close to working. Not quite though Also possibly fixed a very nasty bug, but probably not --- client/index.js | 1 + client/lod_worker.js | 1 + client/speed.js | 17 +++++++++-- client/wasm/lod.c | 60 ++++++++++++++++++++++++++++++--------- client/wasm/lod.wasm | Bin 2793 -> 3187 bytes client/webgl_draw.js | 31 ++++++-------------- client/webgl_geometry.js | 3 ++ 7 files changed, 74 insertions(+), 39 deletions(-) diff --git a/client/index.js b/client/index.js index a986914..f04815c 100644 --- a/client/index.js +++ b/client/index.js @@ -227,6 +227,7 @@ async function main() { 'instance_data_points': tv_create(Float32Array, 4096), 'instance_data_ids': tv_create(Uint32Array, 4096), 'instance_data_pressures': tv_create(Uint8Array, 4096), + 'instance_data_batches': tv_create(Uint32Array, 4096), 'dynamic_instance_points': tv_create(Float32Array, 4096), 'dynamic_instance_pressure': tv_create(Uint8Array, 4096), diff --git a/client/lod_worker.js b/client/lod_worker.js index c21ee3e..2dff3bb 100644 --- a/client/lod_worker.js +++ b/client/lod_worker.js @@ -25,6 +25,7 @@ function work(indices_base, indices_count, zoom, offsets) { offsets['pressures'], offsets['result_buffers'] + thread_id * 4, offsets['result_counts'] + thread_id * 4, + offsets['result_batch_counts'] + thread_id * 4, ); } catch (e) { console.error('WASM:', e); diff --git a/client/speed.js b/client/speed.js index 18c3ca4..906c64c 100644 --- a/client/speed.js +++ b/client/speed.js @@ -43,7 +43,7 @@ async function init_wasm(state) { env: { 'memory': memory } }); - const nworkers = navigator.hardwareConcurrency; + const nworkers = 1; //navigator.hardwareConcurrency; state.wasm.exports = master_wasm.instance.exports; state.wasm.heap_base = state.wasm.exports.alloc_static(0); @@ -185,6 +185,7 @@ async function do_lod(state, context) { const buffers = state.wasm.buffers; const result_buffers = state.wasm.exports.alloc_dynamic(state.wasm.workers.length * 4); const result_counts = state.wasm.exports.alloc_dynamic(state.wasm.workers.length * 4); + const result_batch_counts = state.wasm.exports.alloc_dynamic(state.wasm.workers.length * 4); const clipped_indices = state.wasm.exports.alloc_dynamic(context.clipped_indices.size * 4); const mem = new Uint8Array(state.wasm.memory.buffer); @@ -202,6 +203,7 @@ async function do_lod(state, context) { 'pressures': buffers['pressures'].offset, 'result_buffers': result_buffers, 'result_counts': result_counts, + 'result_batch_counts': result_batch_counts, }; const jobs = []; @@ -226,11 +228,13 @@ async function do_lod(state, context) { const result_offset = state.wasm.exports.merge_results( result_counts, + result_batch_counts, result_buffers, state.wasm.workers.length ); const segment_count = new Int32Array(state.wasm.memory.buffer, result_counts, 1)[0]; // by convention + const batch_count = new Int32Array(state.wasm.memory.buffer, result_batch_counts, 1)[0]; // by convention // Use results without copying from WASM memory const wasm_points = new Float32Array(state.wasm.memory.buffer, @@ -238,16 +242,25 @@ async function do_lod(state, context) { const wasm_ids = new Uint32Array(state.wasm.memory.buffer, result_offset + segment_count * 2 * 4, segment_count); const wasm_pressures = new Uint8Array(state.wasm.memory.buffer, - result_offset + segment_count * 2 * 4 + segment_count * 4, segment_count); + result_offset + segment_count * 3 * 4, segment_count); + const wasm_batches = new Int32Array(state.wasm.memory.buffer, + result_offset + round_to_pow2(segment_count * (3 * 4 + 1), 4), batch_count * 2); context.instance_data_points.data = wasm_points; context.instance_data_points.size = segment_count * 2; + context.instance_data_points.capacity = segment_count * 2; context.instance_data_ids.data = wasm_ids; context.instance_data_ids.size = segment_count; + context.instance_data_ids.capacity = segment_count; context.instance_data_pressures.data = wasm_pressures; context.instance_data_pressures.size = segment_count; + context.instance_data_pressures.capacity = segment_count; + + context.instance_data_batches.data = wasm_batches; + context.instance_data_batches.size = batch_count * 2; + context.instance_data_batches.capacity = batch_count * 2; return segment_count; } diff --git a/client/wasm/lod.c b/client/wasm/lod.c index c1e21e7..d494367 100644 --- a/client/wasm/lod.c +++ b/client/wasm/lod.c @@ -202,7 +202,8 @@ do_lod(int *clipped_indices, int clipped_count, float zoom, float *ys, unsigned char *pressures, char **result_buffer, - int *result_count) + int *result_count, + int *result_batch_count) { if (clipped_count == 0) { result_count[0] = 0; @@ -281,16 +282,18 @@ do_lod(int *clipped_indices, int clipped_count, float zoom, // Write actual coordinates (points) and stroke ids // Do this in one allocation so that they're not interleaved between threads - char *output = alloc_dynamic(segments_head * (3 * 4 + 1) + clipped_count * 4); + char *output = alloc_dynamic(round_to_pow2(segments_head * (3 * 4 + 1), 4) + clipped_count * 4 * 2); // max two ints per stroke for batch info (realistically, much less) float *points = (float *) output; int *ids = (int *) (output + segments_head * 4 * 2); unsigned char *pressures_res = (unsigned char *) (output + segments_head * 4 * 3); - unsigned int *batches = (unsigned int *) (output + segments_head * (4 * 3 + 1)); + int *batches = (int *) (output + round_to_pow2(segments_head * (4 * 3 + 1), 4)); int phead = 0; int ihead = 0; float sqrt_zoom = __builtin_sqrtf(zoom); - int last_lod = -1; + int last_lod = -100; + int batch_count = 0; + int batch_size = 0; for (int i = 0; i < clipped_count; ++i) { int stroke_index = clipped_indices[i]; @@ -316,48 +319,76 @@ do_lod(int *clipped_indices, int clipped_count, float zoom, } } + int segment_count = to - from; + // Compute recommended LOD level, add to current batch or start new batch - float sqrt_width = __builtin_sqrtf(width[stroke_index]); // TOOD: pass in stroke width - int lod = __builtin_round(sqrt_zoom * sqrt_width * 0.3333f); + float sqrt_width = __builtin_sqrtf(width[stroke_index]); + int lod = __builtin_ceil(sqrt_zoom * sqrt_width * 0.3333f); // TODO: round -#if 0 - if (__builtin_abs(lod - last_lod) > 2) { + if (lod > 7) lod = 7; + + if (batch_size > 0 && __builtin_abs(lod - last_lod) > 2) { // Start new batch - } else { - // Add to existing batch + batches[batch_count * 2 + 0] = batch_size; + batches[batch_count * 2 + 1] = last_lod; + ++batch_count; + batch_size = 0; } + batch_size += segment_count; last_lod = lod; -#endif } - + + if (batch_size > 0) { + batches[batch_count * 2 + 0] = batch_size; + batches[batch_count * 2 + 1] = last_lod; + ++batch_count; + } + result_buffer[0] = output; result_count[0] = segments_head; + result_batch_count[0] = batch_count; } // NOT thread-safe, only call from one thread char * -merge_results(int *segment_counts, char **buffers, int nthreads) +merge_results(int *segment_counts, int *batch_counts, char **buffers, int nthreads) { int total_segments = 0; + int total_batches = 0; for (int i = 0; i < nthreads; ++i) { total_segments += segment_counts[i]; + total_batches += batch_counts[i]; } - char *merged = alloc_dynamic(total_segments * (3 * 4 + 1)); + char *merged = alloc_dynamic(round_to_pow2(total_segments * (3 * 4 + 1), 4) + total_batches * 4); float *points = (float *) merged; int *ids = (int *) (merged + total_segments * 4 * 2); unsigned char *pressures = (unsigned char *) (merged + total_segments * 4 * 3); + int *batches = (int *) (merged + round_to_pow2(total_segments * (3 * 4 + 1), 4)); + int batch_base = 0; + int last_batch_lod = -99; + int bhead = 0; + int written_batches = 0; for (int i = 0; i < nthreads; ++i) { int segments = segment_counts[i]; + int nbatches = batch_counts[i]; + int *thread_batches = (int *) (buffers[i] + round_to_pow2(segments * (4 * 3 + 1), 4)); + if (segments > 0) { __builtin_memcpy(points, buffers[i], segments * 4 * 2); __builtin_memcpy(ids, buffers[i] + segments * 4 * 2, segments * 4); __builtin_memcpy(pressures, buffers[i] + segments * 4 * 3, segments); + for (int j = 0; j < nbatches * 2; j += 2) { + batches[bhead++] = written_batches; + batches[bhead++] = thread_batches[j + 1]; + written_batches += thread_batches[j + 0]; + } + points += segments * 2; ids += segments; pressures += segments; @@ -365,6 +396,7 @@ merge_results(int *segment_counts, char **buffers, int nthreads) } segment_counts[0] = total_segments; + batch_counts[0] = total_batches; return(merged); } diff --git a/client/wasm/lod.wasm b/client/wasm/lod.wasm index 23c843d3b9700370db94a9041b19610f22b995c9..6fe6fbc53a4cfe636330794e293bac081714a40f 100755 GIT binary patch literal 3187 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 diff --git a/client/webgl_draw.js b/client/webgl_draw.js index 52d35a5..e7f5517 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -273,13 +273,11 @@ async function draw(state, context, animate, ts) { } } - // TODO: what do we do with this - const circle_lod = Math.round(Math.min(7, 3 * Math.sqrt(state.canvas.zoom))); + // TODO: @speed we can do this once at startup const lod_levels = []; let total_lod_floats = 0; let total_lod_indices = 0; let stat_total_vertices = 0; - for (let i = 0; i <= 7; ++i) { const d = geometry_good_circle_and_dummy(i); lod_levels.push({ @@ -295,20 +293,9 @@ async function draw(state, context, animate, ts) { if (segment_count > 0) { const pr = programs['main']; - const nbatches = 10; - const batches = []; - - for (let i = 0; i < nbatches; ++i) { - batches.push({ - 'index': Math.floor(segment_count / nbatches * i), - 'lod': circle_lod, - }); - - if (i % 2 == 1) { - batches[batches.length - 1].lod = Math.max(0, batches[batches.length - 1].lod); - } - } - batches.push({'index': segment_count, 'lod': -1}); // lod unused + // Last pair (lod unused) to have a proper from;to + tv_add2(context.instance_data_batches, segment_count); + tv_add2(context.instance_data_batches, -1); gl.clear(gl.DEPTH_BUFFER_BIT); // draw strokes above the images gl.useProgram(pr.program); @@ -363,11 +350,10 @@ async function draw(state, context, animate, ts) { gl.vertexAttribDivisor(pr.locations['a_stroke_id'], 1); gl.vertexAttribDivisor(pr.locations['a_pressure'], 1); - for (let b = 0; b < batches.length - 1; ++b) { - const batch = batches[b]; - const batch_from = batches[b].index; - const batch_size = batches[b + 1].index - batch_from; - const level = lod_levels[batch.lod]; + for (let b = 0; b < context.instance_data_batches.size - 2; b += 2) { + const batch_from = context.instance_data_batches.data[b + 0]; + const batch_size = context.instance_data_batches.data[b + 2] - batch_from; + const level = lod_levels[context.instance_data_batches.data[b + 1]]; if (batch_size > 0) { stat_total_vertices += batch_size * level.data.indices.size; @@ -593,7 +579,6 @@ async function draw(state, context, animate, ts) { Strokes onscreen: ${context.clipped_indices.size} Segments onscreen: ${segment_count} Total vertices: ${stat_total_vertices} - Circle LOD: ${circle_lod} Canvas offset: (${Math.round(state.canvas.offset.x * 100) / 100}, ${Math.round(state.canvas.offset.y * 100) / 100}) Canvas zoom level: ${state.canvas.zoom_level} Canvas zoom: ${Math.round(state.canvas.zoom * 100) / 100}`; diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index b770e37..27085ea 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -58,6 +58,9 @@ function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = fa ser_u16(context.stroke_data, b); ser_u16(context.stroke_data, stroke.width); + tv_add(state.wasm.buffers['width'].tv, stroke.width); + state.wasm.buffers['width'].used += 4; + if (!skip_bvh) bvh_add_stroke(state, state.bvh, stroke_index, stroke); }