Google codelab WebGPU入门教程源码<7> - 完整的元胞自动机之生命游戏的完整实现(源码)

news2024/11/22 16:48:19

对应的教程文章: 

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

对应的源码执行效果:

对应的教程源码: 

此处源码和教程本身提供的部分代码可能存在一点差异。

class Color4 {

	r: number;
	g: number;
	b: number;
	a: number;

	constructor(pr = 1.0, pg = 1.0, pb = 1.0, pa = 1.0) {
		this.r = pr;
		this.g = pg;
		this.b = pb;
		this.a = pa;
	}
}

export class WGPUGameOfLife {

	private mRVertices: Float32Array = null;
	private mRPipeline: any | null = null;
	private mRSimulationPipeline: any | null = null;
	private mVtxBuffer: any | null = null;
	private mCanvasFormat: any | null = null;
	private mWGPUDevice: any | null = null;
	private mWGPUContext: any | null = null;
	private mUniformBindGroups: any | null = null;
	private mGridSize = 64;
	private mShdWorkGroupSize = 8;
	constructor() {}
	initialize(): void {

		const canvas = document.createElement("canvas");
		canvas.width = 512;
		canvas.height = 512;
		document.body.appendChild(canvas);
		console.log("ready init webgpu ...");
		this.initWebGPU(canvas).then(() => {
			console.log("webgpu initialization finish ...");

			this.autoUpdate();
		});
	}
	private mUniformObj: any = {uniformArray: null, uniformBuffer: null};

    private m_timeoutId: any = -1;

    private autoUpdate(): void {
        if (this.m_timeoutId > -1) {
            clearTimeout(this.m_timeoutId);
        }
        this.m_timeoutId = setTimeout(this.autoUpdate.bind(this), 100);// 20 fps
        this.updateWGPUCanvas();
    }
	private createStorage(device: any): any {
		// Create an array representing the active state of each cell.
		const cellStateArray = new Uint32Array(this.mGridSize * this.mGridSize);
		// Create two storage buffers to hold the cell state.
		const cellStateStorage = [
			device.createBuffer({
				label: "Cell State A",
				size: cellStateArray.byteLength,
				usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
			}),
			device.createBuffer({
				label: "Cell State B",
				size: cellStateArray.byteLength,
				usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
			})
		];
		// Mark every third cell of the first grid as active.
		for (let i = 0; i < cellStateArray.length; ++i) {
			cellStateArray[i] = Math.random() > 0.6 ? 1 : 0;
		}
		device.queue.writeBuffer(cellStateStorage[0], 0, cellStateArray);
		// Mark every other cell of the second grid as active.
		for (let i = 0; i < cellStateArray.length; i++) {
			cellStateArray[i] = i % 2;
		}
		device.queue.writeBuffer(cellStateStorage[1], 0, cellStateArray);

		return cellStateStorage;
	}

	private createUniform(device: any): any {
		// Create a uniform buffer that describes the grid.
		const uniformArray = new Float32Array([this.mGridSize, this.mGridSize]);
		const uniformBuffer = device.createBuffer({
			label: "Grid Uniforms",
			size: uniformArray.byteLength,
			usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
		});
		device.queue.writeBuffer(uniformBuffer, 0, uniformArray);


		const cellStateStorage = this.createStorage(device);

		// Create the bind group layout and pipeline layout.
		const bindGroupLayout = device.createBindGroupLayout({
			label: "Cell Bind Group Layout",
			entries: [{
				binding: 0,
				visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
				buffer: {} // Grid uniform buffer
			}, {
				binding: 1,
				visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
				buffer: { type: "read-only-storage"} // Cell state input buffer
			}, {
				binding: 2,
				visibility: GPUShaderStage.COMPUTE,
				buffer: { type: "storage"} // Cell state output buffer
			}]
		});
		const bindGroups = [
			device.createBindGroup({
				label: "Cell renderer bind group A",
				layout: bindGroupLayout,
				entries: [
					{
						binding: 0,
						resource: { buffer: uniformBuffer }
					}, {
						binding: 1,
						resource: { buffer: cellStateStorage[0] }
					}, {
						binding: 2,
						resource: { buffer: cellStateStorage[1] }
					}
				],
			}),
			device.createBindGroup({
				label: "Cell renderer bind group B",
				layout: bindGroupLayout,
				entries: [
					{
						binding: 0,
						resource: { buffer: uniformBuffer }
					}, {
						binding: 1,
						resource: { buffer: cellStateStorage[1] }
					}, {
						binding: 2,
						resource: { buffer: cellStateStorage[0] }
					}
				],
			})
		];

		this.mUniformBindGroups = bindGroups;

		const obj = this.mUniformObj;
		obj.uniformArray = uniformArray;
		obj.uniformBuffer = uniformBuffer;

		return bindGroupLayout;
	}
	private mStep = 0;
	private createComputeShader(device: any): any {
		let sgs = this.mShdWorkGroupSize;
		// Create the compute shader that will process the simulation.
		const simulationShaderModule = device.createShaderModule({
			label: "Game of Life simulation shader",
			code: `
			@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(${sgs}, ${sgs})
			fn computeMain(@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;
					}
				}
			}`
		});

		return simulationShaderModule;
	}

