diff --git a/client/index.js b/client/index.js index 808d819..7cd68a4 100644 --- a/client/index.js +++ b/client/index.js @@ -15,7 +15,7 @@ const config = { initial_offline_timeout: 1000, default_color: 0x00, default_width: 8, - bytes_per_point: 8, + bytes_per_point: 8 * 4, initial_static_bytes: 4096 * 16, }; @@ -115,9 +115,7 @@ function main() { 'locations': {}, 'textures': {}, - 'point_serializer': serializer_create(config.initial_static_bytes), - 'index_serializer': serializer_create(config.initial_static_bytes), - 'quad_serializer': serializer_create(config.initial_static_bytes), + 'static_serializer': serializer_create(config.initial_static_bytes), 'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0}, diff --git a/client/webgl_draw.js b/client/webgl_draw.js index bb998b6..055ba8f 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -22,53 +22,32 @@ function draw(state, context) { // SDF locations = context.locations['sdf']; buffers = context.buffers['sdf']; - textures = context.textures['sdf']; gl.useProgram(context.programs['sdf']); gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed']); gl.enableVertexAttribArray(locations['a_pos']); + gl.enableVertexAttribArray(locations['a_line']); gl.enableVertexAttribArray(locations['a_color']); - gl.vertexAttribPointer(locations['a_pos'], 3, gl.FLOAT, false, 4 * 4, 0); - gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, 4 * 4, 4 * 3); + 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.activeTexture(gl.TEXTURE0 + 0); - gl.bindTexture(gl.TEXTURE_2D, textures['points']); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + const npoints = context.static_serializer.offset / config.bytes_per_point; - const npoints = context.point_serializer.offset / (4 * 2); - const nstrokes = context.quad_serializer.offset / (6 * 4 * 4); + // TODO: if changed if (npoints > 0) { - // TOOD: if points changed - if (true) { - const texture_width = Math.min(npoints, 8192); - const texture_height = Math.max(1, Math.ceil(npoints / 8192)); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, texture_width, texture_height, 0, gl.RG, gl.FLOAT, new Float32Array(context.point_serializer.buffer, 0, context.point_serializer.size / 4)); - } - - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, textures['indices']); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - - // TOOD: if points changed - if (true) { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32UI, nstrokes, 1, 0, gl.RG_INTEGER, gl.UNSIGNED_INT, new Uint32Array(context.index_serializer.buffer, 0, context.index_serializer.offset / 4)); - } - 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_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); + gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(context.static_serializer.buffer, 0, context.static_serializer.offset), gl.STATIC_DRAW); + + gl.drawArrays(gl.TRIANGLES, 0, npoints); } // Images diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index ade6a76..6ea340c 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -1,38 +1,25 @@ -function push_point_xy(s, x, y) { +function push_point(s, x, y, ax, ay, bx, by, thickness, r, g, b) { ser_f32(s, x); ser_f32(s, y); -} - -function push_point_xyzrgb(s, x, y, z, r, g, b) { - ser_f32(s, x); - ser_f32(s, y); - ser_f32(s, z); + ser_f32(s, thickness); + ser_f32(s, ax); + ser_f32(s, ay); + ser_f32(s, bx); + ser_f32(s, by); ser_u8(s, r); ser_u8(s, g); ser_u8(s, b); ser_align(s, 4); } -function push_quad_xyzrgb(s, p1x, p1y, p4x, p4y, z, r, g, b) { - push_point_xyzrgb(s, p1x, p1y, z, r, g, b); - push_point_xyzrgb(s, p4x, p1y, z, r, g, b); - push_point_xyzrgb(s, p1x, p4y, z, r, g, b); - - push_point_xyzrgb(s, p4x, p4y, z, r, g, b); - push_point_xyzrgb(s, p1x, 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 +function push_quad(s, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, ax, ay, bx, by, thickness, r, g, b) { + push_point(s, p1x, p1y, ax, ay, bx, by, thickness, r, g, b); + push_point(s, p2x, p2y, ax, ay, bx, by, thickness, r, g, b); + push_point(s, p3x, p3y, ax, ay, bx, by, thickness, r, g, b); - return Math.ceil(points.length / MAX_POINTS_PER_QUAD); + push_point(s, p4x, p4y, ax, ay, bx, by, thickness, r, g, b); + push_point(s, p3x, p3y, ax, ay, bx, by, thickness, r, g, b); + push_point(s, p2x, p2y, ax, ay, bx, by, thickness, r, g, b); } function push_stroke(context, stroke) { @@ -43,77 +30,78 @@ function push_stroke(context, stroke) { const stroke_width = stroke.width; const points = stroke.points; const color_u32 = stroke.color; + const radius = stroke_width / 2; - const r = (color_u32 >> 16) & 0xFF; - const g = (color_u32 >> 8) & 0xFF; - const b = color_u32 & 0xFF; - - if (points.length === 0) { + if (points.length < 2) { return; } - 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; - - min_x = Math.floor(points[0].x - stroke_width / 2); - max_x = Math.ceil(points[0].x + stroke_width / 2); - - min_y = Math.floor(points[0].y - stroke_width / 2); - max_y = Math.ceil(points[0].y + stroke_width / 2); - - let points_per_quad = 0; + const r = (color_u32 >> 16) & 0xFF; + const g = (color_u32 >> 8) & 0xFF; + const b = color_u32 & 0xFF; - for (let i = 0; i < points.length; ++i) { - const p = points[i]; + for (let i = 0; i < points.length - 1; ++i) { + const from = points[i]; + const to = points[i + 1]; - 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)); + const dir_x = to.x - from.x; + const dir_y = to.y - from.y; + const len = Math.sqrt(dir_x * dir_x + dir_y * dir_y); - push_point_xy(context.point_serializer, p.x, p.y); + const dir1_x = dir_x / len; + const dir1_y = dir_y / len; - points_per_quad++; - - if (points_per_quad == MAX_POINTS_PER_QUAD) { - let points_to = points_from + MAX_POINTS_PER_QUAD; + const up_x = dir_y / len; + const up_y = -dir_x / len; - 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); - } + let p1_x = from.x + (up_x - dir1_x) * radius; + let p1_y = from.y + (up_y - dir1_y) * radius; - ser_u32(context.index_serializer, points_to); + let p2_x = to.x + (up_x + dir1_x) * radius; + let p2_y = to.y + (up_y + dir1_y) * radius; - push_quad_xyzrgb(context.quad_serializer, min_x, min_y, max_x, max_y, stroke_width / 2, r, g, b); + let p3_x = from.x + (-up_x - dir1_x) * radius; + let p3_y = from.y + (-up_y - dir1_y) * radius; - min_x = Math.floor(p.x - stroke_width / 2); - max_x = Math.ceil(p.x + stroke_width / 2); + let p4_x = to.x + (-up_x + dir1_x) * radius; + let p4_y = to.y + (-up_y + dir1_y) * radius; - min_y = Math.floor(p.y - stroke_width / 2); - max_y = Math.ceil(p.y + stroke_width / 2); + // TODO: floor, ceil corners depending on direction of line - points_from = points_to; - points_per_quad = 0; + if (p1_x < p2_x) { + p1_x = Math.floor(p1_x); + p3_x = Math.floor(p3_x); + p2_x = Math.ceil(p2_x); + p4_x = Math.ceil(p4_x); + } else { + p1_x = Math.ceil(p1_x); + p3_x = Math.ceil(p3_x); + p2_x = Math.floor(p2_x); + p4_x = Math.floor(p4_x); } - } - - 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); + if (p1_y < p2_y) { + p1_y = Math.floor(p1_y); + p3_y = Math.floor(p3_y); + p2_y = Math.ceil(p2_y); + p4_y = Math.ceil(p4_y); } else { - ser_u32(context.index_serializer, points_from); + p1_y = Math.ceil(p1_y); + p3_y = Math.ceil(p3_y); + p2_y = Math.floor(p2_y); + p4_y = Math.floor(p4_y); } - 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); + push_quad(context.static_serializer, + p1_x, p1_y, + p2_x, p2_y, + p3_x, p3_y, + p4_x, p4_y, + from.x, from.y, + to.x, to.y, + stroke_width, + r, g, b + ); } } @@ -133,38 +121,12 @@ function geometry_prepare_stroke(state) { function geometry_add_stroke(state, context, stroke) { if (!state.online || !stroke) return; - const stroke_quads = count_stroke_quads(stroke.points); - // const stroke_quads = Math.ceil(stroke.points.length / MAX_POINTS_PER_QUAD); - - // Points - let point_bytes_left = context.point_serializer.size - context.point_serializer.offset; - let point_bytes_needed = stroke.points.length * config.bytes_per_point; - - if (point_bytes_needed % 8192 != 0) { - point_bytes_needed += 8192 - point_bytes_needed % 8192; - } - - 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); + let bytes_left = context.static_serializer.size - context.static_serializer.offset; + let bytes_needed = stroke.points.length * 6 * config.bytes_per_point; - 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); + if (bytes_left < bytes_needed) { + const extend_to = Math.ceil((context.static_serializer.size + bytes_needed) * 1.62); + context.static_serializer = ser_extend(context.static_serializer, extend_to); } push_stroke(context, stroke); diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index efe4bbb..cc75166 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -1,24 +1,25 @@ const sdf_vs_src = `#version 300 es - in vec3 a_pos; + in vec3 a_pos; // .z is radius + in vec4 a_line; in vec3 a_color; uniform vec2 u_scale; uniform vec2 u_res; uniform vec2 u_translation; + out vec4 v_line; out vec2 v_texcoord; out vec3 v_color; flat out float v_thickness; - flat out int v_vertexid; void main() { vec2 screen01 = (a_pos.xy * u_scale + u_translation) / u_res; vec2 screen02 = screen01 * 2.0; screen02.y = 2.0 - screen02.y; - + + v_line = a_line; v_texcoord = a_pos.xy; - v_vertexid = gl_VertexID; v_color = a_color; v_thickness = a_pos.z; @@ -29,51 +30,29 @@ const sdf_vs_src = `#version 300 es const sdf_fs_src = `#version 300 es precision highp float; - uniform sampler2D u_texture_points; - uniform highp usampler2D u_texture_indices; uniform int u_debug_mode; + in vec4 v_line; in vec2 v_texcoord; in vec3 v_color; flat in float v_thickness; - flat in int v_vertexid; out vec4 FragColor; void main() { - float mindist = 99999999.9; - - uvec4 indices = texelFetch(u_texture_indices, ivec2(v_vertexid / 6, 0), 0); - - uint v_from = indices.x; - uint v_to = indices.y; - - for (uint i = v_from; i < v_to - uint(1); ++i) { - uint x1 = i % uint(8192); - uint y1 = i / uint(8192); + vec2 a = v_line.xy; + vec2 b = v_line.zw; - 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); - float dist = length(pa - ba * h) - v_thickness; - - mindist = min(mindist, dist); - } + vec2 pa = v_texcoord - a.xy, ba = b.xy - a.xy; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + float dist = length(pa - ba * h) - v_thickness / 2.0; float fade = 0.5 * length(fwidth(v_texcoord)); - float alpha = 1.0 - smoothstep(-fade, fade, mindist); + float alpha = 1.0 - smoothstep(-fade, fade, dist); 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); + FragColor = vec4(1.0, 0.0, 0.0, 1.0); } else { FragColor = vec4(v_color * alpha, alpha); // FragColor = vec4(v_color * alpha, 0.1 + alpha); @@ -155,6 +134,7 @@ function init_webgl(state, context) { context.locations['sdf'] = { 'a_pos': gl.getAttribLocation(context.programs['sdf'], 'a_pos'), + 'a_line': gl.getAttribLocation(context.programs['sdf'], 'a_line'), 'a_color': gl.getAttribLocation(context.programs['sdf'], 'a_color'), 'u_res': gl.getUniformLocation(context.programs['sdf'], 'u_res'),