|
|
|
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);
|
|
|
|
}
|