Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 11 additions & 1 deletion crates/compositor-view-napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ pub struct FramePacket {
/// Le métrage passe de ses coins à l'image par leur homographie (caméra réelle), pas par
/// leur interpolation bilinéaire.
pub footage_projective: bool,
/// Clip actif de la scène quand la frame a été composée (`FramePosition`).
pub clip_index: u32,
/// Temps source de la frame écran composée, en secondes.
pub source_time_sec: f64,
}

/// Renvoie la dernière frame readback du thread de rendu SI elle est plus récente que
Expand Down Expand Up @@ -243,7 +247,7 @@ pub fn read_frame(id: i32, since_gen: f64) -> Result<Option<FramePacket>> {
v.latest_frame_since(since_gen.max(0.0) as u64)
}
};
Ok(slot.map(|(gen, w, h, pixels, footage)| {
Ok(slot.map(|(gen, w, h, pixels, footage, position)| {
debug_assert_eq!(pixels.len(), (w as usize) * (h as usize) * 4);
FramePacket {
gen: gen as f64,
Expand All @@ -252,6 +256,8 @@ pub fn read_frame(id: i32, since_gen: f64) -> Result<Option<FramePacket>> {
data: Buffer::from(pixels),
footage: footage_corners(footage),
footage_projective: footage.is_some_and(|q| q.projective),
clip_index: position.clip_index,
source_time_sec: position.source_time_sec,
}
}))
}
Expand All @@ -276,6 +282,8 @@ pub struct SharedFramePacket {
pub height: u32,
pub footage: Option<Vec<f64>>,
pub footage_projective: bool,
pub clip_index: u32,
pub source_time_sec: f64,
}

/// Livrer les frames de la vue par textures partagées plutôt que par `read_frame`. Rend
Expand Down Expand Up @@ -314,6 +322,8 @@ pub fn read_shared_frame(id: i32, since_gen: f64) -> Result<Option<SharedFramePa
height: f.height,
footage: footage_corners(f.footage),
footage_projective: f.footage.is_some_and(|q| q.projective),
clip_index: f.position.clip_index,
source_time_sec: f.position.source_time_sec,
}))
}

Expand Down
45 changes: 38 additions & 7 deletions crates/compositor/src/live.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,15 +759,34 @@ fn scene_for_clip(scene: &Scene, clip_index: usize) -> Scene {

/// Dernière frame readback vers CPU, prête pour le napi `read_frame`.
///
/// `(gen, w, h, vec)` où `vec.len() == w*h*4` octets RGBA8 tightly-packed (R, G, B, A
/// en mémoire — cf. `Compositor::readback_resized`). `gen` est une génération monotone
/// `(gen, w, h, vec, métrage, position)` où `vec.len() == w*h*4` octets RGBA8 tightly-packed
/// (R, G, B, A en mémoire — cf. `Compositor::readback_resized`). `gen` est une génération monotone
/// (≥ 1, `0` réservé à « le consommateur n'a encore rien vu ») incrémentée à CHAQUE
/// publication, càd uniquement quand une nouvelle frame a réellement été composée (le
/// thread de rendu ne republie pas une frame identique — cf. `stepped || first`). Elle
/// est l'IDENTITÉ de la frame : le consommateur (`read_frame`) ne repaie le clone + l'IPC
/// que lorsqu'elle change. `None` = "aucune frame composée pour l'instant" (toutes les
/// lectures avant la 1re frame composée retournent `None` côté napi, jamais un buffer vide).
pub type LatestFrame = (u64, u32, u32, Vec<u8>, Option<crate::frame_geometry::FootageQuad>);
pub type LatestFrame = (
u64,
u32,
u32,
Vec<u8>,
Option<crate::frame_geometry::FootageQuad>,
FramePosition,
);

/// Où en est la vue quand elle compose une frame : le clip actif de la scène et le temps
/// source de la frame écran. Voyage avec chaque frame publiée, quel que soit le transport,
/// pour que l'app compare la position RÉELLE de la vue à sa propre tête de lecture au lieu
/// de la deviner à partir de l'horloge murale.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FramePosition {
/// Index du clip actif dans `scene.clips`.
pub clip_index: u32,
/// pts de la frame écran composée, en secondes du fichier source.
pub source_time_sec: f64,
}

