This commit is contained in:
Andras Schmelczer 2026-05-17 14:12:01 +01:00
commit 80ed37298b
73 changed files with 3619 additions and 1295 deletions

View file

@ -14,14 +14,16 @@ export class AgentGenerationPipeline {
private readonly bindGroupLayout: GPUBindGroupLayout;
private readonly uniforms: GPUBuffer;
private readonly bindGroup: GPUBindGroup;
private readonly bindGroupsByActiveBuffer = new WeakMap<
GPUBuffer,
WeakMap<GPUBuffer, GPUBindGroup>
>();
private readonly resizePipeline: GPUComputePipeline;
private readonly compactionPipeline: GPUComputePipeline;
private readonly compactedAgentsCopyPipeline: GPUComputePipeline;
public readonly agentsBuffer: GPUBuffer;
private readonly compactedAgentsBuffer: GPUBuffer;
private activeAgentsBuffer: GPUBuffer;
private inactiveAgentsBuffer: GPUBuffer;
private readonly countersBuffer: GPUBuffer;
private readonly countersStagingBuffer: GPUBuffer;
private readonly counterClearValues = new Uint32Array(
@ -69,15 +71,8 @@ export class AgentGenerationPipeline {
],
});
this.agentsBuffer = this.device.createBuffer({
size: this.maxAgentCount * AGENT_SIZE_IN_BYTES,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
this.compactedAgentsBuffer = this.device.createBuffer({
size: this.maxAgentCount * AGENT_SIZE_IN_BYTES,
usage: GPUBufferUsage.STORAGE,
});
this.activeAgentsBuffer = this.createAgentsBuffer();
this.inactiveAgentsBuffer = this.createAgentsBuffer();
this.countersBuffer = this.device.createBuffer({
size: AgentGenerationPipeline.COUNTER_COUNT * Uint32Array.BYTES_PER_ELEMENT,
@ -94,36 +89,6 @@ export class AgentGenerationPipeline {
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
this.bindGroup = this.device.createBindGroup({
layout: this.bindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer: this.uniforms,
},
},
{
binding: 1,
resource: {
buffer: this.agentsBuffer,
},
},
{
binding: 2,
resource: {
buffer: this.countersBuffer,
},
},
{
binding: 3,
resource: {
buffer: this.compactedAgentsBuffer,
},
},
],
});
this.resizePipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [emptyBindGroupLayout, this.bindGroupLayout],
@ -145,15 +110,16 @@ export class AgentGenerationPipeline {
entryPoint: 'main',
},
});
}
this.compactedAgentsCopyPipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [emptyBindGroupLayout, this.bindGroupLayout],
}),
compute: {
module: compactionModule,
entryPoint: 'copyCompactedAgents',
},
public get agentsBuffer(): GPUBuffer {
return this.activeAgentsBuffer;
}
private createAgentsBuffer(): GPUBuffer {
return this.device.createBuffer({
size: this.maxAgentCount * AGENT_SIZE_IN_BYTES,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
}
@ -163,6 +129,10 @@ export class AgentGenerationPipeline {
? this.maxAgentCountUpperLimit
: Number.POSITIVE_INFINITY,
Math.floor(this.device.limits.maxBufferSize / AGENT_SIZE_IN_BYTES) - 1,
Math.floor(
((this.device.limits as GPUSupportedLimits).maxStorageBufferBindingSize ??
this.device.limits.maxBufferSize) / AGENT_SIZE_IN_BYTES
) - 1,
this.device.limits.maxComputeWorkgroupsPerDimension *
AgentGenerationPipeline.WORKGROUP_SIZE
);
@ -170,7 +140,7 @@ export class AgentGenerationPipeline {
public writeAgents(agentOffset: number, data: Float32Array): void {
this.device.queue.writeBuffer(
this.agentsBuffer,
this.activeAgentsBuffer,
agentOffset * AGENT_SIZE_IN_BYTES,
data
);
@ -190,7 +160,7 @@ export class AgentGenerationPipeline {
const commandEncoder = this.device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(this.resizePipeline);
passEncoder.setBindGroup(1, this.bindGroup);
passEncoder.setBindGroup(1, this.getBindGroup());
passEncoder.dispatchWorkgroups(
getWorkgroupCount(agentCount, AgentGenerationPipeline.WORKGROUP_SIZE)
);
@ -204,8 +174,6 @@ export class AgentGenerationPipeline {
return 0;
}
this.counterClearValues.fill(0);
this.agentCountUniformValues.fill(0);
this.agentCountUniformValues[0] = agentCount;
this.device.queue.writeBuffer(this.countersBuffer, 0, this.counterClearValues);
this.device.queue.writeBuffer(this.uniforms, 0, this.agentCountUniformValues);
@ -213,20 +181,12 @@ export class AgentGenerationPipeline {
const commandEncoder = this.device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(this.compactionPipeline);
passEncoder.setBindGroup(1, this.bindGroup);
passEncoder.setBindGroup(1, this.getBindGroup());
passEncoder.dispatchWorkgroups(
getWorkgroupCount(agentCount, AgentGenerationPipeline.WORKGROUP_SIZE)
);
passEncoder.end();
const copyPassEncoder = commandEncoder.beginComputePass();
copyPassEncoder.setPipeline(this.compactedAgentsCopyPipeline);
copyPassEncoder.setBindGroup(1, this.bindGroup);
copyPassEncoder.dispatchWorkgroups(
getWorkgroupCount(agentCount, AgentGenerationPipeline.WORKGROUP_SIZE)
);
copyPassEncoder.end();
commandEncoder.copyBufferToBuffer(
this.countersBuffer,
0,
@ -239,18 +199,74 @@ export class AgentGenerationPipeline {
await this.countersStagingBuffer.mapAsync(GPUMapMode.READ);
const compactedCount = new Uint32Array(
this.countersStagingBuffer.getMappedRange().slice(0, Uint32Array.BYTES_PER_ELEMENT)
this.countersStagingBuffer.getMappedRange(),
0,
1
)[0];
this.countersStagingBuffer.unmap();
this.swapAgentBuffers();
return compactedCount;
}
private getBindGroup(): GPUBindGroup {
let inactiveCache = this.bindGroupsByActiveBuffer.get(this.activeAgentsBuffer);
if (!inactiveCache) {
inactiveCache = new WeakMap<GPUBuffer, GPUBindGroup>();
this.bindGroupsByActiveBuffer.set(this.activeAgentsBuffer, inactiveCache);
}
const cached = inactiveCache.get(this.inactiveAgentsBuffer);
if (cached) {
return cached;
}
const bindGroup = this.device.createBindGroup({
layout: this.bindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer: this.uniforms,
},
},
{
binding: 1,
resource: {
buffer: this.activeAgentsBuffer,
},
},
{
binding: 2,
resource: {
buffer: this.countersBuffer,
},
},
{
binding: 3,
resource: {
buffer: this.inactiveAgentsBuffer,
},
},
],
});
inactiveCache.set(this.inactiveAgentsBuffer, bindGroup);
return bindGroup;
}
private swapAgentBuffers(): void {
[this.activeAgentsBuffer, this.inactiveAgentsBuffer] = [
this.inactiveAgentsBuffer,
this.activeAgentsBuffer,
];
}
public destroy() {
this.uniforms.destroy();
this.countersBuffer.destroy();
this.countersStagingBuffer.destroy();
this.compactedAgentsBuffer.destroy();
this.agentsBuffer.destroy();
this.inactiveAgentsBuffer.destroy();
this.activeAgentsBuffer.destroy();
}
}