| <!DOCTYPE html> |
| <meta charset="utf-8"> |
| <title>WebGPU Hello Triangles</title> |
| <meta name="assert" content="WebGPU correctly renders a green canvas."> |
| <link rel="match" href="buffer-resource-triangles-expected.html"> |
| <p>Pass if square canvas below is completely green.</p> |
| <canvas width="400" height="400"></canvas> |
| <script src="js/webgpu-functions.js"></script> |
| <script> |
| if (window.testRunner) |
| testRunner.waitUntilDone(); |
| |
| const shaderCode = ` |
| #include <metal_stdlib> |
| |
| using namespace metal; |
| |
| struct VertexInput { |
| float4 position [[attribute(0)]]; |
| }; |
| |
| struct Vertex { |
| float4 position [[position]]; |
| }; |
| |
| struct VertexArguments { |
| device Vertex* v0; |
| device Vertex* v1; |
| device Vertex* v2; |
| }; |
| |
| vertex Vertex vertex_main( |
| VertexInput input [[stage_in]], |
| const device VertexArguments& args0 [[buffer(0)]], |
| const device VertexArguments& args1 [[buffer(1)]], |
| uint vid [[vertex_id]]) |
| { |
| switch (vid) |
| { |
| case 0: |
| case 1: |
| case 2: { |
| Vertex out; |
| out.position = input.position; |
| return out; |
| } |
| case 3: return *args0.v0; |
| case 4: return *args0.v1; |
| case 5: return *args0.v2; |
| case 6: return *args1.v0; |
| case 7: return *args1.v1; |
| default: return *args1.v2; |
| } |
| } |
| |
| struct FragmentArguments { |
| device float4* color; |
| }; |
| |
| fragment float4 fragment_main(const device FragmentArguments& args [[buffer(0)]]) |
| { |
| return args.color[0]; |
| } |
| ` |
| |
| const bindingNums = { |
| UL: 0, |
| UM: 1, |
| UR: 2, |
| LL: 3, |
| LR: 4, |
| G: 5 |
| }; |
| |
| function createUniformBufferBindGroupLayout(bindNum, stage = GPUShaderStageBit.VERTEX) { |
| return { |
| binding: bindNum, |
| visibility: stage, |
| type: "uniform-buffer" |
| }; |
| } |
| |
| const vertexSize = 4 * 4; |
| const verticesBufferSize = vertexSize * 3; |
| function createAndUploadVerticesBuffer(device) { |
| const buffer = device.createBuffer({ size:verticesBufferSize, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.TRANSFER_DST }); |
| const arrayBuffer = new Float32Array([ |
| 0, 1, 0, 1, |
| -1, -1, 0, 1, |
| 1, -1, 0, 1 |
| ]).buffer; |
| |
| buffer.setSubData(0, arrayBuffer); |
| return buffer; |
| } |
| |
| function createFloat4Buffer(device, a, b, promises) { |
| const buffer = device.createBuffer({ size: vertexSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.MAP_WRITE }); |
| |
| const promise = buffer.mapWriteAsync().then(mapping => { |
| const mappedArray = new Float32Array(mapping); |
| mappedArray.set([a, b, 0, 1]); |
| buffer.unmap(); |
| }); |
| |
| promises.push(promise); |
| return buffer; |
| } |
| |
| function createBufferBinding(buffer) { |
| return { buffer: buffer, size: vertexSize }; |
| } |
| |
| async function test() { |
| const device = await getBasicDevice(); |
| const canvas = document.querySelector("canvas"); |
| const swapChain = createBasicSwapChain(canvas, device); |
| // FIXME: Replace with non-MSL shaders. |
| const shaderModule = device.createShaderModule({ code: shaderCode }); |
| |
| // Create vertex data GPUBuffers. |
| const verticesBuffer = createAndUploadVerticesBuffer(device); |
| |
| let bufferPromises = []; |
| const upperLeft = createFloat4Buffer(device, -1, 1, bufferPromises); |
| const upperMiddle = createFloat4Buffer(device, 0, 1, bufferPromises); |
| const upperRight = createFloat4Buffer(device, 1, 1, bufferPromises); |
| const lowerLeft = createFloat4Buffer(device, -1, -1, bufferPromises); |
| const lowerRight = createFloat4Buffer(device, 1, -1, bufferPromises); |
| |
| // Color data buffer. |
| const green = createFloat4Buffer(device, 0, 1, bufferPromises); |
| |
| // Create vertex input state. |
| const inputState = { |
| indexFormat: "uint32", |
| attributes: [{ |
| shaderLocation: 0, |
| inputSlot: 0, |
| format: "float4" |
| }], |
| inputs: [{ |
| inputSlot: 0, |
| stride: vertexSize |
| }] |
| }; |
| |
| // Create buffer GPUBindGroupLayoutBindings. |
| const layoutUL = createUniformBufferBindGroupLayout(bindingNums.UL); |
| const layoutUM = createUniformBufferBindGroupLayout(bindingNums.UM); |
| const layoutUR = createUniformBufferBindGroupLayout(bindingNums.UR); |
| const layoutLL = createUniformBufferBindGroupLayout(bindingNums.LL); |
| const layoutLR = createUniformBufferBindGroupLayout(bindingNums.LR); |
| const layoutG = createUniformBufferBindGroupLayout(bindingNums.G, GPUShaderStageBit.FRAGMENT); |
| |
| // GPUBindGroupLayouts |
| const leftTriangleBGLayout = device.createBindGroupLayout({ bindings: [layoutUL, layoutUM, layoutLL, layoutG] }); |
| const rightTriangleBGLayout = device.createBindGroupLayout({ bindings: [layoutUR, layoutUM, layoutLR] }); |
| |
| // GPUPipelineLayout and GPURenderPipeline |
| const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [leftTriangleBGLayout, rightTriangleBGLayout] }); |
| const pipeline = createBasicPipeline(shaderModule, device, null, pipelineLayout, inputState, null, "triangle-list"); |
| |
| // GPUBufferBindings |
| const bindingUL = createBufferBinding(upperLeft); |
| const bindingUM = createBufferBinding(upperMiddle); |
| const bindingUR = createBufferBinding(upperRight); |
| const bindingLL = createBufferBinding(lowerLeft); |
| const bindingLR = createBufferBinding(lowerRight); |
| const bindingG = createBufferBinding(green); |
| |
| // GPUBindGroupBindings |
| const bgBindingUL = { binding: bindingNums.UL, resource: bindingUL }; |
| const bgBindingUM = { binding: bindingNums.UM, resource: bindingUM }; |
| const bgBindingUR = { binding: bindingNums.UR, resource: bindingUR }; |
| const bgBindingLL = { binding: bindingNums.LL, resource: bindingLL }; |
| const bgBindingLR = { binding: bindingNums.LR, resource: bindingLR }; |
| const bgBindingG = { binding: bindingNums.G, resource: bindingG }; |
| |
| // GPUBindGroups |
| const leftTriangleBG = device.createBindGroup({ |
| layout: leftTriangleBGLayout, |
| bindings: [bgBindingUL, bgBindingUM, bgBindingLL, bgBindingG] |
| }); |
| const rightTriangleBG = device.createBindGroup({ |
| layout: rightTriangleBGLayout, |
| bindings: [bgBindingUR, bgBindingUM, bgBindingLR] |
| }); |
| |
| Promise.all(bufferPromises).then(() => { |
| const commandEncoder = device.createCommandEncoder(); |
| const passEncoder = beginBasicRenderPass(swapChain, commandEncoder); |
| passEncoder.setPipeline(pipeline); |
| |
| // Vertex data for upper triangles. |
| passEncoder.setBindGroup(0, leftTriangleBG); |
| passEncoder.setBindGroup(1, rightTriangleBG); |
| // Lower triangle. |
| passEncoder.setVertexBuffers(0, [verticesBuffer], [0]); |
| passEncoder.draw(9, 1, 0, 0); |
| |
| passEncoder.endPass(); |
| const queue = device.getQueue(); |
| queue.submit([commandEncoder.finish()]); |
| |
| if (window.testRunner) |
| testRunner.notifyDone(); |
| }); |
| } |
| |
| test(); |
| </script> |