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.
 
 
 

317 lines
11 KiB

const sdf_vs_src = `#version 300 es
in vec3 a_pos; // .z is radius
in vec4 a_line;
in vec3 a_color;
in uint a_stroke_id;
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
out vec4 v_line;
out vec2 v_texcoord;
out vec3 v_color;
flat out uint v_stroke_id;
flat out float v_thickness;
void main() {
vec2 screen01 = (a_pos.xy * u_scale + u_translation) / u_res;
vec2 screen02 = screen01 * 2.0;
// Inflate quad by 1 pixel
float apron = 1.0;
vec2 line_dir = normalize(a_line.zw - a_line.xy);
vec2 up_dir = vec2(line_dir.y, -line_dir.x);
vec2 pixel = vec2(2.0) / u_res * apron;
int vertex_index = gl_VertexID % 4;
if (vertex_index == 0) {
// "top left" aka "p1"
screen02 += up_dir * pixel - line_dir * pixel;
v_texcoord = a_pos.xy + up_dir * 1.0 / u_scale - line_dir * 1.0 / u_scale;
} else if (vertex_index == 1) {
// "top right" aka "p2"
screen02 += up_dir * pixel + line_dir * pixel;
v_texcoord = a_pos.xy + up_dir * 1.0 / u_scale + line_dir * 1.0 / u_scale;
} else if (vertex_index == 2) {
// "bottom left" aka "p3"
screen02 += -up_dir * pixel - line_dir * pixel;
v_texcoord = a_pos.xy - up_dir * 1.0 / u_scale - line_dir * 1.0 / u_scale;
} else {
// "bottom right" aka "p4"
screen02 += -up_dir * pixel + line_dir * pixel;
v_texcoord = a_pos.xy - up_dir * 1.0 / u_scale + line_dir * 1.0 / u_scale;
}
screen02.y = 2.0 - screen02.y;
v_line = a_line;
v_color = a_color;
v_thickness = a_pos.z;
v_stroke_id = a_stroke_id;
gl_Position = vec4(screen02 - 1.0, 0.0, 1);
}
`;
const sdf_fs_src = `#version 300 es
precision highp float;
uniform int u_debug_mode;
in vec4 v_line;
in vec2 v_texcoord;
in vec3 v_color;
flat in float v_thickness;
out vec4 FragColor;
void main() {
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(pa - ba * h) - v_thickness / 2.0;
float fade = 0.5 * length(fwidth(v_texcoord));
float alpha = 1.0 - smoothstep(-fade, fade, dist);
// float alpha = 1.0 - step(0.0, dist);
if (u_debug_mode == 1) {
FragColor = vec4(1.0, 0.0, 0.0, 0.1);
} else {
FragColor = vec4(v_color * alpha, alpha);
// FragColor = vec4(v_color * alpha, 0.1 + alpha);
}
}
`;
const tiles_fs_src = `#version 300 es
precision highp float;
uniform int u_debug_mode;
in vec4 v_line;
in vec2 v_texcoord;
in vec3 v_color;
flat in uint v_stroke_id;
flat in float v_thickness;
//out uint TileId;
out vec4 FragColor;
void main() {
//TileId = uint(1);
vec3 color = vec3(float(v_stroke_id * 3245u % 255u) / 255.0, float(v_stroke_id * 7343u % 255u) / 255.0, float(v_stroke_id * 5528u % 255u) / 255.0);
FragColor = vec4(color, 1);
}
`;
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 highp 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);
context.gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
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);
const tiles_fs = create_shader(gl, gl.FRAGMENT_SHADER, tiles_fs_src);
context.programs['image'] = create_program(gl, quad_vs, quad_fs);
context.programs['sdf'] = {
'main': create_program(gl, sdf_vs, sdf_fs),
'tiles': create_program(gl, sdf_vs, tiles_fs), // same vertex shader
};
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'] = {
'main': {
'a_pos': gl.getAttribLocation(context.programs['sdf'].main, 'a_pos'),
'a_line': gl.getAttribLocation(context.programs['sdf'].main, 'a_line'),
'a_color': gl.getAttribLocation(context.programs['sdf'].main, 'a_color'),
'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].main, 'a_stroke_id'),
'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_texture_points': gl.getUniformLocation(context.programs['sdf'].main, 'u_texture_points'),
'u_texture_indices': gl.getUniformLocation(context.programs['sdf'].main, 'u_texture_indices'),
'u_debug_mode': gl.getUniformLocation(context.programs['sdf'].main, 'u_debug_mode'),
},
'tiles': {
'a_pos': gl.getAttribLocation(context.programs['sdf'].tiles, 'a_pos'),
'a_line': gl.getAttribLocation(context.programs['sdf'].tiles, 'a_line'),
'a_color': gl.getAttribLocation(context.programs['sdf'].tiles, 'a_color'),
'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].tiles, 'a_stroke_id'),
'u_res': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_translation'),
'u_texture_points': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_texture_points'),
'u_texture_indices': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_texture_indices'),
'u_debug_mode': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_debug_mode'),
}
};
context.buffers['image'] = {
'b_pos': context.gl.createBuffer(),
'b_texcoord': context.gl.createBuffer(),
};
context.buffers['sdf'] = {
'b_packed_static': gl.createBuffer(),
'b_packed_dynamic': gl.createBuffer(),
'b_packed_static_index': gl.createBuffer(),
'b_packed_dynamic_index': gl.createBuffer(),
};
context.textures['sdf'] = {
'tiles': gl.createTexture(),
};
context.framebuffers['sdf'] = {
'tiles': gl.createFramebuffer(),
};
gl.bindTexture(gl.TEXTURE_2D, context.textures['sdf'].tiles);
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.R32UI, context.canvas.width, context.canvas.height, 0, gl.RED_INTEGER, gl.UNSIGNED_INT, null);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, Math.ceil(context.canvas.width / config.tile_size), Math.ceil(context.canvas.height / config.tile_size), 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.bindFramebuffer(gl.FRAMEBUFFER, context.framebuffers['sdf'].tiles);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, context.textures['sdf'].tiles, 0);
// gl.bindFramebuffer(gl.FRAMEBUFFER, null);
context.textures['image'] = {};
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;
gl.bindTexture(gl.TEXTURE_2D, context.textures['sdf'].tiles);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, Math.ceil(context.canvas.width / config.tile_size), Math.ceil(context.canvas.height / config.tile_size), 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
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);
}