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