感谢对应实现流程说明文章: Raw WebGPU
这里先上完整的可执行代码,然后再来解释。请用Chrome浏览器运行。
运行效果:
TypeScript代码:
这份代码完全使用官方的API实现,没有任何私人代码。
如果你要找这些定义的TS版本,在这里可以找到:
https://github.com/vilyLei/voxwebgpu/tree/main/src/voxgpu/gpu
如果你不想麻烦,可以让这些带GPU前缀的命名的类型全部是any类型。
export class ColorTriangle {
canvas: HTMLCanvasElement;
// API Data Structures
adapter: GPUAdapter;
device: GPUDevice;
queue: GPUQueue;
// Frame Backings
context: GPUCanvasContext;
colorTexture: GPUTexture;
colorTextureView: GPUTextureView;
depthTexture: GPUTexture;
depthTextureView: GPUTextureView;
// Resources
positionBuffer: GPUBuffer;
colorBuffer: GPUBuffer;
indexBuffer: GPUBuffer;
vertModule: GPUShaderModule;
fragModule: GPUShaderModule;
pipeline: GPURenderPipeline;
commandEncoder: GPUCommandEncoder;
passEncoder: GPURenderPassEncoder;
private mWGCtx = new WebGPUContext();
constructor() {}
initialize(): void {
const canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 512;
document.body.appendChild(canvas);
const cfg = {
format: "bgra8unorm",
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
alphaMode: "opaque"
};
const ctx = this.mWGCtx;
ctx.initialize(canvas, cfg).then(() => {
console.log("webgpu initialization finish ...");
this.canvas = ctx.canvas;
this.adapter = ctx.gpuAdapter;
this.device = ctx.device;
this.queue = ctx.queue;
this.context = ctx.context;
this.start();
});
}
run(): void {
}
private async start() {
this.resizeBackings();
await this.initializeResources();
this.render();
}
// Initialize resources to render triangle (buffers, shaders, pipeline)
private async initializeResources() {
// Buffers
const createBuffer = (
arr: Float32Array | Uint16Array,
usage: number
) => {
// 📏 Align to 4 bytes (thanks @chrimsonite)
let desc = {
size: (arr.byteLength + 3) & ~3,
usage,
mappedAtCreation: true
} as GPUBufferDescriptor;
let buffer = this.device.createBuffer(desc);
const writeArray =
arr instanceof Uint16Array
? new Uint16Array(buffer.getMappedRange())
: new Float32Array(buffer.getMappedRange());
writeArray.set(arr);
buffer.unmap();
return buffer;
};
this.positionBuffer = createBuffer(positions, GPUBufferUsage.VERTEX);
this.colorBuffer = createBuffer(colors, GPUBufferUsage.VERTEX);
this.indexBuffer = createBuffer(indices, GPUBufferUsage.INDEX);
// Shaders
const vsmDesc = {
code: vertShaderCode
};
this.vertModule = this.device.createShaderModule(vsmDesc);
const fsmDesc = {
code: fragShaderCode
};
this.fragModule = this.device.createShaderModule(fsmDesc);
// Graphics Pipeline
// Input Assembly
const positionAttribDesc: GPUVertexAttribute = {
shaderLocation: 0, // [[location(0)]]
offset: 0,
format: 'float32x3'
};
const colorAttribDesc: GPUVertexAttribute = {
shaderLocation: 1, // [[location(1)]]
offset: 0,
format: 'float32x3'
};
const positionBufferDesc: GPUVertexBufferLayout = {
attributes: [positionAttribDesc],
arrayStride: 4 * 3, // sizeof(float) * 3
stepMode: 'vertex'
};
const colorBufferDesc: GPUVertexBufferLayout = {
attributes: [colorAttribDesc],
arrayStride: 4 * 3, // sizeof(float) * 3
stepMode: 'vertex'
};
// Depth
const depthStencil: GPUDepthStencilState = {
depthWriteEnabled: true,
depthCompare: 'less',
format: 'depth24plus-stencil8'
};
// Uniform Data
const pipelineLayoutDesc = { bindGroupLayouts: [] } as GPUPipelineLayoutDescriptor;
const layout = this.device.createPipelineLayout(pipelineLayoutDesc);
// Shader Stages
const vertex: GPUVertexState = {
module: this.vertModule,
entryPoint: 'main',
buffers: [positionBufferDesc, colorBufferDesc]
};
// Color/Blend State
const colorState: GPUColorTargetState = {
format: 'bgra8unorm'
};
const fragment: GPUFragmentState = {
module: this.fragModule,
entryPoint: 'main',
targets: [colorState]
};
// Rasterization
const primitive: GPUPrimitiveState = {
frontFace: 'cw',
cullMode: 'none',
topology: 'triangle-list'
};
const pipelineDesc: GPURenderPipelineDescriptor = {
layout,
vertex,
fragment,
primitive,
depthStencil
};
this.pipeline = this.device.createRenderPipeline(pipelineDesc);
}
// Resize swapchain, frame buffer attachments
private resizeBackings() {
const depthStencilTextureDesc: GPUTextureDescriptor = {
size: [this.canvas.width, this.canvas.height, 1],
// dimension: '2d',
format: 'depth24plus-stencil8',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
};
this.depthTexture = this.device.createTexture(depthStencilTextureDesc);
this.depthTextureView = this.depthTexture.createView();
}
// Write commands to send to the GPU
private encodeCommands() {
let colorAttachment: GPURenderPassColorAttachment = {
view: this.colorTextureView,
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store'
};
const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: this.depthTextureView,
depthClearValue: 1,
depthLoadOp: 'clear',
depthStoreOp: 'store',
stencilClearValue: 0,
stencilLoadOp: 'clear',
stencilStoreOp: 'store'
};
const renderPassDesc: GPURenderPassDescriptor = {
colorAttachments: [colorAttachment],
depthStencilAttachment: depthStencilAttachment
};
this.commandEncoder = this.device.createCommandEncoder();
// Encode drawing commands
this.passEncoder = this.commandEncoder.beginRenderPass(renderPassDesc);
const pass = this.passEncoder;
pass.setPipeline(this.pipeline);
pass.setViewport(
0,
0,
this.canvas.width,
this.canvas.height,
0,
1
);
pass.setScissorRect(
0,
0,
this.canvas.width,
this.canvas.height
);
pass.setVertexBuffer(0, this.positionBuffer);
pass.setVertexBuffer(1, this.colorBuffer);
pass.setIndexBuffer(this.indexBuffer, indices.BYTES_PER_ELEMENT == 2 ? 'uint16' : 'uint32');
// pass.drawIndexed(positions.length / 3, 1);
pass.drawIndexed(positions.length / 3);
pass.end();
this.queue.submit([this.commandEncoder.finish()]);
}
// 实施渲染
private render(): void{
// Acquire next image from context
this.colorTexture = this.context.getCurrentTexture();
this.colorTextureView = this.colorTexture.createView();
this.encodeCommands();
};
}