diff --git a/client/webgl_draw.js b/client/webgl_draw.js index e596276..118d579 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -273,9 +273,24 @@ async function draw(state, context, animate, ts) { } } - const circle_segments = 32; - const circle_points = circle_segments * 3; - const circle_data = geometry_line_segments_with_two_circles(circle_segments); + if (false) { + // TODO: @speed maybe we can get this while clipping "for free"? + let largest_width = 0; + + for (let i = 0; i < context.clipped_indices.size; ++i) { + const stroke_id = context.clipped_indices.data[i]; + const stroke = state.events[stroke_id]; + if (stroke.width > largest_width) { + largest_width = stroke.width; + } + } + + largest_width *= state.canvas.zoom; + } + + // Largest distance from N-th circle LOD to a real circle should be less than 1 unit (pixel) + const circle_lod = 4; // largest_width > 0 ? Math.ceil(Math.PI / Math.acos(1 - 1 / largest_width)) : 0; + const circle_data = geometry_good_circle_and_dummy(circle_lod); // "Static" data upload if (segment_count > 0) { @@ -287,16 +302,17 @@ async function draw(state, context, animate, ts) { const total_static_size = context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + round_to_pow2(context.instance_data_pressures.size, 4) + - circle_data.length * 4; + circle_data.points.size * 4; gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_strokes_static']); - gl.bufferData(gl.ARRAY_BUFFER, total_static_size, gl.DYNAMIC_DRAW); + gl.bufferData(gl.ARRAY_BUFFER, total_static_size, gl.STREAM_DRAW); 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 + context.instance_data_ids.size * 4, tv_data(context.instance_data_pressures)); - gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + round_to_pow2(context.instance_data_pressures.size, 4), - circle_data); + gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + round_to_pow2(context.instance_data_pressures.size, 4), tv_data(circle_data.points)); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['i_strokes_static']); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, tv_data(circle_data.indices), gl.STREAM_DRAW); gl.bindTexture(gl.TEXTURE_2D, textures['stroke_data']); upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size); @@ -307,8 +323,7 @@ async function draw(state, context, animate, ts) { gl.uniform1i(pr.locations['u_debug_mode'], state.debug.red); gl.uniform1i(pr.locations['u_stroke_data'], 0); gl.uniform1i(pr.locations['u_stroke_texture_size'], config.stroke_texture_size); - gl.uniform1f(pr.locations['u_fixed_pixel_width'], 0); - gl.uniform1i(pr.locations['u_circle_points'], circle_points); + gl.uniform1i(pr.locations['u_circle_points'], circle_data.points.size / 2 - 4); gl.enableVertexAttribArray(pr.locations['a_pos']); gl.enableVertexAttribArray(pr.locations['a_a']); @@ -332,7 +347,7 @@ async function draw(state, context, animate, ts) { gl.vertexAttribDivisor(pr.locations['a_pressure'], 1); // Static draw (everything already bound) - gl.drawArraysInstanced(gl.TRIANGLES, 0, circle_points + 6, segment_count); + gl.drawElementsInstanced(gl.TRIANGLES, circle_data.indices.size, gl.UNSIGNED_INT, 0, segment_count); // I don't really know why I need to do this, but it // makes background patter drawcall work properly @@ -379,7 +394,6 @@ async function draw(state, context, animate, ts) { gl.uniform1i(pr.locations['u_debug_mode'], state.debug.red); gl.uniform1i(pr.locations['u_stroke_data'], 0); gl.uniform1i(pr.locations['u_stroke_texture_size'], config.dynamic_stroke_texture_size); - gl.uniform1f(pr.locations['u_fixed_pixel_width'], 0); gl.enableVertexAttribArray(pr.locations['a_a']); gl.enableVertexAttribArray(pr.locations['a_b']); @@ -434,7 +448,6 @@ async function draw(state, context, animate, ts) { gl.uniform1i(pr.locations['u_debug_mode'], 0); gl.uniform1i(pr.locations['u_stroke_data'], 0); gl.uniform1i(pr.locations['u_stroke_texture_size'], config.ui_texture_size); - gl.uniform1f(pr.locations['u_fixed_pixel_width'], 2); gl.enableVertexAttribArray(pr.locations['a_a']); gl.enableVertexAttribArray(pr.locations['a_b']); diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 5633c0f..b5fce3a 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -547,3 +547,80 @@ function geometry_line_segments_with_two_circles(circle_segments) { return results; } + +function geometry_good_circle_and_dummy(lod) { + const total_points = 3 * Math.pow(2, lod - 1) + 4; // 3, 6, 12, 24, ... + Dummy for line segment + const total_indices = 3 * (Math.pow(3, lod + 1) - 1) / 2 + 6; // 3, 3 + 9, 3 + 9 + 18, ... + Dummy for line segment + const points = tv_create(Float32Array, total_points * 2); + const indices = tv_create(Uint32Array, total_indices); + + // Initital triangle, added even for lod = 0 + tv_add(indices, 0); + tv_add(indices, 1); + tv_add(indices, 2); + + if (lod >= 1) { + tv_add(indices, 0); + tv_add(indices, 3); + tv_add(indices, 1); + + tv_add(indices, 1); + tv_add(indices, 4); + tv_add(indices, 2); + + tv_add(indices, 2); + tv_add(indices, 5); + tv_add(indices, 0); + } + + let last_base = 3; + let last_offset = 0; + + for (let i = 0; i < lod; ++i) { + // generate 3 * Math.pow(2, i) points on a circle + const npoints = 3 * Math.pow(2, i); + const base = indices.size; + + for (let j = 0; j < npoints; ++j) { + // use every second point (except level 0, where all points are used) + if (i === 0 || (j % 2 === 1)) { + const phi = j / npoints * Math.PI * 2; + const x = Math.sin(phi); + const y = Math.cos(phi); + + tv_add(points, x); + tv_add(points, y); + + if (i > 1) { + tv_add(indices, indices.data[last_base + last_offset++]); + tv_add(indices, points.size / 2 - 1); // the middle of the trianle is always the newly added point + tv_add(indices, indices.data[last_base + last_offset]); + + if (j % 4 == 3) { + last_offset++; + } + } + } + } + + if (i > 1) { + last_base = base; + last_offset = 0; + } + } + + // 4 dummy points (8 indices) for the line segment + const dummy_base = points.size / 2; + points.size += 8; + tv_add(indices, dummy_base + 0); + tv_add(indices, dummy_base + 1); + tv_add(indices, dummy_base + 2); + tv_add(indices, dummy_base + 3); + tv_add(indices, dummy_base + 2); + tv_add(indices, dummy_base + 1); + + return { + 'points': points, + 'indices': indices + }; +} diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index 27173b2..04579f0 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -22,11 +22,15 @@ const sdf_vs_src = `#version 300 es void main() { vec2 screen02; - int per_segment_points = u_circle_points * 2 + 6; - int vertex_index = gl_VertexID % per_segment_points; - - int stroke_data_y = a_stroke_id / u_stroke_texture_size; - int stroke_data_x = a_stroke_id % u_stroke_texture_size; + int vertex_index = gl_VertexID; + int stroke_index = a_stroke_id; + + if (a_stroke_id >> 31 != 0) { + stroke_index = a_stroke_id & 0x7FFFFFFF; + } + + int stroke_data_y = stroke_index / u_stroke_texture_size; + int stroke_data_x = stroke_index % u_stroke_texture_size; uvec4 stroke_data = texelFetch(u_stroke_data, ivec2(stroke_data_x, stroke_data_y), 0); float radius = float(stroke_data.w); @@ -37,7 +41,7 @@ const sdf_vs_src = `#version 300 es vec2 pos; if (vertex_index < u_circle_points) { - pos = a_a + a_pos * radius * a_pressure.x * 2.0; + pos = a_a + a_pos * radius * a_pressure.x; } else { int vertex_index_line = vertex_index - u_circle_points; @@ -47,16 +51,16 @@ const sdf_vs_src = `#version 300 es // connecting line if (vertex_index_line == 0) { // top left - pos = a_a + up_dir * radius * a_pressure.x * 2.0; + pos = a_a + up_dir * radius * a_pressure.x; } else if (vertex_index_line == 1 || vertex_index_line == 5) { // top right - pos = a_b + up_dir * radius * a_pressure.y * 2.0; + pos = a_b + up_dir * radius * a_pressure.y; } else if (vertex_index_line == 2 || vertex_index_line == 4) { // bottom left - pos = a_a - up_dir * radius * a_pressure.x * 2.0; + pos = a_a - up_dir * radius * a_pressure.x; } else { // bottom right - pos = a_b - up_dir * radius * a_pressure.y * 2.0; + pos = a_b - up_dir * radius * a_pressure.y; } } @@ -65,12 +69,11 @@ const sdf_vs_src = `#version 300 es v_color = vec3(stroke_data.xyz) / 255.0; -/* if (a_stroke_id >> 31 != 0 && (vertex_index >= u_circle_points)) { screen02 += vec2(100.0); // shift offscreen } -*/ - gl_Position = vec4(screen02 - 1.0, (float(a_stroke_id) / float(u_stroke_count)) * 2.0 - 1.0, 1.0); + + gl_Position = vec4(screen02 - 1.0, (float(stroke_index) / float(u_stroke_count)) * 2.0 - 1.0, 1.0); } `; @@ -359,6 +362,7 @@ function init_webgl(state, context) { context.buffers = { 'b_images': gl.createBuffer(), 'b_strokes_static': gl.createBuffer(), + 'i_strokes_static': gl.createBuffer(), 'b_strokes_dynamic': gl.createBuffer(), 'b_instance_dot': gl.createBuffer(), 'b_instance_grid': gl.createBuffer(),