blob: d34f3c1f9af5f433239d156d672291662080c115 [file] [log] [blame]
<!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>