mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-18 22:29:28 -07:00
Refactoring the pass and pipeline, so that inputs and size are handed to and returned from the build function (formerly setSize). This is now the earliest place to build bind groups, which makes sense, because it's also the earliest place to create textures that are proportional to the size of the canvas.
This commit is contained in:
2
TODO.txt
2
TODO.txt
@@ -1,8 +1,6 @@
|
|||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
WebGPU
|
WebGPU
|
||||||
Rename setSize to rebuild — it is the function that receives inputs as well as screen size
|
|
||||||
Create and store the bloom bind groups on resize
|
|
||||||
Make sure everything is properly commented
|
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
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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, getInputs) => {
|
export default (context) => {
|
||||||
const { config, device } = context;
|
const { config, device } = context;
|
||||||
|
|
||||||
const pyramidHeight = 4;
|
const pyramidHeight = 4;
|
||||||
@@ -14,8 +14,7 @@ export default (context, getInputs) => {
|
|||||||
// 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);
|
||||||
const getOutputs = () => ({ ...getInputs(), bloom: emptyTexture });
|
return makePass(null, (size, inputs) => ({ ...inputs, bloom: emptyTexture }));
|
||||||
return makePass(getOutputs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const assets = [loadShader(device, "shaders/wgsl/bloomBlur.wgsl"), loadShader(device, "shaders/wgsl/bloomCombine.wgsl")];
|
const assets = [loadShader(device, "shaders/wgsl/bloomBlur.wgsl"), loadShader(device, "shaders/wgsl/bloomCombine.wgsl")];
|
||||||
@@ -26,21 +25,19 @@ export default (context, getInputs) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let blurPipeline;
|
let blurPipeline;
|
||||||
let combinePipeline;
|
|
||||||
let hBlurPyramid;
|
let hBlurPyramid;
|
||||||
let vBlurPyramid;
|
let vBlurPyramid;
|
||||||
let hBlurBuffer;
|
let hBlurBuffer;
|
||||||
let vBlurBuffer;
|
let vBlurBuffer;
|
||||||
|
let hBlurBindGroups;
|
||||||
|
let vBlurBindGroups;
|
||||||
|
let combinePipeline;
|
||||||
let combineBuffer;
|
let combineBuffer;
|
||||||
|
let combineBindGroup;
|
||||||
let output;
|
let output;
|
||||||
let scaledScreenSize;
|
let scaledScreenSize;
|
||||||
|
|
||||||
const getOutputs = () => ({
|
const loaded = (async () => {
|
||||||
primary: getInputs().primary,
|
|
||||||
bloom: output,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ready = (async () => {
|
|
||||||
const [blurShader, combineShader] = await Promise.all(assets);
|
const [blurShader, combineShader] = await Promise.all(assets);
|
||||||
|
|
||||||
blurPipeline = device.createComputePipeline({
|
blurPipeline = device.createComputePipeline({
|
||||||
@@ -65,47 +62,58 @@ export default (context, getInputs) => {
|
|||||||
combineBuffer = makeUniformBuffer(device, combineUniforms, { bloomStrength, pyramidHeight });
|
combineBuffer = makeUniformBuffer(device, combineUniforms, { bloomStrength, pyramidHeight });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const setSize = (width, height) => {
|
const build = (screenSize, inputs) => {
|
||||||
|
scaledScreenSize = screenSize.map((x) => Math.floor(x * bloomSize));
|
||||||
|
|
||||||
hBlurPyramid?.destroy();
|
hBlurPyramid?.destroy();
|
||||||
hBlurPyramid = makeComputeTarget(device, Math.floor(width * bloomSize), Math.floor(height * bloomSize), pyramidHeight);
|
hBlurPyramid = makeComputeTarget(device, scaledScreenSize, pyramidHeight);
|
||||||
|
|
||||||
vBlurPyramid?.destroy();
|
vBlurPyramid?.destroy();
|
||||||
vBlurPyramid = makeComputeTarget(device, Math.floor(width * bloomSize), Math.floor(height * bloomSize), pyramidHeight);
|
vBlurPyramid = makeComputeTarget(device, scaledScreenSize, pyramidHeight);
|
||||||
|
|
||||||
output?.destroy();
|
output?.destroy();
|
||||||
output = makeComputeTarget(device, Math.floor(width * bloomSize), Math.floor(height * bloomSize));
|
output = makeComputeTarget(device, scaledScreenSize);
|
||||||
scaledScreenSize = [Math.floor(width * bloomSize), Math.floor(height * bloomSize)];
|
|
||||||
|
const hBlurPyramidViews = [];
|
||||||
|
const vBlurPyramidViews = [];
|
||||||
|
hBlurBindGroups = [];
|
||||||
|
vBlurBindGroups = [];
|
||||||
|
|
||||||
|
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]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
combineBindGroup = makeBindGroup(device, combinePipeline, 0, [combineBuffer, linearSampler, vBlurPyramid.createView(), output.createView()]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...inputs,
|
||||||
|
bloom: output,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const execute = (encoder) => {
|
const run = (encoder) => {
|
||||||
const inputs = getInputs();
|
|
||||||
const tex = inputs.primary;
|
|
||||||
|
|
||||||
const computePass = encoder.beginComputePass();
|
const computePass = encoder.beginComputePass();
|
||||||
|
|
||||||
computePass.setPipeline(blurPipeline);
|
computePass.setPipeline(blurPipeline);
|
||||||
const hBlurPyramidViews = Array(pyramidHeight)
|
|
||||||
.fill()
|
|
||||||
.map((_, level) => makePyramidView(hBlurPyramid, level));
|
|
||||||
const vBlurPyramidViews = Array(pyramidHeight)
|
|
||||||
.fill()
|
|
||||||
.map((_, level) => makePyramidView(vBlurPyramid, level));
|
|
||||||
for (let i = 0; i < pyramidHeight; i++) {
|
for (let i = 0; i < pyramidHeight; i++) {
|
||||||
const downsample = 2 ** -i;
|
const downsample = 2 ** -i;
|
||||||
const size = [Math.ceil(Math.floor(scaledScreenSize[0] * downsample) / 32), Math.floor(Math.floor(scaledScreenSize[1] * downsample)), 1];
|
const dispatchSize = [Math.ceil(Math.floor(scaledScreenSize[0] * downsample) / 32), Math.floor(Math.floor(scaledScreenSize[1] * downsample)), 1];
|
||||||
const srcView = i === 0 ? tex.createView() : hBlurPyramidViews[i - 1];
|
computePass.setBindGroup(0, hBlurBindGroups[i]);
|
||||||
computePass.setBindGroup(0, makeBindGroup(device, blurPipeline, 0, [hBlurBuffer, linearSampler, srcView, hBlurPyramidViews[i]]));
|
computePass.dispatch(...dispatchSize);
|
||||||
computePass.dispatch(...size);
|
computePass.setBindGroup(0, vBlurBindGroups[i]);
|
||||||
computePass.setBindGroup(0, makeBindGroup(device, blurPipeline, 0, [vBlurBuffer, linearSampler, hBlurPyramidViews[i], vBlurPyramidViews[i]]));
|
computePass.dispatch(...dispatchSize);
|
||||||
computePass.dispatch(...size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
computePass.setPipeline(combinePipeline);
|
computePass.setPipeline(combinePipeline);
|
||||||
computePass.setBindGroup(0, makeBindGroup(device, combinePipeline, 0, [combineBuffer, linearSampler, vBlurPyramid.createView(), output.createView()]));
|
computePass.setBindGroup(0, combineBindGroup);
|
||||||
computePass.dispatch(Math.ceil(scaledScreenSize[0] / 32), scaledScreenSize[1], 1);
|
computePass.dispatch(Math.ceil(scaledScreenSize[0] / 32), scaledScreenSize[1], 1);
|
||||||
|
|
||||||
computePass.endPass();
|
computePass.endPass();
|
||||||
};
|
};
|
||||||
|
|
||||||
return makePass(getOutputs, ready, setSize, execute);
|
return makePass(loaded, build, run);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { loadShader, makeBindGroup, makePass } from "./utils.js";
|
|||||||
|
|
||||||
const numVerticesPerQuad = 2 * 3;
|
const numVerticesPerQuad = 2 * 3;
|
||||||
|
|
||||||
export default (context, getInputs) => {
|
export default (context) => {
|
||||||
const { config, device, canvasFormat, canvasContext } = context;
|
const { config, device, canvasFormat, canvasContext } = context;
|
||||||
|
|
||||||
const linearSampler = device.createSampler({
|
const linearSampler = device.createSampler({
|
||||||
@@ -21,10 +21,11 @@ export default (context, getInputs) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let renderPipeline;
|
let renderPipeline;
|
||||||
|
let renderBindGroup;
|
||||||
|
|
||||||
const assets = [loadShader(device, "shaders/wgsl/endPass.wgsl")];
|
const assets = [loadShader(device, "shaders/wgsl/endPass.wgsl")];
|
||||||
|
|
||||||
const ready = (async () => {
|
const loaded = (async () => {
|
||||||
const [imageShader] = await Promise.all(assets);
|
const [imageShader] = await Promise.all(assets);
|
||||||
|
|
||||||
renderPipeline = device.createRenderPipeline({
|
renderPipeline = device.createRenderPipeline({
|
||||||
@@ -44,10 +45,12 @@ export default (context, getInputs) => {
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const execute = (encoder) => {
|
const build = (size, inputs) => {
|
||||||
const inputs = getInputs();
|
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [linearSampler, inputs.primary.createView()]);
|
||||||
const tex = inputs.primary;
|
return null;
|
||||||
const renderBindGroup = makeBindGroup(device, renderPipeline, 0, [linearSampler, tex.createView()]);
|
};
|
||||||
|
|
||||||
|
const run = (encoder) => {
|
||||||
renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView();
|
renderPassConfig.colorAttachments[0].view = canvasContext.getCurrentTexture().createView();
|
||||||
const renderPass = encoder.beginRenderPass(renderPassConfig);
|
const renderPass = encoder.beginRenderPass(renderPassConfig);
|
||||||
renderPass.setPipeline(renderPipeline);
|
renderPass.setPipeline(renderPipeline);
|
||||||
@@ -56,5 +59,5 @@ export default (context, getInputs) => {
|
|||||||
renderPass.endPass();
|
renderPass.endPass();
|
||||||
};
|
};
|
||||||
|
|
||||||
return makePass(null, ready, null, execute);
|
return makePass(loaded, build, run);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +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, getInputs) => {
|
export default (context) => {
|
||||||
const { config, device } = context;
|
const { config, device } = context;
|
||||||
|
|
||||||
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
|
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
|
||||||
@@ -19,12 +19,9 @@ export default (context, getInputs) => {
|
|||||||
let output;
|
let output;
|
||||||
let screenSize;
|
let screenSize;
|
||||||
let backgroundTex;
|
let backgroundTex;
|
||||||
|
let computeBindGroup;
|
||||||
|
|
||||||
const getOutputs = () => ({
|
const loaded = (async () => {
|
||||||
primary: output,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ready = (async () => {
|
|
||||||
const [bgTex, imageShader] = await Promise.all(assets);
|
const [bgTex, imageShader] = await Promise.all(assets);
|
||||||
|
|
||||||
backgroundTex = bgTex;
|
backgroundTex = bgTex;
|
||||||
@@ -37,29 +34,27 @@ export default (context, getInputs) => {
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const setSize = (width, height) => {
|
const build = (size, inputs) => {
|
||||||
output?.destroy();
|
output?.destroy();
|
||||||
output = makeComputeTarget(device, width, height);
|
output = makeComputeTarget(device, size);
|
||||||
screenSize = [width, height];
|
screenSize = size;
|
||||||
};
|
computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
||||||
|
|
||||||
const execute = (encoder) => {
|
|
||||||
const inputs = getInputs();
|
|
||||||
const tex = inputs.primary;
|
|
||||||
const bloomTex = inputs.bloom;
|
|
||||||
const computePass = encoder.beginComputePass();
|
|
||||||
computePass.setPipeline(computePipeline);
|
|
||||||
const computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
|
||||||
linearSampler,
|
linearSampler,
|
||||||
tex.createView(),
|
inputs.primary.createView(),
|
||||||
bloomTex.createView(),
|
inputs.bloom.createView(),
|
||||||
backgroundTex.createView(),
|
backgroundTex.createView(),
|
||||||
output.createView(),
|
output.createView(),
|
||||||
]);
|
]);
|
||||||
|
return { primary: output };
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = (encoder) => {
|
||||||
|
const computePass = encoder.beginComputePass();
|
||||||
|
computePass.setPipeline(computePipeline);
|
||||||
computePass.setBindGroup(0, computeBindGroup);
|
computePass.setBindGroup(0, computeBindGroup);
|
||||||
computePass.dispatch(Math.ceil(screenSize[0] / 32), screenSize[1], 1);
|
computePass.dispatch(Math.ceil(screenSize[0] / 32), screenSize[1], 1);
|
||||||
computePass.endPass();
|
computePass.endPass();
|
||||||
};
|
};
|
||||||
|
|
||||||
return makePass(getOutputs, ready, setSize, execute);
|
return makePass(loaded, build, run);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default async (canvas, config) => {
|
|||||||
const effectName = config.effect in effects ? config.effect : "plain";
|
const effectName = config.effect in effects ? config.effect : "plain";
|
||||||
const pipeline = makePipeline(context, [makeRain, makeBloomPass, effects[effectName], makeEndPass]);
|
const pipeline = makePipeline(context, [makeRain, makeBloomPass, effects[effectName], makeEndPass]);
|
||||||
|
|
||||||
await Promise.all(pipeline.map((step) => step.ready));
|
await Promise.all(pipeline.map((step) => step.loaded));
|
||||||
|
|
||||||
let frames = 0;
|
let frames = 0;
|
||||||
let start = NaN;
|
let start = NaN;
|
||||||
@@ -67,14 +67,14 @@ export default async (canvas, config) => {
|
|||||||
if (canvasSize[0] !== canvasConfig.size[0] || canvasSize[1] !== canvasConfig.size[1]) {
|
if (canvasSize[0] !== canvasConfig.size[0] || canvasSize[1] !== canvasConfig.size[1]) {
|
||||||
canvasConfig.size = canvasSize;
|
canvasConfig.size = canvasSize;
|
||||||
canvasContext.configure(canvasConfig);
|
canvasContext.configure(canvasConfig);
|
||||||
pipeline.forEach((step) => step.setSize(...canvasSize));
|
pipeline.reduce((outputs, step) => step.build(canvasSize, outputs), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
device.queue.writeBuffer(timeBuffer, 0, timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames }));
|
device.queue.writeBuffer(timeBuffer, 0, timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames }));
|
||||||
frames++;
|
frames++;
|
||||||
|
|
||||||
const encoder = device.createCommandEncoder();
|
const encoder = device.createCommandEncoder();
|
||||||
pipeline.forEach((step) => step.execute(encoder));
|
pipeline.forEach((step) => step.run(encoder));
|
||||||
// Eventually, when WebGPU allows it, we'll remove the endPass and just copy from our pipeline's output to the canvas texture.
|
// Eventually, when WebGPU allows it, we'll remove the endPass and just copy from our pipeline's output to the canvas texture.
|
||||||
// encoder.copyTextureToTexture({ texture: pipeline[pipeline.length - 1].getOutputs().primary }, { texture: canvasContext.getCurrentTexture() }, canvasSize);
|
// encoder.copyTextureToTexture({ texture: pipeline[pipeline.length - 1].getOutputs().primary }, { texture: canvasContext.getCurrentTexture() }, canvasSize);
|
||||||
device.queue.submit([encoder.finish()]);
|
device.queue.submit([encoder.finish()]);
|
||||||
|
|||||||
@@ -75,7 +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, getInputs) => {
|
export default (context) => {
|
||||||
const { config, device, timeBuffer } = context;
|
const { config, device, timeBuffer } = context;
|
||||||
|
|
||||||
const linearSampler = device.createSampler({
|
const linearSampler = device.createSampler({
|
||||||
@@ -86,16 +86,13 @@ export default (context, getInputs) => {
|
|||||||
let computePipeline;
|
let computePipeline;
|
||||||
let configBuffer;
|
let configBuffer;
|
||||||
let paletteBuffer;
|
let paletteBuffer;
|
||||||
|
let computeBindGroup;
|
||||||
let output;
|
let output;
|
||||||
let screenSize;
|
let screenSize;
|
||||||
|
|
||||||
const getOutputs = () => ({
|
|
||||||
primary: output,
|
|
||||||
});
|
|
||||||
|
|
||||||
const assets = [loadShader(device, "shaders/wgsl/palettePass.wgsl")];
|
const assets = [loadShader(device, "shaders/wgsl/palettePass.wgsl")];
|
||||||
|
|
||||||
const ready = (async () => {
|
const loaded = (async () => {
|
||||||
const [paletteShader] = await Promise.all(assets);
|
const [paletteShader] = await Promise.all(assets);
|
||||||
|
|
||||||
computePipeline = device.createComputePipeline({
|
computePipeline = device.createComputePipeline({
|
||||||
@@ -113,31 +110,29 @@ export default (context, getInputs) => {
|
|||||||
paletteBuffer = makePalette(device, paletteUniforms, config.paletteEntries);
|
paletteBuffer = makePalette(device, paletteUniforms, config.paletteEntries);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const setSize = (width, height) => {
|
const build = (size, inputs) => {
|
||||||
output?.destroy();
|
output?.destroy();
|
||||||
output = makeComputeTarget(device, width, height);
|
output = makeComputeTarget(device, size);
|
||||||
screenSize = [width, height];
|
screenSize = size;
|
||||||
};
|
computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
||||||
|
|
||||||
const execute = (encoder) => {
|
|
||||||
const inputs = getInputs();
|
|
||||||
const tex = inputs.primary;
|
|
||||||
const bloomTex = inputs.bloom;
|
|
||||||
const computePass = encoder.beginComputePass();
|
|
||||||
computePass.setPipeline(computePipeline);
|
|
||||||
const computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
|
||||||
configBuffer,
|
configBuffer,
|
||||||
paletteBuffer,
|
paletteBuffer,
|
||||||
timeBuffer,
|
timeBuffer,
|
||||||
linearSampler,
|
linearSampler,
|
||||||
tex.createView(),
|
inputs.primary.createView(),
|
||||||
bloomTex.createView(),
|
inputs.bloom.createView(),
|
||||||
output.createView(),
|
output.createView(),
|
||||||
]);
|
]);
|
||||||
|
return { primary: output };
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = (encoder) => {
|
||||||
|
const computePass = encoder.beginComputePass();
|
||||||
|
computePass.setPipeline(computePipeline);
|
||||||
computePass.setBindGroup(0, computeBindGroup);
|
computePass.setBindGroup(0, computeBindGroup);
|
||||||
computePass.dispatch(Math.ceil(screenSize[0] / 32), screenSize[1], 1);
|
computePass.dispatch(Math.ceil(screenSize[0] / 32), screenSize[1], 1);
|
||||||
computePass.endPass();
|
computePass.endPass();
|
||||||
};
|
};
|
||||||
|
|
||||||
return makePass(getOutputs, ready, setSize, execute);
|
return makePass(loaded, build, run);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) =>
|
|||||||
return makeUniformBuffer(device, configUniforms, configData);
|
return makeUniformBuffer(device, configUniforms, configData);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (context, getInputs) => {
|
export default (context) => {
|
||||||
const { config, device, timeBuffer, canvasFormat } = context;
|
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")];
|
||||||
@@ -92,12 +92,7 @@ export default (context, getInputs) => {
|
|||||||
let output;
|
let output;
|
||||||
let highPassOutput;
|
let highPassOutput;
|
||||||
|
|
||||||
const getOutputs = () => ({
|
const loaded = (async () => {
|
||||||
primary: output,
|
|
||||||
highPass: highPassOutput,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ready = (async () => {
|
|
||||||
const [msdfTexture, rainShader] = await Promise.all(assets);
|
const [msdfTexture, rainShader] = await Promise.all(assets);
|
||||||
|
|
||||||
const rainShaderUniforms = structs.from(rainShader.code);
|
const rainShaderUniforms = structs.from(rainShader.code);
|
||||||
@@ -150,9 +145,9 @@ export default (context, getInputs) => {
|
|||||||
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [configBuffer, timeBuffer, sceneBuffer, linearSampler, msdfTexture.createView(), cellsBuffer]);
|
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [configBuffer, timeBuffer, sceneBuffer, linearSampler, msdfTexture.createView(), cellsBuffer]);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const setSize = (width, height) => {
|
const build = (size) => {
|
||||||
// Update scene buffer: camera and transform math for the volumetric mode
|
// Update scene buffer: camera and transform math for the volumetric mode
|
||||||
const aspectRatio = width / height;
|
const aspectRatio = size[0] / size[1];
|
||||||
if (config.effect === "none") {
|
if (config.effect === "none") {
|
||||||
if (aspectRatio > 1) {
|
if (aspectRatio > 1) {
|
||||||
mat4.orthoZO(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000);
|
mat4.orthoZO(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000);
|
||||||
@@ -167,13 +162,18 @@ export default (context, getInputs) => {
|
|||||||
|
|
||||||
// Update
|
// Update
|
||||||
output?.destroy();
|
output?.destroy();
|
||||||
output = makeRenderTarget(device, width, height, canvasFormat);
|
output = makeRenderTarget(device, size, canvasFormat);
|
||||||
|
|
||||||
highPassOutput?.destroy();
|
highPassOutput?.destroy();
|
||||||
highPassOutput = makeRenderTarget(device, width, height, canvasFormat);
|
highPassOutput = makeRenderTarget(device, size, canvasFormat);
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary: output,
|
||||||
|
highPass: highPassOutput,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const execute = (encoder) => {
|
const run = (encoder) => {
|
||||||
// We render the code into an Target using MSDFs: https://github.com/Chlumsky/msdfgen
|
// We render the code into an Target using MSDFs: https://github.com/Chlumsky/msdfgen
|
||||||
|
|
||||||
const computePass = encoder.beginComputePass();
|
const computePass = encoder.beginComputePass();
|
||||||
@@ -191,5 +191,5 @@ export default (context, getInputs) => {
|
|||||||
renderPass.endPass();
|
renderPass.endPass();
|
||||||
};
|
};
|
||||||
|
|
||||||
return makePass(getOutputs, ready, setSize, execute);
|
return makePass(loaded, build, run);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { loadShader, makeUniformBuffer, makeComputeTarget, makeBindGroup, makePa
|
|||||||
|
|
||||||
const numVerticesPerQuad = 2 * 3;
|
const numVerticesPerQuad = 2 * 3;
|
||||||
|
|
||||||
export default (context, getInputs) => {
|
export default (context) => {
|
||||||
const { config, device, timeBuffer } = context;
|
const { config, device, timeBuffer } = context;
|
||||||
|
|
||||||
const linearSampler = device.createSampler({
|
const linearSampler = device.createSampler({
|
||||||
@@ -21,12 +21,13 @@ export default (context, getInputs) => {
|
|||||||
|
|
||||||
let computePipeline;
|
let computePipeline;
|
||||||
let configBuffer;
|
let configBuffer;
|
||||||
|
let computeBindGroup;
|
||||||
let output;
|
let output;
|
||||||
let screenSize;
|
let screenSize;
|
||||||
|
|
||||||
const assets = [loadShader(device, "shaders/wgsl/resurrectionPass.wgsl")];
|
const assets = [loadShader(device, "shaders/wgsl/resurrectionPass.wgsl")];
|
||||||
|
|
||||||
const ready = (async () => {
|
const loaded = (async () => {
|
||||||
const [resurrectionShader] = await Promise.all(assets);
|
const [resurrectionShader] = await Promise.all(assets);
|
||||||
|
|
||||||
computePipeline = device.createComputePipeline({
|
computePipeline = device.createComputePipeline({
|
||||||
@@ -40,34 +41,32 @@ export default (context, getInputs) => {
|
|||||||
configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: 0.05, backgroundColor: config.backgroundColor });
|
configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: 0.05, backgroundColor: config.backgroundColor });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const setSize = (width, height) => {
|
const build = (size, inputs) => {
|
||||||
output?.destroy();
|
output?.destroy();
|
||||||
output = makeComputeTarget(device, width, height);
|
output = makeComputeTarget(device, size);
|
||||||
screenSize = [width, height];
|
screenSize = size;
|
||||||
};
|
|
||||||
|
|
||||||
const getOutputs = () => ({
|
computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
||||||
primary: output,
|
|
||||||
});
|
|
||||||
|
|
||||||
const execute = (encoder) => {
|
|
||||||
const inputs = getInputs();
|
|
||||||
const tex = inputs.primary;
|
|
||||||
const bloomTex = inputs.bloom;
|
|
||||||
const computePass = encoder.beginComputePass();
|
|
||||||
computePass.setPipeline(computePipeline);
|
|
||||||
const computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
|
||||||
configBuffer,
|
configBuffer,
|
||||||
timeBuffer,
|
timeBuffer,
|
||||||
linearSampler,
|
linearSampler,
|
||||||
tex.createView(),
|
inputs.primary.createView(),
|
||||||
bloomTex.createView(),
|
inputs.bloom.createView(),
|
||||||
output.createView(),
|
output.createView(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary: output,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = (encoder) => {
|
||||||
|
const computePass = encoder.beginComputePass();
|
||||||
|
computePass.setPipeline(computePipeline);
|
||||||
computePass.setBindGroup(0, computeBindGroup);
|
computePass.setBindGroup(0, computeBindGroup);
|
||||||
computePass.dispatch(Math.ceil(screenSize[0] / 32), screenSize[1], 1);
|
computePass.dispatch(Math.ceil(screenSize[0] / 32), screenSize[1], 1);
|
||||||
computePass.endPass();
|
computePass.endPass();
|
||||||
};
|
};
|
||||||
|
|
||||||
return makePass(getOutputs, ready, setSize, execute);
|
return makePass(loaded, build, run);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,12 +57,14 @@ export default (context, getInputs) => {
|
|||||||
|
|
||||||
let computePipeline;
|
let computePipeline;
|
||||||
let configBuffer;
|
let configBuffer;
|
||||||
|
let tex;
|
||||||
|
let bloomTex;
|
||||||
let output;
|
let output;
|
||||||
let screenSize;
|
let screenSize;
|
||||||
|
|
||||||
const assets = [loadShader(device, "shaders/wgsl/stripePass.wgsl")];
|
const assets = [loadShader(device, "shaders/wgsl/stripePass.wgsl")];
|
||||||
|
|
||||||
const ready = (async () => {
|
const loaded = (async () => {
|
||||||
const [stripeShader] = await Promise.all(assets);
|
const [stripeShader] = await Promise.all(assets);
|
||||||
|
|
||||||
computePipeline = device.createComputePipeline({
|
computePipeline = device.createComputePipeline({
|
||||||
@@ -76,20 +78,20 @@ export default (context, getInputs) => {
|
|||||||
configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: 0.05, backgroundColor: config.backgroundColor });
|
configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: 0.05, backgroundColor: config.backgroundColor });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const setSize = (width, height) => {
|
const build = (size, inputs) => {
|
||||||
output?.destroy();
|
output?.destroy();
|
||||||
output = makeComputeTarget(device, width, height);
|
output = makeComputeTarget(device, size);
|
||||||
screenSize = [width, height];
|
screenSize = size;
|
||||||
|
|
||||||
|
tex = inputs.primary;
|
||||||
|
bloomTex = inputs.bloom;
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary: output,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOutputs = () => ({
|
const run = (encoder) => {
|
||||||
primary: output,
|
|
||||||
});
|
|
||||||
|
|
||||||
const execute = (encoder) => {
|
|
||||||
const inputs = getInputs();
|
|
||||||
const tex = inputs.primary;
|
|
||||||
const bloomTex = inputs.bloom;
|
|
||||||
const computePass = encoder.beginComputePass();
|
const computePass = encoder.beginComputePass();
|
||||||
computePass.setPipeline(computePipeline);
|
computePass.setPipeline(computePipeline);
|
||||||
const computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
const computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
||||||
@@ -106,5 +108,5 @@ export default (context, getInputs) => {
|
|||||||
computePass.endPass();
|
computePass.endPass();
|
||||||
};
|
};
|
||||||
|
|
||||||
return makePass(getOutputs, ready, setSize, execute);
|
return makePass(loaded, build, run);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,17 +27,17 @@ const loadTexture = async (device, url) => {
|
|||||||
return texture;
|
return texture;
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeRenderTarget = (device, width, height, format, mipLevelCount = 1) =>
|
const makeRenderTarget = (device, size, format, mipLevelCount = 1) =>
|
||||||
device.createTexture({
|
device.createTexture({
|
||||||
size: [width, height, 1],
|
size: [...size, 1],
|
||||||
mipLevelCount,
|
mipLevelCount,
|
||||||
format,
|
format,
|
||||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeComputeTarget = (device, width, height, mipLevelCount = 1) =>
|
const makeComputeTarget = (device, size, mipLevelCount = 1) =>
|
||||||
device.createTexture({
|
device.createTexture({
|
||||||
size: [width, height, 1],
|
size: [...size, 1],
|
||||||
mipLevelCount,
|
mipLevelCount,
|
||||||
format: "rgba8unorm",
|
format: "rgba8unorm",
|
||||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
|
||||||
@@ -96,15 +96,13 @@ const makeBindGroup = (device, pipeline, index, entries) =>
|
|||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
const makePass = (getOutputs, ready, setSize, execute) => ({
|
const makePass = (loaded, build, run) => ({
|
||||||
getOutputs: getOutputs ?? (() => ({})),
|
loaded: loaded ?? Promise.resolve(),
|
||||||
ready: ready ?? Promise.resolve(),
|
build: build ?? ((size, inputs) => inputs),
|
||||||
setSize: setSize ?? (() => {}),
|
run: run ?? (() => {}),
|
||||||
execute: execute ?? (() => {}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const makePipeline = (context, steps) =>
|
const makePipeline = (context, steps) => steps.filter((f) => f != null).map((f) => f(context));
|
||||||
steps.filter((f) => f != null).reduce((pipeline, f, i) => [...pipeline, f(context, i == 0 ? null : pipeline[i - 1].getOutputs)], []);
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getCanvasSize,
|
getCanvasSize,
|
||||||
|
|||||||
Reference in New Issue
Block a user