Skip to main content

vk/
device.rs

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