Ran the format script

This commit is contained in:
Rezmason
2025-05-05 08:52:35 -07:00
parent 7a10893486
commit 237990b44c
25 changed files with 1474 additions and 1403 deletions

View File

@@ -107,34 +107,32 @@ import makeConfig from "./utils/config";
/** @param {MatrixProps} props */ /** @param {MatrixProps} props */
export const Matrix = memo((props) => { export const Matrix = memo((props) => {
const { style, className, ...rest } = props; const { style, className, ...rest } = props;
const elProps = { style, className }; const elProps = { style, className };
const matrix = useRef(null); const matrix = useRef(null);
const rainRef = useRef(null); const rainRef = useRef(null);
const canvasRef = useRef(null); const canvasRef = useRef(null);
useEffect(() => { useEffect(() => {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.style.width = "100%"; canvas.style.width = "100%";
canvas.style.height = "100%"; canvas.style.height = "100%";
canvasRef.current = canvas; canvasRef.current = canvas;
}, []); }, []);
useEffect(() => { useEffect(() => {
matrix.current.appendChild(canvasRef.current); matrix.current.appendChild(canvasRef.current);
const gl = canvasRef.current.getContext("webgl"); const gl = canvasRef.current.getContext("webgl");
createRain(canvasRef.current, makeConfig({ ...rest }), gl).then( createRain(canvasRef.current, makeConfig({ ...rest }), gl).then((handles) => {
(handles) => { rainRef.current = handles;
rainRef.current = handles; });
}
);
return () => { return () => {
if (rainRef.current) { if (rainRef.current) {
destroyRain(rainRef.current); destroyRain(rainRef.current);
} }
}; };
}, [props]); }, [props]);
return <div ref={matrix} {...elProps}></div>; return <div ref={matrix} {...elProps}></div>;
}); });

View File

@@ -6,43 +6,43 @@ import { Matrix } from "./Matrix";
const root = createRoot(document.getElementById("root")); const root = createRoot(document.getElementById("root"));
let idx = 1; let idx = 1;
const versions = [ const versions = [
"3d", "3d",
"trinity", "trinity",
"bugs", "bugs",
"megacity", "megacity",
"nightmare", "nightmare",
"paradise", "paradise",
"resurrections", "resurrections",
"operator", "operator",
"holoplay", "holoplay",
"throwback", "throwback",
"updated", "updated",
"1999", "1999",
"2003", "2003",
"2021", "2021",
]; ];
const App = () => { const App = () => {
const [version, setVersion] = React.useState(versions[0]); const [version, setVersion] = React.useState(versions[0]);
// const [number, setNumber] = React.useState(0); // const [number, setNumber] = React.useState(0);
const onButtonClick = () => { const onButtonClick = () => {
setVersion((s) => { setVersion((s) => {
const newVersion = versions[idx]; const newVersion = versions[idx];
idx = (idx + 1) % versions.length; idx = (idx + 1) % versions.length;
console.log(newVersion); console.log(newVersion);
return newVersion; return newVersion;
}); });
}; };
// const newNum = () => setNumber((n) => n + 1); // const newNum = () => setNumber((n) => n + 1);
console.log("version", version); console.log("version", version);
// console.log("num", number); // console.log("num", number);
return ( return (
<div> <div>
<h1>Rain</h1> <h1>Rain</h1>
<button onClick={onButtonClick}>change version</button> <button onClick={onButtonClick}>change version</button>
{/* <button onClick={newNum}>change number</button> */} {/* <button onClick={newNum}>change number</button> */}
<Matrix version={version} density={7.0} /> <Matrix version={version} density={7.0} />
</div> </div>
); );
}; };
root.render(<App />); root.render(<App />);

View File

@@ -7,7 +7,11 @@ document.addEventListener("touchmove", (e) => e.preventDefault(), {
}); });
const supportsWebGPU = async () => { const supportsWebGPU = async () => {
return window.GPUQueue != null && navigator.gpu != null && navigator.gpu.getPreferredCanvasFormat != null; return (
window.GPUQueue != null &&
navigator.gpu != null &&
navigator.gpu.getPreferredCanvasFormat != null
);
}; };
const isRunningSwiftShader = () => { const isRunningSwiftShader = () => {

View File

@@ -1,7 +1,7 @@
import { makePassFBO, makePass } from "./utils"; import { makePassFBO, makePass } from "./utils";
import highPassFrag from '../../shaders/glsl/bloomPass.highPass.frag.glsl'; import highPassFrag from "../../shaders/glsl/bloomPass.highPass.frag.glsl";
import blurFrag from '../../shaders/glsl/bloomPass.blur.frag.glsl'; import blurFrag from "../../shaders/glsl/bloomPass.blur.frag.glsl";
import combineFrag from '../../shaders/glsl/bloomPass.combine.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.
@@ -16,7 +16,9 @@ const makePyramid = (regl, height, halfFloat) =>
.map((_) => makePassFBO(regl, halfFloat)); .map((_) => makePassFBO(regl, halfFloat));
const resizePyramid = (pyramid, vw, vh, scale) => const resizePyramid = (pyramid, vw, vh, scale) =>
pyramid.forEach((fbo, index) => fbo.resize(Math.floor((vw * scale) / 2 ** index), Math.floor((vh * scale) / 2 ** index))); pyramid.forEach((fbo, index) =>
fbo.resize(Math.floor((vw * scale) / 2 ** index), Math.floor((vh * scale) / 2 ** index)),
);
export default ({ regl, config }, inputs) => { export default ({ regl, config }, inputs) => {
const { bloomStrength, bloomSize, highPassThreshold } = config; const { bloomStrength, bloomSize, highPassThreshold } = config;
@@ -94,12 +96,16 @@ export default ({ regl, config }, inputs) => {
const highPassFBO = highPassPyramid[i]; const highPassFBO = highPassPyramid[i];
const hBlurFBO = hBlurPyramid[i]; const hBlurFBO = hBlurPyramid[i];
const vBlurFBO = vBlurPyramid[i]; const vBlurFBO = vBlurPyramid[i];
highPass({ fbo: highPassFBO, frag: highPassFrag, tex: i === 0 ? inputs.primary : highPassPyramid[i - 1] }); highPass({
fbo: highPassFBO,
frag: highPassFrag,
tex: i === 0 ? inputs.primary : highPassPyramid[i - 1],
});
blur({ fbo: hBlurFBO, frag: blurFrag, tex: highPassFBO, direction: [1, 0] }); blur({ fbo: hBlurFBO, frag: blurFrag, tex: highPassFBO, direction: [1, 0] });
blur({ fbo: vBlurFBO, frag: blurFrag, tex: hBlurFBO, direction: [0, 1] }); blur({ fbo: vBlurFBO, frag: blurFrag, tex: hBlurFBO, direction: [0, 1] });
} }
combine({ frag: combineFrag }); combine({ frag: combineFrag });
} },
); );
}; };

View File

@@ -3,7 +3,8 @@ 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
const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg"; const defaultBGURL =
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg";
export default ({ regl, config }, inputs) => { export default ({ regl, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat); const output = makePassFBO(regl, config.useHalfFloat);
@@ -28,6 +29,6 @@ export default ({ regl, config }, inputs) => {
if (shouldRender) { if (shouldRender) {
render({ frag: imagePassFrag }); render({ frag: imagePassFrag });
} }
} },
); );
}; };

View File

