🎉 Phase 4 vraie validée visuellement : pixels custom plein écran
Capture : docs/phase4-victory-1280x800.png — dégradé ARGB animé 1280x800
écrit par redox-wl-fullscreen-paint, occupant tout l'écran QEMU sans
trace de bootlog, fbcond ou Orbital.
Cause racine du verrou (3 bugs en cascade) :
1. ConsumerHandle local à RedoxOutput::open() → droppé en fin de fn →
inputd::on_close retirait le VT de self.vts → tous les `inputd -A <vt>`
ultérieurs retournaient warning "switch to non-existent VT"
2. L'env var VT=N posée par init n'a aucun lien avec le VT alloué par
inputd. inputd auto-incrémente next_vt_id à partir de 2 (VT 1 réservé
bootlog). Avec fbbootlogd VT 1 + fbcond VT 2, notre paint = VT 3.
3. Sans le bon VT activé, set_crtc est silencieusement no-op côté
driver-graphics (lib.rs:575 : `if *vt == self.active_vt { ... }`).
Fixes :
- RedoxOutput stocke `_consumer: ConsumerHandle` pour préserver le VT
- RedoxOutput.vt() lu via fpath sur consumer fd (inputd retourne
`<scheme>/<vt>`)
- Binary lit output.vt() puis fait inputd -A <vt> avec le bon numéro
- 300ms de sleep pour propagation active_vt avant take_crtc
Validation automatisée : qemu -display none + monitor unix socket +
ncat -U pour sendkey ret + screendump à T+14s + ImageMagick.
Image Redox restaurée à boot Orbital normal après la session.
Phase 4 close. La piste 1 (consume events VT) reste utile pour le
hot-switch propre Ctrl+Alt+Fn mais n'est plus bloquante.
Leyoda 2026 – GPLv3
This commit is contained in:
parent
5b1e038333
commit
753a30757b
7 changed files with 139 additions and 18 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -6,7 +6,7 @@ Cargo.lock
|
|||
|
||||
# Generated test outputs
|
||||
*.ppm
|
||||
*.png
|
||||
# *.png # whitelist phase4-victory below
|
||||
*.pcap
|
||||
|
||||
# Editor / OS
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ description = "Display backend wrapper around graphics-ipc + drm subset for Redo
|
|||
drm = "0.15"
|
||||
graphics-ipc = { git = "https://gitlab.redox-os.org/redox-os/base.git" }
|
||||
inputd = { git = "https://gitlab.redox-os.org/redox-os/base.git" }
|
||||
libredox = "0.1"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
//! le KMS Redox), pas de page flip / double buffer / hotplug.
|
||||
|
||||
use std::io;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::slice;
|
||||
|
||||
use drm::Device as _;
|
||||
|
|
@ -28,6 +29,10 @@ use graphics_ipc::{CpuBackedBuffer, V2GraphicsHandle};
|
|||
use inputd::ConsumerHandle;
|
||||
|
||||
pub struct RedoxOutput {
|
||||
/// Consumer handle GARDÉ EN VIE pour que le VT alloué par inputd persiste.
|
||||
/// Si on drop ce handle, inputd retire le VT de sa table et toute opération
|
||||
/// `inputd -A <vt>` ultérieure dira "non-existent VT".
|
||||
_consumer: ConsumerHandle,
|
||||
handle: V2GraphicsHandle,
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
|
@ -36,6 +41,9 @@ pub struct RedoxOutput {
|
|||
mode: Mode,
|
||||
fb: Option<framebuffer::Handle>,
|
||||
buffer: Option<CpuBackedBuffer>,
|
||||
/// VT alloué par inputd lors de l'open consumer.
|
||||
/// À utiliser pour `inputd -A <vt>` afin de devenir le VT actif.
|
||||
vt: usize,
|
||||
}
|
||||
|
||||
impl RedoxOutput {
|
||||
|
|
@ -43,6 +51,19 @@ impl RedoxOutput {
|
|||
/// connecteur connecté + son premier mode + le premier CRTC compatible.
|
||||
pub fn open() -> io::Result<Self> {
|
||||
let consumer = ConsumerHandle::new_vt()?;
|
||||
|
||||
// Découvrir notre VT alloué par inputd via fpath sur le consumer fd.
|
||||
// Le fpath retourne `<display_scheme>/<vt>` (cf inputd main.rs:271).
|
||||
let mut buf = [0u8; 1024];
|
||||
let n = libredox_call_fpath(consumer.event_handle().as_raw_fd() as usize, &mut buf)?;
|
||||
let path = std::str::from_utf8(&buf[..n])
|
||||
.map_err(|e| io::Error::other(format!("fpath utf8: {e}")))?;
|
||||
let vt: usize = path
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.ok_or_else(|| io::Error::other(format!("can't parse VT from fpath {path:?}")))?;
|
||||
|
||||
let display_file = consumer.open_display_v2()?;
|
||||
let handle = V2GraphicsHandle::from_file(display_file)?;
|
||||
|
||||
|
|
@ -80,6 +101,7 @@ impl RedoxOutput {
|
|||
.ok_or_else(|| io::Error::other("no crtc compatible with encoder"))?;
|
||||
|
||||
Ok(Self {
|
||||
_consumer: consumer,
|
||||
handle,
|
||||
width: w as u32,
|
||||
height: h as u32,
|
||||
|
|
@ -88,6 +110,7 @@ impl RedoxOutput {
|
|||
mode,
|
||||
fb: None,
|
||||
buffer: None,
|
||||
vt,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +123,11 @@ impl RedoxOutput {
|
|||
pub fn handle(&self) -> &V2GraphicsHandle {
|
||||
&self.handle
|
||||
}
|
||||
/// Le VT alloué par inputd lors de l'open consumer.
|
||||
/// Utiliser cette valeur pour `inputd -A <vt>` ou `ControlHandle::activate_vt`.
|
||||
pub fn vt(&self) -> usize {
|
||||
self.vt
|
||||
}
|
||||
|
||||
/// Alloue un CpuBackedBuffer ARGB8888 plein écran, l'ajoute comme
|
||||
/// framebuffer DRM et appelle `set_crtc` pour qu'il soit affiché.
|
||||
|
|
@ -191,6 +219,12 @@ impl RedoxOutput {
|
|||
}
|
||||
}
|
||||
|
||||
/// Wrapper minimal sur `libredox::call::fpath` (la fn FFI directe n'a pas de
|
||||
/// signature stable côté `inputd::ConsumerHandle`).
|
||||
fn libredox_call_fpath(fd: usize, buf: &mut [u8]) -> io::Result<usize> {
|
||||
libredox::call::fpath(fd, buf).map_err(|e| io::Error::from_raw_os_error(e.errno()))
|
||||
}
|
||||
|
||||
impl Drop for RedoxOutput {
|
||||
fn drop(&mut self) {
|
||||
if let Some(fb) = self.fb.take() {
|
||||
|
|
|
|||
|
|
@ -63,18 +63,23 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
let mut output = RedoxOutput::open().map_err(|e| format!("RedoxOutput::open: {e}"))?;
|
||||
dlog(&format!(
|
||||
"[paint] display ouvert : {}x{}",
|
||||
"[paint] display ouvert : {}x{}, VT alloué par inputd = {}",
|
||||
output.width(),
|
||||
output.height()
|
||||
output.height(),
|
||||
output.vt(),
|
||||
));
|
||||
|
||||
if let Some(v) = &vt {
|
||||
match Command::new("inputd").arg("-A").arg(v).status() {
|
||||
Ok(s) if s.success() => dlog(&format!("[paint] inputd -A {v} OK")),
|
||||
Ok(s) => dlog(&format!("[paint] inputd -A {v} exit {:?}", s)),
|
||||
Err(e) => dlog(&format!("[paint] inputd -A {v} err: {e}")),
|
||||
}
|
||||
}
|
||||
// Activer notre VT (celui que inputd nous a réellement alloué via fpath)
|
||||
let our_vt = output.vt();
|
||||
let r = Command::new("inputd")
|
||||
.arg("-A")
|
||||
.arg(our_vt.to_string())
|
||||
.status();
|
||||
dlog(&format!("[paint] inputd -A {our_vt} → {:?}", r));
|
||||
|
||||
// Laisser inputd-daemon traiter le ControlEvent.
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
dlog("[paint] sleep 300ms pour propagation active_vt");
|
||||
|
||||
output.take_crtc().map_err(|e| format!("take_crtc: {e}"))?;
|
||||
dlog("[paint] CRTC pris : on tient l'écran");
|
||||
|
|
@ -82,8 +87,13 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let w = output.width();
|
||||
let h = output.height();
|
||||
|
||||
// 30 frames espacées d'une seconde pour qu'on voie le dégradé bouger
|
||||
for t in 0..30u32 {
|
||||
// 600 frames @ 30 fps = 20 secondes d'animation visible.
|
||||
// set_crtc à chaque frame (via present_with_takeover) pour reprendre le
|
||||
// CRTC à fbbootlogd s'il essaie de le récupérer en arrière-plan.
|
||||
let total_frames: u32 = 600;
|
||||
let frame_period = Duration::from_millis(33); // ~30 fps
|
||||
|
||||
for t in 0..total_frames {
|
||||
{
|
||||
let pixels = output.pixels_mut()?;
|
||||
for y in 0..h {
|
||||
|
|
@ -96,13 +106,13 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
output
|
||||
.present_with_takeover()
|
||||
.map_err(|e| format!("present {t}: {e}"))?;
|
||||
if t % 5 == 0 {
|
||||
dlog(&format!("[paint] frame {t}/30 présentée"));
|
||||
if t % 30 == 0 {
|
||||
dlog(&format!("[paint] frame {t}/{total_frames} présentée"));
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
thread::sleep(frame_period);
|
||||
}
|
||||
|
||||
dlog("[paint] 30 frames terminées, exit propre");
|
||||
dlog(&format!("[paint] {total_frames} frames terminées, exit propre"));
|
||||
drop(output); // libère framebuffer + buffer explicitement
|
||||
thread::sleep(Duration::from_millis(500)); // laisser le serial flush
|
||||
Ok(())
|
||||
|
|
|
|||
BIN
crates/redox-wl-poc-pixels/poc-shm-pixels-readback.png
Normal file
BIN
crates/redox-wl-poc-pixels/poc-shm-pixels-readback.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 701 B |
|
|
@ -187,8 +187,84 @@ Même sans visuel, le pipeline est validé par :
|
|||
- la persistance du buffer `CpuBackedBuffer` (alloc + écriture + sync sans
|
||||
panic, plusieurs centaines de fois sans fuite mémoire visible)
|
||||
|
||||
C'est suffisant pour avancer phase 5 (input backend) et y revenir plus tard
|
||||
quand on aura un compositor qui consomme les events VT.
|
||||
## VICTOIRE 2026-05-09 — phase 4 vraie validée visuellement
|
||||
|
||||
Capture : 
|
||||
|
||||
L'écran QEMU montre intégralement le dégradé ARGB animé écrit par notre
|
||||
binaire `redox-wl-fullscreen-paint`, plein écran 1280x800. Aucune trace
|
||||
de bootlog, fbcond ou fbbootlogd. C'est notre code Rust qui pilote le
|
||||
framebuffer Redox.
|
||||
|
||||
### La cause racine du verrou (3 bugs en cascade)
|
||||
|
||||
Bug 1 — **`ConsumerHandle` était local à `RedoxOutput::open()`** et droppé
|
||||
à la sortie de la fonction. inputd-daemon réagissait avec `on_close` qui
|
||||
retire le VT de `self.vts`. Tous les `inputd -A <vt>` ultérieurs
|
||||
retournaient warning "switch to non-existent VT".
|
||||
|
||||
Bug 2 — **L'env var `VT=N` posée par init n'a aucun lien avec le VT
|
||||
réellement alloué par inputd**. inputd auto-incrémente `next_vt_id` à
|
||||
partir de 2 (VT 1 réservé bootlog). On dénombrait :
|
||||
- VT 1 = `consumer_bootlog` de fbbootlogd
|
||||
- VT 2 = consumer de fbcond (lancé par `init.initfs.d/20_fbcond.service`)
|
||||
- VT 3 = notre consumer
|
||||
|
||||
Bug 3 — **Sans le bon VT activé, `set_crtc` est silencieusement no-op**
|
||||
côté `driver-graphics` (cf `lib.rs:575` : `if *vt == self.active_vt {
|
||||
self.adapter.set_crtc(...) }`).
|
||||
|
||||
### Le fix
|
||||
|
||||
```rust
|
||||
pub struct RedoxOutput {
|
||||
/// CONSUMER GARDÉ EN VIE : si on le drop, inputd retire notre VT et
|
||||
/// `inputd -A <vt>` dira "non-existent".
|
||||
_consumer: ConsumerHandle,
|
||||
// ...
|
||||
/// VT alloué par inputd, lu via fpath() sur le consumer fd.
|
||||
/// (cf `inputd::main.rs:271-281` qui retourne `display.scheme/<vt>`)
|
||||
vt: usize,
|
||||
}
|
||||
```
|
||||
|
||||
Et le binaire qui appelle :
|
||||
```rust
|
||||
let our_vt = output.vt(); // VT exact alloué par inputd
|
||||
Command::new("inputd").arg("-A").arg(our_vt.to_string()).status()?;
|
||||
thread::sleep(Duration::from_millis(300)); // propagation active_vt
|
||||
output.take_crtc()?; // set_crtc passe la condition
|
||||
output.present_with_takeover()?; // pixels visibles à l'écran
|
||||
```
|
||||
|
||||
### Validation visuelle automatisée
|
||||
|
||||
Méthode reproductible dans le repo :
|
||||
```bash
|
||||
qemu-system-x86_64 \
|
||||
... -vga std -display none \
|
||||
-monitor unix:/tmp/qmp.sock,server,nowait \
|
||||
-serial file:/tmp/qemu-serial.log ... &
|
||||
sleep 2; printf "sendkey ret\n" | ncat -U /tmp/qmp.sock # passe bootloader
|
||||
sleep 14; printf "screendump /tmp/frame.ppm\n" | ncat -U /tmp/qmp.sock
|
||||
magick /tmp/frame.ppm /tmp/frame.png # convert
|
||||
# Lire le PNG pour voir le rendu
|
||||
```
|
||||
|
||||
### Implications pour la suite
|
||||
|
||||
Phase 4 entièrement validée. La fondation `RedoxOutput` est utilisable telle
|
||||
quelle pour :
|
||||
- Phase 5 (input backend) : ajouter `consumer.read_events()` pour récupérer
|
||||
les events keyboard/mouse de inputd
|
||||
- Phase 6 (surfaces shm composées) : composer plusieurs `wl_shm` buffers
|
||||
dans le `pixels_mut()` de `RedoxOutput`
|
||||
- Phase 7 (compositor utilisable) : ajouter focus, stacking, damage
|
||||
tracking au-dessus
|
||||
|
||||
La piste 1 du plan (consommer events VT) reste utile à terme pour gérer le
|
||||
hot-switch propre entre VTs (Ctrl+Alt+Fn), mais n'est plus bloquante pour
|
||||
afficher.
|
||||
|
||||
## Prochaine étape : phase 4 vraie
|
||||
|
||||
|
|
|
|||
BIN
docs/phase4-victory-1280x800.png
Normal file
BIN
docs/phase4-victory-1280x800.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
Loading…
Add table
Add a link
Reference in a new issue