From f016f9c4e44a16f1a59948f9b0b83d8e6811a809 Mon Sep 17 00:00:00 2001 From: Christoph Neuhauser Date: Tue, 6 Oct 2026 16:16:27 +0200 Subject: [PATCH 001/135] [include] Update Vulkan headers to version 1.4.363 --- include/vulkan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/vulkan b/include/vulkan index 8864cdc8..6802bb47 160000 --- a/include/vulkan +++ b/include/vulkan @@ -1 +1 @@ -Subproject commit 8864cdc896bbc2a9b6eb36b3218fc9ef57908d77 +Subproject commit 6802bb4733b63ed5efd3adb308a6c885ef180ea1 From 56bac6b1ba854e37e53fe32322a0dd6df8b9d38f Mon Sep 17 00:00:00 2001 From: Christoph Neuhauser Date: Tue, 6 Oct 2026 16:16:50 +0200 Subject: [PATCH 002/135] [dxvk] Restrict sin/cos workaround to pre-Xe3 Intel GPUs The hardware precision for sin/cos near zero on Intel GPU architectures has been improved starting Xe3 (arch 30) and newer. VK_INTEL_device_info is enabled to query the GPU IP version and skip the sin/cos lowering workaround on newer hardware. --- src/dxvk/dxvk_device.cpp | 10 +++++++--- src/dxvk/dxvk_device_info.cpp | 9 +++++++-- src/dxvk/dxvk_device_info.h | 3 +++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index 02218254..5564e001 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -779,9 +779,13 @@ namespace dxvk { if (!(r32Features & VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT)) m_shaderOptions.flags.set(DxvkShaderCompileFlag::TypedR32LoadRequiresFormat); - // Intel's hardware sin/cos is so inaccurate that it causes rendering issues in some games - bool lowerSinCos = m_adapter->matchesDriver(VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA) - || m_adapter->matchesDriver(VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS); + // Up to the Intel Xe2 GPU architecture, the hardware sin/cos precision was low + // (but spec compliant) close to zero, which causes rendering issues in some games. + // Precision close to 0 is significantly improved with Xe3 (arch 30) and newer. + bool lowerSinCos = + (m_adapter->matchesDriver(VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA) + || m_adapter->matchesDriver(VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS)) + && (!m_features.intelDeviceInfo || m_properties.intelDeviceInfo.deviceIpVersionArch < 30u); applyTristate(lowerSinCos, m_options.lowerSinCos); diff --git a/src/dxvk/dxvk_device_info.cpp b/src/dxvk/dxvk_device_info.cpp index ab1f5634..7e3d16a7 100644 --- a/src/dxvk/dxvk_device_info.cpp +++ b/src/dxvk/dxvk_device_info.cpp @@ -76,7 +76,8 @@ namespace dxvk { HANDLE_EXT(nvLowLatency2); \ HANDLE_EXT(nvRawAccessChains); \ HANDLE_EXT(nvxBinaryImport); \ - HANDLE_EXT(nvxImageViewHandle); + HANDLE_EXT(nvxImageViewHandle); \ + HANDLE_EXT(intelDeviceInfo); #define EXTENSIONS_WITH_PROPERTIES \ HANDLE_EXT(extConservativeRasterization); \ @@ -96,7 +97,8 @@ namespace dxvk { HANDLE_EXT(khrMaintenance6); \ HANDLE_EXT(khrMaintenance7); \ HANDLE_EXT(khrMaintenance9); \ - HANDLE_EXT(khrMaintenance10); + HANDLE_EXT(khrMaintenance10); \ + HANDLE_EXT(intelDeviceInfo); DxvkDeviceCapabilities::DxvkDeviceCapabilities( @@ -1114,6 +1116,9 @@ namespace dxvk { /* CUDA interop extensions */ ENABLE_EXT(nvxBinaryImport, false), ENABLE_EXT(nvxImageViewHandle, false), + + /* Intel device info, used to detect the GPU architecture generation */ + ENABLE_EXT(intelDeviceInfo, false), }}; #undef ENABLE_FEATURE diff --git a/src/dxvk/dxvk_device_info.h b/src/dxvk/dxvk_device_info.h index 40dae678..a6af2a40 100644 --- a/src/dxvk/dxvk_device_info.h +++ b/src/dxvk/dxvk_device_info.h @@ -44,6 +44,7 @@ namespace dxvk { VkPhysicalDeviceMaintenance7PropertiesKHR khrMaintenance7 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_7_PROPERTIES_KHR }; VkPhysicalDeviceMaintenance9PropertiesKHR khrMaintenance9 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_9_PROPERTIES_KHR }; VkPhysicalDeviceMaintenance10PropertiesKHR khrMaintenance10 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_10_PROPERTIES_KHR }; + VkPhysicalDeviceInfoPropertiesINTEL intelDeviceInfo = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INFO_PROPERTIES_INTEL }; }; @@ -120,6 +121,7 @@ namespace dxvk { VkPhysicalDeviceRawAccessChainsFeaturesNV nvRawAccessChains = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAW_ACCESS_CHAINS_FEATURES_NV }; VkBool32 nvxBinaryImport = VK_FALSE; VkBool32 nvxImageViewHandle = VK_FALSE; + VkBool32 intelDeviceInfo = VK_FALSE; }; @@ -197,6 +199,7 @@ namespace dxvk { VkExtensionProperties nvRawAccessChains = vk::makeExtension(VK_NV_RAW_ACCESS_CHAINS_EXTENSION_NAME); VkExtensionProperties nvxBinaryImport = vk::makeExtension(VK_NVX_BINARY_IMPORT_EXTENSION_NAME); VkExtensionProperties nvxImageViewHandle = vk::makeExtension(VK_NVX_IMAGE_VIEW_HANDLE_EXTENSION_NAME); + VkExtensionProperties intelDeviceInfo = vk::makeExtension(VK_INTEL_DEVICE_INFO_EXTENSION_NAME); }; From 0e10259b225934aa7be6558adde718f7b0e2fdff Mon Sep 17 00:00:00 2001 From: Paul Gofman Date: Wed, 7 Oct 2026 14:26:50 -0600 Subject: [PATCH 003/135] [d3d11] Support converting d3d12 texture info in D3D11Device::ConvertRuntimeDescriptor(). --- src/d3d11/d3d11_device.cpp | 108 +++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 58 deletions(-) diff --git a/src/d3d11/d3d11_device.cpp b/src/d3d11/d3d11_device.cpp index 13b101d6..323f1821 100644 --- a/src/d3d11/d3d11_device.cpp +++ b/src/d3d11/d3d11_device.cpp @@ -2779,11 +2779,35 @@ namespace dxvk { D3D11_COMMON_TEXTURE_DESC* desc) { if (size == sizeof(d3dkmt.d3d12) && d3dkmt.d3d12.d3d11.dxgi.size == sizeof(d3dkmt.d3d12.d3d11) && d3dkmt.d3d12.d3d11.dxgi.version == 0) { - Logger::warn(str::format("D3D11Device::ConvertRuntimeDescriptor: D3D12 descriptor conversion not implemented")); - return false; - } + const D3D12_RESOURCE_DESC1 *d3d12 = &d3dkmt.d3d12.desc1; + Logger::debug(str::format("D3D11Device::ConvertRuntimeDescriptor: Found D3D12 desc with dimension: ", d3d12->Dimension)); - if (size >= sizeof(d3dkmt.d3d11) && d3dkmt.dxgi.size == sizeof(d3dkmt.d3d11) && d3dkmt.dxgi.version == 4) { + switch (d3d12->Dimension) { + case D3D12_RESOURCE_DIMENSION_TEXTURE2D: + desc->Width = d3d12->Width; + desc->Height = d3d12->Height; + desc->Depth = 1; + desc->MipLevels = d3d12->MipLevels; + desc->ArraySize = d3d12->DepthOrArraySize; + desc->Format = d3d12->Format; + desc->SampleDesc = d3d12->SampleDesc; + desc->Usage = D3D11_USAGE_DEFAULT; + desc->BindFlags = D3D11_BIND_RENDER_TARGET; + if (d3d12->Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) + desc->BindFlags |= D3D11_BIND_DEPTH_STENCIL; + if (d3d12->Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) + desc->BindFlags |= D3D11_BIND_UNORDERED_ACCESS; + if (!(d3d12->Flags & D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE)) + desc->BindFlags |= D3D11_BIND_SHADER_RESOURCE; + desc->CPUAccessFlags = 0; + desc->MiscFlags = D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE; + desc->TextureLayout = D3D11_TEXTURE_LAYOUT_UNDEFINED; + break; + default: + Logger::warn(str::format("D3D11Device::ConvertRuntimeDescriptor: Unsupported dimension: ", d3d12->Dimension)); + return false; + } + } else if (size >= sizeof(d3dkmt.d3d11) && d3dkmt.dxgi.size == sizeof(d3dkmt.d3d11) && d3dkmt.dxgi.version == 4) { Logger::debug(str::format("D3D11Device::ConvertRuntimeDescriptor: Found D3D11 desc with dimension: ", d3dkmt.d3d11.dimension)); switch (d3dkmt.d3d11.dimension) { @@ -2805,42 +2829,9 @@ namespace dxvk { Logger::warn(str::format("D3D11Device::ConvertRuntimeDescriptor: Unsupported dimension: ", d3dkmt.d3d11.dimension)); return false; } - - Logger::debug(str::format("D3D11Device::ConvertRuntimeDescriptor: Translated D3D11 desc:")); - Logger::debug(str::format(" Width: ", desc->Width)); - Logger::debug(str::format(" Height: ", desc->Height)); - Logger::debug(str::format(" Depth: ", desc->Depth)); - Logger::debug(str::format(" MipLevels: ", desc->MipLevels)); - Logger::debug(str::format(" ArraySize: ", desc->ArraySize)); - Logger::debug(str::format(" Format: ", desc->Format)); - Logger::debug(str::format(" SampleDesc.Count: ", desc->SampleDesc.Count)); - Logger::debug(str::format(" SampleDesc.Quality: ", desc->SampleDesc.Quality)); - Logger::debug(str::format(" Usage: ", desc->Usage)); - Logger::debug(str::format(" BindFlags: ", desc->BindFlags)); - Logger::debug(str::format(" CPUAccessFlags: ", desc->CPUAccessFlags)); - Logger::debug(str::format(" MiscFlags: ", desc->MiscFlags)); - Logger::debug(str::format(" TextureLayout: ", desc->TextureLayout)); - return true; } - - if (size >= sizeof(d3dkmt.d3d9) && d3dkmt.dxgi.size == sizeof(d3dkmt.d3d9) && d3dkmt.dxgi.version == 1) { + else if (size >= sizeof(d3dkmt.d3d9) && d3dkmt.dxgi.size == sizeof(d3dkmt.d3d9) && d3dkmt.dxgi.version == 1) { Logger::debug(str::format("D3D11Device::ConvertRuntimeDescriptor: Found D3D9 desc: ", d3dkmt.d3d9.type)); - Logger::debug(str::format(" dxgi.width: ", d3dkmt.d3d9.dxgi.width)); - Logger::debug(str::format(" dxgi.height: ", d3dkmt.d3d9.dxgi.height)); - Logger::debug(str::format(" format: ", d3dkmt.d3d9.format)); - Logger::debug(str::format(" usage: ", d3dkmt.d3d9.usage)); - if (d3dkmt.d3d9.type == D3DRTYPE_TEXTURE) { - Logger::debug(str::format(" texture.width: ", d3dkmt.d3d9.texture.width)); - Logger::debug(str::format(" texture.height: ", d3dkmt.d3d9.texture.height)); - Logger::debug(str::format(" texture.depth: ", d3dkmt.d3d9.texture.depth)); - Logger::debug(str::format(" texture.levels: ", d3dkmt.d3d9.texture.levels)); - } else if (d3dkmt.d3d9.type == D3DRTYPE_SURFACE) { - Logger::debug(str::format(" surface.width: ", d3dkmt.d3d9.surface.width)); - Logger::debug(str::format(" surface.height: ", d3dkmt.d3d9.surface.height)); - } else { - Logger::warn(str::format("D3D11Device::ConvertRuntimeDescriptor: Unsupported D3D9 type: ", d3dkmt.d3d9.type)); - return false; - } desc->Width = d3dkmt.d3d9.dxgi.width; desc->Height = d3dkmt.d3d9.dxgi.height; @@ -2868,29 +2859,30 @@ namespace dxvk { desc->Height = d3dkmt.d3d9.surface.height; break; default: - break; + Logger::warn(str::format("D3D11Device::ConvertRuntimeDescriptor: Unsupported D3D9 type: ", d3dkmt.d3d9.type)); + return false; } - - Logger::debug(str::format("D3D11Device::ConvertRuntimeDescriptor: Translated D3D9 desc:")); - Logger::debug(str::format(" Width: ", desc->Width)); - Logger::debug(str::format(" Height: ", desc->Height)); - Logger::debug(str::format(" Depth: ", desc->Depth)); - Logger::debug(str::format(" MipLevels: ", desc->MipLevels)); - Logger::debug(str::format(" ArraySize: ", desc->ArraySize)); - Logger::debug(str::format(" Format: ", desc->Format)); - Logger::debug(str::format(" SampleDesc.Count: ", desc->SampleDesc.Count)); - Logger::debug(str::format(" SampleDesc.Quality: ", desc->SampleDesc.Quality)); - Logger::debug(str::format(" Usage: ", desc->Usage)); - Logger::debug(str::format(" BindFlags: ", desc->BindFlags)); - Logger::debug(str::format(" CPUAccessFlags: ", desc->CPUAccessFlags)); - Logger::debug(str::format(" MiscFlags: ", desc->MiscFlags)); - Logger::debug(str::format(" TextureLayout: ", desc->TextureLayout)); - return true; + } else { + Logger::warn(str::format("D3D11Device::ConvertRuntimeDescriptor: Unsupported runtime desc size: ", + size, "/", d3dkmt.dxgi.size, " version: ", d3dkmt.dxgi.version)); + return false; } - Logger::warn(str::format("D3D11Device::ConvertRuntimeDescriptor: Unsupported runtime desc size: ", - size, "/", d3dkmt.dxgi.size, " version: ", d3dkmt.dxgi.version)); - return false; + Logger::debug(str::format("D3D11Device::ConvertRuntimeDescriptor: Translated desc:")); + Logger::debug(str::format(" Width: ", desc->Width)); + Logger::debug(str::format(" Height: ", desc->Height)); + Logger::debug(str::format(" Depth: ", desc->Depth)); + Logger::debug(str::format(" MipLevels: ", desc->MipLevels)); + Logger::debug(str::format(" ArraySize: ", desc->ArraySize)); + Logger::debug(str::format(" Format: ", desc->Format)); + Logger::debug(str::format(" SampleDesc.Count: ", desc->SampleDesc.Count)); + Logger::debug(str::format(" SampleDesc.Quality: ", desc->SampleDesc.Quality)); + Logger::debug(str::format(" Usage: ", desc->Usage)); + Logger::debug(str::format(" BindFlags: ", desc->BindFlags)); + Logger::debug(str::format(" CPUAccessFlags: ", desc->CPUAccessFlags)); + Logger::debug(str::format(" MiscFlags: ", desc->MiscFlags)); + Logger::debug(str::format(" TextureLayout: ", desc->TextureLayout)); + return true; } From 852d454f6a023f04453a164e0981785fd5802cc9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 8 Oct 2026 10:10:29 +0200 Subject: [PATCH 004/135] [dxvk] Improve hang detection Sometimes we don't get DEVICE_LOST errors. --- src/dxvk/dxvk_queue.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_queue.cpp b/src/dxvk/dxvk_queue.cpp index e705ed91..d760b383 100644 --- a/src/dxvk/dxvk_queue.cpp +++ b/src/dxvk/dxvk_queue.cpp @@ -265,7 +265,14 @@ namespace dxvk { waitInfo.pSemaphores = semaphores.data(); waitInfo.pValues = timelines.data(); - status = vk->vkWaitSemaphores(vk->device(), &waitInfo, ~0ull); + // Treat a timeout here as DEVICE_LOST. Sometimes we just don't get + // DEVICE_LOST from the driver for some reason. + status = vk->vkWaitSemaphores(vk->device(), &waitInfo, 5'000'000'000ull); + + if (status == VK_TIMEOUT) { + Logger::err("DxvkSubmissionQueue: Timeout occured, GPU likely hung."); + status = VK_ERROR_DEVICE_LOST; + } if (entry.latency.tracker && status == VK_SUCCESS) entry.latency.tracker->notifyGpuExecutionEnd(entry.latency.frameId); From 1cad6cd11d8b0edf89113a0d8e4819ade33fbf9c Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 9 Oct 2026 14:58:20 +0200 Subject: [PATCH 005/135] [dxvk] Rework submission synchronization Should fix #5964. --- src/d3d11/d3d11_context_imm.cpp | 13 ++++----- src/d3d11/d3d11_context_imm.h | 1 - src/d3d11/d3d11_initializer.cpp | 2 +- src/d3d11/d3d11_swapchain.cpp | 4 +-- src/d3d9/d3d9_device.cpp | 8 ++---- src/d3d9/d3d9_device.h | 1 - src/d3d9/d3d9_initializer.cpp | 2 +- src/d3d9/d3d9_swapchain.cpp | 4 +-- src/dxvk/dxvk_context.cpp | 4 +-- src/dxvk/dxvk_context.h | 4 +-- src/dxvk/dxvk_device.cpp | 19 ++++--------- src/dxvk/dxvk_device.h | 16 +++++------ src/dxvk/dxvk_queue.cpp | 50 +++++++++++++++------------------ src/dxvk/dxvk_queue.h | 34 ++++++++-------------- 14 files changed, 65 insertions(+), 97 deletions(-) diff --git a/src/d3d11/d3d11_context_imm.cpp b/src/d3d11/d3d11_context_imm.cpp index 68fcba2c..b21f2932 100644 --- a/src/d3d11/d3d11_context_imm.cpp +++ b/src/d3d11/d3d11_context_imm.cpp @@ -1093,12 +1093,12 @@ namespace dxvk { GpuFlushType FlushType, HANDLE hEvent, BOOL Synchronize) { - if (Synchronize) - m_submitStatus.result = VK_NOT_READY; - // Exit early if there's nothing to do - if (!GetPendingCsChunks() && !hEvent) + if (!GetPendingCsChunks() && !hEvent) { + if (Synchronize) + m_device->waitForSubmission(m_submissionId); return; + } m_hasPendingUnresolvedPass = false; @@ -1117,7 +1117,6 @@ namespace dxvk { EmitCs([ cSubmissionFence = m_submissionFence, cSubmissionId = submissionId, - cSubmissionStatus = Synchronize ? &m_submitStatus : nullptr, cStagingFence = m_stagingBufferFence, cStagingMemory = GetStagingMemoryStatistics().allocatedTotal, cFlushReason = std::exchange(m_flushReason, std::string()) @@ -1126,7 +1125,7 @@ namespace dxvk { ctx->signal(cSubmissionFence, cSubmissionId); ctx->signal(cStagingFence, cStagingMemory); - ctx->flushCommandList(&debugLabel, cSubmissionStatus); + ctx->flushCommandList(&debugLabel, cSubmissionId); }); FlushCsChunk(); @@ -1138,7 +1137,7 @@ namespace dxvk { // If necessary, block calling thread until the // Vulkan queue submission is performed. if (Synchronize) - m_device->waitForSubmission(&m_submitStatus); + m_device->waitForSubmission(submissionId); // Free local staging buffer so that we don't // end up with a persistent allocation diff --git a/src/d3d11/d3d11_context_imm.h b/src/d3d11/d3d11_context_imm.h index 7cafdc35..61d4f804 100644 --- a/src/d3d11/d3d11_context_imm.h +++ b/src/d3d11/d3d11_context_imm.h @@ -126,7 +126,6 @@ namespace dxvk { Rc m_submissionFence; uint64_t m_submissionId = 0ull; - DxvkSubmitStatus m_submitStatus; uint64_t m_flushSeqNum = 0ull; GpuFlushTracker m_flushTracker; diff --git a/src/d3d11/d3d11_initializer.cpp b/src/d3d11/d3d11_initializer.cpp index 473c1986..5718939c 100644 --- a/src/d3d11/d3d11_initializer.cpp +++ b/src/d3d11/d3d11_initializer.cpp @@ -322,7 +322,7 @@ namespace dxvk { void D3D11Initializer::ExecuteFlushLocked() { EmitCsLocked([] (DxvkContext* ctx) { - ctx->flushCommandList(nullptr, nullptr); + ctx->flushCommandList(nullptr, 0u); }); FlushCsChunkLocked(); diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 1f07405a..0b2ea96c 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -524,10 +524,10 @@ namespace dxvk { // Submit current command list and present ctx->synchronizeWsi(cSync); - ctx->flushCommandList(nullptr, nullptr); + ctx->flushCommandList(nullptr, 0u); cDevice->presentImage(cPresenter, cLatency, cFrameId, - cDirtyRects.size(), cDirtyRects.data(), nullptr); + cDirtyRects.size(), cDirtyRects.data(), 0u); }); if (m_backBuffers.size() > 1u) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 4ecf356c..5606986c 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -6122,9 +6122,6 @@ namespace dxvk { void D3D9DeviceEx::ExecuteFlush(bool Synchronize9On12) { D3D9DeviceLock lock = LockDevice(); - if (Synchronize9On12) - m_submitStatus.result = VK_NOT_READY; - // Update signaled staging buffer counter and signal the fence m_stagingMemorySignaled = GetStagingMemoryStatistics().allocatedTotal; @@ -6138,13 +6135,12 @@ namespace dxvk { EmitCs([ cSubmissionFence = m_submissionFence, cSubmissionId = submissionId, - cSubmissionStatus = Synchronize9On12 ? &m_submitStatus : nullptr, cStagingBufferFence = m_stagingBufferFence, cStagingBufferAllocated = m_stagingMemorySignaled ] (DxvkContext* ctx) { ctx->signal(cSubmissionFence, cSubmissionId); ctx->signal(cStagingBufferFence, cStagingBufferAllocated); - ctx->flushCommandList(nullptr, cSubmissionStatus); + ctx->flushCommandList(nullptr, cSubmissionId); }); FlushCsChunk(); @@ -6155,7 +6151,7 @@ namespace dxvk { // If necessary, block calling thread until the // Vulkan queue submission is performed. if (Synchronize9On12) - m_dxvkDevice->waitForSubmission(&m_submitStatus); + m_dxvkDevice->waitForSubmission(submissionId); // Notify the device that the context has been flushed, // this resets some resource initialization heuristics. diff --git a/src/d3d9/d3d9_device.h b/src/d3d9/d3d9_device.h index 83d72c2f..ec67d06f 100644 --- a/src/d3d9/d3d9_device.h +++ b/src/d3d9/d3d9_device.h @@ -1663,7 +1663,6 @@ namespace dxvk { Rc m_submissionFence; uint64_t m_submissionId = 0ull; - DxvkSubmitStatus m_submitStatus; uint64_t m_flushSeqNum = 0ull; GpuFlushTracker m_flushTracker; diff --git a/src/d3d9/d3d9_initializer.cpp b/src/d3d9/d3d9_initializer.cpp index d3930e16..a9980357 100644 --- a/src/d3d9/d3d9_initializer.cpp +++ b/src/d3d9/d3d9_initializer.cpp @@ -162,7 +162,7 @@ namespace dxvk { void D3D9Initializer::ExecuteFlushLocked() { EmitCs([] (DxvkContext* ctx) { - ctx->flushCommandList(nullptr, nullptr); + ctx->flushCommandList(nullptr, 0u); }); FlushCsChunk(); diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index d672f115..824fd25c 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -912,9 +912,9 @@ namespace dxvk { // Submit command list and present ctx->synchronizeWsi(cSync); - ctx->flushCommandList(nullptr, nullptr); + ctx->flushCommandList(nullptr, 0u); - cDevice->presentImage(cPresenter, cLatency, cFrameId, 0, nullptr, nullptr); + cDevice->presentImage(cPresenter, cLatency, cFrameId, 0, nullptr, 0); }); m_parent->FlushCsChunk(); diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index 6692c215..045ad842 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -162,7 +162,7 @@ namespace dxvk { void DxvkContext::flushCommandList( const VkDebugUtilsLabelEXT* reason, - DxvkSubmitStatus* status) { + uint64_t submissionId) { // If necessary, block any async queue on previous command completion if (m_submitWaitId) m_cmd->waitFence(m_trackingFence, std::exchange(m_submitWaitId, 0ull)); @@ -183,7 +183,7 @@ namespace dxvk { m_latencyTracker->notifyCsRenderEnd(m_latencyFrameId); m_device->submitCommandList(this->endRecording(reason), - m_latencyTracker, m_latencyFrameId, status); + m_latencyTracker, m_latencyFrameId, submissionId); // Ensure that subsequent submissions do not see the tracker. // It is important to hide certain internal submissions in diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h index 58d16531..aa5f9f11 100644 --- a/src/dxvk/dxvk_context.h +++ b/src/dxvk/dxvk_context.h @@ -97,11 +97,11 @@ namespace dxvk { * Transparently submits the current command * buffer and allocates a new one. * \param [in] reason Optional debug label describing the reason - * \param [out] status Submission feedback + * \param [in] submissionId Submission timeline */ void flushCommandList( const VkDebugUtilsLabelEXT* reason, - DxvkSubmitStatus* status); + uint64_t submissionId); /** * \brief Synchronizes command list with WSI diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index 5564e001..7bdd651b 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -607,7 +607,7 @@ namespace dxvk { uint64_t frameId, uint32_t rectCount, const VkRectLayerKHR* rects, - DxvkSubmitStatus* status) { + uint64_t submissionId) { DxvkPresentInfo presentInfo = { }; presentInfo.presenter = presenter; presentInfo.frameId = frameId; @@ -619,7 +619,7 @@ namespace dxvk { latencyInfo.tracker = tracker; latencyInfo.frameId = frameId; - m_submissionQueue.present(presentInfo, latencyInfo, status); + m_submissionQueue.present(presentInfo, latencyInfo, submissionId); std::lock_guard statLock(m_statLock); m_statCounters.addCtr(DxvkStatCounter::QueuePresentCount, 1); @@ -630,7 +630,7 @@ namespace dxvk { const Rc& commandList, const Rc& tracker, uint64_t frameId, - DxvkSubmitStatus* status) { + uint64_t submissionId) { DxvkSubmitInfo submitInfo = { }; submitInfo.cmdList = commandList; @@ -638,22 +638,15 @@ namespace dxvk { latencyInfo.tracker = tracker; latencyInfo.frameId = frameId; - m_submissionQueue.submit(submitInfo, latencyInfo, status); + m_submissionQueue.submit(submitInfo, latencyInfo, submissionId); std::lock_guard statLock(m_statLock); m_statCounters.merge(commandList->statCounters()); } - VkResult DxvkDevice::waitForSubmission(DxvkSubmitStatus* status) { - VkResult result = status->result.load(); - - if (result == VK_NOT_READY) { - m_submissionQueue.synchronizeSubmission(status); - result = status->result.load(); - } - - return result; + void DxvkDevice::waitForSubmission(uint64_t submissionId) { + m_submissionQueue.synchronizeSubmission(submissionId); } diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h index 7fb070d5..af234035 100644 --- a/src/dxvk/dxvk_device.h +++ b/src/dxvk/dxvk_device.h @@ -652,7 +652,7 @@ namespace dxvk { * \param [in] frameId Frame ID * \param [in] rectCount Number of dirty rectangles, * \param [in] rects Dirty rectangles - * \param [out] status Present status + * \param [in] submissionId Submission timeline */ void presentImage( const Rc& presenter, @@ -660,7 +660,7 @@ namespace dxvk { uint64_t frameId, uint32_t rectCount, const VkRectLayerKHR* rects, - DxvkSubmitStatus* status); + uint64_t submissionId); /** * \brief Submits a command list @@ -670,13 +670,13 @@ namespace dxvk { * \param [in] commandList The command list to submit * \param [in] tracker Latency tracker * \param [in] frameId Frame ID - * \param [out] status Submission feedback + * \param [in] submissionId Submission timeline */ void submitCommandList( const Rc& commandList, const Rc& tracker, uint64_t frameId, - DxvkSubmitStatus* status); + uint64_t submissionId); /** * \brief Locks submission queue @@ -712,12 +712,10 @@ namespace dxvk { } /** - * \brief Waits for a given submission - * - * \param [in,out] status Submission status - * \returns Result of the submission + * \brief Waits for a given submission to be sent to the GPU + * \param [in] submission Submission timeline to wait for */ - VkResult waitForSubmission(DxvkSubmitStatus* status); + void waitForSubmission(uint64_t submissionId); /** * \brief Waits for a fence to become signaled diff --git a/src/dxvk/dxvk_queue.cpp b/src/dxvk/dxvk_queue.cpp index d760b383..221f444b 100644 --- a/src/dxvk/dxvk_queue.cpp +++ b/src/dxvk/dxvk_queue.cpp @@ -47,7 +47,7 @@ namespace dxvk { void DxvkSubmissionQueue::submit( DxvkSubmitInfo submitInfo, DxvkLatencyInfo latencyInfo, - DxvkSubmitStatus* status) { + uint64_t submissionId) { std::unique_lock lock(m_mutex); m_finishCond.wait(lock, [this] { @@ -55,7 +55,7 @@ namespace dxvk { }); DxvkSubmitEntry entry = { }; - entry.status = status; + entry.timeline = submissionId; entry.submit = std::move(submitInfo); entry.latency = std::move(latencyInfo); @@ -67,11 +67,11 @@ namespace dxvk { void DxvkSubmissionQueue::present( DxvkPresentInfo presentInfo, DxvkLatencyInfo latencyInfo, - DxvkSubmitStatus* status) { + uint64_t submissionId) { std::unique_lock lock(m_mutex); DxvkSubmitEntry entry = { }; - entry.status = status; + entry.timeline = submissionId; entry.present = std::move(presentInfo); entry.latency = std::move(latencyInfo); @@ -80,13 +80,8 @@ namespace dxvk { } - void DxvkSubmissionQueue::synchronizeSubmission( - DxvkSubmitStatus* status) { - std::unique_lock lock(m_mutex); - - m_submitCond.wait(lock, [status] { - return status->result.load() != VK_NOT_READY; - }); + void DxvkSubmissionQueue::synchronizeSubmission(uint64_t submissionId) { + m_submitTimeline.wait(submissionId); } @@ -149,8 +144,11 @@ namespace dxvk { entry = std::move(m_submitQueue.front()); } - // Submit command buffer to device - if (m_lastError != VK_ERROR_DEVICE_LOST) { + // Submit command buffer to device. Don't submit anything after + // device loss so that drivers get a chance to recover + VkResult status = m_lastError; + + if (status != VK_ERROR_DEVICE_LOST) { std::lock_guard lock(m_mutexQueue); if (m_callback) @@ -164,20 +162,20 @@ namespace dxvk { trackedSubmitId = entry.latency.frameId; } - entry.result = entry.submit.cmdList->submit( + status = entry.submit.cmdList->submit( m_semaphores, m_timelines, trackedSubmitId); entry.timelines = m_timelines; } else if (entry.present.presenter != nullptr) { if (entry.latency.tracker) entry.latency.tracker->notifyQueuePresentBegin(entry.latency.frameId); - entry.result = entry.present.presenter->presentImage( + status = entry.present.presenter->presentImage( entry.present.frameId, entry.latency.tracker, entry.present.rects.size(), entry.present.rects.data()); if (entry.latency.tracker) { entry.latency.tracker->notifyQueuePresentEnd( - entry.latency.frameId, entry.result); + entry.latency.frameId, status); trackedPresentId = entry.latency.frameId; trackedSubmitId = 0u; @@ -186,31 +184,27 @@ namespace dxvk { if (m_callback) m_callback(false); - } else { - // Don't submit anything after device loss - // so that drivers get a chance to recover - entry.result = VK_ERROR_DEVICE_LOST; } - if (entry.status) - entry.status->result = entry.result; + if (entry.timeline) + m_submitTimeline.signal(entry.timeline); - if (entry.result == VK_ERROR_DEVICE_LOST && m_checkpoints) + if (status == VK_ERROR_DEVICE_LOST && m_checkpoints) m_checkpoints->printHangInfo(); // On success, pass it on to the queue thread { std::unique_lock lock(m_mutex); - bool doForward = (entry.result == VK_SUCCESS) || - (entry.present.presenter != nullptr && entry.result != VK_ERROR_DEVICE_LOST); + bool doForward = (status == VK_SUCCESS) || + (entry.present.presenter && status != VK_ERROR_DEVICE_LOST); if (doForward) { m_finishQueue.push(std::move(entry)); } else { - Logger::err(str::format("DxvkSubmissionQueue: Command submission failed: ", entry.result)); - m_lastError = entry.result; + Logger::err(str::format("DxvkSubmissionQueue: Command submission failed: ", status)); + m_lastError = status; - if (m_lastError != VK_ERROR_DEVICE_LOST) + if (status != VK_ERROR_DEVICE_LOST) m_device->waitForIdle(); } diff --git a/src/dxvk/dxvk_queue.h b/src/dxvk/dxvk_queue.h index 5e056902..bd3ddf0d 100644 --- a/src/dxvk/dxvk_queue.h +++ b/src/dxvk/dxvk_queue.h @@ -4,6 +4,8 @@ #include #include +#include "../util/sync/sync_signal.h" + #include "../util/thread.h" #include "dxvk_cmdlist.h" @@ -15,17 +17,6 @@ namespace dxvk { class DxvkDevice; class DxvkCheckpointBuffer; - /** - * \brief Submission status - * - * Stores the result of a queue - * submission or a present call. - */ - struct DxvkSubmitStatus { - std::atomic result = { VK_SUCCESS }; - }; - - /** * \brief Queue submission info * @@ -44,8 +35,8 @@ namespace dxvk { * a swap chain image on the device. */ struct DxvkPresentInfo { - Rc presenter; - uint64_t frameId; + Rc presenter = nullptr; + uint64_t frameId = 0u; small_vector rects; }; @@ -66,8 +57,7 @@ namespace dxvk { * \brief Submission queue entry */ struct DxvkSubmitEntry { - VkResult result; - DxvkSubmitStatus* status; + uint64_t timeline = 0u; DxvkSubmitInfo submit; DxvkPresentInfo present; DxvkLatencyInfo latency; @@ -124,7 +114,7 @@ namespace dxvk { void submit( DxvkSubmitInfo submitInfo, DxvkLatencyInfo latencyInfo, - DxvkSubmitStatus* status); + uint64_t submissionId); /** * \brief Presents an image synchronously @@ -139,17 +129,15 @@ namespace dxvk { void present( DxvkPresentInfo presentInfo, DxvkLatencyInfo latencyInfo, - DxvkSubmitStatus* status); + uint64_t submissionId); /** * \brief Synchronizes with one queue submission * - * Waits for the result of the given submission - * or present operation to become available. - * \param [in,out] status Submission status + * Waits for the given submission to be performed. + * \param [in] uint64_t submissionId */ - void synchronizeSubmission( - DxvkSubmitStatus* status); + void synchronizeSubmission(uint64_t submissionId); /** * \brief Synchronizes with queue submissions @@ -221,6 +209,8 @@ namespace dxvk { dxvk::thread m_submitThread; dxvk::thread m_finishThread; + sync::Fence m_submitTimeline; + void submitCmdLists(); void finishCmdLists(); From b6b80629dd113b37b998f103fd5ead7409aa2e19 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 9 Oct 2026 17:15:24 +0200 Subject: [PATCH 006/135] [dxvk] Always compute absolute frame target time Works around an issue where relative timing doesn't actually work on Windows when opting out of FSE. --- src/dxvk/dxvk_presenter.cpp | 23 +++++++++++++---------- src/dxvk/dxvk_presenter.h | 16 ++++++++-------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 643f7c5d..efd637f2 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -175,8 +175,6 @@ namespace dxvk { const VkRectLayerKHR* rects) { PresenterSync& currSync = m_semaphores.at(m_frameIndex); - uint64_t frameDeadline = 0u; - VkPresentIdKHR presentId = { VK_STRUCTURE_TYPE_PRESENT_ID_KHR }; presentId.swapchainCount = 1; presentId.pPresentIds = &frameId; @@ -207,6 +205,8 @@ namespace dxvk { bool waitForPresent = m_hasPresentWait && isFifoMode; + uint64_t frameTargetTime = 0u; + VkPresentTimingInfoEXT timingInfo = { VK_STRUCTURE_TYPE_PRESENT_TIMING_INFO_EXT }; timingInfo.presentStageQueries = m_timingMode.presentStage; timingInfo.timeDomainId = m_timingMode.timeDomainId; @@ -214,19 +214,22 @@ namespace dxvk { if (m_timingMode.presentStage && isFifoMode) { std::lock_guard lock(m_timingMutex); + if (m_timingMode.referenceFrameId) { + frameTargetTime = m_timingMode.referenceTime + + (frameId - m_timingMode.referenceFrameId) * m_timingMode.frameIntervalNs; + } + if (m_timingMode.relativeTiming) { timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_RELATIVE_TIME_BIT_EXT; timingInfo.targetTime = m_timingMode.frameIntervalNs; timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; } else if (m_timingMode.absoluteTiming && m_timingMode.referenceFrameId) { - timingInfo.targetTime = m_timingMode.referenceTime + (frameId - m_timingMode.referenceFrameId) * m_timingMode.frameIntervalNs; + timingInfo.targetTime = frameTargetTime; timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; if (m_timingDisplayInfo && !m_timingDisplayInfo->isVariableRefresh) timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_NEAREST_REFRESH_CYCLE_BIT_EXT; - frameDeadline = timingInfo.targetTime + m_timingMode.frameIntervalNs; - // Skip present_wait in fixed refresh mode if the frame is timed if (!m_timingDisplayInfo || !m_timingDisplayInfo->isVariableRefresh) waitForPresent = waitForPresent && !timingInfo.targetTime; @@ -298,8 +301,8 @@ namespace dxvk { frame.tracker = tracker; frame.mode = m_presentMode; frame.result = status; - frame.targetTime = frameDeadline ? timingInfo.targetTime : 0u; - frame.deadline = frameDeadline; + frame.targetTime = frameTargetTime; + frame.targetDeadline = frameTargetTime ? frameTargetTime + m_timingMode.frameIntervalNs : 0u; frame.isTimed = bool(timingInfo.targetTime); frame.doWait = waitForPresent; @@ -1649,7 +1652,7 @@ namespace dxvk { // absolute timing was used to control the actual presentation. for (const auto& frame : m_frameQueue) { if (frame.frameId == report.presentId) - hasMissedDeadline = reportTimeLocal > frame.deadline; + hasMissedDeadline = reportTimeLocal > frame.targetDeadline; } } } @@ -1661,7 +1664,7 @@ namespace dxvk { // We can't give meaningful feedback w/o QPC timing currently. // TODO figure out correct time domain for dxvk-native if we're - // reslly interested, otherwise just ignore the problem. + // really interested, otherwise just ignore the problem. if (hasQpcDomain()) { feedback.frameId = m_timingMode.lastFrameId; feedback.presentTime = m_timingMode.lastFrameTimeQpc; @@ -2026,7 +2029,7 @@ namespace dxvk { // Apply FPS limiter here to align it as closely with scanout as we can, // and delay signaling the frame latency event to emulate behaviour of a // low refresh rate display as closely as we can. - if (updatePresentTiming(frame.frameId) && frame.isTimed) + if (updatePresentTiming(frame.frameId) && frame.isTimed && frame.targetTime) waitUntilFrameTargetTime(frame); else m_fpsLimiter.delay(); diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index d470c2dd..266d4c42 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -56,14 +56,14 @@ namespace dxvk { * \brief Queued frame */ struct PresenterFrame { - uint64_t frameId = 0u; - Rc tracker = nullptr; - VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR; - VkResult result = VK_NOT_READY; - uint64_t targetTime = 0u; - uint64_t deadline = 0u; - bool isTimed = false; - bool doWait = false; + uint64_t frameId = 0u; + Rc tracker = nullptr; + VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR; + VkResult result = VK_NOT_READY; + uint64_t targetTime = 0u; + uint64_t targetDeadline = 0u; + bool isTimed = false; + bool doWait = false; }; /** From 290c8039edb3e8c9f845f818989049db1a6c4f91 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 9 Oct 2026 17:16:10 +0200 Subject: [PATCH 007/135] [dxvk] Prefer absolute timing for present timing --- src/dxvk/dxvk_presenter.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index efd637f2..067b4b65 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -1463,11 +1463,13 @@ namespace dxvk { return; } - // Probe relative timing first since that is most likely to give us - // consistent pacing, without any setup work required from our side. - if (m_timingMode.supportsRelative) { + if (m_timingMode.supportsAbsolute) { + // Always prefer absolute timimg if available. We need absolute timestamps + // anyway in order to synchronize and return feedback to the application. + m_timingMode.absoluteTiming = true; + } else if (m_timingMode.supportsRelative) { if (m_timingDisplayInfo->isVariableRefresh) { - // Always enable relative timing for VRR + // In VRR mode, presentation should be driving display refresh m_timingMode.relativeTiming = true; } else if (m_timingDisplayInfo->refreshIntervalNs) { // Otherwise, check if the frame duration is reasonably close @@ -1482,10 +1484,6 @@ namespace dxvk { } } - // Fall back to absolute timing if we cannot use relative timimg. - if (m_timingMode.supportsAbsolute) - m_timingMode.absoluteTiming = !m_timingMode.relativeTiming; - // Reset reference time and frame ID for absolute timing // so that we don't end up submitting bogus timestamps. m_timingMode.referenceTime = 0u; From c7bc784fa1e341942b5bfb76765b0b92c9e9b16e Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 9 Oct 2026 20:19:07 +0200 Subject: [PATCH 008/135] [dxvk] Explicitly pass time domain ID around to handle frame reports --- src/dxvk/dxvk_presenter.cpp | 26 ++++++++++++++++++-------- src/dxvk/dxvk_presenter.h | 1 + 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 067b4b65..0563a5d4 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -303,6 +303,7 @@ namespace dxvk { frame.result = status; frame.targetTime = frameTargetTime; frame.targetDeadline = frameTargetTime ? frameTargetTime + m_timingMode.frameIntervalNs : 0u; + frame.timingDomainId = timingInfo.timeDomainId; frame.isTimed = bool(timingInfo.targetTime); frame.doWait = waitForPresent; @@ -1629,9 +1630,23 @@ namespace dxvk { if (!report.reportComplete || !time.time || time.stage != m_timingMode.presentStage) continue; + // Find queued frame entry so we can properly correlate the report + // with expected timings + const PresenterFrame* frameEntry = nullptr; + + for (const auto& frame : m_frameQueue) { + if (frame.frameId == report.presentId) + frameEntry = &frame; + } + + if (!frameEntry || frameEntry->timingDomainId != m_timingMode.timeDomainId) { + Logger::warn(str::format("Presenter: Skipping report for frame ", report.presentId)); + continue; + } + uint64_t reportTimeLocal = translateTimestamp( report.timeDomain, report.timeDomainId, time.time, - VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, m_timingMode.timeDomainId); + VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, frameEntry->timingDomainId); uint64_t reportTimeQpc = 0u; @@ -1646,12 +1661,7 @@ namespace dxvk { m_timingMode.lastFrameTimeLocal = reportTimeLocal; m_timingMode.lastFrameTimeQpc = reportTimeQpc; - // Implicitly handles the case where deadline is 0, i.e. no - // absolute timing was used to control the actual presentation. - for (const auto& frame : m_frameQueue) { - if (frame.frameId == report.presentId) - hasMissedDeadline = reportTimeLocal > frame.targetDeadline; - } + hasMissedDeadline = reportTimeLocal > frameEntry->targetDeadline; } } @@ -1693,7 +1703,7 @@ namespace dxvk { { std::lock_guard lock(m_timingMutex); if (hasQpcDomain()) { qpcTargetTime = translateTimestamp( - VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, m_timingMode.timeDomainId, frame.targetTime, + VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, frame.timingDomainId, frame.targetTime, VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR, 0u); } } diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 266d4c42..17334f04 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -62,6 +62,7 @@ namespace dxvk { VkResult result = VK_NOT_READY; uint64_t targetTime = 0u; uint64_t targetDeadline = 0u; + uint64_t timingDomainId = 0u; bool isTimed = false; bool doWait = false; }; From ca53d543f0ad86e3d002eaf7c711f22681626e89 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 9 Oct 2026 21:41:49 +0200 Subject: [PATCH 009/135] [dxvk] Be more robust against broken QPC calibration --- src/dxvk/dxvk_presenter.cpp | 150 +++++++++++++++++++++++++++--------- src/dxvk/dxvk_presenter.h | 12 ++- 2 files changed, 124 insertions(+), 38 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 0563a5d4..731567fb 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -1443,7 +1443,7 @@ namespace dxvk { m_timingMode.absoluteTiming = false; // Can't meaningfully do timing if we don't know about display timing - if (!m_timingDomains || !m_timingDisplayInfo) + if (!m_timingDomains || !m_timingDisplayInfo || m_calibrationFailed) return; // Compute target frame interval from frame rate limit @@ -1501,9 +1501,9 @@ namespace dxvk { } - void Presenter::recalibrateTimeDomains() { + bool Presenter::recalibrateTimeDomains() { if (!m_timingDomains) - return; + return false; // Calibrate all known time domains at once auto& domains = m_timingDomains->domains; @@ -1529,23 +1529,12 @@ namespace dxvk { } } - // Retry calibration until we get reasonable precision (150us) - constexpr uint32_t MaxAttempts = 5u; - constexpr uint64_t MaxDeviation = 1500000u; + VkResult status = getCalibratedTimestamps(domains.size(), + calibrationInfo.data(), timestamps.data()); - uint64_t maxDeviation = 0u; - - for (uint32_t i = 0u; i < MaxAttempts; i++) { - VkResult status = m_vkd->vkGetCalibratedTimestampsKHR(m_vkd->device(), - domains.size(), calibrationInfo.data(), timestamps.data(), &maxDeviation); - - if (status && !i) { - Logger::warn(str::format("Presenter: Failed to calibrate timestamps: ", status)); - return; - } - - if (status || maxDeviation <= MaxDeviation) - break; + if (status != VK_SUCCESS) { + m_timingDomains->lastCalibration = dxvk::high_resolution_clock::time_point(); + return false; } for (size_t i = 0u; i < domains.size(); i++) @@ -1554,6 +1543,7 @@ namespace dxvk { // Remember when we last calibrated everything so that we can periodically // re-query. Clock drift is a real issue on certain hardware configurations. m_timingDomains->lastCalibration = dxvk::high_resolution_clock::now(); + return true; } @@ -1611,7 +1601,8 @@ namespace dxvk { updateMode = true; updateTimingDomains(); - recalibrateTimeDomains(); + if (!recalibrateTimeDomains()) + return false; } if (updateMode) @@ -1673,7 +1664,7 @@ namespace dxvk { // We can't give meaningful feedback w/o QPC timing currently. // TODO figure out correct time domain for dxvk-native if we're // really interested, otherwise just ignore the problem. - if (hasQpcDomain()) { + if (m_timingMode.lastFrameTimeQpc) { feedback.frameId = m_timingMode.lastFrameId; feedback.presentTime = m_timingMode.lastFrameTimeQpc; } @@ -1693,10 +1684,10 @@ namespace dxvk { } - void Presenter::waitUntilFrameTargetTime( + bool Presenter::waitUntilFrameTargetTime( const PresenterFrame& frame) { if (!frame.targetTime) - return; + return false; uint64_t qpcTargetTime = 0u; @@ -1708,10 +1699,12 @@ namespace dxvk { } } - if (qpcTargetTime) { - auto dxvkTargetTime = dxvk::high_resolution_clock::get_time_from_counter(qpcTargetTime); - Sleep::sleepUntil(dxvk::high_resolution_clock::now(), dxvkTargetTime); - } + if (!qpcTargetTime) + return false; + + auto dxvkTargetTime = dxvk::high_resolution_clock::get_time_from_counter(qpcTargetTime); + Sleep::sleepUntil(dxvk::high_resolution_clock::now(), dxvkTargetTime); + return true; } @@ -1757,8 +1750,8 @@ namespace dxvk { uint64_t calibrationAgeNs = std::chrono::duration_cast( dxvk::high_resolution_clock::now() - m_timingDomains->lastCalibration).count(); - if (calibrationAgeNs > 1000000000ull) - recalibrateTimeDomains(); + if (calibrationAgeNs > 1000000000ull && !recalibrateTimeDomains()) + return 0u; // Check whether the source and destination time domains are known // and compute the delta in terms of nanoseconds or QPC ticks. @@ -1809,10 +1802,8 @@ namespace dxvk { // Try to get reference timestamps for the two domains std::array timestamps = { }; - uint64_t maxDeviation = 0u; - - VkResult status = m_vkd->vkGetCalibratedTimestampsKHR(m_vkd->device(), - calibrationInfo.size(), calibrationInfo.data(), timestamps.data(), &maxDeviation); + VkResult status = getCalibratedTimestamps(calibrationInfo.size(), + calibrationInfo.data(), timestamps.data()); if (status) { Logger::err(str::format("Presenter: Failed to map timestamp from ", @@ -1882,6 +1873,88 @@ namespace dxvk { } + VkResult Presenter::getCalibratedTimestamps( + uint32_t timestampCount, + const VkCalibratedTimestampInfoKHR* pTimestampInfos, + uint64_t* pTimestamps) { + // See below, don't spam the Vulkan call if we can't properly + // calibrate anyway. + if (m_calibrationFailed) + return VK_ERROR_UNKNOWN; + + // Retry calibration until we get reasonable precision (150us) + constexpr uint32_t MaxAttempts = 5u; + constexpr uint64_t MaxDeviation = 1500000u; + + uint64_t maxDeviation = 0u; + + uint64_t t0 = 0u; + uint64_t t1 = 0u; + + for (uint32_t i = 0u; i < MaxAttempts; i++) { + t0 = dxvk::high_resolution_clock::get_counter(); + VkResult status = m_vkd->vkGetCalibratedTimestampsKHR(m_vkd->device(), + timestampCount, pTimestampInfos, pTimestamps, &maxDeviation); + t1 = dxvk::high_resolution_clock::get_counter(); + + if (status && !i) + return status; + + if (status || maxDeviation <= MaxDeviation) + break; + } + + if (env::isWineVulkan()) { + // Upstream wine currently uses different clock sources for win32 QPC + // functions and the corresponding Vulkan function, so verify that the + // timestamps make sense... + uint64_t cpuTime = t0 + (t1 - t0) / 2u; + + for (uint32_t i = 0u; i < timestampCount; i++) { + if (pTimestampInfos[i].timeDomain == VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR) { + auto tMin = dxvk::high_resolution_clock::get_time_from_counter(std::min(cpuTime, pTimestamps[i])); + auto tMax = dxvk::high_resolution_clock::get_time_from_counter(std::max(cpuTime, pTimestamps[i])); + + uint64_t deltaNs = std::chrono::duration_cast(tMax - tMin).count(); + + if (deltaNs > 2u * std::max(maxDeviation, MaxDeviation)) { + // On Mesa drivers and Nvidia, we can reasonably assume that we get + // a "current" timestamp from the driver, so just using our own time + // should be fine. On other drivers, be conservative and error out. + auto driver = m_device->properties().vk12.driverID; + + bool isTrustedDriver = driver == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA + || driver == VK_DRIVER_ID_MESA_RADV + || driver == VK_DRIVER_ID_MESA_LLVMPIPE + || driver == VK_DRIVER_ID_MESA_TURNIP + || driver == VK_DRIVER_ID_MESA_NVK + || driver == VK_DRIVER_ID_NVIDIA_PROPRIETARY; + + if (!std::exchange(m_calibrationWarned, true)) { + uint64_t ms = deltaNs / 100000u; + + Logger::warn(str::format("Presenter: Got QPC delta of ", + (ms / 10u), ".", (ms % 10u), "ms, can't trust calibration?")); + + if (!isTrustedDriver) + Logger::err("Presenter: Timestamp calibration inaccurate, skipping."); + } + + if (!isTrustedDriver) { + m_calibrationFailed = true; + return VK_ERROR_UNKNOWN; + } + + pTimestamps[i] = cpuTime; + } + } + } + } + + return VK_SUCCESS; + } + + void Presenter::destroySwapchain() { // Without present fence support, waiting for the queue or device to go idle // is the only way to properly synchronize swapchain teardown. Care must be @@ -1932,6 +2005,9 @@ namespace dxvk { m_timingDomains = std::nullopt; m_timingDisplayInfo = std::nullopt; m_timingMode = PresenterTimingInfo(); + + m_calibrationFailed = false; + m_calibrationWarned = false; } @@ -2037,9 +2113,11 @@ namespace dxvk { // Apply FPS limiter here to align it as closely with scanout as we can, // and delay signaling the frame latency event to emulate behaviour of a // low refresh rate display as closely as we can. - if (updatePresentTiming(frame.frameId) && frame.isTimed && frame.targetTime) - waitUntilFrameTargetTime(frame); - else + bool waited = updatePresentTiming(frame.frameId) + && frame.isTimed && frame.targetTime + && waitUntilFrameTargetTime(frame); + + if (!waited) m_fpsLimiter.delay(); // Wake up any thread that may be waiting for the queue to become empty diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 17334f04..d444ab91 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -370,6 +370,9 @@ namespace dxvk { VkSurfaceFormatKHR m_preferredFormat = { }; uint32_t m_preferredSyncInterval = 1u; + bool m_calibrationWarned = false; + bool m_calibrationFailed = false; + bool m_dirtySwapchain = false; bool m_dirtySurface = false; @@ -479,14 +482,14 @@ namespace dxvk { void updateTimingMode(); - void recalibrateTimeDomains(); + bool recalibrateTimeDomains(); bool updatePresentTiming(uint64_t frameId); void commitTimingFeedback( const PresenterTimingFeedback& feedback); - void waitUntilFrameTargetTime( + bool waitUntilFrameTargetTime( const PresenterFrame& frame); bool hasQpcDomain(); @@ -502,6 +505,11 @@ namespace dxvk { VkResult createLatencySemaphore(); + VkResult getCalibratedTimestamps( + uint32_t timestampCount, + const VkCalibratedTimestampInfoKHR* pTimestampInfos, + uint64_t* pTimestamps); + void destroySwapchain(); void destroySurface(); From 74b557a1f2d88d53cca2aefa97096ee7a5e7001e Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 9 Oct 2026 22:02:44 +0200 Subject: [PATCH 010/135] [dxvk] Re-enable present timing by default --- src/dxvk/dxvk_options.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_options.cpp b/src/dxvk/dxvk_options.cpp index 52e7b915..e9aef0ed 100644 --- a/src/dxvk/dxvk_options.cpp +++ b/src/dxvk/dxvk_options.cpp @@ -14,7 +14,7 @@ namespace dxvk { enableImplicitResolves = config.getOption ("dxvk.enableImplicitResolves", true); enableNvRawAccessChains = config.getOption ("dxvk.enableNvRawAccessChains", true); enableNvCudaInterop = config.getOption ("dxvk.enableNvCudaInterop", true); - enablePresentTiming = config.getOption ("dxvk.enablePresentTiming", false); + enablePresentTiming = config.getOption ("dxvk.enablePresentTiming", true); trackPipelineLifetime = config.getOption("dxvk.trackPipelineLifetime", Tristate::Auto); useRawSsbo = config.getOption("dxvk.useRawSsbo", Tristate::Auto); hud = config.getOption("dxvk.hud", ""); From 081ee5c3864cdfad0f84eaf5d7a387ad8af9017f Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 8 Oct 2026 15:55:55 +0200 Subject: [PATCH 011/135] [meta] Update dxbc-spirv Adds pass to resolve overlapping buffer loads. Proton: Deadlock (1422450) --- src/dxvk/dxvk_shader_ir.cpp | 1 + subprojects/dxbc-spirv | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_shader_ir.cpp b/src/dxvk/dxvk_shader_ir.cpp index 85fd0aef..0558840a 100644 --- a/src/dxvk/dxvk_shader_ir.cpp +++ b/src/dxvk/dxvk_shader_ir.cpp @@ -2260,6 +2260,7 @@ namespace dxvk { options.derivativeOptions.hoistDescriptorLoads = true; options.cseOptions.relocateDescriptorLoad = true; + options.cseOptions.resolveOverlappingLoads = true; if (m_info.options.spirv.test(DxvkShaderSpirvFlag::SupportsResourceIndexing)) options.descriptorIndexing.optimizeDescriptorIndexing = true; diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index c7f06970..ecb8308f 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit c7f069701227dcb800a01bd93e42f8001b7543e7 +Subproject commit ecb8308fe03fcad695a6ebdacbef6e7f61e77484 From 71ec7b5be6c517db39bcca4d38f38a63f6ddbc56 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 10 Oct 2026 16:51:52 +0200 Subject: [PATCH 012/135] [dxvk] Fix broken negative timeout for keyed mutex --- src/dxvk/dxvk_image.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_image.cpp b/src/dxvk/dxvk_image.cpp index 3b5b7390..3a181b9a 100644 --- a/src/dxvk/dxvk_image.cpp +++ b/src/dxvk/dxvk_image.cpp @@ -57,19 +57,22 @@ namespace dxvk { return DXGI_ERROR_INVALID_CALL; LARGE_INTEGER timeout = { }; + timeout.QuadPart = int64_t(milliseconds) * -10000; + D3DKMT_ACQUIREKEYEDMUTEX acquire = { }; acquire.hKeyedMutex = m_kmtLocal; acquire.Key = key; acquire.pTimeout = &timeout; - timeout.QuadPart = milliseconds * -10000; NTSTATUS status = D3DKMTAcquireKeyedMutex(&acquire); + if (status == STATUS_TIMEOUT) return WAIT_TIMEOUT; if (status) return DXGI_ERROR_INVALID_CALL; VkSemaphore semaphore = m_fence->handle(); + VkSemaphoreWaitInfo info = { VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO }; info.semaphoreCount = 1; info.pSemaphores = &semaphore; From 2c94c5f25cf75c313ef2a55b2e0c9276d67c28d6 Mon Sep 17 00:00:00 2001 From: Blisto91 <47954800+Blisto91@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:16:47 +0200 Subject: [PATCH 013/135] [util] Only spoof vendor id in New Vegas Prevents the confusion from listing every GPU as a 6700 XT in the launchers Graphics Adapter list --- src/util/config/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp index a92edbd8..7be947ec 100644 --- a/src/util/config/config.cpp +++ b/src/util/config/config.cpp @@ -958,7 +958,7 @@ namespace dxvk { * depth buffer resolves */ { R"(\\Fallout( -)? New Vegas.*\\(FalloutNV)?(Launcher)?\.exe$)", {{ { "d3d9.floatEmulation", "Strict" }, - { "d3d9.hideNvidiaGpu", "True" }, + { "d3d9.customVendorId", "1002" }, }} }, /* Dungeons and Dragons: Dragonshard * * Massive FPS decreases in some scenes */ From 822a7fca2830a8e2e72f3e90d2bf6274eff454f9 Mon Sep 17 00:00:00 2001 From: Pranay Sanghai Date: Sun, 11 Oct 2026 14:29:38 -0400 Subject: [PATCH 014/135] [dxvk] Use COM instead of dynamic_cast for D3D11 Views --- src/d3d11/d3d11_context.cpp | 47 ++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/d3d11/d3d11_context.cpp b/src/d3d11/d3d11_context.cpp index 487bcf58..6fd48cc2 100644 --- a/src/d3d11/d3d11_context.cpp +++ b/src/d3d11/d3d11_context.cpp @@ -191,11 +191,25 @@ namespace dxvk { if (!pResourceView || (NumRects && pRects)) return; - // ID3D11View has no methods to query the exact type of - // the view, so we'll have to check each possible class - auto dsv = dynamic_cast(pResourceView); - auto rtv = dynamic_cast(pResourceView); - auto uav = dynamic_cast(pResourceView); + // ID3D11View has no methods to query the exact type of the view, + // so we'll have to check each possible class. Query through COM + // rather than dynamic_cast: dynamic_cast relies on RTTI metadata + // stored just before the object's vtable, which third-party + // vtable-swapping hooks (e.g. overlay/injector DLLs that clone + // only the visible function-pointer table) do not preserve, even + // though they preserve the table itself well enough for ordinary + // virtual calls like QueryInterface to keep working correctly. + Com qiDsv; + Com qiRtv; + Com qiUav; + + pResourceView->QueryInterface(IID_PPV_ARGS(&qiDsv)); + pResourceView->QueryInterface(IID_PPV_ARGS(&qiRtv)); + pResourceView->QueryInterface(IID_PPV_ARGS(&qiUav)); + + auto dsv = static_cast(qiDsv.ptr()); + auto rtv = static_cast(qiRtv.ptr()); + auto uav = static_cast(qiUav.ptr()); if (rtv && rtv->GetBufferView()) return; @@ -725,12 +739,23 @@ namespace dxvk { AddCost(GpuCostEstimate::Transfer); - // ID3D11View has no methods to query the exact type of - // the view, so we'll have to check each possible class - auto dsv = dynamic_cast(pView); - auto rtv = dynamic_cast(pView); - auto uav = dynamic_cast(pView); - auto vov = dynamic_cast(pView); + // ID3D11View has no methods to query the exact type of the view, + // so we'll have to check each possible class. Query through COM + // rather than dynamic_cast; see DiscardViewBase for why. + Com qiDsv; + Com qiRtv; + Com qiUav; + Com qiVov; + + pView->QueryInterface(IID_PPV_ARGS(&qiDsv)); + pView->QueryInterface(IID_PPV_ARGS(&qiRtv)); + pView->QueryInterface(IID_PPV_ARGS(&qiUav)); + pView->QueryInterface(IID_PPV_ARGS(&qiVov)); + + auto dsv = static_cast(qiDsv.ptr()); + auto rtv = static_cast(qiRtv.ptr()); + auto uav = static_cast(qiUav.ptr()); + auto vov = static_cast(qiVov.ptr()); // Retrieve underlying resource view if (dsv) { From 434e2ce744ad70c00643da3365e718cf64dbba61 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 3 Oct 2026 14:08:01 +0300 Subject: [PATCH 015/135] [d3d8] Properly handle X1R5G5B5 in CheckDeviceType --- src/d3d8/d3d8_interface.cpp | 10 ++++----- src/d3d8/d3d8_interface.h | 44 +++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/d3d8/d3d8_interface.cpp b/src/d3d8/d3d8_interface.cpp index bd36e8b6..b3ed0f48 100644 --- a/src/d3d8/d3d8_interface.cpp +++ b/src/d3d8/d3d8_interface.cpp @@ -99,10 +99,10 @@ namespace dxvk { return D3DERR_INVALIDCALL; } - pMode->Width = m_adapterModes[Adapter][Mode].Width; - pMode->Height = m_adapterModes[Adapter][Mode].Height; - pMode->RefreshRate = m_adapterModes[Adapter][Mode].RefreshRate; - pMode->Format = D3DFORMAT(m_adapterModes[Adapter][Mode].Format); + pMode->Width = m_adapterModes[Adapter][Mode].Width; + pMode->Height = m_adapterModes[Adapter][Mode].Height; + pMode->RefreshRate = m_adapterModes[Adapter][Mode].RefreshRate; + pMode->Format = static_cast(m_adapterModes[Adapter][Mode].Format); return D3D_OK; } @@ -127,7 +127,7 @@ namespace dxvk { d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters); res = m_d3d9->CreateDevice( Adapter, - (d3d9::D3DDEVTYPE)DeviceType, + static_cast(DeviceType), hFocusWindow, BehaviorFlags, ¶ms, diff --git a/src/d3d8/d3d8_interface.h b/src/d3d8/d3d8_interface.h index ceecc9e8..7cf9d2a9 100644 --- a/src/d3d8/d3d8_interface.h +++ b/src/d3d8/d3d8_interface.h @@ -4,6 +4,7 @@ #include "d3d8_util.h" #include "d3d8_options.h" #include "d3d8_format.h" + #include "../d3d9/d3d9_bridge.h" namespace dxvk { @@ -68,15 +69,20 @@ namespace dxvk { D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) { - // Ignore the bWindowed parameter when querying D3D9. D3D8 does - // identical validations between windowed and fullscreen modes, adhering - // to the stricter fullscreen adapter and back buffer format validations. + // D3D8 adheres to the stricter fullscreen adapter and back buffer format + // validations, so generally ignore the bWindowed value and use FALSE. + // + // The only exception to this rule is D3DFMT_X1R5G5B5, which is available + // as a windowed back buffer format for the same adapter format. + const BOOL windowed = AdapterFormat == D3DFMT_X1R5G5B5 && + BackBufferFormat == D3DFMT_X1R5G5B5 ? bWindowed : FALSE; + return m_d3d9->CheckDeviceType( - Adapter, - (d3d9::D3DDEVTYPE)DevType, - (d3d9::D3DFORMAT)AdapterFormat, - (d3d9::D3DFORMAT)BackBufferFormat, - FALSE + Adapter, + static_cast(DevType), + static_cast(AdapterFormat), + static_cast(BackBufferFormat), + windowed ); } @@ -95,11 +101,11 @@ namespace dxvk { return m_d3d9->CheckDeviceFormat( Adapter, - (d3d9::D3DDEVTYPE)DeviceType, - (d3d9::D3DFORMAT)AdapterFormat, + static_cast(DeviceType), + static_cast(AdapterFormat), Usage, - (d3d9::D3DRESOURCETYPE)RType, - (d3d9::D3DFORMAT)CheckFormat + static_cast(RType), + static_cast(CheckFormat) ); } @@ -112,10 +118,10 @@ namespace dxvk { DWORD* pQualityLevels = nullptr; return m_d3d9->CheckDeviceMultiSampleType( Adapter, - (d3d9::D3DDEVTYPE)DeviceType, - (d3d9::D3DFORMAT)SurfaceFormat, + static_cast(DeviceType), + static_cast(SurfaceFormat), Windowed, - (d3d9::D3DMULTISAMPLE_TYPE)MultiSampleType, + static_cast(MultiSampleType), pQualityLevels ); } @@ -135,10 +141,10 @@ namespace dxvk { return m_d3d9->CheckDepthStencilMatch( Adapter, - (d3d9::D3DDEVTYPE)DeviceType, - (d3d9::D3DFORMAT)AdapterFormat, - (d3d9::D3DFORMAT)RenderTargetFormat, - (d3d9::D3DFORMAT)DepthStencilFormat + static_cast(DeviceType), + static_cast(AdapterFormat), + static_cast(RenderTargetFormat), + static_cast(DepthStencilFormat) ); } From b2c2622c4f512af18bd5d7b0867ff807e8e5d8d5 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 3 Oct 2026 14:57:19 +0300 Subject: [PATCH 016/135] [d3d9] Fix window handle device resets in fullscreen mode --- src/d3d9/d3d9_swapchain.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 824fd25c..86609a64 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -629,6 +629,12 @@ namespace dxvk { bool changeFullscreen = m_presentParams.Windowed != pPresentParams->Windowed; if (m_window != pPresentParams->hDeviceWindow) { + if (!m_presentParams.Windowed) { + this->LeaveFullscreenMode(); + if (!pPresentParams->Windowed) + changeFullscreen = true; + } + m_window = pPresentParams->hDeviceWindow; m_displayRefreshRateDirty = true; } From 496d635a82ac0148ceb2fab5eafd9cb4e4b59820 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 3 Oct 2026 18:41:20 +0300 Subject: [PATCH 017/135] [d3d8] Use static casts for DWORD backed enum conversions --- src/d3d8/d3d8_batch.h | 2 +- src/d3d8/d3d8_device.cpp | 91 +++++++++++++++++++++++-------------- src/d3d8/d3d8_device.h | 8 ++-- src/d3d8/d3d8_swapchain.cpp | 2 +- src/d3d8/d3d8_texture.cpp | 6 +-- src/d3d8/d3d8_util.h | 24 +++++----- 6 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/d3d8/d3d8_batch.h b/src/d3d8/d3d8_batch.h index 98904c20..686d26a6 100644 --- a/src/d3d8/d3d8_batch.h +++ b/src/d3d8/d3d8_batch.h @@ -111,7 +111,7 @@ namespace dxvk { index -= draw.MinVertex; m_device->DrawIndexedPrimitiveUP( - d3d9::D3DPRIMITIVETYPE(draw.PrimitiveType), + static_cast(draw.PrimitiveType), 0, draw.MaxVertex - draw.MinVertex, draw.PrimitiveCount, diff --git a/src/d3d8/d3d8_device.cpp b/src/d3d8/d3d8_device.cpp index 04ff8c6f..7d70d762 100644 --- a/src/d3d8/d3d8_device.cpp +++ b/src/d3d8/d3d8_device.cpp @@ -246,7 +246,11 @@ namespace dxvk { if (iBackBuffer >= m_backBuffers.size() || m_backBuffers[iBackBuffer] == nullptr) { Com pSurface9; - HRESULT res = GetD3D9()->GetBackBuffer(0, iBackBuffer, (d3d9::D3DBACKBUFFER_TYPE)Type, &pSurface9); + HRESULT res = GetD3D9()->GetBackBuffer( + 0, + iBackBuffer, + static_cast(Type), + &pSurface9); if (unlikely(FAILED(res))) return res; @@ -302,8 +306,8 @@ namespace dxvk { Height, Levels, Usage, - d3d9::D3DFORMAT(Format), - d3d9::D3DPOOL(Pool), + static_cast(Format), + static_cast(Pool), &pTex9, NULL); if (unlikely(FAILED(res))) @@ -345,10 +349,13 @@ namespace dxvk { Com pVolume9; HRESULT res = GetD3D9()->CreateVolumeTexture( - Width, Height, Depth, Levels, + Width, + Height, + Depth, + Levels, Usage, - d3d9::D3DFORMAT(Format), - d3d9::D3DPOOL(Pool), + static_cast(Format), + static_cast(Pool), &pVolume9, NULL); if (unlikely(FAILED(res))) @@ -391,8 +398,8 @@ namespace dxvk { EdgeLength, Levels, Usage, - d3d9::D3DFORMAT(Format), - d3d9::D3DPOOL(Pool), + static_cast(Format), + static_cast(Pool), &pCube9, NULL); if (unlikely(FAILED(res))) @@ -424,7 +431,7 @@ namespace dxvk { // Mark all D3DPOOL_DEFAULT D3DUSAGE_WRITEONLY buffers as // D3DUSAGE_DYNAMIC, to ensure they're directly mapped if (unlikely(m_d3d8Options.forceLegacyBuffers - && d3d9::D3DPOOL(Pool) == d3d9::D3DPOOL_DEFAULT + && Pool == D3DPOOL_DEFAULT && (Usage & D3DUSAGE_WRITEONLY))) Usage |= D3DUSAGE_DYNAMIC; @@ -434,7 +441,13 @@ namespace dxvk { } Com pVertexBuffer9; - HRESULT res = GetD3D9()->CreateVertexBuffer(Length, Usage, FVF, d3d9::D3DPOOL(Pool), &pVertexBuffer9, NULL); + HRESULT res = GetD3D9()->CreateVertexBuffer( + Length, + Usage, + FVF, + static_cast(Pool), + &pVertexBuffer9, + NULL); if (unlikely(FAILED(res))) return res; @@ -457,12 +470,18 @@ namespace dxvk { // Mark all D3DPOOL_DEFAULT D3DUSAGE_WRITEONLY buffers as // D3DUSAGE_DYNAMIC, to ensure they're directly mapped if (unlikely(m_d3d8Options.forceLegacyBuffers - && d3d9::D3DPOOL(Pool) == d3d9::D3DPOOL_DEFAULT + && Pool == D3DPOOL_DEFAULT && (Usage & D3DUSAGE_WRITEONLY))) Usage |= D3DUSAGE_DYNAMIC; Com pIndexBuffer9; - HRESULT res = GetD3D9()->CreateIndexBuffer(Length, Usage, d3d9::D3DFORMAT(Format), d3d9::D3DPOOL(Pool), &pIndexBuffer9, NULL); + HRESULT res = GetD3D9()->CreateIndexBuffer( + Length, + Usage, + static_cast(Format), + static_cast(Pool), + &pIndexBuffer9, + NULL); if (unlikely(FAILED(res))) return res; @@ -498,8 +517,8 @@ namespace dxvk { HRESULT res = GetD3D9()->CreateRenderTarget( Width, Height, - d3d9::D3DFORMAT(Format), - d3d9::D3DMULTISAMPLE_TYPE(MultiSample), + static_cast(Format), + static_cast(MultiSample), 0, Lockable, &pSurf9, @@ -535,8 +554,8 @@ namespace dxvk { HRESULT res = GetD3D9()->CreateDepthStencilSurface( Width, Height, - d3d9::D3DFORMAT(Format), - d3d9::D3DMULTISAMPLE_TYPE(MultiSample), + static_cast(Format), + static_cast(MultiSample), 0, FALSE, // z-buffer discarding is not used in D3D8 &pSurf9, @@ -569,15 +588,15 @@ namespace dxvk { if (unlikely(isD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; - const bool isSupportedSurfaceFormat = m_bridge->IsSupportedSurfaceFormat(d3d9::D3DFORMAT(Format)); - const D3DPOOL pool = isSupportedSurfaceFormat ? D3DPOOL_SYSTEMMEM : D3DPOOL_SCRATCH; + const bool isSupportedSurfaceFormat = m_bridge->IsSupportedSurfaceFormat(static_cast(Format)); + D3DPOOL pool = isSupportedSurfaceFormat ? D3DPOOL_SYSTEMMEM : D3DPOOL_SCRATCH; Com pSurf; HRESULT res = GetD3D9()->CreateOffscreenPlainSurface( Width, Height, - d3d9::D3DFORMAT(Format), - d3d9::D3DPOOL(pool), + static_cast(Format), + static_cast(pool), &pSurf, NULL); if (unlikely(FAILED(res))) @@ -600,7 +619,7 @@ namespace dxvk { HRESULT res = D3D_OK; D3DLOCKED_RECT srcLocked, dstLocked; - const bool compressed = isDXTFormat(D3DFORMAT(srcDesc.Format)); + const bool compressed = isDXTFormat(static_cast(srcDesc.Format)); res = src->LockRect(&srcLocked, &srcRect, D3DLOCK_READONLY); if (unlikely(FAILED(res))) @@ -720,7 +739,7 @@ namespace dxvk { // This method cannot be applied to surfaces whose formats // are classified as depth stencil formats. - if (unlikely(isDepthStencilFormat(D3DFORMAT(srcDesc.Format)))) + if (unlikely(isDepthStencilFormat(static_cast(srcDesc.Format)))) return D3DERR_INVALIDCALL; StateChange(); @@ -809,7 +828,7 @@ namespace dxvk { HRESULT res = CreateImageSurface( srcDesc.Width, srcDesc.Height, - D3DFORMAT(srcDesc.Format), + static_cast(srcDesc.Format), &pTempImageSurface ); if (unlikely(FAILED(res))) @@ -1226,7 +1245,7 @@ namespace dxvk { } Com pStateBlock9; - HRESULT res = GetD3D9()->CreateStateBlock(d3d9::D3DSTATEBLOCKTYPE(Type), &pStateBlock9); + HRESULT res = GetD3D9()->CreateStateBlock(static_cast(Type), &pStateBlock9); if (unlikely(FAILED(res))) return res; @@ -1417,7 +1436,7 @@ namespace dxvk { // if the type has been remapped to a sampler state type: return GetD3D9()->GetSamplerState(Stage, stateType, pValue); } else { - return GetD3D9()->GetTextureStageState(Stage, d3d9::D3DTEXTURESTAGESTATETYPE(Type), pValue); + return GetD3D9()->GetTextureStageState(Stage, static_cast(Type), pValue); } } @@ -1439,7 +1458,7 @@ namespace dxvk { // if the type has been remapped to a sampler state type: return GetD3D9()->SetSamplerState(Stage, stateType, Value); } else { - return GetD3D9()->SetTextureStageState(Stage, d3d9::D3DTEXTURESTAGESTATETYPE(Type), Value); + return GetD3D9()->SetTextureStageState(Stage, static_cast(Type), Value); } } @@ -1473,7 +1492,11 @@ namespace dxvk { if (unlikely(ShouldBatch())) return m_batcher->DrawPrimitive(PrimitiveType, StartVertex, PrimitiveCount); - return GetD3D9()->DrawPrimitive(d3d9::D3DPRIMITIVETYPE(PrimitiveType), StartVertex, PrimitiveCount); + + return GetD3D9()->DrawPrimitive( + static_cast(PrimitiveType), + StartVertex, + PrimitiveCount); } HRESULT STDMETHODCALLTYPE D3D8Device::DrawIndexedPrimitive( @@ -1485,7 +1508,7 @@ namespace dxvk { D3D8DeviceLock lock = LockDevice(); return GetD3D9()->DrawIndexedPrimitive( - d3d9::D3DPRIMITIVETYPE(PrimitiveType), + static_cast(PrimitiveType), static_cast(std::min(m_baseVertexIndex, // set by SetIndices() static_cast(std::numeric_limits::max()))), MinVertexIndex, @@ -1507,7 +1530,7 @@ namespace dxvk { m_streams[0] = D3D8VBO {nullptr, 0}; return GetD3D9()->DrawPrimitiveUP( - d3d9::D3DPRIMITIVETYPE(PrimitiveType), + static_cast(PrimitiveType), PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride); @@ -1532,12 +1555,12 @@ namespace dxvk { m_baseVertexIndex = 0; return GetD3D9()->DrawIndexedPrimitiveUP( - d3d9::D3DPRIMITIVETYPE(PrimitiveType), + static_cast(PrimitiveType), MinVertexIndex, NumVertices, PrimitiveCount, pIndexData, - d3d9::D3DFORMAT(IndexDataFormat), + static_cast(IndexDataFormat), pVertexStreamZeroData, VertexStreamZeroStride); } @@ -1700,7 +1723,7 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D8Device::SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) { D3D8DeviceLock lock = LockDevice(); - d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(State); + d3d9::D3DRENDERSTATETYPE State9 = static_cast(State); switch (State) { // Most render states translate 1:1 to D3D9 @@ -1775,7 +1798,7 @@ namespace dxvk { if (unlikely(pValue == nullptr)) return D3DERR_INVALIDCALL; - d3d9::D3DRENDERSTATETYPE State9 = d3d9::D3DRENDERSTATETYPE(State); + d3d9::D3DRENDERSTATETYPE State9 = static_cast(State); switch (State) { // Most render states translate 1:1 to D3D9 @@ -1961,7 +1984,7 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D8Device::DeleteVertexShader(DWORD Handle) { D3D8DeviceLock lock = LockDevice(); - if (!isFVF(Handle)) { + if (likely(!isFVF(Handle))) { D3D8VertexShaderInfo* info = getVertexShaderInfo(this, Handle); if (unlikely(!info)) diff --git a/src/d3d8/d3d8_device.h b/src/d3d8/d3d8_device.h index b36ad188..21e825fb 100644 --- a/src/d3d8/d3d8_device.h +++ b/src/d3d8/d3d8_device.h @@ -442,7 +442,7 @@ namespace dxvk { Com m_parent; D3DPRESENT_PARAMETERS m_presentParams; - + // Value of D3DRS_LINEPATTERN D3DLINEPATTERN m_linePattern = { }; // Value of D3DRS_ZVISIBLE (although the RS is not supported, its value is stored) @@ -475,10 +475,12 @@ namespace dxvk { Com m_renderTarget; Com m_depthStencil; + // can be a FVF or a programmable VS index (marked by D3DFVF_RESERVED0) + DWORD m_currentVertexShader = 0; std::vector m_vertexShaders; + + DWORD m_currentPixelShader = 0; std::vector> m_pixelShaders; - DWORD m_currentVertexShader = 0; // can be FVF or vs index (marked by D3DFVF_RESERVED0) - DWORD m_currentPixelShader = 0; D3DDEVTYPE m_deviceType; HWND m_window; diff --git a/src/d3d8/d3d8_swapchain.cpp b/src/d3d8/d3d8_swapchain.cpp index 88baff6d..601b15c4 100644 --- a/src/d3d8/d3d8_swapchain.cpp +++ b/src/d3d8/d3d8_swapchain.cpp @@ -24,7 +24,7 @@ namespace dxvk { // Same logic as in D3D8Device::GetBackBuffer if (BackBuffer >= m_backBuffers.size() || m_backBuffers[BackBuffer] == nullptr) { Com pSurface9; - HRESULT res = GetD3D9()->GetBackBuffer(BackBuffer, (d3d9::D3DBACKBUFFER_TYPE)Type, &pSurface9); + HRESULT res = GetD3D9()->GetBackBuffer(BackBuffer, static_cast(Type), &pSurface9); if (unlikely(FAILED(res))) return res; diff --git a/src/d3d8/d3d8_texture.cpp b/src/d3d8/d3d8_texture.cpp index 63240e3c..62c1ecb4 100644 --- a/src/d3d8/d3d8_texture.cpp +++ b/src/d3d8/d3d8_texture.cpp @@ -153,7 +153,7 @@ namespace dxvk { const RECT* pRect, DWORD Flags) { return GetD3D9()->LockRect( - d3d9::D3DCUBEMAP_FACES(Face), + static_cast(Face), Level, reinterpret_cast(pLockedRect), pRect, @@ -161,11 +161,11 @@ namespace dxvk { } HRESULT STDMETHODCALLTYPE D3D8TextureCube::UnlockRect(D3DCUBEMAP_FACES Face, UINT Level) { - return GetD3D9()->UnlockRect(d3d9::D3DCUBEMAP_FACES(Face), Level); + return GetD3D9()->UnlockRect(static_cast(Face), Level); } HRESULT STDMETHODCALLTYPE D3D8TextureCube::AddDirtyRect(D3DCUBEMAP_FACES Face, const RECT* pDirtyRect) { - return GetD3D9()->AddDirtyRect(d3d9::D3DCUBEMAP_FACES(Face), pDirtyRect); + return GetD3D9()->AddDirtyRect(static_cast(Face), pDirtyRect); } } \ No newline at end of file diff --git a/src/d3d8/d3d8_util.h b/src/d3d8/d3d8_util.h index ddeae164..a17c6779 100644 --- a/src/d3d8/d3d8_util.h +++ b/src/d3d8/d3d8_util.h @@ -93,10 +93,10 @@ namespace dxvk { d3d9::D3DPRESENT_PARAMETERS params; params.BackBufferWidth = pParams->BackBufferWidth; params.BackBufferHeight = pParams->BackBufferHeight; - params.BackBufferFormat = d3d9::D3DFORMAT(pParams->BackBufferFormat); + params.BackBufferFormat = static_cast(pParams->BackBufferFormat); params.BackBufferCount = pParams->BackBufferCount; - params.MultiSampleType = d3d9::D3DMULTISAMPLE_TYPE(pParams->MultiSampleType); + params.MultiSampleType = static_cast(pParams->MultiSampleType); // MultiSampleQuality is only used with D3DMULTISAMPLE_NONMASKABLE, which is not available in D3D8 params.MultiSampleQuality = 0; @@ -122,11 +122,11 @@ namespace dxvk { } } - params.SwapEffect = d3d9::D3DSWAPEFFECT(SwapEffect); + params.SwapEffect = static_cast(SwapEffect); params.hDeviceWindow = pParams->hDeviceWindow; params.Windowed = pParams->Windowed; params.EnableAutoDepthStencil = pParams->EnableAutoDepthStencil; - params.AutoDepthStencilFormat = d3d9::D3DFORMAT(pParams->AutoDepthStencilFormat); + params.AutoDepthStencilFormat = static_cast(pParams->AutoDepthStencilFormat); params.Flags = pParams->Flags; // D3DPRESENT_RATE_UNLIMITED is unsupported, use D3DPRESENT_RATE_DEFAULT (or 0) @@ -144,13 +144,13 @@ namespace dxvk { // (8<-9) Convert D3DSURFACE_DESC inline void ConvertSurfaceDesc8(const d3d9::D3DSURFACE_DESC* pSurf9, D3DSURFACE_DESC* pSurf8) { - pSurf8->Format = D3DFORMAT(pSurf9->Format); - pSurf8->Type = D3DRESOURCETYPE(pSurf9->Type); + pSurf8->Format = static_cast(pSurf9->Format); + pSurf8->Type = static_cast(pSurf9->Type); pSurf8->Usage = pSurf9->Usage; - pSurf8->Pool = D3DPOOL(pSurf9->Pool); + pSurf8->Pool = static_cast(pSurf9->Pool); pSurf8->Size = getSurfaceSize(pSurf8->Format, pSurf9->Width, pSurf9->Height); - pSurf8->MultiSampleType = D3DMULTISAMPLE_TYPE(pSurf9->MultiSampleType); + pSurf8->MultiSampleType = static_cast(pSurf9->MultiSampleType); // DX8: No multisample quality pSurf8->Width = pSurf9->Width; pSurf8->Height = pSurf9->Height; @@ -158,10 +158,10 @@ namespace dxvk { // (8<-9) Convert D3DVOLUME_DESC inline void ConvertVolumeDesc8(const d3d9::D3DVOLUME_DESC* pVol9, D3DVOLUME_DESC* pVol8) { - pVol8->Format = D3DFORMAT(pVol9->Format); - pVol8->Type = D3DRESOURCETYPE(pVol9->Type); + pVol8->Format = static_cast(pVol9->Format); + pVol8->Type = static_cast(pVol9->Type); pVol8->Usage = pVol9->Usage; - pVol8->Pool = D3DPOOL(pVol9->Pool); + pVol8->Pool = static_cast(pVol9->Pool); pVol8->Size = getSurfaceSize(pVol8->Format, pVol9->Width, pVol9->Height) * pVol9->Depth; pVol8->Width = pVol9->Width; pVol8->Height = pVol9->Height; @@ -204,4 +204,4 @@ namespace dxvk { } } -} +} \ No newline at end of file From 3c9e4c3c7846c23765e41f8ff4070f5ea035e75b Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 3 Oct 2026 19:09:08 +0300 Subject: [PATCH 018/135] [d3d8] Use switch statements in all format helpers --- src/d3d8/d3d8_device.cpp | 54 ++++++++-------- src/d3d8/d3d8_device.h | 5 +- src/d3d8/d3d8_format.h | 133 ++++++++++++++++++++++---------------- src/d3d8/d3d8_interface.h | 10 +-- src/d3d8/d3d8_util.h | 10 +-- src/d3d9/d3d9_device.cpp | 17 ++--- src/d3d9/d3d9_format.h | 112 +++++++++++++++++++------------- 7 files changed, 194 insertions(+), 147 deletions(-) diff --git a/src/d3d8/d3d8_device.cpp b/src/d3d8/d3d8_device.cpp index 7d70d762..2e936574 100644 --- a/src/d3d8/d3d8_device.cpp +++ b/src/d3d8/d3d8_device.cpp @@ -293,7 +293,7 @@ namespace dxvk { if (unlikely(ppTexture == nullptr)) return D3DERR_INVALIDCALL; - if (unlikely(isD3D9ExclusiveFormat(Format))) + if (unlikely(IsD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; // Nvidia & Intel workaround for The Lord of the Rings: The Fellowship of the Ring @@ -344,7 +344,7 @@ namespace dxvk { if (unlikely(ppVolumeTexture == nullptr)) return D3DERR_INVALIDCALL; - if (unlikely(isD3D9ExclusiveFormat(Format))) + if (unlikely(IsD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; Com pVolume9; @@ -390,7 +390,7 @@ namespace dxvk { if (unlikely(ppCubeTexture == nullptr)) return D3DERR_INVALIDCALL; - if (unlikely(isD3D9ExclusiveFormat(Format))) + if (unlikely(IsD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; Com pCube9; @@ -507,10 +507,10 @@ namespace dxvk { if (unlikely(ppSurface == nullptr)) return D3DERR_INVALIDCALL; - if (unlikely(isD3D9ExclusiveFormat(Format))) + if (unlikely(IsD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; - if (unlikely(!isRenderTargetFormat(Format))) + if (unlikely(!IsRenderTargetFormat(Format))) return D3DERR_INVALIDCALL; Com pSurf9; @@ -547,7 +547,7 @@ namespace dxvk { if (unlikely(ppSurface == nullptr)) return D3DERR_INVALIDCALL; - if (unlikely(isD3D9ExclusiveFormat(Format))) + if (unlikely(IsD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; Com pSurf9; @@ -585,7 +585,7 @@ namespace dxvk { // CreateImageSurface is generally guaranteed to succeed even with unsupported // formats, however D3D9 exclusive formats fail on native D3D8. - if (unlikely(isD3D9ExclusiveFormat(Format))) + if (unlikely(IsD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; const bool isSupportedSurfaceFormat = m_bridge->IsSupportedSurfaceFormat(static_cast(Format)); @@ -619,7 +619,7 @@ namespace dxvk { HRESULT res = D3D_OK; D3DLOCKED_RECT srcLocked, dstLocked; - const bool compressed = isDXTFormat(static_cast(srcDesc.Format)); + const bool compressed = IsDXTFormat(static_cast(srcDesc.Format)); res = src->LockRect(&srcLocked, &srcRect, D3DLOCK_READONLY); if (unlikely(FAILED(res))) @@ -739,7 +739,7 @@ namespace dxvk { // This method cannot be applied to surfaces whose formats // are classified as depth stencil formats. - if (unlikely(isDepthStencilFormat(static_cast(srcDesc.Format)))) + if (unlikely(IsDepthStencilFormat(static_cast(srcDesc.Format)))) return D3DERR_INVALIDCALL; StateChange(); @@ -1397,7 +1397,7 @@ namespace dxvk { if (tex) { D3DSURFACE_DESC surf; tex->GetLevelDesc(0, &surf); - if (isDepthStencilFormat(surf.Format)) { + if (IsDepthStencilFormat(surf.Format)) { // If we bound a depth texture to stage 0 then we need to set the projected flag for stage 0 and 1 // Stage 1 is a non-depth light cookie texture but still requires perspective divide to work GetD3D9()->SetTextureStageState(0, d3d9::D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_PROJECTED); @@ -1887,13 +1887,13 @@ namespace dxvk { } // Set bit to indicate this is not an FVF - *pHandle = getShaderHandle(m_vertexShaders.size()); + *pHandle = GetShaderHandle(m_vertexShaders.size()); return D3D_OK; } - inline D3D8VertexShaderInfo* getVertexShaderInfo(D3D8Device* device, DWORD Handle) { - Handle = getShaderIndex(Handle); + inline D3D8VertexShaderInfo* GetVertexShaderInfo(D3D8Device* device, DWORD Handle) { + Handle = GetShaderIndex(Handle); if (unlikely(Handle >= device->m_vertexShaders.size())) { Logger::warn(str::format("D3D8Device: Invalid vertex shader handle ", std::hex, Handle)); return nullptr; @@ -1918,8 +1918,8 @@ namespace dxvk { } // Check for extra bit that indicates this is not an FVF - if (!isFVF(Handle)) { - D3D8VertexShaderInfo* info = getVertexShaderInfo(this, Handle); + if (!IsFVF(Handle)) { + D3D8VertexShaderInfo* info = GetVertexShaderInfo(this, Handle); if (unlikely(!info)) return D3DERR_INVALIDCALL; @@ -1972,7 +1972,7 @@ namespace dxvk { D3D8VertexShaderInfo& info = m_vertexShaders[i]; if (info.pVertexShader == pVertexShader) { - *pHandle = getShaderHandle(i); + *pHandle = GetShaderHandle(i); return D3D_OK; } } @@ -1984,8 +1984,8 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D8Device::DeleteVertexShader(DWORD Handle) { D3D8DeviceLock lock = LockDevice(); - if (likely(!isFVF(Handle))) { - D3D8VertexShaderInfo* info = getVertexShaderInfo(this, Handle); + if (likely(!IsFVF(Handle))) { + D3D8VertexShaderInfo* info = GetVertexShaderInfo(this, Handle); if (unlikely(!info)) return D3DERR_INVALIDCALL; @@ -2005,7 +2005,7 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderDeclaration(DWORD Handle, void* pData, DWORD* pSizeOfData) { D3D8DeviceLock lock = LockDevice(); - D3D8VertexShaderInfo* pInfo = getVertexShaderInfo(this, Handle); + D3D8VertexShaderInfo* pInfo = GetVertexShaderInfo(this, Handle); if (unlikely(!pInfo)) return D3DERR_INVALIDCALL; @@ -2035,7 +2035,7 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData) { D3D8DeviceLock lock = LockDevice(); - D3D8VertexShaderInfo* pInfo = getVertexShaderInfo(this, Handle); + D3D8VertexShaderInfo* pInfo = GetVertexShaderInfo(this, Handle); if (unlikely(!pInfo)) return D3DERR_INVALIDCALL; @@ -2080,13 +2080,13 @@ namespace dxvk { m_pixelShaders.push_back(std::move(pPixelShader)); // Still set the shader bit, to prevent conflicts with NULL. - *pHandle = getShaderHandle(m_pixelShaders.size()); + *pHandle = GetShaderHandle(m_pixelShaders.size()); return D3D_OK; } - inline d3d9::IDirect3DPixelShader9* getPixelShaderPtr(D3D8Device* device, DWORD Handle) { - Handle = getShaderIndex(Handle); + inline d3d9::IDirect3DPixelShader9* GetPixelShaderPtr(D3D8Device* device, DWORD Handle) { + Handle = GetShaderIndex(Handle); if (unlikely(Handle >= device->m_pixelShaders.size())) { Logger::warn(str::format("D3D8Device: Invalid pixel shader handle ", std::hex, Handle)); @@ -2116,7 +2116,7 @@ namespace dxvk { return GetD3D9()->SetPixelShader(nullptr); } - d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle); + d3d9::IDirect3DPixelShader9* pPixelShader = GetPixelShaderPtr(this, Handle); if (unlikely(!pPixelShader)) return D3DERR_INVALIDCALL; @@ -2147,12 +2147,12 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D8Device::DeletePixelShader(DWORD Handle) { D3D8DeviceLock lock = LockDevice(); - d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle); + d3d9::IDirect3DPixelShader9* pPixelShader = GetPixelShaderPtr(this, Handle); if (unlikely(!pPixelShader)) return D3DERR_INVALIDCALL; - m_pixelShaders[getShaderIndex(Handle)] = nullptr; + m_pixelShaders[GetShaderIndex(Handle)] = nullptr; if (m_currentPixelShader == Handle) m_currentPixelShader = 0; @@ -2163,7 +2163,7 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData) { D3D8DeviceLock lock = LockDevice(); - d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle); + d3d9::IDirect3DPixelShader9* pPixelShader = GetPixelShaderPtr(this, Handle); if (unlikely(!pPixelShader)) return D3DERR_INVALIDCALL; diff --git a/src/d3d8/d3d8_device.h b/src/d3d8/d3d8_device.h index 21e825fb..6006a48a 100644 --- a/src/d3d8/d3d8_device.h +++ b/src/d3d8/d3d8_device.h @@ -431,8 +431,9 @@ namespace dxvk { m_validTextures.erase(textureIter); } - friend d3d9::IDirect3DPixelShader9* getPixelShaderPtr(D3D8Device* device, DWORD Handle); - friend D3D8VertexShaderInfo* getVertexShaderInfo(D3D8Device* device, DWORD Handle); + friend d3d9::IDirect3DPixelShader9* GetPixelShaderPtr(D3D8Device* device, DWORD Handle); + + friend D3D8VertexShaderInfo* GetVertexShaderInfo(D3D8Device* device, DWORD Handle); private: diff --git a/src/d3d8/d3d8_format.h b/src/d3d8/d3d8_format.h index e90f6730..f9851716 100644 --- a/src/d3d8/d3d8_format.h +++ b/src/d3d8/d3d8_format.h @@ -4,68 +4,93 @@ namespace dxvk { - inline bool isDXTFormat(D3DFORMAT fmt) { - return fmt == D3DFMT_DXT1 - || fmt == D3DFMT_DXT2 - || fmt == D3DFMT_DXT3 - || fmt == D3DFMT_DXT4 - || fmt == D3DFMT_DXT5; + inline bool IsDXTFormat(D3DFORMAT fmt) { + switch (fmt) { + case D3DFMT_DXT1: + case D3DFMT_DXT2: + case D3DFMT_DXT3: + case D3DFMT_DXT4: + case D3DFMT_DXT5: + return true; + default: + return false; + } } - inline bool isDepthStencilFormat(D3DFORMAT fmt) { - return fmt == D3DFMT_D16_LOCKABLE - || fmt == D3DFMT_D16 - || fmt == D3DFMT_D32 - || fmt == D3DFMT_D15S1 - || fmt == D3DFMT_D24X4S4 - || fmt == D3DFMT_D24S8 - || fmt == D3DFMT_D24X8; + inline bool IsDepthStencilFormat(D3DFORMAT fmt) { + switch (fmt) { + case D3DFMT_D16_LOCKABLE: + case D3DFMT_D16: + case D3DFMT_D32: + case D3DFMT_D15S1: + case D3DFMT_D24X4S4: + case D3DFMT_D24S8: + case D3DFMT_D24X8: + return true; + default: + return false; + } } - // The d3d8 documentation states: Render target formats are restricted to + // The D3D8 documentation states: Render target formats are restricted to // D3DFMT_X1R5G5B5, D3DFMT_R5G6B5, D3DFMT_X8R8G8B8, and D3DFMT_A8R8G8B8. // This limited RT format support is confirmed by age-accurate drivers. - inline bool isRenderTargetFormat(D3DFORMAT fmt) { - return fmt == D3DFMT_X1R5G5B5 - || fmt == D3DFMT_R5G6B5 - || fmt == D3DFMT_X8R8G8B8 - || fmt == D3DFMT_A8R8G8B8 - // NULL format support was later added to d3d9 with - // GeForce 6 series cards, and also advertised in d3d8. - || fmt == (D3DFORMAT) MAKEFOURCC('N', 'U', 'L', 'L'); + inline bool IsRenderTargetFormat(D3DFORMAT fmt) { + // NULL format support was later added to D3D9 and is also advertised + // in D3D8, though it's unlikely to ever have been used in practice. + if (unlikely(fmt == static_cast(MAKEFOURCC('N', 'U', 'L', 'L')))) + return true; + + switch (fmt) { + case D3DFMT_X1R5G5B5: + case D3DFMT_R5G6B5: + case D3DFMT_X8R8G8B8: + case D3DFMT_A8R8G8B8: + return true; + default: + return false; + } } // Some games will exhaustively query all formats in the 0-100 range, - // so filter out some known formats which are exclusive to d3d9 - inline bool isD3D9ExclusiveFormat(D3DFORMAT fmt) { - const d3d9::D3DFORMAT d3d9Fmt = d3d9::D3DFORMAT(fmt); + // so filter out some known formats which are exclusive to D3D9. + inline bool IsD3D9ExclusiveFormat(D3DFORMAT fmt) { + d3d9::D3DFORMAT fmt9 = static_cast(fmt); - return d3d9Fmt == d3d9::D3DFMT_A8B8G8R8 //32 - || d3d9Fmt == d3d9::D3DFMT_X8B8G8R8 //33 - || d3d9Fmt == d3d9::D3DFMT_A2R10G10B10 //35 - || d3d9Fmt == d3d9::D3DFMT_A16B16G16R16 //36 - || d3d9Fmt == d3d9::D3DFMT_L16 //81 - || d3d9Fmt == d3d9::D3DFMT_D32F_LOCKABLE //82 - || d3d9Fmt == d3d9::D3DFMT_D24FS8 //83 - || d3d9Fmt == d3d9::D3DFMT_D32_LOCKABLE //84 - || d3d9Fmt == d3d9::D3DFMT_S8_LOCKABLE //85 - || d3d9Fmt == d3d9::D3DFMT_Q16W16V16U16 //110 - || d3d9Fmt == d3d9::D3DFMT_R16F //111 - || d3d9Fmt == d3d9::D3DFMT_G16R16F //112 - || d3d9Fmt == d3d9::D3DFMT_A16B16G16R16F //113 - || d3d9Fmt == d3d9::D3DFMT_R32F //114 - || d3d9Fmt == d3d9::D3DFMT_G32R32F //115 - || d3d9Fmt == d3d9::D3DFMT_A32B32G32R32F //116 - || d3d9Fmt == d3d9::D3DFMT_CxV8U8 //117 - || d3d9Fmt == d3d9::D3DFMT_A1 //118 - || d3d9Fmt == d3d9::D3DFMT_A2B10G10R10_XR_BIAS //119 - || d3d9Fmt == (d3d9::D3DFORMAT) MAKEFOURCC('D', 'F', '1', '6') - || d3d9Fmt == (d3d9::D3DFORMAT) MAKEFOURCC('D', 'F', '2', '4') - || d3d9Fmt == (d3d9::D3DFORMAT) MAKEFOURCC('I', 'N', 'T', 'Z'); + // Get some very unlikely FOURCCs out of the way first + if (unlikely(fmt9 == static_cast(MAKEFOURCC('D', 'F', '1', '6')) + || fmt9 == static_cast(MAKEFOURCC('D', 'F', '2', '4')) + || fmt9 == static_cast(MAKEFOURCC('I', 'N', 'T', 'Z')))) + return true; + + switch (fmt9) { + case d3d9::D3DFMT_A8B8G8R8: //32 + case d3d9::D3DFMT_X8B8G8R8: //33 + case d3d9::D3DFMT_A2R10G10B10: //35 + case d3d9::D3DFMT_A16B16G16R16: //36 + case d3d9::D3DFMT_L16: //81 + case d3d9::D3DFMT_D32F_LOCKABLE: //82 + case d3d9::D3DFMT_D24FS8: //83 + case d3d9::D3DFMT_D32_LOCKABLE: //84 + case d3d9::D3DFMT_S8_LOCKABLE: //85 + case d3d9::D3DFMT_Q16W16V16U16: //110 + case d3d9::D3DFMT_R16F: //111 + case d3d9::D3DFMT_G16R16F: //112 + case d3d9::D3DFMT_A16B16G16R16F: //113 + case d3d9::D3DFMT_R32F: //114 + case d3d9::D3DFMT_G32R32F: //115 + case d3d9::D3DFMT_A32B32G32R32F: //116 + case d3d9::D3DFMT_CxV8U8: //117 + case d3d9::D3DFMT_A1: //118 + case d3d9::D3DFMT_A2B10G10R10_XR_BIAS: //119 + return true; + default: + return false; + } } // Get bytes per pixel (or 4x4 block for DXT) - inline UINT getFormatStride(D3DFORMAT fmt) { + inline UINT GetFormatStride(D3DFORMAT fmt) { switch (fmt) { default: case D3DFMT_UNKNOWN: @@ -118,12 +143,12 @@ namespace dxvk { } } - inline UINT getSurfaceSize(D3DFORMAT Format, UINT Width, UINT Height) { - if (isDXTFormat(Format)) { - Width = ((Width + 3) >> 2); + inline UINT GetSurfaceSize(D3DFORMAT Format, UINT Width, UINT Height) { + if (IsDXTFormat(Format)) { + Width = ((Width + 3) >> 2); Height = ((Height + 3) >> 2); } - return Width * Height * getFormatStride(Format); + return Width * Height * GetFormatStride(Format); } -} +} \ No newline at end of file diff --git a/src/d3d8/d3d8_interface.h b/src/d3d8/d3d8_interface.h index 7cf9d2a9..c56bea8c 100644 --- a/src/d3d8/d3d8_interface.h +++ b/src/d3d8/d3d8_interface.h @@ -93,10 +93,10 @@ namespace dxvk { DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) { - if (unlikely(isD3D9ExclusiveFormat(CheckFormat))) + if (unlikely(IsD3D9ExclusiveFormat(CheckFormat))) return D3DERR_NOTAVAILABLE; - if (unlikely((Usage & D3DUSAGE_RENDERTARGET) && !isRenderTargetFormat(CheckFormat))) + if (unlikely((Usage & D3DUSAGE_RENDERTARGET) && !IsRenderTargetFormat(CheckFormat))) return D3DERR_NOTAVAILABLE; return m_d3d9->CheckDeviceFormat( @@ -132,11 +132,11 @@ namespace dxvk { D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) { - if (unlikely(isD3D9ExclusiveFormat(RenderTargetFormat) - || isD3D9ExclusiveFormat(DepthStencilFormat))) + if (unlikely(IsD3D9ExclusiveFormat(RenderTargetFormat) + || IsD3D9ExclusiveFormat(DepthStencilFormat))) return D3DERR_NOTAVAILABLE; - if (unlikely(!isRenderTargetFormat(RenderTargetFormat))) + if (unlikely(!IsRenderTargetFormat(RenderTargetFormat))) return D3DERR_NOTAVAILABLE; return m_d3d9->CheckDepthStencilMatch( diff --git a/src/d3d8/d3d8_util.h b/src/d3d8/d3d8_util.h index a17c6779..531bc587 100644 --- a/src/d3d8/d3d8_util.h +++ b/src/d3d8/d3d8_util.h @@ -148,7 +148,7 @@ namespace dxvk { pSurf8->Type = static_cast(pSurf9->Type); pSurf8->Usage = pSurf9->Usage; pSurf8->Pool = static_cast(pSurf9->Pool); - pSurf8->Size = getSurfaceSize(pSurf8->Format, pSurf9->Width, pSurf9->Height); + pSurf8->Size = GetSurfaceSize(pSurf8->Format, pSurf9->Width, pSurf9->Height); pSurf8->MultiSampleType = static_cast(pSurf9->MultiSampleType); // DX8: No multisample quality @@ -162,7 +162,7 @@ namespace dxvk { pVol8->Type = static_cast(pVol9->Type); pVol8->Usage = pVol9->Usage; pVol8->Pool = static_cast(pVol9->Pool); - pVol8->Size = getSurfaceSize(pVol8->Format, pVol9->Width, pVol9->Height) * pVol9->Depth; + pVol8->Size = GetSurfaceSize(pVol8->Format, pVol9->Width, pVol9->Height) * pVol9->Depth; pVol8->Width = pVol9->Width; pVol8->Height = pVol9->Height; pVol8->Depth = pVol9->Depth; @@ -188,15 +188,15 @@ namespace dxvk { } } - inline DWORD isFVF(DWORD Handle) { + inline DWORD IsFVF(DWORD Handle) { return (Handle & D3DFVF_RESERVED0) == 0; } - inline DWORD getShaderHandle(DWORD Index) { + inline DWORD GetShaderHandle(DWORD Index) { return (Index << 1) | D3DFVF_RESERVED0; } - inline DWORD getShaderIndex(DWORD Handle) { + inline DWORD GetShaderIndex(DWORD Handle) { if ((Handle & D3DFVF_RESERVED0) != 0) { return ((Handle & ~(D3DFVF_RESERVED0)) >> 1) - 1; } else { diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 5606986c..51788b58 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -624,9 +624,8 @@ namespace dxvk { desc.MultisampleQuality = 0; desc.IsBackBuffer = FALSE; desc.IsAttachmentOnly = FALSE; - // Docs: - // Textures placed in the D3DPOOL_DEFAULT pool cannot be locked - // unless they are dynamic textures or they are private, FOURCC, driver formats. + // Docs: "Textures placed in the D3DPOOL_DEFAULT pool cannot be locked + // unless they are dynamic textures or they are private, FOURCC, driver formats." desc.IsLockable = Pool != D3DPOOL_DEFAULT || (Usage & D3DUSAGE_DYNAMIC) || IsVendorFormat(EnumerateFormat(Format)); @@ -707,10 +706,9 @@ namespace dxvk { desc.MultisampleQuality = 0; desc.IsBackBuffer = FALSE; desc.IsAttachmentOnly = FALSE; - // Docs: - // Textures placed in the D3DPOOL_DEFAULT pool cannot be locked - // unless they are dynamic textures. Volume textures do not - // exempt private, FOURCC, driver formats from these checks. + // Docs: "Textures placed in the D3DPOOL_DEFAULT pool cannot be + // locked unless they are dynamic textures. Volume textures do + // not exempt private, FOURCC, driver formats from these checks." desc.IsLockable = Pool != D3DPOOL_DEFAULT || (Usage & D3DUSAGE_DYNAMIC); @@ -774,9 +772,8 @@ namespace dxvk { desc.MultisampleQuality = 0; desc.IsBackBuffer = FALSE; desc.IsAttachmentOnly = FALSE; - // Docs: - // Textures placed in the D3DPOOL_DEFAULT pool cannot be locked - // unless they are dynamic textures or they are private, FOURCC, driver formats. + // Docs: "Textures placed in the D3DPOOL_DEFAULT pool cannot be locked + // unless they are dynamic textures or they are private, FOURCC, driver formats." desc.IsLockable = Pool != D3DPOOL_DEFAULT || (Usage & D3DUSAGE_DYNAMIC) || IsVendorFormat(EnumerateFormat(Format)); diff --git a/src/d3d9/d3d9_format.h b/src/d3d9/d3d9_format.h index 3f2c4178..5a92d4fd 100644 --- a/src/d3d9/d3d9_format.h +++ b/src/d3d9/d3d9_format.h @@ -247,69 +247,93 @@ namespace dxvk { }; inline bool IsFourCCFormat(D3D9Format format) { - // BINARYBUFFER is the largest non-fourcc format + // BINARYBUFFER is the largest non-FOURCC format return format > D3D9Format::BINARYBUFFER; } inline bool IsVendorFormat(D3D9Format format) { - return IsFourCCFormat(format) - && format != D3D9Format::MULTI2_ARGB8 - && format != D3D9Format::UYVY - && format != D3D9Format::R8G8_B8G8 - && format != D3D9Format::YUY2 - && format != D3D9Format::G8R8_G8B8 - && format != D3D9Format::DXT1 - && format != D3D9Format::DXT2 - && format != D3D9Format::DXT3 - && format != D3D9Format::DXT4 - && format != D3D9Format::DXT5; + switch (format) { + case D3D9Format::MULTI2_ARGB8: + case D3D9Format::UYVY: + case D3D9Format::R8G8_B8G8: + case D3D9Format::YUY2: + case D3D9Format::G8R8_G8B8: + case D3D9Format::DXT1: + case D3D9Format::DXT2: + case D3D9Format::DXT3: + case D3D9Format::DXT4: + case D3D9Format::DXT5: + return false; + default: + return IsFourCCFormat(format); + } } inline bool IsDXTFormat(D3D9Format format) { - return format == D3D9Format::DXT1 - || format == D3D9Format::DXT2 - || format == D3D9Format::DXT3 - || format == D3D9Format::DXT4 - || format == D3D9Format::DXT5; + switch (format) { + case D3D9Format::DXT1: + case D3D9Format::DXT2: + case D3D9Format::DXT3: + case D3D9Format::DXT4: + case D3D9Format::DXT5: + return true; + default: + return false; + } } // D3D9 documentation says: IDirect3DSurface9::GetDC is valid on the following formats only: // D3DFMT_R5G6B5, D3DFMT_X1R5G5B5, D3DFMT_R8G8B8, and D3DFMT_X8R8G8B8. However, // the equivalent formats of D3DFMT_A1R5G5B5 and D3DFMT_A8R8G8B8 are also supported. inline bool IsSurfaceGetDCCompatibleFormat(D3D9Format format) { - return format == D3D9Format::R5G6B5 - || format == D3D9Format::X1R5G5B5 - || format == D3D9Format::A1R5G5B5 - || format == D3D9Format::R8G8B8 - || format == D3D9Format::X8R8G8B8 - || format == D3D9Format::A8R8G8B8; + switch (format) { + case D3D9Format::R5G6B5: + case D3D9Format::X1R5G5B5: + case D3D9Format::A1R5G5B5: + case D3D9Format::R8G8B8: + case D3D9Format::X8R8G8B8: + case D3D9Format::A8R8G8B8: + return true; + default: + return false; + } } - inline bool IsDepthFormat(D3D9Format Format) { - return Format == D3D9Format::D16_LOCKABLE - || Format == D3D9Format::D32 - || Format == D3D9Format::D15S1 - || Format == D3D9Format::D24S8 - || Format == D3D9Format::D24X8 - || Format == D3D9Format::D24X4S4 - || Format == D3D9Format::D16 - || Format == D3D9Format::D32F_LOCKABLE - || Format == D3D9Format::D24FS8 - || Format == D3D9Format::D32_LOCKABLE - || Format == D3D9Format::DF16 - || Format == D3D9Format::DF24 - || Format == D3D9Format::INTZ; + inline bool IsDepthFormat(D3D9Format format) { + switch (format) { + case D3D9Format::D16_LOCKABLE: + case D3D9Format::D32: + case D3D9Format::D15S1: + case D3D9Format::D24S8: + case D3D9Format::D24X8: + case D3D9Format::D24X4S4: + case D3D9Format::D16: + case D3D9Format::D32F_LOCKABLE: + case D3D9Format::D24FS8: + case D3D9Format::D32_LOCKABLE: + case D3D9Format::DF16: + case D3D9Format::DF24: + case D3D9Format::INTZ: + return true; + default: + return false; + } } - inline bool IsDepthStencilFormat(D3D9Format Format) { - return IsDepthFormat(Format) || Format == D3D9Format::S8_LOCKABLE; + inline bool IsDepthStencilFormat(D3D9Format format) { + return IsDepthFormat(format) || format == D3D9Format::S8_LOCKABLE; } - inline bool IsLockableDepthStencilFormat(D3D9Format Format) { - return Format == D3D9Format::S8_LOCKABLE - || Format == D3D9Format::D16_LOCKABLE - || Format == D3D9Format::D32_LOCKABLE - || Format == D3D9Format::D32F_LOCKABLE; + inline bool IsLockableDepthStencilFormat(D3D9Format format) { + switch (format) { + case D3D9Format::S8_LOCKABLE: + case D3D9Format::D16_LOCKABLE: + case D3D9Format::D32_LOCKABLE: + case D3D9Format::D32F_LOCKABLE: + return true; + default: + return false; + } } } From 17a85e6b9f5a2addb867d43aa05f4e4f3979fd66 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 3 Oct 2026 19:19:05 +0300 Subject: [PATCH 019/135] [d3d9] Fix POSTBLENDSRGBCONVERT cap reporting and RS placement --- src/d3d8/d3d8_util.h | 3 +- src/d3d9/d3d9_adapter.cpp | 10 +++- src/d3d9/d3d9_device.cpp | 114 +++++++++++++++++++------------------- 3 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/d3d8/d3d8_util.h b/src/d3d8/d3d8_util.h index 531bc587..c261d56f 100644 --- a/src/d3d8/d3d8_util.h +++ b/src/d3d8/d3d8_util.h @@ -35,8 +35,7 @@ namespace dxvk { & ~D3DPMISCCAPS_SEPARATEALPHABLEND & ~D3DPMISCCAPS_MRTINDEPENDENTBITDEPTHS & ~D3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDING - & ~D3DPMISCCAPS_FOGVERTEXCLAMPED - & ~D3DPMISCCAPS_POSTBLENDSRGBCONVERT; + & ~D3DPMISCCAPS_FOGVERTEXCLAMPED; pCaps8->RasterCaps &= ~D3DPRASTERCAPS_SCISSORTEST & ~D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS diff --git a/src/d3d9/d3d9_adapter.cpp b/src/d3d9/d3d9_adapter.cpp index 32a33c4d..8cb7fbf3 100644 --- a/src/d3d9/d3d9_adapter.cpp +++ b/src/d3d9/d3d9_adapter.cpp @@ -483,8 +483,12 @@ namespace dxvk { | D3DPMISCCAPS_SEPARATEALPHABLEND | D3DPMISCCAPS_MRTINDEPENDENTBITDEPTHS | D3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDING - | D3DPMISCCAPS_FOGVERTEXCLAMPED - | D3DPMISCCAPS_POSTBLENDSRGBCONVERT; + | D3DPMISCCAPS_FOGVERTEXCLAMPED; + + // Only 9Ex devices advertise D3DPMISCCAPS_POSTBLENDSRGBCONVERT + if (m_parent->IsD3DCompatibile(D3DCompatibility::D3D9Ex)) + pCaps->PrimitiveMiscCaps |= D3DPMISCCAPS_POSTBLENDSRGBCONVERT; + // Raster Caps pCaps->RasterCaps = D3DPRASTERCAPS_DITHER | D3DPRASTERCAPS_ZTEST @@ -501,7 +505,7 @@ namespace dxvk { | D3DPRASTERCAPS_SCISSORTEST | D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS | D3DPRASTERCAPS_DEPTHBIAS - | D3DPRASTERCAPS_MULTISAMPLE_TOGGLE; // <-- TODO! (but difficult in Vk) + | D3DPRASTERCAPS_MULTISAMPLE_TOGGLE; // Z Comparison Caps pCaps->ZCmpCaps = D3DPCMPCAPS_NEVER | D3DPCMPCAPS_LESS diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 51788b58..4dc6de66 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -8628,81 +8628,82 @@ namespace dxvk { auto& rs = m_state.renderStates; - rs[D3DRS_SEPARATEALPHABLENDENABLE] = FALSE; - rs[D3DRS_ALPHABLENDENABLE] = FALSE; - rs[D3DRS_BLENDOP] = D3DBLENDOP_ADD; - rs[D3DRS_BLENDOPALPHA] = D3DBLENDOP_ADD; - rs[D3DRS_DESTBLEND] = D3DBLEND_ZERO; - rs[D3DRS_DESTBLENDALPHA] = D3DBLEND_ZERO; - rs[D3DRS_COLORWRITEENABLE] = 0x0000000f; - rs[D3DRS_COLORWRITEENABLE1] = 0x0000000f; - rs[D3DRS_COLORWRITEENABLE2] = 0x0000000f; - rs[D3DRS_COLORWRITEENABLE3] = 0x0000000f; - rs[D3DRS_SRCBLEND] = D3DBLEND_ONE; - rs[D3DRS_SRCBLENDALPHA] = D3DBLEND_ONE; + rs[D3DRS_SEPARATEALPHABLENDENABLE] = FALSE; + rs[D3DRS_ALPHABLENDENABLE] = FALSE; + rs[D3DRS_BLENDOP] = D3DBLENDOP_ADD; + rs[D3DRS_BLENDOPALPHA] = D3DBLENDOP_ADD; + rs[D3DRS_DESTBLEND] = D3DBLEND_ZERO; + rs[D3DRS_DESTBLENDALPHA] = D3DBLEND_ZERO; + rs[D3DRS_COLORWRITEENABLE] = 0x0000000f; + rs[D3DRS_COLORWRITEENABLE1] = 0x0000000f; + rs[D3DRS_COLORWRITEENABLE2] = 0x0000000f; + rs[D3DRS_COLORWRITEENABLE3] = 0x0000000f; + rs[D3DRS_SRCBLEND] = D3DBLEND_ONE; + rs[D3DRS_SRCBLENDALPHA] = D3DBLEND_ONE; BindBlendState(); - rs[D3DRS_BLENDFACTOR] = 0xffffffff; + rs[D3DRS_BLENDFACTOR] = 0xffffffff; BindBlendFactor(); - rs[D3DRS_ZENABLE] = pPresentationParameters->EnableAutoDepthStencil - ? D3DZB_TRUE - : D3DZB_FALSE; - rs[D3DRS_ZFUNC] = D3DCMP_LESSEQUAL; - rs[D3DRS_TWOSIDEDSTENCILMODE] = FALSE; - rs[D3DRS_ZWRITEENABLE] = TRUE; - rs[D3DRS_STENCILENABLE] = FALSE; - rs[D3DRS_STENCILFAIL] = D3DSTENCILOP_KEEP; - rs[D3DRS_STENCILZFAIL] = D3DSTENCILOP_KEEP; - rs[D3DRS_STENCILPASS] = D3DSTENCILOP_KEEP; - rs[D3DRS_STENCILFUNC] = D3DCMP_ALWAYS; - rs[D3DRS_CCW_STENCILFAIL] = D3DSTENCILOP_KEEP; - rs[D3DRS_CCW_STENCILZFAIL] = D3DSTENCILOP_KEEP; - rs[D3DRS_CCW_STENCILPASS] = D3DSTENCILOP_KEEP; - rs[D3DRS_CCW_STENCILFUNC] = D3DCMP_ALWAYS; - rs[D3DRS_STENCILMASK] = 0xFFFFFFFF; - rs[D3DRS_STENCILWRITEMASK] = 0xFFFFFFFF; + rs[D3DRS_ZENABLE] = pPresentationParameters->EnableAutoDepthStencil + ? D3DZB_TRUE + : D3DZB_FALSE; + rs[D3DRS_ZFUNC] = D3DCMP_LESSEQUAL; + rs[D3DRS_TWOSIDEDSTENCILMODE] = FALSE; + rs[D3DRS_ZWRITEENABLE] = TRUE; + rs[D3DRS_STENCILENABLE] = FALSE; + rs[D3DRS_STENCILFAIL] = D3DSTENCILOP_KEEP; + rs[D3DRS_STENCILZFAIL] = D3DSTENCILOP_KEEP; + rs[D3DRS_STENCILPASS] = D3DSTENCILOP_KEEP; + rs[D3DRS_STENCILFUNC] = D3DCMP_ALWAYS; + rs[D3DRS_CCW_STENCILFAIL] = D3DSTENCILOP_KEEP; + rs[D3DRS_CCW_STENCILZFAIL] = D3DSTENCILOP_KEEP; + rs[D3DRS_CCW_STENCILPASS] = D3DSTENCILOP_KEEP; + rs[D3DRS_CCW_STENCILFUNC] = D3DCMP_ALWAYS; + rs[D3DRS_STENCILMASK] = 0xFFFFFFFF; + rs[D3DRS_STENCILWRITEMASK] = 0xFFFFFFFF; BindDepthStencilState(); - rs[D3DRS_STENCILREF] = 0; + rs[D3DRS_STENCILREF] = 0; BindDepthStencilReference(); - rs[D3DRS_FILLMODE] = D3DFILL_SOLID; - rs[D3DRS_CULLMODE] = D3DCULL_CCW; - rs[D3DRS_DEPTHBIAS] = bit::cast(0.0f); - rs[D3DRS_SLOPESCALEDEPTHBIAS] = bit::cast(0.0f); + rs[D3DRS_FILLMODE] = D3DFILL_SOLID; + rs[D3DRS_CULLMODE] = D3DCULL_CCW; + rs[D3DRS_DEPTHBIAS] = bit::cast(0.0f); + rs[D3DRS_SLOPESCALEDEPTHBIAS] = bit::cast(0.0f); BindRasterizerState(); BindDepthBias(); - rs[D3DRS_SCISSORTESTENABLE] = FALSE; + rs[D3DRS_SCISSORTESTENABLE] = FALSE; - rs[D3DRS_ALPHATESTENABLE] = FALSE; - rs[D3DRS_ALPHAFUNC] = D3DCMP_ALWAYS; + rs[D3DRS_ALPHATESTENABLE] = FALSE; + rs[D3DRS_ALPHAFUNC] = D3DCMP_ALWAYS; BindAlphaTestState(); - rs[D3DRS_ALPHAREF] = 0; - m_pushData.shared.alphaRef = rs[D3DRS_ALPHAREF]; + rs[D3DRS_ALPHAREF] = 0; + m_pushData.shared.alphaRef = rs[D3DRS_ALPHAREF]; - rs[D3DRS_MULTISAMPLEMASK] = 0xffffffff; + rs[D3DRS_MULTISAMPLEANTIALIAS] = TRUE; + rs[D3DRS_MULTISAMPLEMASK] = 0xffffffff; BindMultiSampleState(); - rs[D3DRS_TEXTUREFACTOR] = 0xffffffff; - m_pushData.ffps.textureFactor = rs[D3DRS_TEXTUREFACTOR]; - - rs[D3DRS_DIFFUSEMATERIALSOURCE] = D3DMCS_COLOR1; - rs[D3DRS_SPECULARMATERIALSOURCE] = D3DMCS_COLOR2; - rs[D3DRS_AMBIENTMATERIALSOURCE] = D3DMCS_MATERIAL; - rs[D3DRS_EMISSIVEMATERIALSOURCE] = D3DMCS_MATERIAL; - rs[D3DRS_LIGHTING] = TRUE; - rs[D3DRS_COLORVERTEX] = TRUE; - rs[D3DRS_LOCALVIEWER] = TRUE; - rs[D3DRS_RANGEFOGENABLE] = FALSE; - rs[D3DRS_NORMALIZENORMALS] = FALSE; + rs[D3DRS_TEXTUREFACTOR] = 0xffffffff; + m_pushData.ffps.textureFactor = rs[D3DRS_TEXTUREFACTOR]; + + rs[D3DRS_DIFFUSEMATERIALSOURCE] = D3DMCS_COLOR1; + rs[D3DRS_SPECULARMATERIALSOURCE] = D3DMCS_COLOR2; + rs[D3DRS_AMBIENTMATERIALSOURCE] = D3DMCS_MATERIAL; + rs[D3DRS_EMISSIVEMATERIALSOURCE] = D3DMCS_MATERIAL; + rs[D3DRS_LIGHTING] = TRUE; + rs[D3DRS_COLORVERTEX] = TRUE; + rs[D3DRS_LOCALVIEWER] = TRUE; + rs[D3DRS_RANGEFOGENABLE] = FALSE; + rs[D3DRS_NORMALIZENORMALS] = FALSE; m_dirty.set(D3D9DeviceDirtyFlag::FFVertexData); // PS - rs[D3DRS_SPECULARENABLE] = FALSE; + rs[D3DRS_SPECULARENABLE] = FALSE; - rs[D3DRS_AMBIENT] = 0; + rs[D3DRS_AMBIENT] = 0; m_dirty.set(D3D9DeviceDirtyFlag::FFVertexData); rs[D3DRS_FOGENABLE] = FALSE; @@ -8714,7 +8715,7 @@ namespace dxvk { rs[D3DRS_FOGVERTEXMODE] = D3DFOG_NONE; m_dirty.set(D3D9DeviceDirtyFlag::Fog); - rs[D3DRS_CLIPPLANEENABLE] = 0; + rs[D3DRS_CLIPPLANEENABLE] = 0; m_dirty.set(D3D9DeviceDirtyFlag::ClipPlanes); const auto& limits = m_dxvkDevice->properties().core.properties.limits; @@ -8755,7 +8756,6 @@ namespace dxvk { rs[D3DRS_WRAP6] = 0; rs[D3DRS_WRAP7] = 0; rs[D3DRS_CLIPPING] = TRUE; - rs[D3DRS_MULTISAMPLEANTIALIAS] = TRUE; rs[D3DRS_PATCHEDGESTYLE] = D3DPATCHEDGE_DISCRETE; rs[D3DRS_DEBUGMONITORTOKEN] = D3DDMT_ENABLE; rs[D3DRS_POSITIONDEGREE] = D3DDEGREE_CUBIC; From a57923a956c46f2273e26d21b8636e402430fd9f Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 3 Oct 2026 22:42:09 +0300 Subject: [PATCH 020/135] [d3d9] Leave device in a not reset state on validation failure --- src/d3d9/d3d9_device.cpp | 34 ++++++++++++++++------------------ src/d3d9/d3d9_interface.cpp | 1 - 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 4dc6de66..a3add52b 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -448,19 +448,8 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D9DeviceEx::Reset(D3DPRESENT_PARAMETERS* pPresentationParameters) { D3D9DeviceLock lock = LockDevice(); - Logger::info("Device reset"); m_deviceLostState = D3D9DeviceLostState::Ok; - HRESULT hr; - // Black Desert creates a D3DDEVTYPE_NULLREF device and - // expects reset to work despite passing invalid parameters. - if (likely(m_deviceType != D3DDEVTYPE_NULLREF)) { - hr = m_parent->ValidatePresentationParameters(pPresentationParameters); - - if (unlikely(FAILED(hr))) - return hr; - } - if (!m_d3dCompatibility.test(D3DCompatibility::D3D9Ex)) { // The internal references are always cleared, regardless of whether the Reset call succeeds. ResetState(pPresentationParameters); @@ -497,10 +486,22 @@ namespace dxvk { SetDepthStencilSurface(nullptr); } - m_cursor.ResetCursor(); - const bool isExtended = m_d3dCompatibility.test(D3DCompatibility::D3D9Ex); + HRESULT hr; + // Black Desert creates a D3DDEVTYPE_NULLREF device and + // expects reset to work despite passing invalid parameters. + if (likely(m_deviceType != D3DDEVTYPE_NULLREF)) { + hr = m_parent->ValidatePresentationParameters(pPresentationParameters); + if (unlikely(FAILED(hr))) { + if (!isExtended) + m_deviceLostState = D3D9DeviceLostState::NotReset; + return hr; + } + } + + m_cursor.ResetCursor(); + /* * Before calling the IDirect3DDevice9::Reset method for a device, * an application should release any explicit render targets, @@ -511,7 +512,7 @@ namespace dxvk { * This matches what Windows D3D9 does. */ if (unlikely(m_losableResourceCounter.load() != 0 && !isExtended && m_d3d9Options.countLosableResources)) { - Logger::warn(str::format("Device reset failed because device still has alive losable resources: Device not reset. Remaining resources: ", m_losableResourceCounter.load())); + Logger::warn(str::format("D3D9DeviceEx::Reset: Device reset failed due to pending losable resources. Remaining resources: ", m_losableResourceCounter.load())); m_deviceLostState = D3D9DeviceLostState::NotReset; // D3D8 returns D3DERR_DEVICELOST here, whereas D3D9 returns D3DERR_INVALIDCALL. return m_d3dCompatibility.test(D3DCompatibility::D3D8) ? D3DERR_DEVICELOST : D3DERR_INVALIDCALL; @@ -519,10 +520,8 @@ namespace dxvk { hr = ResetSwapChain(pPresentationParameters, nullptr); if (unlikely(FAILED(hr))) { - if (!isExtended) { - Logger::warn("Device reset failed: Device not reset"); + if (!isExtended) m_deviceLostState = D3D9DeviceLostState::NotReset; - } return hr; } @@ -4478,7 +4477,6 @@ namespace dxvk { HRESULT hr; if (likely(m_deviceType != D3DDEVTYPE_NULLREF)) { hr = m_parent->ValidatePresentationParametersEx(pPresentationParameters, pFullscreenDisplayMode); - if (unlikely(FAILED(hr))) return hr; } diff --git a/src/d3d9/d3d9_interface.cpp b/src/d3d9/d3d9_interface.cpp index 119dfa41..1d43efd8 100644 --- a/src/d3d9/d3d9_interface.cpp +++ b/src/d3d9/d3d9_interface.cpp @@ -397,7 +397,6 @@ namespace dxvk { // expects it be created despite passing invalid parameters. if (likely(DeviceType != D3DDEVTYPE_NULLREF)) { hr = ValidatePresentationParameters(pPresentationParameters); - if (unlikely(FAILED(hr))) return hr; } From 098e41a323d4e212f1b485ad010dd005027fe5b2 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 3 Oct 2026 23:16:56 +0300 Subject: [PATCH 021/135] [d3d9] Ignore SetLOD calls on non-MANAGED textures --- src/d3d9/d3d9_texture.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/d3d9/d3d9_texture.h b/src/d3d9/d3d9_texture.h index aefd3134..08d195a1 100644 --- a/src/d3d9/d3d9_texture.h +++ b/src/d3d9/d3d9_texture.h @@ -59,12 +59,15 @@ namespace dxvk { DWORD STDMETHODCALLTYPE SetLOD(DWORD LODNew) final { DWORD oldLod = m_lod; - m_lod = std::min(LODNew, m_texture.Desc()->MipLevels - 1); - if (m_lod != oldLod) { - m_texture.CreateSampleView(m_lod); - if (this->GetPrivateRefCount() > 0) - this->m_parent->MarkTextureBindingDirty(this); + if (likely(m_texture.GetPool() == D3DPOOL_MANAGED)) { + m_lod = std::min(LODNew, m_texture.Desc()->MipLevels - 1); + + if (m_lod != oldLod) { + m_texture.CreateSampleView(m_lod); + if (this->GetPrivateRefCount() > 0) + this->m_parent->MarkTextureBindingDirty(this); + } } return oldLod; From 66f252f0ce298200bb3975b53704e6d35b2a3025 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 3 Oct 2026 23:54:32 +0300 Subject: [PATCH 022/135] [d3d9] Report full constant set when in SWVP mode --- src/d3d9/d3d9_device.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index a3add52b..a133f821 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -272,6 +272,10 @@ namespace dxvk { // When in SWVP mode, 256 matrices can be used for indexed vertex blending pCaps->MaxVertexBlendMatrixIndex = m_isSWVP ? 255 : 8; + // When in SWVP mode, 8192 vertex shader constants can be used, + // however D3D8 always reports the programmable VS constant limits + if (m_isSWVP && !m_d3dCompatibility.test(D3DCompatibility::D3D8)) + pCaps->MaxVertexShaderConst = caps::MaxFloatConstantsSoftware; return D3D_OK; } From 6753e0c4bac285accfb76bffadd96c147bf7c02a Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 1 Oct 2026 01:44:32 +0200 Subject: [PATCH 023/135] [d3d9] Slightly clean StretchRect --- src/d3d9/d3d9_device.cpp | 91 +++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index a133f821..338637ea 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -1232,11 +1232,6 @@ namespace dxvk { if (unlikely(src == nullptr || dst == nullptr)) return D3DERR_INVALIDCALL; - if (unlikely(src == dst)) - return D3DERR_INVALIDCALL; - - bool fastPath = true; - D3D9CommonTexture* dstTextureInfo = dst->GetCommonTexture(); D3D9CommonTexture* srcTextureInfo = src->GetCommonTexture(); @@ -1265,35 +1260,6 @@ namespace dxvk { D3D9Format srcFormat = srcTextureInfo->Desc()->Format; D3D9Format dstFormat = dstTextureInfo->Desc()->Format; - // We may only fast path copy non identicals one way! - // We don't know what garbage could be in the X8 data. - bool similar = AreFormatsSimilar(srcFormat, dstFormat); - - // Copies are only supported on similar formats. - fastPath &= similar; - - // Copies are only supported if the sample count matches, - // otherwise we need to resolve. - auto needsResolve = false; - if (srcImage->info().sampleCount != dstImage->info().sampleCount) { - needsResolve = srcImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT; - auto fbBlit = dstImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT; - fastPath &= !fbBlit; - } - - // Copies would only work if we are block aligned. - if (pSourceRect != nullptr) { - fastPath &= (pSourceRect->left % srcFormatInfo->blockSize.width == 0); - fastPath &= (pSourceRect->right % srcFormatInfo->blockSize.width == 0); - fastPath &= (pSourceRect->top % srcFormatInfo->blockSize.height == 0); - fastPath &= (pSourceRect->bottom % srcFormatInfo->blockSize.height == 0); - } - - if (pDestRect != nullptr) { - fastPath &= (pDestRect->left % dstFormatInfo->blockSize.width == 0); - fastPath &= (pDestRect->top % dstFormatInfo->blockSize.height == 0); - } - VkImageSubresourceLayers dstSubresourceLayers = { dstSubresource.aspectMask, dstSubresource.mipLevel, @@ -1344,8 +1310,16 @@ namespace dxvk { || dstCopyExtent.width == 0 || dstCopyExtent.height == 0)) return D3D_OK; + bool stretch = srcCopyExtent != dstCopyExtent; + + bool srcIsSurface = srcTextureInfo->GetType() == D3DRTYPE_SURFACE; + bool dstIsSurface = dstTextureInfo->GetType() == D3DRTYPE_SURFACE; + bool srcHasAttachmentUsage = (srcTextureInfo->Desc()->Usage & (D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL)) != 0; + bool dstHasAttachmentUsage = (dstTextureInfo->Desc()->Usage & (D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL)) != 0; bool srcIsDS = IsDepthStencilFormat(srcFormat); bool dstIsDS = IsDepthStencilFormat(dstFormat); + + // Additional restrictions for depth stencil surfaces if (unlikely(srcIsDS || dstIsDS)) { if (unlikely(!srcIsDS || !dstIsDS)) return D3DERR_INVALIDCALL; @@ -1353,39 +1327,32 @@ namespace dxvk { if (unlikely(srcTextureInfo->Desc()->Discard || dstTextureInfo->Desc()->Discard)) return D3DERR_INVALIDCALL; - if (unlikely(srcCopyExtent.width != srcExtent.width || srcCopyExtent.height != srcExtent.height)) + if (unlikely(stretch)) + return D3DERR_INVALIDCALL; + + if (unlikely(srcFormat != dstFormat)) + return D3DERR_INVALIDCALL; + + if (unlikely(!srcIsSurface || !dstIsSurface)) return D3DERR_INVALIDCALL; if (unlikely(m_inScene)) return D3DERR_INVALIDCALL; } - // Copies would only work if the extents match. (ie. no stretching) - bool stretch = srcCopyExtent != dstCopyExtent; - - bool dstHasAttachmentUsage = (dstTextureInfo->Desc()->Usage & (D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL)) != 0; - bool dstIsSurface = dstTextureInfo->GetType() == D3DRTYPE_SURFACE; if (stretch) { if (unlikely(pSourceSurface == pDestSurface)) return D3DERR_INVALIDCALL; - if (unlikely(dstIsDS)) - return D3DERR_INVALIDCALL; - // The docs say that stretching is only allowed if the destination is either a render target surface or a render target texture. // However in practice, using an offscreen plain surface in D3DPOOL_DEFAULT as the destination works fine. // Using a texture without USAGE_RENDERTARGET as destination however does not. if (unlikely(!dstIsSurface && !dstHasAttachmentUsage)) return D3DERR_INVALIDCALL; } else { - bool srcIsSurface = srcTextureInfo->GetType() == D3DRTYPE_SURFACE; - bool srcHasAttachmentUsage = (srcTextureInfo->Desc()->Usage & (D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL)) != 0; - // D3D9Ex allows StretchRect to regular (non-RT) textures if it is a simple copy. bool isCopy = m_d3dCompatibility.test(D3DCompatibility::D3D9Ex) && pSourceRect == nullptr && pDestRect == nullptr // Yes, the rects have to be null. Even passing a rect that is the same size as the texture is invalid. - && srcTextureInfo->Desc()->Pool == D3DPOOL_DEFAULT - && dstTextureInfo->Desc()->Pool == D3DPOOL_DEFAULT && srcTextureInfo->Desc()->Format == dstTextureInfo->Desc()->Format; // Non-stretching copies are only allowed if: @@ -1398,7 +1365,33 @@ namespace dxvk { return D3DERR_INVALIDCALL; } - fastPath &= !stretch; + // Copies would only work if the extents match. (ie. no stretching) + bool fastPath = !stretch; + + // Copies are only supported on similar formats. + fastPath &= AreFormatsSimilar(srcFormat, dstFormat); + + // Copies are only supported if the sample count matches, + // otherwise we need to resolve. + auto needsResolve = false; + if (srcImage->info().sampleCount != dstImage->info().sampleCount) { + needsResolve = srcImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT; + auto fbBlit = dstImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT; + fastPath &= !fbBlit; + } + + // Copies would only work if we are block aligned. + if (pSourceRect != nullptr) { + fastPath &= (pSourceRect->left % srcFormatInfo->blockSize.width == 0); + fastPath &= (pSourceRect->right % srcFormatInfo->blockSize.width == 0); + fastPath &= (pSourceRect->top % srcFormatInfo->blockSize.height == 0); + fastPath &= (pSourceRect->bottom % srcFormatInfo->blockSize.height == 0); + } + + if (pDestRect != nullptr) { + fastPath &= (pDestRect->left % dstFormatInfo->blockSize.width == 0); + fastPath &= (pDestRect->top % dstFormatInfo->blockSize.height == 0); + } if (!fastPath || needsResolve) { // Compressed destination formats are forbidden for blits. From 44d1e38d8b1e4df70af46c245fa5daacd5f3a97d Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 1 Oct 2026 01:54:25 +0200 Subject: [PATCH 024/135] [d3d9] Remove automatic resolving in LockImage We've blocked the creation of lockable textures for a while. --- src/d3d9/d3d9_device.cpp | 45 ++-------------------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 338637ea..d6d1ad94 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -5056,55 +5056,14 @@ namespace dxvk { } if (pResource->GetImage() != nullptr) { - Rc resourceImage = pResource->GetImage(); - - Rc mappedImage; - if (resourceImage->info().sampleCount != 1) { - mappedImage = pResource->GetResolveImage(); - } else { - mappedImage = std::move(resourceImage); - } - - // When using any map mode which requires the image contents - // to be preserved, and if the GPU has write access to the - // image, copy the current image contents into the buffer. - auto subresourceLayers = vk::makeSubresourceLayers(subresource); - - // We need to resolve this, some games - // lock MSAA render targets even though - // that's entirely illegal and they explicitly - // tell us that they do NOT want to lock them... - // - // resourceImage is null because the image reference was moved to mappedImage - // for images that need to be resolved. - if (resourceImage != nullptr) { - EmitCs([ - cMainImage = resourceImage, - cResolveImage = mappedImage, - cSubresource = subresourceLayers - ] (DxvkContext* ctx) { - VkFormat format = cMainImage->info().format; - - VkImageResolve region; - region.srcSubresource = cSubresource; - region.srcOffset = VkOffset3D { 0, 0, 0 }; - region.dstSubresource = cSubresource; - region.dstOffset = VkOffset3D { 0, 0, 0 }; - region.extent = cMainImage->mipLevelExtent(cSubresource.mipLevel); - - ctx->resolveImage(cResolveImage, cMainImage, region, format, - getDefaultResolveMode(format), VK_RESOLVE_MODE_SAMPLE_ZERO_BIT); - }); - } - // if packedFormat is VK_FORMAT_UNDEFINED // DxvkContext::copyImageToBuffer will automatically take the format from the image VkFormat packedFormat = GetPackedDepthStencilFormat(desc.Format); EmitCs([ cImageBufferSlice = std::move(mappedBufferSlice), - cImage = std::move(mappedImage), - cSubresources = subresourceLayers, + cImage = pResource->GetImage(), + cSubresources = vk::makeSubresourceLayers(subresource), cLevelExtent = levelExtent, cPackedFormat = packedFormat ] (DxvkContext* ctx) { From d263e09059025aebc647fb86138a95facbafe050 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 1 Oct 2026 22:59:51 +0200 Subject: [PATCH 025/135] [d3d9] Stretch rect merge fast path EmitCs calls --- src/d3d9/d3d9_device.cpp | 53 ++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index d6d1ad94..cfa9101c 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -1400,39 +1400,34 @@ namespace dxvk { } if (fastPath) { - if (needsResolve) { - VkImageResolve region; - region.srcSubresource = blitInfo.srcSubresource; - region.srcOffset = blitInfo.srcOffsets[0]; - region.dstSubresource = blitInfo.dstSubresource; - region.dstOffset = blitInfo.dstOffsets[0]; - region.extent = srcCopyExtent; - - EmitCs([ - cDstImage = dstImage, - cSrcImage = srcImage, - cRegion = region - ] (DxvkContext* ctx) { - // Deliberately use AVERAGE even for depth resolves here - ctx->resolveImage(cDstImage, cSrcImage, cRegion, cSrcImage->info().format, - VK_RESOLVE_MODE_AVERAGE_BIT, VK_RESOLVE_MODE_SAMPLE_ZERO_BIT); - }); - } else { - EmitCs([ - cDstImage = dstImage, - cSrcImage = srcImage, - cDstLayers = blitInfo.dstSubresource, - cSrcLayers = blitInfo.srcSubresource, - cDstOffset = blitInfo.dstOffsets[0], - cSrcOffset = blitInfo.srcOffsets[0], - cExtent = srcCopyExtent - ] (DxvkContext* ctx) { + EmitCs([ + cDstImage = dstImage, + cSrcImage = srcImage, + cDstLayers = blitInfo.dstSubresource, + cSrcLayers = blitInfo.srcSubresource, + cDstOffset = blitInfo.dstOffsets[0], + cSrcOffset = blitInfo.srcOffsets[0], + cExtent = srcCopyExtent, + cResolve = needsResolve + ] (DxvkContext* ctx) { + if (!cResolve) { ctx->copyImage( cDstImage, cDstLayers, cDstOffset, cSrcImage, cSrcLayers, cSrcOffset, cExtent); - }); - } + } else { + VkImageResolve region; + region.srcSubresource = cSrcLayers; + region.srcOffset = cSrcOffset; + region.dstSubresource = cDstLayers; + region.dstOffset = cDstOffset; + region.extent = cExtent; + + // Deliberately use AVERAGE even for depth resolves here + ctx->resolveImage(cDstImage, cSrcImage, region, cSrcImage->info().format, + VK_RESOLVE_MODE_AVERAGE_BIT, VK_RESOLVE_MODE_SAMPLE_ZERO_BIT); + } + }); } else { DxvkImageViewKey dstViewInfo; From 2ebe9a6fb5a1f643a7f6662d6bfe425ac613a55b Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 1 Oct 2026 22:05:13 +0200 Subject: [PATCH 026/135] [d3d9] Improve GetMipSize naming --- src/d3d9/d3d9_common_texture.cpp | 6 +++--- src/d3d9/d3d9_common_texture.h | 6 +++--- src/d3d9/d3d9_device.cpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index 9d5fe38d..adb4e3bf 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -86,7 +86,7 @@ namespace dxvk { for (uint32_t i = 0; i < CountSubresources(); i++) { m_memoryOffset[i] = m_totalSize; - m_totalSize += GetMipSize(i); + m_totalSize += GetSubresourceSize(i); } // Add a tiny amount of padding at the end because some games read/write OOB @@ -353,7 +353,7 @@ namespace dxvk { } - VkDeviceSize D3D9CommonTexture::GetMipSize(UINT Subresource) const { + VkDeviceSize D3D9CommonTexture::GetSubresourceSize(UINT Subresource) const { const UINT MipLevel = Subresource % m_desc.MipLevels; const DxvkFormatInfo* formatInfo = m_mapping.Format != VK_FORMAT_UNDEFINED @@ -818,7 +818,7 @@ namespace dxvk { DxvkBufferSlice D3D9CommonTexture::GetBufferSlice(UINT Subresource) { - return DxvkBufferSlice(GetBuffer(), m_memoryOffset[Subresource], GetMipSize(Subresource)); + return DxvkBufferSlice(GetBuffer(), m_memoryOffset[Subresource], GetSubresourceSize(Subresource)); } diff --git a/src/d3d9/d3d9_common_texture.h b/src/d3d9/d3d9_common_texture.h index 1cbc26ce..08bf349d 100644 --- a/src/d3d9/d3d9_common_texture.h +++ b/src/d3d9/d3d9_common_texture.h @@ -496,10 +496,10 @@ namespace dxvk { } /** - * \brief Mip level - * \returns Size of packed mip level in bytes + * \brief Subresource size + * \returns Size of packed subresource level in bytes */ - VkDeviceSize GetMipSize(UINT Subresource) const; + VkDeviceSize GetSubresourceSize(UINT Subresource) const; uint32_t GetTotalSize() const { return m_totalSize; diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index cfa9101c..197fc448 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -5344,7 +5344,7 @@ namespace dxvk { srcBlockCount.height *= std::min(pSrcTexture->GetPlaneCount(), 2u); // the converter can not handle the 4 aligned pitch so we always repack into a staging buffer - D3D9BufferSlice slice = AllocStagingBuffer(pSrcTexture->GetMipSize(SrcSubresource)); + D3D9BufferSlice slice = AllocStagingBuffer(pSrcTexture->GetSubresourceSize(SrcSubresource)); VkDeviceSize pitch = align(srcBlockCount.width * formatElementSize, 4); const DxvkFormatInfo* convertedFormatInfo = lookupFormatInfo(convertFormat.Format); From 9692a69ef016b3534a4c1bb658d57d2ba4d6ed83 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 1 Oct 2026 22:10:06 +0200 Subject: [PATCH 027/135] [d3d9] Move size padding to Getter --- src/d3d9/d3d9_common_texture.cpp | 10 +++------- src/d3d9/d3d9_common_texture.h | 4 +++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index adb4e3bf..4608ae3a 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -89,15 +89,11 @@ namespace dxvk { m_totalSize += GetSubresourceSize(i); } - // Add a tiny amount of padding at the end because some games read/write OOB - // Medieval: Total War 1 for example seems to have an off-by-one bug in copying data for a managed texture. - uint32_t paddedSize = align(m_totalSize + 1, CACHE_LINE_SIZE); - // Initialization is handled by D3D9Initializer if (m_mapMode == D3D9_COMMON_TEXTURE_MAP_MODE_UNMAPPABLE) - m_data = MemoryFileRegion(*m_device->GetAllocator(), paddedSize); + m_data = MemoryFileRegion(*m_device->GetAllocator(), GetTotalSize()); else if (m_mapMode != D3D9_COMMON_TEXTURE_MAP_MODE_NONE && m_desc.Pool != D3DPOOL_DEFAULT) - CreateBuffer(false, paddedSize); + CreateBuffer(false, GetTotalSize()); } @@ -321,7 +317,7 @@ namespace dxvk { return; DxvkBufferCreateInfo info; - info.size = Size; + info.size = GetTotalSize(); info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; diff --git a/src/d3d9/d3d9_common_texture.h b/src/d3d9/d3d9_common_texture.h index 08bf349d..8b62e782 100644 --- a/src/d3d9/d3d9_common_texture.h +++ b/src/d3d9/d3d9_common_texture.h @@ -502,7 +502,9 @@ namespace dxvk { VkDeviceSize GetSubresourceSize(UINT Subresource) const; uint32_t GetTotalSize() const { - return m_totalSize; + // Add a tiny amount of padding at the end because some games read/write OOB + // Medieval: Total War 1 for example seems to have an off-by-one bug in copying data for a managed texture. + return align(m_totalSize + 1, CACHE_LINE_SIZE); } /** From 39e5367f2f669228f5c32c6857f6cdd07943b06e Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 1 Oct 2026 22:06:21 +0200 Subject: [PATCH 028/135] [d3d9] Improve naming of late mapping buffer creation function --- src/d3d9/d3d9_common_texture.cpp | 8 +++++--- src/d3d9/d3d9_common_texture.h | 3 +-- src/d3d9/d3d9_device.cpp | 6 +++--- src/d3d9/d3d9_swapchain.cpp | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index 4608ae3a..fe32b0f4 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -93,7 +93,7 @@ namespace dxvk { if (m_mapMode == D3D9_COMMON_TEXTURE_MAP_MODE_UNMAPPABLE) m_data = MemoryFileRegion(*m_device->GetAllocator(), GetTotalSize()); else if (m_mapMode != D3D9_COMMON_TEXTURE_MAP_MODE_NONE && m_desc.Pool != D3DPOOL_DEFAULT) - CreateBuffer(false, GetTotalSize()); + EnsureBufferExists(false); // No need to initialize here, D3D9Initializer takes care of that. } @@ -312,9 +312,9 @@ namespace dxvk { } - void D3D9CommonTexture::CreateBuffer(bool Initialize, uint32_t Size) { + const Rc& D3D9CommonTexture::EnsureBufferExists(bool Initialize) { if (likely(m_buffer != nullptr)) - return; + return m_buffer; DxvkBufferCreateInfo info; info.size = GetTotalSize(); @@ -346,6 +346,8 @@ namespace dxvk { std::memset(m_buffer->mapPtr(0), 0, m_totalSize); } m_data = {}; + + return m_buffer; } diff --git a/src/d3d9/d3d9_common_texture.h b/src/d3d9/d3d9_common_texture.h index 8b62e782..88212518 100644 --- a/src/d3d9/d3d9_common_texture.h +++ b/src/d3d9/d3d9_common_texture.h @@ -511,10 +511,9 @@ namespace dxvk { * \brief Creates a buffer * Creates the mapping buffer if necessary * \param [in] Initialize Whether to copy over existing data (or clear if there is no data) - * \param [in] Size The size of the buffer * \returns Whether an allocation happened */ - void CreateBuffer(bool Initialize, uint32_t Size); + const Rc& EnsureBufferExists(bool Initialize = true); ID3D9VkInteropTexture* GetVkInterop() { return &m_d3d9Interop; } diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 197fc448..19fca399 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -1176,7 +1176,7 @@ namespace dxvk { || dstTexExtent.width > srcTexExtent.width || dstTexExtent.height > srcTexExtent.height; - dstTexInfo->CreateBuffer(clearDst, dstTexInfo->GetTotalSize()); + dstTexInfo->EnsureBufferExists(clearDst); DxvkBufferSlice dstBufferSlice = dstTexInfo->GetBufferSlice(dst->GetSubresource()); Rc srcImage = srcTexInfo->GetImage(); const DxvkFormatInfo* srcFormatInfo = lookupFormatInfo(srcImage->info().format); @@ -5030,9 +5030,9 @@ namespace dxvk { needsReadback &= pResource->GetImage() != nullptr || !(Flags & D3DLOCK_DISCARD); pResource->SetNeedsReadback(Subresource, false); - if (unlikely(pResource->GetMapMode() == D3D9_COMMON_TEXTURE_MAP_MODE_BACKED || needsReadback)) { + if (unlikely(pResource->GetImage() != nullptr || needsReadback)) { // Create mapping buffer if it doesn't exist yet. (POOL_DEFAULT) - pResource->CreateBuffer(!needsReadback, pResource->GetTotalSize()); + pResource->EnsureBufferExists(!needsReadback); } // Don't use MapTexture here to keep the mapped list small while the resource is still locked. diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 86609a64..4ec93133 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -281,7 +281,7 @@ namespace dxvk { || dstTexExtent.width > srcTexExtent.width || dstTexExtent.height > srcTexExtent.height; - dstTexInfo->CreateBuffer(clearDst, dstTexInfo->GetTotalSize()); + dstTexInfo->EnsureBufferExists(clearDst); DxvkBufferSlice dstBufferSlice = dstTexInfo->GetBufferSlice(dst->GetSubresource()); Rc srcImage = srcTexInfo->GetImage(); From 7bcd0231a26caad5f9c38eaa6aeab6eb00575925 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 1 Oct 2026 23:25:25 +0200 Subject: [PATCH 029/135] [d3d9] Prevent creation of MANAGED surfaces The docs say this is illegal and various functions in our code rely on this assumption anyway. --- src/d3d9/d3d9_common_texture.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index fe32b0f4..0764c703 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -204,7 +204,9 @@ namespace dxvk { if (FAILED(hr)) return hr; } else if (sampleCount != VK_SAMPLE_COUNT_1_BIT) { - // D3D9 only supports MSAA for surfaces + // D3D9 only supports MSAA for surfaces. + // This is unreachable. Only CreateRT or CreateDSV take in a sample count as a parameter. + // Those create D3DPOOL_DEFAULT surfaces. return D3DERR_INVALIDCALL; } @@ -294,6 +296,10 @@ namespace dxvk { if (pDesc->IsLockable && sampleCount > VK_SAMPLE_COUNT_1_BIT) return D3DERR_INVALIDCALL; + // Offscreen plain surfaces don't allow D3DPOOL_MANAGED, all other surface creation functions implicitly use D3DPOOL_DEFAULT + if (unlikely(ResourceType == D3DRTYPE_SURFACE && pDesc->Pool == D3DPOOL_MANAGED)) + return D3DERR_INVALIDCALL; + return D3D_OK; } From 67fc6eb25e4eb7f5def888135d8562a9b2b248de Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Fri, 2 Oct 2026 00:17:02 +0200 Subject: [PATCH 030/135] [d3d9] Correct typo --- src/d3d9/d3d9_device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 19fca399..e3937fbc 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -5251,7 +5251,7 @@ namespace dxvk { auto convertFormat = pDestTexture->GetFormatMapping().ConversionFormatInfo; if (unlikely(pSrcTexture->NeedsReadback(SrcSubresource))) { - // The src texutre has to be in POOL_SYSTEMEM, so it cannot use AUTOMIPGEN. + // The src texture has to be in POOL_SYSTEMEM, so it cannot use AUTOMIPGEN. // That means that NeedsReadback is only true if the texture has been used with GetRTData or GetFrontbufferData before. // Those functions create a buffer, so the buffer always exists here. const Rc& buffer = pSrcTexture->GetBuffer(); From 40b2ed20ee637c3b20b91042bb43fc762ae85b93 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Fri, 2 Oct 2026 19:09:34 +0200 Subject: [PATCH 031/135] [d3d9] Validate texture dimensions --- src/d3d9/d3d9_common_texture.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index 0764c703..46cfa7e5 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -170,6 +170,9 @@ namespace dxvk { if (pDesc->Width == 0 || pDesc->Height == 0 || pDesc->Depth == 0) return D3DERR_INVALIDCALL; + if (pDesc->Width > caps::MaxTextureDimension || pDesc->Height > caps::MaxTextureDimension || pDesc->Depth > caps::MaxTextureDimension) + return D3DERR_INVALIDCALL; + // Native drivers won't allow the creation of DXT format // textures that aren't aligned to block dimensions. if (IsDXTFormat(pDesc->Format)) { From 89e9eca935f6b05d7775a56186a2d1b937f66bc9 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Fri, 2 Oct 2026 19:12:39 +0200 Subject: [PATCH 032/135] [d3d9] Align cubemap face mapping data to 16 bytes Closes #4674 --- src/d3d9/d3d9_common_texture.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index 46cfa7e5..35912fe2 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -85,6 +85,11 @@ namespace dxvk { } for (uint32_t i = 0; i < CountSubresources(); i++) { + if (i % m_desc.MipLevels == 0) { + // Align faces to 16 bytes + m_totalSize = align(m_totalSize, 16u); + } + m_memoryOffset[i] = m_totalSize; m_totalSize += GetSubresourceSize(i); } From 649c4bc0f0e303fcf9e21fad1c36e30ebce79b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Sat, 10 Oct 2026 17:28:24 +0200 Subject: [PATCH 033/135] [meta] Workaround a crash in EA anti cheat code signing checks. EA WRC (1849250) --- meson.build | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/meson.build b/meson.build index 4964301b..d10f07d4 100644 --- a/meson.build +++ b/meson.build @@ -73,6 +73,15 @@ if platform == 'windows' '-Wl,--file-alignment=4096', ] + # Follow PE standards with 8 character name sections, some software that + # inspects our DLLs may otherwise crash. For dwarf2 symbols to work we + # need to keep the mingw defaults. + if not get_option('debug') + link_args += [ + '-Wl,--disable-long-section-names', + ] + endif + # Wine's built-in back traces only work with dwarf4 symbols if get_option('debug') compiler_args += [ From a78a467cadb22bee928904290cf8e7921b121851 Mon Sep 17 00:00:00 2001 From: Paul Gofman Date: Tue, 13 Oct 2026 18:09:05 -0600 Subject: [PATCH 034/135] [dxvk] Avoid requiring "" Vulkan extension in VR interop for empty extension string --- src/dxvk/dxvk_openvr.cpp | 4 ++-- src/dxvk/dxvk_openxr.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dxvk/dxvk_openvr.cpp b/src/dxvk/dxvk_openvr.cpp index c544ffb9..89b3df75 100644 --- a/src/dxvk/dxvk_openvr.cpp +++ b/src/dxvk/dxvk_openvr.cpp @@ -171,7 +171,7 @@ namespace dxvk { extensionList.resize(len); len = m_compositor->GetVulkanInstanceExtensionsRequired(extensionList.data(), len); } - return parseExtensionList(std::string(extensionList.data(), len)); + return parseExtensionList(std::string(extensionList.data())); } @@ -209,7 +209,7 @@ namespace dxvk { len = m_compositor->GetVulkanDeviceExtensionsRequired(adapter->handle(), extensionList.data(), len); } - return parseExtensionList(std::string(extensionList.data(), len)); + return parseExtensionList(std::string(extensionList.data())); } diff --git a/src/dxvk/dxvk_openxr.cpp b/src/dxvk/dxvk_openxr.cpp index 9b304d81..19cd7277 100644 --- a/src/dxvk/dxvk_openxr.cpp +++ b/src/dxvk/dxvk_openxr.cpp @@ -99,7 +99,7 @@ namespace dxvk { return DxvkExtensionList(); } - return parseExtensionList(std::string(extensionList.data(), len)); + return parseExtensionList(std::string(extensionList.data())); } @@ -120,7 +120,7 @@ namespace dxvk { return DxvkExtensionList(); } - return parseExtensionList(std::string(extensionList.data(), len)); + return parseExtensionList(std::string(extensionList.data())); } From eae53c4aecfb3b9a9f284ea70954f7e71a51a7f7 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 15 Oct 2026 15:00:07 +0200 Subject: [PATCH 035/135] [dxvk] Add option to zero-initialize shader scratch variables --- src/dxvk/dxvk_device.cpp | 5 ++++- src/dxvk/dxvk_options.cpp | 1 + src/dxvk/dxvk_options.h | 4 ++++ src/dxvk/dxvk_shader.h | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index 7bdd651b..082c15b9 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -781,7 +781,6 @@ namespace dxvk { && (!m_features.intelDeviceInfo || m_properties.intelDeviceInfo.deviceIpVersionArch < 30u); applyTristate(lowerSinCos, m_options.lowerSinCos); - if (lowerSinCos) m_shaderOptions.flags.set(DxvkShaderCompileFlag::LowerSinCos); @@ -792,6 +791,10 @@ namespace dxvk { DxvkShaderCompileFlag::LowerF32toF16); } + // Enable game workarounds + if (m_options.zeroShaderScratchMemory) + m_shaderOptions.spirv.set(DxvkShaderSpirvFlag::ZeroScratchMemory); + // On AMD, push constant BDA will not be worse than going through a descriptor if (m_adapter->matchesDriver(VK_DRIVER_ID_MESA_RADV) || m_adapter->matchesDriver(VK_DRIVER_ID_AMD_OPEN_SOURCE) diff --git a/src/dxvk/dxvk_options.cpp b/src/dxvk/dxvk_options.cpp index e9aef0ed..965baacc 100644 --- a/src/dxvk/dxvk_options.cpp +++ b/src/dxvk/dxvk_options.cpp @@ -24,6 +24,7 @@ namespace dxvk { disableNvLowLatency2 = config.getOption("dxvk.disableNvLowLatency2", Tristate::Auto); hideIntegratedGraphics = config.getOption ("dxvk.hideIntegratedGraphics", false); zeroMappedMemory = config.getOption ("dxvk.zeroMappedMemory", false); + zeroShaderScratchMemory = config.getOption ("dxvk.zeroShaderScratchMemory", false); allowFse = config.getOption ("dxvk.allowFse", false); deviceFilter = config.getOption("dxvk.deviceFilter", ""); lowerSinCos = config.getOption("dxvk.lowerSinCos", Tristate::Auto); diff --git a/src/dxvk/dxvk_options.h b/src/dxvk/dxvk_options.h index baade522..ebd1bc1f 100644 --- a/src/dxvk/dxvk_options.h +++ b/src/dxvk/dxvk_options.h @@ -92,6 +92,10 @@ namespace dxvk { /// Enable descriptor update templates bool enableDescriptorUpdateTemplates = env::is32BitHostPlatform(); + /// Whether scratch memory in shaders needs + /// zero-initialization to work around game bugs. + bool zeroShaderScratchMemory = false; + /// Device name std::string deviceFilter; }; diff --git a/src/dxvk/dxvk_shader.h b/src/dxvk/dxvk_shader.h index d7619c70..de9a4c64 100644 --- a/src/dxvk/dxvk_shader.h +++ b/src/dxvk/dxvk_shader.h @@ -114,6 +114,8 @@ namespace dxvk { SupportsResourceIndexing = 20u, /// Whether descriptor heap is used as the binding model. SupportsDescriptorHeap = 21u, + /// Whether scratch memory should be zeroed. + ZeroScratchMemory = 22u, }; using DxvkShaderSpirvFlags = Flags; From f12a4348c8a70e5fa698f8433b53967d93637153 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 15 Oct 2026 15:01:34 +0200 Subject: [PATCH 036/135] [util] Enable scratch zero-initialization for Call of Duty Ghosts --- src/util/config/config.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp index 7be947ec..839b4039 100644 --- a/src/util/config/config.cpp +++ b/src/util/config/config.cpp @@ -145,6 +145,10 @@ namespace dxvk { { R"(\\s2_sp64_ship\.exe$)", {{ { "dxgi.hideNvidiaGpu", "False" }, }} }, + /* Call of Duty Ghosts */ + { R"(\\iw6sp64_ship\.exe$)", {{ + { "dxvk.zeroShaderScratchMemory", "True" }, + }} }, /* Need for Speed 2015 */ { R"(\\NFS16\.exe$)", {{ { "dxgi.hideNvidiaGpu", "False" }, From 569f40e2b28d5d7b76f32bd10d8fb3f9cabd14ac Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 15 Oct 2026 12:06:42 +0200 Subject: [PATCH 037/135] [meta] Update dxbc-spirv Scratch memes. --- src/dxvk/dxvk_shader_ir.cpp | 5 +++-- subprojects/dxbc-spirv | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dxvk/dxvk_shader_ir.cpp b/src/dxvk/dxvk_shader_ir.cpp index 0558840a..9fe36824 100644 --- a/src/dxvk/dxvk_shader_ir.cpp +++ b/src/dxvk/dxvk_shader_ir.cpp @@ -2093,10 +2093,11 @@ namespace dxvk { options.includeDebugNames = true; options.nvRawAccessChains = m_info.options.spirv.test(DxvkShaderSpirvFlag::SupportsNvRawAccessChains); options.dualSourceBlending = linkage && linkage->fsDualSrcBlend; + options.zeroInitializeScratch = m_info.options.spirv.test(DxvkShaderSpirvFlag::ZeroScratchMemory); if (m_info.options.spirv.all(DxvkShaderSpirvFlag::IndependentDenormMode, - DxvkShaderSpirvFlag::SupportsRte32, - DxvkShaderSpirvFlag::SupportsDenormFlush32)) { + DxvkShaderSpirvFlag::SupportsRte32, + DxvkShaderSpirvFlag::SupportsDenormFlush32)) { if (m_info.options.spirv.test(DxvkShaderSpirvFlag::SupportsRte16)) options.supportedRoundModesF16 |= dxbc_spv::ir::RoundMode::eNearestEven; if (m_info.options.spirv.test(DxvkShaderSpirvFlag::SupportsRte32)) diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index ecb8308f..ff6673cf 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit ecb8308fe03fcad695a6ebdacbef6e7f61e77484 +Subproject commit ff6673cf7ac88040cd37aee721e744debb5f3d63 From e8c80a8d1cdaa0ad21516bf64d9cb073cb57eec3 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 15 Oct 2026 21:57:18 +0200 Subject: [PATCH 038/135] [d3d9] Don't enable point scaling with weird projection matrices We can't compute a meaningful eye distance if the app doesn't use projection, so just don't bother. Similar to Z fog. Proton: Ridiculous Space Battles (3607230) --- src/d3d9/d3d9_device.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index e3937fbc..6213354c 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -6520,6 +6520,13 @@ namespace dxvk { bool scale = rs[D3DRS_POINTSCALEENABLE] && !UseProgrammableVS(); bool sprite = rs[D3DRS_POINTSPRITEENABLE]; + if (scale) { + // Only enable scaling with perspective projection since we + // otherwise can't compute meaningful eye vectors + auto [wNear, wFar] = ComputeWNearFar(); + scale = wNear != 1.0f || wFar != 1.0f; + } + if (scale && m_dirty.test(D3D9DeviceDirtyFlag::PointScale)) { m_dirty.clr(D3D9DeviceDirtyFlag::PointScale); m_dirty.set(D3D9DeviceDirtyFlag::PushDataFfvs); From dc948177d63dc74fbe34ca8895b6c02a76fa00f2 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 16 Oct 2026 12:42:36 +0200 Subject: [PATCH 039/135] [meta] Update dxbc-spirv Fixes minor function bug. --- subprojects/dxbc-spirv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index ff6673cf..64f078d6 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit ff6673cf7ac88040cd37aee721e744debb5f3d63 +Subproject commit 64f078d685725b6bd5e1a045615705f47b6aa9de From 6689ca92f0f9a1c3ab86c16ce1c7ceebe995454b Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 16 Oct 2026 12:49:29 +0200 Subject: [PATCH 040/135] [dxvk] Bump engine version to 3.2 For Fossilize purposes, since shader code gen has changed considerably. --- src/dxvk/dxvk_instance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_instance.cpp b/src/dxvk/dxvk_instance.cpp index 6ab269ed..a7cb07b9 100644 --- a/src/dxvk/dxvk_instance.cpp +++ b/src/dxvk/dxvk_instance.cpp @@ -245,7 +245,7 @@ namespace dxvk { appInfo.pApplicationName = appName.c_str(); appInfo.applicationVersion = flags.raw(); appInfo.pEngineName = "DXVK"; - appInfo.engineVersion = VK_MAKE_API_VERSION(0, 3, 1, 1); + appInfo.engineVersion = VK_MAKE_API_VERSION(0, 3, 2, 0); appInfo.apiVersion = DxvkVulkanApiVersion; VkInstanceCreateInfo info = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; From 8c8937dd314fc326522c3cb4e74d57cac7a65758 Mon Sep 17 00:00:00 2001 From: Ben Woodring Date: Fri, 16 Oct 2026 14:44:10 -0500 Subject: [PATCH 041/135] [dxvk] Fix inverted check when freeing shader cache instance --- src/dxvk/dxvk_shader_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_shader_cache.cpp b/src/dxvk/dxvk_shader_cache.cpp index 0bec8399..9e0ea3de 100644 --- a/src/dxvk/dxvk_shader_cache.cpp +++ b/src/dxvk/dxvk_shader_cache.cpp @@ -683,7 +683,7 @@ namespace dxvk { // The ref count can only be incremented from 0 to 1 inside a locked // context, so this check is safe. Don't destroy the object if another // thread has essentially revived it. - if (m_useCount.load() || s_instance.instance != this) { + if (!m_useCount.load() || s_instance.instance != this) { if (s_instance.instance == this) s_instance.instance = nullptr; From 0847af33b77bf4006667530d0929b04a69864bdf Mon Sep 17 00:00:00 2001 From: Ben Woodring Date: Fri, 16 Oct 2026 05:57:17 -0500 Subject: [PATCH 042/135] [dxvk] Lock allocator when creating imported and sparse allocations --- src/dxvk/dxvk_memory.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dxvk/dxvk_memory.cpp b/src/dxvk/dxvk_memory.cpp index fc18044d..13562aa8 100644 --- a/src/dxvk/dxvk_memory.cpp +++ b/src/dxvk/dxvk_memory.cpp @@ -1306,6 +1306,8 @@ namespace dxvk { const VkBufferCreateInfo& createInfo, const DxvkAllocationInfo& allocationInfo, const DxvkBufferImportInfo& importInfo) { + std::lock_guard lock(m_mutex); + Rc allocation = m_allocationPool.create(this, nullptr); allocation->m_flags.set(DxvkAllocationFlag::Imported); allocation->m_resourceCookie = allocationInfo.resourceCookie; @@ -1325,6 +1327,8 @@ namespace dxvk { const VkImageCreateInfo& createInfo, const DxvkAllocationInfo& allocationInfo, VkImage imageHandle) { + std::lock_guard lock(m_mutex); + Rc allocation = m_allocationPool.create(this, nullptr); allocation->m_flags.set(DxvkAllocationFlag::Imported); allocation->m_resourceCookie = allocationInfo.resourceCookie; @@ -1586,6 +1590,8 @@ namespace dxvk { DxvkResourceAllocation* DxvkMemoryAllocator::createAllocation( DxvkSparsePageTable* sparsePageTable, const DxvkAllocationInfo& allocationInfo) { + std::lock_guard lock(m_mutex); + auto allocation = m_allocationPool.create(this, nullptr); allocation->m_resourceCookie = allocationInfo.resourceCookie; allocation->m_sparsePageTable = sparsePageTable; From 89b5490ff970b1a3a0aa9a9d81a40144f14d9816 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Mon, 12 Oct 2026 22:12:23 +0200 Subject: [PATCH 043/135] [d3d9] Mark texture as uploaded in FlushImage Cleaner and more intuitive. --- src/d3d9/d3d9_common_texture.h | 1 - src/d3d9/d3d9_device.cpp | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/d3d9/d3d9_common_texture.h b/src/d3d9/d3d9_common_texture.h index 88212518..cfabb802 100644 --- a/src/d3d9/d3d9_common_texture.h +++ b/src/d3d9/d3d9_common_texture.h @@ -412,7 +412,6 @@ namespace dxvk { void SetNeedsUpload(UINT Subresource, bool upload) { m_needsUpload.set(Subresource, upload); } bool NeedsUpload(UINT Subresource) const { return m_needsUpload.get(Subresource); } bool NeedsAnyUpload() { return m_needsUpload.any(); } - void ClearNeedsUpload() { return m_needsUpload.clearAll(); } void SetNeedsMipGen(bool value) { m_needsMipGen = value; } bool NeedsMipGen() const { return m_needsMipGen; } diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 6213354c..7760ab9f 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -5217,6 +5217,10 @@ namespace dxvk { if (pResource->IsAutomaticMip()) MarkTextureMipsDirty(pResource); + pResource->SetNeedsUpload(Subresource, false); + if (!pResource->NeedsAnyUpload()) + MarkTextureUploaded(pResource); + return D3D_OK; } @@ -6430,7 +6434,6 @@ namespace dxvk { } pResource->ClearDirtyBoxes(); - pResource->ClearNeedsUpload(); } From 6cd3cbc99bdc1df82578c89398c9ed47e5ac525b Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 11 Oct 2026 13:28:21 +0200 Subject: [PATCH 044/135] [d3d9] Add warning for stalls in UpdateTextureFromBuffer --- src/d3d9/d3d9_device.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 7760ab9f..e5473897 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -5254,14 +5254,19 @@ namespace dxvk { auto convertFormat = pDestTexture->GetFormatMapping().ConversionFormatInfo; - if (unlikely(pSrcTexture->NeedsReadback(SrcSubresource))) { + const Rc& buffer = pSrcTexture->GetBuffer(); + if (unlikely(pSrcTexture->NeedsReadback(SrcSubresource)) + && !WaitForResource(*buffer, pSrcTexture->GetMappingBufferSequenceNumber(SrcSubresource), D3DLOCK_DONOTWAIT | D3DLOCK_READONLY)) { // The src texture has to be in POOL_SYSTEMEM, so it cannot use AUTOMIPGEN. // That means that NeedsReadback is only true if the texture has been used with GetRTData or GetFrontbufferData before. // Those functions create a buffer, so the buffer always exists here. - const Rc& buffer = pSrcTexture->GetBuffer(); - WaitForResource(*buffer, pSrcTexture->GetMappingBufferSequenceNumber(SrcSubresource), 0); - pSrcTexture->SetNeedsReadback(SrcSubresource, false); + + // If we run into this frequently, consider dynamically skipping the staging buffer if the src texture needs a sync. + Logger::warn("Stalling in UpdateTextureFromBuffer."); + + WaitForResource(*buffer, pSrcTexture->GetMappingBufferSequenceNumber(SrcSubresource), D3DLOCK_READONLY); } + pSrcTexture->SetNeedsReadback(SrcSubresource, false); if (likely(convertFormat.FormatType == D3D9ConversionFormat_None)) { // The texture does not use a format that needs to be converted in a compute shader. From 3a71f3cc74ba2d7976a95184cbaccc7caa8dca85 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Tue, 13 Oct 2026 19:21:55 +0200 Subject: [PATCH 045/135] [d3d9] Switch texture map data to buffers early --- src/d3d9/d3d9_common_texture.cpp | 8 +++----- src/d3d9/d3d9_common_texture.h | 8 ++++++-- src/d3d9/d3d9_device.cpp | 27 +++++++++++++++------------ 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index 35912fe2..111fcda1 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -330,6 +330,9 @@ namespace dxvk { if (likely(m_buffer != nullptr)) return m_buffer; + if (unlikely(m_locked.any() && m_data)) + Logger::err("Switching from memory file to buffer while one of the subresources is locked!"); + DxvkBufferCreateInfo info; info.size = GetTotalSize(); info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT @@ -824,11 +827,6 @@ namespace dxvk { } - const Rc& D3D9CommonTexture::GetBuffer() { - return m_buffer; - } - - DxvkBufferSlice D3D9CommonTexture::GetBufferSlice(UINT Subresource) { return DxvkBufferSlice(GetBuffer(), m_memoryOffset[Subresource], GetSubresourceSize(Subresource)); } diff --git a/src/d3d9/d3d9_common_texture.h b/src/d3d9/d3d9_common_texture.h index cfabb802..7ce4fff4 100644 --- a/src/d3d9/d3d9_common_texture.h +++ b/src/d3d9/d3d9_common_texture.h @@ -161,7 +161,9 @@ namespace dxvk { */ void* GetData(UINT Subresource); - const Rc& GetBuffer(); + const Rc& GetBuffer() const { + return m_buffer; + } DxvkBufferSlice GetBufferSlice(UINT Subresource); @@ -372,7 +374,7 @@ namespace dxvk { D3DPOOL GetPool() const { return m_desc.Pool; } - const D3D9_VK_FORMAT_MAPPING& GetMapping() { return m_mapping; } + const D3D9_VK_FORMAT_MAPPING& GetMapping() const { return m_mapping; } void SetLocked(UINT Subresource, bool value) { m_locked.set(Subresource, value); } @@ -384,6 +386,8 @@ namespace dxvk { bool NeedsReadback(UINT Subresource) const { return m_needsReadback.get(Subresource); } + bool NeedsAnyReadback() const { return m_needsReadback.any(); } + void MarkAllNeedReadback() { m_needsReadback.setAll(); } const Rc& GetSampleView(bool srgb) const { diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index e5473897..c1f4671f 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -5018,26 +5018,29 @@ namespace dxvk { if (desc.Usage & D3DUSAGE_WRITEONLY) Flags &= ~D3DLOCK_READONLY; - // If we recently wrote to the texture on the gpu, - // then we need to copy -> buffer - // We are also always dirty if we are a render target, - // a depth stencil, or auto generate mipmaps. + // If we recently wrote to the texture on the gpu, then we need to copy -> buffer + // We are also always dirty if we are a render target, a depth stencil. bool renderable = desc.Usage & (D3DUSAGE_RENDERTARGET | D3DUSAGE_DEPTHSTENCIL); bool needsReadback = pResource->NeedsReadback(Subresource) || renderable; + // Don't use MapTexture here to keep the mapped list small while the resource is still locked. + void* mapPtr = pResource->GetData(Subresource); + + bool needsBuffer = mapPtr == nullptr; + needsBuffer |= pResource->GetBuffer() == nullptr && (pResource->NeedsAnyReadback() || renderable); + if (unlikely(needsBuffer)) { + // Creating a buffer (or switching from a MemoryFile to a buffer) is done for the entire resource and invalidates + // the pointers. So do it once for the entire resource if any subresource gives us a reason. + // Do it even if we might be able to bypass the readback for this subresource later. + pResource->EnsureBufferExists(true); + mapPtr = pResource->GetData(Subresource); + } + // Skip readback if we discard is specified. We can only do this for textures that have an associated Vulkan image. // Any other texture might write to the Vulkan staging buffer directly. (GetBackbufferData for example) needsReadback &= pResource->GetImage() != nullptr || !(Flags & D3DLOCK_DISCARD); pResource->SetNeedsReadback(Subresource, false); - if (unlikely(pResource->GetImage() != nullptr || needsReadback)) { - // Create mapping buffer if it doesn't exist yet. (POOL_DEFAULT) - pResource->EnsureBufferExists(!needsReadback); - } - - // Don't use MapTexture here to keep the mapped list small while the resource is still locked. - void* mapPtr = pResource->GetData(Subresource); - if (unlikely(needsReadback)) { // The texture was written to on the GPU. // This can be either the image (for D3DPOOL_DEFAULT) From 2d0499f5602c42463f13f041fb7b587986c2f8ae Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Tue, 13 Oct 2026 19:47:34 +0200 Subject: [PATCH 046/135] [util] Fix building memory mapped file code for 64bit Useful for testing. --- src/util/util_unmap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/util_unmap.cpp b/src/util/util_unmap.cpp index 40678d47..c385651b 100644 --- a/src/util/util_unmap.cpp +++ b/src/util/util_unmap.cpp @@ -44,7 +44,7 @@ namespace dxvk { // Bump granularity to the allocator's page size for consistenty result = sysInfo.dwAllocationGranularity; - result = std::max(result, uint32_t(DxvkPageAllocator::PageSize)); + result = std::max(result, size_t(DxvkPageAllocator::PageSize)); s_granularity.store(result, std::memory_order_relaxed); return result; From 227b83b3af7b7efe85d6d1352ca12c4fbb1df366 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Thu, 1 Oct 2026 18:20:03 +0200 Subject: [PATCH 047/135] [d3d8] Reimplement CopyRects in the d3d8<->d3d9 bridge directly --- src/d3d8/d3d8_device.cpp | 287 +----------------------------------- src/d3d9/d3d9_bridge.cpp | 303 +++++++++++++++++++++++++++++++++------ src/d3d9/d3d9_bridge.h | 56 +++++--- src/d3d9/d3d9_device.cpp | 18 +-- 4 files changed, 309 insertions(+), 355 deletions(-) diff --git a/src/d3d8/d3d8_device.cpp b/src/d3d8/d3d8_device.cpp index 2e936574..2b2f2c09 100644 --- a/src/d3d8/d3d8_device.cpp +++ b/src/d3d8/d3d8_device.cpp @@ -692,22 +692,6 @@ namespace dxvk { return res; } - /** - * \brief D3D8 CopyRects implementation - * - * \details - * The following table shows the possible combinations of source - * and destination surface pools, and how we handle each of them. - * - * ┌────────────┬───────────────────────────┬───────────────────────┬───────────────────────┬──────────────────────┐ - * │ Src/Dst │ DEFAULT │ MANAGED │ SYSTEMMEM │ SCRATCH │ - * ├────────────┼───────────────────────────┼───────────────────────┼───────────────────────┼──────────────────────┤ - * │ DEFAULT │ StretchRect │ GetRenderTargetData │ GetRenderTargetData │ GetRenderTargetData │ - * │ MANAGED │ UpdateTextureFromBuffer │ memcpy │ memcpy │ memcpy │ - * │ SYSTEMMEM │ UpdateSurface │ memcpy │ memcpy │ memcpy │ - * │ SCRATCH │ memcpy + UpdateSurface │ memcpy │ memcpy │ memcpy │ - * └────────────┴───────────────────────────┴───────────────────────┴───────────────────────┴──────────────────────┘ - */ HRESULT STDMETHODCALLTYPE D3D8Device::CopyRects( IDirect3DSurface8* pSourceSurface, const RECT* pSourceRectsArray, @@ -723,275 +707,10 @@ namespace dxvk { return D3DERR_INVALIDCALL; } - // TODO: No stretching or clipping of either source or destination rectangles. - // All src/dest rectangles must fit within the dest surface. + D3D8Surface* src = static_cast(pSourceSurface); + D3D8Surface* dst = static_cast(pDestinationSurface); - Com src = static_cast(pSourceSurface); - Com dst = static_cast(pDestinationSurface); - - d3d9::D3DSURFACE_DESC srcDesc, dstDesc; - src->GetD3D9()->GetDesc(&srcDesc); - dst->GetD3D9()->GetDesc(&dstDesc); - - // This method does not support format conversion. - if (unlikely(srcDesc.Format != dstDesc.Format)) - return D3DERR_INVALIDCALL; - - // This method cannot be applied to surfaces whose formats - // are classified as depth stencil formats. - if (unlikely(IsDepthStencilFormat(static_cast(srcDesc.Format)))) - return D3DERR_INVALIDCALL; - - StateChange(); - - // If pSourceRectsArray is NULL, then the entire surface is copied - RECT rect; - POINT point = { 0, 0 }; - if (pSourceRectsArray == NULL) { - cRects = 1; - rect.top = rect.left = 0; - rect.right = srcDesc.Width; - rect.bottom = srcDesc.Height; - pSourceRectsArray = ▭ - - pDestPointsArray = &point; - } - - for (uint32_t i = 0; i < cRects; i++) { - RECT srcRect, dstRect; - srcRect = pSourceRectsArray[i]; - - // True if the copy is asymmetric - bool asymmetric = false; - // True if the copy requires stretching (not technically supported) - bool stretch = false; - - if (pDestPointsArray != NULL) { - dstRect.left = pDestPointsArray[i].x; - dstRect.right = dstRect.left + (srcRect.right - srcRect.left); - dstRect.top = pDestPointsArray[i].y; - dstRect.bottom = dstRect.top + (srcRect.bottom - srcRect.top); - asymmetric = dstRect.left != srcRect.left || dstRect.top != srcRect.top - || dstRect.right != srcRect.right || dstRect.bottom != srcRect.bottom; - - stretch = (dstRect.right-dstRect.left) != (srcRect.right-srcRect.left) - || (dstRect.bottom-dstRect.top) != (srcRect.bottom-srcRect.top); - } else { - dstRect = srcRect; - } - - POINT dstPt = { dstRect.left, dstRect.top }; - - switch (dstDesc.Pool) { - - // Dest: DEFAULT - case d3d9::D3DPOOL_DEFAULT: - switch (srcDesc.Pool) { - case d3d9::D3DPOOL_DEFAULT: { - // DEFAULT -> DEFAULT: use StretchRect - return GetD3D9()->StretchRect( - src->GetD3D9(), - &srcRect, - dst->GetD3D9(), - &dstRect, - d3d9::D3DTEXF_NONE - ); - } - case d3d9::D3DPOOL_MANAGED: { - // MANAGED -> DEFAULT: UpdateTextureFromBuffer - return m_bridge->UpdateTextureFromBuffer( - src->GetD3D9(), - dst->GetD3D9(), - &srcRect, - &dstPt - ); - } - case d3d9::D3DPOOL_SYSTEMMEM: { - // SYSTEMMEM -> DEFAULT: use UpdateSurface - return GetD3D9()->UpdateSurface( - src->GetD3D9(), - &srcRect, - dst->GetD3D9(), - &dstPt - ); - } - case d3d9::D3DPOOL_SCRATCH: { - // SCRATCH -> DEFAULT: memcpy to a SYSTEMMEM temporary buffer and use UpdateSurface - - const bool isSupportedSurfaceFormat = m_bridge->IsSupportedSurfaceFormat(srcDesc.Format); - // UpdateSurface will not work on surface formats unsupported by D3DPOOL_DEFAULT - if (unlikely(!isSupportedSurfaceFormat)) - return D3DERR_INVALIDCALL; - - Com pTempImageSurface; - // The temporary image surface is guaranteed to end up in SYSTEMMEM for supported formats - HRESULT res = CreateImageSurface( - srcDesc.Width, - srcDesc.Height, - static_cast(srcDesc.Format), - &pTempImageSurface - ); - if (unlikely(FAILED(res))) - return res; - - Com pBlitImage = static_cast(pTempImageSurface.ptr()); - // Temporary image surface dimensions are identical, so we can reuse srcDesc/Rect - res = copyTextureBuffers(src.ptr(), pBlitImage.ptr(), srcDesc, srcDesc, srcRect, srcRect); - if (unlikely(FAILED(res))) - return res; - - return GetD3D9()->UpdateSurface( - pBlitImage->GetD3D9(), - &srcRect, - dst->GetD3D9(), - &dstPt - ); - } - default: { - return D3DERR_INVALIDCALL; - } - } break; - - // Dest: MANAGED - case d3d9::D3DPOOL_MANAGED: - switch (srcDesc.Pool) { - // TODO: Copy on GPU (handle MANAGED similarly to SYSTEMMEM for now) - case d3d9::D3DPOOL_DEFAULT: { - // Get temporary off-screen surface for stretching. - Com pBlitImage = dst->GetBlitImage(); - - // Stretch the source RT to the temporary surface. - HRESULT res = GetD3D9()->StretchRect( - src->GetD3D9(), - &srcRect, - pBlitImage.ptr(), - &dstRect, - d3d9::D3DTEXF_NONE); - if (unlikely(FAILED(res))) - return res; - - // Now sync the rendertarget data into main memory. - return GetD3D9()->GetRenderTargetData(pBlitImage.ptr(), dst->GetD3D9()); - } - case d3d9::D3DPOOL_MANAGED: - case d3d9::D3DPOOL_SYSTEMMEM: - case d3d9::D3DPOOL_SCRATCH: { - // MANAGED/SYSMEM/SCRATCH -> MANAGED: LockRect / memcpy - - if (unlikely(stretch)) - return D3DERR_INVALIDCALL; - - return copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect); - } - default: { - return D3DERR_INVALIDCALL; - } - } break; - - // DEST: SYSTEMMEM - case d3d9::D3DPOOL_SYSTEMMEM: { - - // RT (DEFAULT) -> SYSTEMMEM: Use GetRenderTargetData as fast path if possible - if ((srcDesc.Usage & D3DUSAGE_RENDERTARGET || m_renderTarget == src.ptr())) { - - // GetRenderTargetData works if the formats and sizes match - if (srcDesc.MultiSampleType == d3d9::D3DMULTISAMPLE_NONE - && srcDesc.Width == dstDesc.Width - && srcDesc.Height == dstDesc.Height - && srcDesc.Format == dstDesc.Format - && !asymmetric) { - return GetD3D9()->GetRenderTargetData(src->GetD3D9(), dst->GetD3D9()); - } - } - - switch (srcDesc.Pool) { - case d3d9::D3DPOOL_DEFAULT: { - // Get temporary off-screen surface for stretching. - Com pBlitImage = dst->GetBlitImage(); - - // Stretch the source RT to the temporary surface. - HRESULT res = GetD3D9()->StretchRect( - src->GetD3D9(), - &srcRect, - pBlitImage.ptr(), - &dstRect, - d3d9::D3DTEXF_NONE); - if (unlikely(FAILED(res))) - return res; - - // Now sync the rendertarget data into main memory. - return GetD3D9()->GetRenderTargetData(pBlitImage.ptr(), dst->GetD3D9()); - } - // MANAGED/SYSMEM/SCRATCH -> SYSMEM: LockRect / memcpy - case d3d9::D3DPOOL_MANAGED: - case d3d9::D3DPOOL_SYSTEMMEM: - case d3d9::D3DPOOL_SCRATCH: { - if (unlikely(stretch)) - return D3DERR_INVALIDCALL; - - return copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect); - } - default: { - return D3DERR_INVALIDCALL; - } - } break; - } - - // DEST: SCRATCH - case d3d9::D3DPOOL_SCRATCH: { - - // RT (DEFAULT) -> SCRATCH: Use GetRenderTargetData as fast path if possible - if ((srcDesc.Usage & D3DUSAGE_RENDERTARGET || m_renderTarget == src.ptr())) { - - // GetRenderTargetData works if the formats and sizes match - if (srcDesc.MultiSampleType == d3d9::D3DMULTISAMPLE_NONE - && srcDesc.Width == dstDesc.Width - && srcDesc.Height == dstDesc.Height - && srcDesc.Format == dstDesc.Format - && !asymmetric) { - return GetD3D9()->GetRenderTargetData(src->GetD3D9(), dst->GetD3D9()); - } - } - - switch (srcDesc.Pool) { - case d3d9::D3DPOOL_DEFAULT: { - // Get temporary off-screen surface for stretching. - Com pBlitImage = dst->GetBlitImage(); - - // Stretch the source RT to the temporary surface. - HRESULT res = GetD3D9()->StretchRect( - src->GetD3D9(), - &srcRect, - pBlitImage.ptr(), - &dstRect, - d3d9::D3DTEXF_NONE); - if (unlikely(FAILED(res))) - return res; - - // Now sync the rendertarget data into main memory. - return GetD3D9()->GetRenderTargetData(pBlitImage.ptr(), dst->GetD3D9()); - } - // MANAGED/SYSMEM/SCRATCH -> SCRATCH: LockRect / memcpy - case d3d9::D3DPOOL_MANAGED: - case d3d9::D3DPOOL_SYSTEMMEM: - case d3d9::D3DPOOL_SCRATCH: { - if (unlikely(stretch)) - return D3DERR_INVALIDCALL; - - return copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect); - } - default: { - return D3DERR_INVALIDCALL; - } - } break; - } - default: { - return D3DERR_INVALIDCALL; - } - } - } - - return D3DERR_INVALIDCALL; + return m_bridge->CopyRects(src->GetD3D9(), pSourceRectsArray, cRects, dst->GetD3D9(), pDestPointsArray); } HRESULT STDMETHODCALLTYPE D3D8Device::UpdateTexture( diff --git a/src/d3d9/d3d9_bridge.cpp b/src/d3d9/d3d9_bridge.cpp index ae5963f7..66dec187 100644 --- a/src/d3d9/d3d9_bridge.cpp +++ b/src/d3d9/d3d9_bridge.cpp @@ -29,66 +29,287 @@ namespace dxvk { return m_device->QueryInterface(riid, ppvObject); } - HRESULT DxvkLegacyD3DDeviceBridge::UpdateTextureFromBuffer( - IDirect3DSurface9* pDestSurface, - IDirect3DSurface9* pSrcSurface, - const RECT* pSrcRect, - const POINT* pDestPoint) { - auto lock = m_device->LockDevice(); - D3D9Surface* dst = static_cast(pDestSurface); - D3D9Surface* src = static_cast(pSrcSurface); + /** + * \brief D3D8 CopyRects implementation + */ + HRESULT DxvkLegacyD3DDeviceBridge::CopyRects( + IDirect3DSurface9* pSourceSurface, + const RECT* pSourceRectsArray, + UINT cRects, + IDirect3DSurface9* pDestinationSurface, + const POINT* pDestPointsArray) { + D3D9DeviceLock lock = m_device->LockDevice(); - if (unlikely(dst == nullptr || src == nullptr)) - return D3DERR_INVALIDCALL; + // The D3D8 side already checks for NULLs and Src == Dst. - // CopyRects will not pass a null pSrcRect, but check anyway - if (unlikely(pSrcRect == nullptr)) - return D3DERR_INVALIDCALL; + D3D9Surface* src = static_cast(pSourceSurface); + D3D9Surface* dst = static_cast(pDestinationSurface); - // validate dimensions to ensure we calculate a meaningful srcOffset & extent - if (unlikely(pSrcRect->left < 0 - || pSrcRect->top < 0 - || pSrcRect->right <= pSrcRect->left - || pSrcRect->bottom <= pSrcRect->top)) - return D3DERR_INVALIDCALL; + D3D9CommonTexture* srcTex = src->GetCommonTexture(); + D3D9CommonTexture* dstTex = dst->GetCommonTexture(); - // CopyRects will not pass a null pDestPoint, but check anyway - if (unlikely(pDestPoint == nullptr)) + VkExtent3D srcTexLevelExtent = srcTex->GetExtentMip(src->GetMipLevel()); + VkExtent3D dstTexLevelExtent = dstTex->GetExtentMip(dst->GetMipLevel()); + + // This function does not support format conversion. + if (unlikely(srcTex->Desc()->Format != dstTex->Desc()->Format)) return D3DERR_INVALIDCALL; - // validate dimensions to ensure we caculate a meaningful dstOffset - if (unlikely(pDestPoint->x < 0 - || pDestPoint->y < 0)) + // This function cannot be applied to surfaces whose formats are classified as depth stencil formats. + if (unlikely(IsDepthStencilFormat(dstTex->Desc()->Format))) return D3DERR_INVALIDCALL; - D3D9CommonTexture* srcTextureInfo = src->GetCommonTexture(); - D3D9CommonTexture* dstTextureInfo = dst->GetCommonTexture(); + if (unlikely(dstTex->GetFormatMapping().ConversionFormatInfo.FormatType != D3D9ConversionFormat_None && (srcTex->GetImage() != nullptr && dstTex->GetImage() == nullptr))) { + Logger::err("CopyRects with formats, that need conversion, is only supported if it's an image to image copy."); + return D3DERR_NOTAVAILABLE; + } - VkOffset3D srcOffset = { pSrcRect->left, - pSrcRect->top, - 0u }; + // If pSourceRectsArray is NULL, then the entire surface is copied + RECT rect = { 0, 0, int32_t(srcTexLevelExtent.width), int32_t(srcTexLevelExtent.height) }; + POINT point = { 0, 0 }; + if (pSourceRectsArray == nullptr) { + cRects = 1; + pSourceRectsArray = ▭ + pDestPointsArray = &point; + } + + auto formatInfo = lookupFormatInfo(dstTex->GetFormatMapping().Format); - VkExtent3D extent = { uint32_t(pSrcRect->right - pSrcRect->left), uint32_t(pSrcRect->bottom - pSrcRect->top), 1 }; + // Validate all rects & points + for (uint32_t i = 0; i < cRects; i++) { + RECT srcRect = pSourceRectsArray[i]; + POINT dstPoint; + if (pDestPointsArray != nullptr) { + dstPoint = pDestPointsArray[i]; + } else { + dstPoint = { srcRect.left, srcRect.top }; + } - VkOffset3D dstOffset = { pDestPoint->x, - pDestPoint->y, - 0u }; + if (srcRect.left < 0 || srcRect.top < 0 + || srcRect.left >= srcRect.right + || srcRect.top >= srcRect.bottom + || uint32_t(srcRect.right) > srcTexLevelExtent.width + || uint32_t(srcRect.bottom) > srcTexLevelExtent.height + || (srcRect.left % formatInfo->blockSize.width) != 0 + || (srcRect.right % formatInfo->blockSize.width) != 0 + || (srcRect.top % formatInfo->blockSize.height) != 0 + || (srcRect.bottom % formatInfo->blockSize.height) != 0) { + return D3DERR_INVALIDCALL; + } + + VkExtent2D extent = { uint32_t(srcRect.right - srcRect.left), uint32_t(srcRect.bottom - srcRect.top) }; + if (dstPoint.x < 0 || dstPoint.y < 0 + || dstPoint.x + extent.width > dstTexLevelExtent.width + || dstPoint.y + extent.height > dstTexLevelExtent.height + || (dstPoint.x % formatInfo->blockSize.width) != 0 + || (dstPoint.y % formatInfo->blockSize.height) != 0) { + return D3DERR_INVALIDCALL; + } + } - m_device->UpdateTextureFromBuffer( - srcTextureInfo, dstTextureInfo, - src->GetSubresource(), dst->GetSubresource(), - srcOffset, extent, dstOffset - ); + bool srcHasImage = srcTex->GetImage() != nullptr; + bool dstHasImage = dstTex->GetImage() != nullptr; - dstTextureInfo->SetNeedsReadback(dst->GetSubresource(), true); + // Flush remaining managed texture uploads. + if (srcHasImage && srcTex->NeedsUpload(src->GetSubresource())) + m_device->FlushImage(srcTex, src->GetSubresource()); + if (dstHasImage && dstTex->NeedsUpload(dst->GetSubresource())) + m_device->FlushImage(dstTex, dst->GetSubresource()); - if (dstTextureInfo->IsAutomaticMip()) - m_device->MarkTextureMipsDirty(dstTextureInfo); + for (uint32_t i = 0; i < cRects; i++) { + RECT srcRect; + POINT dstPoint; + srcRect = pSourceRectsArray[i]; + if (pDestPointsArray != nullptr) { + dstPoint = pDestPointsArray[i]; + } else { + dstPoint = { srcRect.left, srcRect.top }; + } + + VkOffset3D dstOffset = { dstPoint.x, dstPoint.y, 0}; + VkOffset3D srcOffset = { srcRect.left, srcRect.top, 0 }; + VkExtent3D extent = { uint32_t(srcRect.right - srcRect.left), uint32_t(srcRect.bottom - srcRect.top), 1 }; + + VkExtent3D blockCount = util::computeBlockCount(extent, formatInfo->blockSize); + VkOffset3D srcOffsetBlockCount = util::computeBlockOffset(srcOffset, formatInfo->blockSize); + VkOffset3D dstOffsetBlockCount = util::computeBlockOffset(dstOffset, formatInfo->blockSize); + VkExtent3D srcTexLevelExtentBlockCount = util::computeBlockCount(srcTexLevelExtent, formatInfo->blockSize); + VkExtent3D dstTexLevelExtentBlockCount = util::computeBlockCount(dstTexLevelExtent, formatInfo->blockSize); + VkDeviceSize srcPitch = align(srcTexLevelExtentBlockCount.width * formatInfo->elementSize, 4); + VkDeviceSize dstPitch = align(dstTexLevelExtentBlockCount.width * formatInfo->elementSize, 4); + VkDeviceSize bufferSrcOffset = srcOffsetBlockCount.y * srcPitch + srcOffsetBlockCount.x * formatInfo->elementSize; + VkDeviceSize bufferDstOffset = dstOffsetBlockCount.y * dstPitch + dstOffsetBlockCount.x * formatInfo->elementSize; + + if (srcHasImage && dstHasImage) { + CopyRectImageToImage(srcTex, dstTex, src->GetSubresource(), dst->GetSubresource(), + srcOffset, dstOffset, extent); + } + else if (!srcHasImage && dstHasImage) { + // Just use the staging buffer upload here. + m_device->UpdateTextureFromBuffer(dstTex, srcTex, dst->GetSubresource(), src->GetSubresource(), + srcOffset, extent, dstOffset); + } + else if (srcHasImage && !dstHasImage) { + CopyRectImageToBuffer(srcTex, dstTex, src->GetSubresource(), dst->GetSubresource(), + srcOffset, bufferDstOffset, dstPitch, extent, formatInfo); + } + else if (!srcHasImage && !dstHasImage) { + CopyRectBufferToBuffer(srcTex, dstTex, src->GetSubresource(), dst->GetSubresource(), + bufferSrcOffset, bufferDstOffset, srcPitch, dstPitch, blockCount, formatInfo); + } + } + + // We do GPU copies to the DST unless both src and dst don't have images. + if (srcHasImage || dstHasImage) + dstTex->SetNeedsReadback(dst->GetSubresource(), true); + + if (dstTex->IsAutomaticMip()) + m_device->MarkTextureMipsDirty(dstTex); return D3D_OK; } + void DxvkLegacyD3DDeviceBridge::CopyRectImageToImage( + const D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + VkOffset3D SrcOffset, VkOffset3D DstOffset, VkExtent3D Extent + ) { + // The backend will thankfully handle all VkUsage issues. + + const Rc& srcImage = SrcTex->GetImage(); + const Rc& dstImage = DstTex->GetImage(); + + VkImageSubresourceLayers srcLayers = vk::makeSubresourceLayers(SrcTex->GetSubresourceFromIndex( + VK_IMAGE_ASPECT_COLOR_BIT, SrcSubresource)); + + VkImageSubresourceLayers dstLayers = vk::makeSubresourceLayers(DstTex->GetSubresourceFromIndex( + VK_IMAGE_ASPECT_COLOR_BIT, DstSubresource)); + + if (dstImage->info().sampleCount == srcImage->info().sampleCount + || dstImage->info().sampleCount == 1u) { + m_device->EmitCs([ + cSrcImage = srcImage, + cDstImage = dstImage, + cSrcOffset = SrcOffset, + cDstOffset = DstOffset, + cSrcLayers = srcLayers, + cDstLayers = dstLayers, + cExtent = Extent, + cResolve = srcImage->info().sampleCount != dstImage->info().sampleCount + ] (DxvkContext* ctx) { + if (!cResolve) { + ctx->copyImage(cDstImage, cDstLayers, cDstOffset, + cSrcImage, cSrcLayers, cSrcOffset, cExtent); + } else { + VkImageResolve region; + region.srcSubresource = cSrcLayers; + region.srcOffset = cSrcOffset; + region.dstSubresource = cDstLayers; + region.dstOffset = cDstOffset; + region.extent = cExtent; + + ctx->resolveImage( + cDstImage, cSrcImage, region, cSrcImage->info().format, VK_RESOLVE_MODE_AVERAGE_BIT, + VK_RESOLVE_MODE_SAMPLE_ZERO_BIT); + } + }); + } else { + DxvkImageViewKey srcViewInfo; + srcViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + srcViewInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + srcViewInfo.format = srcImage->info().format; + srcViewInfo.aspects = srcLayers.aspectMask; + srcViewInfo.mipIndex = srcLayers.mipLevel; + srcViewInfo.mipCount = 1u; + srcViewInfo.layerIndex = srcLayers.baseArrayLayer; + srcViewInfo.layerCount = 1u; + srcViewInfo.packedSwizzle = DxvkImageViewKey::packSwizzle(SrcTex->GetMapping().Swizzle); + + DxvkImageViewKey dstViewInfo = srcViewInfo; + dstViewInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + dstViewInfo.mipIndex = dstLayers.mipLevel; + dstViewInfo.layerIndex = dstLayers.baseArrayLayer; + + m_device->EmitCs([ + cSrcView = srcImage->createView(srcViewInfo), + cDstView = dstImage->createView(dstViewInfo), + cDstOffset = DstOffset, + cSrcOffset = SrcOffset, + cExtent = Extent + ] (DxvkContext* ctx) { + std::array offsets = { + cSrcOffset, + VkOffset3D { cSrcOffset.x + int32_t(cExtent.width), cSrcOffset.y + int32_t(cExtent.width), cSrcOffset.z + int32_t(cExtent.depth) }, + cDstOffset, + VkOffset3D { cDstOffset.x + int32_t(cExtent.width), cDstOffset.y + int32_t(cExtent.width), cDstOffset.z + int32_t(cExtent.depth) }, + }; + ctx->blitImageView( + cDstView, &offsets[2], + cSrcView, offsets.data(), + VK_FILTER_NEAREST); + }); + } + } + + void DxvkLegacyD3DDeviceBridge::CopyRectImageToBuffer( + const D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + VkOffset3D SrcOffset, uint32_t DstOffset, uint32_t DstPitch, + VkExtent3D Extent, const DxvkFormatInfo* FormatInfo + ) { + DstTex->EnsureBufferExists(true); + m_device->EmitCs([ + cDstBuffer = DstTex->GetBuffer(), + cSrcImage = SrcTex->GetImage(), + cDstLayers = vk::makeSubresourceLayers(DstTex->GetSubresourceFromIndex( + FormatInfo->aspectMask, DstSubresource)), + cSrcLayers = vk::makeSubresourceLayers(SrcTex->GetSubresourceFromIndex( + FormatInfo->aspectMask, SrcSubresource)), + cSrcOffset = SrcOffset, + cBufferOffset = DstOffset, + cDstPitch = DstPitch, + cExtent = Extent + ] (DxvkContext* ctx) { + ctx->copyImageToBuffer(cDstBuffer, cBufferOffset, cDstPitch, 0, + VK_FORMAT_UNDEFINED, cSrcImage, cSrcLayers, cSrcOffset, cExtent); + }); + m_device->TrackTextureMappingBufferSequenceNumber(DstTex, DstSubresource); + } + + void DxvkLegacyD3DDeviceBridge::CopyRectBufferToBuffer( + D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + uint32_t SrcOffset, uint32_t DstOffset, uint32_t SrcPitch, uint32_t DstPitch, + VkExtent3D BlockCount, const DxvkFormatInfo* FormatInfo + ) { + VkExtent3D alignedExtent = util::computeBlockExtent(BlockCount, FormatInfo->blockSize); + + const Rc& srcBuffer = SrcTex->GetBuffer(); + const Rc& dstBuffer = DstTex->GetBuffer(); + if (unlikely(SrcTex->NeedsReadback(SrcSubresource) + && !m_device->WaitForResource(*srcBuffer, SrcTex->GetMappingBufferSequenceNumber(SrcSubresource), D3DLOCK_DONOTWAIT | D3DLOCK_READONLY))) { + Logger::warn("Stalling in CopyRects because of src."); + m_device->WaitForResource(*srcBuffer, SrcTex->GetMappingBufferSequenceNumber(SrcSubresource), 0); + } + if (unlikely(DstTex->NeedsReadback(DstSubresource)) + && !m_device->WaitForResource(*dstBuffer, DstTex->GetMappingBufferSequenceNumber(DstSubresource), D3DLOCK_DONOTWAIT)) { + Logger::warn("Stalling in CopyRects because of dst."); + m_device->WaitForResource(*dstBuffer, DstTex->GetMappingBufferSequenceNumber(DstSubresource), 0); + } + SrcTex->SetNeedsReadback(SrcSubresource, false); + DstTex->SetNeedsReadback(DstSubresource, false); + + // We assume that using CopyRects with DEFAULT->SYSTEMMEM and then SYSTEMMEM->SYSTEMMEM doesn't really happen in practice. + // So the buffers don't need sync and we can just do the copy on the CPU here. + + const void* srcMapPtr = static_cast(SrcTex->GetData(SrcSubresource)) + SrcOffset; + void* dstMapPtr = static_cast(DstTex->GetData(DstSubresource)) + DstOffset; + VkDeviceSize dirtySize = BlockCount.width * BlockCount.height * BlockCount.depth * FormatInfo->elementSize; + D3D9BufferSlice slice = m_device->AllocStagingBuffer(dirtySize); + util::packImageData( + dstMapPtr, srcMapPtr, SrcPitch, 0, DstPitch, 0, VK_IMAGE_TYPE_2D, alignedExtent, 1, FormatInfo, + VK_IMAGE_ASPECT_COLOR_BIT); + } + + bool DxvkLegacyD3DDeviceBridge::IsSupportedSurfaceFormat(D3DFORMAT Format) { auto mapping = m_device->LookupFormat(EnumerateFormat(Format)); return mapping.IsValid(); diff --git a/src/d3d9/d3d9_bridge.h b/src/d3d9/d3d9_bridge.h index aa814ef2..b735cde4 100644 --- a/src/d3d9/d3d9_bridge.h +++ b/src/d3d9/d3d9_bridge.h @@ -24,23 +24,17 @@ IDxvkLegacyD3DDeviceBridge : public IUnknown { // D3D8 keeps D3D9 objects contained in a namespace. #ifdef DXVK_D3D9_NAMESPACE + using IDirect3DTexture9 = d3d9::IDirect3DTexture9; using IDirect3DSurface9 = d3d9::IDirect3DSurface9; using D3DFORMAT = d3d9::D3DFORMAT; #endif - /** - * \brief Updates a D3D9 surface from a D3D9 buffer - * - * \param [in] pDestSurface Destination surface (typically in VRAM) - * \param [in] pSrcSurface Source surface (typically in system memory) - * \param [in] pSrcRect Source rectangle - * \param [in] pDestPoint Destination (top-left) point - */ - virtual HRESULT UpdateTextureFromBuffer( - IDirect3DSurface9* pDestSurface, - IDirect3DSurface9* pSrcSurface, - const RECT* pSrcRect, - const POINT* pDestPoint) = 0; + virtual HRESULT CopyRects( + IDirect3DSurface9* pSourceSurface, + const RECT* pSourceRectsArray, + UINT cRects, + IDirect3DSurface9* pDestinationSurface, + const POINT* pDestPointsArray) = 0; /** * \brief Checks if a particular surface format is supported by D3D9 @@ -75,6 +69,9 @@ __CRT_UUID_DECL(IDxvkLegacyD3DDeviceBridge, 0xD3D0D3D9, 0x42A9, 0x4C1E, 0xAA, __CRT_UUID_DECL(IDxvkLegacyD3DInterfaceBridge, 0xD3D0D3D9, 0xA407, 0x773E, 0x18, 0xE9, 0xCA, 0xFE, 0xBE, 0xEF, 0x30, 0x00); #endif +struct VkExtent3D; +struct VkOffset3D; + namespace dxvk { using D3DCompatibility = DxvkD3DCompatibility; @@ -82,6 +79,9 @@ namespace dxvk { class D3D9DeviceEx; class D3D9InterfaceEx; + class D3D9CommonTexture; + + struct DxvkFormatInfo; class DxvkLegacyD3DDeviceBridge : public IDxvkLegacyD3DDeviceBridge { @@ -97,16 +97,34 @@ namespace dxvk { REFIID riid, void** ppvObject); - HRESULT UpdateTextureFromBuffer( - IDirect3DSurface9* pDestSurface, - IDirect3DSurface9* pSrcSurface, - const RECT* pSrcRect, - const POINT* pDestPoint); - bool IsSupportedSurfaceFormat(D3DFORMAT Format); + HRESULT CopyRects( + IDirect3DSurface9* pSourceSurface, + const RECT* pSourceRectsArray, + UINT cRects, + IDirect3DSurface9* pDestinationSurface, + const POINT* pDestPointsArray); + private: + void CopyRectImageToImage( + const D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + VkOffset3D SrcOffset, VkOffset3D DstOffset, VkExtent3D Extent + ); + + void CopyRectImageToBuffer( + const D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + VkOffset3D SrcOffset, uint32_t DstOffset, uint32_t DstPitch, + VkExtent3D Extent, const DxvkFormatInfo* FormatInfo + ); + + void CopyRectBufferToBuffer( + D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + uint32_t SrcOffset, uint32_t DstOffset, uint32_t SrcPitch, uint32_t DstPitch, + VkExtent3D BlockCount, const DxvkFormatInfo* FormatInfo + ); + D3D9DeviceEx* m_device; }; diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index c1f4671f..bc5a4a7b 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -1161,9 +1161,7 @@ namespace dxvk { if (unlikely(srcTexInfo->Desc()->Pool != D3DPOOL_DEFAULT)) return D3DERR_INVALIDCALL; - // Our D3D8 implementation uses it in CopyRects to copy DEFAULT -> SYSTEMMEM/SCRATCH/MANAGED. - // TODO: Using it for MANAGED is problematic because it doesn't update the image of a MANAGED texture. - if (unlikely(!m_d3dCompatibility.test(D3DCompatibility::D3D8) && dstTexInfo->Desc()->Pool != D3DPOOL_SYSTEMMEM)) + if (unlikely(dstTexInfo->Desc()->Pool != D3DPOOL_SYSTEMMEM)) return D3DERR_INVALIDCALL; if (unlikely(srcTexInfo->Desc()->MultiSample != D3DMULTISAMPLE_NONE)) @@ -5042,24 +5040,22 @@ namespace dxvk { pResource->SetNeedsReadback(Subresource, false); if (unlikely(needsReadback)) { - // The texture was written to on the GPU. - // This can be either the image (for D3DPOOL_DEFAULT) - // or the buffer directly (for D3DPOOL_SYSTEMMEM). - - DxvkBufferSlice mappedBufferSlice = pResource->GetBufferSlice(Subresource); - const Rc mappedBuffer = pResource->GetBuffer(); + // The texture (or the buffer) was written to on the GPU. + // This can be either the image (for D3DPOOL_DEFAULT) or the buffer directly (for D3DPOOL_SYSTEMMEM). if (unlikely(pResource->GetFormatMapping().ConversionFormatInfo.FormatType != D3D9ConversionFormat_None)) { Logger::err(str::format("Reading back format", pResource->Desc()->Format, " is not supported. It is uploaded using the fomrat converter.")); } if (pResource->GetImage() != nullptr) { + // If an image exists, all GPU writes modify that. Copy it to the mapping buffer. + // if packedFormat is VK_FORMAT_UNDEFINED // DxvkContext::copyImageToBuffer will automatically take the format from the image VkFormat packedFormat = GetPackedDepthStencilFormat(desc.Format); EmitCs([ - cImageBufferSlice = std::move(mappedBufferSlice), + cImageBufferSlice = pResource->GetBufferSlice(Subresource), cImage = pResource->GetImage(), cSubresources = vk::makeSubresourceLayers(subresource), cLevelExtent = levelExtent, @@ -5074,7 +5070,7 @@ namespace dxvk { } // Wait until the buffer is idle which may include the copy (and resolve) we just issued. - if (!WaitForResource(*mappedBuffer, pResource->GetMappingBufferSequenceNumber(Subresource), Flags)) + if (!WaitForResource(*pResource->GetBuffer(), pResource->GetMappingBufferSequenceNumber(Subresource), Flags)) return D3DERR_WASSTILLDRAWING; } From 2f0a0f6216b0384590af870fbdb3782f96781f28 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 17 Oct 2026 01:24:02 +0300 Subject: [PATCH 048/135] [d3d8] Clean up CopyRects related helpers --- src/d3d8/d3d8_buffer.cpp | 2 +- src/d3d8/d3d8_buffer.h | 2 +- src/d3d8/d3d8_caps.h | 2 +- src/d3d8/d3d8_device.cpp | 85 ---------------------------------- src/d3d8/d3d8_format.h | 2 +- src/d3d8/d3d8_interface.cpp | 2 +- src/d3d8/d3d8_interface.h | 2 +- src/d3d8/d3d8_resource.h | 2 +- src/d3d8/d3d8_shader.h | 2 +- src/d3d8/d3d8_state_block.h | 2 +- src/d3d8/d3d8_subresource.h | 2 +- src/d3d8/d3d8_surface.cpp | 31 +------------ src/d3d8/d3d8_surface.h | 14 +----- src/d3d8/d3d8_swapchain.cpp | 2 +- src/d3d8/d3d8_swapchain.h | 2 +- src/d3d8/d3d8_texture.cpp | 2 +- src/d3d8/d3d8_texture.h | 2 +- src/d3d8/d3d8_util.h | 2 +- src/d3d8/d3d8_volume.cpp | 2 +- src/d3d8/d3d8_volume.h | 2 +- src/d3d8/d3d8_wrapped_object.h | 2 +- src/d3d9/d3d9_bridge.cpp | 43 ++++++++--------- src/d3d9/d3d9_bridge.h | 19 ++++++-- 23 files changed, 57 insertions(+), 171 deletions(-) diff --git a/src/d3d8/d3d8_buffer.cpp b/src/d3d8/d3d8_buffer.cpp index 32ee5542..ab567eea 100644 --- a/src/d3d8/d3d8_buffer.cpp +++ b/src/d3d8/d3d8_buffer.cpp @@ -35,4 +35,4 @@ namespace dxvk { return GetD3D9()->GetDesc(reinterpret_cast(pDesc)); } -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_buffer.h b/src/d3d8/d3d8_buffer.h index 092c93ca..32984863 100644 --- a/src/d3d8/d3d8_buffer.h +++ b/src/d3d8/d3d8_buffer.h @@ -88,4 +88,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_caps.h b/src/d3d8/d3d8_caps.h index 0b5c816c..ea1a7813 100644 --- a/src/d3d8/d3d8_caps.h +++ b/src/d3d8/d3d8_caps.h @@ -9,4 +9,4 @@ namespace dxvk::d8caps { constexpr float ZBIAS_SCALE = -1.0f / ((1u << 16) - 1); // Consider D16 precision constexpr float ZBIAS_SCALE_INV = 1 / ZBIAS_SCALE; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_device.cpp b/src/d3d8/d3d8_device.cpp index 2b2f2c09..f01a83ce 100644 --- a/src/d3d8/d3d8_device.cpp +++ b/src/d3d8/d3d8_device.cpp @@ -607,91 +607,6 @@ namespace dxvk { return D3D_OK; } - // Copies texture rect in system mem using memcpy. - // Rects must be congruent, but need not be aligned. - HRESULT copyTextureBuffers( - D3D8Surface* src, - D3D8Surface* dst, - const d3d9::D3DSURFACE_DESC& srcDesc, - const d3d9::D3DSURFACE_DESC& dstDesc, - const RECT& srcRect, - const RECT& dstRect) { - HRESULT res = D3D_OK; - D3DLOCKED_RECT srcLocked, dstLocked; - - const bool compressed = IsDXTFormat(static_cast(srcDesc.Format)); - - res = src->LockRect(&srcLocked, &srcRect, D3DLOCK_READONLY); - if (unlikely(FAILED(res))) - return res; - - res = dst->LockRect(&dstLocked, &dstRect, 0); - if (unlikely(FAILED(res))) { - src->UnlockRect(); - return res; - } - - auto rows = srcRect.bottom - srcRect.top; - auto cols = srcRect.right - srcRect.left; - auto bpp = srcLocked.Pitch / srcDesc.Width; - - if (!compressed - && srcRect.left == 0 - && srcRect.right == LONG(srcDesc.Width) - && srcDesc.Width == dstDesc.Width - && srcLocked.Pitch == dstLocked.Pitch) { - - // If copying the entire texture into a congruent destination, - // we can do this in one continuous copy. - std::memcpy(dstLocked.pBits, srcLocked.pBits, srcLocked.Pitch * rows); - - } else { - // Bytes per row of the rect - auto amplitude = cols * bpp; - - // Handle DXT compressed textures. - if (compressed) { - // DXT blocks are always 4x4 pixels. - constexpr UINT blockWidth = 4; - constexpr UINT blockHeight = 4; - - // Compute rect dimensions in 4x4 blocks - UINT rectWidthBlocks = cols / blockWidth; - UINT rectHeightBlocks = rows / blockHeight; - - // Compute total texture width in blocks - // to derive block size in bytes using the pitch. - UINT texWidthBlocks = std::max(srcDesc.Width / blockWidth, 1u); - UINT bytesPerBlock = srcLocked.Pitch / texWidthBlocks; - - // Copy H/4 rows of W/4 blocks - amplitude = rectWidthBlocks * bytesPerBlock; - rows = rectHeightBlocks; - } - - // Copy one row at a time - size_t srcOffset = 0, dstOffset = 0; - for (auto i = 0; i < rows; i++) { - std::memcpy( - reinterpret_cast(dstLocked.pBits) + dstOffset, - reinterpret_cast(srcLocked.pBits) + srcOffset, - amplitude); - srcOffset += srcLocked.Pitch; - dstOffset += dstLocked.Pitch; - } - } - - res = dst->UnlockRect(); - if (unlikely(FAILED(res))) { - src->UnlockRect(); - return res; - } - - res = src->UnlockRect(); - - return res; - } - HRESULT STDMETHODCALLTYPE D3D8Device::CopyRects( IDirect3DSurface8* pSourceSurface, const RECT* pSourceRectsArray, diff --git a/src/d3d8/d3d8_format.h b/src/d3d8/d3d8_format.h index f9851716..890f8a8a 100644 --- a/src/d3d8/d3d8_format.h +++ b/src/d3d8/d3d8_format.h @@ -151,4 +151,4 @@ namespace dxvk { return Width * Height * GetFormatStride(Format); } -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_interface.cpp b/src/d3d8/d3d8_interface.cpp index b3ed0f48..c11a2f14 100644 --- a/src/d3d8/d3d8_interface.cpp +++ b/src/d3d8/d3d8_interface.cpp @@ -171,4 +171,4 @@ namespace dxvk { return D3D_OK; } -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_interface.h b/src/d3d8/d3d8_interface.h index c56bea8c..e88c20c9 100644 --- a/src/d3d8/d3d8_interface.h +++ b/src/d3d8/d3d8_interface.h @@ -193,4 +193,4 @@ namespace dxvk { D3D8Options m_d3d8Options; }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_resource.h b/src/d3d8/d3d8_resource.h index e4a90a00..d5574f95 100644 --- a/src/d3d8/d3d8_resource.h +++ b/src/d3d8/d3d8_resource.h @@ -112,4 +112,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_shader.h b/src/d3d8/d3d8_shader.h index c72e624c..53eaa598 100644 --- a/src/d3d8/d3d8_shader.h +++ b/src/d3d8/d3d8_shader.h @@ -23,4 +23,4 @@ namespace dxvk { const D3D8Options& overrides, D3D9VertexShaderCode& pTranslatedVS); -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_state_block.h b/src/d3d8/d3d8_state_block.h index 7bec260d..1cfee0aa 100644 --- a/src/d3d8/d3d8_state_block.h +++ b/src/d3d8/d3d8_state_block.h @@ -140,4 +140,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_subresource.h b/src/d3d8/d3d8_subresource.h index d47a2428..06fddec3 100644 --- a/src/d3d8/d3d8_subresource.h +++ b/src/d3d8/d3d8_subresource.h @@ -57,4 +57,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_surface.cpp b/src/d3d8/d3d8_surface.cpp index cb27f37b..cfe0dae9 100644 --- a/src/d3d8/d3d8_surface.cpp +++ b/src/d3d8/d3d8_surface.cpp @@ -46,33 +46,4 @@ namespace dxvk { return GetD3D9()->UnlockRect(); } - // TODO: Consider creating only one texture to - // encompass all surface levels of a texture. - Com D3D8Surface::GetBlitImage() { - if (unlikely(m_blitImage == nullptr)) { - m_blitImage = CreateBlitImage(); - } - - return m_blitImage; - } - - Com D3D8Surface::CreateBlitImage() { - d3d9::D3DSURFACE_DESC desc; - GetD3D9()->GetDesc(&desc); - - // NOTE: This adds a D3DPOOL_DEFAULT resource to the - // device, which counts as losable during device reset - Com image; - HRESULT res = GetParent()->GetD3D9()->CreateRenderTarget( - desc.Width, desc.Height, desc.Format, - d3d9::D3DMULTISAMPLE_NONE, 0, - FALSE, - &image, - NULL); - if (FAILED(res)) - throw DxvkError("D3D8: Failed to create blit image"); - - return image; - } - -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_surface.h b/src/d3d8/d3d8_surface.h index 71db3696..a474d944 100644 --- a/src/d3d8/d3d8_surface.h +++ b/src/d3d8/d3d8_surface.h @@ -33,18 +33,6 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE UnlockRect() final; - /** - * \brief Allocate or reuse an image of the same size - * as this texture for performing blit into system mem. - */ - Com GetBlitImage(); - - private: - - Com CreateBlitImage(); - - Com m_blitImage; - }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_swapchain.cpp b/src/d3d8/d3d8_swapchain.cpp index 601b15c4..dab3dee3 100644 --- a/src/d3d8/d3d8_swapchain.cpp +++ b/src/d3d8/d3d8_swapchain.cpp @@ -36,4 +36,4 @@ namespace dxvk { return D3D_OK; } -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_swapchain.h b/src/d3d8/d3d8_swapchain.h index 6ade066d..081766a5 100644 --- a/src/d3d8/d3d8_swapchain.h +++ b/src/d3d8/d3d8_swapchain.h @@ -28,4 +28,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_texture.cpp b/src/d3d8/d3d8_texture.cpp index 62c1ecb4..641edab2 100644 --- a/src/d3d8/d3d8_texture.cpp +++ b/src/d3d8/d3d8_texture.cpp @@ -168,4 +168,4 @@ namespace dxvk { return GetD3D9()->AddDirtyRect(static_cast(Face), pDirtyRect); } -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_texture.h b/src/d3d8/d3d8_texture.h index b5e8e9ef..7d8f116a 100644 --- a/src/d3d8/d3d8_texture.h +++ b/src/d3d8/d3d8_texture.h @@ -202,4 +202,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_util.h b/src/d3d8/d3d8_util.h index c261d56f..4c6247ee 100644 --- a/src/d3d8/d3d8_util.h +++ b/src/d3d8/d3d8_util.h @@ -203,4 +203,4 @@ namespace dxvk { } } -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_volume.cpp b/src/d3d8/d3d8_volume.cpp index f3fa87a8..8c4f80f9 100644 --- a/src/d3d8/d3d8_volume.cpp +++ b/src/d3d8/d3d8_volume.cpp @@ -38,4 +38,4 @@ namespace dxvk { return GetD3D9()->UnlockBox(); } -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_volume.h b/src/d3d8/d3d8_volume.h index baad95eb..5eab47ba 100644 --- a/src/d3d8/d3d8_volume.h +++ b/src/d3d8/d3d8_volume.h @@ -23,4 +23,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/d3d8/d3d8_wrapped_object.h b/src/d3d8/d3d8_wrapped_object.h index afb015a8..f445ca7e 100644 --- a/src/d3d8/d3d8_wrapped_object.h +++ b/src/d3d8/d3d8_wrapped_object.h @@ -64,4 +64,4 @@ namespace dxvk { }; -} \ No newline at end of file +} diff --git a/src/d3d9/d3d9_bridge.cpp b/src/d3d9/d3d9_bridge.cpp index 66dec187..ce61defc 100644 --- a/src/d3d9/d3d9_bridge.cpp +++ b/src/d3d9/d3d9_bridge.cpp @@ -29,10 +29,6 @@ namespace dxvk { return m_device->QueryInterface(riid, ppvObject); } - - /** - * \brief D3D8 CopyRects implementation - */ HRESULT DxvkLegacyD3DDeviceBridge::CopyRects( IDirect3DSurface9* pSourceSurface, const RECT* pSourceRectsArray, @@ -60,8 +56,9 @@ namespace dxvk { if (unlikely(IsDepthStencilFormat(dstTex->Desc()->Format))) return D3DERR_INVALIDCALL; - if (unlikely(dstTex->GetFormatMapping().ConversionFormatInfo.FormatType != D3D9ConversionFormat_None && (srcTex->GetImage() != nullptr && dstTex->GetImage() == nullptr))) { - Logger::err("CopyRects with formats, that need conversion, is only supported if it's an image to image copy."); + if (unlikely(dstTex->GetFormatMapping().ConversionFormatInfo.FormatType != D3D9ConversionFormat_None + && (srcTex->GetImage() != nullptr && dstTex->GetImage() == nullptr))) { + Logger::err("D3D8Device::CopyRects: Formats that need conversion are only supported on image to image copies"); return D3DERR_NOTAVAILABLE; } @@ -171,9 +168,9 @@ namespace dxvk { } void DxvkLegacyD3DDeviceBridge::CopyRectImageToImage( - const D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, uint32_t SrcSubresource, uint32_t DstSubresource, - VkOffset3D SrcOffset, VkOffset3D DstOffset, VkExtent3D Extent - ) { + const D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, + uint32_t SrcSubresource, uint32_t DstSubresource, + VkOffset3D SrcOffset, VkOffset3D DstOffset, VkExtent3D Extent) { // The backend will thankfully handle all VkUsage issues. const Rc& srcImage = SrcTex->GetImage(); @@ -239,9 +236,13 @@ namespace dxvk { ] (DxvkContext* ctx) { std::array offsets = { cSrcOffset, - VkOffset3D { cSrcOffset.x + int32_t(cExtent.width), cSrcOffset.y + int32_t(cExtent.width), cSrcOffset.z + int32_t(cExtent.depth) }, + VkOffset3D { cSrcOffset.x + int32_t(cExtent.width), + cSrcOffset.y + int32_t(cExtent.width), + cSrcOffset.z + int32_t(cExtent.depth) }, cDstOffset, - VkOffset3D { cDstOffset.x + int32_t(cExtent.width), cDstOffset.y + int32_t(cExtent.width), cDstOffset.z + int32_t(cExtent.depth) }, + VkOffset3D { cDstOffset.x + int32_t(cExtent.width), + cDstOffset.y + int32_t(cExtent.width), + cDstOffset.z + int32_t(cExtent.depth) }, }; ctx->blitImageView( cDstView, &offsets[2], @@ -252,10 +253,10 @@ namespace dxvk { } void DxvkLegacyD3DDeviceBridge::CopyRectImageToBuffer( - const D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + const D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, + uint32_t SrcSubresource, uint32_t DstSubresource, VkOffset3D SrcOffset, uint32_t DstOffset, uint32_t DstPitch, - VkExtent3D Extent, const DxvkFormatInfo* FormatInfo - ) { + VkExtent3D Extent, const DxvkFormatInfo* FormatInfo) { DstTex->EnsureBufferExists(true); m_device->EmitCs([ cDstBuffer = DstTex->GetBuffer(), @@ -276,28 +277,29 @@ namespace dxvk { } void DxvkLegacyD3DDeviceBridge::CopyRectBufferToBuffer( - D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + D3D9CommonTexture* SrcTex, D3D9CommonTexture* DstTex, + uint32_t SrcSubresource, uint32_t DstSubresource, uint32_t SrcOffset, uint32_t DstOffset, uint32_t SrcPitch, uint32_t DstPitch, - VkExtent3D BlockCount, const DxvkFormatInfo* FormatInfo - ) { + VkExtent3D BlockCount, const DxvkFormatInfo* FormatInfo) { VkExtent3D alignedExtent = util::computeBlockExtent(BlockCount, FormatInfo->blockSize); const Rc& srcBuffer = SrcTex->GetBuffer(); const Rc& dstBuffer = DstTex->GetBuffer(); if (unlikely(SrcTex->NeedsReadback(SrcSubresource) && !m_device->WaitForResource(*srcBuffer, SrcTex->GetMappingBufferSequenceNumber(SrcSubresource), D3DLOCK_DONOTWAIT | D3DLOCK_READONLY))) { - Logger::warn("Stalling in CopyRects because of src."); + Logger::warn("D3D8Device::CopyRects: Stalling because of src buffer"); m_device->WaitForResource(*srcBuffer, SrcTex->GetMappingBufferSequenceNumber(SrcSubresource), 0); } if (unlikely(DstTex->NeedsReadback(DstSubresource)) && !m_device->WaitForResource(*dstBuffer, DstTex->GetMappingBufferSequenceNumber(DstSubresource), D3DLOCK_DONOTWAIT)) { - Logger::warn("Stalling in CopyRects because of dst."); + Logger::warn("D3D8Device::CopyRects: Stalling because of dst buffer"); m_device->WaitForResource(*dstBuffer, DstTex->GetMappingBufferSequenceNumber(DstSubresource), 0); } SrcTex->SetNeedsReadback(SrcSubresource, false); DstTex->SetNeedsReadback(DstSubresource, false); - // We assume that using CopyRects with DEFAULT->SYSTEMMEM and then SYSTEMMEM->SYSTEMMEM doesn't really happen in practice. + // We assume that using CopyRects with DEFAULT->SYSTEMMEM and + // then SYSTEMMEM->SYSTEMMEM doesn't really happen in practice. // So the buffers don't need sync and we can just do the copy on the CPU here. const void* srcMapPtr = static_cast(SrcTex->GetData(SrcSubresource)) + SrcOffset; @@ -309,7 +311,6 @@ namespace dxvk { VK_IMAGE_ASPECT_COLOR_BIT); } - bool DxvkLegacyD3DDeviceBridge::IsSupportedSurfaceFormat(D3DFORMAT Format) { auto mapping = m_device->LookupFormat(EnumerateFormat(Format)); return mapping.IsValid(); diff --git a/src/d3d9/d3d9_bridge.h b/src/d3d9/d3d9_bridge.h index b735cde4..dcc178bc 100644 --- a/src/d3d9/d3d9_bridge.h +++ b/src/d3d9/d3d9_bridge.h @@ -24,11 +24,19 @@ IDxvkLegacyD3DDeviceBridge : public IUnknown { // D3D8 keeps D3D9 objects contained in a namespace. #ifdef DXVK_D3D9_NAMESPACE - using IDirect3DTexture9 = d3d9::IDirect3DTexture9; using IDirect3DSurface9 = d3d9::IDirect3DSurface9; using D3DFORMAT = d3d9::D3DFORMAT; #endif + /** + * \brief D3D8 CopyRects implementation + * + * \param [in] pSourceSurface Source D3D9 surface + * \param [in] pSourceRectsArray Source RECTs array (can be NULL) + * \param [in] cRects Number of RECTs + * \param [in] pDestinationSurface Destination D3D9 surface + * \param [in] pDestPointsArray Destination POINTs array (can be NULL) + */ virtual HRESULT CopyRects( IDirect3DSurface9* pSourceSurface, const RECT* pSourceRectsArray, @@ -109,18 +117,21 @@ namespace dxvk { private: void CopyRectImageToImage( - const D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + const D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, + uint32_t SrcSubresource, uint32_t DstSubresource, VkOffset3D SrcOffset, VkOffset3D DstOffset, VkExtent3D Extent ); void CopyRectImageToBuffer( - const D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + const D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, + uint32_t SrcSubresource, uint32_t DstSubresource, VkOffset3D SrcOffset, uint32_t DstOffset, uint32_t DstPitch, VkExtent3D Extent, const DxvkFormatInfo* FormatInfo ); void CopyRectBufferToBuffer( - D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, uint32_t SrcSubresource, uint32_t DstSubresource, + D3D9CommonTexture* srcTex, D3D9CommonTexture* dstTex, + uint32_t SrcSubresource, uint32_t DstSubresource, uint32_t SrcOffset, uint32_t DstOffset, uint32_t SrcPitch, uint32_t DstPitch, VkExtent3D BlockCount, const DxvkFormatInfo* FormatInfo ); From 1197650e3b75fc267f918daa25320da4a24e8265 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 17 Oct 2026 00:37:16 +0200 Subject: [PATCH 049/135] [dxvk] Rework shader cache instance creation --- src/dxvk/dxvk_shader_cache.cpp | 42 +++++++++++++++++++++++++--------- src/dxvk/dxvk_shader_cache.h | 6 +++-- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/dxvk/dxvk_shader_cache.cpp b/src/dxvk/dxvk_shader_cache.cpp index 9e0ea3de..00153ad9 100644 --- a/src/dxvk/dxvk_shader_cache.cpp +++ b/src/dxvk/dxvk_shader_cache.cpp @@ -668,11 +668,31 @@ namespace dxvk { Rc DxvkShaderCache::getInstance() { - std::lock_guard lock(s_instance.mutex); + std::unique_lock lock(s_instance.mutex); + + while (s_instance.instance) { + auto useCount = s_instance.instance->m_useCount.load(); + + while (useCount) { + // Try to acquire reference from a non-zero ref count to + // ensure the object doesn't get destroyed in the meantime + if (s_instance.instance->m_useCount.compare_exchange_strong(useCount, useCount + 1u)) + return Rc::unsafeCreate(s_instance.instance); + } - if (!s_instance.instance) - s_instance.instance = new DxvkShaderCache(); + // If the ref count is zero, don't try to revive the object to avoid + // weird data races. Instead, wait for the object to get torn down + // and potentially recreated by another thread. + // Can't compare the pointers directly since a new cache might get + // the exact same pointer. + s_instance.cond.wait(lock, [&, cVersion = s_instance.version] { + return s_instance.version != cVersion; + }); + } + s_instance.instance = new DxvkShaderCache(); + s_instance.version += 1u; + s_instance.cond.notify_one(); return s_instance.instance; } @@ -680,15 +700,15 @@ namespace dxvk { void DxvkShaderCache::freeInstance() { std::lock_guard lock(s_instance.mutex); - // The ref count can only be incremented from 0 to 1 inside a locked - // context, so this check is safe. Don't destroy the object if another - // thread has essentially revived it. - if (!m_useCount.load() || s_instance.instance != this) { - if (s_instance.instance == this) - s_instance.instance = nullptr; - - delete this; + if (s_instance.instance == this) { + s_instance.instance = nullptr; + s_instance.version += 1u; + s_instance.cond.notify_one(); } + + // Keep lock, we want to ensure all files are unlocked before + // any other thread can recreate the shader cache. + delete this; } diff --git a/src/dxvk/dxvk_shader_cache.h b/src/dxvk/dxvk_shader_cache.h index 2a5b0505..2392dda2 100644 --- a/src/dxvk/dxvk_shader_cache.h +++ b/src/dxvk/dxvk_shader_cache.h @@ -81,8 +81,10 @@ namespace dxvk { private: struct Instance { - dxvk::mutex mutex; - DxvkShaderCache* instance = nullptr; + dxvk::mutex mutex; + dxvk::condition_variable cond; + uint32_t version = 0u; + DxvkShaderCache* instance = nullptr; }; static Instance s_instance; From e2d619fb107d705e809cd6ed9f5b217c8556a131 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 17 Oct 2026 00:41:10 +0200 Subject: [PATCH 050/135] [dxvk] Rename method to create allocation for sparse page table --- src/dxvk/dxvk_memory.cpp | 9 ++++----- src/dxvk/dxvk_memory.h | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/dxvk/dxvk_memory.cpp b/src/dxvk/dxvk_memory.cpp index 13562aa8..c4ea405f 100644 --- a/src/dxvk/dxvk_memory.cpp +++ b/src/dxvk/dxvk_memory.cpp @@ -1074,9 +1074,8 @@ namespace dxvk { logMemoryStats(); } } else { - allocation = createAllocation( - new DxvkSparsePageTable(m_device, createInfo, buffer), - allocationInfo); + auto pageTable = new DxvkSparsePageTable(m_device, createInfo, buffer); + allocation = allocateSparsePageTable(pageTable, allocationInfo); } if (!allocation) { @@ -1223,7 +1222,7 @@ namespace dxvk { allocation->m_sparsePageTable = pageTable.release(); } else { // Just need a page table, but no memory - allocation = createAllocation(pageTable.release(), allocationInfo); + allocation = allocateSparsePageTable(pageTable.release(), allocationInfo); } } @@ -1587,7 +1586,7 @@ namespace dxvk { } - DxvkResourceAllocation* DxvkMemoryAllocator::createAllocation( + DxvkResourceAllocation* DxvkMemoryAllocator::allocateSparsePageTable( DxvkSparsePageTable* sparsePageTable, const DxvkAllocationInfo& allocationInfo) { std::lock_guard lock(m_mutex); diff --git a/src/dxvk/dxvk_memory.h b/src/dxvk/dxvk_memory.h index 8da44ed6..0d0dee89 100644 --- a/src/dxvk/dxvk_memory.h +++ b/src/dxvk/dxvk_memory.h @@ -1490,7 +1490,7 @@ namespace dxvk { const DxvkDeviceMemory& memory, const DxvkAllocationInfo& allocationInfo); - DxvkResourceAllocation* createAllocation( + DxvkResourceAllocation* allocateSparsePageTable( DxvkSparsePageTable* sparsePageTable, const DxvkAllocationInfo& allocationInfo); From 8e73761ddf31b74dd45977b0e34af38123a41e42 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 17 Oct 2026 01:14:04 +0200 Subject: [PATCH 051/135] [vulkan] Add logging capabilities for present stage flags --- src/vulkan/vulkan_names.cpp | 10 ++++++++++ src/vulkan/vulkan_names.h | 1 + 2 files changed, 11 insertions(+) diff --git a/src/vulkan/vulkan_names.cpp b/src/vulkan/vulkan_names.cpp index 1fd855e6..cd157302 100644 --- a/src/vulkan/vulkan_names.cpp +++ b/src/vulkan/vulkan_names.cpp @@ -446,3 +446,13 @@ std::ostream& operator << (std::ostream& os, VkExtent2D e) { std::ostream& operator << (std::ostream& os, VkExtent3D e) { return os << "(" << e.width << "," << e.height << "," << e.depth << ")"; } + +std::ostream& operator << (std::ostream& os, VkPresentStageFlagBitsEXT e) { + switch (e) { + ENUM_NAME(VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT); + ENUM_NAME(VK_PRESENT_STAGE_REQUEST_DEQUEUED_BIT_EXT); + ENUM_NAME(VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT); + ENUM_NAME(VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT); + ENUM_DEFAULT(e); + } +} diff --git a/src/vulkan/vulkan_names.h b/src/vulkan/vulkan_names.h index e421fdfc..e724cd2b 100644 --- a/src/vulkan/vulkan_names.h +++ b/src/vulkan/vulkan_names.h @@ -26,3 +26,4 @@ std::ostream& operator << (std::ostream& os, VkOffset2D e); std::ostream& operator << (std::ostream& os, VkOffset3D e); std::ostream& operator << (std::ostream& os, VkExtent2D e); std::ostream& operator << (std::ostream& os, VkExtent3D e); +std::ostream& operator << (std::ostream& os, VkPresentStageFlagBitsEXT e); From cb0eabc31ed2e7b3d00fefe8aaa8f8e0bec8792d Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 17 Oct 2026 01:16:24 +0200 Subject: [PATCH 052/135] [dxvk] Log present stage for present timing --- src/dxvk/dxvk_presenter.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 731567fb..1ab708a4 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -879,6 +879,9 @@ namespace dxvk { ? (m_timingMode.supportsRelative ? "yes (absolute, relative)" : "yes (absolute)") : (m_timingMode.supportsRelative ? "yes (relative)" : "no")))); + if (m_timingMode.presentStage) + Logger::info(str::format("Presenter: Using present stage ", VkPresentStageFlagBitsEXT(m_timingMode.presentStage))); + if ((status = m_vkd->vkCreateSwapchainKHR(m_vkd->device(), &swapInfo, nullptr, &m_swapchain))) { Logger::err(str::format("Presenter: Failed to create Vulkan swapchain: ", status)); return status; From f94ab2e6a9dcd942c491a5c7200442fd538dfa1d Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 17 Oct 2026 01:36:00 +0200 Subject: [PATCH 053/135] [dxvk] Don't wait for present if timing is enabled Otherwise we get inconsistent pacing in VRR mode. Seen with the new Gamescope implementation. --- src/dxvk/dxvk_presenter.cpp | 23 ++++++++++------------- src/dxvk/dxvk_presenter.h | 2 -- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 1ab708a4..faa6a49f 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -203,8 +203,6 @@ namespace dxvk { bool isFifoMode = m_presentMode == VK_PRESENT_MODE_FIFO_KHR || m_presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; - bool waitForPresent = m_hasPresentWait && isFifoMode; - uint64_t frameTargetTime = 0u; VkPresentTimingInfoEXT timingInfo = { VK_STRUCTURE_TYPE_PRESENT_TIMING_INFO_EXT }; @@ -229,10 +227,6 @@ namespace dxvk { if (m_timingDisplayInfo && !m_timingDisplayInfo->isVariableRefresh) timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_NEAREST_REFRESH_CYCLE_BIT_EXT; - - // Skip present_wait in fixed refresh mode if the frame is timed - if (!m_timingDisplayInfo || !m_timingDisplayInfo->isVariableRefresh) - waitForPresent = waitForPresent && !timingInfo.targetTime; } } @@ -304,8 +298,6 @@ namespace dxvk { frame.targetTime = frameTargetTime; frame.targetDeadline = frameTargetTime ? frameTargetTime + m_timingMode.frameIntervalNs : 0u; frame.timingDomainId = timingInfo.timeDomainId; - frame.isTimed = bool(timingInfo.targetTime); - frame.doWait = waitForPresent; pushFrame(frame); @@ -2088,7 +2080,16 @@ namespace dxvk { // If the present operation has succeeded, actually wait for it to complete. // Don't bother with it on MAILBOX / IMMEDIATE modes since doing so would // restrict us to the display refresh rate on some platforms (XWayland). - if (frame.result >= 0 && frame.doWait) { + // Also don't do this if present timing is enabled because we will wait + // for the target present time anyway. + bool waited = updatePresentTiming(frame.frameId) + && frame.targetTime + && waitUntilFrameTargetTime(frame); + + bool isFifoMode = m_presentMode == VK_PRESENT_MODE_FIFO_KHR + || m_presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; + + if (!waited && isFifoMode && frame.result >= 0 && m_hasPresentWait) { VkResult vr; if (m_device->features().khrPresentWait2.presentWait2) { @@ -2116,10 +2117,6 @@ namespace dxvk { // Apply FPS limiter here to align it as closely with scanout as we can, // and delay signaling the frame latency event to emulate behaviour of a // low refresh rate display as closely as we can. - bool waited = updatePresentTiming(frame.frameId) - && frame.isTimed && frame.targetTime - && waitUntilFrameTargetTime(frame); - if (!waited) m_fpsLimiter.delay(); diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index d444ab91..72f45a7f 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -63,8 +63,6 @@ namespace dxvk { uint64_t targetTime = 0u; uint64_t targetDeadline = 0u; uint64_t timingDomainId = 0u; - bool isTimed = false; - bool doWait = false; }; /** From c8679a55c37b67a8e97b09958801f2371786dc25 Mon Sep 17 00:00:00 2001 From: Nithishvelan M Date: Sat, 17 Oct 2026 22:01:01 +0530 Subject: [PATCH 054/135] [dxvk] Pass null timeout to D3DKMTAcquireKeyedMutex on INFINITE wait When waiting indefinitely on a keyed mutex, D3DKMTAcquireKeyedMutex expects pTimeout to be NULL. Passing a pointer to a computed relative timeout imposes an unintended ~49 day timeout rather than an infinite wait. Closes #5967 --- src/dxvk/dxvk_image.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_image.cpp b/src/dxvk/dxvk_image.cpp index 3a181b9a..5f6b1fa5 100644 --- a/src/dxvk/dxvk_image.cpp +++ b/src/dxvk/dxvk_image.cpp @@ -62,7 +62,7 @@ namespace dxvk { D3DKMT_ACQUIREKEYEDMUTEX acquire = { }; acquire.hKeyedMutex = m_kmtLocal; acquire.Key = key; - acquire.pTimeout = &timeout; + acquire.pTimeout = milliseconds == INFINITE ? nullptr : &timeout; NTSTATUS status = D3DKMTAcquireKeyedMutex(&acquire); From 7d248817da29b423dc8898cd5d6c21692fe94f56 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 18 Oct 2026 00:03:21 +0200 Subject: [PATCH 055/135] Revert "[d3d9] Return null vector when sampling null texture in fixed-function" Turns out that the null vector thing is an AMD quirk that actually causes breakage. Proton: Commandos 3: Destination Berlin (6840) --- src/d3d9/shaders/d3d9_fixed_function_frag.glsl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/d3d9/shaders/d3d9_fixed_function_frag.glsl b/src/d3d9/shaders/d3d9_fixed_function_frag.glsl index 182c6654..f91a4fba 100644 --- a/src/d3d9/shaders/d3d9_fixed_function_frag.glsl +++ b/src/d3d9/shaders/d3d9_fixed_function_frag.glsl @@ -296,10 +296,10 @@ vec4 sampleTexture(uint stage, vec4 texcoord, vec4 previousStageTextureVal) { texcoord = calculateBumpmapCoords(stage, texcoord, previousStageTextureVal); } - // We can encounter null textures in certain scenarios where only alpha - // samples the texture, or the color op itself implicitly samples. For - // some reason this appears to return a null vector on native. - vec4 texVal = vec4(0.0f); + // The only time we should ever be able to observe a null texture is with + // D3DTOP_PREMODULATE. It's not 100% clear what's supposed to happen in + // that case, but make it so that the multiplication has no effect for now. + vec4 texVal = vec4(1.0f); switch (state.type) { case TEXTURE_TYPE_2D: From d744290a405ec542f7b59b91ed11128be797104f Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 18 Oct 2026 00:19:20 +0200 Subject: [PATCH 056/135] [dxvk] Fix native build issues --- include/native/windows/windows_base.h | 2 ++ src/dxgi/dxgi_options.cpp | 13 ++++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/include/native/windows/windows_base.h b/include/native/windows/windows_base.h index 8650d2e3..261d2953 100644 --- a/include/native/windows/windows_base.h +++ b/include/native/windows/windows_base.h @@ -216,6 +216,8 @@ typedef struct RGNDATA { #define WAIT_OBJECT_0 0 #define WAIT_ABANDONED 0x00000080 +#define INFINITE 0xffffffff + #define interface struct #define MIDL_INTERFACE(x) struct diff --git a/src/dxgi/dxgi_options.cpp b/src/dxgi/dxgi_options.cpp index d83342e2..2febb240 100644 --- a/src/dxgi/dxgi_options.cpp +++ b/src/dxgi/dxgi_options.cpp @@ -4,15 +4,9 @@ namespace dxvk { - static bool isVersionAtLeast( - uint32_t major, - uint32_t minor, - uint32_t patch, - uint32_t build, - uint32_t reqMajor, - uint32_t reqMinor, - uint32_t reqPatch, - uint32_t reqBuild) { + #ifdef _WIN32 + static bool isVersionAtLeast(uint32_t major, uint32_t minor, uint32_t patch, uint32_t build, + uint32_t reqMajor, uint32_t reqMinor, uint32_t reqPatch, uint32_t reqBuild) { if (major != reqMajor) return major > reqMajor; if (minor != reqMinor) @@ -21,6 +15,7 @@ namespace dxvk { return patch > reqPatch; return build >= reqBuild; } + #endif static int32_t parsePciId(const std::string& str) { if (str.size() != 4) From 8907e06060ac677659d0229826006f1fece4ab1f Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 17:02:47 +0200 Subject: [PATCH 057/135] [dxvk] Fix scratch memory suballocation --- src/dxvk/dxvk_context.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index 045ad842..9a98603e 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -10937,7 +10937,7 @@ namespace dxvk { if (unlikely(!m_scratchBuffer || m_scratchBuffer->info().size < size)) { // We probably won't need a lot of scratch memory, so keep it small DxvkBufferCreateInfo info = { }; - info.size = DxvkPageAllocator::PageSize; + info.size = std::max(DxvkPageAllocator::PageSize, align(size, alignment)); info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; @@ -10956,13 +10956,14 @@ namespace dxvk { // Suballocate slice from buffer VkDeviceSize offset = align(m_scratchOffset, alignment); - if (offset + size < m_scratchBuffer->info().size) { + if (offset + size > m_scratchBuffer->info().size) { invalidateBuffer(m_scratchBuffer, m_scratchBuffer->allocateStorage()); m_scratchOffset = 0u; + offset = 0; } - DxvkResourceBufferInfo slice = m_scratchBuffer->getSliceInfo(m_scratchOffset, size); - m_scratchOffset += align(size, alignment); + DxvkResourceBufferInfo slice = m_scratchBuffer->getSliceInfo(offset, size); + m_scratchOffset = offset + align(size, alignment); // Unconditionally track for writing to not bother the caller with it. m_cmd->track(m_scratchBuffer, DxvkAccess::Write); From d7e283b16a69d5798aee1e63839fd2fdb62de7b2 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 17:06:15 +0200 Subject: [PATCH 058/135] [dxvk] Fix counter memory when generating mipmaps --- src/dxvk/dxvk_context.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index 9a98603e..d8a89ec6 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -5420,13 +5420,13 @@ namespace dxvk { uint32_t basePass = 0u; + DxvkMetaMipGenPushConstants pushData = { }; + pushData.atomicCounterVa = counterMemory.gpuAddress; + pushData.samplerIndex = sampler->getDescriptor().samplerIndex; + while (basePass < mipGenerator.getPassCount()) { small_vector accessBatch; - DxvkMetaMipGenPushConstants pushData = { }; - pushData.atomicCounterVa = counterMemory.gpuAddress; - pushData.samplerIndex = sampler->getDescriptor().samplerIndex; - if (requiresPrepass && !basePass) { // It is beneficial to run this on the maximum mip count here because // sampling the top-level mip tends to be the primary bottleneck. We From e0fa1bc8b1d52b51fedc1aeb6ebd2535361785cb Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 17:10:24 +0200 Subject: [PATCH 059/135] [dxvk] Don't attach color RT if we're not resolving stencil --- src/dxvk/dxvk_meta_resolve.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dxvk/dxvk_meta_resolve.cpp b/src/dxvk/dxvk_meta_resolve.cpp index d86e59bf..d599c44d 100644 --- a/src/dxvk/dxvk_meta_resolve.cpp +++ b/src/dxvk/dxvk_meta_resolve.cpp @@ -405,8 +405,6 @@ namespace dxvk { // a time by adjusting the write mask. Add the corresponding dynamic state. state.dynamicStateCount = dynState.size(); state.dynamicStates = dynState.data(); - } else { - state.colorFormats[0] = key.format; } pipeline.pipeline = m_device->createBuiltInGraphicsPipeline(pipeline.layout, state); From d3d57f45f4f1d3103611a2d2e3f9507af1e800e3 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 17:14:27 +0200 Subject: [PATCH 060/135] [dxvk] Fix spec constant dirty mask comparison The mask returned by bcndcpy is relative to the dwords passed to it. So we need to shift the mask by the index. --- src/dxvk/dxvk_context.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h index aa5f9f11..1e9780f2 100644 --- a/src/dxvk/dxvk_context.h +++ b/src/dxvk/dxvk_context.h @@ -1190,7 +1190,7 @@ namespace dxvk { auto& scState = pipeline == VK_PIPELINE_BIND_POINT_GRAPHICS ? m_state.gp.constants : m_state.cp.constants; - uint32_t dirtyMask = bit::bcndcpy(&scState.data[index], data, count); + uint32_t dirtyMask = bit::bcndcpy(&scState.data[index], data, count) << index; if (scState.mask & dirtyMask) { m_flags.set(pipeline == VK_PIPELINE_BIND_POINT_GRAPHICS From 0ae00031518ba23395cb907197612b8b0f3635bc Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 18:16:50 +0200 Subject: [PATCH 061/135] [dxvk] Only draw one instance for copies The shader only writes one layer and reads that from push consts. --- src/dxvk/dxvk_context.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index d8a89ec6..ecc8a772 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -5033,7 +5033,7 @@ namespace dxvk { m_cmd->bindResources(DxvkCmdBuffer::ExecBuffer, pipeline.layout, 0u, nullptr, sizeof(copyArgs), ©Args); - m_cmd->cmdDraw(3u, srcSubresource.layerCount, 0u, 1u); + m_cmd->cmdDraw(3u, 1u, 0u, 1u); } // Issue by-region barrier after the copy and before subsequent rendering From b17c9679ab0932a4b5048846074e01aaec45947c Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 18:17:21 +0200 Subject: [PATCH 062/135] [dxvk] Fix R5G6B5 bit shifts in meta copy shader --- src/dxvk/dxvk_meta_copy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_meta_copy.cpp b/src/dxvk/dxvk_meta_copy.cpp index 0fb4977b..661b1ece 100644 --- a/src/dxvk/dxvk_meta_copy.cpp +++ b/src/dxvk/dxvk_meta_copy.cpp @@ -813,7 +813,7 @@ namespace dxvk { uint32_t bitShift = bitIndex; if (isBgr && i < 3u) - bitShift = (hasAlpha ? 10u : 11u) - bitShift; + bitShift = 16u - bitIndex - bitCount; ir::SsaDef scale = builder.makeConstant(float((1u << bitCount) - 1u)); channel = builder.add(ir::Op::FMul(ir::ScalarType::eF32, channel, scale)); From ac400e40d96880fa67b543fe4bb8308617888ddb Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 20:58:21 +0200 Subject: [PATCH 063/135] [dxvk] Fix aspect mask check when performing deferred clears --- src/dxvk/dxvk_context.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index ecc8a772..c4318596 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -7700,7 +7700,7 @@ namespace dxvk { auto& entry = m_deferredClears[srcIndex]; if ((entry.imageView->image() == &image) - && ((entry.clearAspects | entry.discardAspects) | subresources.aspectMask) + && ((entry.clearAspects | entry.discardAspects) & subresources.aspectMask) && (vk::checkSubresourceRangeOverlap(entry.imageView->imageSubresources(), subresources))) { clearBatch.add(batchClear(entry.imageView, -1, entry.discardAspects, entry.clearAspects, entry.clearValue)); srcIndex += 1u; From 14260d1da135995beba53f1b2b9909d150eea950 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 21:03:27 +0200 Subject: [PATCH 064/135] [dxvk] Use 2D_ARRAY views for clears DxvkContext::performClears creates the actual image view and uses that for the renderpass attachment. TYPE_2D makes it clamp the layer count to 1. So we might end up clearing only one layer even if we previously queued up multiple layers for clearing. --- src/dxvk/dxvk_context.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index c4318596..ad3cd33b 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -5902,7 +5902,7 @@ namespace dxvk { // Create an image view that we can use to perform the clear DxvkImageViewKey key = { }; - key.viewType = VK_IMAGE_VIEW_TYPE_2D; + key.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; key.usage = usageBit; key.format = format; key.aspects = region.dstSubresource.aspectMask; From 1de51b9d854ab0d037f6f44553ed010b28093ee7 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 21:20:15 +0200 Subject: [PATCH 065/135] [dxvk] Pop smallest item from fence queue first instead of largest The code in run() seems to assume that already. --- src/dxvk/dxvk_fence.cpp | 2 +- src/dxvk/dxvk_fence.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dxvk/dxvk_fence.cpp b/src/dxvk/dxvk_fence.cpp index 8485c40d..50404579 100644 --- a/src/dxvk/dxvk_fence.cpp +++ b/src/dxvk/dxvk_fence.cpp @@ -147,7 +147,7 @@ namespace dxvk { lock.unlock(); - // Wait for the semaphore to be singaled again and update state. + // Wait for the semaphore to be signaled again and update state. // The timeout is unfortunate, but we can't always know when a // signal operation has been recorded, and the alternative would // be to create a teardown semaphore and use WAIT_ANY, which may diff --git a/src/dxvk/dxvk_fence.h b/src/dxvk/dxvk_fence.h index f348a536..5b285bda 100644 --- a/src/dxvk/dxvk_fence.h +++ b/src/dxvk/dxvk_fence.h @@ -140,7 +140,8 @@ namespace dxvk { D3DKMT_HANDLE m_kmtLocal = 0; D3DKMT_HANDLE m_kmtGlobal = 0; - std::priority_queue m_queue; + std::priority_queue, std::greater<>> m_queue; + bool m_running = false; dxvk::mutex m_mutex; From d213771c8a27daa9f3a1bb9c4ab010dcd38d0131 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 22:17:16 +0200 Subject: [PATCH 066/135] [dxvk] Fix derived types in SPIRV input elimination Argument 2 is the destination but we're looking for access chains that access the result of previously modified access chains. --- src/dxvk/dxvk_shader_spirv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_shader_spirv.cpp b/src/dxvk/dxvk_shader_spirv.cpp index 2e2642e3..99ddfb86 100644 --- a/src/dxvk/dxvk_shader_spirv.cpp +++ b/src/dxvk/dxvk_shader_spirv.cpp @@ -1128,7 +1128,7 @@ namespace dxvk { accessChainIds.insert({ ins.arg(2), depth }); } else { // Access chains derived from the variable - auto entry = accessChainIds.find(ins.arg(2)); + auto entry = accessChainIds.find(ins.arg(3)); if (entry != accessChainIds.end()) { depth += entry->second; ins.setArg(1, privateTypes.at(depth).first); From 0a6363e520566b1b4f590d311f34fec4bb0f2a78 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 22:28:13 +0200 Subject: [PATCH 067/135] [dxvk] Fix skipping name in SPIRV entry point patching Arg 0 is OpEndpoint Arg 1 is the execution model Arg 2 is the entry point id Arg 3 is the name --- src/dxvk/dxvk_shader_spirv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_shader_spirv.cpp b/src/dxvk/dxvk_shader_spirv.cpp index 99ddfb86..3033b33f 100644 --- a/src/dxvk/dxvk_shader_spirv.cpp +++ b/src/dxvk/dxvk_shader_spirv.cpp @@ -886,7 +886,7 @@ namespace dxvk { SpirvCodeBuffer& code, SpirvInstruction& entryPoint, uint32_t varId) { - uint32_t argIdx = 2 + code.strLen(entryPoint.chr(2)); + uint32_t argIdx = 3 + code.strLen(entryPoint.chr(3)); while (argIdx < entryPoint.length()) { if (entryPoint.arg(argIdx) == varId) { From 7e03c5493e39a556e4581b59c40e0af3adde4d9e Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Sun, 18 Oct 2026 22:42:12 +0200 Subject: [PATCH 068/135] [dxvk] Notify all threads waiting for the CS thread Otherwise injectCsChunk can hang if the immediate context was waiting too on a separate thread. --- src/dxvk/dxvk_cs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_cs.cpp b/src/dxvk/dxvk_cs.cpp index 06f6aa48..cf7c7029 100644 --- a/src/dxvk/dxvk_cs.cpp +++ b/src/dxvk/dxvk_cs.cpp @@ -257,7 +257,7 @@ namespace dxvk { auto& counter = isHighPrio ? m_seqHighPrio : m_seqOrdered; counter.store(entry.seq); - m_condOnSync.notify_one(); + m_condOnSync.notify_all(); } // Immediately free the chunk to release From a5250d4a51472da07f8e275134f1fa2581a3aecb Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Mon, 19 Oct 2026 00:03:29 +0200 Subject: [PATCH 069/135] [dxvk] Clear shader cache LUT before re-initializing all files --- src/dxvk/dxvk_shader_cache.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dxvk/dxvk_shader_cache.cpp b/src/dxvk/dxvk_shader_cache.cpp index 00153ad9..7d5ff3c2 100644 --- a/src/dxvk/dxvk_shader_cache.cpp +++ b/src/dxvk/dxvk_shader_cache.cpp @@ -59,6 +59,7 @@ namespace dxvk { if (!shader) { Logger::warn(str::format("Failed to load cached shader ", name)); + m_lut.clear(); if (!openWriteOnlyLocked()) Logger::warn(str::format("Failed to re-initialize shader cache ", name)); From 224969e29300876ba6fba7449b970b9c97484d0e Mon Sep 17 00:00:00 2001 From: Alistair Leslie-Hughes Date: Sun, 18 Oct 2026 10:28:17 +1100 Subject: [PATCH 070/135] D3D11Device::SetExceptionMode fake success. --- src/d3d11/d3d11_device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d3d11/d3d11_device.cpp b/src/d3d11/d3d11_device.cpp index 323f1821..ab1fc41b 100644 --- a/src/d3d11/d3d11_device.cpp +++ b/src/d3d11/d3d11_device.cpp @@ -1851,7 +1851,7 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D11Device::SetExceptionMode(UINT RaiseFlags) { Logger::err("D3D11Device::SetExceptionMode: Not implemented"); - return E_NOTIMPL; + return S_OK; } From 2cb40186c43d617b5bd349a3756721aa4bd24b6d Mon Sep 17 00:00:00 2001 From: Mike Lothian Date: Sun, 18 Oct 2026 23:03:02 +0100 Subject: [PATCH 071/135] [util] Allow AVX builds with clang The guard is there because mingw gcc doesn't realign the stack for 256 bit spills under the 16 byte alignment of the Windows ABI. clang does realign it, so only keep the error for the other compilers --- src/util/util_bit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/util_bit.h b/src/util/util_bit.h index 11e20d13..8f5564ba 100644 --- a/src/util/util_bit.h +++ b/src/util/util_bit.h @@ -12,7 +12,7 @@ #ifdef DXVK_ARCH_X86 #ifndef _MSC_VER - #if defined(_WIN32) && (defined(__AVX__) || defined(__AVX2__)) + #if defined(_WIN32) && !defined(__clang__) && (defined(__AVX__) || defined(__AVX2__)) #error "AVX-enabled builds not supported due to stack alignment issues." #endif #if defined(__WINE__) && defined(__clang__) From b93011202eec64e7b275230bd449b438cf3584b3 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 02:46:30 +0200 Subject: [PATCH 072/135] [d3d11] Actually expose buffer interop interface --- src/d3d11/d3d11_buffer.cpp | 6 +++ src/d3d11/d3d11_buffer.h | 77 +++++++++++++++++++------------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/d3d11/d3d11_buffer.cpp b/src/d3d11/d3d11_buffer.cpp index 9af1b67e..1bee53e6 100644 --- a/src/d3d11/d3d11_buffer.cpp +++ b/src/d3d11/d3d11_buffer.cpp @@ -13,6 +13,7 @@ namespace dxvk { m_desc (*pDesc), m_resource (this, pDevice), m_d3d10 (this), + m_interop (this), m_destructionNotifier(this) { DxvkBufferCreateInfo info; info.flags = 0; @@ -158,6 +159,11 @@ namespace dxvk { return S_OK; } + if (riid == __uuidof(IDXGIVkInteropBuffer)) { + *ppvObject = ref(&m_interop); + return S_OK; + } + if (riid == __uuidof(ID3DDestructionNotifier)) { *ppvObject = ref(&m_destructionNotifier); return S_OK; diff --git a/src/d3d11/d3d11_buffer.h b/src/d3d11/d3d11_buffer.h index 3a05587e..232ac22d 100644 --- a/src/d3d11/d3d11_buffer.h +++ b/src/d3d11/d3d11_buffer.h @@ -35,7 +35,44 @@ namespace dxvk { uint32_t byteOffset; }; - + + /** + * \brief Interop interface for buffers + */ + class D3D11VkInteropBuffer : public IDXGIVkInteropBuffer { + + public: + + D3D11VkInteropBuffer(D3D11Buffer* pBuffer); + + ~D3D11VkInteropBuffer(); + + ULONG STDMETHODCALLTYPE AddRef(); + + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID riid, + void** ppvObject); + + HRESULT STDMETHODCALLTYPE GetDevice( + IDXGIVkInteropDevice** ppDevice); + + HRESULT STDMETHODCALLTYPE GetVulkanBufferInfo( + VkDeviceAddress* pGpuAddress, + VkBuffer* pHandle, + VkDeviceSize* pOffset, + VkBufferCreateInfo* pInfo); + + private: + + D3D11Buffer* m_buffer = nullptr; + + std::atomic m_locked = { false }; + + }; + + class D3D11Buffer : public D3D11DeviceChild { static constexpr VkDeviceSize BufferSliceAlignment = 64; public: @@ -199,6 +236,7 @@ namespace dxvk { D3D11DXGIResource m_resource; D3D10Buffer m_d3d10; + D3D11VkInteropBuffer m_interop; D3DDestructionNotifier m_destructionNotifier; @@ -216,43 +254,6 @@ namespace dxvk { }; - /** - * \brief Interop i - */ - class D3D11VkInteropBuffer : public IDXGIVkInteropBuffer { - - public: - - D3D11VkInteropBuffer(D3D11Buffer* pBuffer); - - ~D3D11VkInteropBuffer(); - - ULONG STDMETHODCALLTYPE AddRef(); - - ULONG STDMETHODCALLTYPE Release(); - - HRESULT STDMETHODCALLTYPE QueryInterface( - REFIID riid, - void** ppvObject); - - HRESULT STDMETHODCALLTYPE GetDevice( - IDXGIVkInteropDevice** ppDevice); - - HRESULT STDMETHODCALLTYPE GetVulkanBufferInfo( - VkDeviceAddress* pGpuAddress, - VkBuffer* pHandle, - VkDeviceSize* pOffset, - VkBufferCreateInfo* pInfo); - - private: - - D3D11Buffer* m_buffer = nullptr; - - std::atomic m_locked = { false }; - - }; - - /** * \brief Retrieves buffer from resource pointer * From ef3ef8712ecfd619775662ee8d2778bf7db89af4 Mon Sep 17 00:00:00 2001 From: Robin Kertels Date: Tue, 20 Oct 2026 02:20:09 +0200 Subject: [PATCH 073/135] [d3d11] Fix losing D3D11_RASTERIZER_DESC1 ForcedSampleCount --- src/d3d11/d3d11_rasterizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d3d11/d3d11_rasterizer.cpp b/src/d3d11/d3d11_rasterizer.cpp index 558f3e34..6f38a8b5 100644 --- a/src/d3d11/d3d11_rasterizer.cpp +++ b/src/d3d11/d3d11_rasterizer.cpp @@ -163,7 +163,7 @@ namespace dxvk { dstDesc.ScissorEnable = pSrcDesc->ScissorEnable; dstDesc.MultisampleEnable = pSrcDesc->MultisampleEnable; dstDesc.AntialiasedLineEnable = pSrcDesc->AntialiasedLineEnable; - dstDesc.ForcedSampleCount = 0; + dstDesc.ForcedSampleCount = pSrcDesc->ForcedSampleCount; dstDesc.ConservativeRaster = D3D11_CONSERVATIVE_RASTERIZATION_MODE_OFF; return dstDesc; } From 6820e925b76019896947d0eb7a37e0dea3d5f88f Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 03:07:14 +0200 Subject: [PATCH 074/135] [dxvk] Make submission timeout configurable --- src/dxvk/dxvk_device.cpp | 7 ++++++- src/dxvk/dxvk_device.h | 6 ++++++ src/dxvk/dxvk_queue.cpp | 9 ++++++++- src/dxvk/dxvk_queue.h | 10 ++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index 082c15b9..a505c958 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -643,7 +643,12 @@ namespace dxvk { std::lock_guard statLock(m_statLock); m_statCounters.merge(commandList->statCounters()); } - + + + void DxvkDevice::setSubmissionTimeout(uint64_t timeout) { + m_submissionQueue.setSubmissionTimeout(timeout); + } + void DxvkDevice::waitForSubmission(uint64_t submissionId) { m_submissionQueue.synchronizeSubmission(submissionId); diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h index af234035..19b3a976 100644 --- a/src/dxvk/dxvk_device.h +++ b/src/dxvk/dxvk_device.h @@ -678,6 +678,12 @@ namespace dxvk { uint64_t frameId, uint64_t submissionId); + /** + * \brief Sets queue submission timeout + * \param [in] timeout Timeout, in nanoseconds + */ + void setSubmissionTimeout(uint64_t timeout); + /** * \brief Locks submission queue * diff --git a/src/dxvk/dxvk_queue.cpp b/src/dxvk/dxvk_queue.cpp index 221f444b..17007039 100644 --- a/src/dxvk/dxvk_queue.cpp +++ b/src/dxvk/dxvk_queue.cpp @@ -123,6 +123,12 @@ namespace dxvk { } + void DxvkSubmissionQueue::setSubmissionTimeout(uint64_t timeout) { + std::unique_lock lock(m_mutex); + m_submissionTimeout = timeout; + } + + void DxvkSubmissionQueue::submitCmdLists() { env::setThreadName("dxvk-submit"); @@ -226,6 +232,7 @@ namespace dxvk { while (!m_stopped.load()) { std::unique_lock lock(m_mutex); + uint64_t submissionTimeout = m_submissionTimeout; if (m_finishQueue.empty()) { auto t0 = dxvk::high_resolution_clock::now(); @@ -261,7 +268,7 @@ namespace dxvk { // Treat a timeout here as DEVICE_LOST. Sometimes we just don't get // DEVICE_LOST from the driver for some reason. - status = vk->vkWaitSemaphores(vk->device(), &waitInfo, 5'000'000'000ull); + status = vk->vkWaitSemaphores(vk->device(), &waitInfo, submissionTimeout); if (status == VK_TIMEOUT) { Logger::err("DxvkSubmissionQueue: Timeout occured, GPU likely hung."); diff --git a/src/dxvk/dxvk_queue.h b/src/dxvk/dxvk_queue.h index bd3ddf0d..07b56b8a 100644 --- a/src/dxvk/dxvk_queue.h +++ b/src/dxvk/dxvk_queue.h @@ -181,6 +181,14 @@ namespace dxvk { * queue used for command buffer submission. */ void unlockDeviceQueue(); + + /** + * \brief Sets submission timeout used to detect GPU hangs + * + * \param [in] timeout Desired timeout, in nanoseconds. + * Pass -1 to disable timeout-based hang detection. + */ + void setSubmissionTimeout(uint64_t timeout); private: @@ -211,6 +219,8 @@ namespace dxvk { sync::Fence m_submitTimeline; + uint64_t m_submissionTimeout = 5'000'000'000ull; + void submitCmdLists(); void finishCmdLists(); From 879fdc82a0401c3589e2e26438488b3956688ea4 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 03:07:26 +0200 Subject: [PATCH 075/135] [d3d11,d3d9] Set infinite submission timeout for reproducibleCommandStreams --- src/d3d11/d3d11_device.cpp | 4 ++++ src/d3d9/d3d9_device.cpp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/d3d11/d3d11_device.cpp b/src/d3d11/d3d11_device.cpp index ab1fc41b..a5a12e8a 100644 --- a/src/d3d11/d3d11_device.cpp +++ b/src/d3d11/d3d11_device.cpp @@ -53,6 +53,10 @@ namespace dxvk { m_shaderOptions (GetShaderOptions(m_dxvkDevice, m_d3d11Options)), m_maxFeatureLevel (D3D11DeviceFeatures::GetMaxFeatureLevel(*m_dxvkDevice)), m_deviceFeatures (*m_dxvkDevice, m_d3d11Options, m_featureLevel) { + // This option might lead to long-running submissions + if (m_d3d11Options.reproducibleCommandStream) + m_dxvkDevice->setSubmissionTimeout(-1ull); + m_initializer = new D3D11Initializer(this); m_context = new D3D11ImmediateContext(this, m_dxvkDevice); m_d3d10Device = new D3D10Device(this, m_context.ptr()); diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index bc5a4a7b..1c34094d 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -63,6 +63,9 @@ namespace dxvk { , m_d3d9On12 ( this ) , m_legacyD3DBridge ( this ) , m_d3dCompatibility ( pParent->GetD3DCompatibilityFlags() ) { + // This option might lead to long-running submissions + if (m_d3d9Options.reproducibleCommandStream) + m_dxvkDevice->setSubmissionTimeout(-1ull); InitShaderOptions(); From c640935915b87c59170daff0f106348a18e06819 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 13:20:02 +0200 Subject: [PATCH 076/135] [util] Revive ticket lock implementation --- src/util/sync/sync_ticketlock.h | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/util/sync/sync_ticketlock.h b/src/util/sync/sync_ticketlock.h index 1c47f839..08a5bc7c 100644 --- a/src/util/sync/sync_ticketlock.h +++ b/src/util/sync/sync_ticketlock.h @@ -4,6 +4,8 @@ #include "../thread.h" +#include "sync_spinlock.h" + namespace dxvk::sync { /** @@ -21,8 +23,9 @@ namespace dxvk::sync { void lock() { uint32_t ticket = m_counter.fetch_add(1); - while (m_serving.load(std::memory_order_acquire) != ticket) - continue; + spin(200, [this, ticket] { + return m_serving.load(std::memory_order_acquire) == ticket; + }); } void unlock() { @@ -30,6 +33,15 @@ namespace dxvk::sync { m_serving.store(serveNext, std::memory_order_release); } + bool try_lock() { + // Perhaps not the most useful thing ever, but if serving == counter + // and no other thread is trying to lock, we can return immediately. + uint32_t serving = m_serving.load(std::memory_order_relaxed); + + return m_counter.compare_exchange_strong(serving, serving + 1u, + std::memory_order_acquire, std::memory_order_relaxed); + } + private: std::atomic m_counter = { 0 }; From 1c27dd44be8efc4f5380ca6afd8eb6e8f6e835bf Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 11:49:20 +0200 Subject: [PATCH 077/135] [dxvk] Handle Nvidia not signaling fence if timing report queue is full --- src/dxvk/dxvk_presenter.cpp | 7 +++++++ src/dxvk/dxvk_presenter.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index faa6a49f..f8ccb6c7 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -50,6 +50,10 @@ namespace dxvk { // the present fence if presentation is queued but fails. // TODO Remove this hack when this gets fixed in stable SteamOS. m_hasGamescopeFenceSignalBug = env::getEnvVar("ENABLE_GAMESCOPE_WSI") == "1"; + + // NV reportely does not signal the present fence when the report + // queue is full. + m_hasNvidiaFenceSignalBug = m_device->adapter()->matchesDriver(VK_DRIVER_ID_NVIDIA_PROPRIETARY); } @@ -280,6 +284,9 @@ namespace dxvk { if (m_hasGamescopeFenceSignalBug) currSync.fenceSignaled = status >= 0; + + if (m_hasNvidiaFenceSignalBug) + currSync.fenceSignaled &= status != VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT; } if (status >= 0) { diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 72f45a7f..fc8df26d 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -427,6 +427,7 @@ namespace dxvk { FpsLimiter m_fpsLimiter; bool m_hasGamescopeFenceSignalBug = false; + bool m_hasNvidiaFenceSignalBug = false; static const std::array, 2> s_colorSpaceFallbacks; From 87d5a68513cfb2728f4bf8543fda1f793597d4f9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 12:50:57 +0200 Subject: [PATCH 078/135] [dxvk] Only query present timing feedback for FIFO modes Mailbox is weird and Immediate doesn't really matter, same issues as with PRESENT_WAIT really. --- src/dxvk/dxvk_presenter.cpp | 39 ++++++++++++++++++++++--------------- src/dxvk/dxvk_presenter.h | 5 ++++- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index f8ccb6c7..cd96680d 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -203,18 +203,20 @@ namespace dxvk { regionInfo.swapchainCount = 1; regionInfo.pRegions = ®ion; - // Present timing isn't useful with Immediate or Mailbox + // Present timing isn't useful with Immediate or Mailbox. Mailbox can also + // lead to the report queue overflowing when running significantly above + // display refresh, so don't bother with report timing. bool isFifoMode = m_presentMode == VK_PRESENT_MODE_FIFO_KHR || m_presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; uint64_t frameTargetTime = 0u; VkPresentTimingInfoEXT timingInfo = { VK_STRUCTURE_TYPE_PRESENT_TIMING_INFO_EXT }; - timingInfo.presentStageQueries = m_timingMode.presentStage; timingInfo.timeDomainId = m_timingMode.timeDomainId; if (m_timingMode.presentStage && isFifoMode) { std::lock_guard lock(m_timingMutex); + timingInfo.presentStageQueries = m_timingMode.presentStage; if (m_timingMode.referenceFrameId) { frameTargetTime = m_timingMode.referenceTime + @@ -305,6 +307,7 @@ namespace dxvk { frame.targetTime = frameTargetTime; frame.targetDeadline = frameTargetTime ? frameTargetTime + m_timingMode.frameIntervalNs : 0u; frame.timingDomainId = timingInfo.timeDomainId; + frame.stageQueries = timingInfo.presentStageQueries; pushFrame(frame); @@ -1549,13 +1552,15 @@ namespace dxvk { } - bool Presenter::updatePresentTiming(uint64_t frameId) { + bool Presenter::updatePresentTimingFeedback( + uint64_t frameId, + VkPresentStageFlagsEXT queryStages) { // Need to access both timing stuff and the frame queue here std::lock_guard lock(m_timingMutex); PresenterTimingFeedback feedback = {}; feedback.frameId = frameId; - feedback.presentTime = dxvk::high_resolution_clock::get_counter(); + feedback.presentTime = frameId ? dxvk::high_resolution_clock::get_counter() : 0u; if (!m_timingMode.presentStage) { commitTimingFeedback(feedback); @@ -1663,10 +1668,11 @@ namespace dxvk { m_timingMode.referenceTime = m_timingMode.lastFrameTimeLocal; } - // We can't give meaningful feedback w/o QPC timing currently. - // TODO figure out correct time domain for dxvk-native if we're - // really interested, otherwise just ignore the problem. - if (m_timingMode.lastFrameTimeQpc) { + // Update feedback from timing reports only if two conditions are true: + // - The current frame was submitted with a timing query. + // - We successfully mapped the report timestamp back to QPC. + // TODO figure out the correct time domain for dxvk-native to use. + if (m_timingMode.lastFrameTimeQpc && (!frameId || !queryStages)) { feedback.frameId = m_timingMode.lastFrameId; feedback.presentTime = m_timingMode.lastFrameTimeQpc; } @@ -2084,15 +2090,12 @@ namespace dxvk { } } - // If the present operation has succeeded, actually wait for it to complete. - // Don't bother with it on MAILBOX / IMMEDIATE modes since doing so would - // restrict us to the display refresh rate on some platforms (XWayland). - // Also don't do this if present timing is enabled because we will wait - // for the target present time anyway. - bool waited = updatePresentTiming(frame.frameId) - && frame.targetTime - && waitUntilFrameTargetTime(frame); + // If the frameis timed, wait for the calculated target time. This intentionally + // does not line up with vblank in fixed refresh rate scenarios and is done so + // that the application's render thread can see consistent frame pacing. + bool waited = frame.targetTime && waitUntilFrameTargetTime(frame); + // If this doesn't apply, bool isFifoMode = m_presentMode == VK_PRESENT_MODE_FIFO_KHR || m_presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; @@ -2121,6 +2124,10 @@ namespace dxvk { frame.tracker = nullptr; } + // Update timing feedback. There is no guarantee when data becomes available, + // but if timing is disabled then we should base feedback on present_wait. + updatePresentTimingFeedback(frame.frameId, frame.stageQueries); + // Apply FPS limiter here to align it as closely with scanout as we can, // and delay signaling the frame latency event to emulate behaviour of a // low refresh rate display as closely as we can. diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index fc8df26d..6b0cc3cd 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -63,6 +63,7 @@ namespace dxvk { uint64_t targetTime = 0u; uint64_t targetDeadline = 0u; uint64_t timingDomainId = 0u; + VkPresentStageFlagsEXT stageQueries = 0u; }; /** @@ -483,7 +484,9 @@ namespace dxvk { bool recalibrateTimeDomains(); - bool updatePresentTiming(uint64_t frameId); + bool updatePresentTimingFeedback( + uint64_t frameId, + VkPresentStageFlagsEXT queryStages); void commitTimingFeedback( const PresenterTimingFeedback& feedback); From 7279c525847d5349de51cc395f34cee4b8204cb5 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 13:20:15 +0200 Subject: [PATCH 079/135] [dxvk] Also update timing reports on feedback query Switch to ticket lock so that an app thread spinning on present statistics won't starve the presenter. --- src/dxvk/dxvk_presenter.cpp | 24 +++++++++++------------- src/dxvk/dxvk_presenter.h | 7 ++++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index cd96680d..e84b746a 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -588,7 +588,7 @@ namespace dxvk { PresenterTimingFeedback Presenter::queryPresentTiming() { std::lock_guard lock(m_timingMutex); - return m_timingFeedback; + return updatePresentTimingFeedback(0u, 0u); } @@ -1552,7 +1552,7 @@ namespace dxvk { } - bool Presenter::updatePresentTimingFeedback( + PresenterTimingFeedback Presenter::updatePresentTimingFeedback( uint64_t frameId, VkPresentStageFlagsEXT queryStages) { // Need to access both timing stuff and the frame queue here @@ -1562,10 +1562,8 @@ namespace dxvk { feedback.frameId = frameId; feedback.presentTime = frameId ? dxvk::high_resolution_clock::get_counter() : 0u; - if (!m_timingMode.presentStage) { - commitTimingFeedback(feedback); - return false; - } + if (!m_timingMode.presentStage) + return commitTimingFeedback(feedback); // Still need to drain the queue even if everything is messed up small_vector stageTimes; @@ -1592,8 +1590,7 @@ namespace dxvk { if (status) { Logger::warn(str::format("Presenter: Failed to query past present timings: ", status)); - commitTimingFeedback(feedback); - return false; + return commitTimingFeedback(feedback); } // Update display timing and time domains as necessary @@ -1609,7 +1606,7 @@ namespace dxvk { updateTimingDomains(); if (!recalibrateTimeDomains()) - return false; + return commitTimingFeedback(feedback); } if (updateMode) @@ -1672,23 +1669,24 @@ namespace dxvk { // - The current frame was submitted with a timing query. // - We successfully mapped the report timestamp back to QPC. // TODO figure out the correct time domain for dxvk-native to use. - if (m_timingMode.lastFrameTimeQpc && (!frameId || !queryStages)) { + if (m_timingMode.lastFrameTimeQpc && (!frameId || queryStages)) { feedback.frameId = m_timingMode.lastFrameId; feedback.presentTime = m_timingMode.lastFrameTimeQpc; } - commitTimingFeedback(feedback); - return true; + return commitTimingFeedback(feedback); } - void Presenter::commitTimingFeedback( + PresenterTimingFeedback Presenter::commitTimingFeedback( const PresenterTimingFeedback& feedback) { // Only commit present timing if the new frame ID is larger than the // previous frame ID, in order to avoid reverting the counter in case // the application spins on present statistics. if (feedback.frameId > m_timingFeedback.frameId) m_timingFeedback = feedback; + + return m_timingFeedback; } diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 6b0cc3cd..26a05c9d 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -13,6 +13,7 @@ #include "../util/util_string.h" #include "../util/sync/sync_signal.h" +#include "../util/sync/sync_ticketlock.h" #include "../vulkan/vulkan_loader.h" @@ -414,7 +415,7 @@ namespace dxvk { uint64_t m_lastCompleted = 0u; alignas(CACHE_LINE_SIZE) - dxvk::mutex m_timingMutex; + sync::TicketLock m_timingMutex; std::optional m_timingDomains; std::optional m_timingDisplayInfo; @@ -484,11 +485,11 @@ namespace dxvk { bool recalibrateTimeDomains(); - bool updatePresentTimingFeedback( + PresenterTimingFeedback updatePresentTimingFeedback( uint64_t frameId, VkPresentStageFlagsEXT queryStages); - void commitTimingFeedback( + PresenterTimingFeedback commitTimingFeedback( const PresenterTimingFeedback& feedback); bool waitUntilFrameTargetTime( From 31cc010db3c62fca5ae117c49ff1074f3f1068f1 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 13:43:27 +0200 Subject: [PATCH 080/135] [dxvk] Do not query timing reports if report queue is full --- src/dxvk/dxvk_presenter.cpp | 15 ++++++++++++++- src/dxvk/dxvk_presenter.h | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index e84b746a..a9a170ab 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -218,6 +218,12 @@ namespace dxvk { std::lock_guard lock(m_timingMutex); timingInfo.presentStageQueries = m_timingMode.presentStage; + // Try to avoid overflowing the driver-internal report queue + if (m_reportQueueAddId >= m_reportQueueTakeId + FrameQueueSize) { + Logger::warn("Presenter: Report queue overflow"); + timingInfo.presentStageQueries = 0u; + } + if (m_timingMode.referenceFrameId) { frameTargetTime = m_timingMode.referenceTime + (frameId - m_timingMode.referenceFrameId) * m_timingMode.frameIntervalNs; @@ -296,6 +302,8 @@ namespace dxvk { m_frameIndex += 1; m_frameIndex %= m_semaphores.size(); + + m_reportQueueAddId = frameId; } // Add frame to waiter queue with current properties @@ -1565,7 +1573,8 @@ namespace dxvk { if (!m_timingMode.presentStage) return commitTimingFeedback(feedback); - // Still need to drain the queue even if everything is messed up + // Query reports in-order so that we can somewhat robustly + // track how many frames are still in the report queue small_vector stageTimes; small_vector reports; @@ -1625,6 +1634,10 @@ namespace dxvk { if (!report.reportComplete || !time.time || time.stage != m_timingMode.presentStage) continue; + // Write back frame ID regardless of whether we do anything with the + // report so that we can track which frames are still in the queue + m_reportQueueTakeId = std::max(m_reportQueueTakeId, report.presentId); + // Find queued frame entry so we can properly correlate the report // with expected timings const PresenterFrame* frameEntry = nullptr; diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 26a05c9d..f0fed989 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -425,6 +425,9 @@ namespace dxvk { double m_frameRateLimit = 0.0; + uint64_t m_reportQueueAddId = 0u; + uint64_t m_reportQueueTakeId = 0u; + alignas(CACHE_LINE_SIZE) FpsLimiter m_fpsLimiter; From 053666307a02f93a7a8afa783edfc35f4da02e6f Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 13:47:50 +0200 Subject: [PATCH 081/135] [dxvk] Remove dynamically sized present timing queue Instead, bump the default queue size a bit. --- src/dxvk/dxvk_presenter.cpp | 23 +++++------------------ src/dxvk/dxvk_presenter.h | 4 +--- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index a9a170ab..2dec8b78 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -274,13 +274,6 @@ namespace dxvk { VkResult status = m_vkd->vkQueuePresentKHR( m_device->queues().graphics.queueHandle, &info); - // Treat a QUEUE_FULL error as a hint to recreate the swapchain with - // a larger queue size. This could probably be done more robustly, - // but since we have latency constraints in the frontend APIs, we - // should never run into this scenario in practice anyway. - if (status == VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT) - m_timingQueueSize *= 2u; - // Maintain valid state if presentation succeeded, even if we want to // recreate the swapchain. Spec says that 'queue' operations, i.e. the // semaphore and fence signals, still happen if present fails with @@ -796,10 +789,7 @@ namespace dxvk { // If present timing is supported, also check if there is a useful present // stage we can use. Prioritize the latest available stage for absolute // timing since we need to correlate with timing reports. - // Also, opt out of timing if we have issues keeping the report queue to - // a reasonable size. - if (presentTimingCaps.presentTimingSupported && presentId2Caps.presentId2Supported - && presentWait2Caps.presentWait2Supported && m_timingQueueSize <= MaxFrameQueueSize) { + if (presentTimingCaps.presentTimingSupported && presentId2Caps.presentId2Supported) { small_vector stages; if (presentTimingCaps.presentAtAbsoluteTimeSupported) { @@ -996,7 +986,7 @@ namespace dxvk { // Set up initial present timing state if (m_timingMode.presentStage) { - m_vkd->vkSetSwapchainPresentTimingQueueSizeEXT(m_vkd->device(), m_swapchain, m_timingQueueSize); + m_vkd->vkSetSwapchainPresentTimingQueueSizeEXT(m_vkd->device(), m_swapchain, FrameQueueSize); updateDisplayTiming(); @@ -1575,13 +1565,10 @@ namespace dxvk { // Query reports in-order so that we can somewhat robustly // track how many frames are still in the report queue - small_vector stageTimes; - small_vector reports; - - stageTimes.resize(m_timingQueueSize); - reports.resize(m_timingQueueSize); + std::array stageTimes = {}; + std::array reports = {}; - for (size_t i = 0u; i < m_timingQueueSize; i++) { + for (size_t i = 0u; i < reports.size(); i++) { reports[i].sType = VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_EXT; reports[i].presentStageCount = 1u; reports[i].pPresentStages = &stageTimes[i]; diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index f0fed989..ad858e57 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -148,8 +148,7 @@ namespace dxvk { * window system integration. */ class Presenter : public RcObject { - static constexpr size_t FrameQueueSize = 32u; - static constexpr size_t MaxFrameQueueSize = 256u; + static constexpr size_t FrameQueueSize = 64u; public: Presenter( @@ -421,7 +420,6 @@ namespace dxvk { std::optional m_timingDisplayInfo; PresenterTimingInfo m_timingMode = { }; PresenterTimingFeedback m_timingFeedback = {}; - uint32_t m_timingQueueSize = FrameQueueSize; double m_frameRateLimit = 0.0; From 67e1e58cb7a1585f51f3eb5038677d521faa60d9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 20:26:21 +0200 Subject: [PATCH 082/135] [dxvk] Only track frames with actual timing query for the present queue --- src/dxvk/dxvk_presenter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 2dec8b78..2e977619 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -296,7 +296,8 @@ namespace dxvk { m_frameIndex += 1; m_frameIndex %= m_semaphores.size(); - m_reportQueueAddId = frameId; + if (timingInfo.presentStageQueries) + m_reportQueueAddId = frameId; } // Add frame to waiter queue with current properties From 80ea5f6a7fc93ebe3bd405f0458b1c8f4891bbd9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 19 Oct 2026 16:32:25 +0200 Subject: [PATCH 083/135] [dxvk] Pass full shader metadata when adding shader stage --- src/dxvk/dxvk_compute.cpp | 2 +- src/dxvk/dxvk_graphics.cpp | 10 +++++----- src/dxvk/dxvk_shader.cpp | 26 ++++++++++++++++++++------ src/dxvk/dxvk_shader.h | 11 +++++++---- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/dxvk/dxvk_compute.cpp b/src/dxvk/dxvk_compute.cpp index a1ce7558..2547e90a 100644 --- a/src/dxvk/dxvk_compute.cpp +++ b/src/dxvk/dxvk_compute.cpp @@ -99,7 +99,7 @@ namespace dxvk { DxvkPipelineSpecConstantState scState(m_shaders.cs->metadata().specConstantMask, state.sc); DxvkShaderStageInfo stageInfo(m_device, layout); - stageInfo.addStage(VK_SHADER_STAGE_COMPUTE_BIT, + stageInfo.addStage(m_shaders.cs->metadata(), m_shaders.cs->getCode(m_layout.getBindingMap(DxvkPipelineLayoutType::Merged), nullptr), &scState.scInfo); diff --git a/src/dxvk/dxvk_graphics.cpp b/src/dxvk/dxvk_graphics.cpp index 9823bace..edabf4fc 100644 --- a/src/dxvk/dxvk_graphics.cpp +++ b/src/dxvk/dxvk_graphics.cpp @@ -1457,16 +1457,16 @@ namespace dxvk { auto layout = m_layout.getLayout(DxvkPipelineLayoutType::Merged); DxvkShaderStageInfo stageInfo(m_device, layout); - stageInfo.addStage(VK_SHADER_STAGE_VERTEX_BIT, getShaderCode(*m_shaders.vs, key.shState.vsInfo), &key.scState.scInfo); + stageInfo.addStage(m_shaders.vs->metadata(), getShaderCode(*m_shaders.vs, key.shState.vsInfo), &key.scState.scInfo); if (m_shaders.tcs) - stageInfo.addStage(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, getShaderCode(*m_shaders.tcs, key.shState.tcsInfo), &key.scState.scInfo); + stageInfo.addStage(m_shaders.tcs->metadata(), getShaderCode(*m_shaders.tcs, key.shState.tcsInfo), &key.scState.scInfo); if (m_shaders.tes) - stageInfo.addStage(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, getShaderCode(*m_shaders.tes, key.shState.tesInfo), &key.scState.scInfo); + stageInfo.addStage(m_shaders.tes->metadata(), getShaderCode(*m_shaders.tes, key.shState.tesInfo), &key.scState.scInfo); if (m_shaders.gs) - stageInfo.addStage(VK_SHADER_STAGE_GEOMETRY_BIT, getShaderCode(*m_shaders.gs, key.shState.gsInfo), &key.scState.scInfo); + stageInfo.addStage(m_shaders.gs->metadata(), getShaderCode(*m_shaders.gs, key.shState.gsInfo), &key.scState.scInfo); if (m_shaders.fs) - stageInfo.addStage(VK_SHADER_STAGE_FRAGMENT_BIT, getShaderCode(*m_shaders.fs, key.shState.fsInfo), &key.scState.scInfo); + stageInfo.addStage(m_shaders.fs->metadata(), getShaderCode(*m_shaders.fs, key.shState.fsInfo), &key.scState.scInfo); VkPipelineCreateFlags2CreateInfo flags = { VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO }; diff --git a/src/dxvk/dxvk_shader.cpp b/src/dxvk/dxvk_shader.cpp index 8f6261ba..a133519d 100644 --- a/src/dxvk/dxvk_shader.cpp +++ b/src/dxvk/dxvk_shader.cpp @@ -87,7 +87,7 @@ namespace dxvk { } void DxvkShaderStageInfo::addStage( - VkShaderStageFlagBits stage, + const DxvkShaderMetadata& metadata, SpirvCodeBuffer&& code, const VkSpecializationInfo* specInfo) { // Take ownership of the SPIR-V code buffer @@ -101,7 +101,7 @@ namespace dxvk { auto& stageInfo = m_stageInfos[m_stageCount]; stageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, &moduleInfo }; - stageInfo.stage = stage; + stageInfo.stage = metadata.stage; stageInfo.pName = "main"; stageInfo.pSpecializationInfo = specInfo; @@ -110,7 +110,7 @@ namespace dxvk { void DxvkShaderStageInfo::addStage( - VkShaderStageFlagBits stage, + const DxvkShaderMetadata& metadata, const VkShaderModuleIdentifierEXT& identifier, const VkSpecializationInfo* specInfo) { // Copy relevant bits of the module identifier @@ -126,7 +126,7 @@ namespace dxvk { auto& stageInfo = m_stageInfos[m_stageCount]; stageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; stageInfo.pNext = &moduleId.createInfo; - stageInfo.stage = stage; + stageInfo.stage = metadata.stage; stageInfo.pName = "main"; stageInfo.pSpecializationInfo = specInfo; @@ -327,7 +327,7 @@ namespace dxvk { if (!identifier->identifierSize) return { VK_NULL_HANDLE, 0 }; - stageInfo.addStage(stage, *identifier, nullptr); + stageInfo.addStage(getShaderMetadata(stage), *identifier, nullptr); } else { // Decompress code and generate identifier as needed SpirvCodeBuffer spirvCode = this->getShaderCode(stage); @@ -335,7 +335,7 @@ namespace dxvk { if (!identifier->identifierSize) this->generateModuleIdentifierLocked(identifier, spirvCode); - stageInfo.addStage(stage, std::move(spirvCode), nullptr); + stageInfo.addStage(getShaderMetadata(stage), std::move(spirvCode), nullptr); } stages &= stages - 1; @@ -596,6 +596,20 @@ namespace dxvk { } + DxvkShaderMetadata DxvkShaderPipelineLibrary::getShaderMetadata( + VkShaderStageFlagBits stage) const { + DxvkShader* shader = getShader(stage); + + if (!shader) { + DxvkShaderMetadata result = {}; + result.stage = stage; + return result; + } + + return shader->metadata(); + } + + void DxvkShaderPipelineLibrary::generateModuleIdentifierLocked( VkShaderModuleIdentifierEXT* identifier, const SpirvCodeBuffer& spirvCode) { diff --git a/src/dxvk/dxvk_shader.h b/src/dxvk/dxvk_shader.h index de9a4c64..1c6903f2 100644 --- a/src/dxvk/dxvk_shader.h +++ b/src/dxvk/dxvk_shader.h @@ -408,24 +408,24 @@ namespace dxvk { /** * \brief Adds a shader stage with specialization info * - * \param [in] stage Shader stage + * \param [in] metadata Shader metadata * \param [in] code SPIR-V code * \param [in] specinfo Specialization info */ void addStage( - VkShaderStageFlagBits stage, + const DxvkShaderMetadata& metadata, SpirvCodeBuffer&& code, const VkSpecializationInfo* specInfo); /** * \brief Adds stage using a module identifier * - * \param [in] stage Shader stage + * \param [in] metadata Shader metadata * \param [in] identifier Shader module identifier * \param [in] specinfo Specialization info */ void addStage( - VkShaderStageFlagBits stage, + const DxvkShaderMetadata& metadata, const VkShaderModuleIdentifierEXT& identifier, const VkSpecializationInfo* specInfo); @@ -665,6 +665,9 @@ namespace dxvk { SpirvCodeBuffer getShaderCode( VkShaderStageFlagBits stage) const; + DxvkShaderMetadata getShaderMetadata( + VkShaderStageFlagBits stage) const; + void generateModuleIdentifierLocked( VkShaderModuleIdentifierEXT* identifier, const SpirvCodeBuffer& spirvCode); From b77e0c59343aa8755200d17a1b1d7b4252a1adfa Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 19 Oct 2026 21:57:15 +0200 Subject: [PATCH 084/135] [dxvk] Remove xfb-specific jank from shader key --- src/d3d11/d3d11_device.cpp | 2 +- src/dxvk/dxvk_shader_key.cpp | 24 +++++++++--------------- src/dxvk/dxvk_shader_key.h | 11 +---------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/d3d11/d3d11_device.cpp b/src/d3d11/d3d11_device.cpp index a5a12e8a..301590a6 100644 --- a/src/d3d11/d3d11_device.cpp +++ b/src/d3d11/d3d11_device.cpp @@ -2085,7 +2085,7 @@ namespace dxvk { } (shaderInfo.getType()); if (ShaderKey.stage() != shaderStage) { - bool mismatch = !ShaderKey.hasXfb() || ( + bool mismatch = ModuleInfo.xfbEntries.empty() || ( shaderStage != VK_SHADER_STAGE_VERTEX_BIT && shaderStage != VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT); diff --git a/src/dxvk/dxvk_shader_key.cpp b/src/dxvk/dxvk_shader_key.cpp index 098a4000..9e7fdca0 100644 --- a/src/dxvk/dxvk_shader_key.cpp +++ b/src/dxvk/dxvk_shader_key.cpp @@ -22,7 +22,7 @@ namespace dxvk { size_t hashSize, const uint8_t* metaHash, size_t metaSize) - : m_stage(uint16_t(stage)), m_xfb(metaSize ? 1u : 0u), m_size(codeSize) { + : m_stage(uint16_t(stage)), m_size(codeSize) { size_t index = 0u; for (size_t i = 0u; i < hashSize; i += 4u) { @@ -41,18 +41,14 @@ namespace dxvk { std::string name; name.reserve(48u); - if (m_xfb) { - name = "xfb"; - } else { - switch (m_stage) { - case VK_SHADER_STAGE_VERTEX_BIT: name = "vs"; break; - case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: name = "tcs"; break; - case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: name = "tes"; break; - case VK_SHADER_STAGE_GEOMETRY_BIT: name = "gs"; break; - case VK_SHADER_STAGE_FRAGMENT_BIT: name = "fs"; break; - case VK_SHADER_STAGE_COMPUTE_BIT: name = "cs"; break; - default: name = "shdr"; - } + switch (m_stage) { + case VK_SHADER_STAGE_VERTEX_BIT: name = "vs"; break; + case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: name = "tcs"; break; + case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: name = "tes"; break; + case VK_SHADER_STAGE_GEOMETRY_BIT: name = "gs"; break; + case VK_SHADER_STAGE_FRAGMENT_BIT: name = "fs"; break; + case VK_SHADER_STAGE_COMPUTE_BIT: name = "cs"; break; + default: name = "shdr"; } name += "."; @@ -70,7 +66,6 @@ namespace dxvk { bool DxvkShaderHash::eq(const DxvkShaderHash& other) const { bool eq = m_stage == other.m_stage - && m_xfb == other.m_xfb && m_size == other.m_size; for (size_t i = 0u; i < m_hash.size(); i++) @@ -83,7 +78,6 @@ namespace dxvk { size_t DxvkShaderHash::hash() const { DxvkHashState hash = { }; hash.add(m_stage); - hash.add(m_xfb); hash.add(m_size); for (auto dw : m_hash) diff --git a/src/dxvk/dxvk_shader_key.h b/src/dxvk/dxvk_shader_key.h index 411d80e1..242525b5 100644 --- a/src/dxvk/dxvk_shader_key.h +++ b/src/dxvk/dxvk_shader_key.h @@ -57,14 +57,6 @@ namespace dxvk { return VkShaderStageFlagBits(m_stage); } - /** - * \brief Whether shader was created using streamout metadata - * \returns \c true if the shader has transform feedback metadata - */ - bool hasXfb() const { - return m_xfb; - } - /** * \brief Generates shader name for the given hash * \returns Shader name to use for tooling etc. @@ -87,8 +79,7 @@ namespace dxvk { private: - uint16_t m_stage = -1; - uint16_t m_xfb = 0u; + uint32_t m_stage = -1u; uint32_t m_size = 0u; std::array m_hash = { }; From 3c40ca3ea7126df4047e55690bfabdc9d854f220 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 21:52:27 +0200 Subject: [PATCH 085/135] [meta] Update dxbc-spirv --- subprojects/dxbc-spirv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index 64f078d6..687a3013 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit 64f078d685725b6bd5e1a045615705f47b6aa9de +Subproject commit 687a30131e50533fedd9e785a1bf3aea463504f2 From cb253e0c2ecb78471624622097b36a36cf39c2d3 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 22:19:08 +0200 Subject: [PATCH 086/135] [dxvk] Fix command buffer splitting --- src/dxvk/dxvk_cmdlist.cpp | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/dxvk/dxvk_cmdlist.cpp b/src/dxvk/dxvk_cmdlist.cpp index 108ce649..9211f159 100644 --- a/src/dxvk/dxvk_cmdlist.cpp +++ b/src/dxvk/dxvk_cmdlist.cpp @@ -440,30 +440,23 @@ namespace dxvk { bool push = m_cmd.sparseBind || m_cmd.execCommands; for (uint32_t i = 0; i < m_cmd.cmdBuffers.size(); i++) { - DxvkCmdBuffer cmdBuffer = DxvkCmdBuffer(i); - - if (cmdBuffer == DxvkCmdBuffer::ExecBuffer && !m_cmd.execCommands) - continue; - - if (m_cmd.cmdBuffers[i]) { - endCommandBuffer(m_cmd.cmdBuffers[i]); - - m_cmd.cmdBuffers[i] = cmdBuffer == DxvkCmdBuffer::ExecBuffer - ? allocateCommandBuffer(cmdBuffer) - : VK_NULL_HANDLE; - - push = true; - } + // An execution command buffer always exists, even if unused. + if (DxvkCmdBuffer(i) != DxvkCmdBuffer::ExecBuffer) + push = push || m_cmd.cmdBuffers[i]; } if (!push) return; + for (uint32_t i = 0; i < m_cmd.cmdBuffers.size(); i++) { + if (m_cmd.cmdBuffers[i]) + endCommandBuffer(m_cmd.cmdBuffers[i]); + } + m_cmdSubmissions.push_back(m_cmd); - m_cmd.execCommands = VK_FALSE; - m_cmd.syncSdma = VK_FALSE; - m_cmd.sparseBind = VK_FALSE; + m_cmd = DxvkCommandSubmissionInfo(); + m_cmd.cmdBuffers[uint32_t(DxvkCmdBuffer::ExecBuffer)] = allocateCommandBuffer(DxvkCmdBuffer::ExecBuffer); } From 65883aa8b109815b10484646637b57f41e35447c Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 20 Oct 2026 22:48:42 +0200 Subject: [PATCH 087/135] [dxvk] Be more robust against broken present timing reports Apparently time can just randomly be 0, so that's fun. --- src/dxvk/dxvk_presenter.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 2e977619..1a85c6cb 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -1619,13 +1619,16 @@ namespace dxvk { const auto& time = stageTimes[i]; const auto& report = reports[i]; - if (!report.reportComplete || !time.time || time.stage != m_timingMode.presentStage) + if (!report.reportComplete) continue; // Write back frame ID regardless of whether we do anything with the // report so that we can track which frames are still in the queue m_reportQueueTakeId = std::max(m_reportQueueTakeId, report.presentId); + if (!time.time || time.stage != m_timingMode.presentStage) + continue; + // Find queued frame entry so we can properly correlate the report // with expected timings const PresenterFrame* frameEntry = nullptr; From d713ef8748a1eee3e90d4b6d5dc2e80f5be4b519 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Wed, 21 Oct 2026 09:49:26 +0200 Subject: [PATCH 088/135] [dxvk] Remove double lock from queryPresentTiming Forgot to change this back during development, sigh. Fixes #5998. --- src/dxvk/dxvk_presenter.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 1a85c6cb..4eee415c 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -589,7 +589,6 @@ namespace dxvk { PresenterTimingFeedback Presenter::queryPresentTiming() { - std::lock_guard lock(m_timingMutex); return updatePresentTimingFeedback(0u, 0u); } From 29dd21b37a1e97f2341850c1b02c1a2975ed58ae Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Wed, 21 Oct 2026 13:57:33 +0200 Subject: [PATCH 089/135] [dxvk] Don't use 0.0 memory priority for HVV allocations Drivers could misinterpret this as an eviction otherwise. --- src/dxvk/dxvk_memory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_memory.cpp b/src/dxvk/dxvk_memory.cpp index c4ea405f..34e48b53 100644 --- a/src/dxvk/dxvk_memory.cpp +++ b/src/dxvk/dxvk_memory.cpp @@ -1374,7 +1374,7 @@ namespace dxvk { } else if (type.properties.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { // Regular HVV allocation. Give this a low priority since these are typically // useful when when placed in system memory. - priorityInfo.priority = 0.0f; + priorityInfo.priority = 0.1f; } else { // Standard priority for resource allocations priorityInfo.priority = 0.5f; From 02e2028c3fe954c8913b1f39bce2f581739f0c41 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Wed, 21 Oct 2026 13:09:31 +0200 Subject: [PATCH 090/135] [dxgi] Advertize fake nonlocal heap on integrated graphics --- src/dxgi/dxgi_adapter.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dxgi/dxgi_adapter.cpp b/src/dxgi/dxgi_adapter.cpp index 259c782d..6eb64e7b 100644 --- a/src/dxgi/dxgi_adapter.cpp +++ b/src/dxgi/dxgi_adapter.cpp @@ -296,9 +296,12 @@ namespace dxvk { VkMemoryHeapFlags heapFlagMask = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT; VkMemoryHeapFlags heapFlags = 0; - if (MemorySegmentGroup == DXGI_MEMORY_SEGMENT_GROUP_LOCAL) + // Apparently, Unreal Engine decided that not having a non-local heap equates + // running on a 15-year old Intel IGP and disables a bunch of effects as a + // result. Simply report the VRAM budgets as a workaround. + if (MemorySegmentGroup == DXGI_MEMORY_SEGMENT_GROUP_LOCAL || m_adapter->isUnifiedMemoryArchitecture()) heapFlags |= VK_MEMORY_HEAP_DEVICE_LOCAL_BIT; - + pVideoMemoryInfo->Budget = 0; pVideoMemoryInfo->CurrentUsage = 0; pVideoMemoryInfo->AvailableForReservation = 0; From ed73f1f2945fc6bbf10d53d4747179ab66582568 Mon Sep 17 00:00:00 2001 From: Blisto91 <47954800+Blisto91@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:23:18 +0200 Subject: [PATCH 091/135] [util] Remove textureMemory config from Dark Sector Original issue seems gone since https://github.com/doitsujin/dxvk/commit/d31ba3e14d6774f0a19828effaeb068077910495 --- src/util/config/config.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp index 839b4039..a32ef3c3 100644 --- a/src/util/config/config.cpp +++ b/src/util/config/config.cpp @@ -1020,10 +1020,6 @@ namespace dxvk { { R"(\\(FarCry2|farcry2game)\.exe$)", {{ { "d3d9.cachedWriteOnlyBuffers", "True" }, }} }, - /* Dark Sector - Crashes in places */ - { R"(\\DS\.exe$)", {{ - { "d3d9.textureMemory", "0" }, - }} }, /* Arcana Heart 3 Love Max + Xtend version * * Game speed is too fast above 60 fps */ { R"(\\(AH3LM|AALib)\.exe$)", {{ From cbd2206d72d3d9dd9941d5d868bb03a858e85f9b Mon Sep 17 00:00:00 2001 From: John-Michael Mulesa Date: Fri, 9 Oct 2026 08:59:02 -0400 Subject: [PATCH 092/135] [native] Find the executable and the Vulkan loader on macOS - util_env: getExePath() had no __APPLE__ branch and fell off the end of a non-void function, which traps on the first Direct3DCreate9. Use _NSGetExecutablePath. - vulkan_loader: also try libvulkan.1.dylib / libvulkan.dylib on macOS. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/util/util_env.cpp | 11 +++++++++++ src/vulkan/vulkan_loader.cpp | 7 +++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/util/util_env.cpp b/src/util/util_env.cpp index 912d6ec9..e3a82f2b 100644 --- a/src/util/util_env.cpp +++ b/src/util/util_env.cpp @@ -10,6 +10,9 @@ #include #include #include +#elif defined(__APPLE__) +#include +#include #endif #include "util_env.h" @@ -102,6 +105,14 @@ namespace dxvk::env { size_t count = readlink("/proc/self/exe", exePath.data(), exePath.size()); return std::string(exePath.begin(), exePath.begin() + count); +#elif defined(__APPLE__) + std::array exePath = {}; + uint32_t size = exePath.size(); + + if (_NSGetExecutablePath(exePath.data(), &size) != 0) + return ""; + + return std::string(exePath.data()); #elif defined(__FreeBSD__) int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, getpid()}; char exePath[PATH_MAX] = {}; diff --git a/src/vulkan/vulkan_loader.cpp b/src/vulkan/vulkan_loader.cpp index f9d1e98d..a9d8ffb9 100644 --- a/src/vulkan/vulkan_loader.cpp +++ b/src/vulkan/vulkan_loader.cpp @@ -10,15 +10,18 @@ namespace dxvk::vk { static std::pair loadVulkanLibrary() { - static const std::array dllNames = {{ + static const std::array dllNames = { #ifdef _WIN32 "winevulkan.dll", "vulkan-1.dll", +#elif defined(__APPLE__) + "libvulkan.1.dylib", + "libvulkan.dylib", #else "libvulkan.so", "libvulkan.so.1", #endif - }}; + }; for (auto dllName : dllNames) { HMODULE library = LoadLibraryA(dllName); From 9c51dd8b5d67ee3236b7839e6e8827b1f78075c9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 27 Oct 2026 12:41:05 +0100 Subject: [PATCH 093/135] [dxvk] Change eviction strategy to keep one chunk free The previous approach based on used memory doesn't work very well if memory is heavily fragmented, especially with very large VRAM pools. --- src/dxvk/dxvk_memory.cpp | 68 +++++++++++++++++++--------------------- src/dxvk/dxvk_memory.h | 1 - 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/dxvk/dxvk_memory.cpp b/src/dxvk/dxvk_memory.cpp index 34e48b53..36a74367 100644 --- a/src/dxvk/dxvk_memory.cpp +++ b/src/dxvk/dxvk_memory.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "../util/util_bit.h" @@ -1353,10 +1354,8 @@ namespace dxvk { freeEmptyChunksInHeap(*type.heap, size, high_resolution_clock::now()); // If we're exceeding vram budget on a dedicated GPU, fall back to system memory. - if (!next && type.heap->enforceBudget && (getMemoryStats(type.heap->index).memoryAllocated + size > type.heap->memoryBudget)) { - type.heap->enableEviction = true; + if (!next && type.heap->enforceBudget && (getMemoryStats(type.heap->index).memoryAllocated + size > type.heap->memoryBudget)) return DxvkDeviceMemory(); - } VkMemoryAllocateInfo memoryInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, next }; memoryInfo.allocationSize = size; @@ -2644,25 +2643,24 @@ namespace dxvk { auto& pool = type.devicePool; // Doing this on integrated graphics would be harmful, so don't' - if (pool.chunks.empty() || !type.heap->enableEviction) + if (pool.chunks.empty() || !type.heap->enforceBudget) return; - // Work out how much memory we should ideally leave unused - VkDeviceSize minUnusedMemory = 2u * pool.maxChunkSize; - - // Account for mapped memory types on the same heap where we may not be - // able to move any resources, and count their total allocated amount. - VkDeviceSize heapUsage = type.stats.memoryUsed; - - for (auto i : bit::BitMask(type.heap->memoryTypes & ~(1u << type.index))) - heapUsage += m_memTypes[i].stats.memoryAllocated; - - // If we're within budget with some headroom already, don't evict anything. - VkDeviceSize heapBudget = getMemoryStats(type.heap->index).memoryBudget; + // If we're within budget with some headroom already, don't evict + // anything as we're likely able to service subsequent allocations. + DxvkMemoryStats heapStats = getMemoryStats(type.heap->index); - if (heapUsage + minUnusedMemory <= heapBudget) + if (heapStats.memoryAllocated + pool.maxChunkSize < heapStats.memoryBudget) return; + // If there's an empty chunk of the maximum allowed size, there's + // no real point in evicting anything either since new allocations + // can just go there. + for (uint32_t i = 0u; i < pool.chunks.size(); i++) { + if (!pool.pageAllocator.pagesUsed(i) && pool.chunks[i].memory.size == pool.maxChunkSize) + return; + } + // Check the old previous chunk to evict unused resources right // away, and then advance to the next available chunk if possible std::array chunkIndices = { pool.nextEvictChunk, ~0u }; @@ -2677,6 +2675,8 @@ namespace dxvk { chunkIndices[1u] = pool.nextEvictChunk; + std::optional evictedChunk; + for (auto chunkIndex : chunkIndices) { // Ensure we actually have a valid, live chunk to work with if (chunkIndex >= pool.chunks.size() || !pool.pageAllocator.chunkIsAvailable(chunkIndex)) @@ -2704,31 +2704,29 @@ namespace dxvk { if (!resource) continue; - bool evicted = resource->requestEviction(); - - if (evicted && (heapUsage + minUnusedMemory > heapBudget + memoryEvicted)) { + if (resource->requestEviction()) { m_relocations.addResource(std::move(resource), a, DxvkAllocationMode::NoDeviceMemory); memoryEvicted += a->getMemoryInfo().size; } - - if (!evicted && memoryEvicted) { - // Relocate other resources within the chunk to reduce fragmentation - m_relocations.addResource(std::move(resource), a, DxvkAllocationModes( - DxvkAllocationMode::NoFallback, DxvkAllocationMode::NoAllocation)); - } } - // Relocate resource in the chunk we evicted from, and override any - // chunk that defragmentation may have picked. This greatly reduces - // fragmentation caused evicting a subset of resources from the chunk. - if (memoryEvicted) { - pool.pageAllocator.killChunk(chunkIndex); + if (memoryEvicted && !evictedChunk) + evictedChunk = std::make_optional(chunkIndex); + } - for (uint32_t i = 0u; i < pool.chunks.size(); i++) { - if (i != chunkIndex && pool.pageAllocator.pagesUsed(i)) - pool.pageAllocator.reviveChunk(i); - } + // Relocate resource in the chunk we evicted from, and override any + // chunk that defragmentation may have picked. This greatly reduces + // fragmentation caused evicting a subset of resources from the chunk. + if (evictedChunk) { + pool.pageAllocator.killChunk(*evictedChunk); + pool.nextDefragChunk = *evictedChunk; + + for (uint32_t i = 0u; i < pool.chunks.size(); i++) { + if (i != *evictedChunk && pool.pageAllocator.pagesUsed(i)) + pool.pageAllocator.reviveChunk(i); } + + moveDefragChunk(type); } } diff --git a/src/dxvk/dxvk_memory.h b/src/dxvk/dxvk_memory.h index 0d0dee89..24a404c1 100644 --- a/src/dxvk/dxvk_memory.h +++ b/src/dxvk/dxvk_memory.h @@ -148,7 +148,6 @@ namespace dxvk { VkDeviceSize memoryBudget = 0u; VkMemoryHeap properties = { }; bool enforceBudget = false; - bool enableEviction = false; }; From 884835c556480c588243274cf2d8909da55b5cdb Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 27 Oct 2026 13:16:34 +0100 Subject: [PATCH 094/135] [dxvk] Deprioritize HVV when freeing unused memory chunks Should avoid scenarios where we pessimize VRAM usage under extreme memory pressure. --- src/dxvk/dxvk_memory.cpp | 36 ++++++++++++++++++++++++++++++++++-- src/dxvk/dxvk_memory.h | 5 +++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/dxvk/dxvk_memory.cpp b/src/dxvk/dxvk_memory.cpp index 36a74367..ab97ae95 100644 --- a/src/dxvk/dxvk_memory.cpp +++ b/src/dxvk/dxvk_memory.cpp @@ -1350,8 +1350,8 @@ namespace dxvk { if (type.bufferUsage && !next) size = align(size, GlobalBufferAlignment); - // Preemptively free some unused allocations to reduce memory waste - freeEmptyChunksInHeap(*type.heap, size, high_resolution_clock::now()); + // Preemptively free some unused allocations to reduce memory waste. + freeEmptyChunksForType(type, size, high_resolution_clock::now()); // If we're exceeding vram budget on a dedicated GPU, fall back to system memory. if (!next && type.heap->enforceBudget && (getMemoryStats(type.heap->index).memoryAllocated + size > type.heap->memoryBudget)) @@ -1726,6 +1726,38 @@ namespace dxvk { } + void DxvkMemoryAllocator::freeEmptyChunksForType( + DxvkMemoryType& type, + VkDeviceSize allocationSize, + high_resolution_clock::time_point time) { + bool freed = false; + + // Always check the memory type that we actually want to allocate memory from first + freed |= freeEmptyChunksInPool(type, type.devicePool, allocationSize, time); + freed |= freeEmptyChunksInPool(type, type.mappedPool, allocationSize, time); + + // When allocating HVV, do not mess around with the main device-local memory type + // since that is generally going to be far more important for performance. Instead, + // simply fall back to system memory. + VkMemoryPropertyFlags hvvFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + bool isHvv = (type.properties.propertyFlags & hvvFlags) == hvvFlags && type.heap->enforceBudget; + + if (!isHvv) { + for (auto typeIndex : bit::BitMask(type.heap->memoryTypes & ~(1u << type.index))) { + auto& type = m_memTypes[typeIndex]; + + freed |= freeEmptyChunksInPool(type, type.devicePool, allocationSize, time); + freed |= freeEmptyChunksInPool(type, type.mappedPool, allocationSize, time); + } + } + + if (freed) + updateMemoryHeapStats(type.heap->index); + } + + void DxvkMemoryAllocator::freeEmptyChunksInHeap( const DxvkMemoryHeap& heap, VkDeviceSize allocationSize, diff --git a/src/dxvk/dxvk_memory.h b/src/dxvk/dxvk_memory.h index 24a404c1..76364227 100644 --- a/src/dxvk/dxvk_memory.h +++ b/src/dxvk/dxvk_memory.h @@ -1457,6 +1457,11 @@ namespace dxvk { uint32_t countEmptyChunksInPool( const DxvkMemoryPool& pool) const; + void freeEmptyChunksForType( + DxvkMemoryType& type, + VkDeviceSize allocationSize, + high_resolution_clock::time_point time); + void freeEmptyChunksInHeap( const DxvkMemoryHeap& heap, VkDeviceSize allocationSize, From 534f0a126f83427b099c14fd9acce8fe347c7d79 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 27 Oct 2026 13:43:49 +0100 Subject: [PATCH 095/135] [dxvk] Only free small chunks when actually allocating memory Avoids situations where we would constantly free and reallocate small memory chunks when under memory pressure. --- src/dxvk/dxvk_memory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_memory.cpp b/src/dxvk/dxvk_memory.cpp index ab97ae95..313c24fe 100644 --- a/src/dxvk/dxvk_memory.cpp +++ b/src/dxvk/dxvk_memory.cpp @@ -1807,7 +1807,7 @@ namespace dxvk { // Free the chunk if it is smaller than the current chunk size of // the pool, since it is unlikely to be useful for future allocations. // Also free if the pending allocation would exceed the heap budget. - bool shouldFree = chunk.memory.size < pool.nextChunkSize + bool shouldFree = (chunk.memory.size < pool.nextChunkSize && allocationSize) || allocationSize + heapAllocated > heapBudget || allocationSize > heapBudget; From f4c7433927b2923034974e54129e8b71e224bac4 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 27 Oct 2026 15:03:50 +0100 Subject: [PATCH 096/135] [dxvk] Introduce "promoted" residency status for resources Makes it so we don't spam residency requests when VRAM is full. --- src/dxvk/dxvk_context.cpp | 4 +++- src/dxvk/dxvk_sparse.h | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index ad3cd33b..66b2c539 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -9114,8 +9114,10 @@ namespace dxvk { for (const auto& e : resourceList) { auto storage = e.resource->relocateStorage(e.mode); - if (!storage) + if (!storage) { + e.resource->revertResidency(); continue; + } Rc image = dynamic_cast(e.resource.ptr()); Rc buffer = dynamic_cast(e.resource.ptr()); diff --git a/src/dxvk/dxvk_sparse.h b/src/dxvk/dxvk_sparse.h index 1dd7ab80..82341206 100644 --- a/src/dxvk/dxvk_sparse.h +++ b/src/dxvk/dxvk_sparse.h @@ -432,6 +432,7 @@ namespace dxvk { Resident = 0u, ///< Resource is resident in desired memory type Demoted = 1u, ///< Resource is likely unused and may get evicted Evicted = 2u, ///< Resource got evicted to system memory + Promoted = 3u, ///< Resource is enqueued to be moved to preferred memory }; @@ -653,12 +654,36 @@ namespace dxvk { if (likely(status == DxvkResourceResidency::Resident)) return; - if (status == DxvkResourceResidency::Demoted - && m_residency.compare_exchange_strong(status, DxvkResourceResidency::Resident)) - return; + while (true) { + switch (status) { + case DxvkResourceResidency::Demoted: { + if (m_residency.compare_exchange_strong(status, DxvkResourceResidency::Resident)) + return; + } break; + + case DxvkResourceResidency::Evicted: { + if (m_residency.compare_exchange_strong(status, DxvkResourceResidency::Promoted)) { + makeResourceResident(); + return; + } + } break; + + default: + return; + } + } + } + + /** + * \brief Reverts residency status to evicted on relocation failure + * + * Only has an effect if the current residency status is \c Promoted. + */ + void revertResidency() { + DxvkResourceResidency status = m_residency.load(); - if (status == DxvkResourceResidency::Evicted) - makeResourceResident(); + if (status == DxvkResourceResidency::Promoted) + m_residency.compare_exchange_strong(status, DxvkResourceResidency::Evicted); } /** From e197a8a576de2748e40dcdbfd7a4a81a326115ce Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 27 Oct 2026 15:54:50 +0100 Subject: [PATCH 097/135] [dxvk] Traverse relocation list in order And loop back once we've reached the end. Fixes some starvation issues. --- src/dxvk/dxvk_memory.cpp | 14 +++++++++++--- src/dxvk/dxvk_memory.h | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/dxvk/dxvk_memory.cpp b/src/dxvk/dxvk_memory.cpp index 313c24fe..7a5ca175 100644 --- a/src/dxvk/dxvk_memory.cpp +++ b/src/dxvk/dxvk_memory.cpp @@ -694,7 +694,13 @@ namespace dxvk { VkDeviceSize totalSize = 0u; for (uint32_t i = 0; i < count; i++) { - auto iter = m_entries.begin(); + // Keep traversing keys in ascending order and only reset once we've + // reached the end. This helps avoid starving higher-ordered requests. + auto iter = m_entries.upper_bound(m_lastKey); + iter = iter == m_entries.end() ? m_entries.begin() : iter; + + if (iter == m_entries.end()) + break; if (totalSize && totalSize + iter->first.size > size) break; @@ -702,12 +708,14 @@ namespace dxvk { // Reduce number of resource evictions performed per request by // overestimating the amount of memory moved. May reduce stutter // in high-ish frame rate scenarios. - totalSize += iter->second.mode == DxvkAllocationMode::NoDeviceMemory + totalSize += iter->second.mode.test(DxvkAllocationMode::NoDeviceMemory) ? iter->first.size * 16u : iter->first.size; result.push_back(std::move(iter->second)); - m_entries.erase(iter); + m_lastKey = iter->first; + + iter = m_entries.erase(iter); } return result; diff --git a/src/dxvk/dxvk_memory.h b/src/dxvk/dxvk_memory.h index 76364227..7929166e 100644 --- a/src/dxvk/dxvk_memory.h +++ b/src/dxvk/dxvk_memory.h @@ -1131,6 +1131,8 @@ namespace dxvk { DxvkRelocationEntry, RelocationOrdering> m_entries; + DxvkResourceMemoryInfo m_lastKey = {}; + }; From bd395bf055e69943e74f869581301ed24cf16b53 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 27 Oct 2026 15:57:45 +0100 Subject: [PATCH 098/135] [dxvk] Also consider dead chunks for eviction We'll sometimes have dead chunks when defragmentation failed, so trying to evict the resources in question might actually be useful. --- src/dxvk/dxvk_memory.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/dxvk/dxvk_memory.cpp b/src/dxvk/dxvk_memory.cpp index 7a5ca175..1ee9d589 100644 --- a/src/dxvk/dxvk_memory.cpp +++ b/src/dxvk/dxvk_memory.cpp @@ -2705,21 +2705,16 @@ namespace dxvk { // away, and then advance to the next available chunk if possible std::array chunkIndices = { pool.nextEvictChunk, ~0u }; - for (uint32_t i = 0u; i < pool.chunks.size(); i++) { - pool.nextEvictChunk += 1u; - pool.nextEvictChunk %= pool.chunks.size(); - - if (pool.pageAllocator.chunkIsAvailable(pool.nextEvictChunk)) - break; - } + pool.nextEvictChunk += 1u; + pool.nextEvictChunk %= pool.chunks.size(); - chunkIndices[1u] = pool.nextEvictChunk; + if (chunkIndices[0u] != pool.nextEvictChunk) + chunkIndices[1u] = pool.nextEvictChunk; std::optional evictedChunk; for (auto chunkIndex : chunkIndices) { - // Ensure we actually have a valid, live chunk to work with - if (chunkIndex >= pool.chunks.size() || !pool.pageAllocator.chunkIsAvailable(chunkIndex)) + if (chunkIndex >= pool.chunks.size()) continue; auto& chunk = pool.chunks[chunkIndex]; From 9c5551cbefa8a6782d1b94554e254a2ff483e254 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 27 Oct 2026 21:31:59 +0100 Subject: [PATCH 099/135] [d3d9] Fix integer divisions in YUV conversion Fixes #6007. --- src/d3d9/shaders/d3d9_convert_common.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/d3d9/shaders/d3d9_convert_common.h b/src/d3d9/shaders/d3d9_convert_common.h index daca40e1..e234a4f5 100644 --- a/src/d3d9/shaders/d3d9_convert_common.h +++ b/src/d3d9/shaders/d3d9_convert_common.h @@ -22,9 +22,9 @@ vec2 unpackUnorm2x8(uint p) { } mat3x4 g_yuv_to_rgb = { - { 298 / 256, 0, 409 / 256, 0.5 }, - { 298 / 256, -100 / 256, -208 / 256, 0.5 }, - { 298 / 256, 516 / 256, 0, 0.5 } + { 298.0f / 256.0f, 0.0f, 409.0f / 256.0f, 0.5f }, + { 298.0f / 256.0f, -100.0f / 256.0f, -208.0f / 256.0f, 0.5f }, + { 298.0f / 256.0f, 516.0f / 256.0f, 0.0f, 0.5f }, }; vec4 convertYUV(vec3 yuv) { @@ -34,9 +34,9 @@ vec4 convertYUV(vec3 yuv) { } mat3x3 g_bt709_to_rgb = { - { 1.164, 0, 1.793 }, - { 1.164, -0.213, -0.533 }, - { 1.164, 2.112, 0 } + { 1.164f, 0.0f, 1.793f }, + { 1.164f, -0.213f, -0.533f }, + { 1.164f, 2.112f, 0.0f }, }; vec4 convertBT_709(vec3 cde) { From 0cfe0898289fb11e3d8d0e1c62cd344b825b7d57 Mon Sep 17 00:00:00 2001 From: Danylo Piliaiev Date: Wed, 28 Oct 2026 13:53:21 +0100 Subject: [PATCH 100/135] [dxvk] Fix DxvkGraphicsPipelineVertexInputState::eq The loop over viDivisorInfo.vertexBindingDivisorCount may have overwrote the `eq` even when previous checks found input state not equal. Surprisingly was found only when running d3d8 traces on Android with DXVK natively compiled via clang. Fixes: 1e56f2b7a ("[dxvk] Factor out vertex input state setup") Signed-off-by: Danylo Piliaiev --- src/dxvk/dxvk_graphics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_graphics.cpp b/src/dxvk/dxvk_graphics.cpp index edabf4fc..f40b6ec2 100644 --- a/src/dxvk/dxvk_graphics.cpp +++ b/src/dxvk/dxvk_graphics.cpp @@ -176,7 +176,7 @@ namespace dxvk { && a.offset == b.offset; } - for (uint32_t i = 0; i < viDivisorInfo.vertexBindingDivisorCount; i++) { + for (uint32_t i = 0; i < viDivisorInfo.vertexBindingDivisorCount && eq; i++) { const auto& a = viDivisors[i]; const auto& b = other.viDivisors[i]; From bdd99c8e352cd5ae6afd47ba38ff5f5e9914279d Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 29 Oct 2026 19:28:19 +0100 Subject: [PATCH 101/135] [dxvk] Log dynamic present mode changes --- src/dxvk/dxvk_presenter.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 4eee415c..0f93d9be 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -160,8 +160,14 @@ namespace dxvk { } // Set dynamic present mode for the next frame if possible - if (!m_dynamicModes.empty()) - m_presentMode = m_dynamicModes.at(m_preferredSyncInterval ? 1u : 0u); + if (!m_dynamicModes.empty()) { + auto mode = m_dynamicModes.at(m_preferredSyncInterval ? 1u : 0u); + + if (m_presentMode != mode) { + m_presentMode = mode; + Logger::info(str::format("Presenter: Changing present mode: ", m_presentMode)); + } + } // Return relevant Vulkan objects for the acquired image sync = m_semaphores.at(m_frameIndex); From 2b804afc30e632c8479d4d805aea85b04c730cd9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 29 Oct 2026 19:39:45 +0100 Subject: [PATCH 102/135] [dxvk] Check actual frame's present mode to decide whether to wait --- src/dxvk/dxvk_presenter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 0f93d9be..9d7f47ea 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -2103,8 +2103,8 @@ namespace dxvk { bool waited = frame.targetTime && waitUntilFrameTargetTime(frame); // If this doesn't apply, - bool isFifoMode = m_presentMode == VK_PRESENT_MODE_FIFO_KHR - || m_presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; + bool isFifoMode = frame.mode == VK_PRESENT_MODE_FIFO_KHR + || frame.mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; if (!waited && isFifoMode && frame.result >= 0 && m_hasPresentWait) { VkResult vr; From 5d9b9cae252c443997ab624a036bfef0dac8e3e3 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 29 Oct 2026 22:43:29 +0100 Subject: [PATCH 103/135] [dxvk] Fix misleading comment --- src/dxvk/dxvk_presenter.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 9d7f47ea..8452d3c1 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -2097,12 +2097,13 @@ namespace dxvk { } } - // If the frameis timed, wait for the calculated target time. This intentionally - // does not line up with vblank in fixed refresh rate scenarios and is done so - // that the application's render thread can see consistent frame pacing. + // If the frame is timed, wait for the calculated target time. This + // intentionally does not line up with vblank in fixed refresh rate + // scenarios and is done so that the application's render thread can + // see consistent frame pacing. bool waited = frame.targetTime && waitUntilFrameTargetTime(frame); - // If this doesn't apply, + // Legacy path, fall back to present_wait if we don't have timing. bool isFifoMode = frame.mode == VK_PRESENT_MODE_FIFO_KHR || frame.mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; From 0ac7e284e1ff71f61021735f7b5a99f492f6d101 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 29 Oct 2026 22:22:56 +0100 Subject: [PATCH 104/135] [dxvk] Reset reference timings when presenting with a non-FIFO mode --- src/dxvk/dxvk_presenter.cpp | 29 +++++++++++++++++++++-------- src/dxvk/dxvk_presenter.h | 1 + 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 8452d3c1..4df94b86 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -224,13 +224,20 @@ namespace dxvk { std::lock_guard lock(m_timingMutex); timingInfo.presentStageQueries = m_timingMode.presentStage; - // Try to avoid overflowing the driver-internal report queue + // If this is the first timed frame in a sequence, ignore older reports + if (!m_timingMode.firstValidFrameId) + m_timingMode.firstValidFrameId = frameId; + + // Try to avoid overflowing the driver-internal report queue. We can still + // work with the existing reports for a short period of time, though. if (m_reportQueueAddId >= m_reportQueueTakeId + FrameQueueSize) { Logger::warn("Presenter: Report queue overflow"); timingInfo.presentStageQueries = 0u; } - if (m_timingMode.referenceFrameId) { + if (m_timingMode.frameIntervalNs + && m_timingMode.referenceFrameId >= m_timingMode.firstValidFrameId + && m_timingMode.referenceFrameId + 2u * FrameQueueSize >= frameId) { frameTargetTime = m_timingMode.referenceTime + (frameId - m_timingMode.referenceFrameId) * m_timingMode.frameIntervalNs; } @@ -239,13 +246,18 @@ namespace dxvk { timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_RELATIVE_TIME_BIT_EXT; timingInfo.targetTime = m_timingMode.frameIntervalNs; timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; - } else if (m_timingMode.absoluteTiming && m_timingMode.referenceFrameId) { + } else if (m_timingMode.absoluteTiming && frameTargetTime) { timingInfo.targetTime = frameTargetTime; timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; if (m_timingDisplayInfo && !m_timingDisplayInfo->isVariableRefresh) timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_NEAREST_REFRESH_CYCLE_BIT_EXT; } + } else { + // When presenting with a non-FIFO mode, ensure that we don't compute + // frame target times based on timings from previous FIFO usage. + std::lock_guard lock(m_timingMutex); + m_timingMode.firstValidFrameId = 0; } VkPresentTimingsInfoEXT timingsInfo = { VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT }; @@ -1451,6 +1463,12 @@ namespace dxvk { m_timingMode.relativeTiming = false; m_timingMode.absoluteTiming = false; + // Reset reference time and frame ID for absolute timing + // so that we don't end up submitting bogus timestamps. + m_timingMode.referenceTime = 0u; + m_timingMode.referenceFrameId = 0u; + m_timingMode.firstValidFrameId = 0u; + // Can't meaningfully do timing if we don't know about display timing if (!m_timingDomains || !m_timingDisplayInfo || m_calibrationFailed) return; @@ -1494,11 +1512,6 @@ namespace dxvk { } } - // Reset reference time and frame ID for absolute timing - // so that we don't end up submitting bogus timestamps. - m_timingMode.referenceTime = 0u; - m_timingMode.referenceFrameId = 0u; - // Log frame rate target like we would for the built-in limiter if (m_timingMode.relativeTiming || m_timingMode.absoluteTiming) { auto frameRateToLog = uint32_t(10000000000.0 / double(m_timingMode.frameIntervalNs)); diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index ad858e57..64672bad 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -130,6 +130,7 @@ namespace dxvk { uint64_t lastFrameTimeLocal = 0u; uint64_t lastFrameTimeQpc = 0u; uint64_t lastFrameId = 0u; + uint64_t firstValidFrameId = 0u; }; /** From 78cdf8f1e62954eda30c382dfb13e65d6d43ba4b Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 29 Oct 2026 22:23:49 +0100 Subject: [PATCH 105/135] [dxvk] Sanitize wait duration for frame target time --- src/dxvk/dxvk_presenter.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 4df94b86..8a1f5013 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -1730,8 +1730,22 @@ namespace dxvk { if (!qpcTargetTime) return false; + auto now = dxvk::high_resolution_clock::now(); auto dxvkTargetTime = dxvk::high_resolution_clock::get_time_from_counter(qpcTargetTime); - Sleep::sleepUntil(dxvk::high_resolution_clock::now(), dxvkTargetTime); + + if (now >= dxvkTargetTime) + return true; + + // If we are supposed to wait for an extremely long duration, + // something is probably off with our timestamps... + auto waitDurationMs = std::chrono::duration_cast(dxvkTargetTime - now).count(); + + if (waitDurationMs > 500) { + Logger::err(str::format("Presenter: Target frame time is, ", waitDurationMs, " into the future, skipping wait.")); + return false; + } + + Sleep::sleepUntil(now, dxvkTargetTime); return true; } From d5df6b768d116452ee737ce845df8b586f403ea9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 29 Oct 2026 11:18:58 +0100 Subject: [PATCH 106/135] [meta] Update dxbc-spirv Buffer indexing optimization. --- src/dxvk/dxvk_shader_ir.cpp | 2 ++ subprojects/dxbc-spirv | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_shader_ir.cpp b/src/dxvk/dxvk_shader_ir.cpp index 9fe36824..3912549e 100644 --- a/src/dxvk/dxvk_shader_ir.cpp +++ b/src/dxvk/dxvk_shader_ir.cpp @@ -2263,6 +2263,8 @@ namespace dxvk { options.cseOptions.relocateDescriptorLoad = true; options.cseOptions.resolveOverlappingLoads = true; + options.bufferIndexing.allowDynamicIndexing = true; + if (m_info.options.spirv.test(DxvkShaderSpirvFlag::SupportsResourceIndexing)) options.descriptorIndexing.optimizeDescriptorIndexing = true; diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index 687a3013..b54d6e62 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit 687a30131e50533fedd9e785a1bf3aea463504f2 +Subproject commit b54d6e6217db3374a846f1577f0806e60eab613b From 21fda8fe3253bb727c01885e96f4f2d879c20b1d Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 30 Oct 2026 15:07:14 +0100 Subject: [PATCH 107/135] [meta] Update dxbc-spirv Comparison opt. --- subprojects/dxbc-spirv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index b54d6e62..76236b82 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit b54d6e6217db3374a846f1577f0806e60eab613b +Subproject commit 76236b827e95381aa1b37bccfb31642896c427f8 From 23b07e9be441f87eb46204d78d9265e565cfbc0f Mon Sep 17 00:00:00 2001 From: Blisto91 <47954800+Blisto91@users.noreply.github.com> Date: Sat, 31 Oct 2026 12:06:40 +0100 Subject: [PATCH 108/135] [meta] Add LLM checkbox to issue templates Adds a required checkbox confirming LLM output was not copy pasted into the description. --- .github/ISSUE_TEMPLATE/bug_report.md | 32 ------------------------ .github/ISSUE_TEMPLATE/bug_report.yml | 36 +++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/other.md | 12 --------- .github/ISSUE_TEMPLATE/other.yml | 16 ++++++++++++ 4 files changed, 52 insertions(+), 44 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/other.md create mode 100644 .github/ISSUE_TEMPLATE/other.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index a47fe653..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug report -about: Report crashes, rendering issues etc. -title: '' -labels: '' -assignees: '' - ---- - -Please describe your issue as accurately as possible. Include screenshots or videos when relevant. - -If you use Windows, please check the following page: https://github.com/doitsujin/dxvk/wiki/Windows - -### Software information -Name of the game, settings used etc. Include any mods or add-ons if applicable. - -### System information -- GPU: -- Driver: -- Wine version: -- DXVK version: - -### Apitrace file(s) -- Put a link here - -For instructions on how to use apitrace, see: https://github.com/doitsujin/dxvk/wiki/Using-Apitrace - -### Log files -Please attach Proton or Wine logs as a text file: -- When using Proton, set the Steam launch options for your game to `PROTON_LOG=1 %command%` and attach the corresponding `steam-xxxxx.log` file in your home directory. -- When using regular Wine, use `wine game.exe > game.log 2>&1` and attach the resulting `game.log` file. -- On Windows, DXVK log files will usually appear next to the games exe or in its base folder. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..c907049f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,36 @@ +name: Bug report +description: Report crashes, rendering issues etc. +body: + - type: checkboxes + attributes: + label: "By opening this issue, I confirm that:" + options: + - label: "I have not copy-pasted an LLM-generated response into the description." + required: true + - type: textarea + attributes: + label: Description + value: | + Please describe your issue as accurately as possible. Include screenshots or videos when relevant. + + If you use Windows, please check the following page: https://github.com/doitsujin/dxvk/wiki/Windows + + ### Software information + Name of the game, settings used etc. Include any mods or add-ons if applicable. + + ### System information + - GPU: + - Driver: + - Wine version: + - DXVK version: + + ### Apitrace file(s) + - Put a link here + + For instructions on how to use apitrace, see: https://github.com/doitsujin/dxvk/wiki/Using-Apitrace + + ### Log files + Please attach Proton or Wine logs as a text file: + - When using Proton, set the Steam launch options for your game to `PROTON_LOG=1 %command%` and attach the corresponding `steam-xxxxx.log` file in your home directory. + - When using regular Wine, use `wine game.exe > game.log 2>&1` and attach the resulting `game.log` file. + - On Windows, DXVK log files will usually appear next to the games exe or in its base folder. diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md deleted file mode 100644 index 0575f9cd..00000000 --- a/.github/ISSUE_TEMPLATE/other.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: Other -about: Questions, feature requests etc. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/other.yml b/.github/ISSUE_TEMPLATE/other.yml new file mode 100644 index 00000000..b58da26f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other.yml @@ -0,0 +1,16 @@ +name: Other +description: Questions, feature requests etc. +body: + - type: checkboxes + attributes: + label: "By opening this issue, I confirm that:" + options: + - label: "I have not copy-pasted an LLM-generated response into the description." + required: true + - type: textarea + attributes: + label: Description + value: | + From c555cc9c87f5a55ede36092870c88c59c8ffc8ca Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 31 Oct 2026 12:12:40 +0100 Subject: [PATCH 109/135] [dxvk] Simplify command list ring buffer Would be nice to get rid of this legacy jank at some point, but well. --- src/dxvk/dxvk_device.cpp | 7 +------ src/dxvk/dxvk_device.h | 2 +- src/dxvk/dxvk_recycler.h | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index a505c958..a9d623e5 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -179,12 +179,7 @@ namespace dxvk { Rc DxvkDevice::createCommandList() { - Rc cmdList = m_recycledCommandLists.retrieveObject(); - - if (cmdList == nullptr) - cmdList = new DxvkCommandList(this); - - return cmdList; + return m_recycledCommandLists.retrieveObject(this); } diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h index 19b3a976..00aa9314 100644 --- a/src/dxvk/dxvk_device.h +++ b/src/dxvk/dxvk_device.h @@ -776,7 +776,7 @@ namespace dxvk { sync::Spinlock m_statLock; DxvkStatCounters m_statCounters; - DxvkRecycler m_recycledCommandLists; + DxvkRecycler m_recycledCommandLists; DxvkSubmissionQueue m_submissionQueue; diff --git a/src/dxvk/dxvk_recycler.h b/src/dxvk/dxvk_recycler.h index 2dd1d595..05cf7313 100644 --- a/src/dxvk/dxvk_recycler.h +++ b/src/dxvk/dxvk_recycler.h @@ -28,13 +28,14 @@ namespace dxvk { * return \c nullptr and a new object has to be created. * \return An object, or \c nullptr */ - Rc retrieveObject() { + template + Rc retrieveObject(Args&&... args) { std::lock_guard lock(m_mutex); + + if (!m_index) + return new T(std::forward(args)...); - if (m_get == m_put) - return nullptr; - - return std::exchange(m_objects[(m_get++) % N], Rc()); + return std::exchange(m_objects.at(--m_index), Rc()); } /** @@ -48,8 +49,8 @@ namespace dxvk { void returnObject(const Rc& object) { std::lock_guard lock(m_mutex); - if (m_put - m_get < N) - m_objects[(m_put++) % N] = object; + if (m_index < N) + m_objects.at(m_index++) = object; } private: @@ -57,9 +58,8 @@ namespace dxvk { dxvk::mutex m_mutex; std::array, N> m_objects; - uint64_t m_get = 0; - uint64_t m_put = 0; + size_t m_index = 0; }; -} \ No newline at end of file +} From 508ad04fa3adde978df64a4c9c88a48edf1e227c Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 31 Oct 2026 12:21:42 +0100 Subject: [PATCH 110/135] [dxvk] Throttle CS chunk submission Main thread can run away otherwise. --- src/dxvk/dxvk_cs.cpp | 3 +++ src/dxvk/dxvk_cs.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_cs.cpp b/src/dxvk/dxvk_cs.cpp index cf7c7029..5109d841 100644 --- a/src/dxvk/dxvk_cs.cpp +++ b/src/dxvk/dxvk_cs.cpp @@ -126,6 +126,9 @@ namespace dxvk { m_condOnAdd.notify_one(); } + + if (seq >= MaxQueuedCsChunks) + synchronize(seq - MaxQueuedCsChunks); return seq; } diff --git a/src/dxvk/dxvk_cs.h b/src/dxvk/dxvk_cs.h index b4d98c39..4414673d 100644 --- a/src/dxvk/dxvk_cs.h +++ b/src/dxvk/dxvk_cs.h @@ -511,7 +511,7 @@ namespace dxvk { * commands on a DXVK context. */ class DxvkCsThread { - + static constexpr uint64_t MaxQueuedCsChunks = 256u; public: constexpr static uint64_t SynchronizeAll = ~0ull; From f863776806824486d709fee16ea619c03e22def4 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 31 Oct 2026 12:23:41 +0100 Subject: [PATCH 111/135] [d3d11] Limit number of command submissions in flight --- src/d3d11/d3d11_context_imm.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/d3d11/d3d11_context_imm.cpp b/src/d3d11/d3d11_context_imm.cpp index b21f2932..b2d6608e 100644 --- a/src/d3d11/d3d11_context_imm.cpp +++ b/src/d3d11/d3d11_context_imm.cpp @@ -1102,6 +1102,11 @@ namespace dxvk { m_hasPendingUnresolvedPass = false; + // Throttle submissions to avoid queueing up too many command + // buffers in case the app never presents + if (m_submissionId >= MaxNumQueuedCommandBuffers) + m_device->waitForSubmission(m_submissionId - MaxNumQueuedCommandBuffers); + // Unbind unused resources ApplyDirtyNullBindings(); From 5c582a0ca7665f4e0c6561b1fe056bb451881b99 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 30 Oct 2026 10:32:23 +0100 Subject: [PATCH 112/135] [util] Use CLOCK_MONOTONIC for dxvk-native on Linux --- src/util/util_time.h | 59 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/util/util_time.h b/src/util/util_time.h index bcc311b6..f1d2e796 100644 --- a/src/util/util_time.h +++ b/src/util/util_time.h @@ -5,8 +5,12 @@ #if defined(_WIN32) && !defined(__WINE__) #include +#elif defined(__linux__) +#include #endif +#include "../vulkan/vulkan_loader.h" + namespace dxvk { #if defined(_WIN32) && !defined(__WINE__) @@ -18,11 +22,11 @@ namespace dxvk { using duration = std::chrono::nanoseconds; using time_point = std::chrono::time_point; - static inline time_point now() noexcept { + static time_point now() noexcept { return get_time_from_counter(get_counter()); } - static inline time_point get_time_from_counter(int64_t counter) { + static time_point get_time_from_counter(int64_t counter) { // Keep the frequency static, this doesn't change at all. static const int64_t freq = get_frequency(); @@ -32,34 +36,73 @@ namespace dxvk { return time_point(duration(whole + part)); } - static inline int64_t get_frequency() { + static int64_t get_frequency() { LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); return freq.QuadPart; } - static inline int64_t get_counter() { + static int64_t get_counter() { LARGE_INTEGER count; QueryPerformanceCounter(&count); return count.QuadPart; } + + static VkTimeDomainKHR get_vk_time_domain() { + return VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_EXT; + } + }; +#elif defined(__linux__) + struct high_resolution_clock { + static constexpr bool is_steady = true; + + using rep = int64_t; + using period = std::nano; + using duration = std::chrono::nanoseconds; + using time_point = std::chrono::time_point; + + static time_point now() noexcept { + return get_time_from_counter(get_counter()); + } + + static time_point get_time_from_counter(int64_t counter) { + return time_point(duration(counter)); + } + + static int64_t get_frequency() { + return period::den; + } + + static int64_t get_counter() { + timespec tp = {}; + ::clock_gettime(CLOCK_MONOTONIC, &tp); + return int64_t(tp.tv_sec) * 1'000'000'000 + int64_t(tp.tv_nsec); + } + + static VkTimeDomainKHR get_vk_time_domain() { + return VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT; + } }; #else struct high_resolution_clock : public std::chrono::high_resolution_clock { - static inline time_point get_time_from_counter(int64_t counter) { + static time_point get_time_from_counter(int64_t counter) { return time_point() + duration(counter); } - static inline int64_t get_frequency() { + static int64_t get_frequency() { return period::den; } - static inline int64_t get_counter() { + static int64_t get_counter() { return now().time_since_epoch().count(); } + + static VkTimeDomainKHR get_vk_time_domain() { + return VK_TIME_DOMAIN_MAX_ENUM_KHR; + } }; #endif -} \ No newline at end of file +} From 0b8460fbd206fbe084f015dd7c25494255805335 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 30 Oct 2026 10:44:50 +0100 Subject: [PATCH 113/135] [dxvk] Support non-QPC host time domains for present timing --- src/dxvk/dxvk_presenter.cpp | 24 ++++++++++++------------ src/dxvk/dxvk_presenter.h | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 8a1f5013..732b9bf0 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -1407,7 +1407,7 @@ namespace dxvk { } for (size_t i = 0u; i < domains.size(); i++) { - if (domains[i] == VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR) { + if (domains[i] == dxvk::high_resolution_clock::get_vk_time_domain()) { auto& domain = info.domains.emplace_back(); domain.timeDomain = domains[i]; } @@ -1665,18 +1665,18 @@ namespace dxvk { report.timeDomain, report.timeDomainId, time.time, VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, frameEntry->timingDomainId); - uint64_t reportTimeQpc = 0u; + uint64_t reportTimeHost = 0u; - if (hasQpcDomain()) { - reportTimeQpc = translateTimestamp( + if (hasTimeDomain(dxvk::high_resolution_clock::get_vk_time_domain())) { + reportTimeHost = translateTimestamp( report.timeDomain, report.timeDomainId, time.time, - VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR, 0u); + dxvk::high_resolution_clock::get_vk_time_domain(), 0u); } if (reportTimeLocal) { m_timingMode.lastFrameId = report.presentId; m_timingMode.lastFrameTimeLocal = reportTimeLocal; - m_timingMode.lastFrameTimeQpc = reportTimeQpc; + m_timingMode.lastFrameTimeHost = reportTimeHost; hasMissedDeadline = reportTimeLocal > frameEntry->targetDeadline; } @@ -1691,9 +1691,9 @@ namespace dxvk { // - The current frame was submitted with a timing query. // - We successfully mapped the report timestamp back to QPC. // TODO figure out the correct time domain for dxvk-native to use. - if (m_timingMode.lastFrameTimeQpc && (!frameId || queryStages)) { + if (m_timingMode.lastFrameTimeHost && (!frameId || queryStages)) { feedback.frameId = m_timingMode.lastFrameId; - feedback.presentTime = m_timingMode.lastFrameTimeQpc; + feedback.presentTime = m_timingMode.lastFrameTimeHost; } return commitTimingFeedback(feedback); @@ -1720,10 +1720,10 @@ namespace dxvk { uint64_t qpcTargetTime = 0u; { std::lock_guard lock(m_timingMutex); - if (hasQpcDomain()) { + if (hasTimeDomain(dxvk::high_resolution_clock::get_vk_time_domain())) { qpcTargetTime = translateTimestamp( VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, frame.timingDomainId, frame.targetTime, - VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR, 0u); + dxvk::high_resolution_clock::get_vk_time_domain(), 0u); } } @@ -1750,12 +1750,12 @@ namespace dxvk { } - bool Presenter::hasQpcDomain() { + bool Presenter::hasTimeDomain(VkTimeDomainKHR timeDomain) const { if (!m_timingDomains) return false; for (const auto& domain : m_timingDomains->domains) { - if (domain.timeDomain == VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR) + if (domain.timeDomain == timeDomain) return true; } diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 64672bad..1b8c8c47 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -81,7 +81,6 @@ namespace dxvk { * * Used for timestamp calibration. Reference times are given in units * of nanoseconds, except for QPC which is given in raw QPC ticks. - * QPC will be unavailable in dxvk-native environments. */ struct PresenterTimeDomain { VkTimeDomainKHR timeDomain = VK_TIME_DOMAIN_MAX_ENUM_KHR; @@ -128,7 +127,7 @@ namespace dxvk { uint64_t referenceTime = 0u; uint64_t referenceFrameId = 0u; uint64_t lastFrameTimeLocal = 0u; - uint64_t lastFrameTimeQpc = 0u; + uint64_t lastFrameTimeHost = 0u; uint64_t lastFrameId = 0u; uint64_t firstValidFrameId = 0u; }; @@ -497,7 +496,8 @@ namespace dxvk { bool waitUntilFrameTargetTime( const PresenterFrame& frame); - bool hasQpcDomain(); + bool hasTimeDomain( + VkTimeDomainKHR timeDomain) const; uint64_t translateTimestamp( VkTimeDomainKHR srcTimeDomain, From 577a3efa8ed59cb64aba128ec2b35bc270f10c86 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 1 Nov 2026 23:31:39 +0100 Subject: [PATCH 114/135] [dxvk] Clarify that present time feedback is in host domain --- src/d3d11/d3d11_swapchain.cpp | 2 +- src/dxvk/dxvk_presenter.cpp | 61 ++++++++++++++++------------------- src/dxvk/dxvk_presenter.h | 27 ++++++++-------- 3 files changed, 42 insertions(+), 48 deletions(-) diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 0b2ea96c..cbacbcd0 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -354,7 +354,7 @@ namespace dxvk { feedback = m_presenter->queryPresentTiming(); pFrameStatistics->PresentCount = std::max(feedback.frameId, DXGI_MAX_SWAP_CHAIN_BUFFERS) - DXGI_MAX_SWAP_CHAIN_BUFFERS; - pFrameStatistics->PresentQPCTime = feedback.presentTime; + pFrameStatistics->PresentQPCTime = feedback.presentTimeHost; } diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 732b9bf0..d824a32e 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -236,10 +236,10 @@ namespace dxvk { } if (m_timingMode.frameIntervalNs - && m_timingMode.referenceFrameId >= m_timingMode.firstValidFrameId - && m_timingMode.referenceFrameId + 2u * FrameQueueSize >= frameId) { - frameTargetTime = m_timingMode.referenceTime + - (frameId - m_timingMode.referenceFrameId) * m_timingMode.frameIntervalNs; + && m_timingMode.reference.frameId >= m_timingMode.firstValidFrameId + && m_timingMode.reference.frameId + 2u * FrameQueueSize >= frameId) { + frameTargetTime = m_timingMode.reference.presentTimeLocal + + (frameId - m_timingMode.reference.frameId) * m_timingMode.frameIntervalNs; } if (m_timingMode.relativeTiming) { @@ -1465,8 +1465,7 @@ namespace dxvk { // Reset reference time and frame ID for absolute timing // so that we don't end up submitting bogus timestamps. - m_timingMode.referenceTime = 0u; - m_timingMode.referenceFrameId = 0u; + m_timingMode.reference = PresenterTimingFeedback(); m_timingMode.firstValidFrameId = 0u; // Can't meaningfully do timing if we don't know about display timing @@ -1577,10 +1576,11 @@ namespace dxvk { PresenterTimingFeedback feedback = {}; feedback.frameId = frameId; - feedback.presentTime = frameId ? dxvk::high_resolution_clock::get_counter() : 0u; + feedback.presentTimeLocal = 0u; + feedback.presentTimeHost = frameId ? dxvk::high_resolution_clock::get_counter() : 0u; if (!m_timingMode.presentStage) - return commitTimingFeedback(feedback); + return commitTimingFeedback(feedback, true); // Query reports in-order so that we can somewhat robustly // track how many frames are still in the report queue @@ -1605,7 +1605,7 @@ namespace dxvk { if (status) { Logger::warn(str::format("Presenter: Failed to query past present timings: ", status)); - return commitTimingFeedback(feedback); + return commitTimingFeedback(feedback, true); } // Update display timing and time domains as necessary @@ -1621,7 +1621,7 @@ namespace dxvk { updateTimingDomains(); if (!recalibrateTimeDomains()) - return commitTimingFeedback(feedback); + return commitTimingFeedback(feedback, true); } if (updateMode) @@ -1661,53 +1661,48 @@ namespace dxvk { continue; } - uint64_t reportTimeLocal = translateTimestamp( - report.timeDomain, report.timeDomainId, time.time, - VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, frameEntry->timingDomainId); + uint64_t reportTimeLocal = translateTimestamp(report.timeDomain, report.timeDomainId, + time.time, VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, frameEntry->timingDomainId); uint64_t reportTimeHost = 0u; if (hasTimeDomain(dxvk::high_resolution_clock::get_vk_time_domain())) { - reportTimeHost = translateTimestamp( - report.timeDomain, report.timeDomainId, time.time, - dxvk::high_resolution_clock::get_vk_time_domain(), 0u); + reportTimeHost = translateTimestamp(report.timeDomain, report.timeDomainId, + time.time, dxvk::high_resolution_clock::get_vk_time_domain(), 0u); } if (reportTimeLocal) { - m_timingMode.lastFrameId = report.presentId; - m_timingMode.lastFrameTimeLocal = reportTimeLocal; - m_timingMode.lastFrameTimeHost = reportTimeHost; + m_timingMode.lastReport.frameId = report.presentId; + m_timingMode.lastReport.presentTimeLocal = reportTimeLocal; + m_timingMode.lastReport.presentTimeHost = reportTimeHost; hasMissedDeadline = reportTimeLocal > frameEntry->targetDeadline; } } - if (!m_timingMode.referenceFrameId || hasMissedDeadline) { - m_timingMode.referenceFrameId = m_timingMode.lastFrameId; - m_timingMode.referenceTime = m_timingMode.lastFrameTimeLocal; - } - // Update feedback from timing reports only if two conditions are true: // - The current frame was submitted with a timing query. - // - We successfully mapped the report timestamp back to QPC. - // TODO figure out the correct time domain for dxvk-native to use. - if (m_timingMode.lastFrameTimeHost && (!frameId || queryStages)) { - feedback.frameId = m_timingMode.lastFrameId; - feedback.presentTime = m_timingMode.lastFrameTimeHost; - } + // - We successfully mapped the report timestamp back to the host domain. + if (m_timingMode.lastReport.presentTimeHost && (!frameId || queryStages)) + feedback = m_timingMode.lastReport; - return commitTimingFeedback(feedback); + return commitTimingFeedback(feedback, hasMissedDeadline); } PresenterTimingFeedback Presenter::commitTimingFeedback( - const PresenterTimingFeedback& feedback) { + const PresenterTimingFeedback& feedback, + bool updateReference) { // Only commit present timing if the new frame ID is larger than the // previous frame ID, in order to avoid reverting the counter in case // the application spins on present statistics. - if (feedback.frameId > m_timingFeedback.frameId) + if (feedback.frameId > m_timingFeedback.frameId) { m_timingFeedback = feedback; + if (!m_timingMode.reference.frameId || updateReference) + m_timingMode.reference = feedback; + } + return m_timingFeedback; } diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 1b8c8c47..d7a78762 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -53,6 +53,15 @@ namespace dxvk { VkBool32 fenceSignaled = VK_FALSE; }; + /** + * \brief Timing info for the last presented frame + */ + struct PresenterTimingFeedback { + uint64_t frameId = 0u; + uint64_t presentTimeLocal = 0u; + uint64_t presentTimeHost = 0u; + }; + /** * \brief Queued frame */ @@ -124,22 +133,11 @@ namespace dxvk { bool absoluteTiming = false; uint64_t timeDomainId = 0u; uint64_t frameIntervalNs = 0u; - uint64_t referenceTime = 0u; - uint64_t referenceFrameId = 0u; - uint64_t lastFrameTimeLocal = 0u; - uint64_t lastFrameTimeHost = 0u; - uint64_t lastFrameId = 0u; + PresenterTimingFeedback reference = {}; + PresenterTimingFeedback lastReport = {}; uint64_t firstValidFrameId = 0u; }; - /** - * \brief Timing info for the last presented frame - */ - struct PresenterTimingFeedback { - uint64_t frameId = 0u; - uint64_t presentTime = 0u; - }; - /** * \brief Vulkan presenter * @@ -491,7 +489,8 @@ namespace dxvk { VkPresentStageFlagsEXT queryStages); PresenterTimingFeedback commitTimingFeedback( - const PresenterTimingFeedback& feedback); + const PresenterTimingFeedback& feedback, + bool updateReference); bool waitUntilFrameTargetTime( const PresenterFrame& frame); From 8ad9b14f84853818ba4ff4722562498d64ab57f1 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 00:56:06 +0100 Subject: [PATCH 115/135] [util] Add helper to convert durations to raw clock ticks Relevant for QPC math. --- src/util/util_time.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/util/util_time.h b/src/util/util_time.h index f1d2e796..7d92e8b8 100644 --- a/src/util/util_time.h +++ b/src/util/util_time.h @@ -50,6 +50,18 @@ namespace dxvk { return count.QuadPart; } + template + static int64_t get_ticks(T dur) { + static const int64_t freq = get_frequency(); + + const int64_t ns = std::chrono::duration_cast(dur).count(); + + const int64_t whole = (ns / period::den) * freq; + const int64_t part = (ns % period::den) * freq / period::den; + + return whole + part; + } + static VkTimeDomainKHR get_vk_time_domain() { return VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_EXT; } @@ -81,6 +93,11 @@ namespace dxvk { return int64_t(tp.tv_sec) * 1'000'000'000 + int64_t(tp.tv_nsec); } + template + static int64_t get_ticks(T dur) { + return std::chrono::duration_cast(dur).count(); + } + static VkTimeDomainKHR get_vk_time_domain() { return VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT; } @@ -99,6 +116,11 @@ namespace dxvk { return now().time_since_epoch().count(); } + template + static int64_t get_ticks(T dur) { + return std::chrono::duration_cast(dur).count(); + } + static VkTimeDomainKHR get_vk_time_domain() { return VK_TIME_DOMAIN_MAX_ENUM_KHR; } From 835bce7380e0d9029ad94a39356bdd591bb22647 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 00:57:07 +0100 Subject: [PATCH 116/135] [dxvk] Factor out function to query frame entry --- src/dxvk/dxvk_presenter.cpp | 17 +++++++++++------ src/dxvk/dxvk_presenter.h | 3 +++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index d824a32e..371f0090 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -1649,12 +1649,7 @@ namespace dxvk { // Find queued frame entry so we can properly correlate the report // with expected timings - const PresenterFrame* frameEntry = nullptr; - - for (const auto& frame : m_frameQueue) { - if (frame.frameId == report.presentId) - frameEntry = &frame; - } + const PresenterFrame* frameEntry = findFrameEntry(report.presentId); if (!frameEntry || frameEntry->timingDomainId != m_timingMode.timeDomainId) { Logger::warn(str::format("Presenter: Skipping report for frame ", report.presentId)); @@ -2096,6 +2091,16 @@ namespace dxvk { } + const PresenterFrame* Presenter::findFrameEntry(uint64_t frameId) const { + for (const auto& frame : m_frameQueue) { + if (frame.frameId == frameId) + return &frame; + } + + return nullptr; + } + + void Presenter::runFrameThread() { env::setThreadName("dxvk-frame"); diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index d7a78762..a8cf843b 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -525,6 +525,9 @@ namespace dxvk { void pushFrame(const PresenterFrame& frame); + const PresenterFrame* findFrameEntry( + uint64_t frameId) const; + void runFrameThread(); static VkResult softError( From e58459d8b123509534a2b6b77a4aa04daec825d0 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 00:57:57 +0100 Subject: [PATCH 117/135] [dxvk] Always wait for frame target time in host domain --- src/dxvk/dxvk_presenter.cpp | 144 ++++++++++++++++++------------------ src/dxvk/dxvk_presenter.h | 13 ++-- 2 files changed, 79 insertions(+), 78 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 371f0090..e11813e0 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -215,49 +215,38 @@ namespace dxvk { bool isFifoMode = m_presentMode == VK_PRESENT_MODE_FIFO_KHR || m_presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; - uint64_t frameTargetTime = 0u; + auto frameTargetTime = PresenterTimingFeedback(); + auto frameIntervalTicks = uint64_t(0); VkPresentTimingInfoEXT timingInfo = { VK_STRUCTURE_TYPE_PRESENT_TIMING_INFO_EXT }; - timingInfo.timeDomainId = m_timingMode.timeDomainId; - if (m_timingMode.presentStage && isFifoMode) { - std::lock_guard lock(m_timingMutex); - timingInfo.presentStageQueries = m_timingMode.presentStage; - - // If this is the first timed frame in a sequence, ignore older reports - if (!m_timingMode.firstValidFrameId) - m_timingMode.firstValidFrameId = frameId; - - // Try to avoid overflowing the driver-internal report queue. We can still - // work with the existing reports for a short period of time, though. - if (m_reportQueueAddId >= m_reportQueueTakeId + FrameQueueSize) { - Logger::warn("Presenter: Report queue overflow"); - timingInfo.presentStageQueries = 0u; - } - - if (m_timingMode.frameIntervalNs - && m_timingMode.reference.frameId >= m_timingMode.firstValidFrameId - && m_timingMode.reference.frameId + 2u * FrameQueueSize >= frameId) { - frameTargetTime = m_timingMode.reference.presentTimeLocal + - (frameId - m_timingMode.reference.frameId) * m_timingMode.frameIntervalNs; - } + { std::lock_guard lock(m_timingMutex); + frameTargetTime = computeFrameTargetTime(frameId); + frameIntervalTicks = dxvk::high_resolution_clock::get_ticks(std::chrono::nanoseconds(m_timingMode.frameIntervalNs)); + + if (isFifoMode && m_timingMode.presentStage) { + timingInfo.timeDomainId = m_timingMode.timeDomainId; + timingInfo.presentStageQueries = m_timingMode.presentStage; + + // Try to avoid overflowing the driver-internal report queue. We can still + // work with the existing reports for a short period of time, though. + if (m_reportQueueAddId >= m_reportQueueTakeId + FrameQueueSize) { + Logger::warn("Presenter: Report queue overflow"); + timingInfo.presentStageQueries = 0u; + } - if (m_timingMode.relativeTiming) { - timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_RELATIVE_TIME_BIT_EXT; - timingInfo.targetTime = m_timingMode.frameIntervalNs; - timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; - } else if (m_timingMode.absoluteTiming && frameTargetTime) { - timingInfo.targetTime = frameTargetTime; - timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; + if (m_timingMode.relativeTiming) { + timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_RELATIVE_TIME_BIT_EXT; + timingInfo.targetTime = m_timingMode.frameIntervalNs; + timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; + } else if (m_timingMode.absoluteTiming && frameTargetTime.presentTimeLocal) { + timingInfo.targetTime = frameTargetTime.presentTimeLocal; + timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; - if (m_timingDisplayInfo && !m_timingDisplayInfo->isVariableRefresh) - timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_NEAREST_REFRESH_CYCLE_BIT_EXT; + if (m_timingDisplayInfo && !m_timingDisplayInfo->isVariableRefresh) + timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_NEAREST_REFRESH_CYCLE_BIT_EXT; + } } - } else { - // When presenting with a non-FIFO mode, ensure that we don't compute - // frame target times based on timings from previous FIFO usage. - std::lock_guard lock(m_timingMutex); - m_timingMode.firstValidFrameId = 0; } VkPresentTimingsInfoEXT timingsInfo = { VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT }; @@ -277,7 +266,7 @@ namespace dxvk { else presentId.pNext = const_cast(std::exchange(info.pNext, &presentId)); - if (timingInfo.presentStageQueries) + if (timingInfo.presentStageQueries || timingInfo.targetTimeDomainPresentStage) timingsInfo.pNext = std::exchange(info.pNext, &timingsInfo); } @@ -324,8 +313,9 @@ namespace dxvk { frame.tracker = tracker; frame.mode = m_presentMode; frame.result = status; - frame.targetTime = frameTargetTime; - frame.targetDeadline = frameTargetTime ? frameTargetTime + m_timingMode.frameIntervalNs : 0u; + frame.targetTimeHost = frameTargetTime.presentTimeHost; + frame.targetStage = timingInfo.targetTimeDomainPresentStage; + frame.deadlineHost = frameTargetTime.presentTimeHost ? frameTargetTime.presentTimeHost + frameIntervalTicks : 0u; frame.timingDomainId = timingInfo.timeDomainId; frame.stageQueries = timingInfo.presentStageQueries; @@ -557,11 +547,16 @@ namespace dxvk { void Presenter::setFrameRateLimit(double frameRate, uint32_t maxLatency) { - m_fpsLimiter.setTargetFrameRate(frameRate, maxLatency); - std::lock_guard lock(m_timingMutex); if (m_frameRateLimit != frameRate) { + uint64_t frameRateInt = uint64_t(std::abs(frameRate * 10.0)); + + Logger::info(frameRate == 0.0f + ? str::format("Presenter: Disabling frame rate limiter.") + : str::format("Presenter: Enabling frame rate limiter (", + frameRateInt / 10u, ".", frameRateInt % 10u, " FPS)")); + m_frameRateLimit = frameRate; updateTimingMode(); } @@ -1010,10 +1005,9 @@ namespace dxvk { updateTimingDomains(); recalibrateTimeDomains(); - - updateTimingMode(); } + updateTimingMode(); return VK_SUCCESS; } @@ -1466,17 +1460,16 @@ namespace dxvk { // Reset reference time and frame ID for absolute timing // so that we don't end up submitting bogus timestamps. m_timingMode.reference = PresenterTimingFeedback(); - m_timingMode.firstValidFrameId = 0u; - - // Can't meaningfully do timing if we don't know about display timing - if (!m_timingDomains || !m_timingDisplayInfo || m_calibrationFailed) - return; // Compute target frame interval from frame rate limit m_timingMode.frameIntervalNs = m_frameRateLimit != 0.0 ? uint64_t(1000000000.0 / std::abs(m_frameRateLimit)) : uint64_t(0u); + // Can't meaningfully do timing if we don't know about display timing + if (!m_timingDomains || !m_timingDisplayInfo || m_calibrationFailed) + return; + // Don't enable timing if we are trying to limit to less than half a // frame per second below maximum refresh. This catches small deltas // between the refresh rates reported by win32 and the Vulkan driver @@ -1671,7 +1664,7 @@ namespace dxvk { m_timingMode.lastReport.presentTimeLocal = reportTimeLocal; m_timingMode.lastReport.presentTimeHost = reportTimeHost; - hasMissedDeadline = reportTimeLocal > frameEntry->targetDeadline; + hasMissedDeadline = reportTimeHost > frameEntry->deadlineHost; } } @@ -1681,6 +1674,11 @@ namespace dxvk { if (m_timingMode.lastReport.presentTimeHost && (!frameId || queryStages)) feedback = m_timingMode.lastReport; + if (!hasMissedDeadline) { + const PresenterFrame* frameEntry = findFrameEntry(feedback.frameId); + hasMissedDeadline = frameEntry && feedback.presentTimeHost > frameEntry->deadlineHost; + } + return commitTimingFeedback(feedback, hasMissedDeadline); } @@ -1702,26 +1700,30 @@ namespace dxvk { } - bool Presenter::waitUntilFrameTargetTime( - const PresenterFrame& frame) { - if (!frame.targetTime) - return false; + PresenterTimingFeedback Presenter::computeFrameTargetTime( + uint64_t frameId) const { + if (!m_timingMode.frameIntervalNs || !m_timingMode.reference.frameId + || (m_timingMode.reference.frameId + 2u * FrameQueueSize < frameId)) + return PresenterTimingFeedback(); - uint64_t qpcTargetTime = 0u; + uint64_t deltaNs = (frameId - m_timingMode.reference.frameId) * m_timingMode.frameIntervalNs; + uint64_t deltaTicks = dxvk::high_resolution_clock::get_ticks(std::chrono::nanoseconds(deltaNs)); + + PresenterTimingFeedback result; + result.frameId = frameId; + result.presentTimeLocal = m_timingMode.reference.presentTimeLocal + deltaNs; + result.presentTimeHost = m_timingMode.reference.presentTimeHost + deltaTicks; + return result; + } - { std::lock_guard lock(m_timingMutex); - if (hasTimeDomain(dxvk::high_resolution_clock::get_vk_time_domain())) { - qpcTargetTime = translateTimestamp( - VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, frame.timingDomainId, frame.targetTime, - dxvk::high_resolution_clock::get_vk_time_domain(), 0u); - } - } - if (!qpcTargetTime) + bool Presenter::waitUntilFrameTargetTime( + const PresenterFrame& frame) { + if (!frame.targetTimeHost) return false; auto now = dxvk::high_resolution_clock::now(); - auto dxvkTargetTime = dxvk::high_resolution_clock::get_time_from_counter(qpcTargetTime); + auto dxvkTargetTime = dxvk::high_resolution_clock::get_time_from_counter(frame.targetTimeHost); if (now >= dxvkTargetTime) return true; @@ -2128,13 +2130,13 @@ namespace dxvk { // intentionally does not line up with vblank in fixed refresh rate // scenarios and is done so that the application's render thread can // see consistent frame pacing. - bool waited = frame.targetTime && waitUntilFrameTargetTime(frame); + bool waited = frame.targetStage && waitUntilFrameTargetTime(frame); - // Legacy path, fall back to present_wait if we don't have timing. + // If we haven't waited yet, use present_wait to bool isFifoMode = frame.mode == VK_PRESENT_MODE_FIFO_KHR || frame.mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; - if (!waited && isFifoMode && frame.result >= 0 && m_hasPresentWait) { + if (isFifoMode && frame.result >= 0 && m_hasPresentWait) { VkResult vr; if (m_device->features().khrPresentWait2.presentWait2) { @@ -2163,11 +2165,11 @@ namespace dxvk { // but if timing is disabled then we should base feedback on present_wait. updatePresentTimingFeedback(frame.frameId, frame.stageQueries); - // Apply FPS limiter here to align it as closely with scanout as we can, - // and delay signaling the frame latency event to emulate behaviour of a - // low refresh rate display as closely as we can. + // If we hit the present_wait path, issue another wait to deal with lower + // than native frame rate limits. We should do so *after* committing present + // timings so that reported timings are reasonably close to actual vblank. if (!waited) - m_fpsLimiter.delay(); + waitUntilFrameTargetTime(frame); // Wake up any thread that may be waiting for the queue to become empty bool canSignal = false; diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index a8cf843b..ac06b9f3 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -8,7 +8,6 @@ #include "../util/log/log.h" #include "../util/util_error.h" -#include "../util/util_fps_limiter.h" #include "../util/util_math.h" #include "../util/util_string.h" @@ -70,8 +69,9 @@ namespace dxvk { Rc tracker = nullptr; VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR; VkResult result = VK_NOT_READY; - uint64_t targetTime = 0u; - uint64_t targetDeadline = 0u; + uint64_t targetTimeHost = 0u; + VkPresentStageFlagsEXT targetStage = 0u; + uint64_t deadlineHost = 0u; uint64_t timingDomainId = 0u; VkPresentStageFlagsEXT stageQueries = 0u; }; @@ -135,7 +135,6 @@ namespace dxvk { uint64_t frameIntervalNs = 0u; PresenterTimingFeedback reference = {}; PresenterTimingFeedback lastReport = {}; - uint64_t firstValidFrameId = 0u; }; /** @@ -424,9 +423,6 @@ namespace dxvk { uint64_t m_reportQueueAddId = 0u; uint64_t m_reportQueueTakeId = 0u; - alignas(CACHE_LINE_SIZE) - FpsLimiter m_fpsLimiter; - bool m_hasGamescopeFenceSignalBug = false; bool m_hasNvidiaFenceSignalBug = false; @@ -492,6 +488,9 @@ namespace dxvk { const PresenterTimingFeedback& feedback, bool updateReference); + PresenterTimingFeedback computeFrameTargetTime( + uint64_t frameId) const; + bool waitUntilFrameTargetTime( const PresenterFrame& frame); From e71623591bdf00d97ad5a1b8a4a60251a96a5497 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 01:01:39 +0100 Subject: [PATCH 118/135] [dxvk] Remove legacy frame rate limiter --- src/dxvk/dxvk_latency_builtin.cpp | 1 - src/util/meson.build | 1 - src/util/util_fps_limiter.cpp | 120 ------------------------------ src/util/util_fps_limiter.h | 62 --------------- 4 files changed, 184 deletions(-) delete mode 100644 src/util/util_fps_limiter.cpp delete mode 100644 src/util/util_fps_limiter.h diff --git a/src/dxvk/dxvk_latency_builtin.cpp b/src/dxvk/dxvk_latency_builtin.cpp index b399f99a..6515f762 100644 --- a/src/dxvk/dxvk_latency_builtin.cpp +++ b/src/dxvk/dxvk_latency_builtin.cpp @@ -4,7 +4,6 @@ #include "../util/log/log.h" -#include "../util/util_fps_limiter.h" #include "../util/util_string.h" namespace dxvk { diff --git a/src/util/meson.build b/src/util/meson.build index 53b04319..befb20bb 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -1,7 +1,6 @@ util_src = files([ 'util_env.cpp', 'util_string.cpp', - 'util_fps_limiter.cpp', 'util_file.cpp', 'util_flush.cpp', 'util_gdi.cpp', diff --git a/src/util/util_fps_limiter.cpp b/src/util/util_fps_limiter.cpp deleted file mode 100644 index 5dd008d1..00000000 --- a/src/util/util_fps_limiter.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include - -#include "thread.h" -#include "util_env.h" -#include "util_fps_limiter.h" -#include "util_sleep.h" -#include "util_string.h" - -#include "./log/log.h" - -using namespace std::chrono_literals; - -namespace dxvk { - - FpsLimiter::FpsLimiter() { - - } - - - FpsLimiter::~FpsLimiter() { - - } - - - void FpsLimiter::setTargetFrameRate(double frameRate, uint32_t maxLatency) { - std::lock_guard lock(m_mutex); - - TimerDuration interval = frameRate != 0.0 - ? TimerDuration(int64_t(double(TimerDuration::period::den) / frameRate)) - : TimerDuration::zero(); - - if (m_targetInterval != interval) { - m_targetInterval = interval; - - m_heuristicFrameTime = TimePoint(); - m_heuristicFrameCount = 0; - m_heuristicEnable = false; - - m_maxLatency = maxLatency; - } - } - - - void FpsLimiter::delay() { - std::unique_lock lock(m_mutex); - auto interval = m_targetInterval; - auto latency = m_maxLatency; - - if (interval == TimerDuration::zero()) { - m_nextFrame = TimePoint(); - return; - } - - auto t1 = dxvk::high_resolution_clock::now(); - - if (interval < TimerDuration::zero()) { - interval = -interval; - - if (!testRefreshHeuristic(interval, t1, latency)) - return; - } - - // Subsequent code must not access any class members - // that can be written by setTargetFrameRate - lock.unlock(); - - if (t1 < m_nextFrame) - Sleep::sleepUntil(t1, m_nextFrame); - - m_nextFrame = (t1 < m_nextFrame + interval) - ? m_nextFrame + interval - : t1 + interval; - } - - - bool FpsLimiter::testRefreshHeuristic(TimerDuration interval, TimePoint now, uint32_t maxLatency) { - if (m_heuristicEnable) - return true; - - constexpr static uint32_t MinWindowSize = 8; - constexpr static uint32_t MaxWindowSize = 128; - - if (m_heuristicFrameCount >= MinWindowSize) { - TimerDuration windowTotalTime = now - m_heuristicFrameTime; - TimerDuration windowExpectedTime = m_heuristicFrameCount * interval; - - uint32_t minFrameCount = m_heuristicFrameCount - 1; - uint32_t maxFrameCount = m_heuristicFrameCount + maxLatency; - - // Enable frame rate limiter if frames have been delivered faster than - // the desired refresh rate even accounting for swap chain buffering. - if ((maxFrameCount * windowTotalTime) < (m_heuristicFrameCount * windowExpectedTime)) { - double got = (double(m_heuristicFrameCount) * double(TimerDuration::period::den)) - / (double(windowTotalTime.count()) * double(TimerDuration::period::num)); - double refresh = double(TimerDuration::period::den) / (double(TimerDuration::period::num) * double(interval.count())); - - Logger::info(str::format("Detected frame rate (~", uint32_t(got), ") higher than selected refresh rate of ~", - uint32_t(refresh), " Hz.\n", "Engaging frame rate limiter.")); - - m_heuristicEnable = true; - return true; - } - - // Reset heuristics if frames have been delivered slower than the refresh rate. - if (((minFrameCount * windowTotalTime) > (m_heuristicFrameCount * windowExpectedTime)) - || (m_heuristicFrameCount >= MaxWindowSize)) { - m_heuristicFrameCount = 1; - m_heuristicFrameTime = now; - return false; - } - } - - if (!m_heuristicFrameCount) - m_heuristicFrameTime = now; - - m_heuristicFrameCount += 1; - return false; - } - -} diff --git a/src/util/util_fps_limiter.h b/src/util/util_fps_limiter.h deleted file mode 100644 index 1f948183..00000000 --- a/src/util/util_fps_limiter.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include -#include - -#include "thread.h" -#include "util_time.h" - -namespace dxvk { - - /** - * \brief Frame rate limiter - * - * Provides functionality to stall an application - * thread in order to maintain a given frame rate. - */ - class FpsLimiter { - - public: - - /** - * \brief Creates frame rate limiter - */ - FpsLimiter(); - - ~FpsLimiter(); - - /** - * \brief Sets target frame rate - * \param [in] frameRate Target frame rate - */ - void setTargetFrameRate(double frameRate, uint32_t maxLatency); - - /** - * \brief Stalls calling thread as necessary - * - * Blocks the calling thread if the limiter is enabled - * and the time since the last call to \ref delay is - * shorter than the target interval. - */ - void delay(); - - private: - - using TimePoint = dxvk::high_resolution_clock::time_point; - using TimerDuration = std::chrono::nanoseconds; - - dxvk::mutex m_mutex; - - TimerDuration m_targetInterval = TimerDuration::zero(); - TimePoint m_nextFrame = TimePoint(); - uint32_t m_maxLatency = 0; - - uint32_t m_heuristicFrameCount = 0; - TimePoint m_heuristicFrameTime = TimePoint(); - bool m_heuristicEnable = false; - - bool testRefreshHeuristic(TimerDuration interval, TimePoint now, uint32_t maxLatency); - - }; - -} From 24ab05c2022cc5e89c1dac5d0c6186a1c458cfcd Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 02:15:11 +0100 Subject: [PATCH 119/135] [dxvk] Remove broken logic around old reference frame data --- src/dxvk/dxvk_presenter.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index e11813e0..4b19e6d0 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -1702,8 +1702,7 @@ namespace dxvk { PresenterTimingFeedback Presenter::computeFrameTargetTime( uint64_t frameId) const { - if (!m_timingMode.frameIntervalNs || !m_timingMode.reference.frameId - || (m_timingMode.reference.frameId + 2u * FrameQueueSize < frameId)) + if (!m_timingMode.frameIntervalNs || !m_timingMode.reference.frameId) return PresenterTimingFeedback(); uint64_t deltaNs = (frameId - m_timingMode.reference.frameId) * m_timingMode.frameIntervalNs; From 93f24465eec1ef69dcadac30755326ec0059f8f5 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 1 Nov 2026 16:11:28 +0100 Subject: [PATCH 120/135] [dxvk] Factor HUD instance out of swapchain blitter --- src/d3d11/d3d11_swapchain.cpp | 19 +++++++++++-------- src/d3d11/d3d11_swapchain.h | 3 ++- src/d3d9/d3d9_swapchain.cpp | 20 ++++++++++++-------- src/d3d9/d3d9_swapchain.h | 2 ++ src/dxvk/dxvk_swapchain_blitter.cpp | 26 +++++++++++--------------- src/dxvk/dxvk_swapchain_blitter.h | 6 +++--- 6 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index cbacbcd0..83197d37 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -464,7 +464,7 @@ namespace dxvk { // Redraw everything if the HUD is active since we don't // keep track of the exact screen areas there. Likewise, // nope out if there is any scaling going on. - if (!m_hasHud && dstRect.extent != srcRect.extent) { + if ((!m_hud || m_hud->empty()) && dstRect.extent != srcRect.extent) { VkRect2D bounds = dstRect; bounds.offset.x -= srcRect.offset.x; bounds.offset.y -= srcRect.offset.y; @@ -495,6 +495,7 @@ namespace dxvk { immediateContext->EmitCs([ cDevice = m_device, cBlitter = m_blitter, + cHud = m_hud, cBackBuffer = backBuffer->createView(viewInfo), cSwapImage = GetBackBufferView(), cSync = sync, @@ -507,6 +508,9 @@ namespace dxvk { cSrcRect = srcRect, cDstRect = dstRect ] (DxvkContext* ctx) { + if (cHud) + cHud->update(); + // Update back buffer color space as necessary if (cSwapImage->image()->info().colorSpace != cColorSpace) { DxvkImageUsageInfo usage = { }; @@ -519,7 +523,7 @@ namespace dxvk { // swap chain and render the HUD if we have one. auto contextObjects = ctx->beginExternalRendering(); - cBlitter->present(contextObjects, cClearColor, + cBlitter->present(contextObjects, cHud, cClearColor, cBackBuffer, cDstRect, cSwapImage, cSrcRect); // Submit current command list and present @@ -677,17 +681,16 @@ namespace dxvk { void D3D11SwapChain::CreateBlitter() { - Rc hud = hud::Hud::createHud(m_device); + m_hud = hud::Hud::createHud(m_device); - if (hud) { - hud->addItem("api", 1, GetApiName()); + if (m_hud) { + m_hud->addItem("api", 1, GetApiName()); if (m_latency) - m_latencyHud = hud->addItem("latency", 4); + m_latencyHud = m_hud->addItem("latency", 4); } - m_hasHud = hud && !hud->empty(); - m_blitter = new DxvkSwapchainBlitter(m_device, std::move(hud)); + m_blitter = new DxvkSwapchainBlitter(m_device); } diff --git a/src/d3d11/d3d11_swapchain.h b/src/d3d11/d3d11_swapchain.h index 13bc196e..8ed6dae5 100644 --- a/src/d3d11/d3d11_swapchain.h +++ b/src/d3d11/d3d11_swapchain.h @@ -122,6 +122,8 @@ namespace dxvk { Rc m_device; Rc m_presenter; + Rc m_hud; + Rc m_blitter; Rc m_latency; @@ -144,7 +146,6 @@ namespace dxvk { double m_targetFrameRate = 0.0; - bool m_hasHud = false; Rc m_latencyHud; Rc GetBackBufferView(); diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 4ec93133..bc291680 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -893,6 +893,7 @@ namespace dxvk { cDevice = m_device, cPresenter = m_wctx->presenter, cBlitter = m_blitter, + cHud = m_hud, cColorSpace = m_colorspace, cSrcView = swapImageView, cSrcRect = srcRect, @@ -902,6 +903,9 @@ namespace dxvk { cFrameId = m_wctx->frameId, cLatency = m_latencyTracker ] (DxvkContext* ctx) { + if (cHud) + cHud->update(); + // Update back buffer color space as necessary if (cSrcView->image()->info().colorSpace != cColorSpace) { DxvkImageUsageInfo usage = { }; @@ -913,7 +917,7 @@ namespace dxvk { // Blit back buffer onto Vulkan swap chain auto contextObjects = ctx->beginExternalRendering(); - cBlitter->present(contextObjects, VkClearColorValue(), + cBlitter->present(contextObjects, cHud, VkClearColorValue(), cDstView, cDstRect, cSrcView, cSrcRect); // Submit command list and present @@ -1090,22 +1094,22 @@ namespace dxvk { void D3D9SwapChainEx::CreateBlitter() { - Rc hud = hud::Hud::createHud(m_device); + m_hud = hud::Hud::createHud(m_device); - if (hud) { - m_apiHud = hud->addItem("api", 1, GetApiName()); + if (m_hud) { + m_apiHud = m_hud->addItem("api", 1, GetApiName()); if (m_latencyTracking) - m_latencyHud = hud->addItem("latency", 4); + m_latencyHud = m_hud->addItem("latency", 4); - hud->addItem("swvp", -1, m_parent); + m_hud->addItem("swvp", -1, m_parent); #ifdef DXVK_USE_UNMAPPABLE_MEMORY - hud->addItem("memory", -1, m_parent); + m_hud->addItem("memory", -1, m_parent); #endif } - m_blitter = new DxvkSwapchainBlitter(m_device, std::move(hud)); + m_blitter = new DxvkSwapchainBlitter(m_device); } diff --git a/src/d3d9/d3d9_swapchain.h b/src/d3d9/d3d9_swapchain.h index 811124d4..643eac04 100644 --- a/src/d3d9/d3d9_swapchain.h +++ b/src/d3d9/d3d9_swapchain.h @@ -149,6 +149,8 @@ namespace dxvk { Rc m_device; Rc m_blitter; + Rc m_hud; + std::unordered_map< HWND, D3D9WindowContext> m_presenters; diff --git a/src/dxvk/dxvk_swapchain_blitter.cpp b/src/dxvk/dxvk_swapchain_blitter.cpp index 1bcdafad..9de90037 100644 --- a/src/dxvk/dxvk_swapchain_blitter.cpp +++ b/src/dxvk/dxvk_swapchain_blitter.cpp @@ -10,10 +10,8 @@ namespace dxvk { - DxvkSwapchainBlitter::DxvkSwapchainBlitter( - const Rc& device, - const Rc& hud) - : m_device(device), m_hud(hud), + DxvkSwapchainBlitter::DxvkSwapchainBlitter(const Rc& device) + : m_device(device), m_blitLayout(createBlitPipelineLayout()), m_cursorLayout(createCursorPipelineLayout()) { this->createSampler(); @@ -33,6 +31,7 @@ namespace dxvk { void DxvkSwapchainBlitter::present( const Rc&ctx, + const Rc& hud, VkClearColorValue clearColor, const Rc& dstView, VkRect2D dstRect, @@ -40,10 +39,6 @@ namespace dxvk { VkRect2D srcRect) { std::unique_lock lock(m_mutex); - // Update HUD, if we have one - if (m_hud) - m_hud->update(); - // Fix up default present areas if necessary if (!dstRect.extent.width || !dstRect.extent.height) { dstRect.offset = { 0, 0 }; @@ -68,8 +63,8 @@ namespace dxvk { // If we can't do proper blending, render the HUD into a separate image bool composite = needsComposition(dstView); - if (m_hud && composite) - renderHudImage(ctx, dstView->mipLevelExtent(0)); + if (hud && composite) + renderHudImage(ctx, hud, dstView->mipLevelExtent(0)); else destroyHudImage(); @@ -117,8 +112,8 @@ namespace dxvk { srcView, srcRect, composite); if (!composite) { - if (m_hud) - m_hud->render(ctx, dstView); + if (hud) + hud->render(ctx, dstView); if (m_cursorView) renderCursor(ctx, dstView); @@ -380,8 +375,9 @@ namespace dxvk { void DxvkSwapchainBlitter::renderHudImage( const Rc& ctx, + const Rc& hud, VkExtent3D extent) { - if (m_hud->empty()) + if (hud->empty()) return; if (!m_hudImage || m_hudImage->info().extent != extent) @@ -426,7 +422,7 @@ namespace dxvk { ctx->cmdBeginRendering(DxvkCmdBuffer::ExecBuffer, &renderInfo); - m_hud->render(ctx, m_hudRtv); + hud->render(ctx, m_hudRtv); ctx->cmdEndRendering(DxvkCmdBuffer::ExecBuffer); @@ -486,7 +482,7 @@ namespace dxvk { m_hudRtv = m_hudImage->createView(viewInfo); viewInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; - m_hudSrv = m_hudImage->createView(viewInfo); + // m_hudSrv = m_hudImage->createView(viewInfo); } diff --git a/src/dxvk/dxvk_swapchain_blitter.h b/src/dxvk/dxvk_swapchain_blitter.h index f5233ba8..555930fc 100644 --- a/src/dxvk/dxvk_swapchain_blitter.h +++ b/src/dxvk/dxvk_swapchain_blitter.h @@ -113,8 +113,7 @@ namespace dxvk { public: DxvkSwapchainBlitter( - const Rc& device, - const Rc& hud); + const Rc& device); ~DxvkSwapchainBlitter(); /** @@ -132,6 +131,7 @@ namespace dxvk { */ void present( const Rc&ctx, + const Rc& hud, VkClearColorValue clearColor, const Rc& dstView, VkRect2D dstRect, @@ -211,7 +211,6 @@ namespace dxvk { }; Rc m_device; - Rc m_hud; dxvk::mutex m_mutex; Rc m_gammaBuffer; @@ -251,6 +250,7 @@ namespace dxvk { void renderHudImage( const Rc& ctx, + const Rc& hud, VkExtent3D extent); void createHudImage( From bcfab0c4d0ab8ee55e1f4f0b19c5bb674bab9f64 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 1 Nov 2026 17:08:20 +0100 Subject: [PATCH 121/135] [hud] Explicitly pass presenter to HUD --- src/d3d11/d3d11_swapchain.cpp | 2 +- src/d3d9/d3d9_hud.cpp | 8 ++++-- src/d3d9/d3d9_hud.h | 8 ++++-- src/d3d9/d3d9_swapchain.cpp | 2 +- src/dxvk/hud/dxvk_hud.cpp | 4 +-- src/dxvk/hud/dxvk_hud.h | 2 +- src/dxvk/hud/dxvk_hud_item.cpp | 52 +++++++++++++++++++++++++--------- src/dxvk/hud/dxvk_hud_item.h | 48 +++++++++++++++++++++++-------- 8 files changed, 91 insertions(+), 35 deletions(-) diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 83197d37..e09edc40 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -509,7 +509,7 @@ namespace dxvk { cDstRect = dstRect ] (DxvkContext* ctx) { if (cHud) - cHud->update(); + cHud->update(*cPresenter); // Update back buffer color space as necessary if (cSwapImage->image()->info().colorSpace != cColorSpace) { diff --git a/src/d3d9/d3d9_hud.cpp b/src/d3d9/d3d9_hud.cpp index ef4e8e99..e2b23884 100644 --- a/src/d3d9/d3d9_hud.cpp +++ b/src/d3d9/d3d9_hud.cpp @@ -8,7 +8,9 @@ namespace dxvk::hud { , m_mappedString ("") { } - void HudTextureMemory::update(dxvk::high_resolution_clock::time_point time) { + void HudTextureMemory::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { auto stats = m_device->GetAllocator()->getStats(); m_maxAllocated = std::max(m_maxAllocated, stats.memoryAllocated); @@ -54,7 +56,9 @@ namespace dxvk::hud { } - void HudSWVPState::update(dxvk::high_resolution_clock::time_point time) { + void HudSWVPState::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { m_swvpShaderCount = str::format(m_device->GetSWVPShaderCount()); if (m_device->IsSWVP()) { diff --git a/src/d3d9/d3d9_hud.h b/src/d3d9/d3d9_hud.h index 5bacd519..f8e86845 100644 --- a/src/d3d9/d3d9_hud.h +++ b/src/d3d9/d3d9_hud.h @@ -14,7 +14,9 @@ namespace dxvk::hud { HudTextureMemory(D3D9DeviceEx* device); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -49,7 +51,9 @@ namespace dxvk::hud { HudSWVPState(D3D9DeviceEx* device); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index bc291680..79e13143 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -904,7 +904,7 @@ namespace dxvk { cLatency = m_latencyTracker ] (DxvkContext* ctx) { if (cHud) - cHud->update(); + cHud->update(*cPresenter); // Update back buffer color space as necessary if (cSrcView->image()->info().colorSpace != cColorSpace) { diff --git a/src/dxvk/hud/dxvk_hud.cpp b/src/dxvk/hud/dxvk_hud.cpp index 0ef6e85e..0a9960ff 100644 --- a/src/dxvk/hud/dxvk_hud.cpp +++ b/src/dxvk/hud/dxvk_hud.cpp @@ -35,8 +35,8 @@ namespace dxvk::hud { } - void Hud::update() { - m_hudItems.update(); + void Hud::update(Presenter& presenter) { + m_hudItems.update(presenter); } diff --git a/src/dxvk/hud/dxvk_hud.h b/src/dxvk/hud/dxvk_hud.h index 1b8f947b..307223ba 100644 --- a/src/dxvk/hud/dxvk_hud.h +++ b/src/dxvk/hud/dxvk_hud.h @@ -27,7 +27,7 @@ namespace dxvk::hud { * Updates the data to display. * Should be called once per frame. */ - void update(); + void update(Presenter& presenter); /** * \brief Render HUD diff --git a/src/dxvk/hud/dxvk_hud_item.cpp b/src/dxvk/hud/dxvk_hud_item.cpp index 39098ab6..e5575d2f 100644 --- a/src/dxvk/hud/dxvk_hud_item.cpp +++ b/src/dxvk/hud/dxvk_hud_item.cpp @@ -20,7 +20,9 @@ namespace dxvk::hud { } - void HudItem::update(dxvk::high_resolution_clock::time_point time) { + void HudItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { // Do nothing by default. Some items won't need this. } @@ -68,11 +70,11 @@ namespace dxvk::hud { } - void HudItemSet::update() { + void HudItemSet::update(Presenter& presenter) { auto time = dxvk::high_resolution_clock::now(); for (const auto& item : m_items) - item->update(time); + item->update(presenter, time); } @@ -181,7 +183,9 @@ namespace dxvk::hud { HudFpsItem::~HudFpsItem() { } - void HudFpsItem::update(dxvk::high_resolution_clock::time_point time) { + void HudFpsItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { m_frameCount += 1; auto elapsed = std::chrono::duration_cast(time - m_lastUpdate); @@ -565,7 +569,9 @@ namespace dxvk::hud { } - void HudSubmissionStatsItem::update(dxvk::high_resolution_clock::time_point time) { + void HudSubmissionStatsItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { DxvkStatCounters counters = m_device->getStatCounters(); uint64_t currSubmitCount = counters.getCtr(DxvkStatCounter::QueueSubmitCount); @@ -630,7 +636,9 @@ namespace dxvk::hud { } - void HudDrawCallStatsItem::update(dxvk::high_resolution_clock::time_point time) { + void HudDrawCallStatsItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { auto elapsed = std::chrono::duration_cast(time - m_lastUpdate); DxvkStatCounters counters = m_device->getStatCounters(); @@ -692,7 +700,9 @@ namespace dxvk::hud { } - void HudPipelineStatsItem::update(dxvk::high_resolution_clock::time_point time) { + void HudPipelineStatsItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { DxvkStatCounters counters = m_device->getStatCounters(); m_graphicsPipelines = counters.getCtr(DxvkStatCounter::PipeCountGraphics); @@ -737,7 +747,9 @@ namespace dxvk::hud { } - void HudDescriptorStatsItem::update(dxvk::high_resolution_clock::time_point time) { + void HudDescriptorStatsItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { uint64_t ticks = std::chrono::duration_cast(time - m_lastUpdate).count(); DxvkStatCounters counters = m_device->getStatCounters(); @@ -818,7 +830,9 @@ namespace dxvk::hud { } - void HudMemoryStatsItem::update(dxvk::high_resolution_clock::time_point time) { + void HudMemoryStatsItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { for (uint32_t i = 0; i < m_memory.memoryHeapCount; i++) m_heaps[i] = m_device->getMemoryStats(i); } @@ -876,7 +890,9 @@ namespace dxvk::hud { } - void HudMemoryDetailsItem::update(dxvk::high_resolution_clock::time_point time) { + void HudMemoryDetailsItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { uint64_t ticks = std::chrono::duration_cast(time - m_lastUpdate).count(); if (ticks >= UpdateInterval) { @@ -1191,7 +1207,9 @@ namespace dxvk::hud { } - void HudCsThreadItem::update(dxvk::high_resolution_clock::time_point time) { + void HudCsThreadItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { uint64_t ticks = std::chrono::duration_cast(time - m_lastUpdate).count(); // Capture the maximum here since it's more useful to @@ -1274,7 +1292,9 @@ namespace dxvk::hud { } - void HudGpuLoadItem::update(dxvk::high_resolution_clock::time_point time) { + void HudGpuLoadItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { uint64_t ticks = std::chrono::duration_cast(time - m_lastUpdate).count(); if (ticks >= UpdateInterval) { @@ -1320,7 +1340,9 @@ namespace dxvk::hud { } - void HudCompilerActivityItem::update(dxvk::high_resolution_clock::time_point time) { + void HudCompilerActivityItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { DxvkStatCounters counters = m_device->getStatCounters(); m_tasksDone = counters.getCtr(DxvkStatCounter::PipeTasksDone); @@ -1411,7 +1433,9 @@ namespace dxvk::hud { } - void HudLatencyItem::update(dxvk::high_resolution_clock::time_point time) { + void HudLatencyItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { uint64_t ticks = std::chrono::duration_cast(time - m_lastUpdate).count(); if (ticks >= UpdateInterval) { diff --git a/src/dxvk/hud/dxvk_hud_item.h b/src/dxvk/hud/dxvk_hud_item.h index e75c4600..577828a9 100644 --- a/src/dxvk/hud/dxvk_hud_item.h +++ b/src/dxvk/hud/dxvk_hud_item.h @@ -8,6 +8,7 @@ #include "../../util/util_time.h" #include "../dxvk_gpu_query.h" +#include "../dxvk_presenter.h" #include "dxvk_hud_renderer.h" @@ -30,6 +31,7 @@ namespace dxvk::hud { * \param [in] time Current time */ virtual void update( + Presenter& presenter, dxvk::high_resolution_clock::time_point time); /** @@ -68,7 +70,7 @@ namespace dxvk::hud { * \brief Updates the HUD * Updates all enabled HUD items. */ - void update(); + void update(Presenter& presenter); /** * \brief Renders the HUD @@ -224,7 +226,9 @@ namespace dxvk::hud { ~HudFpsItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -360,7 +364,9 @@ namespace dxvk::hud { ~HudSubmissionStatsItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -401,7 +407,9 @@ namespace dxvk::hud { ~HudDrawCallStatsItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -439,7 +447,9 @@ namespace dxvk::hud { ~HudPipelineStatsItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -470,7 +480,9 @@ namespace dxvk::hud { ~HudDescriptorStatsItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -513,7 +525,9 @@ namespace dxvk::hud { ~HudMemoryStatsItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -544,7 +558,9 @@ namespace dxvk::hud { ~HudMemoryDetailsItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -627,7 +643,9 @@ namespace dxvk::hud { ~HudCsThreadItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -673,7 +691,9 @@ namespace dxvk::hud { ~HudGpuLoadItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -708,7 +728,9 @@ namespace dxvk::hud { ~HudCompilerActivityItem(); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -751,7 +773,9 @@ namespace dxvk::hud { void accumulateStats(const DxvkLatencyStats& stats); - void update(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, From 884298dcb999628fd93e67ceb7ab2bcb3bfbc077 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sun, 1 Nov 2026 18:11:03 +0100 Subject: [PATCH 122/135] [hud] Use present timing feedback to determine frame rate --- src/dxvk/hud/dxvk_hud_item.cpp | 23 ++++++++++++++++++----- src/dxvk/hud/dxvk_hud_item.h | 3 ++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/dxvk/hud/dxvk_hud_item.cpp b/src/dxvk/hud/dxvk_hud_item.cpp index e5575d2f..882ee832 100644 --- a/src/dxvk/hud/dxvk_hud_item.cpp +++ b/src/dxvk/hud/dxvk_hud_item.cpp @@ -186,15 +186,28 @@ namespace dxvk::hud { void HudFpsItem::update( Presenter& presenter, dxvk::high_resolution_clock::time_point time) { - m_frameCount += 1; - auto elapsed = std::chrono::duration_cast(time - m_lastUpdate); if (elapsed.count() >= UpdateInterval) { - int64_t fps = (10'000'000ll * m_frameCount) / elapsed.count(); + auto feedback = presenter.queryPresentTiming(); + + if (feedback.frameId > m_lastFeedback.frameId) { + uint64_t frameCount = feedback.frameId - m_lastFeedback.frameId; + uint64_t durationUs = std::chrono::duration_cast( + dxvk::high_resolution_clock::get_time_from_counter(feedback.presentTimeHost) - + dxvk::high_resolution_clock::get_time_from_counter(m_lastFeedback.presentTimeHost)).count(); + + uint64_t fps = (double(frameCount) * double(10'000'000.0)) / double(durationUs); + + m_lastFeedback = feedback; + m_frameRate = str::format(fps / 10, ".", fps % 10); + } else if (feedback.frameId < m_lastFeedback.frameId) { + // Reset if there's inconsistencies and re-query as soon as possible. + // Might happen if the presenter randomly changes for some reason. + m_lastFeedback = feedback; + m_frameRate = std::string(); + } - m_frameRate = str::format(fps / 10, ".", fps % 10); - m_frameCount = 0; m_lastUpdate = time; } } diff --git a/src/dxvk/hud/dxvk_hud_item.h b/src/dxvk/hud/dxvk_hud_item.h index 577828a9..b9d1beb2 100644 --- a/src/dxvk/hud/dxvk_hud_item.h +++ b/src/dxvk/hud/dxvk_hud_item.h @@ -239,7 +239,8 @@ namespace dxvk::hud { private: - uint32_t m_frameCount = 0; + PresenterTimingFeedback m_lastFeedback = {}; + dxvk::high_resolution_clock::time_point m_lastUpdate = dxvk::high_resolution_clock::now(); From 988c4885d42af2813e98bcf95c7a378a420c4dc9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 17:42:35 +0100 Subject: [PATCH 123/135] [meta] Update dxbc-spirv --- subprojects/dxbc-spirv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index 76236b82..f528e1e8 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit 76236b827e95381aa1b37bccfb31642896c427f8 +Subproject commit f528e1e821527e361f34829f78d1fbd5340e6f6a From 99f117933db071a79a0ac9fde1ab5cb636003af8 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 19:10:12 +0100 Subject: [PATCH 124/135] [dxvk] Fix FPS limiter on non-timing path Derp. --- src/dxvk/dxvk_presenter.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 4b19e6d0..0fbbbc99 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -1572,8 +1572,10 @@ namespace dxvk { feedback.presentTimeLocal = 0u; feedback.presentTimeHost = frameId ? dxvk::high_resolution_clock::get_counter() : 0u; - if (!m_timingMode.presentStage) - return commitTimingFeedback(feedback, true); + if (!m_timingMode.presentStage) { + const PresenterFrame* frameEntry = findFrameEntry(frameId); + return commitTimingFeedback(feedback, !frameEntry || feedback.presentTimeHost > frameEntry->deadlineHost); + } // Query reports in-order so that we can somewhat robustly // track how many frames are still in the report queue From cf0a0d90f8965147741ad17dcd0b263817572ab3 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 19:18:23 +0100 Subject: [PATCH 125/135] [dxvk] Don't pass shared handle type for images that aren't shared --- src/dxvk/dxvk_image.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_image.cpp b/src/dxvk/dxvk_image.cpp index 5f6b1fa5..5bcb22ab 100644 --- a/src/dxvk/dxvk_image.cpp +++ b/src/dxvk/dxvk_image.cpp @@ -337,7 +337,9 @@ namespace dxvk { allocationInfo.resourceCookie = cookie(); allocationInfo.properties = m_properties; allocationInfo.mode = mode; - allocationInfo.handleType = m_info.sharing.type; + + if (m_shared) + allocationInfo.handleType = m_info.sharing.type; if (m_info.transient) allocationInfo.mode.set(DxvkAllocationMode::NoDedicated); From a248e21698fa3f84ced80d36934950c30fbeeded Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 22:36:27 +0100 Subject: [PATCH 126/135] [dxvk] Remove latency trackers --- dxvk.conf | 30 -- src/d3d11/d3d11_context_imm.cpp | 10 +- src/d3d11/d3d11_context_imm.h | 3 +- src/d3d11/d3d11_device.cpp | 111 +------ src/d3d11/d3d11_device.h | 9 - src/d3d11/d3d11_swapchain.cpp | 64 +--- src/d3d11/d3d11_swapchain.h | 6 - src/d3d9/d3d9_device.cpp | 26 +- src/d3d9/d3d9_device.h | 3 +- src/d3d9/d3d9_swapchain.cpp | 60 +--- src/d3d9/d3d9_swapchain.h | 9 +- src/dxgi/dxgi_swapchain.cpp | 10 +- src/dxgi/dxgi_swapchain.h | 1 - src/dxvk/dxvk_cmdlist.cpp | 20 +- src/dxvk/dxvk_cmdlist.h | 8 +- src/dxvk/dxvk_context.cpp | 39 +-- src/dxvk/dxvk_context.h | 29 -- src/dxvk/dxvk_device.cpp | 34 +- src/dxvk/dxvk_device.h | 16 - src/dxvk/dxvk_latency.h | 223 ------------- src/dxvk/dxvk_latency_builtin.cpp | 408 ----------------------- src/dxvk/dxvk_latency_builtin.h | 127 ------- src/dxvk/dxvk_latency_reflex.cpp | 530 ------------------------------ src/dxvk/dxvk_latency_reflex.h | 199 ----------- src/dxvk/dxvk_options.cpp | 2 - src/dxvk/dxvk_options.h | 6 - src/dxvk/dxvk_presenter.cpp | 13 +- src/dxvk/dxvk_presenter.h | 9 +- src/dxvk/dxvk_queue.cpp | 38 +-- src/dxvk/dxvk_queue.h | 18 - src/dxvk/hud/dxvk_hud_item.cpp | 82 ----- src/dxvk/hud/dxvk_hud_item.h | 44 --- src/dxvk/meson.build | 2 - 33 files changed, 42 insertions(+), 2147 deletions(-) delete mode 100644 src/dxvk/dxvk_latency.h delete mode 100644 src/dxvk/dxvk_latency_builtin.cpp delete mode 100644 src/dxvk/dxvk_latency_builtin.h delete mode 100644 src/dxvk/dxvk_latency_reflex.cpp delete mode 100644 src/dxvk/dxvk_latency_reflex.h diff --git a/dxvk.conf b/dxvk.conf index b403f62c..a11c8531 100644 --- a/dxvk.conf +++ b/dxvk.conf @@ -96,36 +96,6 @@ # dxvk.maxFrameRate = 0 -# Controls latency sleep and Nvidia Reflex support. -# -# Supported values: -# - Auto: By default, DXVK only supports latency sleep in D3D11 games that -# use Reflex if the graphics driver supports VK_NV_low_latency2, -# and if dxvk-nvapi is enabled in Proton. -# - True: Enables built-in latency reduction based on internal timings. -# This assumes that input sampling for any given frame happens after -# the D3D9 or DXGI Present call returns; games that render and present -# asynchronously will not behave as intended. -# Similarly, this will not have any effect in games with built-in frame -# rate limiters, or if an external limiter (such as MangoHud) is used. -# In some games, enabling this may reduce performance or lead to less -# consistent frame pacing. -# The implementation will either use VK_NV_low_latency2 if supported -# by the driver, or a custom algorithm. -# - False: Disable Reflex support as well as built-in latency reduction. - -# dxvk.latencySleep = Auto - - -# Tolerance for the latency sleep heuristic, in microseconds. Higher values -# increase latency, but may lead to better frame pacing in some cases. Does -# not have any effect if NV_low_latency2 is used. -# -# Supported values: Any non-negative number - -# dxvk.latencyTolerance = 1000 - - # Disables the use of VK_NV_low_latency2. This will make Reflex unavailable # in games, and if dxvk.latencySleep is set to True, a custom algorithm will # be used for latency control. By default, the extension will not be used in diff --git a/src/d3d11/d3d11_context_imm.cpp b/src/d3d11/d3d11_context_imm.cpp index b2d6608e..83bf0af0 100644 --- a/src/d3d11/d3d11_context_imm.cpp +++ b/src/d3d11/d3d11_context_imm.cpp @@ -866,8 +866,7 @@ namespace dxvk { } - void D3D11ImmediateContext::EndFrame( - Rc LatencyTracker) { + void D3D11ImmediateContext::EndFrame() { D3D10DeviceLock lock = LockContext(); // Don't keep draw buffers alive indefinitely. This cannot be @@ -875,13 +874,8 @@ namespace dxvk { // flush, so no state changes are allowed to happen there. SetDrawBuffers(nullptr, nullptr); - EmitCs([ - cTracker = std::move(LatencyTracker) - ] (DxvkContext* ctx) { + EmitCs([] (DxvkContext* ctx) { ctx->endFrame(); - - if (cTracker && cTracker->needsAutoMarkers()) - ctx->endLatencyTracking(cTracker); }); } diff --git a/src/d3d11/d3d11_context_imm.h b/src/d3d11/d3d11_context_imm.h index 61d4f804..15286b28 100644 --- a/src/d3d11/d3d11_context_imm.h +++ b/src/d3d11/d3d11_context_imm.h @@ -181,8 +181,7 @@ namespace dxvk { void SynchronizeDevice(); - void EndFrame( - Rc LatencyTracker); + void EndFrame(); bool WaitForResource( const DxvkPagedResource& Resource, diff --git a/src/d3d11/d3d11_device.cpp b/src/d3d11/d3d11_device.cpp index 301590a6..a0179ab7 100644 --- a/src/d3d11/d3d11_device.cpp +++ b/src/d3d11/d3d11_device.cpp @@ -3495,9 +3495,7 @@ namespace dxvk { D3D11Device* pDevice) : m_container(pContainer), m_device(pDevice) { auto dxvkDevice = pDevice->GetDXVKDevice(); - - m_reflexEnabled = dxvkDevice->features().nvLowLatency2 - && dxvkDevice->config().latencySleep == Tristate::Auto; + m_reflexEnabled = dxvkDevice->features().nvLowLatency2; } @@ -3532,16 +3530,7 @@ namespace dxvk { if (!m_reflexEnabled) return DXGI_ERROR_INVALID_CALL; - // Don't keep object locked while sleeping - Rc tracker; - - { std::lock_guard lock(m_mutex); - tracker = m_tracker; - } - - if (tracker) - tracker->latencySleep(); - + // TODO reimplement. return S_OK; } @@ -3555,15 +3544,12 @@ namespace dxvk { std::lock_guard lock(m_mutex); - if (m_tracker) { - m_tracker->setLatencySleepMode( - LowLatencyEnable, LowLatencyBoost, MinIntervalUs); - } - // Write back in case we have no swapchain yet m_enableLowLatency = LowLatencyEnable; m_enableBoost = LowLatencyBoost; m_minIntervalUs = MinIntervalUs; + + // TODO reimplement. return S_OK; } @@ -3574,31 +3560,7 @@ namespace dxvk { if (!m_reflexEnabled) return DXGI_ERROR_INVALID_CALL; - std::lock_guard lock(m_mutex); - - if (m_tracker) { - auto marker = VkLatencyMarkerNV(MarkerType); - m_tracker->setLatencyMarker(FrameId, marker); - - if (marker == VK_LATENCY_MARKER_RENDERSUBMIT_START_NV) { - m_device->GetContext()->InjectCs(DxvkCsQueue::Ordered, [ - cTracker = m_tracker, - cFrameId = FrameId - ] (DxvkContext* ctx) { - uint64_t frameId = cTracker->frameIdFromAppFrameId(cFrameId); - - if (frameId) - ctx->beginLatencyTracking(cTracker, frameId); - }); - } else if (marker == VK_LATENCY_MARKER_RENDERSUBMIT_END_NV) { - m_device->GetContext()->InjectCs(DxvkCsQueue::Ordered, [ - cTracker = m_tracker - ] (DxvkContext* ctx) { - ctx->endLatencyTracking(cTracker); - }); - } - } - + // TODO reimplement. return S_OK; } @@ -3616,72 +3578,11 @@ namespace dxvk { if (!m_reflexEnabled) return DXGI_ERROR_INVALID_CALL; - std::lock_guard lock(m_mutex); - - if (!m_tracker) - return S_OK; - - // Apparently we have to report all 64 frames, or nothing - std::array reports = { }; - uint32_t reportCount = m_tracker->getFrameReports(FrameCount, reports.data()); - - if (reportCount < FrameCount) - return S_OK; - - for (uint32_t i = 0; i < FrameCount; i++) { - auto& src = reports[i]; - auto& dst = pLowLatencyResults->frameReports[i]; - - dst.frameID = src.report.presentID; - dst.inputSampleTime = src.report.inputSampleTimeUs; - dst.simStartTime = src.report.simStartTimeUs; - dst.simEndTime = src.report.simEndTimeUs; - dst.renderSubmitStartTime = src.report.renderSubmitStartTimeUs; - dst.renderSubmitEndTime = src.report.renderSubmitEndTimeUs; - dst.presentStartTime = src.report.presentStartTimeUs; - dst.presentEndTime = src.report.presentEndTimeUs; - dst.driverStartTime = src.report.driverStartTimeUs; - dst.driverEndTime = src.report.driverEndTimeUs; - dst.osRenderQueueStartTime = src.report.osRenderQueueStartTimeUs; - dst.osRenderQueueEndTime = src.report.osRenderQueueEndTimeUs; - dst.gpuRenderStartTime = src.report.gpuRenderStartTimeUs; - dst.gpuRenderEndTime = src.report.gpuRenderEndTimeUs; - dst.gpuActiveRenderTimeUs = src.gpuActiveTimeUs; - dst.gpuFrameTimeUs = 0; - - if (i) { - dst.gpuFrameTimeUs = reports[i - 0].report.gpuRenderEndTimeUs - - reports[i - 1].report.gpuRenderEndTimeUs; - } - } - + // TODO reimplement. return S_OK; } - void D3D11ReflexDevice::RegisterLatencyTracker( - Rc Tracker) { - std::lock_guard lock(m_mutex); - - if (m_tracker) - return; - - if ((m_tracker = dynamic_cast(Tracker.ptr()))) - m_tracker->setLatencySleepMode(m_enableLowLatency, m_enableBoost, m_minIntervalUs); - } - - - void D3D11ReflexDevice::UnregisterLatencyTracker( - Rc Tracker) { - std::lock_guard lock(m_mutex); - - if (m_tracker == Tracker) - m_tracker = nullptr; - } - - - - DXGIVkSwapChainFactory::DXGIVkSwapChainFactory( D3D11DXGIDevice* pContainer, D3D11Device* pDevice) diff --git a/src/d3d11/d3d11_device.h b/src/d3d11/d3d11_device.h index 2bcae473..06fe7d46 100644 --- a/src/d3d11/d3d11_device.h +++ b/src/d3d11/d3d11_device.h @@ -7,7 +7,6 @@ #include "../dxgi/dxgi_interfaces.h" #include "../dxvk/dxvk_cs.h" -#include "../dxvk/dxvk_latency_reflex.h" #include "../d3d10/d3d10_device.h" @@ -822,12 +821,6 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE GetLatencyInfo( D3D_LOW_LATENCY_RESULTS* pLowLatencyResults); - void RegisterLatencyTracker( - Rc Tracker); - - void UnregisterLatencyTracker( - Rc Tracker); - private: D3D11DXGIDevice* m_container; @@ -840,8 +833,6 @@ namespace dxvk { bool m_enableLowLatency = false; bool m_enableBoost = false; uint64_t m_minIntervalUs = 0u; - - Rc m_tracker; }; diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index e09edc40..1a1eac0e 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -2,7 +2,6 @@ #include "d3d11_device.h" #include "d3d11_swapchain.h" -#include "../dxvk/dxvk_latency_builtin.h" #include "../dxvk/dxvk_shader_spirv.h" #include "../util/util_win32_compat.h" @@ -86,7 +85,6 @@ namespace dxvk { m_presenter->destroyResources(); DestroyFrameLatencyEvent(); - DestroyLatencyTracker(); } @@ -287,18 +285,6 @@ namespace dxvk { // applications using the semaphore may deadlock. This works because // we do not increment the frame ID in those situations. SyncFrameLatency(); - - // Ignore latency stuff if presentation failed - DxvkLatencyStats latencyStats = { }; - - if (hr == S_OK && m_latency) { - latencyStats = m_latency->getStatistics(m_frameId); - m_latency->sleepAndBeginFrame(m_frameId + 1, std::abs(m_targetFrameRate)); - } - - if (m_latencyHud) - m_latencyHud->accumulateStats(latencyStats); - return hr; } @@ -415,24 +401,18 @@ namespace dxvk { auto immediateContext = m_parent->GetContext(); auto immediateContextLock = immediateContext->LockContext(); - immediateContext->EndFrame(m_latency); + immediateContext->EndFrame(); immediateContext->ExecuteFlush(GpuFlushType::ExplicitFlush, nullptr, m_parent->Is11on12Device()); m_presenter->setSyncInterval(SyncInterval); // Presentation semaphores and WSI swap chain image - if (m_latency) - m_latency->notifyCpuPresentBegin(m_frameId + 1u); - PresenterSync sync; Rc backBuffer; VkResult status = m_presenter->acquireNextImage(sync, backBuffer); - if (status != VK_SUCCESS && m_latency) - m_latency->discardTimings(); - if (status < 0) return E_FAIL; @@ -500,7 +480,6 @@ namespace dxvk { cSwapImage = GetBackBufferView(), cSync = sync, cPresenter = m_presenter, - cLatency = m_latency, cColorSpace = m_colorSpace, cFrameId = m_frameId, cDirtyRects = std::move(dirtyRects), @@ -530,7 +509,7 @@ namespace dxvk { ctx->synchronizeWsi(cSync); ctx->flushCommandList(nullptr, 0u); - cDevice->presentImage(cPresenter, cLatency, cFrameId, + cDevice->presentImage(cPresenter, cFrameId, cDirtyRects.size(), cDirtyRects.data(), 0u); }); @@ -538,20 +517,6 @@ namespace dxvk { RotateBackBuffers(immediateContext); immediateContext->FlushCsChunk(); - - if (m_latency) { - m_latency->notifyCpuPresentEnd(m_frameId); - - if (m_latency->needsAutoMarkers()) { - immediateContext->EmitCs([ - cLatency = m_latency, - cFrameId = m_frameId - ] (DxvkContext* ctx) { - ctx->beginLatencyTracking(cLatency, cFrameId + 1u); - }); - } - } - return S_OK; } @@ -603,10 +568,7 @@ namespace dxvk { m_presenter->setSurfaceExtent({ m_desc.Width, m_desc.Height }); m_presenter->setFrameRateLimit(m_targetFrameRate, GetActualFrameLatency()); - m_latency = m_device->createLatencyTracker(m_presenter); - - Com reflex = GetReflexDevice(); - reflex->RegisterLatencyTracker(m_latency); + // TODO add Reflex device. } @@ -683,13 +645,9 @@ namespace dxvk { void D3D11SwapChain::CreateBlitter() { m_hud = hud::Hud::createHud(m_device); - if (m_hud) { + if (m_hud) m_hud->addItem("api", 1, GetApiName()); - if (m_latency) - m_latencyHud = m_hud->addItem("latency", 4); - } - m_blitter = new DxvkSwapchainBlitter(m_device); } @@ -699,20 +657,6 @@ namespace dxvk { } - void D3D11SwapChain::DestroyLatencyTracker() { - // Need to make sure the context stops using - // the tracker for submissions - m_parent->GetContext()->InjectCs(DxvkCsQueue::Ordered, [ - cLatency = m_latency - ] (DxvkContext* ctx) { - ctx->endLatencyTracking(cLatency); - }); - - Com reflex = GetReflexDevice(); - reflex->UnregisterLatencyTracker(m_latency); - } - - void D3D11SwapChain::SyncFrameLatency() { // Wait for the sync event so that we respect the maximum frame latency m_frameLatencySignal->wait(m_frameId - GetActualFrameLatency()); diff --git a/src/d3d11/d3d11_swapchain.h b/src/d3d11/d3d11_swapchain.h index 8ed6dae5..4ad29461 100644 --- a/src/d3d11/d3d11_swapchain.h +++ b/src/d3d11/d3d11_swapchain.h @@ -4,7 +4,6 @@ #include "../dxvk/hud/dxvk_hud.h" -#include "../dxvk/dxvk_latency.h" #include "../dxvk/dxvk_swapchain_blitter.h" #include "../util/sync/sync_signal.h" @@ -125,7 +124,6 @@ namespace dxvk { Rc m_hud; Rc m_blitter; - Rc m_latency; small_vector, 4> m_backBuffers; @@ -146,8 +144,6 @@ namespace dxvk { double m_targetFrameRate = 0.0; - Rc m_latencyHud; - Rc GetBackBufferView(); HRESULT PresentImage( @@ -166,8 +162,6 @@ namespace dxvk { void DestroyFrameLatencyEvent(); - void DestroyLatencyTracker(); - void SyncFrameLatency(); uint32_t GetActualFrameLatency(); diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 1c34094d..df93527e 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -4519,7 +4519,7 @@ namespace dxvk { m_implicitSwapchain->Invalidate(pPresentationParameters->hDeviceWindow); try { - auto* swapchain = new D3D9SwapChainEx(this, pPresentationParameters, pFullscreenDisplayMode, false); + auto* swapchain = new D3D9SwapChainEx(this, pPresentationParameters, pFullscreenDisplayMode); *ppSwapChain = ref(swapchain); m_losableResourceCounter++; } @@ -6128,29 +6128,11 @@ namespace dxvk { } - void D3D9DeviceEx::BeginFrame(Rc LatencyTracker, uint64_t FrameId) { + void D3D9DeviceEx::EndFrame() { D3D9DeviceLock lock = LockDevice(); - EmitCs([ - cTracker = std::move(LatencyTracker), - cFrameId = FrameId - ] (DxvkContext* ctx) { - if (cTracker && cTracker->needsAutoMarkers()) - ctx->beginLatencyTracking(cTracker, cFrameId); - }); - } - - - void D3D9DeviceEx::EndFrame(Rc LatencyTracker) { - D3D9DeviceLock lock = LockDevice(); - - EmitCs([ - cTracker = std::move(LatencyTracker) - ] (DxvkContext* ctx) { + EmitCs([] (DxvkContext* ctx) { ctx->endFrame(); - - if (cTracker && cTracker->needsAutoMarkers()) - ctx->endLatencyTracking(cTracker); }); } @@ -8887,7 +8869,7 @@ namespace dxvk { return hr; } else { - m_implicitSwapchain = new D3D9SwapChainEx(this, pPresentationParameters, pFullscreenDisplayMode, true); + m_implicitSwapchain = new D3D9SwapChainEx(this, pPresentationParameters, pFullscreenDisplayMode); m_mostRecentlyUsedSwapchain = m_implicitSwapchain.ptr(); } diff --git a/src/d3d9/d3d9_device.h b/src/d3d9/d3d9_device.h index ec67d06f..cac0c06a 100644 --- a/src/d3d9/d3d9_device.h +++ b/src/d3d9/d3d9_device.h @@ -926,8 +926,7 @@ namespace dxvk { void Flush(); void FlushAndSync9On12(); - void BeginFrame(Rc LatencyTracker, uint64_t FrameId); - void EndFrame(Rc LatencyTracker); + void EndFrame(); template void UpdateAnyColorWrites(); diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 79e13143..415d2e64 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -16,12 +16,10 @@ namespace dxvk { D3D9SwapChainEx::D3D9SwapChainEx( D3D9DeviceEx* pDevice, D3DPRESENT_PARAMETERS* pPresentParams, - const D3DDISPLAYMODEEX* pFullscreenDisplayMode, - bool EnableLatencyTracking) + const D3DDISPLAYMODEEX* pFullscreenDisplayMode) : D3D9SwapChainExBase(pDevice) , m_device (pDevice->GetDXVKDevice()) , m_frameLatencyCap (pDevice->GetOptions()->maxFrameLatency) - , m_latencyTracking (EnableLatencyTracking) , m_swapchainExt (this) { this->NormalizePresentParameters(pPresentParams); m_presentParams = *pPresentParams; @@ -205,7 +203,7 @@ namespace dxvk { #define DCX_USESTYLE 0x00010000 HRESULT D3D9SwapChainEx::PresentImageGDI(HWND Window) { - m_parent->EndFrame(nullptr); + m_parent->EndFrame(); m_parent->Flush(); if (!std::exchange(m_warnedAboutGDIFallback, true)) @@ -765,9 +763,6 @@ namespace dxvk { if (entry->second.presenter) { entry->second.presenter->destroyResources(); entry->second.presenter = nullptr; - - if (m_presentParams.hDeviceWindow == hWindow) - DestroyLatencyTracker(); } if (m_wctx == &entry->second) @@ -847,12 +842,9 @@ namespace dxvk { void D3D9SwapChainEx::PresentImage(UINT SyncInterval) { - m_parent->EndFrame(m_latencyTracker); + m_parent->EndFrame(); m_parent->Flush(); - if (m_latencyTracker) - m_latencyTracker->notifyCpuPresentBegin(m_wctx->frameId + 1u); - // Retrieve the image and image view to present VkResult status = VK_SUCCESS; @@ -900,8 +892,7 @@ namespace dxvk { cDstView = backBuffer->createView(viewInfo), cDstRect = dstRect, cSync = sync, - cFrameId = m_wctx->frameId, - cLatency = m_latencyTracker + cFrameId = m_wctx->frameId ] (DxvkContext* ctx) { if (cHud) cHud->update(*cPresenter); @@ -924,33 +915,14 @@ namespace dxvk { ctx->synchronizeWsi(cSync); ctx->flushCommandList(nullptr, 0u); - cDevice->presentImage(cPresenter, cLatency, cFrameId, 0, nullptr, 0); + cDevice->presentImage(cPresenter, cFrameId, 0, nullptr, 0); }); m_parent->FlushCsChunk(); } - if (m_latencyTracker) { - if (status == VK_SUCCESS) - m_latencyTracker->notifyCpuPresentEnd(m_wctx->frameId); - else - m_latencyTracker->discardTimings(); - } - SyncFrameLatency(); - DxvkLatencyStats latencyStats = { }; - - if (m_latencyTracker && status == VK_SUCCESS) { - latencyStats = m_latencyTracker->getStatistics(m_wctx->frameId); - m_latencyTracker->sleepAndBeginFrame(m_wctx->frameId + 1, std::abs(m_targetFrameRate)); - - m_parent->BeginFrame(m_latencyTracker, m_wctx->frameId + 1u); - } - - if (m_latencyHud) - m_latencyHud->accumulateStats(latencyStats); - // Rotate swap chain buffers so that the back // buffer at index 0 becomes the front buffer. uint32_t rotatingBufferCount = m_backBuffers.size(); @@ -1018,9 +990,6 @@ namespace dxvk { entry->second.frameLatencySignal = new sync::Fence(entry->second.frameId); entry->second.presenter = CreatePresenter(m_window, entry->second.frameLatencySignal); - - if (m_presentParams.hDeviceWindow == m_window && m_latencyTracking) - m_latencyTracker = m_device->createLatencyTracker(entry->second.presenter); } m_wctx = &entry->second; @@ -1098,10 +1067,6 @@ namespace dxvk { if (m_hud) { m_apiHud = m_hud->addItem("api", 1, GetApiName()); - - if (m_latencyTracking) - m_latencyHud = m_hud->addItem("latency", 4); - m_hud->addItem("swvp", -1, m_parent); #ifdef DXVK_USE_UNMAPPABLE_MEMORY @@ -1124,25 +1089,12 @@ namespace dxvk { } - void D3D9SwapChainEx::DestroyLatencyTracker() { - if (!m_latencyTracker) - return; - - m_parent->InjectCs([ - cTracker = std::move(m_latencyTracker) - ] (DxvkContext* ctx) { - ctx->endLatencyTracking(cTracker); - }); - } - - void D3D9SwapChainEx::UpdateTargetFrameRate(uint32_t SyncInterval) { double frameRate = double(m_parent->GetOptions()->maxFrameRate); if (frameRate != -1.0) { if (frameRate == 0.0 && SyncInterval) { - bool engageLimiter = SyncInterval > 1u || m_monitor || - m_device->config().latencySleep == Tristate::True; + bool engageLimiter = SyncInterval > 1u || m_monitor; if (engageLimiter) frameRate = -m_displayRefreshRate / double(SyncInterval); diff --git a/src/d3d9/d3d9_swapchain.h b/src/d3d9/d3d9_swapchain.h index 643eac04..f4b30b2f 100644 --- a/src/d3d9/d3d9_swapchain.h +++ b/src/d3d9/d3d9_swapchain.h @@ -69,8 +69,7 @@ namespace dxvk { D3D9SwapChainEx( D3D9DeviceEx* pDevice, D3DPRESENT_PARAMETERS* pPresentParams, - const D3DDISPLAYMODEEX* pFullscreenDisplayMode, - bool EnableLatencyTracking); + const D3DDISPLAYMODEEX* pFullscreenDisplayMode); ~D3D9SwapChainEx(); @@ -180,11 +179,7 @@ namespace dxvk { VkColorSpaceKHR m_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - bool m_latencyTracking = false; - Rc m_latencyTracker = nullptr; - Rc m_apiHud; - Rc m_latencyHud; std::optional m_hdrMetadata; @@ -202,8 +197,6 @@ namespace dxvk { void CreateBlitter(); - void DestroyLatencyTracker(); - void InitRamp(); void UpdateTargetFrameRate(uint32_t SyncInterval); diff --git a/src/dxgi/dxgi_swapchain.cpp b/src/dxgi/dxgi_swapchain.cpp index 71e69f98..b7d0f76b 100644 --- a/src/dxgi/dxgi_swapchain.cpp +++ b/src/dxgi/dxgi_swapchain.cpp @@ -49,14 +49,6 @@ namespace dxvk { // Ensure that RGBA16 swap chains are scRGB if supported UpdateSourceSize(m_desc.Width, m_desc.Height); UpdateColorSpace(m_desc.Format, m_colorSpace); - - // Somewhat hacky way to determine whether to forward the - // display refresh rate in windowed mode even with a sync - // interval of 1. - if (!m_is_d3d12) { - auto instance = pFactory->GetDXVKInstance(); - m_hasLatencyControl = instance->options().latencySleep == Tristate::True; - } } @@ -1085,7 +1077,7 @@ namespace dxvk { // Engage the frame limiter with large sync intervals even in windowed // mode since we want to avoid double-presenting to the swap chain. if (SyncInterval != m_frameRateSyncInterval && m_descFs.Windowed) { - bool engageLimiter = (SyncInterval > 1u) || (SyncInterval && m_hasLatencyControl); + bool engageLimiter = SyncInterval > 1u; m_frameRateSyncInterval = SyncInterval; m_frameRateRefresh = 0.0f; diff --git a/src/dxgi/dxgi_swapchain.h b/src/dxgi/dxgi_swapchain.h index c8da01d0..7becd55c 100644 --- a/src/dxgi/dxgi_swapchain.h +++ b/src/dxgi/dxgi_swapchain.h @@ -215,7 +215,6 @@ namespace dxvk { VkExtent2D m_sourceSize = {}; uint32_t m_globalHDRStateSerial = 0; - bool m_hasLatencyControl = false; D3DDestructionNotifier m_destructionNotifier; diff --git a/src/dxvk/dxvk_cmdlist.cpp b/src/dxvk/dxvk_cmdlist.cpp index 9211f159..1a5b3ad6 100644 --- a/src/dxvk/dxvk_cmdlist.cpp +++ b/src/dxvk/dxvk_cmdlist.cpp @@ -59,13 +59,9 @@ namespace dxvk { VkResult DxvkCommandSubmission::submit( DxvkDevice* device, - VkQueue queue, - uint64_t frameId) { + VkQueue queue) { auto vk = device->vkd(); - VkLatencySubmissionPresentIdNV latencyInfo = { VK_STRUCTURE_TYPE_LATENCY_SUBMISSION_PRESENT_ID_NV }; - latencyInfo.presentID = frameId; - VkSubmitInfo2 submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO_2 }; if (!m_semaphoreWaits.empty()) { @@ -83,9 +79,6 @@ namespace dxvk { submitInfo.pSignalSemaphoreInfos = m_semaphoreSignals.data(); } - if (frameId && device->features().nvLowLatency2) - latencyInfo.pNext = std::exchange(submitInfo.pNext, &latencyInfo); - VkResult vr = VK_SUCCESS; if (!this->isEmpty()) @@ -254,8 +247,7 @@ namespace dxvk { VkResult DxvkCommandList::submit( const DxvkTimelineSemaphores& semaphores, - DxvkTimelineSemaphoreValues& timelines, - uint64_t trackedId) { + DxvkTimelineSemaphoreValues& timelines) { // Wait for pending descriptor copies to finish m_descriptorSync.synchronize(); @@ -317,7 +309,7 @@ namespace dxvk { m_commandSubmission.signalSemaphore(semaphores.transfer, ++timelines.transfer, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT); - if ((status = m_commandSubmission.submit(m_device, transfer.queueHandle, trackedId))) + if ((status = m_commandSubmission.submit(m_device, transfer.queueHandle))) return status; m_commandSubmission.waitSemaphore(semaphores.transfer, @@ -359,7 +351,7 @@ namespace dxvk { ++timelines.graphics, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT); // Finally, submit all graphics commands of the current submission - if ((status = m_commandSubmission.submit(m_device, graphics.queueHandle, trackedId))) + if ((status = m_commandSubmission.submit(m_device, graphics.queueHandle))) return status; // If there are WSI semaphores involved, do another submit only @@ -369,7 +361,7 @@ namespace dxvk { m_commandSubmission.signalSemaphore(semaphores.graphics, ++timelines.graphics, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT); - if ((status = m_commandSubmission.submit(m_device, graphics.queueHandle, trackedId))) + if ((status = m_commandSubmission.submit(m_device, graphics.queueHandle))) return status; } @@ -379,7 +371,7 @@ namespace dxvk { m_commandSubmission.waitSemaphore(semaphores.graphics, timelines.graphics, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT); - if (isLast && (status = m_commandSubmission.submit(m_device, transfer.queueHandle, trackedId))) + if (isLast && (status = m_commandSubmission.submit(m_device, transfer.queueHandle))) return status; } } diff --git a/src/dxvk/dxvk_cmdlist.h b/src/dxvk/dxvk_cmdlist.h index 38891eb0..585d1c15 100644 --- a/src/dxvk/dxvk_cmdlist.h +++ b/src/dxvk/dxvk_cmdlist.h @@ -127,13 +127,11 @@ namespace dxvk { * * \param [in] device DXVK device * \param [in] queue Queue to submit to - * \param [in] frameId Latency frame ID * \returns Submission return value */ VkResult submit( DxvkDevice* device, - VkQueue queue, - uint64_t frameId); + VkQueue queue); /** * \brief Resets object @@ -254,13 +252,11 @@ namespace dxvk { * * \param [in] semaphores Timeline semaphore pair * \param [in] timelines Timeline semaphore values - * \param [in] frameId Latency frame ID * \returns Submission status */ VkResult submit( const DxvkTimelineSemaphores& semaphores, - DxvkTimelineSemaphoreValues& timelines, - uint64_t frameId); + DxvkTimelineSemaphoreValues& timelines); /** * \brief Stat counters diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index 66b2c539..fb9676c9 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -139,27 +139,6 @@ namespace dxvk { } - void DxvkContext::beginLatencyTracking( - const Rc& tracker, - uint64_t frameId) { - if (tracker && (!m_latencyTracker || m_latencyTracker == tracker)) { - tracker->notifyCsRenderBegin(frameId); - - m_latencyTracker = tracker; - m_latencyFrameId = frameId; - - m_endLatencyTracking = false; - } - } - - - void DxvkContext::endLatencyTracking( - const Rc& tracker) { - if (tracker && tracker == m_latencyTracker) - m_endLatencyTracking = true; - } - - void DxvkContext::flushCommandList( const VkDebugUtilsLabelEXT* reason, uint64_t submissionId) { @@ -177,23 +156,7 @@ namespace dxvk { DxvkContextFeature::DescriptorBuffer)) m_cmd->setDescriptorSyncHandle(m_descriptorWorker.getSyncHandle()); - // Need to call this before submitting so that the last GPU - // submission does not happen before the render end signal. - if (m_endLatencyTracking && m_latencyTracker) - m_latencyTracker->notifyCsRenderEnd(m_latencyFrameId); - - m_device->submitCommandList(this->endRecording(reason), - m_latencyTracker, m_latencyFrameId, submissionId); - - // Ensure that subsequent submissions do not see the tracker. - // It is important to hide certain internal submissions in - // case the application is CPU-bound. - if (m_endLatencyTracking) { - m_latencyTracker = nullptr; - m_latencyFrameId = 0u; - - m_endLatencyTracking = false; - } + m_device->submitCommandList(this->endRecording(reason), submissionId); // If we have a zero buffer, see if we can get rid of it freeZeroBuffer(); diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h index 1e9780f2..e95ae989 100644 --- a/src/dxvk/dxvk_context.h +++ b/src/dxvk/dxvk_context.h @@ -7,7 +7,6 @@ #include "dxvk_descriptor_heap.h" #include "dxvk_descriptor_worker.h" #include "dxvk_implicit_resolve.h" -#include "dxvk_latency.h" #include "dxvk_objects.h" #include "dxvk_queue.h" #include "dxvk_util.h" @@ -67,30 +66,6 @@ namespace dxvk { */ void endFrame(); - /** - * \brief Begins latency tracking - * - * Notifies the beginning of a frame on the CS timeline - * an ensures that subsequent submissions are associated - * with the correct frame ID. Only one tracker can be - * active at any given time. - * \param [in] tracker Latency tracker object - * \param [in] frameId Current frame ID - */ - void beginLatencyTracking( - const Rc& tracker, - uint64_t frameId); - - /** - * \brief Ends latency tracking - * - * Notifies the end of the frame. Ignored if the - * tracker is not currently active. - * \param [in] tracker Latency tracker object - */ - void endLatencyTracking( - const Rc& tracker); - /** * \brief Flushes command buffer * @@ -1401,10 +1376,6 @@ namespace dxvk { DxvkDescriptorCopyWorker m_descriptorWorker; - Rc m_latencyTracker; - uint64_t m_latencyFrameId = 0u; - bool m_endLatencyTracking = false; - DxvkImplicitResolveTracker m_implicitResolves; void blitImageFb( diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index a9d623e5..116f6b2d 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -1,7 +1,5 @@ #include "dxvk_device.h" #include "dxvk_instance.h" -#include "dxvk_latency_builtin.h" -#include "dxvk_latency_reflex.h" #include "dxvk_shader_cache.h" #include "dxvk_shader_ir.h" @@ -579,26 +577,8 @@ namespace dxvk { } - Rc DxvkDevice::createLatencyTracker( - const Rc& presenter) { - if (m_options.latencySleep == Tristate::False) - return nullptr; - - if (m_options.latencySleep == Tristate::Auto) { - if (m_features.nvLowLatency2) - return new DxvkReflexLatencyTrackerNv(presenter); - else - return nullptr; - } - - return new DxvkBuiltInLatencyTracker(presenter, - m_options.latencyTolerance, m_features.nvLowLatency2); - } - - void DxvkDevice::presentImage( const Rc& presenter, - const Rc& tracker, uint64_t frameId, uint32_t rectCount, const VkRectLayerKHR* rects, @@ -610,11 +590,7 @@ namespace dxvk { for (uint32_t i = 0u; i < rectCount; i++) presentInfo.rects.push_back(rects[i]); - DxvkLatencyInfo latencyInfo; - latencyInfo.tracker = tracker; - latencyInfo.frameId = frameId; - - m_submissionQueue.present(presentInfo, latencyInfo, submissionId); + m_submissionQueue.present(presentInfo, submissionId); std::lock_guard statLock(m_statLock); m_statCounters.addCtr(DxvkStatCounter::QueuePresentCount, 1); @@ -623,17 +599,11 @@ namespace dxvk { void DxvkDevice::submitCommandList( const Rc& commandList, - const Rc& tracker, - uint64_t frameId, uint64_t submissionId) { DxvkSubmitInfo submitInfo = { }; submitInfo.cmdList = commandList; - DxvkLatencyInfo latencyInfo; - latencyInfo.tracker = tracker; - latencyInfo.frameId = frameId; - - m_submissionQueue.submit(submitInfo, latencyInfo, submissionId); + m_submissionQueue.submit(submitInfo, submissionId); std::lock_guard statLock(m_statLock); m_statCounters.merge(commandList->statCounters()); diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h index 00aa9314..ab12cc55 100644 --- a/src/dxvk/dxvk_device.h +++ b/src/dxvk/dxvk_device.h @@ -10,7 +10,6 @@ #include "dxvk_hang.h" #include "dxvk_image.h" #include "dxvk_instance.h" -#include "dxvk_latency.h" #include "dxvk_memory.h" #include "dxvk_meta_clear.h" #include "dxvk_objects.h" @@ -631,16 +630,6 @@ namespace dxvk { void requestCompileShader( const Rc& shader); - /** - * \brief Creates latency tracker for a presenter - * - * The specicfic implementation and parameters used - * depend on user configuration. - * \param [in] presenter Presenter instance - */ - Rc createLatencyTracker( - const Rc& presenter); - /** * \brief Presents a swap chain image * @@ -656,7 +645,6 @@ namespace dxvk { */ void presentImage( const Rc& presenter, - const Rc& tracker, uint64_t frameId, uint32_t rectCount, const VkRectLayerKHR* rects, @@ -668,14 +656,10 @@ namespace dxvk { * Submits the given command list to the device using * the given set of optional synchronization primitives. * \param [in] commandList The command list to submit - * \param [in] tracker Latency tracker - * \param [in] frameId Frame ID * \param [in] submissionId Submission timeline */ void submitCommandList( const Rc& commandList, - const Rc& tracker, - uint64_t frameId, uint64_t submissionId); /** diff --git a/src/dxvk/dxvk_latency.h b/src/dxvk/dxvk_latency.h deleted file mode 100644 index 9960087a..00000000 --- a/src/dxvk/dxvk_latency.h +++ /dev/null @@ -1,223 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "../util/util_likely.h" -#include "../util/util_time.h" - -#include "../util/rc/util_rc_ptr.h" - -#include "../vulkan/vulkan_loader.h" - -namespace dxvk { - - /** - * \brief Latency tracker statistics - */ - struct DxvkLatencyStats { - std::chrono::microseconds frameLatency; - std::chrono::microseconds sleepDuration; - }; - - - /** - * \brief Timings for a single tracked frame - */ - struct DxvkLatencyFrameData { - using time_point = dxvk::high_resolution_clock::time_point; - using duration = dxvk::high_resolution_clock::duration; - - uint64_t frameId = 0u; - uint64_t appFrameId = 0u; - time_point frameStart = time_point(); - time_point frameEnd = time_point(); - time_point cpuInputSample = time_point(); - time_point cpuSimBegin = time_point(); - time_point cpuSimEnd = time_point(); - time_point cpuRenderBegin = time_point(); - time_point cpuRenderEnd = time_point(); - time_point cpuPresentBegin = time_point(); - time_point cpuPresentEnd = time_point(); - time_point queueSubmit = time_point(); - time_point queuePresent = time_point(); - time_point gpuExecStart = time_point(); - time_point gpuExecEnd = time_point(); - time_point gpuIdleStart = time_point(); - time_point gpuIdleEnd = time_point(); - duration gpuIdleTime = duration(0u); - duration sleepDuration = duration(0u); - VkResult presentStatus = VK_NOT_READY; - }; - - - /** - * \brief Latency tracker - * - * Accumulates time stamps of certain parts of a frame. - */ - class DxvkLatencyTracker { - - public: - - virtual ~DxvkLatencyTracker() { } - - /** - * \brief Increments ref count - */ - void incRef() { - m_refCount.fetch_add(1); - } - - /** - * \brief Decrements ref count - * - * Destroys the object when there are no users left. - */ - void decRef() { - if (m_refCount.fetch_sub(1) == 1u) - delete this; - } - - /** - * \brief Checks whether automatic markers are needed - * - * Relevant for forwarding the latency tracker to the context. - * \returns \c true if automatic markers are necessary. - */ - virtual bool needsAutoMarkers() = 0; - - /** - * \brief Called when presentation begins on the CPU timeline - * - * Must happen before acquiring an image from the presenter. - * \param [in] frameId Current frame ID - */ - virtual void notifyCpuPresentBegin( - uint64_t frameId) = 0; - - /** - * \brief Called when the CS thread reaches a given frame - * - * Should be recorded into the CS thread after completing - * the previous frame on the application's CPU timeline. - * \param [in] frameId Current frame ID - */ - virtual void notifyCsRenderBegin( - uint64_t frameId) = 0; - - /** - * \brief Called when the CS thread completes a frame - * - * Should be recorded into the CS thread after recording - * presentation commands for that frame. - * \param [in] frameId Current frame ID - */ - virtual void notifyCsRenderEnd( - uint64_t frameId) = 0; - - /** - * \brief Called when presentation ends on the CPU timeline - * - * Must happen after acquiring an image for presentation, but - * before synchronizing with previous frames or performing - * latency sleep. The intention is to measure acquire delays. - * \param [in] frameId Current frame ID - */ - virtual void notifyCpuPresentEnd( - uint64_t frameId) = 0; - - /** - * \brief Called when a command list is submitted to the GPU - * - * \param [in] frameId Associated frame ID - */ - virtual void notifyQueueSubmit( - uint64_t frameId) = 0; - - /** - * \brief Called when a frame is queued for presentation - * - * \param [in] frameId Associated frame ID - */ - virtual void notifyQueuePresentBegin( - uint64_t frameId) = 0; - - /** - * \brief Called after a frame has been queued for presentation - * - * \param [in] frameId Associated frame ID - * \param [in] status Result of the present operation - */ - virtual void notifyQueuePresentEnd( - uint64_t frameId, - VkResult status) = 0; - - /** - * \brief Called when a submission begins execution on the GPU - * - * Any previous submissions will have completed by this time. This - * can be used to measure GPU idle time throughout a frame. - * \param [in] frameId Associated frame ID - */ - virtual void notifyGpuExecutionBegin( - uint64_t frameId) = 0; - - /** - * \brief Called when a submission completes execution on the GPU - * - * The previous submission will have completed by the time this - * gets called. This may be used to measure GPU idle time. - * \param [in] frameId Associated frame ID - */ - virtual void notifyGpuExecutionEnd( - uint64_t frameId) = 0; - - /** - * \brief Called when presentation of a given frame finishes on the GPU - * - * This is generally the last thing that happens within a frame. - * \param [in] frameId Associated frame ID - */ - virtual void notifyGpuPresentEnd( - uint64_t frameId) = 0; - - /** - * \brief Performs latency sleep and begins next frame - * - * Uses latency data from previous frames to estimate when to wake - * up the application thread in order to minimize input latency. - * \param [in] frameId Frame ID of the upcoming frame - * \param [in] maxFrameRate Maximum frame rate or refresh rate - */ - virtual void sleepAndBeginFrame( - uint64_t frameId, - double maxFrameRate) = 0; - - /** - * \brief Discards all current timing data - * - * Should be called to reset latency tracking in case - * presentation failed for any given frame. - */ - virtual void discardTimings() = 0; - - /** - * \brief Queries statistics for the given frame - * - * Returns statistics for the frame closest to \c frameId for - * which data is available. If no such frame exists, the stat - * counters will return 0. - * \param [in] frameId Frame to query - */ - virtual DxvkLatencyStats getStatistics( - uint64_t frameId) = 0; - - private: - - std::atomic m_refCount = { 0u }; - - }; - -} diff --git a/src/dxvk/dxvk_latency_builtin.cpp b/src/dxvk/dxvk_latency_builtin.cpp deleted file mode 100644 index 6515f762..00000000 --- a/src/dxvk/dxvk_latency_builtin.cpp +++ /dev/null @@ -1,408 +0,0 @@ -#include - -#include "dxvk_latency_builtin.h" - -#include "../util/log/log.h" - -#include "../util/util_string.h" - -namespace dxvk { - - DxvkBuiltInLatencyTracker::DxvkBuiltInLatencyTracker( - Rc presenter, - int32_t toleranceUs, - bool useNvLowLatency2) - : m_presenter(std::move(presenter)), - m_tolerance(std::chrono::duration_cast( - std::chrono::microseconds(std::max(toleranceUs, 0)))), - m_useNvLowLatency2(useNvLowLatency2) { - Logger::info(str::format("Latency control enabled, using ", - useNvLowLatency2 ? "VK_NV_low_latency2" : "built-in algorithm")); - } - - - DxvkBuiltInLatencyTracker::~DxvkBuiltInLatencyTracker() { - - } - - - bool DxvkBuiltInLatencyTracker::needsAutoMarkers() { - return true; - } - - - void DxvkBuiltInLatencyTracker::notifyCpuPresentBegin( - uint64_t frameId) { - std::unique_lock lock(m_mutex); - auto frame = findFrame(frameId); - - if (frame) - frame->cpuPresentBegin = dxvk::high_resolution_clock::now(); - } - - - void DxvkBuiltInLatencyTracker::notifyCpuPresentEnd( - uint64_t frameId) { - std::unique_lock lock(m_mutex); - auto frame = findFrame(frameId); - - if (frame) - frame->cpuPresentEnd = dxvk::high_resolution_clock::now(); - } - - - void DxvkBuiltInLatencyTracker::notifyCsRenderBegin( - uint64_t frameId) { - if (forwardLatencyMarkerNv(frameId)) { - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_SIMULATION_END_NV); - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_RENDERSUBMIT_START_NV); - } - } - - - void DxvkBuiltInLatencyTracker::notifyCsRenderEnd( - uint64_t frameId) { - if (forwardLatencyMarkerNv(frameId)) - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_RENDERSUBMIT_END_NV); - } - - - void DxvkBuiltInLatencyTracker::notifyQueueSubmit( - uint64_t frameId) { - std::unique_lock lock(m_mutex); - auto frame = findFrame(frameId); - - if (frame && frame->queueSubmit == time_point()) - frame->queueSubmit = dxvk::high_resolution_clock::now(); - } - - - void DxvkBuiltInLatencyTracker::notifyQueuePresentBegin( - uint64_t frameId) { - if (forwardLatencyMarkerNv(frameId)) - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_PRESENT_START_NV); - } - - - void DxvkBuiltInLatencyTracker::notifyQueuePresentEnd( - uint64_t frameId, - VkResult status) { - { std::unique_lock lock(m_mutex); - auto frame = findFrame(frameId); - - if (frame) { - frame->presentStatus = status; - frame->queuePresent = dxvk::high_resolution_clock::now(); - } - - m_cond.notify_one(); - } - - if (forwardLatencyMarkerNv(frameId)) - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_PRESENT_END_NV); - } - - - void DxvkBuiltInLatencyTracker::notifyGpuExecutionBegin( - uint64_t frameId) { - std::unique_lock lock(m_mutex); - auto frame = findFrame(frameId); - - if (frame) { - auto now = dxvk::high_resolution_clock::now(); - - if (frame->gpuExecStart == time_point()) - frame->gpuExecStart = now; - - if (frame->gpuIdleStart != time_point()) { - frame->gpuIdleTime += now - frame->gpuIdleStart; - frame->gpuIdleEnd = now; - } - } - } - - - void DxvkBuiltInLatencyTracker::notifyGpuExecutionEnd( - uint64_t frameId) { - std::unique_lock lock(m_mutex); - auto frame = findFrame(frameId); - - if (frame) { - auto now = dxvk::high_resolution_clock::now(); - - frame->gpuExecEnd = now; - frame->gpuIdleStart = now; - } - } - - - void DxvkBuiltInLatencyTracker::notifyGpuPresentEnd( - uint64_t frameId) { - std::unique_lock lock(m_mutex); - auto frame = findFrame(frameId); - - if (frame) - frame->frameEnd = dxvk::high_resolution_clock::now(); - - m_cond.notify_one(); - } - - - void DxvkBuiltInLatencyTracker::sleepAndBeginFrame( - uint64_t frameId, - double maxFrameRate) { - auto duration = m_useNvLowLatency2 - ? sleepNv(frameId, maxFrameRate) - : sleepBuiltin(frameId, maxFrameRate); - - { std::unique_lock lock(m_mutex); - - auto next = initFrame(frameId); - next->frameStart = dxvk::high_resolution_clock::now(); - next->sleepDuration = duration; - } - - if (m_useNvLowLatency2) { - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_SIMULATION_START_NV); - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_INPUT_SAMPLE_NV); - } - } - - - void DxvkBuiltInLatencyTracker::discardTimings() { - std::unique_lock lock(m_mutex); - m_validRangeBegin = m_validRangeEnd + 1u; - } - - - DxvkLatencyStats DxvkBuiltInLatencyTracker::getStatistics( - uint64_t frameId) { - std::unique_lock lock(m_mutex); - - DxvkLatencyStats stats = { }; - - while (frameId && frameId >= m_validRangeBegin) { - auto f = findFrame(frameId--); - - if (f && f->frameEnd != time_point()) { - stats.frameLatency = std::chrono::duration_cast(f->frameEnd - f->frameStart); - stats.sleepDuration = std::chrono::duration_cast(f->sleepDuration); - break; - } - } - - return stats; - } - - - DxvkBuiltInLatencyTracker::duration DxvkBuiltInLatencyTracker::sleepNv( - uint64_t frameId, - double maxFrameRate) { - // Set up low latency mode for subsequent frames. The presenter - // will figure out whether to reapply latency state or not. - VkLatencySleepModeInfoNV latencyMode = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV }; - latencyMode.lowLatencyMode = VK_TRUE; - latencyMode.lowLatencyBoost = VK_TRUE; - latencyMode.minimumIntervalUs = 0; - - if (m_envFpsLimit > 0.0) - maxFrameRate = m_envFpsLimit; - - if (maxFrameRate > 0.0) - latencyMode.minimumIntervalUs = uint64_t(1'000'000.0 / maxFrameRate); - - m_presenter->setLatencySleepModeNv(latencyMode); - - // Wait for previous present call to complete in order to - // avoid potential issues with oscillating frame times - bool presentSuccessful = false; - - { std::unique_lock lock(m_mutex); - auto curr = findFrame(frameId - 1u); - - if (curr && curr->cpuPresentEnd != time_point()) { - m_cond.wait(lock, [curr] { - return curr->presentStatus != VK_NOT_READY; - }); - - presentSuccessful = curr->presentStatus >= 0; - } - } - - if (!presentSuccessful) - return duration(0u); - - return m_presenter->latencySleepNv(); - } - - - DxvkBuiltInLatencyTracker::duration DxvkBuiltInLatencyTracker::sleepBuiltin( - uint64_t frameId, - double maxFrameRate) { - // Wait for all relevant timings to become available. This should - // generally not stall for very long if a maximum frame latency of - // 1 is enforced correctly by the swap chain. - std::unique_lock lock(m_mutex); - - for (uint32_t i = 2; i <= FrameCount; i++) { - auto f = findFrame(frameId - i); - - if (!f || f->cpuPresentEnd == time_point()) - return duration(0u); - - m_cond.wait(lock, [f] { - return f->frameEnd != time_point(); - }); - } - - // Wait for the current frame's present call to be processed. Our - // algorithm will otherwise get confused if Present stalls or if - // any CPU work from previous frames delays GPU execution of the - // current frame. - auto curr = findFrame(frameId - 1u); - - if (curr && curr->cpuPresentEnd != time_point()) { - m_cond.wait(lock, [curr] { - return curr->presentStatus != VK_NOT_READY; - }); - } - - // Frame entry of the last frame that fully completed - auto prev = findFrame(frameId - 2u); - - // The way we want to align subsequent frames depends on whether - // we are limited by GPU performance or display refresh. - // - // In either case, we estimate the amount of CPU time the game requires - // before any GPU work can start to be the delay between frame start and - // first submission, plus any GPU idle time during the frame. This is not - // accurate if there are forced GPU sync points, but we can't work around - // that in a meaningful way. - constexpr size_t EntryCount = FrameCount - 1u; - - std::array cpuTimes = { }; - std::array gpuTimes = { }; - - for (uint32_t i = 0; i < EntryCount; i++) { - auto f = findFrame(frameId - (i + 2u)); - - cpuTimes[i] = (f->queueSubmit - f->frameStart) + f->gpuIdleTime; - gpuTimes[i] = (f->gpuExecEnd - f->gpuExecStart) - f->gpuIdleTime; - } - - duration nextCpuTime = estimateTime(cpuTimes.data(), cpuTimes.size()); - duration nextGpuTime = estimateTime(gpuTimes.data(), gpuTimes.size()); - - // Compute the initial deadline based on GPU execution times - time_point gpuDeadline = prev->gpuExecEnd + 2u * nextGpuTime; - - // If we're rendering faster than refresh, use present_wait timings from - // previous frames as a starting point and compute an average in order to - // account for potentially erratic present_wait delays. - duration frameInterval = computeFrameInterval(maxFrameRate); - - if (frameInterval.count()) { - duration nextPresentFromPrev = duration(0u); - - for (uint32_t i = 2; i <= FrameCount; i++) { - auto f = findFrame(frameId - i); - - time_point deadline = f->frameEnd + i * frameInterval - m_tolerance; - nextPresentFromPrev += deadline - prev->frameEnd; - } - - time_point wsiDeadline = prev->frameEnd + (nextPresentFromPrev / int32_t(FrameCount - 1u)); - gpuDeadline = std::max(gpuDeadline, wsiDeadline); - } - - // Line up the next frame in such a way that the first GPU submission - // happens just before the current frame's final submission completes - time_point gpuStartTime = gpuDeadline - nextGpuTime; - time_point cpuStartTime = gpuStartTime - nextCpuTime - m_tolerance; - - time_point now = dxvk::high_resolution_clock::now(); - - // Release lock before actually sleeping, or - // it will affect the time measurements. - lock.unlock(); - - Sleep::sleepUntil(now, cpuStartTime); - return std::max(duration(0u), cpuStartTime - now); - } - - - DxvkLatencyFrameData* DxvkBuiltInLatencyTracker::initFrame( - uint64_t frameId) { - if (m_validRangeEnd + 1u != frameId) - m_validRangeBegin = frameId; - - if (m_validRangeBegin + FrameCount <= frameId) - m_validRangeBegin = frameId + 1u - FrameCount; - - m_validRangeEnd = frameId; - - auto& frame = m_frames[frameId % FrameCount]; - frame = DxvkLatencyFrameData(); - frame.frameId = frameId; - return &frame; - } - - - DxvkLatencyFrameData* DxvkBuiltInLatencyTracker::findFrame( - uint64_t frameId) { - return frameId >= m_validRangeBegin && frameId <= m_validRangeEnd - ? &m_frames[frameId % FrameCount] - : nullptr; - } - - - bool DxvkBuiltInLatencyTracker::forwardLatencyMarkerNv( - uint64_t frameId) { - if (!m_useNvLowLatency2) - return false; - - std::unique_lock lock(m_mutex); - return findFrame(frameId) != nullptr; - } - - - DxvkBuiltInLatencyTracker::duration DxvkBuiltInLatencyTracker::computeFrameInterval( - double maxFrameRate) { - if (m_envFpsLimit > 0.0) - maxFrameRate = m_envFpsLimit; - - return computeIntervalFromRate(maxFrameRate); - } - - - DxvkBuiltInLatencyTracker::duration DxvkBuiltInLatencyTracker::computeIntervalFromRate( - double frameRate) { - if (frameRate <= 0.0 || !std::isnormal(frameRate)) - return duration(0u); - - uint64_t ns = uint64_t(1'000'000'000.0 / frameRate); - return std::chrono::duration_cast(std::chrono::nanoseconds(ns)); - } - - - DxvkBuiltInLatencyTracker::duration DxvkBuiltInLatencyTracker::estimateTime( - const duration* frames, - size_t frameCount) { - // For each frame, find the median of its neighbours, then - // use the maximum of those medians as our estimate. - duration result = duration(0u); - - for (size_t i = 0u; i < frameCount - 2u; i++) { - duration a = frames[i]; - duration b = frames[i + 1]; - duration c = frames[i + 2]; - - duration min = std::min(std::min(a, b), c); - duration max = std::max(std::max(a, b), c); - - result = std::max(result, a + b + c - min - max); - } - - return result; - } -} diff --git a/src/dxvk/dxvk_latency_builtin.h b/src/dxvk/dxvk_latency_builtin.h deleted file mode 100644 index b361d343..00000000 --- a/src/dxvk/dxvk_latency_builtin.h +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -#include - -#include "dxvk_latency.h" -#include "dxvk_presenter.h" - -#include "../util/thread.h" - -#include "../util/util_sleep.h" -#include "../util/util_time.h" - -#include "../util/config/config.h" - -#include "../util/sync/sync_spinlock.h" - -namespace dxvk { - - /** - * \brief Built-in latency tracker - * - * Implements a simple latency reduction algorithm - * based on CPU timestamps received from the backend. - */ - class DxvkBuiltInLatencyTracker : public DxvkLatencyTracker { - using time_point = typename DxvkLatencyFrameData::time_point; - using duration = typename DxvkLatencyFrameData::duration; - - constexpr static size_t FrameCount = 8u; - public: - - DxvkBuiltInLatencyTracker( - Rc presenter, - int32_t toleranceUs, - bool useNvLowLatency2); - - ~DxvkBuiltInLatencyTracker(); - - bool needsAutoMarkers(); - - void notifyCpuPresentBegin( - uint64_t frameId); - - void notifyCpuPresentEnd( - uint64_t frameId); - - void notifyCsRenderBegin( - uint64_t frameId); - - void notifyCsRenderEnd( - uint64_t frameId); - - void notifyQueueSubmit( - uint64_t frameId); - - void notifyQueuePresentBegin( - uint64_t frameId); - - void notifyQueuePresentEnd( - uint64_t frameId, - VkResult status); - - void notifyGpuExecutionBegin( - uint64_t frameId); - - void notifyGpuExecutionEnd( - uint64_t frameId); - - void notifyGpuPresentEnd( - uint64_t frameId); - - void sleepAndBeginFrame( - uint64_t frameId, - double maxFrameRate); - - void discardTimings(); - - DxvkLatencyStats getStatistics( - uint64_t frameId); - - private: - - Rc m_presenter; - - dxvk::mutex m_mutex; - dxvk::condition_variable m_cond; - - duration m_tolerance; - - double m_envFpsLimit = 0.0; - bool m_useNvLowLatency2 = false; - - std::array m_frames = { }; - - uint64_t m_validRangeBegin = 0u; - uint64_t m_validRangeEnd = 0u; - - duration sleepNv( - uint64_t frameId, - double maxFrameRate); - - duration sleepBuiltin( - uint64_t frameId, - double maxFrameRate); - - DxvkLatencyFrameData* initFrame( - uint64_t frameId); - - DxvkLatencyFrameData* findFrame( - uint64_t frameId); - - bool forwardLatencyMarkerNv( - uint64_t frameId); - - duration computeFrameInterval( - double maxFrameRate); - - static duration computeIntervalFromRate( - double frameRate); - - static duration estimateTime( - const duration* frames, - size_t frameCount); - - }; - -} diff --git a/src/dxvk/dxvk_latency_reflex.cpp b/src/dxvk/dxvk_latency_reflex.cpp deleted file mode 100644 index fdb6a974..00000000 --- a/src/dxvk/dxvk_latency_reflex.cpp +++ /dev/null @@ -1,530 +0,0 @@ -#include "dxvk_latency_reflex.h" - -namespace dxvk { - - DxvkReflexLatencyTrackerNv::DxvkReflexLatencyTrackerNv( - const Rc& presenter) - : m_presenter(presenter) { - - } - - DxvkReflexLatencyTrackerNv::~DxvkReflexLatencyTrackerNv() { - - } - - - bool DxvkReflexLatencyTrackerNv::needsAutoMarkers() { - // In markerless mode we want to avoid submitting - // any markers at all and ignore the context - return false; - } - - - void DxvkReflexLatencyTrackerNv::notifyCpuPresentBegin( - uint64_t frameId) { - std::lock_guard lock(m_mutex); - - if (m_lastPresentAppFrameId) { - uint64_t expectedFrameId = lookupFrameId(m_lastPresentAppFrameId); - - if (frameId != expectedFrameId) { - // This is a normal occurence after a swapchain recreation, or if - // tracking got reset for any reason. Remap the current app frame - // to the current internal frame, and map any app frames with a - // higher frame ID to subsequent frame IDs in order to fix the - // mapping; we should catch up within a few frames. - Logger::warn(str::format("Reflex: Expected internal frame ID ", - expectedFrameId, " for ", m_lastPresentAppFrameId, ", got ", frameId)); - - uint64_t nextAppFrameId = m_lastPresentAppFrameId; - uint64_t nextDxvkFrameId = frameId; - - auto entry = m_appToDxvkFrameIds.find(nextAppFrameId); - - while (entry != m_appToDxvkFrameIds.end()) { - nextAppFrameId = entry->first; - - mapFrameId(nextAppFrameId, nextDxvkFrameId++); - - entry = m_appToDxvkFrameIds.upper_bound(nextAppFrameId); - } - - m_nextAllocFrameId = nextDxvkFrameId; - m_nextValidFrameId = nextDxvkFrameId + 1u; - } - - m_lowLatencyNoMarkers = false; - } else if (m_lowLatencyMode) { - // Game seemingly doesn't use markers? - if (!m_lowLatencyNoMarkers) { - Logger::warn("Reflex: No latency markers provided"); - m_lowLatencyNoMarkers = true; - reset(); - } - - // Update sleep duration since we haven't had the chance yet - auto& frame = getFrameData(frameId); - frame.sleepDuration = m_lastSleepDuration; - - m_lastSleepDuration = duration(0u); - } - - m_lastPresentAppFrameId = 0u; - } - - - void DxvkReflexLatencyTrackerNv::notifyCpuPresentEnd( - uint64_t frameId) { - std::lock_guard lock(m_mutex); - m_lastPresentQueued = frameId; - } - - - void DxvkReflexLatencyTrackerNv::notifyCsRenderBegin( - uint64_t frameId) { - bool setMarker = false; - - { std::lock_guard lock(m_mutex); - - auto& frame = getFrameData(frameId); - setMarker = frame.appFrameId && frameId >= m_nextValidFrameId; - } - - if (setMarker) - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_RENDERSUBMIT_START_NV); - } - - - void DxvkReflexLatencyTrackerNv::notifyCsRenderEnd( - uint64_t frameId) { - bool setMarker = false; - - { std::lock_guard lock(m_mutex); - - auto& frame = getFrameData(frameId); - setMarker = frame.appFrameId && frameId >= m_nextValidFrameId; - } - - if (setMarker) - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_RENDERSUBMIT_END_NV); - } - - - void DxvkReflexLatencyTrackerNv::notifyQueueSubmit( - uint64_t frameId) { - std::lock_guard lock(m_mutex); - auto& frame = getFrameData(frameId); - - if (frame.queueSubmit == time_point()) - frame.queueSubmit = dxvk::high_resolution_clock::now(); - } - - - void DxvkReflexLatencyTrackerNv::notifyQueuePresentBegin( - uint64_t frameId) { - bool setMarker = false; - - { std::lock_guard lock(m_mutex); - - auto& frame = getFrameData(frameId); - setMarker = frame.appFrameId && frameId >= m_nextValidFrameId; - } - - if (setMarker) - m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_PRESENT_START_NV); - } - - - void DxvkReflexLatencyTrackerNv::notifyQueuePresentEnd( - uint64_t frameId, - VkResult status) { - bool setMarker = false; - - { std::lock_guard lock(m_mutex); - - auto& frame = getFrameData(frameId); - setMarker = frame.appFrameId && frameId >= m_nextValidFrameId; - } - - time_point cpuTime = time_point(); - - if (setMarker) - cpuTime = m_presenter->setLatencyMarkerNv(frameId, VK_LATENCY_MARKER_PRESENT_END_NV); - - std::lock_guard lock(m_mutex); - - if (setMarker) { - auto& frame = getFrameData(frameId); - frame.presentStatus = status; - frame.queuePresent = cpuTime; - } - - // Ignore errors or we might never wake up a waiting thread - m_lastPresentComplete = frameId; - m_cond.notify_all(); - } - - - void DxvkReflexLatencyTrackerNv::notifyGpuExecutionBegin( - uint64_t frameId) { - std::lock_guard lock(m_mutex); - auto now = dxvk::high_resolution_clock::now(); - - auto& frame = getFrameData(frameId); - frame.gpuIdleEnd = now; - - if (frame.gpuExecStart == time_point()) - frame.gpuExecStart = now; - - if (frame.gpuIdleStart != time_point()) - frame.gpuIdleTime += frame.gpuIdleEnd - frame.gpuIdleStart; - } - - - void DxvkReflexLatencyTrackerNv::notifyGpuExecutionEnd( - uint64_t frameId) { - std::lock_guard lock(m_mutex); - auto now = dxvk::high_resolution_clock::now(); - - auto& frame = getFrameData(frameId); - frame.gpuExecEnd = now; - frame.gpuIdleStart = now; - } - - - void DxvkReflexLatencyTrackerNv::notifyGpuPresentEnd( - uint64_t frameId) { - std::lock_guard lock(m_mutex); - - auto& frame = getFrameData(frameId); - frame.frameEnd = dxvk::high_resolution_clock::now(); - - m_lastCompletedFrameId = frameId; - } - - - void DxvkReflexLatencyTrackerNv::sleepAndBeginFrame( - uint64_t frameId, - double maxFrameRate) { - std::lock_guard lock(m_mutex); - m_lastNoMarkerFrameId = frameId; - - if (m_lowLatencyMode) { - auto& frame = getFrameData(frameId); - frame.frameStart = dxvk::high_resolution_clock::now(); - } - } - - - void DxvkReflexLatencyTrackerNv::discardTimings() { - std::lock_guard lock(m_mutex); - reset(); - } - - - DxvkLatencyStats DxvkReflexLatencyTrackerNv::getStatistics( - uint64_t frameId) { - std::lock_guard lock(m_mutex); - - if (!m_lastCompletedFrameId) - return DxvkLatencyStats(); - - auto& frame = getFrameData(m_lastCompletedFrameId); - - if (frame.frameEnd == time_point()) - return DxvkLatencyStats(); - - time_point frameStart = frame.cpuSimBegin; - - if (frame.cpuInputSample != time_point()) - frameStart = frame.cpuInputSample; - - if (frameStart == time_point()) - frameStart = frame.frameStart; - - if (frameStart == time_point()) - return DxvkLatencyStats(); - - DxvkLatencyStats stats = { }; - stats.frameLatency = std::chrono::duration_cast(frame.frameEnd - frameStart); - stats.sleepDuration = std::chrono::duration_cast(frame.sleepDuration); - return stats; - } - - - void DxvkReflexLatencyTrackerNv::setLatencySleepMode( - bool enableLowLatency, - bool enableBoost, - uint64_t minIntervalUs) { - if (m_lowLatencyMode != enableLowLatency) - Logger::info(str::format("Reflex: Low latency mode ", enableLowLatency ? "enabled" : "disabled")); - - VkLatencySleepModeInfoNV modeInfo = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV }; - modeInfo.lowLatencyMode = enableLowLatency; - modeInfo.lowLatencyBoost = enableBoost; - modeInfo.minimumIntervalUs = minIntervalUs; - - m_presenter->setLatencySleepModeNv(modeInfo); - - m_lowLatencyMode = enableLowLatency; - } - - - void DxvkReflexLatencyTrackerNv::setLatencyMarker( - uint64_t appFrameId, - VkLatencyMarkerNV marker) { - std::unique_lock lock(m_mutex); - - // Find frame ID. If this is the first marker in a new frame, - // try to map it to a new internal frame ID. - uint64_t frameId = lookupFrameId(appFrameId); - - if (!frameId && (marker == VK_LATENCY_MARKER_SIMULATION_START_NV - || marker == VK_LATENCY_MARKER_RENDERSUBMIT_START_NV - || marker == VK_LATENCY_MARKER_INPUT_SAMPLE_NV)) - frameId = allocateFrameId(appFrameId); - - // This can hapen if we reset tracking state and receive - // a stray present or render submit marker. Ignore these - // so that the next presents can recalibrate properly. - if (!frameId) - return; - - // We use present markers to correlate app frame IDs - // with internal frame IDs, so always write this back. - if (marker == VK_LATENCY_MARKER_PRESENT_START_NV) - m_lastPresentAppFrameId = appFrameId; - - // Don't submit markers for invalid frames since - // that could potentially confuse the algorithm - if (frameId < m_nextValidFrameId) - return; - - // Need to unlock here so we don't deadlock with the presenter - auto cpuTime = dxvk::high_resolution_clock::now(); - - if (marker == VK_LATENCY_MARKER_INPUT_SAMPLE_NV - || marker == VK_LATENCY_MARKER_SIMULATION_START_NV - || marker == VK_LATENCY_MARKER_SIMULATION_END_NV - || marker == VK_LATENCY_MARKER_TRIGGER_FLASH_NV) { - lock.unlock(); - - cpuTime = m_presenter->setLatencyMarkerNv(frameId, marker); - - lock.lock(); - } - - // Store CPU timestamp to correlate times - auto& frame = getFrameData(frameId); - - switch (marker) { - case VK_LATENCY_MARKER_INPUT_SAMPLE_NV: - frame.cpuInputSample = cpuTime; - break; - - case VK_LATENCY_MARKER_SIMULATION_START_NV: - frame.cpuSimBegin = cpuTime; - - if (m_lastSleepDuration != duration(0u)) - frame.sleepDuration = std::exchange(m_lastSleepDuration, duration(0u)); - break; - - case VK_LATENCY_MARKER_SIMULATION_END_NV: - frame.cpuSimEnd = cpuTime; - break; - - case VK_LATENCY_MARKER_RENDERSUBMIT_START_NV: - frame.cpuRenderBegin = cpuTime; - break; - - case VK_LATENCY_MARKER_RENDERSUBMIT_END_NV: - frame.cpuRenderEnd = cpuTime; - break; - - case VK_LATENCY_MARKER_PRESENT_START_NV: - frame.cpuPresentBegin = cpuTime; - break; - - case VK_LATENCY_MARKER_PRESENT_END_NV: - frame.cpuPresentEnd = cpuTime; - break; - - case VK_LATENCY_MARKER_TRIGGER_FLASH_NV: - // Nothing to do here - break; - - default: - Logger::warn(str::format("Reflex: Unknown marker ", marker)); - } - } - - - void DxvkReflexLatencyTrackerNv::latencySleep() { - { std::unique_lock lock(m_mutex); - // If the app doesn't use markers, wait for the previous present - // call to complete so that we don't confuse the algorithm by - // sleeping at random times relative to actual graphics work. - if (m_lowLatencyNoMarkers) { - m_cond.wait(lock, [this] { - return m_lastPresentComplete >= m_lastPresentQueued; - }); - } - } - - // Actually sleep and write back sleep duration for the next frame - auto sleepDuration = m_presenter->latencySleepNv(); - - std::lock_guard lock(m_mutex); - m_lastSleepAppFrameId = m_lastBeginAppFrameId; - m_lastSleepDuration = sleepDuration; - - if (m_lowLatencyNoMarkers && m_lastNoMarkerFrameId > m_lastPresentQueued) { - // In markerless mode, assume that this gets called before any - // work is done for the next frame and update the frame start - // time accordingly. - auto& frame = getFrameData(m_lastNoMarkerFrameId); - frame.frameStart = dxvk::high_resolution_clock::now(); - } - } - - - uint32_t DxvkReflexLatencyTrackerNv::getFrameReports( - uint32_t maxCount, - DxvkReflexFrameReport* reports) { - small_vector nvReports(maxCount); - - for (uint32_t i = 0; i < maxCount; i++) - nvReports[i] = { VK_STRUCTURE_TYPE_LATENCY_TIMINGS_FRAME_REPORT_NV }; - - // Adjust some statistics so that we actually return the - // correct timestamps for the application-defined markers - uint32_t count = m_presenter->getLatencyTimingsNv(maxCount, nvReports.data()); - - // Only lock after calling into the presenter to avoid deadlocks - std::lock_guard lock(m_mutex); - - for (uint32_t i = 0; i < count; i++) { - auto& report = nvReports[i]; - const auto& currFrame = m_frames[report.presentID % FrameCount]; - - if (report.presentID != currFrame.frameId || report.presentID < m_nextValidFrameId) - return 0; - - report.presentID = currFrame.appFrameId; - - // These represent when the CS thread starts processing the frame - report.driverStartTimeUs = report.renderSubmitStartTimeUs; - report.driverEndTimeUs = report.renderSubmitEndTimeUs; - - // Return when the app set these markers rather than the time when - // we forward them to the driver - report.renderSubmitStartTimeUs = mapFrameTimestampToReportUs(currFrame, report, currFrame.cpuRenderBegin); - report.renderSubmitEndTimeUs = mapFrameTimestampToReportUs(currFrame, report, currFrame.cpuRenderEnd); - report.presentStartTimeUs = mapFrameTimestampToReportUs(currFrame, report, currFrame.cpuPresentBegin); - report.presentEndTimeUs = mapFrameTimestampToReportUs(currFrame, report, currFrame.cpuPresentEnd); - - // Documentation for the OS timers seems nonsensical, but it seems to - // be the time from the the first submission to the end of the frame - report.osRenderQueueStartTimeUs = mapFrameTimestampToReportUs(currFrame, report, currFrame.queueSubmit); - report.osRenderQueueEndTimeUs = report.gpuRenderEndTimeUs; - - // Apparently gpuRenderEndTime is when presentation completes rather - // than rendering, so we need to compute the active render time using - // our own timestamps - auto gpuActiveTime = currFrame.gpuExecEnd - currFrame.gpuExecStart - currFrame.gpuIdleTime; - - reports[i].report = report; - reports[i].gpuActiveTimeUs = std::max(0u, - std::chrono::duration_cast(gpuActiveTime).count()); - } - - return count; - } - - - uint64_t DxvkReflexLatencyTrackerNv::frameIdFromAppFrameId( - uint64_t appFrameId) { - std::lock_guard lock(m_mutex); - return lookupFrameId(appFrameId); - } - - - DxvkReflexLatencyFrameData& DxvkReflexLatencyTrackerNv::getFrameData( - uint64_t dxvkFrameId) { - auto& frameData = m_frames[dxvkFrameId % FrameCount]; - - if (frameData.frameId != dxvkFrameId) { - m_appToDxvkFrameIds.erase(frameData.appFrameId); - - frameData = DxvkReflexLatencyFrameData(); - frameData.frameId = dxvkFrameId; - } - - return frameData; - } - - - uint64_t DxvkReflexLatencyTrackerNv::lookupFrameId( - uint64_t appFrameId) { - auto entry = m_appToDxvkFrameIds.find(appFrameId); - - if (entry == m_appToDxvkFrameIds.end()) - return 0u; - - return entry->second; - } - - - uint64_t DxvkReflexLatencyTrackerNv::allocateFrameId( - uint64_t appFrameId) { - if (appFrameId <= m_lastBeginAppFrameId) { - Logger::warn(str::format("Reflex: Frame ID ", appFrameId, " not monotonic, last was ", m_lastBeginAppFrameId)); - reset(); - } - - uint64_t frameId = m_nextAllocFrameId++; - mapFrameId(appFrameId, frameId); - - m_lastBeginAppFrameId = appFrameId; - return frameId; - } - - - void DxvkReflexLatencyTrackerNv::mapFrameId( - uint64_t appFrameId, - uint64_t dxvkFrameId) { - while (m_appToDxvkFrameIds.size() > FrameCount) - m_appToDxvkFrameIds.erase(m_appToDxvkFrameIds.begin()); - - m_appToDxvkFrameIds.insert_or_assign(appFrameId, dxvkFrameId); - getFrameData(dxvkFrameId).appFrameId = appFrameId; - } - - - void DxvkReflexLatencyTrackerNv::reset() { - m_nextValidFrameId = uint64_t(-1); - - m_lastSleepDuration = duration(0u); - - m_lastBeginAppFrameId = 0u; - m_lastPresentAppFrameId = 0u; - - for (size_t i = 0; i < FrameCount; i++) - m_frames[i].appFrameId = 0u; - - m_appToDxvkFrameIds.clear(); - } - - - uint64_t DxvkReflexLatencyTrackerNv::mapFrameTimestampToReportUs( - const DxvkReflexLatencyFrameData& frame, - const VkLatencyTimingsFrameReportNV& report, - time_point timestamp) { - if (frame.cpuSimBegin == time_point() || !report.simStartTimeUs) - return 0u; - - int64_t diffUs = std::chrono::duration_cast(timestamp - frame.cpuSimBegin).count(); - return report.simStartTimeUs + diffUs; - } - -} diff --git a/src/dxvk/dxvk_latency_reflex.h b/src/dxvk/dxvk_latency_reflex.h deleted file mode 100644 index c3775227..00000000 --- a/src/dxvk/dxvk_latency_reflex.h +++ /dev/null @@ -1,199 +0,0 @@ -#pragma once - -#include -#include - -#include "dxvk_latency.h" -#include "dxvk_presenter.h" - -#include "../util/thread.h" - -#include "../util/util_sleep.h" -#include "../util/util_time.h" - -#include "../util/config/config.h" - -#include "../util/sync/sync_spinlock.h" - -namespace dxvk { - - /** - * \brief Reflex frame info - * - * Stores frame ID mapping and all sorts of time stamps - * that are used for latency sleep or frame reports. - */ - using DxvkReflexLatencyFrameData = DxvkLatencyFrameData; - - - /** - * \brief Additional frame report info - */ - struct DxvkReflexFrameReport { - VkLatencyTimingsFrameReportNV report; - uint64_t gpuActiveTimeUs; - }; - - - /** - * \brief Built-in latency tracker based on VK_NV_low_latency2 - * - * Implements a simple latency reduction algorithm - * based on CPU timestamps received from the backend. - */ - class DxvkReflexLatencyTrackerNv : public DxvkLatencyTracker { - using time_point = typename DxvkReflexLatencyFrameData::time_point; - using duration = typename DxvkReflexLatencyFrameData::duration; - - // Keep data for a large number of frames around to support - // retrieving statistics from the driver properly. - constexpr static size_t FrameCount = 256u; - public: - - DxvkReflexLatencyTrackerNv( - const Rc& presenter); - - ~DxvkReflexLatencyTrackerNv(); - - bool needsAutoMarkers(); - - void notifyCpuPresentBegin( - uint64_t frameId); - - void notifyCpuPresentEnd( - uint64_t frameId); - - void notifyCsRenderBegin( - uint64_t frameId); - - void notifyCsRenderEnd( - uint64_t frameId); - - void notifyQueueSubmit( - uint64_t frameId); - - void notifyQueuePresentBegin( - uint64_t frameId); - - void notifyQueuePresentEnd( - uint64_t frameId, - VkResult status); - - void notifyGpuExecutionBegin( - uint64_t frameId); - - void notifyGpuExecutionEnd( - uint64_t frameId); - - void notifyGpuPresentEnd( - uint64_t frameId); - - void sleepAndBeginFrame( - uint64_t frameId, - double maxFrameRate); - - void discardTimings(); - - DxvkLatencyStats getStatistics( - uint64_t frameId); - - /** - * \brief Sets Reflex state - * - * \param [in] enableLowLatency Whether to enable latency control - * \param [in] enableBoost Whether to enable boost - * \param [in] minIntervalUs Minimum frame interval - */ - void setLatencySleepMode( - bool enableLowLatency, - bool enableBoost, - uint64_t minIntervalUs); - - /** - * \brief Sets latency marker from application - * - * \param [in] appFrameId Application-provided frame ID - * \param [in] marker Marker to set - */ - void setLatencyMarker( - uint64_t appFrameId, - VkLatencyMarkerNV marker); - - /** - * \brief Performs latency sleep - */ - void latencySleep(); - - /** - * \brief Retrieves frame reports - * - * \param [in] maxCount Maximum number of reports - * \param [out] reports Frame reports - * \returns Number of reports retrieved - */ - uint32_t getFrameReports( - uint32_t maxCount, - DxvkReflexFrameReport* reports); - - /** - * \brief Looks up frame ID from application frame ID - * - * \param [in] appFrameId Application-provided frame ID - * \returns Internal frame ID, or 0 if none was found - */ - uint64_t frameIdFromAppFrameId( - uint64_t appFrameId); - - private: - - Rc m_presenter; - - dxvk::mutex m_mutex; - dxvk::condition_variable m_cond; - - uint64_t m_lastBeginAppFrameId = 0u; - uint64_t m_lastSleepAppFrameId = 0u; - uint64_t m_lastPresentAppFrameId = 0u; - - uint64_t m_nextAllocFrameId = 1u; - uint64_t m_nextValidFrameId = uint64_t(-1); - - uint64_t m_lastCompletedFrameId = 0u; - - uint64_t m_lastPresentQueued = 0u; - uint64_t m_lastPresentComplete = 0u; - - uint64_t m_lastNoMarkerFrameId = 0u; - - duration m_lastSleepDuration = duration(0u); - - bool m_lowLatencyMode = false; - bool m_lowLatencyNoMarkers = false; - - std::array m_frames = { }; - - std::map m_appToDxvkFrameIds; - - DxvkReflexLatencyFrameData& getFrameData( - uint64_t dxvkFrameId); - - uint64_t lookupFrameId( - uint64_t appFrameId); - - uint64_t allocateFrameId( - uint64_t appFrameId); - - void mapFrameId( - uint64_t appFrameId, - uint64_t dxvkFrameId); - - void reset(); - - static uint64_t mapFrameTimestampToReportUs( - const DxvkReflexLatencyFrameData& frame, - const VkLatencyTimingsFrameReportNV& report, - time_point timestamp); - - }; - -} diff --git a/src/dxvk/dxvk_options.cpp b/src/dxvk/dxvk_options.cpp index 965baacc..8478b446 100644 --- a/src/dxvk/dxvk_options.cpp +++ b/src/dxvk/dxvk_options.cpp @@ -19,8 +19,6 @@ namespace dxvk { useRawSsbo = config.getOption("dxvk.useRawSsbo", Tristate::Auto); hud = config.getOption("dxvk.hud", ""); tearFree = config.getOption("dxvk.tearFree", Tristate::Auto); - latencySleep = config.getOption("dxvk.latencySleep", Tristate::Auto); - latencyTolerance = config.getOption ("dxvk.latencyTolerance", 1000); disableNvLowLatency2 = config.getOption("dxvk.disableNvLowLatency2", Tristate::Auto); hideIntegratedGraphics = config.getOption ("dxvk.hideIntegratedGraphics", false); zeroMappedMemory = config.getOption ("dxvk.zeroMappedMemory", false); diff --git a/src/dxvk/dxvk_options.h b/src/dxvk/dxvk_options.h index ebd1bc1f..44f62ecf 100644 --- a/src/dxvk/dxvk_options.h +++ b/src/dxvk/dxvk_options.h @@ -46,12 +46,6 @@ namespace dxvk { /// or FIFO_RELAXED (if false) present mode Tristate tearFree = Tristate::Auto; - /// Enables latency sleep - Tristate latencySleep = Tristate::Auto; - - /// Latency tolerance, in microseconds - int32_t latencyTolerance = 0u; - /// Disable VK_NV_low_latency2. This extension /// appears to be all sorts of broken on 32-bit. Tristate disableNvLowLatency2 = Tristate::Auto; diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 0fbbbc99..21fe2308 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -180,7 +180,6 @@ namespace dxvk { VkResult Presenter::presentImage( uint64_t frameId, - const Rc& tracker, uint32_t rectCount, const VkRectLayerKHR* rects) { PresenterSync& currSync = m_semaphores.at(m_frameIndex); @@ -310,7 +309,6 @@ namespace dxvk { // Add frame to waiter queue with current properties PresenterFrame frame; frame.frameId = frameId; - frame.tracker = tracker; frame.mode = m_presentMode; frame.result = status; frame.targetTimeHost = frameTargetTime.presentTimeHost; @@ -350,9 +348,7 @@ namespace dxvk { } - void Presenter::signalFrame( - uint64_t frameId, - const Rc& tracker) { + void Presenter::signalFrame(uint64_t frameId) { if (m_signal == nullptr || !frameId) return; @@ -2155,13 +2151,6 @@ namespace dxvk { Logger::err(str::format("Presenter: vkWaitForPresentKHR failed: ", vr)); } - // Signal latency tracker right away to get more accurate - // measurements if the frame rate limiter is enabled. - if (frame.tracker) { - frame.tracker->notifyGpuPresentEnd(frame.frameId); - frame.tracker = nullptr; - } - // Update timing feedback. There is no guarantee when data becomes available, // but if timing is disabled then we should base feedback on present_wait. updatePresentTimingFeedback(frame.frameId, frame.stageQueries); diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index ac06b9f3..1efa301e 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -18,7 +18,6 @@ #include "dxvk_format.h" #include "dxvk_image.h" -#include "dxvk_latency.h" namespace dxvk { @@ -66,7 +65,6 @@ namespace dxvk { */ struct PresenterFrame { uint64_t frameId = 0u; - Rc tracker = nullptr; VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR; VkResult result = VK_NOT_READY; uint64_t targetTimeHost = 0u; @@ -190,14 +188,12 @@ namespace dxvk { * * Presents the last successfuly acquired image. * \param [in] frameId Frame number. - * \param [in] tracker Latency tracker * \param [in] rectCount Number of dirty rects * \param [in] rects Dirty rectangles * \returns Status of the operation */ VkResult presentImage( uint64_t frameId, - const Rc& tracker, uint32_t rectCount, const VkRectLayerKHR* rects); @@ -209,11 +205,8 @@ namespace dxvk { * called before GPU work prior to the present submission has * completed in order to maintain consistency. * \param [in] frameId Frame ID - * \param [in] tracker Latency tracker */ - void signalFrame( - uint64_t frameId, - const Rc& tracker); + void signalFrame(uint64_t frameId); /** * \brief Changes sync interval diff --git a/src/dxvk/dxvk_queue.cpp b/src/dxvk/dxvk_queue.cpp index 17007039..9c82015a 100644 --- a/src/dxvk/dxvk_queue.cpp +++ b/src/dxvk/dxvk_queue.cpp @@ -46,7 +46,6 @@ namespace dxvk { void DxvkSubmissionQueue::submit( DxvkSubmitInfo submitInfo, - DxvkLatencyInfo latencyInfo, uint64_t submissionId) { std::unique_lock lock(m_mutex); @@ -57,7 +56,6 @@ namespace dxvk { DxvkSubmitEntry entry = { }; entry.timeline = submissionId; entry.submit = std::move(submitInfo); - entry.latency = std::move(latencyInfo); m_submitQueue.push(std::move(entry)); m_appendCond.notify_all(); @@ -66,14 +64,12 @@ namespace dxvk { void DxvkSubmissionQueue::present( DxvkPresentInfo presentInfo, - DxvkLatencyInfo latencyInfo, uint64_t submissionId) { std::unique_lock lock(m_mutex); DxvkSubmitEntry entry = { }; entry.timeline = submissionId; entry.present = std::move(presentInfo); - entry.latency = std::move(latencyInfo); m_submitQueue.push(std::move(entry)); m_appendCond.notify_all(); @@ -132,9 +128,6 @@ namespace dxvk { void DxvkSubmissionQueue::submitCmdLists() { env::setThreadName("dxvk-submit"); - uint64_t trackedSubmitId = 0u; - uint64_t trackedPresentId = 0u; - while (!m_stopped.load()) { DxvkSubmitEntry entry; @@ -161,31 +154,12 @@ namespace dxvk { m_callback(true); if (entry.submit.cmdList != nullptr) { - if (entry.latency.tracker) { - entry.latency.tracker->notifyQueueSubmit(entry.latency.frameId); - - if (!trackedSubmitId && entry.latency.frameId > trackedPresentId) - trackedSubmitId = entry.latency.frameId; - } - status = entry.submit.cmdList->submit( - m_semaphores, m_timelines, trackedSubmitId); + m_semaphores, m_timelines); entry.timelines = m_timelines; } else if (entry.present.presenter != nullptr) { - if (entry.latency.tracker) - entry.latency.tracker->notifyQueuePresentBegin(entry.latency.frameId); - - status = entry.present.presenter->presentImage( - entry.present.frameId, entry.latency.tracker, + status = entry.present.presenter->presentImage(entry.present.frameId, entry.present.rects.size(), entry.present.rects.data()); - - if (entry.latency.tracker) { - entry.latency.tracker->notifyQueuePresentEnd( - entry.latency.frameId, status); - - trackedPresentId = entry.latency.frameId; - trackedSubmitId = 0u; - } } if (m_callback) @@ -258,9 +232,6 @@ namespace dxvk { std::array semaphores = { m_semaphores.graphics, m_semaphores.transfer }; std::array timelines = { entry.timelines.graphics, entry.timelines.transfer }; - if (entry.latency.tracker) - entry.latency.tracker->notifyGpuExecutionBegin(entry.latency.frameId); - VkSemaphoreWaitInfo waitInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO }; waitInfo.semaphoreCount = semaphores.size(); waitInfo.pSemaphores = semaphores.data(); @@ -274,9 +245,6 @@ namespace dxvk { Logger::err("DxvkSubmissionQueue: Timeout occured, GPU likely hung."); status = VK_ERROR_DEVICE_LOST; } - - if (entry.latency.tracker && status == VK_SUCCESS) - entry.latency.tracker->notifyGpuExecutionEnd(entry.latency.frameId); } if (status == VK_ERROR_DEVICE_LOST && m_checkpoints) @@ -292,7 +260,7 @@ namespace dxvk { // Signal the frame and then immediately destroy the reference. // This is necessary since the front-end may want to explicitly // destroy the presenter object. - entry.present.presenter->signalFrame(entry.present.frameId, entry.latency.tracker); + entry.present.presenter->signalFrame(entry.present.frameId); entry.present.presenter = nullptr; } diff --git a/src/dxvk/dxvk_queue.h b/src/dxvk/dxvk_queue.h index 07b56b8a..267396c5 100644 --- a/src/dxvk/dxvk_queue.h +++ b/src/dxvk/dxvk_queue.h @@ -9,7 +9,6 @@ #include "../util/thread.h" #include "dxvk_cmdlist.h" -#include "dxvk_latency.h" #include "dxvk_presenter.h" namespace dxvk { @@ -41,18 +40,6 @@ namespace dxvk { }; - /** - * \brief Latency info - * - * Optionally stores a latency tracker - * and the associated frame ID. - */ - struct DxvkLatencyInfo { - Rc tracker; - uint64_t frameId = 0; - }; - - /** * \brief Submission queue entry */ @@ -60,7 +47,6 @@ namespace dxvk { uint64_t timeline = 0u; DxvkSubmitInfo submit; DxvkPresentInfo present; - DxvkLatencyInfo latency; DxvkTimelineSemaphoreValues timelines; }; @@ -108,12 +94,10 @@ namespace dxvk { * dedicated submission thread. Use this to take * the submission overhead off the calling thread. * \param [in] submitInfo Submission parameters - * \param [in] latencyInfo Latency tracker info * \param [out] status Submission feedback */ void submit( DxvkSubmitInfo submitInfo, - DxvkLatencyInfo latencyInfo, uint64_t submissionId); /** @@ -123,12 +107,10 @@ namespace dxvk { * and then presents the current swap chain image * of the presenter. May stall the calling thread. * \param [in] present Present parameters - * \param [in] latencyInfo Latency tracker info * \param [out] status Submission feedback */ void present( DxvkPresentInfo presentInfo, - DxvkLatencyInfo latencyInfo, uint64_t submissionId); /** diff --git a/src/dxvk/hud/dxvk_hud_item.cpp b/src/dxvk/hud/dxvk_hud_item.cpp index 882ee832..ce265a29 100644 --- a/src/dxvk/hud/dxvk_hud_item.cpp +++ b/src/dxvk/hud/dxvk_hud_item.cpp @@ -1419,86 +1419,4 @@ namespace dxvk::hud { / (uint32_t(m_tasksTotal - m_offset)); } - - - HudLatencyItem::HudLatencyItem() { - - } - - - HudLatencyItem::~HudLatencyItem() { - - } - - - void HudLatencyItem::accumulateStats(const DxvkLatencyStats& stats) { - std::lock_guard lock(m_mutex); - - if (stats.frameLatency.count()) { - m_accumStats.frameLatency += stats.frameLatency; - m_accumStats.sleepDuration += stats.sleepDuration; - - m_accumFrames += 1u; - } else { - m_accumStats = { }; - m_accumFrames = 0u; - } - } - - - void HudLatencyItem::update( - Presenter& presenter, - dxvk::high_resolution_clock::time_point time) { - uint64_t ticks = std::chrono::duration_cast(time - m_lastUpdate).count(); - - if (ticks >= UpdateInterval) { - std::lock_guard lock(m_mutex); - - if (m_accumFrames) { - uint32_t latency = (m_accumStats.frameLatency / m_accumFrames).count() / 100u; - uint32_t sleep = (m_accumStats.sleepDuration / m_accumFrames).count() / 100u; - - m_latencyString = str::format(latency / 10, ".", latency % 10, " ms"); - m_sleepString = str::format(sleep / 10, ".", sleep % 10, " ms"); - - m_accumStats = { }; - m_accumFrames = 0u; - - m_invalidUpdates = 0u; - } else { - m_latencyString = "--"; - m_sleepString = "--"; - - if (m_invalidUpdates < MaxInvalidUpdates) - m_invalidUpdates += 1u; - } - - m_lastUpdate = time; - } - } - - - HudPos HudLatencyItem::render( - const Rc&ctx, - const HudPipelineKey& key, - const HudOptions& options, - HudRenderer& renderer, - HudPos position) { - if (m_invalidUpdates >= MaxInvalidUpdates) - return position; - - position.y += 16; - - renderer.drawText(16, position, 0xffff60a0u, "Latency: "); - renderer.drawText(16, { position.x + 108, position.y }, 0xffffffffu, m_latencyString); - - position.y += 20; - - renderer.drawText(16, position, 0xffff60a0u, "Sleep: "); - renderer.drawText(16, { position.x + 108, position.y }, 0xffffffffu, m_sleepString); - - position.y += 8; - return position; - } - } diff --git a/src/dxvk/hud/dxvk_hud_item.h b/src/dxvk/hud/dxvk_hud_item.h index b9d1beb2..03e621e2 100644 --- a/src/dxvk/hud/dxvk_hud_item.h +++ b/src/dxvk/hud/dxvk_hud_item.h @@ -758,48 +758,4 @@ namespace dxvk::hud { }; - - /** - * \brief Frame latency item - */ - class HudLatencyItem : public HudItem { - constexpr static int64_t UpdateInterval = 500'000; - - constexpr static uint32_t MaxInvalidUpdates = 20u; - public: - - HudLatencyItem(); - - ~HudLatencyItem(); - - void accumulateStats(const DxvkLatencyStats& stats); - - void update( - Presenter& presenter, - dxvk::high_resolution_clock::time_point time); - - HudPos render( - const Rc&ctx, - const HudPipelineKey& key, - const HudOptions& options, - HudRenderer& renderer, - HudPos position); - - private: - - sync::Spinlock m_mutex; - - DxvkLatencyStats m_accumStats = { }; - uint32_t m_accumFrames = 0u; - - uint32_t m_invalidUpdates = MaxInvalidUpdates; - - std::string m_latencyString; - std::string m_sleepString; - - dxvk::high_resolution_clock::time_point m_lastUpdate - = dxvk::high_resolution_clock::now(); - - }; - } diff --git a/src/dxvk/meson.build b/src/dxvk/meson.build index 6b12f95c..29b6d447 100644 --- a/src/dxvk/meson.build +++ b/src/dxvk/meson.build @@ -54,8 +54,6 @@ dxvk_src = [ 'dxvk_image.cpp', 'dxvk_implicit_resolve.cpp', 'dxvk_instance.cpp', - 'dxvk_latency_builtin.cpp', - 'dxvk_latency_reflex.cpp', 'dxvk_memory.cpp', 'dxvk_meta_blit.cpp', 'dxvk_meta_clear.cpp', From f214814ede3937cd12ff7635e166af0bf7756a3b Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 2 Nov 2026 23:23:49 +0100 Subject: [PATCH 127/135] [dxvk] Simplify Reflex code in presenter --- src/d3d11/d3d11_device.h | 1 + src/dxvk/dxvk_presenter.cpp | 37 ++++++++++++++----------------------- src/dxvk/dxvk_presenter.h | 10 ++++------ 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/d3d11/d3d11_device.h b/src/d3d11/d3d11_device.h index 06fe7d46..9e14a28b 100644 --- a/src/d3d11/d3d11_device.h +++ b/src/d3d11/d3d11_device.h @@ -833,6 +833,7 @@ namespace dxvk { bool m_enableLowLatency = false; bool m_enableBoost = false; uint64_t m_minIntervalUs = 0u; + }; diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 21fe2308..f3741de7 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -412,8 +412,11 @@ namespace dxvk { } - void Presenter::setLatencySleepModeNv( + void Presenter::setLatencySleepMode( const VkLatencySleepModeInfoNV& sleepMode) { + if (!m_device->features().nvLowLatency2) + return; + std::unique_lock lock(m_surfaceMutex); if (sleepMode.sType != VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV) @@ -445,9 +448,12 @@ namespace dxvk { } - dxvk::high_resolution_clock::time_point Presenter::setLatencyMarkerNv( + void Presenter::setLatencyMarker( uint64_t frameId, VkLatencyMarkerNV marker) { + if (!m_device->features().nvLowLatency2) + return; + std::unique_lock lock(m_surfaceMutex); if (!m_latencySleepMode) { @@ -455,14 +461,9 @@ namespace dxvk { // 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; } - // 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; @@ -470,22 +471,17 @@ namespace dxvk { 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() { + void Presenter::latencySleep() { std::unique_lock lock(m_surfaceMutex); if (!m_latencySleepSupported) - return dxvk::high_resolution_clock::duration(0u); + return; - if (!m_latencySemaphore) { - if (createLatencySemaphore() != VK_SUCCESS) - return dxvk::high_resolution_clock::duration(0u); - } + if (!m_latencySemaphore && createLatencySemaphore() != VK_SUCCESS) + return; VkLatencySleepInfoNV info = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_INFO_NV }; info.signalSemaphore = m_latencySemaphore; @@ -495,21 +491,16 @@ namespace dxvk { 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 Presenter::getLatencyTimings( uint32_t timingCount, VkLatencyTimingsFrameReportNV* timings) { std::unique_lock lock(m_surfaceMutex); diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 1efa301e..587c4525 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -286,7 +286,7 @@ namespace dxvk { * Any changes will be applied on the next acquire operation. * \param [in] sleepMode Latency mode info */ - void setLatencySleepModeNv( + void setLatencySleepMode( const VkLatencySleepModeInfoNV& sleepMode); /** @@ -296,9 +296,8 @@ namespace dxvk { * 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( + void setLatencyMarker( uint64_t frameId, VkLatencyMarkerNV marker); @@ -307,9 +306,8 @@ namespace dxvk { * * Ignored if the current swapchain has not been * created with low latency support. - * \returns Sleep duration */ - dxvk::high_resolution_clock::duration latencySleepNv(); + void latencySleep(); /** * \brief Queries latency timings @@ -318,7 +316,7 @@ namespace dxvk { * \param [out] timings Latency timings * \returns Number of frame reports returned */ - uint32_t getLatencyTimingsNv( + uint32_t getLatencyTimings( uint32_t timingCount, VkLatencyTimingsFrameReportNV* timings); From 4f1781c223e9347586b7ad54363ccc53fc4140c4 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 3 Nov 2026 00:18:57 +0100 Subject: [PATCH 128/135] [dxvk] Pass latency frame ID through to presenter And emit present markers as necessary. --- src/d3d11/d3d11_swapchain.cpp | 4 ++-- src/d3d9/d3d9_swapchain.cpp | 2 +- src/dxvk/dxvk_device.cpp | 7 ++++--- src/dxvk/dxvk_device.h | 7 +++---- src/dxvk/dxvk_presenter.cpp | 7 +++++++ src/dxvk/dxvk_presenter.h | 2 ++ src/dxvk/dxvk_queue.cpp | 7 +++---- src/dxvk/dxvk_queue.h | 8 ++++---- 8 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 1a1eac0e..e8d95603 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -509,8 +509,8 @@ namespace dxvk { ctx->synchronizeWsi(cSync); ctx->flushCommandList(nullptr, 0u); - cDevice->presentImage(cPresenter, cFrameId, - cDirtyRects.size(), cDirtyRects.data(), 0u); + cDevice->presentImage(cPresenter, cFrameId, 0, + cDirtyRects.size(), cDirtyRects.data()); }); if (m_backBuffers.size() > 1u) diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 415d2e64..28839592 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -915,7 +915,7 @@ namespace dxvk { ctx->synchronizeWsi(cSync); ctx->flushCommandList(nullptr, 0u); - cDevice->presentImage(cPresenter, cFrameId, 0, nullptr, 0); + cDevice->presentImage(cPresenter, cFrameId, 0, 0, nullptr); }); m_parent->FlushCsChunk(); diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index 116f6b2d..ce957191 100644 --- a/src/dxvk/dxvk_device.cpp +++ b/src/dxvk/dxvk_device.cpp @@ -580,17 +580,18 @@ namespace dxvk { void DxvkDevice::presentImage( const Rc& presenter, uint64_t frameId, + uint64_t latencyFrameId, uint32_t rectCount, - const VkRectLayerKHR* rects, - uint64_t submissionId) { + const VkRectLayerKHR* rects) { DxvkPresentInfo presentInfo = { }; presentInfo.presenter = presenter; presentInfo.frameId = frameId; + presentInfo.latencyFrameId = latencyFrameId; for (uint32_t i = 0u; i < rectCount; i++) presentInfo.rects.push_back(rects[i]); - m_submissionQueue.present(presentInfo, submissionId); + m_submissionQueue.present(presentInfo); std::lock_guard statLock(m_statLock); m_statCounters.addCtr(DxvkStatCounter::QueuePresentCount, 1); diff --git a/src/dxvk/dxvk_device.h b/src/dxvk/dxvk_device.h index ab12cc55..d8cb7d86 100644 --- a/src/dxvk/dxvk_device.h +++ b/src/dxvk/dxvk_device.h @@ -637,18 +637,17 @@ namespace dxvk { * the submission thread. The status of this operation * can be retrieved with \ref waitForSubmission. * \param [in] presenter The presenter - * \param [in] tracker Latency tracker * \param [in] frameId Frame ID + * \param [in] latencyFrameId App-provided Reflex frame ID * \param [in] rectCount Number of dirty rectangles, * \param [in] rects Dirty rectangles - * \param [in] submissionId Submission timeline */ void presentImage( const Rc& presenter, uint64_t frameId, + uint64_t latencyFrameId, uint32_t rectCount, - const VkRectLayerKHR* rects, - uint64_t submissionId); + const VkRectLayerKHR* rects); /** * \brief Submits a command list diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index f3741de7..e5572ca8 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -180,6 +180,7 @@ namespace dxvk { VkResult Presenter::presentImage( uint64_t frameId, + uint64_t latencyFrameId, uint32_t rectCount, const VkRectLayerKHR* rects) { PresenterSync& currSync = m_semaphores.at(m_frameIndex); @@ -277,9 +278,15 @@ namespace dxvk { if (m_hasIncrementalPresent && !m_presentRepaint && m_acquireStatus == VK_SUCCESS && rectCount) regionInfo.pNext = const_cast(std::exchange(info.pNext, ®ionInfo)); + if (latencyFrameId) + setLatencyMarker(latencyFrameId, VK_LATENCY_MARKER_PRESENT_START_NV); + VkResult status = m_vkd->vkQueuePresentKHR( m_device->queues().graphics.queueHandle, &info); + if (latencyFrameId) + setLatencyMarker(latencyFrameId, VK_LATENCY_MARKER_PRESENT_END_NV); + // Maintain valid state if presentation succeeded, even if we want to // recreate the swapchain. Spec says that 'queue' operations, i.e. the // semaphore and fence signals, still happen if present fails with diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 587c4525..99ad54f6 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -188,12 +188,14 @@ namespace dxvk { * * Presents the last successfuly acquired image. * \param [in] frameId Frame number. + * \param [in] latencyFrameId Reflex frame ID * \param [in] rectCount Number of dirty rects * \param [in] rects Dirty rectangles * \returns Status of the operation */ VkResult presentImage( uint64_t frameId, + uint64_t latencyFrameId, uint32_t rectCount, const VkRectLayerKHR* rects); diff --git a/src/dxvk/dxvk_queue.cpp b/src/dxvk/dxvk_queue.cpp index 9c82015a..cfb81a5d 100644 --- a/src/dxvk/dxvk_queue.cpp +++ b/src/dxvk/dxvk_queue.cpp @@ -63,12 +63,10 @@ namespace dxvk { void DxvkSubmissionQueue::present( - DxvkPresentInfo presentInfo, - uint64_t submissionId) { + DxvkPresentInfo presentInfo) { std::unique_lock lock(m_mutex); DxvkSubmitEntry entry = { }; - entry.timeline = submissionId; entry.present = std::move(presentInfo); m_submitQueue.push(std::move(entry)); @@ -159,7 +157,8 @@ namespace dxvk { entry.timelines = m_timelines; } else if (entry.present.presenter != nullptr) { status = entry.present.presenter->presentImage(entry.present.frameId, - entry.present.rects.size(), entry.present.rects.data()); + entry.present.latencyFrameId, entry.present.rects.size(), + entry.present.rects.data()); } if (m_callback) diff --git a/src/dxvk/dxvk_queue.h b/src/dxvk/dxvk_queue.h index 267396c5..817009c9 100644 --- a/src/dxvk/dxvk_queue.h +++ b/src/dxvk/dxvk_queue.h @@ -34,8 +34,9 @@ namespace dxvk { * a swap chain image on the device. */ struct DxvkPresentInfo { - Rc presenter = nullptr; - uint64_t frameId = 0u; + Rc presenter = nullptr; + uint64_t frameId = 0u; + uint64_t latencyFrameId = 0u; small_vector rects; }; @@ -110,8 +111,7 @@ namespace dxvk { * \param [out] status Submission feedback */ void present( - DxvkPresentInfo presentInfo, - uint64_t submissionId); + DxvkPresentInfo presentInfo); /** * \brief Synchronizes with one queue submission From bf621af846e26c26d79d5e38a4baeb3124e85841 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 3 Nov 2026 00:19:28 +0100 Subject: [PATCH 129/135] [d3d11] Re-implement Reflex device --- src/d3d11/d3d11_device.cpp | 94 +++++++++++++++++++++++++++++++---- src/d3d11/d3d11_device.h | 22 +++++--- src/d3d11/d3d11_swapchain.cpp | 56 ++++++++++++++++++++- src/d3d11/d3d11_swapchain.h | 15 ++++++ 4 files changed, 168 insertions(+), 19 deletions(-) diff --git a/src/d3d11/d3d11_device.cpp b/src/d3d11/d3d11_device.cpp index a0179ab7..d29fc0ed 100644 --- a/src/d3d11/d3d11_device.cpp +++ b/src/d3d11/d3d11_device.cpp @@ -3530,8 +3530,17 @@ namespace dxvk { if (!m_reflexEnabled) return DXGI_ERROR_INVALID_CALL; - // TODO reimplement. - return S_OK; + Com swapChain; + + { std::unique_lock lock(m_mutex); + + if (!m_swapChain || !m_swapChain->HasLiveReferences()) + return S_OK; + + swapChain = m_swapChain; + } + + return swapChain->LatencySleep(); } @@ -3545,12 +3554,14 @@ namespace dxvk { std::lock_guard lock(m_mutex); // Write back in case we have no swapchain yet - m_enableLowLatency = LowLatencyEnable; - m_enableBoost = LowLatencyBoost; - m_minIntervalUs = MinIntervalUs; + m_sleepMode.lowLatencyMode = LowLatencyEnable; + m_sleepMode.lowLatencyBoost = LowLatencyBoost; + m_sleepMode.minimumIntervalUs = MinIntervalUs; - // TODO reimplement. - return S_OK; + if (!m_swapChain) + return S_OK; + + return m_swapChain->SetLatencySleepMode(m_sleepMode); } @@ -3560,8 +3571,12 @@ namespace dxvk { if (!m_reflexEnabled) return DXGI_ERROR_INVALID_CALL; - // TODO reimplement. - return S_OK; + std::lock_guard lock(m_mutex); + + if (!m_swapChain) + return S_OK; + + return m_swapChain->SetLatencyMarker(FrameId, VkLatencyMarkerNV(MarkerType)); } @@ -3578,11 +3593,70 @@ namespace dxvk { if (!m_reflexEnabled) return DXGI_ERROR_INVALID_CALL; - // TODO reimplement. + std::lock_guard lock(m_mutex); + + if (!m_swapChain) + return S_OK; + + std::array reports = {}; + + for (size_t i = 0u; i < reports.size(); i++) + reports[i] = { VK_STRUCTURE_TYPE_LATENCY_TIMINGS_FRAME_REPORT_NV }; + + size_t count = m_swapChain->GetLatencyInfo(reports.size(), reports.data()); + + // Apparently we have to report all 64 entries, or nothing. + if (count < reports.size()) + return S_OK; + + for (size_t i = 0u; i < reports.size(); i++) { + const auto& src = reports[i]; + auto& dst = pLowLatencyResults->frameReports[i]; + + dst.frameID = src.presentID; + dst.inputSampleTime = src.inputSampleTimeUs; + dst.simStartTime = src.simStartTimeUs; + dst.simEndTime = src.simEndTimeUs; + dst.renderSubmitStartTime = src.renderSubmitStartTimeUs; + dst.renderSubmitEndTime = src.renderSubmitEndTimeUs; + dst.presentStartTime = src.presentStartTimeUs; + dst.presentEndTime = src.presentEndTimeUs; + dst.driverStartTime = src.driverStartTimeUs; + dst.driverEndTime = src.driverEndTimeUs; + dst.osRenderQueueStartTime = src.osRenderQueueStartTimeUs; + dst.osRenderQueueEndTime = src.osRenderQueueEndTimeUs; + dst.gpuRenderStartTime = src.gpuRenderStartTimeUs; + dst.gpuRenderEndTime = src.gpuRenderEndTimeUs; + // No great way to estimate GPU idle for any given frame + dst.gpuActiveRenderTimeUs = src.gpuRenderEndTimeUs - src.gpuRenderStartTimeUs; + dst.gpuFrameTimeUs = src.gpuRenderEndTimeUs - src.gpuRenderStartTimeUs; + } + return S_OK; } + void D3D11ReflexDevice::RegisterSwapChain(D3D11SwapChain* pSwapChain) { + if (!m_reflexEnabled) + return; + + std::lock_guard lock(m_mutex); + + if (!m_swapChain) { + m_swapChain = pSwapChain; + m_swapChain->SetLatencySleepMode(m_sleepMode); + } + } + + + void D3D11ReflexDevice::UnregisterSwapChain(D3D11SwapChain* pSwapChain) { + std::lock_guard lock(m_mutex); + + if (m_swapChain == pSwapChain) + m_swapChain = nullptr; + } + + DXGIVkSwapChainFactory::DXGIVkSwapChainFactory( D3D11DXGIDevice* pContainer, D3D11Device* pDevice) diff --git a/src/d3d11/d3d11_device.h b/src/d3d11/d3d11_device.h index 9e14a28b..9024930b 100644 --- a/src/d3d11/d3d11_device.h +++ b/src/d3d11/d3d11_device.h @@ -38,6 +38,7 @@ namespace dxvk { class D3D11Texture1D; class D3D11Texture2D; class D3D11Texture3D; + class D3D11SwapChain; /** * \brief D3D11 device implementation @@ -821,18 +822,21 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE GetLatencyInfo( D3D_LOW_LATENCY_RESULTS* pLowLatencyResults); + void RegisterSwapChain(D3D11SwapChain* pSwapChain); + + void UnregisterSwapChain(D3D11SwapChain* pSwapChain); + private: - D3D11DXGIDevice* m_container; - D3D11Device* m_device; + D3D11DXGIDevice* m_container = nullptr; + D3D11Device* m_device = nullptr; - bool m_reflexEnabled = false; + bool m_reflexEnabled = false; - dxvk::mutex m_mutex; + dxvk::mutex m_mutex; - bool m_enableLowLatency = false; - bool m_enableBoost = false; - uint64_t m_minIntervalUs = 0u; + VkLatencySleepModeInfoNV m_sleepMode = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV }; + D3D11SwapChain* m_swapChain = nullptr; }; @@ -984,6 +988,10 @@ namespace dxvk { Rc STDMETHODCALLTYPE GetDXVKDevice(); + D3D11ReflexDevice* GetReflexDevice() { + return &m_d3d11Reflex; + } + BOOL Is11on12Device() const { return m_d3d11on12.Is11on12Device(); } diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index e8d95603..adb4635e 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -82,6 +82,8 @@ namespace dxvk { if (this_thread::isInModuleDetachment()) return; + m_dxgiDevice->GetReflexDevice()->UnregisterSwapChain(this); + m_presenter->destroyResources(); DestroyFrameLatencyEvent(); @@ -374,6 +376,51 @@ namespace dxvk { } + HRESULT D3D11SwapChain::LatencySleep() { + m_presenter->latencySleep(); + return S_OK; + } + + + HRESULT D3D11SwapChain::SetLatencySleepMode( + const VkLatencySleepModeInfoNV& pSleepMode) { + m_presenter->setLatencySleepMode(pSleepMode); + return S_OK; + } + + + HRESULT D3D11SwapChain::SetLatencyMarker( + uint64_t FrameId, + VkLatencyMarkerNV Marker) { + switch (Marker) { + case VK_LATENCY_MARKER_PRESENT_START_NV: { + // We actually need to defer present markers until + // the present happens on the queue thread. + m_nextLatencyFrameId.store(FrameId); + } break; + + case VK_LATENCY_MARKER_PRESENT_END_NV: { + // Reset frame ID in case presentation fails + m_nextLatencyFrameId.store(0); + } break; + + default: { + // Forward all unrelated markers immediately. + m_presenter->setLatencyMarker(FrameId, Marker); + } break; + } + + return S_OK; + } + + + size_t D3D11SwapChain::GetLatencyInfo( + uint32_t ReportCount, + VkLatencyTimingsFrameReportNV* pFrameReports) { + return m_presenter->getLatencyTimings(ReportCount, pFrameReports); + } + + Rc D3D11SwapChain::GetBackBufferView() { Rc image = GetCommonTexture(m_backBuffers[0].ptr())->GetImage(); @@ -460,6 +507,10 @@ namespace dxvk { m_frameId += 1; + // Latch latency frame ID for Reflex markers and replace it + // with zero in order to avoid submitting any marker twice. + uint64_t latencyFrameId = m_nextLatencyFrameId.exchange(0u); + // Present from CS thread so that we don't // have to synchronize with it first. DxvkImageViewKey viewInfo = { }; @@ -482,6 +533,7 @@ namespace dxvk { cPresenter = m_presenter, cColorSpace = m_colorSpace, cFrameId = m_frameId, + cLatencyFrameId = latencyFrameId, cDirtyRects = std::move(dirtyRects), cClearColor = m_clearColor, cSrcRect = srcRect, @@ -509,7 +561,7 @@ namespace dxvk { ctx->synchronizeWsi(cSync); ctx->flushCommandList(nullptr, 0u); - cDevice->presentImage(cPresenter, cFrameId, 0, + cDevice->presentImage(cPresenter, cFrameId, cLatencyFrameId, cDirtyRects.size(), cDirtyRects.data()); }); @@ -568,7 +620,7 @@ namespace dxvk { m_presenter->setSurfaceExtent({ m_desc.Width, m_desc.Height }); m_presenter->setFrameRateLimit(m_targetFrameRate, GetActualFrameLatency()); - // TODO add Reflex device. + m_dxgiDevice->GetReflexDevice()->RegisterSwapChain(this); } diff --git a/src/d3d11/d3d11_swapchain.h b/src/d3d11/d3d11_swapchain.h index 4ad29461..6a9ffaf5 100644 --- a/src/d3d11/d3d11_swapchain.h +++ b/src/d3d11/d3d11_swapchain.h @@ -95,6 +95,19 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE SetRotation( DXGI_MODE_ROTATION Rotation); + HRESULT LatencySleep(); + + HRESULT SetLatencySleepMode( + const VkLatencySleepModeInfoNV& pSleepMode); + + HRESULT SetLatencyMarker( + uint64_t FrameId, + VkLatencyMarkerNV Marker); + + size_t GetLatencyInfo( + uint32_t ReportCount, + VkLatencyTimingsFrameReportNV* pFrameReports); + private: using DirtyRectList = small_vector; @@ -139,6 +152,8 @@ namespace dxvk { HANDLE m_frameLatencyEvent = nullptr; Rc m_frameLatencySignal; + std::atomic m_nextLatencyFrameId = 0; + VkColorSpaceKHR m_colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; VkClearColorValue m_clearColor = {}; From db9f494b265654f2b43fc3c196270a2c3df21086 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 3 Nov 2026 01:14:05 +0100 Subject: [PATCH 130/135] [dxvk] Add Reflex latency statistics to presenter --- src/dxvk/dxvk_presenter.cpp | 26 +++++++++++++++++++++++++- src/dxvk/dxvk_presenter.h | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index e5572ca8..62d50806 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -492,7 +492,7 @@ namespace dxvk { VkLatencySleepInfoNV info = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_INFO_NV }; info.signalSemaphore = m_latencySemaphore; - info.value = ++m_latencySleepCounter; + info.value = m_latencySleepCounter + 1u; m_vkd->vkLatencySleepNV(m_vkd->device(), m_swapchain, &info); @@ -503,7 +503,14 @@ namespace dxvk { waitInfo.pSemaphores = &info.signalSemaphore; waitInfo.pValues = &info.value; + auto t0 = dxvk::high_resolution_clock::now(); m_vkd->vkWaitSemaphores(m_vkd->device(), &waitInfo, ~0ull); + auto t1 = dxvk::high_resolution_clock::now(); + + lock.lock(); + + m_latencySleepCounter += 1u; + m_latencySleepDurationUs += std::chrono::duration_cast(t1 - t0).count(); } @@ -600,6 +607,23 @@ namespace dxvk { } + PresenterLatencyInfo Presenter::queryLatencyStats() { + if (!m_device->features().nvLowLatency2) + return PresenterLatencyInfo(); + + VkLatencyTimingsFrameReportNV report = { VK_STRUCTURE_TYPE_LATENCY_TIMINGS_FRAME_REPORT_NV }; + getLatencyTimings(1, &report); + + std::lock_guard lock(m_surfaceMutex); + + PresenterLatencyInfo result = {}; + result.frameLatencyUs = report.gpuRenderEndTimeUs - (report.inputSampleTimeUs ? report.inputSampleTimeUs : report.simStartTimeUs); + result.sleepCount = m_latencySleepCounter; + result.sleepDurationUs = m_latencySleepDurationUs; + return result; + } + + VkResult Presenter::recreateSwapChain() { VkResult vr; diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 99ad54f6..947ee04a 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -135,6 +135,17 @@ namespace dxvk { PresenterTimingFeedback lastReport = {}; }; + /** + * \brief Latency stats + * + * Used for informational purposes only. + */ + struct PresenterLatencyInfo { + uint64_t frameLatencyUs = 0u; + uint64_t sleepDurationUs = 0u; + uint64_t sleepCount = 0u; + }; + /** * \brief Vulkan presenter * @@ -332,6 +343,12 @@ namespace dxvk { */ PresenterTimingFeedback queryPresentTiming(); + /** + * \brief Queries latency statistics + * \returns Latency statistics + */ + PresenterLatencyInfo queryLatencyStats(); + private: Rc m_device; @@ -385,6 +402,7 @@ namespace dxvk { std::optional m_latencySleepMode; VkSemaphore m_latencySemaphore = VK_NULL_HANDLE; uint64_t m_latencySleepCounter = 0u; + uint64_t m_latencySleepDurationUs = 0u; bool m_latencySleepModeDirty = false; bool m_latencySleepSupported = false; From 4b0df68b32077b2c710ba9ea0571f1b21f0140c4 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 3 Nov 2026 01:14:36 +0100 Subject: [PATCH 131/135] [dxvk] Reimplement latency HUD item --- src/dxvk/hud/dxvk_hud.cpp | 1 + src/dxvk/hud/dxvk_hud_item.cpp | 52 ++++++++++++++++++++++++++++++++++ src/dxvk/hud/dxvk_hud_item.h | 31 ++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/src/dxvk/hud/dxvk_hud.cpp b/src/dxvk/hud/dxvk_hud.cpp index 0a9960ff..8ffe2d58 100644 --- a/src/dxvk/hud/dxvk_hud.cpp +++ b/src/dxvk/hud/dxvk_hud.cpp @@ -17,6 +17,7 @@ namespace dxvk::hud { addItem("version", -1); addItem("devinfo", -1, m_device); addItem("fps", -1); + addItem("latency", -1); addItem("frametimes", -1, device, &m_renderer); addItem("submissions", -1, device); addItem("drawcalls", -1, device); diff --git a/src/dxvk/hud/dxvk_hud_item.cpp b/src/dxvk/hud/dxvk_hud_item.cpp index ce265a29..e941de4f 100644 --- a/src/dxvk/hud/dxvk_hud_item.cpp +++ b/src/dxvk/hud/dxvk_hud_item.cpp @@ -1419,4 +1419,56 @@ namespace dxvk::hud { / (uint32_t(m_tasksTotal - m_offset)); } + + + + HudLatencyItem::HudLatencyItem() { } + HudLatencyItem::~HudLatencyItem() { } + + + void HudLatencyItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { + uint64_t ticks = std::chrono::duration_cast(time - m_lastUpdate).count(); + + if (ticks >= UpdateInterval) { + m_lastUpdate = time; + + auto newStats = presenter.queryLatencyStats(); + auto oldStats = std::exchange(m_stats, newStats); + + if (!newStats.sleepCount) + return; + + uint64_t latency = newStats.frameLatencyUs / 100u; + uint64_t sleepTime = (newStats.sleepDurationUs - oldStats.sleepDurationUs) / + (100u * std::max(newStats.sleepCount - oldStats.sleepCount, uint64_t(1u))); + + m_sleep = str::format(sleepTime / 10u, ".", sleepTime % 10, " ms"); + m_latency = str::format(latency / 10u, ".", latency % 10, " ms"); + } + } + + + HudPos HudLatencyItem::render( + const Rc&ctx, + const HudPipelineKey& key, + const HudOptions& options, + HudRenderer& renderer, + HudPos position) { + if (!m_latency.empty()) { + position.y += 16; + renderer.drawText(16, position, 0xffff60a0u, "Latency:"); + renderer.drawText(16, { position.x + 108, position.y }, 0xffffffffu, m_latency); + + position.y += 16; + renderer.drawText(16, position, 0xffff60a0u, "Sleep:"); + renderer.drawText(16, { position.x + 108, position.y }, 0xffffffffu, m_sleep); + + position.y += 8; + } + + return position; + } + } diff --git a/src/dxvk/hud/dxvk_hud_item.h b/src/dxvk/hud/dxvk_hud_item.h index 03e621e2..9d0e20b6 100644 --- a/src/dxvk/hud/dxvk_hud_item.h +++ b/src/dxvk/hud/dxvk_hud_item.h @@ -758,4 +758,35 @@ namespace dxvk::hud { }; + + class HudLatencyItem : public HudItem { + constexpr static int64_t UpdateInterval = 500'000; + public: + + HudLatencyItem(); + ~HudLatencyItem(); + + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); + + HudPos render( + const Rc&ctx, + const HudPipelineKey& key, + const HudOptions& options, + HudRenderer& renderer, + HudPos position); + + private: + + dxvk::high_resolution_clock::time_point m_lastUpdate + = dxvk::high_resolution_clock::now(); + + std::string m_sleep; + std::string m_latency; + + PresenterLatencyInfo m_stats = {}; + + }; + } From a7e278a638f23c5303a5c6be0c6f79a30c6a9716 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 3 Nov 2026 08:53:53 +0100 Subject: [PATCH 132/135] [dxvk] Fix config option to enable NV_low_latency2 --- dxvk.conf | 9 ++++----- src/dxvk/dxvk_device_info.cpp | 5 ++++- src/dxvk/dxvk_options.cpp | 2 +- src/dxvk/dxvk_options.h | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/dxvk.conf b/dxvk.conf index a11c8531..d3b27f30 100644 --- a/dxvk.conf +++ b/dxvk.conf @@ -96,14 +96,13 @@ # dxvk.maxFrameRate = 0 -# Disables the use of VK_NV_low_latency2. This will make Reflex unavailable -# in games, and if dxvk.latencySleep is set to True, a custom algorithm will -# be used for latency control. By default, the extension will not be used in -# 32-bit applications due to driver issues. +# Whether to enable VK_NV_low_latency2 for the Reflex implementation. +# On 32-bit, this is disabled by default due to massive frame pacing +# issues. # # Supported values: Auto, True, False -# dxvk.disableNvLowLatency2 = Auto +# dxvk.enableNvLowLatency2 = Auto # Override PCI vendor and device IDs reported to the application. Can diff --git a/src/dxvk/dxvk_device_info.cpp b/src/dxvk/dxvk_device_info.cpp index 7e3d16a7..04ff33d8 100644 --- a/src/dxvk/dxvk_device_info.cpp +++ b/src/dxvk/dxvk_device_info.cpp @@ -555,7 +555,10 @@ namespace dxvk { } // Reflex is broken on 32-bit - if (env::is32BitHostPlatform()) + bool enableReflex = !env::is32BitHostPlatform(); + applyTristate(enableReflex, instance.options().enableNvLowLatency2); + + if (!enableReflex) m_featuresSupported.nvLowLatency2 = VK_FALSE; // EXT_multi_draw is broken on proprietary qcom on some devices diff --git a/src/dxvk/dxvk_options.cpp b/src/dxvk/dxvk_options.cpp index 8478b446..ed52368e 100644 --- a/src/dxvk/dxvk_options.cpp +++ b/src/dxvk/dxvk_options.cpp @@ -14,12 +14,12 @@ namespace dxvk { enableImplicitResolves = config.getOption ("dxvk.enableImplicitResolves", true); enableNvRawAccessChains = config.getOption ("dxvk.enableNvRawAccessChains", true); enableNvCudaInterop = config.getOption ("dxvk.enableNvCudaInterop", true); + enableNvLowLatency2 = config.getOption("dxvk.enableNvLowLatency2", Tristate::Auto); enablePresentTiming = config.getOption ("dxvk.enablePresentTiming", true); trackPipelineLifetime = config.getOption("dxvk.trackPipelineLifetime", Tristate::Auto); useRawSsbo = config.getOption("dxvk.useRawSsbo", Tristate::Auto); hud = config.getOption("dxvk.hud", ""); tearFree = config.getOption("dxvk.tearFree", Tristate::Auto); - disableNvLowLatency2 = config.getOption("dxvk.disableNvLowLatency2", Tristate::Auto); hideIntegratedGraphics = config.getOption ("dxvk.hideIntegratedGraphics", false); zeroMappedMemory = config.getOption ("dxvk.zeroMappedMemory", false); zeroShaderScratchMemory = config.getOption ("dxvk.zeroShaderScratchMemory", false); diff --git a/src/dxvk/dxvk_options.h b/src/dxvk/dxvk_options.h index 44f62ecf..994fa9ea 100644 --- a/src/dxvk/dxvk_options.h +++ b/src/dxvk/dxvk_options.h @@ -48,7 +48,7 @@ namespace dxvk { /// Disable VK_NV_low_latency2. This extension /// appears to be all sorts of broken on 32-bit. - Tristate disableNvLowLatency2 = Tristate::Auto; + Tristate enableNvLowLatency2 = Tristate::Auto; // Hides integrated GPUs if dedicated GPUs are // present. May be necessary for some games that From a1aca15ba5c4c9a5c4f4ef3bcb63e0392877c8ed Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Wed, 4 Nov 2026 09:19:21 +0100 Subject: [PATCH 133/135] [dxvk] Forward frame interval to Reflex --- src/dxvk/dxvk_presenter.cpp | 31 ++++++++++++++++++++----------- src/dxvk/dxvk_presenter.h | 2 +- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 62d50806..2342f4bc 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -150,12 +150,20 @@ namespace dxvk { } // Apply latency sleep mode if the swapchain supports it - if (m_latencySleepModeDirty && m_latencySleepMode) { - m_latencySleepModeDirty = false; - + if (m_latencySleepMode + && m_latencySleepModeDirty.load() + && m_latencySleepModeDirty.exchange(false)) { if (m_latencySleepSupported) { - m_vkd->vkSetLatencySleepModeNV(m_vkd->device(), - m_swapchain, &(*m_latencySleepMode)); + auto sleepMode = m_latencySleepMode.value(); + + if (sleepMode.lowLatencyMode) { + std::unique_lock lock(m_timingMutex); + + sleepMode.minimumIntervalUs = std::max(sleepMode.minimumIntervalUs, + uint32_t(m_timingMode.frameIntervalNs / 1000u)); + } + + m_vkd->vkSetLatencySleepModeNV(m_vkd->device(), m_swapchain, &sleepMode); } } @@ -443,12 +451,11 @@ namespace dxvk { m_dirtySwapchain |= !m_latencySleepMode; - if (m_latencySleepMode) { - m_latencySleepModeDirty |= - m_latencySleepMode->lowLatencyMode != sleepMode.lowLatencyMode || - m_latencySleepMode->lowLatencyBoost != sleepMode.lowLatencyBoost || - m_latencySleepMode->minimumIntervalUs != sleepMode.minimumIntervalUs; - } + if (m_latencySleepMode + && (m_latencySleepMode->lowLatencyMode != sleepMode.lowLatencyMode + || m_latencySleepMode->lowLatencyBoost != sleepMode.lowLatencyBoost + || m_latencySleepMode->minimumIntervalUs != sleepMode.minimumIntervalUs)) + m_latencySleepModeDirty.store(true); m_latencySleepMode = sleepMode; m_latencySleepMode->pNext = nullptr; @@ -560,6 +567,8 @@ namespace dxvk { m_frameRateLimit = frameRate; updateTimingMode(); + + m_latencySleepModeDirty.store(true, std::memory_order_release); } } diff --git a/src/dxvk/dxvk_presenter.h b/src/dxvk/dxvk_presenter.h index 947ee04a..008e4c33 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -404,7 +404,7 @@ namespace dxvk { uint64_t m_latencySleepCounter = 0u; uint64_t m_latencySleepDurationUs = 0u; - bool m_latencySleepModeDirty = false; + std::atomic m_latencySleepModeDirty = { false }; bool m_latencySleepSupported = false; alignas(CACHE_LINE_SIZE) From 86a2a2ed9449600e696cf35d08ed17c2e1c8ef8e Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 6 Nov 2026 13:15:28 +0100 Subject: [PATCH 134/135] [dxvk] Restore missing check to skip over present_wait Not sure where we dropped this, but it was accidental. --- src/dxvk/dxvk_presenter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxvk/dxvk_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 2342f4bc..efb0e410 100644 --- a/src/dxvk/dxvk_presenter.cpp +++ b/src/dxvk/dxvk_presenter.cpp @@ -2164,7 +2164,7 @@ namespace dxvk { bool isFifoMode = frame.mode == VK_PRESENT_MODE_FIFO_KHR || frame.mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR; - if (isFifoMode && frame.result >= 0 && m_hasPresentWait) { + if (!waited && isFifoMode && frame.result >= 0 && m_hasPresentWait) { VkResult vr; if (m_device->features().khrPresentWait2.presentWait2) { From 829e377d2d5cf4b07455412fcabed66a1bf4390c Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 6 Nov 2026 13:18:26 +0100 Subject: [PATCH 135/135] [meta] Update dxbc-spirv LDS to scratch copy. --- subprojects/dxbc-spirv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index f528e1e8..d82d9eaa 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit f528e1e821527e361f34829f78d1fbd5340e6f6a +Subproject commit d82d9eaa6cab5c7277a6839f328e1b4d2f146894