186 lines
5.2 KiB
TypeScript
186 lines
5.2 KiB
TypeScript
import { vec2 } from 'gl-matrix';
|
|
|
|
import { appConfig } from '../../config';
|
|
import {
|
|
createCachedBufferWrite,
|
|
writeBufferIfChanged,
|
|
} from '../../utils/graphics/cached-buffer-write';
|
|
import { smartCompile } from '../../utils/graphics/smart-compile';
|
|
import { CommonState } from '../common-state/common-state';
|
|
import {
|
|
LINE_SEGMENT_VERTEX_BUFFER_LAYOUT,
|
|
LINE_SEGMENT_VERTICES,
|
|
LineSegmentBuffer,
|
|
} from '../common/line-segment-buffer';
|
|
import lineSegmentShader from '../common/line-segment.wgsl?raw';
|
|
import {
|
|
ERASER_MASK_TEXTURE_FORMAT,
|
|
TRAIL_SOURCE_TEXTURE_FORMAT,
|
|
} from '../texture-formats';
|
|
import shader from './eraser-texture.wgsl?raw';
|
|
|
|
interface EraserTextureParameters {
|
|
eraserSize: number;
|
|
eraserLineDistanceEpsilon: number;
|
|
eraserClearRed: number;
|
|
eraserClearGreen: number;
|
|
eraserClearBlue: number;
|
|
eraserClearAlpha: number;
|
|
}
|
|
|
|
const UNIFORM_COUNT = 8;
|
|
const TARGET_FORMATS: Array<GPUTextureFormat> = [
|
|
ERASER_MASK_TEXTURE_FORMAT,
|
|
TRAIL_SOURCE_TEXTURE_FORMAT,
|
|
TRAIL_SOURCE_TEXTURE_FORMAT,
|
|
];
|
|
|
|
export class EraserTexturePipeline {
|
|
private readonly bindGroupLayout: GPUBindGroupLayout;
|
|
private readonly bindGroup: GPUBindGroup;
|
|
private readonly combinedPipeline: GPURenderPipeline;
|
|
private readonly uniforms: GPUBuffer;
|
|
private readonly uniformValues = new Float32Array(UNIFORM_COUNT);
|
|
private readonly uniformCache = createCachedBufferWrite(
|
|
UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT
|
|
);
|
|
private readonly segments: LineSegmentBuffer;
|
|
|
|
public constructor(
|
|
private readonly device: GPUDevice,
|
|
private readonly commonState: CommonState
|
|
) {
|
|
this.segments = new LineSegmentBuffer(
|
|
device,
|
|
appConfig.pipelines.eraser.maxTextureLineCount
|
|
);
|
|
|
|
this.bindGroupLayout = device.createBindGroupLayout({
|
|
entries: [
|
|
{
|
|
binding: 0,
|
|
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
buffer: { type: 'uniform' },
|
|
},
|
|
],
|
|
});
|
|
|
|
const shaderModule = smartCompile(
|
|
device,
|
|
CommonState.shaderCode,
|
|
lineSegmentShader,
|
|
shader
|
|
);
|
|
this.combinedPipeline = device.createRenderPipeline({
|
|
layout: device.createPipelineLayout({
|
|
bindGroupLayouts: [this.commonState.bindGroupLayout, this.bindGroupLayout],
|
|
}),
|
|
vertex: {
|
|
module: shaderModule,
|
|
entryPoint: 'vertex',
|
|
buffers: [LINE_SEGMENT_VERTEX_BUFFER_LAYOUT],
|
|
},
|
|
fragment: {
|
|
module: shaderModule,
|
|
entryPoint: 'fragmentCombined',
|
|
targets: TARGET_FORMATS.map((format) => ({ format })),
|
|
},
|
|
primitive: { topology: 'triangle-list' },
|
|
});
|
|
|
|
this.uniforms = device.createBuffer({
|
|
size: UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
});
|
|
|
|
this.bindGroup = device.createBindGroup({
|
|
layout: this.bindGroupLayout,
|
|
entries: [{ binding: 0, resource: { buffer: this.uniforms } }],
|
|
});
|
|
}
|
|
|
|
public addSwipeSegment(from: vec2, to: vec2): void {
|
|
this.segments.add(from, to);
|
|
}
|
|
|
|
public clearSwipes(): void {
|
|
this.segments.clear();
|
|
}
|
|
|
|
public setParameters({
|
|
eraserSize,
|
|
eraserLineDistanceEpsilon,
|
|
eraserClearRed,
|
|
eraserClearGreen,
|
|
eraserClearBlue,
|
|
eraserClearAlpha,
|
|
}: EraserTextureParameters): void {
|
|
const eraserRadius = eraserSize / 2;
|
|
|
|
this.uniformValues[0] = eraserRadius * eraserRadius;
|
|
this.uniformValues[1] = eraserLineDistanceEpsilon;
|
|
this.uniformValues[2] = eraserClearRed;
|
|
this.uniformValues[3] = eraserClearGreen;
|
|
this.uniformValues[4] = eraserClearBlue;
|
|
this.uniformValues[5] = eraserClearAlpha;
|
|
this.uniformValues[6] = eraserRadius;
|
|
writeBufferIfChanged(
|
|
this.device,
|
|
this.uniforms,
|
|
this.uniformValues,
|
|
this.uniformCache
|
|
);
|
|
|
|
this.segments.flush();
|
|
}
|
|
|
|
public executeCombined(
|
|
commandEncoder: GPUCommandEncoder,
|
|
eraserMaskOut: GPUTextureView,
|
|
sourceMapOut: GPUTextureView,
|
|
trailMapOut: GPUTextureView,
|
|
timestampWrites?: GPURenderPassTimestampWrites
|
|
): void {
|
|
const lineCount = this.segments.activeCount;
|
|
if (lineCount === 0) {
|
|
const passEncoder = commandEncoder.beginRenderPass({
|
|
colorAttachments: [
|
|
{
|
|
view: eraserMaskOut,
|
|
clearValue: { r: 1, g: 1, b: 1, a: 1 },
|
|
loadOp: 'clear',
|
|
storeOp: 'store',
|
|
},
|
|
],
|
|
timestampWrites,
|
|
});
|
|
passEncoder.end();
|
|
return;
|
|
}
|
|
|
|
const passEncoder = commandEncoder.beginRenderPass({
|
|
colorAttachments: [
|
|
{
|
|
view: eraserMaskOut,
|
|
clearValue: { r: 1, g: 1, b: 1, a: 1 },
|
|
loadOp: 'clear',
|
|
storeOp: 'store',
|
|
},
|
|
{ view: sourceMapOut, loadOp: 'load', storeOp: 'store' },
|
|
{ view: trailMapOut, loadOp: 'load', storeOp: 'store' },
|
|
],
|
|
timestampWrites,
|
|
});
|
|
passEncoder.setPipeline(this.combinedPipeline);
|
|
this.commonState.execute(passEncoder);
|
|
passEncoder.setBindGroup(1, this.bindGroup);
|
|
passEncoder.setVertexBuffer(0, this.segments.vertexBuffer);
|
|
passEncoder.draw(LINE_SEGMENT_VERTICES, lineCount);
|
|
passEncoder.end();
|
|
}
|
|
|
|
public destroy(): void {
|
|
this.segments.destroy();
|
|
this.uniforms.destroy();
|
|
}
|
|
}
|