const canvas = document.querySelector("#canvas"); const context = canvas.getContext("webgpu"); const frameTime = document.querySelector("#frame_time"); const adapter = await navigator.gpu.requestAdapter(); const adapterInfo = await adapter.requestAdapterInfo(); const device = await adapter.requestDevice(); const devicePixelRatio = window.devicePixelRatio; canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device, format: presentationFormat, alphaMode: 'premultiplied', }); const bindGroupLayout = device.createBindGroupLayout({ entries: [ { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: {}, }, { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: {}, }] }); const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [ bindGroupLayout, ] }); const module = device.createShaderModule({ code: await (await fetch("raytracing.wgsl")).text(), }); const pipeline = device.createRenderPipeline({ layout: pipelineLayout, vertex: { module, entry: "vs_main", }, fragment: { module, entry: "fs_main", targets: [ { format: presentationFormat, }, ], }, primitive: { topology: 'triangle-list', }, }); const timeBuffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); const aspectBuffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); const bindGroup = device.createBindGroup({ layout: bindGroupLayout, entries: [ { binding: 0, resource: { buffer: timeBuffer }}, { binding: 1, resource: { buffer: aspectBuffer }} ], }); const timeArray = new Float32Array(1); const aspectArray = new Float32Array(1); let time = 0; let time_history = []; let elapsed_time = 0; function frame() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; timeArray.set([Math.sin(elapsed_time)]); device.queue.writeBuffer(timeBuffer, 0, timeArray); aspectArray.set([window.innerWidth / window.innerHeight]); device.queue.writeBuffer(aspectBuffer, 0, aspectArray); const commandEncoder = device.createCommandEncoder(); const textureView = context.getCurrentTexture().createView(); const renderPassDescriptor = { colorAttachments: [ { view: textureView, clearValue: [0, 0, 0, 1], loadOp: 'clear', storeOp: 'store', }, ], }; const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); passEncoder.setPipeline(pipeline); passEncoder.setBindGroup(0, bindGroup); passEncoder.draw(6); passEncoder.end(); device.queue.submit([commandEncoder.finish()]); let end_time = Date.now(); let frame_time = end_time - time; elapsed_time += frame_time / 1000; time = end_time; time_history.push(frame_time); if(time_history.length > 10) time_history.shift(); let avg_frame_time = time_history.reduce((acc, v) => acc + v) / time_history.length; frameTime.innerText = `FPS: ${(1 / (avg_frame_time / 1000)).toFixed(2)} Frame time: ${avg_frame_time.toFixed(1)}ms ${adapterInfo.vendor} ${adapterInfo.device} (${adapterInfo.architecture})`; requestAnimationFrame(frame); } requestAnimationFrame(frame);