From dc824c12c9554ee23a1a57d75b5ade7766b7e0e1 Mon Sep 17 00:00:00 2001 From: Aleksey Olokhtonov Date: Sun, 10 Nov 2024 22:31:16 +0300 Subject: [PATCH] Reuse a single circle geometry. One circle per segment. Still needs last circle --- client/config.js | 2 +- client/webgl_draw.js | 21 ++++++++++--- client/webgl_geometry.js | 26 ++++++++++++++++ client/webgl_shaders.js | 64 +++++++++++++--------------------------- 4 files changed, 65 insertions(+), 48 deletions(-) diff --git a/client/config.js b/client/config.js index 3485ac9..7417bbf 100644 --- a/client/config.js +++ b/client/config.js @@ -12,7 +12,7 @@ const config = { draw_fullnodes: false, zoom_delta: 0.05, min_zoom_level: -275, - max_zoom_level: 100, + max_zoom_level: 200, initial_offline_timeout: 1000, default_color: 0x00, default_width: 8, diff --git a/client/webgl_draw.js b/client/webgl_draw.js index 5121d41..e596276 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -273,6 +273,9 @@ 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); // "Static" data upload if (segment_count > 0) { @@ -283,14 +286,17 @@ async function draw(state, context, animate, ts) { const total_static_size = context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + - context.instance_data_pressures.size; + round_to_pow2(context.instance_data_pressures.size, 4) + + circle_data.length * 4; gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_strokes_static']); - gl.bufferData(gl.ARRAY_BUFFER, total_static_size, gl.STREAM_DRAW); + gl.bufferData(gl.ARRAY_BUFFER, total_static_size, gl.DYNAMIC_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.bindTexture(gl.TEXTURE_2D, textures['stroke_data']); upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size); @@ -302,28 +308,35 @@ async function draw(state, context, animate, ts) { 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.enableVertexAttribArray(pr.locations['a_pos']); gl.enableVertexAttribArray(pr.locations['a_a']); gl.enableVertexAttribArray(pr.locations['a_b']); gl.enableVertexAttribArray(pr.locations['a_stroke_id']); gl.enableVertexAttribArray(pr.locations['a_pressure']); + // Circle meshes (shared for all instances) + gl.vertexAttribPointer(pr.locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4 + round_to_pow2(context.instance_data_pressures.size, 4)); + // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values) gl.vertexAttribPointer(pr.locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0); gl.vertexAttribPointer(pr.locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4); gl.vertexAttribIPointer(pr.locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4); gl.vertexAttribPointer(pr.locations['a_pressure'], 2, gl.UNSIGNED_BYTE, true, 1, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4); + gl.vertexAttribDivisor(pr.locations['a_pos'], 0); gl.vertexAttribDivisor(pr.locations['a_a'], 1); gl.vertexAttribDivisor(pr.locations['a_b'], 1); gl.vertexAttribDivisor(pr.locations['a_stroke_id'], 1); gl.vertexAttribDivisor(pr.locations['a_pressure'], 1); // Static draw (everything already bound) - gl.drawArraysInstanced(gl.TRIANGLES, 0, 32 * 3 + 6 + 32 * 3, segment_count); + gl.drawArraysInstanced(gl.TRIANGLES, 0, circle_points + 6, segment_count); // I don't really know why I need to do this, but it // makes background patter drawcall work properly + gl.vertexAttribDivisor(pr.locations['a_pos'], 0); gl.vertexAttribDivisor(pr.locations['a_a'], 0); gl.vertexAttribDivisor(pr.locations['a_b'], 0); gl.vertexAttribDivisor(pr.locations['a_stroke_id'], 0); @@ -332,7 +345,7 @@ async function draw(state, context, animate, ts) { // Dynamic draw (strokes currently being drawn) - if (dynamic_segment_count > 0) { + if (false && dynamic_segment_count > 0) { const pr = programs['main']; // same as static // Dynamic strokes should be drawn above static strokes diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 43d9b99..5633c0f 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -521,3 +521,29 @@ function geometry_generate_handles(state, context, active_image) { 'stroke_data': stroke_data, }; } + +function geometry_line_segments_with_two_circles(circle_segments) { + const results = new Float32Array((circle_segments * 3 + 6) * 2); // triangle fan circle + two triangles, all 2D (x + y) + + // Generate circle as triangle fan at 0, 0 with radius 1 + // This circle will be offset/scaled in the vertex shader + let last_phi = ((circle_segments - 1) / circle_segments) * 2 * Math.PI; + for (let i = 0; i < circle_segments; ++i) { + const phi = i / circle_segments * 2 * Math.PI; + const x1 = Math.cos(phi); + const y1 = Math.sin(phi); + const x2 = Math.cos(last_phi); + const y2 = Math.sin(last_phi); + + results[i * 6 + 0] = x1; + results[i * 6 + 1] = y1; + results[i * 6 + 2] = x2; + results[i * 6 + 3] = y2; + results[i * 6 + 4] = 0; + results[i * 6 + 5] = 0; + + last_phi = phi; + } + + return results; +} diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index 30ec993..27173b2 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -1,4 +1,5 @@ const sdf_vs_src = `#version 300 es + in vec2 a_pos; // for the joins/caps these are relative positions. for line segments these are dummy values in vec2 a_a; // point from in vec2 a_b; // point to @@ -12,76 +13,51 @@ const sdf_vs_src = `#version 300 es uniform int u_stroke_texture_size; uniform highp usampler2D u_stroke_data; uniform float u_fixed_pixel_width; + uniform int u_circle_points; out vec3 v_color; flat out vec2 v_thickness; void main() { - const float PI = 3.1415926; 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; - - vec2 line_dir = normalize(a_b - a_a); - vec2 up_dir = vec2(line_dir.y, -line_dir.x); - uvec4 stroke_data = texelFetch(u_stroke_data, ivec2(stroke_data_x, stroke_data_y), 0); float radius = float(stroke_data.w); if (u_fixed_pixel_width > 0.0) { radius = u_fixed_pixel_width / u_scale.x; } - - int vertex_index = gl_VertexID % 198; vec2 pos; - if (vertex_index < 32 * 3) { - // first cap - float angle1 = float(vertex_index / 3) / 32.0 * PI * 2.0; - float angle2 = float((vertex_index + 1) / 3) / 32.0 * PI * 2.0; - vec2 dir1 = vec2(cos(angle1), sin(angle1)); - vec2 dir2 = vec2(cos(angle2), sin(angle2)); - - if (vertex_index % 3 == 0) { - pos = a_a + dir1 * radius * a_pressure.x * 2.0; - } else if (vertex_index % 3 == 1) { - pos = a_a; - } else { - pos = a_a + dir2 * radius * a_pressure.x * 2.0; - } - } else if (vertex_index < 102) { + if (vertex_index < u_circle_points) { + pos = a_a + a_pos * radius * a_pressure.x * 2.0; + } else { + int vertex_index_line = vertex_index - u_circle_points; + + vec2 line_dir = normalize(a_b - a_a); + vec2 up_dir = vec2(line_dir.y, -line_dir.x); + // connecting line - if (vertex_index == 96) { + if (vertex_index_line == 0) { // top left pos = a_a + up_dir * radius * a_pressure.x * 2.0; - } else if (vertex_index == 97 || vertex_index == 101) { + } else if (vertex_index_line == 1 || vertex_index_line == 5) { // top right pos = a_b + up_dir * radius * a_pressure.y * 2.0; - } else if (vertex_index == 98 || vertex_index == 100) { + } else if (vertex_index_line == 2 || vertex_index_line == 4) { // bottom left pos = a_a - up_dir * radius * a_pressure.x * 2.0; } else { // bottom right pos = a_b - up_dir * radius * a_pressure.y * 2.0; } - - } else { - // second cap - float angle1 = float((vertex_index - 102) / 3) / 32.0 * PI * 2.0; - float angle2 = float((vertex_index - 101) / 3) / 32.0 * PI * 2.0; - vec2 dir1 = vec2(cos(angle1), sin(angle1)); - vec2 dir2 = vec2(cos(angle2), sin(angle2)); - - if ((vertex_index - 102) % 3 == 0) { - pos = a_b + dir1 * radius * a_pressure.y * 2.0; - } else if ((vertex_index - 102) % 3 == 1) { - pos = a_a; - } else { - pos = a_b + dir2 * radius * a_pressure.y * 2.0; - } } screen02 = (pos.xy * u_scale + u_translation) / u_res * 2.0; @@ -89,10 +65,11 @@ const sdf_vs_src = `#version 300 es v_color = vec3(stroke_data.xyz) / 255.0; - if (a_stroke_id >> 31 != 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); } `; @@ -346,10 +323,11 @@ function init_webgl(state, context) { gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - //gl.blendEquation(gl.MAX); + /* gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.NOTEQUAL); + */ context.gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query_webgl2'); if (context.gpu_timer_ext === null) {