mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-14 12:29:30 -07:00
131 lines
4.4 KiB
JavaScript
131 lines
4.4 KiB
JavaScript
import { structs } from "/lib/gpu-buffer.js";
|
|
import { makeComputeTarget, makePyramidView, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js";
|
|
|
|
// 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 = 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]);
|
|
return makePass(null, (size, inputs) => ({ ...inputs, bloom: emptyTexture }));
|
|
}
|
|
|
|
const assets = [loadShader(device, "shaders/wgsl/bloomBlur.wgsl"), loadShader(device, "shaders/wgsl/bloomCombine.wgsl")];
|
|
|
|
const linearSampler = device.createSampler({
|
|
magFilter: "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 hBlurPyramid;
|
|
let vBlurPyramid;
|
|
let hBlurBuffer;
|
|
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;
|
|
let output;
|
|
let scaledScreenSize;
|
|
|
|
const loaded = (async () => {
|
|
const [blurShader, combineShader] = await Promise.all(assets);
|
|
|
|
blurPipeline = device.createComputePipeline({
|
|
compute: {
|
|
module: blurShader.module,
|
|
entryPoint: "computeMain",
|
|
},
|
|
});
|
|
|
|
combinePipeline = device.createComputePipeline({
|
|
compute: {
|
|
module: combineShader.module,
|
|
entryPoint: "computeMain",
|
|
},
|
|
});
|
|
|
|
const blurUniforms = structs.from(blurShader.code).Config;
|
|
hBlurBuffer = makeUniformBuffer(device, blurUniforms, { bloomRadius, direction: [1, 0] });
|
|
vBlurBuffer = makeUniformBuffer(device, blurUniforms, { bloomRadius, direction: [0, 1] });
|
|
|
|
const combineUniforms = structs.from(combineShader.code).Config;
|
|
combineBuffer = makeUniformBuffer(device, combineUniforms, { bloomStrength, pyramidHeight });
|
|
})();
|
|
|
|
const build = (screenSize, inputs) => {
|
|
|
|
// Since the bloom is blurry, we downscale everything
|
|
scaledScreenSize = screenSize.map((x) => Math.floor(x * bloomSize));
|
|
|
|
hBlurPyramid?.destroy();
|
|
hBlurPyramid = makeComputeTarget(device, scaledScreenSize, pyramidHeight);
|
|
|
|
vBlurPyramid?.destroy();
|
|
vBlurPyramid = makeComputeTarget(device, scaledScreenSize, pyramidHeight);
|
|
|
|
output?.destroy();
|
|
output = makeComputeTarget(device, scaledScreenSize);
|
|
|
|
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++) {
|
|
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()]);
|
|
|
|
return {
|
|
...inputs,
|
|
bloom: output,
|
|
};
|
|
};
|
|
|
|
const run = (encoder) => {
|
|
const computePass = encoder.beginComputePass();
|
|
|
|
computePass.setPipeline(blurPipeline);
|
|
for (let i = 0; i < pyramidHeight; i++) {
|
|
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]);
|
|
computePass.dispatch(...dispatchSize);
|
|
}
|
|
|
|
computePass.setPipeline(combinePipeline);
|
|
computePass.setBindGroup(0, combineBindGroup);
|
|
computePass.dispatch(Math.ceil(scaledScreenSize[0] / 32), scaledScreenSize[1], 1);
|
|
|
|
computePass.endPass();
|
|
};
|
|
|
|
return makePass(loaded, build, run);
|
|
};
|