1use std::sync::Arc;
2
3use crate::loader::Loader;
4use crate::vk;
5
6pub struct Device {
46 handle: vk::handles::Device,
47 commands: Box<vk::commands::DeviceCommands>,
48 _loader: Option<Arc<dyn Loader>>,
49}
50
51impl Device {
52 pub(crate) unsafe fn load(
60 handle: vk::handles::Device,
61 get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr,
62 loader: Option<Arc<dyn Loader>>,
63 ) -> Self {
64 let get_device_proc_addr_fn = get_device_proc_addr.expect("vkGetDeviceProcAddr not loaded");
65 let commands = Box::new(unsafe {
67 vk::commands::DeviceCommands::load(|name| {
68 std::mem::transmute(get_device_proc_addr_fn(handle, name.as_ptr()))
69 })
70 });
71 Self {
72 handle,
73 commands,
74 _loader: loader,
75 }
76 }
77
78 pub unsafe fn from_raw_parts(
100 handle: vk::handles::Device,
101 get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr,
102 ) -> Self {
103 unsafe { Self::load(handle, get_device_proc_addr, None) }
105 }
106
107 pub fn handle(&self) -> vk::handles::Device {
109 self.handle
110 }
111
112 pub fn commands(&self) -> &vk::commands::DeviceCommands {
117 &self.commands
118 }
119
120 pub unsafe fn create_graphics_pipeline(
129 &self,
130 pipeline_cache: vk::handles::PipelineCache,
131 create_info: &vk::structs::GraphicsPipelineCreateInfo,
132 allocator: Option<&vk::structs::AllocationCallbacks>,
133 ) -> crate::VkResult<vk::handles::Pipeline> {
134 unsafe { self.create_graphics_pipelines(pipeline_cache, &[*create_info], allocator) }
135 .map(|v| v[0])
136 }
137
138 pub unsafe fn create_compute_pipeline(
147 &self,
148 pipeline_cache: vk::handles::PipelineCache,
149 create_info: &vk::structs::ComputePipelineCreateInfo,
150 allocator: Option<&vk::structs::AllocationCallbacks>,
151 ) -> crate::VkResult<vk::handles::Pipeline> {
152 unsafe { self.create_compute_pipelines(pipeline_cache, &[*create_info], allocator) }
153 .map(|v| v[0])
154 }
155
156 pub unsafe fn map_memory(
167 &self,
168 memory: vk::handles::DeviceMemory,
169 offset: u64,
170 size: u64,
171 flags: vk::structs::MemoryMapFlags,
172 ) -> crate::VkResult<*mut core::ffi::c_void> {
173 let fp = self.commands().map_memory.expect("vkMapMemory not loaded");
174 let mut data: *mut core::ffi::c_void = core::ptr::null_mut();
175 crate::error::check(unsafe { fp(self.handle(), memory, offset, size, flags, &mut data) })?;
176 Ok(data)
177 }
178
179 pub unsafe fn map_memory2(
189 &self,
190 p_memory_map_info: &vk::structs::MemoryMapInfo,
191 ) -> crate::VkResult<*mut core::ffi::c_void> {
192 let fp = self
193 .commands()
194 .map_memory2
195 .expect("vkMapMemory2 not loaded");
196 let mut data: *mut core::ffi::c_void = core::ptr::null_mut();
197 crate::error::check(unsafe { fp(self.handle(), p_memory_map_info, &mut data) })?;
198 Ok(data)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use std::ffi::c_char;
206 use vk::handles::Handle;
207
208 fn fake_handle() -> vk::handles::Device {
209 vk::handles::Device::from_raw(0xBEEF)
210 }
211
212 unsafe extern "system" fn mock_get_device_proc_addr(
214 _device: vk::handles::Device,
215 _name: *const c_char,
216 ) -> vk::structs::PFN_vkVoidFunction {
217 None
218 }
219
220 #[test]
221 fn from_raw_parts_stores_handle() {
222 let device =
223 unsafe { Device::from_raw_parts(fake_handle(), Some(mock_get_device_proc_addr)) };
224 assert_eq!(device.handle().as_raw(), fake_handle().as_raw());
225 }
226
227 #[test]
228 fn handle_returns_value_from_construction() {
229 let device = unsafe { Device::load(fake_handle(), Some(mock_get_device_proc_addr), None) };
230 assert_eq!(device.handle().as_raw(), fake_handle().as_raw());
231 }
232
233 #[test]
234 fn commands_returns_reference() {
235 let device = unsafe { Device::load(fake_handle(), Some(mock_get_device_proc_addr), None) };
236 let _ = device.commands();
239 }
240
241 #[test]
242 fn load_with_loader_reference() {
243 use std::ffi::{CStr, c_void};
244 struct DummyLoader;
245 unsafe impl Loader for DummyLoader {
246 unsafe fn load(&self, _name: &CStr) -> *const c_void {
247 std::ptr::null()
248 }
249 }
250 let loader: Arc<dyn Loader> = Arc::new(DummyLoader);
251 let device = unsafe {
252 Device::load(
253 fake_handle(),
254 Some(mock_get_device_proc_addr),
255 Some(loader.clone()),
256 )
257 };
258 assert_eq!(Arc::strong_count(&loader), 2);
259 assert_eq!(device.handle().as_raw(), fake_handle().as_raw());
260 }
261
262 #[test]
263 fn load_without_loader() {
264 let device = unsafe { Device::load(fake_handle(), Some(mock_get_device_proc_addr), None) };
265 assert_eq!(device.handle().as_raw(), fake_handle().as_raw());
266 assert!(device.commands().device_wait_idle.is_none());
268 }
269
270 #[test]
271 fn commands_all_none_from_null_mock() {
272 let device =
273 unsafe { Device::from_raw_parts(fake_handle(), Some(mock_get_device_proc_addr)) };
274 assert!(device.commands().create_buffer.is_none());
275 assert!(device.commands().destroy_device.is_none());
276 assert!(device.commands().get_device_queue.is_none());
277 }
278
279 unsafe extern "system" fn mock_device_wait_idle(
282 _device: vk::handles::Device,
283 ) -> vk::enums::Result {
284 vk::enums::Result::SUCCESS
285 }
286
287 unsafe extern "system" fn mock_destroy_device(
288 _device: vk::handles::Device,
289 _p_allocator: *const vk::structs::AllocationCallbacks,
290 ) {
291 }
292
293 unsafe extern "system" fn rich_get_device_proc_addr(
295 _device: vk::handles::Device,
296 name: *const c_char,
297 ) -> vk::structs::PFN_vkVoidFunction {
298 let name = unsafe { std::ffi::CStr::from_ptr(name) };
299 match name.to_bytes() {
300 b"vkDeviceWaitIdle" => Some(unsafe {
301 std::mem::transmute::<
302 unsafe extern "system" fn(vk::handles::Device) -> vk::enums::Result,
303 unsafe extern "system" fn(),
304 >(mock_device_wait_idle)
305 }),
306 b"vkDestroyDevice" => Some(unsafe {
307 std::mem::transmute::<
308 unsafe extern "system" fn(
309 vk::handles::Device,
310 *const vk::structs::AllocationCallbacks,
311 ),
312 unsafe extern "system" fn(),
313 >(mock_destroy_device)
314 }),
315 _ => None,
316 }
317 }
318
319 #[test]
320 fn load_with_rich_mock_populates_some_commands() {
321 let device = unsafe { Device::load(fake_handle(), Some(rich_get_device_proc_addr), None) };
322 assert!(
323 device.commands().device_wait_idle.is_some(),
324 "device_wait_idle should be loaded"
325 );
326 assert!(
327 device.commands().destroy_device.is_some(),
328 "destroy_device should be loaded"
329 );
330 assert!(device.commands().create_buffer.is_none());
332 }
333
334 #[test]
335 fn from_raw_parts_with_rich_mock_populates_commands() {
336 let device =
337 unsafe { Device::from_raw_parts(fake_handle(), Some(rich_get_device_proc_addr)) };
338 assert!(device.commands().device_wait_idle.is_some());
339 assert!(device.commands().destroy_device.is_some());
340 }
341
342 #[test]
343 fn device_wait_idle_succeeds_with_mock() {
344 let device =
345 unsafe { Device::from_raw_parts(fake_handle(), Some(rich_get_device_proc_addr)) };
346 let result = unsafe { device.device_wait_idle() };
347 assert!(result.is_ok());
348 }
349
350 #[test]
351 fn destroy_device_succeeds_with_mock() {
352 let device =
353 unsafe { Device::from_raw_parts(fake_handle(), Some(rich_get_device_proc_addr)) };
354 unsafe { device.destroy_device(None) };
356 }
357
358 #[test]
359 fn from_raw_parts_stores_no_loader() {
360 let device =
361 unsafe { Device::from_raw_parts(fake_handle(), Some(mock_get_device_proc_addr)) };
362 assert_eq!(device.handle().as_raw(), fake_handle().as_raw());
364 }
365
366 #[test]
367 fn load_with_loader_keeps_arc_alive() {
368 use std::ffi::{CStr, c_void};
369 struct DummyLoader;
370 unsafe impl Loader for DummyLoader {
371 unsafe fn load(&self, _name: &CStr) -> *const c_void {
372 std::ptr::null()
373 }
374 }
375 let loader: Arc<dyn Loader> = Arc::new(DummyLoader);
376 let weak = Arc::downgrade(&loader);
377 let device =
378 unsafe { Device::load(fake_handle(), Some(mock_get_device_proc_addr), Some(loader)) };
379 assert!(weak.upgrade().is_some(), "loader should still be alive");
381 drop(device);
382 assert!(weak.upgrade().is_none(), "loader should be dropped");
384 }
385
386 #[test]
387 #[ignore] fn device_wait_idle_succeeds() {
389 let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
390 let (instance, device) = create_real_device();
391 unsafe { device.device_wait_idle() }.expect("device_wait_idle failed");
392 unsafe { device.destroy_device(None) };
393 unsafe { instance.destroy_instance(None) };
394 }
395
396 #[test]
397 #[ignore] fn get_device_queue_returns_non_null_queue() {
399 let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
400 let (instance, device) = create_real_device();
401 let queue = unsafe { device.get_device_queue(0, 0) };
402 assert!(!queue.is_null(), "expected non-null queue handle");
403 unsafe { device.destroy_device(None) };
404 unsafe { instance.destroy_instance(None) };
405 }
406
407 #[test]
408 #[ignore] fn queue_wait_idle_succeeds() {
410 let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
411 let (instance, device) = create_real_device();
412 let queue = unsafe { device.get_device_queue(0, 0) };
413 unsafe { device.queue_wait_idle(queue) }.expect("queue_wait_idle failed");
414 unsafe { device.destroy_device(None) };
415 unsafe { instance.destroy_instance(None) };
416 }
417
418 fn create_real_device() -> (crate::instance::Instance, Device) {
419 use crate::entry::Entry;
420 use crate::loader::LibloadingLoader;
421
422 let loader = LibloadingLoader::new().expect("failed to load Vulkan");
423 let entry = unsafe { Entry::new(loader) }.expect("failed to create Entry");
424
425 let api_version_1_0 = 1u32 << 22;
426 let app_info = vk::structs::ApplicationInfo {
427 s_type: vk::enums::StructureType::APPLICATION_INFO,
428 p_next: std::ptr::null(),
429 p_application_name: std::ptr::null(),
430 application_version: 0,
431 p_engine_name: std::ptr::null(),
432 engine_version: 0,
433 api_version: api_version_1_0,
434 };
435 let instance_create_info = vk::structs::InstanceCreateInfo {
436 s_type: vk::enums::StructureType::INSTANCE_CREATE_INFO,
437 p_next: std::ptr::null(),
438 flags: vk::bitmasks::InstanceCreateFlagBits::empty(),
439 p_application_info: &app_info,
440 enabled_layer_count: 0,
441 pp_enabled_layer_names: std::ptr::null(),
442 enabled_extension_count: 0,
443 pp_enabled_extension_names: std::ptr::null(),
444 };
445 let instance = unsafe { entry.create_instance(&instance_create_info, None) }
446 .expect("failed to create instance");
447
448 let physical_devices = unsafe { instance.enumerate_physical_devices() }
449 .expect("failed to enumerate physical devices");
450 let physical_device = physical_devices[0];
451
452 let queue_priority = 1.0f32;
453 let queue_create_info = vk::structs::DeviceQueueCreateInfo {
454 s_type: vk::enums::StructureType::DEVICE_QUEUE_CREATE_INFO,
455 p_next: std::ptr::null(),
456 flags: vk::bitmasks::DeviceQueueCreateFlagBits::empty(),
457 queue_family_index: 0,
458 queue_count: 1,
459 p_queue_priorities: &queue_priority,
460 };
461 let device_create_info = vk::structs::DeviceCreateInfo {
462 s_type: vk::enums::StructureType::DEVICE_CREATE_INFO,
463 p_next: std::ptr::null(),
464 flags: 0,
465 queue_create_info_count: 1,
466 p_queue_create_infos: &queue_create_info,
467 enabled_layer_count: 0,
468 pp_enabled_layer_names: std::ptr::null(),
469 enabled_extension_count: 0,
470 pp_enabled_extension_names: std::ptr::null(),
471 p_enabled_features: std::ptr::null(),
472 };
473 let device = unsafe { instance.create_device(physical_device, &device_create_info, None) }
474 .expect("failed to create device");
475
476 (instance, device)
477 }
478}