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,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() });
},
);
};

View File

@@ -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() });
}
},
);

View File

@@ -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();

View File

@@ -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() });
}
},
);

View File

@@ -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() });
}
},
);

View File

@@ -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],
});
}

View File

@@ -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() });
}
},
);

View File

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