more
This commit is contained in:
parent
2fe3c69963
commit
6bc125be1c
104 changed files with 3084 additions and 2410 deletions
|
|
@ -17,17 +17,19 @@ export interface RenderSettings {
|
|||
backgroundGrainStrength: number;
|
||||
}
|
||||
|
||||
export class RenderPipeline {
|
||||
private static readonly UNIFORM_COUNT = 20;
|
||||
// 3 channel colors (vec3 + f32 padding) + bg color (vec3) + 5 scalars = 20 floats.
|
||||
const UNIFORM_COUNT = 20;
|
||||
|
||||
export class RenderPipeline {
|
||||
private readonly bindGroupLayout: GPUBindGroupLayout;
|
||||
private readonly pipeline: GPURenderPipeline;
|
||||
private readonly noSourcePipeline: GPURenderPipeline;
|
||||
private readonly noGrainPipeline: GPURenderPipeline;
|
||||
private readonly noSourceNoGrainPipeline: GPURenderPipeline;
|
||||
private readonly uniforms: GPUBuffer;
|
||||
private readonly uniformValues = new Float32Array(RenderPipeline.UNIFORM_COUNT);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(
|
||||
RenderPipeline.UNIFORM_COUNT
|
||||
);
|
||||
private readonly uniformValues = new Float32Array(UNIFORM_COUNT);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(UNIFORM_COUNT);
|
||||
private useBackgroundGrain = true;
|
||||
|
||||
private readonly getBindGroup = createBindGroupCache<GPUTextureView, GPUTextureView>(
|
||||
(colorTexture, sourceTexture) =>
|
||||
|
|
@ -46,42 +48,83 @@ export class RenderPipeline {
|
|||
private readonly device: GPUDevice,
|
||||
private readonly commonState: CommonState
|
||||
) {
|
||||
this.bindGroupLayout = device.createBindGroupLayout(RenderPipeline.bindGroupLayout);
|
||||
this.bindGroupLayout = device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
buffer: { type: 'uniform' },
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
texture: { sampleType: 'float' },
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
texture: { sampleType: 'float' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const shaderModule = smartCompile(device, CommonState.shaderCode, shader);
|
||||
const vertex = setUpFullScreenQuad(device);
|
||||
|
||||
const format = navigator.gpu.getPreferredCanvasFormat();
|
||||
this.pipeline = this.createPipeline(format, vertex, 'fragment');
|
||||
this.noSourcePipeline = this.createPipeline(format, vertex, 'fragmentNoSource');
|
||||
const pipelineLayout = device.createPipelineLayout({
|
||||
bindGroupLayouts: [this.commonState.bindGroupLayout, this.bindGroupLayout],
|
||||
});
|
||||
this.pipeline = this.createPipeline(
|
||||
pipelineLayout,
|
||||
vertex,
|
||||
shaderModule,
|
||||
format,
|
||||
'fragment'
|
||||
);
|
||||
this.noSourcePipeline = this.createPipeline(
|
||||
pipelineLayout,
|
||||
vertex,
|
||||
shaderModule,
|
||||
format,
|
||||
'fragmentNoSource'
|
||||
);
|
||||
this.noGrainPipeline = this.createPipeline(
|
||||
pipelineLayout,
|
||||
vertex,
|
||||
shaderModule,
|
||||
format,
|
||||
'fragmentNoGrain'
|
||||
);
|
||||
this.noSourceNoGrainPipeline = this.createPipeline(
|
||||
pipelineLayout,
|
||||
vertex,
|
||||
shaderModule,
|
||||
format,
|
||||
'fragmentNoSourceNoGrain'
|
||||
);
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
size: RenderPipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
this.uniforms = device.createBuffer({
|
||||
size: UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
}
|
||||
|
||||
private createPipeline(
|
||||
format: GPUTextureFormat,
|
||||
layout: GPUPipelineLayout,
|
||||
vertex: GPUVertexState,
|
||||
shaderModule: GPUShaderModule,
|
||||
format: GPUTextureFormat,
|
||||
fragmentEntryPoint: string
|
||||
): GPURenderPipeline {
|
||||
return this.device.createRenderPipeline({
|
||||
layout: this.device.createPipelineLayout({
|
||||
bindGroupLayouts: [this.commonState.bindGroupLayout, this.bindGroupLayout],
|
||||
}),
|
||||
layout,
|
||||
vertex,
|
||||
fragment: {
|
||||
module: smartCompile(this.device, CommonState.shaderCode, shader),
|
||||
module: shaderModule,
|
||||
entryPoint: fragmentEntryPoint,
|
||||
targets: [
|
||||
{
|
||||
format,
|
||||
},
|
||||
],
|
||||
},
|
||||
primitive: {
|
||||
topology: 'triangle-list',
|
||||
targets: [{ format }],
|
||||
},
|
||||
primitive: { topology: 'triangle-list' },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -101,15 +144,13 @@ export class RenderPipeline {
|
|||
this.uniformValues[0] = rgbChannelToUnit(a[0]);
|
||||
this.uniformValues[1] = rgbChannelToUnit(a[1]);
|
||||
this.uniformValues[2] = rgbChannelToUnit(a[2]);
|
||||
this.uniformValues[3] = 0;
|
||||
// uniformValues[3], [7], [11] are WGSL vec3→vec4 alignment padding.
|
||||
this.uniformValues[4] = rgbChannelToUnit(b[0]);
|
||||
this.uniformValues[5] = rgbChannelToUnit(b[1]);
|
||||
this.uniformValues[6] = rgbChannelToUnit(b[2]);
|
||||
this.uniformValues[7] = 0;
|
||||
this.uniformValues[8] = rgbChannelToUnit(c[0]);
|
||||
this.uniformValues[9] = rgbChannelToUnit(c[1]);
|
||||
this.uniformValues[10] = rgbChannelToUnit(c[2]);
|
||||
this.uniformValues[11] = 0;
|
||||
this.uniformValues[12] = rgbChannelToUnit(backgroundColor[0]);
|
||||
this.uniformValues[13] = rgbChannelToUnit(backgroundColor[1]);
|
||||
this.uniformValues[14] = rgbChannelToUnit(backgroundColor[2]);
|
||||
|
|
@ -118,6 +159,7 @@ export class RenderPipeline {
|
|||
this.uniformValues[17] = renderBrushColorBase;
|
||||
this.uniformValues[18] = renderBrushColorStrengthMultiplier;
|
||||
this.uniformValues[19] = backgroundGrainStrength;
|
||||
this.useBackgroundGrain = backgroundGrainStrength !== 0;
|
||||
writeFloat32BufferIfChanged(
|
||||
this.device,
|
||||
this.uniforms,
|
||||
|
|
@ -130,28 +172,18 @@ export class RenderPipeline {
|
|||
commandEncoder: GPUCommandEncoder,
|
||||
colorTexture: GPUTextureView,
|
||||
sourceTexture: GPUTextureView,
|
||||
useSourceTexture = true
|
||||
useSourceTexture = true,
|
||||
timestampWrites?: GPURenderPassTimestampWrites
|
||||
): GPUTexture {
|
||||
const bindGroup = this.getBindGroup(colorTexture, sourceTexture);
|
||||
const canvasTexture = this.context.getCurrentTexture();
|
||||
|
||||
const renderPassDescriptor: GPURenderPassDescriptor = {
|
||||
colorAttachments: [
|
||||
{
|
||||
view: canvasTexture.createView(),
|
||||
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
||||
loadOp: 'clear',
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
};
|
||||
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
|
||||
passEncoder.setPipeline(useSourceTexture ? this.pipeline : this.noSourcePipeline);
|
||||
this.commonState.execute(passEncoder);
|
||||
passEncoder.setBindGroup(1, bindGroup);
|
||||
passEncoder.draw(3, 1);
|
||||
passEncoder.end();
|
||||
|
||||
this.encodePass(
|
||||
commandEncoder,
|
||||
colorTexture,
|
||||
sourceTexture,
|
||||
canvasTexture.createView(),
|
||||
useSourceTexture,
|
||||
timestampWrites
|
||||
);
|
||||
return canvasTexture;
|
||||
}
|
||||
|
||||
|
|
@ -159,56 +191,54 @@ export class RenderPipeline {
|
|||
commandEncoder: GPUCommandEncoder,
|
||||
colorTexture: GPUTextureView,
|
||||
sourceTexture: GPUTextureView,
|
||||
outputTexture: GPUTextureView
|
||||
outputTexture: GPUTextureView,
|
||||
useSourceTexture = true,
|
||||
timestampWrites?: GPURenderPassTimestampWrites
|
||||
) {
|
||||
const bindGroup = this.getBindGroup(colorTexture, sourceTexture);
|
||||
this.encodePass(
|
||||
commandEncoder,
|
||||
colorTexture,
|
||||
sourceTexture,
|
||||
outputTexture,
|
||||
useSourceTexture,
|
||||
timestampWrites
|
||||
);
|
||||
}
|
||||
|
||||
private encodePass(
|
||||
commandEncoder: GPUCommandEncoder,
|
||||
colorTexture: GPUTextureView,
|
||||
sourceTexture: GPUTextureView,
|
||||
output: GPUTextureView,
|
||||
useSourceTexture: boolean,
|
||||
timestampWrites?: GPURenderPassTimestampWrites
|
||||
) {
|
||||
const passEncoder = commandEncoder.beginRenderPass({
|
||||
colorAttachments: [
|
||||
{
|
||||
view: outputTexture,
|
||||
view: output,
|
||||
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
||||
loadOp: 'clear',
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
timestampWrites,
|
||||
});
|
||||
passEncoder.setPipeline(this.pipeline);
|
||||
passEncoder.setPipeline(this.getPipeline(useSourceTexture));
|
||||
this.commonState.execute(passEncoder);
|
||||
passEncoder.setBindGroup(1, bindGroup);
|
||||
passEncoder.setBindGroup(1, this.getBindGroup(colorTexture, sourceTexture));
|
||||
passEncoder.draw(3, 1);
|
||||
passEncoder.end();
|
||||
}
|
||||
|
||||
private getPipeline(useSourceTexture: boolean): GPURenderPipeline {
|
||||
if (useSourceTexture) {
|
||||
return this.useBackgroundGrain ? this.pipeline : this.noGrainPipeline;
|
||||
}
|
||||
return this.useBackgroundGrain ? this.noSourcePipeline : this.noSourceNoGrainPipeline;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.uniforms.destroy();
|
||||
}
|
||||
|
||||
private static get bindGroupLayout(): GPUBindGroupLayoutDescriptor {
|
||||
return {
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
buffer: {
|
||||
type: 'uniform',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
texture: {
|
||||
sampleType: 'float',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 3,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
texture: {
|
||||
sampleType: 'float',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
struct Settings {
|
||||
colorA: vec3<f32>,
|
||||
backgroundColorPadding0: f32,
|
||||
_colorAPadding: f32,
|
||||
colorB: vec3<f32>,
|
||||
backgroundColorPadding1: f32,
|
||||
_colorBPadding: f32,
|
||||
colorC: vec3<f32>,
|
||||
backgroundColorPadding2: f32,
|
||||
_colorCPadding: f32,
|
||||
backgroundColor: vec3<f32>,
|
||||
clarity: f32,
|
||||
traceNormalizationFloor: f32,
|
||||
|
|
@ -24,18 +24,32 @@ fn fragment(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {
|
|||
let pixel = vec2<i32>(position.xy);
|
||||
let traces = textureLoad(trailMap, pixel, 0);
|
||||
let sources = textureLoad(sourceMap, pixel, 0);
|
||||
return renderColor(traces, sources, pixel);
|
||||
return renderColor(traces, sources, getTexturedBackground(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);
|
||||
return renderColor(traces, vec4<f32>(0.0), getTexturedBackground(pixel));
|
||||
}
|
||||
|
||||
fn renderColor(traces: vec4<f32>, sources: vec4<f32>, pixel: vec2<i32>) -> vec4<f32> {
|
||||
let background = getTexturedBackground(pixel);
|
||||
@fragment
|
||||
fn fragmentNoGrain(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let pixel = vec2<i32>(position.xy);
|
||||
let traces = textureLoad(trailMap, pixel, 0);
|
||||
let sources = textureLoad(sourceMap, pixel, 0);
|
||||
return renderColor(traces, sources, getFlatBackground());
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentNoSourceNoGrain(@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), getFlatBackground());
|
||||
}
|
||||
|
||||
fn renderColor(traces: vec4<f32>, sources: vec4<f32>, background: vec3<f32>) -> vec4<f32> {
|
||||
let tracesMax = maxComponent(traces.rgb);
|
||||
let sourcesMax = maxComponent(sources.rgb);
|
||||
if max(tracesMax, sourcesMax) <= 0.0 {
|
||||
|
|
@ -93,7 +107,11 @@ fn maxComponent(v: vec3<f32>) -> f32 {
|
|||
}
|
||||
|
||||
fn clarity(strength: f32) -> f32 {
|
||||
return pow(clamp(strength, 0, 1), settings.clarity);
|
||||
let clamped = clamp(strength, 0, 1);
|
||||
if settings.clarity == 1.0 {
|
||||
return clamped;
|
||||
}
|
||||
return pow(clamped, settings.clarity);
|
||||
}
|
||||
|
||||
fn normalizeColorIntensity(color: vec3<f32>) -> vec3<f32> {
|
||||
|
|
@ -101,10 +119,11 @@ fn normalizeColorIntensity(color: vec3<f32>) -> vec3<f32> {
|
|||
return color / max(settings.traceNormalizationFloor, brightestChannel);
|
||||
}
|
||||
|
||||
fn getFlatBackground() -> vec3<f32> {
|
||||
return clamp(settings.backgroundColor, vec3(0), vec3(1));
|
||||
}
|
||||
|
||||
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