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
269 changes: 229 additions & 40 deletions crates/compositor/src/compositor_linux.rs

Large diffs are not rendered by default.

68 changes: 49 additions & 19 deletions crates/compositor/src/compositor_macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,23 @@ impl Compositor {
}


/// Fige l'image composée dans `ann_copy` (mip 0), qu'y lisent les flous (mips dérivés,
/// `mips`) et le cristal de Prism Glow.
unsafe fn snapshot_rt(&self, cmd: &metal::CommandBufferRef, mips: bool) {
let blit = cmd.new_blit_command_encoder();
blit.copy_from_texture(
&self.rt, 0, 0,
metal::MTLOrigin { x: 0, y: 0, z: 0 },
metal::MTLSize { width: self.render_w as u64, height: self.render_h as u64, depth: 1 },
&self.ann_copy, 0, 0,
metal::MTLOrigin { x: 0, y: 0, z: 0 },
);
if mips {
blit.generate_mipmaps(&self.ann_copy);
}
blit.end_encoding();
}

/// Annotations : calque le plus haut, ancré sur `s_ann` — le rect écran SANS ZOOM, le
/// conteneur que reçoit l'overlay web. Port de `compositor_windows::draw_annotations`.
///
Expand All @@ -1425,37 +1442,31 @@ impl Compositor {
/// grossir les sous-titres sous un zoom (issue #179, puis #397 sur Linux). L'arithmétique
/// elle-même vit dans `frame_geometry::annotation_dst_in`, partagée par les trois backends.
/// Le flou, lui, se place par `g.privacy_mask` : un masque doit rester sur ce qu'il cache.
///
/// `privacy` : les flous de confidentialité seuls, dessinés sur le métrage avant le curseur ;
/// sinon toutes les autres annotations, le calque le plus haut.
unsafe fn draw_annotations(
&self,
cmd: &metal::CommandBufferRef,
scene: Option<&Scene>,
t: f32,
s_ann: [f32; 4],
g: &crate::frame_geometry::FrameGeometry,
privacy: bool,
) -> Result<()> {
let Some(scene) = scene else { return Ok(()) };
if scene.annotations.is_empty() {
return Ok(());
}
let (rw, rh) = (self.render_w as f32, self.render_h as f32);
let visible = |a: &crate::scene::SceneAnnotation| {
t >= a.start_sec as f32 && t < a.end_sec as f32
t >= a.start_sec as f32 && t < a.end_sec as f32 && (a.kind == "blur") == privacy
};
if !scene.annotations.iter().any(visible) {
return Ok(());
}
// UNE seule recopie pour toutes les annotations flou de la frame : leur lecture doit
// voir l'image composée SANS les flous eux-mêmes, sinon deux zones qui se recouvrent
// s'échantillonneraient l'une l'autre selon l'ordre de dessin.
if scene.annotations.iter().any(|a| a.kind == "blur" && visible(a)) {
let blit = cmd.new_blit_command_encoder();
blit.copy_from_texture(
&self.rt, 0, 0,
metal::MTLOrigin { x: 0, y: 0, z: 0 },
metal::MTLSize { width: rw as u64, height: rh as u64, depth: 1 },
&self.ann_copy, 0, 0,
metal::MTLOrigin { x: 0, y: 0, z: 0 },
);
// Seul le mip 0 est rempli ; le GPU dérive le reste.
blit.generate_mipmaps(&self.ann_copy);
blit.end_encoding();
if privacy {
self.snapshot_rt(cmd, true);
}

let enc = self.begin_pass(cmd, &self.rt, None, &self.pipeline_main)?;
Expand Down Expand Up @@ -2066,7 +2077,8 @@ impl Compositor {
/// `frame_geometry::cursor_sprite_cb`, partagée avec Windows et Linux.
///
/// Avec `model`, le même sprite extrudé (mode 15, `cursor_model_cb`) : le sprite en
/// texture(2), son champ de distance en texture(4).
/// texture(2), son champ de distance en texture(4), l'enregistrement que l'appelant a lié en
/// texture(0) et (1).
#[allow(clippy::too_many_arguments)]
unsafe fn draw_cursor_sprite(
&self,
Expand Down Expand Up @@ -2400,6 +2412,11 @@ impl Compositor {

enc.end_encoding();

// --- flous de confidentialité : sur le métrage, AVANT le curseur (parité Windows). Le
// curseur reste net par-dessus, et le cristal de Prism Glow, qui réfracte l'image
// composée, n'y voit que des pixels déjà floutés.
self.draw_annotations(cmd_buf, scene_ref.as_ref(), g.source_t, g.s_ann, &g, true)?;

// --- curseur --- (parité `compositor_windows.rs`, section « curseur custom »)
if let Some(track) = cursor_ref.as_ref() {
let plan = crate::frame_geometry::plan_cursor(
Expand All @@ -2422,6 +2439,12 @@ impl Compositor {
.map(|s| s.cursor.cursor_sprites.clone())
.unwrap_or_default();
let kind = plan.cursor_type.as_deref();
// Le cristal de Prism Glow réfracte l'image telle qu'elle est composée à cet
// instant, flous compris : sa copie en texture(5), une fois pour toutes les copies
// de la traînée (`ann_copy` est libre, les flous l'ont déjà lue).
if plan.glass {
self.snapshot_rt(cmd_buf, false);
}
// L'impact des clics (mode 16, sans texture), posé sur l'écran SOUS le curseur.
if !plan.impacts.is_empty() {
let e = self.begin_pass(cmd_buf, &self.rt, None, &self.pipeline_main)?;
Expand All @@ -2432,6 +2455,9 @@ impl Compositor {
}
if plan.taps <= 1 {
let e = self.begin_pass(cmd_buf, &self.rt, None, &self.pipeline_main)?;
if plan.glass {
e.set_fragment_texture(5, Some(&self.ann_copy));
}
self.draw_cur_themed(
e,
&sprites,
Expand All @@ -2454,6 +2480,9 @@ impl Compositor {
Some(metal::MTLClearColor::new(0.0, 0.0, 0.0, 0.0)),
&self.pipeline_add,
)?;
if plan.glass {
e.set_fragment_texture(5, Some(&self.ann_copy));
}
for k in 0..plan.taps {
let f = k as f32 / (plan.taps - 1) as f32;
let w = crate::frame_geometry::cursor_tap_weight(k, plan.taps);
Expand Down Expand Up @@ -2578,8 +2607,9 @@ impl Compositor {
// --- annotations : calque le plus haut, ancré sur le rect ÉCRAN SANS ZOOM ---
// `s_ann`, pas `s_dst` : le zoom vit dans la boîte depuis l'issue #179, donc `s_dst`
// grandit avec lui et emmenait annotations et sous-titres dans le mouvement. Le flou de
// confidentialité est l'exception : il suit le contenu, d'où `&g`.
self.draw_annotations(cmd_buf, scene_ref.as_ref(), g.source_t, g.s_ann, &g)?;
// confidentialité est l'exception : il suit le contenu, d'où `&g` ; il est passé avant le
// curseur (plus haut).
self.draw_annotations(cmd_buf, scene_ref.as_ref(), g.source_t, g.s_ann, &g, false)?;

// Ni miroir RGBA ni attente ici : le miroir ne sert qu'à `readback_direct` (la
// preview), et l'export ne lit jamais le RGBA — le blit pleine résolution était payé
Expand Down
28 changes: 24 additions & 4 deletions crates/compositor/src/compositor_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2138,6 +2138,11 @@ impl Compositor {
self.ctx.PSSetShaderResources(5, Some(&[None]));
}

// --- flous de confidentialité : sur le métrage, AVANT le curseur. Le curseur reste net
// par-dessus, et le cristal de Prism Glow, qui réfracte l'image composée, n'y voit que des
// pixels déjà floutés. Les autres annotations restent le calque le plus haut (plus bas).
self.draw_annotations(scene_ref.as_ref(), source_t, s_ann, &g, true);

// --- curseur custom : suit le mapping src/dst (zoom+layout), click bounce,
// et flou de mouvement (parité `compositor_macos.rs` et `compositor_linux.rs`) ---
if let Some(track) = cursor_ref.as_ref() {
Expand All @@ -2161,6 +2166,13 @@ impl Compositor {
.map(|s| s.cursor.cursor_sprites.clone())
.unwrap_or_default();
let cursor_type = plan.cursor_type.as_deref();
// Le cristal de Prism Glow réfracte l'image telle qu'elle est composée à cet
// instant, flous compris : sa copie en t5, une fois pour toutes les copies de la
// traînée (`ann_copy` est libre, les flous l'ont déjà lue).
if plan.glass {
self.ctx.CopySubresourceRegion(&self.ann_copy, 0, 0, 0, 0, &self.rt, 0, None);
self.ctx.PSSetShaderResources(5, Some(&[Some(self.ann_copy_srv.clone())]));
}
// L'impact des clics (mode 16, sans texture), posé sur l'écran SOUS le curseur.
for cb in &plan.impacts {
self.draw_solid(cb);
Expand Down Expand Up @@ -2216,6 +2228,9 @@ impl Compositor {
// le dessin de la webcam qui suit juste après.
self.bind_compose_state();
}
if plan.glass {
self.ctx.PSSetShaderResources(5, Some(&[None]));
}
}
}

Expand Down Expand Up @@ -2334,12 +2349,13 @@ impl Compositor {
// la transform, donc les annotations restent en place pendant que le contenu zoome dessous.
// Ce fut `s_dst` tant que le zoom vivait dans la coupe source ; depuis l'issue #179 il vit
// dans la BOÎTE, et `s_dst` emmenait annotations et sous-titres avec lui.
// Exception : le flou de confidentialité suit le contenu (`FrameGeometry::privacy_mask`),
// d'où la géométrie entière passée en plus de `s_ann`.
// Les flous de confidentialité, eux, sont passés avant le curseur (plus haut) : ils suivent
// le contenu (`FrameGeometry::privacy_mask`), d'où la géométrie entière passée en plus de
// `s_ann`.
// `source_t`, la même base de temps que les zoom/speed regions : le temps SOURCE du clip,
// pas le compteur de frames. C'est ce qui garde une annotation alignée sur l'image quand
// une speed region répète ou saute des frames.
self.draw_annotations(scene_ref.as_ref(), source_t, s_ann, &g);
self.draw_annotations(scene_ref.as_ref(), source_t, s_ann, &g, false);
Ok(())
}

Expand All @@ -2351,19 +2367,23 @@ impl Compositor {
/// sous-titres sous un zoom (issue #179, puis #397 sur Linux). L'arithmétique elle-même vit
/// dans `frame_geometry::annotation_dst_in`, partagée par les trois backends. Le flou, lui,
/// se place par `g.privacy_mask` : un masque doit rester sur ce qu'il cache.
///
/// `privacy` : les flous de confidentialité seuls, dessinés sur le métrage avant le curseur ;
/// sinon toutes les autres annotations, le calque le plus haut.
unsafe fn draw_annotations(
&self,
scene: Option<&Scene>,
t: f32,
s_ann: [f32; 4],
g: &crate::frame_geometry::FrameGeometry,
privacy: bool,
) {
let Some(scene) = scene else { return };
if scene.annotations.is_empty() {
return;
}
let visible = |a: &crate::scene::SceneAnnotation| {
t >= a.start_sec as f32 && t < a.end_sec as f32
t >= a.start_sec as f32 && t < a.end_sec as f32 && (a.kind == "blur") == privacy
};
// Une seule recopie du render target pour TOUTES les annotations flou de la frame — leur
// lecture doit voir l'image composée sans les flous eux-mêmes, sinon deux zones qui se
Expand Down
15 changes: 12 additions & 3 deletions crates/compositor/src/frame_geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3426,6 +3426,10 @@ pub struct CursorPlan {
/// L'impact des clics récents sur l'écran (mode 16), à dessiner SOUS le curseur. Vide sans
/// curseur modélisé, sans clic récent ou à `clickBounce` nul.
pub impacts: Vec<LayerCB>,
/// Le curseur modélisé est le cristal de Prism Glow : il réfracte l'image déjà composée, le
/// métrage et ses flous de confidentialité, que le backend lui copie juste avant de le
/// dessiner. Seuls des pixels floutés passent ainsi à travers le verre.
pub glass: bool,
}

impl CursorPlan {
Expand Down Expand Up @@ -3727,6 +3731,7 @@ pub fn plan_cursor(g: &FrameGeometry, input: &CursorPlanInput) -> Option<CursorP
cursor_pose(input.track, input.t, lp.cursor_bounce_scale, pointing)
}),
impacts,
glass: model_sprite.and_then(|s| s.sculpt.as_deref()).is_some_and(crate::sculpt::refracts),
})
}

Expand Down Expand Up @@ -3763,9 +3768,13 @@ pub fn plan_cursor(g: &FrameGeometry, input: &CursorPlanInput) -> Option<CursorP
// radius_px rapport w/h du sprite : sa taille (unités) en découle, plus grand côté = 1
// trail_a le curseur sculpté (0 = le sprite extrudé), l'épaisseur sous z = 0 avant
// écrasement, la hauteur au-dessus de z = 0 (`SpriteShape`), 0
// Textures : le sprite RGBA (alpha droit) et son champ R16F (`cursor_sdf`), sur le même rect.
// Windows et macOS : sprite en t2/texture(2) (`texImg`), champ en t4/texture(4) (`texSdf`).
// Linux : sprite au binding 1 (`texY`), champ au binding 2 (`texU`).
// Textures : le sprite RGBA (alpha droit) et son champ R16F (`cursor_sdf`), sur le même rect ;
// pour le cristal de Prism Glow (`CursorPlan::glass`), une copie de l'image composée, flous de
// confidentialité compris, qu'il réfracte.
// Windows et macOS : sprite en t2/texture(2) (`texImg`), champ en t4/texture(4) (`texSdf`),
// copie en t5/texture(5) (`texDof`).
// Linux : sprite au binding 6 (`texDof`), champ au binding 4 (`texMask`), copie au binding 1
// (`texY`).

/// Épaisseur du sprite extrudé : son dessus est en z = 0, son dessous en z = -épaisseur.
pub const MODEL_THICK: f32 = 0.19;
Expand Down
1 change: 1 addition & 0 deletions crates/compositor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod regions;
// cibles) et le shim C. Seul Linux l'appelle aujourd'hui, parce que c'est la
// seule plateforme dont la capture passe par `MediaRecorder`, mais rien dedans
// n'est spécifique à Linux.
pub mod prism_mesh;
pub mod remux;
pub mod scene;
pub mod sculpt;
Expand Down
Loading
Loading