diff --git a/TODO.txt b/TODO.txt
index 1371c67..1a1c0d4 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,4 +1,9 @@
TODO:
+ TTF --> MSDF
+ Isolate fun stuff from core
+ Separate configs
+ Separate assets
+
Live config update roadmap
Modify regl pass
diff --git a/js/Matrix.js b/js/Matrix.js
index b0f4414..68c33da 100644
--- a/js/Matrix.js
+++ b/js/Matrix.js
@@ -1,8 +1,87 @@
+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";
+
+const inclusion = [
+ 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,
+].reduce((i, s) => s.length + i, 0);
+if (inclusion === 0) console.log("!");
+
import React, { useEffect, useState, useRef, memo } from "react";
-// import { init as initRain, formulate as refreshRain, destroy as destroyRain } from "./regl/main";
-import { init as initRain, formulate as refreshRain, destroy as destroyRain } from "./webgpu/main";
+import * as reglRenderer from "./regl/main";
+import * as webgpuRenderer from "./webgpu/main";
import makeConfig from "./utils/config";
+console.log(webgpuRenderer.init, webgpuRenderer.formulate, webgpuRenderer.destroy);
+
/**
* @typedef {object} Colour
* @property {"hsl"|"rgb"} space
@@ -18,7 +97,7 @@ import makeConfig from "./utils/config";
* "classic" | "megacity" | "neomatrixology" | "operator" |
* "nightmare" | "paradise" | "resurrections" | "trinity" |
* "morpheus" | "bugs" | "palimpsest" | "twilight" |
- * "holoplay" | "3d" | "throwback" | "updated" |
+ * "3d" | "throwback" | "updated" |
* "1999" | "2003" | "2021" | string /* custom * /
* ),
* font?: keyof typeof fonts, // "matrixcode", …
@@ -36,7 +115,6 @@ import makeConfig from "./utils/config";
* renderer?: "regl" | "three" | string,
* suppressWarnings?: boolean,
* useHalfFloat?: boolean,
- * useHoloplay?: boolean,
* isometric?: boolean,
*
* /* ------------- glyph appearance ------------- * /
@@ -119,12 +197,12 @@ export const Matrix = memo((props) => {
canvas.style.width = "100%";
canvas.style.height = "100%";
const init = async () => {
- setRain(await initRain(canvas));
+ setRain(await reglRenderer.init(canvas));
};
init();
return () => {
- destroyRain(rain);
+ reglRenderer.destroy(rain);
setRain(null);
};
}, []);
@@ -134,7 +212,7 @@ export const Matrix = memo((props) => {
return;
}
const refresh = async () => {
- await refreshRain(rain, makeConfig({ ...rest }));
+ await reglRenderer.formulate(rain, makeConfig({ ...rest }));
};
refresh();
}, [props, rain]);
diff --git a/js/fetchLibraries.js b/js/fetchLibraries.js
new file mode 100644
index 0000000..c68bd01
--- /dev/null
+++ b/js/fetchLibraries.js
@@ -0,0 +1,20 @@
+export default async () => {
+ let glMatrix, createREGL;
+
+ try {
+ glMatrix = await import("gl-matrix");
+ createREGL = (await import("regl")).default;
+ } catch {
+ const loadJS = (src) =>
+ new Promise((resolve, reject) => {
+ const tag = document.createElement("script");
+ [tag.onload, tag.onerror, tag.src] = [resolve, reject, src];
+ document.body.appendChild(tag);
+ });
+ await Promise.all([loadJS("lib/regl.min.js"), loadJS("lib/gl-matrix.js")]);
+ glMatrix = globalThis.glMatrix;
+ createREGL = globalThis.createREGL;
+ }
+
+ return { glMatrix, createREGL };
+};
diff --git a/js/index.js b/js/index.js
index d0b3015..415b392 100644
--- a/js/index.js
+++ b/js/index.js
@@ -14,7 +14,6 @@ const versions = [
"paradise",
"resurrections",
"operator",
- "holoplay",
"throwback",
"updated",
"1999",
diff --git a/js/main.js b/js/main.js
index 5001a9f..63c9b2a 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,4 +1,4 @@
-import makeConfig from "./config.js";
+import makeConfig from "./utils/config.js";
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
@@ -27,6 +27,12 @@ document.body.onload = async () => {
const useWebGPU = (await supportsWebGPU()) && ["webgpu"].includes(config.renderer?.toLowerCase());
const solution = import(`./${useWebGPU ? "webgpu" : "regl"}/main.js`);
+ const initialize = async (canvas, config) => {
+ const { init, formulate } = await solution;
+ const rain = await init(canvas);
+ await formulate(rain, config);
+ };
+
if (isRunningSwiftShader() && !config.suppressWarnings) {
const notice = document.createElement("notice");
notice.innerHTML = `
@@ -41,11 +47,11 @@ document.body.onload = async () => {
config.suppressWarnings = true;
urlParams.set("suppressWarnings", true);
history.replaceState({}, "", "?" + unescape(urlParams.toString()));
- (await solution).default(canvas, config);
+ await initialize(canvas, config);
canvas.style.display = "unset";
document.body.removeChild(notice);
});
} else {
- (await solution).default(canvas, config);
+ await initialize(canvas, config);
}
};
diff --git a/js/regl/bloomPass.js b/js/regl/bloomPass.js
index d56a335..a9a3963 100644
--- a/js/regl/bloomPass.js
+++ b/js/regl/bloomPass.js
@@ -1,7 +1,4 @@
-import { makePassFBO, makePass } from "./utils";
-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 { 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, blurred textures.
@@ -20,7 +17,7 @@ const resizePyramid = (pyramid, vw, vh, scale) =>
fbo.resize(Math.floor((vw * scale) / 2 ** index), Math.floor((vh * scale) / 2 ** index)),
);
-export default ({ regl, config }, inputs) => {
+export default ({ regl, cache, config }, inputs) => {
const { bloomStrength, bloomSize, highPassThreshold } = config;
const enabled = bloomSize > 0 && bloomStrength > 0;
@@ -39,6 +36,7 @@ export default ({ regl, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat);
// The high pass restricts the blur to bright things in our input texture.
+ const highPassFrag = loadText(cache, "shaders/glsl/bloomPass.highPass.frag.glsl");
const highPass = regl({
frag: regl.prop("frag"),
uniforms: {
@@ -53,6 +51,7 @@ export default ({ regl, config }, inputs) => {
// by blurring them all, this basic blur approximates a more complex gaussian:
// https://web.archive.org/web/20191124072602/https://software.intel.com/en-us/articles/compute-shader-hdr-and-bloom
+ const blurFrag = loadText(cache, "shaders/glsl/bloomPass.blur.frag.glsl");
const blur = regl({
frag: regl.prop("frag"),
uniforms: {
@@ -65,6 +64,7 @@ export default ({ regl, config }, inputs) => {
});
// The pyramid of textures gets flattened (summed) into a final blurry "bloom" texture
+ const combineFrag = loadText(cache, "shaders/glsl/bloomPass.combine.frag.glsl");
const combine = regl({
frag: regl.prop("frag"),
uniforms: {
@@ -79,7 +79,7 @@ export default ({ regl, config }, inputs) => {
primary: inputs.primary,
bloom: output,
},
- Promise.all([highPassFrag.loaded, blurFrag.loaded]),
+ Promise.all([highPassFrag.loaded, blurFrag.loaded, combineFrag.loaded]),
(w, h) => {
// The blur pyramids can be lower resolution than the screen.
resizePyramid(highPassPyramid, w, h, bloomSize);
@@ -98,14 +98,14 @@ export default ({ regl, config }, inputs) => {
const vBlurFBO = vBlurPyramid[i];
highPass({
fbo: highPassFBO,
- frag: highPassFrag,
+ frag: highPassFrag.text(),
tex: i === 0 ? inputs.primary : highPassPyramid[i - 1],
});
- blur({ fbo: hBlurFBO, frag: blurFrag, tex: highPassFBO, direction: [1, 0] });
- blur({ fbo: vBlurFBO, frag: blurFrag, tex: hBlurFBO, direction: [0, 1] });
+ blur({ fbo: hBlurFBO, frag: blurFrag.text(), tex: highPassFBO, direction: [1, 0] });
+ blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] });
}
- combine({ frag: combineFrag });
+ combine({ frag: combineFrag.text() });
},
);
};
diff --git a/js/regl/imagePass.js b/js/regl/imagePass.js
index 0bd861b..5aaef6b 100644
--- a/js/regl/imagePass.js
+++ b/js/regl/imagePass.js
@@ -1,5 +1,4 @@
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js";
-import imagePassFrag from "../../shaders/glsl/imagePass.frag.glsl";
// Multiplies the rendered rain and bloom by a loaded in image
@@ -10,6 +9,7 @@ export default ({ regl, cache, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat);
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
const background = loadImage(cache, regl, bgURL);
+ const imagePassFrag = loadText(cache, "shaders/glsl/imagePass.frag.glsl");
const render = regl({
frag: regl.prop("frag"),
uniforms: {
@@ -23,11 +23,11 @@ export default ({ regl, cache, config }, inputs) => {
{
primary: output,
},
- Promise.all([background.loaded]),
+ Promise.all([background.loaded, imagePassFrag.loaded]),
(w, h) => output.resize(w, h),
(shouldRender) => {
if (shouldRender) {
- render({ frag: imagePassFrag });
+ render({ frag: imagePassFrag.text() });
}
},
);
diff --git a/js/regl/main.js b/js/regl/main.js
index 753eb6f..4ea3bc5 100644
--- a/js/regl/main.js
+++ b/js/regl/main.js
@@ -1,5 +1,5 @@
import { makeFullScreenQuad, makePipeline } from "./utils.js";
-import createREGL from "regl";
+import fetchLibraries from "../fetchLibraries.js";
import makeRain from "./rainPass.js";
import makeBloomPass from "./bloomPass.js";
import makePalettePass from "./palettePass.js";
@@ -21,7 +21,13 @@ const effects = {
mirror: makeMirrorPass,
};
+let createREGL, glMatrix;
+
export const init = async (canvas) => {
+ const libraries = await fetchLibraries();
+ createREGL = libraries.createREGL;
+ glMatrix = libraries.glMatrix;
+
const resize = () => {
const devicePixelRatio = window.devicePixelRatio ?? 1;
canvas.width = Math.ceil(canvas.clientWidth * devicePixelRatio * rain.resolution);
@@ -74,18 +80,12 @@ export const formulate = async (rain, config) => {
}
const cameraTex = regl.texture(cameraCanvas);
- const lkg = await getLKG(config.useHoloplay, true);
// All this takes place in a full screen quad.
const fullScreenQuad = makeFullScreenQuad(regl);
const effectName = config.effect in effects ? config.effect : "palette";
- const context = { regl, cache, config, lkg, cameraTex, cameraAspectRatio };
- const pipeline = makePipeline(context, [
- makeRain,
- makeBloomPass,
- effects[effectName],
- makeQuiltPass,
- ]);
+ const context = { regl, cache, config, cameraTex, cameraAspectRatio, glMatrix };
+ const pipeline = makePipeline(context, [makeRain, makeBloomPass, effects[effectName]]);
const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
const drawToScreen = regl({ uniforms: screenUniforms });
@@ -150,7 +150,7 @@ export const formulate = async (rain, config) => {
rain.tick = tick;
};
-export const destroy = ({ regl, resize, doubleClick, cache, tick, canvas }) => {
+export const destroy = ({ regl, cache, resize, doubleClick, tick, canvas }) => {
window.removeEventListener("resize", resize);
window.removeEventListener("dblclick", doubleClick);
cache.clear();
diff --git a/js/regl/mirrorPass.js b/js/regl/mirrorPass.js
index f225825..5a63d6a 100644
--- a/js/regl/mirrorPass.js
+++ b/js/regl/mirrorPass.js
@@ -1,5 +1,4 @@
import { loadText, makePassFBO, makePass } from "./utils.js";
-import mirrorPassFrag from "../../shaders/glsl/mirrorPass.frag.glsl";
let start;
const numClicks = 5;
@@ -14,8 +13,9 @@ window.onclick = (e) => {
index = (index + 1) % numClicks;
};
-export default ({ regl, config, cameraTex, cameraAspectRatio }, inputs) => {
+export default ({ regl, cache, config, cameraTex, cameraAspectRatio }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat);
+ const mirrorPassFrag = loadText(cache, "shaders/glsl/mirrorPass.frag.glsl");
const render = regl({
frag: regl.prop("frag"),
uniforms: {
@@ -36,14 +36,14 @@ export default ({ regl, config, cameraTex, cameraAspectRatio }, inputs) => {
{
primary: output,
},
- null, // No async loading, glsl bundled and loaded into memory at document load
+ Promise.all([mirrorPassFrag.loaded]),
(w, h) => {
output.resize(w, h);
aspectRatio = w / h;
},
(shouldRender) => {
if (shouldRender) {
- render({ frag: mirrorPassFrag });
+ render({ frag: mirrorPassFrag.text() });
}
},
);
diff --git a/js/regl/palettePass.js b/js/regl/palettePass.js
index 228e8bb..024627e 100644
--- a/js/regl/palettePass.js
+++ b/js/regl/palettePass.js
@@ -1,6 +1,5 @@
-import colorToRGB from "../utils/colorToRGB";
-import { make1DTexture, makePassFBO, makePass } from "./utils.js";
-import palettePassFrag from "../../shaders/glsl/palettePass.frag.glsl";
+import colorToRGB from "../utils/colorToRGB.js";
+import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
// Maps the brightness of the rendered rain and bloom to colors
// in a 1D gradient palette texture generated from the passed-in color sequence
@@ -55,7 +54,7 @@ const makePalette = (regl, entries) => {
// won't persist across subsequent frames. This is a safe trick
// in screen space.
-export default ({ regl, config }, inputs) => {
+export default ({ regl, cache, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat);
const paletteTex = makePalette(regl, config.palette);
const {
@@ -67,6 +66,7 @@ export default ({ regl, config }, inputs) => {
ditherMagnitude,
} = config;
+ const palettePassFrag = loadText(cache, "shaders/glsl/palettePass.frag.glsl");
const render = regl({
frag: regl.prop("frag"),
@@ -92,7 +92,7 @@ export default ({ regl, config }, inputs) => {
(w, h) => output.resize(w, h),
(shouldRender) => {
if (shouldRender) {
- render({ frag: palettePassFrag });
+ render({ frag: palettePassFrag.text() });
}
},
);
diff --git a/js/regl/rainPass.js b/js/regl/rainPass.js
index 446a468..177f62d 100644
--- a/js/regl/rainPass.js
+++ b/js/regl/rainPass.js
@@ -1,11 +1,4 @@
-import { loadImage, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
-import { mat4, vec3 } from "gl-matrix";
-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 { loadImage, loadText, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
const extractEntries = (src, keys) =>
Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key)));
@@ -37,7 +30,8 @@ const blVert = [1, 0];
const brVert = [1, 1];
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
-export default ({ regl, cache, config, lkg }) => {
+export default ({ regl, cache, config, glMatrix }) => {
+ const { mat4, vec3 } = glMatrix;
// The volumetric mode multiplies the number of columns
// to reach the desired density, and then overlaps them
const volumetric = config.volumetric;
@@ -69,6 +63,7 @@ export default ({ regl, cache, config, lkg }) => {
};
const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns);
+ const rainPassIntro = loadText(cache, "shaders/glsl/rainPass.intro.frag.glsl");
const introUniforms = {
...commonUniforms,
@@ -85,6 +80,7 @@ export default ({ regl, cache, config, lkg }) => {
});
const raindropDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
+ const rainPassRaindrop = loadText(cache, "shaders/glsl/rainPass.raindrop.frag.glsl");
const raindropUniforms = {
...commonUniforms,
@@ -108,6 +104,7 @@ export default ({ regl, cache, config, lkg }) => {
});
const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
+ const rainPassSymbol = loadText(cache, "shaders/glsl/rainPass.symbol.frag.glsl");
const symbolUniforms = {
...commonUniforms,
@@ -125,6 +122,7 @@ export default ({ regl, cache, config, lkg }) => {
});
const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
+ const rainPassEffect = loadText(cache, "shaders/glsl/rainPass.effect.frag.glsl");
const effectUniforms = {
...commonUniforms,
@@ -224,8 +222,6 @@ export default ({ regl, cache, config, lkg }) => {
screenSize: regl.prop("screenSize"),
},
- viewport: regl.prop("viewport"),
-
attributes: {
aPosition: quadPositions,
aCorner: Array(numQuads).fill(quadVertices),
@@ -237,7 +233,6 @@ export default ({ regl, cache, config, lkg }) => {
// Camera and transform math for the volumetric mode
const screenSize = [1, 1];
- //const { mat4, vec3 } = glMatrix;
const transform = mat4.create();
if (volumetric && config.isometric) {
mat4.rotateX(transform, transform, (Math.PI * 1) / 8);
@@ -253,7 +248,17 @@ export default ({ regl, cache, config, lkg }) => {
{
primary: output,
},
- Promise.all([glyphMSDF.loaded, glintMSDF.loaded, baseTexture.loaded, glintTexture.loaded]),
+ Promise.all([
+ glyphMSDF.loaded,
+ glintMSDF.loaded,
+ baseTexture.loaded,
+ glintTexture.loaded,
+ rainPassIntro.loaded,
+ rainPassRaindrop.loaded,
+ rainPassSymbol.loaded,
+ rainPassVert.loaded,
+ rainPassFrag.loaded,
+ ]),
(w, h) => {
output.resize(w, h);
const aspectRatio = w / h;
@@ -276,10 +281,10 @@ export default ({ regl, cache, config, lkg }) => {
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
},
(shouldRender) => {
- intro({ frag: rainPassIntro });
- raindrop({ frag: rainPassRaindrop });
- symbol({ frag: rainPassSymbol });
- effect({ frag: rainPassEffect });
+ intro({ frag: rainPassIntro.text() });
+ raindrop({ frag: rainPassRaindrop.text() });
+ symbol({ frag: rainPassSymbol.text() });
+ effect({ frag: rainPassEffect.text() });
if (shouldRender) {
regl.clear({
@@ -292,8 +297,8 @@ export default ({ regl, cache, config, lkg }) => {
transform,
camera,
screenSize,
- vert: rainPassVert,
- frag: rainPassFrag,
+ vert: rainPassVert.text(),
+ frag: rainPassFrag.text(),
glyphTransform: [1, 0, 0, 1],
});
}
diff --git a/js/regl/stripePass.js b/js/regl/stripePass.js
index ebde1e7..3708618 100644
--- a/js/regl/stripePass.js
+++ b/js/regl/stripePass.js
@@ -1,6 +1,5 @@
-import colorToRGB from "../utils/colorToRGB";
-import { make1DTexture, makePassFBO, makePass } from "./utils";
-import stripePassFrag from "../../shaders/glsl/stripePass.frag.glsl";
+import colorToRGB from "../utils/colorToRGB.js";
+import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
// Multiplies the rendered rain and bloom by a 1D gradient texture
// generated from the passed-in color sequence
@@ -28,7 +27,7 @@ const prideStripeColors = [
.map((color) => Array(2).fill(color))
.flat();
-export default ({ regl, config }, inputs) => {
+export default ({ regl, cache, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat);
const {
@@ -52,6 +51,8 @@ export default ({ regl, config }, inputs) => {
stripeColors.map((color) => [...colorToRGB(color), 1]),
);
+ const stripePassFrag = loadText(cache, "shaders/glsl/stripePass.frag.glsl");
+
const render = regl({
frag: regl.prop("frag"),
@@ -73,11 +74,11 @@ export default ({ regl, config }, inputs) => {
{
primary: output,
},
- null,
+ stripePassFrag.loaded,
(w, h) => output.resize(w, h),
(shouldRender) => {
if (shouldRender) {
- render({ frag: stripePassFrag });
+ render({ frag: stripePassFrag.text() });
}
},
);
diff --git a/js/regl/utils.js b/js/regl/utils.js
index 557b5c8..a5a28b2 100644
--- a/js/regl/utils.js
+++ b/js/regl/utils.js
@@ -29,7 +29,6 @@ const makeDoubleBuffer = (regl, props) => {
const isPowerOfTwo = (x) => Math.log2(x) % 1 == 0;
const loadImage = (cache, regl, url, mipmap) => {
-
const key = `${url}_${mipmap}`;
if (cache.has(key)) {
return cache.get(key);
diff --git a/js/utils/config.js b/js/utils/config.js
index 99c449b..75d6918 100644
--- a/js/utils/config.js
+++ b/js/utils/config.js
@@ -1,84 +1,71 @@
-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";
-
const fonts = {
coptic: {
// The script the Gnostic codices were written in
- glyphMSDFURL: msdfCoptic,
+ glyphMSDFURL: "assets/coptic_msdf.png",
glyphSequenceLength: 32,
glyphTextureGridSize: [8, 8],
},
gothic: {
// The script the Codex Argenteus was written in
- glyphMSDFURL: msdfGothic,
+ glyphMSDFURL: "assets/gothic_msdf.png",
glyphSequenceLength: 27,
glyphTextureGridSize: [8, 8],
},
matrixcode: {
// The glyphs seen in the film trilogy
- glyphMSDFURL: msdfMatrixCode,
+ glyphMSDFURL: "assets/matrixcode_msdf.png",
glyphSequenceLength: 57,
glyphTextureGridSize: [8, 8],
},
+ /*
megacity: {
// The glyphs seen in the film trilogy
- glyphMSDFURL: megacity,
+ glyphMSDFURL: "assets/megacity_msdf.png",
glyphSequenceLength: 64,
glyphTextureGridSize: [8, 8],
},
+ */
resurrections: {
// The glyphs seen in the film trilogy
- glyphMSDFURL: msdfRes,
- glintMSDFURL: msdfResGlint,
+ glyphMSDFURL: "assets/resurrections_msdf.png",
+ glintMSDFURL: "assets/resurrections_glint_msdf.png",
glyphSequenceLength: 135,
glyphTextureGridSize: [13, 12],
},
+ /*
huberfishA: {
- glyphMSDFURL: msdfHuberfishA,
+ glyphMSDFURL: "assets/huberfish_a_msdf.png",
glyphSequenceLength: 34,
glyphTextureGridSize: [6, 6],
},
huberfishD: {
- glyphMSDFURL: msdfHuberfishD,
+ glyphMSDFURL: "assets/huberfish_d_msdf.png",
glyphSequenceLength: 34,
glyphTextureGridSize: [6, 6],
},
gtarg_tenretniolleh: {
- glyphMSDFURL: msdfGtargTenretni,
+ glyphMSDFURL: "assets/gtarg_tenretniolleh_msdf.png",
glyphSequenceLength: 36,
glyphTextureGridSize: [6, 6],
},
gtarg_alientext: {
- glyphMSDFURL: msdfGtargAlienText,
+ glyphMSDFURL: "assets/gtarg_alientext_msdf.png",
glyphSequenceLength: 38,
glyphTextureGridSize: [8, 5],
},
neomatrixology: {
- glyphMSDFURL: msdfNeoMatrixology,
+ glyphMSDFURL: "assets/neomatrixology_msdf.png",
glyphSequenceLength: 12,
glyphTextureGridSize: [4, 4],
},
+ */
};
const textureURLs = {
- sand: texSand,
- pixels: texPixels,
- mesh: texMesh,
- metal: texMetal,
+ // sand: "assets/sand.png",
+ // pixels: "assets/pixel_grid.png",
+ mesh: "assets/mesh.png",
+ metal: "assets/metal.png",
};
const hsl = (...values) => ({ space: "hsl", values });
@@ -149,6 +136,7 @@ const defaults = {
const versions = {
classic: {},
+ /*
megacity: {
font: "megacity",
animationSpeed: 0.5,
@@ -167,6 +155,7 @@ const versions = {
cursorColor: hsl(0.167, 1, 0.75),
cursorIntensity: 2,
},
+ */
operator: {
cursorColor: hsl(0.375, 1, 0.66),
cursorIntensity: 3,
@@ -279,6 +268,7 @@ const versions = {
raindropLength: 0.3,
density: 0.75,
},
+ /*
morpheus: {
font: "resurrections",
glintTexture: "mesh",
@@ -368,7 +358,7 @@ const versions = {
// { color: hsl(0.1, 1.0, 0.9), at: 1.0 },
],
},
-
+ */
["3d"]: {
volumetric: true,
fallSpeed: 0.5,
diff --git a/js/webgpu/bloomPass.js b/js/webgpu/bloomPass.js
index 3b33386..a0767fb 100644
--- a/js/webgpu/bloomPass.js
+++ b/js/webgpu/bloomPass.js
@@ -6,8 +6,6 @@ import {
makeBindGroup,
makePass,
} from "./utils.js";
-import bloomBlurShader from "../../shaders/wgsl/bloomBlur.wgsl";
-import bloomCombineShader from "../../shaders/wgsl/bloomCombine.wgsl";
// const makePyramid = makeComputeTarget;
@@ -56,8 +54,8 @@ export default ({ config, device }) => {
}
const assets = [
- loadShader(device, bloomBlurShader),
- loadShader(device, bloomCombineShader),
+ loadShader(device, "shaders/wgsl/bloomBlur.wgsl"),
+ loadShader(device, "shaders/wgsl/bloomCombine.wgsl"),
];
const linearSampler = device.createSampler({
diff --git a/js/webgpu/endPass.js b/js/webgpu/endPass.js
index 5eb28ac..0f25241 100644
--- a/js/webgpu/endPass.js
+++ b/js/webgpu/endPass.js
@@ -1,7 +1,5 @@
import { loadShader, makeBindGroup, makePass } from "./utils.js";
-import endPassShader from "../../shaders/wgsl/endPass.wgsl";
-
// 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.
@@ -23,7 +21,7 @@ export default ({ device, canvasFormat, canvasContext }) => {
let renderPipeline;
let renderBindGroup;
- const assets = [loadShader(device, endPassShader)];
+ const assets = [loadShader(device, "shaders/wgsl/endPass.wgsl")];
const loaded = (async () => {
const [imageShader] = await Promise.all(assets);
diff --git a/js/webgpu/imagePass.js b/js/webgpu/imagePass.js
index aff7896..f75de8d 100644
--- a/js/webgpu/imagePass.js
+++ b/js/webgpu/imagePass.js
@@ -7,7 +7,6 @@ import {
makeBindGroup,
makePass,
} from "./utils.js";
-import imagePassShader from "../../shaders/wgsl/imagePass.wgsl";
// Multiplies the rendered rain and bloom by a loaded in image
@@ -16,7 +15,10 @@ const defaultBGURL =
export default ({ config, cache, device }) => {
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
- const assets = [loadTexture(device, cache, bgURL), loadShader(device, imagePassShader)];
+ const assets = [
+ loadTexture(device, cache, bgURL),
+ loadShader(device, "shaders/wgsl/imagePass.wgsl"),
+ ];
const linearSampler = device.createSampler({
magFilter: "linear",
diff --git a/js/webgpu/main.js b/js/webgpu/main.js
index 2ec16ff..669f4e7 100644
--- a/js/webgpu/main.js
+++ b/js/webgpu/main.js
@@ -1,5 +1,6 @@
import { structs } from "../../lib/gpu-buffer.js";
import { makeUniformBuffer, makePipeline } from "./utils.js";
+import fetchLibraries from "../fetchLibraries.js";
import makeRain from "./rainPass.js";
import makeBloomPass from "./bloomPass.js";
@@ -23,7 +24,12 @@ const effects = {
mirror: makeMirrorPass,
};
+let glMatrix;
+
export const init = async (canvas) => {
+ const libraries = await fetchLibraries();
+ glMatrix = libraries.glMatrix;
+
const resize = () => {
const devicePixelRatio = window.devicePixelRatio ?? 1;
canvas.width = Math.ceil(canvas.clientWidth * devicePixelRatio * rain.resolution);
@@ -50,7 +56,16 @@ export const init = async (canvas) => {
const device = await adapter.requestDevice();
const cache = new Map();
- const rain = { canvas, resize, doubleClick, cache, canvasContext, adapter, device, resolution: 1 };
+ const rain = {
+ canvas,
+ resize,
+ doubleClick,
+ cache,
+ canvasContext,
+ adapter,
+ device,
+ resolution: 1,
+ };
window.addEventListener("dblclick", doubleClick);
window.addEventListener("resize", resize);
@@ -103,6 +118,7 @@ export const formulate = async (rain, config) => {
cameraTex,
cameraAspectRatio,
cameraSize,
+ glMatrix,
};
const effectName = config.effect in effects ? config.effect : "palette";
diff --git a/js/webgpu/mirrorPass.js b/js/webgpu/mirrorPass.js
index 644d361..e5ec465 100644
--- a/js/webgpu/mirrorPass.js
+++ b/js/webgpu/mirrorPass.js
@@ -6,7 +6,6 @@ import {
makeBindGroup,
makePass,
} from "./utils.js";
-import mirrorPassShader from "../../shaders/wgsl/mirrorPass.wgsl";
let start;
const numTouches = 5;
@@ -26,7 +25,7 @@ window.onclick = (e) => {
};
export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) => {
- const assets = [loadShader(device, mirrorPassShader)];
+ const assets = [loadShader(device, "shaders/wgsl/mirrorPass.wgsl")];
const linearSampler = device.createSampler({
magFilter: "linear",
diff --git a/js/webgpu/palettePass.js b/js/webgpu/palettePass.js
index 1ef1ec0..3aca409 100644
--- a/js/webgpu/palettePass.js
+++ b/js/webgpu/palettePass.js
@@ -7,7 +7,6 @@ import {
makeComputeTarget,
makePass,
} from "./utils.js";
-import palettePassShader from "../../shaders/wgsl/palettePass.wgsl";
// Maps the brightness of the rendered rain and bloom to colors
// in a linear gradient buffer generated from the passed-in color sequence
@@ -87,7 +86,7 @@ export default ({ config, device, timeBuffer }) => {
let output;
let screenSize;
- const assets = [loadShader(device, palettePassShader)];
+ const assets = [loadShader(device, "shaders/wgsl/palettePass.wgsl")];
const loaded = (async () => {
const [paletteShader] = await Promise.all(assets);
diff --git a/js/webgpu/rainPass.js b/js/webgpu/rainPass.js
index 35b7611..6e4143c 100644
--- a/js/webgpu/rainPass.js
+++ b/js/webgpu/rainPass.js
@@ -7,8 +7,6 @@ import {
makeBindGroup,
makePass,
} from "./utils.js";
-import { mat2, mat4, vec2, vec3 } from "gl-matrix";
-import rainPassShader from "../../shaders/wgsl/rainPass.wgsl";
const rippleTypes = {
box: 0,
@@ -35,13 +33,14 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize, gly
return makeUniformBuffer(device, configUniforms, configData);
};
-export default ({ config, cache, device, timeBuffer }) => {
+export default ({ config, glMatrix, cache, device, timeBuffer }) => {
+ const { mat2, mat4, vec2, vec3 } = glMatrix;
const assets = [
loadTexture(device, cache, config.glyphMSDFURL),
loadTexture(device, cache, config.glintMSDFURL),
loadTexture(device, cache, config.baseTextureURL, false, true),
loadTexture(device, cache, config.glintTextureURL, false, true),
- loadShader(device, rainPassShader),
+ loadShader(device, "shaders/wgsl/rainPass.wgsl"),
];
// The volumetric mode multiplies the number of columns
diff --git a/js/webgpu/stripePass.js b/js/webgpu/stripePass.js
index 69f4cbd..79ab6f3 100644
--- a/js/webgpu/stripePass.js
+++ b/js/webgpu/stripePass.js
@@ -8,7 +8,6 @@ import {
makeComputeTarget,
makePass,
} from "./utils.js";
-import stripePassShader from "../../shaders/wgsl/stripePass.wgsl";
// Multiplies the rendered rain and bloom by a 1D gradient texture
// generated from the passed-in color sequence
@@ -69,7 +68,7 @@ export default ({ config, device, timeBuffer }) => {
let output;
let screenSize;
- const assets = [loadShader(device, stripePassShader)];
+ const assets = [loadShader(device, "shaders/wgsl/stripePass.wgsl")];
const loaded = (async () => {
const [stripeShader] = await Promise.all(assets);
diff --git a/js/webgpu/utils.js b/js/webgpu/utils.js
index 0e4d96e..2e286f2 100644
--- a/js/webgpu/utils.js
+++ b/js/webgpu/utils.js
@@ -1,5 +1,4 @@
const loadTexture = async (device, cache, url) => {
-
const key = url;
if (cache.has(key)) {
return cache.get(key);
@@ -63,9 +62,9 @@ const makeComputeTarget = (device, size, mipLevelCount = 1) =>
GPUTextureUsage.STORAGE_BINDING,
});
-const loadShader = async (device, code /*text*/) => {
- // const response = await fetch(url);
- // const code = await response.text();
+const loadShader = async (device, url) => {
+ const response = await fetch(url);
+ const code = await response.text();
return {
code,
module: device.createShaderModule({ code }),
diff --git a/package-lock.json b/package-lock.json
index cd46033..229165f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,10 +19,12 @@
"@babel/preset-react": "^7.22.5",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.3",
+ "@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-url": "^8.0.2",
"babel-loader": "^9.1.3",
+ "copy-webpack-plugin": "^13.0.0",
"html-webpack-plugin": "^5.5.3",
"prettier": "^3.5.3",
"raw-loader": "^4.0.2",
@@ -1966,6 +1968,28 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/@rollup/plugin-image": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-image/-/plugin-image-3.0.3.tgz",
+ "integrity": "sha512-qXWQwsXpvD4trSb8PeFPFajp8JLpRtqqOeNYRUKnEQNHm7e5UP7fuSRcbjQAJ7wDZBbnJvSdY5ujNBQd9B1iFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "mini-svg-data-uri": "^1.4.4"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rollup/plugin-node-resolve": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
@@ -3406,6 +3430,43 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
+ "node_modules/copy-webpack-plugin": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.0.tgz",
+ "integrity": "sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob-parent": "^6.0.1",
+ "normalize-path": "^3.0.0",
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": "^6.0.2",
+ "tinyglobby": "^0.2.12"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/core-js-compat": {
"version": "3.32.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz",
@@ -5190,6 +5251,16 @@
"node": ">=6"
}
},
+ "node_modules/mini-svg-data-uri": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
+ "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mini-svg-data-uri": "cli.js"
+ }
+ },
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -6711,6 +6782,51 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
+ "node_modules/tinyglobby": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
+ "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
diff --git a/package.json b/package.json
index ea85bc8..debad07 100644
--- a/package.json
+++ b/package.json
@@ -26,10 +26,12 @@
"@babel/preset-react": "^7.22.5",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.3",
+ "@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-url": "^8.0.2",
"babel-loader": "^9.1.3",
+ "copy-webpack-plugin": "^13.0.0",
"html-webpack-plugin": "^5.5.3",
"prettier": "^3.5.3",
"raw-loader": "^4.0.2",
diff --git a/rollup.config.mjs b/rollup.config.mjs
index 8293306..3ca14f9 100644
--- a/rollup.config.mjs
+++ b/rollup.config.mjs
@@ -6,6 +6,7 @@ import url from "@rollup/plugin-url";
import { visualizer } from "rollup-plugin-visualizer"; // <- size report
import terser from "@rollup/plugin-terser";
import { string } from "rollup-plugin-string";
+import image from "@rollup/plugin-image";
export default {
input: "js/Matrix.js",
@@ -14,7 +15,8 @@ export default {
peerDepsExternal(), // auto-exclude peerDeps
nodeResolve(), // so Rollup can find deps in node_modules
string({ include: ["**/*.glsl"] }),
- url({ include: ["**/*.png"], limit: 0 }),
+ string({ include: ["**/*.wgsl"] }),
+ image({ include: ["**/*.png"], limit: 0 }),
babel({
exclude: "node_modules/**", // transpile JSX
babelHelpers: "bundled",
@@ -34,6 +36,7 @@ export default {
],
output: [
{
+ inlineDynamicImports: true,
file: "dist/index.cjs.js",
format: "cjs",
exports: "named",
diff --git a/webpack.config.js b/webpack.config.js
index d3ca087..7887383 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,6 +1,7 @@
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
mode: "development",
@@ -12,19 +13,14 @@ module.exports = {
exclude: /node_modules/,
use: ["babel-loader"],
},
+ // {
+ // test: /\.css$/,
+ // use: ["style-loader", "css-loader"],
+ // },
{
- test: /\.css$/,
- use: ["style-loader", "css-loader"],
- },
- {
- test: /\.(png|j?g|svg|gif)?$/,
+ test: /\.(png|jpe?g|svg|glsl|wgsl)?$/,
type: "asset/resource",
},
- {
- test: /\.(glsl|frag|vert|wgsl)$/i,
- exclude: /node_modules/,
- use: ["raw-loader"],
- },
],
},
resolve: {
@@ -41,6 +37,12 @@ module.exports = {
template: path.resolve(__dirname, "public/index.html"),
filename: "index.html",
}),
+ new CopyPlugin({
+ patterns: [
+ { from: "assets", to: "assets" },
+ { from: "shaders", to: "shaders" },
+ ],
+ }),
new webpack.HotModuleReplacementPlugin(),
],
devServer: {