Browse Source

Cull segments, switch to indexed drawing (only for static data for now)

ssao
A.Olokhtonov 1 year ago
parent
commit
46cd48fecd
  1. 6
      client/client_recv.js
  2. 17
      client/default.css
  3. 34
      client/index.html
  4. 73
      client/index.js
  5. 85
      client/math.js
  6. 14
      client/webgl_draw.js
  7. 13
      client/webgl_geometry.js
  8. 2
      client/webgl_listeners.js
  9. 18
      client/webgl_shaders.js

6
client/client_recv.js

@ -304,8 +304,10 @@ async function handle_message(state, context, d) { @@ -304,8 +304,10 @@ async function handle_message(state, context, d) {
switch (message_type) {
case MESSAGE.JOIN:
case MESSAGE.INIT: {
state.server_lsn = des_u32(d);
console.time('init');
state.online = true;
state.server_lsn = des_u32(d);
if (state.server_lsn > state.lsn) {
// Server knows something that we don't
@ -359,6 +361,8 @@ async function handle_message(state, context, d) { @@ -359,6 +361,8 @@ async function handle_message(state, context, d) {
send_ack(event_count);
sync_queue(state);
console.timeEnd('init');
break;
}

17
client/default.css

@ -295,3 +295,20 @@ input[type=range]::-moz-range-track { @@ -295,3 +295,20 @@ input[type=range]::-moz-range-track {
body.offline * {
pointer-events: none;
}
.loader {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 128px;
height: 128px;
pointer-events: none;
background: rgba(0, 0, 0, 0.8);
border-radius: 10px;
transition: opacity .2s;
}
.loader.hidden {
opacity: 0;
}

34
client/index.html

@ -7,20 +7,20 @@ @@ -7,20 +7,20 @@
<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="stylesheet" type="text/css" href="default.css?v=58">
<link rel="stylesheet" type="text/css" href="default.css?v=65">
<script type="text/javascript" src="aux.js?v=58"></script>
<script type="text/javascript" src="math.js?v=58"></script>
<script type="text/javascript" src="tools.js?v=58"></script>
<script type="text/javascript" src="webgl_geometry.js?v=58"></script>
<script type="text/javascript" src="webgl_shaders.js?v=58"></script>
<script type="text/javascript" src="webgl_listeners.js?v=58"></script>
<script type="text/javascript" src="webgl_draw.js?v=58"></script>
<script type="text/javascript" src="index.js?v=58"></script>
<script type="text/javascript" src="aux.js?v=65"></script>
<script type="text/javascript" src="math.js?v=65"></script>
<script type="text/javascript" src="tools.js?v=65"></script>
<script type="text/javascript" src="webgl_geometry.js?v=65"></script>
<script type="text/javascript" src="webgl_shaders.js?v=65"></script>
<script type="text/javascript" src="webgl_listeners.js?v=65"></script>
<script type="text/javascript" src="webgl_draw.js?v=65"></script>
<script type="text/javascript" src="index.js?v=65"></script>
<script type="text/javascript" src="client_send.js?v=58"></script>
<script type="text/javascript" src="client_recv.js?v=58"></script>
<script type="text/javascript" src="websocket.js?v=58"></script>
<script type="text/javascript" src="client_send.js?v=65"></script>
<script type="text/javascript" src="client_recv.js?v=65"></script>
<script type="text/javascript" src="websocket.js?v=65"></script>
</head>
<body>
<div class="main">
@ -69,5 +69,13 @@ @@ -69,5 +69,13 @@
<div class="offline-toast hidden">
Whiteboard offline
</div>
<div class="loader">
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<!-- <circle cx="64" cy="64" r="32" fill="none" stroke="black" opacity="0.5" stroke-width="3" stroke-linecap="round"/> -->
<path id="spinner-path" fill="none" stroke="#aaaaaa" stroke-width="3" stroke-linecap="round"/>
</svg>
</div>
</body>
</html>
</html>

73
client/index.js

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
// NEXT: pan with m3, place dot, cursor size and color, YELLOW and gray
document.addEventListener('DOMContentLoaded', main);
const config = {
@ -48,6 +50,71 @@ const MESSAGE = Object.freeze({ @@ -48,6 +50,71 @@ const MESSAGE = Object.freeze({
JOIN: 105,
});
// Source:
// https://stackoverflow.com/a/18473154
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function describeArc(x, y, radius, startAngle, endAngle) {
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var largeArcFlag = (Math.abs(endAngle - startAngle) % 360) <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");
return d;
}
let iii = 0;
let a_angel = 0;
let b_angel = 180;
let speed_a = 2;
let speed_b = 6;
let b_fast = true;
function start_spinner(state) {
const str = describeArc(64, 64, 32, a_angel, b_angel);
a_angel += speed_a;
b_angel += speed_b;
const diff = b_angel - a_angel;
if (diff > 320) {
speed_a = 6;
speed_b = 2;
} else if (diff < 40) {
speed_a = 2;
speed_b = 6;
}
// if ((speed_a === 1) && Math.abs(a_angel - b_angel) % 360 < 90) {
// speed_a = 3;
// speed_b = 1;
// } else if (Math.abs(a_angel - b_angel) % 360 > 180) {
// speed_a = 1;
// speed_b = 3;
// }
document.querySelector('#spinner-path').setAttribute('d', str);
if (!state.online) {
window.requestAnimationFrame(() => start_spinner(state));
} else {
document.querySelector('.loader').classList.add('hidden');
}
}
function main() {
const state = {
'online': false,
@ -126,13 +193,17 @@ function main() { @@ -126,13 +193,17 @@ function main() {
'textures': {},
'static_serializer': serializer_create(config.initial_static_bytes),
'static_index_serializer': serializer_create(config.initial_static_bytes),
'dynamic_serializer': serializer_create(config.initial_dynamic_bytes),
'dynamic_index_serializer': serializer_create(config.initial_dynamic_bytes),
'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0},
'active_image': null,
};
start_spinner(state);
const url = new URL(window.location.href);
const parts = url.pathname.split('/');
@ -147,4 +218,4 @@ function main() { @@ -147,4 +218,4 @@ function main() {
schedule_draw(state, context);
state.timers.offline_toast = setTimeout(() => ui_offline(), config.initial_offline_timeout);
}
}

85
client/math.js

@ -155,4 +155,87 @@ function mid_v2(a, b) { @@ -155,4 +155,87 @@ function mid_v2(a, b) {
'x': (a.x + b.x) / 2.0,
'y': (a.y + b.y) / 2.0,
};
}
}
function point_in_quad(p, quad_topleft, quad_bottomright) {
if ((quad_topleft.x <= p.x && p.x < quad_bottomright.x) && (quad_topleft.y <= p.y && p.y < quad_bottomright.y)) {
return true;
}
return false;
}
function segment_interesects_quad(a, b, quad_topleft, quad_bottomright, quad_topright, quad_bottomleft) {
if (point_in_quad(a, quad_topleft, quad_bottomright)) {
return true;
}
if (point_in_quad(b, quad_topleft, quad_bottomright)) {
return true;
}
if (segments_intersect(a, b, quad_topleft, quad_topright)) return true;
if (segments_intersect(a, b, quad_topright, quad_bottomright)) return true;
if (segments_intersect(a, b, quad_bottomright, quad_bottomleft)) return true;
if (segments_intersect(a, b, quad_bottomleft, quad_topleft)) return true;
return false;
}
function stroke_bbox(points) {
let min_x = points[0].x;
let max_x = min_x;
let min_y = points[0].y;
let max_y = min_y;
for (const p of points) {
min_x = Math.min(min_x, p.x);
min_y = Math.min(min_y, p.y);
max_x = Math.max(max_x, p.x);
max_y = Math.max(max_y, p.y);
}
return {'x1': min_x, 'y1': min_y, 'x2': max_x, 'y2': max_y};
}
function quad_onscreen(screen, bbox) {
if (screen.x1 < bbox.x2 && screen.x2 > bbox.x1 && screen.y2 > bbox.y1 && screen.y1 < bbox.y2) {
return true;
}
return false;
}
function segments_onscreen(state, context) {
const result = [];
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_topright = { 'x': screen_bottomright.x, 'y': screen_topleft.y };
const screen_bottomleft = { 'x': screen_topleft.x, 'y': screen_bottomright.y };
const screen = {'x1': screen_topleft.x, 'y1': screen_topleft.y, 'x2': screen_bottomright.x, 'y2': screen_bottomright.y};
let head = 0;
for (let i = 0; i < state.events.length; ++i) {
const event = state.events[i];
if (event.type === EVENT.STROKE && !event.deleted) {
if (quad_onscreen(screen, event.bbox)) {
for (let j = 0; j < event.points.length - 1; ++j) {
const a = event.points[j + 0];
const b = event.points[j + 1];
if (segment_interesects_quad(a, b, screen_topleft, screen_bottomright, screen_topright, screen_bottomleft)) {
let base = head + j * 4;
// We draw quads as [1, 2, 3, 4, 3, 2]
result.push(base + 0, base + 1, base + 2);
result.push(base + 3, base + 2, base + 1);
}
}
}
head += (event.points.length - 1) * 4;
}
}
return result;
}

14
client/webgl_draw.js

@ -48,7 +48,7 @@ function draw(state, context) { @@ -48,7 +48,7 @@ function draw(state, context) {
gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 4 * 3 + 4 * 4);
if (context.need_static_allocate) {
console.debug('static allocate');
if (config.debug_print) console.debug('static allocate');
gl.bufferData(gl.ARRAY_BUFFER, context.static_serializer.size, gl.DYNAMIC_DRAW);
context.need_static_allocate = false;
context.static_upload_from = 0;
@ -56,7 +56,7 @@ function draw(state, context) { @@ -56,7 +56,7 @@ function draw(state, context) {
}
if (context.need_static_upload) {
console.debug('static upload');
if (config.debug_print) console.debug('static upload');
const upload_offset = context.static_upload_from;
const upload_size = context.static_serializer.offset - upload_offset;
gl.bufferSubData(gl.ARRAY_BUFFER, upload_offset, new Uint8Array(context.static_serializer.buffer, upload_offset, upload_size));
@ -64,7 +64,11 @@ function draw(state, context) { @@ -64,7 +64,11 @@ function draw(state, context) {
context.static_upload_from = context.static_serializer.offset;
}
gl.drawArrays(gl.TRIANGLES, 0, static_points);
const indices = segments_onscreen(state, context);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['b_packed_static_index']);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indices), gl.DYNAMIC_DRAW);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_INT, 0);
}
if (dynamic_points > 0) {
@ -93,7 +97,7 @@ function draw(state, context) { @@ -93,7 +97,7 @@ function draw(state, context) {
if (wait_status === gl.ALREADY_SIGNALED || wait_status === gl.CONDITION_SATISFIED) {
const frametime_ms = frame_end - frame_start;
gl.deleteSync(sync);
console.debug(frametime_ms);
if (config.debug_print) console.debug(frametime_ms);
} else {
setTimeout(next_tick, 0);
}
@ -147,4 +151,4 @@ function draw(state, context) { @@ -147,4 +151,4 @@ function draw(state, context) {
// gl.bindTexture(gl.TEXTURE_2D, textures[active_image_index].texture);
// gl.drawArrays(gl.TRIANGLES, active_image_index * 6, 6);
// }
}
}

13
client/webgl_geometry.js

@ -16,10 +16,7 @@ function push_quad(s, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, ax, ay, bx, by, th @@ -16,10 +16,7 @@ function push_quad(s, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, ax, ay, bx, by, th
push_point(s, p1x, p1y, ax, ay, bx, by, thickness, r, g, b);
push_point(s, p2x, p2y, ax, ay, bx, by, thickness, r, g, b);
push_point(s, p3x, p3y, ax, ay, bx, by, thickness, r, g, b);
push_point(s, p4x, p4y, ax, ay, bx, by, thickness, r, g, b);
push_point(s, p3x, p3y, ax, ay, bx, by, thickness, r, g, b);
push_point(s, p2x, p2y, ax, ay, bx, by, thickness, r, g, b);
}
function push_stroke(s, stroke) {
@ -88,10 +85,12 @@ function geometry_prepare_stroke(state) { @@ -88,10 +85,12 @@ function geometry_prepare_stroke(state) {
return null;
}
const points = process_stroke(state, state.players[state.me].points);
return {
'color': state.players[state.me].color,
'width': state.players[state.me].width,
'points': process_stroke(state, state.players[state.me].points),
'points': points,
'user_id': state.me,
};
}
@ -99,8 +98,10 @@ function geometry_prepare_stroke(state) { @@ -99,8 +98,10 @@ function geometry_prepare_stroke(state) {
function geometry_add_stroke(state, context, stroke) {
if (!state.online || !stroke) return;
stroke.bbox = stroke_bbox(stroke.points);
let bytes_left = context.static_serializer.size - context.static_serializer.offset;
let bytes_needed = stroke.points.length * 6 * config.bytes_per_point;
let bytes_needed = stroke.points.length * 4 * config.bytes_per_point;
if (bytes_left < bytes_needed) {
const extend_to = Math.ceil((context.static_serializer.size + bytes_needed) * 1.62);
@ -257,4 +258,4 @@ function image_at(state, x, y) { @@ -257,4 +258,4 @@ function image_at(state, x, y) {
}
return null;
}
}

2
client/webgl_listeners.js

@ -445,4 +445,4 @@ async function on_drop(e, state, context) { @@ -445,4 +445,4 @@ async function on_drop(e, state, context) {
schedule_draw(state, context);
return false;
}
}

18
client/webgl_shaders.js

@ -23,17 +23,17 @@ const sdf_vs_src = `#version 300 es @@ -23,17 +23,17 @@ const sdf_vs_src = `#version 300 es
vec2 up_dir = vec2(line_dir.y, -line_dir.x);
vec2 pixel = vec2(2.0) / u_res * apron;
int vertex_index = gl_VertexID % 6;
int vertex_index = gl_VertexID % 4;
if (vertex_index == 0) {
// "top left" aka "p1"
screen02 += up_dir * pixel - line_dir * pixel;
v_texcoord = a_pos.xy + up_dir * 1.0 / u_scale - line_dir * 1.0 / u_scale;
} else if (vertex_index == 1 || vertex_index == 5) {
} else if (vertex_index == 1) {
// "top right" aka "p2"
screen02 += up_dir * pixel + line_dir * pixel;
v_texcoord = a_pos.xy + up_dir * 1.0 / u_scale + line_dir * 1.0 / u_scale;
} else if (vertex_index == 2 || vertex_index == 4) {
} else if (vertex_index == 2) {
// "bottom left" aka "p3"
screen02 += -up_dir * pixel - line_dir * pixel;
v_texcoord = a_pos.xy - up_dir * 1.0 / u_scale - line_dir * 1.0 / u_scale;
@ -179,15 +179,13 @@ function init_webgl(state, context) { @@ -179,15 +179,13 @@ function init_webgl(state, context) {
};
context.buffers['sdf'] = {
'b_packed_static': context.gl.createBuffer(),
'b_packed_dynamic': context.gl.createBuffer(),
};
context.textures['sdf'] = {
'points': gl.createTexture(),
'indices': gl.createTexture()
'b_packed_static': gl.createBuffer(),
'b_packed_dynamic': gl.createBuffer(),
'b_packed_static_index': gl.createBuffer(),
'b_packed_dynamic_index': gl.createBuffer(),
};
context.textures['sdf'] = {};
context.textures['image'] = {};
const resize_canvas = (entries) => {

Loading…
Cancel
Save