feat(mirada): zwp_linux_dmabuf — clientes que pintan por GPU

Fase 1 del plan «shell»: para que carmen pueda hospedar a `shuma-shell`
(y a cualquier app GPUI o navegador acelerado) hace falta que los
clientes con GPU puedan compartir su búfer de vídeo. carmen sólo hablaba
`wl_shm` (búferes de software) — por eso `foot` corría pero las apps
GPUI salían en negro.

- `App` lleva un `DmabufState`; `impl DmabufHandler` con `dmabuf_imported`
  que acepta el búfer (el `GlesRenderer` ya importa DMA-BUF al componer,
  vía `ImportAll`, así que la validación real ocurre al pintar).
- `delegate_dmabuf!(App)`.
- `announce_dmabuf` crea el global con los formatos de `dmabuf_formats()`
  del renderer — se llama en ambos backends una vez creado el renderer.

Pendiente del plan: Fase 2 (`wlr-layer-shell`) y Fase 3 (modo launcher
de `shuma-shell` — barra + input + cajón de resultados).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 05:00:36 +00:00
parent b3b44e2c72
commit 7b5c583a98
5 changed files with 62 additions and 12 deletions
+6 -4
View File
@@ -169,10 +169,12 @@ En modo enlazado el socket de control lo abre el Cerebro (la app
`wl_compositor`, `xdg_shell` (toplevels y popups), `wl_shm`, `wl_seat` `wl_compositor`, `xdg_shell` (toplevels y popups), `wl_shm`, `wl_seat`
(teclado, y ratón en el backend DRM), `wl_output`, `wl_data_device` (teclado, y ratón en el backend DRM), `wl_output`, `wl_data_device`
(selección) y `xdg-decoration` — fuerza decoración del servidor y no (selección), `xdg-decoration` — fuerza decoración del servidor y no
dibuja ninguna, así las ventanas van sin barra de título. Composición dibuja ninguna, así las ventanas van sin barra de título — y
con `GlesRenderer` — en `winit` sobre la ventana, en `drm` con un `zwp_linux_dmabuf`, que deja conectarse a los clientes que pintan por
`DrmCompositor` por salida. GPU (apps GPUI, navegadores acelerados). Composición con `GlesRenderer`
— en `winit` sobre la ventana, en `drm` con un `DrmCompositor` por
salida.
Reusa `mirada-body` para la contabilidad de salidas y superficies, y Reusa `mirada-body` para la contabilidad de salidas y superficies, y
`mirada-link` para el cable hacia un Cerebro externo. Toda la lógica `mirada-link` para el cable hacia un Cerebro externo. Toda la lógica
@@ -710,6 +710,9 @@ pub fn run() -> Result<(), Box<dyn Error>> {
// 7 · El estado Wayland (Cerebro, teclado, keymap, control). // 7 · El estado Wayland (Cerebro, teclado, keymap, control).
println!("[7/8] armando el estado Wayland …"); println!("[7/8] armando el estado Wayland …");
let Setup { mut display, mut app, keymap_path, keymap_watch, ctl } = crate::build_app()?; let Setup { mut display, mut app, keymap_path, keymap_watch, ctl } = crate::build_app()?;
// Con el renderer ya creado, anuncia dmabuf — sin esto las apps que
// pintan por GPU (GPUI, navegadores acelerados) no pueden conectarse.
crate::announce_dmabuf(&mut app, &display.handle(), &renderer);
// La salida del Cerebro = el modo del monitor. // La salida del Cerebro = el modo del monitor.
let ev = app.body.add_output(0, mode_w as i32, mode_h as i32); let ev = app.body.add_output(0, mode_w as i32, mode_h as i32);
app.brain_feed(ev); app.brain_feed(ev);
+44 -3
View File
@@ -20,6 +20,7 @@
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::input::{InputEvent, KeyState, KeyboardKeyEvent}; use smithay::backend::input::{InputEvent, KeyState, KeyboardKeyEvent};
use smithay::backend::renderer::element::surface::{ use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement, render_elements_from_surface_tree, WaylandSurfaceRenderElement,
@@ -30,7 +31,7 @@ use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::utils::{ use smithay::backend::renderer::utils::{
draw_render_elements, on_commit_buffer_handler, with_renderer_surface_state, draw_render_elements, on_commit_buffer_handler, with_renderer_surface_state,
}; };
use smithay::backend::renderer::{Color32F, Frame, Renderer}; use smithay::backend::renderer::{Color32F, Frame, ImportDma, Renderer};
use smithay::backend::winit::{self, WinitEvent}; use smithay::backend::winit::{self, WinitEvent};
use smithay::input::keyboard::{xkb, FilterResult, KeyboardHandle, Keysym, ModifiersState}; use smithay::input::keyboard::{xkb, FilterResult, KeyboardHandle, Keysym, ModifiersState};
use smithay::input::pointer::{CursorImageStatus, CursorImageSurfaceData, PointerHandle}; use smithay::input::pointer::{CursorImageStatus, CursorImageSurfaceData, PointerHandle};
@@ -47,6 +48,7 @@ use smithay::reexports::winit::platform::pump_events::PumpStatus;
use smithay::utils::{Rectangle, SERIAL_COUNTER}; use smithay::utils::{Rectangle, SERIAL_COUNTER};
use smithay::utils::{Serial, Transform}; use smithay::utils::{Serial, Transform};
use smithay::wayland::buffer::BufferHandler; use smithay::wayland::buffer::BufferHandler;
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
use smithay::wayland::compositor::{ use smithay::wayland::compositor::{
with_states, with_surface_tree_downward, CompositorClientState, CompositorHandler, with_states, with_surface_tree_downward, CompositorClientState, CompositorHandler,
CompositorState, SurfaceAttributes, TraversalAction, CompositorState, SurfaceAttributes, TraversalAction,
@@ -63,8 +65,8 @@ use smithay::wayland::shell::xdg::{
use smithay::wayland::output::OutputHandler; use smithay::wayland::output::OutputHandler;
use smithay::wayland::shm::{ShmHandler, ShmState}; use smithay::wayland::shm::{ShmHandler, ShmState};
use smithay::{ use smithay::{
delegate_compositor, delegate_data_device, delegate_output, delegate_seat, delegate_shm, delegate_compositor, delegate_data_device, delegate_dmabuf, delegate_output, delegate_seat,
delegate_xdg_decoration, delegate_xdg_shell, delegate_shm, delegate_xdg_decoration, delegate_xdg_shell,
}; };
use mirada_body::{BodyOp, BodyState}; use mirada_body::{BodyOp, BodyState};
@@ -133,6 +135,9 @@ struct App {
compositor_state: CompositorState, compositor_state: CompositorState,
xdg_shell_state: XdgShellState, xdg_shell_state: XdgShellState,
shm_state: ShmState, shm_state: ShmState,
/// Estado de `zwp_linux_dmabuf` — deja que los clientes con GPU
/// (apps GPUI, navegadores acelerados) compartan búferes de vídeo.
dmabuf_state: DmabufState,
seat_state: SeatState<Self>, seat_state: SeatState<Self>,
data_device_state: DataDeviceState, data_device_state: DataDeviceState,
seat: Seat<Self>, seat: Seat<Self>,
@@ -340,6 +345,24 @@ impl BufferHandler for App {
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {} fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
} }
impl DmabufHandler for App {
fn dmabuf_state(&mut self) -> &mut DmabufState {
&mut self.dmabuf_state
}
/// Un cliente importó un DMA-BUF. El `GlesRenderer` lo importará de
/// verdad al componer; aquí basta con aceptarlo — un búfer inválido
/// sólo dejará en blanco ese cuadro de esa ventana.
fn dmabuf_imported(
&mut self,
_global: &DmabufGlobal,
_dmabuf: Dmabuf,
notifier: ImportNotifier,
) {
let _ = notifier.successful::<App>();
}
}
impl ShmHandler for App { impl ShmHandler for App {
fn shm_state(&self) -> &ShmState { fn shm_state(&self) -> &ShmState {
&self.shm_state &self.shm_state
@@ -494,6 +517,7 @@ impl OutputHandler for App {}
delegate_compositor!(App); delegate_compositor!(App);
delegate_xdg_shell!(App); delegate_xdg_shell!(App);
delegate_xdg_decoration!(App); delegate_xdg_decoration!(App);
delegate_dmabuf!(App);
delegate_shm!(App); delegate_shm!(App);
delegate_seat!(App); delegate_seat!(App);
delegate_data_device!(App); delegate_data_device!(App);
@@ -714,6 +738,19 @@ fn announce_output(
output output
} }
/// Anuncia el global `zwp_linux_dmabuf` con los formatos que el
/// `GlesRenderer` admite. Hay que llamarlo una vez creado el renderer
/// (no antes: los formatos salen de él) — así las apps que pintan por
/// GPU (GPUI, navegadores acelerados) pueden ser clientes del compositor.
fn announce_dmabuf(app: &mut App, dh: &DisplayHandle, renderer: &GlesRenderer) {
let formats: Vec<_> = renderer.dmabuf_formats().into_iter().collect();
println!(
"mirada-compositor · dmabuf: {} formato(s) anunciado(s).",
formats.len()
);
app.dmabuf_state.create_global::<App>(dh, formats);
}
/// Lo que comparten los dos backends gráficos: el `Display` de Wayland, /// Lo que comparten los dos backends gráficos: el `Display` de Wayland,
/// el `App` ya armado y la maquinaria de keymap y control. /// el `App` ya armado y la maquinaria de keymap y control.
struct Setup { struct Setup {
@@ -767,6 +804,7 @@ fn build_app() -> Result<Setup, Box<dyn std::error::Error>> {
compositor_state: CompositorState::new::<App>(&dh), compositor_state: CompositorState::new::<App>(&dh),
xdg_shell_state: XdgShellState::new::<App>(&dh), xdg_shell_state: XdgShellState::new::<App>(&dh),
shm_state: ShmState::new::<App>(&dh, Vec::new()), shm_state: ShmState::new::<App>(&dh, Vec::new()),
dmabuf_state: DmabufState::new(),
seat_state, seat_state,
data_device_state: DataDeviceState::new::<App>(&dh), data_device_state: DataDeviceState::new::<App>(&dh),
seat, seat,
@@ -878,6 +916,9 @@ fn run_winit() -> Result<(), Box<dyn std::error::Error>> {
let start = Instant::now(); let start = Instant::now();
let mut clients = Vec::new(); let mut clients = Vec::new();
// Con el renderer ya creado, anuncia dmabuf (clientes con GPU).
announce_dmabuf(&mut state, &display.handle(), backend.renderer());
// Salida inicial = el tamaño de la ventana winit. // Salida inicial = el tamaño de la ventana winit.
let win_size = backend.window_size(); let win_size = backend.window_size();
let _wl_output = announce_output(&display.handle(), "winit", win_size.w, win_size.h, 60_000); let _wl_output = announce_output(&display.handle(), "winit", win_size.w, win_size.h, 60_000);
+7 -4
View File
@@ -193,10 +193,13 @@ manual) y `mirada-ctl` (CLI, probado vía el ejemplo `headless-ctl`).
El **Cuerpo** ya existe: `mirada-compositor` es un compositor Wayland El **Cuerpo** ya existe: `mirada-compositor` es un compositor Wayland
teselante real sobre `smithay`. Habla `wl_compositor`/`xdg_shell`/ teselante real sobre `smithay`. Habla `wl_compositor`/`xdg_shell`/
`wl_shm`/`wl_seat`/`wl_output`/`wl_data_device`/`xdg-decoration`, compone `wl_shm`/`wl_seat`/`wl_output`/`wl_data_device`/`xdg-decoration`/
las superficies de los clientes con `GlesRenderer` y aplica la geometría `zwp_linux_dmabuf`, compone las superficies de los clientes con
del Cerebro. Fuerza decoración `ServerSide` y no dibuja ninguna: las `GlesRenderer` y aplica la geometría del Cerebro. Fuerza decoración
ventanas teseladas van sin marco (nada de barras de título de cliente). `ServerSide` y no dibuja ninguna: las ventanas teseladas van sin marco
(nada de barras de título de cliente). Con `zwp_linux_dmabuf` los
clientes que pintan por GPU (apps GPUI, navegadores acelerados) pueden
conectarse — el `GlesRenderer` importa sus búferes DMA-BUF al componer.
Reusa `mirada-body` (contabilidad) y `mirada-link` (cable). Dos modos de Reusa `mirada-body` (contabilidad) y `mirada-link` (cable). Dos modos de
Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET` Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET`
→ la app `mirada`). → la app `mirada`).
+2 -1
View File
@@ -1000,7 +1000,8 @@
WAYLAND_DISPLAY=wayland-1 foot # lanza clientes contra él (imprime su WAYLAND_DISPLAY al arrancar) WAYLAND_DISPLAY=wayland-1 foot # lanza clientes contra él (imprime su WAYLAND_DISPLAY al arrancar)
MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada-compositor # enlazado: la app mirada (Cerebro GPUI) decide la geometría MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada-compositor # enlazado: la app mirada (Cerebro GPUI) decide la geometría
cargo run -p mirada-compositor -- --drm # nativo sobre TTY (MIRADA_STARTUP=foot lanza un cliente al arrancar) cargo run -p mirada-compositor -- --drm # nativo sobre TTY (MIRADA_STARTUP=foot lanza un cliente al arrancar)
Habla wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device; compone con GlesRenderer. Reusa mirada-body y mirada-link. Habla wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device/xdg-decoration/zwp_linux_dmabuf; compone con GlesRenderer.
dmabuf permite clientes que pintan por GPU (apps GPUI, navegadores acelerados). Reusa mirada-body y mirada-link.
En --drm el foco sigue al puntero y clics/rueda van a la ventana debajo; el cursor toma la forma del cliente. En --drm el foco sigue al puntero y clics/rueda van a la ventana debajo; el cursor toma la forma del cliente.
Cada ventana lleva un marco fino: azul la enfocada, gris las demás. Cada ventana lleva un marco fino: azul la enfocada, gris las demás.
Super+arrastre mueve la ventana (botón izq.) o la redimensiona (der.) — al arrastrarla pasa a flotar. Super+arrastre mueve la ventana (botón izq.) o la redimensiona (der.) — al arrastrarla pasa a flotar.