Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b07e02e
G5: cache-blocked 32x32 Plane::transpose
claude Oct 10, 2026
4b66a11
G5: Median histogram updates inline (generic column visitor)
claude Oct 10, 2026
2f33ab9
G5: Camera Lens Blur sums iris span counts once per iris
claude Oct 10, 2026
b2b365a
G5: Cell Pattern computes each cell's feature point once per frame
claude Oct 10, 2026
563c523
G5: rustfmt the Cell Pattern feature-point cache
claude Oct 11, 2026
7b6430a
G5: layer cache packs quantised 8/16 bpc entries (u8/u16, lossless) u…
0Elias71 Oct 10, 2026
5a9768b
G5: declare the packed layer-cache modules in render's lib.rs
claude Oct 11, 2026
fe7e8ec
G5: 3D depth of field gathers only the two bracketing blur levels per…
claude Oct 10, 2026
268ca50
G5: skip preparing out-of-run shadow casters when the render casts no…
claude Oct 10, 2026
c663390
G5: expressions collect every host miss per run, prefetch last evalua…
0Elias71 Oct 10, 2026
9807653
G5: expression results shared between evaluations, validated by every…
claude Oct 10, 2026
e0d24ea
G5: bound the shared expression memo by answer bytes; rig timing comp…
claude Oct 10, 2026
802abc2
G5: expression perf tests assign Vec<Layer> directly (no Layers type …
claude Oct 11, 2026
cd870b7
G5: cache finished text geometry and share glyph outlines
claude Oct 10, 2026
229d0cf
G5: font files are read once for all their faces (.ttc collections) a…
0Elias71 Oct 10, 2026
cf21c61
G5: import what the shared font-file cache needs in fonts.rs
claude Oct 11, 2026
5e152f4
G5: text layout oracle without the digit-set substitution (not on thi…
claude Oct 11, 2026
c5bb661
G5: stream property-tree nodes when loading projects
claude Oct 10, 2026
b0fb8d9
G5: skip the effect-instance copy on open when nothing needs upgrading
claude Oct 10, 2026
0788f60
Fix: is_current sees upstream's Hidden/Text → File parameter upgrade …
claude Oct 11, 2026
6e18ebc
G5: export honours the Memory & CPU budget: job layer cache sized by …
0Elias71 Oct 10, 2026
f2d96cb
G5: export renders the next batch while the current one encodes (movi…
0Elias71 Oct 10, 2026
f40f95d
G5: later output modules of a render queue item continue from the pre…
claude Oct 10, 2026
d7ce7d3
G5: render each frame once for output modules that share it
claude Oct 10, 2026
8e4aa92
G5: hand an item's output modules to the exporter at once
claude Oct 10, 2026
c945c8f
G5: the host exporter renders each frame once for an item's output mo…
claude Oct 10, 2026
f3a8f1e
G5: open movies through a file reader (index now, samples on demand) …
0Elias71 Oct 10, 2026
e10400e
G5: header probe without the AVIF sniff (no avif module upstream)
claude Oct 11, 2026
bcc35f2
G5: GPU upload cache drops textures of freed layer buffers on every u…
0Elias71 Oct 10, 2026
d572c29
G5: Roto Brush segmentation cache follows the memory budget; disk-cac…
0Elias71 Oct 10, 2026
9a0e7d6
G5: viewers upload shared frames without copying their pixels; passiv…
0Elias71 Oct 10, 2026
896f927
G5: a playing preview repaints when its next frame is due, not at the…
0Elias71 Oct 10, 2026
070417f
G5: viewer channel/exposure transform runs in parallel (Tier E)
0Elias71 Oct 10, 2026
3524966
G5: rustfmt the viewer upload and channel-transform changes
claude Oct 11, 2026
cd34b61
G5: bound thumbnail textures (Media Browser, Project panel, Home) wit…
0Elias71 Oct 10, 2026
593d59a
G5: declare the thumbnail LRU module in ui-egui's lib.rs
claude Oct 11, 2026
9458c64
G5: Timeline cache bar remembers its frame lists per cache generation…
0Elias71 Oct 10, 2026
9be9207
G5: Lumetri Scopes share their cached scope and read the shown frame …
0Elias71 Oct 10, 2026
c920bbc
G5: Footage panel borrows its footage and decodes once per frame (Tie…
0Elias71 Oct 10, 2026
c6ab753
G5: Footage panel decodes its frame on a worker thread, averaged down…
claude Oct 10, 2026
ccb6bc3
G5: Footage panel timecode takes the borrowed item (clippy needless_b…
claude Oct 11, 2026
66d7342
G5: native menu catches up after a drag instead of rebuilding every f…
0Elias71 Oct 10, 2026
f7b1dee
G5: native menu updates paced to 4 per second and pushed only when th…
claude Oct 10, 2026
fc299e0
Merge remote-tracking branch 'upstream/main' into upstream/combined-perf
claude Oct 13, 2026
15ae8ce
fix(expr): fan-out budget test uses reads the result memo can't fold
claude Oct 13, 2026
04b2d4a
Expressions: the memo skips any run that calls Math.random or Date, h…
claude Oct 13, 2026
98db5fe
PR856: resolve review issues and integrate upstream
eliasx45 Oct 15, 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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ image = { version = "0.25", default-features = false, features = ["png", "jpeg"]

# media codecs + containers: FilmCraft's pure-Rust crates, pinned to a revision (plan/adr/0001).
# Only `crates/media`, `crates/export` and `crates/interchange` may depend on these.
# Header-only probing of standalone audio files (the same build FilmCraft decodes them with; MPL-2.0).
filmcraft-codecs = { git = "https://github.com/storytold/filmcraft", rev = "2c5ba7a72619935f0481f99d59409e0d1844b234" }
filmcraft-media = { git = "https://github.com/storytold/filmcraft", rev = "2c5ba7a72619935f0481f99d59409e0d1844b234" }
filmcraft-frame = { git = "https://github.com/storytold/filmcraft", rev = "2c5ba7a72619935f0481f99d59409e0d1844b234" }
Expand Down
36 changes: 24 additions & 12 deletions apps/effectcraft/src/native_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
//! [`effectcraft_ui_egui::native_menu`]'s spec of the engine's After Effects menu tree.
//!
//! Each frame [`NativeBar::update`] runs menu activations (the same command ids as the in-window
//! bar), and when [`native_menu::state_key`] changed it rebuilds the spec and pushes labels,
//! enabled and check state into the existing items; a structural change (Open Recent, a new
//! shortcut preset) rebuilds the menu. Settings ▸ Appearance ▸ Use In-Window Menu Bar on macOS
//! bar), and when [`native_menu::state_key`] changed (at most every
//! [`native_menu::MENU_REFRESH_INTERVAL`]) it rebuilds the spec and, when that differs, pushes
//! labels, enabled and check state into the existing items; a structural change (Open Recent, a
//! new shortcut preset) rebuilds the menu. Settings ▸ Appearance ▸ Use In-Window Menu Bar on macOS
//! swaps back to the in-window bar.

use std::collections::HashMap;
use std::sync::mpsc::{Receiver, channel};

use effectcraft_ui_egui::EffectcraftApp;
use effectcraft_ui_egui::native_menu::{self, NativeMenu, NativeNode, Role};
use effectcraft_ui_egui::native_menu::{self, MenuPacer, NativeMenu, NativeNode, Pace, Role};
use muda::{CheckMenuItem, Menu, MenuEvent, PredefinedMenuItem, Submenu};

/// The `muda` accelerator for a spec accelerator string.
Expand All @@ -24,7 +25,7 @@ pub struct NativeBar {
items: HashMap<String, CheckMenuItem>,
spec: NativeMenu,
structure: u64,
state: Option<u64>,
pacer: MenuPacer,
events: Receiver<MenuEvent>,
/// The in-window-bar setting last applied (None: not yet).
in_window: Option<bool>,
Expand All @@ -38,7 +39,7 @@ impl NativeBar {
let _ = tx.send(e);
ctx.request_repaint();
}));
NativeBar { menu: None, items: HashMap::new(), spec: NativeMenu::default(), structure: 0, state: None, events: rx, in_window: None }
NativeBar { menu: None, items: HashMap::new(), spec: NativeMenu::default(), structure: 0, pacer: MenuPacer::default(), events: rx, in_window: None }
}

/// Run activations and keep the native menu in step with the app (call once per frame).
Expand All @@ -53,7 +54,7 @@ impl NativeBar {
}
self.items.clear();
} else {
self.state = None;
self.pacer.reset();
self.structure = 0;
}
}
Expand All @@ -62,22 +63,33 @@ impl NativeBar {
log::warn!("menu: {err}");
}
// Check items toggle themselves when clicked: resync with the app.
self.state = None;
self.pacer.reset();
}
if in_window {
return;
}
let key = native_menu::state_key(app);
if self.state == Some(key) && self.menu.is_some() {
// While a button is held (a drag edits on every frame, each a new revision) the menu,
// which can't be opened meanwhile, catches up on release.
if self.menu.is_some() && self.pacer.applied() && ctx.input(|i| i.pointer.any_down()) {
return;
}
self.state = Some(key);
if self.menu.is_none() {
self.pacer.reset();
}
match self.pacer.check(native_menu::state_key(app), ctx.input(|i| i.time)) {
Pace::Current => return,
Pace::Later(wait) => {
ctx.request_repaint_after(std::time::Duration::try_from_secs_f64(wait).unwrap_or_default());
return;
}
Pace::Now => {}
}
let spec = native_menu::build(app);
let structure = spec.structure();
if self.menu.is_none() || structure != self.structure {
self.install(&spec);
self.structure = structure;
} else {
} else if spec != self.spec {
self.refresh(&spec);
}
self.spec = spec;
Expand Down
24 changes: 17 additions & 7 deletions crates/effects/src/blur2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,14 @@ pub fn camera_lens_plain(ctx: &EffectCtx) -> bool {
ctx.params.f("irisProperties/diffractionFringe") <= 0.0 && ctx.params.get("blurMap/blurMapLayer").and_then(|v| v.as_layer()).is_none()
}

/// Camera Lens Blur iris spans `(dy, x_left, x_right)` and their pixel counts.
struct Iris {
outer: Vec<(i64, i64, i64)>,
inner: Vec<(i64, i64, i64)>,
no: f64,
ni: f64,
}

fn camera_lens_blur(ctx: &EffectCtx, mut b: Buf) -> Buf {
let r = ctx.params.f("blurRadius").max(0.0) * b.scale;
if r < 0.5 {
Expand Down Expand Up @@ -406,7 +414,9 @@ fn camera_lens_blur(ctx: &EffectCtx, mut b: Buf) -> Buf {
// 0 = an even disc, 100 = a natural halo, 500 = all of it in the ring.
let fringe = (ctx.params.f("irisProperties/diffractionFringe") / 500.0).clamp(0.0, 1.0);
let (w_in, w_ring) = (1.0 - fringe, 1.0 + 4.0 * fringe);
let iris = |radius: f64| -> (Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>) {
let count = |s: &[(i64, i64, i64)]| s.iter().map(|(_, l, r)| (r - l + 1) as f64).sum::<f64>();
// The outer and inner (fringe) iris spans of a radius, with their pixel counts summed once.
let iris = |radius: f64| -> Iris {
let stretch = |mut s: Vec<(i64, i64, i64)>| {
if aspect > 0.0 && (aspect - 1.0).abs() > 1e-6 {
for v in s.iter_mut() {
Expand All @@ -422,18 +432,18 @@ fn camera_lens_blur(ctx: &EffectCtx, mut b: Buf) -> Buf {
} else {
vec![]
};
(outer, inner)
let (no, ni) = (count(&outer), count(&inner));
Iris { outer, inner, no, ni }
};
let count = |s: &[(i64, i64, i64)]| s.iter().map(|(_, l, r)| (r - l + 1) as f64).sum::<f64>();
// One blurred pixel with the given iris.
let blur_px = |sp: &(Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>), x: usize, y: usize| -> [f32; 4] {
let (outer, inner) = sp;
let blur_px = |sp: &Iris, x: usize, y: usize| -> [f32; 4] {
let Iris { outer, inner, no, ni } = sp;
let (no, ni) = (*no, *ni);
let acc = if inner.is_empty() {
let n = count(outer).max(1.0);
let n = no.max(1.0);
span_sum(outer, x, y).map(|v| v / n)
} else {
let (so, si) = (span_sum(outer, x, y), span_sum(inner, x, y));
let (no, ni) = (count(outer), count(inner));
let norm = (w_in * ni + w_ring * (no - ni)).max(1e-9);
std::array::from_fn(|c| (w_in * si[c] + w_ring * (so[c] - si[c])) / norm)
};
Expand Down
Loading