wgpu_hal/vulkan/swapchain/native.rs
1//! Vulkan Surface and Swapchain implementation using native Vulkan surfaces.
2
3use alloc::{boxed::Box, sync::Arc, vec::Vec};
4use core::any::Any;
5
6use ash::{khr, vk};
7use parking_lot::{Mutex, MutexGuard};
8
9use crate::vulkan::{
10 conv, map_host_device_oom_and_lost_err,
11 semaphore_list::SemaphoreType,
12 swapchain::{Surface, SurfaceTextureMetadata, Swapchain, SwapchainSubmissionSemaphoreGuard},
13 DeviceShared, InstanceShared,
14};
15
16pub(crate) struct NativeSurface {
17 raw: vk::SurfaceKHR,
18 functor: khr::surface::Instance,
19 instance: Arc<InstanceShared>,
20}
21
22impl NativeSurface {
23 pub fn from_vk_surface_khr(instance: &crate::vulkan::Instance, raw: vk::SurfaceKHR) -> Self {
24 let functor = khr::surface::Instance::new(&instance.shared.entry, &instance.shared.raw);
25 Self {
26 raw,
27 functor,
28 instance: Arc::clone(&instance.shared),
29 }
30 }
31
32 pub fn as_raw(&self) -> vk::SurfaceKHR {
33 self.raw
34 }
35}
36
37impl Drop for NativeSurface {
38 fn drop(&mut self) {
39 unsafe {
40 self.functor.destroy_surface(self.raw, None);
41 }
42 }
43}
44
45impl Surface for NativeSurface {
46 fn surface_capabilities(
47 &self,
48 adapter: &crate::vulkan::Adapter,
49 ) -> Option<crate::SurfaceCapabilities> {
50 if !adapter.private_caps.can_present {
51 return None;
52 }
53 let queue_family_index = 0; //TODO
54 {
55 profiling::scope!("vkGetPhysicalDeviceSurfaceSupportKHR");
56 match unsafe {
57 self.functor.get_physical_device_surface_support(
58 adapter.raw,
59 queue_family_index,
60 self.raw,
61 )
62 } {
63 Ok(true) => (),
64 Ok(false) => return None,
65 Err(e) => {
66 log::error!("get_physical_device_surface_support: {e}");
67 return None;
68 }
69 }
70 }
71
72 let caps = {
73 profiling::scope!("vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
74 match unsafe {
75 self.functor
76 .get_physical_device_surface_capabilities(adapter.raw, self.raw)
77 } {
78 Ok(caps) => caps,
79 Err(e) => {
80 log::error!("get_physical_device_surface_capabilities: {e}");
81 return None;
82 }
83 }
84 };
85
86 // If image count is 0, the support number of images is unlimited.
87 let max_image_count = if caps.max_image_count == 0 {
88 !0
89 } else {
90 caps.max_image_count
91 };
92
93 // `0xFFFFFFFF` indicates that the extent depends on the created swapchain.
94 let current_extent = if caps.current_extent.width != !0 && caps.current_extent.height != !0
95 {
96 Some(wgt::Extent3d {
97 width: caps.current_extent.width,
98 height: caps.current_extent.height,
99 depth_or_array_layers: 1,
100 })
101 } else {
102 None
103 };
104
105 let raw_present_modes = {
106 profiling::scope!("vkGetPhysicalDeviceSurfacePresentModesKHR");
107 match unsafe {
108 self.functor
109 .get_physical_device_surface_present_modes(adapter.raw, self.raw)
110 } {
111 Ok(present_modes) => present_modes,
112 Err(e) => {
113 log::error!("get_physical_device_surface_present_modes: {e}");
114 // Per definition of `SurfaceCapabilities`, there must be at least one present mode.
115 return None;
116 }
117 }
118 };
119
120 let raw_surface_formats = {
121 profiling::scope!("vkGetPhysicalDeviceSurfaceFormatsKHR");
122 match unsafe {
123 self.functor
124 .get_physical_device_surface_formats(adapter.raw, self.raw)
125 } {
126 Ok(formats) => formats,
127 Err(e) => {
128 log::error!("get_physical_device_surface_formats: {e}");
129 // Per definition of `SurfaceCapabilities`, there must be at least one present format.
130 return None;
131 }
132 }
133 };
134
135 let formats = raw_surface_formats
136 .into_iter()
137 .filter_map(conv::map_vk_surface_formats)
138 .collect();
139 Some(crate::SurfaceCapabilities {
140 formats,
141 // TODO: Right now we're always truncating the swap chain
142 // (presumably - we're actually setting the min image count which isn't necessarily the swap chain size)
143 // Instead, we should use extensions when available to wait in present.
144 // See https://github.com/gfx-rs/wgpu/issues/2869
145 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`.
146 current_extent,
147 usage: conv::map_vk_image_usage(caps.supported_usage_flags),
148 present_modes: raw_present_modes
149 .into_iter()
150 .flat_map(conv::map_vk_present_mode)
151 .collect(),
152 composite_alpha_modes: conv::map_vk_composite_alpha(caps.supported_composite_alpha),
153 })
154 }
155
156 unsafe fn create_swapchain(
157 &self,
158 device: &crate::vulkan::Device,
159 config: &crate::SurfaceConfiguration,
160 provided_old_swapchain: Option<Box<dyn Swapchain>>,
161 ) -> Result<Box<dyn Swapchain>, crate::SurfaceError> {
162 profiling::scope!("Device::create_swapchain");
163 let functor = khr::swapchain::Device::new(&self.instance.raw, &device.shared.raw);
164
165 let old_swapchain = provided_old_swapchain
166 .as_ref()
167 .map(|osc| osc.as_any().downcast_ref::<NativeSwapchain>().unwrap().raw)
168 .unwrap_or(vk::SwapchainKHR::null());
169
170 let color_space = if config.format == wgt::TextureFormat::Rgba16Float {
171 // Enable wide color gamut mode
172 // Vulkan swapchain for Android only supports DISPLAY_P3_NONLINEAR_EXT and EXTENDED_SRGB_LINEAR_EXT
173 vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT
174 } else {
175 vk::ColorSpaceKHR::SRGB_NONLINEAR
176 };
177
178 let original_format = device.shared.private_caps.map_texture_format(config.format);
179 let mut raw_flags = vk::SwapchainCreateFlagsKHR::empty();
180 let mut raw_view_formats: Vec<vk::Format> = vec![];
181 if !config.view_formats.is_empty() {
182 raw_flags |= vk::SwapchainCreateFlagsKHR::MUTABLE_FORMAT;
183 raw_view_formats = config
184 .view_formats
185 .iter()
186 .map(|f| device.shared.private_caps.map_texture_format(*f))
187 .collect();
188 raw_view_formats.push(original_format);
189 }
190
191 let mut info = vk::SwapchainCreateInfoKHR::default()
192 .flags(raw_flags)
193 .surface(self.raw)
194 .min_image_count(config.maximum_frame_latency + 1) // TODO: https://github.com/gfx-rs/wgpu/issues/2869
195 .image_format(original_format)
196 .image_color_space(color_space)
197 .image_extent(vk::Extent2D {
198 width: config.extent.width,
199 height: config.extent.height,
200 })
201 .image_array_layers(config.extent.depth_or_array_layers)
202 .image_usage(conv::map_texture_usage(config.usage))
203 .image_sharing_mode(vk::SharingMode::EXCLUSIVE)
204 .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY)
205 .composite_alpha(conv::map_composite_alpha_mode(config.composite_alpha_mode))
206 .present_mode(conv::map_present_mode(config.present_mode))
207 .clipped(true)
208 .old_swapchain(old_swapchain);
209
210 let mut format_list_info = vk::ImageFormatListCreateInfo::default();
211 if !raw_view_formats.is_empty() {
212 format_list_info = format_list_info.view_formats(&raw_view_formats);
213 info = info.push_next(&mut format_list_info);
214 }
215
216 let result = {
217 profiling::scope!("vkCreateSwapchainKHR");
218 unsafe { functor.create_swapchain(&info, None) }
219 };
220
221 let raw = match result {
222 Ok(swapchain) => swapchain,
223 Err(error) => {
224 return Err(match error {
225 vk::Result::ERROR_SURFACE_LOST_KHR
226 | vk::Result::ERROR_INITIALIZATION_FAILED => crate::SurfaceError::Lost,
227 vk::Result::ERROR_NATIVE_WINDOW_IN_USE_KHR => {
228 crate::SurfaceError::Other("Native window is in use")
229 }
230 // We don't use VK_EXT_image_compression_control
231 // VK_ERROR_COMPRESSION_EXHAUSTED_EXT
232 other => map_host_device_oom_and_lost_err(other).into(),
233 });
234 }
235 };
236
237 let images = unsafe { functor.get_swapchain_images(raw) }
238 .map_err(crate::vulkan::map_host_device_oom_err)?;
239
240 let fence = unsafe {
241 device
242 .shared
243 .raw
244 .create_fence(&vk::FenceCreateInfo::default(), None)
245 .map_err(crate::vulkan::map_host_device_oom_err)?
246 };
247
248 // NOTE: It's important that we define the same number of acquire/present semaphores
249 // as we will need to index into them with the image index.
250 let acquire_semaphores = (0..images.len())
251 .map(|i| {
252 SwapchainAcquireSemaphore::new(&device.shared, i)
253 .map(Mutex::new)
254 .map(Arc::new)
255 })
256 .collect::<Result<Vec<_>, _>>()?;
257
258 let present_semaphores = (0..images.len())
259 .map(|i| Arc::new(Mutex::new(SwapchainPresentSemaphores::new(i))))
260 .collect::<Vec<_>>();
261
262 Ok(Box::new(NativeSwapchain {
263 raw,
264 functor,
265 device: Arc::clone(&device.shared),
266 images,
267 fence,
268 config: config.clone(),
269 acquire_semaphores,
270 next_acquire_index: 0,
271 present_semaphores,
272 next_present_time: None,
273 }))
274 }
275
276 fn as_any(&self) -> &dyn Any {
277 self
278 }
279}
280
281pub(crate) struct NativeSwapchain {
282 raw: vk::SwapchainKHR,
283 functor: khr::swapchain::Device,
284 device: Arc<DeviceShared>,
285 images: Vec<vk::Image>,
286 /// Fence used to wait on the acquired image.
287 fence: vk::Fence,
288 config: crate::SurfaceConfiguration,
289
290 /// Semaphores used between image acquisition and the first submission
291 /// that uses that image. This is indexed using [`next_acquire_index`].
292 ///
293 /// Because we need to provide this to [`vkAcquireNextImageKHR`], we haven't
294 /// received the swapchain image index for the frame yet, so we cannot use
295 /// that to index it.
296 ///
297 /// Before we pass this to [`vkAcquireNextImageKHR`], we ensure that we wait on
298 /// the submission indicated by [`previously_used_submission_index`]. This ensures
299 /// the semaphore is no longer in use before we use it.
300 ///
301 /// [`next_acquire_index`]: NativeSwapchain::next_acquire_index
302 /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
303 /// [`previously_used_submission_index`]: SwapchainAcquireSemaphore::previously_used_submission_index
304 acquire_semaphores: Vec<Arc<Mutex<SwapchainAcquireSemaphore>>>,
305 /// The index of the next acquire semaphore to use.
306 ///
307 /// This is incremented each time we acquire a new image, and wraps around
308 /// to 0 when it reaches the end of [`acquire_semaphores`].
309 ///
310 /// [`acquire_semaphores`]: NativeSwapchain::acquire_semaphores
311 next_acquire_index: usize,
312
313 /// Semaphore sets used between all submissions that write to an image and
314 /// the presentation of that image.
315 ///
316 /// This is indexed by the swapchain image index returned by
317 /// [`vkAcquireNextImageKHR`].
318 ///
319 /// We know it is safe to use these semaphores because use them
320 /// _after_ the acquire semaphore. Because the acquire semaphore
321 /// has been signaled, the previous presentation using that image
322 /// is known-finished, so this semaphore is no longer in use.
323 ///
324 /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
325 present_semaphores: Vec<Arc<Mutex<SwapchainPresentSemaphores>>>,
326
327 /// The present timing information which will be set in the next call to [`present()`](crate::Queue::present()).
328 ///
329 /// # Safety
330 ///
331 /// This must only be set if [`wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING`] is enabled, and
332 /// so the VK_GOOGLE_display_timing extension is present.
333 next_present_time: Option<vk::PresentTimeGOOGLE>,
334}
335
336impl Drop for NativeSwapchain {
337 fn drop(&mut self) {
338 unsafe {
339 self.functor.destroy_swapchain(self.raw, None);
340 }
341 }
342}
343
344impl Swapchain for NativeSwapchain {
345 unsafe fn release_resources(&mut self, device: &crate::vulkan::Device) {
346 profiling::scope!("Swapchain::release_resources");
347 {
348 profiling::scope!("vkDeviceWaitIdle");
349 // We need to also wait until all presentation work is done. Because there is no way to portably wait until
350 // the presentation work is done, we are forced to wait until the device is idle.
351 let _ = unsafe {
352 device
353 .shared
354 .raw
355 .device_wait_idle()
356 .map_err(map_host_device_oom_and_lost_err)
357 };
358 };
359
360 unsafe { device.shared.raw.destroy_fence(self.fence, None) }
361
362 // We cannot take this by value, as the function returns `self`.
363 for semaphore in self.acquire_semaphores.drain(..) {
364 let arc_removed = Arc::into_inner(semaphore).expect(
365 "Trying to destroy a SwapchainAcquireSemaphore that is still in use by a SurfaceTexture",
366 );
367 let mutex_removed = arc_removed.into_inner();
368
369 unsafe { mutex_removed.destroy(&device.shared.raw) };
370 }
371
372 for semaphore in self.present_semaphores.drain(..) {
373 let arc_removed = Arc::into_inner(semaphore).expect(
374 "Trying to destroy a SwapchainPresentSemaphores that is still in use by a SurfaceTexture",
375 );
376 let mutex_removed = arc_removed.into_inner();
377
378 unsafe { mutex_removed.destroy(&device.shared.raw) };
379 }
380 }
381
382 unsafe fn acquire(
383 &mut self,
384 timeout: Option<core::time::Duration>,
385 fence: &crate::vulkan::Fence,
386 ) -> Result<crate::AcquiredSurfaceTexture<crate::api::Vulkan>, crate::SurfaceError> {
387 let mut timeout_ns = match timeout {
388 Some(duration) => duration.as_nanos() as u64,
389 None => u64::MAX,
390 };
391
392 // AcquireNextImageKHR on Android (prior to Android 11) doesn't support timeouts
393 // and will also log verbose warnings if tying to use a timeout.
394 //
395 // Android 10 implementation for reference:
396 // https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-10.0.0_r13/vulkan/libvulkan/swapchain.cpp#1426
397 // Android 11 implementation for reference:
398 // https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-11.0.0_r45/vulkan/libvulkan/swapchain.cpp#1438
399 //
400 // Android 11 corresponds to an SDK_INT/ro.build.version.sdk of 30
401 if cfg!(target_os = "android") && self.device.instance.android_sdk_version < 30 {
402 timeout_ns = u64::MAX;
403 }
404
405 let acquire_semaphore_arc = self.get_acquire_semaphore();
406 // Nothing should be using this, so we don't block, but panic if we fail to lock.
407 let acquire_semaphore_guard = acquire_semaphore_arc
408 .try_lock()
409 .expect("Failed to lock a SwapchainSemaphores.");
410
411 // Wait for all commands writing to the previously acquired image to
412 // complete.
413 //
414 // Almost all the steps in the usual acquire-draw-present flow are
415 // asynchronous: they get something started on the presentation engine
416 // or the GPU, but on the CPU, control returns immediately. Without some
417 // sort of intervention, the CPU could crank out frames much faster than
418 // the presentation engine can display them.
419 //
420 // This is the intervention: if any submissions drew on this image, and
421 // thus waited for `locked_swapchain_semaphores.acquire`, wait for all
422 // of them to finish, thus ensuring that it's okay to pass `acquire` to
423 // `vkAcquireNextImageKHR` again.
424 let completed = self.device.wait_for_fence(
425 fence,
426 acquire_semaphore_guard.previously_used_submission_index,
427 timeout_ns,
428 )?;
429 if !completed {
430 return Err(crate::SurfaceError::Timeout);
431 }
432
433 // will block if no image is available
434 let (index, suboptimal) = match unsafe {
435 profiling::scope!("vkAcquireNextImageKHR");
436 self.functor.acquire_next_image(
437 self.raw,
438 timeout_ns,
439 acquire_semaphore_guard.acquire,
440 self.fence,
441 )
442 } {
443 // We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android.
444 // See the comment in `Queue::present`.
445 #[cfg(target_os = "android")]
446 Ok((index, _)) => (index, false),
447 #[cfg(not(target_os = "android"))]
448 Ok(pair) => pair,
449 Err(error) => {
450 return match error {
451 vk::Result::TIMEOUT => Err(crate::SurfaceError::Timeout),
452 vk::Result::NOT_READY | vk::Result::ERROR_OUT_OF_DATE_KHR => {
453 Err(crate::SurfaceError::Outdated)
454 }
455 vk::Result::ERROR_SURFACE_LOST_KHR => Err(crate::SurfaceError::Lost),
456 // We don't use VK_EXT_full_screen_exclusive
457 // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
458 other => Err(map_host_device_oom_and_lost_err(other).into()),
459 };
460 }
461 };
462
463 // Wait for the image was acquired to be fully ready to be rendered too.
464 //
465 // This wait is very important on Windows to avoid bad frame pacing on
466 // Windows where the Vulkan driver is using a DXGI swapchain. See
467 // https://github.com/gfx-rs/wgpu/issues/8310 and
468 // https://github.com/gfx-rs/wgpu/issues/8354 for more details.
469 //
470 // On other platforms, this wait may serve to slightly decrease frame
471 // latency, depending on how the platform implements waiting within
472 // acquire.
473 unsafe {
474 // The `wait_all` argument must be `true` to avoid crash on some Android devices. See https://github.com/gfx-rs/wgpu/pull/8769
475 self.device
476 .raw
477 .wait_for_fences(&[self.fence], true, timeout_ns)
478 .map_err(map_host_device_oom_and_lost_err)?;
479
480 self.device
481 .raw
482 .reset_fences(&[self.fence])
483 .map_err(map_host_device_oom_and_lost_err)?;
484 }
485
486 drop(acquire_semaphore_guard);
487 // We only advance the surface semaphores if we successfully acquired an image, otherwise
488 // we should try to re-acquire using the same semaphores.
489 self.advance_acquire_semaphore();
490
491 let present_semaphore_arc = self.get_present_semaphores(index);
492
493 // special case for Intel Vulkan returning bizarre values (ugh)
494 if self.device.vendor_id == crate::auxil::db::intel::VENDOR && index > 0x100 {
495 return Err(crate::SurfaceError::Outdated);
496 }
497
498 let identity = self.device.texture_identity_factory.next();
499
500 let texture = crate::vulkan::SurfaceTexture {
501 index,
502 texture: crate::vulkan::Texture {
503 raw: self.images[index as usize],
504 drop_guard: None,
505 memory: crate::vulkan::TextureMemory::External,
506 format: self.config.format,
507 copy_size: crate::CopyExtent {
508 width: self.config.extent.width,
509 height: self.config.extent.height,
510 depth: 1,
511 },
512 identity,
513 },
514 metadata: Box::new(NativeSurfaceTextureMetadata {
515 acquire_semaphores: acquire_semaphore_arc,
516 present_semaphores: present_semaphore_arc,
517 }),
518 };
519 Ok(crate::AcquiredSurfaceTexture {
520 texture,
521 suboptimal,
522 })
523 }
524
525 unsafe fn discard_texture(
526 &mut self,
527 _texture: crate::vulkan::SurfaceTexture,
528 ) -> Result<(), crate::SurfaceError> {
529 // TODO: Current implementation no-ops
530 Ok(())
531 }
532
533 unsafe fn present(
534 &mut self,
535 queue: &crate::vulkan::Queue,
536 texture: crate::vulkan::SurfaceTexture,
537 ) -> Result<(), crate::SurfaceError> {
538 let metadata = texture
539 .metadata
540 .as_any()
541 .downcast_ref::<NativeSurfaceTextureMetadata>()
542 .unwrap();
543 let mut acquire_semaphore = metadata.acquire_semaphores.lock();
544 let mut present_semaphores = metadata.present_semaphores.lock();
545
546 let wait_semaphores = present_semaphores.get_present_wait_semaphores();
547
548 // Reset the acquire and present semaphores internal state
549 // to be ready for the next frame.
550 //
551 // We do this before the actual call to present to ensure that
552 // even if this method errors and early outs, we have reset
553 // the state for next frame.
554 acquire_semaphore.end_semaphore_usage();
555 present_semaphores.end_semaphore_usage();
556
557 drop(acquire_semaphore);
558
559 let swapchains = [self.raw];
560 let image_indices = [texture.index];
561 let vk_info = vk::PresentInfoKHR::default()
562 .swapchains(&swapchains)
563 .image_indices(&image_indices)
564 .wait_semaphores(&wait_semaphores);
565
566 let mut display_timing;
567 let present_times;
568 let vk_info = if let Some(present_time) = self.next_present_time.take() {
569 debug_assert!(
570 self.device
571 .features
572 .contains(wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING),
573 "`next_present_time` should only be set if `VULKAN_GOOGLE_DISPLAY_TIMING` is enabled"
574 );
575 present_times = [present_time];
576 display_timing = vk::PresentTimesInfoGOOGLE::default().times(&present_times);
577 // SAFETY: We know that VK_GOOGLE_display_timing is present because of the safety contract on `next_present_time`.
578 vk_info.push_next(&mut display_timing)
579 } else {
580 vk_info
581 };
582
583 let suboptimal = {
584 profiling::scope!("vkQueuePresentKHR");
585 unsafe { self.functor.queue_present(queue.raw, &vk_info) }.map_err(|error| {
586 match error {
587 vk::Result::ERROR_OUT_OF_DATE_KHR => crate::SurfaceError::Outdated,
588 vk::Result::ERROR_SURFACE_LOST_KHR => crate::SurfaceError::Lost,
589 // We don't use VK_EXT_full_screen_exclusive
590 // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
591 _ => map_host_device_oom_and_lost_err(error).into(),
592 }
593 })?
594 };
595 if suboptimal {
596 // We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android.
597 // On Android 10+, libvulkan's `vkQueuePresentKHR` implementation returns `VK_SUBOPTIMAL_KHR` if not doing pre-rotation
598 // (i.e `VkSwapchainCreateInfoKHR::preTransform` not being equal to the current device orientation).
599 // 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`.
600 #[cfg(not(target_os = "android"))]
601 log::debug!("Suboptimal present of frame {}", texture.index);
602 }
603 Ok(())
604 }
605
606 fn as_any(&self) -> &dyn Any {
607 self
608 }
609
610 fn as_any_mut(&mut self) -> &mut dyn Any {
611 self
612 }
613}
614
615impl NativeSwapchain {
616 pub(crate) fn as_raw(&self) -> vk::SwapchainKHR {
617 self.raw
618 }
619
620 pub fn set_next_present_time(&mut self, present_timing: vk::PresentTimeGOOGLE) {
621 let features = wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING;
622 if self.device.features.contains(features) {
623 self.next_present_time = Some(present_timing);
624 } else {
625 // Ideally we'd use something like `device.required_features` here, but that's in `wgpu-core`, which we are a dependency of
626 panic!(
627 concat!(
628 "Tried to set display timing properties ",
629 "without the corresponding feature ({:?}) enabled."
630 ),
631 features
632 );
633 }
634 }
635
636 /// Mark the current frame finished, advancing to the next acquire semaphore.
637 fn advance_acquire_semaphore(&mut self) {
638 let semaphore_count = self.acquire_semaphores.len();
639 self.next_acquire_index = (self.next_acquire_index + 1) % semaphore_count;
640 }
641
642 /// Get the next acquire semaphore that should be used with this swapchain.
643 fn get_acquire_semaphore(&self) -> Arc<Mutex<SwapchainAcquireSemaphore>> {
644 self.acquire_semaphores[self.next_acquire_index].clone()
645 }
646
647 /// Get the set of present semaphores that should be used with the given image index.
648 fn get_present_semaphores(&self, index: u32) -> Arc<Mutex<SwapchainPresentSemaphores>> {
649 self.present_semaphores[index as usize].clone()
650 }
651}
652
653/// Semaphore used to acquire a swapchain image.
654#[derive(Debug)]
655struct SwapchainAcquireSemaphore {
656 /// A semaphore that is signaled when this image is safe for us to modify.
657 ///
658 /// When [`vkAcquireNextImageKHR`] returns the index of the next swapchain
659 /// image that we should use, that image may actually still be in use by the
660 /// presentation engine, and is not yet safe to modify. However, that
661 /// function does accept a semaphore that it will signal when the image is
662 /// indeed safe to begin messing with.
663 ///
664 /// This semaphore is:
665 ///
666 /// - waited for by the first queue submission to operate on this image
667 /// since it was acquired, and
668 ///
669 /// - signaled by [`vkAcquireNextImageKHR`] when the acquired image is ready
670 /// for us to use.
671 ///
672 /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR
673 acquire: vk::Semaphore,
674
675 /// True if the next command submission operating on this image should wait
676 /// for [`acquire`].
677 ///
678 /// We must wait for `acquire` before drawing to this swapchain image, but
679 /// because `wgpu-hal` queue submissions are always strongly ordered, only
680 /// the first submission that works with a swapchain image actually needs to
681 /// wait. We set this flag when this image is acquired, and clear it the
682 /// first time it's passed to [`Queue::submit`] as a surface texture.
683 ///
684 /// Additionally, semaphores can only be waited on once, so we need to ensure
685 /// that we only actually pass this semaphore to the first submission that
686 /// uses that image.
687 ///
688 /// [`acquire`]: SwapchainAcquireSemaphore::acquire
689 /// [`Queue::submit`]: crate::Queue::submit
690 should_wait_for_acquire: bool,
691
692 /// The fence value of the last command submission that wrote to this image.
693 ///
694 /// The next time we try to acquire this image, we'll block until
695 /// this submission finishes, proving that [`acquire`] is ready to
696 /// pass to `vkAcquireNextImageKHR` again.
697 ///
698 /// [`acquire`]: SwapchainAcquireSemaphore::acquire
699 previously_used_submission_index: crate::FenceValue,
700}
701
702impl SwapchainAcquireSemaphore {
703 fn new(device: &DeviceShared, index: usize) -> Result<Self, crate::DeviceError> {
704 Ok(Self {
705 acquire: device
706 .new_binary_semaphore(&format!("SwapchainImageSemaphore: Index {index} acquire"))?,
707 should_wait_for_acquire: true,
708 previously_used_submission_index: 0,
709 })
710 }
711
712 /// Sets the fence value which the next acquire will wait for. This prevents
713 /// the semaphore from being used while the previous submission is still in flight.
714 fn set_used_fence_value(&mut self, value: crate::FenceValue) {
715 self.previously_used_submission_index = value;
716 }
717
718 /// Return the semaphore that commands drawing to this image should wait for, if any.
719 ///
720 /// This only returns `Some` once per acquisition; see
721 /// [`SwapchainAcquireSemaphore::should_wait_for_acquire`] for details.
722 fn get_acquire_wait_semaphore(&mut self) -> Option<vk::Semaphore> {
723 if self.should_wait_for_acquire {
724 self.should_wait_for_acquire = false;
725 Some(self.acquire)
726 } else {
727 None
728 }
729 }
730
731 /// Indicates the cpu-side usage of this semaphore has finished for the frame,
732 /// so reset internal state to be ready for the next frame.
733 fn end_semaphore_usage(&mut self) {
734 // Reset the acquire semaphore, so that the next time we acquire this
735 // image, we can wait for it again.
736 self.should_wait_for_acquire = true;
737 }
738
739 unsafe fn destroy(&self, device: &ash::Device) {
740 unsafe {
741 device.destroy_semaphore(self.acquire, None);
742 }
743 }
744}
745
746#[derive(Debug)]
747struct SwapchainPresentSemaphores {
748 /// A pool of semaphores for ordering presentation after drawing.
749 ///
750 /// The first [`present_index`] semaphores in this vector are:
751 ///
752 /// - all waited on by the call to [`vkQueuePresentKHR`] that presents this
753 /// image, and
754 ///
755 /// - each signaled by some [`vkQueueSubmit`] queue submission that draws to
756 /// this image, when the submission finishes execution.
757 ///
758 /// This vector accumulates one semaphore per submission that writes to this
759 /// image. This is awkward, but hard to avoid: [`vkQueuePresentKHR`]
760 /// requires a semaphore to order it with respect to drawing commands, and
761 /// we can't attach new completion semaphores to a command submission after
762 /// it's been submitted. This means that, at submission time, we must create
763 /// the semaphore we might need if the caller's next action is to enqueue a
764 /// presentation of this image.
765 ///
766 /// An alternative strategy would be for presentation to enqueue an empty
767 /// submit, ordered relative to other submits in the usual way, and
768 /// signaling a single presentation semaphore. But we suspect that submits
769 /// are usually expensive enough, and semaphores usually cheap enough, that
770 /// performance-sensitive users will avoid making many submits, so that the
771 /// cost of accumulated semaphores will usually be less than the cost of an
772 /// additional submit.
773 ///
774 /// Only the first [`present_index`] semaphores in the vector are actually
775 /// going to be signalled by submitted commands, and need to be waited for
776 /// by the next present call. Any semaphores beyond that index were created
777 /// for prior presents and are simply being retained for recycling.
778 ///
779 /// [`present_index`]: SwapchainPresentSemaphores::present_index
780 /// [`vkQueuePresentKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueuePresentKHR
781 /// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit
782 present: Vec<vk::Semaphore>,
783
784 /// The number of semaphores in [`present`] to be signalled for this submission.
785 ///
786 /// [`present`]: SwapchainPresentSemaphores::present
787 present_index: usize,
788
789 /// Which image this semaphore set is used for.
790 frame_index: usize,
791}
792
793impl SwapchainPresentSemaphores {
794 pub fn new(frame_index: usize) -> Self {
795 Self {
796 present: Vec::new(),
797 present_index: 0,
798 frame_index,
799 }
800 }
801
802 /// Return the semaphore that the next submission that writes to this image should
803 /// signal when it's done.
804 ///
805 /// See [`SwapchainPresentSemaphores::present`] for details.
806 fn get_submit_signal_semaphore(
807 &mut self,
808 device: &DeviceShared,
809 ) -> Result<vk::Semaphore, crate::DeviceError> {
810 // Try to recycle a semaphore we created for a previous presentation.
811 let sem = match self.present.get(self.present_index) {
812 Some(sem) => *sem,
813 None => {
814 let sem = device.new_binary_semaphore(&format!(
815 "SwapchainImageSemaphore: Image {} present semaphore {}",
816 self.frame_index, self.present_index
817 ))?;
818 self.present.push(sem);
819 sem
820 }
821 };
822
823 self.present_index += 1;
824
825 Ok(sem)
826 }
827
828 /// Indicates the cpu-side usage of this semaphore has finished for the frame,
829 /// so reset internal state to be ready for the next frame.
830 fn end_semaphore_usage(&mut self) {
831 // Reset the index to 0, so that the next time we get a semaphore, we
832 // start from the beginning of the list.
833 self.present_index = 0;
834 }
835
836 /// Return the semaphores that a presentation of this image should wait on.
837 ///
838 /// Return a slice of semaphores that the call to [`vkQueueSubmit`] that
839 /// ends this image's acquisition should wait for. See
840 /// [`SwapchainPresentSemaphores::present`] for details.
841 ///
842 /// Reset `self` to be ready for the next acquisition cycle.
843 ///
844 /// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit
845 fn get_present_wait_semaphores(&mut self) -> Vec<vk::Semaphore> {
846 self.present[0..self.present_index].to_vec()
847 }
848
849 unsafe fn destroy(&self, device: &ash::Device) {
850 unsafe {
851 for sem in &self.present {
852 device.destroy_semaphore(*sem, None);
853 }
854 }
855 }
856}
857
858#[derive(Debug)]
859struct NativeSurfaceTextureMetadata {
860 acquire_semaphores: Arc<Mutex<SwapchainAcquireSemaphore>>,
861 present_semaphores: Arc<Mutex<SwapchainPresentSemaphores>>,
862}
863
864impl SurfaceTextureMetadata for NativeSurfaceTextureMetadata {
865 fn get_semaphore_guard(&self) -> Box<dyn SwapchainSubmissionSemaphoreGuard + '_> {
866 Box::new(NativeSwapchainSubmissionSemaphoreGuard {
867 acquire_semaphore_guard: self
868 .acquire_semaphores
869 .try_lock()
870 .expect("Failed to lock surface acquire semaphore"),
871 present_semaphores_guard: self
872 .present_semaphores
873 .try_lock()
874 .expect("Failed to lock surface present semaphores"),
875 })
876 }
877
878 fn as_any(&self) -> &dyn Any {
879 self
880 }
881}
882
883struct NativeSwapchainSubmissionSemaphoreGuard<'a> {
884 acquire_semaphore_guard: MutexGuard<'a, SwapchainAcquireSemaphore>,
885 present_semaphores_guard: MutexGuard<'a, SwapchainPresentSemaphores>,
886}
887
888impl<'a> SwapchainSubmissionSemaphoreGuard for NativeSwapchainSubmissionSemaphoreGuard<'a> {
889 fn set_used_fence_value(&mut self, value: u64) {
890 self.acquire_semaphore_guard.set_used_fence_value(value);
891 }
892
893 fn get_acquire_wait_semaphore(&mut self) -> Option<SemaphoreType> {
894 self.acquire_semaphore_guard
895 .get_acquire_wait_semaphore()
896 .map(SemaphoreType::Binary)
897 }
898
899 fn get_submit_signal_semaphore(
900 &mut self,
901 device: &DeviceShared,
902 ) -> Result<SemaphoreType, crate::DeviceError> {
903 self.present_semaphores_guard
904 .get_submit_signal_semaphore(device)
905 .map(SemaphoreType::Binary)
906 }
907}