@@ -2,100 +2,95 @@
const HoloPlayCore = require("holoplay-core"); const HoloPlayCore = require("holoplay-core");
const recordedDevice = { const recordedDevice = {
buttons: [0, 0, 0, 0], buttons: [0, 0, 0, 0],
calibration: { calibration: {
DPI: { value: 324 }, DPI: { value: 324 },
center: { value: 0.15018756687641144 }, center: { value: 0.15018756687641144 },
configVersion: "3.0", configVersion: "3.0",
flipImageX: { value: 0 }, flipImageX: { value: 0 },
flipImageY: { value: 0 }, flipImageY: { value: 0 },
flipSubp: { value: 0 }, flipSubp: { value: 0 },
fringe: { value: 0 }, fringe: { value: 0 },
invView: { value: 1 }, invView: { value: 1 },
pitch: { value: 52.58013153076172 }, pitch: { value: 52.58013153076172 },
screenH: { value: 2048 }, screenH: { value: 2048 },
screenW: { value: 1536 }, screenW: { value: 1536 },
slope: { value: -7.145165920257568 }, slope: { value: -7.145165920257568 },
verticalAngle: { value: 0 }, verticalAngle: { value: 0 },
viewCone: { value: 40 }, viewCone: { value: 40 },
}, },
defaultQuilt: { defaultQuilt: {
quiltAspect: 0.75, quiltAspect: 0.75,
quiltX: 3840, quiltX: 3840,
quiltY: 3840, quiltY: 3840,
tileX: 8, tileX: 8,
tileY: 6, tileY: 6,
}, },
hardwareVersion: "portrait", hardwareVersion: "portrait",
hwid: "LKG-P11063", hwid: "LKG-P11063",
index: 0, index: 0,
joystickIndex: -1, joystickIndex: -1,
state: "ok", state: "ok",
unityIndex: 1, unityIndex: 1,
windowCoords: [1440, 900], windowCoords: [1440, 900],
}; };
const interpretDevice = (device) => { const interpretDevice = (device) => {
if (device == null) { if (device == null) {
return { enabled: false, tileX: 1, tileY: 1 }; return { enabled: false, tileX: 1, tileY: 1 };
} }
const fov = 15; const fov = 15;
const calibration = Object.fromEntries( const calibration = Object.fromEntries(
Object.entries(device.calibration) Object.entries(device.calibration)
.map(([key, value]) => [key, value.value]) .map(([key, value]) => [key, value.value])
.filter(([key, value]) => value != null) .filter(([key, value]) => value != null),
); );
const screenInches = calibration.screenW / calibration.DPI; const screenInches = calibration.screenW / calibration.DPI;
const pitch = const pitch = calibration.pitch * screenInches * Math.cos(Math.atan(1.0 / calibration.slope));
calibration.pitch * const tilt =
screenInches * (calibration.screenH / (calibration.screenW * calibration.slope)) *
Math.cos(Math.atan(1.0 / calibration.slope)); -(calibration.flipImageX * 2 - 1);
const tilt = const subp = 1 / (calibration.screenW * 3);
(calibration.screenH / (calibration.screenW * calibration.slope)) *
-(calibration.flipImageX * 2 - 1);
const subp = 1 / (calibration.screenW * 3);
const defaultQuilt = device.defaultQuilt; const defaultQuilt = device.defaultQuilt;
const quiltViewPortion = [ const quiltViewPortion = [
(Math.floor(defaultQuilt.quiltX / defaultQuilt.tileX) * (Math.floor(defaultQuilt.quiltX / defaultQuilt.tileX) * defaultQuilt.tileX) /
defaultQuilt.tileX) / defaultQuilt.quiltX,
defaultQuilt.quiltX, (Math.floor(defaultQuilt.quiltY / defaultQuilt.tileY) * defaultQuilt.tileY) /
(Math.floor(defaultQuilt.quiltY / defaultQuilt.tileY) * defaultQuilt.quiltY,
defaultQuilt.tileY) / ];
defaultQuilt.quiltY,
];
return { return {
...defaultQuilt, ...defaultQuilt,
...calibration, ...calibration,
pitch, pitch,
tilt, tilt,
subp, subp,
quiltViewPortion, quiltViewPortion,
fov, fov,
enabled: true, enabled: true,
}; };
}; };
export default async (useHoloplay = false, useRecordedDevice = false) => { export default async (useHoloplay = false, useRecordedDevice = false) => {
if (!useHoloplay) { if (!useHoloplay) {
return interpretDevice(null); return interpretDevice(null);
} }
// const HoloPlayCore = await import("../../lib/holoplaycore.module.js"); // const HoloPlayCore = await import("../../lib/holoplaycore.module.js");
const device = await new Promise( const device = await new Promise(
(resolve, reject) => (resolve, reject) =>
new HoloPlayCore.Client( new HoloPlayCore.Client(
(data) => resolve(data.devices?.[0]), (data) => resolve(data.devices?.[0]),
(error) => resolve(null) (error) => resolve(null),
) ),
); );
if (device == null && useRecordedDevice) { if (device == null && useRecordedDevice) {
return interpretDevice(recordedDevice); return interpretDevice(recordedDevice);
} }
return interpretDevice(device); return interpretDevice(device);
}; };

View File

@@ -7,24 +7,20 @@ import makeStripePass from "./stripePass.js";
import makeImagePass from "./imagePass.js"; import makeImagePass from "./imagePass.js";
import makeQuiltPass from "./quiltPass.js"; import makeQuiltPass from "./quiltPass.js";
import makeMirrorPass from "./mirrorPass.js"; import makeMirrorPass from "./mirrorPass.js";
import { import { setupCamera, cameraCanvas, cameraAspectRatio } from "../utils/camera.js";
setupCamera,
cameraCanvas,
cameraAspectRatio,
} from "../utils/camera.js";
import getLKG from "./lkgHelper.js"; import getLKG from "./lkgHelper.js";
const effects = { const effects = {
none: null, none: null,
plain: makePalettePass, plain: makePalettePass,
palette: makePalettePass, palette: makePalettePass,
customStripes: makeStripePass, customStripes: makeStripePass,
stripes: makeStripePass, stripes: makeStripePass,
pride: makeStripePass, pride: makeStripePass,
transPride: makeStripePass, transPride: makeStripePass,
trans: makeStripePass, trans: makeStripePass,
image: makeImagePass, image: makeImagePass,
mirror: makeMirrorPass, mirror: makeMirrorPass,
}; };
const dimensions = { width: 1, height: 1 }; const dimensions = { width: 1, height: 1 };
@@ -41,132 +37,124 @@ const dimensions = { width: 1, height: 1 };
// Promise.all([loadJS("lib/regl.min.js"), loadJS("lib/gl-matrix.js")]); // Promise.all([loadJS("lib/regl.min.js"), loadJS("lib/gl-matrix.js")]);
export const createRain = async (canvas, config, gl) => { export const createRain = async (canvas, config, gl) => {
const resize = () => { const resize = () => {
const dpr = window.devicePixelRatio || 1; const dpr = window.devicePixelRatio || 1;
canvas.width = Math.ceil(window.innerWidth * dpr * config.resolution); canvas.width = Math.ceil(window.innerWidth * dpr * config.resolution);
canvas.height = Math.ceil(window.innerHeight * dpr * config.resolution); canvas.height = Math.ceil(window.innerHeight * dpr * config.resolution);
}; };
window.onresize = resize; window.onresize = resize;
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) { if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
window.ondblclick = () => { window.ondblclick = () => {
if (document.fullscreenElement == null) { if (document.fullscreenElement == null) {
if (canvas.webkitRequestFullscreen != null) { if (canvas.webkitRequestFullscreen != null) {
canvas.webkitRequestFullscreen(); canvas.webkitRequestFullscreen();
} else { } else {
canvas.requestFullscreen(); canvas.requestFullscreen();
} }
} else { } else {
document.exitFullscreen(); document.exitFullscreen();
} }
}; };
} }
resize(); resize();
if (config.useCamera) { if (config.useCamera) {
await setupCamera(); await setupCamera();
} }
const extensions = [ const extensions = ["OES_texture_half_float", "OES_texture_half_float_linear"];
"OES_texture_half_float", // These extensions are also needed, but Safari misreports that they are missing
"OES_texture_half_float_linear", const optionalExtensions = [
]; "EXT_color_buffer_half_float",
// These extensions are also needed, but Safari misreports that they are missing "WEBGL_color_buffer_float",
const optionalExtensions = [ "OES_standard_derivatives",
"EXT_color_buffer_half_float", ];
"WEBGL_color_buffer_float",
"OES_standard_derivatives",
];
switch (config.testFix) { switch (config.testFix) {
case "fwidth_10_1_2022_A": case "fwidth_10_1_2022_A":
extensions.push("OES_standard_derivatives"); extensions.push("OES_standard_derivatives");
break; break;
case "fwidth_10_1_2022_B": case "fwidth_10_1_2022_B":
optionalExtensions.forEach((ext) => extensions.push(ext)); optionalExtensions.forEach((ext) => extensions.push(ext));
extensions.length = 0; extensions.length = 0;
break; break;
} }
const regl = createREGL({ const regl = createREGL({
gl, gl,
pixelRatio: 1, pixelRatio: 1,
extensions, extensions,
optionalExtensions, optionalExtensions,
}); });
const cameraTex = regl.texture(cameraCanvas); const cameraTex = regl.texture(cameraCanvas);
const lkg = await getLKG(config.useHoloplay, true); 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, config, lkg, cameraTex, cameraAspectRatio }; const context = { regl, config, lkg, cameraTex, cameraAspectRatio };
const pipeline = makePipeline(context, [ const pipeline = makePipeline(context, [
makeRain, makeRain,
makeBloomPass, makeBloomPass,
effects[effectName], effects[effectName],
makeQuiltPass, 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 });
await Promise.all(pipeline.map((step) => step.ready)); await Promise.all(pipeline.map((step) => step.ready));
pipeline.forEach((step) => step.setSize(canvas.width, canvas.height)); pipeline.forEach((step) => step.setSize(canvas.width, canvas.height));
dimensions.width = canvas.width; dimensions.width = canvas.width;
dimensions.height = canvas.height; dimensions.height = canvas.height;
const targetFrameTimeMilliseconds = 1000 / config.fps; const targetFrameTimeMilliseconds = 1000 / config.fps;
let last = NaN; let last = NaN;
const tick = regl.frame(({ viewportWidth, viewportHeight }) => { const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
if (config.once) { if (config.once) {
tick.cancel(); tick.cancel();
} }
const now = regl.now() * 1000; const now = regl.now() * 1000;
if (isNaN(last)) { if (isNaN(last)) {
last = now; last = now;
} }
const shouldRender = const shouldRender =
config.fps >= 60 || config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once == true;
now - last >= targetFrameTimeMilliseconds ||
config.once == true;
if (shouldRender) { if (shouldRender) {
while (now - targetFrameTimeMilliseconds > last) { while (now - targetFrameTimeMilliseconds > last) {
last += targetFrameTimeMilliseconds; last += targetFrameTimeMilliseconds;
} }
} }
if (config.useCamera) { if (config.useCamera) {
cameraTex(cameraCanvas); cameraTex(cameraCanvas);
} }
if ( if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) {
dimensions.width !== viewportWidth || dimensions.width = viewportWidth;
dimensions.height !== viewportHeight dimensions.height = viewportHeight;
) { for (const step of pipeline) {
dimensions.width = viewportWidth; step.setSize(viewportWidth, viewportHeight);
dimensions.height = viewportHeight; }
for (const step of pipeline) { }
step.setSize(viewportWidth, viewportHeight); fullScreenQuad(() => {
} for (const step of pipeline) {
} step.execute(shouldRender);
fullScreenQuad(() => { }
for (const step of pipeline) { drawToScreen();
step.execute(shouldRender); });
} });
drawToScreen();
});
});
return { regl, tick, canvas }; return { regl, tick, canvas };
}; };
export const destroyRain = ({ regl, tick, canvas }) => { export const destroyRain = ({ regl, tick, canvas }) => {
tick.cancel(); // stop RAF tick.cancel(); // stop RAF
regl.destroy(); // release all GPU resources & event listeners regl.destroy(); // release all GPU resources & event listeners
//canvas.remove(); // drop from the DOM //canvas.remove(); // drop from the DOM
}; };

