From 5feb7c929dbfe31c83a4c508984d24c137b21d70 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 16 Apr 2023 14:05:47 +0100 Subject: [PATCH] Fix issues --- src/index.scss | 2 - src/pipelines/agents/agent-pipeline.ts | 23 ++++--- src/pipelines/agents/agent.ts | 2 +- src/pipelines/agents/agent.wgsl | 4 +- src/pipelines/diffusion/diffuse.wgsl | 52 +++++++--------- src/pipelines/diffusion/diffusion-pipeline.ts | 60 +++++++++++++++---- src/pipelines/render/render-pipeline.ts | 26 ++++---- src/pipelines/render/render.wgsl | 26 +------- src/renderer.ts | 33 +++++----- src/settings.ts | 11 ++-- src/utils/full-screen-quad.ts | 52 ++++++++++++++++ src/utils/full-screen-quad.wgsl | 12 ++++ 12 files changed, 190 insertions(+), 113 deletions(-) create mode 100644 src/utils/full-screen-quad.ts create mode 100644 src/utils/full-screen-quad.wgsl diff --git a/src/index.scss b/src/index.scss index 725dfba..cf543a5 100644 --- a/src/index.scss +++ b/src/index.scss @@ -18,12 +18,10 @@ html { body { height: 100%; - background-color: hotpink; display: flex; } canvas { - background: hotpink; height: 100%; width: 100%; } diff --git a/src/pipelines/agents/agent-pipeline.ts b/src/pipelines/agents/agent-pipeline.ts index 3c65c46..7d10988 100644 --- a/src/pipelines/agents/agent-pipeline.ts +++ b/src/pipelines/agents/agent-pipeline.ts @@ -1,4 +1,4 @@ -import { AGENT_SIZE, Agent } from './agent'; +import { AGENT_SIZE_IN_BYTES, Agent } from './agent'; import shader from './agent.wgsl'; export class AgentPipeline { @@ -8,11 +8,16 @@ export class AgentPipeline { private readonly pipeline: GPUComputePipeline; private readonly uniforms: GPUBuffer; private readonly agentsBuffer: GPUBuffer; + private bindGroup?: GPUBindGroup; private previousTrailMapIn?: GPUTexture; private previousTrailMapOut?: GPUTexture; public constructor(private readonly device: GPUDevice, agents: Array) { + if (agents.length === 0) { + throw new Error('No agents provided'); + } + this.pipeline = device.createComputePipeline({ layout: 'auto', compute: { @@ -29,16 +34,16 @@ export class AgentPipeline { }); const serializedAgents = new Float32Array( - new ArrayBuffer(agents.length * AGENT_SIZE) + new ArrayBuffer(agents.length * AGENT_SIZE_IN_BYTES) ); - agents.forEach((agent, index) => { - serializedAgents[index * 4 + 0] = agent.position[0]; - serializedAgents[index * 4 + 1] = agent.position[1]; - serializedAgents[index * 4 + 2] = agent.angle; + agents.forEach((agent, i) => { + serializedAgents[i * 4 + 0] = agent.position[0]; + serializedAgents[i * 4 + 1] = agent.position[1]; + serializedAgents[i * 4 + 2] = agent.angle; }); this.agentsBuffer = device.createBuffer({ - size: agents.length * AGENT_SIZE, + size: agents.length * AGENT_SIZE_IN_BYTES, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, }); @@ -97,7 +102,9 @@ export class AgentPipeline { passEncoder.setPipeline(this.pipeline); passEncoder.setBindGroup(0, this.bindGroup); passEncoder.dispatchWorkgroups( - Math.ceil(this.agentsBuffer.size / AGENT_SIZE / AgentPipeline.WORKGROUP_SIZE) + Math.ceil( + this.agentsBuffer.size / AGENT_SIZE_IN_BYTES / AgentPipeline.WORKGROUP_SIZE + ) ); passEncoder.end(); } diff --git a/src/pipelines/agents/agent.ts b/src/pipelines/agents/agent.ts index e204892..007c3e7 100644 --- a/src/pipelines/agents/agent.ts +++ b/src/pipelines/agents/agent.ts @@ -5,4 +5,4 @@ export interface Agent { angle: number; } -export const AGENT_SIZE = 4; +export const AGENT_SIZE_IN_BYTES = 4 * 4; diff --git a/src/pipelines/agents/agent.wgsl b/src/pipelines/agents/agent.wgsl index 822d198..d6f76fc 100644 --- a/src/pipelines/agents/agent.wgsl +++ b/src/pipelines/agents/agent.wgsl @@ -18,7 +18,7 @@ struct Settings { }; @group(0) @binding(0) var settings : Settings; -@group(0) @binding(1) var agents: array; +@group(0) @binding(1) var agents : array; @group(0) @binding(2) var TrailMapIn : texture_2d; @group(0) @binding(3) var TrailMapOut : texture_storage_2d; @@ -46,8 +46,6 @@ fn main(@builtin(global_invocation_id) global_id : vec3) { var agent = agents[id]; - - var random = f32(hash( u32( agent.position.y * f32(settings.width) + agent.position.x diff --git a/src/pipelines/diffusion/diffuse.wgsl b/src/pipelines/diffusion/diffuse.wgsl index 56813bd..f117540 100644 --- a/src/pipelines/diffusion/diffuse.wgsl +++ b/src/pipelines/diffusion/diffuse.wgsl @@ -1,34 +1,28 @@ -struct VertexOutput { - @builtin(position) Position : vec4, - @location(0) fragUV : vec2, -} - -@vertex -fn vertex(@builtin(vertex_index) i : u32) -> VertexOutput { - var pos = array, 4>( - vec2(-1.0, 1.0), - vec2(-1.0, -1.0), - vec2(1.0, 1.0), - vec2(1.0, -1.0), - ); - - var output : VertexOutput; - output.Position = vec4(pos[i], 0.0, 1.0); - output.fragUV = output.Position.xy * 0.5 + 0.5; - return output; -} - - -@group(0) @binding(0) var mySampler: sampler; -@group(0) @binding(1) var TargetTexture : texture_2d; +struct Settings { + size : vec2, + diffusionRate : f32, + decayRate : f32, + deltaTime : f32, +}; +@group(0) @binding(0) var settings : Settings; +@group(0) @binding(1) var Sampler: sampler; +@group(0) @binding(2) var trailMap : texture_2d; @fragment -fn fragment(@location(0) fragUV: vec2) -> @location(0) vec4 { - // return vec4(1.0, 0.0, 0.0, 1.0); - return mix( - vec4(textureSample(TargetTexture, mySampler, fragUV).rgb, 0.1), - vec4(1.0, 1.0, 1.0, 1.0), - 0.01 +fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { + let current = textureSample(trailMap, Sampler, uv).rgb; + + let neighbours: vec3 = ( + textureSample(trailMap, Sampler, uv + vec2(0, 1) / settings.size).rgb + + textureSample(trailMap, Sampler, uv + vec2(0, -1) / settings.size).rgb + + textureSample(trailMap, Sampler, uv + vec2(-1, 0) / settings.size).rgb + + textureSample(trailMap, Sampler, uv + vec2(1, 0) / settings.size).rgb ); + + return vec4(mix( + current, + neighbours / 4.0, + settings.diffusionRate + ) * (1.0 - settings.decayRate), 1.0); } diff --git a/src/pipelines/diffusion/diffusion-pipeline.ts b/src/pipelines/diffusion/diffusion-pipeline.ts index ad4ff73..02b1d29 100644 --- a/src/pipelines/diffusion/diffusion-pipeline.ts +++ b/src/pipelines/diffusion/diffusion-pipeline.ts @@ -1,19 +1,23 @@ +import { setUpFullScreenQuad } from '../../utils/full-screen-quad'; import shader from './diffuse.wgsl'; export class DiffusionPipeline { + private static readonly UNIFORM_COUNT = 6; + private readonly pipeline: GPURenderPipeline; + private readonly uniforms: GPUBuffer; + private readonly quadVertexBuffer: GPUBuffer; + private bindGroup?: GPUBindGroup; private previousTrailMapIn?: GPUTexture; public constructor(private readonly device: GPUDevice) { + const { buffer, vertex } = setUpFullScreenQuad(device); + this.quadVertexBuffer = buffer; + this.pipeline = device.createRenderPipeline({ layout: 'auto', - vertex: { - module: device.createShaderModule({ - code: shader, - }), - entryPoint: 'vertex', - }, + vertex, fragment: { module: device.createShaderModule({ code: shader, @@ -29,6 +33,31 @@ export class DiffusionPipeline { topology: 'triangle-strip', }, }); + + this.uniforms = this.device.createBuffer({ + size: DiffusionPipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + } + + public setParameters({ + width, + height, + diffusionRate, + decayRate, + deltaTime, + }: { + width: number; + height: number; + diffusionRate: number; + decayRate: number; + deltaTime: number; + }) { + this.device.queue.writeBuffer( + this.uniforms, + 0, + new Float32Array([width, height, diffusionRate, decayRate, deltaTime]) + ); } public execute( @@ -49,11 +78,12 @@ export class DiffusionPipeline { ], }; - const renderPassEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); - renderPassEncoder.setBindGroup(0, this.bindGroup!); - renderPassEncoder.setPipeline(this.pipeline); - renderPassEncoder.draw(4, 1); - renderPassEncoder.end(); + const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + passEncoder.setPipeline(this.pipeline); + passEncoder.setVertexBuffer(0, this.quadVertexBuffer); + passEncoder.setBindGroup(0, this.bindGroup); + passEncoder.draw(4, 1); + passEncoder.end(); } private ensureBindGroupExists(trailMapIn: GPUTexture) { @@ -63,13 +93,19 @@ export class DiffusionPipeline { entries: [ { binding: 0, + resource: { + buffer: this.uniforms, + }, + }, + { + binding: 1, resource: this.device.createSampler({ magFilter: 'linear', minFilter: 'linear', }), }, { - binding: 1, + binding: 2, resource: trailMapIn.createView(), }, ], diff --git a/src/pipelines/render/render-pipeline.ts b/src/pipelines/render/render-pipeline.ts index 0af5e9c..51d1af1 100644 --- a/src/pipelines/render/render-pipeline.ts +++ b/src/pipelines/render/render-pipeline.ts @@ -1,7 +1,10 @@ +import { setUpFullScreenQuad } from '../../utils/full-screen-quad'; import shader from './render.wgsl'; export class RenderPipeline { private readonly pipeline: GPURenderPipeline; + private readonly quadVertexBuffer: GPUBuffer; + private bindGroup?: GPUBindGroup; private previousColorTexture?: GPUTexture; @@ -10,14 +13,12 @@ export class RenderPipeline { private readonly device: GPUDevice, preferredCanvasFormat: GPUTextureFormat ) { + const { buffer, vertex } = setUpFullScreenQuad(device); + this.quadVertexBuffer = buffer; + this.pipeline = device.createRenderPipeline({ layout: 'auto', - vertex: { - module: device.createShaderModule({ - code: shader, - }), - entryPoint: 'vertex', - }, + vertex, fragment: { module: device.createShaderModule({ code: shader, @@ -42,17 +43,18 @@ export class RenderPipeline { colorAttachments: [ { view: this.context.getCurrentTexture().createView(), - clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, loadOp: 'clear', storeOp: 'store', }, ], }; - const renderPassEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); - renderPassEncoder.setBindGroup(0, this.bindGroup); - renderPassEncoder.setPipeline(this.pipeline); - renderPassEncoder.draw(4, 1); - renderPassEncoder.end(); + const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + passEncoder.setPipeline(this.pipeline); + passEncoder.setVertexBuffer(0, this.quadVertexBuffer); + passEncoder.setBindGroup(0, this.bindGroup); + passEncoder.draw(4, 1); + passEncoder.end(); } private ensureBindGroupExists(colorTexture: GPUTexture) { diff --git a/src/pipelines/render/render.wgsl b/src/pipelines/render/render.wgsl index 6c0be6e..71518ed 100644 --- a/src/pipelines/render/render.wgsl +++ b/src/pipelines/render/render.wgsl @@ -1,30 +1,8 @@ -struct VertexOutput { - @builtin(position) Position : vec4, - @location(0) fragUV : vec2, -} - -@vertex -fn vertex(@builtin(vertex_index) i : u32) -> VertexOutput { - var pos = array, 4>( - vec2(-1.0, 1.0), - vec2(-1.0, -1.0), - vec2(1.0, 1.0), - vec2(1.0, -1.0), - ); - - var output : VertexOutput; - output.Position = vec4(pos[i], 0.0, 1.0); - output.fragUV = output.Position.xy * 0.5 + 0.5; - return output; -} - - @group(0) @binding(0) var mySampler: sampler; @group(0) @binding(1) var TargetTexture : texture_2d; @fragment -fn fragment(@location(0) fragUV: vec2) -> @location(0) vec4 { -// return vec4(1.0, 0.0, 0.0, 1.0); - return textureSample(TargetTexture, mySampler, fragUV); +fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { + return textureSample(TargetTexture, mySampler, uv) * 10.0; } diff --git a/src/renderer.ts b/src/renderer.ts index f6d0fd5..2d01fa3 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -15,7 +15,7 @@ export default class Renderer { private agentPipeline: AgentPipeline; private renderPipeline: RenderPipeline; - private diffusionPipeline: any; + private diffusionPipeline: DiffusionPipeline; private preferredCanvasFormat: GPUTextureFormat; private trailMapA?: GPUTexture; @@ -34,8 +34,8 @@ export default class Renderer { this.resize(); window.addEventListener('resize', this.resize.bind(this)); - const agents: Array = new Array(settings.numAgents).fill(0).map(() => ({ - position: vec2.fromValues(randomBetween(0, 500), randomBetween(0, 500)), + const agents: Array = new Array(settings.agentCount).fill(0).map(() => ({ + position: vec2.fromValues(randomBetween(0, 1000), randomBetween(0, 1000)), angle: randomBetween(0, Math.PI * 2), })); @@ -54,21 +54,14 @@ export default class Renderer { this.canvas.height = this.canvas.clientHeight * devicePixelRatio; this.trailMapA?.destroy(); - this.trailMapA = this.device.createTexture({ - size: { - width: this.canvas.width, - height: this.canvas.height, - depthOrArrayLayers: 1, - }, - format: 'rgba16float', - usage: - GPUTextureUsage.STORAGE_BINDING | - GPUTextureUsage.TEXTURE_BINDING | - GPUTextureUsage.RENDER_ATTACHMENT, - }); + this.trailMapA = this.createTrailMap(); this.trailMapB?.destroy(); - this.trailMapB = this.device.createTexture({ + this.trailMapB = this.createTrailMap(); + } + + private createTrailMap(): GPUTexture { + return this.device.createTexture({ size: { width: this.canvas.width, height: this.canvas.height, @@ -110,11 +103,17 @@ export default class Renderer { sensorAngleDegrees: 45, ...settings, }); + this.diffusionPipeline.setParameters({ + width: this.canvas.width, + height: this.canvas.height, + deltaTime: 0.016, + ...settings, + }); const commandEncoder = this.device.createCommandEncoder(); this.agentPipeline.execute(commandEncoder, this.trailMapA, this.trailMapB); this.diffusionPipeline.execute(commandEncoder, this.trailMapB, this.trailMapA); - this.renderPipeline.execute(commandEncoder, this.trailMapB); + this.renderPipeline.execute(commandEncoder, this.trailMapA); [this.trailMapA, this.trailMapB] = [this.trailMapB, this.trailMapA]; this.queue.submit([commandEncoder.finish()]); diff --git a/src/settings.ts b/src/settings.ts index cbe1eff..1fd7241 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -2,11 +2,11 @@ const SpawnMode = { Random: 0, Point: 1, InwardCircle: 2, RandomCircle: 3 }; interface Settings { stepsPerFrame: number; - numAgents: number; + agentCount: number; spawnMode: number; trailWeight: number; decayRate: number; - diffuseRate: number; + diffusionRate: number; moveSpeed: number; turnSpeed: number; sensorAngleSpacing: number; @@ -16,11 +16,12 @@ interface Settings { export const settings: Settings = { stepsPerFrame: 2, - numAgents: 250000, + agentCount: 500000, spawnMode: SpawnMode.InwardCircle, trailWeight: 5, - decayRate: 0.2, - diffuseRate: 3, + + decayRate: 0.05, + diffusionRate: 0.1, moveSpeed: 20, turnSpeed: 2, diff --git a/src/utils/full-screen-quad.ts b/src/utils/full-screen-quad.ts new file mode 100644 index 0000000..00a2188 --- /dev/null +++ b/src/utils/full-screen-quad.ts @@ -0,0 +1,52 @@ +import shader from './full-screen-quad.wgsl'; + +export const setUpFullScreenQuad = ( + device: GPUDevice +): { + buffer: GPUBuffer; + vertex: GPUVertexState; +} => { + const buffer = device.createBuffer({ + size: 4 * 4 * 4, // 4x vec4 + usage: GPUBufferUsage.VERTEX, + mappedAtCreation: true, + }); + // prettier-ignore + const vertexData = [ + // posX posY U V + -1.0, -1.0, 0.0, 1.0, + +1.0, -1.0, 1.0, 1.0, + -1.0, +1.0, 0.0, 0.0, + +1.0, +1.0, 1.0, 0.0, + ]; + new Float32Array(buffer.getMappedRange()).set(vertexData); + buffer.unmap(); + + return { + buffer, + vertex: { + module: device.createShaderModule({ + code: shader, + }), + entryPoint: 'vertex', + buffers: [ + { + arrayStride: 4 * 4, + stepMode: 'vertex', + attributes: [ + { + shaderLocation: 0, + offset: 0, + format: 'float32x2', + }, + { + shaderLocation: 1, + offset: 8, + format: 'float32x2', + }, + ], + }, + ], + }, + }; +}; diff --git a/src/utils/full-screen-quad.wgsl b/src/utils/full-screen-quad.wgsl new file mode 100644 index 0000000..703e472 --- /dev/null +++ b/src/utils/full-screen-quad.wgsl @@ -0,0 +1,12 @@ +struct VertexOutput { + @builtin(position) position : vec4, + @location(0) uv : vec2, +} + +@vertex +fn vertex( + @location(0) position : vec2, + @location(1) uv : vec2 +) -> VertexOutput { + return VertexOutput(vec4(position, 0.0, 1.0), uv); +}