	private createRectGeometryData(device: any): void {

		let hsize = 0.8;
		let vertices = new Float32Array([
		//   X,    Y,
			-hsize, -hsize, // Triangle 1 (Blue)
			 hsize, -hsize,
			 hsize,  hsize,

			-hsize, -hsize, // Triangle 2 (Red)
			 hsize,  hsize,
			-hsize,  hsize,
		]);

		let vertexBuffer = device.createBuffer({
			label: "Cell vertices",
			size: vertices.byteLength,
			usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
		});
		device.queue.writeBuffer(vertexBuffer, /*bufferOffset=*/0, vertices);

		this.mRVertices = vertices;
		this.mVtxBuffer = vertexBuffer;
	}
	private renderpass(device: any, pass: any, computePass: any): void {

		let vertices = this.mRVertices;
		let vertexBuffer = this.mVtxBuffer;
		let cellPipeline = this.mRPipeline;
		let simulationPipeline = this.mRSimulationPipeline;

		if(!cellPipeline) {

			this.createRectGeometryData( device );

			const vertexBufferLayout = {
				arrayStride: 8,
				attributes: [{
					format: "float32x2",
					offset: 0,
					shaderLocation: 0, // Position, see vertex shader
				}],
			};
			const shaderCodes = `
			struct VertexInput {
				@location(0) pos: vec2f,
				@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 vertexMain(input: VertexInput) -> VertexOutput  {
				let i = f32(input.instance);
				let cell = vec2f(i % grid.x, floor(i / grid.x));
				let cellOffset = cell / grid * 2;

				let state = f32(cellState[input.instance]);
				let gridPos = (input.pos * state + 1) / grid - 1 + cellOffset;

				var output: VertexOutput;
				output.pos = vec4f(gridPos, 0, 1);
				output.cell = cell;
				return output;
			}

			@fragment
			fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
				let c = input.cell/grid;
				return vec4f(c, 1.0 - c.x, 1);
			}
			`;
			const bindGroupLayout = this.createUniform(device);
			const pipelineLayout = device.createPipelineLayout({
				label: "Cell Pipeline Layout",
				bindGroupLayouts: [ bindGroupLayout ],
			});
			const cellShaderModule = device.createShaderModule({
				label: "Cell shader",
				code: shaderCodes
			});
			cellPipeline = device.createRenderPipeline({
				label: "Cell pipeline",
				layout: pipelineLayout,
				vertex: {
					module: cellShaderModule,
					entryPoint: "vertexMain",
					buffers: [vertexBufferLayout]
				},
				fragment: {
					module: cellShaderModule,
					entryPoint: "fragmentMain",
					targets: [{
						format: this.mCanvasFormat
					}]
				},
			});

			const simulationShaderModule = this.createComputeShader( device );
			// Create a compute pipeline that updates the game state.
			simulationPipeline = device.createComputePipeline({
					label: "Simulation pipeline",
					layout: pipelineLayout,
					compute: {
					module: simulationShaderModule,
					entryPoint: "computeMain",
				}
			});

			vertices = this.mRVertices;
			vertexBuffer = this.mVtxBuffer;

			this.mRPipeline = cellPipeline;
			this.mRSimulationPipeline = simulationPipeline;
		}

		const bindGroups = this.mUniformBindGroups;


		pass.setPipeline(cellPipeline);
		pass.setVertexBuffer(0, vertexBuffer);

		pass.setBindGroup(0, bindGroups[this.mStep % 2]);
		// pass.setBindGroup(0, bindGroups[0]);
		pass.draw(vertices.length / 2, this.mGridSize * this.mGridSize);

		pass.end();


		computePass.setPipeline(simulationPipeline),
		computePass.setBindGroup(0, bindGroups[this.mStep % 2]);
		const workgroupCount = Math.ceil(this.mGridSize / this.mShdWorkGroupSize);
		computePass.dispatchWorkgroups(workgroupCount, workgroupCount);
		computePass.end();
		this.mStep ++;
	}

