Skip to content

Commit 46552bc

Browse files
committed
fix(compositor): mac was missing clear_srv_cache after the rebase onto v1.8.0
release/v1.8.0 added an SRV-cache invalidation call in the portable live.rs (comp.clear_srv_cache(), called unconditionally whenever a decoder set is closed) plus a decoder fast-seek path in the Windows Decoder. Both landed during the rebase of this branch, but only pipeline_windows.rs got the method/the fast path applied — compositor_macos::Compositor had no clear_srv_cache at all, which doesn't fail until compiled on macOS. clear_srv_cache() on Metal calls CVMetalTextureCache::flush() — there's no address-keyed HashMap to clear like on D3D11 (CoreVideo already dedupes by IOSurface, not by pointer), so flushing the cache IS the equivalent operation. Also ported the Decoder::seek_to fast path (cur_pts, decode_forward_to, SEEK_FORWARD_MAX_SEC) into pipeline_macos.rs so both engines stay at the same seek performance instead of drifting apart again right after the last ISO-parity pass.
1 parent 209a496 commit 46552bc

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

crates/compositor/src/compositor_macos.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,19 @@ impl Compositor {
658658
Ok((y, uv))
659659
}
660660

661+
/// Vide le `CVMetalTextureCache` — API symétrique de
662+
/// `compositor_windows::Compositor::clear_srv_cache`, même contrat côté appelant
663+
/// (`live.rs` l'appelle sans savoir sur quelle plateforme il tourne) : à invoquer
664+
/// quand un jeu de décodeurs vient d'être fermé, pour ne pas garder de textures
665+
/// pointant sur un IOSurface déjà libéré.
666+
///
667+
/// Pas de `HashMap` keyée par adresse à vider ici (contrairement à Windows) — voir
668+
/// la doc de `CVMetalTextureCache` : CoreVideo est déjà ce cache et le réutilise par
669+
/// IOSurface, pas par pointeur Rust. `flush()` est donc la vidange elle-même.
670+
pub fn clear_srv_cache(&self) {
671+
self.metal_texture_cache.flush();
672+
}
673+
661674
/// Les verbes de dessin, côté Metal. Mêmes noms et mêmes paramètres que leurs
662675
/// homologues de `compositor_windows.rs` — c'est ce qui rend les deux moitiés
663676
/// « dessin » comparables ligne à ligne.

crates/compositor/src/pipeline_macos.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ impl Drop for FrameGuard {
5757
}
5858
}
5959

60+
/// Au-delà de cette distance vers l'avant, `Decoder::seek_to` repart d'une image clé
61+
/// plutôt que de dérouler. Identique à `pipeline_windows::SEEK_FORWARD_MAX_SEC` — le
62+
/// seuil dépend du GOP des captures, pas du backend de décodage.
63+
const SEEK_FORWARD_MAX_SEC: f64 = 0.5;
64+
6065
/// Décodeur ffmpeg — câblage VideoToolbox (et repli logiciel pour les codecs hors-session).
6166
/// Cf. `pipeline_windows::Decoder` pour la version D3D11VA. Mêmes champs publics pour
6267
/// que `live.rs::Player` reste portable ; les détails internes (hw_device_ctx, format
@@ -70,6 +75,10 @@ pub struct Decoder {
7075
pkt: *mut crate::ffi::AVPacket,
7176
frame: *mut crate::ffi::AVFrame,
7277
sent_eof: bool,
78+
/// PTS de la frame actuellement décodée dans `frame`, ou `None` si l'état du décodeur
79+
/// vient d'être jeté (ouverture, seek). Sert au chemin rapide de `seek_to` — symétrique
80+
/// de `pipeline_windows::Decoder::cur_pts`.
81+
cur_pts: Option<i64>,
7382
/// Backend « software fallback » uniquement : convertit la frame système en NV12 +
7483
/// CVPixelBufferRef IOSurface-backed, et la présente sous le même contrat que
7584
/// VideoToolbox (`compositor_macos::nv12_srvs` reconnaît le sentinel `AV_PIX_FMT_D3D11`
@@ -190,6 +199,7 @@ impl Decoder {
190199
pkt: crate::ffi::av_packet_alloc(),
191200
frame: crate::ffi::av_frame_alloc(),
192201
sent_eof: false,
202+
cur_pts: None,
193203
cpu,
194204
})
195205
}
@@ -221,15 +231,38 @@ impl Decoder {
221231
}
222232

