当前示例源码github地址:
https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLifeTest.ts
系统特性:
-
用户态与系统态隔离。
-
高频调用与低频调用隔离。
-
面向用户的易用性封装。
-
渲染数据(内外部相关资源)和渲染机制分离。
-
用户操作和渲染系统调度并行机制。
-
数据/语义驱动。
-
异步并行的场景/模型载入。
-
computing与rendering用法机制一致性。
1). 构造过程一致性。
2). 启用过程一致性。
3). 自动兼容到material多pass以及material graph机制中。
当前示例运行效果:
WGSL顶点与片段shader:
javascript
struct VertexInput {
@location(0) pos: vec3f,
@builtin(instance_index) instance: u32,
};
struct VertexOutput {
@builtin(position) pos: vec4f,
@location(0) cell: vec2f,
};
@group(0) @binding(0) var<uniform> grid: vec2f;
@group(0) @binding(1) var<storage> cellState: array<u32>;
@vertex
fn vertMain(input: VertexInput) -> VertexOutput {
let i = f32(input.instance);
let cell = vec2f(i % grid.x, floor(i / grid.x));
let cellOffset = cell / grid * 2.0;
var state = f32(cellState[input.instance]);
let gridPos = (input.pos.xy * state + 1.0) / grid - 1.0 + cellOffset;
var output: VertexOutput;
output.pos = vec4f(gridPos, 0.0, 1.0);
output.cell = cell;
return output;
}
@fragment
fn fragMain(input: VertexOutput) -> @location(0) vec4f {
let c = input.cell / grid;
return vec4f(c, 1.0 - c.x, 1.0);
}
此示例基于此渲染系统实现,当前示例TypeScript源码如下:
javascript
type NodeType = { rendEntity: FixScreenPlaneEntity; compEntity?: ComputeEntity };
const gridSize = 64;
const shdWorkGroupSize = 8;
const compShdCode = `
@group(0) @binding(0) var<uniform> grid: vec2f;
@group(0) @binding(1) var<storage> cellStateIn: array<u32>;
@group(0) @binding(2) var<storage, read_write> cellStateOut: array<u32>;
fn cellIndex(cell: vec2u) -> u32 {
return (cell.y % u32(grid.y)) * u32(grid.x) +
(cell.x % u32(grid.x));
}
fn cellActive(x: u32, y: u32) -> u32 {
return cellStateIn[cellIndex(vec2(x, y))];
}
@compute @workgroup_size(${shdWorkGroupSize}, ${shdWorkGroupSize})
fn compMain(@builtin(global_invocation_id) cell: vec3u) {
// Determine how many active neighbors this cell has.
let activeNeighbors = cellActive(cell.x+1, cell.y+1) +
cellActive(cell.x+1, cell.y) +
cellActive(cell.x+1, cell.y-1) +
cellActive(cell.x, cell.y-1) +
cellActive(cell.x-1, cell.y-1) +
cellActive(cell.x-1, cell.y) +
cellActive(cell.x-1, cell.y+1) +
cellActive(cell.x, cell.y+1);
let i = cellIndex(cell.xy);
// Conway's game of life rules:
switch activeNeighbors {
case 2: { // Active cells with 2 neighbors stay active.
cellStateOut[i] = cellStateIn[i];
}
case 3: { // Cells with 3 neighbors become or stay active.
cellStateOut[i] = 1;
}
default: { // Cells with < 2 or > 3 neighbors become inactive.
cellStateOut[i] = 0;
}
}
}`;
export class GameOfLifeTest {
private mRscene = new RendererScene();
initialize(): void {
console.log("GameOfLifeTest::initialize() ...");
const rc = this.mRscene;
rc.initialize();
this.initEvent();
this.initScene();
}
private mFlag = 6;
private initEvent(): void {
const rc = this.mRscene;
rc.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDown);
new MouseInteraction().initialize(rc, 0, false).setAutoRunning(true);
}
private mouseDown = (evt: MouseEvent): void => {
this.mFlag = 1;
};
private createUniformValues(): { ufvs0: WGRUniformValue[]; ufvs1: WGRUniformValue[] }[] {
const gridsSizesArray = new Float32Array([gridSize, gridSize]);
const cellStateArray0 = new Uint32Array(gridSize * gridSize);
for (let i = 0; i < cellStateArray0.length; i++) {
cellStateArray0[i] = Math.random() > 0.6 ? 1 : 0;
}
const cellStateArray1 = new Uint32Array(gridSize * gridSize);
for (let i = 0; i < cellStateArray1.length; i++) {
cellStateArray1[i] = i % 2;
}
let shared = true;
let sharedData0 = { data: cellStateArray0 };
let sharedData1 = { data: cellStateArray1 };
const v0 = new WGRUniformValue({ data: gridsSizesArray, stride: 2, shared });
v0.toVisibleAll();
// build rendering uniforms
const va1 = new WGRStorageValue({ sharedData: sharedData0, stride: 1, shared }).toVisibleVertComp();
const vb1 = new WGRStorageValue({ sharedData: sharedData1, stride: 1, shared }).toVisibleVertComp();
// build computing uniforms
const compva1 = new WGRStorageValue({ sharedData: sharedData0, stride: 1, shared }).toVisibleVertComp();
const compva2 = new WGRStorageValue({ sharedData: sharedData1, stride: 1, shared }).toVisibleComp();
compva2.toBufferForStorage();
const compvb1 = new WGRStorageValue({ sharedData: sharedData1, stride: 1, shared }).toVisibleVertComp();
const compvb2 = new WGRStorageValue({ sharedData: sharedData0, stride: 1, shared }).toVisibleComp();
compvb2.toBufferForStorage();
let objs = [
{ ufvs0: [v0, va1], ufvs1: [v0, vb1] },
{ ufvs0: [v0, compva1, compva2], ufvs1: [v0, compvb1, compvb2] }
];
return objs;
}
private mNodes: NodeType[] = [];
private mStep = 0;
private initScene(): void {
const rc = this.mRscene;
let ufvsObjs = this.createUniformValues();
// build ping-pong rendering process
let shaderSrc = {
shaderSrc: {
code: shaderWGSL,
uuid: "shader-gameOfLife",
vertEntryPoint: "vertMain",
fragEntryPoint: "fragMain"
}
} as WGRShderSrcType;
let instanceCount = gridSize * gridSize;
let uniformValues = ufvsObjs[0].ufvs0;
let entity = new FixScreenPlaneEntity({
x: -0.8, y: -0.8, width: 1.6, height: 1.6,
shadinguuid: "rshd0", shaderSrc, uniformValues, instanceCount
});
rc.addEntity(entity);
this.mNodes = [{ rendEntity: entity, compEntity: null }];
entity.rstate.visible = false;
const geometry = this.mNodes[0].rendEntity.geometry;
uniformValues = ufvsObjs[0].ufvs1;
entity = new FixScreenPlaneEntity({ shadinguuid: "rshd1", shaderSrc, uniformValues, instanceCount, geometry });
rc.addEntity(entity);
this.mNodes.push({ rendEntity: entity, compEntity: null });
// build ping-pong computing process
shaderSrc = {
compShaderSrc: {
code: compShdCode,
uuid: "shader-computing",
compEntryPoint: "compMain"
}
};
const workgroupCount = Math.ceil(gridSize / shdWorkGroupSize);
uniformValues = ufvsObjs[1].ufvs1;
let compEentity = new ComputeEntity({ shadinguuid: "compshd0", shaderSrc, uniformValues }).setWorkcounts(workgroupCount, workgroupCount);
rc.addEntity(compEentity);
compEentity.rstate.visible = false;
this.mNodes[0].compEntity = compEentity;
uniformValues = ufvsObjs[1].ufvs0;
compEentity = new ComputeEntity({ shadinguuid: "compshd1", shaderSrc, uniformValues }).setWorkcounts(workgroupCount, workgroupCount);
rc.addEntity(compEentity);
this.mNodes[1].compEntity = compEentity;
}
private mFrameDelay = 3;
run(): void {
if (this.mRscene.renderer.isEnabled()) {
if (this.mFrameDelay > 0) {
this.mFrameDelay--;
return;
}
this.mFrameDelay = 3;
for (let i = 0; i < this.mNodes.length; i++) {
const t = this.mNodes[i];
t.rendEntity.setVisible(false);
if (t.compEntity) t.compEntity.setVisible(false);
}
let index = this.mStep % 2;
this.mNodes[index].rendEntity.setVisible(true);
if (this.mNodes[index].compEntity) this.mNodes[index].compEntity.setVisible(true);
this.mStep++;
this.mRscene.run();
}
}
}