diff --git a/client/index.html b/client/index.html index 5f0a7b2..1a9f710 100644 --- a/client/index.html +++ b/client/index.html @@ -7,20 +7,20 @@ - + - - - - - - - - + + + + + + + + - - - + + +
diff --git a/client/index.js b/client/index.js index 9fe280b..808d819 100644 --- a/client/index.js +++ b/client/index.js @@ -16,7 +16,7 @@ const config = { default_color: 0x00, default_width: 8, bytes_per_point: 8, - initial_static_bytes: 4096, + initial_static_bytes: 4096 * 16, }; const EVENT = Object.freeze({ @@ -108,6 +108,7 @@ function main() { const context = { 'canvas': null, 'gl': null, + 'debug_mode': false, 'programs': {}, 'buffers': {}, diff --git a/client/webgl_draw.js b/client/webgl_draw.js index cf1908b..2555824 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -42,10 +42,12 @@ function draw(state, context) { const npoints = context.point_serializer.offset / (4 * 2); const nstrokes = context.quad_serializer.offset / (6 * 4 * 4); + ser_align(context.point_serializer, 8192); + if (npoints > 0) { // TOOD: if points changed if (true) { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, npoints, 1, 0, gl.RG, gl.FLOAT, new Float32Array(context.point_serializer.buffer, 0, context.point_serializer.offset / 4)); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, Math.min(npoints, 8192), Math.max(1, Math.ceil(npoints / 8192)), 0, gl.RG, gl.FLOAT, new Float32Array(context.point_serializer.buffer, 0, context.point_serializer.offset / 4)); } gl.activeTexture(gl.TEXTURE0 + 1); @@ -63,6 +65,7 @@ function draw(state, context) { gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); gl.uniform1i(locations['u_texture_points'], 0); gl.uniform1i(locations['u_texture_indices'], 1); + gl.uniform1i(locations['u_debug_mode'], context.debug_mode ? 1 : 0); gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(context.quad_serializer.buffer, 0, context.quad_serializer.offset), gl.STATIC_DRAW); gl.drawArrays(gl.TRIANGLES, 0, nstrokes * 6); diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 1649989..128d86a 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -23,7 +23,23 @@ function push_quad_xyzrgb(s, p1x, p1y, p4x, p4y, z, r, g, b) { push_point_xyzrgb(s, p4x, p1y, z, r, g, b); } +const MAX_POINTS_PER_QUAD = 10; +const MAX_QUAD_SIDE = 256; + +function count_stroke_quads(points) { + let min_x, min_y, max_x, max_y; + let points_per_quad = 0; + + // TODO + + return Math.ceil(points.length / MAX_POINTS_PER_QUAD); +} + function push_stroke(context, stroke) { + // if (stroke.stroke_id !== 1123776468) { + // return; + // } + const stroke_width = stroke.width; const points = stroke.points; const color_u32 = stroke.color; @@ -36,11 +52,8 @@ function push_stroke(context, stroke) { return; } - const points_from = context.point_serializer.offset / (4 * 2); // 4 is sizeof(f32) btw, just sain' - const points_to = points_from + points.length; - - ser_u32(context.index_serializer, points_from); - ser_u32(context.index_serializer, points_to); + let points_from = context.point_serializer.offset / (4 * 2); // 4 is sizeof(f32) btw, just sain' + const points_start = points_from; let min_x, min_y, max_x, max_y; @@ -50,15 +63,58 @@ function push_stroke(context, stroke) { min_y = Math.floor(points[0].y - stroke_width / 2); max_y = Math.ceil(points[0].y + stroke_width / 2); - for (const p of points) { + let points_per_quad = 0; + + for (let i = 0; i < points.length; ++i) { + const p = points[i]; + min_x = Math.min(min_x, Math.floor(p.x - stroke_width / 2)); min_y = Math.min(min_y, Math.floor(p.y - stroke_width / 2)); max_x = Math.max(max_x, Math.ceil(p.x + stroke_width / 2)); max_y = Math.max(max_y, Math.ceil(p.y + stroke_width / 2)); + push_point_xy(context.point_serializer, p.x, p.y); + + points_per_quad++; + + if (points_per_quad == MAX_POINTS_PER_QUAD) { + let points_to = points_from + MAX_POINTS_PER_QUAD; + + if (points_from > points_start) { + // 1 point overlap to prevent gaps + ser_u32(context.index_serializer, points_from - 1); + } else { + ser_u32(context.index_serializer, points_from); + } + + ser_u32(context.index_serializer, points_to); + + push_quad_xyzrgb(context.quad_serializer, min_x, min_y, max_x, max_y, stroke_width / 2, r, g, b); + + min_x = Math.floor(p.x - stroke_width / 2); + max_x = Math.ceil(p.x + stroke_width / 2); + + min_y = Math.floor(p.y - stroke_width / 2); + max_y = Math.ceil(p.y + stroke_width / 2); + + points_from = points_to; + points_per_quad = 0; + } } - push_quad_xyzrgb(context.quad_serializer, min_x, min_y, max_x, max_y, stroke_width / 2, r, g, b); + if (points_per_quad > 0) { + const points_to = points_from + points_per_quad; + + if (points_from > points_start) { + ser_u32(context.index_serializer, points_from - 1); + } else { + ser_u32(context.index_serializer, points_from); + } + + ser_u32(context.index_serializer, points_to); + + push_quad_xyzrgb(context.quad_serializer, min_x, min_y, max_x, max_y, stroke_width / 2, r, g, b); + } } function geometry_prepare_stroke(state) { @@ -76,17 +132,35 @@ function geometry_prepare_stroke(state) { function geometry_add_stroke(state, context, stroke) { if (!state.online || !stroke) return; + if (stroke.points.length < 2) return; - const bytes_left = context.point_serializer.size - context.point_serializer.offset; - const bytes_needed = stroke.points.length * config.bytes_per_point; + const stroke_quads = count_stroke_quads(stroke.points); + // const stroke_quads = Math.ceil(stroke.points.length / MAX_POINTS_PER_QUAD); - if (bytes_left < bytes_needed) { - const extend_points_by = Math.ceil((context.point_serializer.size + bytes_needed) * 1.62); - const extend_indices_by = Math.ceil((context.index_serializer.size + stroke.points.length * 4 * 2) * 1.62); - const extend_quads_by = Math.ceil((context.quad_serializer.size + 6 * (4 * 3)) * 1.62); + // Points + const point_bytes_left = context.point_serializer.size - context.point_serializer.offset; + const point_bytes_needed = stroke.points.length * config.bytes_per_point; + if (point_bytes_left < point_bytes_needed) { + const extend_points_by = Math.ceil((context.point_serializer.size + point_bytes_needed) * 1.62); context.point_serializer = ser_extend(context.point_serializer, extend_points_by); + } + + // Indices + const index_bytes_left = context.index_serializer.size - context.index_serializer.offset; + const index_bytes_needed = stroke_quads * (4 * 2); + + if (index_bytes_left < index_bytes_needed) { + const extend_indices_by = Math.ceil((context.index_serializer.size + index_bytes_needed) * 1.62); context.index_serializer = ser_extend(context.index_serializer, extend_indices_by); + } + + // Quads + const quad_bytes_left = context.quad_serializer.size - context.quad_serializer.offset; + const quad_bytes_needed = stroke_quads * 6 * (4 * 4); + + if (quad_bytes_left < quad_bytes_needed) { + const extend_quads_by = Math.ceil((context.quad_serializer.size + quad_bytes_needed) * 1.62); context.quad_serializer = ser_extend(context.quad_serializer, extend_quads_by); } @@ -151,6 +225,8 @@ function geometry_clear_player(state, context, player_id) { } function add_image(context, image_id, bitmap, p) { + return; // TODO + const x = p.x; const y = p.y; const gl = context.gl; @@ -161,7 +237,7 @@ function add_image(context, image_id, bitmap, p) { 'image_id': image_id }; - gl.bindTexture(gl.TEXTURE_2D, context.textures[id].texture); + gl.bindTexture(gl.TEXTURE_2D, context.textures['image'][id].texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index 1c9dc35..6004479 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -46,6 +46,31 @@ function keydown(e, state, context) { } else if (e.code === 'Tab') { e.preventDefault(); zenmode(); + } else if (e.code === 'KeyZ') { + const topleft = screen_to_canvas(state, {'x': 0, 'y': 0}); + const bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height}); + + for (let i = 0; i < state.events.length; ++i) { + const event = state.events[i]; + + if (event.type === EVENT.STROKE) { + let on_screen = false; + + for (const p of event.points) { + if (topleft.x <= p.x && p.x <= bottomright.x && topleft.y <= p.y && p.y <= bottomright.y) { + on_screen = true; + break; + } + } + + if (on_screen) { + console.log(i); + } + } + } + } else if (e.code === 'KeyD') { + context.debug_mode = !context.debug_mode; + schedule_draw(state, context); } } diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index 40980d0..efe4bbb 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -22,15 +22,16 @@ const sdf_vs_src = `#version 300 es v_color = a_color; v_thickness = a_pos.z; - gl_Position = vec4(screen02 - 1.0, 0, 1); + gl_Position = vec4(screen02 - 1.0, 0.0, 1); } `; const sdf_fs_src = `#version 300 es - precision mediump float; + precision highp float; uniform sampler2D u_texture_points; uniform highp usampler2D u_texture_indices; + uniform int u_debug_mode; in vec2 v_texcoord; in vec3 v_color; @@ -41,7 +42,7 @@ const sdf_fs_src = `#version 300 es out vec4 FragColor; void main() { - float mindist = 99999.9; + float mindist = 99999999.9; uvec4 indices = texelFetch(u_texture_indices, ivec2(v_vertexid / 6, 0), 0); @@ -49,8 +50,14 @@ const sdf_fs_src = `#version 300 es uint v_to = indices.y; for (uint i = v_from; i < v_to - uint(1); ++i) { - vec4 a = texelFetch(u_texture_points, ivec2(i, 0), 0); - vec4 b = texelFetch(u_texture_points, ivec2(i + uint(1), 0), 0); + uint x1 = i % uint(8192); + uint y1 = i / uint(8192); + + uint x2 = (i + uint(1)) % uint(8192); + uint y2 = (i + uint(1)) / uint(8192); + + vec4 a = texelFetch(u_texture_points, ivec2(x1, y1), 0); + vec4 b = texelFetch(u_texture_points, ivec2(x2, y2), 0); vec2 pa = v_texcoord - a.xy, ba = b.xy - a.xy; float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); @@ -62,8 +69,15 @@ const sdf_fs_src = `#version 300 es float fade = 0.5 * length(fwidth(v_texcoord)); float alpha = 1.0 - smoothstep(-fade, fade, mindist); - FragColor = vec4(v_color * alpha, alpha); - // FragColor = vec4(v_color, 1.0); + if (u_debug_mode == 1) { + uint x1 = v_from % uint(8192); + uint y1 = v_from / uint(8192); + vec4 a = texelFetch(u_texture_points, ivec2(x1, y1), 0); + FragColor = vec4(a.xy, 0.0, 1.0); + } else { + FragColor = vec4(v_color * alpha, alpha); + // FragColor = vec4(v_color * alpha, 0.1 + alpha); + } } `; @@ -88,7 +102,7 @@ const tquad_vs_src = `#version 300 es `; const tquad_fs_src = `#version 300 es - precision mediump float; + precision highp float; in vec2 v_texcoord; @@ -148,6 +162,7 @@ function init_webgl(state, context) { 'u_translation': gl.getUniformLocation(context.programs['sdf'], 'u_translation'), 'u_texture_points': gl.getUniformLocation(context.programs['sdf'], 'u_texture_points'), 'u_texture_indices': gl.getUniformLocation(context.programs['sdf'], 'u_texture_indices'), + 'u_debug_mode': gl.getUniformLocation(context.programs['sdf'], 'u_debug_mode') }; context.buffers['image'] = { @@ -164,6 +179,8 @@ function init_webgl(state, context) { 'indices': gl.createTexture() }; + context.textures['image'] = {}; + const resize_canvas = (entries) => { // https://www.khronos.org/webgl/wiki/HandlingHighDPI const entry = entries[0];