wgpu_hal/vulkan/mod.rs
1/*!
2# Vulkan API internals.
3
4## Stack memory
5
6Ash expects slices, which we don't generally have available.
7We cope with this requirement by the combination of the following ways:
8 - temporarily allocating `Vec` on heap, where overhead is permitted
9 - growing temporary local storage
10
11## Framebuffers and Render passes
12
13Render passes are cached on the device and kept forever.
14
15Framebuffers are also cached on the device, but they are removed when
16any of the image views (they have) gets removed.
17If Vulkan supports image-less framebuffers,
18then the actual views are excluded from the framebuffer key.
19
20## Fences
21
22If timeline semaphores are available, they are used 1:1 with wgpu-hal fences.
23Otherwise, we manage a pool of `VkFence` objects behind each `hal::Fence`.
24
25!*/
26
27mod adapter;
28mod command;
29pub mod conv;
30mod descriptor;
31mod device;
32mod drm;
33mod instance;
34mod pool_trim;
35mod sampler;
36mod semaphore_list;
37mod swapchain;
38
39pub use adapter::PhysicalDeviceFeatures;
40
41use alloc::{boxed::Box, ffi::CString, sync::Arc, vec::Vec};
42use core::{borrow::Borrow, ffi::CStr, fmt, marker::PhantomData, mem, num::NonZeroU32};
43
44use arrayvec::ArrayVec;
45use ash::{ext, khr, vk};
46use bytemuck::{Pod, Zeroable};
47use hashbrown::HashSet;
48use parking_lot::{Mutex, RwLock};
49
50use naga::FastHashMap;
51use wgt::InternalCounter;
52
53use semaphore_list::SemaphoreList;
54
55use crate::vulkan::semaphore_list::{SemaphoreListMode, SemaphoreType};
56
57const MAX_TOTAL_ATTACHMENTS: usize = crate::MAX_COLOR_ATTACHMENTS * 2 + 1;
58
59#[derive(Clone, Debug)]
60pub struct Api;
61
62impl crate::Api for Api {
63 const VARIANT: wgt::Backend = wgt::Backend::Vulkan;
64
65 type Instance = Instance;
66 type Surface = Surface;
67 type Adapter = Adapter;
68 type Device = Device;
69
70 type Queue = Queue;
71 type CommandEncoder = CommandEncoder;
72 type CommandBuffer = CommandBuffer;
73
74 type Buffer = Buffer;
75 type Texture = Texture;
76 type SurfaceTexture = SurfaceTexture;
77 type TextureView = TextureView;
78 type Sampler = Sampler;
79 type QuerySet = QuerySet;
80 type Fence = Fence;
81 type AccelerationStructure = AccelerationStructure;
82 type PipelineCache = PipelineCache;
83
84 type BindGroupLayout = BindGroupLayout;
85 type BindGroup = BindGroup;
86 type PipelineLayout = PipelineLayout;
87 type ShaderModule = ShaderModule;
88 type RenderPipeline = RenderPipeline;
89 type ComputePipeline = ComputePipeline;
90 type RayTracingPipeline = RayTracingPipeline;
91}
92
93crate::impl_dyn_resource!(
94 Adapter,
95 AccelerationStructure,
96 BindGroup,
97 BindGroupLayout,
98 Buffer,
99 CommandBuffer,
100 CommandEncoder,
101 ComputePipeline,
102 Device,
103 Fence,
104 Instance,
105 PipelineCache,
106 PipelineLayout,
107 QuerySet,
108 Queue,
109 RenderPipeline,
110 RayTracingPipeline,
111 Sampler,
112 ShaderModule,
113 Surface,
114 SurfaceTexture,
115 Texture,
116 TextureView
117);
118
119struct DebugUtils {
120 extension: ext::debug_utils::Instance,
121 messenger: vk::DebugUtilsMessengerEXT,
122
123 /// Owning pointer to the debug messenger callback user data.
124 ///
125 /// `InstanceShared::drop` destroys the debug messenger before
126 /// dropping this, so the callback should never receive a dangling
127 /// user data pointer.
128 #[allow(dead_code)]
129 callback_data: Box<DebugUtilsMessengerUserData>,
130}
131
132#[derive(Debug)]
133pub struct DebugUtilsCreateInfo {
134 severity: vk::DebugUtilsMessageSeverityFlagsEXT,
135 message_type: vk::DebugUtilsMessageTypeFlagsEXT,
136 callback_data: Box<DebugUtilsMessengerUserData>,
137}
138
139#[derive(Debug)]
140/// The properties related to the validation layer needed for the
141/// DebugUtilsMessenger for their workarounds
142struct ValidationLayerProperties {
143 /// Validation layer description, from `vk::LayerProperties`.
144 layer_description: CString,
145
146 /// Validation layer specification version, from `vk::LayerProperties`.
147 layer_spec_version: u32,
148}
149
150/// User data needed by `instance::debug_utils_messenger_callback`.
151///
152/// When we create the [`vk::DebugUtilsMessengerEXT`], the `pUserData`
153/// pointer refers to one of these values.
154#[derive(Debug)]
155pub struct DebugUtilsMessengerUserData {
156 /// The properties related to the validation layer, if present
157 validation_layer_properties: Option<ValidationLayerProperties>,
158
159 /// If the OBS layer is present. OBS never increments the version of their layer,
160 /// so there's no reason to have the version.
161 has_obs_layer: bool,
162}
163
164pub struct InstanceShared {
165 raw: ash::Instance,
166 extensions: Vec<&'static CStr>,
167 flags: wgt::InstanceFlags,
168 memory_budget_thresholds: wgt::MemoryBudgetThresholds,
169 debug_utils: Option<DebugUtils>,
170 get_physical_device_properties: Option<khr::get_physical_device_properties2::Instance>,
171 entry: ash::Entry,
172 has_nv_optimus: bool,
173 android_sdk_version: u32,
174 /// The instance API version.
175 ///
176 /// Which is the version of Vulkan supported for instance-level functionality.
177 ///
178 /// It is associated with a `VkInstance` and its children,
179 /// except for a `VkPhysicalDevice` and its children.
180 instance_api_version: u32,
181
182 // The `drop_guard` field must be the last field of this struct so it is dropped last.
183 // Do not add new fields after it.
184 drop_guard: Option<crate::DropGuard>,
185}
186
187impl fmt::Debug for InstanceShared {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 let Self {
190 raw: _,
191 extensions,
192 flags,
193 memory_budget_thresholds,
194 debug_utils: _,
195 get_physical_device_properties: _,
196 entry: _,
197 has_nv_optimus,
198 android_sdk_version,
199 instance_api_version,
200 drop_guard: _,
201 } = self;
202 f.debug_struct("InstanceShared")
203 .field("extensions", extensions)
204 .field("flags", flags)
205 .field("memory_budget_thresholds", memory_budget_thresholds)
206 .field("has_nv_optimus", has_nv_optimus)
207 .field("android_sdk_version", android_sdk_version)
208 .field("instance_api_version", instance_api_version)
209 .finish_non_exhaustive()
210 }
211}
212
213#[derive(Debug)]
214pub struct Instance {
215 shared: Arc<InstanceShared>,
216}
217
218#[expect(missing_debug_implementations, reason = "TODO?")]
219pub struct Surface {
220 swapchain: RwLock<Option<Box<dyn swapchain::Swapchain>>>,
221 inner: Box<dyn swapchain::Surface>,
222}
223
224impl Surface {
225 /// Returns the raw Vulkan surface handle.
226 ///
227 /// Returns `None` if the surface is a DXGI surface.
228 pub unsafe fn raw_native_handle(&self) -> Option<vk::SurfaceKHR> {
229 Some(
230 self.inner
231 .as_any()
232 .downcast_ref::<swapchain::NativeSurface>()?
233 .as_raw(),
234 )
235 }
236
237 /// Get the raw Vulkan swapchain associated with this surface.
238 ///
239 /// Returns [`None`] if the surface is not configured or if the swapchain
240 /// is a DXGI swapchain.
241 pub fn raw_native_swapchain(&self) -> Option<vk::SwapchainKHR> {
242 let read = self.swapchain.read();
243 Some(
244 read.as_ref()?
245 .as_any()
246 .downcast_ref::<swapchain::NativeSwapchain>()?
247 .as_raw(),
248 )
249 }
250
251 /// Set the present timing information which will be used for the next [presentation](crate::Queue::present()) of this surface,
252 /// using [VK_GOOGLE_display_timing].
253 ///
254 /// This can be used to give an id to presentations, for future use of [`vk::PastPresentationTimingGOOGLE`].
255 /// Note that `wgpu-hal` does *not* provide a way to use that API - you should manually access this through [`ash`].
256 ///
257 /// This can also be used to add a "not before" timestamp to the presentation.
258 ///
259 /// The exact semantics of the fields are also documented in the [specification](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPresentTimeGOOGLE.html) for the extension.
260 ///
261 /// # Panics
262 ///
263 /// - If the surface hasn't been configured.
264 /// - If the surface has been configured for a DXGI swapchain.
265 /// - If the device doesn't [support present timing](wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING).
266 ///
267 /// [VK_GOOGLE_display_timing]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html
268 #[track_caller]
269 pub fn set_next_present_time(&self, present_timing: vk::PresentTimeGOOGLE) {
270 let mut swapchain = self.swapchain.write();
271 swapchain
272 .as_mut()
273 .expect("Surface should have been configured")
274 .as_any_mut()
275 .downcast_mut::<swapchain::NativeSwapchain>()
276 .expect("Surface should have a native Vulkan swapchain")
277 .set_next_present_time(present_timing);
278 }
279}
280
281#[derive(Debug)]
282pub struct SurfaceTexture {
283 index: u32,
284 texture: Texture,
285 metadata: Box<dyn swapchain::SurfaceTextureMetadata>,
286}
287
288impl crate::DynSurfaceTexture for SurfaceTexture {}
289
290impl Borrow<Texture> for SurfaceTexture {
291 fn borrow(&self) -> &Texture {
292 &self.texture
293 }
294}
295
296impl Borrow<dyn crate::DynTexture> for SurfaceTexture {
297 fn borrow(&self) -> &dyn crate::DynTexture {
298 &self.texture
299 }
300}
301
302#[derive(Debug)]
303pub struct Adapter {
304 raw: vk::PhysicalDevice,
305 instance: Arc<InstanceShared>,
306 //queue_families: Vec<vk::QueueFamilyProperties>,
307 known_memory_flags: vk::MemoryPropertyFlags,
308 phd_capabilities: adapter::PhysicalDeviceProperties,
309 phd_features: PhysicalDeviceFeatures,
310 downlevel_flags: wgt::DownlevelFlags,
311 private_caps: PrivateCapabilities,
312 workarounds: Workarounds,
313}
314
315// TODO there's no reason why this can't be unified--the function pointers should all be the same--it's not clear how to do this with `ash`.
316enum ExtensionFn<T> {
317 /// The loaded function pointer struct for an extension.
318 Extension(T),
319 /// The extension was promoted to a core version of Vulkan and the functions on `ash`'s `DeviceV1_x` traits should be used.
320 Promoted,
321}
322
323struct DeviceExtensionFunctions {
324 debug_utils: Option<ext::debug_utils::Device>,
325 draw_indirect_count: Option<khr::draw_indirect_count::Device>,
326 timeline_semaphore: Option<ExtensionFn<khr::timeline_semaphore::Device>>,
327 ray_tracing: Option<RayTracingDeviceExtensionFunctions>,
328 ray_tracing_pipelines: Option<khr::ray_tracing_pipeline::Device>,
329 mesh_shading: Option<ext::mesh_shader::Device>,
330 #[cfg_attr(not(unix), allow(dead_code))]
331 external_memory_fd: Option<khr::external_memory_fd::Device>,
332}
333
334struct RayTracingDeviceExtensionFunctions {
335 acceleration_structure: khr::acceleration_structure::Device,
336 buffer_device_address: khr::buffer_device_address::Device,
337}
338
339/// Set of internal capabilities, which don't show up in the exposed
340/// device geometry, but affect the code paths taken internally.
341#[derive(Clone, Debug)]
342struct PrivateCapabilities {
343 image_view_usage: bool,
344 timeline_semaphores: bool,
345 texture_d24: bool,
346 texture_d24_s8: bool,
347 texture_s8: bool,
348 /// Ability to present contents to any screen. Only needed to work around broken platform configurations.
349 can_present: bool,
350 non_coherent_map_mask: wgt::BufferAddress,
351 multi_draw_indirect: bool,
352 max_draw_indirect_count: u32,
353
354 /// True if this adapter advertises the [`robustBufferAccess`][vrba] feature.
355 ///
356 /// Note that Vulkan's `robustBufferAccess` is not sufficient to implement
357 /// `wgpu_hal`'s guarantee that shaders will not access buffer contents via
358 /// a given bindgroup binding outside that binding's [accessible
359 /// region][ar]. Enabling `robustBufferAccess` does ensure that
360 /// out-of-bounds reads and writes are not undefined behavior (that's good),
361 /// but still permits out-of-bounds reads to return data from anywhere
362 /// within the buffer, not just the accessible region.
363 ///
364 /// [ar]: ../struct.BufferBinding.html#accessible-region
365 /// [vrba]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#features-robustBufferAccess
366 robust_buffer_access: bool,
367
368 robust_image_access: bool,
369
370 /// True if this adapter supports the [`VK_EXT_robustness2`] extension's
371 /// [`robustBufferAccess2`] feature.
372 ///
373 /// This is sufficient to implement `wgpu_hal`'s [required bounds-checking][ar] of
374 /// shader accesses to buffer contents. If this feature is not available,
375 /// this backend must have Naga inject bounds checks in the generated
376 /// SPIR-V.
377 ///
378 /// [`VK_EXT_robustness2`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_robustness2.html
379 /// [`robustBufferAccess2`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceRobustness2FeaturesEXT.html#features-robustBufferAccess2
380 /// [ar]: ../struct.BufferBinding.html#accessible-region
381 robust_buffer_access2: bool,
382
383 robust_image_access2: bool,
384 zero_initialize_workgroup_memory: bool,
385 image_format_list: bool,
386 maximum_samplers: u32,
387
388 /// True if this adapter supports the [`VK_KHR_shader_integer_dot_product`] extension
389 /// (promoted to Vulkan 1.3).
390 ///
391 /// This is used to generate optimized code for WGSL's `dot4{I, U}8Packed`.
392 ///
393 /// [`VK_KHR_shader_integer_dot_product`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_integer_dot_product.html
394 shader_integer_dot_product: bool,
395
396 /// True if this adapter supports 8-bit integers provided by the
397 /// [`VK_KHR_shader_float16_int8`] extension (promoted to Vulkan 1.2).
398 ///
399 /// Allows shaders to declare the "Int8" capability. Note, however, that this
400 /// feature alone allows the use of 8-bit integers "only in the `Private`,
401 /// `Workgroup` (for non-Block variables), and `Function` storage classes"
402 /// ([see spec]). To use 8-bit integers in the interface storage classes (e.g.,
403 /// `StorageBuffer`), you also need to enable the corresponding feature in
404 /// `VkPhysicalDevice8BitStorageFeatures` and declare the corresponding SPIR-V
405 /// capability (e.g., `StorageBuffer8BitAccess`).
406 ///
407 /// [`VK_KHR_shader_float16_int8`]: https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_shader_float16_int8.html
408 /// [see spec]: https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDeviceShaderFloat16Int8Features.html#extension-features-shaderInt8
409 shader_int8: bool,
410
411 /// This is done to panic before undefined behavior, and is imperfect.
412 /// Basically, to allow implementations to emulate mv using instancing, if you
413 /// want to draw `n` instances to VR, you must draw `2n` instances, but you
414 /// can never draw more than `u32::MAX` instances. Therefore, when drawing
415 /// multiview on some vulkan implementations, it might restrict the instance
416 /// count, which isn't usually a thing in webgpu. We don't expose this limit
417 /// because its strange, i.e. only occurs on certain vulkan implementations
418 /// if you are drawing more than 128 million instances. We still want to avoid
419 /// undefined behavior in this situation, so we panic if the limit is violated.
420 multiview_instance_index_limit: u32,
421
422 /// BufferUsages::ACCELERATION_STRUCTURE_SCRATCH allows usage as a scratch buffer.
423 /// Vulkan has no way to specify this as a usage, and it maps to other usages, but
424 /// these usages do not have as high of an alignment requirement using the buffer as
425 /// a scratch buffer when building acceleration structures.
426 scratch_buffer_alignment: u32,
427
428 /// `get_raytracing_pipeline_group_data` requires both a group count and a data size.
429 /// The data size parameter is just this * the group count, so we store this to not
430 /// require an unnecessary parameter.
431 ray_tracing_pipeline_group_data_size: u32,
432}
433
434bitflags::bitflags!(
435 /// Workaround flags.
436 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
437 pub struct Workarounds: u32 {
438 /// Only generate SPIR-V for one entry point at a time.
439 const SEPARATE_ENTRY_POINTS = 0x1;
440 /// Qualcomm OOMs when there are zero color attachments but a non-null pointer
441 /// to a subpass resolve attachment array. This nulls out that pointer in that case.
442 const EMPTY_RESOLVE_ATTACHMENT_LISTS = 0x2;
443 /// If the following code returns false, then nvidia will end up filling the wrong range.
444 ///
445 /// ```skip
446 /// fn nvidia_succeeds() -> bool {
447 /// # let (copy_length, start_offset) = (0, 0);
448 /// if copy_length >= 4096 {
449 /// if start_offset % 16 != 0 {
450 /// if copy_length == 4096 {
451 /// return true;
452 /// }
453 /// if copy_length % 16 == 0 {
454 /// return false;
455 /// }
456 /// }
457 /// }
458 /// true
459 /// }
460 /// ```
461 ///
462 /// As such, we need to make sure all calls to vkCmdFillBuffer are aligned to 16 bytes
463 /// if they cover a range of 4096 bytes or more.
464 const FORCE_FILL_BUFFER_WITH_SIZE_GREATER_4096_ALIGNED_OFFSET_16 = 0x4;
465 }
466);
467
468#[derive(Clone, Debug, Eq, Hash, PartialEq)]
469struct AttachmentKey {
470 format: vk::Format,
471 layout: vk::ImageLayout,
472 ops: crate::AttachmentOps,
473}
474
475impl AttachmentKey {
476 /// Returns an attachment key for a compatible attachment.
477 fn compatible(format: vk::Format, layout: vk::ImageLayout) -> Self {
478 Self {
479 format,
480 layout,
481 ops: crate::AttachmentOps::all(),
482 }
483 }
484}
485
486#[derive(Clone, Eq, Hash, PartialEq)]
487struct ColorAttachmentKey {
488 base: AttachmentKey,
489 resolve: Option<AttachmentKey>,
490}
491
492#[derive(Clone, Eq, Hash, PartialEq)]
493struct DepthStencilAttachmentKey {
494 base: AttachmentKey,
495 stencil_ops: crate::AttachmentOps,
496}
497
498#[derive(Clone, Eq, Default, Hash, PartialEq)]
499struct RenderPassKey {
500 colors: ArrayVec<Option<ColorAttachmentKey>, { crate::MAX_COLOR_ATTACHMENTS }>,
501 depth_stencil: Option<DepthStencilAttachmentKey>,
502 sample_count: u32,
503 multiview_mask: Option<NonZeroU32>,
504}
505
506struct DeviceShared {
507 raw: ash::Device,
508 family_index: u32,
509 queue_index: u32,
510 raw_queue: vk::Queue,
511 instance: Arc<InstanceShared>,
512 physical_device: vk::PhysicalDevice,
513 enabled_extensions: Vec<&'static CStr>,
514 extension_fns: DeviceExtensionFunctions,
515 vendor_id: u32,
516 pipeline_cache_validation_key: [u8; 16],
517 timestamp_period: f32,
518 private_caps: PrivateCapabilities,
519 workarounds: Workarounds,
520 features: wgt::Features,
521 render_passes: Mutex<FastHashMap<RenderPassKey, vk::RenderPass>>,
522 sampler_cache: Mutex<sampler::SamplerCache>,
523 memory_allocations_counter: InternalCounter,
524
525 /// Because we have cached framebuffers which are not deleted from until
526 /// the device is destroyed, if the implementation of vulkan re-uses handles
527 /// we need some way to differentiate between the old handle and the new handle.
528 /// This factory allows us to have a dedicated identity value for each texture.
529 texture_identity_factory: ResourceIdentityFactory<vk::Image>,
530 /// As above, for texture views.
531 texture_view_identity_factory: ResourceIdentityFactory<vk::ImageView>,
532
533 empty_descriptor_set_layout: vk::DescriptorSetLayout,
534
535 /// Whether a bind group layout lets a stage other than fragment or
536 /// compute read a texture. Until one does, a texture read by shaders is
537 /// waited for only in those two stages, so a pass that samples what the
538 /// previous pass drew no longer holds its vertex work behind that pass's
539 /// fragment work. Set once, never cleared. Command buffers begun after
540 /// it is set wait in upstream's stages, and a [`CatchUpBarrier`] covers
541 /// the textures earlier buffers left waiting in fewer.
542 texture_reads_before_fragment: core::sync::atomic::AtomicBool,
543
544 // The `drop_guard` field must be the last field of this struct so it is dropped last.
545 // Do not add new fields after it.
546 drop_guard: Option<crate::DropGuard>,
547}
548
549impl Drop for DeviceShared {
550 fn drop(&mut self) {
551 for &raw in self.render_passes.lock().values() {
552 unsafe { self.raw.destroy_render_pass(raw, None) };
553 }
554 unsafe {
555 self.raw
556 .destroy_descriptor_set_layout(self.empty_descriptor_set_layout, None)
557 };
558 if self.drop_guard.is_none() {
559 unsafe { self.raw.destroy_device(None) };
560 }
561 }
562}
563
564/// The share of the allocator's smallest memory block from which a buffer or
565/// texture takes a dedicated allocation instead of a range of a shared block.
566///
567/// Resources of other lifetimes stay scattered through a shared block, so its
568/// free space splits into small ranges: on a Mali-G76 with 4 MiB host blocks,
569/// a block with 2.9 MiB free held no free 512 KiB range. Each larger buffer
570/// that a frame ring or a glyph arena grew to then took a new block, and the
571/// GL memory the process reports keeps every block it ever had.
572const DEDICATED_ALLOCATION_BLOCK_SHARE: u64 = 8;
573
574#[expect(
575 missing_debug_implementations,
576 reason = "needs work to not be disastrously verbose"
577)]
578pub struct Device {
579 mem_allocator: Mutex<gpu_allocator::vulkan::Allocator>,
580 desc_allocator: Mutex<descriptor::DescriptorAllocator>,
581 valid_ash_memory_types: u32,
582 /// Resources whose memory takes at least this many bytes get dedicated
583 /// allocations; see [`DEDICATED_ALLOCATION_BLOCK_SHARE`].
584 dedicated_allocation_bytes: u64,
585 naga_options: naga::back::spv::Options<'static>,
586 #[cfg(feature = "renderdoc")]
587 render_doc: crate::auxil::renderdoc::RenderDoc,
588 counters: Arc<wgt::HalCounters>,
589 // Struct members are dropped from first to last, put the Device last to ensure that
590 // all resources that depends on it are destroyed before it like the mem_allocator
591 shared: Arc<DeviceShared>,
592}
593
594impl Drop for Device {
595 fn drop(&mut self) {}
596}
597
598/// Semaphores for forcing queue submissions to run in order.
599///
600/// The [`wgpu_hal::Queue`] trait promises that if two calls to [`submit`] are
601/// ordered, then the first submission will finish on the GPU before the second
602/// submission begins. To get this behavior on Vulkan we need to pass semaphores
603/// to [`vkQueueSubmit`] for the commands to wait on before beginning execution,
604/// and to signal when their execution is done.
605///
606/// Normally this can be done with a single semaphore, waited on and then
607/// signalled for each submission. At any given time there's exactly one
608/// submission that would signal the semaphore, and exactly one waiting on it,
609/// as Vulkan requires.
610///
611/// However, as of Oct 2021, bug [#5508] in the Mesa ANV drivers caused them to
612/// hang if we use a single semaphore. The workaround is to alternate between
613/// two semaphores. The bug has been fixed in Mesa, but we should probably keep
614/// the workaround until, say, Oct 2026.
615///
616/// [`wgpu_hal::Queue`]: crate::Queue
617/// [`submit`]: crate::Queue::submit
618/// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit
619/// [#5508]: https://gitlab.freedesktop.org/mesa/mesa/-/issues/5508
620#[derive(Clone)]
621struct RelaySemaphores {
622 /// The semaphore the next submission should wait on before beginning
623 /// execution on the GPU. This is `None` for the first submission, which
624 /// should not wait on anything at all.
625 wait: Option<vk::Semaphore>,
626
627 /// The semaphore the next submission should signal when it has finished
628 /// execution on the GPU.
629 signal: vk::Semaphore,
630}
631
632impl RelaySemaphores {
633 fn new(device: &DeviceShared) -> Result<Self, crate::DeviceError> {
634 Ok(Self {
635 wait: None,
636 signal: device.new_binary_semaphore("RelaySemaphores: 1")?,
637 })
638 }
639
640 /// Advances the semaphores, returning the semaphores that should be used for a submission.
641 fn advance(&mut self, device: &DeviceShared) -> Result<Self, crate::DeviceError> {
642 let old = self.clone();
643
644 // Build the state for the next submission.
645 match self.wait {
646 None => {
647 // The `old` values describe the first submission to this queue.
648 // The second submission should wait on `old.signal`, and then
649 // signal a new semaphore which we'll create now.
650 self.wait = Some(old.signal);
651 self.signal = device.new_binary_semaphore("RelaySemaphores: 2")?;
652 }
653 Some(ref mut wait) => {
654 // What this submission signals, the next should wait.
655 mem::swap(wait, &mut self.signal);
656 }
657 };
658
659 Ok(old)
660 }
661
662 /// Destroys the semaphores.
663 unsafe fn destroy(&self, device: &ash::Device) {
664 unsafe {
665 if let Some(wait) = self.wait {
666 device.destroy_semaphore(wait, None);
667 }
668 device.destroy_semaphore(self.signal, None);
669 }
670 }
671}
672
673pub struct Queue {
674 raw: vk::Queue,
675 device: Arc<DeviceShared>,
676 family_index: u32,
677 relay_semaphores: Mutex<RelaySemaphores>,
678 signal_semaphores: Mutex<SemaphoreList>,
679 wait_semaphores: Mutex<SemaphoreList>,
680 catch_up_barrier: Mutex<Option<CatchUpBarrier>>,
681}
682
683impl fmt::Debug for Queue {
684 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685 let Self {
686 raw: _,
687 device: _,
688 family_index,
689 relay_semaphores: _,
690 signal_semaphores: _,
691 wait_semaphores: _,
692 catch_up_barrier: _,
693 } = self;
694 f.debug_struct("Queue")
695 .field("family_index", family_index)
696 .finish_non_exhaustive()
697 }
698}
699
700impl Queue {
701 pub fn as_raw(&self) -> vk::Queue {
702 self.raw
703 }
704}
705
706impl Drop for Queue {
707 fn drop(&mut self) {
708 unsafe { self.relay_semaphores.lock().destroy(&self.device.raw) };
709 if let Some(barrier) = self.catch_up_barrier.get_mut().take() {
710 unsafe { self.device.raw.destroy_command_pool(barrier.pool, None) };
711 }
712 }
713}
714
715/// A reusable command buffer that orders all earlier work and writes before
716/// all later work. A submission runs it where it moves from command buffers
717/// begun before the device widened its texture stages to ones begun after.
718///
719/// The earlier buffers waited for texture writes only in fragment and
720/// compute, and a later buffer may read such a texture in a vertex shader
721/// while the texture stays in a read state, which takes no new barrier.
722/// Separate submissions already run one after another ([`RelaySemaphores`]),
723/// and waits for earlier reads need nothing: a barrier's source stages
724/// include every logically earlier stage.
725struct CatchUpBarrier {
726 pool: vk::CommandPool,
727 raw: vk::CommandBuffer,
728}
729
730impl CatchUpBarrier {
731 fn record(device: &DeviceShared, family_index: u32) -> Result<Self, crate::DeviceError> {
732 let pool_info = vk::CommandPoolCreateInfo::default().queue_family_index(family_index);
733 let pool = unsafe { device.raw.create_command_pool(&pool_info, None) }
734 .map_err(map_host_device_oom_err)?;
735 match unsafe { Self::record_into(device, pool) } {
736 Ok(raw) => Ok(Self { pool, raw }),
737 Err(err) => {
738 unsafe { device.raw.destroy_command_pool(pool, None) };
739 Err(err)
740 }
741 }
742 }
743
744 unsafe fn record_into(
745 device: &DeviceShared,
746 pool: vk::CommandPool,
747 ) -> Result<vk::CommandBuffer, crate::DeviceError> {
748 let allocate_info = vk::CommandBufferAllocateInfo::default()
749 .command_pool(pool)
750 .level(vk::CommandBufferLevel::PRIMARY)
751 .command_buffer_count(1);
752 let raw = unsafe { device.raw.allocate_command_buffers(&allocate_info) }
753 .map_err(map_host_device_oom_err)?
754 .pop()
755 .ok_or(crate::DeviceError::Unexpected)?;
756 let begin_info = vk::CommandBufferBeginInfo::default()
757 .flags(vk::CommandBufferUsageFlags::SIMULTANEOUS_USE);
758 unsafe { device.raw.begin_command_buffer(raw, &begin_info) }
759 .map_err(map_host_device_oom_err)?;
760 let barrier = vk::MemoryBarrier::default()
761 .src_access_mask(vk::AccessFlags::MEMORY_WRITE)
762 .dst_access_mask(vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE);
763 unsafe {
764 device.raw.cmd_pipeline_barrier(
765 raw,
766 vk::PipelineStageFlags::ALL_COMMANDS,
767 vk::PipelineStageFlags::ALL_COMMANDS,
768 vk::DependencyFlags::empty(),
769 &[barrier],
770 &[],
771 &[],
772 )
773 };
774 unsafe { device.raw.end_command_buffer(raw) }.map_err(map_host_device_oom_err)?;
775 Ok(raw)
776 }
777}
778
779impl Queue {
780 /// The Vulkan command buffers of a submission, with the
781 /// [`CatchUpBarrier`] ahead of each widened buffer that follows an
782 /// unwidened one.
783 fn submission(
784 &self,
785 command_buffers: &[&CommandBuffer],
786 ) -> Result<Vec<vk::CommandBuffer>, crate::DeviceError> {
787 let mut raw = Vec::with_capacity(command_buffers.len() + 1);
788 let mut unwidened_before = false;
789 for cmd in command_buffers {
790 if !cmd.texture_stages_widened {
791 unwidened_before = true;
792 } else if unwidened_before {
793 raw.push(self.catch_up_barrier()?);
794 unwidened_before = false;
795 }
796 raw.push(cmd.raw);
797 }
798 Ok(raw)
799 }
800
801 fn catch_up_barrier(&self) -> Result<vk::CommandBuffer, crate::DeviceError> {
802 let mut barrier = self.catch_up_barrier.lock();
803 if let Some(ref barrier) = *barrier {
804 return Ok(barrier.raw);
805 }
806 let recorded = CatchUpBarrier::record(&self.device, self.family_index)?;
807 let raw = recorded.raw;
808 *barrier = Some(recorded);
809 Ok(raw)
810 }
811}
812#[derive(Debug)]
813enum BufferMemoryBacking {
814 Managed(gpu_allocator::vulkan::Allocation),
815 VulkanMemory {
816 memory: vk::DeviceMemory,
817 offset: u64,
818 size: u64,
819 },
820}
821impl BufferMemoryBacking {
822 fn memory(&self) -> vk::DeviceMemory {
823 match self {
824 Self::Managed(m) => unsafe { m.memory() },
825 Self::VulkanMemory { memory, .. } => *memory,
826 }
827 }
828 fn offset(&self) -> u64 {
829 match self {
830 Self::Managed(m) => m.offset(),
831 Self::VulkanMemory { offset, .. } => *offset,
832 }
833 }
834 fn size(&self) -> u64 {
835 match self {
836 Self::Managed(m) => m.size(),
837 Self::VulkanMemory { size, .. } => *size,
838 }
839 }
840}
841/// Describes who owns a [`Buffer`]'s `vk::Buffer` handle and its backing memory,
842/// and therefore what cleanup is required when the buffer is destroyed.
843#[derive(Debug)]
844enum BufferOwnership {
845 /// wgpu-hal owns the `vk::Buffer` and its backing memory. On cleanup the buffer
846 /// handle is destroyed and the memory is released.
847 Managed(Mutex<BufferMemoryBacking>),
848 /// wgpu-hal owns the `vk::Buffer` handle but the backing memory is kept alive
849 /// by the caller. On cleanup only the buffer handle is destroyed.
850 RawHandle,
851 /// Caller owns the `vk::Buffer` and its backing memory. On cleanup the
852 /// [`crate::DropGuard`] runs the caller's cleanup callback and wgpu-hal touches
853 /// neither the handle nor the memory.
854 External(crate::DropGuard),
855}
856
857#[derive(Debug)]
858pub struct Buffer {
859 raw: vk::Buffer,
860
861 // This field must be last, because it may contain a `DropGuard` which needs to be dropped after all other fields.
862 ownership: BufferOwnership,
863}
864impl Buffer {
865 /// # Safety
866 ///
867 /// - `vk_buffer`'s memory must be managed by the caller
868 /// - Externally imported buffers can't be mapped by `wgpu`
869 pub unsafe fn from_raw(vk_buffer: vk::Buffer) -> Self {
870 Self {
871 raw: vk_buffer,
872 ownership: BufferOwnership::RawHandle,
873 }
874 }
875
876 /// # Safety
877 /// - `vk_buffer` must outlive the returned `Buffer`.
878 /// - wgpu-hal will NOT call `vkDestroyBuffer`; the caller remains responsible for the buffer handle's destruction.
879 /// The `drop_callback` runs when the `Buffer` drops and may be used to release caller-side bookkeeping.
880 /// - Externally imported buffers can't be mapped by `wgpu`.
881 pub unsafe fn from_raw_externally_owned(
882 vk_buffer: vk::Buffer,
883 drop_callback: crate::DropCallback,
884 ) -> Self {
885 Self {
886 raw: vk_buffer,
887 ownership: BufferOwnership::External(crate::DropGuard::new(drop_callback)),
888 }
889 }
890
891 /// # Safety
892 /// - We will use this buffer and the buffer's backing memory range as if we have exclusive ownership over it, until the wgpu resource is dropped and the wgpu-hal object is cleaned up
893 /// - Externally imported buffers can't be mapped by `wgpu`
894 /// - `offset` and `size` must be valid with the allocation of `memory`
895 pub unsafe fn from_raw_managed(
896 vk_buffer: vk::Buffer,
897 memory: vk::DeviceMemory,
898 offset: u64,
899 size: u64,
900 ) -> Self {
901 Self {
902 raw: vk_buffer,
903 ownership: BufferOwnership::Managed(Mutex::new(BufferMemoryBacking::VulkanMemory {
904 memory,
905 offset,
906 size,
907 })),
908 }
909 }
910
911 /// # Safety
912 /// - The buffer handle must not be manually destroyed
913 pub unsafe fn raw_handle(&self) -> vk::Buffer {
914 self.raw
915 }
916}
917
918impl crate::DynBuffer for Buffer {}
919
920#[derive(Debug)]
921pub struct AccelerationStructure {
922 raw: vk::AccelerationStructureKHR,
923 buffer: vk::Buffer,
924 allocation: gpu_allocator::vulkan::Allocation,
925 compacted_size_query: Option<vk::QueryPool>,
926}
927
928impl crate::DynAccelerationStructure for AccelerationStructure {}
929
930#[derive(Debug)]
931pub enum TextureMemory {
932 // shared memory in GPU allocator (owned by wgpu-hal)
933 Allocation(gpu_allocator::vulkan::Allocation),
934
935 // dedicated memory (owned by wgpu-hal)
936 Dedicated(vk::DeviceMemory),
937
938 // memory not owned by wgpu
939 External,
940}
941
942#[derive(Debug)]
943pub struct Texture {
944 raw: vk::Image,
945 memory: TextureMemory,
946 format: wgt::TextureFormat,
947 copy_size: crate::CopyExtent,
948 identity: ResourceIdentity<vk::Image>,
949
950 // The `drop_guard` field must be the last field of this struct so it is dropped last.
951 // Do not add new fields after it.
952 drop_guard: Option<crate::DropGuard>,
953}
954
955impl crate::DynTexture for Texture {}
956
957impl Texture {
958 /// # Safety
959 ///
960 /// - The image handle must not be manually destroyed
961 pub unsafe fn raw_handle(&self) -> vk::Image {
962 self.raw
963 }
964
965 /// # Safety
966 ///
967 /// - The caller must not free the `vk::DeviceMemory` or
968 /// `gpu_alloc::MemoryBlock` in the returned `TextureMemory`.
969 pub unsafe fn memory(&self) -> &TextureMemory {
970 &self.memory
971 }
972}
973
974pub struct TextureView {
975 raw_texture: vk::Image,
976 raw: vk::ImageView,
977 _layers: NonZeroU32,
978 format: wgt::TextureFormat,
979 raw_format: vk::Format,
980 base_mip_level: u32,
981 dimension: wgt::TextureViewDimension,
982 texture_identity: ResourceIdentity<vk::Image>,
983 view_identity: ResourceIdentity<vk::ImageView>,
984}
985
986// Written out to leave `raw_format` alone: every hal resource is formatted
987// through its `dyn` vtable, which keeps ash's large `vk::Format` table in
988// every binary for a value `format` already names.
989impl fmt::Debug for TextureView {
990 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
991 f.debug_struct("TextureView")
992 .field("raw", &self.raw)
993 .field("format", &self.format)
994 .field("dimension", &self.dimension)
995 .field("base_mip_level", &self.base_mip_level)
996 .finish_non_exhaustive()
997 }
998}
999
1000impl crate::DynTextureView for TextureView {}
1001
1002impl TextureView {
1003 /// # Safety
1004 ///
1005 /// - The image view handle must not be manually destroyed
1006 pub unsafe fn raw_handle(&self) -> vk::ImageView {
1007 self.raw
1008 }
1009
1010 /// Returns the raw texture view, along with its identity.
1011 fn identified_raw_view(&self) -> IdentifiedTextureView {
1012 IdentifiedTextureView {
1013 raw: self.raw,
1014 identity: self.view_identity,
1015 }
1016 }
1017}
1018
1019pub struct Sampler {
1020 raw: vk::Sampler,
1021 create_info: vk::SamplerCreateInfo<'static>,
1022}
1023
1024// Written out to leave `create_info` alone: formatting it keeps ash's
1025// `vk::StructureType` table, about 36 KB of code, in every binary.
1026impl fmt::Debug for Sampler {
1027 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1028 f.debug_struct("Sampler")
1029 .field("raw", &self.raw)
1030 .finish_non_exhaustive()
1031 }
1032}
1033
1034impl crate::DynSampler for Sampler {}
1035
1036/// Information about a binding within a specific BindGroupLayout / BindGroup.
1037/// This will be used to construct a [`naga::back::spv::BindingInfo`], where
1038/// the descriptor set value will be taken from the index of the group.
1039#[derive(Copy, Clone, Debug)]
1040struct BindingInfo {
1041 binding: u32,
1042 binding_array_size: Option<NonZeroU32>,
1043}
1044
1045#[derive(Debug)]
1046pub struct BindGroupLayout {
1047 raw: vk::DescriptorSetLayout,
1048 desc_count: descriptor::DescriptorCounts,
1049 /// Sorted list of entries.
1050 entries: Box<[wgt::BindGroupLayoutEntry]>,
1051 /// Map of original binding index to remapped binding index and optional
1052 /// array size.
1053 binding_map: Vec<(u32, BindingInfo)>,
1054 contains_binding_arrays: bool,
1055}
1056
1057impl crate::DynBindGroupLayout for BindGroupLayout {}
1058
1059#[derive(Debug)]
1060pub struct PipelineLayout {
1061 raw: vk::PipelineLayout,
1062 binding_map: naga::back::spv::BindingMap,
1063}
1064
1065impl crate::DynPipelineLayout for PipelineLayout {}
1066
1067#[derive(Debug)]
1068pub struct BindGroup {
1069 set: descriptor::DescriptorSet,
1070}
1071
1072impl crate::DynBindGroup for BindGroup {}
1073
1074/// Miscellaneous allocation recycling pool for `CommandAllocator`.
1075#[derive(Default)]
1076struct Temp {
1077 marker: Vec<u8>,
1078 buffer_barriers: Vec<vk::BufferMemoryBarrier<'static>>,
1079 image_barriers: Vec<vk::ImageMemoryBarrier<'static>>,
1080}
1081
1082impl Temp {
1083 fn clear(&mut self) {
1084 self.marker.clear();
1085 self.buffer_barriers.clear();
1086 self.image_barriers.clear();
1087 }
1088
1089 fn make_c_str(&mut self, name: &str) -> &CStr {
1090 self.marker.clear();
1091 self.marker.extend_from_slice(name.as_bytes());
1092 self.marker.push(0);
1093 unsafe { CStr::from_bytes_with_nul_unchecked(&self.marker) }
1094 }
1095}
1096
1097/// Generates unique IDs for each resource of type `T`.
1098///
1099/// Because vk handles are not permanently unique, this
1100/// provides a way to generate unique IDs for each resource.
1101struct ResourceIdentityFactory<T> {
1102 #[cfg(not(target_has_atomic = "64"))]
1103 next_id: Mutex<u64>,
1104 #[cfg(target_has_atomic = "64")]
1105 next_id: core::sync::atomic::AtomicU64,
1106 _phantom: PhantomData<T>,
1107}
1108
1109impl<T> ResourceIdentityFactory<T> {
1110 fn new() -> Self {
1111 Self {
1112 #[cfg(not(target_has_atomic = "64"))]
1113 next_id: Mutex::new(0),
1114 #[cfg(target_has_atomic = "64")]
1115 next_id: core::sync::atomic::AtomicU64::new(0),
1116 _phantom: PhantomData,
1117 }
1118 }
1119
1120 /// Returns a new unique ID for a resource of type `T`.
1121 fn next(&self) -> ResourceIdentity<T> {
1122 #[cfg(not(target_has_atomic = "64"))]
1123 {
1124 let mut next_id = self.next_id.lock();
1125 let id = *next_id;
1126 *next_id += 1;
1127 ResourceIdentity {
1128 id,
1129 _phantom: PhantomData,
1130 }
1131 }
1132
1133 #[cfg(target_has_atomic = "64")]
1134 ResourceIdentity {
1135 id: self
1136 .next_id
1137 .fetch_add(1, core::sync::atomic::Ordering::Relaxed),
1138 _phantom: PhantomData,
1139 }
1140 }
1141}
1142
1143/// A unique identifier for a resource of type `T`.
1144///
1145/// This is used as a hashable key for resources, which
1146/// is permanently unique through the lifetime of the program.
1147#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
1148struct ResourceIdentity<T> {
1149 id: u64,
1150 _phantom: PhantomData<T>,
1151}
1152
1153#[derive(Clone, Eq, Hash, PartialEq)]
1154struct FramebufferKey {
1155 raw_pass: vk::RenderPass,
1156 /// Because this is used as a key in a hash map, we need to include the identity
1157 /// so that this hashes differently, even if the ImageView handles are the same
1158 /// between different views.
1159 attachment_identities: ArrayVec<ResourceIdentity<vk::ImageView>, { MAX_TOTAL_ATTACHMENTS }>,
1160 /// While this is redundant for calculating the hash, we need access to an array
1161 /// of all the raw ImageViews when we are creating the actual framebuffer,
1162 /// so we store this here.
1163 attachment_views: ArrayVec<vk::ImageView, { MAX_TOTAL_ATTACHMENTS }>,
1164 extent: wgt::Extent3d,
1165}
1166
1167impl FramebufferKey {
1168 fn push_view(&mut self, view: IdentifiedTextureView) {
1169 self.attachment_identities.push(view.identity);
1170 self.attachment_views.push(view.raw);
1171 }
1172}
1173
1174/// A texture view paired with its identity.
1175#[derive(Copy, Clone)]
1176struct IdentifiedTextureView {
1177 raw: vk::ImageView,
1178 identity: ResourceIdentity<vk::ImageView>,
1179}
1180
1181#[derive(Clone, Eq, Hash, PartialEq)]
1182struct TempTextureViewKey {
1183 texture: vk::Image,
1184 /// As this is used in a hashmap, we need to
1185 /// include the identity so that this hashes differently,
1186 /// even if the Image handles are the same between different images.
1187 texture_identity: ResourceIdentity<vk::Image>,
1188 format: vk::Format,
1189 mip_level: u32,
1190 depth_slice: u32,
1191}
1192
1193// Any state in this struct that may be dirty after an abandoned encoding must
1194// be reset for reused encoders in `begin_encoding`.
1195pub struct CommandEncoder {
1196 raw: vk::CommandPool,
1197 device: Arc<DeviceShared>,
1198
1199 /// The current command buffer, if `self` is in the ["recording"]
1200 /// state.
1201 ///
1202 /// ["recording"]: crate::CommandEncoder
1203 ///
1204 /// If non-`null`, the buffer is in the Vulkan "recording" state.
1205 active: vk::CommandBuffer,
1206
1207 /// What kind of pass we are currently within: compute or render.
1208 bind_point: vk::PipelineBindPoint,
1209
1210 /// Whether the device had widened its texture stages when the active
1211 /// buffer began, so that every texture barrier it records waits in
1212 /// upstream's stages.
1213 texture_stages_widened: bool,
1214
1215 /// Allocation recycling pool for this encoder.
1216 temp: Temp,
1217
1218 /// A pool of available command buffers.
1219 ///
1220 /// These are all in the Vulkan "initial" state.
1221 free: Vec<vk::CommandBuffer>,
1222
1223 /// A pool of discarded command buffers.
1224 ///
1225 /// These could be in any Vulkan state except "pending".
1226 discarded: Vec<vk::CommandBuffer>,
1227
1228 /// If this is true, the active renderpass enabled a debug span,
1229 /// and needs to be disabled on renderpass close.
1230 rpass_debug_marker_active: bool,
1231
1232 /// If set, the end of the next render/compute pass will write a timestamp at
1233 /// the given pool & location.
1234 end_of_pass_timer_query: Option<(vk::QueryPool, u32)>,
1235
1236 framebuffers: FastHashMap<FramebufferKey, vk::Framebuffer>,
1237 temp_texture_views: FastHashMap<TempTextureViewKey, IdentifiedTextureView>,
1238
1239 counters: Arc<wgt::HalCounters>,
1240
1241 current_pipeline_is_multiview: bool,
1242
1243 /// When a reset of `raw` gives its memory back.
1244 pool_trim: pool_trim::PoolTrim,
1245}
1246
1247impl Drop for CommandEncoder {
1248 fn drop(&mut self) {
1249 // SAFETY:
1250 //
1251 // VUID-vkDestroyCommandPool-commandPool-00041: wgpu_hal requires that a
1252 // `CommandBuffer` must live until its execution is complete, and that a
1253 // `CommandBuffer` must not outlive the `CommandEncoder` that built it.
1254 // Thus, we know that none of our `CommandBuffers` are in the "pending"
1255 // state.
1256 //
1257 // The other VUIDs are pretty obvious.
1258 unsafe {
1259 // `vkDestroyCommandPool` also frees any command buffers allocated
1260 // from that pool, so there's no need to explicitly call
1261 // `vkFreeCommandBuffers` on `cmd_encoder`'s `free` and `discarded`
1262 // fields.
1263 self.device.raw.destroy_command_pool(self.raw, None);
1264 }
1265
1266 for (_, fb) in self.framebuffers.drain() {
1267 unsafe { self.device.raw.destroy_framebuffer(fb, None) };
1268 }
1269
1270 for (_, view) in self.temp_texture_views.drain() {
1271 unsafe { self.device.raw.destroy_image_view(view.raw, None) };
1272 }
1273
1274 self.counters.command_encoders.sub(1);
1275 }
1276}
1277
1278impl CommandEncoder {
1279 /// # Safety
1280 ///
1281 /// - The command buffer handle must not be manually destroyed
1282 pub unsafe fn raw_handle(&self) -> vk::CommandBuffer {
1283 self.active
1284 }
1285}
1286
1287impl fmt::Debug for CommandEncoder {
1288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1289 f.debug_struct("CommandEncoder")
1290 .field("raw", &self.raw)
1291 .finish()
1292 }
1293}
1294
1295#[derive(Debug)]
1296pub struct CommandBuffer {
1297 raw: vk::CommandBuffer,
1298 /// Whether every texture barrier in the buffer waits in upstream's
1299 /// stages; see [`CatchUpBarrier`].
1300 texture_stages_widened: bool,
1301}
1302
1303impl crate::DynCommandBuffer for CommandBuffer {}
1304
1305#[derive(Debug)]
1306pub enum ShaderModule {
1307 Raw(vk::ShaderModule),
1308 Intermediate {
1309 naga_shader: crate::NagaShader,
1310 runtime_checks: wgt::ShaderRuntimeChecks,
1311 },
1312}
1313
1314impl crate::DynShaderModule for ShaderModule {}
1315
1316#[derive(Debug)]
1317pub struct RenderPipeline {
1318 raw: vk::Pipeline,
1319 is_multiview: bool,
1320}
1321
1322impl crate::DynRenderPipeline for RenderPipeline {}
1323
1324#[derive(Debug)]
1325pub struct ComputePipeline {
1326 raw: vk::Pipeline,
1327}
1328
1329impl crate::DynComputePipeline for ComputePipeline {}
1330
1331#[derive(Debug)]
1332pub struct RayTracingPipeline {
1333 raw: vk::Pipeline,
1334}
1335
1336impl crate::DynRayTracingPipeline for RayTracingPipeline {}
1337
1338#[derive(Debug)]
1339pub struct PipelineCache {
1340 raw: vk::PipelineCache,
1341}
1342
1343impl crate::DynPipelineCache for PipelineCache {}
1344
1345#[derive(Debug)]
1346pub struct QuerySet {
1347 raw: vk::QueryPool,
1348}
1349
1350impl crate::DynQuerySet for QuerySet {}
1351
1352/// The [`Api::Fence`] type for [`vulkan::Api`].
1353///
1354/// This is an `enum` because there are two possible implementations of
1355/// `wgpu-hal` fences on Vulkan: Vulkan fences, which work on any version of
1356/// Vulkan, and Vulkan timeline semaphores, which are easier and cheaper but
1357/// require non-1.0 features.
1358///
1359/// [`Device::create_fence`] returns a [`TimelineSemaphore`] if
1360/// [`VK_KHR_timeline_semaphore`] is available and enabled, and a [`FencePool`]
1361/// otherwise.
1362///
1363/// [`Api::Fence`]: crate::Api::Fence
1364/// [`vulkan::Api`]: Api
1365/// [`Device::create_fence`]: crate::Device::create_fence
1366/// [`TimelineSemaphore`]: Fence::TimelineSemaphore
1367/// [`VK_KHR_timeline_semaphore`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VK_KHR_timeline_semaphore
1368/// [`FencePool`]: Fence::FencePool
1369#[derive(Debug)]
1370pub enum Fence {
1371 /// A Vulkan [timeline semaphore].
1372 ///
1373 /// These are simpler to use than Vulkan fences, since timeline semaphores
1374 /// work exactly the way [`wpgu_hal::Api::Fence`] is specified to work.
1375 ///
1376 /// [timeline semaphore]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-semaphores
1377 /// [`wpgu_hal::Api::Fence`]: crate::Api::Fence
1378 TimelineSemaphore(vk::Semaphore),
1379
1380 /// A collection of Vulkan [fence]s, each associated with a [`FenceValue`].
1381 ///
1382 /// The effective [`FenceValue`] of this variant is the greater of
1383 /// `last_completed` and the maximum value associated with a signalled fence
1384 /// in `active`.
1385 ///
1386 /// Fences are available in all versions of Vulkan, but since they only have
1387 /// two states, "signaled" and "unsignaled", we need to use a separate fence
1388 /// for each queue submission we might want to wait for, and remember which
1389 /// [`FenceValue`] each one represents.
1390 ///
1391 /// One should keep the fence pool read while there are any references to the
1392 /// fences inside of them. This ensures there are no race conditions when
1393 /// resetting the fences
1394 ///
1395 /// [fence]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-fences
1396 /// [`FenceValue`]: crate::FenceValue
1397 FencePool(RwLock<FencePool>),
1398}
1399
1400/// A shared fence type. The arc is expect to have a ref-count of one once a function has finished being called
1401///
1402/// A fence should have access synchronised as fence resetting might happen at any point. Resetting checks the ref-count
1403/// of the fence, so instead of copying the fence, it should have its `Arc` container cloned which shows not to reset
1404/// this fence as it is being used.
1405pub(super) type SynchronizedFence = Arc<vk::Fence>;
1406
1407#[derive(Debug)]
1408pub struct FencePool {
1409 last_completed: crate::FenceValue,
1410 /// The pending fence values have to be ascending.
1411 active: Vec<(crate::FenceValue, SynchronizedFence)>,
1412 // Don't need extra synchronisation around the fences here, if they are used they should be put into active.
1413 free: Vec<vk::Fence>,
1414}
1415
1416impl crate::DynFence for Fence {}
1417
1418impl Fence {
1419 /// Return the highest [`FenceValue`] among the signalled fences in `active`.
1420 ///
1421 /// As an optimization, assume that we already know that the fence has
1422 /// reached `last_completed`, and don't bother checking fences whose values
1423 /// are less than that: those fences remain in the `active` array only
1424 /// because we haven't called `maintain` yet to clean them up.
1425 ///
1426 /// [`FenceValue`]: crate::FenceValue
1427 fn check_active(
1428 device: &ash::Device,
1429 mut last_completed: crate::FenceValue,
1430 active: &[(crate::FenceValue, SynchronizedFence)],
1431 ) -> Result<crate::FenceValue, crate::DeviceError> {
1432 for &(value, ref raw) in active.iter() {
1433 unsafe {
1434 if value > last_completed
1435 && device
1436 // Don't need to clone as active should be from a read or
1437 // write lock which means this is already synchronised.
1438 .get_fence_status(**raw)
1439 .map_err(map_host_device_oom_and_lost_err)?
1440 {
1441 last_completed = value;
1442 }
1443 }
1444 }
1445 Ok(last_completed)
1446 }
1447
1448 /// Return the highest signalled [`FenceValue`] for `self`.
1449 ///
1450 /// [`FenceValue`]: crate::FenceValue
1451 fn get_latest(
1452 &self,
1453 device: &ash::Device,
1454 extension: Option<&ExtensionFn<khr::timeline_semaphore::Device>>,
1455 ) -> Result<crate::FenceValue, crate::DeviceError> {
1456 match *self {
1457 Self::TimelineSemaphore(raw) => unsafe {
1458 Ok(match *extension.unwrap() {
1459 ExtensionFn::Extension(ref ext) => ext
1460 .get_semaphore_counter_value(raw)
1461 .map_err(map_host_device_oom_and_lost_err)?,
1462 ExtensionFn::Promoted => device
1463 .get_semaphore_counter_value(raw)
1464 .map_err(map_host_device_oom_and_lost_err)?,
1465 })
1466 },
1467 Self::FencePool(ref pool) => {
1468 let FencePool {
1469 last_completed,
1470 ref active,
1471 free: _,
1472 } = *pool.read();
1473 Self::check_active(device, last_completed, active)
1474 }
1475 }
1476 }
1477
1478 /// Trim the internal state of this [`Fence`].
1479 ///
1480 /// This function has no externally visible effect, but you should call it
1481 /// periodically to keep this fence's resource consumption under control.
1482 ///
1483 /// For fences using the [`FencePool`] implementation, this function
1484 /// recycles fences that have been signaled. If you don't call this,
1485 /// [`Queue::submit`] will just keep allocating a new Vulkan fence every
1486 /// time it's called.
1487 ///
1488 /// [`FencePool`]: Fence::FencePool
1489 /// [`Queue::submit`]: crate::Queue::submit
1490 fn maintain(&self, device: &ash::Device) -> Result<(), crate::DeviceError> {
1491 match *self {
1492 Self::TimelineSemaphore(_) => {}
1493 Self::FencePool(ref pool) => {
1494 let FencePool {
1495 ref mut last_completed,
1496 ref mut active,
1497 ref mut free,
1498 } = *pool.write();
1499
1500 let base_free = free.len();
1501 let latest = Self::check_active(device, *last_completed, active)?;
1502
1503 active.retain_mut(|&mut (value, ref mut fence)| {
1504 if value > latest {
1505 true
1506 } else if let Some(fence) = Arc::get_mut(fence) {
1507 // No other references to these, so we have exclusive access. Add them to free and reset them later,
1508 // but drop them from active immediately
1509 free.push(*fence);
1510 false
1511 } else {
1512 // some other function is using it. Although this shouldn't be to long,
1513 // maintain shouldn't block, and it should be cleared up by the next time it happens
1514 true
1515 }
1516 });
1517
1518 if free.len() != base_free {
1519 unsafe { device.reset_fences(&free[base_free..]) }
1520 .map_err(map_device_oom_err)?
1521 }
1522 *last_completed = latest;
1523 }
1524 }
1525 Ok(())
1526 }
1527}
1528
1529impl crate::Queue for Queue {
1530 type A = Api;
1531
1532 unsafe fn submit(
1533 &self,
1534 command_buffers: &[&CommandBuffer],
1535 surface_textures: &[&SurfaceTexture],
1536 (signal_fence, signal_value): (&Fence, crate::FenceValue),
1537 ) -> Result<(), crate::DeviceError> {
1538 let mut fence_raw = vk::Fence::null();
1539
1540 let mut wait_semaphores = SemaphoreList::new(SemaphoreListMode::Wait);
1541 let mut signal_semaphores = SemaphoreList::new(SemaphoreListMode::Signal);
1542
1543 // Double check that the same swapchain image isn't being given to us multiple times,
1544 // as that will deadlock when we try to lock them all.
1545 debug_assert!(
1546 {
1547 let mut check = HashSet::with_capacity(surface_textures.len());
1548 // We compare the Box by pointer, as Eq isn't well defined for SurfaceSemaphores.
1549 for st in surface_textures {
1550 let ptr: *const () = <*const _>::cast(&*st.metadata);
1551 check.insert(ptr as usize);
1552 }
1553 check.len() == surface_textures.len()
1554 },
1555 "More than one surface texture is being used from the same swapchain. This will cause a deadlock in release."
1556 );
1557
1558 let locked_swapchain_semaphores = surface_textures
1559 .iter()
1560 .map(|st| st.metadata.get_semaphore_guard())
1561 .collect::<Vec<_>>();
1562
1563 for mut semaphores in locked_swapchain_semaphores {
1564 semaphores.set_used_fence_value(signal_value);
1565
1566 // If we're the first submission to operate on this image, wait on
1567 // its acquire semaphore, to make sure the presentation engine is
1568 // done with it.
1569 if let Some(sem) = semaphores.get_acquire_wait_semaphore() {
1570 wait_semaphores.push_wait(sem, vk::PipelineStageFlags::TOP_OF_PIPE);
1571 }
1572
1573 // Get a semaphore to signal when we're done writing to this surface
1574 // image. Presentation of this image will wait for this.
1575 let signal_semaphore = semaphores.get_submit_signal_semaphore(&self.device)?;
1576 signal_semaphores.push_signal(signal_semaphore);
1577 }
1578
1579 let mut guard = self.signal_semaphores.lock();
1580 if !guard.is_empty() {
1581 signal_semaphores.append(&mut guard);
1582 }
1583
1584 let mut wait_guard = self.wait_semaphores.lock();
1585 if !wait_guard.is_empty() {
1586 wait_semaphores.append(&mut wait_guard);
1587 }
1588
1589 // In order for submissions to be strictly ordered, we encode a dependency between each submission
1590 // using a pair of semaphores. This adds a wait if it is needed, and signals the next semaphore.
1591 let semaphore_state = self.relay_semaphores.lock().advance(&self.device)?;
1592
1593 if let Some(sem) = semaphore_state.wait {
1594 wait_semaphores.push_wait(
1595 SemaphoreType::Binary(sem),
1596 vk::PipelineStageFlags::TOP_OF_PIPE,
1597 );
1598 }
1599
1600 signal_semaphores.push_signal(SemaphoreType::Binary(semaphore_state.signal));
1601
1602 // We need to signal our wgpu::Fence if we have one, this adds it to the signal list.
1603 signal_fence.maintain(&self.device.raw)?;
1604 // Keeping the Arc around is probably unneeded - the fence should never be signaled as it was reset,
1605 // and newer submits should not happen until this submit is done. Therefore, it should be too high
1606 // to be reset.
1607 let shared_fence;
1608 match *signal_fence {
1609 Fence::TimelineSemaphore(raw) => {
1610 signal_semaphores.push_signal(SemaphoreType::Timeline(raw, signal_value));
1611 }
1612 Fence::FencePool(ref pool) => {
1613 let FencePool {
1614 ref mut active,
1615 ref mut free,
1616 ..
1617 } = *pool.write();
1618 shared_fence = match free.pop() {
1619 Some(raw) => Arc::new(raw),
1620 None => unsafe {
1621 let fence = self
1622 .device
1623 .raw
1624 .create_fence(&vk::FenceCreateInfo::default(), None)
1625 .map_err(map_host_device_oom_err)?;
1626 Arc::new(fence)
1627 },
1628 };
1629 fence_raw = *shared_fence;
1630 active.push((signal_value, shared_fence.clone()));
1631 }
1632 }
1633
1634 let vk_cmd_buffers = self.submission(command_buffers)?;
1635
1636 let mut vk_info = vk::SubmitInfo::default().command_buffers(&vk_cmd_buffers);
1637 let mut vk_timeline_info = mem::MaybeUninit::uninit();
1638 vk_info = SemaphoreList::add_to_submit(
1639 &mut wait_semaphores,
1640 &mut signal_semaphores,
1641 vk_info,
1642 &mut vk_timeline_info,
1643 );
1644
1645 profiling::scope!("vkQueueSubmit");
1646 unsafe {
1647 self.device
1648 .raw
1649 .queue_submit(self.raw, &[vk_info], fence_raw)
1650 .map_err(map_host_device_oom_and_lost_err)?
1651 };
1652 Ok(())
1653 }
1654
1655 unsafe fn present(
1656 &self,
1657 surface: &Surface,
1658 texture: SurfaceTexture,
1659 ) -> Result<(), crate::SurfaceError> {
1660 let mut swapchain = surface.swapchain.write();
1661
1662 unsafe { swapchain.as_mut().unwrap().present(self, texture) }
1663 }
1664
1665 unsafe fn get_timestamp_period(&self) -> f32 {
1666 self.device.timestamp_period
1667 }
1668
1669 unsafe fn wait_for_idle(&self) -> Result<(), crate::DeviceError> {
1670 unsafe { self.device.raw.queue_wait_idle(self.raw) }
1671 .map_err(map_host_device_oom_and_lost_err)
1672 }
1673}
1674
1675impl Queue {
1676 pub fn raw_device(&self) -> &ash::Device {
1677 &self.device.raw
1678 }
1679
1680 pub fn add_signal_semaphore(&self, semaphore: vk::Semaphore, semaphore_value: Option<u64>) {
1681 let mut guard = self.signal_semaphores.lock();
1682 if let Some(value) = semaphore_value {
1683 guard.push_signal(SemaphoreType::Timeline(semaphore, value));
1684 } else {
1685 guard.push_signal(SemaphoreType::Binary(semaphore));
1686 }
1687 }
1688
1689 /// Remove `semaphore` from the pending signal list if it is still present.
1690 ///
1691 /// Returns `true` if the semaphore was found and removed. If the submit
1692 /// already consumed it, this is a harmless no-op that returns `false`.
1693 pub fn remove_signal_semaphore(&self, semaphore: vk::Semaphore) -> bool {
1694 self.signal_semaphores.lock().remove(semaphore)
1695 }
1696
1697 /// Stage a semaphore wait on the next [`crate::Queue::submit`] call.
1698 ///
1699 /// `semaphore_value` selects the kind of payload the wait targets:
1700 ///
1701 /// - `Some(value)` - wait until `semaphore` (a timeline semaphore) has been signalled to at least `value`.
1702 /// - `None` - wait on a binary semaphore signal.
1703 ///
1704 /// `stage` is the pipeline stage at which the wait blocks downstream
1705 /// work (e.g. `vk::PipelineStageFlags::TOP_OF_PIPE` to gate the
1706 /// entire submission, or a more specific stage when only that stage
1707 /// reads the synchronised resource).
1708 pub fn add_wait_semaphore(
1709 &self,
1710 semaphore: vk::Semaphore,
1711 semaphore_value: Option<u64>,
1712 stage: vk::PipelineStageFlags,
1713 ) {
1714 let mut guard = self.wait_semaphores.lock();
1715 if let Some(value) = semaphore_value {
1716 guard.push_wait(SemaphoreType::Timeline(semaphore, value), stage);
1717 } else {
1718 guard.push_wait(SemaphoreType::Binary(semaphore), stage);
1719 }
1720 }
1721
1722 /// Remove `semaphore` from the pending wait list if it is still present.
1723 ///
1724 /// Returns `true` if the semaphore was found and removed. If the submit
1725 /// already consumed it, this is a no-op that returns `false`.
1726 pub fn remove_wait_semaphore(&self, semaphore: vk::Semaphore) -> bool {
1727 self.wait_semaphores.lock().remove(semaphore)
1728 }
1729}
1730
1731/// Maps
1732///
1733/// - VK_ERROR_OUT_OF_HOST_MEMORY
1734/// - VK_ERROR_OUT_OF_DEVICE_MEMORY
1735fn map_host_device_oom_err(err: vk::Result) -> crate::DeviceError {
1736 match err {
1737 vk::Result::ERROR_OUT_OF_HOST_MEMORY | vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => {
1738 get_oom_err(err)
1739 }
1740 e => get_unexpected_err(e),
1741 }
1742}
1743
1744/// Maps
1745///
1746/// - VK_ERROR_OUT_OF_HOST_MEMORY
1747/// - VK_ERROR_OUT_OF_DEVICE_MEMORY
1748/// - VK_ERROR_DEVICE_LOST
1749fn map_host_device_oom_and_lost_err(err: vk::Result) -> crate::DeviceError {
1750 match err {
1751 vk::Result::ERROR_DEVICE_LOST => get_lost_err(),
1752 other => map_host_device_oom_err(other),
1753 }
1754}
1755
1756/// Maps
1757///
1758/// - VK_ERROR_OUT_OF_HOST_MEMORY
1759/// - VK_ERROR_OUT_OF_DEVICE_MEMORY
1760/// - VK_ERROR_FRAGMENTATION
1761fn map_host_device_oom_and_fragmentation_err(err: vk::Result) -> crate::DeviceError {
1762 match err {
1763 vk::Result::ERROR_FRAGMENTATION => get_oom_err(err),
1764 other => map_host_device_oom_err(other),
1765 }
1766}
1767
1768/// Maps
1769///
1770/// - VK_ERROR_OUT_OF_HOST_MEMORY
1771/// - VK_ERROR_OUT_OF_DEVICE_MEMORY
1772/// - VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR
1773fn map_host_device_oom_and_ioca_err(err: vk::Result) -> crate::DeviceError {
1774 // We don't use VK_KHR_buffer_device_address
1775 // VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR
1776 map_host_device_oom_err(err)
1777}
1778
1779/// Maps
1780///
1781/// - VK_ERROR_OUT_OF_HOST_MEMORY
1782fn map_host_oom_err(err: vk::Result) -> crate::DeviceError {
1783 match err {
1784 vk::Result::ERROR_OUT_OF_HOST_MEMORY => get_oom_err(err),
1785 e => get_unexpected_err(e),
1786 }
1787}
1788
1789/// Maps
1790///
1791/// - VK_ERROR_OUT_OF_DEVICE_MEMORY
1792fn map_device_oom_err(err: vk::Result) -> crate::DeviceError {
1793 match err {
1794 vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => get_oom_err(err),
1795 e => get_unexpected_err(e),
1796 }
1797}
1798
1799/// Maps
1800///
1801/// - VK_ERROR_OUT_OF_HOST_MEMORY
1802/// - VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR
1803fn map_host_oom_and_ioca_err(err: vk::Result) -> crate::DeviceError {
1804 // We don't use VK_KHR_buffer_device_address
1805 // VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR
1806 map_host_oom_err(err)
1807}
1808
1809/// Maps
1810///
1811/// - VK_ERROR_OUT_OF_HOST_MEMORY
1812/// - VK_ERROR_OUT_OF_DEVICE_MEMORY
1813/// - VK_PIPELINE_COMPILE_REQUIRED_EXT
1814/// - VK_ERROR_INVALID_SHADER_NV
1815fn map_pipeline_err(err: vk::Result) -> crate::DeviceError {
1816 // We don't use VK_EXT_pipeline_creation_cache_control
1817 // VK_PIPELINE_COMPILE_REQUIRED_EXT
1818 // We don't use VK_NV_glsl_shader
1819 // VK_ERROR_INVALID_SHADER_NV
1820 map_host_device_oom_err(err)
1821}
1822
1823/// Returns [`crate::DeviceError::Unexpected`] or panics if the `internal_error_panic`
1824/// feature flag is enabled.
1825fn get_unexpected_err(_err: vk::Result) -> crate::DeviceError {
1826 #[cfg(feature = "internal_error_panic")]
1827 panic!("Unexpected Vulkan error: {_err:?}");
1828
1829 #[allow(unreachable_code)]
1830 crate::DeviceError::Unexpected
1831}
1832
1833/// Returns [`crate::DeviceError::OutOfMemory`].
1834fn get_oom_err(_err: vk::Result) -> crate::DeviceError {
1835 crate::DeviceError::OutOfMemory
1836}
1837
1838/// Returns [`crate::DeviceError::Lost`] or panics if the `device_lost_panic`
1839/// feature flag is enabled.
1840fn get_lost_err() -> crate::DeviceError {
1841 #[cfg(feature = "device_lost_panic")]
1842 panic!("Device lost");
1843
1844 #[allow(unreachable_code)]
1845 crate::DeviceError::Lost
1846}
1847
1848#[derive(Clone, Copy, Pod, Zeroable)]
1849#[repr(C)]
1850struct RawTlasInstance {
1851 transform: [f32; 12],
1852 custom_data_and_mask: u32,
1853 shader_binding_table_record_offset_and_flags: u32,
1854 acceleration_structure_reference: u64,
1855}
1856
1857/// Arguments to the [`CreateDeviceCallback`].
1858#[derive(Debug)]
1859pub struct CreateDeviceCallbackArgs<'arg, 'pnext, 'this>
1860where
1861 'this: 'pnext,
1862{
1863 /// The extensions to enable for the device. You must not remove anything from this list,
1864 /// but you may add to it.
1865 pub extensions: &'arg mut Vec<&'static CStr>,
1866 /// The physical device features to enable. You may enable features, but must not disable any.
1867 pub device_features: &'arg mut PhysicalDeviceFeatures,
1868 /// The queue create infos for the device. You may add or modify queue create infos as needed.
1869 pub queue_create_infos: &'arg mut Vec<vk::DeviceQueueCreateInfo<'pnext>>,
1870 /// The create info for the device. You may add or modify things in the pnext chain, but
1871 /// do not turn features off. Additionally, do not add things to the list of extensions,
1872 /// or to the feature set, as all changes to that member will be overwritten.
1873 pub create_info: &'arg mut vk::DeviceCreateInfo<'pnext>,
1874 /// We need to have `'this` in the struct, so we can declare that all lifetimes coming from
1875 /// captures in the closure will live longer (and hence satisfy) `'pnext`. However, we
1876 /// don't actually directly use `'this`
1877 _phantom: PhantomData<&'this ()>,
1878}
1879
1880/// Callback to allow changing the vulkan device creation parameters.
1881///
1882/// # Safety:
1883/// - If you want to add extensions, add the to the `Vec<'static CStr>` not the create info,
1884/// as the create info value will be overwritten.
1885/// - Callback must not remove features.
1886/// - Callback must not change anything to what the instance does not support.
1887pub type CreateDeviceCallback<'this> =
1888 dyn for<'arg, 'pnext> FnOnce(CreateDeviceCallbackArgs<'arg, 'pnext, 'this>) + 'this;
1889
1890/// Arguments to the [`CreateInstanceCallback`].
1891#[expect(missing_debug_implementations, reason = "TODO?")]
1892pub struct CreateInstanceCallbackArgs<'arg, 'pnext, 'this>
1893where
1894 'this: 'pnext,
1895{
1896 /// The extensions to enable for the instance. You must not remove anything from this list,
1897 /// but you may add to it.
1898 pub extensions: &'arg mut Vec<&'static CStr>,
1899 /// The create info for the instance. You may add or modify things in the pnext chain, but
1900 /// do not turn features off. Additionally, do not add things to the list of extensions,
1901 /// all changes to that member will be overwritten.
1902 pub create_info: &'arg mut vk::InstanceCreateInfo<'pnext>,
1903 /// Vulkan entry point.
1904 pub entry: &'arg ash::Entry,
1905 /// We need to have `'this` in the struct, so we can declare that all lifetimes coming from
1906 /// captures in the closure will live longer (and hence satisfy) `'pnext`. However, we
1907 /// don't actually directly use `'this`
1908 _phantom: PhantomData<&'this ()>,
1909}
1910
1911/// Callback to allow changing the vulkan instance creation parameters.
1912///
1913/// # Safety:
1914/// - If you want to add extensions, add the to the `Vec<'static CStr>` not the create info,
1915/// as the create info value will be overwritten.
1916/// - Callback must not remove features.
1917/// - Callback must not change anything to what the instance does not support.
1918pub type CreateInstanceCallback<'this> =
1919 dyn for<'arg, 'pnext> FnOnce(CreateInstanceCallbackArgs<'arg, 'pnext, 'this>) + 'this;