diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 769d5406f..3641aa239 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -50,6 +50,7 @@ namespace dxvk { Presenter::~Presenter() { destroySwapchain(); destroySurface(); + destroyLatencySemaphore(); if (m_frameThread.joinable()) { { std::lock_guard lock(m_frameMutex); @@ -143,6 +144,16 @@ namespace dxvk { } } + // Apply latency sleep mode if the swapchain supports it + if (m_latencySleepModeDirty && m_latencySleepMode) { + m_latencySleepModeDirty = false; + + if (m_latencySleepSupported) { + m_vkd->vkSetLatencySleepModeNV(m_vkd->device(), + m_swapchain, &(*m_latencySleepMode)); + } + } + // Set dynamic present mode for the next frame if possible if (!m_dynamicModes.empty()) m_presentMode = m_dynamicModes.at(m_preferredSyncInterval ? 1u : 0u); @@ -305,6 +316,120 @@ namespace dxvk { } + void Presenter::setLatencySleepModeNv( + const VkLatencySleepModeInfoNV& sleepMode) { + std::unique_lock lock(m_surfaceMutex); + + if (sleepMode.sType != VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV) + return; + + if (sleepMode.pNext) + Logger::warn("Presenter: Extended sleep mode info not supported"); + + // Avoid creating a swapchain with low-latency features + // enabled if the functionality isn't required + bool isDefault = !sleepMode.lowLatencyMode + && !sleepMode.lowLatencyBoost + && !sleepMode.minimumIntervalUs; + + if (!m_latencySleepMode && isDefault) + return; + + m_dirtySwapchain |= !m_latencySleepMode; + + if (m_latencySleepMode) { + m_latencySleepModeDirty |= + m_latencySleepMode->lowLatencyMode != sleepMode.lowLatencyMode || + m_latencySleepMode->lowLatencyBoost != sleepMode.lowLatencyBoost || + m_latencySleepMode->minimumIntervalUs != sleepMode.minimumIntervalUs; + } + + m_latencySleepMode = sleepMode; + m_latencySleepMode->pNext = nullptr; + } + + + dxvk::high_resolution_clock::time_point Presenter::setLatencyMarkerNv( + uint64_t frameId, + VkLatencyMarkerNV marker) { + std::unique_lock lock(m_surfaceMutex); + + if (!m_latencySleepMode) { + // Applications may use latency markers without enabling + // low-latency mode, make sure we have a compatible swapchain + m_latencySleepMode = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV }; + m_dirtySwapchain = true; + + return dxvk::high_resolution_clock::now(); + } + + // Return a CPU timestamp to correlate timestamps from + // latency frame reports with actual CPU timestamps + auto t0 = dxvk::high_resolution_clock::now(); + + if (m_latencySleepSupported) { + VkSetLatencyMarkerInfoNV info = { VK_STRUCTURE_TYPE_SET_LATENCY_MARKER_INFO_NV }; + info.presentID = frameId; + info.marker = marker; + + m_vkd->vkSetLatencyMarkerNV(m_vkd->device(), m_swapchain, &info); + } + + auto t1 = dxvk::high_resolution_clock::now(); + return t0 + (t1 - t0) / 2u; + } + + + dxvk::high_resolution_clock::duration Presenter::latencySleepNv() { + std::unique_lock lock(m_surfaceMutex); + + if (!m_latencySleepSupported) + return dxvk::high_resolution_clock::duration(0u); + + if (!m_latencySemaphore) { + if (createLatencySemaphore() != VK_SUCCESS) + return dxvk::high_resolution_clock::duration(0u); + } + + VkLatencySleepInfoNV info = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_INFO_NV }; + info.signalSemaphore = m_latencySemaphore; + info.value = ++m_latencySleepCounter; + + m_vkd->vkLatencySleepNV(m_vkd->device(), m_swapchain, &info); + + lock.unlock(); + + auto t0 = dxvk::high_resolution_clock::now(); + + VkSemaphoreWaitInfo waitInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO }; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &info.signalSemaphore; + waitInfo.pValues = &info.value; + + m_vkd->vkWaitSemaphores(m_vkd->device(), &waitInfo, ~0ull); + + auto t1 = dxvk::high_resolution_clock::now(); + return t1 - t0; + } + + + uint32_t Presenter::getLatencyTimingsNv( + uint32_t timingCount, + VkLatencyTimingsFrameReportNV* timings) { + std::unique_lock lock(m_surfaceMutex); + + if (!m_latencySleepSupported) + return 0u; + + VkGetLatencyMarkerInfoNV info = { VK_STRUCTURE_TYPE_GET_LATENCY_MARKER_INFO_NV }; + info.timingCount = timingCount; + info.pTimings = timings; + + m_vkd->vkGetLatencyTimingsNV(m_vkd->device(), m_swapchain, &info); + return info.timingCount; + } + + void Presenter::setSyncInterval(uint32_t syncInterval) { std::lock_guard lock(m_surfaceMutex); @@ -541,6 +666,9 @@ namespace dxvk { modeInfo.presentModeCount = compatibleModes.size(); modeInfo.pPresentModes = compatibleModes.data(); + VkSwapchainLatencyCreateInfoNV latencyInfo = { VK_STRUCTURE_TYPE_SWAPCHAIN_LATENCY_CREATE_INFO_NV }; + latencyInfo.latencyModeEnable = m_latencySleepMode.has_value(); + VkSwapchainCreateInfoKHR swapInfo = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; swapInfo.surface = m_surface; swapInfo.minImageCount = pickImageCount(minImageCount, maxImageCount); @@ -562,6 +690,9 @@ namespace dxvk { if (m_device->features().extSwapchainMaintenance1.swapchainMaintenance1) modeInfo.pNext = std::exchange(swapInfo.pNext, &modeInfo); + if (m_device->features().nvLowLatency2) + latencyInfo.pNext = std::exchange(swapInfo.pNext, &latencyInfo); + Logger::info(str::format( "Presenter: Actual swapchain properties:" "\n Format: ", swapInfo.imageFormat, @@ -639,7 +770,7 @@ namespace dxvk { } // Invalidate indices - m_hdrMetadataDirty = true; + m_latencySleepSupported = m_device->features().nvLowLatency2 && latencyInfo.latencyModeEnable; m_imageIndex = 0; m_frameIndex = 0; @@ -984,6 +1115,20 @@ namespace dxvk { } + VkResult Presenter::createLatencySemaphore() { + VkSemaphoreTypeCreateInfo typeInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO }; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + + VkSemaphoreCreateInfo info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &typeInfo }; + VkResult vr = m_vkd->vkCreateSemaphore(m_vkd->device(), &info, nullptr, &m_latencySemaphore); + + if (vr != VK_SUCCESS) + Logger::err(str::format("Presenter: Failed to create latency semaphore: ", vr)); + + return vr; + } + + void Presenter::destroySwapchain() { if (m_signal != nullptr) m_signal->wait(m_lastFrameId.load(std::memory_order_acquire)); @@ -1007,6 +1152,11 @@ namespace dxvk { m_acquireStatus = VK_NOT_READY; m_presentPending = false; + + m_hdrMetadataDirty = true; + + m_latencySleepModeDirty = true; + m_latencySleepSupported = false; } @@ -1017,6 +1167,13 @@ namespace dxvk { } + void Presenter::destroyLatencySemaphore() { + m_vkd->vkDestroySemaphore(m_vkd->device(), m_latencySemaphore, nullptr); + + m_latencySemaphore = VK_NULL_HANDLE; + } + + void Presenter::waitForSwapchainFence( PresenterSync& sync) { if (!sync.fenceSignaled) diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 633ca7753..ff20eab99 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -124,10 +124,10 @@ namespace dxvk { * * Presents the last successfuly acquired image. * \param [in] frameId Frame number. - * Must increase monotonically. * \returns Status of the operation */ - VkResult presentImage(uint64_t frameId); + VkResult presentImage( + uint64_t frameId); /** * \brief Signals a given frame @@ -217,6 +217,48 @@ namespace dxvk { */ void destroyResources(); + /** + * \brief Sets latency sleep mode + * + * Any changes will be applied on the next acquire operation. + * \param [in] sleepMode Latency mode info + */ + void setLatencySleepModeNv( + const VkLatencySleepModeInfoNV& sleepMode); + + /** + * \brief Sets latency marker + * + * Ignored if the current swapchain has not been + * created with low latency support. + * \param [in] frameId Frame ID + * \param [in] marker Marker + * \returns CPU timestamp of the marker + */ + dxvk::high_resolution_clock::time_point setLatencyMarkerNv( + uint64_t frameId, + VkLatencyMarkerNV marker); + + /** + * \brief Executes latency sleep + * + * Ignored if the current swapchain has not been + * created with low latency support. + * \returns Sleep duration + */ + dxvk::high_resolution_clock::duration latencySleepNv(); + + /** + * \brief Queries latency timings + * + * \param [in] timingCount Number of timings to query + * \param [out] timings Latency timings + * \returns Number of frame reports returned + */ + uint32_t getLatencyTimingsNv( + uint32_t timingCount, + VkLatencyTimingsFrameReportNV* timings); + private: Rc m_device; @@ -258,6 +300,13 @@ namespace dxvk { std::optional m_hdrMetadata; bool m_hdrMetadataDirty = false; + std::optional m_latencySleepMode; + VkSemaphore m_latencySemaphore = VK_NULL_HANDLE; + uint64_t m_latencySleepCounter = 0u; + + bool m_latencySleepModeDirty = false; + bool m_latencySleepSupported = false; + alignas(CACHE_LINE_SIZE) dxvk::mutex m_frameMutex; dxvk::condition_variable m_frameCond; @@ -317,10 +366,14 @@ namespace dxvk { VkResult createSurface(); + VkResult createLatencySemaphore(); + void destroySwapchain(); void destroySurface(); + void destroyLatencySemaphore(); + void waitForSwapchainFence( PresenterSync& sync);