Browse Source

Don't repeat points segment points. Introduce "tv" (typedvector)

ssao
A.Olokhtonov 10 months ago
parent
commit
847fb70381
  1. 1
      .gitignore
  2. 55
      README.md
  3. 43
      client/aux.js
  4. 3
      client/index.html
  5. 36
      client/index.js
  6. 25
      client/math.js
  7. 131
      client/webgl_draw.js
  8. 170
      client/webgl_geometry.js
  9. 40
      client/webgl_listeners.js
  10. 25
      client/webgl_shaders.js

1
.gitignore vendored

@ -2,3 +2,4 @@ server/images @@ -2,3 +2,4 @@ server/images
doca.txt
data/
client/*.dot
server/points.txt

55
README.md

@ -1,11 +1,48 @@ @@ -1,11 +1,48 @@
test
Release:
* Engine
+ 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)
- Textured quads (pictures, code already written in older version)
- Resize and move pictures (draw handles)
- Z-prepass fringe bug (also, when do we enable the prepass?)
- Restore ability to limit event range
- Only upload stroke data to texture as it arrives (texSubImage2D)
- Listeners/events
- Investigate skipped inputs on mobile (panning, zooming)
- Save events to indexeddb (as some kind of a blob), restore on reconnect and page reload
- Separate events and other data clearly (events are self-contained, other data is temporal/non-vital)
- Do NOT use session id as player id LUL
- Local prediction for tools!
- Drag with mouse button 3
- Missing features I do not consider bonus
- Eraser
- Line drawing
- Player screens/pointers
- Follow player (like Ligma)
- Color picker (or at the very least an Open Color color pallete)
- Undo/redo
- Dynamic svg cursor to represent the brush
- Polish
- Show what's happening while the desk is loading (downloading, processing, uploading to gpu)
- Settings panel (including the setting for "offline mode")
- Presentation / "marketing"
- Title
- Icon
- Product page (github readme, demo videos)
# test
Bonus:
- Handle pressure
- Add pressure data to quads
- Draw capsules instead of segments
- Adjust curve simplification to include pressure info
- Curve modification
- Select curves (with a lasso?)
- Move whole curve
- Move single point
- Move multiple points
- Customizable background
- Color, textures, procedural
a
- b
- c
*d*
**e**
Bonus-bonus:
- Actually infinite canvas (replace floats with something, some kind of fixed point scheme? chunks? multilevel scheme?)

43
client/aux.js

@ -101,4 +101,45 @@ function find_image(state, image_id) { @@ -101,4 +101,45 @@ function find_image(state, image_id) {
return event;
}
}
}
}
// TODO: move these to a file? TypedVector
function tv_create(class_name, capacity) {
return {
'class_name': class_name,
'data': new class_name(capacity),
'capacity': capacity,
'size': 0,
};
}
function tv_data(tv) {
return tv.data.subarray(0, tv.size);
}
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;
}
return tv;
}
function tv_ensure_by(tv, by) {
return tv_ensure(tv, tv.capacity + by);
}
function tv_add(tv, item) {
tv.data[tv.size++] = item;
}
function tv_clear(tv) {
tv.size = 0;
}

3
client/index.html

@ -41,7 +41,6 @@ @@ -41,7 +41,6 @@
<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-draw-bvh">Draw BVH</label>
<div class="flexcol">
<label><input type="checkbox" id="debug-limit-from">Limit events from</label>
@ -52,6 +51,8 @@ @@ -52,6 +51,8 @@
<label><input type="checkbox" id="debug-limit-to">Limit events to</label>
<input type="range" min="0" max="0" value="0" id="debug-render-to">
</div>
<button id="debug-begin-benchmark" title="Do not forget to enable recording in your browser!">Benchmark</button>
</div>
<div class="sizer-wrapper">

36
client/index.js

@ -1,9 +1,3 @@ @@ -1,9 +1,3 @@
// NEXT: pan with m3, place dot, cursor size and color, YELLOW and gray, show user cursor, background styles,
// view desks, undo, eraser, ruler, images (movable), quadtree for clipping, f5 without redownload, progress bar
//
// use returning ID on insert intead of (COLLIDING!) rand_32
// look into using u64 for point coordinates? fix bad drawings on zoomout
document.addEventListener('DOMContentLoaded', main);
const config = {
@ -24,14 +18,16 @@ const config = { @@ -24,14 +18,16 @@ const config = {
initial_offline_timeout: 1000,
default_color: 0x00,
default_width: 8,
bytes_per_instance: 4 * 4 + 4, // axy, bxy, stroke_id
bytes_per_stroke: 3 + 1, // r, g, b, width
bytes_per_instance: 4 * 2 + 4, // axy, stroke_id
bytes_per_stroke: 2 * 3 + 2, // r, g, b, width
initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096,
tile_size: 16,
clip_zoom_threshold: 0.00003,
stroke_texture_size: 1024,
rdp_cache_threshold: 100,
benchmark: {
zoom: 0.035,
offset: { x: 900, y: 400 },
frames: 500,
},
};
const EVENT = Object.freeze({
@ -178,6 +174,18 @@ function main() { @@ -178,6 +174,18 @@ function main() {
'count': 0,
},
'segments_from': {
'data': null,
'count': 0,
'cap': 0,
},
'segments': {
'data': null,
'count': 0,
'cap': 0,
},
'bvh': {
'nodes': [],
'root': null,
@ -209,7 +217,6 @@ function main() { @@ -209,7 +217,6 @@ function main() {
'limit_to': false,
'render_from': 0,
'render_to': 0,
'draw_bvh': false,
},
'rdp_cache': {},
@ -236,8 +243,9 @@ function main() { @@ -236,8 +243,9 @@ function main() {
'cap': 0,
},
'instance_data': serializer_create(config.initial_static_bytes),
'instance_data_points': tv_create(Float32Array, 4096),
'instance_data_ids': tv_create(Uint32Array, 4096),
'lods': [],
'stroke_data': serializer_create(config.initial_static_bytes),

25
client/math.js

@ -9,18 +9,10 @@ function screen_to_canvas(state, p) { @@ -9,18 +9,10 @@ function screen_to_canvas(state, p) {
return {'x': xc, 'y': yc};
}
/*
function rdp_find_max(state, zoom, stroke, start, end) {
// Finds a point from the range [start, end) with the maximum distance from the line (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;
let cache_key = null;
if (end - start > config.rdp_cache_threshold) {
cache_key = stroke.index + '-' + zoom + '-' + start + '-' + end;
if (cache_key in state.rdp_cache) {
return state.rdp_cache[cache_key];
}
}
let result = -1;
let max_dist = 0;
@ -54,14 +46,10 @@ function rdp_find_max(state, zoom, stroke, start, end) { @@ -54,14 +46,10 @@ function rdp_find_max(state, zoom, stroke, start, end) {
state.stats.rdp_max_count++;
state.stats.rdp_segments += end - start - 1;
if (end - start > config.rdp_cache_threshold) {
state.rdp_cache[cache_key] = result;
}
return result;
}
*/
function process_rdp_indices_r(state, zoom, mask, stroke, start, end) {
// Looks like the recursive implementation spends most of its time in the function call overhead
// Let's try to use an explicit stack instead to give the js engine more room to play with
@ -127,14 +115,13 @@ function process_stroke(state, zoom, stroke) { @@ -127,14 +115,13 @@ function process_stroke(state, zoom, stroke) {
return 2;
}
// const result0 = process_ewmv(points);
const result1 = process_rdp_indices(state, zoom, stroke, true);
const npoints = process_rdp_indices(state, zoom, stroke, true);
if (result1 === 2 && zoom > stroke.turns_into_straight_line_zoom) {
if (npoints === 2 && zoom > stroke.turns_into_straight_line_zoom) {
stroke.turns_into_straight_line_zoom = zoom;
}
return result1;
return npoints;
}
function strokes_intersect_line(state, a, b) {

131
client/webgl_draw.js

@ -66,19 +66,18 @@ function draw(state, context) { @@ -66,19 +66,18 @@ function draw(state, context) {
gl.clearDepth(0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
locations = context.locations['sdf'].main;
buffers = context.buffers['sdf'];
gl.useProgram(context.programs['sdf'].main);
bvh_clip(state, context);
const segment_count = geometry_write_instances(state, context);
// TODO: maybe have a pool of buffers (pow2?) and select an appropriate one
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(context.instance_data.buffer, 0, segment_count * config.bytes_per_instance), gl.DYNAMIC_DRAW);
gl.bufferData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4, gl.STREAM_DRAW);
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.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
@ -88,13 +87,17 @@ function draw(state, context) { @@ -88,13 +87,17 @@ function draw(state, context) {
gl.uniform1i(locations['u_stroke_data'], 0);
gl.uniform1i(locations['u_stroke_texture_size'], config.stroke_texture_size);
gl.enableVertexAttribArray(locations['a_ab']);
gl.enableVertexAttribArray(locations['a_a']);
gl.enableVertexAttribArray(locations['a_b']);
gl.enableVertexAttribArray(locations['a_stroke_id']);
// Points (a, b) and stroke ids are not stored in separate cpu buffers so that points can be resued
gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4);
gl.vertexAttribPointer(locations['a_ab'], 4, gl.FLOAT, false, config.bytes_per_instance, 0);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_instance, 4 * 4);
gl.vertexAttribDivisor(locations['a_ab'], 1);
gl.vertexAttribDivisor(locations['a_a'], 1);
gl.vertexAttribDivisor(locations['a_b'], 1);
gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
@ -109,69 +112,6 @@ function draw(state, context) { @@ -109,69 +112,6 @@ function draw(state, context) {
<span>Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y})</span>
<span>Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000}</span>`;
/*
const after_clip = performance.now();
gl.bindBuffer(gl.ARRAY_BUFFER, lod.data_buffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, lod.index_buffer);
upload_if_needed(gl, gl.ARRAY_BUFFER, lod.vertices);
upload_if_needed(gl, gl.ELEMENT_ARRAY_BUFFER, lod.indices);
if (index_count > 0) {
// DEPTH PREPASS
if (state.debug.do_prepass) {
gl.drawBuffers([gl.NONE]);
locations = context.locations['sdf'].opaque;
gl.useProgram(context.programs['sdf'].opaque);
gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
gl.uniform1i(locations['u_stroke_count'], state.stroke_count);
gl.enableVertexAttribArray(locations['a_pos']);
gl.enableVertexAttribArray(locations['a_line']);
gl.enableVertexAttribArray(locations['a_stroke_id']);
gl.vertexAttribPointer(locations['a_pos'], 3, gl.FLOAT, false, config.bytes_per_point, 0);
gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4);
gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0);
}
// MAIN PASS
gl.drawBuffers([gl.BACK]);
locations = context.locations['sdf'].main;
gl.useProgram(context.programs['sdf'].main);
gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
gl.uniform1i(locations['u_stroke_count'], state.stroke_count);
gl.uniform1i(locations['u_debug_mode'], state.debug.red);
gl.enableVertexAttribArray(locations['a_pos']);
gl.enableVertexAttribArray(locations['a_line']);
gl.enableVertexAttribArray(locations['a_color']);
gl.enableVertexAttribArray(locations['a_stroke_id']);
gl.vertexAttribPointer(locations['a_pos'], 3, gl.FLOAT, false, config.bytes_per_point, 0);
gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3);
gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 4 * 3 + 4 * 4);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4);
//index_buffer.reverse();
gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0);
}
*/
/*
// Dynamic data (stroke previews that are currently in progress)
const dynamic_points = context.dynamic_serializer.offset / config.bytes_per_point;
@ -234,46 +174,6 @@ function draw(state, context) { @@ -234,46 +174,6 @@ function draw(state, context) {
gl.drawElements(gl.TRIANGLES, dynamic_indices.length, gl.UNSIGNED_INT, 0);
}
*/
if (state.debug.draw_bvh) {
const points = new Float32Array(state.bvh.nodes.length * 6 * 2);
for (let i = 0; i < state.bvh.nodes.length; ++i) {
const box = state.bvh.nodes[i].bbox;
points[i * 12 + 0] = box.x1;
points[i * 12 + 1] = box.y1;
points[i * 12 + 2] = box.x2;
points[i * 12 + 3] = box.y1;
points[i * 12 + 4] = box.x1;
points[i * 12 + 5] = box.y2;
points[i * 12 + 6] = box.x2;
points[i * 12 + 7] = box.y2;
points[i * 12 + 8] = box.x1;
points[i * 12 + 9] = box.y2;
points[i * 12 + 10] = box.x2;
points[i * 12 + 11] = box.y1;
}
locations = context.locations['debug'];
buffers = context.buffers['debug'];
gl.useProgram(context.programs['debug']);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed']);
gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
gl.enableVertexAttribArray(locations['a_pos']);
gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 8, 0);
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);
}
if (context.gpu_timer_ext) {
gl.endQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT);
@ -308,4 +208,11 @@ function draw(state, context) { @@ -308,4 +208,11 @@ function draw(state, context) {
document.querySelector('.debug-timings .cpu').innerHTML = 'Last CPU Frametime: ' + Math.round((cpu_after - cpu_before) * 100) / 100 + 'ms';
if (state.debug.benchmark_mode) {
const redraw = state.debug.on_benchmark();
if (redraw) {
schedule_draw(state, context);
}
}
}

170
client/webgl_geometry.js

@ -36,68 +36,148 @@ function geometry_prepare_stroke(state) { @@ -36,68 +36,148 @@ function geometry_prepare_stroke(state) {
};
}
function rdp_find_max(state, zoom, stroke, 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;
let result = -1;
let max_dist = 0;
const ax = state.coordinates.data[stroke.coords_from + start * 2 + 0];
const ay = state.coordinates.data[stroke.coords_from + start * 2 + 1];
const bx = state.coordinates.data[stroke.coords_from + end * 2 + 0];
const by = state.coordinates.data[stroke.coords_from + end * 2 + 1];
const dx = bx - ax;
const dy = by - ay;
const dist_ab = Math.sqrt(dx * dx + dy * dy);
const dir_nx = dy / dist_ab;
const dir_ny = -dx / dist_ab;
for (let i = start + 1; i < end; ++i) {
const px = state.coordinates.data[stroke.coords_from + i * 2 + 0];
const py = state.coordinates.data[stroke.coords_from + i * 2 + 1];
const apx = px - ax;
const apy = py - ay;
const dist = Math.abs(apx * dir_nx + apy * dir_ny);
if (dist > EPS && dist > max_dist) {
result = i;
max_dist = dist;
}
}
state.stats.rdp_max_count++;
state.stats.rdp_segments += end - start - 1;
return result;
}
function geometry_write_instances(state, context) {
context.instance_data = ser_ensure(context.instance_data, state.coordinates.count / 2 * config.bytes_per_instance);
ser_clear(context.instance_data);
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.count / 2) {
state.segments.cap = round_to_pow2(state.coordinates.count, 4096);
state.segments.data = new Uint32Array(state.segments.cap);
}
state.segments_from.count = 0;
state.segments.count = 0;
state.stats.rdp_max_count = 0;
state.stats.rdp_segments = 0;
let segment_count = 0;
let fast_path_count = 0;
let slow_path_count = 0;
const stack = [];
for (let i = 0; i < context.clipped_indices.count; ++i) {
const stroke_index = context.clipped_indices.data[i];
const stroke = state.events[stroke_index];
const lod_indices_count = process_stroke(state, state.canvas.zoom, stroke);
segment_count += lod_indices_count - 1;
if (lod_indices_count === 2) {
fast_path_count++;
// Fast path
const ax = state.coordinates.data[stroke.coords_from + 0];
const ay = state.coordinates.data[stroke.coords_from + 1];
const bx = state.coordinates.data[stroke.coords_to - 2];
const by = state.coordinates.data[stroke.coords_to - 1];
ser_f32(context.instance_data, ax);
ser_f32(context.instance_data, ay);
ser_f32(context.instance_data, bx);
ser_f32(context.instance_data, by);
ser_u32(context.instance_data, stroke_index);
const point_count = (stroke.coords_to - stroke.coords_from) / 2;
// Basic CSR crap
state.segments_from.data[i] = state.segments.count;
if (state.canvas.zoom <= stroke.turns_into_straight_line_zoom) {
state.segments.data[state.segments.count++] = 0;
state.segments.data[state.segments.count++] = point_count - 1;
} else {
slow_path_count++;
let base_this = 0;
let base_next = 0;
for (let j = 0; j < lod_indices_count - 1; ++j) {
while (state.rdp_mask[base_this] == 0) base_this++;
base_next = base_this + 1;
while (state.rdp_mask[base_next] == 0) base_next++;
const ax = state.coordinates.data[stroke.coords_from + base_this * 2 + 0];
const ay = state.coordinates.data[stroke.coords_from + base_this * 2 + 1];
const bx = state.coordinates.data[stroke.coords_from + base_next * 2 + 0];
const by = state.coordinates.data[stroke.coords_from + base_next * 2 + 1];
ser_f32(context.instance_data, ax);
ser_f32(context.instance_data, ay);
ser_f32(context.instance_data, bx);
ser_f32(context.instance_data, by);
ser_u32(context.instance_data, stroke_index);
base_this = base_next;
let segment_count = 2;
state.segments.data[state.segments.count++] = 0;
stack.length = 0;
stack.push({'type': 0, 'start': 0, 'end': point_count - 1});
while (stack.length > 0) {
const entry = stack.pop();
if (entry.type === 1) {
state.segments.data[state.segments.count++] = entry.value;
} else {
const max = rdp_find_max(state, state.canvas.zoom, stroke, entry.start, entry.end);
if (max !== -1) {
segment_count += 1;
stack.push({'type': 0, 'start': max, 'end': entry.end});
stack.push({'type': 1, 'value': max});
stack.push({'type': 0, 'start': entry.start, 'end': max});
}
}
}
state.segments.data[state.segments.count++] = point_count - 1;
if (segment_count === 2 && state.canvas.zoom > stroke.turns_into_straight_line_zoom) {
stroke.turns_into_straight_line_zoom = state.canvas.zoom;
}
}
}
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_clear(context.instance_data_points);
tv_clear(context.instance_data_ids);
for (let i = 0; i < state.segments_from.count - 1; ++i) {
const stroke_index = context.clipped_indices.data[i];
const stroke = state.events[stroke_index];
const from = state.segments_from.data[i];
const to = state.segments_from.data[i + 1];
for (let j = from; j < to; ++j) {
const base_this = state.segments.data[j];
const ax = state.coordinates.data[stroke.coords_from + base_this * 2 + 0];
const ay = state.coordinates.data[stroke.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));
}
}
}
console.debug('fast:', fast_path_count, 'slow:', slow_path_count);
console.debug('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 segment_count;
return state.segments.count;
}
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {

40
client/webgl_listeners.js

@ -26,12 +26,6 @@ function debug_panel_init(state, context) { @@ -26,12 +26,6 @@ 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-draw-bvh').checked = state.debug.draw_bvh;
document.getElementById('debug-draw-bvh').addEventListener('change', (e) => {
state.debug.draw_bvh = e.target.checked;
schedule_draw(state, context);
});
document.getElementById('debug-red').addEventListener('change', (e) => {
state.debug.red = e.target.checked;
@ -62,6 +56,40 @@ function debug_panel_init(state, context) { @@ -62,6 +56,40 @@ function debug_panel_init(state, context) {
state.debug.render_to = parseInt(e.target.value);
schedule_draw(state, context);
});
document.getElementById('debug-begin-benchmark').addEventListener('click', (e) => {
state.canvas.zoom = config.benchmark.zoom;
state.canvas.offset.x = config.benchmark.offset.x;
state.canvas.offset.y = config.benchmark.offset.y;
state.debug.benchmark_mode = true;
const origin_x = state.canvas.offset.x;
const origin_y = state.canvas.offset.y;
const original_button_text = e.target.innerText;
let frame = 0;
state.debug.on_benchmark = () => {
if (frame >= config.benchmark.frames) {
state.debug.benchmark_mode = false;
e.target.disabled = false;
e.target.innerText = original_button_text;
return false;
}
state.canvas.offset.x = origin_x + Math.round(100 * Math.cos(frame / 360));
state.canvas.offset.y = origin_y + Math.round(100 * Math.sin(frame / 360));
frame += 1;
return true;
}
e.target.disabled = true;
e.target.innerText = 'Benchmark in progress...';
schedule_draw(state, context);
});
}
function cancel(e) {

25
client/webgl_shaders.js

@ -102,7 +102,8 @@ const nop_fs_src = `#version 300 es @@ -102,7 +102,8 @@ const nop_fs_src = `#version 300 es
`;
const sdf_vs_src = `#version 300 es
in vec4 a_ab; // original points
in vec2 a_a; // point from
in vec2 a_b; // point to
in float a_radius;
in int a_stroke_id;
@ -130,10 +131,7 @@ const sdf_vs_src = `#version 300 es @@ -130,10 +131,7 @@ const sdf_vs_src = `#version 300 es
uvec4 stroke_data = texelFetch(u_stroke_data, ivec2(stroke_data_x, stroke_data_y), 0);
float radius = float(stroke_data.w);
vec2 a = a_ab.xy;
vec2 b = a_ab.zw;
vec2 line_dir = normalize(b - a);
vec2 line_dir = normalize(a_b - a_a);
vec2 up_dir = vec2(line_dir.y, -line_dir.x);
vec2 pixel = vec2(2.0) / u_res * apron;
float rscale = apron / u_scale.x;
@ -145,19 +143,19 @@ const sdf_vs_src = `#version 300 es @@ -145,19 +143,19 @@ const sdf_vs_src = `#version 300 es
if (vertex_index == 0) {
// "top left" aka "p1"
origin = a;
origin = a_a;
outwards = up_dir - line_dir;
} else if (vertex_index == 1 || vertex_index == 5) {
// "top right" aka "p2"
origin = b;
origin = a_b;
outwards = up_dir + line_dir;
} else if (vertex_index == 2 || vertex_index == 4) {
// "bottom left" aka "p3"
origin = a;
origin = a_a;
outwards = -up_dir - line_dir;
} else {
// "bottom right" aka "p4"
origin = b;
origin = a_b;
outwards = -up_dir + line_dir;
}
@ -167,10 +165,14 @@ const sdf_vs_src = `#version 300 es @@ -167,10 +165,14 @@ const sdf_vs_src = `#version 300 es
screen02.y = 2.0 - screen02.y;
v_line = vec4(a, b);
v_line = vec4(a_a, a_b);
v_thickness = radius;
v_color = vec3(stroke_data.xyz) / 255.0;
if (a_stroke_id >> 31 != 0) {
screen02 += vec2(100.0); // shift offscreen
}
gl_Position = vec4(screen02 - 1.0, (float(a_stroke_id) / float(u_stroke_count)) * 2.0 - 1.0, 1);
}
`;
@ -307,7 +309,8 @@ function init_webgl(state, context) { @@ -307,7 +309,8 @@ function init_webgl(state, context) {
},
'main': {
'a_ab': gl.getAttribLocation(context.programs['sdf'].main, 'a_ab'),
'a_a': gl.getAttribLocation(context.programs['sdf'].main, 'a_a'),
'a_b': gl.getAttribLocation(context.programs['sdf'].main, 'a_b'),
'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].main, 'a_stroke_id'),
'u_res': gl.getUniformLocation(context.programs['sdf'].main, 'u_res'),

Loading…
Cancel
Save