View File

@@ -45,6 +45,6 @@ export default ({ regl, config, cameraTex, cameraAspectRatio }, inputs) => {
if (shouldRender) { if (shouldRender) {
render({ frag: mirrorPassFrag }); render({ frag: mirrorPassFrag });
} }
} },
); );
}; };

View File

@@ -8,47 +8,45 @@ import palettePassFrag from "../../shaders/glsl/palettePass.frag.glsl";
// This shader introduces noise into the renders, to avoid banding // This shader introduces noise into the renders, to avoid banding
const makePalette = (regl, entries) => { const makePalette = (regl, entries) => {
const PALETTE_SIZE = 2048; const PALETTE_SIZE = 2048;
const paletteColors = Array(PALETTE_SIZE); const paletteColors = Array(PALETTE_SIZE);
// Convert HSL gradient into sorted RGB gradient, capping the ends // Convert HSL gradient into sorted RGB gradient, capping the ends
const sortedEntries = entries const sortedEntries = entries
.slice() .slice()
.sort((e1, e2) => e1.at - e2.at) .sort((e1, e2) => e1.at - e2.at)
.map((entry) => ({ .map((entry) => ({
rgb: colorToRGB(entry.color), rgb: colorToRGB(entry.color),
arrayIndex: Math.floor( arrayIndex: Math.floor(Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1)),
Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1) }));
), sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 });
})); sortedEntries.push({
sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 }); rgb: sortedEntries[sortedEntries.length - 1].rgb,
sortedEntries.push({ arrayIndex: PALETTE_SIZE - 1,
rgb: sortedEntries[sortedEntries.length - 1].rgb, });
arrayIndex: PALETTE_SIZE - 1,
});
// Interpolate between the sorted RGB entries to generate // Interpolate between the sorted RGB entries to generate
// the palette texture data // the palette texture data
sortedEntries.forEach((entry, index) => { sortedEntries.forEach((entry, index) => {
paletteColors[entry.arrayIndex] = entry.rgb.slice(); paletteColors[entry.arrayIndex] = entry.rgb.slice();
if (index + 1 < sortedEntries.length) { if (index + 1 < sortedEntries.length) {
const nextEntry = sortedEntries[index + 1]; const nextEntry = sortedEntries[index + 1];
const diff = nextEntry.arrayIndex - entry.arrayIndex; const diff = nextEntry.arrayIndex - entry.arrayIndex;
for (let i = 0; i < diff; i++) { for (let i = 0; i < diff; i++) {
const ratio = i / diff; const ratio = i / diff;
paletteColors[entry.arrayIndex + i] = [ paletteColors[entry.arrayIndex + i] = [
entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio, entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio,
entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio, entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio,
entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio, entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio,
]; ];
} }
} }
}); });
return make1DTexture( return make1DTexture(
regl, regl,
paletteColors.map((rgb) => [...rgb, 1]) paletteColors.map((rgb) => [...rgb, 1]),
); );
}; };
// The rendered texture's values are mapped to colors in a palette texture. // The rendered texture's values are mapped to colors in a palette texture.
@@ -58,44 +56,44 @@ const makePalette = (regl, entries) => {
// in screen space. // in screen space.
export default ({ regl, config }, inputs) => { export default ({ regl, 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 {
backgroundColor, backgroundColor,
cursorColor, cursorColor,
glintColor, glintColor,
cursorIntensity, cursorIntensity,
glintIntensity, glintIntensity,
ditherMagnitude, ditherMagnitude,
} = config; } = config;
const render = regl({ const render = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
backgroundColor: colorToRGB(backgroundColor), backgroundColor: colorToRGB(backgroundColor),
cursorColor: colorToRGB(cursorColor), cursorColor: colorToRGB(cursorColor),
glintColor: colorToRGB(glintColor), glintColor: colorToRGB(glintColor),
cursorIntensity, cursorIntensity,
glintIntensity, glintIntensity,
ditherMagnitude, ditherMagnitude,
tex: inputs.primary, tex: inputs.primary,
bloomTex: inputs.bloom, bloomTex: inputs.bloom,
paletteTex, paletteTex,
}, },
framebuffer: output, framebuffer: output,
}); });
return makePass( return makePass(
{ {
primary: output, primary: output,
}, },
palettePassFrag.loaded, palettePassFrag.loaded,
(w, h) => output.resize(w, h), (w, h) => output.resize(w, h),
(shouldRender) => { (shouldRender) => {
if (shouldRender) { if (shouldRender) {
render({ frag: palettePassFrag }); render({ frag: palettePassFrag });
} }
} },
); );
}; };

View File

@@ -30,6 +30,6 @@ export default ({ regl, config, lkg }, inputs) => {
if (shouldRender) { if (shouldRender) {
render({ frag: quiltPassFrag }); render({ frag: quiltPassFrag });
} }
} },
); );
}; };

View File

