more
This commit is contained in:
parent
2fe3c69963
commit
6bc125be1c
104 changed files with 3084 additions and 2410 deletions
|
|
@ -1,5 +1,4 @@
|
|||
const AGENT_WORKGROUP_SIZE = 64;
|
||||
export const AGENT_MAX_DISPATCHABLE_COUNT = 65_535 * AGENT_WORKGROUP_SIZE;
|
||||
export const AGENT_WORKGROUP_SIZE = 64;
|
||||
|
||||
export const dispatchAgentWorkgroups = (
|
||||
passEncoder: GPUComputePassEncoder,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import { vec2 } from 'gl-matrix';
|
|||
|
||||
import { createBindGroupCache } from '../../../utils/graphics/bind-group-cache';
|
||||
import { smartCompile } from '../../../utils/graphics/smart-compile';
|
||||
import { AGENT_MAX_DISPATCHABLE_COUNT, dispatchAgentWorkgroups } from '../agent-dispatch';
|
||||
import { dispatchAgentWorkgroups } from '../agent-dispatch';
|
||||
import { AGENT_SIZE_IN_BYTES, getMaxSupportedAgentCount } from '../agent-limits';
|
||||
import compactionShader from './agent-compaction.wgsl?raw';
|
||||
import resizeShader from './agent-resize.wgsl?raw';
|
||||
import agentSchema from './agent-schema.wgsl?raw';
|
||||
|
||||
export const AGENT_FLOAT_COUNT = 8;
|
||||
const AGENT_SIZE_IN_BYTES = AGENT_FLOAT_COUNT * Float32Array.BYTES_PER_ELEMENT;
|
||||
export { AGENT_FLOAT_COUNT } from '../agent-limits';
|
||||
|
||||
export class AgentGenerationPipeline {
|
||||
private static readonly UNIFORM_COUNT = 4;
|
||||
|
|
@ -224,15 +224,7 @@ export class AgentGenerationPipeline {
|
|||
? Math.floor(value)
|
||||
: 0;
|
||||
return Math.min(
|
||||
Number.isFinite(this.maxAgentCountUpperLimit)
|
||||
? this.maxAgentCountUpperLimit
|
||||
: Number.POSITIVE_INFINITY,
|
||||
Math.floor(this.device.limits.maxBufferSize / AGENT_SIZE_IN_BYTES),
|
||||
Math.floor(
|
||||
((this.device.limits as GPUSupportedLimits).maxStorageBufferBindingSize ??
|
||||
this.device.limits.maxBufferSize) / AGENT_SIZE_IN_BYTES
|
||||
),
|
||||
AGENT_MAX_DISPATCHABLE_COUNT,
|
||||
getMaxSupportedAgentCount(this.device, this.maxAgentCountUpperLimit),
|
||||
Math.max(0, requestedMaxAgentCount)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
25
src/pipelines/agents/agent-limits.ts
Normal file
25
src/pipelines/agents/agent-limits.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { AGENT_WORKGROUP_SIZE } from './agent-dispatch';
|
||||
|
||||
export const AGENT_FLOAT_COUNT = 8;
|
||||
export const AGENT_SIZE_IN_BYTES = AGENT_FLOAT_COUNT * Float32Array.BYTES_PER_ELEMENT;
|
||||
|
||||
export const getMaxSupportedAgentCount = (
|
||||
device: GPUDevice,
|
||||
maxAgentCountUpperLimit = Number.POSITIVE_INFINITY
|
||||
): number => {
|
||||
const storageBufferBindingSize =
|
||||
device.limits.maxStorageBufferBindingSize ?? device.limits.maxBufferSize;
|
||||
const upperLimit = Number.isFinite(maxAgentCountUpperLimit)
|
||||
? Math.floor(maxAgentCountUpperLimit)
|
||||
: Number.POSITIVE_INFINITY;
|
||||
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
upperLimit,
|
||||
Math.floor(device.limits.maxBufferSize / AGENT_SIZE_IN_BYTES),
|
||||
Math.floor(storageBufferBindingSize / AGENT_SIZE_IN_BYTES),
|
||||
Math.floor(device.limits.maxComputeWorkgroupsPerDimension) * AGENT_WORKGROUP_SIZE
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { createBindGroupCache3 } from '../../utils/graphics/bind-group-cache';
|
||||
import {
|
||||
createCachedFloat32BufferWrite,
|
||||
writeFloat32BufferIfChanged,
|
||||
|
|
@ -38,43 +39,91 @@ export interface AgentSettings {
|
|||
randomTimeScale: number;
|
||||
}
|
||||
|
||||
export class AgentPipeline {
|
||||
private static readonly UNIFORM_COUNT = 30;
|
||||
const UNIFORM_COUNT = 30;
|
||||
|
||||
export class AgentPipeline {
|
||||
private readonly bindGroupLayout: GPUBindGroupLayout;
|
||||
private readonly pipeline: GPUComputePipeline;
|
||||
private readonly normalPipeline: GPUComputePipeline;
|
||||
private readonly uniforms: GPUBuffer;
|
||||
private readonly uniformValues = new Float32Array(AgentPipeline.UNIFORM_COUNT);
|
||||
private readonly uniformValues = new Float32Array(UNIFORM_COUNT);
|
||||
private readonly uniformUintValues = new Uint32Array(this.uniformValues.buffer);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(
|
||||
AgentPipeline.UNIFORM_COUNT
|
||||
);
|
||||
private readonly bindGroupsByAgentsBuffer = new WeakMap<
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(UNIFORM_COUNT);
|
||||
private readonly bindGroupCache = createBindGroupCache3<
|
||||
GPUBuffer,
|
||||
WeakMap<GPUTextureView, WeakMap<GPUTextureView, GPUBindGroup>>
|
||||
>();
|
||||
GPUTextureView,
|
||||
GPUTextureView
|
||||
>((agentsBuffer, trailMapIn, trailMapOut) =>
|
||||
this.device.createBindGroup({
|
||||
layout: this.bindGroupLayout,
|
||||
entries: [
|
||||
{ binding: 0, resource: { buffer: this.uniforms } },
|
||||
{ binding: 1, resource: { buffer: agentsBuffer } },
|
||||
{ binding: 2, resource: trailMapIn },
|
||||
{ binding: 3, resource: trailMapOut },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
private agentCount = 0;
|
||||
private useIntroPipeline = true;
|
||||
|
||||
public constructor(
|
||||
private readonly device: GPUDevice,
|
||||
private readonly commonState: CommonState,
|
||||
private readonly getAgentsBuffer: () => GPUBuffer // doesn't get destroyed
|
||||
private readonly getAgentsBuffer: () => GPUBuffer
|
||||
) {
|
||||
this.bindGroupLayout = device.createBindGroupLayout(AgentPipeline.bindGroupLayout);
|
||||
this.bindGroupLayout = device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: { type: 'uniform' },
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: { type: 'storage' },
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
texture: { sampleType: 'float' },
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
storageTexture: { format: 'rgba16float' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const shaderModule = smartCompile(
|
||||
device,
|
||||
CommonState.shaderCode,
|
||||
agentSchema,
|
||||
shader
|
||||
);
|
||||
const pipelineLayout = device.createPipelineLayout({
|
||||
bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout],
|
||||
});
|
||||
this.pipeline = device.createComputePipeline({
|
||||
layout: device.createPipelineLayout({
|
||||
bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout],
|
||||
}),
|
||||
layout: pipelineLayout,
|
||||
compute: {
|
||||
module: smartCompile(device, CommonState.shaderCode, agentSchema, shader),
|
||||
module: shaderModule,
|
||||
entryPoint: 'main',
|
||||
},
|
||||
});
|
||||
this.normalPipeline = device.createComputePipeline({
|
||||
layout: pipelineLayout,
|
||||
compute: {
|
||||
module: shaderModule,
|
||||
entryPoint: 'mainNormal',
|
||||
},
|
||||
});
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
size: AgentPipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
this.uniforms = device.createBuffer({
|
||||
size: UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
}
|
||||
|
|
@ -118,6 +167,7 @@ export class AgentPipeline {
|
|||
introProgress?: number;
|
||||
}) {
|
||||
this.agentCount = agentCount;
|
||||
this.useIntroPipeline = (introProgress ?? 1) < introProgressCutoff;
|
||||
this.uniformValues[0] = moveSpeed * deltaTime;
|
||||
this.uniformValues[1] = turnSpeed * deltaTime;
|
||||
const sensorAngle = (sensorOffsetAngle * Math.PI) / 180;
|
||||
|
|
@ -160,110 +210,27 @@ export class AgentPipeline {
|
|||
public execute(
|
||||
commandEncoder: GPUCommandEncoder,
|
||||
trailMapIn: GPUTextureView,
|
||||
trailMapOut: GPUTextureView
|
||||
trailMapOut: GPUTextureView,
|
||||
timestampWrites?: GPUComputePassTimestampWrites
|
||||
) {
|
||||
if (this.agentCount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bindGroup = this.getBindGroup(trailMapIn, trailMapOut);
|
||||
|
||||
const passEncoder = commandEncoder.beginComputePass();
|
||||
passEncoder.setPipeline(this.pipeline);
|
||||
const passEncoder = commandEncoder.beginComputePass(
|
||||
timestampWrites ? { timestampWrites } : undefined
|
||||
);
|
||||
passEncoder.setPipeline(this.useIntroPipeline ? this.pipeline : this.normalPipeline);
|
||||
this.commonState.execute(passEncoder);
|
||||
passEncoder.setBindGroup(1, bindGroup);
|
||||
passEncoder.setBindGroup(
|
||||
1,
|
||||
this.bindGroupCache(this.getAgentsBuffer(), trailMapIn, trailMapOut)
|
||||
);
|
||||
dispatchAgentWorkgroups(passEncoder, this.agentCount);
|
||||
passEncoder.end();
|
||||
}
|
||||
|
||||
private getBindGroup(
|
||||
trailMapIn: GPUTextureView,
|
||||
trailMapOut: GPUTextureView
|
||||
): GPUBindGroup {
|
||||
const agentsBuffer = this.getAgentsBuffer();
|
||||
let textureCache = this.bindGroupsByAgentsBuffer.get(agentsBuffer);
|
||||
if (!textureCache) {
|
||||
textureCache = new WeakMap<GPUTextureView, WeakMap<GPUTextureView, GPUBindGroup>>();
|
||||
this.bindGroupsByAgentsBuffer.set(agentsBuffer, textureCache);
|
||||
}
|
||||
|
||||
let outputCache = textureCache.get(trailMapIn);
|
||||
if (!outputCache) {
|
||||
outputCache = new WeakMap<GPUTextureView, GPUBindGroup>();
|
||||
textureCache.set(trailMapIn, outputCache);
|
||||
}
|
||||
|
||||
const cached = outputCache.get(trailMapOut);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const bindGroup = this.device.createBindGroup({
|
||||
layout: this.bindGroupLayout,
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: this.uniforms,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: agentsBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
resource: trailMapIn,
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
resource: trailMapOut,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
outputCache.set(trailMapOut, bindGroup);
|
||||
return bindGroup;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.uniforms.destroy();
|
||||
}
|
||||
|
||||
private static get bindGroupLayout(): GPUBindGroupLayoutDescriptor {
|
||||
return {
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'uniform',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'storage',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
texture: {
|
||||
sampleType: 'float',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
storageTexture: {
|
||||
format: 'rgba16float',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
const PI: f32 = 3.14159265359;
|
||||
|
||||
struct Settings {
|
||||
moveRate: f32,
|
||||
turnRate: f32,
|
||||
|
|
@ -142,7 +144,80 @@ fn main(
|
|||
|
||||
let nextPosition = clamp(position + step, vec2<f32>(0, 0), maxPosition);
|
||||
if nextPosition.x == 0 || nextPosition.x == maxPosition.x || nextPosition.y == 0 || nextPosition.y == maxPosition.y {
|
||||
rotation = 3.14159265359 + random_float(randomSeed + 22695477u) - 0.5;
|
||||
rotation = PI + random_float(randomSeed + 22695477u) - 0.5;
|
||||
}
|
||||
|
||||
var trailBelow = textureLoad(trailMapIn, vec2<i32>(nextPosition), 0);
|
||||
trailBelow = vec4<f32>(
|
||||
trailBelow.rgb + channelMask * settings.individualTrailWeight,
|
||||
max(trailBelow.a, 0.0)
|
||||
);
|
||||
|
||||
textureStore(trailMapOut, vec2<i32>(nextPosition), trailBelow);
|
||||
agents[id].angle = angle + rotation;
|
||||
agents[id].position = nextPosition;
|
||||
}
|
||||
|
||||
@compute @workgroup_size(64)
|
||||
fn mainNormal(
|
||||
@builtin(global_invocation_id) global_id: vec3<u32>
|
||||
) {
|
||||
let id = get_id(global_id);
|
||||
|
||||
if id >= settings.agentCount {
|
||||
return;
|
||||
}
|
||||
|
||||
let colorIndex = agents[id].colorIndex;
|
||||
if colorIndex < 0.0 || colorIndex >= 2.5 {
|
||||
return;
|
||||
}
|
||||
|
||||
var position = agents[id].position;
|
||||
var angle = agents[id].angle;
|
||||
let channelMask = get_channel_mask(colorIndex);
|
||||
let reactionMask = get_reaction_mask(colorIndex);
|
||||
let randomSeed = random_seed(id);
|
||||
let maxPosition = state.size - vec2<f32>(1.0, 1.0);
|
||||
let randomTurn = random_float(randomSeed);
|
||||
let direction = vec2(cos(angle), sin(angle));
|
||||
|
||||
let forwardSensor = sensor_position(position, direction, settings.sensorOffset, maxPosition);
|
||||
let leftSensor = sensor_position(
|
||||
position,
|
||||
rotate_direction(direction, settings.sensorAngleSin, settings.sensorAngleCos),
|
||||
settings.sensorOffset,
|
||||
maxPosition
|
||||
);
|
||||
let rightSensor = sensor_position(
|
||||
position,
|
||||
rotate_direction(direction, -settings.sensorAngleSin, settings.sensorAngleCos),
|
||||
settings.sensorOffset,
|
||||
maxPosition
|
||||
);
|
||||
|
||||
let trailForward = textureLoad(trailMapIn, forwardSensor, 0);
|
||||
let trailLeft = textureLoad(trailMapIn, leftSensor, 0);
|
||||
let trailRight = textureLoad(trailMapIn, rightSensor, 0);
|
||||
|
||||
let weightForward = dot(trailForward.rgb, reactionMask);
|
||||
let weightLeft = dot(trailLeft.rgb, reactionMask);
|
||||
let weightRight = dot(trailRight.rgb, reactionMask);
|
||||
|
||||
var rotation = (randomTurn - 0.5) * settings.turnWhenLost;
|
||||
if weightForward >= weightLeft && weightForward >= weightRight {
|
||||
rotation = rotation * settings.forwardRotationScale;
|
||||
} else {
|
||||
rotation += sign(weightLeft - weightRight) * settings.turnRate;
|
||||
}
|
||||
|
||||
let nextPosition = clamp(
|
||||
position + direction * settings.moveRate,
|
||||
vec2<f32>(0, 0),
|
||||
maxPosition
|
||||
);
|
||||
if nextPosition.x == 0 || nextPosition.x == maxPosition.x || nextPosition.y == 0 || nextPosition.y == maxPosition.y {
|
||||
rotation = PI + random_float(randomSeed + 22695477u) - 0.5;
|
||||
}
|
||||
|
||||
var trailBelow = textureLoad(trailMapIn, vec2<i32>(nextPosition), 0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue