More clean up
This commit is contained in:
parent
c94ffcc506
commit
f03da42b5e
43 changed files with 818 additions and 1076 deletions
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const getSafePixelRatio = (pixelRatio: number | undefined): number =>
|
|||
? pixelRatio
|
||||
: 1;
|
||||
|
||||
export const setBrushUniformValues = (
|
||||
const setBrushUniformValues = (
|
||||
target: Float32Array,
|
||||
{
|
||||
brushSize,
|
||||
|
|
@ -105,7 +105,7 @@ export class BrushPipeline {
|
|||
});
|
||||
|
||||
const shaderModule = smartCompile(device, CommonState.shaderCode, shader);
|
||||
this.multiTargetPipeline = this.createPipeline(shaderModule, 'fragmentMrt', 2);
|
||||
this.multiTargetPipeline = this.createPipeline(shaderModule, 'fragmentMrt', 1);
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
size: BrushPipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
|
|
@ -209,12 +209,10 @@ export class BrushPipeline {
|
|||
|
||||
public executeMultiTarget(
|
||||
commandEncoder: GPUCommandEncoder,
|
||||
sourceMapOut: GPUTextureView,
|
||||
influenceMapOut: GPUTextureView
|
||||
): void {
|
||||
this.executeWithPipeline(commandEncoder, this.multiTargetPipeline, [
|
||||
sourceMapOut: GPUTextureView
|
||||
): boolean {
|
||||
return this.executeWithPipeline(commandEncoder, this.multiTargetPipeline, [
|
||||
sourceMapOut,
|
||||
influenceMapOut,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -222,9 +220,9 @@ export class BrushPipeline {
|
|||
commandEncoder: GPUCommandEncoder,
|
||||
pipeline: GPURenderPipeline,
|
||||
textureViews: Array<GPUTextureView>
|
||||
): void {
|
||||
): boolean {
|
||||
if (this.lineCount === 0) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const renderPassDescriptor: GPURenderPassDescriptor = {
|
||||
|
|
@ -242,6 +240,7 @@ export class BrushPipeline {
|
|||
passEncoder.setVertexBuffer(0, this.vertexBuffer);
|
||||
passEncoder.draw(BrushPipeline.VERTICES_PER_LINE_SEGMENT, this.lineCount);
|
||||
passEncoder.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ struct VertexOutput {
|
|||
|
||||
struct BrushTargets {
|
||||
@location(0) source: vec4<f32>,
|
||||
@location(1) influence: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
|
|
@ -53,6 +52,22 @@ fn fragmentMrt(
|
|||
@location(2) @interpolate(flat) direction: vec2<f32>,
|
||||
@location(3) @interpolate(flat) inverseLengthSquared: f32
|
||||
) -> BrushTargets {
|
||||
let strength = brushStrength(screenPosition, start, direction, inverseLengthSquared);
|
||||
|
||||
if(strength < settings.brushDiscardThreshold) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let color = brushOutput(strength);
|
||||
return BrushTargets(color);
|
||||
}
|
||||
|
||||
fn brushStrength(
|
||||
screenPosition: vec2<f32>,
|
||||
start: vec2<f32>,
|
||||
direction: vec2<f32>,
|
||||
inverseLengthSquared: f32
|
||||
) -> f32 {
|
||||
let distanceSquared = distanceSquaredFromLine(
|
||||
screenPosition,
|
||||
start,
|
||||
|
|
@ -60,24 +75,9 @@ fn fragmentMrt(
|
|||
inverseLengthSquared
|
||||
);
|
||||
if distanceSquared > settings.brushGeometryRadiusSquared {
|
||||
discard;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let strength = brushStrength(screenPosition, distanceSquared);
|
||||
|
||||
if(strength < settings.brushDiscardThreshold) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let color = brushOutput(strength);
|
||||
return BrushTargets(color, color);
|
||||
}
|
||||
|
||||
fn brushStrength(
|
||||
screenPosition: vec2<f32>,
|
||||
distanceSquared: f32
|
||||
) -> f32 {
|
||||
let distance = sqrt(distanceSquared);
|
||||
let coarseNoise = textureSampleLevel(
|
||||
noise,
|
||||
noiseSampler,
|
||||
|
|
@ -85,7 +85,7 @@ fn brushStrength(
|
|||
0.0
|
||||
).r;
|
||||
let radius = settings.brushSize + (coarseNoise - 0.5) * settings.brushSizeVariation;
|
||||
let edge = 1.0 - step(radius, distance);
|
||||
let edge = 1.0 - step(radius * radius, distanceSquared);
|
||||
if edge * max(settings.brushGrainMinStrength, settings.brushGrainMaxStrength) < settings.brushDiscardThreshold {
|
||||
return 0.0;
|
||||
}
|
||||
|
|
@ -112,10 +112,6 @@ fn distanceSquaredFromLine(
|
|||
) -> f32 {
|
||||
let pa = position - start;
|
||||
|
||||
if inverseLengthSquared <= 0.0 {
|
||||
return dot(pa, pa);
|
||||
}
|
||||
|
||||
let q = clamp(dot(pa, direction) * inverseLengthSquared, 0, 1);
|
||||
let nearestOffset = pa - direction * q;
|
||||
return dot(nearestOffset, nearestOffset);
|
||||
|
|
@ -140,14 +136,13 @@ fn segment_vertex_position(
|
|||
}
|
||||
|
||||
fn segment_vertex_corner(index: u32) -> vec2<f32> {
|
||||
if index == 0u {
|
||||
return vec2<f32>(-1.0, 1.0);
|
||||
}
|
||||
if index == 1u || index == 3u {
|
||||
return vec2<f32>(-1.0, -1.0);
|
||||
}
|
||||
if index == 2u || index == 4u {
|
||||
return vec2<f32>(1.0, 1.0);
|
||||
}
|
||||
return vec2<f32>(1.0, -1.0);
|
||||
let corners = array<vec2<f32>, 6>(
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(1.0, -1.0),
|
||||
);
|
||||
return corners[index];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const TILE_TEXEL_COUNT = TILE_SIZE_X * TILE_SIZE_Y;
|
|||
@group(0) @binding(2) var trailMapOut: texture_storage_2d<rgba16float, write>;
|
||||
|
||||
var<workgroup> tile: array<vec4<f32>, 324>;
|
||||
var<workgroup> tileTrailStrength: array<f32, 324>;
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn main(
|
||||
|
|
@ -28,31 +29,32 @@ fn main(
|
|||
@builtin(workgroup_id) workgroup_id: vec3<u32>
|
||||
) {
|
||||
let textureSize = vec2<i32>(textureDimensions(trailMap, 0));
|
||||
let textureSizeU32 = vec2<u32>(textureSize);
|
||||
let localLinearIndex = local_id.y * WORKGROUP_SIZE_X + local_id.x;
|
||||
var tileIndex = localLinearIndex;
|
||||
|
||||
loop {
|
||||
if tileIndex >= TILE_TEXEL_COUNT {
|
||||
break;
|
||||
}
|
||||
let workgroupOrigin = workgroup_id.xy * vec2<u32>(WORKGROUP_SIZE_X, WORKGROUP_SIZE_Y);
|
||||
let isInteriorTile =
|
||||
workgroupOrigin.x > 0u &&
|
||||
workgroupOrigin.y > 0u &&
|
||||
workgroupOrigin.x + WORKGROUP_SIZE_X < textureSizeU32.x &&
|
||||
workgroupOrigin.y + WORKGROUP_SIZE_Y < textureSizeU32.y;
|
||||
|
||||
for (var tileIndex = localLinearIndex; tileIndex < TILE_TEXEL_COUNT; tileIndex += WORKGROUP_SIZE_X * WORKGROUP_SIZE_Y) {
|
||||
let tilePosition = vec2<u32>(tileIndex % TILE_SIZE_X, tileIndex / TILE_SIZE_X);
|
||||
let sourcePixelU32 =
|
||||
workgroup_id.xy * vec2<u32>(WORKGROUP_SIZE_X, WORKGROUP_SIZE_Y) +
|
||||
tilePosition;
|
||||
let sourcePixel = clamp(
|
||||
vec2<i32>(i32(sourcePixelU32.x), i32(sourcePixelU32.y)) - vec2<i32>(1, 1),
|
||||
vec2<i32>(0, 0),
|
||||
textureSize - vec2<i32>(1, 1)
|
||||
);
|
||||
tile[tileIndex] = textureLoad(trailMap, sourcePixel, 0);
|
||||
tileIndex += WORKGROUP_SIZE_X * WORKGROUP_SIZE_Y;
|
||||
let unclampedSourcePixel = vec2<i32>(workgroupOrigin + tilePosition) - vec2<i32>(1, 1);
|
||||
var sourcePixel = unclampedSourcePixel;
|
||||
if !isInteriorTile {
|
||||
sourcePixel = clamp(unclampedSourcePixel, vec2<i32>(0, 0), textureSize - vec2<i32>(1, 1));
|
||||
}
|
||||
let texel = textureLoad(trailMap, sourcePixel, 0);
|
||||
tile[tileIndex] = texel;
|
||||
tileTrailStrength[tileIndex] = length(texel.rgb);
|
||||
}
|
||||
|
||||
workgroupBarrier();
|
||||
|
||||
let pixel = vec2<i32>(i32(global_id.x), i32(global_id.y));
|
||||
if pixel.x >= textureSize.x || pixel.y >= textureSize.y {
|
||||
let inBounds = pixel.x < textureSize.x && pixel.y < textureSize.y;
|
||||
if !inBounds {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -110,11 +112,12 @@ fn propagate(
|
|||
brushWeight: f32
|
||||
) -> vec4<f32> {
|
||||
let neighbourIndex = i32(centerTileIndex) + offsetY * i32(TILE_SIZE_X) + offsetX;
|
||||
let neighbour = tile[u32(neighbourIndex)];
|
||||
let neighbourTileIndex = u32(neighbourIndex);
|
||||
let neighbour = tile[neighbourTileIndex];
|
||||
let difference = clamp(neighbour - currentColor, vec4(0), vec4(1));
|
||||
|
||||
return vec4(
|
||||
vec3(length(neighbour.rgb) * trailWeight),
|
||||
vec3(tileTrailStrength[neighbourTileIndex] * trailWeight),
|
||||
neighbour.a * brushWeight
|
||||
) * difference;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ type DiffusionUniformSettings = Pick<
|
|||
| 'brushDecayAlphaOffset'
|
||||
>;
|
||||
|
||||
export const getSafeInverseDiffusionRate = (diffusionRate: number): number =>
|
||||
const getSafeInverseDiffusionRate = (diffusionRate: number): number =>
|
||||
1 /
|
||||
(Number.isFinite(diffusionRate) &&
|
||||
diffusionRate > appConfig.pipelines.diffusion.minDiffusionRate
|
||||
? diffusionRate
|
||||
: appConfig.pipelines.diffusion.minDiffusionRate);
|
||||
|
||||
export const setDiffusionUniformValues = (
|
||||
const setDiffusionUniformValues = (
|
||||
target: Float32Array,
|
||||
{
|
||||
diffusionRateTrails,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
import {
|
||||
createCachedFloat32BufferWrite,
|
||||
writeFloat32BufferIfChanged,
|
||||
|
|
@ -86,9 +88,11 @@ export class EraserAgentPipeline {
|
|||
public setParameters({
|
||||
agentCount,
|
||||
eraserMaskAlphaThreshold,
|
||||
maskSize,
|
||||
}: {
|
||||
agentCount: number;
|
||||
eraserMaskAlphaThreshold: number;
|
||||
maskSize: vec2;
|
||||
}): void {
|
||||
this.agentCount = agentCount;
|
||||
this.activeSegmentCount = this.pendingSegmentCount;
|
||||
|
|
@ -96,8 +100,8 @@ export class EraserAgentPipeline {
|
|||
|
||||
this.uniformUintValues[0] = Math.max(0, Math.floor(agentCount));
|
||||
this.uniformValues[1] = eraserMaskAlphaThreshold;
|
||||
this.uniformValues[2] = 0;
|
||||
this.uniformValues[3] = 0;
|
||||
this.uniformUintValues[2] = Math.max(0, Math.floor(maskSize[0]));
|
||||
this.uniformUintValues[3] = Math.max(0, Math.floor(maskSize[1]));
|
||||
writeFloat32BufferIfChanged(
|
||||
this.device,
|
||||
this.uniforms,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
struct Settings {
|
||||
agentCount: u32,
|
||||
eraserMaskAlphaThreshold: f32,
|
||||
padding1: f32,
|
||||
padding2: f32,
|
||||
maskWidth: u32,
|
||||
maskHeight: u32,
|
||||
};
|
||||
|
||||
@group(1) @binding(0) var<uniform> settings: Settings;
|
||||
|
|
@ -10,10 +10,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;
|
||||
|
|
@ -24,7 +23,7 @@ fn main(
|
|||
return;
|
||||
}
|
||||
|
||||
let maskSize = vec2<i32>(textureDimensions(eraserMask));
|
||||
let maskSize = vec2<i32>(i32(settings.maskWidth), i32(settings.maskHeight));
|
||||
let maskPosition = clamp(
|
||||
vec2<i32>(agents[id].position),
|
||||
vec2<i32>(0, 0),
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ export class EraserTexturePipeline {
|
|||
'r8unorm',
|
||||
'rgba16float',
|
||||
'rgba16float',
|
||||
'rgba16float',
|
||||
]);
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
|
|
@ -169,7 +168,6 @@ export class EraserTexturePipeline {
|
|||
commandEncoder: GPUCommandEncoder,
|
||||
eraserMaskOut: GPUTextureView,
|
||||
sourceMapOut: GPUTextureView,
|
||||
influenceMapOut: GPUTextureView,
|
||||
trailMapOut: GPUTextureView
|
||||
): void {
|
||||
if (this.lineCount === 0) {
|
||||
|
|
@ -200,11 +198,6 @@ export class EraserTexturePipeline {
|
|||
loadOp: 'load',
|
||||
storeOp: 'store',
|
||||
},
|
||||
{
|
||||
view: influenceMapOut,
|
||||
loadOp: 'load',
|
||||
storeOp: 'store',
|
||||
},
|
||||
{
|
||||
view: trailMapOut,
|
||||
loadOp: 'load',
|
||||
|
|
|
|||
|
|
@ -19,17 +19,10 @@ struct VertexOutput {
|
|||
@location(3) @interpolate(flat) inverseLengthSquared: f32,
|
||||
}
|
||||
|
||||
struct EraserTextureTargets {
|
||||
@location(0) source: vec4<f32>,
|
||||
@location(1) influence: vec4<f32>,
|
||||
@location(2) trail: vec4<f32>,
|
||||
}
|
||||
|
||||
struct EraserCombinedTargets {
|
||||
@location(0) mask: vec4<f32>,
|
||||
@location(1) source: vec4<f32>,
|
||||
@location(2) influence: vec4<f32>,
|
||||
@location(3) trail: vec4<f32>,
|
||||
@location(2) trail: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
|
|
@ -50,35 +43,6 @@ fn vertex(
|
|||
return VertexOutput(vec4(position, 0.0, 1.0), screenPosition, start, direction, inverseLengthSquared);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) @interpolate(flat) start: vec2<f32>,
|
||||
@location(2) @interpolate(flat) direction: vec2<f32>,
|
||||
@location(3) @interpolate(flat) inverseLengthSquared: f32
|
||||
) -> @location(0) vec4<f32> {
|
||||
if shouldDiscardEraserFragment(screenPosition, start, direction, inverseLengthSquared) {
|
||||
discard;
|
||||
}
|
||||
|
||||
return getEraserClearValue();
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentMrt(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) @interpolate(flat) start: vec2<f32>,
|
||||
@location(2) @interpolate(flat) direction: vec2<f32>,
|
||||
@location(3) @interpolate(flat) inverseLengthSquared: f32
|
||||
) -> EraserTextureTargets {
|
||||
if shouldDiscardEraserFragment(screenPosition, start, direction, inverseLengthSquared) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let cleared = getEraserClearValue();
|
||||
return EraserTextureTargets(cleared, cleared, cleared);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentCombined(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
|
|
@ -91,7 +55,7 @@ fn fragmentCombined(
|
|||
}
|
||||
|
||||
let cleared = getEraserClearValue();
|
||||
return EraserCombinedTargets(getEraserMaskValue(), cleared, cleared, cleared);
|
||||
return EraserCombinedTargets(getEraserMaskValue(), cleared, cleared);
|
||||
}
|
||||
|
||||
fn getEraserMaskValue() -> vec4<f32> {
|
||||
|
|
@ -124,10 +88,6 @@ fn distanceSquaredFromLine(
|
|||
) -> f32 {
|
||||
let pa = position - start;
|
||||
|
||||
if inverseLengthSquared <= 0.0 {
|
||||
return dot(pa, pa);
|
||||
}
|
||||
|
||||
let q = clamp(dot(pa, direction) * inverseLengthSquared, 0.0, 1.0);
|
||||
let nearestOffset = pa - direction * q;
|
||||
return dot(nearestOffset, nearestOffset);
|
||||
|
|
@ -152,14 +112,13 @@ fn segment_vertex_position(
|
|||
}
|
||||
|
||||
fn segment_vertex_corner(index: u32) -> vec2<f32> {
|
||||
if index == 0u {
|
||||
return vec2<f32>(-1.0, 1.0);
|
||||
}
|
||||
if index == 1u || index == 3u {
|
||||
return vec2<f32>(-1.0, -1.0);
|
||||
}
|
||||
if index == 2u || index == 4u {
|
||||
return vec2<f32>(1.0, 1.0);
|
||||
}
|
||||
return vec2<f32>(1.0, -1.0);
|
||||
let corners = array<vec2<f32>, 6>(
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(1.0, -1.0),
|
||||
);
|
||||
return corners[index];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export class RenderPipeline {
|
|||
|
||||
private readonly bindGroupLayout: GPUBindGroupLayout;
|
||||
private readonly pipeline: GPURenderPipeline;
|
||||
private readonly noSourcePipeline: GPURenderPipeline;
|
||||
private readonly uniforms: GPUBuffer;
|
||||
private readonly uniformValues = new Float32Array(RenderPipeline.UNIFORM_COUNT);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(
|
||||
|
|
@ -36,6 +37,7 @@ export class RenderPipeline {
|
|||
|
||||
const format = navigator.gpu.getPreferredCanvasFormat();
|
||||
this.pipeline = this.createPipeline(format, vertex, 'fragment');
|
||||
this.noSourcePipeline = this.createPipeline(format, vertex, 'fragmentNoSource');
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
size: RenderPipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
|
|
@ -112,7 +114,8 @@ export class RenderPipeline {
|
|||
public execute(
|
||||
commandEncoder: GPUCommandEncoder,
|
||||
colorTexture: GPUTextureView,
|
||||
sourceTexture: GPUTextureView
|
||||
sourceTexture: GPUTextureView,
|
||||
useSourceTexture = true
|
||||
): GPUTexture {
|
||||
const bindGroup = this.getBindGroup(colorTexture, sourceTexture);
|
||||
const canvasTexture = this.context.getCurrentTexture();
|
||||
|
|
@ -128,7 +131,7 @@ export class RenderPipeline {
|
|||
],
|
||||
};
|
||||
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
|
||||
passEncoder.setPipeline(this.pipeline);
|
||||
passEncoder.setPipeline(useSourceTexture ? this.pipeline : this.noSourcePipeline);
|
||||
this.commonState.execute(passEncoder);
|
||||
passEncoder.setBindGroup(1, bindGroup);
|
||||
passEncoder.draw(3, 1);
|
||||
|
|
|
|||
|
|
@ -27,9 +27,18 @@ fn fragment(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {
|
|||
return renderColor(traces, sources, pixel);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentNoSource(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let pixel = vec2<i32>(position.xy);
|
||||
let traces = textureLoad(trailMap, pixel, 0);
|
||||
return renderColor(traces, vec4<f32>(0.0), pixel);
|
||||
}
|
||||
|
||||
fn renderColor(traces: vec4<f32>, sources: vec4<f32>, pixel: vec2<i32>) -> vec4<f32> {
|
||||
let background = getTexturedBackground(pixel);
|
||||
if max(max(max(traces.r, traces.g), traces.b), max(max(sources.r, sources.g), sources.b)) <= 0.0 {
|
||||
let tracesMax = maxComponent(traces.rgb);
|
||||
let sourcesMax = maxComponent(sources.rgb);
|
||||
if max(tracesMax, sourcesMax) <= 0.0 {
|
||||
return vec4(background, 1);
|
||||
}
|
||||
|
||||
|
|
@ -38,13 +47,13 @@ fn renderColor(traces: vec4<f32>, sources: vec4<f32>, pixel: vec2<i32>) -> vec4<
|
|||
clarity(traces.g),
|
||||
clarity(traces.b)
|
||||
);
|
||||
if max(max(sources.r, sources.g), sources.b) <= 0.0 {
|
||||
if sourcesMax <= 0.0 {
|
||||
let traceColor =
|
||||
traceStrengths.r * settings.colorA
|
||||
+ traceStrengths.g * settings.colorB
|
||||
+ traceStrengths.b * settings.colorC;
|
||||
let normalizedTraceColor = normalizeColorIntensity(traceColor);
|
||||
let traceStrength = max(max(traceStrengths.r, traceStrengths.g), traceStrengths.b);
|
||||
let traceStrength = maxComponent(traceStrengths);
|
||||
return vec4(mix(background, clamp(normalizedTraceColor, vec3(0), vec3(1)), traceStrength), 1);
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +73,7 @@ fn renderColor(traces: vec4<f32>, sources: vec4<f32>, pixel: vec2<i32>) -> vec4<
|
|||
+ sourceStrengths.g * settings.colorB
|
||||
+ sourceStrengths.b * settings.colorC;
|
||||
let normalizedBrushColor = normalizeColorIntensity(brushColor);
|
||||
let brushStrength = max(max(sourceStrengths.r, sourceStrengths.g), sourceStrengths.b);
|
||||
let brushStrength = maxComponent(sourceStrengths);
|
||||
let brushVisibility = clamp(
|
||||
brushStrength * (
|
||||
settings.brushColorBase +
|
||||
|
|
@ -75,20 +84,27 @@ fn renderColor(traces: vec4<f32>, sources: vec4<f32>, pixel: vec2<i32>) -> vec4<
|
|||
);
|
||||
let color = max(normalizedTraceColor, normalizedBrushColor);
|
||||
|
||||
let strength = max(max(max(strengths.r, strengths.g), strengths.b), brushVisibility);
|
||||
let strength = max(maxComponent(strengths), brushVisibility);
|
||||
return vec4(mix(background, clamp(color, vec3(0), vec3(1)), strength), 1);
|
||||
}
|
||||
|
||||
fn maxComponent(v: vec3<f32>) -> f32 {
|
||||
return max(max(v.r, v.g), v.b);
|
||||
}
|
||||
|
||||
fn clarity(strength: f32) -> f32 {
|
||||
return pow(clamp(strength, 0, 1), settings.clarity);
|
||||
}
|
||||
|
||||
fn normalizeColorIntensity(color: vec3<f32>) -> vec3<f32> {
|
||||
let brightestChannel = max(max(color.r, color.g), color.b);
|
||||
let brightestChannel = maxComponent(color);
|
||||
return color / max(settings.traceNormalizationFloor, brightestChannel);
|
||||
}
|
||||
|
||||
fn getTexturedBackground(pixel: vec2<i32>) -> vec3<f32> {
|
||||
if settings.backgroundGrainStrength == 0.0 {
|
||||
return clamp(settings.backgroundColor, vec3(0), vec3(1));
|
||||
}
|
||||
let noiseCoord = vec2<i32>(vec2<u32>(pixel) & vec2<u32>(NOISE_TEXTURE_MASK));
|
||||
let grain = textureLoad(noise, noiseCoord, 0).r - 0.5;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue