Browse Source

Generate the circles using indexed geometry

sdf
A.Olokhtonov 3 weeks ago
parent
commit
07bdbb585b
  1. 37
      client/webgl_draw.js
  2. 77
      client/webgl_geometry.js
  3. 30
      client/webgl_shaders.js

37
client/webgl_draw.js

@ -273,9 +273,24 @@ async function draw(state, context, animate, ts) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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']);

77
client/webgl_geometry.js

@ -547,3 +547,80 @@ function geometry_line_segments_with_two_circles(circle_segments) { @@ -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
};
}

30
client/webgl_shaders.js

@ -22,11 +22,15 @@ const sdf_vs_src = `#version 300 es @@ -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 @@ -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 @@ -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 @@ -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) { @@ -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(),

Loading…
Cancel
Save