Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion js/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,43 @@ export const versions = {
glyphRandomFlip: true,
},

["mathcode-3d"]: {
font: "mathcode",
volumetric: true,

// Favor readability in depth: fewer columns than 2D mathcode,
// higher density, and a moderate forward motion.
numColumns: 44,
density: 1.35,
forwardSpeed: 0.22,

animationSpeed: 0.75,
cycleSpeed: 0.045,
fallSpeed: 0.55,
raindropLength: 0.55,

// Keep 3D "tunnel" clean.
isPolar: false,
slant: 0,

baseBrightness: -0.85,
baseContrast: 1.6,
bloomStrength: 0.8,
bloomSize: 0.45,
highPassThreshold: 0,

cursorColor: hsl(0.55, 1, 0.95),
cursorIntensity: 2.2,
palette: [
{ color: hsl(0.55, 0.9, 0.0), at: 0.0 },
{ color: hsl(0.55, 1.0, 0.35), at: 0.55 },
{ color: hsl(0.56, 1.0, 0.8), at: 0.9 },
{ color: hsl(0.57, 1.0, 1.0), at: 1.0 },
],

glyphRandomFlip: true,
},
Comment on lines +672 to +707
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new version preset (mathcode-3d) is added, but there is no automated smoke coverage to ensure it boots cleanly on at least WebGL (and ideally WebGPU when available). Since the repo already has Playwright version smoke coverage (e.g. VERSION_SAMPLES in tests), please add mathcode-3d to that matrix (or add a targeted test) so regressions in volumetric/depth setup are caught.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 18f41f8.

mathcode-3d is now included in smoke coverage via VERSION_SAMPLES in tests/matrix-smoke.spec.js, and the renderer depth-path fixes from the same review thread are included in that commit.

Screenshot: /home/runner/work/matrix/matrix/test-results/mathcode-3d-webgl.png


