🎉 Phase 6.3 — display + input + compositor-core intégrés runtime
Captures preuves dans docs/phase6-3-*.png : 4 frames qui prouvent
visuellement que raise change l'ordre Z et que compose_into propage le
résultat à l'écran QEMU :
- default-z.png : 3 surfaces overlap, blue top (créé en dernier)
- red-top.png : sendkey 1 → raise(red) → red couvre vert et bleu
- green-top.png : sendkey 2 → raise(green) → green couvre tout dans sa zone
- blue-top.png : sendkey 3 → raise(blue) → retour visuel à initial
Modifications :
compositor-core (commit dbf3bff → maintenant) :
- + iter_z_order_front_to_back() : utile pour hit testing
- + hit_test(x, y) -> Option<SurfaceId> : trouve la surface visible la
plus haute qui contient le point
- + 4 tests unitaires : 27 total / 27 pass natif (0.00s)
redox-wl-display :
- + dep redox-wl-compositor-core
- + impl Framebuffer for RedoxOutput (délègue à pixels_mut + width/height)
bin redox-wl-test-compose-static (190 lignes) :
- ouvre RedoxOutput + take_crtc
- crée InputBackend partagé
- 3 surfaces ARGB unies (rouge/vert/bleu) avec overlap centré
- boucle event : '1'/'2'/'3' raise resp. red/green/blue
- clic souris → hit_test puis raise (motion non testé sans usb-tablet)
- ré-render seulement si raise → économie CPU
- present_with_takeover() à chaque iter pour tenir le CRTC
Validation QEMU automatisée : sendkey 1/2/3 + screendump entre chaque.
Les 4 PNG montrent l'ordre Z évoluer correctement.
Image Redox restaurée à boot Orbital normal.
docs/phase6-compositor-core.md : compte-rendu 6.1-6.3 complet,
architecture, dépendances, API, limitations, plan 6.4.
Phase 6.3 close. Reste 6.4 (frontend Wayland : wl_compositor + wl_shm
+ xdg-shell mappés vers compositor-core, damage tracking, frame
callbacks). Estimé 2-3 sessions.
Leyoda 2026 – GPLv3
This commit is contained in:
parent
dbf3bffa2b
commit
509aae7769
10 changed files with 608 additions and 0 deletions
251
crates/redox-wl-test-compose-static/src/main.rs
Normal file
251
crates/redox-wl-test-compose-static/src/main.rs
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
//! Phase 6.3 — Test d'intégration display + input + compositor-core.
|
||||
//!
|
||||
//! 3 surfaces ARGB synthétiques (rouge, vert, bleu) qui se chevauchent
|
||||
//! sur un fond gris foncé. Touche '1', '2', '3' raise resp. la rouge,
|
||||
//! verte, bleue. Clic souris → hit-test et raise la surface ciblée.
|
||||
//! Recompose + present à chaque event.
|
||||
//!
|
||||
//! Ce binaire est la première fois où **tous les morceaux phase 4-6.1-6.2**
|
||||
//! travaillent ensemble :
|
||||
//! - `RedoxOutput` (display backend, 4)
|
||||
//! - `InputBackend` (input, 5)
|
||||
//! - `SurfaceRegistry` + `compose_into` (composition, 6.1-6.2)
|
||||
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, ExitCode};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use redox_wl_compositor_core::{Framebuffer as _, SurfaceBuffer, SurfaceId, SurfaceRegistry};
|
||||
use redox_wl_display::RedoxOutput;
|
||||
use redox_wl_input::{InputBackend, InputEvent};
|
||||
|
||||
struct DebugSink(Mutex<Option<std::fs::File>>);
|
||||
impl DebugSink {
|
||||
fn new() -> Self {
|
||||
Self(Mutex::new(
|
||||
OpenOptions::new().write(true).open("/scheme/debug").ok(),
|
||||
))
|
||||
}
|
||||
fn writeln(&self, s: &str) {
|
||||
println!("{s}");
|
||||
if let Ok(mut g) = self.0.lock() {
|
||||
if let Some(f) = g.as_mut() {
|
||||
let _ = writeln!(f, "{s}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn dlog(s: &str) {
|
||||
static SINK: OnceLock<DebugSink> = OnceLock::new();
|
||||
SINK.get_or_init(DebugSink::new).writeln(s);
|
||||
}
|
||||
|
||||
const BG_COLOR: u32 = 0xFF202028; // gris foncé bleuté
|
||||
|
||||
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dlog("[compose] Phase 6.3 — composition statique 3 surfaces");
|
||||
|
||||
// Display setup ---------------------------------------------------------
|
||||
let mut output = RedoxOutput::open().map_err(|e| format!("RedoxOutput::open: {e}"))?;
|
||||
let our_vt = output.vt();
|
||||
let fb_w = output.width();
|
||||
let fb_h = output.height();
|
||||
dlog(&format!("[compose] display {fb_w}x{fb_h}, VT={our_vt}"));
|
||||
|
||||
let _ = Command::new("inputd")
|
||||
.arg("-A")
|
||||
.arg(our_vt.to_string())
|
||||
.status();
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
|
||||
output
|
||||
.take_crtc()
|
||||
.map_err(|e| format!("take_crtc: {e}"))?;
|
||||
dlog("[compose] CRTC pris");
|
||||
|
||||
// Input setup -----------------------------------------------------------
|
||||
let input = InputBackend::new(output.consumer());
|
||||
dlog("[compose] InputBackend prêt");
|
||||
|
||||
// Compositor scene ------------------------------------------------------
|
||||
let mut registry = SurfaceRegistry::new();
|
||||
|
||||
// Centrer les 3 surfaces avec overlap. Tailles relatives au display.
|
||||
let surf_w = (fb_w / 3).max(200);
|
||||
let surf_h = (fb_h / 3).max(150);
|
||||
let cx = (fb_w / 2) as i32;
|
||||
let cy = (fb_h / 2) as i32;
|
||||
let off = (surf_w / 4) as i32;
|
||||
|
||||
let red = registry.create();
|
||||
registry.modify_pending(red, |s| {
|
||||
s.x = cx - surf_w as i32 / 2 - off;
|
||||
s.y = cy - surf_h as i32 / 2 - off / 2;
|
||||
s.visible = true;
|
||||
s.buffer = Some(SurfaceBuffer::new_filled(surf_w, surf_h, 0xFF_E0_30_30));
|
||||
});
|
||||
registry.commit(red);
|
||||
|
||||
let green = registry.create();
|
||||
registry.modify_pending(green, |s| {
|
||||
s.x = cx - surf_w as i32 / 2;
|
||||
s.y = cy - surf_h as i32 / 2 + off / 2;
|
||||
s.visible = true;
|
||||
s.buffer = Some(SurfaceBuffer::new_filled(surf_w, surf_h, 0xFF_30_C0_30));
|
||||
});
|
||||
registry.commit(green);
|
||||
|
||||
let blue = registry.create();
|
||||
registry.modify_pending(blue, |s| {
|
||||
s.x = cx - surf_w as i32 / 2 + off;
|
||||
s.y = cy - surf_h as i32 / 2 - off / 2;
|
||||
s.visible = true;
|
||||
s.buffer = Some(SurfaceBuffer::new_filled(surf_w, surf_h, 0xFF_30_60_E0));
|
||||
});
|
||||
registry.commit(blue);
|
||||
|
||||
dlog(&format!(
|
||||
"[compose] 3 surfaces créées : red={red:?} green={green:?} blue={blue:?}"
|
||||
));
|
||||
|
||||
let surfaces: [(SurfaceId, &str); 3] = [(red, "red"), (green, "green"), (blue, "blue")];
|
||||
|
||||
// Helper de rendu : clear + compose + present.
|
||||
let render = |output: &mut RedoxOutput, registry: &SurfaceRegistry| -> io::Result<()> {
|
||||
// Clear le framebuffer au fond gris foncé. compose_into est overwrite,
|
||||
// donc il faut peindre le fond avant si on veut une zone non couverte.
|
||||
for p in <RedoxOutput as Framebuffer>::pixels_mut(output).iter_mut() {
|
||||
*p = BG_COLOR;
|
||||
}
|
||||
registry.compose_into(output);
|
||||
output
|
||||
.present_with_takeover()
|
||||
.map_err(|e| io::Error::other(format!("present: {e}")))
|
||||
};
|
||||
|
||||
// Premier rendu
|
||||
render(&mut output, ®istry)?;
|
||||
dlog("[compose] première frame présentée");
|
||||
|
||||
// Mouse position track (pour hit_test)
|
||||
let mut mouse_x: i32 = (fb_w / 2) as i32;
|
||||
let mut mouse_y: i32 = (fb_h / 2) as i32;
|
||||
|
||||
// Boucle principale 30 secondes
|
||||
let start = Instant::now();
|
||||
let total = Duration::from_secs(30);
|
||||
let mut event_count = 0usize;
|
||||
let mut quit = false;
|
||||
|
||||
while start.elapsed() < total && !quit {
|
||||
let events = input.poll().map_err(|e| format!("poll: {e}"))?;
|
||||
let mut need_redraw = false;
|
||||
|
||||
for ev in events {
|
||||
event_count += 1;
|
||||
match ev {
|
||||
InputEvent::Key {
|
||||
character,
|
||||
scancode,
|
||||
pressed,
|
||||
} => {
|
||||
if !pressed {
|
||||
continue;
|
||||
}
|
||||
// Touches '1' / '2' / '3' raise la surface correspondante.
|
||||
let target = match character {
|
||||
'1' => Some((red, "red")),
|
||||
'2' => Some((green, "green")),
|
||||
'3' => Some((blue, "blue")),
|
||||
_ => None,
|
||||
};
|
||||
if let Some((id, name)) = target {
|
||||
registry.raise(id);
|
||||
dlog(&format!("[compose] raise {name} (key '{character}')"));
|
||||
need_redraw = true;
|
||||
} else if scancode == 0x01 {
|
||||
// Esc → quit
|
||||
dlog("[compose] Esc → quit");
|
||||
quit = true;
|
||||
}
|
||||
}
|
||||
InputEvent::PointerMotion { x, y } => {
|
||||
mouse_x = x;
|
||||
mouse_y = y;
|
||||
}
|
||||
InputEvent::PointerMotionRelative { dx, dy } => {
|
||||
mouse_x = (mouse_x + dx).clamp(0, fb_w as i32 - 1);
|
||||
mouse_y = (mouse_y + dy).clamp(0, fb_h as i32 - 1);
|
||||
}
|
||||
InputEvent::PointerButton { left, .. } => {
|
||||
if left {
|
||||
// Hit-test à la position courante
|
||||
if let Some(id) = registry.hit_test(mouse_x, mouse_y) {
|
||||
registry.raise(id);
|
||||
let name = surfaces
|
||||
.iter()
|
||||
.find(|(s, _)| *s == id)
|
||||
.map(|(_, n)| *n)
|
||||
.unwrap_or("unknown");
|
||||
dlog(&format!(
|
||||
"[compose] click at ({mouse_x},{mouse_y}) → raise {name}"
|
||||
));
|
||||
need_redraw = true;
|
||||
} else {
|
||||
dlog(&format!(
|
||||
"[compose] click at ({mouse_x},{mouse_y}) → miss (no surface)"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
InputEvent::Quit => {
|
||||
quit = true;
|
||||
}
|
||||
InputEvent::Handoff => {
|
||||
dlog("[compose] handoff received");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if need_redraw {
|
||||
if let Err(e) = render(&mut output, ®istry) {
|
||||
dlog(&format!("[compose] render error: {e}"));
|
||||
}
|
||||
} else {
|
||||
// Re-present sans recomposer pour tenir le CRTC face à fbcond.
|
||||
let _ = output.present_with_takeover();
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
|
||||
dlog(&format!(
|
||||
"[compose] fin — {event_count} events en {:.1}s",
|
||||
start.elapsed().as_secs_f32()
|
||||
));
|
||||
let _ = env::var("VT");
|
||||
drop(output);
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => {
|
||||
dlog("[compose] PASS");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
dlog(&format!("[compose] FAIL: {e}"));
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use redox_wl_compositor_core::Framebuffer;
|
||||
use std::io;
|
||||
Loading…
Add table
Add a link
Reference in a new issue