@@ -1,9 +1,4 @@
import { import { loadImage, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
loadImage,
makePassFBO,
makeDoubleBuffer,
makePass,
} from "./utils.js";
import { mat4, vec3 } from "gl-matrix"; import { mat4, vec3 } from "gl-matrix";
import rainPassIntro from "../../shaders/glsl/rainPass.intro.frag.glsl"; import rainPassIntro from "../../shaders/glsl/rainPass.intro.frag.glsl";
import rainPassRaindrop from "../../shaders/glsl/rainPass.raindrop.frag.glsl"; import rainPassRaindrop from "../../shaders/glsl/rainPass.raindrop.frag.glsl";
@@ -13,13 +8,11 @@ import rainPassVert from "../../shaders/glsl/rainPass.vert.glsl";
import rainPassFrag from "../../shaders/glsl/rainPass.frag.glsl"; import rainPassFrag from "../../shaders/glsl/rainPass.frag.glsl";
const extractEntries = (src, keys) => const extractEntries = (src, keys) =>
Object.fromEntries( Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key)));
Array.from(Object.entries(src)).filter(([key]) => keys.includes(key))
);
const rippleTypes = { const rippleTypes = {
box: 0, box: 0,
circle: 1, circle: 1,
}; };
// These compute buffers are used to compute the properties of cells in the grid. // These compute buffers are used to compute the properties of cells in the grid.
@@ -30,12 +23,12 @@ const rippleTypes = {
// These double buffers are smaller than the screen, because their pixels correspond // These double buffers are smaller than the screen, because their pixels correspond
// with cells in the grid, and the cells' glyphs are much larger than a pixel. // with cells in the grid, and the cells' glyphs are much larger than a pixel.
const makeComputeDoubleBuffer = (regl, height, width) => const makeComputeDoubleBuffer = (regl, height, width) =>
makeDoubleBuffer(regl, { makeDoubleBuffer(regl, {
width, width,
height, height,
wrapT: "clamp", wrapT: "clamp",
type: "half float", type: "half float",
}); });
const numVerticesPerQuad = 2 * 3; const numVerticesPerQuad = 2 * 3;
const tlVert = [0, 0]; const tlVert = [0, 0];
@@ -45,352 +38,301 @@ const brVert = [1, 1];
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert]; const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
export default ({ regl, config, lkg }) => { export default ({ regl, config, lkg }) => {
// 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;
const density = volumetric && config.effect !== "none" ? config.density : 1; const density = volumetric && config.effect !== "none" ? config.density : 1;
const [numRows, numColumns] = [ const [numRows, numColumns] = [config.numColumns, Math.floor(config.numColumns * density)];
config.numColumns,
Math.floor(config.numColumns * density),
];
// The volumetric mode requires us to create a grid of quads, // The volumetric mode requires us to create a grid of quads,
// rather than a single quad for our geometry // rather than a single quad for our geometry
const [numQuadRows, numQuadColumns] = volumetric const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
? [numRows, numColumns] const numQuads = numQuadRows * numQuadColumns;
: [1, 1]; const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
const numQuads = numQuadRows * numQuadColumns;
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
// Various effect-related values // Various effect-related values
const rippleType = const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1;
config.rippleTypeName in rippleTypes const slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
? rippleTypes[config.rippleTypeName] const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
: -1; const showDebugView = config.effect === "none";
const slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
const slantScale =
1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
const showDebugView = config.effect === "none";
const commonUniforms = { const commonUniforms = {
...extractEntries(config, [ ...extractEntries(config, [
"animationSpeed", "animationSpeed",
"glyphHeightToWidth", "glyphHeightToWidth",
"glyphSequenceLength", "glyphSequenceLength",
"glyphTextureGridSize", "glyphTextureGridSize",
]), ]),
numColumns, numColumns,
numRows, numRows,
showDebugView, showDebugView,
}; };
const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns); const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns);
const introUniforms = { const introUniforms = {
...commonUniforms, ...commonUniforms,
...extractEntries(config, ["fallSpeed", "skipIntro"]), ...extractEntries(config, ["fallSpeed", "skipIntro"]),
}; };
const intro = regl({ const intro = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
...introUniforms, ...introUniforms,
previousIntroState: introDoubleBuffer.back, previousIntroState: introDoubleBuffer.back,
}, },
framebuffer: introDoubleBuffer.front, framebuffer: introDoubleBuffer.front,
}); });
const raindropDoubleBuffer = makeComputeDoubleBuffer( const raindropDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
regl,
numRows,
numColumns
);
const raindropUniforms = { const raindropUniforms = {
...commonUniforms, ...commonUniforms,
...extractEntries(config, [ ...extractEntries(config, [
"brightnessDecay", "brightnessDecay",
"fallSpeed", "fallSpeed",
"raindropLength", "raindropLength",
"loops", "loops",
"skipIntro", "skipIntro",
]), ]),
}; };
const raindrop = regl({ const raindrop = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
...raindropUniforms, ...raindropUniforms,
introState: introDoubleBuffer.front, introState: introDoubleBuffer.front,
previousRaindropState: raindropDoubleBuffer.back, previousRaindropState: raindropDoubleBuffer.back,
}, },
framebuffer: raindropDoubleBuffer.front, framebuffer: raindropDoubleBuffer.front,
}); });
const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
const symbolUniforms = { const symbolUniforms = {
...commonUniforms, ...commonUniforms,
...extractEntries(config, ["cycleSpeed", "cycleFrameSkip", "loops"]), ...extractEntries(config, ["cycleSpeed", "cycleFrameSkip", "loops"]),
}; };
const symbol = regl({ const symbol = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
...symbolUniforms, ...symbolUniforms,
raindropState: raindropDoubleBuffer.front, raindropState: raindropDoubleBuffer.front,
previousSymbolState: symbolDoubleBuffer.back, previousSymbolState: symbolDoubleBuffer.back,
}, },
framebuffer: symbolDoubleBuffer.front, framebuffer: symbolDoubleBuffer.front,
}); });
const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
const effectUniforms = { const effectUniforms = {
...commonUniforms, ...commonUniforms,
...extractEntries(config, [ ...extractEntries(config, [
"hasThunder", "hasThunder",
"rippleScale", "rippleScale",
"rippleSpeed", "rippleSpeed",
"rippleThickness", "rippleThickness",
"loops", "loops",
]), ]),
rippleType, rippleType,
}; };
const effect = regl({ const effect = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
...effectUniforms, ...effectUniforms,
raindropState: raindropDoubleBuffer.front, raindropState: raindropDoubleBuffer.front,
previousEffectState: effectDoubleBuffer.back, previousEffectState: effectDoubleBuffer.back,
}, },
framebuffer: effectDoubleBuffer.front, framebuffer: effectDoubleBuffer.front,
}); });
const quadPositions = Array(numQuadRows) const quadPositions = Array(numQuadRows)
.fill() .fill()
.map((_, y) => .map((_, y) =>
Array(numQuadColumns) Array(numQuadColumns)
.fill() .fill()
.map((_, x) => Array(numVerticesPerQuad).fill([x, y])) .map((_, x) => Array(numVerticesPerQuad).fill([x, y])),
); );
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
const glyphMSDF = loadImage(regl, config.glyphMSDFURL); const glyphMSDF = loadImage(regl, config.glyphMSDFURL);
const glintMSDF = loadImage(regl, config.glintMSDFURL); const glintMSDF = loadImage(regl, config.glintMSDFURL);
const baseTexture = loadImage(regl, config.baseTextureURL, true); const baseTexture = loadImage(regl, config.baseTextureURL, true);
const glintTexture = loadImage(regl, config.glintTextureURL, true); const glintTexture = loadImage(regl, config.glintTextureURL, true);
const output = makePassFBO(regl, config.useHalfFloat); const output = makePassFBO(regl, config.useHalfFloat);
const renderUniforms = { const renderUniforms = {
...commonUniforms, ...commonUniforms,
...extractEntries(config, [ ...extractEntries(config, [
// vertex // vertex
"forwardSpeed", "forwardSpeed",
"glyphVerticalSpacing", "glyphVerticalSpacing",
// fragment // fragment
"baseBrightness", "baseBrightness",
"baseContrast", "baseContrast",
"glintBrightness", "glintBrightness",
"glintContrast", "glintContrast",
"hasBaseTexture", "hasBaseTexture",
"hasGlintTexture", "hasGlintTexture",
"brightnessThreshold", "brightnessThreshold",
"brightnessOverride", "brightnessOverride",
"isolateCursor", "isolateCursor",
"isolateGlint", "isolateGlint",
"glyphEdgeCrop", "glyphEdgeCrop",
"isPolar", "isPolar",
]), ]),
density, density,
numQuadColumns, numQuadColumns,
numQuadRows, numQuadRows,
quadSize, quadSize,
slantScale, slantScale,
slantVec, slantVec,
volumetric, volumetric,
}; };
const render = regl({ const render = regl({
blend: { blend: {
enable: true, enable: true,
func: { func: {
src: "one", src: "one",
dst: "one", dst: "one",
}, },
}, },
vert: regl.prop("vert"), vert: regl.prop("vert"),
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
...renderUniforms, ...renderUniforms,
raindropState: raindropDoubleBuffer.front, raindropState: raindropDoubleBuffer.front,
symbolState: symbolDoubleBuffer.front, symbolState: symbolDoubleBuffer.front,
effectState: effectDoubleBuffer.front, effectState: effectDoubleBuffer.front,
glyphMSDF: glyphMSDF.texture, glyphMSDF: glyphMSDF.texture,
glintMSDF: glintMSDF.texture, glintMSDF: glintMSDF.texture,
baseTexture: baseTexture.texture, baseTexture: baseTexture.texture,
glintTexture: glintTexture.texture, glintTexture: glintTexture.texture,
glyphTransform: regl.prop('glyphTransform'), glyphTransform: regl.prop("glyphTransform"),
msdfPxRange: 4.0, msdfPxRange: 4.0,
glyphMSDFSize: () => [glyphMSDF.width(), glyphMSDF.height()], glyphMSDFSize: () => [glyphMSDF.width(), glyphMSDF.height()],
glintMSDFSize: () => [glintMSDF.width(), glintMSDF.height()], glintMSDFSize: () => [glintMSDF.width(), glintMSDF.height()],
camera: regl.prop("camera"), camera: regl.prop("camera"),
transform: regl.prop("transform"), transform: regl.prop("transform"),
screenSize: regl.prop("screenSize"), screenSize: regl.prop("screenSize"),
}, },
viewport: regl.prop("viewport"), viewport: regl.prop("viewport"),
attributes: { attributes: {
aPosition: quadPositions, aPosition: quadPositions,
aCorner: Array(numQuads).fill(quadVertices), aCorner: Array(numQuads).fill(quadVertices),
}, },
count: numQuads * numVerticesPerQuad, count: numQuads * numVerticesPerQuad,
framebuffer: output, framebuffer: output,
}); });
// 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 { 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);
mat4.rotateY(transform, transform, (Math.PI * 1) / 4); mat4.rotateY(transform, transform, (Math.PI * 1) / 4);
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
mat4.scale(transform, transform, vec3.fromValues(1, 1, 2)); mat4.scale(transform, transform, vec3.fromValues(1, 1, 2));
} else if (lkg.enabled) { } else if (lkg.enabled) {
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1.1)); mat4.translate(transform, transform, vec3.fromValues(0, 0, -1.1));
mat4.scale(transform, transform, vec3.fromValues(1, 1, 1)); mat4.scale(transform, transform, vec3.fromValues(1, 1, 1));
mat4.scale(transform, transform, vec3.fromValues(0.15, 0.15, 0.15)); mat4.scale(transform, transform, vec3.fromValues(0.15, 0.15, 0.15));
} else { } else {
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
} }
const camera = mat4.create(); const camera = mat4.create();
const vantagePoints = []; const vantagePoints = [];
return makePass( return makePass(
{ {
primary: output, primary: output,
}, },
Promise.all([ Promise.all([glyphMSDF.loaded, glintMSDF.loaded, baseTexture.loaded, glintTexture.loaded]),
glyphMSDF.loaded, (w, h) => {
glintMSDF.loaded, output.resize(w, h);
baseTexture.loaded, const aspectRatio = w / h;
glintTexture.loaded,
]),
(w, h) => {
output.resize(w, h);
const aspectRatio = w / h;
const [numTileColumns, numTileRows] = [lkg.tileX, lkg.tileY]; const [numTileColumns, numTileRows] = [lkg.tileX, lkg.tileY];
const numVantagePoints = numTileRows * numTileColumns; const numVantagePoints = numTileRows * numTileColumns;
const tileWidth = Math.floor(w / numTileColumns); const tileWidth = Math.floor(w / numTileColumns);
const tileHeight = Math.floor(h / numTileRows); const tileHeight = Math.floor(h / numTileRows);
vantagePoints.length = 0; vantagePoints.length = 0;
for (let row = 0; row < numTileRows; row++) { for (let row = 0; row < numTileRows; row++) {
for (let column = 0; column < numTileColumns; column++) { for (let column = 0; column < numTileColumns; column++) {
const index = column + row * numTileColumns; const index = column + row * numTileColumns;
const camera = mat4.create(); const camera = mat4.create();
if (volumetric && config.isometric) { if (volumetric && config.isometric) {
if (aspectRatio > 1) { if (aspectRatio > 1) {
mat4.ortho( mat4.ortho(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000);
camera, } else {
-1.5 * aspectRatio, mat4.ortho(camera, -1.5, 1.5, -1.5 / aspectRatio, 1.5 / aspectRatio, -1000, 1000);
1.5 * aspectRatio, }
-1.5, } else if (lkg.enabled) {
1.5, mat4.perspective(camera, (Math.PI / 180) * lkg.fov, lkg.quiltAspect, 0.0001, 1000);
-1000,
1000
);
} else {
mat4.ortho(
camera,
-1.5,
1.5,
-1.5 / aspectRatio,
1.5 / aspectRatio,
-1000,
1000
);
}
} else if (lkg.enabled) {
mat4.perspective(
camera,
(Math.PI / 180) * lkg.fov,
lkg.quiltAspect,
0.0001,
1000
);
const distanceToTarget = -1; // TODO: Get from somewhere else const distanceToTarget = -1; // TODO: Get from somewhere else
let vantagePointAngle = let vantagePointAngle =
(Math.PI / 180) * (Math.PI / 180) * lkg.viewCone * (index / (numVantagePoints - 1) - 0.5);
lkg.viewCone * if (isNaN(vantagePointAngle)) {
(index / (numVantagePoints - 1) - 0.5); vantagePointAngle = 0;
if (isNaN(vantagePointAngle)) { }
vantagePointAngle = 0; const xOffset = distanceToTarget * Math.tan(vantagePointAngle);
}
const xOffset = distanceToTarget * Math.tan(vantagePointAngle);
mat4.translate(camera, camera, vec3.fromValues(xOffset, 0, 0)); mat4.translate(camera, camera, vec3.fromValues(xOffset, 0, 0));
camera[8] = camera[8] =
-xOffset / -xOffset /
(distanceToTarget * (distanceToTarget * Math.tan((Math.PI / 180) * 0.5 * lkg.fov) * lkg.quiltAspect); // Is this right??
Math.tan((Math.PI / 180) * 0.5 * lkg.fov) * } else {
lkg.quiltAspect); // Is this right?? mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
} else { }
mat4.perspective(
camera,
(Math.PI / 180) * 90,
aspectRatio,
0.0001,
1000
);
}
const viewport = { const viewport = {
x: column * tileWidth, x: column * tileWidth,
y: row * tileHeight, y: row * tileHeight,
width: tileWidth, width: tileWidth,
height: tileHeight, height: tileHeight,
}; };
vantagePoints.push({ camera, viewport }); vantagePoints.push({ camera, viewport });
} }
} }
[screenSize[0], screenSize[1]] = [screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; },
}, (shouldRender) => {
(shouldRender) => { intro({ frag: rainPassIntro });
intro({ frag: rainPassIntro }); raindrop({ frag: rainPassRaindrop });
raindrop({ frag: rainPassRaindrop }); symbol({ frag: rainPassSymbol });
symbol({ frag: rainPassSymbol }); effect({ frag: rainPassEffect });
effect({ frag: rainPassEffect });
if (shouldRender) { if (shouldRender) {
regl.clear({ regl.clear({
depth: 1, depth: 1,
color: [0, 0, 0, 1], color: [0, 0, 0, 1],
framebuffer: output, framebuffer: output,
}); });
for (const vantagePoint of vantagePoints) { for (const vantagePoint of vantagePoints) {
render({ render({
...vantagePoint, ...vantagePoint,
transform, transform,
screenSize, screenSize,
vert: rainPassVert, vert: rainPassVert,
frag: rainPassFrag, frag: rainPassFrag,
glyphTransform: [1, 0, 0, 1] glyphTransform: [1, 0, 0, 1],
}); });
} }
} }
} },
); );
}; };

