| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta name="viewport" content="width=600"> |
| <meta http-equiv="Content-type" content="text/html; charset=utf-8"> |
| <title>WebGPU Hello Triangle</title> |
| <link rel="stylesheet" href="css/style.css"> |
| <style> |
| body { |
| text-align: center; |
| } |
| canvas { |
| margin: 0 auto; |
| } |
| </style> |
| </head> |
| <body> |
| <canvas></canvas> |
| <script> |
| globalThis.testRunner?.waitUntilDone(); |
| const log = console.debug; |
| |
| async function helloTriangle() { |
| if (!navigator.gpu || GPUBufferUsage.COPY_SRC === undefined) { |
| document.body.className = 'error'; |
| return; |
| } |
| |
| const adapter = await navigator.gpu.requestAdapter(); |
| const device = await adapter.requestDevice(); |
| |
| /*** Vertex Buffer Setup ***/ |
| |
| /* Vertex Data */ |
| const vertexStride = 8 * 4; |
| const vertexDataSize = vertexStride * 3; |
| |
| /* GPUBufferDescriptor */ |
| const vertexDataBufferDescriptor = { |
| size: vertexDataSize, |
| usage: GPUBufferUsage.VERTEX |
| }; |
| |
| /* GPUBuffer */ |
| const vertexBuffer = device.createBuffer(vertexDataBufferDescriptor); |
| |
| /*** Shader Setup ***/ |
| const wgslSource = ` |
| struct Vertex { |
| @builtin(position) Position: vec4<f32>, |
| @location(0) color: vec4<f32>, |
| @location(1) multiplier: f32, |
| } |
| |
| @vertex fn vsmain(@builtin(vertex_index) VertexIndex: u32) -> Vertex |
| { |
| var pos: array<vec2<f32>, 3> = array<vec2<f32>, 3>( |
| vec2<f32>( 0.0, 0.5), |
| vec2<f32>(-0.5, -0.5), |
| vec2<f32>( 0.5, -0.5) |
| ); |
| var vertex_out : Vertex; |
| vertex_out.Position = vec4<f32>(pos[VertexIndex] * 1.5, 0.0, 1.0); |
| vertex_out.color = vec4<f32>(1.0, 1.0, 1.0, 1.0) - vec4<f32>(pos[VertexIndex] + vec2<f32>(0.5, 0.5), 0.0, 0.0); |
| vertex_out.multiplier = f32(VertexIndex * 50); |
| return vertex_out; |
| } |
| |
| @fragment fn fsmain(in: Vertex) -> @location(0) vec4<f32> |
| { |
| return in.color * in.multiplier; |
| } |
| `; |
| const shaderModule = device.createShaderModule({ code: wgslSource }); |
| |
| /* GPUPipelineStageDescriptors */ |
| const vertexStageDescriptor = { module: shaderModule, entryPoint: "vsmain" }; |
| |
| const fragmentStageDescriptor = { module: shaderModule, entryPoint: "fsmain", targets: [ {format: "rgba16float" }, ], }; |
| |
| /* GPURenderPipelineDescriptor */ |
| |
| const renderPipelineDescriptor = { |
| layout: 'auto', |
| vertex: vertexStageDescriptor, |
| fragment: fragmentStageDescriptor, |
| primitive: {topology: "triangle-list" }, |
| }; |
| /* GPURenderPipeline */ |
| const renderPipeline = device.createRenderPipeline(renderPipelineDescriptor); |
| |
| /*** Swap Chain Setup ***/ |
| |
| const canvas = document.querySelector("canvas"); |
| canvas.width = 600; |
| canvas.height = 600; |
| |
| const gpuContext = canvas.getContext("webgpu"); |
| |
| /* GPUCanvasConfiguration */ |
| const canvasConfiguration = { device: device, format: "rgba16float", toneMapping: { mode: 'extended' } }; |
| gpuContext.configure(canvasConfiguration); |
| /* GPUTexture */ |
| const currentTexture = gpuContext.getCurrentTexture(); |
| |
| /*** Render Pass Setup ***/ |
| |
| /* Acquire Texture To Render To */ |
| |
| /* GPUTextureView */ |
| const renderAttachment = currentTexture.createView(); |
| |
| /* GPUColor */ |
| const darkBlue = { r: 1, g: 1, b: 1, a: 1 }; |
| |
| /* GPURenderPassColorATtachmentDescriptor */ |
| const colorAttachmentDescriptor = { |
| view: renderAttachment, |
| loadOp: "clear", |
| storeOp: "store", |
| clearValue: darkBlue |
| }; |
| |
| /* GPURenderPassDescriptor */ |
| const renderPassDescriptor = { colorAttachments: [colorAttachmentDescriptor] }; |
| |
| /*** Rendering ***/ |
| |
| /* GPUCommandEncoder */ |
| const commandEncoder = device.createCommandEncoder(); |
| /* GPURenderPassEncoder */ |
| const renderPassEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); |
| |
| renderPassEncoder.setPipeline(renderPipeline); |
| const vertexBufferSlot = 0; |
| renderPassEncoder.setVertexBuffer(vertexBufferSlot, vertexBuffer, 0); |
| renderPassEncoder.draw(3, 1, 0, 0); // 3 vertices, 1 instance, 0th vertex, 0th instance. |
| renderPassEncoder.end(); |
| |
| /* GPUComamndBuffer */ |
| const commandBuffer = commandEncoder.finish(); |
| |
| /* GPUQueue */ |
| const queue = device.queue; |
| queue.submit([commandBuffer]); |
| await queue.onSubmittedWorkDone(); |
| |
| log('Pass'); |
| globalThis.testRunner?.dumpAsText(); |
| globalThis.testRunner?.notifyDone(); |
| } |
| |
| window.addEventListener("DOMContentLoaded", helloTriangle); |
| |
| </script> |
| </body> |
| </html> |