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