View File

@@ -8,77 +8,77 @@ import stripePassFrag from "../../shaders/glsl/stripePass.frag.glsl";
// This shader introduces noise into the renders, to avoid banding // This shader introduces noise into the renders, to avoid banding
const transPrideStripeColors = [ const transPrideStripeColors = [
{ space: "rgb", values: [0.36, 0.81, 0.98] }, { space: "rgb", values: [0.36, 0.81, 0.98] },
{ space: "rgb", values: [0.96, 0.66, 0.72] }, { space: "rgb", values: [0.96, 0.66, 0.72] },
{ space: "rgb", values: [1.0, 1.0, 1.0] }, { space: "rgb", values: [1.0, 1.0, 1.0] },
{ space: "rgb", values: [0.96, 0.66, 0.72] }, { space: "rgb", values: [0.96, 0.66, 0.72] },
{ space: "rgb", values: [0.36, 0.81, 0.98] }, { space: "rgb", values: [0.36, 0.81, 0.98] },
] ]
.map((color) => Array(3).fill(color)) .map((color) => Array(3).fill(color))
.flat(); .flat();
const prideStripeColors = [ const prideStripeColors = [
{ space: "rgb", values: [0.89, 0.01, 0.01] }, { space: "rgb", values: [0.89, 0.01, 0.01] },
{ space: "rgb", values: [1.0, 0.55, 0.0] }, { space: "rgb", values: [1.0, 0.55, 0.0] },
{ space: "rgb", values: [1.0, 0.93, 0.0] }, { space: "rgb", values: [1.0, 0.93, 0.0] },
{ space: "rgb", values: [0.0, 0.5, 0.15] }, { space: "rgb", values: [0.0, 0.5, 0.15] },
{ space: "rgb", values: [0.0, 0.3, 1.0] }, { space: "rgb", values: [0.0, 0.3, 1.0] },
{ space: "rgb", values: [0.46, 0.03, 0.53] }, { space: "rgb", values: [0.46, 0.03, 0.53] },
] ]
.map((color) => Array(2).fill(color)) .map((color) => Array(2).fill(color))
.flat(); .flat();
export default ({ regl, config }, inputs) => { export default ({ regl, config }, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat); const output = makePassFBO(regl, config.useHalfFloat);
const { const {
backgroundColor, backgroundColor,
cursorColor, cursorColor,
glintColor, glintColor,
cursorIntensity, cursorIntensity,
glintIntensity, glintIntensity,
ditherMagnitude, ditherMagnitude,
} = config; } = config;
// Expand and convert stripe colors into 1D texture data // Expand and convert stripe colors into 1D texture data
const stripeColors = const stripeColors =
"stripeColors" in config "stripeColors" in config
? config.stripeColors ? config.stripeColors
: config.effect === "pride" : config.effect === "pride"
? prideStripeColors ? prideStripeColors
: transPrideStripeColors; : transPrideStripeColors;
const stripeTex = make1DTexture( const stripeTex = make1DTexture(
regl, regl,
stripeColors.map((color) => [...colorToRGB(color), 1]) stripeColors.map((color) => [...colorToRGB(color), 1]),
); );
const render = regl({ const render = regl({
frag: regl.prop("frag"), frag: regl.prop("frag"),
uniforms: { uniforms: {
backgroundColor: colorToRGB(backgroundColor), backgroundColor: colorToRGB(backgroundColor),
cursorColor: colorToRGB(cursorColor), cursorColor: colorToRGB(cursorColor),
glintColor: colorToRGB(glintColor), glintColor: colorToRGB(glintColor),
cursorIntensity, cursorIntensity,
glintIntensity, glintIntensity,
ditherMagnitude, ditherMagnitude,
tex: inputs.primary, tex: inputs.primary,
bloomTex: inputs.bloom, bloomTex: inputs.bloom,
stripeTex, stripeTex,
}, },
framebuffer: output, framebuffer: output,
}); });
return makePass( return makePass(
{ {
primary: output, primary: output,
}, },
null, null,
(w, h) => output.resize(w, h), (w, h) => output.resize(w, h),
(shouldRender) => { (shouldRender) => {
if (shouldRender) { if (shouldRender) {
render({ frag: stripePassFrag }); render({ frag: stripePassFrag });
} }
} },
); );
}; };

