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