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