|
|
|
const sdf_vs_src = `#version 300 es
|
|
|
|
in vec2 a_pos;
|
|
|
|
in vec3 a_color;
|
|
|
|
|
|
|
|
uniform vec2 u_scale;
|
|
|
|
uniform vec2 u_res;
|
|
|
|
uniform vec2 u_translation;
|
|
|
|
|
|
|
|
out vec2 v_texcoord;
|
|
|
|
out vec3 v_color;
|
|
|
|
flat out int v_vertexid;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
vec2 screen01 = (a_pos * u_scale + u_translation) / u_res;
|
|
|
|
vec2 screen02 = screen01 * 2.0;
|
|
|
|
screen02.y = 2.0 - screen02.y;
|
|
|
|
|
|
|
|
v_texcoord = a_pos + vec2(0.5);
|
|
|
|
v_vertexid = gl_VertexID;
|
|
|
|
v_color = a_color;
|
|
|
|
|
|
|
|
gl_Position = vec4(screen02 - 1.0, 0, 1);
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const sdf_fs_src = `#version 300 es
|
|
|
|
precision mediump float;
|
|
|
|
|
|
|
|
uniform sampler2D u_texture_points;
|
|
|
|
uniform highp usampler2D u_texture_indices;
|
|
|
|
|
|
|
|
in vec2 v_texcoord;
|
|
|
|
in vec3 v_color;
|
|
|
|
flat in int v_vertexid;
|
|
|
|
|
|
|
|
out vec4 FragColor;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
float mindist = 99999.9;
|
|
|
|
float th = 5.0;
|
|
|
|
|
|
|
|
uvec4 indices = texelFetch(u_texture_indices, ivec2(v_vertexid / 6, 0), 0);
|
|
|
|
|
|
|
|
uint v_from = indices.x;
|
|
|
|
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);
|
|
|
|
|
|
|
|
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(pa - ba * h) - th;
|
|
|
|
|
|
|
|
mindist = min(mindist, dist);
|
|
|
|
}
|
|
|
|
|
|
|
|
float fade = 0.5 * length(fwidth(v_texcoord));
|
|
|
|
float alpha = 1.0 - smoothstep(-fade, fade, mindist);
|
|
|
|
|
|
|
|
FragColor = vec4(v_color * alpha, 0.1 + alpha);
|
|
|
|
// FragColor = vec4(v_color, 1.0);
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const tquad_vs_src = `#version 300 es
|
|
|
|
in vec2 a_pos;
|
|
|
|
in vec2 a_texcoord;
|
|
|
|
|
|
|
|
uniform vec2 u_scale;
|
|
|
|
uniform vec2 u_res;
|
|
|
|
uniform vec2 u_translation;
|
|
|
|
|
|
|
|
out vec2 v_texcoord;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
vec2 screen01 = (a_pos * u_scale + u_translation) / u_res;
|
|
|
|
vec2 screen02 = screen01 * 2.0;
|
|
|
|
screen02.y = 2.0 - screen02.y;
|
|
|
|
vec2 screen11 = screen02 - 1.0;
|
|
|
|
v_texcoord = a_texcoord;
|
|
|
|
gl_Position = vec4(screen11, 0, 1);
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const tquad_fs_src = `#version 300 es
|
|
|
|
precision mediump float;
|
|
|
|
|
|
|
|
in vec2 v_texcoord;
|
|
|
|
|
|
|
|
uniform sampler2D u_texture;
|
|
|
|
uniform bool u_outline;
|
|
|
|
|
|
|
|
out vec4 FragColor;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
if (!u_outline) {
|
|
|
|
FragColor = texture(u_texture, v_texcoord);
|
|
|
|
} else {
|
|
|
|
FragColor = mix(texture(u_texture, v_texcoord), vec4(0.7, 0.7, 0.95, 1), 0.5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
function init_webgl(state, context) {
|
|
|
|
context.canvas = document.querySelector('#c');
|
|
|
|
context.gl = context.canvas.getContext('webgl2', {
|
|
|
|
'preserveDrawingBuffer': true,
|
|
|
|
'desynchronized': true,
|
|
|
|
'antialias': false,
|
|
|
|
});
|
|
|
|
|
|
|
|
const gl = context.gl;
|
|
|
|
|
|
|
|
gl.enable(gl.BLEND);
|
|
|
|
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
context.programs['image'] = create_program(gl, quad_vs, quad_fs);
|
|
|
|
context.programs['sdf'] = create_program(gl, sdf_vs, sdf_fs);
|
|
|
|
|
|
|
|
context.locations['image'] = {
|
|
|
|
'a_pos': gl.getAttribLocation(context.programs['image'], 'a_pos'),
|
|
|
|
'a_texcoord': gl.getAttribLocation(context.programs['image'], 'a_texcoord'),
|
|
|
|
|
|
|
|
'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_outline': gl.getUniformLocation(context.programs['image'], 'u_outline'),
|
|
|
|
'u_texture': gl.getUniformLocation(context.programs['image'], 'u_texture'),
|
|
|
|
};
|
|
|
|
|
|
|
|
context.locations['sdf'] = {
|
|
|
|
'a_pos': gl.getAttribLocation(context.programs['sdf'], 'a_pos'),
|
|
|
|
'a_color': gl.getAttribLocation(context.programs['sdf'], 'a_color'),
|
|
|
|
|
|
|
|
'u_res': gl.getUniformLocation(context.programs['sdf'], 'u_res'),
|
|
|
|
'u_scale': gl.getUniformLocation(context.programs['sdf'], 'u_scale'),
|
|
|
|
'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'),
|
|
|
|
};
|
|
|
|
|
|
|
|
context.buffers['image'] = {
|
|
|
|
'b_pos': context.gl.createBuffer(),
|
|
|
|
'b_texcoord': context.gl.createBuffer(),
|
|
|
|
};
|
|
|
|
|
|
|
|
context.buffers['sdf'] = {
|
|
|
|
'b_packed': context.gl.createBuffer(),
|
|
|
|
};
|
|
|
|
|
|
|
|
context.textures['sdf'] = {
|
|
|
|
'points': gl.createTexture(),
|
|
|
|
'indices': gl.createTexture()
|
|
|
|
};
|
|
|
|
|
|
|
|
const resize_canvas = (entries) => {
|
|
|
|
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
|
|
|
|
const entry = entries[0];
|
|
|
|
|
|
|
|
let width;
|
|
|
|
let height;
|
|
|
|
|
|
|
|
if (entry.devicePixelContentBoxSize) {
|
|
|
|
width = entry.devicePixelContentBoxSize[0].inlineSize;
|
|
|
|
height = entry.devicePixelContentBoxSize[0].blockSize;
|
|
|
|
} else if (entry.contentBoxSize) {
|
|
|
|
// fallback for Safari that will not always be correct
|
|
|
|
width = Math.round(entry.contentBoxSize[0].inlineSize * devicePixelRatio);
|
|
|
|
height = Math.round(entry.contentBoxSize[0].blockSize * devicePixelRatio);
|
|
|
|
}
|
|
|
|
|
|
|
|
context.canvas.width = width;
|
|
|
|
context.canvas.height = height;
|
|
|
|
|
|
|
|
schedule_draw(state, context);
|
|
|
|
}
|
|
|
|
|
|
|
|
const resize_observer = new ResizeObserver(resize_canvas);
|
|
|
|
resize_observer.observe(context.canvas);
|
|
|
|
}
|
|
|
|
|
|
|
|
function create_shader(gl, type, source) {
|
|
|
|
const shader = gl.createShader(type);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|