webgpu-app/snake.js
2026-08-05 13:20:03 -05:00

202 lines
6.4 KiB
JavaScript

import { getPath } from "./common.js";
class SnakeRenderer {
createBuffers(device)
{
const boneCount = 100;
const boneLength = 0.1;
this.boneSystemAddress = this.module.instance.exports.bone_system__create(boneCount, boneLength);
const mem = new DataView(this.module.memory.buffer);
this.bonesAddress = mem.getUint32(this.boneSystemAddress, true);
const configurationOffset = this.module.instance.exports.bone_system__configuration_offset();
this.configurationAddress = this.boneSystemAddress + configurationOffset;
this.bonesBufferSize = this.module.instance.exports.bone_system__bones_size(boneCount);
this.configurationBufferSize = this.module.instance.exports.bone_system__configuration_size();
this.frames = [];
for (let i = 0; i < 2; i++) {
const bonesBuffer = device.createBuffer({
label: `bone buffer ${i}`,
size: this.bonesBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
const configurationBuffer = device.createBuffer({
label: `bone configuration buffer ${i}`,
size: this.configurationBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
const bindGroup = device.createBindGroup({
label: `bone buffer bind group ${i}`,
layout: this.bindGroupLayout.bone,
entries: [{
binding: 0,
resource: { buffer: bonesBuffer },
}, {
binding: 1,
resource: { buffer: configurationBuffer },
}]
});
this.frames.push({
bonesBuffer: bonesBuffer,
configurationBuffer: configurationBuffer,
bindGroup: bindGroup,
});
}
}
constructor(device, canvasFormat, viewUniformBuffer, wgsl, module)
{
const label = "bone";
this.module = module;
//////////////////////////////////////////////////////////////////////
// shader module
//////////////////////////////////////////////////////////////////////
const shaderModule = device.createShaderModule({
label: `${label} shader`,
code: wgsl,
});
//////////////////////////////////////////////////////////////////////
// index buffer
//////////////////////////////////////////////////////////////////////
const array = new Uint16Array(6);
array[0] = 0;
array[1] = 1;
array[2] = 2;
array[3] = 0;
array[4] = 3;
array[5] = 1;
this.buffer = device.createBuffer({
label: `${label} renderer buffer`,
size: array.byteLength,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(this.buffer, 0, array, 0, array.length);
//////////////////////////////////////////////////////////////////////
// pipeline
//////////////////////////////////////////////////////////////////////
this.bindGroupLayout = {
view: device.createBindGroupLayout({
label: `${label} bind group layout view`,
entries: [{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "uniform" }
}]
}),
bone: device.createBindGroupLayout({
label: `${label} bind group layout particle`,
entries: [{ // bones
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
buffer: { type: "read-only-storage" }
}, { // configuration
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
buffer: { type: "uniform" }
}]
}),
};
const pipelineLayout = device.createPipelineLayout({
label: `${label} pipeline layout`,
bindGroupLayouts: [
this.bindGroupLayout.view,
this.bindGroupLayout.bone
],
});
this.renderPipeline = device.createRenderPipeline({
label: `${label} pipeline`,
layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: "vertexMain",
},
fragment: {
module: shaderModule,
entryPoint: "fragmentMain",
targets: [{
format: canvasFormat,
}]
},
primitive: {
topology: "triangle-list",
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: "less",
format: "depth24plus",
},
});
//////////////////////////////////////////////////////////////////////
// bones
//////////////////////////////////////////////////////////////////////
this.createBuffers(device);
//////////////////////////////////////////////////////////////////////
// bind group
//////////////////////////////////////////////////////////////////////
this.viewBindGroup = device.createBindGroup({
label: `${label} bind group`,
layout: this.bindGroupLayout.view,
entries: [{
binding: 0,
resource: { buffer: viewUniformBuffer },
}],
});
}
update(device, frameNumber, mousePosition, configuration)
{
this.module.instance.exports.bone_system__update_configuration(this.boneSystemAddress,
configuration.boneCount,
configuration.boneLength,
configuration.velocityDamping,
configuration.velocityAcceleration);
this.module.instance.exports.bone_system__update(this.boneSystemAddress,
mousePosition[0], mousePosition[1], mousePosition[2]);
device.queue.writeBuffer(this.frames[frameNumber].bonesBuffer, 0,
this.module.memory.buffer, this.bonesAddress, this.bonesBufferSize);
device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0,
this.module.memory.buffer, this.configurationAddress, this.configurationBufferSize);
}
render(renderPass, frameNumber)
{
renderPass.setPipeline(this.renderPipeline);
renderPass.setIndexBuffer(this.buffer, "uint16");
renderPass.setBindGroup(0, this.viewBindGroup);
renderPass.setBindGroup(1, this.frames[frameNumber].bindGroup);
renderPass.drawIndexed(6);
}
}
async function loadSnake(device, canvasFormat, viewUniformBuffer, module)
{
const snakeWgsl = await getPath("snake.wgsl");
const renderer = new SnakeRenderer(device, canvasFormat, viewUniformBuffer, snakeWgsl, module);
return renderer;
}
export { loadSnake };