• Google codelab WebGPU入门教程源码<6> - 使用计算着色器实现计算元胞自动机之生命游戏模拟过程(源码)


    对应的教程文章: 

    https://codelabs.developers.google.com/your-first-webgpu-app?hl=zh-cn#7

    对应的源码执行效果:

    对应的教程源码: 

    此处源码和教程本身提供的部分代码可能存在一点差异。点击画面,切换效果。

    1. class Color4 {
    2. r: number;
    3. g: number;
    4. b: number;
    5. a: number;
    6. constructor(pr = 1.0, pg = 1.0, pb = 1.0, pa = 1.0) {
    7. this.r = pr;
    8. this.g = pg;
    9. this.b = pb;
    10. this.a = pa;
    11. }
    12. }
    13. export class WGPUSimulation {
    14. private mRVertices: Float32Array = null;
    15. private mRPipeline: any | null = null;
    16. private mRSimulationPipeline: any | null = null;
    17. private mVtxBuffer: any | null = null;
    18. private mCanvasFormat: any | null = null;
    19. private mWGPUDevice: any | null = null;
    20. private mWGPUContext: any | null = null;
    21. private mUniformBindGroups: any | null = null;
    22. private mGridSize = 32;
    23. private mShdWorkGroupSize = 8;
    24. constructor() {}
    25. initialize(): void {
    26. const canvas = document.createElement("canvas");
    27. canvas.width = 512;
    28. canvas.height = 512;
    29. document.body.appendChild(canvas);
    30. console.log("ready init webgpu ...");
    31. this.initWebGPU(canvas).then(() => {
    32. console.log("webgpu initialization finish ...");
    33. this.updateWGPUCanvas();
    34. });
    35. document.onmousedown = (evt):void => {
    36. this.updateWGPUCanvas( new Color4(0.05, 0.05, 0.1) );
    37. }
    38. }
    39. private mUniformObj: any = {uniformArray: null, uniformBuffer: null};
    40. private createStorage(device: any): any {
    41. // Create an array representing the active state of each cell.
    42. const cellStateArray = new Uint32Array(this.mGridSize * this.mGridSize);
    43. // Create two storage buffers to hold the cell state.
    44. const cellStateStorage = [
    45. device.createBuffer({
    46. label: "Cell State A",
    47. size: cellStateArray.byteLength,
    48. usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
    49. }),
    50. device.createBuffer({
    51. label: "Cell State B",
    52. size: cellStateArray.byteLength,
    53. usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
    54. })
    55. ];
    56. // Mark every third cell of the first grid as active.
    57. for (let i = 0; i < cellStateArray.length; i+=3) {
    58. cellStateArray[i] = 1;
    59. }
    60. device.queue.writeBuffer(cellStateStorage[0], 0, cellStateArray);
    61. // Mark every other cell of the second grid as active.
    62. for (let i = 0; i < cellStateArray.length; i++) {
    63. cellStateArray[i] = i % 2;
    64. }
    65. device.queue.writeBuffer(cellStateStorage[1], 0, cellStateArray);
    66. return cellStateStorage;
    67. }
    68. private createUniform(device: any): any {
    69. // Create a uniform buffer that describes the grid.
    70. const uniformArray = new Float32Array([this.mGridSize, this.mGridSize]);
    71. const uniformBuffer = device.createBuffer({
    72. label: "Grid Uniforms",
    73. size: uniformArray.byteLength,
    74. usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
    75. });
    76. device.queue.writeBuffer(uniformBuffer, 0, uniformArray);
    77. const cellStateStorage = this.createStorage(device);
    78. // Create the bind group layout and pipeline layout.
    79. const bindGroupLayout = device.createBindGroupLayout({
    80. label: "Cell Bind Group Layout",
    81. entries: [{
    82. binding: 0,
    83. visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
    84. buffer: {} // Grid uniform buffer
    85. }, {
    86. binding: 1,
    87. visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
    88. buffer: { type: "read-only-storage"} // Cell state input buffer
    89. }, {
    90. binding: 2,
    91. visibility: GPUShaderStage.COMPUTE,
    92. buffer: { type: "storage"} // Cell state output buffer
    93. }]
    94. });
    95. const bindGroups = [
    96. device.createBindGroup({
    97. label: "Cell renderer bind group A",
    98. layout: bindGroupLayout,
    99. entries: [
    100. {
    101. binding: 0,
    102. resource: { buffer: uniformBuffer }
    103. }, {
    104. binding: 1,
    105. resource: { buffer: cellStateStorage[0] }
    106. }, {
    107. binding: 2,
    108. resource: { buffer: cellStateStorage[1] }
    109. }
    110. ],
    111. }),
    112. device.createBindGroup({
    113. label: "Cell renderer bind group B",
    114. layout: bindGroupLayout,
    115. entries: [
    116. {
    117. binding: 0,
    118. resource: { buffer: uniformBuffer }
    119. }, {
    120. binding: 1,
    121. resource: { buffer: cellStateStorage[1] }
    122. }, {
    123. binding: 2,
    124. resource: { buffer: cellStateStorage[0] }
    125. }
    126. ],
    127. })
    128. ];
    129. this.mUniformBindGroups = bindGroups;
    130. const obj = this.mUniformObj;
    131. obj.uniformArray = uniformArray;
    132. obj.uniformBuffer = uniformBuffer;
    133. return bindGroupLayout;
    134. }
    135. private mStep = 0;
    136. private createComputeShader(device: any): any {
    137. let sgs = this.mShdWorkGroupSize;
    138. // Create the compute shader that will process the simulation.
    139. const simulationShaderModule = device.createShaderModule({
    140. label: "Game of Life simulation shader",
    141. code: `
    142. @group(0) @binding(0) var grid: vec2f;
    143. @group(0) @binding(1) var cellStateIn: array;
    144. @group(0) @binding(2) var cellStateOut: array;
    145. fn cellIndex(cell: vec2u) -> u32 {
    146. return cell.y * u32(grid.x) + cell.x;
    147. }
    148. @compute @workgroup_size(${sgs}, ${sgs})
    149. fn computeMain(@builtin(global_invocation_id) cell: vec3u) {
    150. if (cellStateIn[cellIndex(cell.xy)] == 1) {
    151. cellStateOut[cellIndex(cell.xy)] = 0;
    152. } else {
    153. cellStateOut[cellIndex(cell.xy)] = 1;
    154. }
    155. }`
    156. });
    157. return simulationShaderModule;
    158. }
    159. private createRectGeometryData(device: any, pass: any, computePass: any): void {
    160. let vertices = this.mRVertices;
    161. let vertexBuffer = this.mVtxBuffer;
    162. let cellPipeline = this.mRPipeline;
    163. let simulationPipeline = this.mRSimulationPipeline;
    164. if(!cellPipeline) {
    165. let hsize = 0.8;
    166. vertices = new Float32Array([
    167. // X, Y,
    168. -hsize, -hsize, // Triangle 1 (Blue)
    169. hsize, -hsize,
    170. hsize, hsize,
    171. -hsize, -hsize, // Triangle 2 (Red)
    172. hsize, hsize,
    173. -hsize, hsize,
    174. ]);
    175. vertexBuffer = device.createBuffer({
    176. label: "Cell vertices",
    177. size: vertices.byteLength,
    178. usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
    179. });
    180. device.queue.writeBuffer(vertexBuffer, /*bufferOffset=*/0, vertices);
    181. const vertexBufferLayout = {
    182. arrayStride: 8,
    183. attributes: [{
    184. format: "float32x2",
    185. offset: 0,
    186. shaderLocation: 0, // Position, see vertex shader
    187. }],
    188. };
    189. const shaderCodes = `
    190. struct VertexInput {
    191. @location(0) pos: vec2f,
    192. @builtin(instance_index) instance: u32,
    193. };
    194. struct VertexOutput {
    195. @builtin(position) pos: vec4f,
    196. @location(0) cell: vec2f,
    197. };
    198. @group(0) @binding(0) var grid: vec2f;
    199. @group(0) @binding(1) var cellState: array;
    200. @vertex
    201. fn vertexMain(input: VertexInput) -> VertexOutput {
    202. let i = f32(input.instance);
    203. let cell = vec2f(i % grid.x, floor(i / grid.x));
    204. let cellOffset = cell / grid * 2;
    205. let state = f32(cellState[input.instance]);
    206. let gridPos = (input.pos * state + 1) / grid - 1 + cellOffset;
    207. var output: VertexOutput;
    208. output.pos = vec4f(gridPos, 0, 1);
    209. output.cell = cell;
    210. return output;
    211. }
    212. @fragment
    213. fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
    214. // return vec4f(input.cell, 0, 1);
    215. let c = input.cell/grid;
    216. return vec4f(c, 1.0 - c.x, 1);
    217. }
    218. `;
    219. const bindGroupLayout = this.createUniform(device);
    220. const pipelineLayout = device.createPipelineLayout({
    221. label: "Cell Pipeline Layout",
    222. bindGroupLayouts: [ bindGroupLayout ],
    223. });
    224. const cellShaderModule = device.createShaderModule({
    225. label: "Cell shader",
    226. code: shaderCodes
    227. });
    228. cellPipeline = device.createRenderPipeline({
    229. label: "Cell pipeline",
    230. layout: pipelineLayout,
    231. vertex: {
    232. module: cellShaderModule,
    233. entryPoint: "vertexMain",
    234. buffers: [vertexBufferLayout]
    235. },
    236. fragment: {
    237. module: cellShaderModule,
    238. entryPoint: "fragmentMain",
    239. targets: [{
    240. format: this.mCanvasFormat
    241. }]
    242. },
    243. });
    244. const simulationShaderModule = this.createComputeShader( device );
    245. // Create a compute pipeline that updates the game state.
    246. simulationPipeline = device.createComputePipeline({
    247. label: "Simulation pipeline",
    248. layout: pipelineLayout,
    249. compute: {
    250. module: simulationShaderModule,
    251. entryPoint: "computeMain",
    252. }
    253. });
    254. this.mRVertices = vertices;
    255. this.mVtxBuffer = vertexBuffer;
    256. this.mRPipeline = cellPipeline;
    257. this.mRSimulationPipeline = simulationPipeline;
    258. }
    259. const bindGroups = this.mUniformBindGroups;
    260. computePass.setPipeline(simulationPipeline),
    261. computePass.setBindGroup(0, bindGroups[this.mStep % 2]);
    262. const workgroupCount = Math.ceil(this.mGridSize / this.mShdWorkGroupSize);
    263. computePass.dispatchWorkgroups(workgroupCount, workgroupCount);
    264. pass.setPipeline(cellPipeline);
    265. pass.setVertexBuffer(0, vertexBuffer);
    266. // pass.setBindGroup(0, this.mUniformBindGroup);
    267. pass.setBindGroup(0, bindGroups[this.mStep % 2]);
    268. pass.draw(vertices.length / 2, this.mGridSize * this.mGridSize);
    269. this.mStep ++;
    270. }
    271. private updateWGPUCanvas(clearColor: Color4 = null): void {
    272. clearColor = clearColor ? clearColor : new Color4(0.05, 0.05, 0.1);
    273. const device = this.mWGPUDevice;
    274. const context = this.mWGPUContext;
    275. const rpassParam = {
    276. colorAttachments: [
    277. {
    278. clearValue: clearColor,
    279. // clearValue: [0.3,0.7,0.5,1.0], // yes
    280. view: context.getCurrentTexture().createView(),
    281. loadOp: "clear",
    282. storeOp: "store"
    283. }
    284. ]
    285. };
    286. const encoder = device.createCommandEncoder();
    287. const pass = encoder.beginRenderPass( rpassParam );
    288. const computeEncoder = device.createCommandEncoder();
    289. const computePass = computeEncoder.beginComputePass()
    290. this.createRectGeometryData(device, pass, computePass);
    291. pass.end();
    292. computePass.end();
    293. device.queue.submit([ encoder.finish() ]);
    294. device.queue.submit([ computeEncoder.finish() ]);
    295. }
    296. private async initWebGPU(canvas: HTMLCanvasElement) {
    297. const gpu = (navigator as any).gpu;
    298. if (gpu) {
    299. console.log("WebGPU supported on this browser.");
    300. const adapter = await gpu.requestAdapter();
    301. if (adapter) {
    302. console.log("Appropriate GPUAdapter found.");
    303. const device = await adapter.requestDevice();
    304. if (device) {
    305. this.mWGPUDevice = device;
    306. console.log("Appropriate GPUDevice found.");
    307. const context = canvas.getContext("webgpu") as any;
    308. const canvasFormat = gpu.getPreferredCanvasFormat();
    309. this.mWGPUContext = context;
    310. this.mCanvasFormat = canvasFormat;
    311. console.log("canvasFormat: ", canvasFormat);
    312. context.configure({
    313. device: device,
    314. format: canvasFormat,
    315. alphaMode: "premultiplied"
    316. });
    317. } else {
    318. throw new Error("No appropriate GPUDevice found.");
    319. }
    320. } else {
    321. throw new Error("No appropriate GPUAdapter found.");
    322. }
    323. } else {
    324. throw new Error("WebGPU not supported on this browser.");
    325. }
    326. }
    327. run(): void {}
    328. }

  • 相关阅读:
    【Matlab笔记_17】查找数组中相同元素
    Mac搭建Jmeter分布式加压步骤
    2022-10-27-梯度下降法结合线性回归预测公交车数量和GDP关系
    【万字详解】JavaScript算法 | 力扣经典题~收藏起来,面试用得上
    ArcGIS JS API 4.23 Web服务器部署
    es6 函数的使用
    什么是顺序表?
    如何实现CSDN评论区粉丝幸运抽选功能:一场代码与运气的碰撞
    编译原理—运行环境、局部存储分配、活动记录、全局栈式存储分配、非局部名字的访问、参数传递
    Linux(阿里云服务器)中安装Nginx命令
  • 原文地址:https://blog.csdn.net/vily_lei/article/details/134439019