diff --git a/package-lock.json b/package-lock.json index 0c9bbc0..fecec7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "tweakpane": "^4.0.5" + "tweakpane": "^4.0.5", + "typegpu": "^0.3.0-alpha.6" }, "devDependencies": { "@eslint/js": "^9.13.0", @@ -2982,9 +2983,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -2992,6 +2993,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3925,6 +3927,15 @@ "node": ">=0.8" } }, + "node_modules/tinyest": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tinyest/-/tinyest-0.0.0.tgz", + "integrity": "sha512-rCvaLewct/tm5CVLMdoa6xKNHWxT60AwhrmF7mjtkhj8w8VCeAg44NB2bzohEfN1SzfPCSyuB8GMmC/BV2gyjw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3996,6 +4007,25 @@ "node": ">= 0.8.0" } }, + "node_modules/typed-binary": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/typed-binary/-/typed-binary-4.3.1.tgz", + "integrity": "sha512-yLoFov7uL+PItmyqbxcP8HnEm7R1wkzzTe2rkX1h+HDPZQIKGRJkfSd3egE0itPXxubPpQ9lTdko+zzjOCIXOA==", + "license": "MIT" + }, + "node_modules/typegpu": { + "version": "0.3.0-alpha.6", + "resolved": "https://registry.npmjs.org/typegpu/-/typegpu-0.3.0-alpha.6.tgz", + "integrity": "sha512-Ylhr5D9lyaBRQBcaFjQelUjNWpBTJuGu9tWkqf49iqIpbV4IgK0t8varlzVcrgll+ugjCflAWX2M6nDtrFHC5w==", + "license": "MIT", + "dependencies": { + "tinyest": "~0.0.0", + "typed-binary": "^4.3.0" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", diff --git a/package.json b/package.json index 25abafc..6a55855 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "tweakpane": "^4.0.5" + "tweakpane": "^4.0.5", + "typegpu": "^0.3.0-alpha.6" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/src/slime-mold/create-pipeline-fns.ts b/src/slime-mold/create-pipeline-fns.ts index 73c62ef..eac691c 100644 --- a/src/slime-mold/create-pipeline-fns.ts +++ b/src/slime-mold/create-pipeline-fns.ts @@ -1,59 +1,45 @@ -import commonUniformsWGSL from './shaders/common-uniforms.wgsl?raw'; +import tgpu, { type Storage, type TgpuBuffer, type TgpuRoot, type Uniform } from 'typegpu'; +import type { TgpuArray } from 'typegpu/data'; import c1UpdateAgentsWGSL from './shaders/compute-01-update-agents.wgsl?raw'; import c2FadeAgentsTrailWGSL from './shaders/compute-02-fade-agents-trail.wgsl?raw'; import c3BlurAgentsTrailWGSL from './shaders/compute-03-blur-agents-trail.wgsl?raw'; import r1DrawAgentsWGSL from './shaders/render-01-draw-agents.wgsl?raw'; +import { AgentArray, AgentStruct, ColorizationUniformsStruct, SlimeSimUniformsStruct } from './data-types'; + +// Definitions shared between the different pipelines +const commonDependencies = { + SlimeSimUniformsStruct, + ColorizationUniformsStruct, +}; const createUpdateAgentsComputePipeline = ( - device: GPUDevice, - slimeSimUniformsBufferGPU: GPUBuffer, - agentsBufferGPU: GPUBuffer, + root: TgpuRoot, + slimeSimUniformsBufferGPU: TgpuBuffer & Uniform, + agentsBufferGPU: TgpuBuffer> & Storage, gpuTextureForReadView: GPUTextureView, gpuTextureForStorageView: GPUTextureView, ) => { - const updateAgentsWGSL = [commonUniformsWGSL, c1UpdateAgentsWGSL].join(''); + const device = root.device; + // Generating definitions of common dependencies based on + // their TypeGPU definitions. + const updateAgentsWGSL = tgpu.resolve({ + input: c1UpdateAgentsWGSL, + extraDependencies: commonDependencies, + }); const updateAgentsShaderModule = device.createShaderModule({ label: 'update agents: create shader module', code: updateAgentsWGSL, }); - const updateAgentsComputeBindGroupLayout = device.createBindGroupLayout({ - label: 'update agents: create compute bind group layout', - entries: [ - { - binding: 0, - visibility: GPUShaderStage.COMPUTE, - buffer: { - type: 'uniform', - }, - }, - { - binding: 1, - visibility: GPUShaderStage.COMPUTE, - buffer: { - type: 'storage', - }, - }, - { - binding: 2, - visibility: GPUShaderStage.COMPUTE, - texture: { - viewDimension: '2d', - }, - }, - { - binding: 3, - visibility: GPUShaderStage.COMPUTE, - storageTexture: { - format: 'rgba8unorm', - access: 'write-only', - viewDimension: '2d', - }, - }, - ], - }); + + const updateAgentsComputeBindGroupLayout = tgpu.bindGroupLayout({ + uSlimeSim: { uniform: SlimeSimUniformsStruct }, + agentsArray: { storage: AgentArray, access: 'mutable' }, + readFromThisTexture: { texture: 'float' }, + writeToThisTexture: { storageTexture: 'rgba8unorm', access: 'writeonly'}, + }).$name('update agents: create compute bind group layout'); const updateAgentsComputePipelineLayout = device.createPipelineLayout({ label: 'update agents: create compute pipeline layout', - bindGroupLayouts: [updateAgentsComputeBindGroupLayout], + bindGroupLayouts: [root.unwrap(updateAgentsComputeBindGroupLayout)], }); const updateAgentsComputePipeline = device.createComputePipeline({ label: 'update agents: create compute pipeline', @@ -65,80 +51,40 @@ const createUpdateAgentsComputePipeline = ( module: updateAgentsShaderModule, }, }); - const updateAgentsComputeBindGroup = device.createBindGroup({ - label: 'update agents: create compute bind group', - layout: updateAgentsComputeBindGroupLayout, - // layout: updateAgentsComputePipeline.getBindGroupLayout(0), - entries: [ - { - binding: 0, - resource: { - buffer: slimeSimUniformsBufferGPU, - }, - }, - { - binding: 1, - resource: { - buffer: agentsBufferGPU, - }, - }, - { - binding: 2, - resource: gpuTextureForReadView, - }, - { - binding: 3, - resource: gpuTextureForStorageView, - }, - ], + const updateAgentsComputeBindGroup = root.createBindGroup(updateAgentsComputeBindGroupLayout, { + uSlimeSim: slimeSimUniformsBufferGPU, + agentsArray: agentsBufferGPU, + readFromThisTexture: gpuTextureForReadView, + writeToThisTexture: gpuTextureForStorageView, }); return { updateAgentsComputePipeline, updateAgentsComputeBindGroup }; }; const createFadeAgentsTrailComputePipeline = ( - device: GPUDevice, - slimeSimUniformsBufferGPU: GPUBuffer, + root: TgpuRoot, + slimeSimUniformsBufferGPU: TgpuBuffer & Uniform, gpuTextureForReadView: GPUTextureView, gpuTextureForStorageView: GPUTextureView, ) => { - const fadeAgentsTrailWGSL = [commonUniformsWGSL, c2FadeAgentsTrailWGSL].join( - '', - ); + const device = root.device; + // Generating definitions of common dependencies based on + // their TypeGPU definitions. + const fadeAgentsTrailWGSL = tgpu.resolve({ + input: c2FadeAgentsTrailWGSL, + extraDependencies: commonDependencies, + }); const fadeAgentsTrailShaderModule = device.createShaderModule({ label: 'fade agents trail: create shader module', code: fadeAgentsTrailWGSL, }); - const fadeAgentsTrailComputeBindGroupLayout = device.createBindGroupLayout({ - label: 'fade agents trail: create bind group layout', - entries: [ - { - binding: 0, - visibility: GPUShaderStage.COMPUTE, - buffer: { - type: 'uniform', - }, - }, - { - binding: 1, - visibility: GPUShaderStage.COMPUTE, - texture: { - viewDimension: '2d', - }, - }, - { - binding: 2, - visibility: GPUShaderStage.COMPUTE, - storageTexture: { - format: 'rgba8unorm', - access: 'write-only', - viewDimension: '2d', - }, - }, - ], - }); + const fadeAgentsTrailComputeBindGroupLayout = tgpu.bindGroupLayout({ + uSlimeSim: { uniform: SlimeSimUniformsStruct }, + readFromThisTexture: { texture: 'float' }, + writeToThisTexture: { storageTexture: 'rgba8unorm', access: 'writeonly' }, + }).$name('fade agents trail: create bind group layout'); const fadeAgentsTrailComputePipelineLayout = device.createPipelineLayout({ label: 'fade agents trail: create compute pipeline layout', - bindGroupLayouts: [fadeAgentsTrailComputeBindGroupLayout], + bindGroupLayouts: [root.unwrap(fadeAgentsTrailComputeBindGroupLayout)], }); const fadeAgentsTrailComputePipeline = device.createComputePipeline({ label: 'fade agents trail: create compute pipeline', @@ -148,82 +94,42 @@ const createFadeAgentsTrailComputePipeline = ( module: fadeAgentsTrailShaderModule, }, }); - const fadeAgentsTrailBindGroup = device.createBindGroup({ - label: 'fade agents trail: create bind group', - // layout: fadeAgentsTrailComputePipeline.getBindGroupLayout(0), - layout: fadeAgentsTrailComputeBindGroupLayout, - entries: [ - { - binding: 0, - resource: { - buffer: slimeSimUniformsBufferGPU, - }, - }, - { - binding: 1, - resource: gpuTextureForReadView, - }, - { - binding: 2, - resource: gpuTextureForStorageView, - }, - ], + const fadeAgentsTrailBindGroup = root.createBindGroup(fadeAgentsTrailComputeBindGroupLayout, { + uSlimeSim: slimeSimUniformsBufferGPU, + readFromThisTexture: gpuTextureForReadView, + writeToThisTexture: gpuTextureForStorageView, }); return { fadeAgentsTrailComputePipeline, fadeAgentsTrailBindGroup }; }; const createBlurAgentsTrailComputePipeline = ( - device: GPUDevice, - slimeSimUniformsBufferGPU: GPUBuffer, - colorizationUniformsBufferGPU: GPUBuffer, + root: TgpuRoot, + slimeSimUniformsBufferGPU: TgpuBuffer & Uniform, + colorizationUniformsBufferGPU: TgpuBuffer & Uniform, gpuTextureForReadView: GPUTextureView, gpuTextureForStorageView: GPUTextureView, ) => { - // prettier-ignore - const blurAgentsTrailWGSL = [commonUniformsWGSL, c3BlurAgentsTrailWGSL].join(''); + const device = root.device; + // Generating definitions of common dependencies based on + // their TypeGPU definitions. + const blurAgentsTrailWGSL = tgpu.resolve({ + input: c3BlurAgentsTrailWGSL, + extraDependencies: commonDependencies, + }); const blurAgentsTrailShaderModule = device.createShaderModule({ label: 'blur agents trail: create shader module', code: blurAgentsTrailWGSL, }); - const blurAgentsTrailComputeBindGroupLayout = device.createBindGroupLayout({ - label: 'blur agents trail: create bindgroup layout', - entries: [ - { - binding: 0, - visibility: GPUShaderStage.COMPUTE, - buffer: { - type: 'uniform', - }, - }, - { - binding: 1, - visibility: GPUShaderStage.COMPUTE, - buffer: { - type: 'uniform', - }, - }, - { - binding: 2, - visibility: GPUShaderStage.COMPUTE, - texture: { - viewDimension: '2d', - }, - }, - { - binding: 3, - visibility: GPUShaderStage.COMPUTE, - storageTexture: { - format: 'rgba8unorm', - access: 'write-only', - viewDimension: '2d', - }, - }, - ], - }); + const blurAgentsTrailComputeBindGroupLayout = tgpu.bindGroupLayout({ + uSlimeSim: { uniform: SlimeSimUniformsStruct }, + uColorization: { uniform: ColorizationUniformsStruct }, + readFromThisTexture: { texture: 'float', viewDimension: '2d' }, + writeToThisTexture: { storageTexture: 'rgba8unorm', viewDimension: '2d', access: 'writeonly' }, + }).$name('blur agents trail: create bindgroup layout'); const blurAgentsTrailPipelineLayout = device.createPipelineLayout({ label: 'blur agents trail: create pipeline layout', - bindGroupLayouts: [blurAgentsTrailComputeBindGroupLayout], + bindGroupLayouts: [root.unwrap(blurAgentsTrailComputeBindGroupLayout)], }); const blurAgentsTrailPipeline = device.createComputePipeline({ label: 'blur agents trail: create compute pipeline', @@ -233,77 +139,42 @@ const createBlurAgentsTrailComputePipeline = ( module: blurAgentsTrailShaderModule, }, }); - const blurAgentsTrailBindGroup = device.createBindGroup({ - label: 'blur agents trail: create bind group', - layout: blurAgentsTrailComputeBindGroupLayout, - entries: [ - { - binding: 0, - resource: { - buffer: slimeSimUniformsBufferGPU, - }, - }, - { - binding: 1, - resource: { - buffer: colorizationUniformsBufferGPU, - }, - }, - { - binding: 2, - resource: gpuTextureForReadView, - }, - { - binding: 3, - resource: gpuTextureForStorageView, - }, - ], + const blurAgentsTrailBindGroup = root.createBindGroup(blurAgentsTrailComputeBindGroupLayout, { + uSlimeSim: slimeSimUniformsBufferGPU, + uColorization: colorizationUniformsBufferGPU, + readFromThisTexture: gpuTextureForReadView, + writeToThisTexture: gpuTextureForStorageView, }); return { blurAgentsTrailPipeline, blurAgentsTrailBindGroup }; }; const createDrawAgentsRenderPipeline = ( - device: GPUDevice, + root: TgpuRoot, canvasFormat: GPUTextureFormat, - slimeSimUniformsBufferGPU: GPUBuffer, - colorizationUniformsBufferGPU: GPUBuffer, + slimeSimUniformsBufferGPU: TgpuBuffer & Uniform, + colorizationUniformsBufferGPU: TgpuBuffer & Uniform, gpuTextureForReadView: GPUTextureView, ) => { - const drawAgentsWGSL = [commonUniformsWGSL, r1DrawAgentsWGSL].join(''); + const device = root.device; + // Generating definitions of common dependencies based on + // their TypeGPU definitions. + const drawAgentsWGSL = tgpu.resolve({ + input: r1DrawAgentsWGSL, + extraDependencies: commonDependencies, + }); const drawAgentsShaderModule = device.createShaderModule({ label: 'draw agents: create shader module', code: drawAgentsWGSL, }); - const drawAgentsBindGroupLayout = device.createBindGroupLayout({ - label: 'draw agents: create bind group layout', - entries: [ - { - binding: 0, - visibility: GPUShaderStage.FRAGMENT, - buffer: { - type: 'uniform', - }, - }, - { - binding: 1, - visibility: GPUShaderStage.FRAGMENT, - buffer: { - type: 'uniform', - }, - }, - { - binding: 2, - visibility: GPUShaderStage.FRAGMENT, - texture: { - sampleType: 'float', - }, - }, - ], - }); + const drawAgentsBindGroupLayout = tgpu.bindGroupLayout({ + uSlimeSim: { uniform: SlimeSimUniformsStruct }, + uColorization: { uniform: ColorizationUniformsStruct }, + readFromThisTexture: { texture: 'float', viewDimension: '2d' }, + }).$name('draw agents: create bind group layout'); const drawAgentsRenderPipeline = device.createRenderPipeline({ label: 'draw agents: create render pipeline', layout: device.createPipelineLayout({ - bindGroupLayouts: [drawAgentsBindGroupLayout], + bindGroupLayouts: [root.unwrap(drawAgentsBindGroupLayout)], }), vertex: { entryPoint: 'vertexShader', @@ -322,27 +193,10 @@ const createDrawAgentsRenderPipeline = ( topology: 'triangle-list', }, }); - const drawAgentsBindGroup = device.createBindGroup({ - label: 'draw agents: create bind group', - layout: drawAgentsBindGroupLayout, - entries: [ - { - binding: 0, - resource: { - buffer: slimeSimUniformsBufferGPU, - }, - }, - { - binding: 1, - resource: { - buffer: colorizationUniformsBufferGPU, - }, - }, - { - binding: 2, - resource: gpuTextureForReadView, - }, - ], + const drawAgentsBindGroup = root.createBindGroup(drawAgentsBindGroupLayout, { + uSlimeSim: slimeSimUniformsBufferGPU, + uColorization: colorizationUniformsBufferGPU, + readFromThisTexture: gpuTextureForReadView, }); return { drawAgentsRenderPipeline, drawAgentsBindGroup }; }; diff --git a/src/slime-mold/data-types.ts b/src/slime-mold/data-types.ts new file mode 100644 index 0000000..9d5b0fe --- /dev/null +++ b/src/slime-mold/data-types.ts @@ -0,0 +1,31 @@ +import { arrayOf, f32, struct, vec2f, vec3f } from 'typegpu/data'; + +export const SlimeSimUniformsStruct = struct({ + resolution: vec2f, + + // general + radius: f32, + stepSize: f32, + decayT: f32, + + // sensor + sensorOffset: f32, + sensorAngle: f32, + rotationAngle: f32, + + // other + diffuseKernel: f32, +}).$name('SlimeSimUniformsStruct'); + +export const ColorizationUniformsStruct = struct({ + blurTrail: f32, + enableLighting: f32, + slimeColor: vec3f, +}).$name('ColorizationUniformsStruct'); + +export const AgentStruct = struct({ + position: vec2f, + direction: vec2f, +}).$name('Agent'); + +export const AgentArray = (count: number) => arrayOf(AgentStruct, count); diff --git a/src/slime-mold/initialize-fns.ts b/src/slime-mold/initialize-fns.ts index 87d68fd..8b2b9da 100644 --- a/src/slime-mold/initialize-fns.ts +++ b/src/slime-mold/initialize-fns.ts @@ -1,3 +1,4 @@ +import tgpu, { type TgpuBuffer, type TgpuRoot, type Uniform } from 'typegpu'; import { Pane } from 'tweakpane'; import { UNIFORMS_COLORIZATION, UNIFORMS_SLIME_SIM } from './uniforms'; import { @@ -6,6 +7,7 @@ import { resetGPUTextures, resetSlimeSimUniformsBuffer, } from './initialize-helpers'; +import { AgentArray, ColorizationUniformsStruct, SlimeSimUniformsStruct } from './data-types'; const initializeWebGPU = async (canvasWidth: number, canvasHeight: number) => { const canvas = document.querySelector('canvas'); @@ -13,19 +15,8 @@ const initializeWebGPU = async (canvasWidth: number, canvasHeight: number) => { throw new Error('No canvas detected in the browser.'); } - if (!navigator.gpu) { - throw new Error('WebGPU is not supported in this browser.'); - } - - const adapter = await navigator.gpu.requestAdapter(); - if (!adapter) { - throw new Error('No appropriate GPUAdapter found.'); - } - - // https://eliemichel.github.io/LearnWebGPU/getting-started/adapter-and-device/the-device.html - // device is used to create all other gpu objects - // once device is created, the adapter should in general no longer be used - const device = await adapter.requestDevice(); + const root = await tgpu.init(); + const device = root.device; device.addEventListener('uncapturederror', (event) => { console.log( @@ -47,14 +38,14 @@ const initializeWebGPU = async (canvasWidth: number, canvasHeight: number) => { format: canvasFormat, }); - return { device, canvas, canvasFormat, context }; + return { root, canvas, canvasFormat, context }; }; const initializeSlimeSimUniforms = ( - device: GPUDevice, + root: TgpuRoot, canvas: HTMLCanvasElement, pane: Pane, -): GPUBuffer => { +): TgpuBuffer & Uniform => { // ============================================================= // Set up tweakpane settings. // ============================================================= @@ -86,25 +77,18 @@ const initializeSlimeSimUniforms = ( // ============================================================= // Create gpu buffer(s). - // - // Array of size 8 is too small. // ============================================================= - const uniformsCPU = new Float32Array(10); - const uniformsBufferGPU = device.createBuffer({ - label: 'create uniforms buffer for gpu', - size: uniformsCPU.byteLength, - usage: - GPUBufferUsage.COPY_DST | - GPUBufferUsage.COPY_SRC | - GPUBufferUsage.UNIFORM, - }); - resetSlimeSimUniformsBuffer(device, canvas, uniformsCPU, uniformsBufferGPU); + const uniformsBufferGPU = root + .createBuffer(SlimeSimUniformsStruct) + .$name('create uniforms buffer for gpu') + .$usage('uniform'); + resetSlimeSimUniformsBuffer(canvas, uniformsBufferGPU); // ============================================================= // Add tweakpane handlers. // ============================================================= pane.on('change', () => { - resetSlimeSimUniformsBuffer(device, canvas, uniformsCPU, uniformsBufferGPU); + resetSlimeSimUniformsBuffer(canvas, uniformsBufferGPU); console.log('pane changed!'); }); randomizeAgentSettingsButton.on('click', () => { @@ -141,7 +125,7 @@ const initializeSlimeSimUniforms = ( return uniformsBufferGPU; }; -const initializeColorizationUniforms = (device: GPUDevice, pane: Pane) => { +const initializeColorizationUniforms = (root: TgpuRoot, pane: Pane) => { // ============================================================= // Set up tweakpane settings. // ============================================================= @@ -159,18 +143,11 @@ const initializeColorizationUniforms = (device: GPUDevice, pane: Pane) => { // ============================================================= // Create gpu buffer(s). // ============================================================= - const colorizationUniformsArrayCPU = new Float32Array(12); - const colorizationUniformsBufferGPU = device.createBuffer({ - label: 'colorization uniforms buffer', - size: colorizationUniformsArrayCPU.byteLength, - usage: - GPUBufferUsage.COPY_DST | - GPUBufferUsage.COPY_SRC | - GPUBufferUsage.UNIFORM, - }); + const colorizationUniformsBufferGPU = root + .createBuffer(ColorizationUniformsStruct) + .$name('colorization uniforms buffer') + .$usage('uniform'); resetColorizationUniformsBuffer( - device, - colorizationUniformsArrayCPU, colorizationUniformsBufferGPU, ); @@ -179,8 +156,6 @@ const initializeColorizationUniforms = (device: GPUDevice, pane: Pane) => { // ============================================================= colorizationFolder.on('change', () => { resetColorizationUniformsBuffer( - device, - colorizationUniformsArrayCPU, colorizationUniformsBufferGPU, ); }); @@ -228,12 +203,13 @@ const initializeGPUTextures = ( }; const initializeAgents = ( - device: GPUDevice, + root: TgpuRoot, canvas: HTMLCanvasElement, pane: Pane, gpuTextureForStorage: GPUTexture, gpuTextureForRead: GPUTexture, ) => { + const device = root.device; // ============================================================= // Set up tweakpane settings. // ============================================================= @@ -256,24 +232,18 @@ const initializeAgents = ( // could be to re-create bindgroups with agentsBufferGPU whenever it changes. // this will not incur performance hit because compute pass runs with SLIME_MOLD_UNIFORMS.numOfAgents // even if this gpu array is large, we won't use it for most calculations when numOfAgents is small - const maxAgentsArraySize = UNIFORMS_SLIME_SIM.numOfAgents.max * 4; - const fakeArrayCPU = new Float32Array(maxAgentsArraySize); - const agentsBufferGPU = device.createBuffer({ - label: 'create agents buffer', - size: fakeArrayCPU.byteLength, - usage: - GPUBufferUsage.STORAGE | - GPUBufferUsage.COPY_SRC | - GPUBufferUsage.COPY_DST, - }); - resetAgentsBuffer(device, canvas, agentsBufferGPU); + const agentsBufferGPU = root + .createBuffer(AgentArray(UNIFORMS_SLIME_SIM.numOfAgents.max)) + .$name('create agents buffer') + .$usage('storage'); + resetAgentsBuffer(canvas, agentsBufferGPU); resetGPUTextures(device, canvas, gpuTextureForStorage, gpuTextureForRead); // ============================================================= // Add tweakpane handlers. // ============================================================= simulationFolder.on('change', () => { - resetAgentsBuffer(device, canvas, agentsBufferGPU); + resetAgentsBuffer(canvas, agentsBufferGPU); resetGPUTextures(device, canvas, gpuTextureForStorage, gpuTextureForRead); }); diff --git a/src/slime-mold/initialize-helpers.ts b/src/slime-mold/initialize-helpers.ts index d52bdee..5b7efff 100644 --- a/src/slime-mold/initialize-helpers.ts +++ b/src/slime-mold/initialize-helpers.ts @@ -1,82 +1,61 @@ +import type { TgpuBuffer } from 'typegpu'; +import { type TgpuArray, vec2f, vec3f } from 'typegpu/data'; import { UNIFORMS_COLORIZATION, UNIFORMS_SLIME_SIM } from './uniforms'; +import { AgentStruct, ColorizationUniformsStruct, SlimeSimUniformsStruct } from './data-types'; const resetAgentsBuffer = ( - device: GPUDevice, canvas: HTMLCanvasElement, - agentsBufferGPU: GPUBuffer, + agentsBufferGPU: TgpuBuffer>, ) => { + const startRadius = UNIFORMS_SLIME_SIM.startRadius.value; const numOfAgents = UNIFORMS_SLIME_SIM.numOfAgents.value; - const agentsArraySize = numOfAgents * 4; - const agentsArrayCPU = new Float32Array(agentsArraySize); - // initialize agents data - for (let i = 0; i < numOfAgents; i++) { - // agent position + // write to gpu buffer + agentsBufferGPU.write(Array.from({ length: numOfAgents }).map(() => { let x = canvas.width / 2; let y = canvas.height / 2; let r = Math.random() * 10; - agentsArrayCPU[i * 4 + 0] = - x + Math.cos(r) * UNIFORMS_SLIME_SIM.startRadius.value; - agentsArrayCPU[i * 4 + 1] = - y + Math.sin(r) * UNIFORMS_SLIME_SIM.startRadius.value; - - // agent direction - agentsArrayCPU[i * 4 + 2] = Math.random() * 2 - 1; - agentsArrayCPU[i * 4 + 3] = Math.random() * 2 - 1; - } - // write to gpu buffer - device.queue.writeBuffer(agentsBufferGPU, 0, agentsArrayCPU); + return { + position: vec2f(x + Math.cos(r) * startRadius, y + Math.sin(r) * startRadius), + direction: vec2f(Math.random() * 2 - 1, Math.random() * 2 - 1), + }; + })); return agentsBufferGPU; }; const resetSlimeSimUniformsBuffer = ( - device: GPUDevice, canvas: HTMLCanvasElement, - uniformsCPU: Float32Array, - uniformsBufferGPU: GPUBuffer, + uniformsBufferGPU: TgpuBuffer, ) => { - // canvas - uniformsCPU[0] = canvas.width; - uniformsCPU[1] = canvas.height; + uniformsBufferGPU.write({ + // canvas + resolution: vec2f(canvas.width, canvas.height), + // general + radius: UNIFORMS_SLIME_SIM.radius.value, + stepSize: UNIFORMS_SLIME_SIM.stepSize.value, + decayT: UNIFORMS_SLIME_SIM.decayT.value, + // sensor + sensorOffset: UNIFORMS_SLIME_SIM.sensorOffset.value, + sensorAngle: UNIFORMS_SLIME_SIM.sensorAngle.value, + rotationAngle: UNIFORMS_SLIME_SIM.rotationAngle.value, + diffuseKernel: 0, + }); - // general - uniformsCPU[2] = UNIFORMS_SLIME_SIM.radius.value; - uniformsCPU[3] = UNIFORMS_SLIME_SIM.stepSize.value; - uniformsCPU[4] = UNIFORMS_SLIME_SIM.decayT.value; - - // sensor - uniformsCPU[5] = UNIFORMS_SLIME_SIM.sensorOffset.value; - uniformsCPU[6] = UNIFORMS_SLIME_SIM.sensorAngle.value; - uniformsCPU[7] = UNIFORMS_SLIME_SIM.rotationAngle.value; - - device.queue.writeBuffer(uniformsBufferGPU, 0, uniformsCPU); return uniformsBufferGPU; }; const resetColorizationUniformsBuffer = ( - device: GPUDevice, - colorizationUniformsCPU: Float32Array, - colorizationUniformsBufferGPU: GPUBuffer, + colorizationUniformsBufferGPU: TgpuBuffer, ) => { - colorizationUniformsCPU[0] = UNIFORMS_COLORIZATION.blurTrail.value ? 1 : 0; - colorizationUniformsCPU[1] = UNIFORMS_COLORIZATION.enableLighting.value - ? 1 - : 0; - const slimeColor = UNIFORMS_COLORIZATION.slimeColor.value; - // todo: dive a little deeper into struct memory packing - // todo: for some reason, this needs to be 4th value, can't be the 1st one? - colorizationUniformsCPU[4] = slimeColor.r / 255; - colorizationUniformsCPU[5] = slimeColor.g / 255; - colorizationUniformsCPU[6] = slimeColor.b / 255; - device.queue.writeBuffer( - colorizationUniformsBufferGPU, - 0, - colorizationUniformsCPU, - ); + colorizationUniformsBufferGPU.write({ + blurTrail: UNIFORMS_COLORIZATION.blurTrail.value ? 1 : 0, + enableLighting: UNIFORMS_COLORIZATION.enableLighting.value ? 1 : 0, + slimeColor: vec3f(slimeColor.r / 255, slimeColor.g / 255, slimeColor.b / 255), + }); }; const resetGPUTextures = ( diff --git a/src/slime-mold/main.ts b/src/slime-mold/main.ts index fb10d9e..64adb95 100644 --- a/src/slime-mold/main.ts +++ b/src/slime-mold/main.ts @@ -1,4 +1,5 @@ import { Pane } from 'tweakpane'; +import type { TgpuRoot } from 'typegpu'; import { UNIFORMS_SLIME_SIM } from './uniforms'; import { initializeWebGPU, @@ -34,13 +35,15 @@ const main = async () => { // canvasFormat -> needed for render pipeline fragment shader settings // context -> needed to create a view in the render pass // ============================================================= + let root: TgpuRoot | null = null; let device: GPUDevice | null = null; let canvas: HTMLCanvasElement | null = null; let canvasFormat: GPUTextureFormat | null = null; let context: GPUCanvasContext | null = null; try { const result = await initializeWebGPU(canvasWidth, canvasHeight); - device = result.device; + root = result.root; + device = result.root.device; canvas = result.canvas; canvasFormat = result.canvasFormat; context = result.context; @@ -69,7 +72,7 @@ const main = async () => { // Initialize the agents buffer. // ============================================================= const agentsBufferGPU = initializeAgents( - device, + root, canvas, initializedPane, gpuTextureForStorage, @@ -80,7 +83,7 @@ const main = async () => { // Initialize slime sim uniforms and tweakpane. // ============================================================= const slimeSimUniformsBufferGPU = initializeSlimeSimUniforms( - device, + root, canvas, initializedPane, ); @@ -89,7 +92,7 @@ const main = async () => { // Initialize colorization uniforms and tweakpane. // ============================================================= const colorizationUniformsBufferGPU = initializeColorizationUniforms( - device, + root, initializedPane, ); @@ -101,7 +104,7 @@ const main = async () => { // ============================================================= const { updateAgentsComputePipeline, updateAgentsComputeBindGroup } = createUpdateAgentsComputePipeline( - device, + root, slimeSimUniformsBufferGPU, agentsBufferGPU, gpuTextureForReadView, @@ -113,7 +116,7 @@ const main = async () => { // ============================================================= const { fadeAgentsTrailComputePipeline, fadeAgentsTrailBindGroup } = createFadeAgentsTrailComputePipeline( - device, + root, slimeSimUniformsBufferGPU, gpuTextureForReadView, gpuTextureForStorageView, @@ -124,7 +127,7 @@ const main = async () => { // ============================================================= const { blurAgentsTrailPipeline, blurAgentsTrailBindGroup } = createBlurAgentsTrailComputePipeline( - device, + root, slimeSimUniformsBufferGPU, colorizationUniformsBufferGPU, gpuTextureForReadView, @@ -136,7 +139,7 @@ const main = async () => { // ============================================================= const { drawAgentsRenderPipeline, drawAgentsBindGroup } = createDrawAgentsRenderPipeline( - device, + root, canvasFormat, slimeSimUniformsBufferGPU, colorizationUniformsBufferGPU, @@ -160,7 +163,7 @@ const main = async () => { label: 'update agents: begin compute pass', }); updateAgentsComputePass.setPipeline(updateAgentsComputePipeline); - updateAgentsComputePass.setBindGroup(0, updateAgentsComputeBindGroup); + updateAgentsComputePass.setBindGroup(0, root.unwrap(updateAgentsComputeBindGroup)); updateAgentsComputePass.dispatchWorkgroups( UNIFORMS_SLIME_SIM.numOfAgents.value, ); @@ -186,7 +189,7 @@ const main = async () => { label: 'fade agents trail: begin compute pass', }); fadeAgentsTrailComputePass.setPipeline(fadeAgentsTrailComputePipeline); - fadeAgentsTrailComputePass.setBindGroup(0, fadeAgentsTrailBindGroup); + fadeAgentsTrailComputePass.setBindGroup(0, root.unwrap(fadeAgentsTrailBindGroup)); fadeAgentsTrailComputePass.dispatchWorkgroups(canvas.width, canvas.height); fadeAgentsTrailComputePass.end(); @@ -206,7 +209,7 @@ const main = async () => { label: 'blur agents trail: begin compute pass', }); blurAgentsTrailComputePass.setPipeline(blurAgentsTrailPipeline); - blurAgentsTrailComputePass.setBindGroup(0, blurAgentsTrailBindGroup); + blurAgentsTrailComputePass.setBindGroup(0, root.unwrap(blurAgentsTrailBindGroup)); blurAgentsTrailComputePass.dispatchWorkgroups( // This means we need to create 8x8 workgroups in compute shader. canvas.width / 8, @@ -238,7 +241,7 @@ const main = async () => { ], }); drawAgentsRenderPass.setPipeline(drawAgentsRenderPipeline); - drawAgentsRenderPass.setBindGroup(0, drawAgentsBindGroup); + drawAgentsRenderPass.setBindGroup(0, root.unwrap(drawAgentsBindGroup)); drawAgentsRenderPass.draw(6); drawAgentsRenderPass.end(); diff --git a/src/slime-mold/shaders/common-uniforms.wgsl b/src/slime-mold/shaders/common-uniforms.wgsl deleted file mode 100644 index d801e10..0000000 --- a/src/slime-mold/shaders/common-uniforms.wgsl +++ /dev/null @@ -1,22 +0,0 @@ -struct SlimeSimUniformsStruct { - resolution: vec2f, - - // general - radius: f32, - stepSize: f32, - decayT: f32, - - // sensor - sensorOffset: f32, - sensorAngle: f32, - rotationAngle: f32, - - // other - diffuseKernel: f32, -}; - -struct ColorizationUniformsStruct { - blurTrail: f32, - enableLighting: f32, - slimeColor: vec3f, -};