230 lines
8.3 KiB
Markdown
230 lines
8.3 KiB
Markdown
|
|
# Phase 6 — Compositor core : résultats
|
|||
|
|
|
|||
|
|
> Document produit le 2026-05-09 dans le cadre du plan directeur
|
|||
|
|
> `REDOX_COSMIC_XWAYLAND_RS_PLAN.md`.
|
|||
|
|
>
|
|||
|
|
> **Périmètre 6.1-6.3** : structures core, pipeline de composition,
|
|||
|
|
> intégration runtime display + input + composition.
|
|||
|
|
>
|
|||
|
|
> **Hors scope (6.4)** : frontend Wayland (wl_compositor / wl_shm /
|
|||
|
|
> xdg-shell), damage tracking, frame callbacks.
|
|||
|
|
|
|||
|
|
## Verdict 6.1-6.3
|
|||
|
|
|
|||
|
|
**✅ Cœur compositor fonctionnel et intégré.**
|
|||
|
|
|
|||
|
|
- 27 tests unitaires sur `compositor-core` (12 structures + 11 composition + 4 hit_test)
|
|||
|
|
- `RedoxOutput` impl `Framebuffer` → composition directe dans le framebuffer Redox
|
|||
|
|
- Bin d'intégration `redox-wl-test-compose-static` qui combine display + input + compositor
|
|||
|
|
|
|||
|
|
Captures preuves :
|
|||
|
|
|
|||
|
|
| Frame | Z-order (bottom → top) | PNG |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Initial (3 surfaces créées) | red, green, blue | `phase6-3-default-z.png` |
|
|||
|
|
| Après `sendkey 1` (raise red) | green, blue, red | `phase6-3-red-top.png` |
|
|||
|
|
| Après `sendkey 2` (raise green) | blue, red, green | `phase6-3-green-top.png` |
|
|||
|
|
| Après `sendkey 3` (raise blue) | red, green, blue | `phase6-3-blue-top.png` |
|
|||
|
|
|
|||
|
|
Chaque pression de touche déclenche `registry.raise(id)` puis
|
|||
|
|
`registry.compose_into(&mut output)` puis `output.present_with_takeover()`,
|
|||
|
|
le tout dans le même binaire qui consomme aussi les events via
|
|||
|
|
`InputBackend`.
|
|||
|
|
|
|||
|
|
## Architecture finale 6.3
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────────────────────┐
|
|||
|
|
│ redox-wl-test-compose-static (binaire) │
|
|||
|
|
│ │
|
|||
|
|
│ RedoxOutput ──────┐ │
|
|||
|
|
│ (display) │ │
|
|||
|
|
│ │ │ Arc<ConsumerHandle> │
|
|||
|
|
│ │ impl Framebuffer│ │
|
|||
|
|
│ │ ▼ │
|
|||
|
|
│ │ InputBackend ──> InputEvent │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ ▼ ▼ │
|
|||
|
|
│ SurfaceRegistry ◄── (raise, hit_test, modify) │
|
|||
|
|
│ │ │
|
|||
|
|
│ │ compose_into(&mut output) │
|
|||
|
|
│ ▼ │
|
|||
|
|
│ pixels écrits dans le framebuffer Redox │
|
|||
|
|
│ │ │
|
|||
|
|
│ └──> present_with_takeover │
|
|||
|
|
└──────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Dépendances** (graph propre, pas de cycle) :
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
redox-wl-compositor-core ← lib pure Rust, sans dep externe
|
|||
|
|
▲
|
|||
|
|
├──── redox-wl-display (impl Framebuffer for RedoxOutput)
|
|||
|
|
│
|
|||
|
|
└──── redox-wl-test-compose-static (bin)
|
|||
|
|
▲
|
|||
|
|
└──── redox-wl-input (lib)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## API publique 6.1-6.3
|
|||
|
|
|
|||
|
|
### Types core
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
pub struct SurfaceId(u64);
|
|||
|
|
|
|||
|
|
pub struct SurfaceBuffer {
|
|||
|
|
pub pixels: Arc<Vec<u32>>, // ARGB8888
|
|||
|
|
pub width: u32,
|
|||
|
|
pub height: u32,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pub struct SurfaceState {
|
|||
|
|
pub x: i32,
|
|||
|
|
pub y: i32,
|
|||
|
|
pub buffer: Option<SurfaceBuffer>,
|
|||
|
|
pub visible: bool,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pub struct Surface { /* id, current, pending */ }
|
|||
|
|
impl Surface {
|
|||
|
|
pub fn id(&self) -> SurfaceId;
|
|||
|
|
pub fn current(&self) -> &SurfaceState;
|
|||
|
|
pub fn pending(&self) -> &SurfaceState;
|
|||
|
|
pub fn pending_mut(&mut self) -> &mut SurfaceState;
|
|||
|
|
pub fn commit(&mut self);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `SurfaceRegistry`
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
pub struct SurfaceRegistry { /* ... */ }
|
|||
|
|
impl SurfaceRegistry {
|
|||
|
|
pub fn create(&mut self) -> SurfaceId;
|
|||
|
|
pub fn destroy(&mut self, id: SurfaceId) -> bool;
|
|||
|
|
pub fn raise(&mut self, id: SurfaceId);
|
|||
|
|
pub fn get(&self, id: SurfaceId) -> Option<&Surface>;
|
|||
|
|
pub fn get_mut(&mut self, id: SurfaceId) -> Option<&mut Surface>;
|
|||
|
|
pub fn commit(&mut self, id: SurfaceId) -> bool;
|
|||
|
|
pub fn modify_pending<F>(&mut self, id: SurfaceId, f: F) -> bool;
|
|||
|
|
|
|||
|
|
pub fn iter_z_order_back_to_front(&self) -> impl Iterator<Item = &Surface>;
|
|||
|
|
pub fn iter_z_order_front_to_back(&self) -> impl Iterator<Item = &Surface>;
|
|||
|
|
pub fn hit_test(&self, x: i32, y: i32) -> Option<SurfaceId>;
|
|||
|
|
pub fn compose_into<F: Framebuffer + ?Sized>(&self, target: &mut F);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Trait `Framebuffer`
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
pub trait Framebuffer {
|
|||
|
|
fn width(&self) -> u32;
|
|||
|
|
fn height(&self) -> u32;
|
|||
|
|
fn pixels_mut(&mut self) -> &mut [u32];
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Implémenté pour `RedoxOutput` dans `redox-wl-display`. Le bin d'intégration
|
|||
|
|
peut faire `registry.compose_into(&mut output)` directement.
|
|||
|
|
|
|||
|
|
## Tests unitaires (27 total, 0.00s natif)
|
|||
|
|
|
|||
|
|
| Catégorie | # | Couverture |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Surface registry | 6 | create/destroy/raise (idempotent, unknown, etc.) |
|
|||
|
|
| Pending/current state | 2 | modification + commit + isolation |
|
|||
|
|
| Composition | 9 | empty, fullscreen, partiel, clipping (4 bords), invisible, sans buffer, z-order, current vs pending |
|
|||
|
|
| Hit testing | 4 | topmost, skip invisible, after raise, hors écran |
|
|||
|
|
| End-to-end | 2 | iter z-order, workflow compositor typique |
|
|||
|
|
| **Buffer construction** | 1 | `new_filled` produit bonne taille + couleur |
|
|||
|
|
|
|||
|
|
Compile aussi pour `x86_64-unknown-redox` (pure Rust, aucune dep system).
|
|||
|
|
|
|||
|
|
## Méthode validation runtime
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Build
|
|||
|
|
cd crates/redox-wl-test-compose-static && redoxer build --release
|
|||
|
|
|
|||
|
|
# Push dans image
|
|||
|
|
mount image via redoxfs
|
|||
|
|
cp binary into /usr/bin/
|
|||
|
|
modify init.d/20_orbital → nowait VT=2 redox-wl-test-compose-static
|
|||
|
|
clear init.d/30_console
|
|||
|
|
unmount
|
|||
|
|
|
|||
|
|
# Boot QEMU headless avec capture
|
|||
|
|
qemu ... -display none -monitor unix:/tmp/qmp.sock,...
|
|||
|
|
sleep 2 && sendkey ret # bootloader
|
|||
|
|
sleep 13 # boot complete
|
|||
|
|
screendump /tmp/frame-1.ppm # initial state
|
|||
|
|
sendkey 1 && sleep 1 && screendump frame-2.ppm # red top
|
|||
|
|
sendkey 2 && sleep 1 && screendump frame-3.ppm # green top
|
|||
|
|
sendkey 3 && sleep 1 && screendump frame-4.ppm # blue top
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Les 4 captures sont visibles dans `docs/phase6-3-*.png`.
|
|||
|
|
|
|||
|
|
## Limitations / hors scope
|
|||
|
|
|
|||
|
|
### Pas de damage tracking
|
|||
|
|
Chaque `compose_into()` rerend les surfaces complètement. Pour 3
|
|||
|
|
surfaces ARGB de ~400x300, c'est ~720 KiB recopiés à chaque event.
|
|||
|
|
Acceptable (on est largement sous le ms côté CPU). À reconsidérer
|
|||
|
|
en 6.4 quand `wl_surface.damage_buffer` arrivera côté frontend.
|
|||
|
|
|
|||
|
|
### Pas de blending alpha
|
|||
|
|
`compose_into` overwrite les pixels. Une surface ARGB avec α<255 est
|
|||
|
|
traitée comme opaque (l'alpha est ignoré au niveau de la copie).
|
|||
|
|
Pour Wayland natif on aura besoin de blending pour les decorations
|
|||
|
|
côté client transparentes. Reportable à 6.4 ou 6.5.
|
|||
|
|
|
|||
|
|
### Pas de cursor visible
|
|||
|
|
Le bin d'intégration ne dessine pas de curseur. Le pointeur souris
|
|||
|
|
QEMU n'apparaît donc pas à l'écran. Hit-test fonctionne (on a vu
|
|||
|
|
les events `PointerButton`) mais sans feedback visuel. Add à phase
|
|||
|
|
7 (curseur stable).
|
|||
|
|
|
|||
|
|
### Hit-test au clic non testé visuellement
|
|||
|
|
Le test runtime utilise les touches '1'/'2'/'3' pour raise. Les
|
|||
|
|
events `PointerMotion` n'arrivent pas dans la config QEMU minimale
|
|||
|
|
(pas de `-device usb-tablet`). Le hit-test fonctionne sur les coords
|
|||
|
|
qu'on garde côté binaire ; à valider avec un vrai input mouse en 7.
|
|||
|
|
|
|||
|
|
## Code source
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
crates/redox-wl-compositor-core/ # lib pure Rust (450 lignes + 27 tests)
|
|||
|
|
├── Cargo.toml
|
|||
|
|
└── src/lib.rs
|
|||
|
|
|
|||
|
|
crates/redox-wl-display/ # MODIFIÉ
|
|||
|
|
├── Cargo.toml # + dep redox-wl-compositor-core
|
|||
|
|
└── src/lib.rs # + impl Framebuffer for RedoxOutput
|
|||
|
|
|
|||
|
|
crates/redox-wl-test-compose-static/ # bin d'intégration (190 lignes)
|
|||
|
|
├── Cargo.toml
|
|||
|
|
└── src/main.rs
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Suite phase 6.4
|
|||
|
|
|
|||
|
|
- Ajouter dep `wayland-server` au futur `redox-wl-wayland-frontend`
|
|||
|
|
- Mapper `wl_compositor.create_surface` → `registry.create()`
|
|||
|
|
- Mapper `wl_shm_pool.create_buffer` → wrapper sur mmap'd region
|
|||
|
|
- Mapper `wl_surface.attach` → `state.buffer = Some(...)`
|
|||
|
|
- Mapper `wl_surface.commit` → `registry.commit(id)`
|
|||
|
|
- Mapper `wl_surface.damage_buffer` → suivi pour optimiser `compose_into`
|
|||
|
|
- Mapper `wl_surface.frame` → callback après `present`
|
|||
|
|
- Tester avec un client Wayland simple (par ex. `weston-info` ou
|
|||
|
|
un client de test custom utilisant `wayland-client`)
|
|||
|
|
|
|||
|
|
Estimé 2-3 sessions de 2h.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*Fin du document de phase 6.1-6.3.*
|