223233
/// Seek keyframe vers `seconds` puis décode-avant jusqu'à la 1re frame dont le
224-
/// temps ≥ `seconds`. Symétrique de `pipeline_windows::Decoder::seek_to`.
234+
/// temps ≥ `seconds`. Symétrique de `pipeline_windows::Decoder::seek_to`, chemin
235+
/// rapide compris : mêmes seuils, même critère d'arrêt (`decode_forward_to`), pour
236+
/// que les deux moteurs rendent la même frame au même coût relatif.
225237
pub unsafe fn seek_to(&mut self, seconds: f64) -> Result<*mut crate::ffi::AVFrame> {
226238
let tb_sec = self.tb_sec();
239+
240+
if tb_sec > 0.0 {
241+
if let Some(pts) = self.cur_pts {
242+
let cur = pts as f64 * tb_sec;
243+
let frame_dur = 1.0 / self.fps().max(1.0);
244+
// 1) La frame courante EST celle demandée : rien à décoder du tout.
245+
// `cur_frame()`, pas `self.frame` : en backend CPU la frame exploitable
246+
// est la texture NV12 déjà présentée, pas la frame système du décodeur.
247+
if (cur - seconds).abs() < frame_dur * 0.5 {
248+
return Ok(self.cur_frame());
249+
}
250+
// 2) La cible est DEVANT et à portée : dérouler depuis ici plutôt que de
251+
// repartir d'une image clé (cf. `pipeline_windows::SEEK_FORWARD_MAX_SEC`).
252+
if cur < seconds && seconds - cur <= SEEK_FORWARD_MAX_SEC {
253+
return self.decode_forward_to(seconds, tb_sec);
254+
}
255+
}
256+
}
257+
227258
let target = if tb_sec > 0.0 { (seconds / tb_sec) as i64 } else { 0 };
228259
crate::ffi::averr(
229260
crate::ffi::av_seek_frame(self.fmt, self.vidx, target, crate::ffi::AVSEEK_FLAG_BACKWARD),
230261
"seek_to",
231262
)?;
232263
crate::ffi::avcodec_flush_buffers(self.dctx);
264+
// L'état vient d'être jeté : plus aucune frame courante exploitable.
265+
self.cur_pts = None;
233266
self.sent_eof = false;
234267
loop {
235268
let f = self.next()?;
@@ -246,6 +279,24 @@ impl Decoder {
246279
}
247280
}
248281

282+
/// Déroule le décodeur en avant jusqu'à la première frame à `seconds` ou après, SANS
283+
/// jeter son état. Symétrique de `pipeline_windows::Decoder::decode_forward_to`.
284+
unsafe fn decode_forward_to(&mut self, seconds: f64, tb_sec: f64) -> Result<*mut crate::ffi::AVFrame> {
285+
loop {
286+
let f = self.next()?;
287+
if f.is_null() {
288+
return Ok(ptr::null_mut());
289+
}
290+
let pts = (*f).best_effort_timestamp;
291+
if pts == i64::MIN {
292+
return Ok(f);
293+
}
294+
if (pts as f64) * tb_sec >= seconds - tb_sec * 0.5 {
295+
return Ok(f);
296+
}
297+
}
298+
}
299+
249300
/// Rend la prochaine frame (valide jusqu'au prochain appel), ou null à EOF.
250301
/// Symétrique de `pipeline_windows::Decoder::next`. Boucle `avcodec_receive_frame`
251302
/// / `av_read_frame` avec gestion d'EOF et AVERROR_EAGAIN — identique au chemin
@@ -255,6 +306,8 @@ impl Decoder {
255306
loop {
256307
let r = crate::ffi::avcodec_receive_frame(self.dctx, self.frame);
257308
if r == 0 {
309+
let pts = (*self.frame).best_effort_timestamp;
310+
self.cur_pts = if pts == i64::MIN { None } else { Some(pts) };
258311
return match &mut self.cpu {
259312
Some(cpu) => cpu.present(self.frame),
260313
None => Ok(self.frame),

0 commit comments

Comments
 (0)