Browse Source

Recompute LODs and instance data on demand - ??? - it works???

ssao
A.Olokhtonov 11 months ago
parent
commit
a60c3d1948
  1. 51
      client/bvh.js
  2. 44
      client/client_recv.js
  3. 1
      client/client_send.js
  4. 16
      client/index.js
  5. 94
      client/math.js
  6. 44
      client/webgl_draw.js
  7. 58
      client/webgl_geometry.js
  8. 2
      client/webgl_listeners.js
  9. 1
      client/webgl_shaders.js
  10. BIN
      server/data-local.sqlite
  11. 2
      server/recv.js
  12. 7
      server/send.js
  13. 9
      server/serializer.js
  14. 13
      server/storage.js

51
client/bvh.js

@ -155,9 +155,9 @@ function bvh_add_stroke(bvh, index, stroke) {
} }
} }
function bvh_intersect_quad(bvh, quad) { function bvh_intersect_quad(bvh, quad, result_buffer) {
if (bvh.root === null) { if (bvh.root === null) {
return []; return;
} }
const stack = [bvh.root]; const stack = [bvh.root];
@ -172,20 +172,25 @@ function bvh_intersect_quad(bvh, quad) {
} }
if (node.is_leaf) { if (node.is_leaf) {
result.push(node.stroke_index); result_buffer.data[result_buffer.count] = node.stroke_index;
result_buffer.count += 1;
} else { } else {
stack.push(node.child1, node.child2); stack.push(node.child1, node.child2);
} }
} }
}
return result; function bvh_clip(state, context) {
if (state.stroke_count === 0) {
return;
} }
function bvh_clip(state, context, lod_level) { if (context.clipped_indices.cap < state.stroke_count) {
const lod = context.lods[lod_level]; context.clipped_indices.cap = round_to_pow2(state.stroke_count, 4096);
context.clipped_indices.data = new Uint32Array(context.clipped_indices.cap);
}
lod.indices = ser_ensure(lod.indices, lod.total_points * 6 * 4); context.clipped_indices.count = 0;
ser_clear(lod.indices);
const screen_topleft = screen_to_canvas(state, {'x': 0, 'y': 0}); 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_bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height});
@ -199,35 +204,9 @@ function bvh_clip(state, context, lod_level) {
'y2': screen_bottomright.y 'y2': screen_bottomright.y
}; };
const stroke_indices = bvh_intersect_quad(state.bvh, screen); bvh_intersect_quad(state.bvh, screen, context.clipped_indices);
stroke_indices.sort((a, b) => a - b);
for (const i of stroke_indices) {
if (state.debug.limit_to && i >= state.debug.render_to) break;
const event = state.events[i];
if (!(state.debug.limit_from && i < state.debug.render_from)) {
if (event.type === EVENT.STROKE && !event.deleted && event.points.length > 0) {
const points = event.lods[lod_level].points;
for (let j = 0; j < points.length - 1; ++j) {
const base = event.lods[lod_level].starting_index + j * 4;
// We draw quads as [1, 2, 3, 4, 3, 2]
ser_u32(lod.indices, base + 0);
ser_u32(lod.indices, base + 1);
ser_u32(lod.indices, base + 2);
ser_u32(lod.indices, base + 3);
ser_u32(lod.indices, base + 2);
ser_u32(lod.indices, base + 1);
}
}
}
}
return lod.indices.offset / 4; new Uint32Array(context.clipped_indices.data.buffer, 0, context.clipped_indices.count).sort(); // we need to draw back to front still!
} }
function bvh_construct_rec(bvh, vertical, strokes) { function bvh_construct_rec(bvh, vertical, strokes) {

44
client/client_recv.js

@ -32,22 +32,23 @@ function des_f32(d) {
return value; return value;
} }
function des_f32array(d, count) { function des_align(d, to) {
const result = []; // TODO: non-stupid version of this
while (d.offset % to != 0) {
for (let i = 0; i < count; ++i) { d.offset++;
const item = d.view.getFloat32(d.offset, true); }
d.offset += 4;
result.push(item);
} }
function des_f32array(d, count) {
const result = new Float32Array(d.buffer, d.offset, count);
d.offset += 4 * count;
return result; return result;
} }
function des_event(d) { function des_event(d, state = null) {
const event = {}; const event = {};
event.type = des_u8(d); event.type = des_u32(d);
event.user_id = des_u32(d); event.user_id = des_u32(d);
switch (event.type) { switch (event.type) {
@ -76,18 +77,18 @@ function des_event(d) {
const point_count = des_u16(d); const point_count = des_u16(d);
const width = des_u16(d); const width = des_u16(d);
const color = des_u32(d); const color = des_u32(d);
const coords = des_f32array(d, point_count * 2); const coords = des_f32array(d, point_count * 2);
event.coords_from = state.coordinates.count;
event.coords_to = state.coordinates.count + point_count * 2;
state.coordinates.data.set(coords, state.coordinates.count);
state.coordinates.count += point_count * 2;
event.stroke_id = stroke_id; event.stroke_id = stroke_id;
event.points = [];
event.lods = []; event.lods = [];
for (let i = 0; i < point_count; ++i) {
const x = coords[2 * i + 0];
const y = coords[2 * i + 1];
event.points.push({'x': x, 'y': y});
}
event.color = color; event.color = color;
event.width = width; event.width = width;
@ -178,6 +179,8 @@ function handle_event(state, context, event, options = {}) {
need_draw = true; need_draw = true;
//} //}
event.index = state.events.length;
geometry_add_stroke(state, context, event, state.events.length, options.skip_bvh === true); geometry_add_stroke(state, context, event, state.events.length, options.skip_bvh === true);
state.stroke_count++; state.stroke_count++;
@ -342,6 +345,9 @@ async function handle_message(state, context, d) {
const event_count = des_u32(d); const event_count = des_u32(d);
const user_count = des_u32(d); const user_count = des_u32(d);
const total_points = des_u32(d);
state.coordinates.data = new Float32Array(round_to_pow2(total_points * 2, 4096));
if (config.debug_print) console.debug(`${event_count} events in init`); if (config.debug_print) console.debug(`${event_count} events in init`);
@ -355,11 +361,13 @@ async function handle_message(state, context, d) {
init_player_defaults(state, user_id, user_color, user_width); init_player_defaults(state, user_id, user_color, user_width);
} }
des_align(d, 4);
for (let i = 0; i < event_count; ++i) { for (let i = 0; i < event_count; ++i) {
const event = des_event(d); const event = des_event(d, state);
handle_event(state, context, event, {'skip_bvh': true}); handle_event(state, context, event, {'skip_bvh': true});
if (event.type !== EVENT.STROKE || event.points.length > 0) { if (event.type !== EVENT.STROKE || event.coords_to - event.coords_from > 0) {
state.events.push(event); state.events.push(event);
} }
} }

1
client/client_send.js

@ -152,6 +152,7 @@ async function send_ack(sn) {
} }
async function sync_queue(state) { async function sync_queue(state) {
if (ws === null) { if (ws === null) {
if (config.debug_print) console.debug('socket has closed, stopping SYNs'); if (config.debug_print) console.debug('socket has closed, stopping SYNs');
return; return;

16
client/index.js

@ -24,7 +24,7 @@ const config = {
initial_offline_timeout: 1000, initial_offline_timeout: 1000,
default_color: 0x00, default_color: 0x00,
default_width: 8, default_width: 8,
bytes_per_quad: 4 * 4 + 4, // axy, bxy, stroke_id bytes_per_instance: 4 * 4 + 4, // axy, bxy, stroke_id
bytes_per_stroke: 3 + 1, // r, g, b, width bytes_per_stroke: 3 + 1, // r, g, b, width
initial_static_bytes: 4096 * 16, initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096, initial_dynamic_bytes: 4096,
@ -172,6 +172,11 @@ function main() {
'starting_index': 0, 'starting_index': 0,
'total_points': 0, 'total_points': 0,
'coordinates': {
'data': null,
'count': 0,
},
'bvh': { 'bvh': {
'nodes': [], 'nodes': [],
'root': null, 'root': null,
@ -219,6 +224,15 @@ function main() {
'dynamic_serializer': serializer_create(config.initial_dynamic_bytes), 'dynamic_serializer': serializer_create(config.initial_dynamic_bytes),
'dynamic_index_serializer': serializer_create(config.initial_dynamic_bytes), 'dynamic_index_serializer': serializer_create(config.initial_dynamic_bytes),
// TODO: i seem to have a lot of these, maybe make a few utility functions? similar to serializer, but for pure typedarray
'clipped_indices': {
'data': null,
'count': 0,
'cap': 0,
},
'instance_data': serializer_create(config.initial_static_bytes),
'lods': [], 'lods': [],
'stroke_data': serializer_create(config.initial_static_bytes), 'stroke_data': serializer_create(config.initial_static_bytes),

94
client/math.js

@ -1,3 +1,7 @@
function round_to_pow2(value, multiple) {
return (value + multiple - 1) & -multiple;
}
function screen_to_canvas(state, p) { function screen_to_canvas(state, p) {
// should be called with coordinates obtained from MouseEvent.clientX/clientY * window.devicePixelRatio // should be called with coordinates obtained from MouseEvent.clientX/clientY * window.devicePixelRatio
const xc = (p.x - state.canvas.offset.x) / state.canvas.zoom; const xc = (p.x - state.canvas.offset.x) / state.canvas.zoom;
@ -6,36 +10,39 @@ function screen_to_canvas(state, p) {
return {'x': xc, 'y': yc}; return {'x': xc, 'y': yc};
} }
function rdp_find_max(zoom, points, start, end) { function rdp_find_max(state, zoom, stroke, start, end) {
const EPS = 1.0 / zoom; const EPS = 1.0 / zoom;
// const EPS = 10.0; // const EPS = 10.0;
let result = -1; let result = -1;
let max_dist = 0; let max_dist = 0;
const a = points[start]; const ax = state.coordinates.data[stroke.coords_from + start * 2 + 0];
const b = points[end]; 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 = b.x - a.x; const dx = bx - ax;
const dy = b.y - a.y; const dy = by - ay;
const dist_ab = Math.sqrt(dx * dx + dy * dy); const dist_ab = Math.sqrt(dx * dx + dy * dy);
const sin_theta = dy / dist_ab; const sin_theta = dy / dist_ab;
const cos_theta = dx / dist_ab; const cos_theta = dx / dist_ab;
for (let i = start; i < end; ++i) { for (let i = start; i < end; ++i) {
const p = points[i]; const px = state.coordinates.data[stroke.coords_from + i * 2 + 0];
const py = state.coordinates.data[stroke.coords_from + i * 2 + 1];
const ox = p.x - a.x; const ox = px - ax;
const oy = p.y - a.y; const oy = py - ay;
const rx = cos_theta * ox + sin_theta * oy; const rx = cos_theta * ox + sin_theta * oy;
const ry = -sin_theta * ox + cos_theta * oy; const ry = -sin_theta * ox + cos_theta * oy;
const x = rx + a.x; const x = rx + ax;
const y = ry + a.y; const y = ry + ay;
const dist = Math.abs(y - a.y); const dist = Math.abs(y - ay);
if (dist > EPS && dist > max_dist) { if (dist > EPS && dist > max_dist) {
result = i; result = i;
@ -46,45 +53,37 @@ function rdp_find_max(zoom, points, start, end) {
return result; return result;
} }
function process_rdp_r(zoom, mask, points, start, end) { function process_rdp_indices_r(state, zoom, mask, stroke, start, end) {
let result = 0; let result = 0;
const max = rdp_find_max(zoom, points, start, end); const max = rdp_find_max(state, zoom, stroke, start, end);
if (max !== -1) { if (max !== -1) {
mask[max] = 1; mask[max] = 1;
result += 1; result += 1;
result += process_rdp_r(zoom, mask, points, start, max); result += process_rdp_indices_r(state, zoom, mask, stroke, start, max);
result += process_rdp_r(zoom, mask, points, max, end); result += process_rdp_indices_r(state, zoom, mask, stroke, max, end);
} }
return result; return result;
} }
function process_rdp(state, zoom, points) { function process_rdp_indices(state, zoom, stroke) {
if (state.rdp_mask.length < points.length) { const point_count = (stroke.coords_to - stroke.coords_from) / 2;
state.rdp_mask = new Uint8Array(points.length);
if (state.rdp_mask.length < point_count) {
state.rdp_mask = new Uint8Array(point_count);
} }
state.rdp_mask.fill(0, 0, points.length); state.rdp_mask.fill(0, 0, point_count);
const mask = state.rdp_mask; const mask = state.rdp_mask;
const npoints = process_rdp_r(zoom, mask, points, 0, points.length - 1); const npoints = 2 + process_rdp_indices_r(state, zoom, mask, stroke, 0, point_count - 1); // 2 is for the first and last vertex, which do not get included by the recursive functions, but should always be there at any lod level
mask[0] = 1; mask[0] = 1;
mask[points.length - 1] = 1; mask[point_count - 1] = 1;
const result = new Array(npoints);
let j = 0;
for (let i = 0; i < points.length; ++i) {
if (mask[i] === 1) {
result[j] = points[i];
++j;
}
}
return result; return npoints;
} }
function process_ewmv(points, round = false) { function process_ewmv(points, round = false) {
@ -103,9 +102,9 @@ function process_ewmv(points, round = false) {
return result; return result;
} }
function process_stroke(state, zoom, points) { function process_stroke(state, zoom, stroke) {
// const result0 = process_ewmv(points); // const result0 = process_ewmv(points);
const result1 = process_rdp(state, zoom, points, true); const result1 = process_rdp_indices(state, zoom, stroke, true);
return result1; return result1;
} }
@ -202,20 +201,23 @@ function segment_interesects_quad(a, b, quad_topleft, quad_bottomright, quad_top
return false; return false;
} }
function stroke_bbox(stroke) { function stroke_bbox(state, stroke) {
const radius = stroke.width / 2; const radius = stroke.width / 2;
let min_x = stroke.points[0].x - radius; let min_x = state.coordinates.data[stroke.coords_from + 0] - radius;
let max_x = stroke.points[0].x + radius; let max_x = state.coordinates.data[stroke.coords_from + 0] + radius;
let min_y = stroke.points[0].y - radius; let min_y = state.coordinates.data[stroke.coords_from + 1] - radius;
let max_y = stroke.points[0].y + radius; let max_y = state.coordinates.data[stroke.coords_from + 1] + radius;
for (const p of stroke.points) { for (let i = stroke.coords_from + 2; i < stroke.coords_to; i += 2) {
min_x = Math.min(min_x, p.x - radius); const px = state.coordinates.data[i + 0];
min_y = Math.min(min_y, p.y - radius); const py = state.coordinates.data[i + 1];
max_x = Math.max(max_x, p.x + radius);
max_y = Math.max(max_y, p.y + radius); min_x = Math.min(min_x, px - radius);
min_y = Math.min(min_y, py - radius);
max_x = Math.max(max_x, px + radius);
max_y = Math.max(max_y, py + radius);
} }
return {'x1': min_x, 'y1': min_y, 'x2': max_x, 'y2': max_y, 'cx': (max_x + min_x) / 2, 'cy': (max_y + min_y) / 2}; return {'x1': min_x, 'y1': min_y, 'x2': max_x, 'y2': max_y, 'cx': (max_x + min_x) / 2, 'cy': (max_y + min_y) / 2};
@ -246,6 +248,10 @@ function quad_union(a, b) {
}; };
} }
function box_area(box) {
return (box.x2 - box.x1) * (box.y2 - box.y1);
}
function segments_onscreen(state, context, do_clip) { function segments_onscreen(state, context, do_clip) {
// TODO: handle stroke width // TODO: handle stroke width

44
client/webgl_draw.js

@ -48,18 +48,6 @@ function upload_square_rgba16ui_texture(gl, serializer, texture_size) {
function draw(state, context) { function draw(state, context) {
const cpu_before = performance.now(); const cpu_before = performance.now();
let lod_level = -1;
for (let i = context.lods.length - 1; i >= 0; --i) {
const level = context.lods[i];
if (state.canvas.zoom <= level.max_zoom) {
lod_level = i;
break;
}
}
const lod = context.lods[lod_level];
state.timers.raf = false; state.timers.raf = false;
const gl = context.gl; const gl = context.gl;
@ -79,18 +67,18 @@ function draw(state, context) {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, lod.data_buffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, lod.index_buffer);
// static data, per-quad: points, stroke_ids
// static data, per-stroke (texture): color, width (radius)
upload_if_needed(gl, gl.ARRAY_BUFFER, lod.segments);
locations = context.locations['sdf'].main; locations = context.locations['sdf'].main;
buffers = context.buffers['sdf'];
gl.useProgram(context.programs['sdf'].main); gl.useProgram(context.programs['sdf'].main);
const segment_count = lod.segments.offset / config.bytes_per_quad;
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.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); 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_scale'], state.canvas.zoom, state.canvas.zoom);
@ -103,23 +91,26 @@ function draw(state, context) {
gl.enableVertexAttribArray(locations['a_ab']); gl.enableVertexAttribArray(locations['a_ab']);
gl.enableVertexAttribArray(locations['a_stroke_id']); gl.enableVertexAttribArray(locations['a_stroke_id']);
gl.vertexAttribPointer(locations['a_ab'], 4, gl.FLOAT, false, 5 * 4, 0); gl.vertexAttribPointer(locations['a_ab'], 4, gl.FLOAT, false, config.bytes_per_instance, 0);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 5 * 4, 4 * 4); 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_ab'], 1);
gl.vertexAttribDivisor(locations['a_stroke_id'], 1); gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']); gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
// TODO: this is stable data, only upload new strokes as they arrive
upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size); upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size);
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count); // TODO: based on clipping results gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count); // TODO: based on clipping results
document.getElementById('debug-stats').innerHTML = `
<span>Segments onscreen: ${segment_count}</span>
<span>Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y})</span>
<span>Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000}</span>`;
/* /*
const before_clip = performance.now();
const index_count = bvh_clip(state, context, lod_level);
const after_clip = performance.now(); const after_clip = performance.now();
gl.bindBuffer(gl.ARRAY_BUFFER, lod.data_buffer); gl.bindBuffer(gl.ARRAY_BUFFER, lod.data_buffer);
@ -128,11 +119,6 @@ function draw(state, context) {
upload_if_needed(gl, gl.ARRAY_BUFFER, lod.vertices); upload_if_needed(gl, gl.ARRAY_BUFFER, lod.vertices);
upload_if_needed(gl, gl.ELEMENT_ARRAY_BUFFER, lod.indices); upload_if_needed(gl, gl.ELEMENT_ARRAY_BUFFER, lod.indices);
document.getElementById('debug-stats').innerHTML = `
<span>LOD level: ${lod_level}</span>
<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 * 100000) / 100000}</span>`;
if (index_count > 0) { if (index_count > 0) {
// DEPTH PREPASS // DEPTH PREPASS

58
client/webgl_geometry.js

@ -36,41 +36,51 @@ function geometry_prepare_stroke(state) {
}; };
} }
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) { function geometry_write_instances(state, context) {
if (!state.online || !stroke || stroke.points.length === 0) return; context.instance_data = ser_ensure(context.instance_data, state.coordinates.count / 2 * config.bytes_per_instance);
ser_clear(context.instance_data);
stroke.index = state.events.length; let segment_count = 0;
for (let i = 0; i < config.lod_levels; ++i) { for (let i = 0; i < context.clipped_indices.count; ++i) {
const lod = context.lods[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);
const points = (i > 0 ? process_stroke(state, lod.max_zoom, stroke.points) : stroke.points); segment_count += lod_indices_count - 1;
const segment_serializer = lod.segments = ser_ensure_by(lod.segments, (points.length - 1) * config.bytes_per_quad);
let starting_index = 0; let base_this = 0;
let base_next = 0;
if (state.events.length > 0) { for (let j = 0; j < lod_indices_count - 1; ++j) {
const last_stroke = state.events[stroke_index - 1].lods[i]; while (state.rdp_mask[base_this] == 0) base_this++;
starting_index = last_stroke.starting_index + (last_stroke.points.length - 1) * 4; base_next = base_this + 1;
} while (state.rdp_mask[base_next] == 0) base_next++;
stroke.lods.push({
'points': points,
'starting_index': starting_index,
'width': stroke.width,
'color': stroke.color,
});
context.lods[i].total_points += points.length; 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];
push_stroke(segment_serializer, stroke.lods[stroke.lods.length - 1], stroke_index); 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);
if (i === 0) { base_this = base_next;
stroke.bbox = stroke_bbox(stroke);
stroke.area = (stroke.bbox.x2 - stroke.bbox.x1) * (stroke.bbox.y2 - stroke.bbox.y1);
} }
} }
return segment_count;
}
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {
if (!state.online || !stroke || stroke.coords_to - stroke.coords_from === 0) return;
stroke.bbox = stroke_bbox(state, stroke);
stroke.area = box_area(stroke.bbox);
context.stroke_data = ser_ensure_by(context.stroke_data, config.bytes_per_stroke); context.stroke_data = ser_ensure_by(context.stroke_data, config.bytes_per_stroke);
const color_u32 = stroke.color; const color_u32 = stroke.color;

2
client/webgl_listeners.js

@ -451,7 +451,7 @@ function touchend(e, state, context) {
const stroke = geometry_prepare_stroke(state); const stroke = geometry_prepare_stroke(state);
if (stroke) { if (false && stroke) { // TODO: FIX!
geometry_add_stroke(state, context, stroke, 0); // TODO: stroke index geometry_add_stroke(state, context, stroke, 0); // TODO: stroke index
queue_event(state, stroke_event(state)); queue_event(state, stroke_event(state));
geometry_clear_player(state, context, state.me); geometry_clear_player(state, context, state.me);

1
client/webgl_shaders.js

@ -333,6 +333,7 @@ function init_webgl(state, context) {
context.buffers['sdf'] = { context.buffers['sdf'] = {
'b_packed_dynamic': gl.createBuffer(), 'b_packed_dynamic': gl.createBuffer(),
'b_packed_dynamic_index': gl.createBuffer(), 'b_packed_dynamic_index': gl.createBuffer(),
'b_instance': gl.createBuffer(),
}; };
context.textures = { context.textures = {

BIN
server/data-local.sqlite

Binary file not shown.

2
server/recv.js

@ -127,6 +127,8 @@ function handle_event(session, event) {
'$y': 0, '$y': 0,
}); });
desks[session.desk_id].total_points += event.points.length;
break; break;
} }

7
server/send.js

@ -7,7 +7,7 @@ import { MESSAGE, SESSION, EVENT } from './enums';
import { sessions, desks } from './storage'; import { sessions, desks } from './storage';
function event_size(event) { function event_size(event) {
let size = 1 + 4; // type + user_id let size = 4 + 4; // type + user_id
switch (event.type) { switch (event.type) {
case EVENT.PREDRAW: { case EVENT.PREDRAW: {
@ -94,7 +94,7 @@ export async function send_init(ws) {
const desk = desks[desk_id]; const desk = desks[desk_id];
let opcode = MESSAGE.INIT; let opcode = MESSAGE.INIT;
let size = 1 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count + user count let size = 1 + 4 + 4 + 4 + 4 + 4 + 3; // opcode + user_id + lsn + event count + stroke count + user count + total_point_count + align on 4
let session = null; let session = null;
if (session_id in sessions && sessions[session_id].desk_id == desk_id) { if (session_id in sessions && sessions[session_id].desk_id == desk_id) {
@ -142,6 +142,7 @@ export async function send_init(ws) {
ser.u32(s, desk.events.length); ser.u32(s, desk.events.length);
ser.u32(s, user_count); ser.u32(s, user_count);
ser.u32(s, desk.total_points);
for (const sid in sessions) { for (const sid in sessions) {
const other_session = sessions[sid]; const other_session = sessions[sid];
@ -153,6 +154,8 @@ export async function send_init(ws) {
} }
} }
ser.align(s, 4);
for (const event of desk.events) { for (const event of desk.events) {
ser.event(s, event); ser.event(s, event);
} }

9
server/serializer.js

@ -36,8 +36,15 @@ export function bytes(s, bytes) {
s.offset += bytes.byteLength; s.offset += bytes.byteLength;
} }
export function align(s, to) {
// TODO: non-stupid version of this
while (s.offset % to != 0) {
s.offset++;
}
}
export function event(s, event) { export function event(s, event) {
u8(s, event.type); u32(s, event.type); // for alignment reasons
u32(s, event.user_id); u32(s, event.user_id);
switch (event.type) { switch (event.type) {

13
server/storage.js

@ -100,14 +100,15 @@ export function startup() {
const stroke_dict = {}; const stroke_dict = {};
for (const stroke of stored_strokes) {
stroke.points = new Float32Array(stroke.points.buffer);
stroke_dict[stroke.id] = stroke;
}
for (const desk of stored_desks) { for (const desk of stored_desks) {
desks[desk.id] = desk; desks[desk.id] = desk;
desks[desk.id].events = []; desks[desk.id].events = [];
desks[desk.id].total_points = 0;
}
for (const stroke of stored_strokes) {
stroke.points = new Float32Array(stroke.points.buffer);
stroke_dict[stroke.id] = stroke;
} }
for (const event of stored_events) { for (const event of stored_events) {
@ -116,6 +117,8 @@ export function startup() {
event.points = stroke.points; event.points = stroke.points;
event.color = stroke.color; event.color = stroke.color;
event.width = stroke.width; event.width = stroke.width;
desks[event.desk_id].total_points += stroke.points.length / 2;
} }
desks[event.desk_id].events.push(event); desks[event.desk_id].events.push(event);

Loading…
Cancel
Save