async function loadFile(url) { const res = await fetch(url); return res.text(); } function compileShader(gl, source, type) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('Shader compile error:', gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } function createTexture(gl) { const tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); // Set parameters so we can handle non-power-of-2 images if needed gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); return tex; } function updateCharTexture(gl, texture, text) { const height = 256; // Dynamic width based on text length, min 1 const chars = Math.max(1, text.length); const width = 256 * chars; const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, width, height); ctx.fillStyle = 'white'; ctx.font = 'bold 200px monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(text, width / 2, height / 2); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); return chars; // Return aspect ratio (width/height) } async function main() { const canvas = document.getElementById('glcanvas'); const gl = canvas.getContext('webgl'); if (!gl) { alert('WebGL not supported'); return; } // Load Shaders const vsSource = await loadFile('/static/shaders/vertex.glsl'); // Using simple Cache Busting for reloading shader during dev if needed, but not strictly necessary here const fsSource = await loadFile('/static/shaders/fragment.glsl'); const vs = compileShader(gl, vsSource, gl.VERTEX_SHADER); const fs = compileShader(gl, fsSource, gl.FRAGMENT_SHADER); const program = gl.createProgram(); gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Program link error:', gl.getProgramInfoLog(program)); return; } gl.useProgram(program); // Quad Buffer const verts = new Float32Array([ -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, ]); const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW); const posLoc = gl.getAttribLocation(program, 'position'); gl.enableVertexAttribArray(posLoc); gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0); // Uniforms const uTime = gl.getUniformLocation(program, 'u_time'); const uRes = gl.getUniformLocation(program, 'u_resolution'); const uCharPos = gl.getUniformLocation(program, 'u_charPos'); const uCharSize = gl.getUniformLocation(program, 'u_charSize'); // Texture uniform usually defaults to 0, which is fine // State const charTexture = createTexture(gl); let charX = 0.5; let charY = 0.5; let charW = 0.5; let charH = 0.5; let currentAspect = 1.0; // UI Handling const input = document.getElementById('textInput'); const btn = document.getElementById('renderBtn'); function updateText() { // Use placeholder or "A" if empty to avoid 0 size let text = input.value.toUpperCase().substring(0, 12); if (text.length === 0) return; currentAspect = updateCharTexture(gl, charTexture, text); resize(); // Recalculate size } btn.addEventListener('click', updateText); // Initial Text currentAspect = updateCharTexture(gl, charTexture, "HELLO"); function resize() { // Look up container size const container = canvas.parentElement; canvas.width = container.clientWidth; canvas.height = container.clientHeight; gl.viewport(0, 0, canvas.width, canvas.height); // Adjust aspect ratio const screenAspect = canvas.width / canvas.height; // Target: charW / charH = currentAspect / screenAspect // Try filling 60% of height charH = 0.6; charW = charH * currentAspect / screenAspect; // Constrain width to 95% if (charW > 0.95) { charW = 0.95; charH = charW * screenAspect / currentAspect; } charX = 0.5 - charW / 2; charY = 0.5 - charH / 2; } window.addEventListener('resize', resize); resize(); function render(time) { time *= 0.001; // Seconds gl.uniform1f(uTime, time); gl.uniform2f(uRes, canvas.width, canvas.height); gl.uniform2f(uCharPos, charX, charY); // uniform needs bottom-left gl.uniform2f(uCharSize, charW, charH); gl.drawArrays(gl.TRIANGLES, 0, 6); requestAnimationFrame(render); } requestAnimationFrame(render); } main();