Skip to content

Commit 268363b

Browse files
committed
test(compositor): pin the iso-render claim to a number
"ISO with D3D" was an assertion. This makes the part of it that CAN be measured into a failing test, and says plainly what the rest is worth. The two backends cannot run on the same machine, so iso cannot be established by comparing two rendered images. What can be — and what is the layer where divergence actually happened on this branch — is the geometry: `plan_frame` is literally the same code on both sides, so its 15 outputs must be bit-identical. `plan_frame_is_pinned_bit_for_bit` pins them for a scene that exercises padding, crop, a displaced PiP camera, corner radii and the zoom, comparing with `to_bits()` rather than an epsilon. It runs in BOTH CI jobs — `rust-macos-compositor-check` and `rust-windows-compositor-check`. If the two platforms ever compute different placements, one of them goes red. That is the guarantee that was being claimed and never held: `webcam_shape_code` returned "rounded" on Windows and "rectangle" on macOS for months, and `LiveParams::default()` disagreed on two more fields, precisely because nothing compared them. What this test does NOT cover, and must not be read as covering: rasterisation. D3D11 and Metal will never be bit-identical — PR #162 measured 93-95 % of channels identical with a max deviation of 3/255 between two backends on the SAME machine, and that is the floor, not the target. Shader parity is held separately, by `shaders.metal` and `shaders.hlsl` having been diffed line by line across all 14 modes; nine divergences came out of that pass, three of them live. The expected values are measured from this code, not chosen. A drift is a divergence to explain, not a tolerance to loosen.
1 parent 77cbe2f commit 268363b

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

