Browse Source

No clipping at zoom < 0.3. Really fast

ssao
A.Olokhtonov 12 months ago
parent
commit
1bc6f2c3fe
  1. 3
      client/client_recv.js
  2. 35
      client/default.css
  3. 45
      client/index.html
  4. 24
      client/index.js
  5. 46
      client/math.js
  6. 64
      client/webgl_draw.js
  7. 45
      client/webgl_listeners.js
  8. 21
      client/webgl_shaders.js

3
client/client_recv.js

@ -179,6 +179,9 @@ function handle_event(state, context, event) {
state.stroke_count++; state.stroke_count++;
document.getElementById('debug-render-from').max = state.stroke_count;
document.getElementById('debug-render-to').max = state.stroke_count;
break; break;
} }

35
client/default.css

@ -27,6 +27,11 @@ body.offline .main {
display: none !important; display: none !important;
} }
.flexcol {
display: flex;
flex-direction: column;
}
canvas { canvas {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -171,17 +176,17 @@ canvas.movemode.moving {
filter: invert(100%); filter: invert(100%);
} }
input[type=range] { .sizer input[type=range] {
-webkit-appearance: none; -webkit-appearance: none;
width: 200px; width: 200px;
background: transparent; background: transparent;
} }
input[type=range]:focus { .sizer input[type=range]:focus {
outline: none; outline: none;
} }
input[type=range]::-webkit-slider-thumb { .sizer input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
border: none; border: none;
background: white; background: white;
@ -193,7 +198,7 @@ input[type=range]::-webkit-slider-thumb {
margin-top: -6px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ margin-top: -6px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */
} }
input[type=range]::-moz-range-thumb { .sizer input[type=range]::-moz-range-thumb {
border: none; border: none;
background: white; background: white;
height: 16px; height: 16px;
@ -203,7 +208,7 @@ input[type=range]::-moz-range-thumb {
border: 2px solid var(--dark-blue); border: 2px solid var(--dark-blue);
} }
input[type=range]::-webkit-slider-runnable-track { .sizer input[type=range]::-webkit-slider-runnable-track {
width: 100%; width: 100%;
height: 8px; height: 8px;
cursor: pointer; cursor: pointer;
@ -212,7 +217,7 @@ input[type=range]::-webkit-slider-runnable-track {
border: none; border: none;
} }
input[type=range]:focus::-webkit-slider-runnable-track { .sizer input[type=range]:focus::-webkit-slider-runnable-track {
width: 100%; width: 100%;
height: 8px; height: 8px;
cursor: pointer; cursor: pointer;
@ -221,7 +226,7 @@ input[type=range]:focus::-webkit-slider-runnable-track {
border: none; border: none;
} }
input[type=range]::-moz-range-track { .sizer input[type=range]::-moz-range-track {
width: 100%; width: 100%;
height: 8px; height: 8px;
cursor: pointer; cursor: pointer;
@ -311,4 +316,18 @@ body.offline * {
.loader.hidden { .loader.hidden {
opacity: 0; opacity: 0;
} }
.debug-window {
position: absolute;
min-width: 256px;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
user-select: none;
padding: 5px;
background: white;
border: 1px solid var(--dark-blue);
}

45
client/index.html

@ -7,20 +7,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon"> <link rel="shortcut icon" href="icons/favicon.svg" id="favicon">
<link rel="stylesheet" type="text/css" href="default.css?v=65"> <link rel="stylesheet" type="text/css" href="default.css?v=66">
<script type="text/javascript" src="aux.js?v=65"></script> <script type="text/javascript" src="aux.js?v=66"></script>
<script type="text/javascript" src="math.js?v=65"></script> <script type="text/javascript" src="math.js?v=66"></script>
<script type="text/javascript" src="tools.js?v=65"></script> <script type="text/javascript" src="tools.js?v=66"></script>
<script type="text/javascript" src="webgl_geometry.js?v=65"></script> <script type="text/javascript" src="webgl_geometry.js?v=66"></script>
<script type="text/javascript" src="webgl_shaders.js?v=65"></script> <script type="text/javascript" src="webgl_shaders.js?v=66"></script>
<script type="text/javascript" src="webgl_listeners.js?v=65"></script> <script type="text/javascript" src="webgl_listeners.js?v=66"></script>
<script type="text/javascript" src="webgl_draw.js?v=65"></script> <script type="text/javascript" src="webgl_draw.js?v=66"></script>
<script type="text/javascript" src="index.js?v=65"></script> <script type="text/javascript" src="index.js?v=66"></script>
<script type="text/javascript" src="client_send.js?v=65"></script> <script type="text/javascript" src="client_send.js?v=66"></script>
<script type="text/javascript" src="client_recv.js?v=65"></script> <script type="text/javascript" src="client_recv.js?v=66"></script>
<script type="text/javascript" src="websocket.js?v=65"></script> <script type="text/javascript" src="websocket.js?v=66"></script>
</head> </head>
<body> <body>
<div class="main"> <div class="main">
@ -30,6 +30,23 @@
<polyline points="150,150 225,230 280,120" / fill="none" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"> <polyline points="150,150 225,230 280,120" / fill="none" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="5">
</svg> --> </svg> -->
<div class="debug-window dhide">
<div id="debug-stats" class="flexcol"></div>
<div id="debug-timings" class="flexcol"></div>
<label><input type="checkbox" id="debug-red">Simple shader</label>
<label><input type="checkbox" id="debug-do-prepass">Depth prepass</label>
<div class="flexcol">
<label><input type="checkbox" id="debug-limit-from">Limit events from</label>
<input type="range" min="0" max="0" value="0" id="debug-render-from">
</div>
<div class="flexcol">
<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>
</div>
<div class="sizer-wrapper"> <div class="sizer-wrapper">
<div class="sizer"> <div class="sizer">

24
client/index.js

@ -3,9 +3,12 @@
document.addEventListener('DOMContentLoaded', main); document.addEventListener('DOMContentLoaded', main);
const config = { const config = {
ws_url: 'wss://desk.some.website/ws/', // ws_url: 'wss://desk.some.website/ws/',
ping_url: 'https://desk.some.website/api/ping', // ping_url: 'https://desk.some.website/api/ping',
image_url: 'https://desk.some.website/images/', // image_url: 'https://desk.some.website/images/',
ws_url: 'wss://192.168.100.2/ws/',
ping_url: 'https://192.168.100.2/api/ping',
image_url: 'https://192.168.100.2/images/',
sync_timeout: 1000, sync_timeout: 1000,
ws_reconnect_timeout: 2000, ws_reconnect_timeout: 2000,
brush_preview_timeout: 1000, brush_preview_timeout: 1000,
@ -22,6 +25,7 @@ const config = {
initial_dynamic_bytes: 4096, initial_dynamic_bytes: 4096,
frametime_window_size: 100, frametime_window_size: 100,
tile_size: 16, tile_size: 16,
clip_zoom_threshold: 0.3,
}; };
const EVENT = Object.freeze({ const EVENT = Object.freeze({
@ -176,13 +180,20 @@ function main() {
'players': {}, 'players': {},
'onscreen_segments': null, 'onscreen_segments': null,
'debug': {
'red': false,
'do_prepass': true,
'limit_from': false,
'limit_to': false,
'render_from': 0,
'render_to': 0,
}
}; };
const context = { const context = {
'canvas': null, 'canvas': null,
'gl': null, 'gl': null,
'debug_mode': false,
'do_prepass': true,
'frametime_window': [], 'frametime_window': [],
'frametime_window_head': 0, 'frametime_window_head': 0,
@ -190,6 +201,9 @@ function main() {
'need_static_allocate': true, 'need_static_allocate': true,
'need_static_upload': true, 'need_static_upload': true,
'need_dynamic_upload': false, 'need_dynamic_upload': false,
'need_index_upload': true,
'full_index_count': 0,
'programs': {}, 'programs': {},
'buffers': {}, 'buffers': {},

46
client/math.js

@ -215,7 +215,7 @@ function quad_fully_onscreen(screen, bbox) {
return false; return false;
} }
function segments_onscreen(state, context) { function segments_onscreen(state, context, do_clip) {
// TODO: handle stroke width // TODO: handle stroke width
if (state.onscreen_segments === null) { if (state.onscreen_segments === null) {
@ -251,30 +251,36 @@ function segments_onscreen(state, context) {
let head = 0; let head = 0;
for (let i = 0; i < state.events.length; ++i) { for (let i = 0; i < state.events.length; ++i) {
if (state.debug.limit_to && i >= state.debug.render_to) break;
const event = state.events[i]; const event = state.events[i];
if (event.type === EVENT.STROKE && !event.deleted) {
if (quad_onscreen(screen, event.bbox)) { if (!(state.debug.limit_from && i < state.debug.render_from)) {
const fully_onscreen = quad_fully_onscreen(screen, event.bbox); if (event.type === EVENT.STROKE && !event.deleted) {
for (let j = 0; j < event.points.length - 1; ++j) { if (!do_clip || quad_onscreen(screen, event.bbox)) {
const a = event.points[j + 0]; const fully_onscreen = !do_clip || quad_fully_onscreen(screen, event.bbox);
const b = event.points[j + 1]; for (let j = 0; j < event.points.length - 1; ++j) {
const a = event.points[j + 0];
if (fully_onscreen || segment_interesects_quad(a, b, screen_topleft, screen_bottomright, screen_topright, screen_bottomleft)) { const b = event.points[j + 1];
let base = head + j * 4;
// We draw quads as [1, 2, 3, 4, 3, 2] if (fully_onscreen || segment_interesects_quad(a, b, screen_topleft, screen_bottomright, screen_topright, screen_bottomleft)) {
state.onscreen_segments[at + 0] = base + 0; let base = head + j * 4;
state.onscreen_segments[at + 1] = base + 1; // We draw quads as [1, 2, 3, 4, 3, 2]
state.onscreen_segments[at + 2] = base + 2; state.onscreen_segments[at + 0] = base + 0;
state.onscreen_segments[at + 3] = base + 3; state.onscreen_segments[at + 1] = base + 1;
state.onscreen_segments[at + 4] = base + 2; state.onscreen_segments[at + 2] = base + 2;
state.onscreen_segments[at + 5] = base + 1; state.onscreen_segments[at + 3] = base + 3;
state.onscreen_segments[at + 4] = base + 2;
at += 6; state.onscreen_segments[at + 5] = base + 1;
at += 6;
}
} }
} }
} }
head += (event.points.length - 1) * 4;
} }
head += (event.points.length - 1) * 4;
} }
return at; return at;

64
client/webgl_draw.js

@ -1,10 +1,6 @@
function schedule_draw(state, context) { function schedule_draw(state, context) {
if (!state.timers.raf) { if (!state.timers.raf) {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
context._DRAW_TO_TEXTURE = true;
draw(state, context);
context._DRAW_TO_TEXTURE = false;
draw(state, context) draw(state, context)
}); });
state.timers.raf = true; state.timers.raf = true;
@ -58,11 +54,29 @@ function draw(state, context) {
gl.clearDepth(0.0); gl.clearDepth(0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const before_clip = performance.now(); let index_count;
const index_count = segments_onscreen(state, context); const do_clip = (state.canvas.zoom > config.clip_zoom_threshold);
const after_clip = performance.now();
if (do_clip) {
context.need_index_upload = true;
}
if (do_clip || context.need_index_upload) {
const before_clip = performance.now();
index_count = segments_onscreen(state, context, do_clip);
const after_clip = performance.now();
}
if (!do_clip && !context.need_index_upload) {
index_count = context.full_index_count;
}
//console.debug('clip', after_clip - before_clip); //console.debug('clip', after_clip - before_clip);
document.getElementById('debug-stats').innerHTML = `
<span>Segments onscreen: ${index_count}</span>
<span>Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y})</span>
<span>Canvas zoom: ${Math.round(state.canvas.zoom * 100) / 100}</span>`;
if (index_count > 0) { if (index_count > 0) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['b_packed_static_index']); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['b_packed_static_index']);
@ -70,12 +84,18 @@ function draw(state, context) {
const static_points = context.static_serializer.offset / config.bytes_per_point; const static_points = context.static_serializer.offset / config.bytes_per_point;
//const dynamic_points = context.dynamic_serializer.offset / config.bytes_per_point; //const dynamic_points = context.dynamic_serializer.offset / config.bytes_per_point;
if (!do_clip) {
// Almost everything on screen anyways. Only upload indices once
if (context.need_index_upload) {
context.full_index_count = index_count;
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index_buffer, gl.STATIC_DRAW);
context.need_index_upload = false;
}
}
if (static_points > 0) { if (static_points > 0) {
// DEPTH PREPASS // DEPTH PREPASS
if (state.debug.do_prepass && do_clip) {
index_buffer.reverse();
if (context.do_prepass) {
gl.drawBuffers([gl.NONE]); gl.drawBuffers([gl.NONE]);
locations = context.locations['sdf'].opaque; locations = context.locations['sdf'].opaque;
@ -95,11 +115,14 @@ function draw(state, context) {
gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3); 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.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index_buffer, gl.DYNAMIC_DRAW); if (do_clip) {
index_buffer.reverse();
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index_buffer, gl.DYNAMIC_DRAW);
}
gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0); gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0);
} }
// MAIN PASS // MAIN PASS
gl.drawBuffers([gl.BACK]); gl.drawBuffers([gl.BACK]);
@ -111,6 +134,7 @@ function draw(state, context) {
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); 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.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_stroke_count'], state.stroke_count);
gl.uniform1i(locations['u_debug_mode'], state.debug.red);
gl.enableVertexAttribArray(locations['a_pos']); gl.enableVertexAttribArray(locations['a_pos']);
gl.enableVertexAttribArray(locations['a_line']); gl.enableVertexAttribArray(locations['a_line']);
@ -122,8 +146,13 @@ function draw(state, context) {
gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 4 * 3 + 4 * 4); 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); gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4);
index_buffer.reverse(); if (do_clip) {
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index_buffer, gl.DYNAMIC_DRAW); if (state.debug.do_prepass) {
index_buffer.reverse();
}
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index_buffer, gl.DYNAMIC_DRAW);
}
gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0); gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0);
} }
@ -162,7 +191,8 @@ function draw(state, context) {
if (available && !disjoint) { if (available && !disjoint) {
// See how much time the rendering of the object took in nanoseconds. // See how much time the rendering of the object took in nanoseconds.
const timeElapsed = gl.getQueryParameter(query, gl.QUERY_RESULT); const timeElapsed = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.debug(timeElapsed / 1000000); //console.debug(timeElapsed / 1000000);
document.getElementById('debug-timings').innerHTML = 'Frametime: ' + Math.round(timeElapsed / 10000) / 100 + 'ms';
} }
if (available || disjoint) { if (available || disjoint) {

45
client/webgl_listeners.js

@ -17,6 +17,45 @@ function init_listeners(state, context) {
context.canvas.addEventListener('drop', (e) => on_drop(e, state, context)); context.canvas.addEventListener('drop', (e) => on_drop(e, state, context));
context.canvas.addEventListener('dragover', (e) => mousemove(e, state, context)); context.canvas.addEventListener('dragover', (e) => mousemove(e, state, context));
debug_panel_init(state, context);
}
function debug_panel_init(state, context) {
document.getElementById('debug-red').checked = state.debug.red;
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-red').addEventListener('click', (e) => {
state.debug.red = e.target.checked;
schedule_draw(state, context);
});
document.getElementById('debug-do-prepass').addEventListener('click', (e) => {
state.debug.do_prepass = e.target.checked;
schedule_draw(state, context);
});
document.getElementById('debug-limit-from').addEventListener('click', (e) => {
state.debug.limit_from = e.target.checked;
schedule_draw(state, context);
});
document.getElementById('debug-limit-to').addEventListener('click', (e) => {
state.debug.limit_to = e.target.checked;
schedule_draw(state, context);
});
document.getElementById('debug-render-from').addEventListener('input', (e) => {
state.debug.render_from = parseInt(e.target.value);
schedule_draw(state, context);
});
document.getElementById('debug-render-to').addEventListener('input', (e) => {
state.debug.render_to = parseInt(e.target.value);
schedule_draw(state, context);
});
} }
function cancel(e) { function cancel(e) {
@ -69,11 +108,7 @@ function keydown(e, state, context) {
} }
} }
} else if (e.code === 'KeyD') { } else if (e.code === 'KeyD') {
context.debug_mode = !context.debug_mode; document.querySelector('.debug-window').classList.toggle('dhide');
schedule_draw(state, context);
} else if (e.code === 'KeyP') {
context.do_prepass = !context.do_prepass;
schedule_draw(state, context);
} }
} }

21
client/webgl_shaders.js

@ -122,20 +122,20 @@ const sdf_fs_src = `#version 300 es
out vec4 FragColor; out vec4 FragColor;
void main() { void main() {
vec2 a = v_line.xy; if (u_debug_mode == 0) {
vec2 b = v_line.zw; vec2 a = v_line.xy;
vec2 b = v_line.zw;
vec2 pa = v_texcoord - a.xy, ba = b.xy - a.xy; vec2 pa = v_texcoord - a.xy, ba = b.xy - a.xy;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
float dist = length(pa - ba * h) - v_thickness / 2.0; float dist = length(pa - ba * h) - v_thickness / 2.0;
float fade = 0.5 * length(fwidth(v_texcoord)); float fade = 0.5 * length(fwidth(v_texcoord));
float alpha = 1.0 - smoothstep(0.0, fade, dist); float alpha = 1.0 - smoothstep(-fade, fade, dist);
if (u_debug_mode == 1) {
FragColor = vec4(1.0, 0.0, 0.0, 0.1);
} else {
FragColor = vec4(v_color * alpha, alpha); FragColor = vec4(v_color * alpha, alpha);
} else {
FragColor = vec4(1.0, 0.0, 0.0, 1.0 / 32.0);
} }
} }
`; `;
@ -191,6 +191,7 @@ 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.blendFunc(gl.SRC_ALPHA, gl.DST_ALPHA);
gl.enable(gl.DEPTH_TEST); gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.GEQUAL); gl.depthFunc(gl.GEQUAL);

Loading…
Cancel
Save