/// État partagé thread appelant → thread de rendu (commandes sans blocage).
struct Shared {
Expand Down Expand Up @@ -1840,7 +1859,11 @@ unsafe fn render_thread(

if stepped || first || publish_pending {
if pw > 0 && ph > 0 {
match publish_shared(&shared, &gpu, &comp, &mut ring) {
let position = FramePosition {
clip_index: active_clip_index as u32,
source_time_sec: player.screen_time_sec(),
};
match publish_shared(&shared, &gpu, &comp, &mut ring, position) {
SharedPublish::Published => {
first = false;
publish_pending = false;
Expand Down Expand Up @@ -1874,7 +1897,7 @@ unsafe fn render_thread(
// l'ancienne dérivation tant que le slot n'est pas vidé.
let next_gen = shared.frame_gen.fetch_add(1, Ordering::Relaxed) + 1;
if let Ok(mut slot) = shared.latest_frame.lock() {
*slot = Some((next_gen, rw, rh, rgba, comp.footage_quad()));
*slot = Some((next_gen, rw, rh, rgba, comp.footage_quad(), position));
}
first = false;
publish_pending = false;
Expand Down Expand Up @@ -1918,6 +1941,7 @@ unsafe fn publish_shared(
gpu: &Gpu,
comp: &Compositor,
ring: &mut Ring,
position: FramePosition,
) -> SharedPublish {
if !shared.shared_frames.load(Ordering::Relaxed) {
return SharedPublish::Off;
Expand Down Expand Up @@ -1967,6 +1991,7 @@ unsafe fn publish_shared(
width,
height,
footage: comp.footage_quad(),
position,
});
}
SharedPublish::Published
Expand All @@ -1976,7 +2001,13 @@ unsafe fn publish_shared(
}

#[cfg(not(windows))]
unsafe fn publish_shared(_: &Shared, _: &Gpu, _: &Compositor, _: &mut Ring) -> SharedPublish {
unsafe fn publish_shared(
_: &Shared,
_: &Gpu,
_: &Compositor,
_: &mut Ring,
_: FramePosition,
) -> SharedPublish {
SharedPublish::Off
}

Expand Down Expand Up @@ -2113,7 +2144,7 @@ pub fn run_standalone(screen: &str, webcam: &str, cursor_json: &str) -> Result<(
// standalone n'affiche pas réellement les pixels ici (l'embed Electron est
// le consumer réel). On imprime juste une frame de temps en temps pour
// confirmer que la chaîne fonctionne.
if let Some((_gen, fw, fh, _pixels, _)) = view.latest_frame() {
if let Some((_gen, fw, fh, _pixels, _, _)) = view.latest_frame() {
if (fw, fh) != (w, h) {
// garde-fou : la staging de readback suit `set_rect` côté thread
// de rendu, donc ce serait une désynchro transitoire — acceptable.
Expand Down
12 changes: 11 additions & 1 deletion crates/compositor/src/shared_frames.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//! macOS (IOSurface) et Linux (dmabuf) restent sur le readback.

use crate::frame_geometry::FootageQuad;
use crate::live::FramePosition;
use std::time::{Duration, Instant};

/// Cases de l'anneau : une prête, une en transit vers le renderer (tenue jusqu'à ce que
Expand All @@ -37,6 +38,7 @@ pub struct SharedFrame {
pub width: u32,
pub height: u32,
pub footage: Option<FootageQuad>,
pub position: FramePosition,
}

/// Une case prise par JS : la génération qu'elle porte et quand elle est partie.
Expand Down Expand Up @@ -244,7 +246,15 @@ mod tests {
use super::*;

fn frame(gen: u64, slot: u32) -> SharedFrame {
SharedFrame { gen, slot, handle: 0, width: 2, height: 2, footage: None }
SharedFrame {
gen,
slot,
handle: 0,
width: 2,
height: 2,
footage: None,
position: FramePosition { clip_index: 0, source_time_sec: 0.0 },
}
}

/// Toutes les cases prises par JS, la case `n` portant la génération `n + 1`.
Expand Down
2 changes: 2 additions & 0 deletions electron/native-bridge/services/compositorViewService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,8 @@ export class CompositorViewService {
height: frame.height,
footage: frame.footage ?? null,
footageProjective: frame.footageProjective ?? false,
clipIndex: frame.clipIndex,
sourceTimeSec: frame.sourceTimeSec,
};
const api = this.sharedTextureApi();
let imported: Electron.SharedTextureImported | undefined;
Expand Down
8 changes: 8 additions & 0 deletions electron/native/compositor-view/addon.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export interface NativeFramePacket {
footage?: number[] | null;
/** The footage maps from those corners by their homography (real camera), not bilinearly. */
footageProjective?: boolean;
/** Where the view was when it composed this frame: the active clip in `scene.clips` and the
* screen frame's time in its source file. Absent from an older `.node`. */
clipIndex?: number;
sourceTimeSec?: number;
}

/** A preview frame left in a shared GPU texture instead of copied into RAM (Windows, hardware
Expand All @@ -53,6 +57,10 @@ export interface NativeSharedFramePacket {
height: number;
footage?: number[] | null;
footageProjective?: boolean;
/** Where the view was when it composed this frame: the active clip in `scene.clips` and the
* screen frame's time in its source file. Absent from an older `.node`. */
clipIndex?: number;
sourceTimeSec?: number;
}

export interface ExportStats {
Expand Down
Loading
Loading