	private updateWGPUCanvas(clearColor: Color4 = null): void {

		clearColor = clearColor ? clearColor : new Color4(0.05, 0.05, 0.1);
		const device = this.mWGPUDevice;
		const context = this.mWGPUContext;
		const rpassParam = {
			colorAttachments: [
				{
					clearValue: clearColor,
					// clearValue: [0.3,0.7,0.5,1.0], // yes
					view: context.getCurrentTexture().createView(),
					loadOp: "clear",
					storeOp: "store"
				}
			]
		};

		const encoder = device.createCommandEncoder();
		const pass = encoder.beginRenderPass( rpassParam );

		const computeEncoder = device.createCommandEncoder();
		const computePass = computeEncoder.beginComputePass();

		this.renderpass(device, pass, computePass);


		device.queue.submit([ encoder.finish() ]);
		device.queue.submit([ computeEncoder.finish() ]);
	}
	private async initWebGPU(canvas: HTMLCanvasElement) {

		const gpu = (navigator as any).gpu;
		if (gpu) {
			console.log("WebGPU supported on this browser.");

			const adapter = await gpu.requestAdapter();
			if (adapter) {
				console.log("Appropriate GPUAdapter found.");
				const device = await adapter.requestDevice();
				if (device) {
					this.mWGPUDevice = device;
					console.log("Appropriate GPUDevice found.");
					const context = canvas.getContext("webgpu") as any;
					const canvasFormat = gpu.getPreferredCanvasFormat();
					this.mWGPUContext = context;
					this.mCanvasFormat = canvasFormat;
					console.log("canvasFormat: ", canvasFormat);
					context.configure({
						device: device,
						format: canvasFormat,
						alphaMode: "premultiplied"
					});
				} else {
					throw new Error("No appropriate GPUDevice found.");
				}
			} else {
				throw new Error("No appropriate GPUAdapter found.");
			}
		} else {
			throw new Error("WebGPU not supported on this browser.");
		}
	}
	run(): void {}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1220560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剑指offer(C++)-JZ39:数组中出现次数超过一半的数字(算法-其他)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 题目描述&#xff1a; 给一个长度为 n 的数组&#xff0c;数组中有一个数字出现的次数超过数组长度的一半&#xff0c;请找出这个…

分布式任务调度-XXL-job

源码仓库地址 http://gitee.com/xuxueli0323/xxl-job 前置环境 docker容器环境配置 拉取msyql镜像&#xff1a; docker pull mysql:5.7创建mysql容器: docker run -p 3306:3306 --name mysql57 \ -v /opt/mysql/conf:/etc/mysql \ -v /opt/mysql/logs:/var/log/mysql \ -v …

AR眼镜_单目光波导VS双目光波导方案

双目光波导AR眼镜方案是一种创新的智能设备&#xff0c;可以在现实场景中叠加虚拟信息&#xff0c;提供增强的视觉体验和交互体验。光学显示方案是AR眼镜的核心技术之一&#xff0c;它对眼镜的性能和使用体验起着决定性的作用。 相比于单目AR眼镜&#xff0c;双目AR眼镜具有更好…

opencv(5): 滤波器

滤波的作用&#xff1a;一幅图像通过滤波器得到另一幅图像&#xff1b;其中滤波器又称为卷积核&#xff0c;滤波的过程称为卷积。 锐化&#xff1a;边缘变清晰 低通滤波&#xff08;Low-pass Filtering&#xff09;&#xff1a; 目标&#xff1a;去除图像中的高频成分&#…

【Proteus仿真】【Arduino单片机】DS1302时钟

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用PCF8574、LCD1602液晶、DS1302等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示时间日期。 二、软件设计 /* 作者&#xff1a;…

SmartX 超融合 5.1 版本有哪些新特性和技术提升?

近日&#xff0c;SmartX 正式发布了超融合产品组合 SmartX HCI 5.1 版本&#xff0c;以全面升级的超融合软件、分布式块存储、容器管理与服务、软件定义的网络与安全等组件&#xff0c;为虚拟化和容器负载在计算、存储、网络和管理层面提供统一的架构和生产级别的能力支持。本期…

三菱FX3U小项目—运料小车自动化

目录 一、项目描述 二、IO口分配 三、项目流程图 四、项目程序 五、总结 一、项目描述 设备如下图所示&#xff0c;其中启动按钮SB1用来开启运料小车&#xff0c;停止按钮SB2用来手动停止运料小车(其工作方式任务模式要求)。当小车在原点SQ1位置&#xff0c;按下启动按钮S…

【深度学习实验】网络优化与正则化(七):超参数优化方法——网格搜索、随机搜索、贝叶斯优化、动态资源分配、神经架构搜索

文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、优化算法0. 导入必要的库1. 随机梯度下降SGD算法a. PyTorch中的SGD优化器b. 使用SGD优化器的前馈神经网络 2.随机梯度下降的改进方法a. 学习率调整b. 梯度估计修正 3. 梯度估计修正&#xff1a;动量法Momen…

什么是模糊测试?

背景&#xff1a;近年来&#xff0c;随着信息技术的发展&#xff0c;各种新型自动化测试技术如雨后春笋般出现。其中&#xff0c;模糊测试&#xff08;fuzz testing&#xff09;技术开始受到行业关注&#xff0c;它尤其适用于发现未知的、隐蔽性较强的底层缺陷。这里&#xff0…

crontab定时任务是否执行

centos查看 crontab 是否启动 systemctl status crond.service 查看cron服务的启动状态 systemctl start crond.service 启动cron服务[命令没有提示] systemctl stop crond.service 停止cron服务[命令没有提示] systemctl restart crond.service 重启cron服务[命令没有提示] s…

23111701[含文档+PPT+源码等]计算机毕业设计javaweb点餐系统全套餐饮就餐订餐餐厅

文章目录 **项目功能简介:****点餐系统分为前台和后台****前台功能介绍&#xff1a;****后台功能介绍&#xff1a;** **论文截图&#xff1a;****实现&#xff1a;****代码片段&#xff1a;** 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;77687156…

23111704[含文档+PPT+源码等]计算机毕业设计springboot办公管理系统oa人力人事办公

文章目录 **软件开发环境及开发工具&#xff1a;****功能介绍&#xff1a;****实现&#xff1a;****代码片段&#xff1a;** 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;776871563 软件开发环境及开发工具&#xff1a; 前端技术&#xff1a;jsc…

契约锁助力货物进出口全程无纸化,加速通关、降低贸易成本

我国作为全球最大的制造业国家和最大的货物贸易国家&#xff0c;政府始终注重引入数字化技术&#xff0c;创新管理和服务模式&#xff0c;帮助降低企业进出口成本&#xff0c;加速货物流通。 近年国家海关总署、商务部、税务总局及各地政府在进出口“报关”、“提货”、“收货备…

【Coppeliasim仿真】 坐标系间平滑插补

在仿真环境中控制两个参考框架&#xff08;ReferenceFrame1和ReferenceFrame2&#xff09;之间进行平滑的插值运动。在两个参考框架之间插值运动的过程中&#xff0c;使用了两种不同的方法&#xff0c;通过设置useMethodNb来选择使用的方法。 方法1使用了旋转轴和角度的计算&a…

如何选择合适的数据库管理工具?Navicat Or DBeaver

写在前面 在阅读本文之前&#xff0c;糖糖给大家准备了Navicat和DBeaver安装包&#xff0c;在公众号内回复“Navicat”或“DBeaver”或"数据库管理工具"来下载。 引言 对于测试而言&#xff0c;在实际工作中往往会用到数据库&#xff0c;那么选择使用哪种类型的数…

SSL加密

小王学习录 今日摘录前言HTTP + SSL = HTTPSSSL加密1. 对称加密2. 非对称加密 + 对称加密3. 证书今日摘录 但愿四海无尘沙,有人卖酒仍卖花。 前言 SSL表示安全套接层,是一个用于保护计算机网络中数据传输安全的协议。SSL通过加密来防止第三方恶意截取并篡改数据。在实际应用…

MySQL数据库干货_30——【精选】JDBC常用操作

JDBC批量添加数据 批量添加数据简介 在JDBC中通过PreparedStatement的对象的addBatch()和executeBatch()方法进行数据的批量插入。 addBatch()把若干SQL语句装载到一起&#xff0c;然后一次性传送到数据库执行&#xff0c;即是批量处理sql数据的。executeBatch()会将装载到一…

【实习】串口通信

modbus介绍 详解Modbus通信协议—清晰易懂 Modbus协议是一个master/slave架构的协议。有一个节点是master节点&#xff0c;其他使用Modbus协议参与通信的节点是slave节点。每一个slave设备都有一个唯一的地址。在串行和MB网络中&#xff0c;只有被指定为主节点的节点可以启动一…

技术分享 | JMeter性能测试实现与分析

导语 JMeter是由著名开源软件巨头Apache组织开发的纯Java的压力测试工具&#xff0c;它即能测试动态服务&#xff08;WebService&#xff09;&#xff0c;也能测试静态资源&#xff0c;包括Servlet服务、CGI脚本等&#xff0c;还能测试动态语言服务&#xff08;PHP、Java、ASP…

串口通信

1.1 串口简介 在串口中 用0和1表示高低电平 VCC供电 设备A给设备B供电 如果各自都有供电的模块就不需要连接这个线 GND的连线是为了获取相同的电压基准 因为有时候获得电压各自判断的标准不一样 可能获得不一样的电压 如果想A发送数据给B那么蓝线不需要连接 如果想B发送给A那么…