More clean up

This commit is contained in:
Andras Schmelczer 2026-05-20 21:03:41 +01:00
commit f03da42b5e
43 changed files with 818 additions and 1076 deletions

View file

@ -1,28 +1,19 @@
export const AGENT_WORKGROUP_SIZE = 64;
export const AGENT_MAX_DISPATCH_WORKGROUPS_PER_DIMENSION = 65_535;
export const AGENT_MAX_DISPATCHABLE_COUNT = 0xffffffff;
const AGENT_WORKGROUP_SIZE = 64;
const AGENT_MAX_DISPATCH_WORKGROUPS_PER_DIMENSION = 65_535;
export const AGENT_MAX_DISPATCHABLE_COUNT =
AGENT_MAX_DISPATCH_WORKGROUPS_PER_DIMENSION * AGENT_WORKGROUP_SIZE;
export const getAgentDispatchWorkgroups = (agentCount: number): [number, number] => {
const getAgentDispatchWorkgroups = (agentCount: number): [number, number] => {
if (!Number.isFinite(agentCount) || agentCount <= 0) {
throw new Error('Agent count must be a positive finite number');
}
const workgroupCount = Math.ceil(agentCount / AGENT_WORKGROUP_SIZE);
if (workgroupCount <= AGENT_MAX_DISPATCH_WORKGROUPS_PER_DIMENSION) {
return [workgroupCount, 1];
}
const workgroupX = Math.min(
Math.ceil(Math.sqrt(workgroupCount)),
AGENT_MAX_DISPATCH_WORKGROUPS_PER_DIMENSION
);
const workgroupY = Math.ceil(workgroupCount / workgroupX);
if (workgroupY > AGENT_MAX_DISPATCH_WORKGROUPS_PER_DIMENSION) {
if (workgroupCount > AGENT_MAX_DISPATCH_WORKGROUPS_PER_DIMENSION) {
throw new Error('Agent count exceeds dispatchable workgroup range');
}
return [workgroupX, workgroupY];
return [workgroupCount, 1];
};
export const dispatchAgentWorkgroups = (

View file

@ -9,6 +9,8 @@ struct Counters {
aliveAgentCount: atomic<u32>,
};
const clearCompactedTailStride = 4u;
@group(1) @binding(0) var<uniform> settings: Settings;
@group(1) @binding(2) var<storage, read_write> counters: Counters;
@group(1) @binding(3) var<storage, read_write> compactedAgents: array<Agent>;
@ -20,10 +22,9 @@ var<workgroup> clearAliveAgentCount: u32;
@compute @workgroup_size(64)
fn main(
@builtin(global_invocation_id) global_id: vec3<u32>,
@builtin(local_invocation_id) local_id: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>
@builtin(local_invocation_id) local_id: vec3<u32>
) {
let id = get_id(global_id, num_workgroups);
let id = get_id(global_id);
if local_id.x == 0u {
atomicStore(&workgroupAliveCount, 0u);
@ -63,10 +64,9 @@ fn main(
@compute @workgroup_size(64)
fn clearCompactedTail(
@builtin(global_invocation_id) global_id: vec3<u32>,
@builtin(local_invocation_id) local_id: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>
@builtin(local_invocation_id) local_id: vec3<u32>
) {
let id = get_id(global_id, num_workgroups);
let id = get_id(global_id);
if local_id.x == 0u {
clearAliveAgentCount = atomicLoad(&counters.aliveAgentCount);
@ -74,11 +74,11 @@ fn clearCompactedTail(
workgroupBarrier();
if id >= settings.agentCount {
return;
}
if id >= clearAliveAgentCount {
compactedAgents[id].colorIndex = -1.0;
let firstClearId = clearAliveAgentCount + id * clearCompactedTailStride;
for (var offset = 0u; offset < clearCompactedTailStride; offset += 1u) {
let clearId = firstClearId + offset;
if clearId < settings.agentCount {
compactedAgents[clearId].colorIndex = -1.0;
}
}
}

View file

@ -1,21 +1,19 @@
import { vec2 } from 'gl-matrix';
import { smartCompile } from '../../../utils/graphics/smart-compile';
import {
AGENT_MAX_DISPATCHABLE_COUNT,
dispatchAgentWorkgroups,
} from '../agent-dispatch';
import { AGENT_MAX_DISPATCHABLE_COUNT, dispatchAgentWorkgroups } from '../agent-dispatch';
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;
export const AGENT_SIZE_IN_BYTES =
AGENT_FLOAT_COUNT * Float32Array.BYTES_PER_ELEMENT;
const AGENT_SIZE_IN_BYTES = AGENT_FLOAT_COUNT * Float32Array.BYTES_PER_ELEMENT;
export class AgentGenerationPipeline {
private static readonly UNIFORM_COUNT = 4;
private static readonly COUNTER_COUNT = 1;
private static readonly CLEAR_COMPACTED_TAIL_STRIDE = 4;
private static readonly ALLOCATION_GROWTH_FACTOR = 1.25;
private readonly bindGroupLayout: GPUBindGroupLayout;
private readonly uniforms: GPUBuffer;
@ -33,21 +31,14 @@ export class AgentGenerationPipeline {
private allocatedMaxAgentCount: number;
private readonly countersBuffer: GPUBuffer;
private readonly countersStagingBuffer: GPUBuffer;
private readonly counterClearValues = new Uint32Array(
AgentGenerationPipeline.COUNTER_COUNT
);
private readonly agentCountUniformValues = new Uint32Array(
AgentGenerationPipeline.UNIFORM_COUNT
);
private readonly resizeUniformBuffer = new ArrayBuffer(
AgentGenerationPipeline.UNIFORM_COUNT * Uint32Array.BYTES_PER_ELEMENT
);
private readonly resizeUniformFloatValues = new Float32Array(
this.resizeUniformBuffer
);
private readonly resizeUniformUintValues = new Uint32Array(
this.resizeUniformBuffer
);
private readonly resizeUniformFloatValues = new Float32Array(this.resizeUniformBuffer);
private readonly resizeUniformUintValues = new Uint32Array(this.resizeUniformBuffer);
public constructor(
private readonly device: GPUDevice,
@ -173,11 +164,19 @@ export class AgentGenerationPipeline {
requestedMaxAgentCount: number,
activeAgentCount: number
): number {
const nextMaxAgentCount = this.clampMaxAgentCount(requestedMaxAgentCount);
if (nextMaxAgentCount <= this.allocatedMaxAgentCount) {
const requestedClampedMaxAgentCount = this.clampMaxAgentCount(requestedMaxAgentCount);
if (requestedClampedMaxAgentCount <= this.allocatedMaxAgentCount) {
return this.allocatedMaxAgentCount;
}
const nextMaxAgentCount = this.clampMaxAgentCount(
Math.max(
requestedClampedMaxAgentCount,
Math.ceil(
this.allocatedMaxAgentCount * AgentGenerationPipeline.ALLOCATION_GROWTH_FACTOR
)
)
);
const previousActiveAgentsBuffer = this.activeAgentsBuffer;
const previousMaxAgentCount = this.allocatedMaxAgentCount;
this.allocatedMaxAgentCount = nextMaxAgentCount;
@ -270,16 +269,19 @@ export class AgentGenerationPipeline {
this.inactiveAgentsBuffer = this.createAgentsBuffer();
this.agentCountUniformValues[0] = agentCount;
this.device.queue.writeBuffer(this.countersBuffer, 0, this.counterClearValues);
this.device.queue.writeBuffer(this.uniforms, 0, this.agentCountUniformValues);
const commandEncoder = this.device.createCommandEncoder();
commandEncoder.clearBuffer(this.countersBuffer, 0, Uint32Array.BYTES_PER_ELEMENT);
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(this.compactionPipeline);
passEncoder.setBindGroup(1, this.getBindGroup());
dispatchAgentWorkgroups(passEncoder, agentCount);
passEncoder.setPipeline(this.clearCompactedTailPipeline);
dispatchAgentWorkgroups(passEncoder, agentCount);
dispatchAgentWorkgroups(
passEncoder,
Math.ceil(agentCount / AgentGenerationPipeline.CLEAR_COMPACTED_TAIL_STRIDE)
);
passEncoder.end();
commandEncoder.copyBufferToBuffer(

View file

@ -8,10 +8,9 @@ struct ResizeSettings {
@compute @workgroup_size(64)
fn main(
@builtin(global_invocation_id) global_id: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>
@builtin(global_invocation_id) global_id: vec3<u32>
) {
let id = get_id(global_id, num_workgroups);
let id = get_id(global_id);
if id >= resizeSettings.agentCount {
return;

View file

@ -11,6 +11,6 @@ struct Agent {
const agentWorkgroupSize = 64u;
fn get_id(global_id: vec3<u32>, num_workgroups: vec3<u32>) -> u32 {
return global_id.x + global_id.y * num_workgroups.x * agentWorkgroupSize;
fn get_id(global_id: vec3<u32>) -> u32 {
return global_id.x;
}

View file

@ -79,10 +79,12 @@ export class AgentPipeline {
introNearMoveMultiplier,
introStepStopDistance,
randomTimeScale,
time,
agentCount,
introProgress,
}: AgentSettings & {
deltaTime: number;
time: number;
agentCount: number;
introProgress?: number;
}) {
@ -117,7 +119,7 @@ export class AgentPipeline {
this.uniformValues[26] = introFarMoveMultiplier;
this.uniformValues[27] = introNearMoveMultiplier;
this.uniformValues[28] = introStepStopDistance;
this.uniformValues[29] = randomTimeScale;
this.uniformUintValues[29] = Math.max(0, Math.floor(time * randomTimeScale)) >>> 0;
writeFloat32BufferIfChanged(
this.device,
this.uniforms,

View file

@ -28,7 +28,7 @@ struct Settings {
introFarMoveMultiplier: f32,
introNearMoveMultiplier: f32,
introStepStopDistance: f32,
randomTimeScale: f32,
randomTimeSeed: u32,
};
@group(1) @binding(0) var<uniform> settings: Settings;
@ -37,10 +37,9 @@ struct Settings {
@compute @workgroup_size(64)
fn main(
@builtin(global_invocation_id) global_id: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>
@builtin(global_invocation_id) global_id: vec3<u32>
) {
let id = get_id(global_id, num_workgroups);
let id = get_id(global_id);
if id >= settings.agentCount {
return;
@ -65,7 +64,8 @@ fn main(
let channelMask = get_channel_mask(colorIndex);
let reactionMask = get_reaction_mask(colorIndex);
let randomSeed = random_seed(id, state.time);
let randomSeed = random_seed(id);
let maxPosition = state.size - vec2<f32>(1.0, 1.0);
var rotation = 0.0;
var step = vec2<f32>(0.0, 0.0);
@ -108,16 +108,18 @@ fn main(
let randomTurn = random_float(randomSeed);
let direction = vec2(cos(angle), sin(angle));
let forwardSensor = sensor_position(position, direction, settings.sensorOffset);
let forwardSensor = sensor_position(position, direction, settings.sensorOffset, maxPosition);
let leftSensor = sensor_position(
position,
rotate_direction(direction, settings.sensorAngleSin, settings.sensorAngleCos),
settings.sensorOffset
settings.sensorOffset,
maxPosition
);
let rightSensor = sensor_position(
position,
rotate_direction(direction, -settings.sensorAngleSin, settings.sensorAngleCos),
settings.sensorOffset
settings.sensorOffset,
maxPosition
);
let trailForward = textureLoad(trailMapIn, forwardSensor, 0);
@ -138,7 +140,6 @@ fn main(
step = direction * settings.moveRate;
}
let maxPosition = state.size - vec2<f32>(1.0, 1.0);
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;
@ -155,11 +156,16 @@ fn main(
agents[id].position = nextPosition;
}
fn sensor_position(agentPosition: vec2<f32>, direction: vec2<f32>, sensorOffset: f32) -> vec2<i32> {
fn sensor_position(
agentPosition: vec2<f32>,
direction: vec2<f32>,
sensorOffset: f32,
maxPosition: vec2<f32>
) -> vec2<i32> {
return vec2<i32>(clamp(
agentPosition + direction * sensorOffset,
vec2<f32>(0, 0),
state.size - vec2<f32>(1, 1)
maxPosition
));
}
@ -212,9 +218,8 @@ fn angle_delta(sourceAngle: f32, targetAngle: f32) -> f32 {
return atan2(sin(targetAngle - sourceAngle), cos(targetAngle - sourceAngle));
}
fn random_seed(id: u32, time: f32) -> u32 {
let timeSeed = u32(time * settings.randomTimeScale);
return id * 747796405u + timeSeed * 2891336453u;
fn random_seed(id: u32) -> u32 {
return id * 747796405u + settings.randomTimeSeed * 2891336453u;
}
fn random_float(seed: u32) -> f32 {