ferroui_vulkan/
vulkan_platform_graphics.rs1use crate::interop::{VulkanDevice, VulkanInstance};
2use crate::vulkan_context::VulkanContext;
3use crate::{
4 IVulkanDevice, IVulkanInstance, VulkanDeviceCreationOptions, VulkanException, VulkanOptions,
5 VulkanPlatformSpecificOptions,
6};
7use ferroui_base::logging::{LogEventLevel, Logger};
8use ferroui_base::platform::{IPlatformGraphics, IPlatformGraphicsContext};
9use std::rc::Rc;
10use std::sync::Arc;
11
12pub struct VulkanPlatformGraphics {
16 factory: Arc<dyn IVulkanDeviceFactory>,
17 platform_options: VulkanPlatformSpecificOptions,
18}
19
20pub trait IVulkanDeviceFactory: Send + Sync {
22 fn uses_shaded_device(&self) -> bool;
24
25 fn get_shared_device(&self, platform_options: &VulkanPlatformSpecificOptions) -> Arc<dyn IVulkanDevice>;
30
31 fn create_device(
36 &self,
37 platform_options: &VulkanPlatformSpecificOptions,
38 ) -> Result<Arc<dyn IVulkanDevice>, VulkanException>;
39}
40
41struct DefaultDeviceFactory {
42 instance: Arc<dyn IVulkanInstance>,
43 device_options: VulkanDeviceCreationOptions,
44}
45
46impl IVulkanDeviceFactory for DefaultDeviceFactory {
47 fn uses_shaded_device(&self) -> bool {
48 false
49 }
50
51 fn get_shared_device(&self, _platform_options: &VulkanPlatformSpecificOptions) -> Arc<dyn IVulkanDevice> {
52 panic!("Specified method is not supported.");
53 }
54
55 fn create_device(
56 &self,
57 platform_options: &VulkanPlatformSpecificOptions,
58 ) -> Result<Arc<dyn IVulkanDevice>, VulkanException> {
59 VulkanDevice::create(self.instance.clone(), &self.device_options, platform_options)
60 }
61}
62
63struct CustomSharedDeviceFactory {
64 device: Arc<dyn IVulkanDevice>,
65}
66
67impl IVulkanDeviceFactory for CustomSharedDeviceFactory {
68 fn uses_shaded_device(&self) -> bool {
69 true
70 }
71
72 fn get_shared_device(&self, _platform_options: &VulkanPlatformSpecificOptions) -> Arc<dyn IVulkanDevice> {
73 self.device.clone()
74 }
75
76 fn create_device(
77 &self,
78 _platform_options: &VulkanPlatformSpecificOptions,
79 ) -> Result<Arc<dyn IVulkanDevice>, VulkanException> {
80 panic!("Specified method is not supported.");
81 }
82}
83
84impl VulkanPlatformGraphics {
85 pub fn new(factory: Arc<dyn IVulkanDeviceFactory>, platform_options: VulkanPlatformSpecificOptions) -> Self {
87 Self { factory, platform_options }
88 }
89
90 pub fn try_create(options: &VulkanOptions, platform_options: VulkanPlatformSpecificOptions) -> Option<Arc<Self>> {
96 if let Some(custom_shared_device) = &options.custom_shared_device {
97 return Some(Arc::new(Self::new(
98 Arc::new(CustomSharedDeviceFactory { device: custom_shared_device.clone() }),
99 platform_options,
100 )));
101 }
102
103 let create = || -> Result<Arc<Self>, VulkanException> {
104 let instance = VulkanInstance::create(&options.vulkan_instance_creation_options, &platform_options)?;
105
106 let dev_opts = options.vulkan_device_creation_options.clone();
107 VulkanDevice::create(instance.clone(), &dev_opts, &platform_options)?.dispose();
108
109 Ok(Arc::new(Self::new(
110 Arc::new(DefaultDeviceFactory { instance, device_options: dev_opts }),
111 platform_options.clone(),
112 )))
113 };
114 match create() {
115 Ok(graphics) => Some(graphics),
116 Err(e) => {
117 if let Some(logger) = Logger::try_get(LogEventLevel::Error, "Vulkan") {
119 logger.log_with_values(None, "Unable to initialize Vulkan rendering: {0}", &[&e]);
120 }
121 None
122 }
123 }
124 }
125}
126
127impl IPlatformGraphics for VulkanPlatformGraphics {
128 fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
129 let context = self
130 .factory
131 .create_device(&self.platform_options)
132 .and_then(|device| VulkanContext::new(device, &self.platform_options.platform_features));
133 match context {
134 Ok(context) => context,
135 Err(error) => panic!("{error}"),
136 }
137 }
138
139 fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
140 let device = self.factory.get_shared_device(&self.platform_options);
143 match VulkanContext::new(device, &self.platform_options.platform_features) {
144 Ok(context) => context,
145 Err(error) => panic!("{error}"),
146 }
147 }
148
149 fn uses_shared_context(&self) -> bool {
150 self.factory.uses_shaded_device()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::IVulkanPlatformGraphicsContext;
158 use ferroui_base::platform::IOptionalFeatureProvider;
159 use ferroui_base::reactive::{Disposable, IDisposable};
160 use std::any::{Any, TypeId};
161 use std::sync::atomic::{AtomicUsize, Ordering};
162
163 struct Instance;
164
165 impl IVulkanInstance for Instance {
166 fn handle(&self) -> isize {
167 0x1000
168 }
169
170 fn get_instance_proc_address(&self, _instance: isize, _name: &str) -> isize {
171 0
172 }
173
174 fn get_device_proc_address(&self, _device: isize, _name: &str) -> isize {
175 0
176 }
177
178 fn enabled_extensions(&self) -> Vec<String> {
179 Vec::new()
180 }
181 }
182
183 #[derive(Default)]
184 struct Device {
185 locks: AtomicUsize,
186 }
187
188 impl IOptionalFeatureProvider for Device {
189 fn try_get_feature(&self, _feature_type: TypeId) -> Option<Rc<dyn Any>> {
190 None
191 }
192 }
193
194 impl IVulkanDevice for Device {
195 fn handle(&self) -> isize {
196 0x2000
197 }
198
199 fn physical_device_handle(&self) -> isize {
200 0x3000
201 }
202
203 fn main_queue_handle(&self) -> isize {
204 0x4000
205 }
206
207 fn graphics_queue_family_index(&self) -> u32 {
208 7
209 }
210
211 fn instance(&self) -> Arc<dyn IVulkanInstance> {
212 Arc::new(Instance)
213 }
214
215 fn is_lost(&self) -> bool {
216 false
217 }
218
219 fn lock(&self) -> Rc<dyn IDisposable> {
220 self.locks.fetch_add(1, Ordering::SeqCst);
221 Disposable::empty()
222 }
223
224 fn enabled_extensions(&self) -> Vec<String> {
225 Vec::new()
226 }
227
228 fn dispose(&self) {}
229 }
230
231 #[test]
232 fn a_custom_device_is_shared_by_the_contexts() {
233 let device = Arc::new(Device::default());
234 let options = VulkanOptions { custom_shared_device: Some(device.clone()), ..Default::default() };
235 let graphics = VulkanPlatformGraphics::try_create(&options, Default::default()).expect("no instance is needed");
236 assert!(graphics.uses_shared_context());
237
238 let context = graphics.get_shared_context();
241 assert!(device.locks.load(Ordering::SeqCst) >= 1);
242 assert!(!context.is_lost());
243
244 let features: &dyn IOptionalFeatureProvider = &*context;
245 let vulkan = features.try_get::<dyn IVulkanPlatformGraphicsContext>().expect("the context is a Vulkan context");
246 assert_eq!(vulkan.device().handle(), 0x2000);
247 assert_eq!(vulkan.instance().handle(), 0x1000);
248 assert_eq!(ash::vk::Handle::as_raw(vulkan.device_handle()), 0x2000);
249 assert_eq!(ash::vk::Handle::as_raw(vulkan.physical_device_handle()), 0x3000);
250 assert_eq!(ash::vk::Handle::as_raw(vulkan.instance_handle()), 0x1000);
251 assert_eq!(ash::vk::Handle::as_raw(vulkan.main_queue_handle()), 0x4000);
252 assert_eq!(vulkan.graphics_queue_family_index(), 7);
253 assert!(features.try_get::<dyn crate::IVulkanContextExternalObjectsFeature>().is_none());
256 assert!(features.try_get_shared::<dyn crate::IVulkanKhrSurfacePlatformSurfaceFactory>().is_none());
257 context.dispose();
258 }
259
260 #[test]
261 #[should_panic(expected = "Specified method is not supported.")]
262 fn a_custom_device_factory_creates_no_devices() {
263 let options = VulkanOptions { custom_shared_device: Some(Arc::new(Device::default())), ..Default::default() };
264 let graphics = VulkanPlatformGraphics::try_create(&options, Default::default()).unwrap();
265 let _ = graphics.create_context();
266 }
267
268 #[test]
269 fn without_a_way_to_the_entry_points_there_are_no_platform_graphics() {
270 assert!(VulkanPlatformGraphics::try_create(&VulkanOptions::default(), Default::default()).is_none());
271 }
272
273 #[test]
274 #[should_panic(expected = "Unable to find a suitable platform surface")]
275 fn a_context_without_a_suitable_surface_creates_no_render_target() {
276 let options = VulkanOptions { custom_shared_device: Some(Arc::new(Device::default())), ..Default::default() };
277 let graphics = VulkanPlatformGraphics::try_create(&options, Default::default()).unwrap();
278 let context = graphics.get_shared_context();
279 let features: &dyn IOptionalFeatureProvider = &*context;
280 let vulkan = features.try_get::<dyn IVulkanPlatformGraphicsContext>().unwrap();
281 let _ = vulkan.create_render_target(&[]);
282 }
283
284 #[test]
285 fn the_platform_graphics_are_shared_by_threads() {
286 fn assert_send_sync<T: Send + Sync>() {}
287 assert_send_sync::<VulkanPlatformGraphics>();
288 assert_send_sync::<Arc<dyn IVulkanDevice>>();
289 assert_send_sync::<Arc<dyn IVulkanInstance>>();
290 }
291}