View File

@@ -8,7 +8,8 @@ const makePassTexture = (regl, halfFloat) =>
mag: "linear", mag: "linear",
}); });
const makePassFBO = (regl, halfFloat) => regl.framebuffer({ color: makePassTexture(regl, halfFloat) }); const makePassFBO = (regl, halfFloat) =>
regl.framebuffer({ color: makePassTexture(regl, halfFloat) });
const makeDoubleBuffer = (regl, props) => { const makeDoubleBuffer = (regl, props) => {
const state = Array(2) const state = Array(2)
@@ -17,7 +18,7 @@ const makeDoubleBuffer = (regl, props) => {
regl.framebuffer({ regl.framebuffer({
color: regl.texture(props), color: regl.texture(props),
depthStencil: false, depthStencil: false,
}) }),
); );
return { return {
front: ({ tick }) => state[tick % 2], front: ({ tick }) => state[tick % 2],
@@ -149,6 +150,21 @@ const makePass = (outputs, ready, setSize, execute) => ({
}); });
const makePipeline = (context, steps) => const makePipeline = (context, steps) =>
steps.filter((f) => f != null).reduce((pipeline, f, i) => [...pipeline, f(context, i == 0 ? null : pipeline[i - 1].outputs)], []); steps
.filter((f) => f != null)
.reduce(
(pipeline, f, i) => [...pipeline, f(context, i == 0 ? null : pipeline[i - 1].outputs)],
[],
);
export { makePassTexture, makePassFBO, makeDoubleBuffer, loadImage, loadText, makeFullScreenQuad, make1DTexture, makePass, makePipeline }; export {
makePassTexture,
makePassFBO,
makeDoubleBuffer,
loadImage,
loadText,
makeFullScreenQuad,
make1DTexture,
makePass,
makePipeline,
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
import { structs } from "../../lib/gpu-buffer.js"; import { structs } from "../../lib/gpu-buffer.js";
import { makeComputeTarget, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js"; import {
makeComputeTarget,
loadShader,
makeUniformBuffer,
makeBindGroup,
makePass,
} from "./utils.js";
// const makePyramid = makeComputeTarget; // const makePyramid = makeComputeTarget;
@@ -20,8 +26,8 @@ const makePyramid = (device, size, pyramidHeight) =>
.map((_, index) => .map((_, index) =>
makeComputeTarget( makeComputeTarget(
device, device,
size.map((x) => Math.floor(x * 2 ** -index)) size.map((x) => Math.floor(x * 2 ** -index)),
) ),
); );
const destroyPyramid = (pyramid) => pyramid?.forEach((texture) => texture.destroy()); const destroyPyramid = (pyramid) => pyramid?.forEach((texture) => texture.destroy());
@@ -47,7 +53,10 @@ export default ({ config, device }) => {
return makePass("No Bloom", null, (size, inputs) => ({ ...inputs, bloom: emptyTexture })); return makePass("No Bloom", null, (size, inputs) => ({ ...inputs, bloom: emptyTexture }));
} }
const assets = [loadShader(device, "shaders/wgsl/bloomBlur.wgsl"), loadShader(device, "shaders/wgsl/bloomCombine.wgsl")]; const assets = [
loadShader(device, "shaders/wgsl/bloomBlur.wgsl"),
loadShader(device, "shaders/wgsl/bloomCombine.wgsl"),
];
const linearSampler = device.createSampler({ const linearSampler = device.createSampler({
magFilter: "linear", magFilter: "linear",
@@ -122,12 +131,27 @@ export default ({ config, device }) => {
for (let i = 0; i < pyramidHeight; i++) { for (let i = 0; i < pyramidHeight; i++) {
const hBlurPyramidView = makePyramidLevelView(hBlurPyramid, i); const hBlurPyramidView = makePyramidLevelView(hBlurPyramid, i);
const vBlurPyramidView = makePyramidLevelView(vBlurPyramid, i); const vBlurPyramidView = makePyramidLevelView(vBlurPyramid, i);
hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [hBlurBuffer, linearSampler, srcView, hBlurPyramidView]); hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [
vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [vBlurBuffer, linearSampler, hBlurPyramidView, vBlurPyramidView]); hBlurBuffer,
linearSampler,
srcView,
hBlurPyramidView,
]);
vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [
vBlurBuffer,
linearSampler,
hBlurPyramidView,
vBlurPyramidView,
]);
srcView = hBlurPyramidView; srcView = hBlurPyramidView;
} }
combineBindGroup = makeBindGroup(device, combinePipeline, 0, [combineBuffer, linearSampler, ...makePyramidViews(vBlurPyramid), output.createView()]); combineBindGroup = makeBindGroup(device, combinePipeline, 0, [
combineBuffer,
linearSampler,
...makePyramidViews(vBlurPyramid),
output.createView(),
]);
return { return {
...inputs, ...inputs,
@@ -144,7 +168,11 @@ export default ({ config, device }) => {
computePass.setPipeline(blurPipeline); computePass.setPipeline(blurPipeline);
for (let i = 0; i < pyramidHeight; i++) { for (let i = 0; i < pyramidHeight; i++) {
const dispatchSize = [Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32), Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)), 1]; const dispatchSize = [
Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32),
Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)),
1,
];
computePass.setBindGroup(0, hBlurBindGroups[i]); computePass.setBindGroup(0, hBlurBindGroups[i]);
computePass.dispatchWorkgroups(...dispatchSize); computePass.dispatchWorkgroups(...dispatchSize);
computePass.setBindGroup(0, vBlurBindGroups[i]); computePass.setBindGroup(0, vBlurBindGroups[i]);

View File

@@ -45,7 +45,10 @@ export default ({ device, canvasFormat, canvasContext }) => {
})(); })();
const build = (size, inputs) => { const build = (size, inputs) => {
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [nearestSampler, inputs.primary.createView()]); renderBindGroup = makeBindGroup(device, renderPipeline, 0, [
nearestSampler,
inputs.primary.createView(),
]);
return null; return null;
}; };

