not sure
This commit is contained in:
parent
560398fefb
commit
80ed37298b
73 changed files with 3619 additions and 1295 deletions
|
|
@ -18,7 +18,10 @@ export class EraserAgentPipeline {
|
|||
private readonly uniformCache = createCachedFloat32BufferWrite(
|
||||
EraserAgentPipeline.UNIFORM_COUNT
|
||||
);
|
||||
private readonly bindGroupsByMask = new WeakMap<GPUTextureView, GPUBindGroup>();
|
||||
private readonly bindGroupsByAgentsBuffer = new WeakMap<
|
||||
GPUBuffer,
|
||||
WeakMap<GPUTextureView, GPUBindGroup>
|
||||
>();
|
||||
|
||||
private pendingSegmentCount = 0;
|
||||
private activeSegmentCount = 0;
|
||||
|
|
@ -26,7 +29,7 @@ export class EraserAgentPipeline {
|
|||
|
||||
public constructor(
|
||||
private readonly device: GPUDevice,
|
||||
private readonly agentsBuffer: GPUBuffer
|
||||
private readonly getAgentsBuffer: () => GPUBuffer
|
||||
) {
|
||||
const emptyBindGroupLayout = device.createBindGroupLayout({ entries: [] });
|
||||
this.bindGroupLayout = device.createBindGroupLayout({
|
||||
|
|
@ -80,13 +83,19 @@ export class EraserAgentPipeline {
|
|||
this.activeSegmentCount = 0;
|
||||
}
|
||||
|
||||
public setParameters({ agentCount }: { agentCount: number }): void {
|
||||
public setParameters({
|
||||
agentCount,
|
||||
eraserMaskAlphaThreshold,
|
||||
}: {
|
||||
agentCount: number;
|
||||
eraserMaskAlphaThreshold: number;
|
||||
}): void {
|
||||
this.agentCount = agentCount;
|
||||
this.activeSegmentCount = this.pendingSegmentCount;
|
||||
this.pendingSegmentCount = 0;
|
||||
|
||||
this.uniformValues[0] = agentCount;
|
||||
this.uniformValues[1] = 0;
|
||||
this.uniformValues[1] = eraserMaskAlphaThreshold;
|
||||
this.uniformValues[2] = 0;
|
||||
this.uniformValues[3] = 0;
|
||||
writeFloat32BufferIfChanged(
|
||||
|
|
@ -120,7 +129,14 @@ export class EraserAgentPipeline {
|
|||
}
|
||||
|
||||
private getBindGroup(eraserMask: GPUTextureView): GPUBindGroup {
|
||||
const cached = this.bindGroupsByMask.get(eraserMask);
|
||||
const agentsBuffer = this.getAgentsBuffer();
|
||||
let maskCache = this.bindGroupsByAgentsBuffer.get(agentsBuffer);
|
||||
if (!maskCache) {
|
||||
maskCache = new WeakMap<GPUTextureView, GPUBindGroup>();
|
||||
this.bindGroupsByAgentsBuffer.set(agentsBuffer, maskCache);
|
||||
}
|
||||
|
||||
const cached = maskCache.get(eraserMask);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
|
@ -137,7 +153,7 @@ export class EraserAgentPipeline {
|
|||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: this.agentsBuffer,
|
||||
buffer: agentsBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -147,7 +163,7 @@ export class EraserAgentPipeline {
|
|||
],
|
||||
});
|
||||
|
||||
this.bindGroupsByMask.set(eraserMask, bindGroup);
|
||||
maskCache.set(eraserMask, bindGroup);
|
||||
return bindGroup;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
struct Settings {
|
||||
agentCount: f32,
|
||||
padding0: f32,
|
||||
eraserMaskAlphaThreshold: f32,
|
||||
padding1: f32,
|
||||
padding2: f32,
|
||||
};
|
||||
|
|
@ -18,21 +18,20 @@ fn main(
|
|||
return;
|
||||
}
|
||||
|
||||
var agent = agents[id];
|
||||
if agent.colorIndex < 0.0 {
|
||||
let colorIndex = agents[id].colorIndex;
|
||||
if colorIndex < 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let maskSize = vec2<i32>(textureDimensions(eraserMask));
|
||||
let maskPosition = clamp(
|
||||
vec2<i32>(agent.position),
|
||||
vec2<i32>(agents[id].position),
|
||||
vec2<i32>(0, 0),
|
||||
maskSize - vec2<i32>(1, 1)
|
||||
);
|
||||
let maskSample = textureLoad(eraserMask, maskPosition, 0);
|
||||
|
||||
if maskSample.a < 0.5 {
|
||||
agent.colorIndex = -1.0;
|
||||
agents[id] = agent;
|
||||
if maskSample.a < settings.eraserMaskAlphaThreshold {
|
||||
agents[id].colorIndex = -1.0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,14 @@ interface LineSegment {
|
|||
}
|
||||
|
||||
export class EraserTexturePipeline {
|
||||
private static readonly UNIFORM_COUNT = 4;
|
||||
private static readonly UNIFORM_COUNT = 8;
|
||||
private static readonly MAX_LINE_COUNT = appConfig.pipelines.eraser.maxTextureLineCount;
|
||||
private static readonly VERTICES_PER_LINE_SEGMENT = 6;
|
||||
private static readonly ATTRIBUTES_PER_LINE_SEGMENT = 6;
|
||||
private static readonly ATTRIBUTES_PER_LINE_SEGMENT = 4;
|
||||
|
||||
private readonly bindGroupLayout: GPUBindGroupLayout;
|
||||
private readonly bindGroup: GPUBindGroup;
|
||||
private readonly pipeline: GPURenderPipeline;
|
||||
private readonly multiTargetPipeline: GPURenderPipeline;
|
||||
private readonly combinedPipeline: GPURenderPipeline;
|
||||
private readonly uniforms: GPUBuffer;
|
||||
private readonly uniformValues = new Float32Array(EraserTexturePipeline.UNIFORM_COUNT);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(
|
||||
|
|
@ -47,7 +46,7 @@ export class EraserTexturePipeline {
|
|||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
||||
buffer: {
|
||||
type: 'uniform',
|
||||
},
|
||||
|
|
@ -65,8 +64,7 @@ export class EraserTexturePipeline {
|
|||
});
|
||||
|
||||
const shaderModule = smartCompile(device, CommonState.shaderCode, shader);
|
||||
this.pipeline = this.createPipeline(shaderModule, 'fragment', 1);
|
||||
this.multiTargetPipeline = this.createPipeline(shaderModule, 'fragmentMrt', 3);
|
||||
this.combinedPipeline = this.createPipeline(shaderModule, 'fragmentCombined', 4);
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
size: EraserTexturePipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
|
|
@ -98,13 +96,31 @@ export class EraserTexturePipeline {
|
|||
this.actualSegments.length = 0;
|
||||
}
|
||||
|
||||
public setParameters({ eraserSize }: { eraserSize: number }): void {
|
||||
public setParameters({
|
||||
eraserSize,
|
||||
eraserLineDistanceEpsilon,
|
||||
eraserClearRed,
|
||||
eraserClearGreen,
|
||||
eraserClearBlue,
|
||||
eraserClearAlpha,
|
||||
}: {
|
||||
eraserSize: number;
|
||||
eraserLineDistanceEpsilon: number;
|
||||
eraserClearRed: number;
|
||||
eraserClearGreen: number;
|
||||
eraserClearBlue: number;
|
||||
eraserClearAlpha: number;
|
||||
}): void {
|
||||
const eraserRadius = eraserSize / 2;
|
||||
|
||||
this.uniformValues[0] = eraserRadius * eraserRadius;
|
||||
this.uniformValues[1] = 0;
|
||||
this.uniformValues[2] = 0;
|
||||
this.uniformValues[3] = 0;
|
||||
this.uniformValues[1] = eraserLineDistanceEpsilon;
|
||||
this.uniformValues[2] = eraserClearRed;
|
||||
this.uniformValues[3] = eraserClearGreen;
|
||||
this.uniformValues[4] = eraserClearBlue;
|
||||
this.uniformValues[5] = eraserClearAlpha;
|
||||
this.uniformValues[6] = 0;
|
||||
this.uniformValues[7] = 0;
|
||||
writeFloat32BufferIfChanged(
|
||||
this.device,
|
||||
this.uniforms,
|
||||
|
|
@ -131,8 +147,7 @@ export class EraserTexturePipeline {
|
|||
this.vertexUploadData,
|
||||
floatOffset,
|
||||
segment.from,
|
||||
segment.to,
|
||||
eraserRadius
|
||||
segment.to
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -145,46 +160,60 @@ export class EraserTexturePipeline {
|
|||
);
|
||||
}
|
||||
|
||||
public execute(commandEncoder: GPUCommandEncoder, textureOut: GPUTextureView): void {
|
||||
this.executeWithPipeline(commandEncoder, this.pipeline, [textureOut]);
|
||||
}
|
||||
|
||||
public executeMultiTarget(
|
||||
public executeCombined(
|
||||
commandEncoder: GPUCommandEncoder,
|
||||
eraserMaskOut: GPUTextureView,
|
||||
sourceMapOut: GPUTextureView,
|
||||
influenceMapOut: GPUTextureView,
|
||||
trailMapOut: GPUTextureView
|
||||
): void {
|
||||
this.executeWithPipeline(commandEncoder, this.multiTargetPipeline, [
|
||||
sourceMapOut,
|
||||
influenceMapOut,
|
||||
trailMapOut,
|
||||
]);
|
||||
}
|
||||
|
||||
private executeWithPipeline(
|
||||
commandEncoder: GPUCommandEncoder,
|
||||
pipeline: GPURenderPipeline,
|
||||
textureViews: Array<GPUTextureView>
|
||||
): void {
|
||||
if (this.lineCount === 0) {
|
||||
const passEncoder = commandEncoder.beginRenderPass({
|
||||
colorAttachments: [
|
||||
{
|
||||
view: eraserMaskOut,
|
||||
clearValue: { r: 1, g: 1, b: 1, a: 1 },
|
||||
loadOp: 'clear',
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
});
|
||||
passEncoder.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const renderPassDescriptor: GPURenderPassDescriptor = {
|
||||
colorAttachments: textureViews.map<GPURenderPassColorAttachment>((view) => ({
|
||||
view,
|
||||
loadOp: 'load',
|
||||
storeOp: 'store',
|
||||
})),
|
||||
colorAttachments: [
|
||||
{
|
||||
view: eraserMaskOut,
|
||||
clearValue: { r: 1, g: 1, b: 1, a: 1 },
|
||||
loadOp: 'clear',
|
||||
storeOp: 'store',
|
||||
},
|
||||
{
|
||||
view: sourceMapOut,
|
||||
loadOp: 'load',
|
||||
storeOp: 'store',
|
||||
},
|
||||
{
|
||||
view: influenceMapOut,
|
||||
loadOp: 'load',
|
||||
storeOp: 'store',
|
||||
},
|
||||
{
|
||||
view: trailMapOut,
|
||||
loadOp: 'load',
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
|
||||
passEncoder.setPipeline(pipeline);
|
||||
passEncoder.setPipeline(this.combinedPipeline);
|
||||
this.commonState.execute(passEncoder);
|
||||
passEncoder.setBindGroup(1, this.bindGroup);
|
||||
passEncoder.setVertexBuffer(0, this.vertexBuffer);
|
||||
passEncoder.draw(EraserTexturePipeline.VERTICES_PER_LINE_SEGMENT * this.lineCount, 1);
|
||||
passEncoder.draw(EraserTexturePipeline.VERTICES_PER_LINE_SEGMENT, this.lineCount);
|
||||
passEncoder.end();
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +236,10 @@ export class EraserTexturePipeline {
|
|||
entryPoint: 'vertex',
|
||||
buffers: [
|
||||
{
|
||||
arrayStride: Float32Array.BYTES_PER_ELEMENT * 6,
|
||||
arrayStride:
|
||||
Float32Array.BYTES_PER_ELEMENT *
|
||||
EraserTexturePipeline.ATTRIBUTES_PER_LINE_SEGMENT,
|
||||
stepMode: 'instance',
|
||||
attributes: [
|
||||
{
|
||||
shaderLocation: 0,
|
||||
|
|
@ -219,11 +251,6 @@ export class EraserTexturePipeline {
|
|||
format: 'float32x2',
|
||||
offset: Float32Array.BYTES_PER_ELEMENT * 2,
|
||||
},
|
||||
{
|
||||
shaderLocation: 2,
|
||||
format: 'float32x2',
|
||||
offset: Float32Array.BYTES_PER_ELEMENT * 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -261,84 +288,8 @@ export class EraserTexturePipeline {
|
|||
target: Float32Array,
|
||||
offset: number,
|
||||
from: vec2,
|
||||
to: vec2,
|
||||
width: number
|
||||
): number {
|
||||
const dx = to[0] - from[0];
|
||||
const dy = to[1] - from[1];
|
||||
const length = Math.hypot(dx, dy);
|
||||
const directionX = length > 0 ? dx / length : 1;
|
||||
const directionY = length > 0 ? dy / length : 0;
|
||||
const scaledDirectionX = directionX * width;
|
||||
const scaledDirectionY = directionY * width;
|
||||
const perpendicularX = directionY * width;
|
||||
const perpendicularY = -directionX * width;
|
||||
|
||||
const startX = from[0] - scaledDirectionX;
|
||||
const startY = from[1] - scaledDirectionY;
|
||||
const endX = to[0] + scaledDirectionX;
|
||||
const endY = to[1] + scaledDirectionY;
|
||||
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
startX + perpendicularX,
|
||||
startY + perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
startX - perpendicularX,
|
||||
startY - perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
endX + perpendicularX,
|
||||
endY + perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
startX - perpendicularX,
|
||||
startY - perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
endX + perpendicularX,
|
||||
endY + perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
return this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
endX - perpendicularX,
|
||||
endY - perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
}
|
||||
|
||||
private writeVertex(
|
||||
target: Float32Array,
|
||||
offset: number,
|
||||
screenX: number,
|
||||
screenY: number,
|
||||
from: vec2,
|
||||
to: vec2
|
||||
): number {
|
||||
target[offset++] = screenX;
|
||||
target[offset++] = screenY;
|
||||
target[offset++] = from[0];
|
||||
target[offset++] = from[1];
|
||||
target[offset++] = to[0];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
struct Settings {
|
||||
eraserRadiusSquared: f32,
|
||||
lineDistanceEpsilon: f32,
|
||||
clearRed: f32,
|
||||
clearGreen: f32,
|
||||
clearBlue: f32,
|
||||
clearAlpha: f32,
|
||||
padding0: f32,
|
||||
padding1: f32,
|
||||
padding2: f32,
|
||||
};
|
||||
|
||||
@group(1) @binding(0) var<uniform> settings: Settings;
|
||||
|
|
@ -10,8 +14,8 @@ struct Settings {
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) start: vec2<f32>,
|
||||
@location(2) end: vec2<f32>
|
||||
@location(1) @interpolate(flat) start: vec2<f32>,
|
||||
@location(2) @interpolate(flat) end: vec2<f32>
|
||||
}
|
||||
|
||||
struct EraserTextureTargets {
|
||||
|
|
@ -20,12 +24,20 @@ struct EraserTextureTargets {
|
|||
@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>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vertex(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) @interpolate(flat) start: vec2<f32>,
|
||||
@location(2) @interpolate(flat) end: vec2<f32>
|
||||
@builtin(vertex_index) vertexIndex: u32,
|
||||
@location(0) start: vec2<f32>,
|
||||
@location(1) end: vec2<f32>
|
||||
) -> VertexOutput {
|
||||
let screenPosition = segment_vertex_position(vertexIndex, start, end, sqrt(settings.eraserRadiusSquared));
|
||||
let uv = screenPosition / state.size;
|
||||
let position = vec2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
|
||||
return VertexOutput(vec4(position, 0.0, 1.0), screenPosition, start, end);
|
||||
|
|
@ -34,30 +46,53 @@ fn vertex(
|
|||
@fragment
|
||||
fn fragment(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) start: vec2<f32>,
|
||||
@location(2) end: vec2<f32>
|
||||
@location(1) @interpolate(flat) start: vec2<f32>,
|
||||
@location(2) @interpolate(flat) end: vec2<f32>
|
||||
) -> @location(0) vec4<f32> {
|
||||
if shouldDiscardEraserFragment(screenPosition, start, end) {
|
||||
discard;
|
||||
}
|
||||
|
||||
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
return getEraserClearValue();
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentMrt(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) start: vec2<f32>,
|
||||
@location(2) end: vec2<f32>
|
||||
@location(1) @interpolate(flat) start: vec2<f32>,
|
||||
@location(2) @interpolate(flat) end: vec2<f32>
|
||||
) -> EraserTextureTargets {
|
||||
if shouldDiscardEraserFragment(screenPosition, start, end) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let cleared = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
let cleared = getEraserClearValue();
|
||||
return EraserTextureTargets(cleared, cleared, cleared);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentCombined(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) @interpolate(flat) start: vec2<f32>,
|
||||
@location(2) @interpolate(flat) end: vec2<f32>
|
||||
) -> EraserCombinedTargets {
|
||||
if shouldDiscardEraserFragment(screenPosition, start, end) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let cleared = getEraserClearValue();
|
||||
return EraserCombinedTargets(cleared, cleared, cleared, cleared);
|
||||
}
|
||||
|
||||
fn getEraserClearValue() -> vec4<f32> {
|
||||
return vec4<f32>(
|
||||
settings.clearRed,
|
||||
settings.clearGreen,
|
||||
settings.clearBlue,
|
||||
settings.clearAlpha
|
||||
);
|
||||
}
|
||||
|
||||
fn shouldDiscardEraserFragment(
|
||||
screenPosition: vec2<f32>,
|
||||
start: vec2<f32>,
|
||||
|
|
@ -71,7 +106,7 @@ fn distanceSquaredFromLine(position: vec2<f32>, start: vec2<f32>, end: vec2<f32>
|
|||
let direction = end - start;
|
||||
let denominator = dot(direction, direction);
|
||||
|
||||
if denominator <= 0.0001 {
|
||||
if denominator <= settings.lineDistanceEpsilon {
|
||||
return dot(pa, pa);
|
||||
}
|
||||
|
||||
|
|
@ -79,3 +114,34 @@ fn distanceSquaredFromLine(position: vec2<f32>, start: vec2<f32>, end: vec2<f32>
|
|||
let nearestOffset = pa - direction * q;
|
||||
return dot(nearestOffset, nearestOffset);
|
||||
}
|
||||
|
||||
fn segment_vertex_position(
|
||||
vertexIndex: u32,
|
||||
start: vec2<f32>,
|
||||
end: vec2<f32>,
|
||||
radius: f32
|
||||
) -> vec2<f32> {
|
||||
let directionVector = end - start;
|
||||
let segmentLength = length(directionVector);
|
||||
var direction = vec2<f32>(1.0, 0.0);
|
||||
if segmentLength > 0.0 {
|
||||
direction = directionVector / segmentLength;
|
||||
}
|
||||
let perpendicular = vec2<f32>(direction.y, -direction.x);
|
||||
let corner = segment_vertex_corner(vertexIndex % 6u);
|
||||
let center = mix(start, end, (corner.x + 1.0) * 0.5);
|
||||
return center + direction * corner.x * radius + perpendicular * corner.y * radius;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue