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,
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 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 #[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 #[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 #[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 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 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}