const vertex_shader_source = ` attribute vec2 a_pos; attribute vec3 a_color; uniform vec2 u_scale; uniform vec2 u_res; uniform vec2 u_translation; uniform int u_layer; varying vec3 v_color; 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_color = a_color; gl_Position = vec4(screen11, u_layer, 1); } `; const fragment_shader_source = ` precision mediump float; varying vec3 v_color; void main() { gl_FragColor = vec4(v_color, 1.0); } `; function init_webgl(state, context) { context.canvas = document.querySelector('#c'); context.gl = context.canvas.getContext('webgl', { 'preserveDrawingBuffer': true, 'desynchronized': true, 'antialias': true, }); context.gl.enable(context.gl.BLEND); context.gl.blendFunc(context.gl.ONE, context.gl.ONE_MINUS_SRC_ALPHA); const vertex_shader = create_shader(context.gl, context.gl.VERTEX_SHADER, vertex_shader_source); const fragment_shader = create_shader(context.gl, context.gl.FRAGMENT_SHADER, fragment_shader_source); const program = create_program(context.gl, vertex_shader, fragment_shader) context.program = program; context.locations['a_pos'] = context.gl.getAttribLocation(program, 'a_pos'); context.locations['a_color'] = context.gl.getAttribLocation(program, 'a_color'); context.locations['u_res'] = context.gl.getUniformLocation(program, 'u_res'); context.locations['u_scale'] = context.gl.getUniformLocation(program, 'u_scale'); context.locations['u_translation'] = context.gl.getUniformLocation(program, 'u_translation'); context.locations['u_layer'] = context.gl.getUniformLocation(program, 'u_layer'); context.buffers['b_pos'] = context.gl.createBuffer(); context.buffers['b_color'] = context.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); } context.canvas.width = width; context.canvas.height = height; window.requestAnimationFrame(() => 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); }