Exploring ways to preserve the vanilla JS browser demo without compromising on the bundle. Experimenting with embedding images in the bundle as data URIs

This commit is contained in:
Rezmason
2025-05-06 12:59:02 -07:00
parent 6663c92f99
commit eea341f50c
27 changed files with 372 additions and 137 deletions

View File

@@ -1,4 +1,9 @@
TODO: TODO:
TTF --> MSDF
Isolate fun stuff from core
Separate configs
Separate assets
Live config update roadmap Live config update roadmap
Modify regl pass Modify regl pass

View File

@@ -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 React, { useEffect, useState, useRef, memo } from "react";
// import { init as initRain, formulate as refreshRain, destroy as destroyRain } from "./regl/main"; import * as reglRenderer from "./regl/main";
import { init as initRain, formulate as refreshRain, destroy as destroyRain } from "./webgpu/main"; import * as webgpuRenderer from "./webgpu/main";
import makeConfig from "./utils/config"; import makeConfig from "./utils/config";
console.log(webgpuRenderer.init, webgpuRenderer.formulate, webgpuRenderer.destroy);
/** /**
* @typedef {object} Colour * @typedef {object} Colour
* @property {"hsl"|"rgb"} space * @property {"hsl"|"rgb"} space
@@ -18,7 +97,7 @@ import makeConfig from "./utils/config";
* "classic" | "megacity" | "neomatrixology" | "operator" | * "classic" | "megacity" | "neomatrixology" | "operator" |
* "nightmare" | "paradise" | "resurrections" | "trinity" | * "nightmare" | "paradise" | "resurrections" | "trinity" |
* "morpheus" | "bugs" | "palimpsest" | "twilight" | * "morpheus" | "bugs" | "palimpsest" | "twilight" |
* "holoplay" | "3d" | "throwback" | "updated" | * "3d" | "throwback" | "updated" |
* "1999" | "2003" | "2021" | string /* custom * / * "1999" | "2003" | "2021" | string /* custom * /
* ), * ),
* font?: keyof typeof fonts, // "matrixcode", … * font?: keyof typeof fonts, // "matrixcode", …
@@ -36,7 +115,6 @@ import makeConfig from "./utils/config";
* renderer?: "regl" | "three" | string, * renderer?: "regl" | "three" | string,
* suppressWarnings?: boolean, * suppressWarnings?: boolean,
* useHalfFloat?: boolean, * useHalfFloat?: boolean,
* useHoloplay?: boolean,
* isometric?: boolean, * isometric?: boolean,
* *
* /* ------------- glyph appearance ------------- * / * /* ------------- glyph appearance ------------- * /
@@ -119,12 +197,12 @@ export const Matrix = memo((props) => {
canvas.style.width = "100%"; canvas.style.width = "100%";
canvas.style.height = "100%"; canvas.style.height = "100%";
const init = async () => { const init = async () => {
setRain(await initRain(canvas)); setRain(await reglRenderer.init(canvas));
}; };
init(); init();
return () => { return () => {
destroyRain(rain); reglRenderer.destroy(rain);
setRain(null); setRain(null);
}; };
}, []); }, []);
@@ -134,7 +212,7 @@ export const Matrix = memo((props) => {
return; return;
} }
const refresh = async () => { const refresh = async () => {
await refreshRain(rain, makeConfig({ ...rest })); await reglRenderer.formulate(rain, makeConfig({ ...rest }));
}; };
refresh(); refresh();
}, [props, rain]); }, [props, rain]);

20
js/fetchLibraries.js Normal file
View File

@@ -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 };
};

View File

@@ -14,7 +14,6 @@ const versions = [
"paradise", "paradise",
"resurrections", "resurrections",
"operator", "operator",
"holoplay",
"throwback", "throwback",
"updated", "updated",
"1999", "1999",

View File

@@ -1,4 +1,4 @@
import makeConfig from "./config.js"; import makeConfig from "./utils/config.js";
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
document.body.appendChild(canvas); document.body.appendChild(canvas);
@@ -27,6 +27,12 @@ document.body.onload = async () => {
const useWebGPU = (await supportsWebGPU()) && ["webgpu"].includes(config.renderer?.toLowerCase()); const useWebGPU = (await supportsWebGPU()) && ["webgpu"].includes(config.renderer?.toLowerCase());
const solution = import(`./${useWebGPU ? "webgpu" : "regl"}/main.js`); 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) { if (isRunningSwiftShader() && !config.suppressWarnings) {
const notice = document.createElement("notice"); const notice = document.createElement("notice");
notice.innerHTML = `<div class="notice"> notice.innerHTML = `<div class="notice">
@@ -41,11 +47,11 @@ document.body.onload = async () => {
config.suppressWarnings = true; config.suppressWarnings = true;
urlParams.set("suppressWarnings", true); urlParams.set("suppressWarnings", true);
history.replaceState({}, "", "?" + unescape(urlParams.toString())); history.replaceState({}, "", "?" + unescape(urlParams.toString()));
(await solution).default(canvas, config); await initialize(canvas, config);
canvas.style.display = "unset"; canvas.style.display = "unset";
document.body.removeChild(notice); document.body.removeChild(notice);
}); });
} else { } else {
(await solution).default(canvas, config); await initialize(canvas, config);
} }
}; };

View File

@@ -1,7 +1,4 @@
import { makePassFBO, makePass } from "./utils"; import { loadText, makePassFBO, makePass } from "./utils.js";
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";
// The bloom pass is basically an added high-pass blur. // The bloom pass is basically an added high-pass blur.
// The blur approximation is the sum of a pyramid of downscaled, blurred textures. // 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)), 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 { bloomStrength, bloomSize, highPassThreshold } = config;
const enabled = bloomSize > 0 && bloomStrength > 0; const enabled = bloomSize > 0 && bloomStrength > 0;
@@ -39,6 +36,7 @@ export default ({ regl, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat); const output = makePassFBO(regl, config.useHalfFloat);
// The high pass restricts the blur to bright things in our input texture. // 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({ const highPass = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
@@ -53,6 +51,7 @@ export default ({ regl, config }, inputs) => {
// by blurring them all, this basic blur approximates a more complex gaussian: // 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 // 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({ const blur = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
@@ -65,6 +64,7 @@ export default ({ regl, config }, inputs) => {
}); });
// The pyramid of textures gets flattened (summed) into a final blurry "bloom" texture // 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({ const combine = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
@@ -79,7 +79,7 @@ export default ({ regl, config }, inputs) => {
primary: inputs.primary, primary: inputs.primary,
bloom: output, bloom: output,
}, },
Promise.all([highPassFrag.loaded, blurFrag.loaded]), Promise.all([highPassFrag.loaded, blurFrag.loaded, combineFrag.loaded]),
(w, h) => { (w, h) => {
// The blur pyramids can be lower resolution than the screen. // The blur pyramids can be lower resolution than the screen.
resizePyramid(highPassPyramid, w, h, bloomSize); resizePyramid(highPassPyramid, w, h, bloomSize);
@@ -98,14 +98,14 @@ export default ({ regl, config }, inputs) => {
const vBlurFBO = vBlurPyramid[i]; const vBlurFBO = vBlurPyramid[i];
highPass({ highPass({
fbo: highPassFBO, fbo: highPassFBO,
frag: highPassFrag, frag: highPassFrag.text(),
tex: i === 0 ? inputs.primary : highPassPyramid[i - 1], tex: i === 0 ? inputs.primary : highPassPyramid[i - 1],
}); });
blur({ fbo: hBlurFBO, frag: blurFrag, tex: highPassFBO, direction: [1, 0] }); blur({ fbo: hBlurFBO, frag: blurFrag.text(), tex: highPassFBO, direction: [1, 0] });
blur({ fbo: vBlurFBO, frag: blurFrag, tex: hBlurFBO, direction: [0, 1] }); blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] });
} }
combine({ frag: combineFrag }); combine({ frag: combineFrag.text() });
}, },
); );
}; };

View File

@@ -1,5 +1,4 @@
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js"; 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 // 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 output = makePassFBO(regl, config.useHalfFloat);
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
const background = loadImage(cache, regl, bgURL); const background = loadImage(cache, regl, bgURL);
const imagePassFrag = loadText(cache, "shaders/glsl/imagePass.frag.glsl");
const render = regl({ const render = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
@@ -23,11 +23,11 @@ export default ({ regl, cache, config }, inputs) => {
{ {
primary: output, primary: output,
}, },
Promise.all([background.loaded]), Promise.all([background.loaded, imagePassFrag.loaded]),
(w, h) => output.resize(w, h), (w, h) => output.resize(w, h),
(shouldRender) => { (shouldRender) => {
if (shouldRender) { if (shouldRender) {
render({ frag: imagePassFrag }); render({ frag: imagePassFrag.text() });
} }
}, },
); );

View File

@@ -1,5 +1,5 @@
import { makeFullScreenQuad, makePipeline } from "./utils.js"; import { makeFullScreenQuad, makePipeline } from "./utils.js";
import createREGL from "regl"; import fetchLibraries from "../fetchLibraries.js";
import makeRain from "./rainPass.js"; import makeRain from "./rainPass.js";
import makeBloomPass from "./bloomPass.js"; import makeBloomPass from "./bloomPass.js";
import makePalettePass from "./palettePass.js"; import makePalettePass from "./palettePass.js";
@@ -21,7 +21,13 @@ const effects = {
mirror: makeMirrorPass, mirror: makeMirrorPass,
}; };
let createREGL, glMatrix;
export const init = async (canvas) => { export const init = async (canvas) => {
const libraries = await fetchLibraries();
createREGL = libraries.createREGL;
glMatrix = libraries.glMatrix;
const resize = () => { const resize = () => {
const devicePixelRatio = window.devicePixelRatio ?? 1; const devicePixelRatio = window.devicePixelRatio ?? 1;
canvas.width = Math.ceil(canvas.clientWidth * devicePixelRatio * rain.resolution); 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 cameraTex = regl.texture(cameraCanvas);
const lkg = await getLKG(config.useHoloplay, true);
// All this takes place in a full screen quad. // All this takes place in a full screen quad.
const fullScreenQuad = makeFullScreenQuad(regl); const fullScreenQuad = makeFullScreenQuad(regl);
const effectName = config.effect in effects ? config.effect : "palette"; const effectName = config.effect in effects ? config.effect : "palette";
const context = { regl, cache, config, lkg, cameraTex, cameraAspectRatio }; const context = { regl, cache, config, cameraTex, cameraAspectRatio, glMatrix };
const pipeline = makePipeline(context, [ const pipeline = makePipeline(context, [makeRain, makeBloomPass, effects[effectName]]);
makeRain,
makeBloomPass,
effects[effectName],
makeQuiltPass,
]);
const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary }; const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
const drawToScreen = regl({ uniforms: screenUniforms }); const drawToScreen = regl({ uniforms: screenUniforms });
@@ -150,7 +150,7 @@ export const formulate = async (rain, config) => {
rain.tick = tick; 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("resize", resize);
window.removeEventListener("dblclick", doubleClick); window.removeEventListener("dblclick", doubleClick);
cache.clear(); cache.clear();

View File

@@ -1,5 +1,4 @@
import { loadText, makePassFBO, makePass } from "./utils.js"; import { loadText, makePassFBO, makePass } from "./utils.js";
import mirrorPassFrag from "../../shaders/glsl/mirrorPass.frag.glsl";
let start; let start;
const numClicks = 5; const numClicks = 5;
@@ -14,8 +13,9 @@ window.onclick = (e) => {
index = (index + 1) % numClicks; 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 output = makePassFBO(regl, config.useHalfFloat);
const mirrorPassFrag = loadText(cache, "shaders/glsl/mirrorPass.frag.glsl");
const render = regl({ const render = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
@@ -36,14 +36,14 @@ export default ({ regl, config, cameraTex, cameraAspectRatio }, inputs) => {
{ {
primary: output, primary: output,
}, },
null, // No async loading, glsl bundled and loaded into memory at document load Promise.all([mirrorPassFrag.loaded]),
(w, h) => { (w, h) => {
output.resize(w, h); output.resize(w, h);
aspectRatio = w / h; aspectRatio = w / h;
}, },
(shouldRender) => { (shouldRender) => {
if (shouldRender) { if (shouldRender) {
render({ frag: mirrorPassFrag }); render({ frag: mirrorPassFrag.text() });
} }
}, },
); );

View File

@@ -1,6 +1,5 @@
import colorToRGB from "../utils/colorToRGB"; import colorToRGB from "../utils/colorToRGB.js";
import { make1DTexture, makePassFBO, makePass } from "./utils.js"; import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
import palettePassFrag from "../../shaders/glsl/palettePass.frag.glsl";
// Maps the brightness of the rendered rain and bloom to colors // Maps the brightness of the rendered rain and bloom to colors
// in a 1D gradient palette texture generated from the passed-in color sequence // 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 // won't persist across subsequent frames. This is a safe trick
// in screen space. // in screen space.
export default ({ regl, config }, inputs) => { export default ({ regl, cache, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat); const output = makePassFBO(regl, config.useHalfFloat);
const paletteTex = makePalette(regl, config.palette); const paletteTex = makePalette(regl, config.palette);
const { const {
@@ -67,6 +66,7 @@ export default ({ regl, config }, inputs) => {
ditherMagnitude, ditherMagnitude,
} = config; } = config;
const palettePassFrag = loadText(cache, "shaders/glsl/palettePass.frag.glsl");
const render = regl({ const render = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
@@ -92,7 +92,7 @@ export default ({ regl, config }, inputs) => {
(w, h) => output.resize(w, h), (w, h) => output.resize(w, h),
(shouldRender) => { (shouldRender) => {
if (shouldRender) { if (shouldRender) {
render({ frag: palettePassFrag }); render({ frag: palettePassFrag.text() });
} }
}, },
); );

View File

@@ -1,11 +1,4 @@
import { loadImage, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js"; import { loadImage, loadText, 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";
const extractEntries = (src, keys) => const extractEntries = (src, keys) =>
Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key))); 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 brVert = [1, 1];
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert]; 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 // The volumetric mode multiplies the number of columns
// to reach the desired density, and then overlaps them // to reach the desired density, and then overlaps them
const volumetric = config.volumetric; const volumetric = config.volumetric;
@@ -69,6 +63,7 @@ export default ({ regl, cache, config, lkg }) => {
}; };
const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns); const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns);
const rainPassIntro = loadText(cache, "shaders/glsl/rainPass.intro.frag.glsl");
const introUniforms = { const introUniforms = {
...commonUniforms, ...commonUniforms,
@@ -85,6 +80,7 @@ export default ({ regl, cache, config, lkg }) => {
}); });
const raindropDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); const raindropDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
const rainPassRaindrop = loadText(cache, "shaders/glsl/rainPass.raindrop.frag.glsl");
const raindropUniforms = { const raindropUniforms = {
...commonUniforms, ...commonUniforms,
@@ -108,6 +104,7 @@ export default ({ regl, cache, config, lkg }) => {
}); });
const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
const rainPassSymbol = loadText(cache, "shaders/glsl/rainPass.symbol.frag.glsl");
const symbolUniforms = { const symbolUniforms = {
...commonUniforms, ...commonUniforms,
@@ -125,6 +122,7 @@ export default ({ regl, cache, config, lkg }) => {
}); });
const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
const rainPassEffect = loadText(cache, "shaders/glsl/rainPass.effect.frag.glsl");
const effectUniforms = { const effectUniforms = {
...commonUniforms, ...commonUniforms,
@@ -224,8 +222,6 @@ export default ({ regl, cache, config, lkg }) => {
screenSize: regl.prop("screenSize"), screenSize: regl.prop("screenSize"),
}, },
viewport: regl.prop("viewport"),
attributes: { attributes: {
aPosition: quadPositions, aPosition: quadPositions,
aCorner: Array(numQuads).fill(quadVertices), aCorner: Array(numQuads).fill(quadVertices),
@@ -237,7 +233,6 @@ export default ({ regl, cache, config, lkg }) => {
// Camera and transform math for the volumetric mode // Camera and transform math for the volumetric mode
const screenSize = [1, 1]; const screenSize = [1, 1];
//const { mat4, vec3 } = glMatrix;
const transform = mat4.create(); const transform = mat4.create();
if (volumetric && config.isometric) { if (volumetric && config.isometric) {
mat4.rotateX(transform, transform, (Math.PI * 1) / 8); mat4.rotateX(transform, transform, (Math.PI * 1) / 8);
@@ -253,7 +248,17 @@ export default ({ regl, cache, config, lkg }) => {
{ {
primary: output, 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) => { (w, h) => {
output.resize(w, h); output.resize(w, h);
const aspectRatio = 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]; [screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
}, },
(shouldRender) => { (shouldRender) => {
intro({ frag: rainPassIntro }); intro({ frag: rainPassIntro.text() });
raindrop({ frag: rainPassRaindrop }); raindrop({ frag: rainPassRaindrop.text() });
symbol({ frag: rainPassSymbol }); symbol({ frag: rainPassSymbol.text() });
effect({ frag: rainPassEffect }); effect({ frag: rainPassEffect.text() });
if (shouldRender) { if (shouldRender) {
regl.clear({ regl.clear({
@@ -292,8 +297,8 @@ export default ({ regl, cache, config, lkg }) => {
transform, transform,
camera, camera,
screenSize, screenSize,
vert: rainPassVert, vert: rainPassVert.text(),
frag: rainPassFrag, frag: rainPassFrag.text(),
glyphTransform: [1, 0, 0, 1], glyphTransform: [1, 0, 0, 1],
}); });
} }

View File

@@ -1,6 +1,5 @@
import colorToRGB from "../utils/colorToRGB"; import colorToRGB from "../utils/colorToRGB.js";
import { make1DTexture, makePassFBO, makePass } from "./utils"; import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
import stripePassFrag from "../../shaders/glsl/stripePass.frag.glsl";
// Multiplies the rendered rain and bloom by a 1D gradient texture // Multiplies the rendered rain and bloom by a 1D gradient texture
// generated from the passed-in color sequence // generated from the passed-in color sequence
@@ -28,7 +27,7 @@ const prideStripeColors = [
.map((color) => Array(2).fill(color)) .map((color) => Array(2).fill(color))
.flat(); .flat();
export default ({ regl, config }, inputs) => { export default ({ regl, cache, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat); const output = makePassFBO(regl, config.useHalfFloat);
const { const {
@@ -52,6 +51,8 @@ export default ({ regl, config }, inputs) => {
stripeColors.map((color) => [...colorToRGB(color), 1]), stripeColors.map((color) => [...colorToRGB(color), 1]),
); );
const stripePassFrag = loadText(cache, "shaders/glsl/stripePass.frag.glsl");
const render = regl({ const render = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
@@ -73,11 +74,11 @@ export default ({ regl, config }, inputs) => {
{ {
primary: output, primary: output,
}, },
null, stripePassFrag.loaded,
(w, h) => output.resize(w, h), (w, h) => output.resize(w, h),
(shouldRender) => { (shouldRender) => {
if (shouldRender) { if (shouldRender) {
render({ frag: stripePassFrag }); render({ frag: stripePassFrag.text() });
} }
}, },
); );

View File

@@ -29,7 +29,6 @@ const makeDoubleBuffer = (regl, props) => {
const isPowerOfTwo = (x) => Math.log2(x) % 1 == 0; const isPowerOfTwo = (x) => Math.log2(x) % 1 == 0;
const loadImage = (cache, regl, url, mipmap) => { const loadImage = (cache, regl, url, mipmap) => {
const key = `${url}_${mipmap}`; const key = `${url}_${mipmap}`;
if (cache.has(key)) { if (cache.has(key)) {
return cache.get(key); return cache.get(key);

View File

@@ -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 = { const fonts = {
coptic: { coptic: {
// The script the Gnostic codices were written in // The script the Gnostic codices were written in
glyphMSDFURL: msdfCoptic, glyphMSDFURL: "assets/coptic_msdf.png",
glyphSequenceLength: 32, glyphSequenceLength: 32,
glyphTextureGridSize: [8, 8], glyphTextureGridSize: [8, 8],
}, },
gothic: { gothic: {
// The script the Codex Argenteus was written in // The script the Codex Argenteus was written in
glyphMSDFURL: msdfGothic, glyphMSDFURL: "assets/gothic_msdf.png",
glyphSequenceLength: 27, glyphSequenceLength: 27,
glyphTextureGridSize: [8, 8], glyphTextureGridSize: [8, 8],
}, },
matrixcode: { matrixcode: {
// The glyphs seen in the film trilogy // The glyphs seen in the film trilogy
glyphMSDFURL: msdfMatrixCode, glyphMSDFURL: "assets/matrixcode_msdf.png",
glyphSequenceLength: 57, glyphSequenceLength: 57,
glyphTextureGridSize: [8, 8], glyphTextureGridSize: [8, 8],
}, },
/*
megacity: { megacity: {
// The glyphs seen in the film trilogy // The glyphs seen in the film trilogy
glyphMSDFURL: megacity, glyphMSDFURL: "assets/megacity_msdf.png",
glyphSequenceLength: 64, glyphSequenceLength: 64,
glyphTextureGridSize: [8, 8], glyphTextureGridSize: [8, 8],
}, },
*/
resurrections: { resurrections: {
// The glyphs seen in the film trilogy // The glyphs seen in the film trilogy
glyphMSDFURL: msdfRes, glyphMSDFURL: "assets/resurrections_msdf.png",
glintMSDFURL: msdfResGlint, glintMSDFURL: "assets/resurrections_glint_msdf.png",
glyphSequenceLength: 135, glyphSequenceLength: 135,
glyphTextureGridSize: [13, 12], glyphTextureGridSize: [13, 12],
}, },
/*
huberfishA: { huberfishA: {
glyphMSDFURL: msdfHuberfishA, glyphMSDFURL: "assets/huberfish_a_msdf.png",
glyphSequenceLength: 34, glyphSequenceLength: 34,
glyphTextureGridSize: [6, 6], glyphTextureGridSize: [6, 6],
}, },
huberfishD: { huberfishD: {
glyphMSDFURL: msdfHuberfishD, glyphMSDFURL: "assets/huberfish_d_msdf.png",
glyphSequenceLength: 34, glyphSequenceLength: 34,
glyphTextureGridSize: [6, 6], glyphTextureGridSize: [6, 6],
}, },
gtarg_tenretniolleh: { gtarg_tenretniolleh: {
glyphMSDFURL: msdfGtargTenretni, glyphMSDFURL: "assets/gtarg_tenretniolleh_msdf.png",
glyphSequenceLength: 36, glyphSequenceLength: 36,
glyphTextureGridSize: [6, 6], glyphTextureGridSize: [6, 6],
}, },
gtarg_alientext: { gtarg_alientext: {
glyphMSDFURL: msdfGtargAlienText, glyphMSDFURL: "assets/gtarg_alientext_msdf.png",
glyphSequenceLength: 38, glyphSequenceLength: 38,
glyphTextureGridSize: [8, 5], glyphTextureGridSize: [8, 5],
}, },
neomatrixology: { neomatrixology: {
glyphMSDFURL: msdfNeoMatrixology, glyphMSDFURL: "assets/neomatrixology_msdf.png",
glyphSequenceLength: 12, glyphSequenceLength: 12,
glyphTextureGridSize: [4, 4], glyphTextureGridSize: [4, 4],
}, },
*/
}; };
const textureURLs = { const textureURLs = {
sand: texSand, // sand: "assets/sand.png",
pixels: texPixels, // pixels: "assets/pixel_grid.png",
mesh: texMesh, mesh: "assets/mesh.png",
metal: texMetal, metal: "assets/metal.png",
}; };
const hsl = (...values) => ({ space: "hsl", values }); const hsl = (...values) => ({ space: "hsl", values });
@@ -149,6 +136,7 @@ const defaults = {
const versions = { const versions = {
classic: {}, classic: {},
/*
megacity: { megacity: {
font: "megacity", font: "megacity",
animationSpeed: 0.5, animationSpeed: 0.5,
@@ -167,6 +155,7 @@ const versions = {
cursorColor: hsl(0.167, 1, 0.75), cursorColor: hsl(0.167, 1, 0.75),
cursorIntensity: 2, cursorIntensity: 2,
}, },
*/
operator: { operator: {
cursorColor: hsl(0.375, 1, 0.66), cursorColor: hsl(0.375, 1, 0.66),
cursorIntensity: 3, cursorIntensity: 3,
@@ -279,6 +268,7 @@ const versions = {
raindropLength: 0.3, raindropLength: 0.3,
density: 0.75, density: 0.75,
}, },
/*
morpheus: { morpheus: {
font: "resurrections", font: "resurrections",
glintTexture: "mesh", glintTexture: "mesh",
@@ -368,7 +358,7 @@ const versions = {
// { color: hsl(0.1, 1.0, 0.9), at: 1.0 }, // { color: hsl(0.1, 1.0, 0.9), at: 1.0 },
], ],
}, },
*/
["3d"]: { ["3d"]: {
volumetric: true, volumetric: true,
fallSpeed: 0.5, fallSpeed: 0.5,

View File

@@ -6,8 +6,6 @@ import {
makeBindGroup, makeBindGroup,
makePass, makePass,
} from "./utils.js"; } from "./utils.js";
import bloomBlurShader from "../../shaders/wgsl/bloomBlur.wgsl";
import bloomCombineShader from "../../shaders/wgsl/bloomCombine.wgsl";
// const makePyramid = makeComputeTarget; // const makePyramid = makeComputeTarget;
@@ -56,8 +54,8 @@ export default ({ config, device }) => {
} }
const assets = [ const assets = [
loadShader(device, bloomBlurShader), loadShader(device, "shaders/wgsl/bloomBlur.wgsl"),
loadShader(device, bloomCombineShader), loadShader(device, "shaders/wgsl/bloomCombine.wgsl"),
]; ];
const linearSampler = device.createSampler({ const linearSampler = device.createSampler({

View File

@@ -1,7 +1,5 @@
import { loadShader, makeBindGroup, makePass } from "./utils.js"; 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. // 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. // Until then, this render pass does the job.
@@ -23,7 +21,7 @@ export default ({ device, canvasFormat, canvasContext }) => {
let renderPipeline; let renderPipeline;
let renderBindGroup; let renderBindGroup;
const assets = [loadShader(device, endPassShader)]; const assets = [loadShader(device, "shaders/wgsl/endPass.wgsl")];
const loaded = (async () => { const loaded = (async () => {
const [imageShader] = await Promise.all(assets); const [imageShader] = await Promise.all(assets);

View File

@@ -7,7 +7,6 @@ import {
makeBindGroup, makeBindGroup,
makePass, makePass,
} from "./utils.js"; } from "./utils.js";
import imagePassShader from "../../shaders/wgsl/imagePass.wgsl";
// Multiplies the rendered rain and bloom by a loaded in image // Multiplies the rendered rain and bloom by a loaded in image
@@ -16,7 +15,10 @@ const defaultBGURL =
export default ({ config, cache, device }) => { export default ({ config, cache, device }) => {
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; 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({ const linearSampler = device.createSampler({
magFilter: "linear", magFilter: "linear",

View File

@@ -1,5 +1,6 @@
import { structs } from "../../lib/gpu-buffer.js"; import { structs } from "../../lib/gpu-buffer.js";
import { makeUniformBuffer, makePipeline } from "./utils.js"; import { makeUniformBuffer, makePipeline } from "./utils.js";
import fetchLibraries from "../fetchLibraries.js";
import makeRain from "./rainPass.js"; import makeRain from "./rainPass.js";
import makeBloomPass from "./bloomPass.js"; import makeBloomPass from "./bloomPass.js";
@@ -23,7 +24,12 @@ const effects = {
mirror: makeMirrorPass, mirror: makeMirrorPass,
}; };
let glMatrix;
export const init = async (canvas) => { export const init = async (canvas) => {
const libraries = await fetchLibraries();
glMatrix = libraries.glMatrix;
const resize = () => { const resize = () => {
const devicePixelRatio = window.devicePixelRatio ?? 1; const devicePixelRatio = window.devicePixelRatio ?? 1;
canvas.width = Math.ceil(canvas.clientWidth * devicePixelRatio * rain.resolution); canvas.width = Math.ceil(canvas.clientWidth * devicePixelRatio * rain.resolution);
@@ -50,7 +56,16 @@ export const init = async (canvas) => {
const device = await adapter.requestDevice(); const device = await adapter.requestDevice();
const cache = new Map(); 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("dblclick", doubleClick);
window.addEventListener("resize", resize); window.addEventListener("resize", resize);
@@ -103,6 +118,7 @@ export const formulate = async (rain, config) => {
cameraTex, cameraTex,
cameraAspectRatio, cameraAspectRatio,
cameraSize, cameraSize,
glMatrix,
}; };
const effectName = config.effect in effects ? config.effect : "palette"; const effectName = config.effect in effects ? config.effect : "palette";

View File

@@ -6,7 +6,6 @@ import {
makeBindGroup, makeBindGroup,
makePass, makePass,
} from "./utils.js"; } from "./utils.js";
import mirrorPassShader from "../../shaders/wgsl/mirrorPass.wgsl";
let start; let start;
const numTouches = 5; const numTouches = 5;
@@ -26,7 +25,7 @@ window.onclick = (e) => {
}; };
export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) => { export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) => {
const assets = [loadShader(device, mirrorPassShader)]; const assets = [loadShader(device, "shaders/wgsl/mirrorPass.wgsl")];
const linearSampler = device.createSampler({ const linearSampler = device.createSampler({
magFilter: "linear", magFilter: "linear",

View File

@@ -7,7 +7,6 @@ import {
makeComputeTarget, makeComputeTarget,
makePass, makePass,
} from "./utils.js"; } from "./utils.js";
import palettePassShader from "../../shaders/wgsl/palettePass.wgsl";
// Maps the brightness of the rendered rain and bloom to colors // Maps the brightness of the rendered rain and bloom to colors
// in a linear gradient buffer generated from the passed-in color sequence // in a linear gradient buffer generated from the passed-in color sequence
@@ -87,7 +86,7 @@ export default ({ config, device, timeBuffer }) => {
let output; let output;
let screenSize; let screenSize;
const assets = [loadShader(device, palettePassShader)]; const assets = [loadShader(device, "shaders/wgsl/palettePass.wgsl")];
const loaded = (async () => { const loaded = (async () => {
const [paletteShader] = await Promise.all(assets); const [paletteShader] = await Promise.all(assets);

View File

@@ -7,8 +7,6 @@ import {
makeBindGroup, makeBindGroup,
makePass, makePass,
} from "./utils.js"; } from "./utils.js";
import { mat2, mat4, vec2, vec3 } from "gl-matrix";
import rainPassShader from "../../shaders/wgsl/rainPass.wgsl";
const rippleTypes = { const rippleTypes = {
box: 0, box: 0,
@@ -35,13 +33,14 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize, gly
return makeUniformBuffer(device, configUniforms, configData); 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 = [ const assets = [
loadTexture(device, cache, config.glyphMSDFURL), loadTexture(device, cache, config.glyphMSDFURL),
loadTexture(device, cache, config.glintMSDFURL), loadTexture(device, cache, config.glintMSDFURL),
loadTexture(device, cache, config.baseTextureURL, false, true), loadTexture(device, cache, config.baseTextureURL, false, true),
loadTexture(device, cache, config.glintTextureURL, 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 // The volumetric mode multiplies the number of columns

View File

@@ -8,7 +8,6 @@ import {
makeComputeTarget, makeComputeTarget,
makePass, makePass,
} from "./utils.js"; } from "./utils.js";
import stripePassShader from "../../shaders/wgsl/stripePass.wgsl";
// Multiplies the rendered rain and bloom by a 1D gradient texture // Multiplies the rendered rain and bloom by a 1D gradient texture
// generated from the passed-in color sequence // generated from the passed-in color sequence
@@ -69,7 +68,7 @@ export default ({ config, device, timeBuffer }) => {
let output; let output;
let screenSize; let screenSize;
const assets = [loadShader(device, stripePassShader)]; const assets = [loadShader(device, "shaders/wgsl/stripePass.wgsl")];
const loaded = (async () => { const loaded = (async () => {
const [stripeShader] = await Promise.all(assets); const [stripeShader] = await Promise.all(assets);

View File

@@ -1,5 +1,4 @@
const loadTexture = async (device, cache, url) => { const loadTexture = async (device, cache, url) => {
const key = url; const key = url;
if (cache.has(key)) { if (cache.has(key)) {
return cache.get(key); return cache.get(key);
@@ -63,9 +62,9 @@ const makeComputeTarget = (device, size, mipLevelCount = 1) =>
GPUTextureUsage.STORAGE_BINDING, GPUTextureUsage.STORAGE_BINDING,
}); });
const loadShader = async (device, code /*text*/) => { const loadShader = async (device, url) => {
// const response = await fetch(url); const response = await fetch(url);
// const code = await response.text(); const code = await response.text();
return { return {
code, code,
module: device.createShaderModule({ code }), module: device.createShaderModule({ code }),

116
package-lock.json generated
View File

@@ -19,10 +19,12 @@
"@babel/preset-react": "^7.22.5", "@babel/preset-react": "^7.22.5",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-url": "^8.0.2", "@rollup/plugin-url": "^8.0.2",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"copy-webpack-plugin": "^13.0.0",
"html-webpack-plugin": "^5.5.3", "html-webpack-plugin": "^5.5.3",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
@@ -1966,6 +1968,28 @@
"url": "https://github.com/sponsors/jonschlinkert" "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": { "node_modules/@rollup/plugin-node-resolve": {
"version": "16.0.1", "version": "16.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
@@ -3406,6 +3430,43 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true "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": { "node_modules/core-js-compat": {
"version": "3.32.0", "version": "3.32.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz",
@@ -5190,6 +5251,16 @@
"node": ">=6" "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": { "node_modules/minimalistic-assert": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -6711,6 +6782,51 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true "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": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",

View File

@@ -26,10 +26,12 @@
"@babel/preset-react": "^7.22.5", "@babel/preset-react": "^7.22.5",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-url": "^8.0.2", "@rollup/plugin-url": "^8.0.2",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"copy-webpack-plugin": "^13.0.0",
"html-webpack-plugin": "^5.5.3", "html-webpack-plugin": "^5.5.3",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",

View File

@@ -6,6 +6,7 @@ import url from "@rollup/plugin-url";
import { visualizer } from "rollup-plugin-visualizer"; // <- size report import { visualizer } from "rollup-plugin-visualizer"; // <- size report
import terser from "@rollup/plugin-terser"; import terser from "@rollup/plugin-terser";
import { string } from "rollup-plugin-string"; import { string } from "rollup-plugin-string";
import image from "@rollup/plugin-image";
export default { export default {
input: "js/Matrix.js", input: "js/Matrix.js",
@@ -14,7 +15,8 @@ export default {
peerDepsExternal(), // auto-exclude peerDeps peerDepsExternal(), // auto-exclude peerDeps
nodeResolve(), // so Rollup can find deps in node_modules nodeResolve(), // so Rollup can find deps in node_modules
string({ include: ["**/*.glsl"] }), string({ include: ["**/*.glsl"] }),
url({ include: ["**/*.png"], limit: 0 }), string({ include: ["**/*.wgsl"] }),
image({ include: ["**/*.png"], limit: 0 }),
babel({ babel({
exclude: "node_modules/**", // transpile JSX exclude: "node_modules/**", // transpile JSX
babelHelpers: "bundled", babelHelpers: "bundled",
@@ -34,6 +36,7 @@ export default {
], ],
output: [ output: [
{ {
inlineDynamicImports: true,
file: "dist/index.cjs.js", file: "dist/index.cjs.js",
format: "cjs", format: "cjs",
exports: "named", exports: "named",

View File

@@ -1,6 +1,7 @@
const webpack = require("webpack"); const webpack = require("webpack");
const path = require("path"); const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
module.exports = { module.exports = {
mode: "development", mode: "development",
@@ -12,19 +13,14 @@ module.exports = {
exclude: /node_modules/, exclude: /node_modules/,
use: ["babel-loader"], use: ["babel-loader"],
}, },
// {
// test: /\.css$/,
// use: ["style-loader", "css-loader"],
// },
{ {
test: /\.css$/, test: /\.(png|jpe?g|svg|glsl|wgsl)?$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(png|j?g|svg|gif)?$/,
type: "asset/resource", type: "asset/resource",
}, },
{
test: /\.(glsl|frag|vert|wgsl)$/i,
exclude: /node_modules/,
use: ["raw-loader"],
},
], ],
}, },
resolve: { resolve: {
@@ -41,6 +37,12 @@ module.exports = {
template: path.resolve(__dirname, "public/index.html"), template: path.resolve(__dirname, "public/index.html"),
filename: "index.html", filename: "index.html",
}), }),
new CopyPlugin({
patterns: [
{ from: "assets", to: "assets" },
{ from: "shaders", to: "shaders" },
],
}),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
], ],
devServer: { devServer: {