Browse Source

Limit max points per quad, fix wrong rounding of texture size

ssao
A.Olokhtonov 1 year ago
parent
commit
46587068e6
  1. 24
      client/index.html
  2. 3
      client/index.js
  3. 5
      client/webgl_draw.js
  4. 104
      client/webgl_geometry.js
  5. 25
      client/webgl_listeners.js
  6. 33
      client/webgl_shaders.js

24
client/index.html

@ -7,20 +7,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon"> <link rel="shortcut icon" href="icons/favicon.svg" id="favicon">
<link rel="stylesheet" type="text/css" href="default.css?v=28"> <link rel="stylesheet" type="text/css" href="default.css?v=38">
<script type="text/javascript" src="aux.js?v=28"></script> <script type="text/javascript" src="aux.js?v=38"></script>
<script type="text/javascript" src="math.js?v=28"></script> <script type="text/javascript" src="math.js?v=38"></script>
<script type="text/javascript" src="tools.js?v=28"></script> <script type="text/javascript" src="tools.js?v=38"></script>
<script type="text/javascript" src="webgl_geometry.js?v=28"></script> <script type="text/javascript" src="webgl_geometry.js?v=38"></script>
<script type="text/javascript" src="webgl_shaders.js?v=28"></script> <script type="text/javascript" src="webgl_shaders.js?v=38"></script>
<script type="text/javascript" src="webgl_listeners.js?v=28"></script> <script type="text/javascript" src="webgl_listeners.js?v=38"></script>
<script type="text/javascript" src="webgl_draw.js?v=28"></script> <script type="text/javascript" src="webgl_draw.js?v=38"></script>
<script type="text/javascript" src="index.js?v=28"></script> <script type="text/javascript" src="index.js?v=38"></script>
<script type="text/javascript" src="client_send.js?v=28"></script> <script type="text/javascript" src="client_send.js?v=38"></script>
<script type="text/javascript" src="client_recv.js?v=28"></script> <script type="text/javascript" src="client_recv.js?v=38"></script>
<script type="text/javascript" src="websocket.js?v=28"></script> <script type="text/javascript" src="websocket.js?v=38"></script>
</head> </head>
<body> <body>
<div class="main"> <div class="main">

3
client/index.js

