diff --git a/client/index.html b/client/index.html
index 5f0a7b2..1a9f710 100644
--- a/client/index.html
+++ b/client/index.html
@@ -7,20 +7,20 @@
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
diff --git a/client/index.js b/client/index.js
index 9fe280b..808d819 100644
--- a/client/index.js
+++ b/client/index.js
@@ -16,7 +16,7 @@ const config = {
default_color: 0x00,
default_width: 8,
bytes_per_point: 8,
- initial_static_bytes: 4096,
+ initial_static_bytes: 4096 * 16,
};
const EVENT = Object.freeze({
@@ -108,6 +108,7 @@ function main() {
const context = {
'canvas': null,
'gl': null,
+ 'debug_mode': false,
'programs': {},
'buffers': {},
diff --git a/client/webgl_draw.js b/client/webgl_draw.js
index cf1908b..2555824 100644
--- a/client/webgl_draw.js
+++ b/client/webgl_draw.js
@@ -42,10 +42,12 @@ function draw(state, context) {
const npoints = context.point_serializer.offset / (4 * 2);
const nstrokes = context.quad_serializer.offset / (6 * 4 * 4);
+ ser_align(context.point_serializer, 8192);
+
if (npoints > 0) {
// TOOD: if points changed
if (true) {
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, npoints, 1, 0, gl.RG, gl.FLOAT, new Float32Array(context.point_serializer.buffer, 0, context.point_serializer.offset / 4));
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, Math.min(npoints, 8192), Math.max(1, Math.ceil(npoints / 8192)), 0, gl.RG, gl.FLOAT, new Float32Array(context.point_serializer.buffer, 0, context.point_serializer.offset / 4));
}
gl.activeTexture(gl.TEXTURE0 + 1);
@@ -63,6 +65,7 @@ function draw(state, context) {
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);
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index 1649989..128d86a 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -23,7 +23,23 @@ function push_quad_xyzrgb(s, p1x, p1y, p4x, 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
+
+ return Math.ceil(points.length / MAX_POINTS_PER_QUAD);
+}
+
function push_stroke(context, stroke) {
+ // if (stroke.stroke_id !== 1123776468) {
+ // return;
+ // }
+
const stroke_width = stroke.width;
const points = stroke.points;
const color_u32 = stroke.color;
@@ -36,11 +52,8 @@ function push_stroke(context, stroke) {
return;
}
- const points_from = context.point_serializer.offset / (4 * 2); // 4 is sizeof(f32) btw, just sain'
- const points_to = points_from + points.length;
-
- ser_u32(context.index_serializer, points_from);
- ser_u32(context.index_serializer, points_to);
+ 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;
@@ -50,15 +63,58 @@ function push_stroke(context, stroke) {
min_y = Math.floor(points[0].y - stroke_width / 2);
max_y = Math.ceil(points[0].y + stroke_width / 2);
- for (const p of points) {
+ let points_per_quad = 0;
+
+ for (let i = 0; i < points.length; ++i) {
+ const p = points[i];
+
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));
+
push_point_xy(context.point_serializer, p.x, p.y);
+
+ points_per_quad++;
+
+ if (points_per_quad == MAX_POINTS_PER_QUAD) {
+ let points_to = points_from + MAX_POINTS_PER_QUAD;
+
+ 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);
+ }
+
+ 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);
+
+ min_x = Math.floor(p.x - stroke_width / 2);
+ max_x = Math.ceil(p.x + stroke_width / 2);
+
+ min_y = Math.floor(p.y - stroke_width / 2);
+ max_y = Math.ceil(p.y + stroke_width / 2);
+
+ points_from = points_to;
+ points_per_quad = 0;
+ }
}
- push_quad_xyzrgb(context.quad_serializer, min_x, min_y, max_x, max_y, stroke_width / 2, r, g, b);
+ 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);
+ } else {
+ ser_u32(context.index_serializer, points_from);
+ }
+
+ 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);
+ }
}
function geometry_prepare_stroke(state) {
@@ -76,17 +132,35 @@ function geometry_prepare_stroke(state) {
function geometry_add_stroke(state, context, stroke) {
if (!state.online || !stroke) return;
+ if (stroke.points.length < 2) return;
- const bytes_left = context.point_serializer.size - context.point_serializer.offset;
- const bytes_needed = stroke.points.length * config.bytes_per_point;
+ const stroke_quads = count_stroke_quads(stroke.points);
+ // const stroke_quads = Math.ceil(stroke.points.length / MAX_POINTS_PER_QUAD);
- if (bytes_left < bytes_needed) {
- const extend_points_by = Math.ceil((context.point_serializer.size + bytes_needed) * 1.62);
- const extend_indices_by = Math.ceil((context.index_serializer.size + stroke.points.length * 4 * 2) * 1.62);
- const extend_quads_by = Math.ceil((context.quad_serializer.size + 6 * (4 * 3)) * 1.62);
+ // Points
+ const point_bytes_left = context.point_serializer.size - context.point_serializer.offset;
+ const point_bytes_needed = stroke.points.length * config.bytes_per_point;
+ 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);
+
+ 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);
}
@@ -151,6 +225,8 @@ function geometry_clear_player(state, context, player_id) {
}
function add_image(context, image_id, bitmap, p) {
+ return; // TODO
+
const x = p.x;
const y = p.y;
const gl = context.gl;
@@ -161,7 +237,7 @@ function add_image(context, image_id, bitmap, p) {
'image_id': image_id
};
- gl.bindTexture(gl.TEXTURE_2D, context.textures[id].texture);
+ gl.bindTexture(gl.TEXTURE_2D, context.textures['image'][id].texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index 1c9dc35..6004479 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -46,6 +46,31 @@ function keydown(e, state, context) {
} else if (e.code === 'Tab') {
e.preventDefault();
zenmode();
+ } else if (e.code === 'KeyZ') {
+ const topleft = screen_to_canvas(state, {'x': 0, 'y': 0});
+ const bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height});
+
+ for (let i = 0; i < state.events.length; ++i) {
+ const event = state.events[i];
+
+ if (event.type === EVENT.STROKE) {
+ let on_screen = false;
+
+ for (const p of event.points) {
+ if (topleft.x <= p.x && p.x <= bottomright.x && topleft.y <= p.y && p.y <= bottomright.y) {
+ on_screen = true;
+ break;
+ }
+ }
+
+ if (on_screen) {
+ console.log(i);
+ }
+ }
+ }
+ } else if (e.code === 'KeyD') {
+ context.debug_mode = !context.debug_mode;
+ schedule_draw(state, context);
}
}
diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js
index 40980d0..efe4bbb 100644
--- a/client/webgl_shaders.js
+++ b/client/webgl_shaders.js
@@ -22,15 +22,16 @@ const sdf_vs_src = `#version 300 es
v_color = a_color;
v_thickness = a_pos.z;
- gl_Position = vec4(screen02 - 1.0, 0, 1);
+ gl_Position = vec4(screen02 - 1.0, 0.0, 1);
}
`;
const sdf_fs_src = `#version 300 es
- precision mediump float;
+ precision highp float;
uniform sampler2D u_texture_points;
uniform highp usampler2D u_texture_indices;
+ uniform int u_debug_mode;
in vec2 v_texcoord;
in vec3 v_color;
@@ -41,7 +42,7 @@ const sdf_fs_src = `#version 300 es
out vec4 FragColor;
void main() {
- float mindist = 99999.9;
+ float mindist = 99999999.9;
uvec4 indices = texelFetch(u_texture_indices, ivec2(v_vertexid / 6, 0), 0);
@@ -49,8 +50,14 @@ const sdf_fs_src = `#version 300 es
uint v_to = indices.y;
for (uint i = v_from; i < v_to - uint(1); ++i) {
- vec4 a = texelFetch(u_texture_points, ivec2(i, 0), 0);
- vec4 b = texelFetch(u_texture_points, ivec2(i + uint(1), 0), 0);
+ uint x1 = i % uint(8192);
+ uint y1 = i / uint(8192);
+
+ 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);
@@ -62,8 +69,15 @@ const sdf_fs_src = `#version 300 es
float fade = 0.5 * length(fwidth(v_texcoord));
float alpha = 1.0 - smoothstep(-fade, fade, mindist);
- FragColor = vec4(v_color * alpha, alpha);
- // FragColor = vec4(v_color, 1.0);
+ 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);
+ } else {
+ FragColor = vec4(v_color * alpha, alpha);
+ // FragColor = vec4(v_color * alpha, 0.1 + alpha);
+ }
}
`;
@@ -88,7 +102,7 @@ const tquad_vs_src = `#version 300 es
`;
const tquad_fs_src = `#version 300 es
- precision mediump float;
+ precision highp float;
in vec2 v_texcoord;
@@ -148,6 +162,7 @@ function init_webgl(state, context) {
'u_translation': gl.getUniformLocation(context.programs['sdf'], 'u_translation'),
'u_texture_points': gl.getUniformLocation(context.programs['sdf'], 'u_texture_points'),
'u_texture_indices': gl.getUniformLocation(context.programs['sdf'], 'u_texture_indices'),
+ 'u_debug_mode': gl.getUniformLocation(context.programs['sdf'], 'u_debug_mode')
};
context.buffers['image'] = {
@@ -164,6 +179,8 @@ function init_webgl(state, context) {
'indices': gl.createTexture()
};
+ context.textures['image'] = {};
+
const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0];