webgpu-app-nico/triangle.js
2026-08-14 13:25:45 -05:00

145 lines
4.3 KiB
JavaScript

import { getPathText } from "./common.js"
// Triangle vertices, one vertex per line. Hand-transcribed from a
// .obj file exported from Blender to this form.
//
// I've also written myriad scripts to do this automatically for
// larger models.
//
// Float32Array is a fairly convenient way to convert floating point
// literals to bytes.
const triangleVertices = new Float32Array([
// [position ] [texture ] [normal ]
0.0, 1.0, 0.0, 0.5, 1.0, 0.0, 1.0, 0.0,
1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
-1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
]);
class TriangleRenderer {
constructor(device, canvasFormat, wgsl)
{
const label = "TriangleRenderer";
//////////////////////////////////////////////////////////////////////
// shader module
//////////////////////////////////////////////////////////////////////
const shaderModule = device.createShaderModule({
label: `${label} shader`,
code: wgsl,
});
//////////////////////////////////////////////////////////////////////
// pipeline
//////////////////////////////////////////////////////////////////////
// "bind group layouts" are a declaration of which `var` bindings
// exist in the shader programs loaded by this pipeline
//
// None are used in triangle.wgsl, so this list is empty.
const bindGroupLayouts = [
];
const pipelineLayout = device.createPipelineLayout({
label: `${label} pipeline layout`,
bindGroupLayouts: bindGroupLayouts,
});
// vertexBufferLayouts describes the position of each element for
// each buffer. There is only one vertex buffer used in this demo,
// so there is only one vertex buffer layout in this list.
//
// shaderLocation matches the location numbers defined in
// triangle.wgsl.
//
// "offset" is the starting offset in bytes, 4 bytes per floating
// point number. This matches the arrangement in bytes of the
// triangleVertices Float32Array.
const arrayStride = (4 * 3) + (4 * 2) + (4 * 3);
const vertexBufferLayouts = [
{
arrayStride: arrayStride,
attributes: [{
format: "float32x3",
offset: 0,
shaderLocation: 0,
}, {
format: "float32x2",
offset: 12,
shaderLocation: 1,
}, {
format: "float32x3",
offset: 20,
shaderLocation: 2,
}],
},
];
this.renderPipeline = device.createRenderPipeline({
label: `${label} render pipeline`,
layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: "vertexMain",
buffers: vertexBufferLayouts,
},
fragment: {
module: shaderModule,
entryPoint: "fragmentMain",
targets: [{
format: canvasFormat,
}]
},
primitive: {
topology: "triangle-list",
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: "less",
format: "depth24plus",
},
});
//////////////////////////////////////////////////////////////////////
// bind group
//////////////////////////////////////////////////////////////////////
// there are no bind groups, because triangle.wgsl doesn't use any.
//////////////////////////////////////////////////////////////////////
// buffer
//////////////////////////////////////////////////////////////////////
this.vertexBuffer = device.createBuffer({
label: `${label} vertex buffer`,
size: triangleVertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
// copy the (system RAM) triangleVertices to the (gpu RAM) vertexBuffer.
device.queue.writeBuffer(this.vertexBuffer, 0, triangleVertices);
}
render(renderPass)
{
renderPass.setPipeline(this.renderPipeline);
renderPass.setVertexBuffer(0, this.vertexBuffer);
const vertexCount = 3;
renderPass.draw(vertexCount);
}
}
// This "loadTriangleRenderer" function is separate from the
// constructor because Javascript class constructors can't be async
// functions.
async function loadTriangleRenderer(device, canvasFormat)
{
const triangleWgsl = await getPathText("triangle.wgsl");
const renderer = new TriangleRenderer(device, canvasFormat, triangleWgsl);
return renderer;
}
export { loadTriangleRenderer };