This commit is contained in:
Andras Schmelczer 2026-05-17 14:12:01 +01:00
commit 80ed37298b
73 changed files with 3619 additions and 1295 deletions

View file

@ -16,11 +16,10 @@ interface LineSegment {
}
export class BrushPipeline {
private static readonly UNIFORM_COUNT = 8;
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 = 6;
private static readonly FEATHER_RADIUS_RATIO = 0.22;
private static readonly ATTRIBUTES_PER_LINE_SEGMENT = 4;
private readonly bindGroupLayout: GPUBindGroupLayout;
private readonly bindGroup: GPUBindGroup;
@ -91,22 +90,40 @@ export class BrushPipeline {
public setParameters({
brushSize,
brushSizeVariation,
brushAlpha,
brushFeatherRatio,
brushMinimumFeather,
brushDiscardThreshold,
brushCoarseNoiseScale,
brushGrainNoiseScale,
brushGrainNoiseOffsetX,
brushGrainNoiseOffsetY,
brushGrainMinStrength,
brushGrainMaxStrength,
selectedColorIndex,
}: BrushSettings & { selectedColorIndex: number }) {
const brushRadius = brushSize / 2;
const brushRadiusVariation = Math.floor(brushRadius * brushSizeVariation);
const brushFeather = Math.max(1, brushRadius * BrushPipeline.FEATHER_RADIUS_RATIO);
const brushFeather = Math.max(brushMinimumFeather, brushRadius * brushFeatherRatio);
const brushGeometryRadius =
brushRadius + Math.max(0, brushRadiusVariation) + brushFeather;
this.uniformValues[0] = brushRadius;
this.uniformValues[1] = brushRadiusVariation;
this.uniformValues[2] = 0;
this.uniformValues[3] = 0;
this.uniformValues[2] = brushFeatherRatio;
this.uniformValues[3] = brushMinimumFeather;
this.uniformValues[4] = selectedColorIndex === 0 ? 1 : 0;
this.uniformValues[5] = selectedColorIndex === 1 ? 1 : 0;
this.uniformValues[6] = selectedColorIndex === 2 ? 1 : 0;
this.uniformValues[7] = 1;
this.uniformValues[7] = brushAlpha;
this.uniformValues[8] = brushCoarseNoiseScale;
this.uniformValues[9] = brushGrainNoiseScale;
this.uniformValues[10] = brushGrainNoiseOffsetX;
this.uniformValues[11] = brushGrainNoiseOffsetY;
this.uniformValues[12] = brushDiscardThreshold;
this.uniformValues[13] = brushGrainMinStrength;
this.uniformValues[14] = brushGrainMaxStrength;
this.uniformValues[15] = brushGeometryRadius;
writeFloat32BufferIfChanged(
this.device,
this.uniforms,
@ -133,8 +150,7 @@ export class BrushPipeline {
this.vertexUploadData,
floatOffset,
segment.from,
segment.to,
brushGeometryRadius
segment.to
);
}
@ -167,84 +183,8 @@ export class BrushPipeline {
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];
@ -285,7 +225,7 @@ export class BrushPipeline {
this.commonState.execute(passEncoder);
passEncoder.setBindGroup(1, this.bindGroup);
passEncoder.setVertexBuffer(0, this.vertexBuffer);
passEncoder.draw(BrushPipeline.VERTICES_PER_LINE_SEGMENT * this.lineCount, 1);
passEncoder.draw(BrushPipeline.VERTICES_PER_LINE_SEGMENT, this.lineCount);
passEncoder.end();
}
@ -308,7 +248,9 @@ export class BrushPipeline {
entryPoint: 'vertex',
buffers: [
{
arrayStride: Float32Array.BYTES_PER_ELEMENT * 6,
arrayStride:
Float32Array.BYTES_PER_ELEMENT * BrushPipeline.ATTRIBUTES_PER_LINE_SEGMENT,
stepMode: 'instance',
attributes: [
{
shaderLocation: 0,
@ -320,11 +262,6 @@ export class BrushPipeline {
format: 'float32x2',
offset: Float32Array.BYTES_PER_ELEMENT * 2,
},
{
shaderLocation: 2,
format: 'float32x2',
offset: Float32Array.BYTES_PER_ELEMENT * 4,
},
],
},
],
@ -366,7 +303,7 @@ export class BrushPipeline {
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
buffer: {
type: 'uniform',
},