From b26155d20ea8d813cfd4cb6edabf10a039f2325b Mon Sep 17 00:00:00 2001 From: Rezmason Date: Mon, 15 Nov 2021 01:05:05 -0800 Subject: [PATCH] Adding some more comments. Destructuring the context object in the pass modules. A little code cleanup in bloomPass. Changing the endPass sampler to be cheaper. --- TODO.txt | 1 - js/regl/bloomPass.js | 2 +- js/webgpu/bloomPass.js | 37 +++++++++++++++++++++++------------ js/webgpu/endPass.js | 14 ++++++------- js/webgpu/imagePass.js | 4 +--- js/webgpu/palettePass.js | 4 +--- js/webgpu/rainPass.js | 4 +--- js/webgpu/resurrectionPass.js | 4 +--- js/webgpu/stripePass.js | 4 +--- shaders/wgsl/endPass.wgsl | 4 ++-- 10 files changed, 38 insertions(+), 40 deletions(-) diff --git a/TODO.txt b/TODO.txt index 72f2a23..3b48688 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,7 +1,6 @@ TODO: WebGPU - Make sure everything is properly commented Update links in issues Get rid of end pass once it's possible to copy a bgra8unorm to a canvas texture Switch to rgba32float somehow? diff --git a/js/regl/bloomPass.js b/js/regl/bloomPass.js index c796039..4bd6d83 100644 --- a/js/regl/bloomPass.js +++ b/js/regl/bloomPass.js @@ -1,7 +1,7 @@ import { loadText, makePassFBO, makePass } from "./utils.js"; // The bloom pass is basically an added high-pass blur. -// The blur approximation is the sum of a pyramid of downscaled textures. +// The blur approximation is the sum of a pyramid of downscaled, blurred textures. const pyramidHeight = 5; const levelStrengths = Array(pyramidHeight) diff --git a/js/webgpu/bloomPass.js b/js/webgpu/bloomPass.js index 8f55b18..2822ab8 100644 --- a/js/webgpu/bloomPass.js +++ b/js/webgpu/bloomPass.js @@ -1,19 +1,20 @@ import { structs } from "/lib/gpu-buffer.js"; import { makeComputeTarget, makePyramidView, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js"; -export default (context) => { - const { config, device } = context; +// The bloom pass is basically an added blur of the rain pass's high-pass output. +// The blur approximation is the sum of a pyramid of downscaled, blurred textures. +export default ({ config, device }) => { const pyramidHeight = 4; const bloomSize = config.bloomSize; const bloomStrength = config.bloomStrength; const bloomRadius = 2; // Looks better with more, but is more costly - const enabled = true; + const enabled = bloomSize > 0 && bloomStrength > 0; // If there's no bloom to apply, return a no-op pass with an empty bloom texture if (!enabled) { - const emptyTexture = makeComputeTarget(device, 1, 1); + const emptyTexture = makeComputeTarget(device, [1, 1]); return makePass(null, (size, inputs) => ({ ...inputs, bloom: emptyTexture })); } @@ -24,6 +25,8 @@ export default (context) => { minFilter: "linear", }); + // The blur pipeline applies a blur in one direction; it's applied horizontally + // to the first image pyramid, and then vertically to the second image pyramid. let blurPipeline; let hBlurPyramid; let vBlurPyramid; @@ -31,6 +34,8 @@ export default (context) => { let vBlurBuffer; let hBlurBindGroups; let vBlurBindGroups; + + // The combine pipeline blends the last image pyramid's layers into the output. let combinePipeline; let combineBuffer; let combineBindGroup; @@ -63,6 +68,8 @@ export default (context) => { })(); const build = (screenSize, inputs) => { + + // Since the bloom is blurry, we downscale everything scaledScreenSize = screenSize.map((x) => Math.floor(x * bloomSize)); hBlurPyramid?.destroy(); @@ -74,17 +81,18 @@ export default (context) => { output?.destroy(); output = makeComputeTarget(device, scaledScreenSize); - const hBlurPyramidViews = []; - const vBlurPyramidViews = []; hBlurBindGroups = []; vBlurBindGroups = []; + // The first pyramid's level 1 texture is the input texture blurred. + // The subsequent levels of the pyramid are the preceding level blurred. + let srcView = inputs.highPass.createView(); for (let i = 0; i < pyramidHeight; i++) { - hBlurPyramidViews[i] = makePyramidView(hBlurPyramid, i); - vBlurPyramidViews[i] = makePyramidView(vBlurPyramid, i); - const srcView = i === 0 ? inputs.highPass.createView() : hBlurPyramidViews[i - 1]; - hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [hBlurBuffer, linearSampler, srcView, hBlurPyramidViews[i]]); - vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [vBlurBuffer, linearSampler, hBlurPyramidViews[i], vBlurPyramidViews[i]]); + const hBlurPyramidView = makePyramidView(hBlurPyramid, i); + const vBlurPyramidView = makePyramidView(vBlurPyramid, i); + hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [hBlurBuffer, linearSampler, srcView, hBlurPyramidView]); + vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [vBlurBuffer, linearSampler, hBlurPyramidView, vBlurPyramidView]); + srcView = hBlurPyramidView; } combineBindGroup = makeBindGroup(device, combinePipeline, 0, [combineBuffer, linearSampler, vBlurPyramid.createView(), output.createView()]); @@ -100,8 +108,11 @@ export default (context) => { computePass.setPipeline(blurPipeline); for (let i = 0; i < pyramidHeight; i++) { - const downsample = 2 ** -i; - const dispatchSize = [Math.ceil(Math.floor(scaledScreenSize[0] * downsample) / 32), Math.floor(Math.floor(scaledScreenSize[1] * downsample)), 1]; + const dispatchSize = [ + Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32), + Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)), + 1 + ]; computePass.setBindGroup(0, hBlurBindGroups[i]); computePass.dispatch(...dispatchSize); computePass.setBindGroup(0, vBlurBindGroups[i]); diff --git a/js/webgpu/endPass.js b/js/webgpu/endPass.js index 525bd30..0a9022b 100644 --- a/js/webgpu/endPass.js +++ b/js/webgpu/endPass.js @@ -1,14 +1,12 @@ import { loadShader, makeBindGroup, makePass } from "./utils.js"; +// Eventually, WebGPU will allow the output of the final pass in the pipeline to be copied to the canvas texture. +// Until then, this render pass does the job. + const numVerticesPerQuad = 2 * 3; -export default (context) => { - const { config, device, canvasFormat, canvasContext } = context; - - const linearSampler = device.createSampler({ - magFilter: "linear", - minFilter: "linear", - }); +export default ({ device, canvasFormat, canvasContext }) => { + const nearestSampler = device.createSampler(); const renderPassConfig = { colorAttachments: [ @@ -46,7 +44,7 @@ export default (context) => { })(); const build = (size, inputs) => { - renderBindGroup = makeBindGroup(device, renderPipeline, 0, [linearSampler, inputs.primary.createView()]); + renderBindGroup = makeBindGroup(device, renderPipeline, 0, [nearestSampler, inputs.primary.createView()]); return null; }; diff --git a/js/webgpu/imagePass.js b/js/webgpu/imagePass.js index 5997cb2..4e2ec1e 100644 --- a/js/webgpu/imagePass.js +++ b/js/webgpu/imagePass.js @@ -4,9 +4,7 @@ import { makeComputeTarget, loadTexture, loadShader, makeBindGroup, makePass } f const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg"; -export default (context) => { - const { config, device } = context; - +export default ({ config, device }) => { const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; const assets = [loadTexture(device, bgURL), loadShader(device, "shaders/wgsl/imagePass.wgsl")]; diff --git a/js/webgpu/palettePass.js b/js/webgpu/palettePass.js index 42f8327..37212ec 100644 --- a/js/webgpu/palettePass.js +++ b/js/webgpu/palettePass.js @@ -75,9 +75,7 @@ const makePalette = (device, paletteUniforms, entries) => { // won't persist across subsequent frames. This is a safe trick // in screen space. -export default (context) => { - const { config, device, timeBuffer } = context; - +export default ({ config, device, timeBuffer }) => { const linearSampler = device.createSampler({ magFilter: "linear", minFilter: "linear", diff --git a/js/webgpu/rainPass.js b/js/webgpu/rainPass.js index 3ff12ea..c22c30e 100644 --- a/js/webgpu/rainPass.js +++ b/js/webgpu/rainPass.js @@ -31,9 +31,7 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) => return makeUniformBuffer(device, configUniforms, configData); }; -export default (context) => { - const { config, device, timeBuffer, canvasFormat } = context; - +export default ({ config, device, timeBuffer, canvasFormat }) => { const assets = [loadTexture(device, config.glyphTexURL), loadShader(device, "shaders/wgsl/rainPass.wgsl")]; // The volumetric mode multiplies the number of columns diff --git a/js/webgpu/resurrectionPass.js b/js/webgpu/resurrectionPass.js index ff25ad6..0b149ed 100644 --- a/js/webgpu/resurrectionPass.js +++ b/js/webgpu/resurrectionPass.js @@ -11,9 +11,7 @@ import { loadShader, makeUniformBuffer, makeComputeTarget, makeBindGroup, makePa const numVerticesPerQuad = 2 * 3; -export default (context) => { - const { config, device, timeBuffer } = context; - +export default ({ config, device, timeBuffer }) => { const linearSampler = device.createSampler({ magFilter: "linear", minFilter: "linear", diff --git a/js/webgpu/stripePass.js b/js/webgpu/stripePass.js index 5f112d8..6a74fbd 100644 --- a/js/webgpu/stripePass.js +++ b/js/webgpu/stripePass.js @@ -37,9 +37,7 @@ const numVerticesPerQuad = 2 * 3; // won't persist across subsequent frames. This is a safe trick // in screen space. -export default (context, getInputs) => { - const { config, device, timeBuffer } = context; - +export default ({ config, device, timeBuffer }) => { // Expand and convert stripe colors into 1D texture data const input = "stripeColors" in config ? config.stripeColors.split(",").map(parseFloat) : config.effect === "pride" ? prideStripeColors : transPrideStripeColors; diff --git a/shaders/wgsl/endPass.wgsl b/shaders/wgsl/endPass.wgsl index 12c29e0..3ac1d32 100644 --- a/shaders/wgsl/endPass.wgsl +++ b/shaders/wgsl/endPass.wgsl @@ -1,4 +1,4 @@ -[[group(0), binding(0)]] var linearSampler : sampler; +[[group(0), binding(0)]] var nearestSampler : sampler; [[group(0), binding(1)]] var tex : texture_2d; struct VertOutput { @@ -15,5 +15,5 @@ struct VertOutput { [[stage(fragment)]] fn fragMain(input : VertOutput) -> [[location(0)]] vec4 { var uv = input.uv; uv.y = 1.0 - uv.y; - return textureSample( tex, linearSampler, uv ); + return textureSample( tex, nearestSampler, uv ); }