| <!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="vertex-buffer-triangle-strip-expected.html"> |
| <p>Pass if square canvas below is a 4 by 4 blue/green checkerboard.</p> |
| <canvas width="400" height="400"></canvas> |
| <script src="js/webgpu-functions.js"></script> |
| <script> |
| if (window.testRunner) |
| testRunner.waitUntilDone(); |
| |
| const positionBufferIndex = 0; |
| const texCoordsBufferIndex = 1; |
| const positionAttributeNum = 0; |
| const texCoordsAttributeNum = 1; |
| const bindGroupIndex = 0; |
| const textureBindingNum = 0; |
| const samplerBindingNum = 1; |
| |
| const shaderCode = ` |
| #include <metal_stdlib> |
| |
| using namespace metal; |
| |
| struct VertexIn |
| { |
| float4 position [[attribute(${positionAttributeNum})]]; |
| float2 texCoords [[attribute(${texCoordsAttributeNum})]]; |
| }; |
| |
| struct VertexOut |
| { |
| float4 position [[position]]; |
| float2 texCoords; |
| }; |
| |
| vertex VertexOut vertex_main(VertexIn vertexIn [[stage_in]]) |
| { |
| VertexOut vOut; |
| vOut.position = vertexIn.position; |
| vOut.texCoords = vertexIn.texCoords; |
| return vOut; |
| } |
| |
| struct TextureSampler |
| { |
| texture2d<float> t [[id(${textureBindingNum})]]; |
| sampler s [[id(${samplerBindingNum})]]; |
| }; |
| |
| fragment float4 fragment_main(VertexOut v [[stage_in]], const device TextureSampler& args [[buffer(${bindGroupIndex})]]) |
| { |
| return args.t.sample(args.s, v.texCoords); |
| } |
| ` |
| |
| function createInputStateDescriptor() { |
| return { |
| indexFormat: "uint32", |
| attributes: [{ |
| shaderLocation: positionAttributeNum, |
| inputSlot: positionBufferIndex, |
| format: "float4" |
| }, { |
| shaderLocation: texCoordsAttributeNum, |
| inputSlot: texCoordsBufferIndex, |
| format: "float2" |
| }], |
| inputs: [{ |
| inputSlot: positionBufferIndex, |
| stride: 4 * 4 |
| }, { |
| inputSlot: texCoordsBufferIndex, |
| stride: 4 * 2 |
| }] |
| } |
| } |
| |
| 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 }); |
| |
| const positionArray = new Float32Array([ |
| // float4 xyzw |
| -1, 1, 0, 1, |
| -1, -1, 0, 1, |
| 1, 1, 0, 1, |
| 1, -1, 0, 1 |
| ]); |
| const positionBuffer = device.createBuffer({ size: positionArray.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.TRANSFER_DST }); |
| positionBuffer.setSubData(0, positionArray.buffer); |
| |
| const texCoordsArray = new Float32Array([ |
| // float2 texCoords |
| 0, 0, |
| 0, 1, |
| 1, 0, |
| 1, 1 |
| ]); |
| const textureCoordBuffer = device.createBuffer({ size: texCoordsArray.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.TRANSFER_DST }); |
| textureCoordBuffer.setSubData(0, texCoordsArray.buffer); |
| |
| const inputStateDescriptor = createInputStateDescriptor(); |
| |
| // Load texture image |
| const image = new Image(); |
| const imageLoadPromise = new Promise(resolve => { |
| image.onload = () => resolve(); |
| image.src = "resources/blue-checkered.png"; |
| }); |
| await Promise.resolve(imageLoadPromise); |
| |
| // Convert image to data and fill GPUBuffer |
| const canvas2d = document.createElement("canvas"); |
| canvas2d.width = image.width; |
| canvas2d.height = image.height; |
| const context2d = canvas2d.getContext("2d"); |
| context2d.drawImage(image, 0, 0); |
| const imageData = context2d.getImageData(0, 0, image.width, image.height); |
| |
| const textureBufferDescriptor = { |
| size: imageData.data.length, |
| usage: GPUBufferUsage.TRANSFER_SRC | GPUBufferUsage.TRANSFER_DST |
| }; |
| const textureBuffer = device.createBuffer(textureBufferDescriptor); |
| textureBuffer.setSubData(0, imageData.data.buffer); |
| |
| // Create GPUTexture |
| const textureSize = { |
| width: image.width, |
| height: image.height, |
| depth: 1 |
| }; |
| |
| const textureDescriptor = { |
| size: { width: image.width, height: image.height, depth: 1 }, |
| format: "rgba8unorm", |
| usage: GPUTextureUsage.TRANSFER_DST | GPUTextureUsage.SAMPLED |
| }; |
| const texture = device.createTexture(textureDescriptor); |
| |
| // Bind texture and a sampler to pipeline |
| const textureLayoutBinding = { |
| binding: textureBindingNum, |
| visibility: GPUShaderStageBit.FRAGMENT, |
| type: "sampled-texture" |
| }; |
| const samplerLayoutBinding = { |
| binding: samplerBindingNum, |
| visibility: GPUShaderStageBit.FRAGMENT, |
| type: "sampler" |
| }; |
| |
| const bindGroupLayoutDescriptor = { |
| bindings: [textureLayoutBinding, samplerLayoutBinding] |
| }; |
| bindGroupLayout = device.createBindGroupLayout(bindGroupLayoutDescriptor); |
| const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }); |
| |
| const textureBinding = { |
| binding: textureBindingNum, |
| resource: texture.createDefaultView() |
| }; |
| const samplerBinding = { |
| binding: samplerBindingNum, |
| resource: device.createSampler({ minFilter: "nearest", magFilter: "nearest" }) |
| }; |
| |
| const bindGroupDescriptor = { |
| layout: bindGroupLayout, |
| bindings: [textureBinding, samplerBinding] |
| }; |
| const bindGroup = device.createBindGroup(bindGroupDescriptor); |
| |
| // Pipeline and render |
| const pipeline = createBasicPipeline(shaderModule, device, null, pipelineLayout, inputStateDescriptor); |
| const commandEncoder = device.createCommandEncoder(); |
| |
| const bufferCopyView = { |
| buffer: textureBuffer, |
| rowPitch: image.width * 4, |
| imageHeight: 0 |
| }; |
| const textureCopyView = { texture: texture }; |
| commandEncoder.copyBufferToTexture(bufferCopyView, textureCopyView, textureSize); |
| const passEncoder = beginBasicRenderPass(swapChain, commandEncoder); |
| passEncoder.setPipeline(pipeline); |
| passEncoder.setBindGroup(bindGroupIndex, bindGroup); |
| passEncoder.setVertexBuffers(positionBufferIndex, [positionBuffer, textureCoordBuffer], [0, 0]); |
| passEncoder.draw(4, 1, 0, 0); |
| passEncoder.endPass(); |
| |
| const queue = device.getQueue(); |
| queue.submit([commandEncoder.finish()]); |
| positionBuffer.destroy(); |
| textureCoordBuffer.destroy(); |
| texture.destroy(); |
| |
| if (window.testRunner) |
| testRunner.notifyDone(); |
| } |
| |
| test(); |
| </script> |