perf(preview): pool de décodeurs — plus de réouverture au franchissement de clip au scrub - #209
Merged
Merged
Conversation
…ent de clip au scrub
Mesuré (headless, offscreen) : franchir un clip cross-média coûtait ~120 ms de blocage
synchrone sur le thread de rendu — 2× `Decoder::open` (avformat + init D3D11VA + avcodec_open2,
~39 ms) puis 2× seek keyframe + décode-avant (~81 ms). C'est le plus gros à-coup du scrub
(les pics p99 100-167 ms de la sonde), ~13× le readback. Et jusqu'ici on JETAIT les décodeurs
sortants (`apply_prefetched`), donc revenir sur un clip (motif A→B→A ultra-fréquent) rouvrait
tout à neuf + reseek keyframe complet.
On garde désormais un petit pool LRU (cap 3) de paires INACTIVES, ouvertes à leur dernière
position. Au franchissement cross-média (chemin scrub `active_clip_request`, hors lecture libre
déjà préchargée) : si la cible est en pool → reseek au lieu de rouvrir ; sinon ouverture neuve.
Dans les deux cas la paire QUITTÉE part en pool (dédup par clé + éviction LRU) au lieu d'être
fermée.
Effet mesuré, borné honnêtement :
- scrub qui SAUTE loin : POOL_HIT ~50-120 ms (open éliminé, seek keyframe restant) vs ~120 ms.
- scrub PRÈS d'une frontière (le cas ressenti : on traîne la tête à travers la coupe) :
le décodeur poolé est à ~1 frame → chemin rapide `decode_forward` → POOL_HIT ~5-9 ms,
soit ~20× moins que les ~120 ms d'avant.
Sûreté (la classe « frame sans texture → preview noire définitive » a déjà mordu ici) :
- la paire poolée est reseekée AVANT installation ; échec du seek → on la jette et on ouvre
à neuf (chemin connu sûr), jamais une frame vide (`seek_pair` rend `false`).
- le cache de SRV (keyé sur l'adresse de texture) reste vidé à chaque franchissement comme
avant — over-clear est sûr et bon marché, ce qui écarte tout « image du clip précédent ».
- lecture libre inchangée : `apply_prefetched`/`advance_to_next_scene_clip` droppent toujours
(déjà préchargé, pas de stall) ; seul le scrub alimente/consulte le pool.
Vérifié headless (harness bench-clipswitch, OPENSCREEN_CLIPSWITCH_TIMING=1) sur 3 phases —
sauts, retours locaux, et cyclage de 5 assets distincts (force l'éviction LRU) : le pool reste
borné à 3 et AUCUN franchissement, éviction comprise, ne produit de frame noire. cargo check OK.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Jul 30, 2026
EtienneLescot
added a commit
that referenced
this pull request
Jul 30, 2026
…nt bute sur l'EOF Suite du pool de décodeurs (#209). Un décodeur réactivé depuis le pool est souvent laissé en FIN de flux (on venait d'y scruber près de la fin avant de le quitter). Quand on y revient et qu'on le repositionne un poil plus loin, le chemin rapide `decode_forward_to` bute sur l'EOF et rendait `null` — ce qui forçait `swap_clip_pooled` à JETER la paire poolée et à tout ROUVRIR : mesuré ~190 ms, les pires à-coups ressentis au franchissement (13 % des franchissements dans un scrub réel, cf. les logs `OPENSCREEN_CLIPSWITCH_TIMING`). Correctif dans `seek_to` (donc utile aussi à `seek_active`, pas seulement au pool) : quand le décode-avant atteint l'EOF avant la cible, on NE rend plus `null` — on retombe sur le seek keyframe complet en dessous, qui rembobine + réarme le décodeur et repart proprement. Si la cible est réellement au-delà de l'EOF, le seek complet rend `null` lui aussi : comportement inchangé pour ce cas. Appliqué aux deux backends (`pipeline_windows` + `pipeline_macos`). Effet mesuré (headless, GPU au repos) : les reseeks près de l'EOF passent de OPEN ~190 ms à POOL_HIT ~50-80 ms. Jamais pire que l'état d'avant (pire cas identique : même repli ouverture). Sûreté vérifiée (harnais avec lecture des pixels, pas seulement le compteur de générations) : sur 46 franchissements — sauts, retours locaux, éviction LRU 5 assets, ET près de l'EOF — **0 preview noire, 0 frame stale** ; chaque franchissement produit une frame fraîche valide. (Les "frames absentes" d'un premier jet étaient des faux positifs : un `pump` trop court qui expirait avant qu'un seek keyframe près de l'EOF, GPU chargé, ait fini.) cargo check OK.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contexte
Le plus gros à-coup ressenti au scrub, c'est franchir une frontière de clip (les pics p99 100-167 ms de la sonde de fluidité). Dé-risque headless (offscreen, sur cette machine AMD) : un franchissement cross-média coûte ~120 ms de blocage synchrone sur le thread de rendu —
Decoder::open(avformat + init D3D11VA + avcodec_open2)Et jusqu'ici on jetait les décodeurs sortants (
apply_prefetched), donc revenir sur un clip — le motif A→B→A ultra-fréquent quand on traîne la tête à travers une coupe — rouvrait tout à neuf plus un seek keyframe complet.Le correctif
Un petit pool LRU (cap 3) de paires de décodeurs INACTIVES, gardées ouvertes à leur dernière position. Au franchissement cross-média (chemin scrub
active_clip_requestuniquement — la lecture libre est déjà préchargée, sans stall) : si la cible est en pool → reseek au lieu de rouvrir ; sinon ouverture neuve. Dans les deux cas la paire QUITTÉE part en pool (dédup par clé + éviction LRU) au lieu d'être fermée.Comme un décodeur poolé reste à sa dernière position, revenir près de là — exactement ce qu'on fait en scrubant autour d'une coupe — emprunte le chemin rapide
decode_forwardau lieu d'un seek keyframe.Mesures (headless,
OPENSCREEN_CLIPSWITCH_TIMING=1)Sûreté
La classe « frame sans texture → preview noire définitive » a déjà mordu dans ce fichier, donc :
seek_pairrendfalse) ;apply_prefetched/advance_to_next_scene_clipdroppent toujours (déjà préchargé) ; seul le scrub alimente/consulte le pool ;DECODER_POOL_CAP=3(VRAM : chaque paire retient son pool de surfaces D3D11VA) ; vidé quand la vue est détruite / le document rechargé.Vérification
cargo checkOK. Harnais headless sur 3 phases — sauts, retours locaux, et cyclage de 5 assets distincts (force l'éviction LRU) : le pool reste borné à 3 et aucun franchissement, éviction comprise, ne produit de frame noire (générations = l'assertion, cf. la convention de test natif du repo — pas de CI Rust). Re-vérifié après rebase sur la base courante (rework macOS des 54 commits ; la logique décodeurs Windows/D3D11 est inchangée).