Browse Source

You can draw once again!

ssao
A.Olokhtonov 10 months ago
parent
commit
c893a73ec5
  1. 4
      README.md
  2. 2
      client/aux.js
  3. 5
      client/client_recv.js
  4. 21
      client/client_send.js
  5. 22
      client/index.js
  6. 67
      client/math.js
  7. 102
      client/webgl_draw.js
  8. 54
      client/webgl_geometry.js
  9. 4
      client/webgl_listeners.js
  10. 15
      client/webgl_shaders.js
  11. 3
      server/deserializer.js
  12. 2
      server/recv.js
  13. 16
      server/send.js

4
README.md

@ -2,7 +2,7 @@ Release:
* Engine * Engine
+ Benchmark harness + 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) + 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) + Draw dynamic data (strokes in progress)
- Textured quads (pictures, code already written in older version) - Textured quads (pictures, code already written in older version)
- Resize and move pictures (draw handles) - Resize and move pictures (draw handles)
- Z-prepass fringe bug (also, when do we enable the prepass?) - Z-prepass fringe bug (also, when do we enable the prepass?)
@ -26,6 +26,8 @@ Release:
- Polish - Polish
- Show what's happening while the desk is loading (downloading, processing, uploading to gpu) - Show what's happening while the desk is loading (downloading, processing, uploading to gpu)
- Settings panel (including the setting for "offline mode") - Settings panel (including the setting for "offline mode")
- Use typedvector where appropriate
- Set up VAOs
- Presentation / "marketing" - Presentation / "marketing"
- Title - Title
- Icon - Icon

2
client/aux.js

