Files
WebGpu-Wasm-Raytracing-And-…/wasm_render.html
2025-12-25 10:57:33 +01:00

187 lines
5.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Fullscreen WASM Raytracer - Fast Loop</title>
<style>
html, body {
margin: 0; padding: 0; overflow: hidden; height: 100%;
background: black;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
#fps {
position: fixed;
top: 5px; left: 5px;
color: white;
background: rgba(0,0,0,0.5);
padding: 4px 8px;
font-family: monospace;
font-size: 14px;
z-index: 100;
}
</style>
</head>
<body>
<div id="fps">FPS: 0</div>
<canvas></canvas>
<script type="module">
import WasmModule from './wasm_renderer.js';
async function main() {
const canvas = document.querySelector('canvas');
const fpsElem = document.getElementById('fps');
const gl = canvas.getContext('webgl');
if (!gl) {
alert("WebGL not supported");
return;
}
// Shader setup (same as before)
const vertexShaderSource = `
attribute vec2 a_pos;
varying vec2 v_uv;
void main() {
v_uv = (a_pos + 1.0) * 0.5;
gl_Position = vec4(a_pos, 0, 1);
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec2 v_uv;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_uv);
}
`;
function compileShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader));
}
return shader;
}
function createProgram(vsSource, fsSource) {
const program = gl.createProgram();
gl.attachShader(program, compileShader(gl.VERTEX_SHADER, vsSource));
gl.attachShader(program, compileShader(gl.FRAGMENT_SHADER, fsSource));
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(gl.getProgramInfoLog(program));
}
return program;
}
const program = createProgram(vertexShaderSource, fragmentShaderSource);
gl.useProgram(program);
const posLoc = gl.getAttribLocation(program, 'a_pos');
const texLoc = gl.getUniformLocation(program, 'u_texture');
// Fullscreen quad (2 triangles)
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1
]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
// Create texture
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.uniform1i(texLoc, 0);
// Load WASM module
const wasm = await WasmModule();
let width = window.innerWidth;
let height = window.innerHeight;
function resize() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
wasm._set_resolution(width, height);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.viewport(0, 0, width, height);
}
window.addEventListener('resize', resize);
resize();
const framebufferPtr = wasm._get_framebuffer_ptr();
let framebufferUint8 = new Uint8Array(wasm.HEAPU8.buffer, framebufferPtr, width * height * 4);
// FPS tracking
let frameCount = 0;
let lastTime = performance.now();
// Setup MessageChannel for uncapped fast loop
const channel = new MessageChannel();
function render(time) {
wasm._update_framebuffer(time * 0.001);
// Update framebuffer view in case of resize (optional)
framebufferUint8 = new Uint8Array(wasm.HEAPU8.buffer, framebufferPtr, width * height * 4);
// Upload texture and draw
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height,
gl.RGBA, gl.UNSIGNED_BYTE, framebufferUint8);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// FPS counting
frameCount++;
const now = performance.now();
const delta = now - lastTime;
if (delta >= 1000) {
const fps = (frameCount * 1000) / delta;
fpsElem.textContent = `FPS: ${fps.toFixed(1)}`;
frameCount = 0;
lastTime = now;
}
// Schedule next frame ASAP through message channel
channel.port2.postMessage(performance.now());
}
channel.port1.onmessage = (e) => render(e.data);
// Start loop
channel.port2.postMessage(performance.now());
}
main();
</script>
</body>
</html>