@ -16,7 +16,7 @@ const config = {
default_color: 0x00, default_color: 0x00,
default_width: 8, default_width: 8,
bytes_per_point: 8, bytes_per_point: 8,
initial_static_bytes: 4096, initial_static_bytes: 4096 * 16,
}; };
const EVENT = Object.freeze({ const EVENT = Object.freeze({
@ -108,6 +108,7 @@ function main() {
const context = { const context = {
'canvas': null, 'canvas': null,
'gl': null, 'gl': null,
'debug_mode': false,
'programs': {}, 'programs': {},
'buffers': {}, 'buffers': {},

5
client/webgl_draw.js

@ -42,10 +42,12 @@ function draw(state, context) {
const npoints = context.point_serializer.offset / (4 * 2); const npoints = context.point_serializer.offset / (4 * 2);
const nstrokes = context.quad_serializer.offset / (6 * 4 * 4); const nstrokes = context.quad_serializer.offset / (6 * 4 * 4);
ser_align(context.point_serializer, 8192);
if (npoints > 0) { if (npoints > 0) {
// TOOD: if points changed // TOOD: if points changed
if (true) { 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); 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.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
gl.uniform1i(locations['u_texture_points'], 0); gl.uniform1i(locations['u_texture_points'], 0);
gl.uniform1i(locations['u_texture_indices'], 1); 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.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.drawArrays(gl.TRIANGLES, 0, nstrokes * 6);

104
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); 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) { function push_stroke(context, stroke) {
// if (stroke.stroke_id !== 1123776468) {
// return;
// }
const stroke_width = stroke.width; const stroke_width = stroke.width;
const points = stroke.points; const points = stroke.points;
const color_u32 = stroke.color; const color_u32 = stroke.color;
@ -36,11 +52,8 @@ function push_stroke(context, stroke) {
return; return;
} }
const points_from = context.point_serializer.offset / (4 * 2); // 4 is sizeof(f32) btw, just sain' let points_from = context.point_serializer.offset / (4 * 2); // 4 is sizeof(f32) btw, just sain'
const points_to = points_from + points.length; const points_start = points_from;
ser_u32(context.index_serializer, points_from);
ser_u32(context.index_serializer, points_to);
let min_x, min_y, max_x, max_y; 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); min_y = Math.floor(points[0].y - stroke_width / 2);
max_y = Math.ceil(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_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)); 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_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)); max_y = Math.max(max_y, Math.ceil(p.y + stroke_width / 2));
push_point_xy(context.point_serializer, p.x, p.y); 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) { function geometry_prepare_stroke(state) {
@ -76,17 +132,35 @@ function geometry_prepare_stroke(state) {
function geometry_add_stroke(state, context, stroke) { function geometry_add_stroke(state, context, stroke) {
if (!state.online || !stroke) return; if (!state.online || !stroke) return;
if (stroke.points.length < 2) return;
const bytes_left = context.point_serializer.size - context.point_serializer.offset; const stroke_quads = count_stroke_quads(stroke.points);
const bytes_needed = stroke.points.length * config.bytes_per_point; // const stroke_quads = Math.ceil(stroke.points.length / MAX_POINTS_PER_QUAD);
if (bytes_left < bytes_needed) { // Points
const extend_points_by = Math.ceil((context.point_serializer.size + bytes_needed) * 1.62); const point_bytes_left = context.point_serializer.size - context.point_serializer.offset;
const extend_indices_by = Math.ceil((context.index_serializer.size + stroke.points.length * 4 * 2) * 1.62); const point_bytes_needed = stroke.points.length * config.bytes_per_point;
const extend_quads_by = Math.ceil((context.quad_serializer.size + 6 * (4 * 3)) * 1.62);
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); 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); 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); 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) { function add_image(context, image_id, bitmap, p) {
return; // TODO
const x = p.x; const x = p.x;
const y = p.y; const y = p.y;
const gl = context.gl; const gl = context.gl;
@ -161,7 +237,7 @@ function add_image(context, image_id, bitmap, p) {
'image_id': image_id '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.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_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);

25
client/webgl_listeners.js

@ -46,6 +46,31 @@ function keydown(e, state, context) {
} else if (e.code === 'Tab') { } else if (e.code === 'Tab') {
e.preventDefault(); e.preventDefault();
zenmode(); 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);
} }
} }

33
client/webgl_shaders.js

@ -22,15 +22,16 @@ const sdf_vs_src = `#version 300 es
v_color = a_color; v_color = a_color;
v_thickness = a_pos.z; 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 const sdf_fs_src = `#version 300 es
precision mediump float; precision highp float;
uniform sampler2D u_texture_points; uniform sampler2D u_texture_points;
uniform highp usampler2D u_texture_indices; uniform highp usampler2D u_texture_indices;
uniform int u_debug_mode;
in vec2 v_texcoord; in vec2 v_texcoord;
in vec3 v_color; in vec3 v_color;
@ -41,7 +42,7 @@ const sdf_fs_src = `#version 300 es
out vec4 FragColor; out vec4 FragColor;
void main() { void main() {
float mindist = 99999.9; float mindist = 99999999.9;
uvec4 indices = texelFetch(u_texture_indices, ivec2(v_vertexid / 6, 0), 0); 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; uint v_to = indices.y;
for (uint i = v_from; i < v_to - uint(1); ++i) { for (uint i = v_from; i < v_to - uint(1); ++i) {
vec4 a = texelFetch(u_texture_points, ivec2(i, 0), 0); uint x1 = i % uint(8192);
vec4 b = texelFetch(u_texture_points, ivec2(i + uint(1), 0), 0); 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; 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 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 fade = 0.5 * length(fwidth(v_texcoord));
float alpha = 1.0 - smoothstep(-fade, fade, mindist); float alpha = 1.0 - smoothstep(-fade, fade, mindist);
FragColor = vec4(v_color * alpha, alpha); if (u_debug_mode == 1) {
// FragColor = vec4(v_color, 1.0); 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 const tquad_fs_src = `#version 300 es
precision mediump float; precision highp float;
in vec2 v_texcoord; in vec2 v_texcoord;
@ -148,6 +162,7 @@ function init_webgl(state, context) {
'u_translation': gl.getUniformLocation(context.programs['sdf'], 'u_translation'), 'u_translation': gl.getUniformLocation(context.programs['sdf'], 'u_translation'),
'u_texture_points': gl.getUniformLocation(context.programs['sdf'], 'u_texture_points'), 'u_texture_points': gl.getUniformLocation(context.programs['sdf'], 'u_texture_points'),
'u_texture_indices': gl.getUniformLocation(context.programs['sdf'], 'u_texture_indices'), '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'] = { context.buffers['image'] = {
@ -164,6 +179,8 @@ function init_webgl(state, context) {
'indices': gl.createTexture() 'indices': gl.createTexture()
}; };
context.textures['image'] = {};
const resize_canvas = (entries) => { const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI // https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0]; const entry = entries[0];

Loading…
Cancel
Save