You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
16 KiB

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;
2 years ago
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;
uniform vec2 u_ssao;
out vec4 v_line;
out vec2 v_texcoord;
out vec3 v_color;
flat out vec2 v_thickness;
2 years ago
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;
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;
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;
outwards = up_dir - line_dir;
} else if (vertex_index == 1 || vertex_index == 5) {
// "top right" aka "p2"
origin = a_b;
outwards = up_dir + line_dir;
} else if (vertex_index == 2 || vertex_index == 4) {
// "bottom left" aka "p3"
origin = a_a;
outwards = -up_dir - line_dir;
} else {
// "bottom right" aka "p4"
origin = a_b;
outwards = -up_dir + line_dir;
}
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 * u_ssao * 2.0 + outwards * pixel * apron;
v_texcoord = pos.xy;
// v_texcoord = pos.xy + outwards * rscale;
2 years ago
screen02.y = 2.0 - screen02.y;
v_line = vec4(a_a, a_b);
v_thickness = radius * a_pressure; // pressure 0.5 is the "neutral" pressure
v_color = vec3(stroke_data.xyz) / 255.0;
if (a_stroke_id >> 31 != 0) {
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);
2 years ago
}
`;
const sdf_fs_src = `#version 300 es
precision highp float;
2 years ago
uniform int u_debug_mode;
in vec4 v_line;
in vec2 v_texcoord;
in vec3 v_color;
flat in vec2 v_thickness;
layout(location = 0) out vec4 FragColor;
2 years ago
void main() {
if (u_debug_mode == 0) {
vec2 a = v_line.xy;
vec2 b = v_line.zw;
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(v_texcoord - (a + ba * h)) - mix(v_thickness.x, v_thickness.y, h);
2 years ago
/*
float fade = 0.5 * length(fwidth(v_texcoord));
float alpha = 1.0 - smoothstep(-fade, fade, dist);
*/
float alpha = 1.0 - step(0.0, dist);
alpha = clamp(0.0, 1.0, alpha);
if (alpha == 0.0) {
discard;
} else {
alpha = 0.5;
FragColor = vec4(v_color * alpha, alpha);
}
} else {
FragColor = vec4(0.2, 0.0, 0.0, 0.2);
}
2 years ago
}
`;
const tquad_vs_src = `#version 300 es
in vec2 a_pos;
2 years ago
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
6 months ago
out vec2 v_texcoord;
2 years ago
void main() {
vec2 screen01 = (a_pos * u_scale + u_translation) / u_res;
vec2 screen02 = screen01 * 2.0;
6 months ago
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);
}
screen02.y = 2.0 - screen02.y;
vec2 screen11 = screen02 - 1.0;
2 years ago
gl_Position = vec4(screen11, 0, 1);
2 years ago
}
`;
const tquad_fs_src = `#version 300 es
precision highp float;
2 years ago
in vec2 v_texcoord;
2 years ago
uniform sampler2D u_texture;
uniform int u_solid;
uniform vec4 u_color;
2 years ago
layout(location = 0) out vec4 FragColor;
2 years ago
void main() {
if (u_solid == 0) {
FragColor = texture(u_texture, v_texcoord);
} else {
FragColor = u_color;
}
2 years ago
}
`;
const grid_vs_src = `#version 300 es
in vec2 a_data; // per-instance
out float v_fadeout;
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
uniform float u_fadeout;
void main() {
vec2 origin;
vec2 minor_offset;
vec2 major_offset;
vec2 pixel = 2.0 / u_res;
if (a_data.x > 0.0) {
// Vertical, treat Y as X
float x = a_data.y;
origin = vec2(x, 0.0);
minor_offset = pixel * vec2(1.0, 0.0);
major_offset = vec2(0.0, 2.0);
} else {
// Horizontal, treat Y as Y
float y = a_data.y;
origin = vec2(0.0, y);
minor_offset = pixel * vec2(0.0, 1.0);
major_offset = vec2(2.0, 0.0);
}
vec2 v = (origin * u_scale + u_translation) / u_res * 2.0;
vec2 pos;
if (a_data.x > 0.0) {
v.y = 0.0;
} else {
v.x = 0.0;
}
if (gl_VertexID % 6 == 0) {
pos = v;
} else if (gl_VertexID % 6 == 1 || gl_VertexID % 6 == 5) {
pos = v + (a_data.x > 0.0 ? minor_offset : major_offset);
//pos = v + minor_offset;
} else if (gl_VertexID % 6 == 2 || gl_VertexID % 6 == 4) {
pos = v + (a_data.x > 0.0 ? major_offset : minor_offset);
//pos = v + major_offset;
} else if (gl_VertexID % 6 == 3) {
pos = v + major_offset + minor_offset;
//pos = v + major_offset + minor_offset;
}
vec2 screen02 = pos;
screen02.y = 2.0 - screen02.y;
v_fadeout = u_fadeout;
gl_Position = vec4(screen02 - 1.0, 0.0, 1.0);
}
`;
const dots_vs_src = `#version 300 es
in vec2 a_center; // per-instance
out float v_fadeout;
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
uniform float u_fadeout;
void main() {
vec2 v = (a_center * u_scale + u_translation) / u_res * 2.0;
vec2 pos;
vec2 pixel = 2.0 / u_res;
if (gl_VertexID % 6 == 0) {
pos = v + pixel * vec2(-1.0);
} else if (gl_VertexID % 6 == 1) {
pos = v + pixel * vec2(1.0, -1.0);
} else if (gl_VertexID % 6 == 2) {
pos = v + pixel * vec2(-1.0, 1.0);
} else if (gl_VertexID % 6 == 3) {
pos = v + pixel * vec2(1.0);
} else if (gl_VertexID % 6 == 4) {
pos = v + pixel * vec2(-1.0, 1.0);
} else if (gl_VertexID % 6 == 5) {
pos = v + pixel * vec2(1.0, -1.0);
}
vec2 screen02 = pos;
screen02.y = 2.0 - screen02.y;
v_fadeout = u_fadeout;
gl_Position = vec4(screen02 - 1.0, 0.0, 1.0);
}
`;
const dots_fs_src = `#version 300 es
precision highp float;
in float v_fadeout;
layout(location = 0) out vec4 FragColor;
void main() {
vec3 color = vec3(0.5);
FragColor = vec4(color * v_fadeout, v_fadeout);
}
`;
2 years ago
function init_webgl(state, context) {
context.canvas = document.querySelector('#c');
context.gl = context.canvas.getContext('webgl2', {
'preserveDrawingBuffer': true,
2 years ago
'desynchronized': true,
'antialias': true,
2 years ago
});
2 years ago
const gl = context.gl;
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.NOTEQUAL);
context.gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (context.gpu_timer_ext === null) {
context.gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query');
}
2 years ago
const quad_vs = create_shader(gl, gl.VERTEX_SHADER, tquad_vs_src);
const quad_fs = create_shader(gl, gl.FRAGMENT_SHADER, tquad_fs_src);
const sdf_vs = create_shader(gl, gl.VERTEX_SHADER, sdf_vs_src);
const sdf_fs = create_shader(gl, gl.FRAGMENT_SHADER, sdf_fs_src);
2 years ago
const dots_vs = create_shader(gl, gl.VERTEX_SHADER, dots_vs_src);
const dots_fs = create_shader(gl, gl.FRAGMENT_SHADER, dots_fs_src);
const grid_vs = create_shader(gl, gl.VERTEX_SHADER, grid_vs_src);
context.programs['image'] = create_program(gl, quad_vs, quad_fs);
context.programs['sdf'] = {
'main': create_program(gl, sdf_vs, sdf_fs),
};
context.programs['pattern'] = {
'dots': create_program(gl, dots_vs, dots_fs),
'grid': create_program(gl, grid_vs, dots_fs),
};
6 months ago
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'),
'u_solid': gl.getUniformLocation(context.programs['image'], 'u_solid'),
'u_color': gl.getUniformLocation(context.programs['image'], 'u_color'),
6 months ago
};
context.locations['sdf'] = {
'main': {
'a_a': gl.getAttribLocation(context.programs['sdf'].main, 'a_a'),
'a_b': gl.getAttribLocation(context.programs['sdf'].main, 'a_b'),
'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].main, 'a_stroke_id'),
'a_pressure': gl.getAttribLocation(context.programs['sdf'].main, 'a_pressure'),
'u_res': gl.getUniformLocation(context.programs['sdf'].main, 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['sdf'].main, 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['sdf'].main, 'u_translation'),
'u_debug_mode': gl.getUniformLocation(context.programs['sdf'].main, 'u_debug_mode'),
'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'),
'u_ssao': gl.getUniformLocation(context.programs['sdf'].main, 'u_ssao'),
}
2 years ago
};
context.locations['pattern'] = {
'dots': {
'a_xy': gl.getAttribLocation(context.programs['pattern'].dots, 'a_xy'),
'a_center': gl.getAttribLocation(context.programs['pattern'].dots, 'a_center'),
'u_res': gl.getUniformLocation(context.programs['pattern'].dots, 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['pattern'].dots, 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['pattern'].dots, 'u_translation'),
'u_fadeout': gl.getUniformLocation(context.programs['pattern'].dots, 'u_fadeout'),
},
'grid': {
'a_data': gl.getAttribLocation(context.programs['pattern'].grid, 'a_data'),
'u_res': gl.getUniformLocation(context.programs['pattern'].grid, 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['pattern'].grid, 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['pattern'].grid, 'u_translation'),
'u_fadeout': gl.getUniformLocation(context.programs['pattern'].grid, 'u_fadeout'),
}
};
6 months ago
context.buffers['image'] = {
'b_quads': gl.createBuffer(),
};
context.buffers['sdf'] = {
'b_instance': gl.createBuffer(),
'b_dynamic_instance': gl.createBuffer(),
};
context.buffers['pattern'] = {
'b_instance_dot': gl.createBuffer(),
'b_instance_grid': gl.createBuffer(),
'b_dot': gl.createBuffer(),
};
context.textures = {
'stroke_data': gl.createTexture(),
'dynamic_stroke_data': gl.createTexture(),
'ui': gl.createTexture(),
};
gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
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.stroke_texture_size, config.stroke_texture_size, 0, gl.RGBA_INTEGER, gl.UNSIGNED_SHORT, new Uint16Array(config.stroke_texture_size * config.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['dynamic_stroke_data']);
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.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
2 years ago
const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0];
2 years ago
2 years ago
let width;
let height;
if (entry.devicePixelContentBoxSize) {
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
} else if (entry.contentBoxSize) {
2 years ago
// fallback for Safari that will not always be correct
2 years ago
width = Math.round(entry.contentBoxSize[0].inlineSize * devicePixelRatio);
height = Math.round(entry.contentBoxSize[0].blockSize * devicePixelRatio);
}
context.canvas.width = width * config.ssao;
context.canvas.height = height * config.ssao;
2 years ago
schedule_draw(state, context);
2 years ago
}
const resize_observer = new ResizeObserver(resize_canvas);
resize_observer.observe(context.canvas);
}
function create_shader(gl, type, source) {
const shader = gl.createShader(type);
2 years ago
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
return shader;
}
console.error(type, ':', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
function create_program(gl, vs, fs) {
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
return program;
}
console.error('link:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}