From 1438b0ad732b843929c56473f41fd700a6c22f35 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Wed, 20 Dec 2023 02:26:14 +0300 Subject: [PATCH] LODs work! Need to fix that memory usage though --- client/bvh.js | 63 ++++++------ client/client_recv.js | 12 +-- client/client_send.js | 39 +++++-- client/index.js | 36 +++---- client/math.js | 19 ++++ client/webgl_draw.js | 214 +++++++++++++-------------------------- client/webgl_geometry.js | 54 +++++++--- client/webgl_shaders.js | 14 ++- server/config.js | 3 +- server/math.js | 4 - server/milton.js | 1 - server/recv.js | 8 +- server/send.js | 7 +- server/storage.js | 9 +- 14 files changed, 238 insertions(+), 245 deletions(-) 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; } }