/*! # Vulkan API internals. ## Stack memory Ash expects slices, which we don't generally have available. We cope with this requirement by the combination of the following ways: - temporarily allocating `Vec` on heap, where overhead is permitted - growing temporary local storage - using `implace_it` on iterators ## Framebuffers and Render passes Render passes are cached on the device and kept forever. Framebuffers are also cached on the device, but they are removed when any of the image views (they have) gets removed. If Vulkan supports image-less framebuffers, then the actual views are excluded from the framebuffer key. ## Fences If timeline semaphores are available, they are used 1:1 with wgpu-hal fences. Otherwise, we manage a pool of `VkFence` objects behind each `hal::Fence`. !*/ mod adapter; mod command; mod conv; mod device; mod instance; use std::{ borrow::Borrow, ffi::CStr, fmt, num::NonZeroU32, sync::{ atomic::{AtomicIsize, Ordering}, Arc, }, }; use arrayvec::ArrayVec; use ash::{ extensions::{ext, khr}, vk, }; use parking_lot::{Mutex, RwLock}; const MILLIS_TO_NANOS: u64 = 1_000_000; const MAX_TOTAL_ATTACHMENTS: usize = crate::MAX_COLOR_ATTACHMENTS * 2 + 1; #[derive(Clone, Debug)] pub struct Api; impl crate::Api for Api { type Instance = Instance; type Surface = Surface; type Adapter = Adapter; type Device = Device; type Queue = Queue; type CommandEncoder = CommandEncoder; type CommandBuffer = CommandBuffer; type Buffer = Buffer; type Texture = Texture; type SurfaceTexture = SurfaceTexture; type TextureView = TextureView; type Sampler = Sampler; type QuerySet = QuerySet; type Fence = Fence; type AccelerationStructure = AccelerationStructure; type BindGroupLayout = BindGroupLayout; type BindGroup = BindGroup; type PipelineLayout = PipelineLayout; type ShaderModule = ShaderModule; type RenderPipeline = RenderPipeline; type ComputePipeline = ComputePipeline; } struct DebugUtils { extension: ext::DebugUtils, messenger: vk::DebugUtilsMessengerEXT, /// Owning pointer to the debug messenger callback user data. /// /// `InstanceShared::drop` destroys the debug messenger before /// dropping this, so the callback should never receive a dangling /// user data pointer. #[allow(dead_code)] callback_data: Box, } pub struct DebugUtilsCreateInfo { severity: vk::DebugUtilsMessageSeverityFlagsEXT, message_type: vk::DebugUtilsMessageTypeFlagsEXT, callback_data: Box, } #[derive(Debug)] /// The properties related to the validation layer needed for the /// DebugUtilsMessenger for their workarounds struct ValidationLayerProperties { /// Validation layer description, from `vk::LayerProperties`. layer_description: std::ffi::CString, /// Validation layer specification version, from `vk::LayerProperties`. layer_spec_version: u32, } /// User data needed by `instance::debug_utils_messenger_callback`. /// /// When we create the [`vk::DebugUtilsMessengerEXT`], the `pUserData` /// pointer refers to one of these values. #[derive(Debug)] pub struct DebugUtilsMessengerUserData { /// The properties related to the validation layer, if present validation_layer_properties: Option, /// If the OBS layer is present. OBS never increments the version of their layer, /// so there's no reason to have the version. has_obs_layer: bool, } pub struct InstanceShared { raw: ash::Instance, extensions: Vec<&'static CStr>, drop_guard: Option, flags: wgt::InstanceFlags, debug_utils: Option, get_physical_device_properties: Option, entry: ash::Entry, has_nv_optimus: bool, android_sdk_version: u32, /// The instance API version. /// /// Which is the version of Vulkan supported for instance-level functionality. /// /// It is associated with a `VkInstance` and its children, /// except for a `VkPhysicalDevice` and its children. instance_api_version: u32, } pub struct Instance { shared: Arc, } struct Swapchain { raw: vk::SwapchainKHR, raw_flags: vk::SwapchainCreateFlagsKHR, functor: khr::Swapchain, device: Arc, images: Vec, config: crate::SurfaceConfiguration, view_formats: Vec, /// One wait semaphore per swapchain image. This will be associated with the /// surface texture, and later collected during submission. surface_semaphores: Vec, /// Current semaphore index to use when acquiring a surface. next_surface_index: usize, } pub struct Surface { raw: vk::SurfaceKHR, functor: khr::Surface, instance: Arc, swapchain: RwLock>, } #[derive(Debug)] pub struct SurfaceTexture { index: u32, texture: Texture, wait_semaphore: vk::Semaphore, } impl Borrow for SurfaceTexture { fn borrow(&self) -> &Texture { &self.texture } } pub struct Adapter { raw: vk::PhysicalDevice, instance: Arc, //queue_families: Vec, known_memory_flags: vk::MemoryPropertyFlags, phd_capabilities: adapter::PhysicalDeviceProperties, //phd_features: adapter::PhysicalDeviceFeatures, downlevel_flags: wgt::DownlevelFlags, private_caps: PrivateCapabilities, workarounds: Workarounds, } // 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`. enum ExtensionFn { /// The loaded function pointer struct for an extension. Extension(T), /// The extension was promoted to a core version of Vulkan and the functions on `ash`'s `DeviceV1_x` traits should be used. Promoted, } struct DeviceExtensionFunctions { draw_indirect_count: Option, timeline_semaphore: Option>, ray_tracing: Option, } struct RayTracingDeviceExtensionFunctions { acceleration_structure: khr::AccelerationStructure, buffer_device_address: khr::BufferDeviceAddress, } /// Set of internal capabilities, which don't show up in the exposed /// device geometry, but affect the code paths taken internally. #[derive(Clone, Debug)] struct PrivateCapabilities { /// Y-flipping is implemented with either `VK_AMD_negative_viewport_height` or `VK_KHR_maintenance1`/1.1+. The AMD extension for negative viewport height does not require a Y shift. /// /// This flag is `true` if the device has `VK_KHR_maintenance1`/1.1+ and `false` otherwise (i.e. in the case of `VK_AMD_negative_viewport_height`). flip_y_requires_shift: bool, imageless_framebuffers: bool, image_view_usage: bool, timeline_semaphores: bool, texture_d24: bool, texture_d24_s8: bool, texture_s8: bool, /// Ability to present contents to any screen. Only needed to work around broken platform configurations. can_present: bool, non_coherent_map_mask: wgt::BufferAddress, robust_buffer_access: bool, robust_image_access: bool, robust_buffer_access2: bool, robust_image_access2: bool, zero_initialize_workgroup_memory: bool, image_format_list: bool, subgroup_size_control: bool, } bitflags::bitflags!( /// Workaround flags. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Workarounds: u32 { /// Only generate SPIR-V for one entry point at a time. const SEPARATE_ENTRY_POINTS = 0x1; /// Qualcomm OOMs when there are zero color attachments but a non-null pointer /// to a subpass resolve attachment array. This nulls out that pointer in that case. const EMPTY_RESOLVE_ATTACHMENT_LISTS = 0x2; /// If the following code returns false, then nvidia will end up filling the wrong range. /// /// ```skip /// fn nvidia_succeeds() -> bool { /// # let (copy_length, start_offset) = (0, 0); /// if copy_length >= 4096 { /// if start_offset % 16 != 0 { /// if copy_length == 4096 { /// return true; /// } /// if copy_length % 16 == 0 { /// return false; /// } /// } /// } /// true /// } /// ``` /// /// As such, we need to make sure all calls to vkCmdFillBuffer are aligned to 16 bytes /// if they cover a range of 4096 bytes or more. const FORCE_FILL_BUFFER_WITH_SIZE_GREATER_4096_ALIGNED_OFFSET_16 = 0x4; } ); #[derive(Clone, Debug, Eq, Hash, PartialEq)] struct AttachmentKey { format: vk::Format, layout: vk::ImageLayout, ops: crate::AttachmentOps, } impl AttachmentKey { /// Returns an attachment key for a compatible attachment. fn compatible(format: vk::Format, layout: vk::ImageLayout) -> Self { Self { format, layout, ops: crate::AttachmentOps::all(), } } } #[derive(Clone, Eq, Hash, PartialEq)] struct ColorAttachmentKey { base: AttachmentKey, resolve: Option, } #[derive(Clone, Eq, Hash, PartialEq)] struct DepthStencilAttachmentKey { base: AttachmentKey, stencil_ops: crate::AttachmentOps, } #[derive(Clone, Eq, Default, Hash, PartialEq)] struct RenderPassKey { colors: ArrayVec, { crate::MAX_COLOR_ATTACHMENTS }>, depth_stencil: Option, sample_count: u32, multiview: Option, } #[derive(Clone, Debug, Eq, Hash, PartialEq)] struct FramebufferAttachment { /// Can be NULL if the framebuffer is image-less raw: vk::ImageView, raw_image_flags: vk::ImageCreateFlags, view_usage: crate::TextureUses, view_format: wgt::TextureFormat, raw_view_formats: Vec, } #[derive(Clone, Eq, Hash, PartialEq)] struct FramebufferKey { attachments: ArrayVec, extent: wgt::Extent3d, sample_count: u32, } struct DeviceShared { raw: ash::Device, family_index: u32, queue_index: u32, raw_queue: ash::vk::Queue, handle_is_owned: bool, instance: Arc, physical_device: ash::vk::PhysicalDevice, enabled_extensions: Vec<&'static CStr>, extension_fns: DeviceExtensionFunctions, vendor_id: u32, timestamp_period: f32, private_caps: PrivateCapabilities, workarounds: Workarounds, render_passes: Mutex>, framebuffers: Mutex>, } pub struct Device { shared: Arc, mem_allocator: Mutex>, desc_allocator: Mutex>, valid_ash_memory_types: u32, naga_options: naga::back::spv::Options<'static>, #[cfg(feature = "renderdoc")] render_doc: crate::auxil::renderdoc::RenderDoc, } pub struct Queue { raw: vk::Queue, swapchain_fn: khr::Swapchain, device: Arc, family_index: u32, /// We use a redundant chain of semaphores to pass on the signal /// from submissions to the last present, since it's required by the /// specification. /// It would be correct to use a single semaphore there, but /// [Intel hangs in `anv_queue_finish`](https://gitlab.freedesktop.org/mesa/mesa/-/issues/5508). relay_semaphores: [vk::Semaphore; 2], relay_index: AtomicIsize, } #[derive(Debug)] pub struct Buffer { raw: vk::Buffer, block: Option>>, } #[derive(Debug)] pub struct AccelerationStructure { raw: vk::AccelerationStructureKHR, buffer: vk::Buffer, block: Mutex>, } #[derive(Debug)] pub struct Texture { raw: vk::Image, drop_guard: Option, block: Option>, usage: crate::TextureUses, format: wgt::TextureFormat, raw_flags: vk::ImageCreateFlags, copy_size: crate::CopyExtent, view_formats: Vec, } impl Texture { /// # Safety /// /// - The image handle must not be manually destroyed pub unsafe fn raw_handle(&self) -> vk::Image { self.raw } } #[derive(Debug)] pub struct TextureView { raw: vk::ImageView, layers: NonZeroU32, attachment: FramebufferAttachment, } impl TextureView { /// # Safety /// /// - The image view handle must not be manually destroyed pub unsafe fn raw_handle(&self) -> vk::ImageView { self.raw } } #[derive(Debug)] pub struct Sampler { raw: vk::Sampler, } #[derive(Debug)] pub struct BindGroupLayout { raw: vk::DescriptorSetLayout, desc_count: gpu_descriptor::DescriptorTotalCount, types: Box<[(vk::DescriptorType, u32)]>, /// Map of binding index to size, binding_arrays: Vec<(u32, NonZeroU32)>, } #[derive(Debug)] pub struct PipelineLayout { raw: vk::PipelineLayout, binding_arrays: naga::back::spv::BindingMap, } #[derive(Debug)] pub struct BindGroup { set: gpu_descriptor::DescriptorSet, } /// Miscellaneous allocation recycling pool for `CommandAllocator`. #[derive(Default)] struct Temp { marker: Vec, buffer_barriers: Vec, image_barriers: Vec, } unsafe impl Send for Temp {} unsafe impl Sync for Temp {} impl Temp { fn clear(&mut self) { self.marker.clear(); self.buffer_barriers.clear(); self.image_barriers.clear(); //see also - https://github.com/NotIntMan/inplace_it/issues/8 } fn make_c_str(&mut self, name: &str) -> &CStr { self.marker.clear(); self.marker.extend_from_slice(name.as_bytes()); self.marker.push(0); unsafe { CStr::from_bytes_with_nul_unchecked(&self.marker) } } } pub struct CommandEncoder { raw: vk::CommandPool, device: Arc, /// The current command buffer, if `self` is in the ["recording"] /// state. /// /// ["recording"]: crate::CommandEncoder /// /// If non-`null`, the buffer is in the Vulkan "recording" state. active: vk::CommandBuffer, /// What kind of pass we are currently within: compute or render. bind_point: vk::PipelineBindPoint, /// Allocation recycling pool for this encoder. temp: Temp, /// A pool of available command buffers. /// /// These are all in the Vulkan "initial" state. free: Vec, /// A pool of discarded command buffers. /// /// These could be in any Vulkan state except "pending". discarded: Vec, /// If this is true, the active renderpass enabled a debug span, /// and needs to be disabled on renderpass close. rpass_debug_marker_active: bool, /// If set, the end of the next render/compute pass will write a timestamp at /// the given pool & location. end_of_pass_timer_query: Option<(vk::QueryPool, u32)>, } impl CommandEncoder { /// # Safety /// /// - The command buffer handle must not be manually destroyed pub unsafe fn raw_handle(&self) -> vk::CommandBuffer { self.active } } impl fmt::Debug for CommandEncoder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CommandEncoder") .field("raw", &self.raw) .finish() } } #[derive(Debug)] pub struct CommandBuffer { raw: vk::CommandBuffer, } #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum ShaderModule { Raw(vk::ShaderModule), Intermediate { naga_shader: crate::NagaShader, runtime_checks: bool, }, } #[derive(Debug)] pub struct RenderPipeline { raw: vk::Pipeline, } #[derive(Debug)] pub struct ComputePipeline { raw: vk::Pipeline, } #[derive(Debug)] pub struct QuerySet { raw: vk::QueryPool, } /// The [`Api::Fence`] type for [`vulkan::Api`]. /// /// This is an `enum` because there are two possible implementations of /// `wgpu-hal` fences on Vulkan: Vulkan fences, which work on any version of /// Vulkan, and Vulkan timeline semaphores, which are easier and cheaper but /// require non-1.0 features. /// /// [`Device::create_fence`] returns a [`TimelineSemaphore`] if /// [`VK_KHR_timeline_semaphore`] is available and enabled, and a [`FencePool`] /// otherwise. /// /// [`Api::Fence`]: crate::Api::Fence /// [`vulkan::Api`]: Api /// [`Device::create_fence`]: crate::Device::create_fence /// [`TimelineSemaphore`]: Fence::TimelineSemaphore /// [`VK_KHR_timeline_semaphore`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VK_KHR_timeline_semaphore /// [`FencePool`]: Fence::FencePool #[derive(Debug)] pub enum Fence { /// A Vulkan [timeline semaphore]. /// /// These are simpler to use than Vulkan fences, since timeline semaphores /// work exactly the way [`wpgu_hal::Api::Fence`] is specified to work. /// /// [timeline semaphore]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-semaphores /// [`wpgu_hal::Api::Fence`]: crate::Api::Fence TimelineSemaphore(vk::Semaphore), /// A collection of Vulkan [fence]s, each associated with a [`FenceValue`]. /// /// The effective [`FenceValue`] of this variant is the greater of /// `last_completed` and the maximum value associated with a signalled fence /// in `active`. /// /// Fences are available in all versions of Vulkan, but since they only have /// two states, "signaled" and "unsignaled", we need to use a separate fence /// for each queue submission we might want to wait for, and remember which /// [`FenceValue`] each one represents. /// /// [fence]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-fences /// [`FenceValue`]: crate::FenceValue FencePool { last_completed: crate::FenceValue, /// The pending fence values have to be ascending. active: Vec<(crate::FenceValue, vk::Fence)>, free: Vec, }, } impl Fence { /// Return the highest [`FenceValue`] among the signalled fences in `active`. /// /// As an optimization, assume that we already know that the fence has /// reached `last_completed`, and don't bother checking fences whose values /// are less than that: those fences remain in the `active` array only /// because we haven't called `maintain` yet to clean them up. /// /// [`FenceValue`]: crate::FenceValue fn check_active( device: &ash::Device, mut last_completed: crate::FenceValue, active: &[(crate::FenceValue, vk::Fence)], ) -> Result { for &(value, raw) in active.iter() { unsafe { if value > last_completed && device.get_fence_status(raw)? { last_completed = value; } } } Ok(last_completed) } /// Return the highest signalled [`FenceValue`] for `self`. /// /// [`FenceValue`]: crate::FenceValue fn get_latest( &self, device: &ash::Device, extension: Option<&ExtensionFn>, ) -> Result { match *self { Self::TimelineSemaphore(raw) => unsafe { Ok(match *extension.unwrap() { ExtensionFn::Extension(ref ext) => ext.get_semaphore_counter_value(raw)?, ExtensionFn::Promoted => device.get_semaphore_counter_value(raw)?, }) }, Self::FencePool { last_completed, ref active, free: _, } => Self::check_active(device, last_completed, active), } } /// Trim the internal state of this [`Fence`]. /// /// This function has no externally visible effect, but you should call it /// periodically to keep this fence's resource consumption under control. /// /// For fences using the [`FencePool`] implementation, this function /// recycles fences that have been signaled. If you don't call this, /// [`Queue::submit`] will just keep allocating a new Vulkan fence every /// time it's called. /// /// [`FencePool`]: Fence::FencePool /// [`Queue::submit`]: crate::Queue::submit fn maintain(&mut self, device: &ash::Device) -> Result<(), crate::DeviceError> { match *self { Self::TimelineSemaphore(_) => {} Self::FencePool { ref mut last_completed, ref mut active, ref mut free, } => { let latest = Self::check_active(device, *last_completed, active)?; let base_free = free.len(); for &(value, raw) in active.iter() { if value <= latest { free.push(raw); } } if free.len() != base_free { active.retain(|&(value, _)| value > latest); unsafe { device.reset_fences(&free[base_free..])?; } } *last_completed = latest; } } Ok(()) } } impl crate::Queue for Queue { type A = Api; unsafe fn submit( &self, command_buffers: &[&CommandBuffer], surface_textures: &[&SurfaceTexture], signal_fence: Option<(&mut Fence, crate::FenceValue)>, ) -> Result<(), crate::DeviceError> { let mut fence_raw = vk::Fence::null(); let mut wait_stage_masks = Vec::new(); let mut wait_semaphores = Vec::new(); let mut signal_semaphores = ArrayVec::<_, 2>::new(); let mut signal_values = ArrayVec::<_, 2>::new(); for &surface_texture in surface_textures { wait_stage_masks.push(vk::PipelineStageFlags::TOP_OF_PIPE); wait_semaphores.push(surface_texture.wait_semaphore); } let old_index = self.relay_index.load(Ordering::Relaxed); let sem_index = if old_index >= 0 { wait_stage_masks.push(vk::PipelineStageFlags::TOP_OF_PIPE); wait_semaphores.push(self.relay_semaphores[old_index as usize]); (old_index as usize + 1) % self.relay_semaphores.len() } else { 0 }; signal_semaphores.push(self.relay_semaphores[sem_index]); self.relay_index .store(sem_index as isize, Ordering::Relaxed); if let Some((fence, value)) = signal_fence { fence.maintain(&self.device.raw)?; match *fence { Fence::TimelineSemaphore(raw) => { signal_semaphores.push(raw); signal_values.push(!0); signal_values.push(value); } Fence::FencePool { ref mut active, ref mut free, .. } => { fence_raw = match free.pop() { Some(raw) => raw, None => unsafe { self.device .raw .create_fence(&vk::FenceCreateInfo::builder(), None)? }, }; active.push((value, fence_raw)); } } } let vk_cmd_buffers = command_buffers .iter() .map(|cmd| cmd.raw) .collect::>(); let mut vk_info = vk::SubmitInfo::builder().command_buffers(&vk_cmd_buffers); vk_info = vk_info .wait_semaphores(&wait_semaphores) .wait_dst_stage_mask(&wait_stage_masks) .signal_semaphores(&signal_semaphores); let mut vk_timeline_info; if !signal_values.is_empty() { vk_timeline_info = vk::TimelineSemaphoreSubmitInfo::builder().signal_semaphore_values(&signal_values); vk_info = vk_info.push_next(&mut vk_timeline_info); } profiling::scope!("vkQueueSubmit"); unsafe { self.device .raw .queue_submit(self.raw, &[vk_info.build()], fence_raw)? }; Ok(()) } unsafe fn present( &self, surface: &Surface, texture: SurfaceTexture, ) -> Result<(), crate::SurfaceError> { let mut swapchain = surface.swapchain.write(); let ssc = swapchain.as_mut().unwrap(); let swapchains = [ssc.raw]; let image_indices = [texture.index]; let mut vk_info = vk::PresentInfoKHR::builder() .swapchains(&swapchains) .image_indices(&image_indices); let old_index = self.relay_index.swap(-1, Ordering::Relaxed); if old_index >= 0 { vk_info = vk_info.wait_semaphores( &self.relay_semaphores[old_index as usize..old_index as usize + 1], ); } let suboptimal = { profiling::scope!("vkQueuePresentKHR"); unsafe { self.swapchain_fn.queue_present(self.raw, &vk_info) }.map_err(|error| { match error { vk::Result::ERROR_OUT_OF_DATE_KHR => crate::SurfaceError::Outdated, vk::Result::ERROR_SURFACE_LOST_KHR => crate::SurfaceError::Lost, _ => crate::DeviceError::from(error).into(), } })? }; if suboptimal { // We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android. // On Android 10+, libvulkan's `vkQueuePresentKHR` implementation returns `VK_SUBOPTIMAL_KHR` if not doing pre-rotation // (i.e `VkSwapchainCreateInfoKHR::preTransform` not being equal to the current device orientation). // This is always the case when the device orientation is anything other than the identity one, as we unconditionally use `VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR`. #[cfg(not(target_os = "android"))] log::warn!("Suboptimal present of frame {}", texture.index); } Ok(()) } unsafe fn get_timestamp_period(&self) -> f32 { self.device.timestamp_period } } impl From for crate::DeviceError { fn from(result: vk::Result) -> Self { #![allow(unreachable_code)] match result { vk::Result::ERROR_OUT_OF_HOST_MEMORY | vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => { #[cfg(feature = "oom_panic")] panic!("Out of memory ({result:?})"); Self::OutOfMemory } vk::Result::ERROR_DEVICE_LOST => { #[cfg(feature = "device_lost_panic")] panic!("Device lost"); Self::Lost } _ => { #[cfg(feature = "internal_error_panic")] panic!("Internal error: {result:?}"); log::warn!("Unrecognized device error {result:?}"); Self::Lost } } } }