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.

This commit is contained in:
Rezmason
2021-11-15 01:05:05 -08:00
parent 1b61e304a5
commit b26155d20e
10 changed files with 38 additions and 40 deletions

View File

@@ -1,7 +1,6 @@
TODO: TODO:
WebGPU WebGPU
Make sure everything is properly commented
Update links in issues Update links in issues
Get rid of end pass once it's possible to copy a bgra8unorm to a canvas texture Get rid of end pass once it's possible to copy a bgra8unorm to a canvas texture
Switch to rgba32float somehow? Switch to rgba32float somehow?

View File

@@ -1,7 +1,7 @@
import { loadText, makePassFBO, makePass } from "./utils.js"; import { loadText, makePassFBO, makePass } from "./utils.js";
// The bloom pass is basically an added high-pass blur. // 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 pyramidHeight = 5;
const levelStrengths = Array(pyramidHeight) const levelStrengths = Array(pyramidHeight)

View File

@@ -1,19 +1,20 @@
import { structs } from "/lib/gpu-buffer.js"; import { structs } from "/lib/gpu-buffer.js";
import { makeComputeTarget, makePyramidView, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js"; import { makeComputeTarget, makePyramidView, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js";
export default (context) => { // The bloom pass is basically an added blur of the rain pass's high-pass output.
const { config, device } = context; // The blur approximation is the sum of a pyramid of downscaled, blurred textures.
export default ({ config, device }) => {
const pyramidHeight = 4; const pyramidHeight = 4;
const bloomSize = config.bloomSize; const bloomSize = config.bloomSize;
const bloomStrength = config.bloomStrength; const bloomStrength = config.bloomStrength;
const bloomRadius = 2; // Looks better with more, but is more costly 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 there's no bloom to apply, return a no-op pass with an empty bloom texture
if (!enabled) { if (!enabled) {
const emptyTexture = makeComputeTarget(device, 1, 1); const emptyTexture = makeComputeTarget(device, [1, 1]);
return makePass(null, (size, inputs) => ({ ...inputs, bloom: emptyTexture })); return makePass(null, (size, inputs) => ({ ...inputs, bloom: emptyTexture }));
} }
@@ -24,6 +25,8 @@ export default (context) => {
minFilter: "linear", 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 blurPipeline;
let hBlurPyramid; let hBlurPyramid;
let vBlurPyramid; let vBlurPyramid;
@@ -31,6 +34,8 @@ export default (context) => {
let vBlurBuffer; let vBlurBuffer;
let hBlurBindGroups; let hBlurBindGroups;
let vBlurBindGroups; let vBlurBindGroups;
// The combine pipeline blends the last image pyramid's layers into the output.
let combinePipeline; let combinePipeline;
let combineBuffer; let combineBuffer;
let combineBindGroup; let combineBindGroup;
@@ -63,6 +68,8 @@ export default (context) => {
})(); })();
const build = (screenSize, inputs) => { const build = (screenSize, inputs) => {
// Since the bloom is blurry, we downscale everything
scaledScreenSize = screenSize.map((x) => Math.floor(x * bloomSize)); scaledScreenSize = screenSize.map((x) => Math.floor(x * bloomSize));
hBlurPyramid?.destroy(); hBlurPyramid?.destroy();
@@ -74,17 +81,18 @@ export default (context) => {
output?.destroy(); output?.destroy();
output = makeComputeTarget(device, scaledScreenSize); output = makeComputeTarget(device, scaledScreenSize);
const hBlurPyramidViews = [];
const vBlurPyramidViews = [];
hBlurBindGroups = []; hBlurBindGroups = [];
vBlurBindGroups = []; 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++) { for (let i = 0; i < pyramidHeight; i++) {
hBlurPyramidViews[i] = makePyramidView(hBlurPyramid, i); const hBlurPyramidView = makePyramidView(hBlurPyramid, i);
vBlurPyramidViews[i] = makePyramidView(vBlurPyramid, i); const vBlurPyramidView = makePyramidView(vBlurPyramid, i);
const srcView = i === 0 ? inputs.highPass.createView() : hBlurPyramidViews[i - 1]; hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [hBlurBuffer, linearSampler, srcView, hBlurPyramidView]);
hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [hBlurBuffer, linearSampler, srcView, hBlurPyramidViews[i]]); vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [vBlurBuffer, linearSampler, hBlurPyramidView, vBlurPyramidView]);
vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [vBlurBuffer, linearSampler, hBlurPyramidViews[i], vBlurPyramidViews[i]]); srcView = hBlurPyramidView;
} }
combineBindGroup = makeBindGroup(device, combinePipeline, 0, [combineBuffer, linearSampler, vBlurPyramid.createView(), output.createView()]); combineBindGroup = makeBindGroup(device, combinePipeline, 0, [combineBuffer, linearSampler, vBlurPyramid.createView(), output.createView()]);
@@ -100,8 +108,11 @@ export default (context) => {
computePass.setPipeline(blurPipeline); computePass.setPipeline(blurPipeline);
for (let i = 0; i < pyramidHeight; i++) { for (let i = 0; i < pyramidHeight; i++) {
const downsample = 2 ** -i; const dispatchSize = [
const dispatchSize = [Math.ceil(Math.floor(scaledScreenSize[0] * downsample) / 32), Math.floor(Math.floor(scaledScreenSize[1] * downsample)), 1]; Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32),
Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)),
1
];
computePass.setBindGroup(0, hBlurBindGroups[i]); computePass.setBindGroup(0, hBlurBindGroups[i]);
computePass.dispatch(...dispatchSize); computePass.dispatch(...dispatchSize);
computePass.setBindGroup(0, vBlurBindGroups[i]); computePass.setBindGroup(0, vBlurBindGroups[i]);