mathcode_alphabet_three: {
/*
* Mathcode + alphabet via Three.js (experimental): even columns sample the mathcode
Expand Down Expand Up @@ -716,7 +753,6 @@ export const versions = {
],
raindropLength: 1.0,
resolution: 0.8,
glyphRandomFlip: true,
},

alphabet: {
Expand Down
2 changes: 1 addition & 1 deletion js/favicon.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const FONT_CHARS = {
coptic: [..."ϢϣϤϥϦϧϨϩϪϫϬϭϮϯⲀⲁⲂⲃⲄⲅⲆⲇⲈⲉⲊⲋⲌⲍⲎⲏⲐⲑⲒⲓ"],

// Latin A–Za–z (matches `fonts.alphabet` / alphabet version)
alphabet: [...("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")],
alphabet: [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"],

// Mathcode — mathematical symbols, Greek letters, set notation, arrows, stars
mathcode: [
Expand Down
8 changes: 2 additions & 6 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ function enforceHoloplayRenderer(config) {
config.renderer = "webgl";
}
if (r === "three" || r === "p5") {
console.warn(
"[Matrix] Looking Glass (Holoplay) requires the regl/WebGL rain path; ignoring experimental renderer for this session.",
);
console.warn("[Matrix] Looking Glass (Holoplay) requires the regl/WebGL rain path; ignoring experimental renderer for this session.");
config.renderer = "webgl";
}
}
Expand Down Expand Up @@ -695,9 +693,7 @@ async function initializeGalleryMode() {
? await import("./three-rain/main.js")
: rendererName === "p5"
? await import("./p5-rain/main.js")
: await import(
`./${(await supportsWebGPU()) && rendererName === "webgpu" ? "webgpu" : "webgl"}/main.js`,
);
: await import(`./${(await supportsWebGPU()) && rendererName === "webgpu" ? "webgpu" : "webgl"}/main.js`);
currentMatrixRenderer = solution;
await startMatrix(currentMatrixRenderer, canvas, newConfig);
} else {
Expand Down
7 changes: 5 additions & 2 deletions js/p5-rain/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ export default async function main(canvas, config) {

function canvasCssSize() {
const dpr = Math.min(window.devicePixelRatio ?? 1, 2);
const w = Math.ceil(canvas.clientWidth * dpr * res);
const h = Math.ceil(canvas.clientHeight * dpr * res);
// The app's primary Matrix canvas can be hidden in experimental p5 mode; do not
// size off its client rect. Use viewport size so Playwright (and users) always
// get a non-zero drawable surface.
const w = Math.ceil(window.innerWidth * dpr * res);
const h = Math.ceil(window.innerHeight * dpr * res);
return { w, h };
}

Expand Down
2 changes: 1 addition & 1 deletion js/three-rain/glyphs.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const MATHCODE_GLYPHS = [
"𝝿",
];

export const ALPHABET_GLYPHS = [...("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")];
export const ALPHABET_GLYPHS = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"];

export const GLYPH_COUNT_MATH = MATHCODE_GLYPHS.length;
export const GLYPH_COUNT_ALPHA = ALPHABET_GLYPHS.length;
Expand Down
14 changes: 2 additions & 12 deletions js/three-rain/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@
import * as THREE from "../../lib/three.module.js";
import { setupFullscreenToggle } from "../fullscreen.js";
import { buildGlyphAtlas } from "./glyphAtlas.js";
import {
MATHCODE_GLYPHS,
ALPHABET_GLYPHS,
GLYPH_COUNT_MATH,
GLYPH_ID_ALPHA_START,
} from "./glyphs.js";
import { MATHCODE_GLYPHS, ALPHABET_GLYPHS, GLYPH_COUNT_MATH, GLYPH_ID_ALPHA_START } from "./glyphs.js";

const vertexShader = /* glsl */ `
precision highp float;
Expand Down Expand Up @@ -84,11 +79,7 @@ export default async function main(canvas, config) {
const dropsPerColumn = 32;
const count = numColumns * dropsPerColumn;

const { texture, cols: atlasCols, rows: atlasRows, totalGlyphs } = buildGlyphAtlas(
THREE,
MATHCODE_GLYPHS,
ALPHABET_GLYPHS,
);
const { texture, cols: atlasCols, rows: atlasRows, totalGlyphs } = buildGlyphAtlas(THREE, MATHCODE_GLYPHS, ALPHABET_GLYPHS);

const geometry = new THREE.PlaneGeometry((2 / numColumns) * 0.92, 0.11, 1, 1);
const phase = new Float32Array(count);
Expand Down Expand Up @@ -179,5 +170,4 @@ export default async function main(canvas, config) {
}
glyphAttr.needsUpdate = true;
}, cycleMs);

}
55 changes: 46 additions & 9 deletions js/webgl/rainPass.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
loadImage,
loadText,
makePassFBO,
makePassTexture,
makeDoubleBuffer,
makePass,
fullscreenPassVertGLSL,
Expand Down Expand Up @@ -114,7 +115,14 @@ export default ({ regl, config, lkg }) => {
const glintTexture = loadImage(regl, config.glintTextureURL, true);
const rainPassVert = loadText("shaders/glsl/rainPass.vert.glsl");
const rainPassFrag = loadText("shaders/glsl/rainPass.frag.glsl");
const output = makePassFBO(regl, config.useHalfFloat);
// Volumetric rendering benefits from a depth buffer so nearer glyphs can occlude
// farther ones. In 2D mode we keep the lighter-weight color-only FBO.
const output = volumetric
? regl.framebuffer({
color: makePassTexture(regl, config.useHalfFloat),
depth: true,
})
: makePassFBO(regl, config.useHalfFloat);
const renderUniforms = {
...commonUniforms,
...extractEntries(config, [
Expand Down Expand Up @@ -149,6 +157,7 @@ export default ({ regl, config, lkg }) => {
let raindrop;
let symbol;
let effect;
let renderDepth;
let render;
// Bind all rain GLSL as static strings after fetch. `regl.prop("frag")` with a
// missing/undefined source becomes shaderSource(undefined) → GLSL `undefined` at line 0.
Expand Down Expand Up @@ -232,14 +241,7 @@ export default ({ regl, config, lkg }) => {
},
framebuffer: effectDoubleBuffer.front,
});
render = regl({
blend: {
enable: true,
func: {
src: "one",
dst: "one",
},
},
const renderOptions = {
vert: vertSource,
frag: fragSource,

Expand Down Expand Up @@ -273,6 +275,38 @@ export default ({ regl, config, lkg }) => {
count: numQuads * numVerticesPerQuad,

framebuffer: output,
};

if (volumetric) {
renderDepth = regl({
...renderOptions,
colorMask: [false, false, false, false],
depth: {
enable: true,
mask: true,
func: "less",
},
});
}

render = regl({
...renderOptions,
blend: {
enable: true,
func: {
src: "one",
dst: "one",
},
},
depth: volumetric
? {
enable: true,
mask: false,
func: "equal",
}
: {
enable: false,
},
});
});

Expand Down Expand Up @@ -362,6 +396,9 @@ export default ({ regl, config, lkg }) => {
});

for (const vantagePoint of vantagePoints) {
if (volumetric) {
renderDepth({ ...vantagePoint, transform, screenSize });
}
render({ ...vantagePoint, transform, screenSize });
}
}
Expand Down
29 changes: 29 additions & 0 deletions js/webgpu/rainPass.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export default ({ config, device, timeBuffer }) => {
let renderBindGroup;
let output;
let highPassOutput;
let depthTexture;

const loaded = (async () => {
const [glyphMSDFTexture, glintMSDFTexture, baseTexture, glintTexture, rainShader] = await Promise.all(assets);
Expand Down Expand Up @@ -256,6 +257,15 @@ export default ({ config, device, timeBuffer }) => {
module: rainShader.module,
entryPoint: "vertMain",
},
...(config.volumetric
? {
depthStencil: {
format: "depth24plus",
depthWriteEnabled: true,
depthCompare: "less",
},
}
: {}),
fragment: {
module: rainShader.module,
entryPoint: "fragMain",
Expand Down Expand Up @@ -316,6 +326,15 @@ export default ({ config, device, timeBuffer }) => {
highPassOutput?.destroy();
highPassOutput = makeRenderTarget(device, size, renderFormat);

depthTexture?.destroy();
depthTexture = config.volumetric
? device.createTexture({
size: [size[0], size[1], 1],
format: "depth24plus",
usage: GPUTextureUsage.RENDER_ATTACHMENT,
})
: null;

return {
primary: output,
highPass: highPassOutput,
Expand All @@ -340,6 +359,16 @@ export default ({ config, device, timeBuffer }) => {
if (shouldRender) {
renderPassConfig.colorAttachments[0].view = output.createView();
renderPassConfig.colorAttachments[1].view = highPassOutput.createView();
if (config.volumetric && depthTexture != null) {
renderPassConfig.depthStencilAttachment = {
view: depthTexture.createView(),
depthLoadOp: "clear",
depthStoreOp: "store",
depthClearValue: 1.0,
};
} else {
delete renderPassConfig.depthStencilAttachment;
}
const renderPass = encoder.beginRenderPass(renderPassConfig);
renderPass.setPipeline(renderPipeline);
renderPass.setBindGroup(0, renderBindGroup);
Expand Down
Loading
Loading