Browse Source

Resize handles for images

ssao
A.Olokhtonov 5 months ago
parent
commit
c1583cb8fb
  1. 1
      client/index.js
  2. 52
      client/webgl_draw.js
  3. 103
      client/webgl_geometry.js
  4. 30
      client/webgl_shaders.js

1
client/index.js

@ -26,6 +26,7 @@ const config = { @@ -26,6 +26,7 @@ const config = {
initial_wasm_bytes: 4096,
stroke_texture_size: 1024, // means no more than 1024^2 = 1M strokes in total (this is a LOT. HMH blackboard has like 80K)
dynamic_stroke_texture_size: 128, // means no more than 128^2 = 16K dynamic strokes at once
ui_texture_size: 16,
bvh_fullnode_depth: 5,
pattern_fadeout_min: 0.3,
pattern_fadeout_max: 0.75,

52
client/webgl_draw.js

@ -273,6 +273,7 @@ async function draw(state, context, animate, ts) { @@ -273,6 +273,7 @@ async function draw(state, context, animate, ts) {
gl.uniform1i(locations['u_debug_mode'], state.debug.red);
gl.uniform1i(locations['u_stroke_data'], 0);
gl.uniform1i(locations['u_stroke_texture_size'], config.stroke_texture_size);
gl.uniform1f(locations['u_fixed_pixel_width'], 0);
gl.enableVertexAttribArray(locations['a_a']);
gl.enableVertexAttribArray(locations['a_b']);
@ -328,10 +329,12 @@ async function draw(state, context, animate, ts) { @@ -328,10 +329,12 @@ async function draw(state, context, animate, ts) {
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_stroke_count'], context.dynamic_stroke_count);
gl.uniform1i(locations['u_debug_mode'], state.debug.red);
gl.uniform1i(locations['u_stroke_data'], 0);
gl.uniform1i(locations['u_stroke_texture_size'], config.dynamic_stroke_texture_size);
gl.uniform1f(locations['u_fixed_pixel_width'], 0);
gl.enableVertexAttribArray(locations['a_a']);
gl.enableVertexAttribArray(locations['a_b']);
@ -357,6 +360,55 @@ async function draw(state, context, animate, ts) { @@ -357,6 +360,55 @@ async function draw(state, context, animate, ts) {
gl.vertexAttribDivisor(locations['a_pressure'], 0);
}
// HUD: resize handles, etc
if (context.active_image !== null) {
const handles = geometry_generate_handles(state, context, context.active_image);
const ui_segments = 7 * 4 - 1; // each square = 4, each line = 1, square->line = 1, line->square = 1
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
gl.bufferData(gl.ARRAY_BUFFER, handles.points.byteLength + handles.ids.byteLength + handles.pressures.byteLength, gl.STREAM_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, handles.points);
gl.bufferSubData(gl.ARRAY_BUFFER, handles.points.byteLength, handles.ids);
gl.bufferSubData(gl.ARRAY_BUFFER, handles.points.byteLength + handles.ids.byteLength, handles.pressures);
gl.bindTexture(gl.TEXTURE_2D, context.textures['ui']);
upload_square_rgba16ui_texture(gl, handles.stroke_data, config.ui_texture_size);
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_stroke_count'], 8);
gl.uniform1i(locations['u_debug_mode'], 0);
gl.uniform1i(locations['u_stroke_data'], 0);
gl.uniform1i(locations['u_stroke_texture_size'], config.ui_texture_size);
gl.uniform1f(locations['u_fixed_pixel_width'], 2);
gl.enableVertexAttribArray(locations['a_a']);
gl.enableVertexAttribArray(locations['a_b']);
gl.enableVertexAttribArray(locations['a_stroke_id']);
gl.enableVertexAttribArray(locations['a_pressure']);
gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, handles.points.byteLength);
gl.vertexAttribPointer(locations['a_pressure'], 2, gl.UNSIGNED_BYTE, true, 1, handles.points.byteLength + handles.ids.byteLength);
gl.vertexAttribDivisor(locations['a_a'], 1);
gl.vertexAttribDivisor(locations['a_b'], 1);
gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
gl.vertexAttribDivisor(locations['a_pressure'], 1);
// Static draw (everything already bound)
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, ui_segments);
// I don't really know why I need to do this, but it
// makes background patter drawcall work properly
gl.vertexAttribDivisor(locations['a_a'], 0);
gl.vertexAttribDivisor(locations['a_b'], 0);
gl.vertexAttribDivisor(locations['a_stroke_id'], 0);
gl.vertexAttribDivisor(locations['a_pressure'], 0);
}
document.getElementById('debug-stats').innerHTML = `
<span>Strokes onscreen: ${context.clipped_indices.size}</span>
<span>Segments onscreen: ${segment_count}</span>

103
client/webgl_geometry.js

@ -375,3 +375,106 @@ function geometry_image_quads(state, context) { @@ -375,3 +375,106 @@ function geometry_image_quads(state, context) {
return result;
}
function geometry_generate_handles(state, context, active_image) {
let image = null;
for (const entry of context.images) {
if (entry.key === active_image) {
image = entry;
break;
}
}
const x1 = image.at.x;
const y1 = image.at.y;
const x2 = image.at.x + image.width;
const y2 = image.at.y + image.height;
const width = 4 / state.canvas.zoom;
const points = new Float32Array([
// top-left handle
x1 - width, y1 - width,
x1 + width, y1 - width,
x1 + width, y1 + width,
x1 - width, y1 + width,
x1 - width, y1 - width,
// -> top-right
x1 + width, y1,
x2 - width, y1,
// top-right handle
x2 - width, y1 - width,
x2 + width, y1 - width,
x2 + width, y1 + width,
x2 - width, y1 + width,
x2 - width, y1 - width,
// -> bottom-right
x2, y1 + width,
x2, y2 - width,
// bottom-right handle
x2 - width, y2 - width,
x2 + width, y2 - width,
x2 + width, y2 + width,
x2 - width, y2 + width,
x2 - width, y2 - width,
// -> bottom-left
x2 - width, y2,
x1 + width, y2,
// bottom-left handle
x1 - width, y2 - width,
x1 + width, y2 - width,
x1 + width, y2 + width,
x1 - width, y2 + width,
x1 - width, y2 - width,
// -> top-left
x1, y2 - width,
x1, y1 + width,
]);
const ids = new Uint32Array([
0, 0, 0, 0, 0 | (1 << 31),
1, 1 | (1 << 31),
2, 2, 2, 2, 2 | (1 << 31),
3, 3 | (1 << 31),
4, 4, 4, 4, 4 | (1 << 31),
5, 5 | (1 << 31),
6, 6, 6, 6, 6 | (1 << 31),
7, 7 | (1 << 31),
]);
const pressures = new Uint8Array([
128, 128, 128, 128, 128,
128, 128, 128,
128, 128, 128, 128, 128,
128, 128, 128,
128, 128, 128, 128, 128,
128, 128, 128,
128, 128, 128, 128, 128,
128, 128, 128,
]);
const stroke_data = serializer_create(8 * 4 * 2);
for (let i = 0; i < 8; ++i) {
ser_u16(stroke_data, 34);
ser_u16(stroke_data, 139);
ser_u16(stroke_data, 230);
ser_u16(stroke_data, 0);
}
return {
'points': points,
'ids': ids,
'pressures': pressures,
'stroke_data': stroke_data,
};
}

30
client/webgl_shaders.js

@ -104,33 +104,48 @@ const nop_fs_src = `#version 300 es @@ -104,33 +104,48 @@ const nop_fs_src = `#version 300 es
const sdf_vs_src = `#version 300 es
in vec2 a_a; // point from
in vec2 a_b; // point to
in int a_stroke_id;
in vec2 a_pressure;
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
uniform int u_stroke_count;
uniform int u_stroke_texture_size;
uniform highp usampler2D u_stroke_data;
uniform float u_fixed_pixel_width;
out vec4 v_line;
out vec2 v_texcoord;
out vec3 v_color;
flat out vec2 v_thickness;
void main() {
vec2 screen02;
float apron = 1.0; // google "futanari inflation rule 34"
int stroke_data_y = 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);
float radius = float(stroke_data.w);
vec2 line_dir = normalize(a_b - a_a);
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;
uvec4 stroke_data = texelFetch(u_stroke_data, ivec2(stroke_data_x, stroke_data_y), 0);
float radius = float(stroke_data.w);
if (u_fixed_pixel_width > 0.0) {
radius = u_fixed_pixel_width / u_scale.x;
}
float rscale = apron / u_scale.x;
int vertex_index = gl_VertexID % 6;
vec2 outwards;
vec2 origin;
if (vertex_index == 0) {
// "top left" aka "p1"
origin = a_a;
@ -150,7 +165,7 @@ const sdf_vs_src = `#version 300 es @@ -150,7 +165,7 @@ 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
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 * apron;
v_texcoord = pos.xy + outwards * rscale;
screen02.y = 2.0 - screen02.y;
v_line = vec4(a_a, a_b);
@ -449,6 +464,7 @@ function init_webgl(state, context) { @@ -449,6 +464,7 @@ function init_webgl(state, context) {
'u_stroke_count': gl.getUniformLocation(context.programs['sdf'].main, 'u_stroke_count'),
'u_stroke_data': gl.getUniformLocation(context.programs['sdf'].main, 'u_stroke_data'),
'u_stroke_texture_size': gl.getUniformLocation(context.programs['sdf'].main, 'u_stroke_texture_size'),
'u_fixed_pixel_width': gl.getUniformLocation(context.programs['sdf'].main, 'u_fixed_pixel_width'),
}
};
@ -495,6 +511,7 @@ function init_webgl(state, context) { @@ -495,6 +511,7 @@ function init_webgl(state, context) {
context.textures = {
'stroke_data': gl.createTexture(),
'dynamic_stroke_data': gl.createTexture(),
'ui': gl.createTexture(),
};
gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
@ -507,6 +524,11 @@ function init_webgl(state, context) { @@ -507,6 +524,11 @@ function init_webgl(state, context) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16UI, config.dynamic_stroke_texture_size, config.dynamic_stroke_texture_size, 0, gl.RGBA_INTEGER, gl.UNSIGNED_SHORT, new Uint16Array(config.dynamic_stroke_texture_size * config.dynamic_stroke_texture_size * 4)); // fill the whole texture once with zeroes to kill a warning about a partial upload
gl.bindTexture(gl.TEXTURE_2D, context.textures['ui']);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16UI, config.ui_texture_size, config.ui_texture_size, 0, gl.RGBA_INTEGER, gl.UNSIGNED_SHORT, new Uint16Array(config.ui_texture_size * config.ui_texture_size * 4)); // fill the whole texture once with zeroes to kill a warning about a partial upload
const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0];

Loading…
Cancel
Save