Browse Source

The images are in

ssao
A.Olokhtonov 6 months ago
parent
commit
8a15093147
  1. 1
      .gitignore
  2. 6
      README.txt
  3. 1
      client/client_recv.js
  4. 3
      client/index.js
  5. 41
      client/webgl_draw.js
  6. 64
      client/webgl_geometry.js
  7. 2
      client/webgl_listeners.js
  8. 52
      client/webgl_shaders.js
  9. 4
      server/http.js

1
.gitignore vendored

@ -1,4 +1,5 @@
server/images server/images
server/server.log
doca.txt doca.txt
data/ data/
client/*.dot client/*.dot

6
README.txt

@ -8,8 +8,8 @@ Release:
+ Do not copy memory to wasm, instead use wasm memory to store data in the first place + Do not copy memory to wasm, instead use wasm memory to store data in the first place
+ SIMD for LOD? + SIMD for LOD?
+ Multithreading for LOD + Multithreading for LOD
+ Textured quads (pictures, code already written in older version)
- Z-prepass fringe bug (also, when do we enable the prepass?) - Z-prepass fringe bug (also, when do we enable the prepass?)
- Textured quads (pictures, code already written in older version)
- Resize and move pictures (draw handles) - Resize and move pictures (draw handles)
+ Bugs + Bugs
+ GC stalls!!! + GC stalls!!!
@ -41,6 +41,7 @@ Release:
- Eraser - Eraser
- Line drawing - Line drawing
+ Undo + Undo
- Undo for images
- Redo - Redo
* Polish * Polish
+ Use typedvector where appropriate + Use typedvector where appropriate
@ -68,7 +69,8 @@ Bonus:
- Move multiple points - Move multiple points
* Customizable background * Customizable background
+ Dots pattern + Dots pattern
* Grid pattern + Grid pattern
- Menu option
Bonus-bonus: Bonus-bonus:
- Actually infinite canvas (replace floats with something, some kind of fixed point scheme? chunks? multilevel scheme?) - Actually infinite canvas (replace floats with something, some kind of fixed point scheme? chunks? multilevel scheme?)

1
client/client_recv.js

@ -372,6 +372,7 @@ function handle_event(state, context, event, options = {}) {
event.width = bitmap.width; event.width = bitmap.width;
event.height = bitmap.height; event.height = bitmap.height;
geometry_add_dummy_stroke(context);
add_image(context, event.image_id, bitmap, p); add_image(context, event.image_id, bitmap, p);
// God knows when this will actually complete (it loads the image from the server) // God knows when this will actually complete (it loads the image from the server)

3
client/index.js

@ -104,7 +104,7 @@ let b_fast = true;
function start_spinner(state) { function start_spinner(state) {
const str = describeArc(64, 64, 32, a_angel, b_angel); const str = describeArc(64, 64, 32, a_angel, b_angel);
4
a_angel += speed_a; a_angel += speed_a;
b_angel += speed_b; b_angel += speed_b;
@ -243,6 +243,7 @@ async function main() {
'buffers': {}, 'buffers': {},
'locations': {}, 'locations': {},
'textures': {}, 'textures': {},
'images': [],
'dynamic_serializer': serializer_create(config.initial_dynamic_bytes), 'dynamic_serializer': serializer_create(config.initial_dynamic_bytes),
'dynamic_index_serializer': serializer_create(config.initial_dynamic_bytes), 'dynamic_index_serializer': serializer_create(config.initial_dynamic_bytes),

41
client/webgl_draw.js

@ -180,8 +180,6 @@ async function draw(state, context, animate, ts) {
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.uniform1f(locations['u_fadeout'], 1.0); gl.uniform1f(locations['u_fadeout'], 1.0);
// Opacity for major lines goes on a curve 0 / 1 \ 0
// Previous level (major lines) // Previous level (major lines)
{ {
const grid_instances = new Float32Array(geometry_gen_fullscreen_grid_1d(state, context, 32 / zoom_previous, 32 / zoom_previous)); const grid_instances = new Float32Array(geometry_gen_fullscreen_grid_1d(state, context, 32 / zoom_previous, 32 / zoom_previous));
@ -200,7 +198,7 @@ async function draw(state, context, animate, ts) {
{ {
const grid_instances = new Float32Array(geometry_gen_fullscreen_grid_1d(state, context, 32 / zoom_next, 32 / zoom_next)); const grid_instances = new Float32Array(geometry_gen_fullscreen_grid_1d(state, context, 32 / zoom_next, 32 / zoom_next));
let t = (zoom_next / zoom - 1) / 7; let t = (zoom_next / zoom - 1) / 7;
t = Math.min(0.1, -t + 1); t = Math.min(0.1, -t + 1); // slight fade-in
gl.uniform1f(locations['u_fadeout'], t); gl.uniform1f(locations['u_fadeout'], t);
@ -211,7 +209,42 @@ async function draw(state, context, animate, ts) {
} }
} }
gl.clear(gl.DEPTH_BUFFER_BIT); // draw strokes above the background pattern gl.clear(gl.DEPTH_BUFFER_BIT); // draw images above the background pattern
gl.useProgram(context.programs['image']);
buffers = context.buffers['image'];
locations = context.locations['image'];
{
let offset = 0;
const quads = geometry_image_quads(state, context);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_quads']);
gl.bufferData(gl.ARRAY_BUFFER, quads, gl.STATIC_DRAW);
gl.vertexAttribDivisor(locations['a_pos'], 0);
gl.enableVertexAttribArray(locations['a_pos']);
gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, 0);
for (const entry of context.images) {
if (state.active_image === entry.key) {
//gl.uniform1i(locations['u_active'], 1);
} else {
//gl.uniform1i(locations['u_active'], 0);
}
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'], 0); // Only 1 active texture for each drawcall
gl.bindTexture(gl.TEXTURE_2D, entry.texture);
gl.drawArrays(gl.TRIANGLES, offset, 6);
offset += 6;
}
}
gl.clear(gl.DEPTH_BUFFER_BIT); // draw strokes above the images
gl.useProgram(context.programs['sdf'].main); gl.useProgram(context.programs['sdf'].main);
buffers = context.buffers['sdf']; buffers = context.buffers['sdf'];
locations = context.locations['sdf'].main; locations = context.locations['sdf'].main;

64
client/webgl_geometry.js

@ -203,40 +203,22 @@ function add_image(context, image_id, bitmap, p) {
const x = p.x; const x = p.x;
const y = p.y; const y = p.y;
const gl = context.gl; const gl = context.gl;
const id = Object.keys(context.textures['image']).length; const id = Object.keys(context.images).length;
const entry = {
context.textures['image'][id] = {
'texture': gl.createTexture(), 'texture': gl.createTexture(),
'image_id': image_id 'key': image_id,
'at': p,
'width': bitmap.width,
'height': bitmap.height,
}; };
gl.bindTexture(gl.TEXTURE_2D, context.textures['image'][id].texture); context.images.push(entry);
gl.bindTexture(gl.TEXTURE_2D, entry.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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
context.quad_positions.push(...[
x, y,
x, y + bitmap.height,
x + bitmap.width, y + bitmap.height,
x + bitmap.width, y,
x, y,
x + bitmap.width, y + bitmap.height,
]);
context.quad_texcoords.push(...[
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
1, 1,
]);
context.quad_positions_f32 = new Float32Array(context.quad_positions);
context.quad_texcoords_f32 = new Float32Array(context.quad_texcoords);
} }
function move_image(context, image_event) { function move_image(context, image_event) {
@ -365,3 +347,31 @@ function geometry_gen_fullscreen_grid_1d(state, context, step_x, step_y) {
return result; return result;
} }
function geometry_image_quads(state, context) {
const result = new Float32Array(context.images.length * 12);
for (let i = 0; i < context.images.length; ++i) {
const entry = context.images[i];
result[i * 12 + 0] = entry.at.x;
result[i * 12 + 1] = entry.at.y;
result[i * 12 + 2] = entry.at.x + entry.width;
result[i * 12 + 3] = entry.at.y;
result[i * 12 + 4] = entry.at.x;
result[i * 12 + 5] = entry.at.y + entry.height;
result[i * 12 + 6] = entry.at.x + entry.width;
result[i * 12 + 7] = entry.at.y + entry.height;
result[i * 12 + 8] = entry.at.x;
result[i * 12 + 9] = entry.at.y + entry.height;
result[i * 12 + 10] = entry.at.x + entry.width;
result[i * 12 + 11] = entry.at.y;
}
return result;
}

2
client/webgl_listeners.js

@ -653,7 +653,5 @@ async function on_drop(e, state, context) {
const file = e.dataTransfer.files[0]; const file = e.dataTransfer.files[0];
await insert_image(state, context, file); await insert_image(state, context, file);
schedule_draw(state, context);
return false; return false;
} }

52
client/webgl_shaders.js

@ -105,43 +105,32 @@ const sdf_vs_src = `#version 300 es
in vec2 a_a; // point from in vec2 a_a; // point from
in vec2 a_b; // point to in vec2 a_b; // point to
in int a_stroke_id; in int a_stroke_id;
in vec2 a_pressure; in vec2 a_pressure;
uniform vec2 u_scale; uniform vec2 u_scale;
uniform vec2 u_res; uniform vec2 u_res;
uniform vec2 u_translation; uniform vec2 u_translation;
uniform int u_stroke_count; uniform int u_stroke_count;
uniform int u_stroke_texture_size; uniform int u_stroke_texture_size;
uniform highp usampler2D u_stroke_data; uniform highp usampler2D u_stroke_data;
out vec4 v_line; out vec4 v_line;
out vec2 v_texcoord; out vec2 v_texcoord;
out vec3 v_color; out vec3 v_color;
flat out vec2 v_thickness; flat out vec2 v_thickness;
void main() { void main() {
vec2 screen02; vec2 screen02;
float apron = 1.0; // google "futanari inflation rule 34" float apron = 1.0; // google "futanari inflation rule 34"
int stroke_data_y = a_stroke_id / u_stroke_texture_size; int stroke_data_y = a_stroke_id / u_stroke_texture_size;
int stroke_data_x = a_stroke_id % u_stroke_texture_size; int stroke_data_x = a_stroke_id % u_stroke_texture_size;
uvec4 stroke_data = texelFetch(u_stroke_data, ivec2(stroke_data_x, stroke_data_y), 0); uvec4 stroke_data = texelFetch(u_stroke_data, ivec2(stroke_data_x, stroke_data_y), 0);
float radius = float(stroke_data.w); float radius = float(stroke_data.w);
vec2 line_dir = normalize(a_b - a_a); vec2 line_dir = normalize(a_b - a_a);
vec2 up_dir = vec2(line_dir.y, -line_dir.x); vec2 up_dir = vec2(line_dir.y, -line_dir.x);
vec2 pixel = vec2(2.0) / u_res * apron; vec2 pixel = vec2(2.0) / u_res * apron;
float rscale = apron / u_scale.x; float rscale = apron / u_scale.x;
int vertex_index = gl_VertexID % 6; int vertex_index = gl_VertexID % 6;
vec2 outwards; vec2 outwards;
vec2 origin; vec2 origin;
if (vertex_index == 0) { if (vertex_index == 0) {
// "top left" aka "p1" // "top left" aka "p1"
origin = a_a; origin = a_a;
@ -163,17 +152,13 @@ const sdf_vs_src = `#version 300 es
vec2 pos = origin + normalize(outwards) * radius * 2.0 * max(a_pressure.x, a_pressure.y); // doubling is to account for max possible pressure vec2 pos = origin + normalize(outwards) * radius * 2.0 * max(a_pressure.x, a_pressure.y); // doubling is to account for max possible pressure
screen02 = (pos.xy * u_scale + u_translation) / u_res * 2.0 + outwards * pixel; screen02 = (pos.xy * u_scale + u_translation) / u_res * 2.0 + outwards * pixel;
v_texcoord = pos.xy + outwards * rscale; v_texcoord = pos.xy + outwards * rscale;
screen02.y = 2.0 - screen02.y; screen02.y = 2.0 - screen02.y;
v_line = vec4(a_a, a_b); v_line = vec4(a_a, a_b);
v_thickness = radius * a_pressure; // pressure 0.5 is the "neutral" pressure v_thickness = radius * a_pressure; // pressure 0.5 is the "neutral" pressure
v_color = vec3(stroke_data.xyz) / 255.0; v_color = vec3(stroke_data.xyz) / 255.0;
if (a_stroke_id >> 31 != 0) { if (a_stroke_id >> 31 != 0) {
screen02 += vec2(100.0); // shift offscreen 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(a_stroke_id) / float(u_stroke_count)) * 2.0 - 1.0, 1.0);
} }
`; `;
@ -212,12 +197,11 @@ const sdf_fs_src = `#version 300 es
const tquad_vs_src = `#version 300 es const tquad_vs_src = `#version 300 es
in vec2 a_pos; in vec2 a_pos;
in vec2 a_texcoord;
uniform vec2 u_scale; uniform vec2 u_scale;
uniform vec2 u_res; uniform vec2 u_res;
uniform vec2 u_translation; uniform vec2 u_translation;
out vec2 v_texcoord; out vec2 v_texcoord;
void main() { void main() {
@ -225,7 +209,19 @@ const tquad_vs_src = `#version 300 es
vec2 screen02 = screen01 * 2.0; vec2 screen02 = screen01 * 2.0;
screen02.y = 2.0 - screen02.y; screen02.y = 2.0 - screen02.y;
vec2 screen11 = screen02 - 1.0; vec2 screen11 = screen02 - 1.0;
v_texcoord = a_texcoord;
int vertex_index = gl_VertexID % 6;
if (vertex_index == 0) {
v_texcoord = vec2(0.0, 0.0);
} else if (vertex_index == 1 || vertex_index == 5) {
v_texcoord = vec2(1.0, 0.0);
} else if (vertex_index == 2 || vertex_index == 4) {
v_texcoord = vec2(0.0, 1.0);
} else {
v_texcoord = vec2(1.0, 1.0);
}
gl_Position = vec4(screen11, 0, 1); gl_Position = vec4(screen11, 0, 1);
} }
`; `;
@ -236,16 +232,11 @@ const tquad_fs_src = `#version 300 es
in vec2 v_texcoord; in vec2 v_texcoord;
uniform sampler2D u_texture; uniform sampler2D u_texture;
uniform bool u_outline;
layout(location = 0) out vec4 FragColor; layout(location = 0) out vec4 FragColor;
void main() { void main() {
if (!u_outline) { FragColor = texture(u_texture, v_texcoord);
FragColor = texture(u_texture, v_texcoord);
} else {
FragColor = mix(texture(u_texture, v_texcoord), vec4(0.7, 0.7, 0.95, 1), 0.5);
}
} }
`; `;
@ -407,6 +398,15 @@ function init_webgl(state, context) {
'grid': create_program(gl, grid_vs, dots_fs), 'grid': create_program(gl, grid_vs, dots_fs),
}; };
context.locations['image'] = {
'a_pos': gl.getAttribLocation(context.programs['image'], 'a_pos'),
'u_res': gl.getUniformLocation(context.programs['image'], 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['image'], 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['image'], 'u_translation'),
'u_texture': gl.getUniformLocation(context.programs['image'], 'u_texture'),
};
context.locations['debug'] = { context.locations['debug'] = {
'a_pos': gl.getAttribLocation(context.programs['debug'], 'a_pos'), 'a_pos': gl.getAttribLocation(context.programs['debug'], 'a_pos'),
@ -468,6 +468,10 @@ function init_webgl(state, context) {
'b_packed': gl.createBuffer(), 'b_packed': gl.createBuffer(),
}; };
context.buffers['image'] = {
'b_quads': gl.createBuffer(),
};
context.buffers['sdf'] = { context.buffers['sdf'] = {
'b_instance': gl.createBuffer(), 'b_instance': gl.createBuffer(),
'b_dynamic_instance': gl.createBuffer(), 'b_dynamic_instance': gl.createBuffer(),

4
server/http.js

@ -9,7 +9,7 @@ export async function route(req) {
const desk_id = url.searchParams.get('deskId') || '0'; const desk_id = url.searchParams.get('deskId') || '0';
const formdata = await req.formData(); const formdata = await req.formData();
const file = formdata.get('file'); const file = formdata.get('file');
const image_id = math.fast_random32(); const image_id = math.crypto_random32();
Bun.write(config.IMAGEDIR + '/' + image_id, file); Bun.write(config.IMAGEDIR + '/' + image_id, file);
@ -17,4 +17,4 @@ export async function route(req) {
} else if (url.pathname === '/api/ping') { } else if (url.pathname === '/api/ping') {
return new Response('pong'); return new Response('pong');
} }
} }

Loading…
Cancel
Save