View File

@@ -1,9 +1,17 @@
import { structs } from "../../lib/gpu-buffer.js"; import { structs } from "../../lib/gpu-buffer.js";
import { makeComputeTarget, makeUniformBuffer, loadTexture, loadShader, makeBindGroup, makePass } from "./utils.js"; import {
makeComputeTarget,
makeUniformBuffer,
loadTexture,
loadShader,
makeBindGroup,
makePass,
} from "./utils.js";
// Multiplies the rendered rain and bloom by a loaded in image // Multiplies the rendered rain and bloom by a loaded in image
const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg"; const defaultBGURL =
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg";
export default ({ config, device }) => { export default ({ config, device }) => {
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;

View File

@@ -74,7 +74,10 @@ export default async (canvas, config) => {
const cameraTex = device.createTexture({ const cameraTex = device.createTexture({
size: cameraSize, size: cameraSize,
format: "rgba8unorm", format: "rgba8unorm",
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
}); });
const context = { const context = {
@@ -90,7 +93,12 @@ export default async (canvas, config) => {
}; };
const effectName = config.effect in effects ? config.effect : "palette"; const effectName = config.effect in effects ? config.effect : "palette";
const pipeline = await makePipeline(context, [makeRain, makeBloomPass, effects[effectName], makeEndPass]); const pipeline = await makePipeline(context, [
makeRain,
makeBloomPass,
effects[effectName],
makeEndPass,
]);
const targetFrameTimeMilliseconds = 1000 / config.fps; const targetFrameTimeMilliseconds = 1000 / config.fps;
let frames = 0; let frames = 0;
@@ -107,7 +115,8 @@ export default async (canvas, config) => {
last = start; last = start;
} }
const shouldRender = config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once; const shouldRender =
config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once;
if (shouldRender) { if (shouldRender) {
while (now - targetFrameTimeMilliseconds > last) { while (now - targetFrameTimeMilliseconds > last) {
last += targetFrameTimeMilliseconds; last += targetFrameTimeMilliseconds;
@@ -125,10 +134,18 @@ export default async (canvas, config) => {
} }
if (config.useCamera) { if (config.useCamera) {
device.queue.copyExternalImageToTexture({ source: cameraCanvas }, { texture: cameraTex }, cameraSize); device.queue.copyExternalImageToTexture(
{ source: cameraCanvas },
{ texture: cameraTex },
cameraSize,
);
} }
device.queue.writeBuffer(timeBuffer, 0, timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames })); device.queue.writeBuffer(
timeBuffer,
0,
timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames }),
);
frames++; frames++;
const encoder = device.createCommandEncoder(); const encoder = device.createCommandEncoder();

View File

@@ -1,5 +1,11 @@
import { structs } from "../../lib/gpu-buffer.js"; import { structs } from "../../lib/gpu-buffer.js";
import { makeComputeTarget, makeUniformBuffer, loadShader, makeBindGroup, makePass } from "./utils.js"; import {
makeComputeTarget,
makeUniformBuffer,
loadShader,
makeBindGroup,
makePass,
} from "./utils.js";
let start; let start;
const numTouches = 5; const numTouches = 5;
@@ -77,7 +83,11 @@ export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) =>
]); ]);
const screenAspectRatio = size[0] / size[1]; const screenAspectRatio = size[0] / size[1];
device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.toBuffer({ screenAspectRatio, cameraAspectRatio })); device.queue.writeBuffer(
sceneBuffer,
0,
sceneUniforms.toBuffer({ screenAspectRatio, cameraAspectRatio }),
);
return { primary: output }; return { primary: output };
}; };

View File

@@ -1,6 +1,12 @@
import colorToRGB from "../colorToRGB.js"; import colorToRGB from "../colorToRGB.js";
import { structs } from "../../lib/gpu-buffer.js"; import { structs } from "../../lib/gpu-buffer.js";
import { loadShader, makeUniformBuffer, makeBindGroup, makeComputeTarget, makePass } from "./utils.js"; import {
loadShader,
makeUniformBuffer,
makeBindGroup,
makeComputeTarget,
makePass,
} from "./utils.js";
// 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

View File

