Skip to main content

vk/core/
device.rs

1//! Device dispatch: loaded via `vkGetDeviceProcAddr` for optimal routing.
2use super::Instance;
3use crate::core as vk;
4use crate::load::{
5	CallError, Capabilities, CommandSupport, CreatedHandle, InitializationFailure, LoadError,
6};
7use libloading::Library;
8use std::sync::Arc;
9
10/// Device handle and dispatch. Call `destroy` explicitly; Drop never destroys Vulkan objects.
11pub struct Device {
12	handle: vk::DeviceHandle,
13	fp: vk::FnDevice,
14	gdpa:
15		unsafe extern "system" fn(vk::DeviceHandle, *const std::ffi::c_char) -> vk::PFN_vkVoidFunction,
16	_library: Option<Arc<Library>>,
17	capabilities: Option<Capabilities>,
18}
19
20impl Device {
21	/// Associate a live raw device with the instance that created it.
22	///
23	/// # Safety
24	/// `handle` must be live and have been created from `instance`. The caller
25	/// transfers ownership and must not create duplicate wrappers.
26	/// On error the caller retains ownership. This raw import has no creation
27	/// provenance; enabled-command and feature validation remain caller obligations.
28	pub unsafe fn new_from_handle(
29		instance: &Instance,
30		handle: vk::DeviceHandle,
31	) -> Result<Self, LoadError> {
32		if handle.is_null() {
33			return Err(LoadError::NullHandle("device"));
34		}
35		let gdpa = instance
36			.fp()
37			.get_device_proc_addr
38			.ok_or(LoadError::MissingSymbol("vkGetDeviceProcAddr"))?;
39		// SAFETY: caller guarantees a live device from this instance; gdpa is valid.
40		let fp = unsafe { vk::FnDevice::new_from_resolver(gdpa, handle) };
41		if fp.destroy_device.is_none() {
42			return Err(LoadError::MissingSymbol("vkDestroyDevice"));
43		}
44		Ok(Self {
45			handle,
46			fp,
47			gdpa,
48			_library: instance.library.clone(),
49			capabilities: None,
50		})
51	}
52
53	/// Import a native device with owned creation metadata and filtered dispatch.
54	///
55	/// # Errors
56	/// Reports wrong snapshot scope/parent metadata, a null handle or unavailable
57	/// resolver/destruction. Every error leaves native ownership with the caller.
58	///
59	/// # Safety
60	/// Handle must be live, belong to instance and have no other wrapper owner.
61	/// Capabilities must truthfully describe this exact device, its actual parent
62	/// instance, selected physical-device version and enabled feature chain.
63	/// Parent/callback lifetimes and ordinary Vulkan valid usage remain required.
64	pub unsafe fn new_from_handle_with_capabilities(
65		instance: &Instance,
66		handle: vk::DeviceHandle,
67		capabilities: Capabilities,
68	) -> Result<Self, LoadError> {
69		if !capabilities.is_device() {
70			return Err(LoadError::InvalidMetadata(
71				"device import needs device capabilities",
72			));
73		}
74		if let Some(parent) = instance.capabilities()
75			&& !capabilities.matches_instance(parent)
76		{
77			return Err(LoadError::InvalidMetadata(
78				"device capabilities disagree with parent instance",
79			));
80		}
81		// SAFETY: caller guarantees this device's lifetime, parent and exclusive import.
82		let mut device = unsafe { Self::new_from_handle(instance, handle) }?;
83		device
84			.fp
85			.retain_enabled(|command| capabilities.support(command) == CommandSupport::Enabled);
86		device.capabilities = Some(capabilities);
87		Ok(device)
88	}
89
90	/// Return the raw handle. Its use must respect this device's lifetime.
91	pub fn handle(&self) -> vk::DeviceHandle {
92		self.handle
93	}
94
95	/// Borrow the full device dispatch table.
96	pub fn fp(&self) -> &vk::FnDevice {
97		&self.fp
98	}
99
100	/// Creation version/extension provenance, or None for an imported raw handle.
101	pub fn capabilities(&self) -> Option<&Capabilities> {
102		self.capabilities.as_ref()
103	}
104
105	/// Destroy once, using callbacks compatible with device creation.
106	///
107	/// # Safety
108	/// All children and outstanding device operations must be complete. The
109	/// allocator must match creation, with its user data and functions still live.
110	pub unsafe fn destroy(self, allocator: Option<&vk::AllocationCallbacks>) {
111		// SAFETY: caller supplies the canonical destruction guarantees.
112		unsafe { destroy_device(self, allocator) };
113	}
114}
115
116use super::command::commands;
117commands! { Device, device;
118	/// Retrieve a queue handle from this device.
119	///
120	/// # Safety
121	/// `queue_family_index` and `queue_index` must be within the bounds of the
122	/// queue families requested at device creation.
123	pub unsafe fn get_device_queue(
124		queue_family_index: u32,
125		queue_index: u32,
126	) -> Result<vk::Queue, CallError> {
127		let f = device
128			.fp
129			.get_device_queue
130			.ok_or(CallError::MissingCommand("vkGetDeviceQueue"))?;
131		let mut queue = vk::Queue::NULL;
132		// SAFETY: caller guarantees valid family and queue indices.
133		unsafe { f(device.handle, queue_family_index, queue_index, &mut queue) };
134		Ok(queue)
135	}
136
137	/// Resolve a device-specific function by name using this device's `gdpa`.
138	///
139	/// Returns `None` if the function is not available for this device.
140	///
141	/// # Safety
142	/// `name` must be a valid NUL-terminated command name. The caller must cast
143	/// and use the returned pointer only while this device is live.
144	pub unsafe fn get_device_proc_addr(name: &std::ffi::CStr) -> vk::PFN_vkVoidFunction {
145		// SAFETY: the caller keeps this device alive while using returned functions.
146		unsafe { (device.gdpa)(device.handle, name.as_ptr()) }
147	}
148
149	/// Record a compute dispatch into a command buffer.
150	///
151	/// # Errors
152	/// Returns `CallError::MissingCommand` if `vkCmdDispatch` was not loaded
153	/// (extension not enabled or unsupported device).
154	///
155	/// # Safety
156	/// `command_buffer` must be in the recording state. `group_count_*` must not
157	/// exceed `VkPhysicalDeviceLimits::max_compute_work_group_count`.
158	pub unsafe fn cmd_dispatch(
159		command_buffer: vk::CommandBuffer,
160		group_count_x: u32,
161		group_count_y: u32,
162		group_count_z: u32,
163	) -> Result<(), CallError> {
164		let f = device
165			.fp
166			.cmd_dispatch
167			.ok_or(CallError::MissingCommand("vkCmdDispatch"))?;
168		// SAFETY: caller guarantees the command buffer is recording and group counts are valid.
169		unsafe { f(command_buffer, group_count_x, group_count_y, group_count_z) };
170		Ok(())
171	}
172
173	/// Allocate device memory.
174	///
175	/// # Errors
176	/// `CallError::MissingCommand` if unavailable; `CallError::Vulkan` on failure.
177	///
178	/// # Safety
179	/// The `allocate_info` must be valid and reference a legal memory type index.
180	/// The allocation must be freed exactly once via [`Device::free_memory`].
181	pub unsafe fn allocate_memory(
182		allocate_info: &vk::MemoryAllocateInfo,
183		allocator: Option<&vk::AllocationCallbacks>,
184	) -> Result<vk::DeviceMemory, CallError> {
185		let f = device
186			.fp
187			.allocate_memory
188			.ok_or(CallError::MissingCommand("vkAllocateMemory"))?;
189		let mut handle = vk::DeviceMemory::NULL;
190		let result = unsafe {
191			f(
192				device.handle,
193				allocate_info,
194				allocator.map_or(std::ptr::null(), |a| a),
195				&mut handle,
196			)
197		};
198		if result == vk::Result::SUCCESS {
199			Ok(handle)
200		} else {
201			Err(CallError::Vulkan(result))
202		}
203	}
204
205	/// Free a previously allocated device memory object.
206	///
207	/// # Safety
208	/// All resources bound to this memory must have been destroyed. The memory
209	/// must not be freed more than once.
210	pub unsafe fn free_memory(
211		memory: vk::DeviceMemory,
212		allocator: Option<&vk::AllocationCallbacks>,
213	) -> Result<(), CallError> {
214		let f = device
215			.fp
216			.free_memory
217			.ok_or(CallError::MissingCommand("vkFreeMemory"))?;
218		unsafe {
219			f(
220				device.handle,
221				memory,
222				allocator.map_or(std::ptr::null(), |a| a),
223			)
224		};
225		Ok(())
226	}
227
228	/// Map a range of a device memory object into host address space.
229	///
230	/// Returns a raw pointer to the start of the mapped range.
231	///
232	/// # Safety
233	/// `memory` must be host-visible, not already mapped, and remain live until
234	/// [`Device::unmap_memory`] is called.
235	pub unsafe fn map_memory(
236		memory: vk::DeviceMemory,
237		offset: vk::DeviceSize,
238		size: vk::DeviceSize,
239		flags: vk::MemoryMapFlags,
240	) -> Result<*mut std::ffi::c_void, CallError> {
241		let f = device
242			.fp
243			.map_memory
244			.ok_or(CallError::MissingCommand("vkMapMemory"))?;
245		let mut ptr = std::ptr::null_mut();
246		let result = unsafe { f(device.handle, memory, offset, size, flags, &mut ptr) };
247		if result == vk::Result::SUCCESS {
248			Ok(ptr)
249		} else {
250			Err(CallError::Vulkan(result))
251		}
252	}
253
254	/// Unmap a previously mapped memory object.
255	///
256	/// # Safety
257	/// `memory` must currently be mapped and must not be used from host
258	/// after this call.
259	pub unsafe fn unmap_memory(memory: vk::DeviceMemory) -> Result<(), CallError> {
260		let f = device
261			.fp
262			.unmap_memory
263			.ok_or(CallError::MissingCommand("vkUnmapMemory"))?;
264		unsafe { f(device.handle, memory) };
265		Ok(())
266	}
267
268	/// Flush mapped memory ranges to make host writes visible to the device.
269	///
270	/// Required for non-host-coherent memory types before GPU reads.
271	///
272	/// # Safety
273	/// All ranges must be within currently mapped memory objects.
274	pub unsafe fn flush_mapped_memory_ranges(
275		ranges: &[vk::MappedMemoryRange],
276	) -> Result<(), CallError> {
277		let f = device
278			.fp
279			.flush_mapped_memory_ranges
280			.ok_or(CallError::MissingCommand("vkFlushMappedMemoryRanges"))?;
281		let result = unsafe {
282			f(
283				device.handle,
284				u32::try_from(ranges.len()).map_err(|_| CallError::CountOverflow(ranges.len()))?,
285				ranges.as_ptr(),
286			)
287		};
288		if result == vk::Result::SUCCESS {
289			Ok(())
290		} else {
291			Err(CallError::Vulkan(result))
292		}
293	}
294
295	/// Invalidate mapped memory ranges to make device writes visible to the host.
296	///
297	/// Required for non-host-coherent memory types after GPU writes.
298	///
299	/// # Safety
300	/// All ranges must be within currently mapped memory objects.
301	pub unsafe fn invalidate_mapped_memory_ranges(
302		ranges: &[vk::MappedMemoryRange],
303	) -> Result<(), CallError> {
304		let f = device
305			.fp
306			.invalidate_mapped_memory_ranges
307			.ok_or(CallError::MissingCommand("vkInvalidateMappedMemoryRanges"))?;
308		let result = unsafe {
309			f(
310				device.handle,
311				u32::try_from(ranges.len()).map_err(|_| CallError::CountOverflow(ranges.len()))?,
312				ranges.as_ptr(),
313			)
314		};
315		if result == vk::Result::SUCCESS {
316			Ok(())
317		} else {
318			Err(CallError::Vulkan(result))
319		}
320	}
321
322	/// Create a buffer object.
323	///
324	/// # Safety
325	/// `create_info` must be valid. Memory must be bound before use.
326	pub unsafe fn create_buffer(
327		create_info: &vk::BufferCreateInfo,
328		allocator: Option<&vk::AllocationCallbacks>,
329	) -> Result<vk::Buffer, CallError> {
330		let f = device
331			.fp
332			.create_buffer
333			.ok_or(CallError::MissingCommand("vkCreateBuffer"))?;
334		let mut handle = vk::Buffer::NULL;
335		let result = unsafe {
336			f(
337				device.handle,
338				create_info,
339				allocator.map_or(std::ptr::null(), |a| a),
340				&mut handle,
341			)
342		};
343		if result == vk::Result::SUCCESS {
344			Ok(handle)
345		} else {
346			Err(CallError::Vulkan(result))
347		}
348	}
349
350	/// Destroy a buffer object.
351	///
352	/// # Safety
353	/// The buffer must not be in use by the GPU and must not be destroyed twice.
354	pub unsafe fn destroy_buffer(
355		buffer: vk::Buffer,
356		allocator: Option<&vk::AllocationCallbacks>,
357	) -> Result<(), CallError> {
358		let f = device
359			.fp
360			.destroy_buffer
361			.ok_or(CallError::MissingCommand("vkDestroyBuffer"))?;
362		unsafe {
363			f(
364				device.handle,
365				buffer,
366				allocator.map_or(std::ptr::null(), |a| a),
367			)
368		};
369		Ok(())
370	}
371
372	/// Query the memory requirements of a buffer (size, alignment, type bits).
373	///
374	/// # Safety
375	/// `buffer` must be a live handle created from this device.
376	pub unsafe fn get_buffer_memory_requirements(
377		buffer: vk::Buffer,
378	) -> Result<vk::MemoryRequirements, CallError> {
379		let f = device
380			.fp
381			.get_buffer_memory_requirements
382			.ok_or(CallError::MissingCommand("vkGetBufferMemoryRequirements"))?;
383		let mut reqs = unsafe { std::mem::zeroed::<vk::MemoryRequirements>() };
384		unsafe { f(device.handle, buffer, &mut reqs) };
385		Ok(reqs)
386	}
387
388	/// Bind device memory to a buffer at the given offset.
389	///
390	/// # Safety
391	/// `memory` must have been allocated with a compatible type and sufficient size.
392	/// The buffer must not already have memory bound.
393	pub unsafe fn bind_buffer_memory(
394		buffer: vk::Buffer,
395		memory: vk::DeviceMemory,
396		memory_offset: vk::DeviceSize,
397	) -> Result<(), CallError> {
398		let f = device
399			.fp
400			.bind_buffer_memory
401			.ok_or(CallError::MissingCommand("vkBindBufferMemory"))?;
402		let result = unsafe { f(device.handle, buffer, memory, memory_offset) };
403		if result == vk::Result::SUCCESS {
404			Ok(())
405		} else {
406			Err(CallError::Vulkan(result))
407		}
408	}
409
410	/// Create a command pool.
411	///
412	/// # Safety
413	/// `create_info.queue_family_index` must be valid for this device.
414	pub unsafe fn create_command_pool(
415		create_info: &vk::CommandPoolCreateInfo,
416		allocator: Option<&vk::AllocationCallbacks>,
417	) -> Result<vk::CommandPool, CallError> {
418		let f = device
419			.fp
420			.create_command_pool
421			.ok_or(CallError::MissingCommand("vkCreateCommandPool"))?;
422		let mut handle = vk::CommandPool::NULL;
423		let result = unsafe {
424			f(
425				device.handle,
426				create_info,
427				allocator.map_or(std::ptr::null(), |a| a),
428				&mut handle,
429			)
430		};
431		if result == vk::Result::SUCCESS {
432			Ok(handle)
433		} else {
434			Err(CallError::Vulkan(result))
435		}
436	}
437
438	/// Destroy a command pool and all command buffers allocated from it.
439	///
440	/// # Safety
441	/// All command buffers from this pool must have finished execution on the GPU.
442	pub unsafe fn destroy_command_pool(
443		pool: vk::CommandPool,
444		allocator: Option<&vk::AllocationCallbacks>,
445	) -> Result<(), CallError> {
446		let f = device
447			.fp
448			.destroy_command_pool
449			.ok_or(CallError::MissingCommand("vkDestroyCommandPool"))?;
450		unsafe {
451			f(
452				device.handle,
453				pool,
454				allocator.map_or(std::ptr::null(), |a| a),
455			)
456		};
457		Ok(())
458	}
459
460	/// Allocate command buffers from a pool.
461	///
462	/// # Safety
463	/// `allocate_info.command_pool` must be a live pool from this device.
464	pub unsafe fn allocate_command_buffers(
465		allocate_info: &vk::CommandBufferAllocateInfo,
466	) -> Result<Vec<vk::CommandBuffer>, CallError> {
467		let f = device
468			.fp
469			.allocate_command_buffers
470			.ok_or(CallError::MissingCommand("vkAllocateCommandBuffers"))?;
471		let count = allocate_info.command_buffer_count as usize;
472		let mut bufs = vec![vk::CommandBuffer::NULL; count];
473		let result = unsafe { f(device.handle, allocate_info, bufs.as_mut_ptr()) };
474		if result == vk::Result::SUCCESS {
475			Ok(bufs)
476		} else {
477			Err(CallError::Vulkan(result))
478		}
479	}
480
481	/// Free command buffers back to their pool.
482	///
483	/// # Safety
484	/// All buffers must have finished execution on the GPU.
485	pub unsafe fn free_command_buffers(
486		pool: vk::CommandPool,
487		buffers: &[vk::CommandBuffer],
488	) -> Result<(), CallError> {
489		let f = device
490			.fp
491			.free_command_buffers
492			.ok_or(CallError::MissingCommand("vkFreeCommandBuffers"))?;
493		unsafe {
494			f(
495				device.handle,
496				pool,
497				u32::try_from(buffers.len()).map_err(|_| CallError::CountOverflow(buffers.len()))?,
498				buffers.as_ptr(),
499			)
500		};
501		Ok(())
502	}
503
504	/// Begin recording a command buffer.
505	///
506	/// # Safety
507	/// The command buffer must be in the initial or reset state.
508	pub unsafe fn begin_command_buffer(
509		command_buffer: vk::CommandBuffer,
510		begin_info: &vk::CommandBufferBeginInfo,
511	) -> Result<(), CallError> {
512		let f = device
513			.fp
514			.begin_command_buffer
515			.ok_or(CallError::MissingCommand("vkBeginCommandBuffer"))?;
516		let result = unsafe { f(command_buffer, begin_info) };
517		if result == vk::Result::SUCCESS {
518			Ok(())
519		} else {
520			Err(CallError::Vulkan(result))
521		}
522	}
523
524	/// End recording a command buffer.
525	///
526	/// # Safety
527	/// The command buffer must be in the recording state.
528	pub unsafe fn end_command_buffer(command_buffer: vk::CommandBuffer) -> Result<(), CallError> {
529		let f = device
530			.fp
531			.end_command_buffer
532			.ok_or(CallError::MissingCommand("vkEndCommandBuffer"))?;
533		let result = unsafe { f(command_buffer) };
534		if result == vk::Result::SUCCESS {
535			Ok(())
536		} else {
537			Err(CallError::Vulkan(result))
538		}
539	}
540
541	/// Fill a buffer region with a fixed 32-bit value.
542	///
543	/// # Safety
544	/// `buffer` must be bound to device memory, in the executable/pending state,
545	/// and `offset`/`size` must be 4-byte aligned.
546	pub unsafe fn cmd_fill_buffer(
547		command_buffer: vk::CommandBuffer,
548		buffer: vk::Buffer,
549		offset: vk::DeviceSize,
550		size: vk::DeviceSize,
551		data: u32,
552	) -> Result<(), CallError> {
553		let f = device
554			.fp
555			.cmd_fill_buffer
556			.ok_or(CallError::MissingCommand("vkCmdFillBuffer"))?;
557		unsafe { f(command_buffer, buffer, offset, size, data) };
558		Ok(())
559	}
560
561	/// Submit command buffers to a queue.
562	///
563	/// # Safety
564	/// All resources referenced by the command buffers must be live and not
565	/// mutated by the host until the fence signals.
566	pub unsafe fn queue_submit(
567		queue: vk::Queue,
568		submits: &[vk::SubmitInfo],
569		fence: vk::Fence,
570	) -> Result<(), CallError> {
571		let f = device
572			.fp
573			.queue_submit
574			.ok_or(CallError::MissingCommand("vkQueueSubmit"))?;
575		let result = unsafe {
576			f(
577				queue,
578				u32::try_from(submits.len()).map_err(|_| CallError::CountOverflow(submits.len()))?,
579				submits.as_ptr(),
580				fence,
581			)
582		};
583		if result == vk::Result::SUCCESS {
584			Ok(())
585		} else {
586			Err(CallError::Vulkan(result))
587		}
588	}
589
590	/// Wait for a queue to become idle (all submitted work complete).
591	///
592	/// # Safety
593	/// `queue` must be a live handle from this device.
594	pub unsafe fn queue_wait_idle(queue: vk::Queue) -> Result<(), CallError> {
595		let f = device
596			.fp
597			.queue_wait_idle
598			.ok_or(CallError::MissingCommand("vkQueueWaitIdle"))?;
599		let result = unsafe { f(queue) };
600		if result == vk::Result::SUCCESS {
601			Ok(())
602		} else {
603			Err(CallError::Vulkan(result))
604		}
605	}
606
607	/// Create a fence.
608	///
609	/// # Safety
610	/// `create_info` must be valid. The fence must be destroyed exactly once.
611	pub unsafe fn create_fence(
612		create_info: &vk::FenceCreateInfo,
613		allocator: Option<&vk::AllocationCallbacks>,
614	) -> Result<vk::Fence, CallError> {
615		let f = device
616			.fp
617			.create_fence
618			.ok_or(CallError::MissingCommand("vkCreateFence"))?;
619		let mut handle = vk::Fence::NULL;
620		let result = unsafe {
621			f(
622				device.handle,
623				create_info,
624				allocator.map_or(std::ptr::null(), |a| a),
625				&mut handle,
626			)
627		};
628		if result == vk::Result::SUCCESS {
629			Ok(handle)
630		} else {
631			Err(CallError::Vulkan(result))
632		}
633	}
634
635	/// Destroy a fence.
636	///
637	/// # Safety
638	/// The fence must not be pending or in use by the GPU.
639	pub unsafe fn destroy_fence(
640		fence: vk::Fence,
641		allocator: Option<&vk::AllocationCallbacks>,
642	) -> Result<(), CallError> {
643		let f = device
644			.fp
645			.destroy_fence
646			.ok_or(CallError::MissingCommand("vkDestroyFence"))?;
647		unsafe {
648			f(
649				device.handle,
650				fence,
651				allocator.map_or(std::ptr::null(), |a| a),
652			)
653		};
654		Ok(())
655	}
656
657	/// Block until one or all of the given fences signal, or the timeout expires.
658	///
659	/// Returns `Ok(true)` if all fences signalled within the timeout,
660	/// `Ok(false)` if the timeout expired first.
661	///
662	/// # Safety
663	/// All fences must be live handles from this device.
664	pub unsafe fn wait_for_fences(
665		fences: &[vk::Fence],
666		wait_all: bool,
667		timeout_ns: u64,
668	) -> Result<bool, CallError> {
669		let f = device
670			.fp
671			.wait_for_fences
672			.ok_or(CallError::MissingCommand("vkWaitForFences"))?;
673		let result = unsafe {
674			f(
675				device.handle,
676				u32::try_from(fences.len()).map_err(|_| CallError::CountOverflow(fences.len()))?,
677				fences.as_ptr(),
678				wait_all as vk::Bool32,
679				timeout_ns,
680			)
681		};
682		match result {
683			vk::Result::SUCCESS => Ok(true),
684			vk::Result::TIMEOUT => Ok(false),
685			other => Err(CallError::Vulkan(other)),
686		}
687	}
688
689	/// Reset fences to the unsignalled state.
690	///
691	/// # Safety
692	/// No fences in the slice may be pending (submitted but not yet signalled).
693	pub unsafe fn reset_fences(fences: &[vk::Fence]) -> Result<(), CallError> {
694		let f = device
695			.fp
696			.reset_fences
697			.ok_or(CallError::MissingCommand("vkResetFences"))?;
698		let result = unsafe {
699			f(
700				device.handle,
701				u32::try_from(fences.len()).map_err(|_| CallError::CountOverflow(fences.len()))?,
702				fences.as_ptr(),
703			)
704		};
705		if result == vk::Result::SUCCESS {
706			Ok(())
707		} else {
708			Err(CallError::Vulkan(result))
709		}
710	}
711
712}
713
714/// Create a logical device from a physical device.
715///
716/// # Errors
717/// Reports preflight/metadata failures, driver errors, invalid native outputs
718/// and unavailable destruction. If rollback is impossible,
719/// `CallError::InitializationFailed` retains the device for explicit recovery.
720///
721/// # Safety
722/// The create info, its reachable pointers and allocator callbacks must meet
723/// Vulkan valid usage. Allocator state must remain alive until destruction.
724pub unsafe fn create_device(
725	instance: &Instance,
726	physical_device: vk::PhysicalDevice,
727	create_info: &vk::DeviceCreateInfo,
728	allocator: Option<&vk::AllocationCallbacks>,
729) -> Result<Device, CallError> {
730	// Resolve indispensable context before making a native allocation.
731	let gdpa = instance
732		.fp()
733		.get_device_proc_addr
734		.ok_or(CallError::MissingCommand("vkGetDeviceProcAddr"))?;
735	let capabilities = if let Some(parent) = instance.capabilities() {
736		// A physical device supports at least 1.0. Higher requested versions must
737		// be capped using this selected device, not the loader's version.
738		let physical_version = if parent.api_version() == vk::API_VERSION_1_0 {
739			vk::API_VERSION_1_0
740		} else {
741			// SAFETY: caller guarantees this live physical device belongs to instance.
742			unsafe { instance.get_physical_device_properties(physical_device) }?.api_version
743		};
744		// SAFETY: caller guarantees validity of the device create-info graph.
745		Some(unsafe { parent.new_from_device_create_info(create_info, physical_version) }?)
746	} else {
747		None
748	};
749	let f = instance
750		.fp()
751		.create_device
752		.ok_or(CallError::MissingCommand("vkCreateDevice"))?;
753	let allocator_ptr = allocator.map_or(std::ptr::null(), |a| a);
754	let mut handle = vk::DeviceHandle::NULL;
755	// SAFETY: caller guarantees create-info validity; PFN is from a live instance.
756	let result = unsafe { f(physical_device, create_info, allocator_ptr, &mut handle) };
757	if result != vk::Result::SUCCESS {
758		return Err(CallError::Vulkan(result));
759	}
760	if handle.is_null() {
761		return Err(CallError::InvalidOutput(
762			"vkCreateDevice returned a null handle",
763		));
764	}
765	// SAFETY: successful creation returned a live non-null handle for gdpa.
766	let mut fp = unsafe { vk::FnDevice::new_from_resolver(gdpa, handle) };
767	if fp.destroy_device.is_none() {
768		let failure = InitializationFailure {
769			handle: CreatedHandle::Device(handle),
770			missing_command: "vkDestroyDevice",
771			library: instance.library.clone(),
772			instance: instance.handle(),
773			gipa: instance.gipa,
774			gdpa: Some(gdpa),
775		};
776		// SAFETY: the new device has no children/work; use its original callbacks.
777		return match unsafe { failure.destroy(allocator) } {
778			Ok(()) => Err(CallError::MissingCommand("vkDestroyDevice")),
779			Err(failure) => Err(CallError::InitializationFailed(failure)),
780		};
781	}
782	if let Some(capabilities) = &capabilities {
783		fp.retain_enabled(|command| capabilities.support(command) == CommandSupport::Enabled);
784	}
785	Ok(Device {
786		handle,
787		fp,
788		gdpa,
789		_library: instance.library.clone(),
790		capabilities,
791	})
792}
793
794impl Device {
795	/// Create a logical device through its parent instance's cached dispatch.
796	/// # Safety
797	/// Device create info and callbacks must satisfy Vulkan valid usage and remain live as required.
798	pub unsafe fn new(
799		instance: &Instance,
800		physical_device: vk::PhysicalDevice,
801		create_info: &vk::DeviceCreateInfo,
802		allocator: Option<&vk::AllocationCallbacks>,
803	) -> Result<Self, CallError> {
804		// SAFETY: same caller guarantees as the canonical operation.
805		unsafe { create_device(instance, physical_device, create_info, allocator) }
806	}
807}
808
809impl Instance {
810	/// Create a logical device through this instance's cached dispatch.
811	/// # Safety
812	/// Device create info and callbacks must satisfy Vulkan valid usage and remain live as required.
813	pub unsafe fn create_device(
814		&self,
815		physical_device: vk::PhysicalDevice,
816		create_info: &vk::DeviceCreateInfo,
817		allocator: Option<&vk::AllocationCallbacks>,
818	) -> Result<Device, CallError> {
819		// SAFETY: same caller guarantees as the canonical operation.
820		unsafe { create_device(self, physical_device, create_info, allocator) }
821	}
822}
823
824/// Destroy once, using callbacks compatible with device creation.
825///
826/// # Safety
827/// All children and outstanding device operations must be complete. The
828/// allocator must match creation, with its user data and functions still live.
829pub unsafe fn destroy_device(device: Device, allocator: Option<&vk::AllocationCallbacks>) {
830	// SAFETY: caller guarantees all destruction and allocator requirements.
831	unsafe {
832		device.fp.destroy_device.unwrap()(device.handle, allocator.map_or(std::ptr::null(), |a| a))
833	};
834}