Skip to main content

freya_winit/drivers/
mod.rs

1#[cfg(any(target_os = "linux", target_os = "windows", target_os = "android"))]
2mod gl;
3#[cfg(target_os = "macos")]
4mod metal;
5mod software;
6#[cfg(any(target_os = "linux", target_os = "windows"))]
7mod vulkan;
8
9use freya_engine::prelude::Surface as SkiaSurface;
10use winit::{
11    dpi::PhysicalSize,
12    event_loop::ActiveEventLoop,
13    window::{
14        Window,
15        WindowAttributes,
16    },
17};
18
19/// Unrecoverable graphics error requiring a driver rebuild.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[allow(dead_code)]
22pub enum DriverError {
23    /// The GPU device was lost.
24    DeviceLost,
25    /// The GPU or host ran out of memory.
26    OutOfMemory,
27}
28
29#[allow(clippy::large_enum_variant)]
30pub enum GraphicsDriver {
31    #[cfg(any(target_os = "linux", target_os = "windows", target_os = "android"))]
32    OpenGl(gl::OpenGLDriver),
33    #[cfg(target_os = "macos")]
34    Metal(metal::MetalDriver),
35    #[cfg(any(target_os = "linux", target_os = "windows"))]
36    Vulkan(vulkan::VulkanDriver),
37    Software(software::SoftwareDriver),
38}
39
40impl GraphicsDriver {
41    #[allow(clippy::needless_return)]
42    pub fn new(
43        event_loop: &ActiveEventLoop,
44        window_attributes: WindowAttributes,
45        gpu_resource_cache_limit: usize,
46    ) -> (Self, Window) {
47        let renderer = std::env::var("FREYA_RENDERER")
48            .ok()
49            .map(|v| v.to_ascii_lowercase());
50        let renderer = renderer.as_deref();
51
52        // Opt-in via FREYA_RENDERER=software, available on every platform.
53        if renderer == Some("software") {
54            match software::SoftwareDriver::new(event_loop, window_attributes.clone()) {
55                Ok((driver, window)) => return (Self::Software(driver), window),
56                Err(err) => {
57                    tracing::warn!(
58                        "Software renderer initialization failed, falling back to default: {err}"
59                    );
60                }
61            }
62        }
63
64        // Metal (macOS)
65        #[cfg(target_os = "macos")]
66        {
67            let (driver, window) =
68                metal::MetalDriver::new(event_loop, window_attributes, gpu_resource_cache_limit);
69
70            return (Self::Metal(driver), window);
71        }
72
73        // OpenGL only on Android.
74        #[cfg(target_os = "android")]
75        {
76            match gl::OpenGLDriver::new(
77                event_loop,
78                window_attributes.clone(),
79                gpu_resource_cache_limit,
80            ) {
81                Ok((driver, window)) => return (Self::OpenGl(driver), window),
82                Err(err) => {
83                    tracing::warn!("OpenGL initialization failed, falling back to software: {err}");
84                }
85            }
86
87            let (driver, window) = software::SoftwareDriver::new(event_loop, window_attributes)
88                .expect("Failed to initialize software renderer fallback");
89            return (Self::Software(driver), window);
90        }
91
92        // Linux: Vulkan by default, set FREYA_RENDERER=opengl to force OpenGL.
93        // Windows: OpenGL by default, set FREYA_RENDERER=vulkan to force Vulkan.
94        // If both fail, falls back to the software renderer.
95        #[cfg(all(not(target_os = "macos"), not(target_os = "android")))]
96        {
97            let use_vulkan = if cfg!(target_os = "windows") {
98                renderer == Some("vulkan")
99            } else {
100                renderer != Some("opengl")
101            };
102
103            if use_vulkan {
104                match vulkan::VulkanDriver::new(
105                    event_loop,
106                    window_attributes.clone(),
107                    gpu_resource_cache_limit,
108                ) {
109                    Ok((driver, window)) => return (Self::Vulkan(driver), window),
110                    Err(err) => {
111                        tracing::warn!(
112                            "Vulkan initialization failed, falling back to OpenGL: {err}"
113                        );
114                    }
115                }
116            }
117
118            match gl::OpenGLDriver::new(
119                event_loop,
120                window_attributes.clone(),
121                gpu_resource_cache_limit,
122            ) {
123                Ok((driver, window)) => return (Self::OpenGl(driver), window),
124                Err(err) => {
125                    tracing::warn!("OpenGL initialization failed, falling back to software: {err}");
126                }
127            }
128
129            let (driver, window) = software::SoftwareDriver::new(event_loop, window_attributes)
130                .expect("Failed to initialize software renderer fallback");
131            return (Self::Software(driver), window);
132        }
133    }
134
135    /// Rebuild the driver on the existing window, skipping Vulkan.
136    pub fn recover_reusing_window(
137        event_loop: &ActiveEventLoop,
138        window: &Window,
139        gpu_resource_cache_limit: usize,
140        transparent: bool,
141    ) -> Self {
142        #[cfg(target_os = "macos")]
143        let _ = (event_loop, gpu_resource_cache_limit, transparent);
144
145        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "android"))]
146        match gl::OpenGLDriver::from_window(
147            event_loop,
148            window,
149            gpu_resource_cache_limit,
150            transparent,
151        ) {
152            Ok(driver) => return Self::OpenGl(driver),
153            Err(err) => {
154                tracing::warn!("OpenGL recovery failed, falling back to software: {err}");
155            }
156        }
157
158        let driver = software::SoftwareDriver::from_window(window)
159            .expect("Failed to initialize software renderer fallback");
160        Self::Software(driver)
161    }
162
163    pub fn present(
164        &mut self,
165        _size: PhysicalSize<u32>,
166        window: &Window,
167        render: impl FnOnce(&mut SkiaSurface),
168    ) -> Result<(), DriverError> {
169        match self {
170            #[cfg(any(target_os = "linux", target_os = "windows", target_os = "android"))]
171            Self::OpenGl(gl) => {
172                gl.present(window, render);
173                Ok(())
174            }
175            #[cfg(target_os = "macos")]
176            Self::Metal(mtl) => {
177                mtl.present(_size, window, render);
178                Ok(())
179            }
180            #[cfg(any(target_os = "linux", target_os = "windows"))]
181            Self::Vulkan(vk) => vk.present(_size, window, render),
182            Self::Software(sw) => {
183                sw.present(_size, window, render);
184                Ok(())
185            }
186        }
187    }
188
189    /// The name of the active graphics driver.
190    pub fn name(&self) -> &'static str {
191        match self {
192            #[cfg(any(target_os = "linux", target_os = "windows", target_os = "android"))]
193            Self::OpenGl(_) => "OpenGL",
194            #[cfg(target_os = "macos")]
195            Self::Metal(_) => "Metal",
196            #[cfg(any(target_os = "linux", target_os = "windows"))]
197            Self::Vulkan(_) => "Vulkan",
198            Self::Software(_) => "Software",
199        }
200    }
201
202    pub fn resize(&mut self, size: PhysicalSize<u32>) {
203        match self {
204            #[cfg(any(target_os = "linux", target_os = "windows", target_os = "android"))]
205            Self::OpenGl(gl) => gl.resize(size),
206            #[cfg(target_os = "macos")]
207            Self::Metal(mtl) => mtl.resize(size),
208            #[cfg(any(target_os = "linux", target_os = "windows"))]
209            Self::Vulkan(vk) => {
210                // Recovered on the next present, so just log it.
211                if let Err(error) = vk.resize(size) {
212                    tracing::warn!("Vulkan resize failed ({error:?}), recovering on next present");
213                }
214            }
215            Self::Software(sw) => sw.resize(size),
216        }
217    }
218}