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
2 changes: 1 addition & 1 deletion crates/compositor-view-napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ pub fn read_frame(id: i32, since_gen: f64) -> Result<Option<FramePacket>> {
}

/// Param live (inspector). Le type de valeur route vers le bon setter :
/// bool = switch (backgroundBlur…), number = slider (shadow/roundness/motionBlur),
/// bool = switch (webcamMirror…), number = slider (shadow/roundness/motionBlur/backgroundBlur),
/// string = sélection (backgroundColor "#rrggbb").
#[napi]
pub fn set_param(id: i32, key: String, value: Either3<bool, f64, String>) {
Expand Down
70 changes: 36 additions & 34 deletions crates/compositor/src/compositor_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -993,13 +993,14 @@ impl Compositor {
src: &wgpu::TextureView,
dst: &wgpu::TextureView,
src_px: [f32; 2],
off: f32,
) {
let cb = LayerCB {
mode: -1.0,
color: [1.0, 1.0, 1.0, 1.0],
// Convention HLSL/Metal (`compositor_windows::blur_bg`) : texel de la
// SOURCE en .xy, offset 2.2 en .z. Parite des trois backends.
fx: [1.0 / src_px[0], 1.0 / src_px[1], 2.2, 0.0],
// SOURCE en .xy, offset en .z. Parite des trois backends.
fx: [1.0 / src_px[0], 1.0 / src_px[1], off, 0.0],
..Default::default()
};
let uniform = self.gpu.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
Expand Down Expand Up @@ -1044,28 +1045,30 @@ impl Compositor {
rpass.draw(0..3, 0..1);
}

/// Floute le RT (le fond deja dessine) : dual-Kawase 3 down (RT -> 1/2 ->
/// 1/4 -> 1/8) + 3 up (1/8 -> 1/4 -> 1/2 -> RT). ~gaussien a cout constant.
fn blur_bg(&self, encoder: &mut wgpu::CommandEncoder) {
let (rw, rh) = (self.render_w as f32, self.render_h as f32);
let (hw, hh) = (
(self.render_w / 2).max(1) as f32,
(self.render_h / 2).max(1) as f32,
);
let (qw, qh) = (
(self.render_w / 4).max(1) as f32,
(self.render_h / 4).max(1) as f32,
);
let (ow, oh) = (
(self.render_w / 8).max(1) as f32,
(self.render_h / 8).max(1) as f32,
);
self.blur_pass(encoder, &self.blur_down, &self.rt_view, &self.blur_half, [rw, rh]);
self.blur_pass(encoder, &self.blur_down, &self.blur_half, &self.blur_qtr, [hw, hh]);
self.blur_pass(encoder, &self.blur_down, &self.blur_qtr, &self.blur_oct, [qw, qh]);
self.blur_pass(encoder, &self.blur_up, &self.blur_oct, &self.blur_qtr, [ow, oh]);
self.blur_pass(encoder, &self.blur_up, &self.blur_qtr, &self.blur_half, [qw, qh]);
self.blur_pass(encoder, &self.blur_up, &self.blur_half, &self.rt_view, [hw, hh]);
/// Floute le RT (le fond deja dessine) : dual-Kawase, `levels` down (RT -> 1/2 ->
/// 1/4 -> 1/8) puis autant d'up, niveaux et ecart tires de la force
/// (`background_blur_steps`, partage avec D3D/Metal). ~gaussien a cout borne.
fn blur_bg(&self, encoder: &mut wgpu::CommandEncoder, amount: f32) {
let Some((levels, off)) = crate::frame_geometry::background_blur_steps(amount) else {
return;
};
let dims = |d: u32| {
[(self.render_w / d).max(1) as f32, (self.render_h / d).max(1) as f32]
};
let pyramid = [
(&self.rt_view, dims(1)),
(&self.blur_half, dims(2)),
(&self.blur_qtr, dims(4)),
(&self.blur_oct, dims(8)),
];
for i in 0..levels {
let (src, src_px) = pyramid[i];
self.blur_pass(encoder, &self.blur_down, src, pyramid[i + 1].0, src_px, off);
}
for i in (0..levels).rev() {
let (src, src_px) = pyramid[i + 1];
self.blur_pass(encoder, &self.blur_up, src, pyramid[i].0, src_px, off);
}
}

/// Dimensions paires (NV12 4:2:0), min 2x2. Symetrie avec les autres backends.
Expand Down Expand Up @@ -3054,9 +3057,7 @@ impl Compositor {
}
}
// Blur du fond (avant l'ecran), si active par la scene/l'inspector.
if cfg.bg_blur {
self.blur_bg(&mut encoder);
}
self.blur_bg(&mut encoder, cfg.bg_blur);
// Passe 2 : avant-plan (ecran + webcam), compose par-dessus le fond
// (eventuellement floute) avec `LoadOp::Load`. Les annotations sont dans
// une passe a part, cf. plus bas.
Expand Down Expand Up @@ -4170,8 +4171,9 @@ mod tests {
let mut encoder = gpu
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("test-blur") });
comp.blur_pass(&mut encoder, &comp.pipeline_copy, &src_view, &comp.rt_view, [w as f32, h as f32]);
comp.blur_bg(&mut encoder);
comp.blur_pass(&mut encoder, &comp.pipeline_copy, &src_view, &comp.rt_view, [w as f32, h as f32], 0.0);
// 0,5 = l'ancien interrupteur : trois niveaux, ecart 2,2 (`HP` ci-dessous).
comp.blur_bg(&mut encoder, 0.5);
gpu.context.submit(std::iter::once(encoder.finish()));
let (_, _, px) = unsafe { comp.readback_direct().expect("readback_direct") };

Expand Down Expand Up @@ -4549,7 +4551,7 @@ mod tests {
let screen = FakeFrame::new(gpu, 128, 128, |_, _| 126);
let webcam = FakeFrame::new(gpu, 64, 64, |_, _| Y_WHITE);
let mut cfg = Cfg::c8();
cfg.bg_blur = false;
cfg.bg_blur = 0.0;
cfg.zoom = false;
cfg.layout_anim = false;
cfg.cursor = false;
Expand Down Expand Up @@ -4895,7 +4897,7 @@ mod tests {
let screen = FakeFrame::new(gpu, 640, 360, |_, _| 60);
let webcam = FakeFrame::new(gpu, 64, 64, |_, _| 60);
let mut cfg = Cfg::c8();
cfg.bg_blur = false;
cfg.bg_blur = 0.0;
cfg.zoom = false;
cfg.layout_anim = false;
cfg.cursor = false;
Expand Down Expand Up @@ -4934,7 +4936,7 @@ mod tests {
let screen = FakeFrame::new(gpu, 640, 360, |_, _| 60);
let webcam = FakeFrame::new(gpu, 64, 64, |_, _| 60);
let mut cfg = Cfg::c8();
cfg.bg_blur = false;
cfg.bg_blur = 0.0;
cfg.cursor = false;
cfg.mblur_n = 1;
cfg.shadow = true;
Expand Down Expand Up @@ -5026,7 +5028,7 @@ mod tests {
let screen = FakeFrame::new(&gpu, 640, 360, |_, _| 60);
let webcam = FakeFrame::new(&gpu, 64, 64, |_, _| 60);
let mut cfg = Cfg::c8();
cfg.bg_blur = false;
cfg.bg_blur = 0.0;
cfg.zoom = false;
cfg.layout_anim = false;
cfg.cursor = false;
Expand Down Expand Up @@ -5236,7 +5238,7 @@ mod tests {

fn model_cfg() -> crate::config::Cfg {
let mut cfg = crate::config::Cfg::c8();
cfg.bg_blur = false;
cfg.bg_blur = 0.0;
cfg.zoom = false;
cfg.layout_anim = false;
cfg.cursor = true;
Expand Down
46 changes: 28 additions & 18 deletions crates/compositor/src/compositor_macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1228,21 +1228,31 @@ impl Compositor {
Ok(())
}

/// Dual-Kawase sur le contenu courant du RT : trois passes DOWN puis trois UP, la
/// dernière réécrivant le RT. Port des six `fs_pass` de `compositor_windows::blur_bg`,
/// mêmes tailles et mêmes texels.
unsafe fn blur_bg(&self, cmd: &metal::CommandBufferRef) -> Result<()> {
let off = 2.2; // spread par passe
/// Dual-Kawase sur le contenu courant du RT : `levels` passes DOWN puis autant d'UP, la
/// dernière réécrivant le RT. Port de `compositor_windows::blur_bg`, mêmes tailles, mêmes
/// texels, même force → niveaux (`background_blur_steps`).
unsafe fn blur_bg(&self, cmd: &metal::CommandBufferRef, amount: f32) -> Result<()> {
let Some((levels, off)) = crate::frame_geometry::background_blur_steps(amount) else {
return Ok(());
};
let (rw, rh) = (self.render_w as f32, self.render_h as f32);
let (hw, hh) = (rw * 0.5, rh * 0.5);
// DOWN : texel = 1/(dims de la SOURCE échantillonnée)
self.fs_pass(cmd, &self.blur_half, &self.rt, &self.pipeline_kdown, [1.0 / rw, 1.0 / rh, off, 0.0])?;
self.fs_pass(cmd, &self.blur_quarter, &self.blur_half, &self.pipeline_kdown, [1.0 / hw, 1.0 / hh, off, 0.0])?;
self.fs_pass(cmd, &self.blur_eighth, &self.blur_quarter, &self.pipeline_kdown, [2.0 / hw, 2.0 / hh, off, 0.0])?;
// UP
self.fs_pass(cmd, &self.blur_quarter, &self.blur_eighth, &self.pipeline_kup, [4.0 / hw, 4.0 / hh, off, 0.0])?;
self.fs_pass(cmd, &self.blur_half, &self.blur_quarter, &self.pipeline_kup, [2.0 / hw, 2.0 / hh, off, 0.0])?;
self.fs_pass(cmd, &self.rt, &self.blur_half, &self.pipeline_kup, [1.0 / hw, 1.0 / hh, off, 0.0])?;
let pyramid = [
(&self.rt, rw, rh),
(&self.blur_half, rw * 0.5, rh * 0.5),
(&self.blur_quarter, rw * 0.25, rh * 0.25),
(&self.blur_eighth, rw * 0.125, rh * 0.125),
];
// texel = 1/(dims de la SOURCE échantillonnée)
for i in 0..levels {
let (src, sw, sh) = pyramid[i];
let (dst, ..) = pyramid[i + 1];
self.fs_pass(cmd, dst, src, &self.pipeline_kdown, [1.0 / sw, 1.0 / sh, off, 0.0])?;
}
for i in (0..levels).rev() {
let (src, sw, sh) = pyramid[i + 1];
let (dst, ..) = pyramid[i];
self.fs_pass(cmd, dst, src, &self.pipeline_kup, [1.0 / sw, 1.0 / sh, off, 0.0])?;
}
Ok(())
}

Expand Down Expand Up @@ -2278,8 +2288,8 @@ impl Compositor {
// de dessiner, pas la vidéo. No-op visuel sur une couleur plate, effet réel sur un
// gradient ou une image. Il lui faut ses propres passes, d'où la coupure ici.
enc.end_encoding();
if scene_ref.as_ref().map(|s| s.effects.blur).unwrap_or(false) {
self.blur_bg(cmd_buf)?;
if let Some(blur) = scene_ref.as_ref().map(|s| s.effects.blur) {
self.blur_bg(cmd_buf, blur)?;
}
// --- écran : ombre puis vidéo ---
let s_px = [g.s_dst[2] * rw, g.s_dst[3] * rh];
Expand Down Expand Up @@ -3242,7 +3252,7 @@ mod tests {
let screen = FakeFrame::new(128, 128, |_, _| 126);
let webcam = FakeFrame::new(64, 64, |_, _| Y_WHITE);
let mut cfg = crate::config::Cfg::c8();
cfg.bg_blur = false;
cfg.bg_blur = 0.0;
cfg.zoom = false;
cfg.layout_anim = false;
cfg.cursor = false;
Expand Down Expand Up @@ -3863,7 +3873,7 @@ mod tests {
comp.set_cursor_time(Some(2.0));
comp.set_timeline_time(Some(2.0));
let mut cfg = crate::config::Cfg::c8();
cfg.bg_blur = false;
cfg.bg_blur = 0.0;
cfg.zoom = false;
cfg.layout_anim = false;
cfg.cursor = true;
Expand Down
53 changes: 29 additions & 24 deletions crates/compositor/src/compositor_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -900,32 +900,37 @@ impl Compositor {
}

/// Fond flouté (§7), dual-Kawase : suppose le screen déjà dessiné plein écran dans le RT.
/// Chaîne down (RT→960→480→240) puis up (240→480→960→RT). ~6 passes de 5-8 taps
/// à résolution décroissante, vs 2 passes gaussiennes 49-tap. Le RT devient le fond.
pub unsafe fn blur_bg(&self, _sigma: f32) {
let off = 2.2; // spread par passe
/// Chaîne down (RT→960→480→240) puis up (240→480→960→RT), tronquée au nombre de niveaux
/// que demande la force (`background_blur_steps`). ~5-8 taps par passe à résolution
/// décroissante, vs 2 passes gaussiennes 49-tap. Le RT devient le fond.
pub unsafe fn blur_bg(&self, amount: f32) {
let Some((levels, off)) = crate::frame_geometry::background_blur_steps(amount) else {
return;
};
// La pyramide se dérive de la taille de rendu, pas d'une constante : sinon
// le rayon effectif du flou changerait avec la résolution de sortie (un
// demi de 1080 n'est pas un demi de 2160), et le fond flouté ne serait plus
// le même effet d'un format à l'autre.
let (rw_i, rh_i) = self.render_dims();
let (half_w, half_h) = (rw_i / 2, rh_i / 2);
let hw = half_w as f32;
let hh = half_h as f32;
// DOWN : texel = 1/(dims de la SOURCE échantillonnée)
self.fs_pass(&self.half_a_rtv, &self.rt_srv, &self.ps_kdown, half_w, half_h,
[1.0 / self.rw(), 1.0 / self.rh(), off, 0.0]);
self.fs_pass(&self.q_rtv, &self.half_a_srv, &self.ps_kdown, half_w / 2, half_h / 2,
[1.0 / hw, 1.0 / hh, off, 0.0]);
self.fs_pass(&self.e_rtv, &self.q_srv, &self.ps_kdown, half_w / 4, half_h / 4,
[2.0 / hw, 2.0 / hh, off, 0.0]);
// UP
self.fs_pass(&self.q_rtv, &self.e_srv, &self.ps_kup, half_w / 2, half_h / 2,
[4.0 / hw, 4.0 / hh, off, 0.0]);
self.fs_pass(&self.half_a_rtv, &self.q_srv, &self.ps_kup, half_w, half_h,
[2.0 / hw, 2.0 / hh, off, 0.0]);
self.fs_pass(&self.rtv, &self.half_a_srv, &self.ps_kup, rw_i, rh_i,
[1.0 / hw, 1.0 / hh, off, 0.0]);
let pyramid = [
(&self.rtv, &self.rt_srv, rw_i, rh_i),
(&self.half_a_rtv, &self.half_a_srv, half_w, half_h),
(&self.q_rtv, &self.q_srv, half_w / 2, half_h / 2),
(&self.e_rtv, &self.e_srv, half_w / 4, half_h / 4),
];
// texel = 1/(dims de la SOURCE échantillonnée)
let fx = |(w, h): (u32, u32)| [1.0 / w.max(1) as f32, 1.0 / h.max(1) as f32, off, 0.0];
for i in 0..levels {
let (_, src, sw, sh) = pyramid[i];
let (dst, _, dw, dh) = pyramid[i + 1];
self.fs_pass(dst, src, &self.ps_kdown, dw, dh, fx((sw, sh)));
}
for i in (0..levels).rev() {
let (_, src, sw, sh) = pyramid[i + 1];
let (dst, _, dw, dh) = pyramid[i];
self.fs_pass(dst, src, &self.ps_kup, dw, dh, fx((sw, sh)));
}
}

/// Remplit la pyramide de profondeur de champ depuis la frame écran et rend sa SRV.
Expand Down Expand Up @@ -1966,11 +1971,11 @@ impl Compositor {
// « Blur BG » (parité web blurredBackgroundLayer) : floute CE wallpaper qu'on vient
// de dessiner (dual-Kawase, déjà utilisé pour le fond fixture ci-dessous). No-op
// visuel sur une couleur plate, effet réel sur gradient/image.
if blur_wallpaper {
self.blur_bg(18.0);
if blur_wallpaper > 0.0 {
self.blur_bg(blur_wallpaper);
self.bind_compose_state();
}
} else if cfg.bg_blur {
} else if cfg.bg_blur > 0.0 {
let over = 0.06;
self.draw_video(
&LayerCB {
Expand All @@ -1984,7 +1989,7 @@ impl Compositor {
&sy,
&suv,
);
self.blur_bg(18.0);
self.blur_bg(cfg.bg_blur);
self.bind_compose_state();
self.draw_solid(&LayerCB {
dst: [0.0, 0.0, 1.0, 1.0],
Expand Down
6 changes: 3 additions & 3 deletions crates/compositor/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub struct Cfg {
pub composite: bool, // C1+ : composite 2 sources (sinon décode+encode seul = C0)
pub rounded: bool, // C2+ : coins arrondis (SDF)
pub shadow: bool, // C3+ : ombres portées
pub bg_blur: bool, // C4+ : fond flouté (gaussien séparable)
pub bg_blur: f32, // C4+ : fond flouté, force 0..1 (0 = off)
pub zoom: bool, // C5+ : zoom animé
pub layout_anim: bool, // C6+ : animation de layout A<->B
pub cursor: bool, // C7+ : curseur custom + click bounce
Expand Down Expand Up @@ -40,7 +40,7 @@ pub fn all() -> Vec<Cfg> {
composite: false,
rounded: false,
shadow: false,
bg_blur: false,
bg_blur: 0.0,
zoom: false,
layout_anim: false,
cursor: false,
Expand All @@ -50,7 +50,7 @@ pub fn all() -> Vec<Cfg> {
let c1 = Cfg { name: "C1", composite: true, desc: "+ fond, layout, 2 sources (E1)", ..base.clone() };
let c2 = Cfg { name: "C2", rounded: true, desc: "+ coins arrondis (E2)", ..c1.clone() };
let c3 = Cfg { name: "C3", shadow: true, desc: "+ ombres portées (E4)", ..c2.clone() };
let c4 = Cfg { name: "C4", bg_blur: true, desc: "+ fond flouté (E3)", ..c3.clone() };
let c4 = Cfg { name: "C4", bg_blur: 0.5, desc: "+ fond flouté (E3)", ..c3.clone() };
let c5 = Cfg { name: "C5", zoom: true, desc: "+ zoom animé", ..c4.clone() };
let c6 = Cfg { name: "C6", layout_anim: true, desc: "+ animation de layout", ..c5.clone() };
let c7 = Cfg { name: "C7", cursor: true, desc: "+ curseur custom (bounce)", ..c6.clone() };
Expand Down
38 changes: 38 additions & 0 deletions crates/compositor/src/frame_geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,26 @@ pub fn dof_pyramid_levels(w: u32, h: u32) -> u32 {
const DOF_PYRAMID_LEVELS: u32 = 5;
DOF_PYRAMID_LEVELS.min(32 - w.max(h).max(1).leading_zeros())
}
/// Flou du fond : force 0..1 → (niveaux de la pyramide dual-Kawase, écart par passe), `None`
/// à 0. Le rayon d'un dual-Kawase croît comme `2^niveaux × écart` : on vise un rayon
/// proportionnel à la force et on prend le moins de niveaux qui l'atteint sans écarter les taps
/// au point de dédoubler l'image. 0,5 retombe exactement sur l'ancien interrupteur « Blur BG »
/// (3 niveaux, écart 2,2). Les trois backends lisent ceci, ils floutent donc pareil.
pub fn background_blur_steps(amount: f32) -> Option<(usize, f32)> {
const RADIUS_AT_FULL: f32 = 2.0 * 8.0 * 2.2;
if !(amount > 0.001) {
return None;
}
let radius = amount.min(1.0) * RADIUS_AT_FULL;
let levels: usize = if radius <= 4.0 {
1
} else if radius <= 12.0 {
2
} else {
3
};
Some((levels, radius / (1 << levels) as f32))
}
/// Longueurs de style exprimées en FRACTION du petit côté du cadre, et non en pixels.
///
/// Elles étaient écrites en px bruts au point d'appel, ce qui voulait dire « px du render
Expand Down Expand Up @@ -4252,6 +4272,24 @@ pub fn lru_evictions(entries: &[(String, u64, u64)], budget: u64, protect_from:
mod tests {
use super::lru_evictions;

#[test]
fn background_blur_steps_off_at_zero_and_old_switch_at_half() {
use super::background_blur_steps;
assert_eq!(background_blur_steps(0.0), None);
let (levels, off) = background_blur_steps(0.5).unwrap();
assert_eq!(levels, 3);
assert!((off - 2.2).abs() < 1e-5);
// Le rayon (2^niveaux × écart) croît avec la force, sans saut en arrière d'un niveau à
// l'autre.
let radius = |a: f32| background_blur_steps(a).map_or(0.0, |(l, o)| (1 << l) as f32 * o);
let mut prev = 0.0;
for i in 1..=100 {
let r = radius(i as f32 / 100.0);
assert!(r > prev, "rayon non croissant à {i}%");
prev = r;
}
}

/// `(clé, octets, tick)` — le tick croît avec l'usage, donc le plus petit est le plus ancien.
fn e(key: &str, mb: u64, tick: u64) -> (String, u64, u64) {
(key.to_string(), mb * 1024 * 1024, tick)
Expand Down
Loading
Loading