Added cache check to WebGPU renderer's loadShader method. Un-commented entries into config.js. Inclusions are now explicit dynamic import lambdas, so the cache functions can detect and call them; however, webpack and rollup seem to use them differently.

This commit is contained in:
Rezmason
2025-05-20 07:57:27 -07:00
parent 24e939008e
commit f61a4e29c9
20 changed files with 268 additions and 160 deletions

View File

@@ -112,7 +112,9 @@ export const Matrix = memo((props) => {
const [rRenderer, setRenderer] = useState(null);
const [rRain, setRain] = useState(null);
const configProps = Object.fromEntries(Object.entries(rawConfigProps).filter(([_, value]) => value != null));
const configProps = Object.fromEntries(
Object.entries(rawConfigProps).filter(([_, value]) => value != null),
);
const supportsWebGPU = () => {
return (
@@ -155,7 +157,12 @@ export const Matrix = memo((props) => {
setCanvas(canvas);
const loadRain = async () => {
const renderer = await import(`./${useWebGPU ? "webgpu" : "regl"}/main.js`);
let renderer;
if (useWebGPU) {
renderer = await import("./webgpu/main.js");
} else {
renderer = await import("./regl/main.js");
}
setRenderer(renderer);
const rain = await renderer.init(canvas);
setRain(rain);

View File

@@ -2,7 +2,4 @@ import { Matrix } from "./Matrix";
import inclusions from "./inclusions";
import * as reglRenderer from "./regl/main";
import * as webgpuRenderer from "./webgpu/main";
globalThis.inclusions = inclusions;
globalThis.reglRenderer = reglRenderer;
globalThis.webgpuRenderer = webgpuRenderer;
globalThis.Matrix = Matrix;
export { inclusions, reglRenderer, webgpuRenderer, Matrix };

View File

@@ -1,9 +1,10 @@
export default async () => {
let glMatrix, createREGL;
let glMatrix, createREGL, inclusions;
try {
glMatrix = await import("gl-matrix");
createREGL = (await import("regl")).default;
inclusions = (await import("./inclusions.js")).default;
} catch {
const loadJS = (src) =>
new Promise((resolve, reject) => {
@@ -14,7 +15,8 @@ export default async () => {
await Promise.all([loadJS("lib/regl.min.js"), loadJS("lib/gl-matrix.js")]);
glMatrix = globalThis.glMatrix;
createREGL = globalThis.createREGL;
inclusions = [];
}
return { glMatrix, createREGL };
return { glMatrix, createREGL, inclusions };
};

View File

@@ -1,75 +1,74 @@
import highPassFrag from "../shaders/glsl/bloomPass.highPass.frag.glsl";
import blurFrag from "../shaders/glsl/bloomPass.blur.frag.glsl";
import combineFrag from "../shaders/glsl/bloomPass.combine.frag.glsl";
import imagePassFrag from "../shaders/glsl/imagePass.frag.glsl";
import mirrorPassFrag from "../shaders/glsl/mirrorPass.frag.glsl";
import palettePassFrag from "../shaders/glsl/palettePass.frag.glsl";
import rainPassIntro from "../shaders/glsl/rainPass.intro.frag.glsl";
import rainPassRaindrop from "../shaders/glsl/rainPass.raindrop.frag.glsl";
import rainPassSymbol from "../shaders/glsl/rainPass.symbol.frag.glsl";
import rainPassEffect from "../shaders/glsl/rainPass.effect.frag.glsl";
import rainPassVert from "../shaders/glsl/rainPass.vert.glsl";
import rainPassFrag from "../shaders/glsl/rainPass.frag.glsl";
import stripePassFrag from "../shaders/glsl/stripePass.frag.glsl";
import msdfCoptic from "../assets/coptic_msdf.png";
import msdfGothic from "../assets/gothic_msdf.png";
import msdfMatrixCode from "../assets/matrixcode_msdf.png";
import msdfRes from "../assets/resurrections_msdf.png";
// import megacity from "../assets/megacity_msdf.png";
import msdfResGlint from "../assets/resurrections_glint_msdf.png";
// import msdfHuberfishA from "../assets/huberfish_a_msdf.png";
// import msdfHuberfishD from "../assets/huberfish_d_msdf.png";
// import msdfGtargTenretni from "../assets/gtarg_tenretniolleh_msdf.png";
// import msdfGtargAlienText from "../assets/gtarg_alientext_msdf.png";
// import msdfNeoMatrixology from "../assets/neomatrixology_msdf.png";
// import texSand from "../assets/sand.png";
// import texPixels from "../assets/pixel_grid.png";
import texMesh from "../assets/mesh.png";
import texMetal from "../assets/metal.png";
import bloomBlurShader from "../shaders/wgsl/bloomBlur.wgsl";
import bloomCombineShader from "../shaders/wgsl/bloomCombine.wgsl";
import endPassShader from "../shaders/wgsl/endPass.wgsl";
import imagePassShader from "../shaders/wgsl/imagePass.wgsl";
import mirrorPassShader from "../shaders/wgsl/mirrorPass.wgsl";
import palettePassShader from "../shaders/wgsl/palettePass.wgsl";
import rainPassShader from "../shaders/wgsl/rainPass.wgsl";
import stripePassShader from "../shaders/wgsl/stripePass.wgsl";
export default [
highPassFrag,
blurFrag,
combineFrag,
imagePassFrag,
mirrorPassFrag,
palettePassFrag,
rainPassIntro,
rainPassRaindrop,
rainPassSymbol,
rainPassEffect,
rainPassVert,
rainPassFrag,
stripePassFrag,
msdfCoptic,
msdfGothic,
msdfMatrixCode,
msdfRes,
// megacity,
msdfResGlint,
// msdfHuberfishA,
// msdfHuberfishD,
// msdfGtargTenretni,
// msdfGtargAlienText,
// msdfNeoMatrixology,
// texSand,
// texPixels,
texMesh,
texMetal,
bloomBlurShader,
bloomCombineShader,
endPassShader,
imagePassShader,
mirrorPassShader,
palettePassShader,
rainPassShader,
stripePassShader,
[
"import::shaders/glsl/bloomPass.highPass.frag.glsl",
() => import("../shaders/glsl/bloomPass.highPass.frag.glsl"),
],
[
"import::shaders/glsl/bloomPass.blur.frag.glsl",
() => import("../shaders/glsl/bloomPass.blur.frag.glsl"),
],
[
"import::shaders/glsl/bloomPass.combine.frag.glsl",
() => import("../shaders/glsl/bloomPass.combine.frag.glsl"),
],
["import::shaders/glsl/imagePass.frag.glsl", () => import("../shaders/glsl/imagePass.frag.glsl")],
[
"import::shaders/glsl/mirrorPass.frag.glsl",
() => import("../shaders/glsl/mirrorPass.frag.glsl"),
],
[
"import::shaders/glsl/palettePass.frag.glsl",
() => import("../shaders/glsl/palettePass.frag.glsl"),
],
[
"import::shaders/glsl/rainPass.intro.frag.glsl",
() => import("../shaders/glsl/rainPass.intro.frag.glsl"),
],
[
"import::shaders/glsl/rainPass.raindrop.frag.glsl",
() => import("../shaders/glsl/rainPass.raindrop.frag.glsl"),
],
[
"import::shaders/glsl/rainPass.symbol.frag.glsl",
() => import("../shaders/glsl/rainPass.symbol.frag.glsl"),
],
[
"import::shaders/glsl/rainPass.effect.frag.glsl",
() => import("../shaders/glsl/rainPass.effect.frag.glsl"),
],
["import::shaders/glsl/rainPass.vert.glsl", () => import("../shaders/glsl/rainPass.vert.glsl")],
["import::shaders/glsl/rainPass.frag.glsl", () => import("../shaders/glsl/rainPass.frag.glsl")],
[
"import::shaders/glsl/stripePass.frag.glsl",
() => import("../shaders/glsl/stripePass.frag.glsl"),
],
["import::assets/coptic_msdf.png", () => import("../assets/coptic_msdf.png")],
["import::assets/gothic_msdf.png", () => import("../assets/gothic_msdf.png")],
["import::assets/matrixcode_msdf.png", () => import("../assets/matrixcode_msdf.png")],
["import::assets/resurrections_msdf.png", () => import("../assets/resurrections_msdf.png")],
["import::assets/megacity_msdf.png", () => import("../assets/megacity_msdf.png")],
[
"import::assets/resurrections_glint_msdf.png",
() => import("../assets/resurrections_glint_msdf.png"),
],
["import::assets/huberfish_a_msdf.png", () => import("../assets/huberfish_a_msdf.png")],
["import::assets/huberfish_d_msdf.png", () => import("../assets/huberfish_d_msdf.png")],
[
"import::assets/gtarg_tenretniolleh_msdf.png",
() => import("../assets/gtarg_tenretniolleh_msdf.png"),
],
["import::assets/gtarg_alientext_msdf.png", () => import("../assets/gtarg_alientext_msdf.png")],
["import::assets/neomatrixology_msdf.png", () => import("../assets/neomatrixology_msdf.png")],
["import::assets/sand.png", () => import("../assets/sand.png")],
["import::assets/pixel_grid.png", () => import("../assets/pixel_grid.png")],
["import::assets/mesh.png", () => import("../assets/mesh.png")],
["import::assets/metal.png", () => import("../assets/metal.png")],
["import::shaders/wgsl/bloomBlur.wgsl", () => import("../shaders/wgsl/bloomBlur.wgsl")],
["import::shaders/wgsl/bloomCombine.wgsl", () => import("../shaders/wgsl/bloomCombine.wgsl")],
["import::shaders/wgsl/endPass.wgsl", () => import("../shaders/wgsl/endPass.wgsl")],
["import::shaders/wgsl/imagePass.wgsl", () => import("../shaders/wgsl/imagePass.wgsl")],
["import::shaders/wgsl/mirrorPass.wgsl", () => import("../shaders/wgsl/mirrorPass.wgsl")],
["import::shaders/wgsl/palettePass.wgsl", () => import("../shaders/wgsl/palettePass.wgsl")],
["import::shaders/wgsl/rainPass.wgsl", () => import("../shaders/wgsl/rainPass.wgsl")],
["import::shaders/wgsl/stripePass.wgsl", () => import("../shaders/wgsl/stripePass.wgsl")],
];

View File

@@ -4,7 +4,6 @@ import { createRoot } from "react-dom/client";
import { Matrix } from "./Matrix";
const root = createRoot(document.getElementById("root"));
let idx = 1;
const versions = [
"classic",
"3d",
@@ -17,24 +16,42 @@ const versions = [
"bugs",
"morpheus",
];
const effects = ["none", "plain", "palette", "stripes", "pride", "trans", "image", "mirror"];
const App = () => {
const [version, setVersion] = useState(versions[0]);
const [effect, setEffect] = useState("plain");
const [numColumns, setNumColumns] = useState(80);
const [cursorColor, setCursorColor] = useState(null);
const [backgroundColor, setBackgroundColor] = useState("0,0,0");
const [rendererType, setRendererType] = useState(null);
const [density, setDensity] = useState(2);
const [destroyed, setDestroyed] = useState(false);
const onButtonClick = () => {
const onVersionButtonClick = () => {
setVersion((s) => {
const newVersion = versions[idx];
idx = (idx + 1) % versions.length;
console.log(newVersion);
let index = versions.indexOf(version) + 1;
if (index === versions.length) {
index = 0;
}
const newVersion = versions[index];
console.log("version:", newVersion);
return newVersion;
});
setCursorColor(null);
setBackgroundColor(null);
};
const onEffectButtonClick = () => {
setEffect((s) => {
let index = effects.indexOf(effect) + 1;
if (index === effects.length) {
index = 0;
}
const newEffect = effects[index];
console.log("effect:", newEffect);
return newEffect;
});
setCursorColor(null);
setBackgroundColor(null);
};
const onRendererButtonClick = () => {
setRendererType(() => (rendererType === "webgpu" ? "regl" : "webgpu"));
};
@@ -45,7 +62,8 @@ const App = () => {
return (
<div>
<h1>Rain</h1>
<button onClick={onButtonClick}>Version: "{version}"</button>
<button onClick={onVersionButtonClick}>Version: "{version}"</button>
<button onClick={onEffectButtonClick}>Effect: "{effect}"</button>
<button onClick={onRendererButtonClick}>Renderer: {rendererType ?? "default (regl)"}</button>
<button onClick={onDestroyButtonClick}>Destroy</button>
<label htmlFor="cursor-color">Cursor color: </label>
@@ -86,6 +104,7 @@ const App = () => {
<Matrix
style={{ width: "80vw", height: "45vh" }}
version={version}
effect={effect}
numColumns={numColumns}
renderer={rendererType}
cursorColor={cursorColor}

View File

@@ -21,12 +21,13 @@ const effects = {
mirror: makeMirrorPass,
};
let createREGL, glMatrix;
let createREGL, glMatrix, inclusions;
export const init = async (canvas) => {
const libraries = await fetchLibraries();
createREGL = libraries.createREGL;
glMatrix = libraries.glMatrix;
inclusions = libraries.inclusions;
const resize = () => {
const devicePixelRatio = window.devicePixelRatio ?? 1;

View File

@@ -17,14 +17,12 @@ const fonts = {
glyphSequenceLength: 57,
glyphTextureGridSize: [8, 8],
},
/*
megacity: {
// The glyphs seen in the film trilogy
glyphMSDFURL: "assets/megacity_msdf.png",
glyphSequenceLength: 64,
glyphTextureGridSize: [8, 8],
},
*/
resurrections: {
// The glyphs seen in the film trilogy
glyphMSDFURL: "assets/resurrections_msdf.png",
@@ -32,7 +30,6 @@ const fonts = {
glyphSequenceLength: 135,
glyphTextureGridSize: [13, 12],
},
/*
huberfishA: {
glyphMSDFURL: "assets/huberfish_a_msdf.png",
glyphSequenceLength: 34,
@@ -58,12 +55,11 @@ const fonts = {
glyphSequenceLength: 12,
glyphTextureGridSize: [4, 4],
},
*/
};
const textureURLs = {
// sand: "assets/sand.png",
// pixels: "assets/pixel_grid.png",
sand: "assets/sand.png",
pixels: "assets/pixel_grid.png",
mesh: "assets/mesh.png",
metal: "assets/metal.png",
};
@@ -136,7 +132,6 @@ const defaults = {
const versions = {
classic: {},
/*
megacity: {
font: "megacity",
animationSpeed: 0.5,
@@ -155,7 +150,6 @@ const versions = {
cursorColor: hsl(0.167, 1, 0.75),
cursorIntensity: 2,
},
*/
operator: {
cursorColor: hsl(0.375, 1, 0.66),
cursorIntensity: 3,
@@ -268,7 +262,6 @@ const versions = {
raindropLength: 0.3,
density: 0.75,
},
/*
morpheus: {
font: "resurrections",
glintTexture: "mesh",
@@ -358,7 +351,6 @@ const versions = {
// { color: hsl(0.1, 1.0, 0.9), at: 1.0 },
],
},
*/
["3d"]: {
volumetric: true,
fallSpeed: 0.5,

View File

@@ -39,7 +39,7 @@ const makePyramidViews = (pyramid) => pyramid.map((tex) => tex.createView());
// 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 }) => {
export default ({ config, device, cache }) => {
const pyramidHeight = 4;
const bloomSize = config.bloomSize;
const bloomStrength = config.bloomStrength;
@@ -54,8 +54,8 @@ export default ({ config, device }) => {
}
const assets = [
loadShader(device, "shaders/wgsl/bloomBlur.wgsl"),
loadShader(device, "shaders/wgsl/bloomCombine.wgsl"),
loadShader(device, cache, "shaders/wgsl/bloomBlur.wgsl"),
loadShader(device, cache, "shaders/wgsl/bloomCombine.wgsl"),
];
const linearSampler = device.createSampler({

View File

@@ -5,7 +5,7 @@ import { loadShader, makeBindGroup, makePass } from "./utils.js";
const numVerticesPerQuad = 2 * 3;
export default ({ device, canvasFormat, canvasContext }) => {
export default ({ device, cache, canvasFormat, canvasContext }) => {
const nearestSampler = device.createSampler();
const renderPassConfig = {
@@ -21,7 +21,7 @@ export default ({ device, canvasFormat, canvasContext }) => {
let renderPipeline;
let renderBindGroup;
const assets = [loadShader(device, "shaders/wgsl/endPass.wgsl")];
const assets = [loadShader(device, cache, "shaders/wgsl/endPass.wgsl")];
const loaded = (async () => {
const [imageShader] = await Promise.all(assets);

View File

@@ -17,7 +17,7 @@ export default ({ config, cache, device }) => {
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
const assets = [
loadTexture(device, cache, bgURL),
loadShader(device, "shaders/wgsl/imagePass.wgsl"),
loadShader(device, cache, "shaders/wgsl/imagePass.wgsl"),
];
const linearSampler = device.createSampler({

View File

@@ -24,11 +24,12 @@ const effects = {
mirror: makeMirrorPass,
};
let glMatrix;
let glMatrix, inclusions;
export const init = async (canvas) => {
const libraries = await fetchLibraries();
glMatrix = libraries.glMatrix;
inclusions = libraries.inclusions;
const resize = () => {
const devicePixelRatio = window.devicePixelRatio ?? 1;

View File

@@ -24,8 +24,8 @@ window.onclick = (e) => {
touchesChanged = true;
};
export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) => {
const assets = [loadShader(device, "shaders/wgsl/mirrorPass.wgsl")];
export default ({ config, device, cache, cameraTex, cameraAspectRatio, timeBuffer }) => {
const assets = [loadShader(device, cache, "shaders/wgsl/mirrorPass.wgsl")];
const linearSampler = device.createSampler({
magFilter: "linear",

View File

@@ -73,7 +73,7 @@ const makePalette = (device, paletteUniforms, entries) => {
// won't persist across subsequent frames. This is a safe trick
// in screen space.
export default ({ config, device, timeBuffer }) => {
export default ({ config, device, cache, timeBuffer }) => {
const linearSampler = device.createSampler({
magFilter: "linear",
minFilter: "linear",
@@ -86,7 +86,7 @@ export default ({ config, device, timeBuffer }) => {
let output;
let screenSize;
const assets = [loadShader(device, "shaders/wgsl/palettePass.wgsl")];
const assets = [loadShader(device, cache, "shaders/wgsl/palettePass.wgsl")];
const loaded = (async () => {
const [paletteShader] = await Promise.all(assets);

View File

@@ -39,7 +39,7 @@ export default ({ config, glMatrix, cache, device, timeBuffer }) => {
loadTexture(device, cache, config.glintMSDFURL),
loadTexture(device, cache, config.baseTextureURL, false, true),
loadTexture(device, cache, config.glintTextureURL, false, true),
loadShader(device, "shaders/wgsl/rainPass.wgsl"),
loadShader(device, cache, "shaders/wgsl/rainPass.wgsl"),
];
// The volumetric mode multiplies the number of columns

View File

@@ -43,7 +43,7 @@ const numVerticesPerQuad = 2 * 3;
// won't persist across subsequent frames. This is a safe trick
// in screen space.
export default ({ config, device, timeBuffer }) => {
export default ({ config, device, cache, timeBuffer }) => {
// Expand and convert stripe colors into 1D texture data
const stripeColors =
"stripeColors" in config
@@ -68,7 +68,7 @@ export default ({ config, device, timeBuffer }) => {
let output;
let screenSize;
const assets = [loadShader(device, "shaders/wgsl/stripePass.wgsl")];
const assets = [loadShader(device, cache, "shaders/wgsl/stripePass.wgsl")];
const loaded = (async () => {
const [stripeShader] = await Promise.all(assets);

View File

@@ -62,7 +62,11 @@ const makeComputeTarget = (device, size, mipLevelCount = 1) =>
GPUTextureUsage.STORAGE_BINDING,
});
const loadShader = async (device, url) => {
const loadShader = async (device, cache, url) => {
const key = url;
if (cache.has(key)) {
return cache.get(key);
}
const response = await fetch(url);
const code = await response.text();
return {