diff --git a/client/bvh.js b/client/bvh.js
index c499c55..fc270f5 100644
--- a/client/bvh.js
+++ b/client/bvh.js
@@ -91,10 +91,6 @@ function bvh_find_best_sibling(bvh, leaf_index) {
return best_index;
}
-function bvh_rotate(bvh, index) {
-
-}
-
function bvh_add_stroke(bvh, index, stroke) {
const leaf_index = bvh_make_leaf(bvh, index, stroke);
@@ -147,7 +143,7 @@ function bvh_add_stroke(bvh, index, stroke) {
bvh.nodes[new_parent].bbox = new_bbox;
bvh.nodes[new_parent].area = (new_bbox.x2 - new_bbox.x1) * (new_bbox.y2 - new_bbox.y1);
- // 3. Refit and rotate
+ // 3. Refit
let refit_index = bvh.nodes[leaf_index].parent_index;
while (refit_index !== null) {
const child1 = bvh.nodes[refit_index].child1;
@@ -155,8 +151,6 @@ function bvh_add_stroke(bvh, index, stroke) {
bvh.nodes[refit_index].bbox = quad_union(bvh.nodes[child1].bbox, bvh.nodes[child2].bbox);
- bvh_rotate(bvh, refit_index);
-
refit_index = bvh.nodes[refit_index].parent_index;
}
}
@@ -187,48 +181,53 @@ function bvh_intersect_quad(bvh, quad) {
return result;
}
-function bvh_clip(state, context) {
- if (state.onscreen_segments.length < Math.ceil(state.total_points * 6 * 1.2)) {
- state.onscreen_segments = new Uint32Array(state.total_points * 6 * 2);
- }
+function bvh_clip(state, context, lod_level) {
+ const lod = context.lods[lod_level];
- let at = 0;
+ lod.indices = ser_ensure(lod.indices, lod.total_points * 6 * 4);
+ 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_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};
+ 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
+ };
const stroke_indices = bvh_intersect_quad(state.bvh, screen);
+
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) {
- for (let j = 0; j < event.points.length - 1; ++j) {
- let base = event.starting_index + j * 4;
+ 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]
- state.onscreen_segments[at + 0] = base + 0;
- state.onscreen_segments[at + 1] = base + 1;
- state.onscreen_segments[at + 2] = base + 2;
- state.onscreen_segments[at + 3] = base + 3;
- state.onscreen_segments[at + 4] = base + 2;
- state.onscreen_segments[at + 5] = base + 1;
-
- at += 6;
+ 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 at;
-
+ return lod.indices.offset / 4;
}
function bvh_construct_rec(bvh, vertical, strokes) {
@@ -263,5 +262,7 @@ function bvh_construct_rec(bvh, vertical, strokes) {
}
function bvh_construct(state) {
- state.bvh.root = bvh_construct_rec(state.bvh, true, state.events);
+ if (state.events.length > 0) {
+ state.bvh.root = bvh_construct_rec(state.bvh, true, state.events);
+ }
}
diff --git a/client/client_recv.js b/client/client_recv.js
index f82d77a..e3743b5 100644
--- a/client/client_recv.js
+++ b/client/client_recv.js
@@ -80,6 +80,7 @@ function des_event(d) {
event.stroke_id = stroke_id;
event.points = [];
+ event.lods = [];
for (let i = 0; i < point_count; ++i) {
const x = coords[2 * i + 0];
@@ -176,16 +177,7 @@ function handle_event(state, context, event, options = {}) {
geometry_clear_player(state, context, event.user_id);
need_draw = true;
//}
-
- event.index = state.events.length;
- event.starting_index = state.starting_index;
-
- if (event.points.length > 1) {
- state.starting_index += (event.points.length - 1) * 4;
- }
-
- state.total_points += event.points.length;
-
+
geometry_add_stroke(state, context, event, state.events.length, options.skip_bvh === true);
state.stroke_count++;
diff --git a/client/client_send.js b/client/client_send.js
index b95d104..980cdc9 100644
--- a/client/client_send.js
+++ b/client/client_send.js
@@ -6,18 +6,41 @@ function serializer_create(size) {
'buffer': buffer,
'view': new DataView(buffer),
'strview': new Uint8Array(buffer),
+
+ 'need_gpu_allocate': true, // need to call glBufferData to create a GPU buffer of size serializer.size
+ 'gpu_upload_from': 0, // need to call glBufferSubData for bytes in [serializer.gpu_upload_from, serializer.offset)
};
}
-function ser_extend(s, by) {
- const old_view = s.strview;
- const old_offset = s.offset;
- const s_copy = serializer_create(s.size + by)
+function ser_ensure(s, size) {
+ if (s.size < size) {
+ const new_s = serializer_create(Math.ceil(size * 2));
+
+ new_s.strview.set(s.strview);
+ new_s.offset = s.offset;
+
+ return new_s;
+ }
+
+ return s;
+}
+
+function ser_ensure_by(s, by) {
+ if (s.offset + by > s.size) {
+ const new_s = serializer_create(Math.ceil((s.size + by) * 2));
+
+ new_s.strview.set(s.strview);
+ new_s.offset = s.offset;
+
+ return new_s;
+ }
- s_copy.strview.set(old_view);
- s_copy.offset = old_offset;
+ return s;
+}
- return s_copy;
+function ser_clear(s) {
+ s.offset = 0;
+ s.gpu_upload_from = 0;
}
function ser_u8(s, value) {
@@ -294,4 +317,4 @@ function clear_event(state) {
return {
'type': EVENT.CLEAR
};
-}
\ No newline at end of file
+}
diff --git a/client/index.js b/client/index.js
index de15e72..80d34ee 100644
--- a/client/index.js
+++ b/client/index.js
@@ -19,15 +19,14 @@ const config = {
second_finger_timeout: 500,
buffer_first_touchmoves: 5,
debug_print: false,
- min_zoom: 0.01,
- max_zoom: 1000,
+ min_zoom: 0.00001,
+ max_zoom: 1,
initial_offline_timeout: 1000,
default_color: 0x00,
default_width: 8,
bytes_per_point: 9 * 4,
initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096,
- frametime_window_size: 100,
tile_size: 16,
clip_zoom_threshold: 0.00003,
};
@@ -207,30 +206,20 @@ function main() {
const context = {
'canvas': null,
'gl': null,
- 'frametime_window': [],
- 'frametime_window_head': 0,
-
- 'static_upload_from': 0,
- 'need_static_allocate': true,
- 'need_static_upload': true,
- 'need_dynamic_upload': false,
- 'need_index_upload': true,
-
- 'full_index_count': 0,
'programs': {},
'buffers': {},
'locations': {},
'textures': {},
- 'framebuffers': {},
- '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),
+ 'lods': [],
+
'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0},
+ 'gpu_timer_ext': null,
'active_image': null,
};
@@ -238,7 +227,20 @@ function main() {
const url = new URL(window.location.href);
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.5, 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,
+ 'vertices': serializer_create(config.initial_static_bytes),
+ 'indices': serializer_create(config.initial_static_bytes),
+ 'data_buffer': null,
+ 'index_buffer': null,
+ });
+ }
+
state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0;
init_webgl(state, context);
diff --git a/client/math.js b/client/math.js
index 6a2ef52..71d58fb 100644
--- a/client/math.js
+++ b/client/math.js
@@ -60,6 +60,25 @@ function process_rdp_r(state, points, start, end) {
return result;
}
+function rdp_indices_r(zoom, points, start, end) {
+ let result = [];
+
+ const max = rdp_find_max({'canvas': {'zoom': zoom}}, points, start, end);
+
+ if (max !== -1) {
+ const before = rdp_indices_r(zoom, points, start, max);
+ const after = rdp_indices_r(zoom, points, max, end);
+ result = [...before, max, ...after];
+ }
+
+ return result;
+}
+
+function rdp_indices(zoom, points) {
+ const result = [0, ...rdp_indices_r(zoom, points, 0, points.length - 1), points.length - 1];
+ return result;
+}
+
function process_rdp(state, points) {
const result = process_rdp_r(state, points, 0, points.length - 1);
result.unshift(points[0]);
diff --git a/client/webgl_draw.js b/client/webgl_draw.js
index db7c470..41e0a66 100644
--- a/client/webgl_draw.js
+++ b/client/webgl_draw.js
@@ -7,30 +7,38 @@ function schedule_draw(state, context) {
}
}
-function upload_if_needed(context) {
- const gl = context.gl;
-
- if (context.need_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;
- context.need_static_upload = true;
+function upload_if_needed(gl, buffer_kind, serializer) {
+ if (serializer.need_gpu_allocate) {
+ if (config.debug_print) console.debug('gpu allocate');
+ gl.bufferData(buffer_kind, serializer.size, gl.DYNAMIC_DRAW);
+ serializer.need_gpu_allocate = false;
+ serializer.gpu_upload_from = 0;
}
- if (context.need_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));
- context.need_static_upload = false;
- context.static_upload_from = context.static_serializer.offset;
+ if (serializer.gpu_upload_from < serializer.offset) {
+ if (config.debug_print) console.debug('gpu upload');
+ const upload_offset = serializer.gpu_upload_from;
+ const upload_size = serializer.offset - upload_offset;
+ gl.bufferSubData(buffer_kind, upload_offset, new Uint8Array(serializer.buffer, upload_offset, upload_size));
+ serializer.gpu_upload_from = serializer.offset;
}
}
function draw(state, context) {
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;
const gl = context.gl;
@@ -43,122 +51,81 @@ function draw(state, context) {
query = gl.createQuery();
gl.beginQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT, query);
}
-
- let locations;
- let buffers;
-
- buffers = context.buffers['sdf'];
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed_static']);
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['b_packed_static_index']);
- upload_if_needed(context);
-
+
gl.viewport(0, 0, context.canvas.width, context.canvas.height);
gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1);
gl.clearDepth(0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- let index_count;
- const do_clip = true;//(state.canvas.zoom > config.clip_zoom_threshold);
+ const before_clip = performance.now();
+ const index_count = bvh_clip(state, context, lod_level);
+ const after_clip = performance.now();
- if (do_clip) {
- context.need_index_upload = true;
- }
+ gl.bindBuffer(gl.ARRAY_BUFFER, lod.data_buffer);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, lod.index_buffer);
- if (do_clip || context.need_index_upload) {
- const before_clip = performance.now();
- index_count = bvh_clip(state, context);
- const after_clip = performance.now();
- }
-
- if (!do_clip && !context.need_index_upload) {
- index_count = context.full_index_count;
- }
-
+ upload_if_needed(gl, gl.ARRAY_BUFFER, lod.vertices);
+ upload_if_needed(gl, gl.ELEMENT_ARRAY_BUFFER, lod.indices);
document.getElementById('debug-stats').innerHTML = `
- Segments onscreen: ${do_clip ? index_count : '-' }
+ LOD level: ${lod_level}
+ Segments onscreen: ${index_count}
Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y})
Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000}`;
if (index_count > 0) {
- const index_buffer = new Uint32Array(state.onscreen_segments.buffer, 0, index_count);
- const static_points = context.static_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) {
- // DEPTH PREPASS
- if (state.debug.do_prepass && do_clip) {
- 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);
-
- 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);
- }
-
- // MAIN PASS
- gl.drawBuffers([gl.BACK]);
+ // DEPTH PREPASS
+ if (state.debug.do_prepass) {
+ gl.drawBuffers([gl.NONE]);
- locations = context.locations['sdf'].main;
+ locations = context.locations['sdf'].opaque;
- gl.useProgram(context.programs['sdf'].main);
+ 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.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);
- if (do_clip) {
- 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);
}
+
+ // 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;
@@ -219,7 +186,7 @@ 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);
@@ -259,7 +226,7 @@ function draw(state, context) {
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);
@@ -289,52 +256,7 @@ function draw(state, context) {
setTimeout(next_tick, 0);
}
- // Images
- // locations = context.locations['image'];
- // buffers = context.buffers['image'];
- // textures = context.textures['image'];
-
- // gl.useProgram(context.programs['image']);
- // gl.enableVertexAttribArray(locations['a_pos']);
- // gl.enableVertexAttribArray(locations['a_texcoord']);
-
- // 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_texture'], 0);
-
- // if (context.quad_positions_f32.byteLength > 0) {
- // gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']);
- // gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0);
- // gl.bufferData(gl.ARRAY_BUFFER, context.quad_positions_f32, gl.STATIC_DRAW);
-
- // gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']);
- // gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0);
- // gl.bufferData(gl.ARRAY_BUFFER, context.quad_texcoords_f32, gl.STATIC_DRAW);
- // }
-
- // const count = Object.keys(textures).length;
- // let active_image_index = -1;
-
- // gl.uniform1i(locations['u_outline'], 0);
-
- // for (let key = 0; key < count; ++key) {
- // if (textures[key].image_id === context.active_image) {
- // active_image_index = key;
- // continue;
- // }
-
- // gl.bindTexture(gl.TEXTURE_2D, textures[key].texture);
- // gl.drawArrays(gl.TRIANGLES, key * 6, 6);
- // }
-
- // if (active_image_index !== -1) {
- // gl.uniform1i(locations['u_outline'], 1);
- // gl.bindTexture(gl.TEXTURE_2D, textures[active_image_index].texture);
- // gl.drawArrays(gl.TRIANGLES, active_image_index * 6, 6);
- // }
- //
const cpu_after = performance.now();
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index 367b211..11c71d7 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -96,22 +96,52 @@ function geometry_prepare_stroke(state) {
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {
if (!state.online || !stroke || stroke.points.length === 0) return;
- stroke.bbox = stroke_bbox(stroke);
- stroke.area = (stroke.bbox.x2 - stroke.bbox.x1) * (stroke.bbox.y2 - stroke.bbox.y1);
+ stroke.index = state.events.length;
+
+ for (let i = 0; i < config.lod_levels; ++i) {
+ // TODO: just pass zoom to process_stroke ?
+ const saved_zoom = state.canvas.zoom;
+ state.canvas.zoom = Math.pow(0.5, i);
+ const points = (i > 0 ? process_stroke(state, stroke.points) : stroke.points);
+ state.canvas.zoom = saved_zoom;
+
+ const vertex_serializer = context.lods[i].vertices = ser_ensure_by(context.lods[i].vertices, points.length * 4 * config.bytes_per_point);
+ /*
+ event.index = state.events.length;
+ event.starting_index = state.starting_index;
+
+ if (event.points.length > 1) {
+ state.starting_index += (event.points.length - 1) * 4;
+ }
- let bytes_left = context.static_serializer.size - context.static_serializer.offset;
- let bytes_needed = stroke.points.length * 4 * config.bytes_per_point;
+ state.total_points += event.points.length;
+ */
- if (bytes_left < bytes_needed) {
- const extend_to = Math.ceil((context.static_serializer.size + bytes_needed) * 1.62 / 4) * 4;
- context.static_serializer = ser_extend(context.static_serializer, extend_to);
- context.need_static_allocate = true;
- }
+ let starting_index = 0;
- push_stroke(context.static_serializer, stroke, stroke_index);
- if (!skip_bvh) bvh_add_stroke(state.bvh, stroke_index, stroke);
+ if (state.events.length > 0) {
+ const last_stroke = state.events[stroke_index - 1].lods[i];
+ starting_index = last_stroke.starting_index + (last_stroke.points.length - 1) * 4;
+ }
+
+ stroke.lods.push({
+ 'points': points,
+ 'starting_index': starting_index,
+ 'width': stroke.width,
+ 'color': stroke.color,
+ });
- context.need_static_upload = true;
+ context.lods[i].total_points += points.length;
+
+ push_stroke(vertex_serializer, stroke.lods[stroke.lods.length - 1], stroke_index);
+
+ if (i === 0) {
+ stroke.bbox = stroke_bbox(stroke);
+ stroke.area = (stroke.bbox.x2 - stroke.bbox.x1) * (stroke.bbox.y2 - stroke.bbox.y1);
+ }
+ }
+
+ if (!skip_bvh) bvh_add_stroke(state.bvh, stroke_index, stroke);
}
function geometry_delete_stroke(state, context, stroke_index) {
diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js
index b96a8ac..c451d09 100644
--- a/client/webgl_shaders.js
+++ b/client/webgl_shaders.js
@@ -187,7 +187,7 @@ const sdf_fs_src = `#version 300 es
FragColor = vec4(v_color * alpha, alpha);
} else {
- FragColor = vec4(1.0, 0.0, 0.0, 1.0 / 32.0);
+ FragColor = vec4(0.2, 0.0, 0.0, 0.2);
}
}
`;
@@ -246,12 +246,12 @@ function init_webgl(state, context) {
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.GEQUAL);
-
+/*
context.gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (context.gpu_timer_ext === null) {
context.gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query');
}
-
+*/
const quad_vs = create_shader(gl, gl.VERTEX_SHADER, tquad_vs_src);
const quad_fs = create_shader(gl, gl.FRAGMENT_SHADER, tquad_fs_src);
@@ -306,14 +306,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'] = {
'b_packed': gl.createBuffer(),
};
context.buffers['sdf'] = {
- 'b_packed_static': gl.createBuffer(),
'b_packed_dynamic': gl.createBuffer(),
- 'b_packed_static_index': gl.createBuffer(),
'b_packed_dynamic_index': gl.createBuffer(),
};
diff --git a/server/config.js b/server/config.js
index 1ce0326..d70e101 100644
--- a/server/config.js
+++ b/server/config.js
@@ -2,5 +2,6 @@ export const HOST = '127.0.0.1';
export const PORT = 3003;
export const DATADIR = 'data';
export const SYNC_TIMEOUT = 1000;
+export const SYNC_MAX_ATTEMPTS = 3;
export const IMAGEDIR = 'images';
-export const DEBUG_PRINT = true;
\ No newline at end of file
+export const DEBUG_PRINT = true;
diff --git a/server/math.js b/server/math.js
index d9f27b5..ded4ae2 100644
--- a/server/math.js
+++ b/server/math.js
@@ -8,7 +8,3 @@ export function crypto_random32() {
return dataview.getUint32(0);
}
-
-export function fast_random32() {
- return Math.floor(Math.random() * 4294967296);
-}
\ No newline at end of file
diff --git a/server/milton.js b/server/milton.js
index bdb442a..fc24fe1 100644
--- a/server/milton.js
+++ b/server/milton.js
@@ -7,7 +7,6 @@ let first_point_x = null;
let first_point_y = null;
function parse_and_insert_stroke(desk_id, line) {
- const stroke_id = math.fast_random32();
const words = line.split(' ');
const width = parseInt(words.shift());
const points = new Float32Array(words.map(i => parseFloat(i)));
diff --git a/server/recv.js b/server/recv.js
index 82b2932..daa0e65 100644
--- a/server/recv.js
+++ b/server/recv.js
@@ -13,6 +13,7 @@ function recv_ack(d, session) {
session.state = SESSION.READY;
session.sn = sn;
+ session.sync_attempts = 0;
if (config.DEBUG_PRINT) console.log(`ack ${sn} in`);
}
@@ -108,15 +109,14 @@ function recv_fire(d, session) {
function handle_event(session, event) {
switch (event.type) {
case EVENT.STROKE: {
- event.stroke_id = math.fast_random32();
-
- storage.queries.insert_stroke.run({
- '$id': event.stroke_id,
+ const stroke_result = storage.queries.insert_stroke.get({
'$width': event.width,
'$color': event.color,
'$points': event.points
});
+ event.stroke_id = stroke_result.id;
+
storage.queries.insert_event.run({
'$type': event.type,
'$desk_id': session.desk_id,
diff --git a/server/send.js b/server/send.js
index 80907b5..5032630 100644
--- a/server/send.js
+++ b/server/send.js
@@ -232,8 +232,11 @@ async function sync_session(session_id) {
if (config.DEBUG_PRINT) console.log(`syn ${desk.sn} out`);
await session.ws.send(s.buffer);
-
- session.sync_timer = setTimeout(() => sync_session(session_id), config.SYNC_TIMEOUT);
+
+ if (session.sync_attempts < config.SYNC_MAX_ATTEMPTS) {
+ session.sync_attempts += 1;
+ session.sync_timer = setTimeout(() => sync_session(session_id), config.SYNC_TIMEOUT);
+ }
}
export function sync_desk(desk_id) {
diff --git a/server/storage.js b/server/storage.js
index ba9c274..61af85c 100644
--- a/server/storage.js
+++ b/server/storage.js
@@ -68,10 +68,10 @@ export function startup() {
);`).run();
// INSERT
- queries.insert_desk = db.query('INSERT INTO desks (id, title, sn) VALUES ($id, $title, 0)');
- queries.insert_stroke = db.query('INSERT INTO strokes (id, width, color, points) VALUES ($id, $width, $color, $points) RETURNING id');
- queries.insert_session = db.query('INSERT INTO sessions (id, desk_id, lsn) VALUES ($id, $desk_id, 0)');
- queries.insert_event = db.query('INSERT INTO events (type, desk_id, session_id, stroke_id, image_id, x, y) VALUES ($type, $desk_id, $session_id, $stroke_id, $image_id, $x, $y)');
+ queries.insert_desk = db.query('INSERT INTO desks (id, title, sn) VALUES ($id, $title, 0) RETURNING id');
+ queries.insert_stroke = db.query('INSERT INTO strokes (width, color, points) VALUES ($width, $color, $points) RETURNING id');
+ queries.insert_session = db.query('INSERT INTO sessions (id, desk_id, lsn) VALUES ($id, $desk_id, 0) RETURNING id');
+ queries.insert_event = db.query('INSERT INTO events (type, desk_id, session_id, stroke_id, image_id, x, y) VALUES ($type, $desk_id, $session_id, $stroke_id, $image_id, $x, $y) RETURNING id');
// UPDATE
queries.update_desk_sn = db.query('UPDATE desks SET sn = $sn WHERE id = $id');
@@ -124,6 +124,7 @@ export function startup() {
for (const session of stored_sessions) {
session.state = SESSION.CLOSED;
session.ws = null;
+ session.sync_attempts = 0;
sessions[session.id] = session;
}
}