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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[allow(dead_code)]
22pub enum DriverError {
23 DeviceLost,
25 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 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 #[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 #[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 #[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 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 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 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}