crates/compositor/src/frame_geometry.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,94 @@ pub fn plan_cursor(g: &FrameGeometry, input: &CursorPlanInput) -> Option<CursorP
12121212
mod tests {
12131213
use super::*;
12141214

1215+
/// La scène de référence du golden : un cas qui exerce le padding, le crop, le zoom,
1216+
/// une caméra PiP décalée, un rayon et une inclinaison nulle.
1217+
fn golden_scene() -> Scene {
1218+
Scene::from_json(
1219+
r##"{
1220+
"clips":[{"screenPath":"/s.mp4","webcamPath":"/w.mp4","sourceStartSec":0,"sourceEndSec":10,"webcamOffsetSec":0,"hasAudio":true}],
1221+
"layout":{"preset":"picture-in-picture","webcamSize":0.44,"webcamShape":"circle","webcamMirror":false,
1222+
"webcamPosition":{"cx":0.8577,"cy":0.8159},"webcamReactiveZoom":false},
1223+
"effects":{"padding":0.51,"blur":false,"shadow":0.35,"roundnessFrac":0.0255,"motionBlur":0.35},
1224+
"background":{"kind":"color","color":"#1e1e2e"},
1225+
"zoomRegions":[],
1226+
"cursor":{"show":true,"size":7.76,"smoothing":0,"motionBlur":0.35,"clickBounce":1,"clipToBounds":false,"theme":"default"},
1227+
"cropByClip":[{"x":0,"y":0,"width":0.61,"height":0.61}],
1228+
"output":{"width":1170,"height":658,"fps":60}
1229+
}"##,
1230+
)
1231+
.expect("golden scene")
1232+
}
1233+
1234+
fn golden_input(scene: &Scene, cfg: &Cfg) -> FrameGeometryInput<'static> {
1235+
// SAFETY-free: on fuit volontairement les deux références pour obtenir un
1236+
// `'static` dans le test — la scène et le cfg vivent jusqu'à la fin du process.
1237+
let scene: &'static Scene = Box::leak(Box::new(scene.clone()));
1238+
let cfg: &'static Cfg = Box::leak(Box::new(cfg.clone()));
1239+
FrameGeometryInput {
1240+
render_px: [1170.0, 658.0],
1241+
screen_tex_px: [1920.0, 1088.0],
1242+
screen_visible_px: [1920.0, 1080.0],
1243+
webcam_visible_px: [1280.0, 720.0],
1244+
u_max: 1920.0 / 1920.0,
1245+
v_max: 1080.0 / 1088.0,
1246+
frame: 90.0,
1247+
cfg,
1248+
live: live_params_from_scene(scene),
1249+
scene: Some(scene),
1250+
cursor: None,
1251+
timeline_t_override: Some(1.5),
1252+
}
1253+
}
1254+
1255+
/// **Le golden iso-render.**
1256+
///
1257+
/// Les deux backends ne peuvent pas tourner sur la même machine, donc « iso avec
1258+
/// D3D » ne peut pas être mesuré en comparant deux images rendues. Ce qui PEUT l'être,
1259+
/// et qui est la couche où la divergence s'est effectivement produite, c'est la
1260+
/// géométrie : `plan_frame` est le MÊME code des deux côtés, et ce test épingle ses 15
1261+
/// sorties au bit près. Il tourne dans le job macOS ET dans le job Windows, donc si un
1262+
/// jour les deux plateformes calculent des placements différents, l'un des deux vire au
1263+
/// rouge — ce qui est exactement la garantie qu'on cherche.
1264+
///
1265+
/// Ce que ce test ne couvre PAS, et qu'il ne faut pas lui faire dire : la rastérisation.
1266+
/// D3D11 et Metal ne rendront jamais bit-à-bit identique (la PR #162 a mesuré 93-95 %
1267+
/// de canaux identiques, écart max 3/255, entre deux backends sur la MÊME machine).
1268+
/// La parité des shaders est tenue séparément, par le fait que `shaders.metal` et
1269+
/// `shaders.hlsl` ont été diffés ligne à ligne sur les 14 modes.
1270+
#[test]
1271+
fn plan_frame_is_pinned_bit_for_bit() {
1272+
let scene = golden_scene();
1273+
let cfg = crate::config::all().pop().expect("au moins une config");
1274+
let g = plan_frame(&golden_input(&scene, &cfg));
1275+
let got = [
1276+
g.s_dst[0], g.s_dst[1], g.s_dst[2], g.s_dst[3],
1277+
g.w_dst[0], g.w_dst[1], g.w_dst[2], g.w_dst[3],
1278+
g.cut[0], g.cut[1], g.cut[2], g.cut[3],
1279+
g.s_radius, g.w_radius, g.w_px[0], g.w_px[1],
1280+
g.frame_min_px, g.padding_scale, g.shape_fade, g.mb_taps, g.source_t,
1281+
];
1282+
// Valeurs mesurées, pas devinées : toute dérive est une divergence à expliquer,
1283+
// pas un seuil à relâcher.
1284+
// Mesuré sur ce code, pas deviné : toute dérive est une divergence à expliquer,
1285+
// pas un seuil à relâcher. Ordre : s_dst[4], w_dst[4], cut[4], s_radius, w_radius,
1286+
// w_px[2], frame_min_px, padding_scale, shape_fade, mb_taps, source_t.
1287+
let want: [f32; 21] = [
1288+
0.10207555, 0.102, 0.7958489, 0.796,
1289+
0.9058473, 0.8325926, 0.0733194, 0.13037036,
1290+
0.0, 0.0, 0.61, 0.6055147,
1291+
16.779, 42.89185, 85.7837, 85.7837,
1292+
658.0, 0.796, 1.0, 6.25, 1.5,
1293+
];
1294+
for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
1295+
assert_eq!(
1296+
a.to_bits(),
1297+
b.to_bits(),
1298+
"sortie #{i} de plan_frame : {a} != {b}",
1299+
);
1300+
}
1301+
}
1302+
12151303
/// Le contrat cross-backend, verrouillé octet par octet. Un shader qui lit un champ
12161304
/// décalé ne lève rien : il rend faux, en silence.
12171305
#[test]

0 commit comments

Comments
 (0)