当前示例源码github地址:
https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/GameOfLifePretty.ts
系统特性:
1. 用户态与系统态隔离。
2. 基于算力驱动的系统设计。
3. 高频调用与低频调用隔离。
4. 面向用户的易用性封装。
5. 渲染数据(内外部相关资源)和渲染机制分离。
6. 用户操作和渲染系统调度并行机制。
7. 数据/语义驱动。
8. 异步并行的场景/模型载入。
9. 保持computing与rendering机制一致性。
1). 构造一致性。
2). 使用一致性。
3). 自动兼容materialmulti-pass、material graph、pass(pass node) graph机制。
当前示例运行效果:
WGSL顶点与片段shader:
- struct VertexInput {
- @location(0) pos: vec3f,
- @location(1) uv : vec2f,
- @builtin(instance_index) instance: u32
- };
-
- struct VertexOutput {
- @builtin(position) pos: vec4f,
- @location(0) cell: vec2f,
- @location(1) uv: vec2f,
- @location(2) instance: f32,
- };
- @group(0) @binding(0) var
grid: vec2f; - @group(0) @binding(1) var
cellState: array<u32>; - @group(0) @binding(2) var
lifeState: array<f32>; - @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;
- output.uv = input.uv;
- output.instance = i;
- return output;
- }
-
- @fragment
- fn fragMain(input: VertexOutput) -> @location(0) vec4f {
- let c = input.cell / grid;
- var dis = length(input.uv - vec2<f32>(0.5, 0.5));
- dis = min(dis/0.15, 1.0);
- let i = u32(input.instance);
- var f = clamp((lifeState[i])/100.0, 0.0005, 1.0);
- dis = (1.0 - pow(dis, 100.0)) * (1.0 - f) + f;
- var c3 = vec3f(c, (1.0 - c.x) * (1.0 - f) + f) * dis;
- return vec4f(c3, 1.0);
- }
此示例基于此渲染系统实现,当前示例TypeScript源码如下:
- const gridSize = 64;
- const shdWorkGroupSize = 8;
-
- const compShdCode = `
- @group(0) @binding(0) var
grid: vec2f; - @group(0) @binding(1) var
cellStateIn: array; - @group(0) @binding(2) var
cellStateOut: array; - @group(0) @binding(3) var
lifeState: array; - 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];
- if(cellStateOut[i] > 0) {
- lifeState[i] += 0.5;
- } else {
- lifeState[i] -= 0.5;
- }
- }
- case 3: { // Cells with 3 neighbors become or stay active.
- cellStateOut[i] = 1;
- lifeState[i] += 0.5;
- }
- default: { // Cells with < 2 or > 3 neighbors become inactive.
- cellStateOut[i] = 0;
- lifeState[i] = 0.01;
- }
- }
- if(lifeState[i] < 0.01) { lifeState[i] = 0.01; }
- }`;
- export class GameOfLifePretty {
- private mRscene = new RendererScene();
-
- initialize(): void {
- console.log("GameOfLifePretty::initialize() ...");
-
- this.initScene();
- }
- 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.8 ? 1 : 0;
- }
- const cellStateArray1 = new Uint32Array(gridSize * gridSize);
- for (let i = 0; i < cellStateArray1.length; i++) {
- cellStateArray1[i] = i % 2;
- }
-
- const lifeStateArray3 = new Float32Array(gridSize * gridSize);
- for (let i = 0; i < lifeStateArray3.length; i++) {
- lifeStateArray3[i] = 0.01;
- }
-
- let shared = true;
- let sharedData0 = { data: cellStateArray0 };
- let sharedData1 = { data: cellStateArray1 };
- let sharedData3 = { data: lifeStateArray3 };
-
- 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();
- const vc1 = new WGRStorageValue({ sharedData: sharedData3, stride: 1, shared }).toVisibleAll();
-
- // 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();
-
- const compv3 = new WGRStorageValue({ sharedData: sharedData3, stride: 1, shared }).toVisibleComp();
- compv3.toBufferForStorage();
-
- return [
- { ufvs0: [v0, va1, vc1], ufvs1: [v0, vb1, vc1] },
- { ufvs0: [v0, compva1, compva2, compv3], ufvs1: [v0, compvb1, compvb2, compv3] }
- ];
- }
- private mEntity: FixScreenPlaneEntity;
- private mStep = 0;
-
- private createMaterial(shaderCodeSrc: WGRShderSrcType, uniformValues: WGRUniformValue[], shadinguuid: string, instanceCount: number): WGMaterial {
- return new WGMaterial({
- shadinguuid,
- shaderCodeSrc,
- instanceCount,
- uniformValues
- });
- }
- private createCompMaterial(shaderCodeSrc: WGRShderSrcType, uniformValues: WGRUniformValue[], shadinguuid: string, workgroupCount = 2): WGCompMaterial {
- return new WGCompMaterial({
- shadinguuid,
- shaderCodeSrc,
- uniformValues
- }).setWorkcounts(workgroupCount, workgroupCount);
- }
- private initScene(): void {
- const rc = this.mRscene;
-
- const ufvsObjs = this.createUniformValues();
-
- const instanceCount = gridSize * gridSize;
- const workgroupCount = Math.ceil(gridSize / shdWorkGroupSize);
-
- let shaderSrc = {
- shaderSrc: {
- code: shaderWGSL,
- uuid: "shader-gameOfLife",
- }
- };
- let compShaderSrc = {
- comp: {
- code: compShdCode,
- uuid: "shader-computing",
- }
- };
- const materials: WGMaterial[] = [
- // build ping-pong rendering process
- this.createMaterial(shaderSrc, ufvsObjs[0].ufvs0, "rshd0", instanceCount),
- this.createMaterial(shaderSrc, ufvsObjs[0].ufvs1, "rshd1", instanceCount),
- // build ping-pong computing process
- this.createCompMaterial(compShaderSrc, ufvsObjs[1].ufvs1, "compshd0", workgroupCount),
- this.createCompMaterial(compShaderSrc, ufvsObjs[1].ufvs0, "compshd1", workgroupCount),
- ];
-
- let entity = new FixScreenPlaneEntity({
- x: -0.8, y: -0.8, width: 1.6, height: 1.6,
- materials
- });
- rc.addEntity(entity);
-
- this.mEntity = entity;
- }
-
- private mFrameDelay = 3;
- run(): void {
- let rendering = this.mEntity.isRendering();
- if (rendering) {
- if (this.mFrameDelay > 0) {
- this.mFrameDelay--;
- return;
- }
- this.mFrameDelay = 3;
-
- const ms = this.mEntity.materials;
- for (let i = 0; i < ms.length; i++) {
- ms[i].visible = (this.mStep % 2 + i) % 2 == 0;
- }
- this.mStep++;
- }
- this.mRscene.run(rendering);
- }
- }