Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4f126a0
feat: add 3D world effect pack and multipass host
jmeistrich Oct 29, 2026
3d85dff
feat: add world lighting and animated data scenes
jmeistrich Oct 29, 2026
5f1e596
feat: add cinematic scenes and adaptive resolution
jmeistrich Oct 29, 2026
88a6da2
fix: refine world glow, reflections and lighting
jmeistrich Oct 29, 2026
39fcda0
fix: cull hidden world spheres before rasterization
jmeistrich Oct 29, 2026
5d6d70d
fix: preserve GPU backgrounds across slide navigation
jmeistrich Oct 29, 2026
fd96554
Merge branch 'slides-3d-world-look' into slides-3d-world
jmeistrich Oct 29, 2026
db5caf4
fix: round bloom halos for small bright objects
jmeistrich Oct 29, 2026
3f2eca8
feat: make the data flow example full-bleed and denser
jmeistrich Oct 29, 2026
5460f61
fix: lower compiled deck classes for Hermes
jmeistrich Oct 29, 2026
65bde91
fix: skip zero-count GPU draws in render graphs
jmeistrich Oct 29, 2026
0fbd270
Merge branch 'slides-3d-world-native' into slides-3d-world-dataflow
jmeistrich Oct 29, 2026
061829e
fix: keep changed rows highlighted after their flash
jmeistrich Oct 29, 2026
bbb47af
fix: tighten the changed rows glow
jmeistrich Oct 30, 2026
7c602fe
fix: keep one source per spray particle
jmeistrich Oct 30, 2026
e56b16b
fix: keep Worklets frames on every macOS display pulse
jmeistrich Oct 30, 2026
20a25a4
fix: stop empty particle work and render-time scale reads in GPU frames
jmeistrich Oct 30, 2026
028f0a6
fix: keep pure annotations in compiled decks
jmeistrich Oct 30, 2026
9a0e3d2
fix: order World draws by code unit
jmeistrich Oct 30, 2026
70961b8
test: run the real World decks in Hermes and Dawn
jmeistrich Oct 30, 2026
233fcd3
test: guard Worklets frame samples before measuring
jmeistrich Oct 30, 2026
e9c5dce
ci: complete Slides verification entry points
jmeistrich Oct 30, 2026
5ac67d6
feat: resolve effect packs next to the deck
jmeistrich Oct 30, 2026
ee38db9
fix: render 3D worlds at device resolution with MSAA and clean shafts
jmeistrich Oct 30, 2026
dba9fc7
fix: keep slides split after comparisons in JSX props
jmeistrich Oct 30, 2026
d567df8
fix: remove the pillar base seam and trim World post-pass cost
jmeistrich Oct 31, 2026
9010dc3
perf: reuse World uniforms and skip unchanged instance slots
jmeistrich Oct 31, 2026
8843113
fix: stop charging WebGPU textures to the Hermes heap
jmeistrich Oct 31, 2026
8d083f6
fix: render native WebGPU at device pixels and keep MSAA draws on chip
jmeistrich Oct 31, 2026
4d219f2
perf: skip World MSAA where the drawable already supersamples
jmeistrich Oct 31, 2026
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
51 changes: 51 additions & 0 deletions .github/workflows/slides-world.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Slides World

