more
This commit is contained in:
parent
2fe3c69963
commit
6bc125be1c
104 changed files with 3084 additions and 2410 deletions
|
|
@ -7,6 +7,12 @@ import {
|
|||
} 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 shader from './brush.wgsl?raw';
|
||||
|
||||
export interface BrushSettings {
|
||||
|
|
@ -20,12 +26,7 @@ export interface BrushSettings {
|
|||
brushGrainMaxStrength: number;
|
||||
}
|
||||
|
||||
interface LineSegment {
|
||||
from: vec2;
|
||||
to: vec2;
|
||||
}
|
||||
|
||||
interface BrushParameterSettings extends BrushSettings {
|
||||
interface BrushParameters extends BrushSettings {
|
||||
pixelRatio?: number;
|
||||
selectedColorIndex: number;
|
||||
}
|
||||
|
|
@ -35,6 +36,8 @@ export const getSafePixelRatio = (pixelRatio: number | undefined): number =>
|
|||
? pixelRatio
|
||||
: 1;
|
||||
|
||||
const UNIFORM_COUNT = 16;
|
||||
|
||||
const setBrushUniformValues = (
|
||||
target: Float32Array,
|
||||
{
|
||||
|
|
@ -48,15 +51,14 @@ const setBrushUniformValues = (
|
|||
brushGrainMaxStrength,
|
||||
selectedColorIndex,
|
||||
pixelRatio,
|
||||
}: BrushParameterSettings
|
||||
}: BrushParameters
|
||||
): void => {
|
||||
const safePixelRatio = getSafePixelRatio(pixelRatio);
|
||||
const brushRadius = (brushSize * safePixelRatio) / 2;
|
||||
|
||||
target[0] = brushRadius;
|
||||
target[1] = brushRadius * brushRadius;
|
||||
target[2] = 0;
|
||||
target[3] = 0;
|
||||
// target[2], target[3] are WGSL alignment padding for brushValue:vec4 — never read by the shader.
|
||||
target[4] = selectedColorIndex === 0 ? 1 : 0;
|
||||
target[5] = selectedColorIndex === 1 ? 1 : 0;
|
||||
target[6] = selectedColorIndex === 2 ? 1 : 0;
|
||||
|
|
@ -70,78 +72,81 @@ const setBrushUniformValues = (
|
|||
};
|
||||
|
||||
export class BrushPipeline {
|
||||
private static readonly UNIFORM_COUNT = 16;
|
||||
private static readonly MAX_LINE_COUNT = appConfig.pipelines.brush.maxLineCount;
|
||||
private static readonly VERTICES_PER_LINE_SEGMENT = 6;
|
||||
private static readonly ATTRIBUTES_PER_LINE_SEGMENT = 4;
|
||||
|
||||
private readonly bindGroupLayout: GPUBindGroupLayout;
|
||||
private readonly bindGroup: GPUBindGroup;
|
||||
private readonly multiTargetPipeline: GPURenderPipeline;
|
||||
private readonly renderPipeline: GPURenderPipeline;
|
||||
private readonly uniforms: GPUBuffer;
|
||||
private readonly uniformValues = new Float32Array(BrushPipeline.UNIFORM_COUNT);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(
|
||||
BrushPipeline.UNIFORM_COUNT
|
||||
);
|
||||
private readonly vertexBuffer: GPUBuffer;
|
||||
private readonly vertexUploadData = new Float32Array(
|
||||
BrushPipeline.MAX_LINE_COUNT *
|
||||
BrushPipeline.VERTICES_PER_LINE_SEGMENT *
|
||||
BrushPipeline.ATTRIBUTES_PER_LINE_SEGMENT
|
||||
);
|
||||
|
||||
private lineSegments: Array<LineSegment> = [];
|
||||
private actualSegments: Array<LineSegment> = [];
|
||||
private readonly uniformValues = new Float32Array(UNIFORM_COUNT);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(UNIFORM_COUNT);
|
||||
private readonly segments: LineSegmentBuffer;
|
||||
|
||||
public constructor(
|
||||
private readonly device: GPUDevice,
|
||||
private readonly commonState: CommonState
|
||||
) {
|
||||
this.bindGroupLayout = device.createBindGroupLayout(BrushPipeline.bindGroupLayout);
|
||||
this.segments = new LineSegmentBuffer(device, appConfig.pipelines.brush.maxLineCount);
|
||||
|
||||
this.vertexBuffer = device.createBuffer({
|
||||
size:
|
||||
BrushPipeline.MAX_LINE_COUNT *
|
||||
BrushPipeline.VERTICES_PER_LINE_SEGMENT *
|
||||
BrushPipeline.ATTRIBUTES_PER_LINE_SEGMENT *
|
||||
Float32Array.BYTES_PER_ELEMENT,
|
||||
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
const shaderModule = smartCompile(device, CommonState.shaderCode, shader);
|
||||
this.multiTargetPipeline = this.createPipeline(shaderModule, 'fragmentMrt', 1);
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
size: BrushPipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.bindGroup = this.device.createBindGroup({
|
||||
layout: this.bindGroupLayout,
|
||||
this.bindGroupLayout = device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: this.uniforms,
|
||||
},
|
||||
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
||||
buffer: { type: 'uniform' },
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public addSwipeSegment(from: vec2, to: vec2) {
|
||||
this.lineSegments.push({
|
||||
from: vec2.clone(from),
|
||||
to: vec2.clone(to),
|
||||
const shaderModule = smartCompile(
|
||||
device,
|
||||
CommonState.shaderCode,
|
||||
lineSegmentShader,
|
||||
shader
|
||||
);
|
||||
this.renderPipeline = 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: 'fragmentMrt',
|
||||
targets: [
|
||||
{
|
||||
format: 'rgba16float',
|
||||
blend: {
|
||||
color: { operation: 'max', srcFactor: 'one', dstFactor: 'one' },
|
||||
alpha: { operation: 'max', srcFactor: 'one', dstFactor: 'one' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
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 clearSwipes() {
|
||||
this.lineSegments.length = 0;
|
||||
this.actualSegments.length = 0;
|
||||
public addSwipeSegment(from: vec2, to: vec2): void {
|
||||
this.segments.add(from, to);
|
||||
}
|
||||
|
||||
public setParameters(parameters: BrushParameterSettings) {
|
||||
public clearSwipes(): void {
|
||||
this.segments.clear();
|
||||
}
|
||||
|
||||
public setParameters(parameters: BrushParameters): void {
|
||||
setBrushUniformValues(this.uniformValues, parameters);
|
||||
writeFloat32BufferIfChanged(
|
||||
this.device,
|
||||
|
|
@ -149,188 +154,34 @@ export class BrushPipeline {
|
|||
this.uniformValues,
|
||||
this.uniformCache
|
||||
);
|
||||
|
||||
this.actualSegments = this.lineSegments.slice();
|
||||
this.lineSegments.length = 0;
|
||||
|
||||
if (this.actualSegments.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.actualSegments.length > BrushPipeline.MAX_LINE_COUNT) {
|
||||
this.actualSegments = BrushPipeline.subsampleSegments(this.actualSegments);
|
||||
}
|
||||
|
||||
const lineCount = this.lineCount;
|
||||
let floatOffset = 0;
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const segment = this.actualSegments[i];
|
||||
floatOffset = this.writeSegmentVertices(
|
||||
this.vertexUploadData,
|
||||
floatOffset,
|
||||
segment.from,
|
||||
segment.to
|
||||
);
|
||||
}
|
||||
|
||||
this.device.queue.writeBuffer(
|
||||
this.vertexBuffer,
|
||||
0,
|
||||
this.vertexUploadData,
|
||||
0,
|
||||
floatOffset
|
||||
);
|
||||
}
|
||||
|
||||
private static subsampleSegments(segments: Array<LineSegment>): Array<LineSegment> {
|
||||
if (segments.length <= BrushPipeline.MAX_LINE_COUNT) {
|
||||
return segments;
|
||||
}
|
||||
|
||||
const result: Array<LineSegment> = [];
|
||||
for (let i = 0; i < BrushPipeline.MAX_LINE_COUNT; i++) {
|
||||
const index = Math.round(
|
||||
(i * (segments.length - 1)) / (BrushPipeline.MAX_LINE_COUNT - 1)
|
||||
);
|
||||
result.push(segments[index]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private writeSegmentVertices(
|
||||
target: Float32Array,
|
||||
offset: number,
|
||||
from: vec2,
|
||||
to: vec2
|
||||
): number {
|
||||
target[offset++] = from[0];
|
||||
target[offset++] = from[1];
|
||||
target[offset++] = to[0];
|
||||
target[offset++] = to[1];
|
||||
return offset;
|
||||
this.segments.flush();
|
||||
}
|
||||
|
||||
public executeMultiTarget(
|
||||
commandEncoder: GPUCommandEncoder,
|
||||
sourceMapOut: GPUTextureView
|
||||
sourceMapOut: GPUTextureView,
|
||||
timestampWrites?: GPURenderPassTimestampWrites
|
||||
): boolean {
|
||||
return this.executeWithPipeline(commandEncoder, this.multiTargetPipeline, [
|
||||
sourceMapOut,
|
||||
]);
|
||||
}
|
||||
|
||||
private executeWithPipeline(
|
||||
commandEncoder: GPUCommandEncoder,
|
||||
pipeline: GPURenderPipeline,
|
||||
textureViews: Array<GPUTextureView>
|
||||
): boolean {
|
||||
if (this.lineCount === 0) {
|
||||
const lineCount = this.segments.activeCount;
|
||||
if (lineCount === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const renderPassDescriptor: GPURenderPassDescriptor = {
|
||||
colorAttachments: textureViews.map<GPURenderPassColorAttachment>((view) => ({
|
||||
view,
|
||||
loadOp: 'load',
|
||||
storeOp: 'store',
|
||||
})),
|
||||
};
|
||||
|
||||
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
|
||||
passEncoder.setPipeline(pipeline);
|
||||
const passEncoder = commandEncoder.beginRenderPass({
|
||||
colorAttachments: [{ view: sourceMapOut, loadOp: 'load', storeOp: 'store' }],
|
||||
timestampWrites,
|
||||
});
|
||||
passEncoder.setPipeline(this.renderPipeline);
|
||||
this.commonState.execute(passEncoder);
|
||||
passEncoder.setBindGroup(1, this.bindGroup);
|
||||
passEncoder.setVertexBuffer(0, this.vertexBuffer);
|
||||
passEncoder.draw(BrushPipeline.VERTICES_PER_LINE_SEGMENT, this.lineCount);
|
||||
passEncoder.setVertexBuffer(0, this.segments.vertexBuffer);
|
||||
passEncoder.draw(LINE_SEGMENT_VERTICES, lineCount);
|
||||
passEncoder.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.vertexBuffer.destroy();
|
||||
public destroy(): void {
|
||||
this.segments.destroy();
|
||||
this.uniforms.destroy();
|
||||
}
|
||||
|
||||
private createPipeline(
|
||||
shaderModule: GPUShaderModule,
|
||||
fragmentEntryPoint: string,
|
||||
colorTargetCount: number
|
||||
): GPURenderPipeline {
|
||||
return this.device.createRenderPipeline({
|
||||
layout: this.device.createPipelineLayout({
|
||||
bindGroupLayouts: [this.commonState.bindGroupLayout, this.bindGroupLayout],
|
||||
}),
|
||||
vertex: {
|
||||
module: shaderModule,
|
||||
entryPoint: 'vertex',
|
||||
buffers: [
|
||||
{
|
||||
arrayStride:
|
||||
Float32Array.BYTES_PER_ELEMENT * BrushPipeline.ATTRIBUTES_PER_LINE_SEGMENT,
|
||||
stepMode: 'instance',
|
||||
attributes: [
|
||||
{
|
||||
shaderLocation: 0,
|
||||
format: 'float32x2',
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
shaderLocation: 1,
|
||||
format: 'float32x2',
|
||||
offset: Float32Array.BYTES_PER_ELEMENT * 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
fragment: {
|
||||
module: shaderModule,
|
||||
entryPoint: fragmentEntryPoint,
|
||||
targets: Array.from(
|
||||
{ length: colorTargetCount },
|
||||
() => BrushPipeline.colorTarget
|
||||
),
|
||||
},
|
||||
primitive: {
|
||||
topology: 'triangle-list',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private static get colorTarget(): GPUColorTargetState {
|
||||
return {
|
||||
format: 'rgba16float',
|
||||
blend: {
|
||||
color: {
|
||||
operation: 'max',
|
||||
srcFactor: 'one',
|
||||
dstFactor: 'one',
|
||||
},
|
||||
alpha: {
|
||||
operation: 'max',
|
||||
srcFactor: 'one',
|
||||
dstFactor: 'one',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static get bindGroupLayout(): GPUBindGroupLayoutDescriptor {
|
||||
return {
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
||||
buffer: {
|
||||
type: 'uniform',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private get lineCount() {
|
||||
return this.actualSegments.length;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue