implcrate::Api for Api { const VARIANT: wgt::Backend = wgt::Backend::Vulkan;
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 PipelineCache = PipelineCache;
type BindGroupLayout = BindGroupLayout; type BindGroup = BindGroup; type PipelineLayout = PipelineLayout; type ShaderModule = ShaderModule; type RenderPipeline = RenderPipeline; type ComputePipeline = ComputePipeline;
}
/// 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<DebugUtilsMessengerUserData>,
}
#[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: 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)] pubstruct DebugUtilsMessengerUserData { /// The properties related to the validation layer, if present
validation_layer_properties: Option<ValidationLayerProperties>,
/// 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,
}
pubstruct InstanceShared {
raw: ash::Instance,
extensions: Vec<&'static CStr>,
flags: wgt::InstanceFlags,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
debug_utils: Option<DebugUtils>,
get_physical_device_properties: Option<khr::get_physical_device_properties2::Instance>,
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,
// The `drop_guard` field must be the last field of this struct so it is dropped last. // Do not add new fields after it.
drop_guard: Option<crate::DropGuard>,
}
impl Surface { /// Returns the raw Vulkan surface handle. /// /// Returns `None` if the surface is a DXGI surface. pubunsafefn raw_native_handle(&self) -> Option<vk::SurfaceKHR> {
Some( self.inner
.as_any()
.downcast_ref::<swapchain::NativeSurface>()?
.as_raw(),
)
}
/// Get the raw Vulkan swapchain associated with this surface. /// /// Returns [`None`] if the surface is not configured or if the swapchain /// is a DXGI swapchain. pubfn raw_native_swapchain(&self) -> Option<vk::SwapchainKHR> { let read = self.swapchain.read();
Some(
read.as_ref()?
.as_any()
.downcast_ref::<swapchain::NativeSwapchain>()?
.as_raw(),
)
}
/// Set the present timing information which will be used for the next [presentation](crate::Queue::present()) of this surface, /// using [VK_GOOGLE_display_timing]. /// /// This can be used to give an id to presentations, for future use of [`vk::PastPresentationTimingGOOGLE`]. /// Note that `wgpu-hal` does *not* provide a way to use that API - you should manually access this through [`ash`]. /// /// This can also be used to add a "not before" timestamp to the presentation. /// /// 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. /// /// # Panics /// /// - If the surface hasn't been configured. /// - If the surface has been configured for a DXGI swapchain. /// - If the device doesn't [support present timing](wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING). /// /// [VK_GOOGLE_display_timing]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html #[track_caller] pubfn set_next_present_time(&self, present_timing: vk::PresentTimeGOOGLE) { letmut swapchain = self.swapchain.write();
swapchain
.as_mut()
.expect("Surface should have been configured")
.as_any_mut()
.downcast_mut::<swapchain::NativeSwapchain>()
.expect("Surface should have a native Vulkan swapchain")
.set_next_present_time(present_timing);
}
}
// 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<T> { /// 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,
}
/// 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 {
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,
multi_draw_indirect: bool,
max_draw_indirect_count: u32,
/// True if this adapter advertises the [`robustBufferAccess`][vrba] feature. /// /// Note that Vulkan's `robustBufferAccess` is not sufficient to implement /// `wgpu_hal`'s guarantee that shaders will not access buffer contents via /// a given bindgroup binding outside that binding's [accessible /// region][ar]. Enabling `robustBufferAccess` does ensure that /// out-of-bounds reads and writes are not undefined behavior (that's good), /// but still permits out-of-bounds reads to return data from anywhere /// within the buffer, not just the accessible region. /// /// [ar]: ../struct.BufferBinding.html#accessible-region /// [vrba]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#features-robustBufferAccess
robust_buffer_access: bool,
/// True if this adapter supports 8-bit integers provided by the /// [`VK_KHR_shader_float16_int8`] extension (promoted to Vulkan 1.2). /// /// Allows shaders to declare the "Int8" capability. Note, however, that this /// feature alone allows the use of 8-bit integers "only in the `Private`, /// `Workgroup` (for non-Block variables), and `Function` storage classes" /// ([see spec]). To use 8-bit integers in the interface storage classes (e.g., /// `StorageBuffer`), you also need to enable the corresponding feature in /// `VkPhysicalDevice8BitStorageFeatures` and declare the corresponding SPIR-V /// capability (e.g., `StorageBuffer8BitAccess`). /// /// [`VK_KHR_shader_float16_int8`]: https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_shader_float16_int8.html /// [see spec]: https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDeviceShaderFloat16Int8Features.html#extension-features-shaderInt8
shader_int8: bool,
/// This is done to panic before undefined behavior, and is imperfect. /// Basically, to allow implementations to emulate mv using instancing, if you /// want to draw `n` instances to VR, you must draw `2n` instances, but you /// can never draw more than `u32::MAX` instances. Therefore, when drawing /// multiview on some vulkan implementations, it might restrict the instance /// count, which isn't usually a thing in webgpu. We don't expose this limit /// because its strange, i.e. only occurs on certain vulkan implementations /// if you are drawing more than 128 million instances. We still want to avoid /// undefined behavior in this situation, so we panic if the limit is violated.
multiview_instance_index_limit: u32,
/// BufferUsages::ACCELERATION_STRUCTURE_SCRATCH allows usage as a scratch buffer. /// Vulkan has no way to specify this as a usage, and it maps to other usages, but /// these usages do not have as high of an alignment requirement using the buffer as /// a scratch buffer when building acceleration structures.
scratch_buffer_alignment: u32,
}
bitflags::bitflags!( /// Workaround flags. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pubstruct 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;
}
);
/// Because we have cached framebuffers which are not deleted from until /// the device is destroyed, if the implementation of vulkan re-uses handles /// we need some way to differentiate between the old handle and the new handle. /// This factory allows us to have a dedicated identity value for each texture.
texture_identity_factory: ResourceIdentityFactory<vk::Image>, /// As above, for texture views.
texture_view_identity_factory: ResourceIdentityFactory<vk::ImageView>,
// The `drop_guard` field must be the last field of this struct so it is dropped last. // Do not add new fields after it.
drop_guard: Option<crate::DropGuard>,
}
pubstruct Device {
mem_allocator: Mutex<gpu_allocator::vulkan::Allocator>,
desc_allocator: Mutex<descriptor::DescriptorAllocator>,
valid_ash_memory_types: u32,
naga_options: naga::back::spv::Options<'static>, #[cfg(feature = "renderdoc")]
render_doc: crate::auxil::renderdoc::RenderDoc,
counters: Arc<wgt::HalCounters>, // Struct members are dropped from first to last, put the Device last to ensure that // all resources that depends on it are destroyed before it like the mem_allocator
shared: Arc<DeviceShared>,
}
impl Drop for Device { fn drop(&mutself) {}
}
/// Semaphores for forcing queue submissions to run in order. /// /// The [`wgpu_hal::Queue`] trait promises that if two calls to [`submit`] are /// ordered, then the first submission will finish on the GPU before the second /// submission begins. To get this behavior on Vulkan we need to pass semaphores /// to [`vkQueueSubmit`] for the commands to wait on before beginning execution, /// and to signal when their execution is done. /// /// Normally this can be done with a single semaphore, waited on and then /// signalled for each submission. At any given time there's exactly one /// submission that would signal the semaphore, and exactly one waiting on it, /// as Vulkan requires. /// /// However, as of Oct 2021, bug [#5508] in the Mesa ANV drivers caused them to /// hang if we use a single semaphore. The workaround is to alternate between /// two semaphores. The bug has been fixed in Mesa, but we should probably keep /// the workaround until, say, Oct 2026. /// /// [`wgpu_hal::Queue`]: crate::Queue /// [`submit`]: crate::Queue::submit /// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit /// [#5508]: https://gitlab.freedesktop.org/mesa/mesa/-/issues/5508 #[derive(Clone)] struct RelaySemaphores { /// The semaphore the next submission should wait on before beginning /// execution on the GPU. This is `None` for the first submission, which /// should not wait on anything at all.
wait: Option<vk::Semaphore>,
/// The semaphore the next submission should signal when it has finished /// execution on the GPU.
signal: vk::Semaphore,
}
/// Advances the semaphores, returning the semaphores that should be used for a submission. fn advance(&mutself, device: &DeviceShared) -> Result<Self, crate::DeviceError> { let old = self.clone();
// Build the state for the next submission. matchself.wait {
None => { // The `old` values describe the first submission to this queue. // The second submission should wait on `old.signal`, and then // signal a new semaphore which we'll create now. self.wait = Some(old.signal); self.signal = device.new_binary_semaphore("RelaySemaphores: 2")?;
}
Some(refmut wait) => { // What this submission signals, the next should wait.
mem::swap(wait, &mutself.signal);
}
};
impl Drop for Queue { fn drop(&mutself) { unsafe { self.relay_semaphores.lock().destroy(&self.device.raw) };
}
} #[derive(Debug)] enum BufferMemoryBacking {
Managed(gpu_allocator::vulkan::Allocation),
VulkanMemory {
memory: vk::DeviceMemory,
offset: u64,
size: u64,
},
} impl BufferMemoryBacking { fn memory(&self) -> vk::DeviceMemory { matchself { Self::Managed(m) => unsafe { m.memory() }, Self::VulkanMemory { memory, .. } => *memory,
}
} fn offset(&self) -> u64 { matchself { Self::Managed(m) => m.offset(), Self::VulkanMemory { offset, .. } => *offset,
}
} fn size(&self) -> u64 { matchself { Self::Managed(m) => m.size(), Self::VulkanMemory { size, .. } => *size,
}
}
} /// Describes who owns a [`Buffer`]'s `vk::Buffer` handle and its backing memory, /// and therefore what cleanup is required when the buffer is destroyed. #[derive(Debug)] enum BufferOwnership { /// wgpu-hal owns the `vk::Buffer` and its backing memory. On cleanup the buffer /// handle is destroyed and the memory is released.
Managed(Mutex<BufferMemoryBacking>), /// wgpu-hal owns the `vk::Buffer` handle but the backing memory is kept alive /// by the caller. On cleanup only the buffer handle is destroyed.
RawHandle, /// Caller owns the `vk::Buffer` and its backing memory. On cleanup the /// [`crate::DropGuard`] runs the caller's cleanup callback and wgpu-hal touches /// neither the handle nor the memory.
External(crate::DropGuard),
}
// This field must be last, because it may contain a `DropGuard` which needs to be dropped after all other fields.
ownership: BufferOwnership,
} impl Buffer { /// # Safety /// /// - `vk_buffer`'s memory must be managed by the caller /// - Externally imported buffers can't be mapped by `wgpu` pubunsafefn from_raw(vk_buffer: vk::Buffer) -> Self { Self {
raw: vk_buffer,
ownership: BufferOwnership::RawHandle,
}
}
/// # Safety /// - `vk_buffer` must outlive the returned `Buffer`. /// - wgpu-hal will NOT call `vkDestroyBuffer`; the caller remains responsible for the buffer handle's destruction. /// The `drop_callback` runs when the `Buffer` drops and may be used to release caller-side bookkeeping. /// - Externally imported buffers can't be mapped by `wgpu`. pubunsafefn from_raw_externally_owned(
vk_buffer: vk::Buffer,
drop_callback: crate::DropCallback,
) -> Self { Self {
raw: vk_buffer,
ownership: BufferOwnership::External(crate::DropGuard::new(drop_callback)),
}
}
/// # Safety /// - 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 /// - Externally imported buffers can't be mapped by `wgpu` /// - `offset` and `size` must be valid with the allocation of `memory` pubunsafefn from_raw_managed(
vk_buffer: vk::Buffer,
memory: vk::DeviceMemory,
offset: u64,
size: u64,
) -> Self { Self {
raw: vk_buffer,
ownership: BufferOwnership::Managed(Mutex::new(BufferMemoryBacking::VulkanMemory {
memory,
offset,
size,
})),
}
}
/// # Safety /// - The buffer handle must not be manually destroyed pubunsafefn raw_handle(&self) -> vk::Buffer { self.raw
}
}
// The `drop_guard` field must be the last field of this struct so it is dropped last. // Do not add new fields after it.
drop_guard: Option<crate::DropGuard>,
}
implcrate::DynTexture for Texture {}
impl Texture { /// # Safety /// /// - The image handle must not be manually destroyed pubunsafefn raw_handle(&self) -> vk::Image { self.raw
}
/// # Safety /// /// - The caller must not free the `vk::DeviceMemory` or /// `gpu_alloc::MemoryBlock` in the returned `TextureMemory`. pubunsafefn memory(&self) -> &TextureMemory {
&self.memory
}
}
impl TextureView { /// # Safety /// /// - The image view handle must not be manually destroyed pubunsafefn raw_handle(&self) -> vk::ImageView { self.raw
}
/// Returns the raw texture view, along with its identity. fn identified_raw_view(&self) -> IdentifiedTextureView {
IdentifiedTextureView {
raw: self.raw,
identity: self.view_identity,
}
}
}
/// Information about a binding within a specific BindGroupLayout / BindGroup. /// This will be used to construct a [`naga::back::spv::BindingInfo`], where /// the descriptor set value will be taken from the index of the group. #[derive(Copy, Clone, Debug)] struct BindingInfo {
binding: u32,
binding_array_size: Option<NonZeroU32>,
}
#[derive(Debug)] pubstruct BindGroupLayout {
raw: vk::DescriptorSetLayout,
desc_count: descriptor::DescriptorCounts, /// Sorted list of entries.
entries: Box<[wgt::BindGroupLayoutEntry]>, /// Map of original binding index to remapped binding index and optional /// array size.
binding_map: Vec<(u32, BindingInfo)>,
contains_binding_arrays: bool,
}
implcrate::DynBindGroupLayout for BindGroupLayout {}
/// Generates unique IDs for each resource of type `T`. /// /// Because vk handles are not permanently unique, this /// provides a way to generate unique IDs for each resource. struct ResourceIdentityFactory<T> { #[cfg(not(target_has_atomic = "64"))]
next_id: Mutex<u64>, #[cfg(target_has_atomic = "64")]
next_id: core::sync::atomic::AtomicU64,
_phantom: PhantomData<T>,
}
/// Returns a new unique ID for a resource of type `T`. fn next(&self) -> ResourceIdentity<T> { #[cfg(not(target_has_atomic = "64"))]
{ letmut next_id = self.next_id.lock(); let id = *next_id;
*next_id += 1;
ResourceIdentity {
id,
_phantom: PhantomData,
}
}
/// A unique identifier for a resource of type `T`. /// /// This is used as a hashable key for resources, which /// is permanently unique through the lifetime of the program. #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] struct ResourceIdentity<T> {
id: u64,
_phantom: PhantomData<T>,
}
#[derive(Clone, Eq, Hash, PartialEq)] struct FramebufferKey {
raw_pass: vk::RenderPass, /// Because this is used as a key in a hash map, we need to include the identity /// so that this hashes differently, even if the ImageView handles are the same /// between different views.
attachment_identities: ArrayVec<ResourceIdentity<vk::ImageView>, { MAX_TOTAL_ATTACHMENTS }>, /// While this is redundant for calculating the hash, we need access to an array /// of all the raw ImageViews when we are creating the actual framebuffer, /// so we store this here.
attachment_views: ArrayVec<vk::ImageView, { MAX_TOTAL_ATTACHMENTS }>,
extent: wgt::Extent3d,
}
/// A texture view paired with its identity. #[derive(Copy, Clone)] struct IdentifiedTextureView {
raw: vk::ImageView,
identity: ResourceIdentity<vk::ImageView>,
}
#[derive(Clone, Eq, Hash, PartialEq)] struct TempTextureViewKey {
texture: vk::Image, /// As this is used in a hashmap, we need to /// include the identity so that this hashes differently, /// even if the Image handles are the same between different images.
texture_identity: ResourceIdentity<vk::Image>,
format: vk::Format,
mip_level: u32,
depth_slice: u32,
}
/// 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<vk::CommandBuffer>,
/// A pool of discarded command buffers. /// /// These could be in any Vulkan state except "pending".
discarded: Vec<vk::CommandBuffer>,
/// 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 Drop for CommandEncoder { fn drop(&mutself) { // SAFETY: // // VUID-vkDestroyCommandPool-commandPool-00041: wgpu_hal requires that a // `CommandBuffer` must live until its execution is complete, and that a // `CommandBuffer` must not outlive the `CommandEncoder` that built it. // Thus, we know that none of our `CommandBuffers` are in the "pending" // state. // // The other VUIDs are pretty obvious. unsafe { // `vkDestroyCommandPool` also frees any command buffers allocated // from that pool, so there's no need to explicitly call // `vkFreeCommandBuffers` on `cmd_encoder`'s `free` and `discarded` // fields. self.device.raw.destroy_command_pool(self.raw, None);
}
/// 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)] pubenum 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. /// /// One should keep the fence pool read while there are any references to the /// fences inside of them. This ensures there are no race conditions when /// resetting the fences /// /// [fence]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-fences /// [`FenceValue`]: crate::FenceValue
FencePool(RwLock<FencePool>),
}
/// A shared fence type. The arc is expect to have a ref-count of one once a function has finished being called /// /// A fence should have access synchronised as fence resetting might happen at any point. Resetting checks the ref-count /// of the fence, so instead of copying the fence, it should have its `Arc` container cloned which shows not to reset /// this fence as it is being used. pub(super) type SynchronizedFence = Arc<vk::Fence>;
#[derive(Debug)] pubstruct FencePool {
last_completed: crate::FenceValue, /// The pending fence values have to be ascending.
active: Vec<(crate::FenceValue, SynchronizedFence)>, // Don't need extra synchronisation around the fences here, if they are used they should be put into active.
free: Vec<vk::Fence>,
}
implcrate::DynFence for Fence {}
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, SynchronizedFence)],
) -> Result<crate::FenceValue, crate::DeviceError> { for &(value, ref raw) in active.iter() { unsafe { if value > last_completed
&& device // Don't need to clone as active should be from a read or // write lock which means this is already synchronised.
.get_fence_status(**raw)
.map_err(map_host_device_oom_and_lost_err)?
{
last_completed = value;
}
}
}
Ok(last_completed)
}
/// 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(&self, device: &ash::Device) -> Result<(), crate::DeviceError> { match *self { Self::TimelineSemaphore(_) => {} Self::FencePool(ref pool) => { let FencePool { refmut last_completed, refmut active, refmut free,
} = *pool.write();
let base_free = free.len(); let latest = Self::check_active(device, *last_completed, active)?;
active.retain_mut(|&mut (value, refmut fence)| { if value > latest { true
} elseiflet Some(fence) = Arc::get_mut(fence) { // No other references to these, so we have exclusive access. Add them to free and reset them later, // but drop them from active immediately
free.push(*fence); false
} else { // some other function is using it. Although this shouldn't be to long, // maintain shouldn't block, and it should be cleared up by the next time it happens true
}
});
// Double check that the same swapchain image isn't being given to us multiple times, // as that will deadlock when we try to lock them all.
debug_assert!(
{ letmut check = HashSet::with_capacity(surface_textures.len()); // We compare the Box by pointer, as Eq isn't well defined for SurfaceSemaphores. for st in surface_textures { let ptr: *const () = <*const _>::cast(&*st.metadata);
check.insert(ptr as usize);
}
check.len() == surface_textures.len()
}, "More than one surface texture is being used from the same swapchain. This will cause a deadlock in release."
);
let locked_swapchain_semaphores = surface_textures
.iter()
.map(|st| st.metadata.get_semaphore_guard())
.collect::<Vec<_>>();
formut semaphores in locked_swapchain_semaphores {
semaphores.set_used_fence_value(signal_value);
// If we're the first submission to operate on this image, wait on // its acquire semaphore, to make sure the presentation engine is // done with it. iflet Some(sem) = semaphores.get_acquire_wait_semaphore() {
wait_semaphores.push_wait(sem, vk::PipelineStageFlags::TOP_OF_PIPE);
}
// Get a semaphore to signal when we're done writing to this surface // image. Presentation of this image will wait for this. let signal_semaphore = semaphores.get_submit_signal_semaphore(&self.device)?;
signal_semaphores.push_signal(signal_semaphore);
}
letmut guard = self.signal_semaphores.lock(); if !guard.is_empty() {
signal_semaphores.append(&mut guard);
}
letmut wait_guard = self.wait_semaphores.lock(); if !wait_guard.is_empty() {
wait_semaphores.append(&mut wait_guard);
}
// In order for submissions to be strictly ordered, we encode a dependency between each submission // using a pair of semaphores. This adds a wait if it is needed, and signals the next semaphore. let semaphore_state = self.relay_semaphores.lock().advance(&self.device)?;
// We need to signal our wgpu::Fence if we have one, this adds it to the signal list.
signal_fence.maintain(&self.device.raw)?; // Keeping the Arc around is probably unneeded - the fence should never be signaled as it was reset, // and newer submits should not happen until this submit is done. Therefore, it should be too high // to be reset. let shared_fence; match *signal_fence {
Fence::TimelineSemaphore(raw) => {
signal_semaphores.push_signal(SemaphoreType::Timeline(raw, signal_value));
}
Fence::FencePool(ref pool) => { let FencePool { refmut active, refmut free,
..
} = *pool.write();
shared_fence = match free.pop() {
Some(raw) => Arc::new(raw),
None => unsafe { let fence = self
.device
.raw
.create_fence(&vk::FenceCreateInfo::default(), None)
.map_err(map_host_device_oom_err)?;
Arc::new(fence)
},
};
fence_raw = *shared_fence;
active.push((signal_value, shared_fence.clone()));
}
}
let vk_cmd_buffers = command_buffers
.iter()
.map(|cmd| cmd.raw)
.collect::<Vec<_>>();
/// Remove `semaphore` from the pending signal list if it is still present. /// /// Returns `true` if the semaphore was found and removed. If the submit /// already consumed it, this is a harmless no-op that returns `false`. pubfn remove_signal_semaphore(&self, semaphore: vk::Semaphore) -> bool { self.signal_semaphores.lock().remove(semaphore)
}
/// Stage a semaphore wait on the next [`crate::Queue::submit`] call. /// /// `semaphore_value` selects the kind of payload the wait targets: /// /// - `Some(value)` - wait until `semaphore` (a timeline semaphore) has been signalled to at least `value`. /// - `None` - wait on a binary semaphore signal. /// /// `stage` is the pipeline stage at which the wait blocks downstream /// work (e.g. `vk::PipelineStageFlags::TOP_OF_PIPE` to gate the /// entire submission, or a more specific stage when only that stage /// reads the synchronised resource). pubfn add_wait_semaphore(
&self,
semaphore: vk::Semaphore,
semaphore_value: Option<u64>,
stage: vk::PipelineStageFlags,
) { letmut guard = self.wait_semaphores.lock(); iflet Some(value) = semaphore_value {
guard.push_wait(SemaphoreType::Timeline(semaphore, value), stage);
} else {
guard.push_wait(SemaphoreType::Binary(semaphore), stage);
}
}
/// Remove `semaphore` from the pending wait list if it is still present. /// /// Returns `true` if the semaphore was found and removed. If the submit /// already consumed it, this is a no-op that returns `false`. pubfn remove_wait_semaphore(&self, semaphore: vk::Semaphore) -> bool { self.wait_semaphores.lock().remove(semaphore)
}
}
/// Returns [`crate::DeviceError::Lost`] or panics if the `device_lost_panic` /// feature flag is enabled. fn get_lost_err() -> crate::DeviceError { #[cfg(feature = "device_lost_panic")]
panic!("Device lost");
/// Arguments to the [`CreateDeviceCallback`]. pubstruct CreateDeviceCallbackArgs<'arg, 'pnext, 'this> where 'this: 'pnext,
{ /// The extensions to enable for the device. You must not remove anything from this list, /// but you may add to it. pub extensions: &'arg mut Vec<&'static CStr>, /// The physical device features to enable. You may enable features, but must not disable any. pub device_features: &'arg mut PhysicalDeviceFeatures, /// The queue create infos for the device. You may add or modify queue create infos as needed. pub queue_create_infos: &'arg mut Vec<vk::DeviceQueueCreateInfo<'pnext>>, /// The create info for the device. You may add or modify things in the pnext chain, but /// do not turn features off. Additionally, do not add things to the list of extensions, /// or to the feature set, as all changes to that member will be overwritten. pub create_info: &'arg mut vk::DeviceCreateInfo<'pnext>, /// We need to have `'this` in the struct, so we can declare that all lifetimes coming from /// captures in the closure will live longer (and hence satisfy) `'pnext`. However, we /// don't actually directly use `'this`
_phantom: PhantomData<&'this ()>,
}
/// Callback to allow changing the vulkan device creation parameters. /// /// # Safety: /// - If you want to add extensions, add the to the `Vec<'static CStr>` not the create info, /// as the create info value will be overwritten. /// - Callback must not remove features. /// - Callback must not change anything to what the instance does not support. pubtype CreateDeviceCallback<'this> = dynfor<'arg, 'pnext> FnOnce(CreateDeviceCallbackArgs<'arg, 'pnext, 'this>) + 'this;
/// Arguments to the [`CreateInstanceCallback`]. pubstruct CreateInstanceCallbackArgs<'arg, 'pnext, 'this> where 'this: 'pnext,
{ /// The extensions to enable for the instance. You must not remove anything from this list, /// but you may add to it. pub extensions: &'arg mut Vec<&'static CStr>, /// The create info for the instance. You may add or modify things in the pnext chain, but /// do not turn features off. Additionally, do not add things to the list of extensions, /// all changes to that member will be overwritten. pub create_info: &'arg mut vk::InstanceCreateInfo<'pnext>, /// Vulkan entry point. pub entry: &'arg ash::Entry, /// We need to have `'this` in the struct, so we can declare that all lifetimes coming from /// captures in the closure will live longer (and hence satisfy) `'pnext`. However, we /// don't actually directly use `'this`
_phantom: PhantomData<&'this ()>,
}
/// Callback to allow changing the vulkan instance creation parameters. /// /// # Safety: /// - If you want to add extensions, add the to the `Vec<'static CStr>` not the create info, /// as the create info value will be overwritten. /// - Callback must not remove features. /// - Callback must not change anything to what the instance does not support. pubtype CreateInstanceCallback<'this> = dynfor<'arg, 'pnext> FnOnce(CreateInstanceCallbackArgs<'arg, 'pnext, 'this>) + 'this;
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.27Bemerkung:
(vorverarbeitet am 2026-08-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.