///////////////////////////////////////////////////////////////////////////////
const mat4 = glMatrix.mat4;
const init = async () => {
// Init
/////////////////////////////////////////////////////////////////////////
const gpu = navigator.gpu;
const adapter = await gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.getElementById("my_canvas");
const context = canvas.getContext("webgpu");
/////////////////////////////////////////////////////////////////////////
// Swap Chain
/////////////////////////////////////////////////////////////////////////
const presentationFormat = gpu.getPreferredCanvasFormat();
context.configure({
device, // Create link between GPU and canvas.
format: presentationFormat,
alphaMode: "opaque"
});
/////////////////////////////////////////////////////////////////////////
// SetUp Vertex (position (vec4), color(vec4))
/////////////////////////////////////////////////////////////////////////
// Pack them all into one array
// Each vertex has a position and a color packed in memory in (x, y, z, w) / (xn, yn, zn) order
const vertices = new Float32Array([
// Vertex 0
-0.5, -0.5, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0,
// Vertex 1
0.5, -0.5, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0,
// Vertex 2
0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0,
// Vertex 3
-0.5, 0.5, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0
]);
/////////////////////////////////////////////////////////////////////////
// SetUp Indexes
/////////////////////////////////////////////////////////////////////////
let indices = new Uint16Array([
0, 1, 2,
0, 2, 3
]);
/////////////////////////////////////////////////////////////////////////
// Define Basic Shader
/////////////////////////////////////////////////////////////////////////
const shaderModule = device.createShaderModule({
code: `
@group(0) @binding(0) var viewProjection : mat4x4;
@group(0) @binding(1) var model : mat4x4;
struct VertexOut {
@builtin(position) position : vec4,
@location(0) color : vec4,
};
@vertex
fn vertex_main(@location(0) position: vec4,
@location(1) color: vec4) -> VertexOut
{
var output : VertexOut;
output.position = viewProjection * model * position;
output.color = color;
return output;
}
@fragment
fn fragment_main(fragData: VertexOut) -> @location(0) vec4
{
return fragData.color;
}
`,
});
/////////////////////////////////////////////////////////////////////////
// Create Camera
/////////////////////////////////////////////////////////////////////////
const cameraPosition = [0, 1, 2];
const cameraTarget = [0, 0, 0];
const cameraUp = [0, 1, 0];
const viewMatrix = mat4.create();
mat4.lookAt(viewMatrix, cameraPosition, cameraTarget, cameraUp);
const aspect = canvas.width / canvas.height;
const fieldOfView = (45 * Math.PI) / 180;
const near = 0.1;
const far = 100.0;
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, fieldOfView, aspect, near, far);
const viewProjectionMatrix = mat4.create();
mat4.multiply(viewProjectionMatrix, projectionMatrix, viewMatrix);
const viewProjectionBuffer = device.createBuffer({
size: viewProjectionMatrix.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
});
new Float32Array(viewProjectionBuffer.getMappedRange()).set(viewProjectionMatrix);
viewProjectionBuffer.unmap();
/////////////////////////////////////////////////////////////////////////
// Vertex Buffer
/////////////////////////////////////////////////////////////////////////
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Float32Array(vertexBuffer.getMappedRange()).set(vertices);
vertexBuffer.unmap();
// Vertex Descriptor
/////////////////////////////////////////////////////////////////////////
const vertexBuffersDescriptors = [
{
attributes: [
{
shaderLocation: 0,
offset: 0,
format: "float32x4",
},
{
shaderLocation: 1,
offset: 16,
format: "float32x4",
},
],
arrayStride: 32,
stepMode: "vertex",
},
];
/////////////////////////////////////////////////////////////////////////
// Index Buffers
/////////////////////////////////////////////////////////////////////////
const indexBuffer = device.createBuffer({
size: indices.byteLength,
usage: GPUBufferUsage.INDEX,
mappedAtCreation: true,
});
new Uint16Array(indexBuffer.getMappedRange()).set(indices);
indexBuffer.unmap();
/////////////////////////////////////////////////////////////////////////
// Create render pipeline
/////////////////////////////////////////////////////////////////////////
const pipeline = device.createRenderPipeline({
layout: "auto",
vertex: {
module: shaderModule,
entryPoint: "vertex_main",
buffers: vertexBuffersDescriptors,
},
fragment: {
module: shaderModule,
entryPoint: "fragment_main",
targets: [
{
format: presentationFormat,
},
],
},
primitive: {
topology: "triangle-list",
indexFormat: "uint16",
},
});
/////////////////////////////////////////////////////////////////////////
// Define render loop
function frame() {
// Add Transformations
/////////////////////////////////////////////////////////////////////////
const modelMatrix = mat4.create();
// Add rotation to the model matrix
const time = performance.now() / 750;
mat4.rotateX(modelMatrix, modelMatrix, time * 0.1);
mat4.rotateY(modelMatrix, modelMatrix, time * 0.2);
mat4.rotateZ(modelMatrix, modelMatrix, time * 0.3);
const modelBuffer = device.createBuffer({
size: modelMatrix.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
});
new Float32Array(modelBuffer.getMappedRange()).set(modelMatrix);
modelBuffer.unmap();
/////////////////////////////////////////////////////////////////////////
// Pass the camera & model uniforms
/////////////////////////////////////////////////////////////////////////
const bindGroupLayout = pipeline.getBindGroupLayout(0);
const bindGroup = device.createBindGroup({
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer: viewProjectionBuffer,
offset: 0,
size: viewProjectionMatrix.byteLength
}
},
{
binding: 1,
resource: {
buffer: modelBuffer,
offset: 0,
size: modelMatrix.byteLength
}
}
]
});
/////////////////////////////////////////////////////////////////////////
// Create render pass descriptor
/////////////////////////////////////////////////////////////////////////
const renderPassDescriptor = {
colorAttachments: [
{
loadOp: "clear", // Clear image on each load
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, // Clear image with this color
storeOp: "store", // Write result to the view
},
],
};
// Get the latest swap chain image, set as our output color attachment image
renderPassDescriptor.colorAttachments[0].view = context.getCurrentTexture().createView();
/////////////////////////////////////////////////////////////////////////
// Begin to draw
/////////////////////////////////////////////////////////////////////////
// Create command encoder to record rendering commands
const commandEncoder = device.createCommandEncoder();
// Pass render pass descriptor to get back a GPURenderPassEncorder
// So that we can record rendering commands
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
/////////////////////////////////////////////////////////////////////////
// Draw
/////////////////////////////////////////////////////////////////////////
// Configure the pass encoder
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
// Draw the triangle
passEncoder.setVertexBuffer(0, vertexBuffer);
passEncoder.setIndexBuffer(indexBuffer, "uint16");
passEncoder.drawIndexed(indices.length);
/////////////////////////////////////////////////////////////////////////
// End draw
/////////////////////////////////////////////////////////////////////////
// End the render pass
passEncoder.end();
// Get command buffer to submit to GPU, by calling commandEncoder.finish()
device.queue.submit([commandEncoder.finish()]);
/////////////////////////////////////////////////////////////////////////
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
};
init();
///////////////////////////////////////////////////////////////////////////////