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