Browse Source

Support different LOD level per batch of instances. Mostly OpenGL

busywork
sdf
A.Olokhtonov 3 weeks ago
parent
commit
55f390e55b
  1. 5
      client/config.js
  2. 26
      client/wasm/lod.c
  3. 77
      client/webgl_draw.js
  4. 2
      client/webgl_shaders.js

5
client/config.js

@ -33,5 +33,10 @@ const config = {
offset: { x: 654, y: 372 }, offset: { x: 654, y: 372 },
frames: 500, frames: 500,
}, },
/*
* points of interest (desk/zoomlevel/x/y
* 1/32/-2075/1020
*/
}; };

26
client/wasm/lod.c

@ -83,23 +83,25 @@ rdp_find_max(float *xs, float *ys, unsigned char *pressures, float zoom, int coo
float dir_ny = -dx / dist_ab * 255.0f; float dir_ny = -dx / dist_ab * 255.0f;
#if 0 #if 0
for (int i = segment_start + 1; i < segment_end; ++i) { // Scalar version preserved for reference
float px = xs[coords_from + i];
float py = ys[coords_from + i];
unsigned char pp = pressures[coords_from + i]; for (int i = segment_start + 1; i < segment_end; ++i) {
float px = xs[coords_from + i];
float py = ys[coords_from + i];
float apx = px - ax; unsigned char pp = pressures[coords_from + i];
float apy = py - ay;
float dist = __builtin_fabsf(apx * dir_nx + apy * dir_ny) float apx = px - ax;
+ __builtin_abs(pp - ap) + __builtin_abs(pp - bp); float apy = py - ay;
if (dist > EPS && dist > max_dist) { float dist = __builtin_fabsf(apx * dir_nx + apy * dir_ny)
result = i; + __builtin_abs(pp - ap) + __builtin_abs(pp - bp);
max_dist = dist;
} if (dist > EPS && dist > max_dist) {
result = i;
max_dist = dist;
} }
}
#else #else
v128_t ax_x4 = wasm_f32x4_splat(ax); v128_t ax_x4 = wasm_f32x4_splat(ax);
v128_t ay_x4 = wasm_f32x4_splat(ay); v128_t ay_x4 = wasm_f32x4_splat(ay);

77
client/webgl_draw.js

@ -275,7 +275,21 @@ async function draw(state, context, animate, ts) {
// TODO: what do we do with this // TODO: what do we do with this
const circle_lod = Math.round(Math.min(7, 3 * Math.sqrt(state.canvas.zoom))); const circle_lod = Math.round(Math.min(7, 3 * Math.sqrt(state.canvas.zoom)));
const circle_data = geometry_good_circle_and_dummy(circle_lod); 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({
'data': d,
'vertices_offset': total_lod_floats * 4,
'indices_offset': total_lod_indices * 4,
});
total_lod_floats += d.points.size;
total_lod_indices += d.indices.size;
}
// "Static" data upload // "Static" data upload
if (segment_count > 0) { if (segment_count > 0) {
@ -285,9 +299,16 @@ async function draw(state, context, animate, ts) {
const batches = []; const batches = [];
for (let i = 0; i < nbatches; ++i) { for (let i = 0; i < nbatches; ++i) {
batches.push(Math.floor(segment_count / 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 - 4);
}
} }
batches.push(segment_count); batches.push({'index': segment_count, 'lod': -1}); // lod unused
gl.clear(gl.DEPTH_BUFFER_BIT); // draw strokes above the images gl.clear(gl.DEPTH_BUFFER_BIT); // draw strokes above the images
gl.useProgram(pr.program); gl.useProgram(pr.program);
@ -295,17 +316,29 @@ async function draw(state, context, animate, ts) {
const total_static_size = context.instance_data_points.size * 4 + const total_static_size = context.instance_data_points.size * 4 +
context.instance_data_ids.size * 4 + context.instance_data_ids.size * 4 +
round_to_pow2(context.instance_data_pressures.size, 4) + round_to_pow2(context.instance_data_pressures.size, 4) +
circle_data.points.size * 4; total_lod_floats * 4;
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_strokes_static']); gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_strokes_static']);
gl.bufferData(gl.ARRAY_BUFFER, total_static_size, gl.STREAM_DRAW); gl.bufferData(gl.ARRAY_BUFFER, total_static_size, gl.STREAM_DRAW);
// Segment points, segment stroke ids, segment pressures
gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.instance_data_points)); gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.instance_data_points));
gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4, tv_data(context.instance_data_ids)); gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4, tv_data(context.instance_data_ids));
gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4, gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4,
tv_data(context.instance_data_pressures)); tv_data(context.instance_data_pressures));
gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + round_to_pow2(context.instance_data_pressures.size, 4), tv_data(circle_data.points));
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['i_strokes_static']); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['i_strokes_static']);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, tv_data(circle_data.indices), gl.STREAM_DRAW); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, total_lod_indices * 4, gl.STREAM_DRAW);
// Upload all variants of LOD vertices/indices
const base_lod_points_offset = context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + round_to_pow2(context.instance_data_pressures.size, 4);
for (const level of lod_levels) {
gl.bufferSubData(gl.ARRAY_BUFFER, base_lod_points_offset + level.vertices_offset, tv_data(level.data.points));
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, level.indices_offset, tv_data(level.data.indices));
}
// Per-stroke data (base width, color)
gl.bindTexture(gl.TEXTURE_2D, textures['stroke_data']); gl.bindTexture(gl.TEXTURE_2D, textures['stroke_data']);
upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size); upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size);
@ -316,7 +349,6 @@ async function draw(state, context, animate, ts) {
gl.uniform1i(pr.locations['u_debug_mode'], state.debug.red); gl.uniform1i(pr.locations['u_debug_mode'], state.debug.red);
gl.uniform1i(pr.locations['u_stroke_data'], 0); gl.uniform1i(pr.locations['u_stroke_data'], 0);
gl.uniform1i(pr.locations['u_stroke_texture_size'], config.stroke_texture_size); gl.uniform1i(pr.locations['u_stroke_texture_size'], config.stroke_texture_size);
gl.uniform1i(pr.locations['u_circle_points'], circle_data.points.size / 2 - 4);
gl.enableVertexAttribArray(pr.locations['a_pos']); gl.enableVertexAttribArray(pr.locations['a_pos']);
gl.enableVertexAttribArray(pr.locations['a_a']); gl.enableVertexAttribArray(pr.locations['a_a']);
@ -324,9 +356,6 @@ async function draw(state, context, animate, ts) {
gl.enableVertexAttribArray(pr.locations['a_stroke_id']); gl.enableVertexAttribArray(pr.locations['a_stroke_id']);
gl.enableVertexAttribArray(pr.locations['a_pressure']); gl.enableVertexAttribArray(pr.locations['a_pressure']);
// Circle meshes (shared for all instances)
gl.vertexAttribPointer(pr.locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + round_to_pow2(context.instance_data_pressures.size, 4));
gl.vertexAttribDivisor(pr.locations['a_pos'], 0); gl.vertexAttribDivisor(pr.locations['a_pos'], 0);
gl.vertexAttribDivisor(pr.locations['a_a'], 1); gl.vertexAttribDivisor(pr.locations['a_a'], 1);
@ -335,16 +364,26 @@ async function draw(state, context, animate, ts) {
gl.vertexAttribDivisor(pr.locations['a_pressure'], 1); gl.vertexAttribDivisor(pr.locations['a_pressure'], 1);
for (let b = 0; b < batches.length - 1; ++b) { for (let b = 0; b < batches.length - 1; ++b) {
const batch_from = batches[b]; const batch = batches[b];
const batch_size = batches[b + 1] - batch_from; const batch_from = batches[b].index;
const batch_size = batches[b + 1].index - batch_from;
const level = lod_levels[batch.lod];
if (batch_size > 0) {
stat_total_vertices += batch_size * level.data.indices.size;
// Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values) gl.uniform1i(pr.locations['u_circle_points'], level.data.points.size / 2 - 4);
gl.vertexAttribPointer(pr.locations['a_a'], 2, gl.FLOAT, false, 2 * 4, batch_from * 2 * 4);
gl.vertexAttribPointer(pr.locations['a_b'], 2, gl.FLOAT, false, 2 * 4, batch_from * 2 * 4 + 2 * 4);
gl.vertexAttribIPointer(pr.locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4 + batch_from * 4);
gl.vertexAttribPointer(pr.locations['a_pressure'], 2, gl.UNSIGNED_BYTE, true, 1, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + batch_from);
gl.drawElementsInstanced(gl.TRIANGLES, circle_data.indices.size, gl.UNSIGNED_INT, 0, batch_size); // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
gl.vertexAttribPointer(pr.locations['a_a'], 2, gl.FLOAT, false, 2 * 4, batch_from * 2 * 4);
gl.vertexAttribPointer(pr.locations['a_b'], 2, gl.FLOAT, false, 2 * 4, batch_from * 2 * 4 + 2 * 4);
gl.vertexAttribIPointer(pr.locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4 + batch_from * 4);
gl.vertexAttribPointer(pr.locations['a_pressure'], 2, gl.UNSIGNED_BYTE, true, 1, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + batch_from);
gl.vertexAttribPointer(pr.locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, base_lod_points_offset + level.vertices_offset, 4);
gl.drawElementsInstanced(gl.TRIANGLES, level.data.indices.size, gl.UNSIGNED_INT, level.indices_offset, batch_size);
}
} }
// I don't really know why I need to do this, but it // I don't really know why I need to do this, but it
@ -553,7 +592,7 @@ async function draw(state, context, animate, ts) {
document.getElementById('debug-stats').innerHTML = ` document.getElementById('debug-stats').innerHTML = `
<span>Strokes onscreen: ${context.clipped_indices.size}</span> <span>Strokes onscreen: ${context.clipped_indices.size}</span>
<span>Segments onscreen: ${segment_count}</span> <span>Segments onscreen: ${segment_count}</span>
<span>Total vertices: ${segment_count * circle_data.indices.size}</span> <span>Total vertices: ${stat_total_vertices}</span>
<span>Circle LOD: ${circle_lod}</span> <span>Circle LOD: ${circle_lod}</span>
<span>Canvas offset: (${Math.round(state.canvas.offset.x * 100) / 100}, ${Math.round(state.canvas.offset.y * 100) / 100})</span> <span>Canvas offset: (${Math.round(state.canvas.offset.x * 100) / 100}, ${Math.round(state.canvas.offset.y * 100) / 100})</span>
<span>Canvas zoom level: ${state.canvas.zoom_level}</span> <span>Canvas zoom level: ${state.canvas.zoom_level}</span>

2
client/webgl_shaders.js

@ -322,10 +322,8 @@ function init_webgl(state, context) {
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
/*
gl.enable(gl.DEPTH_TEST); gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.NOTEQUAL); gl.depthFunc(gl.NOTEQUAL);
*/
context.gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query_webgl2'); context.gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (context.gpu_timer_ext === null) { if (context.gpu_timer_ext === null) {

Loading…
Cancel
Save