Browse Source

Good speed (quad per line segment). Need to figure out rounding

ssao
A.Olokhtonov 1 year ago
parent
commit
01db70cab0
  1. 6
      client/index.js
  2. 39
      client/webgl_draw.js
  3. 180
      client/webgl_geometry.js
  4. 48
      client/webgl_shaders.js

6
client/index.js

@ -15,7 +15,7 @@ const config = { @@ -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() { @@ -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},

39
client/webgl_draw.js

@ -22,53 +22,32 @@ function draw(state, context) { @@ -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

180
client/webgl_geometry.js

@ -1,38 +1,25 @@ @@ -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) { @@ -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) { @@ -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);

48
client/webgl_shaders.js

@ -1,24 +1,25 @@ @@ -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 @@ -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) { @@ -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'),

Loading…
Cancel
Save