kanat is too fat
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.
 
 
 

250 lines
7.5 KiB

let programs = {};
let buffers = {};
let timers = {};
let config = {
bytes_per_quad: 20,
w: 24,
h: 24,
};
let canvas = null;
let gl = null;
let gpu_timer_ext = null;
let offset = { x: 0, y: 0 };
let scale = 1;
const tquad_vs_src = `#version 300 es
in vec2 a_pos;
in vec2 a_size;
in vec4 a_color;
uniform vec2 u_res;
uniform vec2 u_translation;
uniform float u_scale;
out vec4 v_color;
void main() {
int vertex_index = gl_VertexID % 6;
vec2 corner;
if (vertex_index == 0) {
// "top left" aka "p1"
corner = a_pos + vec2(1.0);
} else if (vertex_index == 1 || vertex_index == 5) {
// "top right" aka "p2"
corner = a_pos + vec2(a_size.x - 1.0, 1.0);
} else if (vertex_index == 2 || vertex_index == 4) {
// "bottom left" aka "p3"
corner = a_pos + vec2(1.0, a_size.y - 1.0);
} else {
// "bottom right" aka "p4"
corner = a_pos + a_size - vec2(1.0);
}
vec2 screen02 = (corner.xy * vec2(u_scale) + u_translation) / u_res * 2.0;
screen02.y = 2.0 - screen02.y;
v_color = a_color;
gl_Position = vec4(screen02 - 1.0, 1.0, 1.0);
}
`;
const tquad_fs_src = `#version 300 es
precision highp float;
in vec4 v_color;
layout(location = 0) out vec4 FragColor;
void main() {
FragColor = v_color;
}
`;
function schedule_draw() {
if (!timers.raf) {
window.requestAnimationFrame(draw);
timers.raf = true;
}
}
function draw() {
const cpu_before = performance.now();
const width = window.innerWidth;
const height = window.innerHeight;
let query = null;
if (gpu_timer_ext !== null) {
query = gl.createQuery();
gl.beginQuery(gpu_timer_ext.TIME_ELAPSED_EXT, query);
}
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
const quads = generate('0');
if (quads.count > 0) {
const program = programs['quad'];
gl.useProgram(program.program);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed']);
gl.bufferData(gl.ARRAY_BUFFER, quads.count * config.bytes_per_quad, gl.STATIC_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, quads.pos);
gl.bufferSubData(gl.ARRAY_BUFFER, quads.pos.byteLength, quads.size);
gl.bufferSubData(gl.ARRAY_BUFFER, quads.pos.byteLength + quads.size.byteLength, quads.color);
gl.uniform2f(program.locations['u_res'], canvas.width, canvas.height);
gl.uniform2f(program.locations['u_translation'], offset.x, offset.y);
gl.uniform1f(program.locations['u_scale'], scale);
gl.enableVertexAttribArray(program.locations['a_pos']);
gl.enableVertexAttribArray(program.locations['a_size']);
gl.enableVertexAttribArray(program.locations['a_color']);
gl.vertexAttribPointer(program.locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, 0);
gl.vertexAttribPointer(program.locations['a_size'], 2, gl.FLOAT, false, 2 * 4, quads.pos.byteLength);
gl.vertexAttribPointer(program.locations['a_color'], 4, gl.UNSIGNED_BYTE, true, 4 * 1, quads.pos.byteLength + quads.size.byteLength);
gl.vertexAttribDivisor(program.locations['a_pos'], 1);
gl.vertexAttribDivisor(program.locations['a_size'], 1);
gl.vertexAttribDivisor(program.locations['a_color'], 1);
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, quads.count);
gl.vertexAttribDivisor(program.locations['a_pos'], 0);
gl.vertexAttribDivisor(program.locations['a_size'], 0);
gl.vertexAttribDivisor(program.locations['a_color'], 0);
}
if (gpu_timer_ext) {
gl.endQuery(gpu_timer_ext.TIME_ELAPSED_EXT);
const next_tick = () => {
if (query) {
const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
const disjoint = gl.getParameter(gpu_timer_ext.GPU_DISJOINT_EXT);
if (available && !disjoint) {
const timeElapsed = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('Last GPU Frametime: ' + Math.round(timeElapsed / 10000) / 100 + 'ms');
}
if (available || disjoint) {
gl.deleteQuery(query);
query = null;
} else if (!available) {
setTimeout(next_tick, 0);
}
}
}
setTimeout(next_tick, 0);
}
const cpu_after = performance.now();
timers.raf = false;
console.log('Last CPU Frametime: ' + Math.round((cpu_after - cpu_before) * 100) / 100 + 'ms');
}
function init_webgl() {
canvas = document.querySelector('#c');
gl = canvas.getContext('webgl2');
gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (gpu_timer_ext === null) {
gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query');
}
const quad_vs = create_shader(gl, gl.VERTEX_SHADER, tquad_vs_src);
const quad_fs = create_shader(gl, gl.FRAGMENT_SHADER, tquad_fs_src);
programs = {
'quad': create_program(gl, quad_vs, quad_fs),
};
buffers = {
'b_packed': gl.createBuffer(),
};
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);
}
canvas.width = width;
canvas.height = height;
schedule_draw();
}
const resize_observer = new ResizeObserver(resize_canvas);
resize_observer.observe(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)) {
// src: tiny-sdf
// https://github.com/mapbox/tiny-sdf
const wrapper = {program};
const num_attrs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
const num_uniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
wrapper.locations = {};
for (let i = 0; i < num_attrs; i++) {
const attribute = gl.getActiveAttrib(program, i);
wrapper.locations[attribute.name] = gl.getAttribLocation(program, attribute.name);
}
for (let i = 0; i < num_uniforms; i++) {
const uniform = gl.getActiveUniform(program, i);
wrapper.locations[uniform.name] = gl.getUniformLocation(program, uniform.name);
}
return wrapper;
}
console.error('link:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}