Skip to content

Instantly share code, notes, and snippets.

@grorg
Last active December 11, 2018 01:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grorg/5cbb17ba9a1e1b78f3a6a54aaffb03c4 to your computer and use it in GitHub Desktop.
Save grorg/5cbb17ba9a1e1b78f3a6a54aaffb03c4 to your computer and use it in GitHub Desktop.
<!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="simple-triangle-strip-expected.html">
<p>Pass if square canvas below is completely green.</p>
<canvas width="400" height="400"></canvas>
<script>
const shaderCode = `
#include <metal_stdlib>
using namespace metal;
struct Vertex
{
float4 position [[position]];
};
vertex Vertex vertex_main(const Vertex& vertex [[buffer(0)])
{
Vertex v;
// switch (vid) {
// case 0:
// v.position = float4(-1, 1, 0, 1);
// break;
// case 1:
// v.position = float4(-1, -1, 0, 1);
// break;
// case 2:
// v.position = float4(1, 1, 0, 1);
// break;
// default:
// v.position = float4(1, -1, 0, 1);
// }
v.position = vertex.position;
return v;
}
fragment float4 fragment_main(Vertex vertexIn [[stage_in]])
{
return float4(0.0, 1.0, 0.0, 1.0);
}
`
async function getBasicDevice() {
// FIXME: requestAdapter should take a WebGPUAdapterDescriptor.
const adapter = await window.webgpu.requestAdapter({});
const device = adapter.createDevice();
return device;
}
function createBasicContext(canvas, device) {
const context = canvas.getContext("webgpu");
// FIXME: Implement and specify a WebGPUTextureUsageEnum.
context.configure({ device: device, format:"B8G8R8A8Unorm", width: canvas.width, height: canvas.height });
return context;
}
function createBasicPipeline(shaderModule, device, inputState) {
vertexStageDescriptor = {
module: shaderModule,
stage: WebGPUShaderStage.VERTEX,
entryPoint: "vertex_main"
};
fragmentStageDescriptor = {
module: shaderModule,
stage: WebGPUShaderStage.FRAGMENT,
entryPoint: "fragment_main"
};
pipelineDescriptor = {
stages: [vertexStageDescriptor, fragmentStageDescriptor],
primitiveTopology: "triangleStrip",
inputState
};
return device.createRenderPipeline(pipelineDescriptor);
}
function beginBasicRenderPass(context, commandBuffer) {
const basicAttachment = {
attachment: context.getNextTexture().createDefaultTextureView(),
clearColor: { r: 1.0, g: 0, b: 0, a: 1.0 }
}
// FIXME: Flesh out the rest of WebGPURenderPassDescriptor.
return commandBuffer.beginRenderPass({ colorAttachments : [basicAttachment] });
}
function encodeBasicCommands(renderPassEncoder, renderPipeline, buffer) {
renderPassEncoder.setVertexBuffers(0, [buffer], [0]);
renderPassEncoder.setPipeline(renderPipeline);
renderPassEncoder.draw(4, 1, 0, 0);
return renderPassEncoder.endPass();
}
async function test() {
const device = await getBasicDevice();
const canvas = document.querySelector("canvas");
const context = createBasicContext(canvas, device);
// FIXME: Replace with non-MSL shaders.
const shaderModule = device.createShaderModule({ code: shaderCode });
const size = 4 * 4 * 4;
const buffer = device.createBuffer({ size, usage: WebGPUBufferUsage.MAP_WRITE });
// mapWriteAsync returns a then-able object, which hopefully
// should be enough for it to operate as a Promise.
// I'm not sure this will actually work.
const mappedMemory = await buffer.mapWriteAsync(0, size);
ASSERT(!mappedMemory.isPending())
// This will not be called getPointer(), but it is at the moment.
const floatArray = new Float32Array(mappedMemory.getPointer());
ASSERT(floatArray.length == 16);
floatArray[0] = -1;
floatArray[1] = 1;
...
floatArray[15] = 1;
// We're done writing to the buffer.
buffer.unmap();
const inputStateDescriptor = {
attributes: [
{
shaderLocation: 0,
// inputSlot used to go here, but it will now just be the index
offset: 0,
format: WebGPUVertexFormat.FLOAT_R32_G32_B32_A32 // I don't think this matters here.
}
],
inputs: [
{
// inputSlot - not needed any more.
stride: 4 * 4,
stepMode: WebGPUInputStepMode.VERTEX
}
]
};
const pipeline = createBasicPipeline(shaderModule, device, inputStateDescriptor);
const commandBuffer = device.createCommandBuffer();
const passEncoder = beginBasicRenderPass(context, commandBuffer);
const endCommandBuffer = encodeBasicCommands(passEncoder, pipeline, buffer);
const queue = device.getQueue();
queue.submit([endCommandBuffer]);
context.present();
}
test();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment