Skip to content
Open
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
39 changes: 25 additions & 14 deletions crates/compositor/src/compositor_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ fn layer_source(models: bool) -> String {
/// en parcourant les 18 wallpapers livres) en laissant le jeu actif resident.
const IMG_CACHE_BUDGET_BYTES: u64 = 512 * 1024 * 1024;

/// Taille du buffer uniforme d'un calque : `LayerCB` entier (176 octets), le `struct Layer` de
/// Taille du buffer uniforme d'un calque : `LayerCB` entier (192 octets), le `struct Layer` de
/// `layer.wgsl`. `blur.wgsl` n'en lit que les 128 premiers.
const LAYER_BYTES: u64 = std::mem::size_of::<LayerCB>() as u64;

/// `&LayerCB` -> ses octets. `LayerCB` est `#[repr(C, align(16))]`, son layout EST le buffer
/// uniforme WGSL (dix vec4 et un vec2 + 2 f32 = 176 octets).
/// uniforme WGSL (douze vec4 = 192 octets).
fn layer_bytes(cb: &LayerCB) -> &[u8] {
unsafe { std::slice::from_raw_parts(cb as *const LayerCB as *const u8, LAYER_BYTES as usize) }
}
Expand Down Expand Up @@ -2485,25 +2485,24 @@ impl Compositor {
let [cu0, cv0, cu1, cv1] = crate::frame_geometry::webcam_source_rect(
[wcw, wch],
[wtw as f32, wth as f32],
scene_ref
.as_ref()
.and_then(|scene| scene.layout.webcam_crop),
if g.webcam.full_frame {
None
} else {
scene_ref.as_ref().and_then(|scene| scene.layout.webcam_crop)
},
g.w_px[0] / g.w_px[1].max(0.0001),
);
// MIROIR : on inverse l'intervalle u. Le VS interpole `src`
// lineairement et `fs_main` ne re-clampe pas `i.uv`, donc un
// intervalle a l'envers suffit -- aucune retouche du WGSL. Apres le
// cover-crop les deux bornes sont strictement a l'interieur de la
// texture, donc le sampler ClampToEdge ne bave pas sur les bords.
let (u0, u1) = if lp.webcam_mirror { (cu1, cu0) } else { (cu0, cu1) };
// Mirror and the desk-shot turn are both bound swaps: u for horizontal, v for vertical.
let (u0, u1) = if g.webcam.flip_u { (cu1, cu0) } else { (cu0, cu1) };
let (v0, v1) = if g.webcam.flip_v { (cv1, cv0) } else { (cv0, cv1) };
// `src_prev` doit valoir EXACTEMENT le `src` de ce draw, miroir
// compris : le shader s'en sert pour reconstruire l'UV de la frame
// precedente, et un rect source qui ne correspond pas au calque
// dessine ferait diverger la trainee vers une zone de la texture qui
// n'a jamais ete affichee. Seul `dst_prev` porte le mouvement.
let cb = LayerCB {
dst: g.w_dst,
src: [u0, cv0, u1, cv1],
src: [u0, v0, u1, v1],
quad_px: g.w_px,
radius_px: g.w_radius,
mode: 0.0,
Expand All @@ -2515,9 +2514,10 @@ impl Compositor {
// `fx.z` = mode, `fx.w` = intensite du flou. Contrat commun aux
// trois back-ends, cf. `layer.wgsl` et `webcam-segmentation.md`.
fx: [w_valid[0], w_valid[1], effect_code, blur_intensity],
src_prev: [u0, cv0, u1, cv1],
src_prev: [u0, v0, u1, v1],
dst_prev: g.w_dst_prev,
mb: [g.mb_taps, g.mb_amount, 1.0, 0.0],
cover: [g.webcam_cover, 0.04 * g.w_px[0].min(g.w_px[1]) * g.webcam_cover, 0.35, 0.0],
..Default::default()
};
// Le masque est lie par `make_bind` sur tous les draws, pas seulement
Expand Down Expand Up @@ -2753,6 +2753,13 @@ impl Compositor {
if text.content.trim().is_empty() {
continue;
}
// The desk label shows only while the camera is covered: skip the rest of
// its section before rasterizing anything.
if crate::text_anim::is_desk_cover(text.animation.as_deref())
&& g.webcam_cover <= 0.0
{
continue;
}
let color = parse_hex(&text.color).unwrap_or([1.0, 1.0, 1.0, 1.0]);
let background =
parse_hex(&text.background_color).unwrap_or([0.0, 0.0, 0.0, 0.0]);
Expand Down Expand Up @@ -2787,10 +2794,11 @@ impl Compositor {
// et remis a l'echelle de la sortie, comme la taille de
// police : en px absolus la meme animation sauterait deux
// fois plus haut dans un rendu 4K que dans l'apercu.
let anim = crate::text_anim::text_animation_state(
let anim = crate::text_anim::annotation_text_state(
text.animation.as_deref(),
(g.source_t - a.start_sec as f32) * 1000.0,
((a.end_sec - a.start_sec) * 1000.0) as f32,
g.webcam_cover,
);
let anim_px = rh / crate::text_anim::ANIMATION_REFERENCE_HEIGHT;
let (mut ax, mut ay, mut aw, mut ah) = (
Expand Down Expand Up @@ -4888,6 +4896,9 @@ mod tests {
clip_index: None,
start_sec: 2.0,
end_sec: 8.0,
rotation: 0,
mirror: None,
full_frame: false,
});
// 5 s : la montee (~1 s) est finie, le retour (~1,5 s) n'a pas commence.
comp.set_timeline_time(Some(5.0));
Expand Down
25 changes: 20 additions & 5 deletions crates/compositor/src/compositor_macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,13 @@ impl Compositor {
if text.content.trim().is_empty() {
continue;
}
// The desk label shows only while the camera is covered: skip the rest of
// its section before rasterizing anything.
if crate::text_anim::is_desk_cover(text.animation.as_deref())
&& g.webcam_cover <= 0.0
{
continue;
}
let spec = crate::text::TextSpec {
content: text.content.clone(),
color: parse_hex(&text.color).unwrap_or([1.0, 1.0, 1.0, 1.0]),
Expand Down Expand Up @@ -1706,10 +1713,11 @@ impl Compositor {
}) else {
continue;
};
let anim = crate::text_anim::text_animation_state(
let anim = crate::text_anim::annotation_text_state(
text.animation.as_deref(),
(t - a.start_sec as f32) * 1000.0,
((a.end_sec - a.start_sec) * 1000.0) as f32,
g.webcam_cover,
);
let anim_px = rh / crate::text_anim::ANIMATION_REFERENCE_HEIGHT;
let (mut ax, mut ay, mut aw, mut ah) = (
Expand Down Expand Up @@ -2663,10 +2671,16 @@ impl Compositor {
let [cu0, cv0, cu1, cv1] = crate::frame_geometry::webcam_source_rect(
[wcw, wch],
[wtw as f32, wth as f32],
scene_ref.as_ref().and_then(|scene| scene.layout.webcam_crop),
if g.webcam.full_frame {
None
} else {
scene_ref.as_ref().and_then(|scene| scene.layout.webcam_crop)
},
g.w_px[0] / g.w_px[1].max(0.0001),
);
let (u0, u1) = if lp.webcam_mirror { (cu1, cu0) } else { (cu0, cu1) };
// Mirror and the desk-shot turn are both bound swaps: u for horizontal, v for vertical.
let (u0, u1) = if g.webcam.flip_u { (cu1, cu0) } else { (cu0, cu1) };
let (v0, v1) = if g.webcam.flip_v { (cv1, cv0) } else { (cv0, cv1) };
let webcam_is_block = matches!(
g.scene_preset.as_deref(),
Some("dual-frame") | Some("vertical-stack")
Expand Down Expand Up @@ -2733,17 +2747,18 @@ impl Compositor {
enc,
&LayerCB {
dst: g.w_dst,
src: [u0, cv0, u1, cv1],
src: [u0, v0, u1, v1],
quad_px: g.w_px,
radius_px: g.w_radius,
mode: 0.0,
// `color.a` porte l'alpha du découpage (`color.a * personne`) ; le RGB n'est
// plus lu, le fond ayant déjà été peint sous la caméra.
color: [0.0, 0.0, 0.0, 1.0],
fx: [w_valid[0], w_valid[1], effect_code, blur_intensity],
src_prev: [u0, cv0, u1, cv1],
src_prev: [u0, v0, u1, v1],
dst_prev: g.w_dst_prev,
mb: [g.mb_taps, g.mb_amount, 1.0, 0.0],
cover: [g.webcam_cover, 0.04 * g.w_px[0].min(g.w_px[1]) * g.webcam_cover, 0.35, 0.0],
..Default::default()
},
wy,
Expand Down
30 changes: 22 additions & 8 deletions crates/compositor/src/compositor_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2438,13 +2438,16 @@ impl Compositor {
let [su0, sv0, su1, sv1] = crate::frame_geometry::webcam_source_rect(
[wcw, wch],
[wtw as f32, wth as f32],
scene_ref
.as_ref()
.and_then(|scene| scene.layout.webcam_crop),
if g.webcam.full_frame {
None
} else {
scene_ref.as_ref().and_then(|scene| scene.layout.webcam_crop)
},
w_px[0] / w_px[1].max(0.0001),
);
// miroir = échanger les bornes u du rect source (flip horizontal).
let (u0, u1) = if lp.webcam_mirror { (su1, su0) } else { (su0, su1) };
// Mirror and the desk-shot turn are both bound swaps: u for horizontal, v for vertical.
let (u0, u1) = if g.webcam.flip_u { (su1, su0) } else { (su0, su1) };
let (v0, v1) = if g.webcam.flip_v { (sv1, sv0) } else { (sv0, sv1) };
if lp.has_webcam {
// L'ombre portée appartient à la bulle flottante PiP : elle se retire avec elle
// (`shape_fade`), pour qu'au plein écran plus rien n'encadre la caméra. C'est une
Expand Down Expand Up @@ -2508,17 +2511,18 @@ impl Compositor {
self.draw_video(
&LayerCB {
dst: w_dst,
src: [u0, sv0, u1, sv1],
src: [u0, v0, u1, v1],
quad_px: w_px,
radius_px: w_radius,
mode: 0.0,
// `color.a` porte l'alpha du découpage (`color.a * personne`) ; le RGB n'est
// plus lu, le fond ayant déjà été peint sous la caméra.
color: [0.0, 0.0, 0.0, 1.0],
fx: [w_valid[0], w_valid[1], effect_code, blur_intensity],
src_prev: [u0, sv0, u1, sv1], // src fixe (pas de zoom webcam)
src_prev: [u0, v0, u1, v1], // src fixe (pas de zoom webcam)
dst_prev: w_dst_prev,
mb: [mb_taps, mb_amount, 1.0, 0.0],
cover: [g.webcam_cover, 0.04 * w_px[0].min(w_px[1]) * g.webcam_cover, 0.35, 0.0],
..Default::default()
},
&wy,
Expand Down Expand Up @@ -2741,6 +2745,13 @@ impl Compositor {
if text.content.trim().is_empty() {
continue;
}
// The desk label shows only while the camera is covered: skip the rest of
// its section before rasterizing anything.
if crate::text_anim::is_desk_cover(text.animation.as_deref())
&& g.webcam_cover <= 0.0
{
continue;
}
// `font_size_rel` est une fraction de la HAUTEUR DE LA BOÎTE D'ANCRAGE — rect
// écran, ou cadre de sortie pour un sous-titre (cf. le contrat et
// `annotationScale.ts`) : on la ramène en pixels de sortie ici, avec le même
Expand Down Expand Up @@ -2786,10 +2797,13 @@ impl Compositor {
// dispose ici) : dans une région accélérée, elle défile donc au rythme du
// clip. À vitesse 1 — le cas de toutes les annotations existantes — c'est
// exactement le timing de l'aperçu DOM.
let anim = crate::text_anim::text_animation_state(
// The desk label is the exception: its opacity is the camera cover, which
// runs on the screen clock (`annotation_text_state`).
let anim = crate::text_anim::annotation_text_state(
text.animation.as_deref(),
(t - annotation.start_sec as f32) * 1000.0,
((annotation.end_sec - annotation.start_sec) * 1000.0) as f32,
g.webcam_cover,
);
// Les décalages sont donnés à la hauteur de référence : on les ramène à la
// sortie, comme la taille de police, pour que l'animation ait la même
Expand Down
Loading
Loading