@@ -1,5 +1,12 @@
import { structs } from "../../lib/gpu-buffer.js"; import { structs } from "../../lib/gpu-buffer.js";
import { makeRenderTarget, loadTexture, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js"; import {
makeRenderTarget,
loadTexture,
loadShader,
makeUniformBuffer,
makeBindGroup,
makePass,
} from "./utils.js";
const rippleTypes = { const rippleTypes = {
box: 0, box: 0,
@@ -46,7 +53,10 @@ export default ({ config, device, timeBuffer }) => {
// rather than a single quad for our geometry // rather than a single quad for our geometry
const numQuads = config.volumetric ? numCells : 1; const numQuads = config.volumetric ? numCells : 1;
const glyphTransform = mat2.fromScaling(mat2.create(), vec2.fromValues(config.glyphFlip ? -1 : 1, 1)); const glyphTransform = mat2.fromScaling(
mat2.create(),
vec2.fromValues(config.glyphFlip ? -1 : 1, 1),
);
mat2.rotate(glyphTransform, glyphTransform, (config.glyphRotation * Math.PI) / 180); mat2.rotate(glyphTransform, glyphTransform, (config.glyphRotation * Math.PI) / 180);
const transform = mat4.create(); const transform = mat4.create();
@@ -98,10 +108,18 @@ export default ({ config, device, timeBuffer }) => {
let highPassOutput; let highPassOutput;
const loaded = (async () => { const loaded = (async () => {
const [glyphMSDFTexture, glintMSDFTexture, baseTexture, glintTexture, rainShader] = await Promise.all(assets); const [glyphMSDFTexture, glintMSDFTexture, baseTexture, glintTexture, rainShader] =
await Promise.all(assets);
const rainShaderUniforms = structs.from(rainShader.code); const rainShaderUniforms = structs.from(rainShader.code);
configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize, glyphTransform); configBuffer = makeConfigBuffer(
device,
rainShaderUniforms.Config,
config,
density,
gridSize,
glyphTransform,
);
const introCellsBuffer = device.createBuffer({ const introCellsBuffer = device.createBuffer({
size: gridSize[0] * rainShaderUniforms.IntroCell.minSize, size: gridSize[0] * rainShaderUniforms.IntroCell.minSize,
@@ -168,8 +186,17 @@ export default ({ config, device, timeBuffer }) => {
}), }),
]); ]);
introBindGroup = makeBindGroup(device, introPipeline, 0, [configBuffer, timeBuffer, introCellsBuffer]); introBindGroup = makeBindGroup(device, introPipeline, 0, [
computeBindGroup = makeBindGroup(device, computePipeline, 0, [configBuffer, timeBuffer, cellsBuffer, introCellsBuffer]); configBuffer,
timeBuffer,
introCellsBuffer,
]);
computeBindGroup = makeBindGroup(device, computePipeline, 0, [
configBuffer,
timeBuffer,
cellsBuffer,
introCellsBuffer,
]);
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [ renderBindGroup = makeBindGroup(device, renderPipeline, 0, [
configBuffer, configBuffer,
timeBuffer, timeBuffer,
@@ -196,7 +223,11 @@ export default ({ config, device, timeBuffer }) => {
mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
} }
const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.toBuffer({ screenSize, camera, transform })); device.queue.writeBuffer(
sceneBuffer,
0,
sceneUniforms.toBuffer({ screenSize, camera, transform }),
);
// Update // Update
output?.destroy(); output?.destroy();

View File

@@ -1,6 +1,13 @@
import colorToRGB from "../colorToRGB.js"; import colorToRGB from "../colorToRGB.js";
import { structs } from "../../lib/gpu-buffer.js"; import { structs } from "../../lib/gpu-buffer.js";
import { loadShader, make1DTexture, makeUniformBuffer, makeBindGroup, makeComputeTarget, makePass } from "./utils.js"; import {
loadShader,
make1DTexture,
makeUniformBuffer,
makeBindGroup,
makeComputeTarget,
makePass,
} from "./utils.js";
// 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
@@ -38,10 +45,15 @@ const numVerticesPerQuad = 2 * 3;
export default ({ config, device, timeBuffer }) => { export default ({ config, device, timeBuffer }) => {
// Expand and convert stripe colors into 1D texture data // Expand and convert stripe colors into 1D texture data
const stripeColors = "stripeColors" in config ? config.stripeColors : config.effect === "pride" ? prideStripeColors : transPrideStripeColors; const stripeColors =
"stripeColors" in config
? config.stripeColors
: config.effect === "pride"
? prideStripeColors
: transPrideStripeColors;
const stripeTex = make1DTexture( const stripeTex = make1DTexture(
device, device,
stripeColors.map((color) => [...colorToRGB(color), 1]) stripeColors.map((color) => [...colorToRGB(color), 1]),
); );
const linearSampler = device.createSampler({ const linearSampler = device.createSampler({

View File

@@ -3,7 +3,10 @@ const loadTexture = async (device, url) => {
return device.createTexture({ return device.createTexture({
size: [1, 1, 1], size: [1, 1, 1],
format: "rgba8unorm", format: "rgba8unorm",
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
}); });
} }
@@ -15,7 +18,10 @@ const loadTexture = async (device, url) => {
const texture = device.createTexture({ const texture = device.createTexture({
size, size,
format: "rgba8unorm", format: "rgba8unorm",
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
}); });
device.queue.copyExternalImageToTexture({ source, flipY: true }, { texture }, size); device.queue.copyExternalImageToTexture({ source, flipY: true }, { texture }, size);
@@ -28,7 +34,11 @@ const makeRenderTarget = (device, size, format, mipLevelCount = 1) =>
size: [...size, 1], size: [...size, 1],
mipLevelCount, mipLevelCount,
format, format,
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_SRC |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
}); });
const makeComputeTarget = (device, size, mipLevelCount = 1) => const makeComputeTarget = (device, size, mipLevelCount = 1) =>
@@ -36,7 +46,11 @@ const makeComputeTarget = (device, size, mipLevelCount = 1) =>
size: [...size, 1], size: [...size, 1],
mipLevelCount, mipLevelCount,
format: "rgba8unorm", format: "rgba8unorm",
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING, usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_SRC |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.STORAGE_BINDING,
}); });
const loadShader = async (device, url) => { const loadShader = async (device, url) => {
@@ -105,4 +119,14 @@ const makePipeline = async (context, steps) => {
}; };
}; };
export { makeRenderTarget, makeComputeTarget, make1DTexture, loadTexture, loadShader, makeUniformBuffer, makePass, makePipeline, makeBindGroup }; export {
makeRenderTarget,
makeComputeTarget,
make1DTexture,
loadTexture,
loadShader,
makeUniformBuffer,
makePass,
makePipeline,
makeBindGroup,
};

View File

@@ -8,37 +8,37 @@ import terser from "@rollup/plugin-terser";
import { string } from "rollup-plugin-string"; import { string } from "rollup-plugin-string";
export default { export default {
input: "js/Matrix.js", input: "js/Matrix.js",
external: ["react", "react-dom"], // keep them out of your bundle external: ["react", "react-dom"], // keep them out of your bundle
plugins: [ plugins: [
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 }), url({ include: ["**/*.png"], limit: 0 }),
babel({ babel({
exclude: "node_modules/**", // transpile JSX exclude: "node_modules/**", // transpile JSX
babelHelpers: "bundled", babelHelpers: "bundled",
presets: ["@babel/preset-react", "@babel/preset-env"], presets: ["@babel/preset-react", "@babel/preset-env"],
}), }),
commonjs(), // turn CJS deps into ES commonjs(), // turn CJS deps into ES
terser({ terser({
sourceMap: false, // <- suppress .map generation sourceMap: false, // <- suppress .map generation
format: { comments: false }, format: { comments: false },
}), }),
visualizer({ visualizer({
filename: "dist/stats.html", filename: "dist/stats.html",
gzipSize: true, gzipSize: true,
brotliSize: true, brotliSize: true,
includeAssets: true, includeAssets: true,
}), // bundle-size treemap }), // bundle-size treemap
], ],
output: [ output: [
{ {
file: "dist/index.cjs.js", file: "dist/index.cjs.js",
format: "cjs", format: "cjs",
exports: "named", exports: "named",
sourcemap: false, sourcemap: false,
}, },
// { file: 'dist/index.esm.js', format: 'es' } // optional ESM build // { file: 'dist/index.esm.js', format: 'es' } // optional ESM build
], ],
}; };

View File

@@ -3,52 +3,52 @@ const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = { module.exports = {
mode: "development", mode: "development",
entry: path.resolve(__dirname, "./js/index.js"), entry: path.resolve(__dirname, "./js/index.js"),
module: { module: {
rules: [ rules: [
{ {
test: /\.(js|jsx)$/, test: /\.(js|jsx)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ["babel-loader"], use: ["babel-loader"],
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: ["style-loader", "css-loader"], use: ["style-loader", "css-loader"],
}, },
{ {
test: /\.(png|j?g|svg|gif)?$/, test: /\.(png|j?g|svg|gif)?$/,
type: "asset/resource", type: "asset/resource",
}, },
{ {
test: /\.(glsl|frag|vert)$/i, test: /\.(glsl|frag|vert)$/i,
exclude: /node_modules/, exclude: /node_modules/,
use: ["raw-loader"], use: ["raw-loader"],
}, },
], ],
}, },
resolve: { resolve: {
extensions: ["*", ".js", ".jsx"], extensions: ["*", ".js", ".jsx"],
}, },
output: { output: {
path: path.resolve(__dirname, "./dist"), path: path.resolve(__dirname, "./dist"),
filename: "[name].bundle.js", filename: "[name].bundle.js",
clean: true, clean: true,
}, },
devtool: "inline-source-map", devtool: "inline-source-map",
plugins: [ plugins: [
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html"), template: path.resolve(__dirname, "public/index.html"),
filename: "index.html", filename: "index.html",
}), }),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
], ],
devServer: { devServer: {
historyApiFallback: true, historyApiFallback: true,
static: path.resolve(__dirname, "./dist"), static: path.resolve(__dirname, "./dist"),
compress: true, compress: true,
hot: true, hot: true,
open: true, open: true,
port: 3000, port: 3000,
}, },
}; };