Merge pull request #2517 from MatejKozlovsky/crypto-pick-probe

Cryptomatte pick: keep a hover probe, built off the UI thread (#473)
This commit is contained in:
BFlat
2026-10-29 10:09:49 +07:00
committed by GitHub
7 changed files with 822 additions and 30 deletions
+8 -2
View File
@@ -91,6 +91,8 @@ impl Session {
pub fn bump(&mut self) {
self.revision += 1;
// A Cryptomatte probe is keyed by the revision: an older one never answers again.
self.drop_crypto_probe();
// Back at the saved state (undo, redo, a history jump or a rolled-back batch).
if Arc::ptr_eq(&self.project, &self.saved_project) {
self.saved_revision = self.revision;
@@ -166,12 +168,14 @@ impl Session {
pub fn undo(&mut self) -> bool {
let Some((label, p)) = self.history.undo.pop() else { return false };
let edited = commands::text_edit::active_editor_snapshot(self);
let probe = commands::kept_crypto_probe(self);
let cur = std::mem::replace(&mut self.project, p);
self.keep_view_settings(&cur);
self.history.redo.push((label, cur));
self.history.merge_key = None;
self.sanitize_after_replacement(edited.as_ref());
self.bump();
commands::keep_crypto_probe(self, probe, &cur);
self.history.redo.push((label, cur));
true
}
@@ -182,12 +186,14 @@ impl Session {
pub fn redo(&mut self) -> bool {
let Some((label, p)) = self.history.redo.pop() else { return false };
let edited = commands::text_edit::active_editor_snapshot(self);
let probe = commands::kept_crypto_probe(self);
let cur = std::mem::replace(&mut self.project, p);
self.keep_view_settings(&cur);
self.history.undo.push((label, cur));
self.history.merge_key = None;
self.sanitize_after_replacement(edited.as_ref());
self.bump();
commands::keep_crypto_probe(self, probe, &cur);
self.history.undo.push((label, cur));
true
}
+44 -3
View File
@@ -1,4 +1,4 @@
use super::{EngineError, Image, ItemId, RenderOpts, Renderer, Result, Session, ThumbnailJob, Tick};
use super::{EngineError, Image, ItemId, RenderOpts, Renderer, Result, Session, ThumbnailJob, Tick, Value, commands};
impl Session {
/// Render a comp frame with the session's footage + expression hosts.
@@ -9,14 +9,54 @@ impl Session {
/// The renderer [`Session::render`] uses: the session's footage, expressions, layer cache and
/// accelerator, and the render prefs (nested switches, draft shadows) over `opts`.
pub fn renderer(&self, opts: RenderOpts) -> Renderer<'_> {
let opts = RenderOpts { nested_switches: self.prefs.general.switches_affect_nested_comps, draft_shadows: self.prefs.three_d.realtime_shadows, ..opts };
let mut r = Renderer::new(&self.project, self.footage.as_ref(), opts);
let mut r = Renderer::new(&self.project, self.footage.as_ref(), self.render_opts(opts));
r.expr = self.expr.as_deref();
r.cache = Some(&self.layer_cache);
r.accel = self.accel.as_deref();
r
}
/// `opts` with the render prefs (nested switches, draft shadows), as [`Session::renderer`].
pub fn render_opts(&self, opts: RenderOpts) -> RenderOpts {
RenderOpts { nested_switches: self.prefs.general.switches_affect_nested_comps, draft_shadows: self.prefs.three_d.realtime_shadows, ..opts }
}
/// Whether `effect.cryptomatteAt` with params `p` answers from the kept probe (a few array
/// reads; `probeOnly` queries answer `pending` until then). Changes nothing: ask it every
/// frame, and start [`Session::crypto_probe_job`] only when a build will actually run.
pub fn crypto_probe_ready(&self, p: &Value) -> Result<bool> {
commands::crypto_probe_ready(self, p)
}
/// For the Cryptomatte eyedropper: `None` when `effect.cryptomatteAt` with params `p` answers
/// from the kept probe, else a job that builds that probe (it may render a precomp's ID pass)
/// off the UI thread, for the next query to use. Asking for a job supersedes a build in
/// flight for another probe (it then stores nothing): ask only to run it.
pub fn crypto_probe_job(&self, p: &Value) -> Result<Option<commands::CryptoProbeJob>> {
commands::crypto_probe_job(self, p)
}
/// Let go of the kept Cryptomatte probe (and its aux channels), and cancel a build in
/// flight: the eyedropper was disarmed. Edits, footage changes and Open / New do it too.
pub fn drop_crypto_probe(&self) {
if let Ok(mut slot) = self.crypto_probe.lock() {
slot.clear();
}
}
/// Whether a Cryptomatte probe is kept, and how many were built so far (for tests).
#[doc(hidden)]
pub fn crypto_probe_stats(&self) -> (bool, u64) {
self.crypto_probe.lock().map_or((false, 0), |s| (s.kept(), s.builds().1))
}
/// How many probe jobs ran so far, built or cancelled before they began (for tests: each
/// is a thread in the eyedropper).
#[doc(hidden)]
pub fn crypto_probe_jobs(&self) -> u64 {
self.crypto_probe.lock().map_or(0, |s| s.builds().0)
}
/// Render a comp frame (comp time `t`) as opaque 8-bit RGBA over the comp background, scaled
/// so the longest side is at most `max_side` pixels (0 = full size). Returns (width, height, rgba).
pub fn render_rgba8(&self, comp: ItemId, t: Tick, max_side: u32) -> Result<(u32, u32, Vec<u8>)> {
@@ -95,5 +135,6 @@ impl Session {
self.footage.forget(p);
}
self.layer_cache.clear();
self.drop_crypto_probe();
}
}