Repository navigation
chore(release): release v2.1.1 into main #1324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
216aed2
chore(release): bump to 2.1.0-rc.1 [skip ci]
github-actions[bot] c5bc8be
chore(release): bump to 2.1.0-rc.2 [skip ci]
github-actions[bot] 9b3730d
fix(preview): advance the camera while a screen frame is held
EtienneLescot 3038507
fix(timeline): copy a whole pill, not the fragment clicked
EtienneLescot a1ccced
fix(compositor): read the macOS preview back through Managed textures…
EtienneLescot 3b988ed
fix(preview): give a shared frame's slot back 40 ms after Chromium le…
EtienneLescot e3dc421
fix(preview): eight ring slots, and a slow display's two frames as th…
EtienneLescot e90a13d
test(compositor): the stale-ready test holds every slot but the last,…
EtienneLescot 523a3e4
chore(release): bump to 2.1.0-rc.3 [skip ci]
github-actions[bot] 336366f
fix(ai): rewind a chat message from the view it was sent in
EtienneLescot 9c42819
fix(ai): point an open rewind confirmation at the stored message id
EtienneLescot 89cdff2
chore(release): bump to 2.1.0-rc.4 [skip ci]
github-actions[bot] de0cea2
fix(compositor): no Full Camera trail on a clip's first frame when it…
EtienneLescot 57686f5
fix(preview): restart the playback clock at the requested time on a h…
EtienneLescot 1180671
fix(preview): recompose a paused frame at the time it was shown at
EtienneLescot 3db4c7c
chore(release): bump to 2.1.0-rc.5 [skip ci]
github-actions[bot] 421802f
fix(export): no Full Camera trail at a cut whose chunk start rounds up
EtienneLescot cffb06f
test(timeline): assert a cut-edge piece bound equals the cut in f32
EtienneLescot 36b3814
chore(release): bump to 2.1.0-rc.6 [skip ci]
github-actions[bot] 5141050
chore(release): bump to 2.1.0 [skip ci]
github-actions[bot] 0913998
fix(compositor): tilt the whole card under a filled format
EtienneLescot ce866df
fix(compositor): keep blur masks and motion blur right on a tilted fi…
EtienneLescot 6ec38bf
chore(release): bump to 2.1.1-rc.1 [skip ci]
github-actions[bot] 0bcd3d9
chore(release): bump to 2.1.1 [skip ci]
github-actions[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| //! Le remplissage du format sous la caméra en orbite, rendu par le vrai compositeur D3D11. | ||
| //! | ||
| //! Un enregistrement 4:3 remplit une sortie 16:9 (`screenCover` + `screenFollow`), zoom ×2 en | ||
| //! orbite, pointeur garé en bas à droite : la caméra tourne à fond. La carte doit pencher ENTIÈRE, | ||
| //! ses quatre coins arrondis. De v2.0.0 à v2.1.0, le plan de l'enregistrement entier penchait | ||
| //! dans le rect droit du slot, qui le rognait : des coins coupés à plat, par une arête alignée sur | ||
| //! les axes, alors que la carte tenait dans la sortie. | ||
| //! | ||
| //! Frame NV12 synthétique et unie, fond noir, sans ombre : le métrage est clair, tout le reste | ||
| //! noir. Sans adaptateur matériel, le test se saute. | ||
|
|
||
| // Windows seulement : le readback et le décodage D3D11VA de ce harnais n'existent que là. | ||
| #![cfg(windows)] | ||
|
|
||
| use openscreen_compositor::compositor::Compositor; | ||
| use openscreen_compositor::config::Cfg; | ||
| use openscreen_compositor::cursor::CursorTrack; | ||
| use openscreen_compositor::frame_geometry::{live_params_from_scene, plan_frame, FrameGeometryInput}; | ||
| use openscreen_compositor::scene::Scene; | ||
|
|
||
| mod common; | ||
| use common::{gpu, Nv12Frame}; | ||
|
|
||
| const SRC: (u32, u32) = (640, 480); | ||
| const OUT: (u32, u32) = (1280, 720); | ||
| /// En plein palier de la région (0..10 s), le cadreur posé. | ||
| const T: f32 = 3.0; | ||
|
|
||
| fn scene() -> Scene { | ||
| Scene::from_json( | ||
| r##"{"clips":[{"screenPath":"/s.mp4","webcamPath":"","sourceStartSec":0,"sourceEndSec":10,"webcamOffsetSec":0,"hasAudio":false}], | ||
| "layout":{"preset":"no-webcam","webcamSize":1,"webcamShape":"rounded","webcamMirror":false,"webcamPosition":null,"webcamReactiveZoom":false, | ||
| "screenRect":{"x":0.1,"y":0.1,"width":0.8,"height":0.8},"screenCover":true,"screenFollow":true}, | ||
| "effects":{"padding":0.2,"blur":false,"shadow":0,"roundnessFrac":0.04,"motionBlur":0,"depthOfField":false}, | ||
| "background":{"kind":"color","color":"#000000"}, | ||
| "zoomRegions":[{"clipIndex":0,"startSec":0,"endSec":10,"scale":2.0,"focusX":0.5,"focusY":0.5,"focusMode":"auto","rotation":"orbit"}], | ||
| "annotations":[], | ||
| "cursor":{"show":true,"size":0.05,"smoothing":0,"motionBlur":0,"clickBounce":0,"clipToBounds":false,"theme":"none"}, | ||
| "cropByClip":[null], | ||
| "output":{"width":1280,"height":720,"fps":30}}"##, | ||
| ) | ||
| .expect("scène valide") | ||
| } | ||
|
|
||
| /// Pointeur garé en (x, y), échantillonné à 60 Hz sur 10 s, écrit dans un sidecar temporaire. | ||
| fn parked(x: f32, y: f32) -> CursorTrack { | ||
| let body: Vec<String> = | ||
| (0..=600).map(|i| format!(r#"{{"timeMs":{},"cx":{x},"cy":{y}}}"#, (i as f32 * 1000.0 / 60.0).round())).collect(); | ||
| let path = std::env::temp_dir().join(format!("os_fill_tilt_{}.json", std::process::id())); | ||
| std::fs::write(&path, format!(r#"{{"samples":[{}]}}"#, body.join(","))).expect("sidecar"); | ||
| let track = CursorTrack::load(path.to_str().unwrap(), 0.0, 10.0).expect("piste curseur"); | ||
| let _ = std::fs::remove_file(&path); | ||
| track | ||
| } | ||
|
|
||
| fn cfg() -> Cfg { | ||
| let mut cfg = Cfg::c8(); | ||
| cfg.bg_blur = 0.0; | ||
| cfg.zoom = false; | ||
| cfg.layout_anim = false; | ||
| cfg.cursor = true; | ||
| cfg.mblur_n = 1; | ||
| cfg.shadow = false; | ||
| cfg | ||
| } | ||
|
|
||
| /// Hors de la sortie, rien n'est dessiné : noir, comme le fond. | ||
| fn luma(rgba: &[u8], x: f32, y: f32) -> f32 { | ||
| let (x, y) = (x.round() as i32, y.round() as i32); | ||
| if !(0..OUT.0 as i32).contains(&x) || !(0..OUT.1 as i32).contains(&y) { | ||
| return 0.0; | ||
| } | ||
| let i = ((y * OUT.0 as i32 + x) * 4) as usize; | ||
| 0.2126 * rgba[i] as f32 + 0.7152 * rgba[i + 1] as f32 + 0.0722 * rgba[i + 2] as f32 | ||
| } | ||
|
|
||
| /// Chaque coin de la carte inclinée est arrondi : sur la bissectrice, à 0,15 rayon du sommet | ||
| /// projeté, le fond (l'arrondi l'a retiré) ; à 1,3 rayon, le métrage. Un coin coupé par le rect | ||
| /// du slot laissait du noir là où la carte devait être, ou la poussait hors de la sortie. | ||
| #[test] | ||
| fn every_corner_of_a_filled_card_stays_rounded_under_the_orbit() { | ||
| let Some(gpu) = gpu() else { return }; | ||
| let comp = Compositor::new_sized(&gpu, OUT.0, OUT.1).expect("compositor"); | ||
| let screen = Nv12Frame::new(&gpu, SRC, |_, _| 220, |_, _| [128, 128]); | ||
| let scene = scene(); | ||
| let track = parked(0.9, 0.9); | ||
|
|
||
| let mut live = live_params_from_scene(&scene); | ||
| live.has_webcam = false; | ||
| comp.set_live_params(live); | ||
| comp.set_has_webcam(false); | ||
| comp.set_scene(Some(scene.clone())); | ||
| comp.set_cursor(track.clone()); | ||
| comp.set_cursor_time(Some(T)); | ||
| comp.set_timeline_time(Some(T)); | ||
| let cfg = cfg(); | ||
| let (w, h, rgba) = unsafe { | ||
| comp.compose_frame(screen.as_ptr(), screen.as_ptr(), 0.0, &cfg).expect("compose_frame"); | ||
| comp.readback_direct().expect("readback") | ||
| }; | ||
| assert_eq!((w, h), OUT); | ||
| if let Ok(dir) = std::env::var("OPENSCREEN_FILL_TILT_OUT") { | ||
| image::RgbaImage::from_raw(w, h, rgba.clone()).unwrap().save(format!("{dir}/fill-orbit.png")).unwrap(); | ||
| } | ||
|
|
||
| let render_px = [w as f32, h as f32]; | ||
| let src = [SRC.0 as f32, SRC.1 as f32]; | ||
| let g = plan_frame(&FrameGeometryInput { | ||
| render_px, | ||
| screen_tex_px: src, | ||
| screen_visible_px: src, | ||
| webcam_visible_px: src, | ||
| u_max: 1.0, | ||
| v_max: 1.0, | ||
| frame: 0.0, | ||
| cfg: &cfg, | ||
| live, | ||
| scene: Some(&scene), | ||
| cursor: Some(&track), | ||
| timeline_t_override: Some(T), | ||
| programme_time: None, | ||
| }); | ||
| let camera = g.camera.expect("la caméra réelle doit être posée"); | ||
| assert!(camera.orbit[0] > 0.8, "garde : la caméra doit tourner à fond, orbite {:?}", camera.orbit); | ||
| let s_px = [g.s_dst[2] * render_px[0], g.s_dst[3] * render_px[1]]; | ||
| let quad = g.screen_tilt(s_px).expect("quad de la caméra"); | ||
| let center = [(g.s_dst[0] + g.s_dst[2] * 0.5) * render_px[0], (g.s_dst[1] + g.s_dst[3] * 0.5) * render_px[1]]; | ||
| let r = g.s_radius * quad.scale; | ||
| assert!(r > 20.0, "garde : rayon {r} px, trop petit pour se mesurer"); | ||
|
|
||
| let corners = quad.corners.map(|(x, y)| [center[0] + x, center[1] + y]); | ||
| let mid = [corners.iter().map(|c| c[0]).sum::<f32>() / 4.0, corners.iter().map(|c| c[1]).sum::<f32>() / 4.0]; | ||
| for (name, c) in ["haut-gauche", "haut-droit", "bas-droit", "bas-gauche"].iter().zip(corners) { | ||
| let (dx, dy) = (mid[0] - c[0], mid[1] - c[1]); | ||
| let n = dx.hypot(dy); | ||
| let at = |k: f32| luma(&rgba, c[0] + dx / n * k * r, c[1] + dy / n * k * r); | ||
| let (rounded, inside) = (at(0.15), at(1.3)); | ||
| println!("coin {name} ({:.0}, {:.0}) : arrondi {rounded:.0}, dedans {inside:.0}", c[0], c[1]); | ||
| assert!(rounded < 40.0, "coin {name} : pas arrondi (luma {rounded:.0} au sommet)"); | ||
| assert!(inside > 150.0, "coin {name} : la carte est coupée (luma {inside:.0} à 1,3 rayon)"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Anchor the camera test clock at the seek target, not at the held frame's pts.
present_frame(1.0)seeks the camera to 1.0 s. It also composes at1.0.max(held).heldis the screen pts fromseek_to_or_last(1.0), soheld <= 1.0. On a variable-frame-rate screen source,heldcan be far below 1.0. A stream on a motionless surface delivers one frame and then nothing: a static wallpaper was measured at 1 frame in 3.1 seconds. Withheld = 0.6, the test setsclock = 0.85. The camera already sits near 1.0, soadvance_webcam_to(0.85)holds. In that case both assertions fail:camera_after > camera_beforeandcamera_after <= clock.Production does not compose at this clock. After a seek,
render_threadsetsacc = anchor_acc(target, pts), so the hold clock is1.0 + elapsed. The file header also assumes a camera frame between 1 s and 1.25 s, which matches a clock of 1.25. The first test in this file already acceptsheld < 1.0(1.0f64.max(held)).🐛 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents