mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
Factor out swapchain to abstracted interface (#8356)
This commit is contained in:
parent
0810110bd7
commit
ef1d31955a
@ -4,7 +4,9 @@ use core::{ffi::CStr, marker::PhantomData};
|
||||
use ash::{ext, google, khr, vk};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::conv;
|
||||
use crate::vulkan::semaphore_list::SemaphoreList;
|
||||
|
||||
use super::semaphore_list::SemaphoreListMode;
|
||||
|
||||
fn depth_stencil_required_flags() -> vk::FormatFeatureFlags {
|
||||
vk::FormatFeatureFlags::SAMPLED_IMAGE | vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT
|
||||
@ -1990,8 +1992,6 @@ impl super::Adapter {
|
||||
}
|
||||
});
|
||||
|
||||
let swapchain_fn = khr::swapchain::Device::new(&self.instance.raw, &raw_device);
|
||||
|
||||
// Note that VK_EXT_debug_utils is an instance extension (enabled at the instance
|
||||
// level) but contains a few functions that can be loaded directly on the Device for a
|
||||
// dispatch-table-less pointer.
|
||||
@ -2271,11 +2271,10 @@ impl super::Adapter {
|
||||
|
||||
let queue = super::Queue {
|
||||
raw: raw_queue,
|
||||
swapchain_fn,
|
||||
device: Arc::clone(&shared),
|
||||
family_index,
|
||||
relay_semaphores: Mutex::new(relay_semaphores),
|
||||
signal_semaphores: Default::default(),
|
||||
signal_semaphores: Mutex::new(SemaphoreList::new(SemaphoreListMode::Signal)),
|
||||
};
|
||||
|
||||
let mem_allocator = {
|
||||
@ -2597,113 +2596,7 @@ impl crate::Adapter for super::Adapter {
|
||||
&self,
|
||||
surface: &super::Surface,
|
||||
) -> Option<crate::SurfaceCapabilities> {
|
||||
if !self.private_caps.can_present {
|
||||
return None;
|
||||
}
|
||||
let queue_family_index = 0; //TODO
|
||||
{
|
||||
profiling::scope!("vkGetPhysicalDeviceSurfaceSupportKHR");
|
||||
match unsafe {
|
||||
surface.functor.get_physical_device_surface_support(
|
||||
self.raw,
|
||||
queue_family_index,
|
||||
surface.raw,
|
||||
)
|
||||
} {
|
||||
Ok(true) => (),
|
||||
Ok(false) => return None,
|
||||
Err(e) => {
|
||||
log::error!("get_physical_device_surface_support: {e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let caps = {
|
||||
profiling::scope!("vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
|
||||
match unsafe {
|
||||
surface
|
||||
.functor
|
||||
.get_physical_device_surface_capabilities(self.raw, surface.raw)
|
||||
} {
|
||||
Ok(caps) => caps,
|
||||
Err(e) => {
|
||||
log::error!("get_physical_device_surface_capabilities: {e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If image count is 0, the support number of images is unlimited.
|
||||
let max_image_count = if caps.max_image_count == 0 {
|
||||
!0
|
||||
} else {
|
||||
caps.max_image_count
|
||||
};
|
||||
|
||||
// `0xFFFFFFFF` indicates that the extent depends on the created swapchain.
|
||||
let current_extent = if caps.current_extent.width != !0 && caps.current_extent.height != !0
|
||||
{
|
||||
Some(wgt::Extent3d {
|
||||
width: caps.current_extent.width,
|
||||
height: caps.current_extent.height,
|
||||
depth_or_array_layers: 1,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let raw_present_modes = {
|
||||
profiling::scope!("vkGetPhysicalDeviceSurfacePresentModesKHR");
|
||||
match unsafe {
|
||||
surface
|
||||
.functor
|
||||
.get_physical_device_surface_present_modes(self.raw, surface.raw)
|
||||
} {
|
||||
Ok(present_modes) => present_modes,
|
||||
Err(e) => {
|
||||
log::error!("get_physical_device_surface_present_modes: {e}");
|
||||
// Per definition of `SurfaceCapabilities`, there must be at least one present mode.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let raw_surface_formats = {
|
||||
profiling::scope!("vkGetPhysicalDeviceSurfaceFormatsKHR");
|
||||
match unsafe {
|
||||
surface
|
||||
.functor
|
||||
.get_physical_device_surface_formats(self.raw, surface.raw)
|
||||
} {
|
||||
Ok(formats) => formats,
|
||||
Err(e) => {
|
||||
log::error!("get_physical_device_surface_formats: {e}");
|
||||
// Per definition of `SurfaceCapabilities`, there must be at least one present format.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let formats = raw_surface_formats
|
||||
.into_iter()
|
||||
.filter_map(conv::map_vk_surface_formats)
|
||||
.collect();
|
||||
Some(crate::SurfaceCapabilities {
|
||||
formats,
|
||||
// TODO: Right now we're always trunkating the swap chain
|
||||
// (presumably - we're actually setting the min image count which isn't necessarily the swap chain size)
|
||||
// Instead, we should use extensions when available to wait in present.
|
||||
// See https://github.com/gfx-rs/wgpu/issues/2869
|
||||
maximum_frame_latency: (caps.min_image_count - 1)..=(max_image_count - 1), // Note this can't underflow since both `min_image_count` is at least one and we already patched `max_image_count`.
|
||||
current_extent,
|
||||
usage: conv::map_vk_image_usage(caps.supported_usage_flags),
|
||||
present_modes: raw_present_modes
|
||||
.into_iter()
|
||||
.flat_map(conv::map_vk_present_mode)
|
||||
.collect(),
|
||||
composite_alpha_modes: conv::map_vk_composite_alpha(caps.supported_composite_alpha),
|
||||
})
|
||||
surface.inner.surface_capabilities(self)
|
||||
}
|
||||
|
||||
unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp {
|
||||
|
||||
@ -8,7 +8,7 @@ use core::{
|
||||
};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use ash::{ext, khr, vk};
|
||||
use ash::{ext, vk};
|
||||
use hashbrown::hash_map::Entry;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
@ -489,130 +489,6 @@ struct CompiledStage {
|
||||
}
|
||||
|
||||
impl super::Device {
|
||||
pub(super) unsafe fn create_swapchain(
|
||||
&self,
|
||||
surface: &super::Surface,
|
||||
config: &crate::SurfaceConfiguration,
|
||||
provided_old_swapchain: Option<super::Swapchain>,
|
||||
) -> Result<super::Swapchain, crate::SurfaceError> {
|
||||
profiling::scope!("Device::create_swapchain");
|
||||
let functor = khr::swapchain::Device::new(&surface.instance.raw, &self.shared.raw);
|
||||
|
||||
let old_swapchain = match provided_old_swapchain {
|
||||
Some(osc) => osc.raw,
|
||||
None => vk::SwapchainKHR::null(),
|
||||
};
|
||||
|
||||
let color_space = if config.format == wgt::TextureFormat::Rgba16Float {
|
||||
// Enable wide color gamut mode
|
||||
// Vulkan swapchain for Android only supports DISPLAY_P3_NONLINEAR_EXT and EXTENDED_SRGB_LINEAR_EXT
|
||||
vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT
|
||||
} else {
|
||||
vk::ColorSpaceKHR::SRGB_NONLINEAR
|
||||
};
|
||||
|
||||
let original_format = self.shared.private_caps.map_texture_format(config.format);
|
||||
let mut raw_flags = vk::SwapchainCreateFlagsKHR::empty();
|
||||
let mut raw_view_formats: Vec<vk::Format> = vec![];
|
||||
if !config.view_formats.is_empty() {
|
||||
raw_flags |= vk::SwapchainCreateFlagsKHR::MUTABLE_FORMAT;
|
||||
raw_view_formats = config
|
||||
.view_formats
|
||||
.iter()
|
||||
.map(|f| self.shared.private_caps.map_texture_format(*f))
|
||||
.collect();
|
||||
raw_view_formats.push(original_format);
|
||||
}
|
||||
|
||||
let mut info = vk::SwapchainCreateInfoKHR::default()
|
||||
.flags(raw_flags)
|
||||
.surface(surface.raw)
|
||||
.min_image_count(config.maximum_frame_latency + 1) // TODO: https://github.com/gfx-rs/wgpu/issues/2869
|
||||
.image_format(original_format)
|
||||
.image_color_space(color_space)
|
||||
.image_extent(vk::Extent2D {
|
||||
width: config.extent.width,
|
||||
height: config.extent.height,
|
||||
})
|
||||
.image_array_layers(config.extent.depth_or_array_layers)
|
||||
.image_usage(conv::map_texture_usage(config.usage))
|
||||
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY)
|
||||
.composite_alpha(conv::map_composite_alpha_mode(config.composite_alpha_mode))
|
||||
.present_mode(conv::map_present_mode(config.present_mode))
|
||||
.clipped(true)
|
||||
.old_swapchain(old_swapchain);
|
||||
|
||||
let mut format_list_info = vk::ImageFormatListCreateInfo::default();
|
||||
if !raw_view_formats.is_empty() {
|
||||
format_list_info = format_list_info.view_formats(&raw_view_formats);
|
||||
info = info.push_next(&mut format_list_info);
|
||||
}
|
||||
|
||||
let result = {
|
||||
profiling::scope!("vkCreateSwapchainKHR");
|
||||
unsafe { functor.create_swapchain(&info, None) }
|
||||
};
|
||||
|
||||
// doing this before bailing out with error
|
||||
if old_swapchain != vk::SwapchainKHR::null() {
|
||||
unsafe { functor.destroy_swapchain(old_swapchain, None) }
|
||||
}
|
||||
|
||||
let raw = match result {
|
||||
Ok(swapchain) => swapchain,
|
||||
Err(error) => {
|
||||
return Err(match error {
|
||||
vk::Result::ERROR_SURFACE_LOST_KHR
|
||||
| vk::Result::ERROR_INITIALIZATION_FAILED => crate::SurfaceError::Lost,
|
||||
vk::Result::ERROR_NATIVE_WINDOW_IN_USE_KHR => {
|
||||
crate::SurfaceError::Other("Native window is in use")
|
||||
}
|
||||
// We don't use VK_EXT_image_compression_control
|
||||
// VK_ERROR_COMPRESSION_EXHAUSTED_EXT
|
||||
other => super::map_host_device_oom_and_lost_err(other).into(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let images =
|
||||
unsafe { functor.get_swapchain_images(raw) }.map_err(super::map_host_device_oom_err)?;
|
||||
|
||||
let fence = unsafe {
|
||||
self.shared
|
||||
.raw
|
||||
.create_fence(&vk::FenceCreateInfo::default(), None)
|
||||
.map_err(super::map_host_device_oom_err)?
|
||||
};
|
||||
|
||||
// NOTE: It's important that we define the same number of acquire/present semaphores
|
||||
// as we will need to index into them with the image index.
|
||||
let acquire_semaphores = (0..=images.len())
|
||||
.map(|i| {
|
||||
super::SwapchainAcquireSemaphore::new(&self.shared, i)
|
||||
.map(Mutex::new)
|
||||
.map(Arc::new)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let present_semaphores = (0..=images.len())
|
||||
.map(|i| Arc::new(Mutex::new(super::SwapchainPresentSemaphores::new(i))))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(super::Swapchain {
|
||||
raw,
|
||||
functor,
|
||||
device: Arc::clone(&self.shared),
|
||||
images,
|
||||
fence,
|
||||
config: config.clone(),
|
||||
acquire_semaphores,
|
||||
next_acquire_index: 0,
|
||||
present_semaphores,
|
||||
next_present_time: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// - `vk_image` must be created respecting `desc`
|
||||
|
||||
@ -2,6 +2,7 @@ use alloc::{borrow::ToOwned as _, boxed::Box, ffi::CString, string::String, sync
|
||||
use core::{
|
||||
ffi::{c_void, CStr},
|
||||
marker::PhantomData,
|
||||
mem::ManuallyDrop,
|
||||
slice,
|
||||
str::FromStr,
|
||||
};
|
||||
@ -170,48 +171,6 @@ impl super::DebugUtilsCreateInfo {
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Swapchain {
|
||||
/// # Safety
|
||||
///
|
||||
/// - The device must have been made idle before calling this function.
|
||||
unsafe fn release_resources(mut self, device: &ash::Device) -> Self {
|
||||
profiling::scope!("Swapchain::release_resources");
|
||||
{
|
||||
profiling::scope!("vkDeviceWaitIdle");
|
||||
// We need to also wait until all presentation work is done. Because there is no way to portably wait until
|
||||
// the presentation work is done, we are forced to wait until the device is idle.
|
||||
let _ = unsafe {
|
||||
device
|
||||
.device_wait_idle()
|
||||
.map_err(super::map_host_device_oom_and_lost_err)
|
||||
};
|
||||
};
|
||||
|
||||
unsafe { device.destroy_fence(self.fence, None) }
|
||||
|
||||
// We cannot take this by value, as the function returns `self`.
|
||||
for semaphore in self.acquire_semaphores.drain(..) {
|
||||
let arc_removed = Arc::into_inner(semaphore).expect(
|
||||
"Trying to destroy a SurfaceAcquireSemaphores that is still in use by a SurfaceTexture",
|
||||
);
|
||||
let mutex_removed = arc_removed.into_inner();
|
||||
|
||||
unsafe { mutex_removed.destroy(device) };
|
||||
}
|
||||
|
||||
for semaphore in self.present_semaphores.drain(..) {
|
||||
let arc_removed = Arc::into_inner(semaphore).expect(
|
||||
"Trying to destroy a SurfacePresentSemaphores that is still in use by a SurfaceTexture",
|
||||
);
|
||||
let mutex_removed = arc_removed.into_inner();
|
||||
|
||||
unsafe { mutex_removed.destroy(device) };
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl super::InstanceShared {
|
||||
pub fn entry(&self) -> &ash::Entry {
|
||||
&self.entry
|
||||
@ -589,11 +548,11 @@ impl super::Instance {
|
||||
&self,
|
||||
surface: vk::SurfaceKHR,
|
||||
) -> super::Surface {
|
||||
let functor = khr::surface::Instance::new(&self.shared.entry, &self.shared.raw);
|
||||
let native_surface =
|
||||
crate::vulkan::swapchain::NativeSurface::from_vk_surface_khr(self, surface);
|
||||
|
||||
super::Surface {
|
||||
raw: surface,
|
||||
functor,
|
||||
instance: Arc::clone(&self.shared),
|
||||
inner: ManuallyDrop::new(Box::new(native_surface)),
|
||||
swapchain: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
@ -1027,7 +986,7 @@ impl crate::Instance for super::Instance {
|
||||
|
||||
impl Drop for super::Surface {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.functor.destroy_surface(self.raw, None) };
|
||||
unsafe { ManuallyDrop::take(&mut self.inner).delete_surface() };
|
||||
}
|
||||
}
|
||||
|
||||
@ -1041,21 +1000,23 @@ impl crate::Surface for super::Surface {
|
||||
) -> Result<(), crate::SurfaceError> {
|
||||
// SAFETY: `configure`'s contract guarantees there are no resources derived from the swapchain in use.
|
||||
let mut swap_chain = self.swapchain.write();
|
||||
let old = swap_chain
|
||||
.take()
|
||||
.map(|sc| unsafe { sc.release_resources(&device.shared.raw) });
|
||||
|
||||
let swapchain = unsafe { device.create_swapchain(self, config, old)? };
|
||||
let mut old = swap_chain.take();
|
||||
if let Some(ref mut old) = old {
|
||||
unsafe { old.release_resources(device) };
|
||||
}
|
||||
|
||||
let swapchain = unsafe { self.inner.create_swapchain(device, config, old)? };
|
||||
*swap_chain = Some(swapchain);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn unconfigure(&self, device: &super::Device) {
|
||||
if let Some(sc) = self.swapchain.write().take() {
|
||||
if let Some(mut sc) = self.swapchain.write().take() {
|
||||
// SAFETY: `unconfigure`'s contract guarantees there are no resources derived from the swapchain in use.
|
||||
let swapchain = unsafe { sc.release_resources(&device.shared.raw) };
|
||||
unsafe { swapchain.functor.destroy_swapchain(swapchain.raw, None) };
|
||||
unsafe { sc.release_resources(device) };
|
||||
unsafe { sc.delete_swapchain() };
|
||||
}
|
||||
}
|
||||
|
||||
@ -1067,140 +1028,17 @@ impl crate::Surface for super::Surface {
|
||||
let mut swapchain = self.swapchain.write();
|
||||
let swapchain = swapchain.as_mut().unwrap();
|
||||
|
||||
let mut timeout_ns = match timeout {
|
||||
Some(duration) => duration.as_nanos() as u64,
|
||||
None => u64::MAX,
|
||||
};
|
||||
|
||||
// AcquireNextImageKHR on Android (prior to Android 11) doesn't support timeouts
|
||||
// and will also log verbose warnings if tying to use a timeout.
|
||||
//
|
||||
// Android 10 implementation for reference:
|
||||
// https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-10.0.0_r13/vulkan/libvulkan/swapchain.cpp#1426
|
||||
// Android 11 implementation for reference:
|
||||
// https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-11.0.0_r45/vulkan/libvulkan/swapchain.cpp#1438
|
||||
//
|
||||
// Android 11 corresponds to an SDK_INT/ro.build.version.sdk of 30
|
||||
if cfg!(target_os = "android") && self.instance.android_sdk_version < 30 {
|
||||
timeout_ns = u64::MAX;
|
||||
}
|
||||
|
||||
let acquire_semaphore_arc = swapchain.get_acquire_semaphore();
|
||||
// Nothing should be using this, so we don't block, but panic if we fail to lock.
|
||||
let acquire_semaphore_guard = acquire_semaphore_arc
|
||||
.try_lock()
|
||||
.expect("Failed to lock a SwapchainSemaphores.");
|
||||
|
||||
// Wait for all commands writing to the previously acquired image to
|
||||
// complete.
|
||||
//
|
||||
// Almost all the steps in the usual acquire-draw-present flow are
|
||||
// asynchronous: they get something started on the presentation engine
|
||||
// or the GPU, but on the CPU, control returns immediately. Without some
|
||||
// sort of intervention, the CPU could crank out frames much faster than
|
||||
// the presentation engine can display them.
|
||||
//
|
||||
// This is the intervention: if any submissions drew on this image, and
|
||||
// thus waited for `locked_swapchain_semaphores.acquire`, wait for all
|
||||
// of them to finish, thus ensuring that it's okay to pass `acquire` to
|
||||
// `vkAcquireNextImageKHR` again.
|
||||
swapchain.device.wait_for_fence(
|
||||
fence,
|
||||
acquire_semaphore_guard.previously_used_submission_index,
|
||||
timeout_ns,
|
||||
)?;
|
||||
|
||||
// will block if no image is available
|
||||
let (index, suboptimal) = match unsafe {
|
||||
profiling::scope!("vkAcquireNextImageKHR");
|
||||
swapchain.functor.acquire_next_image(
|
||||
swapchain.raw,
|
||||
timeout_ns,
|
||||
acquire_semaphore_guard.acquire,
|
||||
swapchain.fence,
|
||||
)
|
||||
} {
|
||||
// We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android.
|
||||
// See the comment in `Queue::present`.
|
||||
#[cfg(target_os = "android")]
|
||||
Ok((index, _)) => (index, false),
|
||||
#[cfg(not(target_os = "android"))]
|
||||
Ok(pair) => pair,
|
||||
Err(error) => {
|
||||
return match error {
|
||||
vk::Result::TIMEOUT => Ok(None),
|
||||
vk::Result::NOT_READY | vk::Result::ERROR_OUT_OF_DATE_KHR => {
|
||||
Err(crate::SurfaceError::Outdated)
|
||||
}
|
||||
vk::Result::ERROR_SURFACE_LOST_KHR => Err(crate::SurfaceError::Lost),
|
||||
// We don't use VK_EXT_full_screen_exclusive
|
||||
// VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
|
||||
other => Err(super::map_host_device_oom_and_lost_err(other).into()),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for the image was acquired to be fully ready to be rendered too.
|
||||
//
|
||||
// This wait is very important on Windows to avoid bad frame pacing on
|
||||
// Windows where the Vulkan driver is using a DXGI swapchain. See
|
||||
// https://github.com/gfx-rs/wgpu/issues/8310 and
|
||||
// https://github.com/gfx-rs/wgpu/issues/8354 for more details.
|
||||
//
|
||||
// On other platforms, this wait may serve to slightly decrease frame
|
||||
// latency, depending on how the platform implements waiting within
|
||||
// acquire.
|
||||
unsafe {
|
||||
swapchain
|
||||
.device
|
||||
.raw
|
||||
.wait_for_fences(&[swapchain.fence], false, timeout_ns)
|
||||
.map_err(super::map_host_device_oom_and_lost_err)?;
|
||||
|
||||
swapchain
|
||||
.device
|
||||
.raw
|
||||
.reset_fences(&[swapchain.fence])
|
||||
.map_err(super::map_host_device_oom_and_lost_err)?;
|
||||
}
|
||||
|
||||
drop(acquire_semaphore_guard);
|
||||
// We only advance the surface semaphores if we successfully acquired an image, otherwise
|
||||
// we should try to re-acquire using the same semaphores.
|
||||
swapchain.advance_acquire_semaphore();
|
||||
|
||||
let present_semaphore_arc = swapchain.get_present_semaphores(index);
|
||||
|
||||
// special case for Intel Vulkan returning bizarre values (ugh)
|
||||
if swapchain.device.vendor_id == crate::auxil::db::intel::VENDOR && index > 0x100 {
|
||||
return Err(crate::SurfaceError::Outdated);
|
||||
}
|
||||
|
||||
let identity = swapchain.device.texture_identity_factory.next();
|
||||
|
||||
let texture = super::SurfaceTexture {
|
||||
index,
|
||||
texture: super::Texture {
|
||||
raw: swapchain.images[index as usize],
|
||||
drop_guard: None,
|
||||
block: None,
|
||||
external_memory: None,
|
||||
format: swapchain.config.format,
|
||||
copy_size: crate::CopyExtent {
|
||||
width: swapchain.config.extent.width,
|
||||
height: swapchain.config.extent.height,
|
||||
depth: 1,
|
||||
},
|
||||
identity,
|
||||
},
|
||||
acquire_semaphores: acquire_semaphore_arc,
|
||||
present_semaphores: present_semaphore_arc,
|
||||
};
|
||||
Ok(Some(crate::AcquiredSurfaceTexture {
|
||||
texture,
|
||||
suboptimal,
|
||||
}))
|
||||
unsafe { swapchain.acquire(timeout, fence) }
|
||||
}
|
||||
|
||||
unsafe fn discard_texture(&self, _texture: super::SurfaceTexture) {}
|
||||
unsafe fn discard_texture(&self, texture: super::SurfaceTexture) {
|
||||
unsafe {
|
||||
self.swapchain
|
||||
.write()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.discard_texture(texture)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,11 +32,19 @@ mod drm;
|
||||
mod instance;
|
||||
mod sampler;
|
||||
mod semaphore_list;
|
||||
mod swapchain;
|
||||
|
||||
pub use adapter::PhysicalDeviceFeatures;
|
||||
|
||||
use alloc::{boxed::Box, ffi::CString, sync::Arc, vec::Vec};
|
||||
use core::{borrow::Borrow, ffi::CStr, fmt, marker::PhantomData, mem, num::NonZeroU32};
|
||||
use core::{
|
||||
borrow::Borrow,
|
||||
ffi::CStr,
|
||||
fmt,
|
||||
marker::PhantomData,
|
||||
mem::{self, ManuallyDrop},
|
||||
num::NonZeroU32,
|
||||
};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use ash::{ext, khr, vk};
|
||||
@ -49,6 +57,8 @@ use wgt::InternalCounter;
|
||||
|
||||
use semaphore_list::SemaphoreList;
|
||||
|
||||
use crate::vulkan::semaphore_list::{SemaphoreListMode, SemaphoreType};
|
||||
|
||||
const MAX_TOTAL_ATTACHMENTS: usize = crate::MAX_COLOR_ATTACHMENTS * 2 + 1;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -180,302 +190,36 @@ pub struct Instance {
|
||||
shared: Arc<InstanceShared>,
|
||||
}
|
||||
|
||||
/// Semaphore used to acquire a swapchain image.
|
||||
#[derive(Debug)]
|
||||
struct SwapchainAcquireSemaphore {
|
||||
/// A semaphore that is signaled when this image is safe for us to modify.
|
||||
///
|
||||
/// When [`vkAcquireNextImageKHR`] returns the index of the next swapchain
|
||||
/// image that we should use, that image may actually still be in use by the
|
||||
/// presentation engine, and is not yet safe to modify. However, that
|
||||
/// function does accept a semaphore that it will signal when the image is
|
||||
/// indeed safe to begin messing with.
|
||||
///
|
||||
/// This semaphore is:
|
||||
///
|
||||
/// - waited for by the first queue submission to operate on this image
|
||||
/// since it was acquired, and
|
||||
///
|
||||
/// - signaled by [`vkAcquireNextImageKHR`] when the acquired image is ready
|
||||
/// for us to use.
|
||||
///
|
||||
/// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
|
||||
acquire: vk::Semaphore,
|
||||
|
||||
/// True if the next command submission operating on this image should wait
|
||||
/// for [`acquire`].
|
||||
///
|
||||
/// We must wait for `acquire` before drawing to this swapchain image, but
|
||||
/// because `wgpu-hal` queue submissions are always strongly ordered, only
|
||||
/// the first submission that works with a swapchain image actually needs to
|
||||
/// wait. We set this flag when this image is acquired, and clear it the
|
||||
/// first time it's passed to [`Queue::submit`] as a surface texture.
|
||||
///
|
||||
/// Additionally, semaphores can only be waited on once, so we need to ensure
|
||||
/// that we only actually pass this semaphore to the first submission that
|
||||
/// uses that image.
|
||||
///
|
||||
/// [`acquire`]: SwapchainAcquireSemaphore::acquire
|
||||
/// [`Queue::submit`]: crate::Queue::submit
|
||||
should_wait_for_acquire: bool,
|
||||
|
||||
/// The fence value of the last command submission that wrote to this image.
|
||||
///
|
||||
/// The next time we try to acquire this image, we'll block until
|
||||
/// this submission finishes, proving that [`acquire`] is ready to
|
||||
/// pass to `vkAcquireNextImageKHR` again.
|
||||
///
|
||||
/// [`acquire`]: SwapchainAcquireSemaphore::acquire
|
||||
previously_used_submission_index: crate::FenceValue,
|
||||
}
|
||||
|
||||
impl SwapchainAcquireSemaphore {
|
||||
fn new(device: &DeviceShared, index: usize) -> Result<Self, crate::DeviceError> {
|
||||
Ok(Self {
|
||||
acquire: device
|
||||
.new_binary_semaphore(&format!("SwapchainImageSemaphore: Index {index} acquire"))?,
|
||||
should_wait_for_acquire: true,
|
||||
previously_used_submission_index: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the fence value which the next acquire will wait for. This prevents
|
||||
/// the semaphore from being used while the previous submission is still in flight.
|
||||
fn set_used_fence_value(&mut self, value: crate::FenceValue) {
|
||||
self.previously_used_submission_index = value;
|
||||
}
|
||||
|
||||
/// Return the semaphore that commands drawing to this image should wait for, if any.
|
||||
///
|
||||
/// This only returns `Some` once per acquisition; see
|
||||
/// [`SwapchainAcquireSemaphore::should_wait_for_acquire`] for details.
|
||||
fn get_acquire_wait_semaphore(&mut self) -> Option<vk::Semaphore> {
|
||||
if self.should_wait_for_acquire {
|
||||
self.should_wait_for_acquire = false;
|
||||
Some(self.acquire)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates the cpu-side usage of this semaphore has finished for the frame,
|
||||
/// so reset internal state to be ready for the next frame.
|
||||
fn end_semaphore_usage(&mut self) {
|
||||
// Reset the acquire semaphore, so that the next time we acquire this
|
||||
// image, we can wait for it again.
|
||||
self.should_wait_for_acquire = true;
|
||||
}
|
||||
|
||||
unsafe fn destroy(&self, device: &ash::Device) {
|
||||
unsafe {
|
||||
device.destroy_semaphore(self.acquire, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SwapchainPresentSemaphores {
|
||||
/// A pool of semaphores for ordering presentation after drawing.
|
||||
///
|
||||
/// The first [`present_index`] semaphores in this vector are:
|
||||
///
|
||||
/// - all waited on by the call to [`vkQueuePresentKHR`] that presents this
|
||||
/// image, and
|
||||
///
|
||||
/// - each signaled by some [`vkQueueSubmit`] queue submission that draws to
|
||||
/// this image, when the submission finishes execution.
|
||||
///
|
||||
/// This vector accumulates one semaphore per submission that writes to this
|
||||
/// image. This is awkward, but hard to avoid: [`vkQueuePresentKHR`]
|
||||
/// requires a semaphore to order it with respect to drawing commands, and
|
||||
/// we can't attach new completion semaphores to a command submission after
|
||||
/// it's been submitted. This means that, at submission time, we must create
|
||||
/// the semaphore we might need if the caller's next action is to enqueue a
|
||||
/// presentation of this image.
|
||||
///
|
||||
/// An alternative strategy would be for presentation to enqueue an empty
|
||||
/// submit, ordered relative to other submits in the usual way, and
|
||||
/// signaling a single presentation semaphore. But we suspect that submits
|
||||
/// are usually expensive enough, and semaphores usually cheap enough, that
|
||||
/// performance-sensitive users will avoid making many submits, so that the
|
||||
/// cost of accumulated semaphores will usually be less than the cost of an
|
||||
/// additional submit.
|
||||
///
|
||||
/// Only the first [`present_index`] semaphores in the vector are actually
|
||||
/// going to be signalled by submitted commands, and need to be waited for
|
||||
/// by the next present call. Any semaphores beyond that index were created
|
||||
/// for prior presents and are simply being retained for recycling.
|
||||
///
|
||||
/// [`present_index`]: SwapchainPresentSemaphores::present_index
|
||||
/// [`vkQueuePresentKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueuePresentKHR
|
||||
/// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit
|
||||
present: Vec<vk::Semaphore>,
|
||||
|
||||
/// The number of semaphores in [`present`] to be signalled for this submission.
|
||||
///
|
||||
/// [`present`]: SwapchainPresentSemaphores::present
|
||||
present_index: usize,
|
||||
|
||||
/// Which image this semaphore set is used for.
|
||||
frame_index: usize,
|
||||
}
|
||||
|
||||
impl SwapchainPresentSemaphores {
|
||||
pub fn new(frame_index: usize) -> Self {
|
||||
Self {
|
||||
present: Vec::new(),
|
||||
present_index: 0,
|
||||
frame_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the semaphore that the next submission that writes to this image should
|
||||
/// signal when it's done.
|
||||
///
|
||||
/// See [`SwapchainPresentSemaphores::present`] for details.
|
||||
fn get_submit_signal_semaphore(
|
||||
&mut self,
|
||||
device: &DeviceShared,
|
||||
) -> Result<vk::Semaphore, crate::DeviceError> {
|
||||
// Try to recycle a semaphore we created for a previous presentation.
|
||||
let sem = match self.present.get(self.present_index) {
|
||||
Some(sem) => *sem,
|
||||
None => {
|
||||
let sem = device.new_binary_semaphore(&format!(
|
||||
"SwapchainImageSemaphore: Image {} present semaphore {}",
|
||||
self.frame_index, self.present_index
|
||||
))?;
|
||||
self.present.push(sem);
|
||||
sem
|
||||
}
|
||||
};
|
||||
|
||||
self.present_index += 1;
|
||||
|
||||
Ok(sem)
|
||||
}
|
||||
|
||||
/// Indicates the cpu-side usage of this semaphore has finished for the frame,
|
||||
/// so reset internal state to be ready for the next frame.
|
||||
fn end_semaphore_usage(&mut self) {
|
||||
// Reset the index to 0, so that the next time we get a semaphore, we
|
||||
// start from the beginning of the list.
|
||||
self.present_index = 0;
|
||||
}
|
||||
|
||||
/// Return the semaphores that a presentation of this image should wait on.
|
||||
///
|
||||
/// Return a slice of semaphores that the call to [`vkQueueSubmit`] that
|
||||
/// ends this image's acquisition should wait for. See
|
||||
/// [`SwapchainPresentSemaphores::present`] for details.
|
||||
///
|
||||
/// Reset `self` to be ready for the next acquisition cycle.
|
||||
///
|
||||
/// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit
|
||||
fn get_present_wait_semaphores(&mut self) -> Vec<vk::Semaphore> {
|
||||
self.present[0..self.present_index].to_vec()
|
||||
}
|
||||
|
||||
unsafe fn destroy(&self, device: &ash::Device) {
|
||||
unsafe {
|
||||
for sem in &self.present {
|
||||
device.destroy_semaphore(*sem, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Swapchain {
|
||||
raw: vk::SwapchainKHR,
|
||||
functor: khr::swapchain::Device,
|
||||
device: Arc<DeviceShared>,
|
||||
images: Vec<vk::Image>,
|
||||
/// Fence used to wait on the acquired image.
|
||||
fence: vk::Fence,
|
||||
config: crate::SurfaceConfiguration,
|
||||
|
||||
/// Semaphores used between image acquisition and the first submission
|
||||
/// that uses that image. This is indexed using [`next_acquire_index`].
|
||||
///
|
||||
/// Because we need to provide this to [`vkAcquireNextImageKHR`], we haven't
|
||||
/// received the swapchain image index for the frame yet, so we cannot use
|
||||
/// that to index it.
|
||||
///
|
||||
/// Before we pass this to [`vkAcquireNextImageKHR`], we ensure that we wait on
|
||||
/// the submission indicated by [`previously_used_submission_index`]. This enusres
|
||||
/// the semaphore is no longer in use before we use it.
|
||||
///
|
||||
/// [`next_acquire_index`]: Swapchain::next_acquire_index
|
||||
/// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
|
||||
/// [`previously_used_submission_index`]: SwapchainAcquireSemaphore::previously_used_submission_index
|
||||
acquire_semaphores: Vec<Arc<Mutex<SwapchainAcquireSemaphore>>>,
|
||||
/// The index of the next acquire semaphore to use.
|
||||
///
|
||||
/// This is incremented each time we acquire a new image, and wraps around
|
||||
/// to 0 when it reaches the end of [`acquire_semaphores`].
|
||||
///
|
||||
/// [`acquire_semaphores`]: Swapchain::acquire_semaphores
|
||||
next_acquire_index: usize,
|
||||
|
||||
/// Semaphore sets used between all submissions that write to an image and
|
||||
/// the presentation of that image.
|
||||
///
|
||||
/// This is indexed by the swapchain image index returned by
|
||||
/// [`vkAcquireNextImageKHR`].
|
||||
///
|
||||
/// We know it is safe to use these semaphores because use them
|
||||
/// _after_ the acquire semaphore. Because the acquire semaphore
|
||||
/// has been signaled, the previous presentation using that image
|
||||
/// is known-finished, so this semaphore is no longer in use.
|
||||
///
|
||||
/// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
|
||||
present_semaphores: Vec<Arc<Mutex<SwapchainPresentSemaphores>>>,
|
||||
|
||||
/// The present timing information which will be set in the next call to [`present()`](crate::Queue::present()).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This must only be set if [`wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING`] is enabled, and
|
||||
/// so the VK_GOOGLE_display_timing extension is present.
|
||||
next_present_time: Option<vk::PresentTimeGOOGLE>,
|
||||
}
|
||||
|
||||
impl Swapchain {
|
||||
/// Mark the current frame finished, advancing to the next acquire semaphore.
|
||||
fn advance_acquire_semaphore(&mut self) {
|
||||
let semaphore_count = self.acquire_semaphores.len();
|
||||
self.next_acquire_index = (self.next_acquire_index + 1) % semaphore_count;
|
||||
}
|
||||
|
||||
/// Get the next acquire semaphore that should be used with this swapchain.
|
||||
fn get_acquire_semaphore(&self) -> Arc<Mutex<SwapchainAcquireSemaphore>> {
|
||||
self.acquire_semaphores[self.next_acquire_index].clone()
|
||||
}
|
||||
|
||||
/// Get the set of present semaphores that should be used with the given image index.
|
||||
fn get_present_semaphores(&self, index: u32) -> Arc<Mutex<SwapchainPresentSemaphores>> {
|
||||
self.present_semaphores[index as usize].clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Surface {
|
||||
raw: vk::SurfaceKHR,
|
||||
functor: khr::surface::Instance,
|
||||
instance: Arc<InstanceShared>,
|
||||
swapchain: RwLock<Option<Swapchain>>,
|
||||
inner: ManuallyDrop<Box<dyn swapchain::Surface>>,
|
||||
swapchain: RwLock<Option<Box<dyn swapchain::Swapchain>>>,
|
||||
}
|
||||
|
||||
impl Surface {
|
||||
pub unsafe fn raw_handle(&self) -> vk::SurfaceKHR {
|
||||
self.raw
|
||||
/// Returns the raw Vulkan surface handle.
|
||||
///
|
||||
/// Returns `None` if the surface is a DXGI surface.
|
||||
pub unsafe fn 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.
|
||||
pub fn raw_swapchain(&self) -> Option<vk::SwapchainKHR> {
|
||||
/// Returns [`None`] if the surface is not configured or if the swapchain
|
||||
/// is a DXGI swapchain.
|
||||
pub fn raw_native_swapchain(&self) -> Option<vk::SwapchainKHR> {
|
||||
let read = self.swapchain.read();
|
||||
read.as_ref().map(|it| it.raw)
|
||||
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,
|
||||
@ -491,28 +235,20 @@ impl Surface {
|
||||
/// # 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]
|
||||
pub fn set_next_present_time(&self, present_timing: vk::PresentTimeGOOGLE) {
|
||||
let mut swapchain = self.swapchain.write();
|
||||
let swapchain = swapchain
|
||||
swapchain
|
||||
.as_mut()
|
||||
.expect("Surface should have been configured");
|
||||
let features = wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING;
|
||||
if swapchain.device.features.contains(features) {
|
||||
swapchain.next_present_time = Some(present_timing);
|
||||
} else {
|
||||
// Ideally we'd use something like `device.required_features` here, but that's in `wgpu-core`, which we are a dependency of
|
||||
panic!(
|
||||
concat!(
|
||||
"Tried to set display timing properties ",
|
||||
"without the corresponding feature ({:?}) enabled."
|
||||
),
|
||||
features
|
||||
);
|
||||
}
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -520,8 +256,7 @@ impl Surface {
|
||||
pub struct SurfaceTexture {
|
||||
index: u32,
|
||||
texture: Texture,
|
||||
acquire_semaphores: Arc<Mutex<SwapchainAcquireSemaphore>>,
|
||||
present_semaphores: Arc<Mutex<SwapchainPresentSemaphores>>,
|
||||
metadata: Box<dyn swapchain::SurfaceTextureMetadata>,
|
||||
}
|
||||
|
||||
impl crate::DynSurfaceTexture for SurfaceTexture {}
|
||||
@ -854,7 +589,6 @@ impl RelaySemaphores {
|
||||
|
||||
pub struct Queue {
|
||||
raw: vk::Queue,
|
||||
swapchain_fn: khr::swapchain::Device,
|
||||
device: Arc<DeviceShared>,
|
||||
family_index: u32,
|
||||
relay_semaphores: Mutex<RelaySemaphores>,
|
||||
@ -1472,56 +1206,43 @@ impl crate::Queue for Queue {
|
||||
) -> 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 = SemaphoreList::default();
|
||||
let mut wait_semaphores = SemaphoreList::new(SemaphoreListMode::Wait);
|
||||
let mut signal_semaphores = SemaphoreList::new(SemaphoreListMode::Signal);
|
||||
|
||||
// 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!(
|
||||
{
|
||||
let mut check = HashSet::with_capacity(surface_textures.len());
|
||||
// We compare the Arcs by pointer, as Eq isn't well defined for SurfaceSemaphores.
|
||||
// We compare the Box by pointer, as Eq isn't well defined for SurfaceSemaphores.
|
||||
for st in surface_textures {
|
||||
check.insert(Arc::as_ptr(&st.acquire_semaphores) as usize);
|
||||
check.insert(Arc::as_ptr(&st.present_semaphores) as usize);
|
||||
let ptr: *const () = <*const _>::cast(&*st.metadata);
|
||||
check.insert(ptr as usize);
|
||||
}
|
||||
check.len() == surface_textures.len() * 2
|
||||
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| {
|
||||
let acquire = st
|
||||
.acquire_semaphores
|
||||
.try_lock()
|
||||
.expect("Failed to lock surface acquire semaphore");
|
||||
let present = st
|
||||
.present_semaphores
|
||||
.try_lock()
|
||||
.expect("Failed to lock surface present semaphore");
|
||||
|
||||
(acquire, present)
|
||||
})
|
||||
.map(|st| st.metadata.get_semaphore_guard())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (mut acquire_semaphore, mut present_semaphores) in locked_swapchain_semaphores {
|
||||
acquire_semaphore.set_used_fence_value(signal_value);
|
||||
for mut 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.
|
||||
if let Some(sem) = acquire_semaphore.get_acquire_wait_semaphore() {
|
||||
wait_stage_masks.push(vk::PipelineStageFlags::TOP_OF_PIPE);
|
||||
wait_semaphores.push(sem);
|
||||
if let 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 = present_semaphores.get_submit_signal_semaphore(&self.device)?;
|
||||
signal_semaphores.push_binary(signal_semaphore);
|
||||
let signal_semaphore = semaphores.get_submit_signal_semaphore(&self.device)?;
|
||||
signal_semaphores.push_signal(signal_semaphore);
|
||||
}
|
||||
|
||||
let mut guard = self.signal_semaphores.lock();
|
||||
@ -1534,17 +1255,19 @@ impl crate::Queue for Queue {
|
||||
let semaphore_state = self.relay_semaphores.lock().advance(&self.device)?;
|
||||
|
||||
if let Some(sem) = semaphore_state.wait {
|
||||
wait_stage_masks.push(vk::PipelineStageFlags::TOP_OF_PIPE);
|
||||
wait_semaphores.push(sem);
|
||||
wait_semaphores.push_wait(
|
||||
SemaphoreType::Binary(sem),
|
||||
vk::PipelineStageFlags::TOP_OF_PIPE,
|
||||
);
|
||||
}
|
||||
|
||||
signal_semaphores.push_binary(semaphore_state.signal);
|
||||
signal_semaphores.push_signal(SemaphoreType::Binary(semaphore_state.signal));
|
||||
|
||||
// We need to signal our wgpu::Fence if we have one, this adds it to the signal list.
|
||||
signal_fence.maintain(&self.device.raw)?;
|
||||
match *signal_fence {
|
||||
Fence::TimelineSemaphore(raw) => {
|
||||
signal_semaphores.push_timeline(raw, signal_value);
|
||||
signal_semaphores.push_signal(SemaphoreType::Timeline(raw, signal_value));
|
||||
}
|
||||
Fence::FencePool {
|
||||
ref mut active,
|
||||
@ -1570,13 +1293,13 @@ impl crate::Queue for Queue {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut vk_info = vk::SubmitInfo::default().command_buffers(&vk_cmd_buffers);
|
||||
|
||||
vk_info = vk_info
|
||||
.wait_semaphores(&wait_semaphores)
|
||||
.wait_dst_stage_mask(&wait_stage_masks);
|
||||
|
||||
let mut vk_timeline_info = mem::MaybeUninit::uninit();
|
||||
vk_info = signal_semaphores.add_to_submit(vk_info, &mut vk_timeline_info);
|
||||
vk_info = SemaphoreList::add_to_submit(
|
||||
&mut wait_semaphores,
|
||||
&mut signal_semaphores,
|
||||
vk_info,
|
||||
&mut vk_timeline_info,
|
||||
);
|
||||
|
||||
profiling::scope!("vkQueueSubmit");
|
||||
unsafe {
|
||||
@ -1594,68 +1317,8 @@ impl crate::Queue for Queue {
|
||||
texture: SurfaceTexture,
|
||||
) -> Result<(), crate::SurfaceError> {
|
||||
let mut swapchain = surface.swapchain.write();
|
||||
let ssc = swapchain.as_mut().unwrap();
|
||||
let mut acquire_semaphore = texture.acquire_semaphores.lock();
|
||||
let mut present_semaphores = texture.present_semaphores.lock();
|
||||
|
||||
let wait_semaphores = present_semaphores.get_present_wait_semaphores();
|
||||
|
||||
// Reset the acquire and present semaphores internal state
|
||||
// to be ready for the next frame.
|
||||
//
|
||||
// We do this before the actual call to present to ensure that
|
||||
// even if this method errors and early outs, we have reset
|
||||
// the state for next frame.
|
||||
acquire_semaphore.end_semaphore_usage();
|
||||
present_semaphores.end_semaphore_usage();
|
||||
|
||||
drop(acquire_semaphore);
|
||||
|
||||
let swapchains = [ssc.raw];
|
||||
let image_indices = [texture.index];
|
||||
let vk_info = vk::PresentInfoKHR::default()
|
||||
.swapchains(&swapchains)
|
||||
.image_indices(&image_indices)
|
||||
.wait_semaphores(&wait_semaphores);
|
||||
|
||||
let mut display_timing;
|
||||
let present_times;
|
||||
let vk_info = if let Some(present_time) = ssc.next_present_time.take() {
|
||||
debug_assert!(
|
||||
ssc.device
|
||||
.features
|
||||
.contains(wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING),
|
||||
"`next_present_time` should only be set if `VULKAN_GOOGLE_DISPLAY_TIMING` is enabled"
|
||||
);
|
||||
present_times = [present_time];
|
||||
display_timing = vk::PresentTimesInfoGOOGLE::default().times(&present_times);
|
||||
// SAFETY: We know that VK_GOOGLE_display_timing is present because of the safety contract on `next_present_time`.
|
||||
vk_info.push_next(&mut display_timing)
|
||||
} else {
|
||||
vk_info
|
||||
};
|
||||
|
||||
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,
|
||||
// We don't use VK_EXT_full_screen_exclusive
|
||||
// VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
|
||||
_ => map_host_device_oom_and_lost_err(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 { swapchain.as_mut().unwrap().present(self, texture) }
|
||||
}
|
||||
|
||||
unsafe fn get_timestamp_period(&self) -> f32 {
|
||||
@ -1671,9 +1334,9 @@ impl Queue {
|
||||
pub fn add_signal_semaphore(&self, semaphore: vk::Semaphore, semaphore_value: Option<u64>) {
|
||||
let mut guard = self.signal_semaphores.lock();
|
||||
if let Some(value) = semaphore_value {
|
||||
guard.push_timeline(semaphore, value);
|
||||
guard.push_signal(SemaphoreType::Timeline(semaphore, value));
|
||||
} else {
|
||||
guard.push_binary(semaphore);
|
||||
guard.push_signal(SemaphoreType::Binary(semaphore));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,10 +4,17 @@ use alloc::vec::Vec;
|
||||
use ash::vk;
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
/// A list of Vulkan semaphores to signal.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SemaphoreListMode {
|
||||
Wait,
|
||||
Signal,
|
||||
}
|
||||
|
||||
/// A list of Vulkan semaphores to wait for or signal.
|
||||
///
|
||||
/// This represents a list of binary or timeline semaphores, together
|
||||
/// with values for the timeline semaphores.
|
||||
/// with values for the timeline semaphores, and stage masks, if these
|
||||
/// are used for waiting.
|
||||
///
|
||||
/// This type ensures that the array of semaphores to be signaled
|
||||
/// stays aligned with the array of values for timeline semaphores
|
||||
@ -16,14 +23,17 @@ use core::mem::MaybeUninit;
|
||||
/// actually have.
|
||||
///
|
||||
/// [`add_to_submit`]: SemaphoreList::add_to_submit
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct SemaphoreList {
|
||||
/// Semaphores to signal.
|
||||
/// Mode of the semaphore list. Used for validation.
|
||||
mode: SemaphoreListMode,
|
||||
|
||||
/// Semaphores to use.
|
||||
///
|
||||
/// This can be a mix of binary and timeline semaphores.
|
||||
semaphores: Vec<vk::Semaphore>,
|
||||
|
||||
/// Values for timeline semaphores.
|
||||
/// Values for the timeline semaphores.
|
||||
///
|
||||
/// If no timeline semaphores are present in [`semaphores`], this
|
||||
/// is empty. If any timeline semaphores are present, then this
|
||||
@ -33,9 +43,23 @@ pub struct SemaphoreList {
|
||||
///
|
||||
/// [`semaphores`]: Self::semaphores
|
||||
values: Vec<u64>,
|
||||
|
||||
/// Stage masks for wait semaphores.
|
||||
///
|
||||
/// This is only used if `mode` is `Wait`.
|
||||
pub stage_masks: Vec<vk::PipelineStageFlags>,
|
||||
}
|
||||
|
||||
impl SemaphoreList {
|
||||
pub fn new(mode: SemaphoreListMode) -> Self {
|
||||
Self {
|
||||
mode,
|
||||
semaphores: Vec::new(),
|
||||
values: Vec::new(),
|
||||
stage_masks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.semaphores.is_empty()
|
||||
}
|
||||
@ -50,43 +74,83 @@ impl SemaphoreList {
|
||||
/// list's values, and add it to `submit_info`s extension chain.
|
||||
///
|
||||
/// Return the revised `submit_info` value.
|
||||
pub fn add_to_submit<'i, 's: 'i>(
|
||||
&'s self,
|
||||
submit_info: vk::SubmitInfo<'i>,
|
||||
timeline_info: &'i mut MaybeUninit<vk::TimelineSemaphoreSubmitInfo<'i>>,
|
||||
) -> vk::SubmitInfo<'i> {
|
||||
self.check();
|
||||
let mut submit_info = submit_info.signal_semaphores(&self.semaphores);
|
||||
if !self.values.is_empty() {
|
||||
let timeline_info = timeline_info.write(
|
||||
vk::TimelineSemaphoreSubmitInfo::default().signal_semaphore_values(&self.values),
|
||||
);
|
||||
pub fn add_to_submit<'info, 'semaphores: 'info>(
|
||||
wait_semaphores: &'semaphores mut Self,
|
||||
signal_semaphores: &'semaphores mut Self,
|
||||
submit_info: vk::SubmitInfo<'info>,
|
||||
timeline_info: &'info mut MaybeUninit<vk::TimelineSemaphoreSubmitInfo<'info>>,
|
||||
) -> vk::SubmitInfo<'info> {
|
||||
wait_semaphores.check();
|
||||
signal_semaphores.check();
|
||||
|
||||
assert!(matches!(wait_semaphores.mode, SemaphoreListMode::Wait));
|
||||
assert!(matches!(signal_semaphores.mode, SemaphoreListMode::Signal));
|
||||
|
||||
let timeline_info = timeline_info.write(vk::TimelineSemaphoreSubmitInfo::default());
|
||||
|
||||
let mut uses_timeline = false;
|
||||
|
||||
if !wait_semaphores.values.is_empty() {
|
||||
*timeline_info = timeline_info.wait_semaphore_values(&wait_semaphores.values);
|
||||
uses_timeline = true;
|
||||
}
|
||||
|
||||
if !signal_semaphores.values.is_empty() {
|
||||
*timeline_info = timeline_info.signal_semaphore_values(&signal_semaphores.values);
|
||||
uses_timeline = true;
|
||||
}
|
||||
|
||||
let mut submit_info = submit_info
|
||||
.wait_semaphores(&wait_semaphores.semaphores)
|
||||
.wait_dst_stage_mask(&wait_semaphores.stage_masks)
|
||||
.signal_semaphores(&signal_semaphores.semaphores);
|
||||
|
||||
if uses_timeline {
|
||||
submit_info = submit_info.push_next(timeline_info);
|
||||
}
|
||||
|
||||
submit_info
|
||||
}
|
||||
|
||||
/// Add a binary semaphore to this list.
|
||||
pub fn push_binary(&mut self, semaphore: vk::Semaphore) {
|
||||
self.semaphores.push(semaphore);
|
||||
// Push a dummy value if necessary.
|
||||
if !self.values.is_empty() {
|
||||
self.values.push(!0);
|
||||
}
|
||||
self.check();
|
||||
/// Add a semaphore to be signaled. Panics if this is a list of semaphores to wait.
|
||||
pub fn push_signal(&mut self, semaphore: SemaphoreType) {
|
||||
assert!(matches!(self.mode, SemaphoreListMode::Signal));
|
||||
self.push_inner(semaphore);
|
||||
}
|
||||
|
||||
/// Add a timeline semaphore to this list, to be signalled with
|
||||
/// `value`.
|
||||
pub fn push_timeline(&mut self, semaphore: vk::Semaphore, value: u64) {
|
||||
self.pad_values();
|
||||
self.semaphores.push(semaphore);
|
||||
self.values.push(value);
|
||||
/// Add a semaphore to be waited for. Panics if this is a list of semaphores to signal.
|
||||
pub fn push_wait(&mut self, semaphore: SemaphoreType, stage: vk::PipelineStageFlags) {
|
||||
assert!(matches!(self.mode, SemaphoreListMode::Wait));
|
||||
|
||||
self.stage_masks.push(stage);
|
||||
self.push_inner(semaphore);
|
||||
}
|
||||
|
||||
fn push_inner(&mut self, semaphore: SemaphoreType) {
|
||||
match semaphore {
|
||||
SemaphoreType::Binary(semaphore) => {
|
||||
self.semaphores.push(semaphore);
|
||||
// Push a dummy value if necessary.
|
||||
if !self.values.is_empty() {
|
||||
self.values.push(!0);
|
||||
}
|
||||
}
|
||||
SemaphoreType::Timeline(semaphore, value) => {
|
||||
// We may be the first timeline semaphore, ensure that the values
|
||||
// array is filled with dummy values for existing binary semaphores.
|
||||
self.pad_values();
|
||||
self.semaphores.push(semaphore);
|
||||
self.values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
self.check();
|
||||
}
|
||||
|
||||
/// Append `other` to `self`, leaving `other` empty.
|
||||
pub fn append(&mut self, other: &mut Self) {
|
||||
assert_eq!(self.mode, other.mode);
|
||||
|
||||
// If we're about to receive values, ensure we're aligned first.
|
||||
if !other.values.is_empty() {
|
||||
self.pad_values();
|
||||
@ -97,6 +161,7 @@ impl SemaphoreList {
|
||||
if !self.values.is_empty() {
|
||||
self.pad_values();
|
||||
}
|
||||
self.stage_masks.append(&mut other.stage_masks);
|
||||
self.check();
|
||||
}
|
||||
|
||||
@ -111,5 +176,20 @@ impl SemaphoreList {
|
||||
#[track_caller]
|
||||
fn check(&self) {
|
||||
debug_assert!(self.values.is_empty() || self.values.len() == self.semaphores.len());
|
||||
match self.mode {
|
||||
SemaphoreListMode::Wait => {
|
||||
debug_assert!(
|
||||
self.stage_masks.is_empty() || self.stage_masks.len() == self.semaphores.len()
|
||||
);
|
||||
}
|
||||
SemaphoreListMode::Signal => {
|
||||
debug_assert!(self.stage_masks.is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SemaphoreType {
|
||||
Binary(vk::Semaphore),
|
||||
Timeline(vk::Semaphore, u64),
|
||||
}
|
||||
|
||||
107
wgpu-hal/src/vulkan/swapchain/mod.rs
Normal file
107
wgpu-hal/src/vulkan/swapchain/mod.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use alloc::boxed::Box;
|
||||
use core::{any::Any, fmt::Debug, time::Duration};
|
||||
|
||||
use crate::vulkan::{semaphore_list::SemaphoreType, DeviceShared};
|
||||
|
||||
pub(super) use native::*;
|
||||
|
||||
mod native;
|
||||
|
||||
pub(super) trait Surface: Send + Sync + 'static {
|
||||
/// Deletes the surface and associated resources.
|
||||
///
|
||||
/// The surface must not be in use when it is deleted.
|
||||
unsafe fn delete_surface(self: Box<Self>);
|
||||
|
||||
/// Returns the surface capabilities for the given adapter.
|
||||
///
|
||||
/// Returns `None` if the surface is not compatible with the adapter.
|
||||
fn surface_capabilities(&self, adapter: &super::Adapter) -> Option<crate::SurfaceCapabilities>;
|
||||
|
||||
/// Creates a swapchain for the surface with the given configuration.
|
||||
///
|
||||
/// If this is not the first swapchain created for the surface, the old swapchain
|
||||
/// must be provided. [`Swapchain::release_resources`] must be called on the old swapchain
|
||||
/// before calling this method.
|
||||
unsafe fn create_swapchain(
|
||||
&self,
|
||||
device: &super::Device,
|
||||
config: &crate::SurfaceConfiguration,
|
||||
provided_old_swapchain: Option<Box<dyn Swapchain>>,
|
||||
) -> Result<Box<dyn Swapchain>, crate::SurfaceError>;
|
||||
|
||||
/// Allows downcasting to the concrete type.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub(super) trait Swapchain: Send + Sync + 'static {
|
||||
/// Releases all resources associated with the swapchain, without
|
||||
/// destroying the swapchain itself. Must be called before calling
|
||||
/// either [`Surface::create_swapchain`] or [`Swapchain::delete_swapchain`].
|
||||
///
|
||||
/// The swapchain must not be in use when this is called.
|
||||
unsafe fn release_resources(&mut self, device: &super::Device);
|
||||
|
||||
/// Deletes the swapchain.
|
||||
///
|
||||
/// The swapchain must not be in use when it is deleted and
|
||||
/// [`Swapchain::release_resources`] must have been called first.
|
||||
unsafe fn delete_swapchain(self: Box<Self>);
|
||||
|
||||
/// Acquires the next available surface texture for rendering.
|
||||
///
|
||||
/// `timeout` specifies the maximum time to wait for an image to become available.
|
||||
/// If `None` is specified, this function will wait indefinitely.
|
||||
///
|
||||
/// Returns `Ok(None)` if the timeout elapsed before an image became available.
|
||||
unsafe fn acquire(
|
||||
&mut self,
|
||||
timeout: Option<Duration>,
|
||||
fence: &super::Fence,
|
||||
) -> Result<Option<crate::AcquiredSurfaceTexture<crate::api::Vulkan>>, crate::SurfaceError>;
|
||||
|
||||
/// Tries to discard the acquired texture without presenting it.
|
||||
///
|
||||
/// In practice, this doesn't really work in the current implementations.
|
||||
unsafe fn discard_texture(
|
||||
&mut self,
|
||||
texture: super::SurfaceTexture,
|
||||
) -> Result<(), crate::SurfaceError>;
|
||||
|
||||
/// Presents the given surface texture using the queue.
|
||||
unsafe fn present(
|
||||
&mut self,
|
||||
queue: &super::Queue,
|
||||
texture: crate::vulkan::SurfaceTexture,
|
||||
) -> Result<(), crate::SurfaceError>;
|
||||
|
||||
/// Allows downcasting to the concrete type.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Allows downcasting to the concrete type mutably.
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
/// Swapchain specific metadata associated with a surface texture.
|
||||
pub(super) trait SurfaceTextureMetadata: Debug + Send + Sync + 'static {
|
||||
/// Returns a guard which can yield the semaphores needed for submission using this swapchain texture.
|
||||
fn get_semaphore_guard(&self) -> Box<dyn SwapchainSubmissionSemaphoreGuard + '_>;
|
||||
|
||||
/// Allows downcasting to the concrete type.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
/// Guard type for managing swapchain submission semaphores.
|
||||
pub(super) trait SwapchainSubmissionSemaphoreGuard {
|
||||
/// Sets the Fence value for this submission.
|
||||
fn set_used_fence_value(&mut self, value: u64);
|
||||
|
||||
/// Gets semaphores to wait on before doing GPU work for this swapchain texture.
|
||||
fn get_acquire_wait_semaphore(&mut self) -> Option<SemaphoreType>;
|
||||
|
||||
/// Gets the semaphore to signal when GPU work for this swapchain texture is complete.
|
||||
fn get_submit_signal_semaphore(
|
||||
&mut self,
|
||||
device: &DeviceShared,
|
||||
) -> Result<SemaphoreType, crate::DeviceError>;
|
||||
}
|
||||
904
wgpu-hal/src/vulkan/swapchain/native.rs
Normal file
904
wgpu-hal/src/vulkan/swapchain/native.rs
Normal file
@ -0,0 +1,904 @@
|
||||
//! Vulkan Surface and Swapchain implementation using native Vulkan surfaces.
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use core::any::Any;
|
||||
|
||||
use ash::{khr, vk};
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
|
||||
use crate::vulkan::{
|
||||
conv, map_host_device_oom_and_lost_err,
|
||||
semaphore_list::SemaphoreType,
|
||||
swapchain::{Surface, SurfaceTextureMetadata, Swapchain, SwapchainSubmissionSemaphoreGuard},
|
||||
DeviceShared, InstanceShared,
|
||||
};
|
||||
|
||||
pub(crate) struct NativeSurface {
|
||||
raw: vk::SurfaceKHR,
|
||||
functor: khr::surface::Instance,
|
||||
instance: Arc<InstanceShared>,
|
||||
}
|
||||
|
||||
impl NativeSurface {
|
||||
pub fn from_vk_surface_khr(instance: &crate::vulkan::Instance, raw: vk::SurfaceKHR) -> Self {
|
||||
let functor = khr::surface::Instance::new(&instance.shared.entry, &instance.shared.raw);
|
||||
Self {
|
||||
raw,
|
||||
functor,
|
||||
instance: Arc::clone(&instance.shared),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raw(&self) -> vk::SurfaceKHR {
|
||||
self.raw
|
||||
}
|
||||
}
|
||||
|
||||
impl Surface for NativeSurface {
|
||||
unsafe fn delete_surface(self: Box<Self>) {
|
||||
unsafe {
|
||||
self.functor.destroy_surface(self.raw, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn surface_capabilities(
|
||||
&self,
|
||||
adapter: &crate::vulkan::Adapter,
|
||||
) -> Option<crate::SurfaceCapabilities> {
|
||||
if !adapter.private_caps.can_present {
|
||||
return None;
|
||||
}
|
||||
let queue_family_index = 0; //TODO
|
||||
{
|
||||
profiling::scope!("vkGetPhysicalDeviceSurfaceSupportKHR");
|
||||
match unsafe {
|
||||
self.functor.get_physical_device_surface_support(
|
||||
adapter.raw,
|
||||
queue_family_index,
|
||||
self.raw,
|
||||
)
|
||||
} {
|
||||
Ok(true) => (),
|
||||
Ok(false) => return None,
|
||||
Err(e) => {
|
||||
log::error!("get_physical_device_surface_support: {e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let caps = {
|
||||
profiling::scope!("vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
|
||||
match unsafe {
|
||||
self.functor
|
||||
.get_physical_device_surface_capabilities(adapter.raw, self.raw)
|
||||
} {
|
||||
Ok(caps) => caps,
|
||||
Err(e) => {
|
||||
log::error!("get_physical_device_surface_capabilities: {e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If image count is 0, the support number of images is unlimited.
|
||||
let max_image_count = if caps.max_image_count == 0 {
|
||||
!0
|
||||
} else {
|
||||
caps.max_image_count
|
||||
};
|
||||
|
||||
// `0xFFFFFFFF` indicates that the extent depends on the created swapchain.
|
||||
let current_extent = if caps.current_extent.width != !0 && caps.current_extent.height != !0
|
||||
{
|
||||
Some(wgt::Extent3d {
|
||||
width: caps.current_extent.width,
|
||||
height: caps.current_extent.height,
|
||||
depth_or_array_layers: 1,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let raw_present_modes = {
|
||||
profiling::scope!("vkGetPhysicalDeviceSurfacePresentModesKHR");
|
||||
match unsafe {
|
||||
self.functor
|
||||
.get_physical_device_surface_present_modes(adapter.raw, self.raw)
|
||||
} {
|
||||
Ok(present_modes) => present_modes,
|
||||
Err(e) => {
|
||||
log::error!("get_physical_device_surface_present_modes: {e}");
|
||||
// Per definition of `SurfaceCapabilities`, there must be at least one present mode.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let raw_surface_formats = {
|
||||
profiling::scope!("vkGetPhysicalDeviceSurfaceFormatsKHR");
|
||||
match unsafe {
|
||||
self.functor
|
||||
.get_physical_device_surface_formats(adapter.raw, self.raw)
|
||||
} {
|
||||
Ok(formats) => formats,
|
||||
Err(e) => {
|
||||
log::error!("get_physical_device_surface_formats: {e}");
|
||||
// Per definition of `SurfaceCapabilities`, there must be at least one present format.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let formats = raw_surface_formats
|
||||
.into_iter()
|
||||
.filter_map(conv::map_vk_surface_formats)
|
||||
.collect();
|
||||
Some(crate::SurfaceCapabilities {
|
||||
formats,
|
||||
// TODO: Right now we're always truncating the swap chain
|
||||
// (presumably - we're actually setting the min image count which isn't necessarily the swap chain size)
|
||||
// Instead, we should use extensions when available to wait in present.
|
||||
// See https://github.com/gfx-rs/wgpu/issues/2869
|
||||
maximum_frame_latency: (caps.min_image_count - 1)..=(max_image_count - 1), // Note this can't underflow since both `min_image_count` is at least one and we already patched `max_image_count`.
|
||||
current_extent,
|
||||
usage: conv::map_vk_image_usage(caps.supported_usage_flags),
|
||||
present_modes: raw_present_modes
|
||||
.into_iter()
|
||||
.flat_map(conv::map_vk_present_mode)
|
||||
.collect(),
|
||||
composite_alpha_modes: conv::map_vk_composite_alpha(caps.supported_composite_alpha),
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn create_swapchain(
|
||||
&self,
|
||||
device: &crate::vulkan::Device,
|
||||
config: &crate::SurfaceConfiguration,
|
||||
provided_old_swapchain: Option<Box<dyn Swapchain>>,
|
||||
) -> Result<Box<dyn Swapchain>, crate::SurfaceError> {
|
||||
profiling::scope!("Device::create_swapchain");
|
||||
let functor = khr::swapchain::Device::new(&self.instance.raw, &device.shared.raw);
|
||||
|
||||
let old_swapchain = match provided_old_swapchain {
|
||||
Some(osc) => osc.as_any().downcast_ref::<NativeSwapchain>().unwrap().raw,
|
||||
None => vk::SwapchainKHR::null(),
|
||||
};
|
||||
|
||||
let color_space = if config.format == wgt::TextureFormat::Rgba16Float {
|
||||
// Enable wide color gamut mode
|
||||
// Vulkan swapchain for Android only supports DISPLAY_P3_NONLINEAR_EXT and EXTENDED_SRGB_LINEAR_EXT
|
||||
vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT
|
||||
} else {
|
||||
vk::ColorSpaceKHR::SRGB_NONLINEAR
|
||||
};
|
||||
|
||||
let original_format = device.shared.private_caps.map_texture_format(config.format);
|
||||
let mut raw_flags = vk::SwapchainCreateFlagsKHR::empty();
|
||||
let mut raw_view_formats: Vec<vk::Format> = vec![];
|
||||
if !config.view_formats.is_empty() {
|
||||
raw_flags |= vk::SwapchainCreateFlagsKHR::MUTABLE_FORMAT;
|
||||
raw_view_formats = config
|
||||
.view_formats
|
||||
.iter()
|
||||
.map(|f| device.shared.private_caps.map_texture_format(*f))
|
||||
.collect();
|
||||
raw_view_formats.push(original_format);
|
||||
}
|
||||
|
||||
let mut info = vk::SwapchainCreateInfoKHR::default()
|
||||
.flags(raw_flags)
|
||||
.surface(self.raw)
|
||||
.min_image_count(config.maximum_frame_latency + 1) // TODO: https://github.com/gfx-rs/wgpu/issues/2869
|
||||
.image_format(original_format)
|
||||
.image_color_space(color_space)
|
||||
.image_extent(vk::Extent2D {
|
||||
width: config.extent.width,
|
||||
height: config.extent.height,
|
||||
})
|
||||
.image_array_layers(config.extent.depth_or_array_layers)
|
||||
.image_usage(conv::map_texture_usage(config.usage))
|
||||
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY)
|
||||
.composite_alpha(conv::map_composite_alpha_mode(config.composite_alpha_mode))
|
||||
.present_mode(conv::map_present_mode(config.present_mode))
|
||||
.clipped(true)
|
||||
.old_swapchain(old_swapchain);
|
||||
|
||||
let mut format_list_info = vk::ImageFormatListCreateInfo::default();
|
||||
if !raw_view_formats.is_empty() {
|
||||
format_list_info = format_list_info.view_formats(&raw_view_formats);
|
||||
info = info.push_next(&mut format_list_info);
|
||||
}
|
||||
|
||||
let result = {
|
||||
profiling::scope!("vkCreateSwapchainKHR");
|
||||
unsafe { functor.create_swapchain(&info, None) }
|
||||
};
|
||||
|
||||
// doing this before bailing out with error
|
||||
if old_swapchain != vk::SwapchainKHR::null() {
|
||||
unsafe { functor.destroy_swapchain(old_swapchain, None) }
|
||||
}
|
||||
|
||||
let raw = match result {
|
||||
Ok(swapchain) => swapchain,
|
||||
Err(error) => {
|
||||
return Err(match error {
|
||||
vk::Result::ERROR_SURFACE_LOST_KHR
|
||||
| vk::Result::ERROR_INITIALIZATION_FAILED => crate::SurfaceError::Lost,
|
||||
vk::Result::ERROR_NATIVE_WINDOW_IN_USE_KHR => {
|
||||
crate::SurfaceError::Other("Native window is in use")
|
||||
}
|
||||
// We don't use VK_EXT_image_compression_control
|
||||
// VK_ERROR_COMPRESSION_EXHAUSTED_EXT
|
||||
other => map_host_device_oom_and_lost_err(other).into(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let images = unsafe { functor.get_swapchain_images(raw) }
|
||||
.map_err(crate::vulkan::map_host_device_oom_err)?;
|
||||
|
||||
let fence = unsafe {
|
||||
device
|
||||
.shared
|
||||
.raw
|
||||
.create_fence(&vk::FenceCreateInfo::default(), None)
|
||||
.map_err(crate::vulkan::map_host_device_oom_err)?
|
||||
};
|
||||
|
||||
// NOTE: It's important that we define the same number of acquire/present semaphores
|
||||
// as we will need to index into them with the image index.
|
||||
let acquire_semaphores = (0..images.len())
|
||||
.map(|i| {
|
||||
SwapchainAcquireSemaphore::new(&device.shared, i)
|
||||
.map(Mutex::new)
|
||||
.map(Arc::new)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let present_semaphores = (0..images.len())
|
||||
.map(|i| Arc::new(Mutex::new(SwapchainPresentSemaphores::new(i))))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Box::new(NativeSwapchain {
|
||||
raw,
|
||||
functor,
|
||||
device: Arc::clone(&device.shared),
|
||||
images,
|
||||
fence,
|
||||
config: config.clone(),
|
||||
acquire_semaphores,
|
||||
next_acquire_index: 0,
|
||||
present_semaphores,
|
||||
next_present_time: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct NativeSwapchain {
|
||||
raw: vk::SwapchainKHR,
|
||||
functor: khr::swapchain::Device,
|
||||
device: Arc<DeviceShared>,
|
||||
images: Vec<vk::Image>,
|
||||
/// Fence used to wait on the acquired image.
|
||||
fence: vk::Fence,
|
||||
config: crate::SurfaceConfiguration,
|
||||
|
||||
/// Semaphores used between image acquisition and the first submission
|
||||
/// that uses that image. This is indexed using [`next_acquire_index`].
|
||||
///
|
||||
/// Because we need to provide this to [`vkAcquireNextImageKHR`], we haven't
|
||||
/// received the swapchain image index for the frame yet, so we cannot use
|
||||
/// that to index it.
|
||||
///
|
||||
/// Before we pass this to [`vkAcquireNextImageKHR`], we ensure that we wait on
|
||||
/// the submission indicated by [`previously_used_submission_index`]. This ensures
|
||||
/// the semaphore is no longer in use before we use it.
|
||||
///
|
||||
/// [`next_acquire_index`]: NativeSwapchain::next_acquire_index
|
||||
/// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
|
||||
/// [`previously_used_submission_index`]: SwapchainAcquireSemaphore::previously_used_submission_index
|
||||
acquire_semaphores: Vec<Arc<Mutex<SwapchainAcquireSemaphore>>>,
|
||||
/// The index of the next acquire semaphore to use.
|
||||
///
|
||||
/// This is incremented each time we acquire a new image, and wraps around
|
||||
/// to 0 when it reaches the end of [`acquire_semaphores`].
|
||||
///
|
||||
/// [`acquire_semaphores`]: NativeSwapchain::acquire_semaphores
|
||||
next_acquire_index: usize,
|
||||
|
||||
/// Semaphore sets used between all submissions that write to an image and
|
||||
/// the presentation of that image.
|
||||
///
|
||||
/// This is indexed by the swapchain image index returned by
|
||||
/// [`vkAcquireNextImageKHR`].
|
||||
///
|
||||
/// We know it is safe to use these semaphores because use them
|
||||
/// _after_ the acquire semaphore. Because the acquire semaphore
|
||||
/// has been signaled, the previous presentation using that image
|
||||
/// is known-finished, so this semaphore is no longer in use.
|
||||
///
|
||||
/// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
|
||||
present_semaphores: Vec<Arc<Mutex<SwapchainPresentSemaphores>>>,
|
||||
|
||||
/// The present timing information which will be set in the next call to [`present()`](crate::Queue::present()).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This must only be set if [`wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING`] is enabled, and
|
||||
/// so the VK_GOOGLE_display_timing extension is present.
|
||||
next_present_time: Option<vk::PresentTimeGOOGLE>,
|
||||
}
|
||||
|
||||
impl Swapchain for NativeSwapchain {
|
||||
unsafe fn release_resources(&mut self, device: &crate::vulkan::Device) {
|
||||
profiling::scope!("Swapchain::release_resources");
|
||||
{
|
||||
profiling::scope!("vkDeviceWaitIdle");
|
||||
// We need to also wait until all presentation work is done. Because there is no way to portably wait until
|
||||
// the presentation work is done, we are forced to wait until the device is idle.
|
||||
let _ = unsafe {
|
||||
device
|
||||
.shared
|
||||
.raw
|
||||
.device_wait_idle()
|
||||
.map_err(map_host_device_oom_and_lost_err)
|
||||
};
|
||||
};
|
||||
|
||||
unsafe { device.shared.raw.destroy_fence(self.fence, None) }
|
||||
|
||||
// We cannot take this by value, as the function returns `self`.
|
||||
for semaphore in self.acquire_semaphores.drain(..) {
|
||||
let arc_removed = Arc::into_inner(semaphore).expect(
|
||||
"Trying to destroy a SwapchainAcquireSemaphore that is still in use by a SurfaceTexture",
|
||||
);
|
||||
let mutex_removed = arc_removed.into_inner();
|
||||
|
||||
unsafe { mutex_removed.destroy(&device.shared.raw) };
|
||||
}
|
||||
|
||||
for semaphore in self.present_semaphores.drain(..) {
|
||||
let arc_removed = Arc::into_inner(semaphore).expect(
|
||||
"Trying to destroy a SwapchainPresentSemaphores that is still in use by a SurfaceTexture",
|
||||
);
|
||||
let mutex_removed = arc_removed.into_inner();
|
||||
|
||||
unsafe { mutex_removed.destroy(&device.shared.raw) };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn delete_swapchain(self: Box<Self>) {
|
||||
unsafe { self.functor.destroy_swapchain(self.raw, None) };
|
||||
}
|
||||
|
||||
unsafe fn acquire(
|
||||
&mut self,
|
||||
timeout: Option<core::time::Duration>,
|
||||
fence: &crate::vulkan::Fence,
|
||||
) -> Result<Option<crate::AcquiredSurfaceTexture<crate::api::Vulkan>>, crate::SurfaceError>
|
||||
{
|
||||
let mut timeout_ns = match timeout {
|
||||
Some(duration) => duration.as_nanos() as u64,
|
||||
None => u64::MAX,
|
||||
};
|
||||
|
||||
// AcquireNextImageKHR on Android (prior to Android 11) doesn't support timeouts
|
||||
// and will also log verbose warnings if tying to use a timeout.
|
||||
//
|
||||
// Android 10 implementation for reference:
|
||||
// https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-10.0.0_r13/vulkan/libvulkan/swapchain.cpp#1426
|
||||
// Android 11 implementation for reference:
|
||||
// https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-11.0.0_r45/vulkan/libvulkan/swapchain.cpp#1438
|
||||
//
|
||||
// Android 11 corresponds to an SDK_INT/ro.build.version.sdk of 30
|
||||
if cfg!(target_os = "android") && self.device.instance.android_sdk_version < 30 {
|
||||
timeout_ns = u64::MAX;
|
||||
}
|
||||
|
||||
let acquire_semaphore_arc = self.get_acquire_semaphore();
|
||||
// Nothing should be using this, so we don't block, but panic if we fail to lock.
|
||||
let acquire_semaphore_guard = acquire_semaphore_arc
|
||||
.try_lock()
|
||||
.expect("Failed to lock a SwapchainSemaphores.");
|
||||
|
||||
// Wait for all commands writing to the previously acquired image to
|
||||
// complete.
|
||||
//
|
||||
// Almost all the steps in the usual acquire-draw-present flow are
|
||||
// asynchronous: they get something started on the presentation engine
|
||||
// or the GPU, but on the CPU, control returns immediately. Without some
|
||||
// sort of intervention, the CPU could crank out frames much faster than
|
||||
// the presentation engine can display them.
|
||||
//
|
||||
// This is the intervention: if any submissions drew on this image, and
|
||||
// thus waited for `locked_swapchain_semaphores.acquire`, wait for all
|
||||
// of them to finish, thus ensuring that it's okay to pass `acquire` to
|
||||
// `vkAcquireNextImageKHR` again.
|
||||
self.device.wait_for_fence(
|
||||
fence,
|
||||
acquire_semaphore_guard.previously_used_submission_index,
|
||||
timeout_ns,
|
||||
)?;
|
||||
|
||||
// will block if no image is available
|
||||
let (index, suboptimal) = match unsafe {
|
||||
profiling::scope!("vkAcquireNextImageKHR");
|
||||
self.functor.acquire_next_image(
|
||||
self.raw,
|
||||
timeout_ns,
|
||||
acquire_semaphore_guard.acquire,
|
||||
self.fence,
|
||||
)
|
||||
} {
|
||||
// We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android.
|
||||
// See the comment in `Queue::present`.
|
||||
#[cfg(target_os = "android")]
|
||||
Ok((index, _)) => (index, false),
|
||||
#[cfg(not(target_os = "android"))]
|
||||
Ok(pair) => pair,
|
||||
Err(error) => {
|
||||
return match error {
|
||||
vk::Result::TIMEOUT => Ok(None),
|
||||
vk::Result::NOT_READY | vk::Result::ERROR_OUT_OF_DATE_KHR => {
|
||||
Err(crate::SurfaceError::Outdated)
|
||||
}
|
||||
vk::Result::ERROR_SURFACE_LOST_KHR => Err(crate::SurfaceError::Lost),
|
||||
// We don't use VK_EXT_full_screen_exclusive
|
||||
// VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
|
||||
other => Err(map_host_device_oom_and_lost_err(other).into()),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for the image was acquired to be fully ready to be rendered too.
|
||||
//
|
||||
// This wait is very important on Windows to avoid bad frame pacing on
|
||||
// Windows where the Vulkan driver is using a DXGI swapchain. See
|
||||
// https://github.com/gfx-rs/wgpu/issues/8310 and
|
||||
// https://github.com/gfx-rs/wgpu/issues/8354 for more details.
|
||||
//
|
||||
// On other platforms, this wait may serve to slightly decrease frame
|
||||
// latency, depending on how the platform implements waiting within
|
||||
// acquire.
|
||||
unsafe {
|
||||
self.device
|
||||
.raw
|
||||
.wait_for_fences(&[self.fence], false, timeout_ns)
|
||||
.map_err(map_host_device_oom_and_lost_err)?;
|
||||
|
||||
self.device
|
||||
.raw
|
||||
.reset_fences(&[self.fence])
|
||||
.map_err(map_host_device_oom_and_lost_err)?;
|
||||
}
|
||||
|
||||
drop(acquire_semaphore_guard);
|
||||
// We only advance the surface semaphores if we successfully acquired an image, otherwise
|
||||
// we should try to re-acquire using the same semaphores.
|
||||
self.advance_acquire_semaphore();
|
||||
|
||||
let present_semaphore_arc = self.get_present_semaphores(index);
|
||||
|
||||
// special case for Intel Vulkan returning bizarre values (ugh)
|
||||
if self.device.vendor_id == crate::auxil::db::intel::VENDOR && index > 0x100 {
|
||||
return Err(crate::SurfaceError::Outdated);
|
||||
}
|
||||
|
||||
let identity = self.device.texture_identity_factory.next();
|
||||
|
||||
let texture = crate::vulkan::SurfaceTexture {
|
||||
index,
|
||||
texture: crate::vulkan::Texture {
|
||||
raw: self.images[index as usize],
|
||||
drop_guard: None,
|
||||
block: None,
|
||||
external_memory: None,
|
||||
format: self.config.format,
|
||||
copy_size: crate::CopyExtent {
|
||||
width: self.config.extent.width,
|
||||
height: self.config.extent.height,
|
||||
depth: 1,
|
||||
},
|
||||
identity,
|
||||
},
|
||||
metadata: Box::new(NativeSurfaceTextureMetadata {
|
||||
acquire_semaphores: acquire_semaphore_arc,
|
||||
present_semaphores: present_semaphore_arc,
|
||||
}),
|
||||
};
|
||||
Ok(Some(crate::AcquiredSurfaceTexture {
|
||||
texture,
|
||||
suboptimal,
|
||||
}))
|
||||
}
|
||||
|
||||
unsafe fn discard_texture(
|
||||
&mut self,
|
||||
_texture: crate::vulkan::SurfaceTexture,
|
||||
) -> Result<(), crate::SurfaceError> {
|
||||
// TODO: Current implementation no-ops
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn present(
|
||||
&mut self,
|
||||
queue: &crate::vulkan::Queue,
|
||||
texture: crate::vulkan::SurfaceTexture,
|
||||
) -> Result<(), crate::SurfaceError> {
|
||||
let metadata = texture
|
||||
.metadata
|
||||
.as_any()
|
||||
.downcast_ref::<NativeSurfaceTextureMetadata>()
|
||||
.unwrap();
|
||||
let mut acquire_semaphore = metadata.acquire_semaphores.lock();
|
||||
let mut present_semaphores = metadata.present_semaphores.lock();
|
||||
|
||||
let wait_semaphores = present_semaphores.get_present_wait_semaphores();
|
||||
|
||||
// Reset the acquire and present semaphores internal state
|
||||
// to be ready for the next frame.
|
||||
//
|
||||
// We do this before the actual call to present to ensure that
|
||||
// even if this method errors and early outs, we have reset
|
||||
// the state for next frame.
|
||||
acquire_semaphore.end_semaphore_usage();
|
||||
present_semaphores.end_semaphore_usage();
|
||||
|
||||
drop(acquire_semaphore);
|
||||
|
||||
let swapchains = [self.raw];
|
||||
let image_indices = [texture.index];
|
||||
let vk_info = vk::PresentInfoKHR::default()
|
||||
.swapchains(&swapchains)
|
||||
.image_indices(&image_indices)
|
||||
.wait_semaphores(&wait_semaphores);
|
||||
|
||||
let mut display_timing;
|
||||
let present_times;
|
||||
let vk_info = if let Some(present_time) = self.next_present_time.take() {
|
||||
debug_assert!(
|
||||
self.device
|
||||
.features
|
||||
.contains(wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING),
|
||||
"`next_present_time` should only be set if `VULKAN_GOOGLE_DISPLAY_TIMING` is enabled"
|
||||
);
|
||||
present_times = [present_time];
|
||||
display_timing = vk::PresentTimesInfoGOOGLE::default().times(&present_times);
|
||||
// SAFETY: We know that VK_GOOGLE_display_timing is present because of the safety contract on `next_present_time`.
|
||||
vk_info.push_next(&mut display_timing)
|
||||
} else {
|
||||
vk_info
|
||||
};
|
||||
|
||||
let suboptimal = {
|
||||
profiling::scope!("vkQueuePresentKHR");
|
||||
unsafe { self.functor.queue_present(queue.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,
|
||||
// We don't use VK_EXT_full_screen_exclusive
|
||||
// VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
|
||||
_ => map_host_device_oom_and_lost_err(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(())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeSwapchain {
|
||||
pub(crate) fn as_raw(&self) -> vk::SwapchainKHR {
|
||||
self.raw
|
||||
}
|
||||
|
||||
pub fn set_next_present_time(&mut self, present_timing: vk::PresentTimeGOOGLE) {
|
||||
let features = wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING;
|
||||
if self.device.features.contains(features) {
|
||||
self.next_present_time = Some(present_timing);
|
||||
} else {
|
||||
// Ideally we'd use something like `device.required_features` here, but that's in `wgpu-core`, which we are a dependency of
|
||||
panic!(
|
||||
concat!(
|
||||
"Tried to set display timing properties ",
|
||||
"without the corresponding feature ({:?}) enabled."
|
||||
),
|
||||
features
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark the current frame finished, advancing to the next acquire semaphore.
|
||||
fn advance_acquire_semaphore(&mut self) {
|
||||
let semaphore_count = self.acquire_semaphores.len();
|
||||
self.next_acquire_index = (self.next_acquire_index + 1) % semaphore_count;
|
||||
}
|
||||
|
||||
/// Get the next acquire semaphore that should be used with this swapchain.
|
||||
fn get_acquire_semaphore(&self) -> Arc<Mutex<SwapchainAcquireSemaphore>> {
|
||||
self.acquire_semaphores[self.next_acquire_index].clone()
|
||||
}
|
||||
|
||||
/// Get the set of present semaphores that should be used with the given image index.
|
||||
fn get_present_semaphores(&self, index: u32) -> Arc<Mutex<SwapchainPresentSemaphores>> {
|
||||
self.present_semaphores[index as usize].clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Semaphore used to acquire a swapchain image.
|
||||
#[derive(Debug)]
|
||||
struct SwapchainAcquireSemaphore {
|
||||
/// A semaphore that is signaled when this image is safe for us to modify.
|
||||
///
|
||||
/// When [`vkAcquireNextImageKHR`] returns the index of the next swapchain
|
||||
/// image that we should use, that image may actually still be in use by the
|
||||
/// presentation engine, and is not yet safe to modify. However, that
|
||||
/// function does accept a semaphore that it will signal when the image is
|
||||
/// indeed safe to begin messing with.
|
||||
///
|
||||
/// This semaphore is:
|
||||
///
|
||||
/// - waited for by the first queue submission to operate on this image
|
||||
/// since it was acquired, and
|
||||
///
|
||||
/// - signaled by [`vkAcquireNextImageKHR`] when the acquired image is ready
|
||||
/// for us to use.
|
||||
///
|
||||
/// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
|
||||
acquire: vk::Semaphore,
|
||||
|
||||
/// True if the next command submission operating on this image should wait
|
||||
/// for [`acquire`].
|
||||
///
|
||||
/// We must wait for `acquire` before drawing to this swapchain image, but
|
||||
/// because `wgpu-hal` queue submissions are always strongly ordered, only
|
||||
/// the first submission that works with a swapchain image actually needs to
|
||||
/// wait. We set this flag when this image is acquired, and clear it the
|
||||
/// first time it's passed to [`Queue::submit`] as a surface texture.
|
||||
///
|
||||
/// Additionally, semaphores can only be waited on once, so we need to ensure
|
||||
/// that we only actually pass this semaphore to the first submission that
|
||||
/// uses that image.
|
||||
///
|
||||
/// [`acquire`]: SwapchainAcquireSemaphore::acquire
|
||||
/// [`Queue::submit`]: crate::Queue::submit
|
||||
should_wait_for_acquire: bool,
|
||||
|
||||
/// The fence value of the last command submission that wrote to this image.
|
||||
///
|
||||
/// The next time we try to acquire this image, we'll block until
|
||||
/// this submission finishes, proving that [`acquire`] is ready to
|
||||
/// pass to `vkAcquireNextImageKHR` again.
|
||||
///
|
||||
/// [`acquire`]: SwapchainAcquireSemaphore::acquire
|
||||
previously_used_submission_index: crate::FenceValue,
|
||||
}
|
||||
|
||||
impl SwapchainAcquireSemaphore {
|
||||
fn new(device: &DeviceShared, index: usize) -> Result<Self, crate::DeviceError> {
|
||||
Ok(Self {
|
||||
acquire: device
|
||||
.new_binary_semaphore(&format!("SwapchainImageSemaphore: Index {index} acquire"))?,
|
||||
should_wait_for_acquire: true,
|
||||
previously_used_submission_index: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the fence value which the next acquire will wait for. This prevents
|
||||
/// the semaphore from being used while the previous submission is still in flight.
|
||||
fn set_used_fence_value(&mut self, value: crate::FenceValue) {
|
||||
self.previously_used_submission_index = value;
|
||||
}
|
||||
|
||||
/// Return the semaphore that commands drawing to this image should wait for, if any.
|
||||
///
|
||||
/// This only returns `Some` once per acquisition; see
|
||||
/// [`SwapchainAcquireSemaphore::should_wait_for_acquire`] for details.
|
||||
fn get_acquire_wait_semaphore(&mut self) -> Option<vk::Semaphore> {
|
||||
if self.should_wait_for_acquire {
|
||||
self.should_wait_for_acquire = false;
|
||||
Some(self.acquire)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates the cpu-side usage of this semaphore has finished for the frame,
|
||||
/// so reset internal state to be ready for the next frame.
|
||||
fn end_semaphore_usage(&mut self) {
|
||||
// Reset the acquire semaphore, so that the next time we acquire this
|
||||
// image, we can wait for it again.
|
||||
self.should_wait_for_acquire = true;
|
||||
}
|
||||
|
||||
unsafe fn destroy(&self, device: &ash::Device) {
|
||||
unsafe {
|
||||
device.destroy_semaphore(self.acquire, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SwapchainPresentSemaphores {
|
||||
/// A pool of semaphores for ordering presentation after drawing.
|
||||
///
|
||||
/// The first [`present_index`] semaphores in this vector are:
|
||||
///
|
||||
/// - all waited on by the call to [`vkQueuePresentKHR`] that presents this
|
||||
/// image, and
|
||||
///
|
||||
/// - each signaled by some [`vkQueueSubmit`] queue submission that draws to
|
||||
/// this image, when the submission finishes execution.
|
||||
///
|
||||
/// This vector accumulates one semaphore per submission that writes to this
|
||||
/// image. This is awkward, but hard to avoid: [`vkQueuePresentKHR`]
|
||||
/// requires a semaphore to order it with respect to drawing commands, and
|
||||
/// we can't attach new completion semaphores to a command submission after
|
||||
/// it's been submitted. This means that, at submission time, we must create
|
||||
/// the semaphore we might need if the caller's next action is to enqueue a
|
||||
/// presentation of this image.
|
||||
///
|
||||
/// An alternative strategy would be for presentation to enqueue an empty
|
||||
/// submit, ordered relative to other submits in the usual way, and
|
||||
/// signaling a single presentation semaphore. But we suspect that submits
|
||||
/// are usually expensive enough, and semaphores usually cheap enough, that
|
||||
/// performance-sensitive users will avoid making many submits, so that the
|
||||
/// cost of accumulated semaphores will usually be less than the cost of an
|
||||
/// additional submit.
|
||||
///
|
||||
/// Only the first [`present_index`] semaphores in the vector are actually
|
||||
/// going to be signalled by submitted commands, and need to be waited for
|
||||
/// by the next present call. Any semaphores beyond that index were created
|
||||
/// for prior presents and are simply being retained for recycling.
|
||||
///
|
||||
/// [`present_index`]: SwapchainPresentSemaphores::present_index
|
||||
/// [`vkQueuePresentKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueuePresentKHR
|
||||
/// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit
|
||||
present: Vec<vk::Semaphore>,
|
||||
|
||||
/// The number of semaphores in [`present`] to be signalled for this submission.
|
||||
///
|
||||
/// [`present`]: SwapchainPresentSemaphores::present
|
||||
present_index: usize,
|
||||
|
||||
/// Which image this semaphore set is used for.
|
||||
frame_index: usize,
|
||||
}
|
||||
|
||||
impl SwapchainPresentSemaphores {
|
||||
pub fn new(frame_index: usize) -> Self {
|
||||
Self {
|
||||
present: Vec::new(),
|
||||
present_index: 0,
|
||||
frame_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the semaphore that the next submission that writes to this image should
|
||||
/// signal when it's done.
|
||||
///
|
||||
/// See [`SwapchainPresentSemaphores::present`] for details.
|
||||
fn get_submit_signal_semaphore(
|
||||
&mut self,
|
||||
device: &DeviceShared,
|
||||
) -> Result<vk::Semaphore, crate::DeviceError> {
|
||||
// Try to recycle a semaphore we created for a previous presentation.
|
||||
let sem = match self.present.get(self.present_index) {
|
||||
Some(sem) => *sem,
|
||||
None => {
|
||||
let sem = device.new_binary_semaphore(&format!(
|
||||
"SwapchainImageSemaphore: Image {} present semaphore {}",
|
||||
self.frame_index, self.present_index
|
||||
))?;
|
||||
self.present.push(sem);
|
||||
sem
|
||||
}
|
||||
};
|
||||
|
||||
self.present_index += 1;
|
||||
|
||||
Ok(sem)
|
||||
}
|
||||
|
||||
/// Indicates the cpu-side usage of this semaphore has finished for the frame,
|
||||
/// so reset internal state to be ready for the next frame.
|
||||
fn end_semaphore_usage(&mut self) {
|
||||
// Reset the index to 0, so that the next time we get a semaphore, we
|
||||
// start from the beginning of the list.
|
||||
self.present_index = 0;
|
||||
}
|
||||
|
||||
/// Return the semaphores that a presentation of this image should wait on.
|
||||
///
|
||||
/// Return a slice of semaphores that the call to [`vkQueueSubmit`] that
|
||||
/// ends this image's acquisition should wait for. See
|
||||
/// [`SwapchainPresentSemaphores::present`] for details.
|
||||
///
|
||||
/// Reset `self` to be ready for the next acquisition cycle.
|
||||
///
|
||||
/// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit
|
||||
fn get_present_wait_semaphores(&mut self) -> Vec<vk::Semaphore> {
|
||||
self.present[0..self.present_index].to_vec()
|
||||
}
|
||||
|
||||
unsafe fn destroy(&self, device: &ash::Device) {
|
||||
unsafe {
|
||||
for sem in &self.present {
|
||||
device.destroy_semaphore(*sem, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NativeSurfaceTextureMetadata {
|
||||
acquire_semaphores: Arc<Mutex<SwapchainAcquireSemaphore>>,
|
||||
present_semaphores: Arc<Mutex<SwapchainPresentSemaphores>>,
|
||||
}
|
||||
|
||||
impl SurfaceTextureMetadata for NativeSurfaceTextureMetadata {
|
||||
fn get_semaphore_guard(&self) -> Box<dyn SwapchainSubmissionSemaphoreGuard + '_> {
|
||||
Box::new(NativeSwapchainSubmissionSemaphoreGuard {
|
||||
acquire_semaphore_guard: self
|
||||
.acquire_semaphores
|
||||
.try_lock()
|
||||
.expect("Failed to lock surface acquire semaphore"),
|
||||
present_semaphores_guard: self
|
||||
.present_semaphores
|
||||
.try_lock()
|
||||
.expect("Failed to lock surface present semaphores"),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct NativeSwapchainSubmissionSemaphoreGuard<'a> {
|
||||
acquire_semaphore_guard: MutexGuard<'a, SwapchainAcquireSemaphore>,
|
||||
present_semaphores_guard: MutexGuard<'a, SwapchainPresentSemaphores>,
|
||||
}
|
||||
|
||||
impl<'a> SwapchainSubmissionSemaphoreGuard for NativeSwapchainSubmissionSemaphoreGuard<'a> {
|
||||
fn set_used_fence_value(&mut self, value: u64) {
|
||||
self.acquire_semaphore_guard.set_used_fence_value(value);
|
||||
}
|
||||
|
||||
fn get_acquire_wait_semaphore(&mut self) -> Option<SemaphoreType> {
|
||||
self.acquire_semaphore_guard
|
||||
.get_acquire_wait_semaphore()
|
||||
.map(SemaphoreType::Binary)
|
||||
}
|
||||
|
||||
fn get_submit_signal_semaphore(
|
||||
&mut self,
|
||||
device: &DeviceShared,
|
||||
) -> Result<SemaphoreType, crate::DeviceError> {
|
||||
self.present_semaphores_guard
|
||||
.get_submit_signal_semaphore(device)
|
||||
.map(SemaphoreType::Binary)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user