@ -33,7 +33,7 @@ async function insert_image(state, context, file) {
} }
function event_size(event) { function event_size(event) {
let size = 1 + 3; // type + padding let size = 4; // type
switch (event.type) { switch (event.type) {
case EVENT.PREDRAW: { case EVENT.PREDRAW: {

5
client/client_recv.js

@ -87,7 +87,6 @@ function des_event(d, state = null) {
state.coordinates.count += point_count * 2; state.coordinates.count += point_count * 2;
event.stroke_id = stroke_id; event.stroke_id = stroke_id;
event.lods = [];
event.color = color; event.color = color;
event.width = width; event.width = width;
@ -304,7 +303,7 @@ function handle_event(state, context, event, options = {}) {
} }
async function handle_message(state, context, d) { async function handle_message(state, context, d) {
const message_type = des_u8(d); const message_type = des_u32(d);
let do_draw = false; let do_draw = false;
// if (config.debug_print) console.debug(message_type); // if (config.debug_print) console.debug(message_type);
@ -421,7 +420,7 @@ async function handle_message(state, context, d) {
if (config.debug_print) console.debug(`syn ${sn} in`); if (config.debug_print) console.debug(`syn ${sn} in`);
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
const event = des_event(d); const event = des_event(d, state);
if (i >= first) { if (i >= first) {
const need_draw = handle_event(state, context, event); const need_draw = handle_event(state, context, event);
do_draw = do_draw || need_draw; do_draw = do_draw || need_draw;

21
client/client_send.js

@ -43,11 +43,6 @@ function ser_clear(s) {
s.gpu_upload_from = 0; s.gpu_upload_from = 0;
} }
function ser_u8(s, value) {
s.view.setUint8(s.offset, value);
s.offset += 1;
}
function ser_u16(s, value) { function ser_u16(s, value) {
s.view.setUint16(s.offset, value, true); s.view.setUint16(s.offset, value, true);
s.offset += 2; s.offset += 2;
@ -71,7 +66,7 @@ function ser_align(s, to) {
} }
function ser_event(s, event) { function ser_event(s, event) {
ser_u8(s, event.type); ser_u32(s, event.type);
switch (event.type) { switch (event.type) {
case EVENT.PREDRAW: { case EVENT.PREDRAW: {
@ -101,8 +96,6 @@ function ser_event(s, event) {
if (config.debug_print) console.debug('original', event.points); if (config.debug_print) console.debug('original', event.points);
ser_align(s, 4);
for (const point of event.points) { for (const point of event.points) {
ser_f32(s, point.x); ser_f32(s, point.x);
ser_f32(s, point.y); ser_f32(s, point.y);
@ -137,9 +130,9 @@ function ser_event(s, event) {
} }
async function send_ack(sn) { async function send_ack(sn) {
const s = serializer_create(1 + 4); const s = serializer_create(4 + 4);
ser_u8(s, MESSAGE.ACK); ser_u32(s, MESSAGE.ACK);
ser_u32(s, sn); ser_u32(s, sn);
if (config.debug_print) console.debug(`ack ${sn} out`); if (config.debug_print) console.debug(`ack ${sn} out`);
@ -158,7 +151,7 @@ async function sync_queue(state) {
return; return;
} }
let size = 1 + 3 + 4 + 4; // opcode + lsn + event count let size = 4 + 4 + 4; // opcode + lsn + event count
let count = state.lsn - state.server_lsn; let count = state.lsn - state.server_lsn;
if (count === 0) { if (count === 0) {
@ -174,7 +167,7 @@ async function sync_queue(state) {
const s = serializer_create(size); const s = serializer_create(size);
ser_u8(s, MESSAGE.SYN); ser_u32(s, MESSAGE.SYN);
ser_u32(s, state.lsn); ser_u32(s, state.lsn);
ser_u32(s, count); ser_u32(s, count);
@ -251,9 +244,9 @@ function queue_event(state, event, skip = false) {
async function fire_event(state, event) { async function fire_event(state, event) {
if (!state.online) { return; } if (!state.online) { return; }
const s = serializer_create(1 + event_size(event)); const s = serializer_create(4 + event_size(event));
ser_u8(s, MESSAGE.FIRE); ser_u32(s, MESSAGE.FIRE);
ser_event(s, event); ser_event(s, event);
try { try {

22
client/index.js

@ -22,7 +22,8 @@ const config = {
bytes_per_stroke: 2 * 3 + 2, // r, g, b, width bytes_per_stroke: 2 * 3 + 2, // r, g, b, width
initial_static_bytes: 4096 * 16, initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096, initial_dynamic_bytes: 4096,
stroke_texture_size: 1024, stroke_texture_size: 1024, // means no more than 1024^2 = 1M strokes in total (this is a LOT. HMH blackboard has like 80K)
dynamic_stroke_texture_size: 128, // means no more than 128^2 = 16K dynamic strokes at once
benchmark: { benchmark: {
zoom: 0.035, zoom: 0.035,
offset: { x: 900, y: 400 }, offset: { x: 900, y: 400 },
@ -246,9 +247,14 @@ function main() {
'instance_data_points': tv_create(Float32Array, 4096), 'instance_data_points': tv_create(Float32Array, 4096),
'instance_data_ids': tv_create(Uint32Array, 4096), 'instance_data_ids': tv_create(Uint32Array, 4096),
'lods': [], 'dynamic_instance_points': tv_create(Float32Array, 4096),
'dynamic_instance_ids': tv_create(Uint32Array, 4096),
'stroke_data': serializer_create(config.initial_static_bytes), 'stroke_data': serializer_create(config.initial_static_bytes),
'dynamic_stroke_data': serializer_create(config.initial_static_bytes),
'dynamic_stroke_count': 0,
'dynamic_segment_count': 0,
'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0}, 'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0},
@ -261,18 +267,6 @@ function main() {
const url = new URL(window.location.href); const url = new URL(window.location.href);
const parts = url.pathname.split('/'); const parts = url.pathname.split('/');
config.lod_levels = Math.ceil(Math.log2(1.0 / config.min_zoom));
for (let i = 0; i < config.lod_levels; ++i) {
context.lods.push({
'max_zoom': Math.pow(0.25, i), // use this LOD level when current canvas.zoom is less than this value, but not less than the next level max_zoom (or if this is the last zoom level)
'total_points': 0,
'segments': serializer_create(config.initial_static_bytes),
'data_buffer': null,
'index_buffer': null,
});
}
state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0; state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0;
init_webgl(state, context); init_webgl(state, context);

67
client/math.js

@ -50,6 +50,7 @@ function rdp_find_max(state, zoom, stroke, start, end) {
return result; return result;
} }
*/ */
function process_rdp_indices_r(state, zoom, mask, stroke, start, end) { 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 // 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 // Let's try to use an explicit stack instead to give the js engine more room to play with
@ -124,6 +125,72 @@ function process_stroke(state, zoom, stroke) {
return npoints; return npoints;
} }
function rdp_find_max2(points, start, end) {
const EPS = 0.5;
let result = -1;
let max_dist = 0;
const a = points[start];
const b = points[end];
const dx = b.x - a.x;
const dy = b.y - a.y;
const dist_ab = Math.sqrt(dx * dx + dy * dy);
const sin_theta = dy / dist_ab;
const cos_theta = dx / dist_ab;
for (let i = start; i < end; ++i) {
const p = points[i];
const ox = p.x - a.x;
const oy = p.y - a.y;
const rx = cos_theta * ox + sin_theta * oy;
const ry = -sin_theta * ox + cos_theta * oy;
const x = rx + a.x;
const y = ry + a.y;
const dist = Math.abs(y - a.y);
if (dist > EPS && dist > max_dist) {
result = i;
max_dist = dist;
}
}
return result;
}
function process_rdp_r2(points, start, end) {
let result = [];
const max = rdp_find_max2(points, start, end);
if (max !== -1) {
const before = process_rdp_r2(points, start, max);
const after = process_rdp_r2(points, max, end);
result = [...before, points[max], ...after];
}
return result;
}
function process_rdp2(points) {
const result = process_rdp_r2(points, 0, points.length - 1);
result.unshift(points[0]);
result.push(points[points.length - 1]);
return result;
}
// TODO: unify with regular process stroke
function process_stroke2(points) {
const result = process_rdp2(points);
return result;
}
function strokes_intersect_line(state, a, b) { function strokes_intersect_line(state, a, b) {
// TODO: handle stroke / eraser width // TODO: handle stroke / eraser width
const result = []; const result = [];

102
client/webgl_draw.js

@ -73,11 +73,17 @@ function draw(state, context) {
bvh_clip(state, context); bvh_clip(state, context);
const segment_count = geometry_write_instances(state, context); const segment_count = geometry_write_instances(state, context);
const dynamic_segment_count = context.dynamic_segment_count;
const dynamic_stroke_count = context.dynamic_stroke_count;
// "Static" data upload
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']); gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
gl.bufferData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4, gl.STREAM_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, 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.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);
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);
@ -91,7 +97,7 @@ function draw(state, context) {
gl.enableVertexAttribArray(locations['a_b']); gl.enableVertexAttribArray(locations['a_b']);
gl.enableVertexAttribArray(locations['a_stroke_id']); 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 // 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(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0); 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.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.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4);
@ -100,80 +106,46 @@ function draw(state, context) {
gl.vertexAttribDivisor(locations['a_b'], 1); gl.vertexAttribDivisor(locations['a_b'], 1);
gl.vertexAttribDivisor(locations['a_stroke_id'], 1); gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']); // Static draw (everything already bound)
// TODO: this is stable data, only upload new strokes as they arrive gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count);
upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size);
gl.activeTexture(gl.TEXTURE0);
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>`;
/*
// Dynamic data (stroke previews that are currently in progress)
const dynamic_points = context.dynamic_serializer.offset / config.bytes_per_point;
if (dynamic_points > 0) {
gl.drawBuffers([gl.BACK]);
locations = context.locations['sdf'].main; // Dynamic strokes should be drawn above static strokes
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.useProgram(context.programs['sdf'].main); // Dynamic draw (strokes currently being drawn)
gl.uniform1i(locations['u_stroke_count'], dynamic_stroke_count);
gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); gl.uniform1i(locations['u_stroke_data'], 0);
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); gl.uniform1i(locations['u_stroke_texture_size'], config.dynamic_stroke_texture_size);
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.clear(gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed_dynamic']);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['b_packed_dynamic_index']);
gl.enableVertexAttribArray(locations['a_pos']); gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_dynamic_instance']);
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); // Dynamic data upload
gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3); gl.bufferData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4, gl.STREAM_DRAW);
gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 4 * 3 + 4 * 4); gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.dynamic_instance_points));
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4); gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4, tv_data(context.dynamic_instance_ids));
gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']);
upload_square_rgba16ui_texture(gl, context.dynamic_stroke_data, config.dynamic_stroke_texture_size);
const dynamic_indices = []; gl.enableVertexAttribArray(locations['a_a']);
let base = 0; gl.enableVertexAttribArray(locations['a_b']);
gl.enableVertexAttribArray(locations['a_stroke_id']);
for (const player_id in state.players) { // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
// player has the same data as their current stroke: points, color, width gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
const player = state.players[player_id]; gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
if (player.points.length > 1) { gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.dynamic_instance_points.size * 4);
for (let i = 0; i < player.points.length - 1; ++i) {
dynamic_indices.push(base + 0);
dynamic_indices.push(base + 1);
dynamic_indices.push(base + 2);
dynamic_indices.push(base + 3);
dynamic_indices.push(base + 2);
dynamic_indices.push(base + 1);
base += 4; gl.vertexAttribDivisor(locations['a_a'], 1);
} gl.vertexAttribDivisor(locations['a_b'], 1);
} gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
}
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dynamic_segment_count);
if (context.need_dynamic_upload) { document.getElementById('debug-stats').innerHTML = `
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(context.dynamic_serializer.buffer, 0, context.dynamic_serializer.offset), gl.DYNAMIC_DRAW); <span>Segments onscreen: ${segment_count}</span>
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(dynamic_indices), gl.DYNAMIC_DRAW); <span>Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y})</span>
context.need_dynamic_upload = false; <span>Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000}</span>`;
}
gl.drawElements(gl.TRIANGLES, dynamic_indices.length, gl.UNSIGNED_INT, 0);
}
*/
if (context.gpu_timer_ext) { if (context.gpu_timer_ext) {
gl.endQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT); gl.endQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT);

54
client/webgl_geometry.js

@ -26,7 +26,7 @@ function geometry_prepare_stroke(state) {
return null; return null;
} }
const points = process_stroke(state, state.canvas.zoom, state.players[state.me].points); const points = process_stroke2(state.players[state.me].points);
return { return {
'color': state.players[state.me].color, 'color': state.players[state.me].color,
@ -223,30 +223,62 @@ function geometry_delete_stroke(state, context, stroke_index) {
} }
function recompute_dynamic_data(state, context) { function recompute_dynamic_data(state, context) {
let bytes_needed = 0; let total_points = 0;
let total_strokes = 0;
for (const player_id in state.players) { for (const player_id in state.players) {
const player = state.players[player_id]; const player = state.players[player_id];
if (player.points.length > 0) { if (player.points.length > 0) {
bytes_needed += player.points.length * 6 * config.bytes_per_point; total_points += player.points.length;
total_strokes += 1;
} }
} }
if (bytes_needed > context.dynamic_serializer.size) { context.dynamic_instance_data = tv_ensure(context.dynamic_instance_points, round_to_pow2(total_points * 2, 4096));
context.dynamic_serializer = serializer_create(Math.ceil(bytes_needed * 1.62)); context.dynamic_instance_ids = tv_ensure(context.dynamic_instance_ids, round_to_pow2(total_points, 4096));
} else {
context.dynamic_serializer.offset = 0; tv_clear(context.dynamic_instance_points);
} tv_clear(context.dynamic_instance_ids);
context.dynamic_stroke_data = ser_ensure(context.dynamic_stroke_data, config.bytes_per_stroke * total_strokes);
ser_clear(context.dynamic_stroke_data);
let stroke_index = 0;
for (const player_id in state.players) { for (const player_id in state.players) {
// player has the same data as their current stroke: points, color, width // player has the same data as their current stroke: points, color, width
const player = state.players[player_id]; const player = state.players[player_id];
if (player.points.length > 1) {
push_stroke(context.dynamic_serializer, player, 0); for (let i = 0; i < player.points.length; ++i) {
const p = player.points[i];
tv_add(context.dynamic_instance_points, p.x);
tv_add(context.dynamic_instance_points, p.y);
if (i !== player.points.length - 1) {
tv_add(context.dynamic_instance_ids, stroke_index);
} else {
tv_add(context.dynamic_instance_ids, stroke_index | (1 << 31));
}
}
if (player.points.length > 0) {
const color_u32 = player.color;
const r = (color_u32 >> 16) & 0xFF;
const g = (color_u32 >> 8) & 0xFF;
const b = color_u32 & 0xFF;
ser_u16(context.dynamic_stroke_data, r);
ser_u16(context.dynamic_stroke_data, g);
ser_u16(context.dynamic_stroke_data, b);
ser_u16(context.dynamic_stroke_data, player.width);
stroke_index += 1; // TODO: proper player Z order
} }
} }
context.need_dynamic_upload = true; context.dynamic_segment_count = total_points;
context.dynamic_stroke_count = total_strokes;
} }
function geometry_add_point(state, context, player_id, point) { function geometry_add_point(state, context, player_id, point) {

4
client/webgl_listeners.js

@ -279,9 +279,9 @@ function mouseup(e, state, context) {
const stroke = geometry_prepare_stroke(state); const stroke = geometry_prepare_stroke(state);
if (stroke) { if (stroke) {
//geometry_add_stroke(state, context, stroke, 0); // TODO: stroke index? //geometry_add_stroke(state, context, stroke, 0);
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);
schedule_draw(state, context); schedule_draw(state, context);
} }

15
client/webgl_shaders.js

@ -323,24 +323,18 @@ function init_webgl(state, context) {
} }
}; };
for (let i = 0; i < context.lods.length; ++i) {
const level = context.lods[i];
level.data_buffer = gl.createBuffer();
level.index_buffer = gl.createBuffer();
}
context.buffers['debug'] = { context.buffers['debug'] = {
'b_packed': gl.createBuffer(), 'b_packed': gl.createBuffer(),
}; };
context.buffers['sdf'] = { context.buffers['sdf'] = {
'b_packed_dynamic': gl.createBuffer(),
'b_packed_dynamic_index': gl.createBuffer(),
'b_instance': gl.createBuffer(), 'b_instance': gl.createBuffer(),
'b_dynamic_instance': gl.createBuffer(),
}; };
context.textures = { context.textures = {
'stroke_data': gl.createTexture(), 'stroke_data': gl.createTexture(),
'dynamic_stroke_data': gl.createTexture(),
}; };
gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']); gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
@ -348,6 +342,11 @@ function init_webgl(state, context) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16UI, config.stroke_texture_size, config.stroke_texture_size, 0, gl.RGBA_INTEGER, gl.UNSIGNED_SHORT, null); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16UI, config.stroke_texture_size, config.stroke_texture_size, 0, gl.RGBA_INTEGER, gl.UNSIGNED_SHORT, null);
gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16UI, config.dynamic_stroke_texture_size, config.dynamic_stroke_texture_size, 0, gl.RGBA_INTEGER, gl.UNSIGNED_SHORT, null);
const resize_canvas = (entries) => { const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI // https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0]; const entry = entries[0];

3
server/deserializer.js

@ -47,7 +47,7 @@ export function align(d, to) {
export function event(d) { export function event(d) {
const event = {}; const event = {};
event.type = u8(d); event.type = u32(d);
switch (event.type) { switch (event.type) {
case EVENT.PREDRAW: { case EVENT.PREDRAW: {
@ -75,7 +75,6 @@ export function event(d) {
const point_count = u16(d); const point_count = u16(d);
const width = u16(d); const width = u16(d);
const color = u32(d); const color = u32(d);
align(d, 4);
event.width = width; event.width = width;
event.color = color; event.color = color;
event.points = f32array(d, point_count * 2); event.points = f32array(d, point_count * 2);

2
server/recv.js

@ -164,7 +164,7 @@ export async function handle_message(ws, d) {
const session = sessions[ws.data.session_id]; const session = sessions[ws.data.session_id];
const desk_id = session.desk_id; const desk_id = session.desk_id;
const message_type = des.u8(d); const message_type = des.u32(d);
switch (message_type) { switch (message_type) {
case MESSAGE.FIRE: { case MESSAGE.FIRE: {

16
server/send.js

@ -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 + 4 + 3; // opcode + user_id + lsn + event count + stroke count + user count + total_point_count + align on 4 let size = 4 + 4 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count + user count + total_point_count
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) {
@ -130,7 +130,7 @@ export async function send_init(ws) {
const s = ser.create(size); const s = ser.create(size);
ser.u8(s, opcode); ser.u32(s, opcode);
ser.u32(s, session.lsn); ser.u32(s, session.lsn);
if (opcode === MESSAGE.JOIN) { if (opcode === MESSAGE.JOIN) {
@ -168,10 +168,10 @@ export function send_ack(ws, lsn) {
return; return;
} }
const size = 1 + 4; // opcode + lsn const size = 4 + 4; // opcode + lsn
const s = ser.create(size); const s = ser.create(size);
ser.u8(s, MESSAGE.ACK); ser.u32(s, MESSAGE.ACK);
ser.u32(s, lsn); ser.u32(s, lsn);
if (config.DEBUG_PRINT) console.log(`ack ${lsn} out`); if (config.DEBUG_PRINT) console.log(`ack ${lsn} out`);
@ -184,9 +184,9 @@ export function send_fire(ws, event) {
return; return;
} }
const s = ser.create(1 + 4 + event_size(event)); const s = ser.create(4 + 4 + event_size(event));
ser.u8(s, MESSAGE.FIRE); ser.u32(s, MESSAGE.FIRE);
ser.event(s, event); ser.event(s, event);
ws.send(s.buffer); ws.send(s.buffer);
@ -208,7 +208,7 @@ async function sync_session(session_id) {
return; return;
} }
let size = 1 + 4 + 4; // opcode + sn + event count let size = 4 + 4 + 4; // opcode + sn + event count
let count = desk.sn - session.sn; let count = desk.sn - session.sn;
if (count === 0) { if (count === 0) {
@ -223,7 +223,7 @@ async function sync_session(session_id) {
const s = ser.create(size); const s = ser.create(size);
ser.u8(s, MESSAGE.SYN); ser.u32(s, MESSAGE.SYN);
ser.u32(s, desk.sn); ser.u32(s, desk.sn);
ser.u32(s, count); ser.u32(s, count);

Loading…
Cancel
Save