|
|
|
@@ -0,0 +1,823 @@
|
|
|
|
|
//! llimphi-hal — Puente al Silicio.
|
|
|
|
|
//!
|
|
|
|
|
//! Aísla el motor del sistema operativo. Pinta en ventana Wayland/X11
|
|
|
|
|
//! (vía `mirada` en producción, vía `winit` en dev) o framebuffer directo
|
|
|
|
|
//! del kernel `wawa` (TODO). Trait `Surface` abstracto + struct `Hal`
|
|
|
|
|
//! que posee Instance/Adapter/Device/Queue de wgpu.
|
|
|
|
|
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
pub use raw_window_handle;
|
|
|
|
|
pub use wgpu;
|
|
|
|
|
pub use winit;
|
|
|
|
|
|
|
|
|
|
use winit::window::Window;
|
|
|
|
|
|
|
|
|
|
/// Errores al adquirir un frame de la superficie.
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum SurfaceError {
|
|
|
|
|
Lost,
|
|
|
|
|
Outdated,
|
|
|
|
|
OutOfMemory,
|
|
|
|
|
Timeout,
|
|
|
|
|
Other(String),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for SurfaceError {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Lost => write!(f, "surface lost"),
|
|
|
|
|
Self::Outdated => write!(f, "surface outdated"),
|
|
|
|
|
Self::OutOfMemory => write!(f, "surface out of memory"),
|
|
|
|
|
Self::Timeout => write!(f, "surface timeout"),
|
|
|
|
|
Self::Other(s) => write!(f, "surface error: {s}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::error::Error for SurfaceError {}
|
|
|
|
|
|
|
|
|
|
/// Errores al construir Hal o crear una Surface.
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum HalError {
|
|
|
|
|
NoAdapter,
|
|
|
|
|
RequestDevice(String),
|
|
|
|
|
CreateSurface(String),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for HalError {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Self::NoAdapter => write!(f, "no GPU adapter available"),
|
|
|
|
|
Self::RequestDevice(s) => write!(f, "request_device failed: {s}"),
|
|
|
|
|
Self::CreateSurface(s) => write!(f, "create_surface failed: {s}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::error::Error for HalError {}
|
|
|
|
|
|
|
|
|
|
/// Superficie gráfica donde llimphi pinta.
|
|
|
|
|
///
|
|
|
|
|
/// Vello (rasterizador) emite a una textura intermedia con storage binding
|
|
|
|
|
/// (la única forma portable: los formatos de swapchain no aceptan writes
|
|
|
|
|
/// de compute shader en muchos adapters). En `present` se blittea la
|
|
|
|
|
/// intermedia al swapchain real y se hace el flip.
|
|
|
|
|
///
|
|
|
|
|
/// Implementaciones:
|
|
|
|
|
/// - [`WinitSurface`]: ventana Wayland/X11 (dev + producción vía mirada).
|
|
|
|
|
/// - `WawaFramebufferSurface` (TODO): framebuffer directo del kernel wawa.
|
|
|
|
|
pub trait Surface {
|
|
|
|
|
fn size(&self) -> (u32, u32);
|
|
|
|
|
fn resize(&mut self, width: u32, height: u32);
|
|
|
|
|
/// Adquiere la textura intermedia donde el raster pinta este frame.
|
|
|
|
|
fn acquire(&mut self) -> Result<Frame, SurfaceError>;
|
|
|
|
|
/// Blittea la intermedia al swapchain y la presenta.
|
|
|
|
|
fn present(&mut self, frame: Frame, hal: &Hal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Frame en curso. `view()` devuelve la textura intermedia (Rgba8Unorm,
|
|
|
|
|
/// STORAGE_BINDING) lista para que vello escriba sobre ella.
|
|
|
|
|
pub struct Frame {
|
|
|
|
|
surface_texture: wgpu::SurfaceTexture,
|
|
|
|
|
surface_view: wgpu::TextureView,
|
|
|
|
|
intermediate_view: wgpu::TextureView,
|
|
|
|
|
/// Textura secundaria para la capa de overlay (menús/paleta/modal)
|
|
|
|
|
/// cuando hay contenido `gpu_paint` que la taparía. El overlay se
|
|
|
|
|
/// rasteriza acá con fondo transparente y luego se compone con
|
|
|
|
|
/// alpha SOBRE la intermedia (que ya tiene UI + video). Ver
|
|
|
|
|
/// [`OverlayCompositor`] y el eventloop de `llimphi-ui`.
|
|
|
|
|
overlay_view: wgpu::TextureView,
|
|
|
|
|
width: u32,
|
|
|
|
|
height: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Frame {
|
|
|
|
|
pub fn view(&self) -> &wgpu::TextureView {
|
|
|
|
|
&self.intermediate_view
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vista de la textura de overlay (mismo tamaño y formato que la
|
|
|
|
|
/// intermedia). Sólo se usa en el camino de compositing del overlay.
|
|
|
|
|
pub fn overlay_view(&self) -> &wgpu::TextureView {
|
|
|
|
|
&self.overlay_view
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn size(&self) -> (u32, u32) {
|
|
|
|
|
(self.width, self.height)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Estado wgpu compartido. Una instancia por proceso. `Device` y `Queue`
|
|
|
|
|
/// son `Arc` internamente, así que clonar es barato.
|
|
|
|
|
pub struct Hal {
|
|
|
|
|
pub instance: wgpu::Instance,
|
|
|
|
|
pub adapter: wgpu::Adapter,
|
|
|
|
|
pub device: wgpu::Device,
|
|
|
|
|
pub queue: wgpu::Queue,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Hal {
|
|
|
|
|
/// Construye Hal pidiendo un adapter compatible con una surface dada
|
|
|
|
|
/// (recomendado: pasar `Some(&surface)` para garantizar que el adapter
|
|
|
|
|
/// elegido sabe presentar a esa surface).
|
|
|
|
|
pub async fn new(
|
|
|
|
|
compatible_surface: Option<&wgpu::Surface<'static>>,
|
|
|
|
|
) -> Result<Self, HalError> {
|
|
|
|
|
let opts = wgpu::RequestAdapterOptions {
|
|
|
|
|
power_preference: wgpu::PowerPreference::HighPerformance,
|
|
|
|
|
force_fallback_adapter: false,
|
|
|
|
|
compatible_surface,
|
|
|
|
|
};
|
|
|
|
|
// Preferimos backends PRIMARY (Vulkan/Metal/DX12). El backend GL de
|
|
|
|
|
// wgpu sobre Mesa/Wayland tiene un bug de teardown: al soltar la
|
|
|
|
|
// instancia, `eglTerminate` marshalea sobre una conexión Wayland ya
|
|
|
|
|
// muerta (`wl_proxy_marshal`) y revienta con SIGSEGV. Con
|
|
|
|
|
// `Backends::all()` (el default), wgpu puede elegir GL aun habiendo
|
|
|
|
|
// Vulkan, y la app crashea al cerrar/teardown. Forzamos PRIMARY; si la
|
|
|
|
|
// máquina no tiene Vulkan/Metal/DX12 (VM vieja, etc.) caemos a todos
|
|
|
|
|
// los backends —incluido GL— para no dejarla sin gráficos. En el
|
|
|
|
|
// camino de escritorio `compatible_surface` es `None` (la surface se
|
|
|
|
|
// crea después contra esta misma instancia), así que cambiar de
|
|
|
|
|
// instancia aquí es seguro.
|
|
|
|
|
let primary = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
|
|
|
|
backends: wgpu::Backends::PRIMARY,
|
|
|
|
|
..Default::default()
|
|
|
|
|
});
|
|
|
|
|
let (instance, adapter) = match primary.request_adapter(&opts).await {
|
|
|
|
|
Some(a) => (primary, a),
|
|
|
|
|
None => {
|
|
|
|
|
let all = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
|
|
|
|
|
let a = all.request_adapter(&opts).await.ok_or(HalError::NoAdapter)?;
|
|
|
|
|
(all, a)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// `Limits::default()` cubre los 5 storage buffers/stage que vello
|
|
|
|
|
// necesita. `downlevel_defaults()` solo expone 4 y rompe el raster.
|
|
|
|
|
// Si el adapter no lo aguanta, `using_resolution` recorta lo recortable
|
|
|
|
|
// (texturas/buffers grandes) preservando los conteos mínimos.
|
|
|
|
|
let limits = wgpu::Limits::default().using_resolution(adapter.limits());
|
|
|
|
|
let (device, queue) = adapter
|
|
|
|
|
.request_device(
|
|
|
|
|
&wgpu::DeviceDescriptor {
|
|
|
|
|
label: Some("llimphi-hal-device"),
|
|
|
|
|
required_features: wgpu::Features::empty(),
|
|
|
|
|
required_limits: limits,
|
|
|
|
|
memory_hints: wgpu::MemoryHints::Performance,
|
|
|
|
|
},
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| HalError::RequestDevice(e.to_string()))?;
|
|
|
|
|
Ok(Self {
|
|
|
|
|
instance,
|
|
|
|
|
adapter,
|
|
|
|
|
device,
|
|
|
|
|
queue,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Construye el `Hal` **y** una [`RawSurface`] a la vez, eligiendo el adaptador
|
|
|
|
|
/// **compatible con esa surface** — el dispositivo que el compositor sabe
|
|
|
|
|
/// presentar. Es el camino correcto para el backend layer-shell de `pata`.
|
|
|
|
|
///
|
|
|
|
|
/// El problema que resuelve: en sistemas multi-GPU (Optimus), pedir el
|
|
|
|
|
/// adaptador sin pista de surface (`new(None)` con `HighPerformance`) puede
|
|
|
|
|
/// elegir la dGPU mientras el compositor compone en la iGPU → los dmabuf
|
|
|
|
|
/// cruzan dispositivos y `get_capabilities` devuelve 0 formatos (la surface
|
|
|
|
|
/// "no expone formatos"). Pasar `compatible_surface` ata el adaptador al
|
|
|
|
|
/// dispositivo del compositor. Como la surface hace falta ANTES de pedir el
|
|
|
|
|
/// adaptador, y `new` crea la instancia internamente, este constructor une los
|
|
|
|
|
/// dos pasos.
|
|
|
|
|
///
|
|
|
|
|
/// `make_target` reconstruye el `SurfaceTargetUnsafe` cada vez que se llama
|
|
|
|
|
/// (los `RawHandle` son `Copy`): `create_surface_unsafe` consume el target y
|
|
|
|
|
/// puede que probemos dos instancias (PRIMARY y, si no hay adaptador, todos
|
|
|
|
|
/// los backends — el GL de Mesa/Wayland revienta en teardown, por eso PRIMARY
|
|
|
|
|
/// primero, igual que [`Hal::new`]).
|
|
|
|
|
///
|
|
|
|
|
/// # Safety
|
|
|
|
|
/// Los handles que produce `make_target` deben apuntar a objetos Wayland/…
|
|
|
|
|
/// vivos durante toda la vida de la `RawSurface` devuelta.
|
|
|
|
|
pub async unsafe fn new_for_raw_surface(
|
|
|
|
|
make_target: impl Fn() -> wgpu::SurfaceTargetUnsafe,
|
|
|
|
|
width: u32,
|
|
|
|
|
height: u32,
|
|
|
|
|
) -> Result<(Self, RawSurface), HalError> {
|
|
|
|
|
// PRIMARY (Vulkan/Metal/DX12) primero; si no hay adaptador compatible, a
|
|
|
|
|
// todos los backends recreando instancia y surface.
|
|
|
|
|
let primary = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
|
|
|
|
backends: wgpu::Backends::PRIMARY,
|
|
|
|
|
..Default::default()
|
|
|
|
|
});
|
|
|
|
|
let prim_surface = unsafe { primary.create_surface_unsafe(make_target()) }
|
|
|
|
|
.map_err(|e| HalError::CreateSurface(e.to_string()))?;
|
|
|
|
|
let prim_adapter = primary
|
|
|
|
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
|
|
|
|
power_preference: wgpu::PowerPreference::HighPerformance,
|
|
|
|
|
force_fallback_adapter: false,
|
|
|
|
|
compatible_surface: Some(&prim_surface),
|
|
|
|
|
})
|
|
|
|
|
.await;
|
|
|
|
|
let (instance, adapter, wgpu_surface) = match prim_adapter {
|
|
|
|
|
Some(a) => (primary, a, prim_surface),
|
|
|
|
|
None => {
|
|
|
|
|
let all = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
|
|
|
|
|
let surface = unsafe { all.create_surface_unsafe(make_target()) }
|
|
|
|
|
.map_err(|e| HalError::CreateSurface(e.to_string()))?;
|
|
|
|
|
let a = all
|
|
|
|
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
|
|
|
|
power_preference: wgpu::PowerPreference::HighPerformance,
|
|
|
|
|
force_fallback_adapter: false,
|
|
|
|
|
compatible_surface: Some(&surface),
|
|
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.ok_or(HalError::NoAdapter)?;
|
|
|
|
|
(all, a, surface)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let limits = wgpu::Limits::default().using_resolution(adapter.limits());
|
|
|
|
|
let (device, queue) = adapter
|
|
|
|
|
.request_device(
|
|
|
|
|
&wgpu::DeviceDescriptor {
|
|
|
|
|
label: Some("llimphi-hal-device"),
|
|
|
|
|
required_features: wgpu::Features::empty(),
|
|
|
|
|
required_limits: limits,
|
|
|
|
|
memory_hints: wgpu::MemoryHints::Performance,
|
|
|
|
|
},
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| HalError::RequestDevice(e.to_string()))?;
|
|
|
|
|
let hal = Self {
|
|
|
|
|
instance,
|
|
|
|
|
adapter,
|
|
|
|
|
device,
|
|
|
|
|
queue,
|
|
|
|
|
};
|
|
|
|
|
let surface = RawSurface::from_surface(&hal, wgpu_surface, width, height)?;
|
|
|
|
|
Ok((hal, surface))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Surface basada en `winit::window::Window`. Mantiene una textura
|
|
|
|
|
/// intermedia `Rgba8Unorm` con storage binding (donde pinta vello) y
|
|
|
|
|
/// un `TextureBlitter` que la copia al swapchain al presentar.
|
|
|
|
|
pub struct WinitSurface {
|
|
|
|
|
_window: Arc<Window>,
|
|
|
|
|
surface: wgpu::Surface<'static>,
|
|
|
|
|
config: wgpu::SurfaceConfiguration,
|
|
|
|
|
device: wgpu::Device,
|
|
|
|
|
intermediate: wgpu::Texture,
|
|
|
|
|
intermediate_view: wgpu::TextureView,
|
|
|
|
|
/// Textura de la capa de overlay (ver [`Frame::overlay_view`]).
|
|
|
|
|
overlay: wgpu::Texture,
|
|
|
|
|
overlay_view: wgpu::TextureView,
|
|
|
|
|
blitter: wgpu::util::TextureBlitter,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const INTERMEDIATE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
|
|
|
|
|
|
|
|
|
|
impl WinitSurface {
|
|
|
|
|
/// Constructor "feliz": crea la `wgpu::Surface` internamente.
|
|
|
|
|
/// Conveniente en desktop donde la secuencia normal es
|
|
|
|
|
/// `Hal::new(None)` → `WinitSurface::new(hal, window)`. **En Android
|
|
|
|
|
/// usar [`WinitSurface::from_surface`]** — allí la surface debe
|
|
|
|
|
/// existir antes del `request_adapter(compatible_surface=Some(...))`,
|
|
|
|
|
/// y crearla dos veces sobre la misma `ANativeWindow` falla con
|
|
|
|
|
/// `ERROR_NATIVE_WINDOW_IN_USE_KHR`.
|
|
|
|
|
pub fn new(hal: &Hal, window: Arc<Window>) -> Result<Self, HalError> {
|
|
|
|
|
let surface = hal
|
|
|
|
|
.instance
|
|
|
|
|
.create_surface(window.clone())
|
|
|
|
|
.map_err(|e| HalError::CreateSurface(e.to_string()))?;
|
|
|
|
|
Self::from_surface(hal, window, surface)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Constructor reutilizable: arma el `WinitSurface` envolviendo una
|
|
|
|
|
/// `wgpu::Surface` ya creada por el caller. Necesario en Android
|
|
|
|
|
/// porque el orden allí es:
|
|
|
|
|
///
|
|
|
|
|
/// 1. `instance.create_surface(window)`
|
|
|
|
|
/// 2. `instance.request_adapter(compatible_surface=Some(&surface))`
|
|
|
|
|
/// 3. `adapter.request_device(...)`
|
|
|
|
|
/// 4. `WinitSurface::from_surface(hal, window, surface)`
|
|
|
|
|
///
|
|
|
|
|
/// — no se puede dropear la surface entre 2 y 4 ni recrearla, porque
|
|
|
|
|
/// Android reserva la `ANativeWindow` por VkSurface y rechaza un
|
|
|
|
|
/// segundo `vkCreateAndroidSurfaceKHR` sobre la misma ventana.
|
|
|
|
|
pub fn from_surface(
|
|
|
|
|
hal: &Hal,
|
|
|
|
|
window: Arc<Window>,
|
|
|
|
|
surface: wgpu::Surface<'static>,
|
|
|
|
|
) -> Result<Self, HalError> {
|
|
|
|
|
let size = window.inner_size();
|
|
|
|
|
let caps = surface.get_capabilities(&hal.adapter);
|
|
|
|
|
// Preferimos Bgra8Unorm o Rgba8Unorm (no sRGB) para que el blit
|
|
|
|
|
// desde la intermedia lineal preserve los valores tal cual.
|
|
|
|
|
let format = caps
|
|
|
|
|
.formats
|
|
|
|
|
.iter()
|
|
|
|
|
.copied()
|
|
|
|
|
.find(|f| matches!(f, wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm))
|
|
|
|
|
.unwrap_or(caps.formats[0]);
|
|
|
|
|
let config = wgpu::SurfaceConfiguration {
|
|
|
|
|
// El swapchain solo necesita render-attachment: vello no escribe
|
|
|
|
|
// directo, escribe a la intermedia y luego se blittea.
|
|
|
|
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
|
|
|
format,
|
|
|
|
|
width: size.width.max(1),
|
|
|
|
|
height: size.height.max(1),
|
|
|
|
|
present_mode: choose_present_mode(&caps),
|
|
|
|
|
desired_maximum_frame_latency: 2,
|
|
|
|
|
alpha_mode: caps.alpha_modes[0],
|
|
|
|
|
view_formats: vec![],
|
|
|
|
|
};
|
|
|
|
|
surface.configure(&hal.device, &config);
|
|
|
|
|
let (intermediate, intermediate_view) =
|
|
|
|
|
create_intermediate(&hal.device, config.width, config.height);
|
|
|
|
|
let (overlay, overlay_view) =
|
|
|
|
|
create_intermediate(&hal.device, config.width, config.height);
|
|
|
|
|
let blitter = wgpu::util::TextureBlitter::new(&hal.device, format);
|
|
|
|
|
Ok(Self {
|
|
|
|
|
_window: window,
|
|
|
|
|
surface,
|
|
|
|
|
config,
|
|
|
|
|
device: hal.device.clone(),
|
|
|
|
|
intermediate,
|
|
|
|
|
intermediate_view,
|
|
|
|
|
overlay,
|
|
|
|
|
overlay_view,
|
|
|
|
|
blitter,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn format(&self) -> wgpu::TextureFormat {
|
|
|
|
|
self.config.format
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Surface sobre una `wgpu::Surface` creada desde **handles raw** (sin
|
|
|
|
|
/// `winit::Window`): la usa el backend `wlr-layer-shell` de `pata` para pintar
|
|
|
|
|
/// en una *layer surface* de Wayland (barras/paneles al nivel de eww/waybar).
|
|
|
|
|
/// Misma mecánica que [`WinitSurface`] —intermedia `Rgba8Unorm` + blit al
|
|
|
|
|
/// swapchain— pero el tamaño se pasa explícito porque no hay ventana que
|
|
|
|
|
/// consultar. La `wgpu::Surface` la crea el caller (típicamente con
|
|
|
|
|
/// `instance.create_surface_unsafe` desde los punteros `wl_display`/`wl_surface`).
|
|
|
|
|
pub struct RawSurface {
|
|
|
|
|
surface: wgpu::Surface<'static>,
|
|
|
|
|
config: wgpu::SurfaceConfiguration,
|
|
|
|
|
device: wgpu::Device,
|
|
|
|
|
intermediate: wgpu::Texture,
|
|
|
|
|
intermediate_view: wgpu::TextureView,
|
|
|
|
|
overlay: wgpu::Texture,
|
|
|
|
|
overlay_view: wgpu::TextureView,
|
|
|
|
|
blitter: wgpu::util::TextureBlitter,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RawSurface {
|
|
|
|
|
/// Envuelve una `wgpu::Surface` ya creada, con el tamaño físico inicial.
|
|
|
|
|
pub fn from_surface(
|
|
|
|
|
hal: &Hal,
|
|
|
|
|
surface: wgpu::Surface<'static>,
|
|
|
|
|
width: u32,
|
|
|
|
|
height: u32,
|
|
|
|
|
) -> Result<Self, HalError> {
|
|
|
|
|
let caps = surface.get_capabilities(&hal.adapter);
|
|
|
|
|
let info = hal.adapter.get_info();
|
|
|
|
|
// Si la superficie no expone formatos, el compositor no la soporta por
|
|
|
|
|
// este backend (Vulkan/GL WSI): error claro en vez de un panic por
|
|
|
|
|
// indexar `formats[0]` sobre una lista vacía.
|
|
|
|
|
let format = match caps
|
|
|
|
|
.formats
|
|
|
|
|
.iter()
|
|
|
|
|
.copied()
|
|
|
|
|
.find(|f| matches!(f, wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm))
|
|
|
|
|
.or_else(|| caps.formats.first().copied())
|
|
|
|
|
{
|
|
|
|
|
Some(f) => f,
|
|
|
|
|
None => {
|
|
|
|
|
return Err(HalError::CreateSurface(format!(
|
|
|
|
|
"la superficie no expone formatos (adapter {:?}/{:?}): el compositor no la soporta por {:?} WSI",
|
|
|
|
|
info.backend, info.device_type, info.backend
|
|
|
|
|
)))
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let alpha_mode = caps
|
|
|
|
|
.alpha_modes
|
|
|
|
|
.first()
|
|
|
|
|
.copied()
|
|
|
|
|
.unwrap_or(wgpu::CompositeAlphaMode::Auto);
|
|
|
|
|
let config = wgpu::SurfaceConfiguration {
|
|
|
|
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
|
|
|
format,
|
|
|
|
|
width: width.max(1),
|
|
|
|
|
height: height.max(1),
|
|
|
|
|
present_mode: choose_present_mode(&caps),
|
|
|
|
|
desired_maximum_frame_latency: 2,
|
|
|
|
|
alpha_mode,
|
|
|
|
|
view_formats: vec![],
|
|
|
|
|
};
|
|
|
|
|
surface.configure(&hal.device, &config);
|
|
|
|
|
let (intermediate, intermediate_view) =
|
|
|
|
|
create_intermediate(&hal.device, config.width, config.height);
|
|
|
|
|
let (overlay, overlay_view) =
|
|
|
|
|
create_intermediate(&hal.device, config.width, config.height);
|
|
|
|
|
let blitter = wgpu::util::TextureBlitter::new(&hal.device, format);
|
|
|
|
|
Ok(Self {
|
|
|
|
|
surface,
|
|
|
|
|
config,
|
|
|
|
|
device: hal.device.clone(),
|
|
|
|
|
intermediate,
|
|
|
|
|
intermediate_view,
|
|
|
|
|
overlay,
|
|
|
|
|
overlay_view,
|
|
|
|
|
blitter,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn format(&self) -> wgpu::TextureFormat {
|
|
|
|
|
self.config.format
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Surface for RawSurface {
|
|
|
|
|
fn size(&self) -> (u32, u32) {
|
|
|
|
|
(self.config.width, self.config.height)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn resize(&mut self, width: u32, height: u32) {
|
|
|
|
|
let (w, h) = (width.max(1), height.max(1));
|
|
|
|
|
// Sin cambio de tamaño NO reconfiguramos. El backend layer-shell de `pata`
|
|
|
|
|
// llama a `resize` en cada cuadro (no tiene eventos de resize como winit);
|
|
|
|
|
// reconfigurar el swapchain por cuadro lo reconstruye una y otra vez, y en
|
|
|
|
|
// Vulkan WSI eso **destruye el `wl_buffer` recién presentado antes de que el
|
|
|
|
|
// compositor lo componga** — wlroots lo tolera, smithay (mirada) no, y la
|
|
|
|
|
// superficie queda en negro (el compositor ve `buffer=None`).
|
|
|
|
|
if self.config.width == w && self.config.height == h {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.config.width = w;
|
|
|
|
|
self.config.height = h;
|
|
|
|
|
self.surface.configure(&self.device, &self.config);
|
|
|
|
|
let (tex, view) = create_intermediate(&self.device, self.config.width, self.config.height);
|
|
|
|
|
self.intermediate = tex;
|
|
|
|
|
self.intermediate_view = view;
|
|
|
|
|
let (otex, oview) =
|
|
|
|
|
create_intermediate(&self.device, self.config.width, self.config.height);
|
|
|
|
|
self.overlay = otex;
|
|
|
|
|
self.overlay_view = oview;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn acquire(&mut self) -> Result<Frame, SurfaceError> {
|
|
|
|
|
let texture = match self.surface.get_current_texture() {
|
|
|
|
|
Ok(t) => t,
|
|
|
|
|
// El backend layer-shell no tiene un evento de resize que reconfigure
|
|
|
|
|
// el swapchain; si quedó obsoleto/perdido, lo reconstruimos aquí mismo
|
|
|
|
|
// y reintentamos una vez. Sin esto el panel quedaría en negro para
|
|
|
|
|
// siempre tras el primer `Outdated`.
|
|
|
|
|
Err(e @ (wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost)) => {
|
|
|
|
|
self.surface.configure(&self.device, &self.config);
|
|
|
|
|
self.surface.get_current_texture().map_err(|_| match e {
|
|
|
|
|
wgpu::SurfaceError::Lost => SurfaceError::Lost,
|
|
|
|
|
_ => SurfaceError::Outdated,
|
|
|
|
|
})?
|
|
|
|
|
}
|
|
|
|
|
Err(wgpu::SurfaceError::OutOfMemory) => return Err(SurfaceError::OutOfMemory),
|
|
|
|
|
Err(wgpu::SurfaceError::Timeout) => return Err(SurfaceError::Timeout),
|
|
|
|
|
Err(other) => return Err(SurfaceError::Other(format!("{other:?}"))),
|
|
|
|
|
};
|
|
|
|
|
let surface_view = texture
|
|
|
|
|
.texture
|
|
|
|
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
|
|
|
|
Ok(Frame {
|
|
|
|
|
surface_texture: texture,
|
|
|
|
|
surface_view,
|
|
|
|
|
intermediate_view: self.intermediate_view.clone(),
|
|
|
|
|
overlay_view: self.overlay_view.clone(),
|
|
|
|
|
width: self.config.width,
|
|
|
|
|
height: self.config.height,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn present(&mut self, frame: Frame, hal: &Hal) {
|
|
|
|
|
let mut encoder = hal.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
|
|
|
label: Some("llimphi-blit-raw"),
|
|
|
|
|
});
|
|
|
|
|
self.blitter.copy(
|
|
|
|
|
&hal.device,
|
|
|
|
|
&mut encoder,
|
|
|
|
|
&frame.intermediate_view,
|
|
|
|
|
&frame.surface_view,
|
|
|
|
|
);
|
|
|
|
|
hal.queue.submit(std::iter::once(encoder.finish()));
|
|
|
|
|
frame.surface_texture.present();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Elige el modo de presentación del swapchain.
|
|
|
|
|
///
|
|
|
|
|
/// Default: **Mailbox** si el driver lo expone, sino **Fifo**. La razón es
|
|
|
|
|
/// el cuelgue observado en las apps Llimphi (investigación 2026-05-30): con
|
|
|
|
|
/// `Fifo`/`AutoVsync`, `surface.get_current_texture()` **bloquea** esperando
|
|
|
|
|
/// el frame-callback del compositor Wayland — si el compositor no suelta un
|
|
|
|
|
/// buffer, el hilo del UI queda dormido (CPU baja, deadlock aparente).
|
|
|
|
|
/// `Mailbox` no bloquea (triple-buffer, descarta frames viejos), así que el
|
|
|
|
|
/// loop nunca se queda esperando al compositor. `Fifo` está garantizado por
|
|
|
|
|
/// spec como fallback.
|
|
|
|
|
///
|
|
|
|
|
/// Override por entorno para A/B sin recompilar (útil en la laptop con
|
|
|
|
|
/// display real): `LLIMPHI_PRESENT_MODE = fifo | mailbox | immediate |
|
|
|
|
|
/// fifo_relaxed`. Si el modo pedido no está soportado, se ignora y se aplica
|
|
|
|
|
/// el default.
|
|
|
|
|
fn choose_present_mode(caps: &wgpu::SurfaceCapabilities) -> wgpu::PresentMode {
|
|
|
|
|
use wgpu::PresentMode::{Fifo, FifoRelaxed, Immediate, Mailbox};
|
|
|
|
|
if let Ok(v) = std::env::var("LLIMPHI_PRESENT_MODE") {
|
|
|
|
|
let want = match v.trim().to_ascii_lowercase().as_str() {
|
|
|
|
|
"fifo" | "vsync" => Some(Fifo),
|
|
|
|
|
"fifo_relaxed" | "fiforelaxed" => Some(FifoRelaxed),
|
|
|
|
|
"mailbox" => Some(Mailbox),
|
|
|
|
|
"immediate" | "novsync" => Some(Immediate),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
if let Some(m) = want {
|
|
|
|
|
if caps.present_modes.contains(&m) {
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if caps.present_modes.contains(&Mailbox) {
|
|
|
|
|
Mailbox
|
|
|
|
|
} else {
|
|
|
|
|
Fifo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_intermediate(
|
|
|
|
|
device: &wgpu::Device,
|
|
|
|
|
width: u32,
|
|
|
|
|
height: u32,
|
|
|
|
|
) -> (wgpu::Texture, wgpu::TextureView) {
|
|
|
|
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
|
|
|
|
label: Some("llimphi-intermediate"),
|
|
|
|
|
size: wgpu::Extent3d {
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
depth_or_array_layers: 1,
|
|
|
|
|
},
|
|
|
|
|
mip_level_count: 1,
|
|
|
|
|
sample_count: 1,
|
|
|
|
|
dimension: wgpu::TextureDimension::D2,
|
|
|
|
|
format: INTERMEDIATE_FORMAT,
|
|
|
|
|
// STORAGE_BINDING: vello escribe via compute shader.
|
|
|
|
|
// TEXTURE_BINDING: el blitter la lee como sampler source.
|
|
|
|
|
// RENDER_ATTACHMENT: render passes con clear-only (sin vello)
|
|
|
|
|
// también escriben acá — desktop drivers lo tolerían sin este
|
|
|
|
|
// flag, Adreno con validación estricta rechaza el frame.
|
|
|
|
|
usage: wgpu::TextureUsages::STORAGE_BINDING
|
|
|
|
|
| wgpu::TextureUsages::TEXTURE_BINDING
|
|
|
|
|
| wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
|
|
|
view_formats: &[],
|
|
|
|
|
});
|
|
|
|
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
|
|
|
(texture, view)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Compositor de la capa de overlay: alpha-blittea una textura source (el
|
|
|
|
|
/// overlay rasterizado por vello sobre fondo transparente) SOBRE una textura
|
|
|
|
|
/// target (la intermedia, que ya tiene la UI principal + el video pintado por
|
|
|
|
|
/// `gpu_paint`). Resuelve el z-order: sin esto, el blit de `gpu_paint` (video)
|
|
|
|
|
/// queda encima de la capa vello del overlay y los menús se ven por debajo del
|
|
|
|
|
/// video.
|
|
|
|
|
///
|
|
|
|
|
/// Es un pase de pantalla completa (triángulo) que samplea el source y lo
|
|
|
|
|
/// emite con alpha-over. El factor de blend asume alpha **premultiplicado**
|
|
|
|
|
/// (lo que produce vello); si en pantalla los menús se ven con halos oscuros o
|
|
|
|
|
/// transparencia rara, exportar `LLIMPHI_OVERLAY_BLEND=straight` para usar
|
|
|
|
|
/// alpha recto sin recompilar.
|
|
|
|
|
pub struct OverlayCompositor {
|
|
|
|
|
pipeline: wgpu::RenderPipeline,
|
|
|
|
|
sampler: wgpu::Sampler,
|
|
|
|
|
bind_layout: wgpu::BindGroupLayout,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl OverlayCompositor {
|
|
|
|
|
pub fn new(device: &wgpu::Device) -> Self {
|
|
|
|
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
|
|
|
|
label: Some("llimphi-overlay-composite"),
|
|
|
|
|
source: wgpu::ShaderSource::Wgsl(OVERLAY_COMPOSITE_WGSL.into()),
|
|
|
|
|
});
|
|
|
|
|
let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
|
|
|
label: Some("llimphi-overlay-bgl"),
|
|
|
|
|
entries: &[
|
|
|
|
|
wgpu::BindGroupLayoutEntry {
|
|
|
|
|
binding: 0,
|
|
|
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
|
|
|
ty: wgpu::BindingType::Texture {
|
|
|
|
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
|
|
|
|
view_dimension: wgpu::TextureViewDimension::D2,
|
|
|
|
|
multisampled: false,
|
|
|
|
|
},
|
|
|
|
|
count: None,
|
|
|
|
|
},
|
|
|
|
|
wgpu::BindGroupLayoutEntry {
|
|
|
|
|
binding: 1,
|
|
|
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
|
|
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
|
|
|
|
count: None,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
|
|
|
|
label: Some("llimphi-overlay-pl"),
|
|
|
|
|
bind_group_layouts: &[&bind_layout],
|
|
|
|
|
push_constant_ranges: &[],
|
|
|
|
|
});
|
|
|
|
|
// Alpha-over. `src_factor` distingue premultiplicado (One) de recto
|
|
|
|
|
// (SrcAlpha); el resto es siempre OneMinusSrcAlpha.
|
|
|
|
|
let straight = std::env::var("LLIMPHI_OVERLAY_BLEND")
|
|
|
|
|
.map(|v| v.trim().eq_ignore_ascii_case("straight"))
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
let color_src = if straight {
|
|
|
|
|
wgpu::BlendFactor::SrcAlpha
|
|
|
|
|
} else {
|
|
|
|
|
wgpu::BlendFactor::One
|
|
|
|
|
};
|
|
|
|
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
|
|
|
|
label: Some("llimphi-overlay-pipe"),
|
|
|
|
|
layout: Some(&pipeline_layout),
|
|
|
|
|
vertex: wgpu::VertexState {
|
|
|
|
|
module: &shader,
|
|
|
|
|
entry_point: Some("vs"),
|
|
|
|
|
buffers: &[],
|
|
|
|
|
compilation_options: Default::default(),
|
|
|
|
|
},
|
|
|
|
|
fragment: Some(wgpu::FragmentState {
|
|
|
|
|
module: &shader,
|
|
|
|
|
entry_point: Some("fs"),
|
|
|
|
|
targets: &[Some(wgpu::ColorTargetState {
|
|
|
|
|
format: INTERMEDIATE_FORMAT,
|
|
|
|
|
blend: Some(wgpu::BlendState {
|
|
|
|
|
color: wgpu::BlendComponent {
|
|
|
|
|
src_factor: color_src,
|
|
|
|
|
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
|
|
|
|
operation: wgpu::BlendOperation::Add,
|
|
|
|
|
},
|
|
|
|
|
alpha: wgpu::BlendComponent {
|
|
|
|
|
src_factor: wgpu::BlendFactor::One,
|
|
|
|
|
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
|
|
|
|
operation: wgpu::BlendOperation::Add,
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
write_mask: wgpu::ColorWrites::ALL,
|
|
|
|
|
})],
|
|
|
|
|
compilation_options: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
primitive: wgpu::PrimitiveState::default(),
|
|
|
|
|
depth_stencil: None,
|
|
|
|
|
multisample: wgpu::MultisampleState::default(),
|
|
|
|
|
multiview: None,
|
|
|
|
|
cache: None,
|
|
|
|
|
});
|
|
|
|
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
|
|
|
|
label: Some("llimphi-overlay-sampler"),
|
|
|
|
|
..Default::default()
|
|
|
|
|
});
|
|
|
|
|
OverlayCompositor {
|
|
|
|
|
pipeline,
|
|
|
|
|
sampler,
|
|
|
|
|
bind_layout,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Compone `source` (overlay con fondo transparente) sobre `target` (la
|
|
|
|
|
/// intermedia), preservando el contenido previo del target (LoadOp::Load)
|
|
|
|
|
/// y mezclando con alpha. Graba un render pass en `encoder`.
|
|
|
|
|
pub fn composite(
|
|
|
|
|
&self,
|
|
|
|
|
device: &wgpu::Device,
|
|
|
|
|
encoder: &mut wgpu::CommandEncoder,
|
|
|
|
|
target: &wgpu::TextureView,
|
|
|
|
|
source: &wgpu::TextureView,
|
|
|
|
|
) {
|
|
|
|
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
|
|
|
label: Some("llimphi-overlay-bg"),
|
|
|
|
|
layout: &self.bind_layout,
|
|
|
|
|
entries: &[
|
|
|
|
|
wgpu::BindGroupEntry {
|
|
|
|
|
binding: 0,
|
|
|
|
|
resource: wgpu::BindingResource::TextureView(source),
|
|
|
|
|
},
|
|
|
|
|
wgpu::BindGroupEntry {
|
|
|
|
|
binding: 1,
|
|
|
|
|
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
|
|
|
label: Some("llimphi-overlay-composite-pass"),
|
|
|
|
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
|
|
|
view: target,
|
|
|
|
|
resolve_target: None,
|
|
|
|
|
ops: wgpu::Operations {
|
|
|
|
|
load: wgpu::LoadOp::Load,
|
|
|
|
|
store: wgpu::StoreOp::Store,
|
|
|
|
|
},
|
|
|
|
|
})],
|
|
|
|
|
depth_stencil_attachment: None,
|
|
|
|
|
timestamp_writes: None,
|
|
|
|
|
occlusion_query_set: None,
|
|
|
|
|
});
|
|
|
|
|
pass.set_pipeline(&self.pipeline);
|
|
|
|
|
pass.set_bind_group(0, &bind_group, &[]);
|
|
|
|
|
pass.draw(0..3, 0..1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pase de pantalla completa que samplea la textura de overlay y la emite
|
|
|
|
|
/// para alpha-over. Triángulo grande que cubre el viewport; UV mapea clip
|
|
|
|
|
/// → texel 1:1 (Y invertida, igual que un blit estándar).
|
|
|
|
|
const OVERLAY_COMPOSITE_WGSL: &str = r#"
|
|
|
|
|
struct VsOut {
|
|
|
|
|
@builtin(position) pos: vec4<f32>,
|
|
|
|
|
@location(0) uv: vec2<f32>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@vertex
|
|
|
|
|
fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
|
|
|
|
|
var corners = array<vec2<f32>, 3>(
|
|
|
|
|
vec2<f32>(-1.0, -1.0),
|
|
|
|
|
vec2<f32>( 3.0, -1.0),
|
|
|
|
|
vec2<f32>(-1.0, 3.0),
|
|
|
|
|
);
|
|
|
|
|
let xy = corners[vi];
|
|
|
|
|
var out: VsOut;
|
|
|
|
|
out.pos = vec4<f32>(xy, 0.0, 1.0);
|
|
|
|
|
out.uv = vec2<f32>((xy.x + 1.0) * 0.5, (1.0 - xy.y) * 0.5);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@group(0) @binding(0) var src_tex: texture_2d<f32>;
|
|
|
|
|
@group(0) @binding(1) var src_samp: sampler;
|
|
|
|
|
|
|
|
|
|
@fragment
|
|
|
|
|
fn fs(in: VsOut) -> @location(0) vec4<f32> {
|
|
|
|
|
return textureSample(src_tex, src_samp, in.uv);
|
|
|
|
|
}
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
impl Surface for WinitSurface {
|
|
|
|
|
fn size(&self) -> (u32, u32) {
|
|
|
|
|
(self.config.width, self.config.height)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn resize(&mut self, width: u32, height: u32) {
|
|
|
|
|
self.config.width = width.max(1);
|
|
|
|
|
self.config.height = height.max(1);
|
|
|
|
|
self.surface.configure(&self.device, &self.config);
|
|
|
|
|
let (tex, view) = create_intermediate(&self.device, self.config.width, self.config.height);
|
|
|
|
|
self.intermediate = tex;
|
|
|
|
|
self.intermediate_view = view;
|
|
|
|
|
let (otex, oview) =
|
|
|
|
|
create_intermediate(&self.device, self.config.width, self.config.height);
|
|
|
|
|
self.overlay = otex;
|
|
|
|
|
self.overlay_view = oview;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn acquire(&mut self) -> Result<Frame, SurfaceError> {
|
|
|
|
|
let texture = self.surface.get_current_texture().map_err(|e| match e {
|
|
|
|
|
wgpu::SurfaceError::Lost => SurfaceError::Lost,
|
|
|
|
|
wgpu::SurfaceError::Outdated => SurfaceError::Outdated,
|
|
|
|
|
wgpu::SurfaceError::OutOfMemory => SurfaceError::OutOfMemory,
|
|
|
|
|
wgpu::SurfaceError::Timeout => SurfaceError::Timeout,
|
|
|
|
|
other => SurfaceError::Other(format!("{other:?}")),
|
|
|
|
|
})?;
|
|
|
|
|
let surface_view = texture
|
|
|
|
|
.texture
|
|
|
|
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
|
|
|
|
// `TextureView` envuelve un Arc — clonar es atomic-incref, no
|
|
|
|
|
// recrea la vista. La intermedia sólo cambia en `resize`.
|
|
|
|
|
Ok(Frame {
|
|
|
|
|
surface_texture: texture,
|
|
|
|
|
surface_view,
|
|
|
|
|
intermediate_view: self.intermediate_view.clone(),
|
|
|
|
|
overlay_view: self.overlay_view.clone(),
|
|
|
|
|
width: self.config.width,
|
|
|
|
|
height: self.config.height,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn present(&mut self, frame: Frame, hal: &Hal) {
|
|
|
|
|
let mut encoder = hal.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
|
|
|
label: Some("llimphi-blit"),
|
|
|
|
|
});
|
|
|
|
|
self.blitter.copy(
|
|
|
|
|
&hal.device,
|
|
|
|
|
&mut encoder,
|
|
|
|
|
&frame.intermediate_view,
|
|
|
|
|
&frame.surface_view,
|
|
|
|
|
);
|
|
|
|
|
hal.queue.submit(std::iter::once(encoder.finish()));
|
|
|
|
|
frame.surface_texture.present();
|
|
|
|
|
}
|
|
|
|
|
}
|