on:
pull_request:
paths:
- apps/slides/**
- packages/presentation/**
- packages/slides-effects/**
- patches/**
- shell/src/global.css
- shell/package.json
- scripts/compile-slides.ts
- package.json
- bun.lock
- .github/workflows/slides-world.yml
workflow_dispatch:

jobs:
# Compiles the real World decks and runs the mocked host tests. Darwin-only
# Hermes and Dawn tests are skipped here, not validated.
portable:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14
- run: bun install --frozen-lockfile
# Bun shares module mocks across files, so each file gets its own process.
- run: |
for file in \
apps/slides/src/__tests__/typeGPUShader.test.ts \
apps/slides/src/__tests__/gpuRenderGraph.test.ts \
apps/slides/src/__tests__/exampleShaders.test.ts \
packages/presentation/src/__tests__/compileDeck.test.ts \
packages/slides-effects/tests/worldDataFlow.test.tsx \
packages/slides-effects/tests/worldRender.test.ts; do
bun test --timeout 60000 --preload ./apps/slides/src/__tests__/nativeMock.ts "$file"
done

# Executes compiled decks in React Native's macOS Hermes runtime; needs no GPU.
hermes:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14
- run: bun install --frozen-lockfile
- run: bun test --timeout 60000 --preload ./apps/slides/src/__tests__/nativeMock.ts packages/presentation/src/__tests__/compileDeck.test.ts -t "in Hermes"
- run: bun test --timeout 60000 --preload ./apps/slides/src/__tests__/nativeMock.ts apps/slides/src/__tests__/exampleShaders.test.ts -t "evaluate in Hermes"
14 changes: 14 additions & 0 deletions apps/slides/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -869,3 +869,17 @@ boundaries in the compiler, source editor, and speaker-notes editor. A top-level
dash separator starts a slide even directly after text; use `## Heading` for a
heading instead of setext underline syntax. Separators inside code and JSX stay
inside their containing slide.

### Native frame scheduler check

After normal macOS native preparation applies the tracked Worklets patch, run
this local check from the repository root with a 60 Hz display:

```sh
patches/tests/run-react-native-worklets-frame-queue.sh
```

The check compiles the installed source under `shell/.legend/spark/node_modules`;
it does not apply the patch. To check another candidate, pass its exact
`AnimationFrameQueue.mm` path and set `SPARK_NODE_MODULES` to the matching native
dependencies. This display-dependent check is separate from hosted CI.
82 changes: 82 additions & 0 deletions apps/slides/examples/3d/cinematic-title.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: Quiet Structures
width: 1920
height: 1080
transition: fade
theme:
backgroundColor: "#04060b"
color: "#f8fafc"
---

import { Background } from "@legend-apps/presentation"
import { Text } from "react-native"
import { World, Camera, Box, Ring, Orbs, Material, Floor, SpotLight, RimLight, Fog, Shafts, DOF, Bloom, Vignette, Grain } from "slides:world/World"

export const structures = <>
<Material id="stone" albedo="#101827" roughness={0.45} metal={0.2} rim={1.5} emissive={0} />
<Box material="stone" position={[-5, 0.5, 0]} scale={[1, 7, 1]} />
<Box material="stone" position={[-2, 0, 0]} scale={[1, 6, 1]} />
<Box material="stone" position={[1, 1, 0]} scale={[1, 8, 1]} />
<Box material="stone" position={[4, -0.5, 0]} scale={[1, 5, 1]} />
<Ring position={[0, -2.97, 0]} radius={7.5} thickness={0.025} color="#22d3ee" emissive={2} />
<Floor mirror y={-3} color="#04060b" roughness={0.06} reflectivity={0.85} size={60} />
<RimLight color="#f59e0b" intensity={0.15} />
</>

<Background>
<World id="quiet-structures" background quality="auto" exposure={1.1} scrim={0.12}>
<Camera shots={[{ step: 0, orbit: { center: [0, 0, 0], radius: 20, height: 5, speed: 0.045 }, fov: 38 }]} />
{structures}
<Orbs count={24} colors={["#22d3ee", "#f59e0b"]} radius={0.035} spread={[16, 6, 12]} position={[0, 1, 0]} seed={7} drift={0.25} emissive={3} />
<SpotLight position={[-6, 9, 3]} target={[-2, -3, 0]} color="#22d3ee" intensity={8} range={35} angle={0.35} />
<SpotLight position={[7, 10, -2]} target={[2, -3, 0]} color="#f59e0b" intensity={14} range={35} angle={0.35} />
<Fog density={0.035} height={-3} falloff={0.12} color="#07101b" />
<Shafts intensity={0.55} density={0.025} anisotropy={0.35} samples={32} />
<DOF focus={20} aperture={0.25} maxBlur={6} />
<Bloom intensity={0.9} /><Vignette amount={0.35} /><Grain amount={0.01} />
</World>
</Background>

# Quiet structures

<Text className="text-3xl text-slate-300 text-center self-center">Light, air and a slow orbit</Text>

---

<Background>
<World id="quiet-structures" background quality="auto" enter={{ from: "previous", duration: 1.5 }} exposure={[{ step: 0, value: 0.55 }, { step: 1, value: 1.2, duration: 2.5 }]} scrim={0.1}>
<Camera duration={2.5} shots={[{ step: 0, position: [9, 4, 15], target: [0, 0, 0], fov: 38 }, { step: 1, orbit: { center: [0, 0, 0], radius: 18, height: 4, speed: 0.04 } }]} />
{structures}
<Orbs count={24} colors={["#22d3ee", "#f59e0b"]} radius={0.035} spread={[16, 6, 12]} position={[0, 1, 0]} seed={7} drift={0.25} emissive={3} />
<SpotLight position={[-6, 9, 3]} target={[-2, -3, 0]} color="#22d3ee" intensity={[{ step: 0, value: 0.3 }, { step: 1, value: 12, duration: 2.5 }]} range={35} angle={0.35} />
<SpotLight position={[7, 10, -2]} target={[2, -3, 0]} color="#f59e0b" intensity={[{ step: 0, value: 0.5 }, { step: 1, value: 18, duration: 2.5 }]} range={35} angle={0.35} />
<Fog density={[{ step: 0, value: 0.09 }, { step: 1, value: 0.035, duration: 2.5 }]} height={-3} falloff={0.12} color="#07101b" />
<Shafts intensity={[{ step: 0, value: 0.1 }, { step: 1, value: 0.7, duration: 2.5 }]} density={0.025} anisotropy={0.35} samples={32} />
<DOF focus={18} aperture={0.25} maxBlur={6} />
<Bloom intensity={1} /><Vignette amount={0.35} /><Grain amount={0.01} />
</World>
</Background>

# Through the air

<Step at={1}><Text className="text-3xl text-cyan-300 text-center self-center">A quiet reveal</Text></Step>

---

<Background>
<World id="quiet-structures" background quality="auto" enter={{ from: "previous", duration: 1.5 }} exposure={1} scrim={0.12}>
<Camera shots={[{ at: 0, position: [8, 5, 20], target: [0, 0, 0], fov: 38 }, { at: 1.5, position: [0, 7, 34], target: [0, 0, 0], duration: 6, ease: "smoothstep" }]} />
{structures}
<Orbs count={64} colors={["#22d3ee", "#f59e0b"]} radius={0.035} spread={[24, 10, 18]} position={[0, 2, 0]} seed={7} drift={0.25} emissive={2} />
<SpotLight position={[-6, 9, 3]} target={[-2, -3, 0]} color="#22d3ee" intensity={8} range={35} angle={0.35} />
<SpotLight position={[7, 10, -2]} target={[2, -3, 0]} color="#f59e0b" intensity={12} range={35} angle={0.35} />
<Fog density={0.025} height={-3} falloff={0.12} color="#07101b" />
<Shafts intensity={0.4} density={0.025} anisotropy={0.35} samples={32} />
<DOF focus={26} aperture={0.2} maxBlur={5} />
<Bloom intensity={0.8} /><Vignette amount={0.3} /><Grain amount={0.01} />
</World>
</Background>

# Room to breathe

<Text className="text-3xl text-slate-300 text-center self-center">A wider view</Text>
8 changes: 8 additions & 0 deletions apps/slides/examples/3d/cinematic-title.mdx.effects.lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schemaVersion": 1,
"packs": {
"world": {
"path": "../../../../packages/slides-effects/packs/world"
}
}
}
162 changes: 162 additions & 0 deletions apps/slides/examples/3d/data-flow.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
title: Data Flow
width: 1920
height: 1080
transition: fade
theme:
backgroundColor: "#050811"
color: "#f8fafc"
---

import { Background } from "@legend-apps/presentation"
import { Text } from "react-native"
import { World, Camera, Graph, tree, wave, subset, PathPulse, Spray, Orbs, Rows, Voxels, WorldCounter, Stage, flyTo, Box, Ring, Floor, PointLight, RimLight, Bloom, Fog, DOF, Vignette, Grain } from "slides:world/World"

export const network = tree("root", [
["north", [["n1", ["n11", "n12", "n13"]], ["n2", ["n21", "n22", "n23"]], ["n3", ["n31", "n32", "n33"]]]],
["east", [["e1", ["e11", "e12", "e13"]], ["e2", ["e21", "e22", "e23"]], ["e3", ["e31", "e32", "e33"]]]],
["south", [["s1", ["s11", "s12", "s13"]], ["s2", ["s21", "s22", "s23"]], ["s3", ["s31", "s32", "s33"]]]],
["west", [["w1", ["w11", "w12", "w13"]], ["w2", ["w21", "w22", "w23"]], ["w3", ["w31", "w32", "w33"]]]],
])

export const branching = { data: network, radius: 6.8, levelHeight: 1.7, position: [0, 3.2, 0], edge: { curve: 0.45, thickness: 0.025, glow: 3 } }

export const atmosphere = <>
<Fog density={0.035} height={-3} falloff={0.12} color="#101c30" />
<Bloom intensity={1.8} threshold={0.7} radius={0.9} />
<Vignette amount={0.25} /><Grain amount={0.012} />
</>

<Background>
<World background quality="auto" exposure={1.3} scrim={0.08}>
<Camera duration={1.8} shots={[
{ step: 0, position: [8, 1.8, 13], target: [0, 0.6, 0], fov: 44 },
{ step: 2, follow: "node:e2", dolly: 10, focus: "node:e2" },
]} />
<Graph {...branching} node={{ color: "#ff36c8", dim: "#647b9a", size: 0.24, emissive: 7 }} edge={{ ...branching.edge, color: "#ff36c8" }} activate={[
wave({ from: "root", step: 1, speed: 0.32, duration: 0.45 }),
subset({ branches: ["east"], step: 2, duration: 0.8 }),
]} />
<Spray under="leaves:lit" color="#ff9ee5" rate={36} lifetime={1.6} size={0.025} spread={[0.35, 0.2, 0.35]} velocity={[0, -0.8, 0]} />
<Ring position={[0, -2.8, 0]} radius={6.8} thickness={0.025} color="#ff36c8" emissive={2.5} />
<Floor mirror y={-3} color="#101827" roughness={0.08} reflectivity={0.65} size={40} />
<PointLight position={[4, 6, 10]} color="#d6e7ff" intensity={220} range={40} />
<DOF focus={{ 0: "node:n2", 2: "node:e2" }} aperture={0.45} maxBlur={10} />
{atmosphere}
</World>
</Background>

<View className="flex-1 justify-end items-start">
<View className="rounded-2xl bg-black/70 px-8 py-5">
<Steps count={3}>{step => <Text className="text-6xl font-bold text-white">{step === 2 ? "Pruned subset" : "Cascade"}</Text>}</Steps>
</View>
</View>

---

<Background>
<World background quality="auto" exposure={1.3} scrim={0.08}>
<Camera duration={1.5} shots={[
{ step: 0, position: [8, 1.8, 13], target: [0, 0.6, 0], fov: 44 },
{ step: 1, follow: "pulse", dolly: 7, focus: "pulse", fov: 48 },
]} />
<Graph {...branching} node={{ color: "#22d3ee", dim: "#647b9a", size: 0.2 }} />
<PathPulse id="pulse" path={["root", "north", "n2", "n22"]} step={1} duration={4.5} color="#22d3ee" size={0.2} emissive={14} trail={0.1} />
<Ring position={[0, -2.8, 0]} radius={6.8} thickness={0.025} color="#22d3ee" emissive={1.8} />
<Floor mirror y={-3} color="#101827" roughness={0.08} reflectivity={0.6} size={40} />
<PointLight position={[4, 6, 10]} color="#d6e7ff" intensity={220} range={40} />
<DOF focus={{ 0: "node:n2", 1: "pulse" }} aperture={0.65} maxBlur={12} />
{atmosphere}
</World>
</Background>

<View className="flex-1 justify-end items-start">
<View className="rounded-2xl bg-black/70 px-8 py-5">
<Steps count={2}>{() => <Text className="text-6xl font-bold text-white">One path</Text>}</Steps>
</View>
</View>

---

<Background>
<World background quality="auto" exposure={1.3} scrim={0.08}>
<Camera shots={[{ step: 0, position: [4, -3, 7], target: [0, 1, 0], fov: 46 }]} />
<Rows id="rows" count={15} position={[0, 0, 0]} spacing={1} speed={0.3} width={4} depth={0.7} changes={[{ row: 7, step: 1 }, { row: 9, step: 2 }]} flashDuration={2.4} color="#607b96" flashColor="#ff36c8" />
<DOF focus={{ 0: "rows:row:7", 2: "rows:row:9" }} aperture={0.8} maxBlur={14} />
<PointLight position={[3, 5, 7]} color="#c5e9ff" intensity={140} range={30} />
<Orbs count={48} colors={["#22d3ee", "#ff36c8"]} radius={0.025} spread={[12, 16, 8]} seed={12} drift={0.15} emissive={3} />
<Floor mirror y={-8} color="#101827" roughness={0.12} reflectivity={0.5} size={40} />
{atmosphere}
</World>
</Background>

<View className="flex-1 justify-end items-start">
<View className="rounded-2xl bg-black/70 px-8 py-5">
<Steps count={3}>{() => <Text className="text-6xl font-bold text-white">Changed rows</Text>}</Steps>
</View>
</View>

---

<Background>
<World background quality="auto" exposure={1.3} scrim={0.08}>
<Camera shots={[
flyTo("a", { step: 0, offset: [2, 2.5, 10], fov: 46, focus: "stage:a" }),
flyTo("b", { step: 1, arc: 3, duration: 1.8, offset: [2, 2.5, 10], focus: "stage:b" }),
flyTo("c", { step: 2, arc: 3, duration: 1.8, offset: [2, 2.5, 10], focus: "stage:c" }),
]} />
<Stage id="a" position={[-10, 0, 0]} glow={{ 0: 1, 1: 0.35 }}>
<Box scale={[0.7, 2, 0.7]} color="#22d3ee" emissive={6} />
<Orbs count={20} colors={["#22d3ee"]} radius={0.08} spread={[6, 3, 5]} seed={3} drift={0.15} emissive={4} />
<Ring position={[0, -2, 0]} radius={3} color="#f59e0b" emissive={3} />
</Stage>
<Stage id="b" position={[0, 0, 0]} glow={{ 0: 0.08, 1: 0.7, 2: 0.25 }}>
<Box scale={[0.7, 2, 0.7]} color="#22d3ee" emissive={6} />
<Orbs count={20} colors={["#22d3ee"]} radius={0.08} spread={[6, 3, 5]} seed={3} drift={0.15} emissive={4} id="middle" />
<Ring position={[0, -2, 0]} radius={3} color="#f59e0b" emissive={3} />
</Stage>
<Stage id="c" position={[10, 0, 0]} glow={{ 0: 0.08, 2: 0.45 }}>
<Box scale={[0.7, 2, 0.7]} color="#22d3ee" emissive={6} />
<Orbs count={20} colors={["#22d3ee"]} radius={0.08} spread={[6, 3, 5]} seed={3} drift={0.15} emissive={4} id="last" />
<Ring position={[0, -2, 0]} radius={3} color="#f59e0b" emissive={3} />
</Stage>
<Floor mirror y={-3} color="#101827" roughness={0.08} reflectivity={0.7} size={40} />
<PointLight position={[0, 6, 6]} color="#d6e7ff" intensity={180} range={40} />
<RimLight color="#f59e0b" intensity={0.5} />
<DOF aperture={0.45} maxBlur={10} />
{atmosphere}
</World>
</Background>

<View className="flex-1 justify-end items-start">
<View className="rounded-2xl bg-black/70 px-8 py-5">
<Steps count={3}>{() => <Text className="text-6xl font-bold text-white">Three stages</Text>}</Steps>
</View>
</View>

---

<Background>
<World background quality="auto" exposure={1.3} scrim={0.08}>
<Camera duration={2.4} shots={[
{ step: 0, position: [7, 3.5, 10], target: [0, 0, 0], fov: 42 },
{ step: 1, position: [4.5, 2, 7], target: [0, 0, 0], focus: 8.5 },
]} />
<Voxels id="voxels" grid={[10, 10, 10]} spacing={0.45} size={0.17} color="#22d3ee" emissive={3} collapse={{ step: 1, to: 0.2, duration: 2.4 }} />
<WorldCounter source="voxels" position={[0.82, 0.19]} size={0.065} color="#ffffff" />
<Ring position={[0, -2.8, 0]} radius={4} thickness={0.025} color="#f59e0b" emissive={2.5} />
<Floor mirror y={-3} color="#101827" roughness={0.08} reflectivity={0.65} size={40} />
<RimLight color="#f59e0b" intensity={0.5} />
<DOF aperture={0.3} maxBlur={8} />
{atmosphere}
</World>
</Background>

<View className="flex-1 justify-end items-start">
<View className="rounded-2xl bg-black/70 px-8 py-5">
<Steps count={2}>{() => <Text className="text-6xl font-bold text-white">Less to carry</Text>}</Steps>
</View>
</View>
<View className="absolute top-0 right-0 rounded-2xl bg-black/70 px-8 py-5">
<Text className="text-3xl text-white">Active cells</Text>
</View>
8 changes: 8 additions & 0 deletions apps/slides/examples/3d/data-flow.mdx.effects.lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schemaVersion": 1,
"packs": {
"world": {
"path": "../../../../packages/slides-effects/packs/world"
}
}
}
Loading
Loading