View File

@@ -1,14 +1,12 @@
import { loadShader, makeBindGroup, makePass } from "./utils.js"; 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; const numVerticesPerQuad = 2 * 3;
export default (context) => { export default ({ device, canvasFormat, canvasContext }) => {
const { config, device, canvasFormat, canvasContext } = context; const nearestSampler = device.createSampler();
const linearSampler = device.createSampler({
magFilter: "linear",
minFilter: "linear",
});
const renderPassConfig = { const renderPassConfig = {
colorAttachments: [ colorAttachments: [
@@ -46,7 +44,7 @@ export default (context) => {
})(); })();
const build = (size, inputs) => { const build = (size, inputs) => {
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [linearSampler, inputs.primary.createView()]); renderBindGroup = makeBindGroup(device, renderPipeline, 0, [nearestSampler, inputs.primary.createView()]);
return null; return null;
}; };

View File

@@ -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"; const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg";
export default (context) => { export default ({ config, device }) => {
const { config, device } = context;
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
const assets = [loadTexture(device, bgURL), loadShader(device, "shaders/wgsl/imagePass.wgsl")]; const assets = [loadTexture(device, bgURL), loadShader(device, "shaders/wgsl/imagePass.wgsl")];

View File

@@ -75,9 +75,7 @@ const makePalette = (device, paletteUniforms, entries) => {
// won't persist across subsequent frames. This is a safe trick // won't persist across subsequent frames. This is a safe trick
// in screen space. // in screen space.
export default (context) => { export default ({ config, device, timeBuffer }) => {
const { config, device, timeBuffer } = context;
const linearSampler = device.createSampler({ const linearSampler = device.createSampler({
magFilter: "linear", magFilter: "linear",
minFilter: "linear", minFilter: "linear",

View File

@@ -31,9 +31,7 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) =>
return makeUniformBuffer(device, configUniforms, configData); return makeUniformBuffer(device, configUniforms, configData);
}; };
export default (context) => { export default ({ config, device, timeBuffer, canvasFormat }) => {
const { config, device, timeBuffer, canvasFormat } = context;
const assets = [loadTexture(device, config.glyphTexURL), loadShader(device, "shaders/wgsl/rainPass.wgsl")]; const assets = [loadTexture(device, config.glyphTexURL), loadShader(device, "shaders/wgsl/rainPass.wgsl")];
// The volumetric mode multiplies the number of columns // The volumetric mode multiplies the number of columns

View File

@@ -11,9 +11,7 @@ import { loadShader, makeUniformBuffer, makeComputeTarget, makeBindGroup, makePa
const numVerticesPerQuad = 2 * 3; const numVerticesPerQuad = 2 * 3;
export default (context) => { export default ({ config, device, timeBuffer }) => {
const { config, device, timeBuffer } = context;
const linearSampler = device.createSampler({ const linearSampler = device.createSampler({
magFilter: "linear", magFilter: "linear",
minFilter: "linear", minFilter: "linear",

View File

@@ -37,9 +37,7 @@ const numVerticesPerQuad = 2 * 3;
// won't persist across subsequent frames. This is a safe trick // won't persist across subsequent frames. This is a safe trick
// in screen space. // in screen space.
export default (context, getInputs) => { export default ({ config, device, timeBuffer }) => {
const { config, device, timeBuffer } = context;
// Expand and convert stripe colors into 1D texture data // Expand and convert stripe colors into 1D texture data
const input = const input =
"stripeColors" in config ? config.stripeColors.split(",").map(parseFloat) : config.effect === "pride" ? prideStripeColors : transPrideStripeColors; "stripeColors" in config ? config.stripeColors.split(",").map(parseFloat) : config.effect === "pride" ? prideStripeColors : transPrideStripeColors;

View File

@@ -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<f32>; [[group(0), binding(1)]] var tex : texture_2d<f32>;
struct VertOutput { struct VertOutput {
@@ -15,5 +15,5 @@ struct VertOutput {
[[stage(fragment)]] fn fragMain(input : VertOutput) -> [[location(0)]] vec4<f32> { [[stage(fragment)]] fn fragMain(input : VertOutput) -> [[location(0)]] vec4<f32> {
var uv = input.uv; var uv = input.uv;
uv.y = 1.0 - uv.y; uv.y = 1.0 - uv.y;
return textureSample( tex, linearSampler, uv ); return textureSample( tex, nearestSampler, uv );
} }