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.
//! Elección automática del renderizador de una ventana:
//!
//! 1. `AURA_RENDERER=cpu` fuerza la CPU (y `AURA_RENDERER=gpu` desactiva la caída a CPU, para depurar).
//! 2. Se pide un adaptador a wgpu, que prueba Metal, DirectX 12, Vulkan y OpenGL (en Windows 7 sin DX12 suele quedar
//!    Vulkan u OpenGL).
//! 3. Sin adaptador, sin controladores o con uno emulado por software (WARP, llvmpipe), pinta la CPU: el rasterizador
//!    propio gasta menos memoria y CPU que un emulador de GPU.

use crate::gpu::cpu::SoftbufferPresenter;
use crate::gpu::{GpuContext, Renderer};
use std::sync::Arc;
use winit::event_loop::ActiveEventLoop;
use winit::window::Window;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Preference {
    Auto,
    Gpu,
    Cpu,
}

fn preference(value: Option<&str>) -> Preference {
    match value.map(str::to_ascii_lowercase).as_deref() {
        Some("cpu" | "software") => Preference::Cpu,
        Some("gpu") => Preference::Gpu,
        _ => Preference::Auto,
    }
}

fn gpu(event_loop: &ActiveEventLoop, window: &Arc<Window>, size: (u32, u32)) -> Result<GpuContext, String> {
    let descriptor = wgpu::InstanceDescriptor::new_with_display_handle(Box::new(event_loop.owned_display_handle()));
    pollster::block_on(GpuContext::new(descriptor, window.clone(), size.0, size.1)).map_err(|error| error.to_string())
}

pub(crate) fn create(
    event_loop: &ActiveEventLoop,
    window: &Arc<Window>,
    size: (u32, u32),
) -> Result<Renderer, Box<dyn std::error::Error>> {
    let requested = std::env::var("AURA_RENDERER").ok();
    let renderer = choose(event_loop, window, size, preference(requested.as_deref()))?;
    // Quien lo fuerza quiere saber qué quedó; en el caso normal no se escribe nada.
    if requested.is_some() {
        crate::app::console::warn(format_args!("se dibuja con {}", renderer.description()));
    }
    Ok(renderer)
}

fn choose(
    event_loop: &ActiveEventLoop,
    window: &Arc<Window>,
    size: (u32, u32),
    wanted: Preference,
) -> Result<Renderer, Box<dyn std::error::Error>> {
    if wanted != Preference::Cpu {
        let reason = match gpu(event_loop, window, size) {
            Ok(context) if !context.is_software() || wanted == Preference::Gpu => match Renderer::gpu(context) {
                Ok(renderer) => return Ok(renderer),
                Err(error) if wanted == Preference::Gpu => return Err(error.into()),
                Err(error) => error.to_string(),
            },
            Ok(context) => format!("la GPU «{}» es emulada por software", context.adapter.name),
            Err(error) if wanted == Preference::Gpu => return Err(error.into()),
            Err(error) => error,
        };
        crate::app::console::warn(format_args!("sin GPU utilizable ({reason}); se dibuja con la CPU"));
    }
    let presenter = SoftbufferPresenter::new(window.clone(), window.clone())?;
    Ok(Renderer::cpu(Box::new(presenter), size.0, size.1))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn la_preferencia_se_lee_sin_distinguir_mayusculas() {
        assert_eq!(preference(Some("CPU")), Preference::Cpu);
        assert_eq!(preference(Some("software")), Preference::Cpu);
        assert_eq!(preference(Some("gpu")), Preference::Gpu);
        assert_eq!(preference(Some("otra")), Preference::Auto);
        assert_eq!(preference(None), Preference::Auto);
    }
}