@@ -99,6 +99,10 @@ static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<vk::Surfac
9999vk::PresentModeKHR GPUDisplayBackendVulkan::chooseSwapPresentMode ()
100100{
101101 // Lowest latency first. VSync: no tearing, MAILBOX, then FIFO_LATEST_READY (shows the newest ready image at each vblank). No VSync: IMMEDIATE, else the same (often no IMMEDIATE on Wayland). FIFO is always supported.
102+ mPresentModeForVSyncLimit = fpsLimitMode () == FPS_LIMIT_VSYNC ;
103+ if (mPresentModeForVSyncLimit ) {
104+ return vk::PresentModeKHR::eFifo; // Refresh rate limit: one frame per refresh, queue depth limited by present wait
105+ }
102106 const bool vsync = mDisplay ->cfgR ().drawQualityVSync ;
103107 const auto & available = mSwapChainDetails .presentModes ;
104108 for (vk::PresentModeKHR mode : {vk::PresentModeKHR::eImmediate, vk::PresentModeKHR::eMailbox, vk::PresentModeKHR::eFifoLatestReady, vk::PresentModeKHR::eFifo}) {
@@ -313,6 +317,25 @@ static void clearFeatures(vk::StructureChain<T...>& chain) // Zero all feature f
313317 ((memset (reinterpret_cast <char *>(&chain.template get <T>()) + sizeof (VkBaseOutStructure), 0 , sizeof (T) - sizeof (VkBaseOutStructure))), ...);
314318}
315319
320+ template <class T > // Enable the first available of the extensions (aliases) if the device supports the feature, chaining the feature struct in front of chain
321+ static bool enableOptionalFeature (vk::PhysicalDevice device, const std::vector<vk::ExtensionProperties>& available, std::initializer_list<const char *> extensions, vk::Bool32 T::* feature, T& featureStruct, std::vector<const char *>& enabledExtensions, void *& chain)
322+ {
323+ for (const char * ext : extensions) {
324+ if (std::none_of (available.begin (), available.end (), [ext](const auto & e) { return strcmp (ext, e.extensionName ) == 0 ; })) {
325+ continue ;
326+ }
327+ if (!(device.getFeatures2 <vk::PhysicalDeviceFeatures2, T>().template get <T>().*feature)) {
328+ return false ;
329+ }
330+ enabledExtensions.emplace_back (ext);
331+ featureStruct.*feature = VK_TRUE ;
332+ featureStruct.pNext = chain;
333+ chain = &featureStruct;
334+ return true ;
335+ }
336+ return false ;
337+ }
338+
316339template <class ... T>
317340static vk::StructureChain<T...> getFeatures (vk::PhysicalDevice device, const vk::StructureChain<T...>&)
318341{
@@ -536,18 +559,10 @@ void GPUDisplayBackendVulkan::createDevice()
536559 mSmoothLinesSupported = getFeatures (mPhysicalDevice , reqDeviceFeatures).get <vk::PhysicalDeviceVulkan14Features>().smoothLines ;
537560 reqDeviceFeatures.get <vk::PhysicalDeviceVulkan14Features>().smoothLines = mSmoothLinesSupported ;
538561 std::vector<const char *> deviceExtensions = reqDeviceExtensions;
539- vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR fifoLatestReadyFeatures{} ;
562+ void * featureChain = &reqDeviceFeatures. get < vk::PhysicalDeviceFeatures2>() ;
540563 const std::vector<vk::ExtensionProperties> availableDeviceExtensions = mPhysicalDevice .enumerateDeviceExtensionProperties (nullptr );
541- for (const char * ext : {VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME , VK_EXT_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME }) { // Optional, same feature struct for both
542- if (std::any_of (availableDeviceExtensions.begin (), availableDeviceExtensions.end (), [ext](const auto & e) { return strcmp (ext, e.extensionName ) == 0 ; })) {
543- mFifoLatestReadySupported = mPhysicalDevice .getFeatures2 <vk::PhysicalDeviceFeatures2, vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR>().get <vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR>().presentModeFifoLatestReady ;
544- if (mFifoLatestReadySupported ) {
545- deviceExtensions.emplace_back (ext);
546- fifoLatestReadyFeatures.presentModeFifoLatestReady = VK_TRUE ;
547- }
548- break ;
549- }
550- }
564+ vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR fifoLatestReadyFeatures{};
565+ mFifoLatestReadySupported = enableOptionalFeature (mPhysicalDevice , availableDeviceExtensions, {VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME , VK_EXT_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME }, &vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR::presentModeFifoLatestReady, fifoLatestReadyFeatures, deviceExtensions, featureChain);
551566
552567 updateSwapChainDetails (mPhysicalDevice );
553568 vk::PhysicalDeviceProperties deviceProperties = mPhysicalDevice .getProperties ();
@@ -572,11 +587,7 @@ void GPUDisplayBackendVulkan::createDevice()
572587 vk::DeviceCreateInfo deviceCreateInfo{};
573588 deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
574589 deviceCreateInfo.queueCreateInfoCount = 1 ;
575- deviceCreateInfo.pNext = &reqDeviceFeatures.get <vk::PhysicalDeviceFeatures2>();
576- if (mFifoLatestReadySupported ) {
577- fifoLatestReadyFeatures.pNext = &reqDeviceFeatures.get <vk::PhysicalDeviceFeatures2>();
578- deviceCreateInfo.pNext = &fifoLatestReadyFeatures;
579- }
590+ deviceCreateInfo.pNext = featureChain;
580591 deviceCreateInfo.pEnabledFeatures = nullptr ;
581592 deviceCreateInfo.enabledExtensionCount = static_cast <uint32_t >(deviceExtensions.size ());
582593 deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data ();
@@ -648,13 +659,27 @@ void GPUDisplayBackendVulkan::createSemaphores()
648659 mTimelineValue = 0 ;
649660 mFrameTimelineValue .assign (mFramesInFlight , 0 );
650661 mImageTimelineValue .assign (mImageCount , 0 );
662+ for (uint32_t i = 0 ; i < mImageCount ; i++) {
663+ mPresentFences .emplace_back (mDevice .createFence (vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled}, nullptr ));
664+ }
665+ }
666+
667+ void GPUDisplayBackendVulkan::waitPresentFences ()
668+ {
669+ if (mPresentFences .size ()) { // Timeout in case a failed present did not signal its fence
670+ vk::Result retVal = mDevice .waitForFences (mPresentFences .size (), mPresentFences .data (), true , 1'000'000'000 );
671+ if (retVal != vk::Result::eTimeout) {
672+ CHKERR (retVal);
673+ }
674+ }
651675}
652676
653677void GPUDisplayBackendVulkan::clearSemaphores ()
654678{
655679 clearVector (mImageAvailableSemaphore , [&](auto & x) { mDevice .destroySemaphore (x, nullptr ); });
656680 clearVector (mPresentSemaphore , [&](auto & x) { mDevice .destroySemaphore (x, nullptr ); });
657681 mDevice .destroySemaphore (mTimelineSemaphore , nullptr );
682+ clearVector (mPresentFences , [&](auto & x) { mDevice .destroyFence (x, nullptr ); });
658683}
659684
660685// ---------------------------- VULKAN UNIFORM LAYOUTS AND BUFFERS ----------------------------
@@ -770,8 +795,23 @@ void GPUDisplayBackendVulkan::createSwapChain(vk::SwapchainKHR oldSwapChain)
770795 swapCreateInfo.presentMode = mPresentMode ;
771796 swapCreateInfo.clipped = true ;
772797 swapCreateInfo.oldSwapchain = oldSwapChain;
798+ vk::SwapchainPresentScalingCreateInfoKHR scalingInfo{};
799+ { // Let the compositor scale the last image while resizing, until the new swapchain is presented
800+ const vk::SurfacePresentModeKHR surfacePresentMode{mPresentMode };
801+ const auto caps = mPhysicalDevice .getSurfaceCapabilities2KHR <vk::SurfaceCapabilities2KHR, vk::SurfacePresentScalingCapabilitiesKHR>(vk::PhysicalDeviceSurfaceInfo2KHR{mSurface , &surfacePresentMode}).get <vk::SurfacePresentScalingCapabilitiesKHR>();
802+ auto gravity = [](vk::PresentGravityFlagsKHR f) { return (f & vk::PresentGravityFlagBitsKHR::eCentered) ? vk::PresentGravityFlagBitsKHR::eCentered : (f & vk::PresentGravityFlagBitsKHR::eMin) ? vk::PresentGravityFlagBitsKHR::eMin
803+ : vk::PresentGravityFlagsKHR (f & vk::PresentGravityFlagBitsKHR::eMax); };
804+ scalingInfo.scalingBehavior = (caps.supportedPresentScaling & vk::PresentScalingFlagBitsKHR::eAspectRatioStretch) ? vk::PresentScalingFlagBitsKHR::eAspectRatioStretch : vk::PresentScalingFlagsKHR (caps.supportedPresentScaling & vk::PresentScalingFlagBitsKHR::eStretch);
805+ scalingInfo.presentGravityX = gravity (caps.supportedPresentGravityX );
806+ scalingInfo.presentGravityY = gravity (caps.supportedPresentGravityY );
807+ if (scalingInfo.scalingBehavior && scalingInfo.presentGravityX && scalingInfo.presentGravityY ) {
808+ swapCreateInfo.pNext = &scalingInfo;
809+ }
810+ }
773811 mSwapChain = mDevice .createSwapchainKHR (swapCreateInfo, nullptr );
812+ mLastPresentId = 0 ; // Present IDs are per swapchain
774813 if (oldSwapChain) {
814+ waitPresentFences (); // Presentation engine done with the old swapchain
775815 mDevice .destroySwapchainKHR (oldSwapChain, nullptr );
776816 }
777817
@@ -809,6 +849,7 @@ void GPUDisplayBackendVulkan::clearSwapChain(bool keepSwapChain)
809849{
810850 clearVector (mSwapChainImageViews , [&](auto & x) { mDevice .destroyImageView (x, nullptr ); });
811851 if (!keepSwapChain) {
852+ waitPresentFences ();
812853 mDevice .destroySwapchainKHR (mSwapChain , nullptr );
813854 }
814855}
@@ -1424,11 +1465,22 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4&
14241465 if (includeMixImage == 0 .f ) {
14251466 mCurrentFrame = (mCurrentFrame + 1 ) % mFramesInFlight ;
14261467 waitTimeline (mFrameTimelineValue [mCurrentFrame ]);
1468+ if (mLastPresentId > 1 && fpsLimitMode () == FPS_LIMIT_VSYNC ) { // Refresh rate limit (FIFO): at most one frame queued (wait for the second-last present), keeps the rate if a frame misses the deadline, timeout in case the window is hidden
1469+ const vk::Result retVal = (vk::Result)VULKAN_HPP_DEFAULT_DISPATCHER .vkWaitForPresentKHR (static_cast <VkDevice>(mDevice ), static_cast <VkSwapchainKHR>(mSwapChain ), mLastPresentId - 1 , 100'000'000 );
1470+ if (retVal == vk::Result::eErrorOutOfDateKHR || retVal == vk::Result::eSuboptimalKHR) {
1471+ mMustUpdateSwapChain = true ;
1472+ } else if (retVal != vk::Result::eTimeout) {
1473+ CHKERR (retVal);
1474+ }
1475+ }
14271476 auto getImage = [&]() {
14281477 return mDevice .acquireNextImageKHR (mSwapChain , UINT64_MAX , mImageAvailableSemaphore [mCurrentFrame ], VkFence (VK_NULL_HANDLE ), &mCurrentImageIndex );
14291478 };
14301479
14311480 vk::Result retVal = vk::Result::eSuccess;
1481+ if (mPresentModeForVSyncLimit != (fpsLimitMode () == FPS_LIMIT_VSYNC )) { // Present mode depends on the FPS limit mode
1482+ mMustUpdateSwapChain = true ;
1483+ }
14321484 bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay ->updateRenderPipeline () || (toMixBuffer && !mMixingSupported ) || mDownsampleFactor != getDownsampleFactor (requestScreenshot) || mSmoothLines != useNativeSmoothLines ();
14331485 if (!mustUpdateSwapChain) {
14341486 retVal = getImage ();
@@ -1442,7 +1494,11 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4&
14421494 }
14431495 mMustUpdateSwapChain = retVal == vk::Result::eSuboptimalKHR;
14441496 CHKERR (retVal);
1445- waitTimeline (mImageTimelineValue [mCurrentImageIndex ]); // Previous frame using this image's resources (command buffers, uniform buffer, ...) finished
1497+ waitTimeline (mImageTimelineValue [mCurrentImageIndex ]); // Previous frame using this image's resources (command buffers, uniform buffer, ...) finished
1498+ if (mPresentFences .size ()) { // Previous present of this image done, its present semaphore can be signaled again
1499+ CHKERR (mDevice .waitForFences (1 , &mPresentFences [mCurrentImageIndex ], true , 1'000'000'000 )); // Timeout (positive result, not an error) in case a failed present did not signal it
1500+ CHKERR (mDevice .resetFences (1 , &mPresentFences [mCurrentImageIndex ]));
1501+ }
14461502
14471503 mHasDrawnText = false ;
14481504 mFrameCommandBuffers .clear ();
@@ -1521,6 +1577,17 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f
15211577 presentInfo.pSwapchains = &mSwapChain ;
15221578 presentInfo.pImageIndices = &mCurrentImageIndex ;
15231579 presentInfo.pResults = nullptr ;
1580+ const uint64_t presentId = mLastPresentId + 1 ;
1581+ vk::PresentIdKHR presentIdInfo{1 , &presentId};
1582+ if (fpsLimitMode () == FPS_LIMIT_VSYNC ) { // Present wait only for the refresh rate limit, the fpsRate limit (frontend timer) must not be capped at the refresh rate
1583+ presentInfo.pNext = &presentIdInfo;
1584+ mLastPresentId = presentId;
1585+ }
1586+ vk::SwapchainPresentFenceInfoKHR presentFenceInfo{};
1587+ if (mPresentFences .size ()) {
1588+ presentFenceInfo = vk::SwapchainPresentFenceInfoKHR{1 , &mPresentFences [mCurrentImageIndex ], presentInfo.pNext };
1589+ presentInfo.pNext = &presentFenceInfo;
1590+ }
15241591 vk::Result retVal = mGraphicsQueue .presentKHR (&presentInfo);
15251592 if (retVal == vk::Result::eErrorOutOfDateKHR || retVal == vk::Result::eSuboptimalKHR) {
15261593 mMustUpdateSwapChain = true ;
0 commit comments