aura-gui 0.3.0

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Inicialización de `wgpu`: instancia, adaptador, dispositivo y swapchain.

use std::fmt;

/// Error de inicialización o de presentación del backend gráfico.
#[derive(Debug)]
pub struct Error(pub String);

impl fmt::Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "error de GPU: {}", self.0)
    }
}

impl std::error::Error for Error {}

fn gpu_error(error: impl fmt::Display) -> Error {
    Error(error.to_string())
}

pub struct GpuContext {
    /// Nombre, tipo (integrada, dedicada, software) y backend del adaptador elegido.
    pub adapter: wgpu::AdapterInfo,
    pub device: wgpu::Device,
    pub queue: wgpu::Queue,
    pub surface: wgpu::Surface<'static>,
    pub config: wgpu::SurfaceConfiguration,
}

impl GpuContext {
    /// Crea el contexto priorizando la GPU integrada (`LowPower`).
    ///
    /// El descriptor de instancia lo arma quien posee la ventana, porque es
    /// quien conoce el manejador de display; así `aura_gpu` no depende de winit.
    pub async fn new(
        descriptor: wgpu::InstanceDescriptor,
        target: impl Into<wgpu::SurfaceTarget<'static>>,
        width: u32,
        height: u32,
    ) -> Result<Self, Error> {
        let instance = wgpu::Instance::new(descriptor);
        let surface = instance.create_surface(target).map_err(gpu_error)?;

        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::LowPower,
                compatible_surface: Some(&surface),
                ..Default::default()
            })
            .await
            .map_err(gpu_error)?;

        let (device, queue) = adapter
            .request_device(&wgpu::DeviceDescriptor {
                label: Some("aura_device"),
                memory_hints: wgpu::MemoryHints::MemoryUsage,
                // Los límites por defecto piden más de lo que dan algunas GPU móviles y el simulador de iOS
                // (15 variables entre etapas en lugar de 16): se rebajan a lo que el adaptador ofrece.
                required_limits: wgpu::Limits::default().or_worse_values_from(&adapter.limits()),
                ..Default::default()
            })
            .await
            .map_err(gpu_error)?;

        let mut config = surface
            .get_default_config(&adapter, width.max(1), height.max(1))
            .ok_or_else(|| Error("la superficie no es compatible con el adaptador".into()))?;
        let capabilities = surface.get_capabilities(&adapter);
        if let Some(format) = Self::preferred_format(&capabilities.formats) {
            config.format = format;
        }
        config.present_mode = wgpu::PresentMode::Fifo;
        // Una sola imagen en vuelo: en 2560×1638 cada imagen adicional cuesta ~16 MB
        // de footprint. La latencia extra es irrelevante para un editor y el uso de
        // RAM es el presupuesto prioritario.
        config.desired_maximum_frame_latency = 1;
        surface.configure(&device, &config);

        Ok(Self { adapter: adapter.get_info(), device, queue, surface, config })
    }

    /// El adaptador es un emulador por software (WARP, llvmpipe, SwiftShader): más lento y pesado que el
    /// rasterizador propio por CPU, así que conviene usar ese.
    pub fn is_software(&self) -> bool {
        self.adapter.device_type == wgpu::DeviceType::Cpu
    }

    /// Formato no-sRGB: los colores hexadecimales del tema se ven tal como se escribieron.
    fn preferred_format(formats: &[wgpu::TextureFormat]) -> Option<wgpu::TextureFormat> {
        formats.iter().copied().find(|format| !format.is_srgb())
    }

    pub fn resize(&mut self, width: u32, height: u32) {
        if width == 0 || height == 0 {
            return;
        }
        self.config.width = width;
        self.config.height = height;
        self.surface.configure(&self.device, &self.config);
    }
}