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: | + diff --git a/dxvk.conf b/dxvk.conf index b403f62c..d3b27f30 100644 --- a/dxvk.conf +++ b/dxvk.conf @@ -96,44 +96,13 @@ # 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 -# 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/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/include/vulkan b/include/vulkan index 8864cdc8..6802bb47 160000 --- a/include/vulkan +++ b/include/vulkan @@ -1 +1 @@ -Subproject commit 8864cdc896bbc2a9b6eb36b3218fc9ef57908d77 +Subproject commit 6802bb4733b63ed5efd3adb308a6c885ef180ea1 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 += [ 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 * 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) { diff --git a/src/d3d11/d3d11_context_imm.cpp b/src/d3d11/d3d11_context_imm.cpp index 68fcba2c..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); }); } @@ -1093,15 +1087,20 @@ 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; + // 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(); @@ -1117,7 +1116,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 +1124,7 @@ namespace dxvk { ctx->signal(cSubmissionFence, cSubmissionId); ctx->signal(cStagingFence, cStagingMemory); - ctx->flushCommandList(&debugLabel, cSubmissionStatus); + ctx->flushCommandList(&debugLabel, cSubmissionId); }); FlushCsChunk(); @@ -1138,7 +1136,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..15286b28 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; @@ -182,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 13b101d6..d29fc0ed 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()); @@ -1851,7 +1855,7 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D11Device::SetExceptionMode(UINT RaiseFlags) { Logger::err("D3D11Device::SetExceptionMode: Not implemented"); - return E_NOTIMPL; + return S_OK; } @@ -2081,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); @@ -2779,11 +2783,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 +2833,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 +2863,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; } @@ -3499,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; } @@ -3536,17 +3530,17 @@ namespace dxvk { if (!m_reflexEnabled) return DXGI_ERROR_INVALID_CALL; - // Don't keep object locked while sleeping - Rc tracker; + Com swapChain; - { std::lock_guard lock(m_mutex); - tracker = m_tracker; - } + { std::unique_lock lock(m_mutex); - if (tracker) - tracker->latencySleep(); + if (!m_swapChain || !m_swapChain->HasLiveReferences()) + return S_OK; - return S_OK; + swapChain = m_swapChain; + } + + return swapChain->LatencySleep(); } @@ -3559,16 +3553,15 @@ 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; - return S_OK; + m_sleepMode.lowLatencyMode = LowLatencyEnable; + m_sleepMode.lowLatencyBoost = LowLatencyBoost; + m_sleepMode.minimumIntervalUs = MinIntervalUs; + + if (!m_swapChain) + return S_OK; + + return m_swapChain->SetLatencySleepMode(m_sleepMode); } @@ -3580,30 +3573,10 @@ namespace dxvk { 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); - }); - } - } + if (!m_swapChain) + return S_OK; - return S_OK; + return m_swapChain->SetLatencyMarker(FrameId, VkLatencyMarkerNV(MarkerType)); } @@ -3622,70 +3595,68 @@ namespace dxvk { std::lock_guard lock(m_mutex); - if (!m_tracker) + if (!m_swapChain) 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()); + 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()); - if (reportCount < FrameCount) + // Apparently we have to report all 64 entries, or nothing. + if (count < reports.size()) return S_OK; - for (uint32_t i = 0; i < FrameCount; i++) { - auto& src = reports[i]; + for (size_t i = 0u; i < reports.size(); i++) { + const 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; - } + 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::RegisterLatencyTracker( - Rc Tracker) { - std::lock_guard lock(m_mutex); - - if (m_tracker) + void D3D11ReflexDevice::RegisterSwapChain(D3D11SwapChain* pSwapChain) { + if (!m_reflexEnabled) return; - if ((m_tracker = dynamic_cast(Tracker.ptr()))) - m_tracker->setLatencySleepMode(m_enableLowLatency, m_enableBoost, m_minIntervalUs); + std::lock_guard lock(m_mutex); + + if (!m_swapChain) { + m_swapChain = pSwapChain; + m_swapChain->SetLatencySleepMode(m_sleepMode); + } } - void D3D11ReflexDevice::UnregisterLatencyTracker( - Rc Tracker) { + void D3D11ReflexDevice::UnregisterSwapChain(D3D11SwapChain* pSwapChain) { std::lock_guard lock(m_mutex); - if (m_tracker == Tracker) - m_tracker = nullptr; + 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 2bcae473..9024930b 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" @@ -39,6 +38,7 @@ namespace dxvk { class D3D11Texture1D; class D3D11Texture2D; class D3D11Texture3D; + class D3D11SwapChain; /** * \brief D3D11 device implementation @@ -822,26 +822,22 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE GetLatencyInfo( D3D_LOW_LATENCY_RESULTS* pLowLatencyResults); - void RegisterLatencyTracker( - Rc Tracker); + void RegisterSwapChain(D3D11SwapChain* pSwapChain); - void UnregisterLatencyTracker( - Rc Tracker); + 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; - Rc m_tracker; }; @@ -992,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_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_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; } diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 1f07405a..adb4635e 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" @@ -83,10 +82,11 @@ namespace dxvk { if (this_thread::isInModuleDetachment()) return; + m_dxgiDevice->GetReflexDevice()->UnregisterSwapChain(this); + m_presenter->destroyResources(); DestroyFrameLatencyEvent(); - DestroyLatencyTracker(); } @@ -287,18 +287,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; } @@ -354,7 +342,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; } @@ -388,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(); @@ -415,24 +448,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; @@ -464,7 +491,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; @@ -480,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 = { }; @@ -495,18 +526,22 @@ namespace dxvk { immediateContext->EmitCs([ cDevice = m_device, cBlitter = m_blitter, + cHud = m_hud, cBackBuffer = backBuffer->createView(viewInfo), cSwapImage = GetBackBufferView(), cSync = sync, cPresenter = m_presenter, - cLatency = m_latency, cColorSpace = m_colorSpace, cFrameId = m_frameId, + cLatencyFrameId = latencyFrameId, cDirtyRects = std::move(dirtyRects), cClearColor = m_clearColor, cSrcRect = srcRect, cDstRect = dstRect ] (DxvkContext* ctx) { + if (cHud) + cHud->update(*cPresenter); + // Update back buffer color space as necessary if (cSwapImage->image()->info().colorSpace != cColorSpace) { DxvkImageUsageInfo usage = { }; @@ -519,35 +554,21 @@ 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 ctx->synchronizeWsi(cSync); - ctx->flushCommandList(nullptr, nullptr); + ctx->flushCommandList(nullptr, 0u); - cDevice->presentImage(cPresenter, cLatency, cFrameId, - cDirtyRects.size(), cDirtyRects.data(), nullptr); + cDevice->presentImage(cPresenter, cFrameId, cLatencyFrameId, + cDirtyRects.size(), cDirtyRects.data()); }); if (m_backBuffers.size() > 1u) 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; } @@ -599,10 +620,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); + m_dxgiDevice->GetReflexDevice()->RegisterSwapChain(this); } @@ -677,17 +695,12 @@ 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_hasHud = hud && !hud->empty(); - m_blitter = new DxvkSwapchainBlitter(m_device, std::move(hud)); + m_blitter = new DxvkSwapchainBlitter(m_device); } @@ -696,20 +709,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 13bc196e..6a9ffaf5 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" @@ -96,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; @@ -122,8 +134,9 @@ namespace dxvk { Rc m_device; Rc m_presenter; + Rc m_hud; + Rc m_blitter; - Rc m_latency; small_vector, 4> m_backBuffers; @@ -139,14 +152,13 @@ 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 = {}; double m_targetFrameRate = 0.0; - bool m_hasHud = false; - Rc m_latencyHud; - Rc GetBackBufferView(); HRESULT PresentImage( @@ -165,8 +177,6 @@ namespace dxvk { void DestroyFrameLatencyEvent(); - void DestroyLatencyTracker(); - void SyncFrameLatency(); uint32_t GetActualFrameLatency(); 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_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 04ff8c6f..f01a83ce 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; @@ -289,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 @@ -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))) @@ -340,15 +344,18 @@ namespace dxvk { if (unlikely(ppVolumeTexture == nullptr)) return D3DERR_INVALIDCALL; - if (unlikely(isD3D9ExclusiveFormat(Format))) + if (unlikely(IsD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; 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))) @@ -383,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; @@ -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; @@ -488,18 +507,18 @@ 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; HRESULT res = GetD3D9()->CreateRenderTarget( Width, Height, - d3d9::D3DFORMAT(Format), - d3d9::D3DMULTISAMPLE_TYPE(MultiSample), + static_cast(Format), + static_cast(MultiSample), 0, Lockable, &pSurf9, @@ -528,15 +547,15 @@ namespace dxvk { if (unlikely(ppSurface == nullptr)) return D3DERR_INVALIDCALL; - if (unlikely(isD3D9ExclusiveFormat(Format))) + if (unlikely(IsD3D9ExclusiveFormat(Format))) return D3DERR_INVALIDCALL; Com pSurf9; 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, @@ -566,18 +585,18 @@ 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(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))) @@ -588,107 +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(D3DFORMAT(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; - } - - /** - * \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, @@ -704,275 +622,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. - - 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(D3DFORMAT(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; - } + D3D8Surface* src = static_cast(pSourceSurface); + D3D8Surface* dst = static_cast(pDestinationSurface); - 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, - D3DFORMAT(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( @@ -1226,7 +879,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; @@ -1378,7 +1031,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); @@ -1417,7 +1070,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 +1092,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 +1126,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 +1142,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 +1164,7 @@ namespace dxvk { m_streams[0] = D3D8VBO {nullptr, 0}; return GetD3D9()->DrawPrimitiveUP( - d3d9::D3DPRIMITIVETYPE(PrimitiveType), + static_cast(PrimitiveType), PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride); @@ -1532,12 +1189,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 +1357,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 +1432,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 @@ -1864,13 +1521,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; @@ -1895,8 +1552,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; @@ -1949,7 +1606,7 @@ namespace dxvk { D3D8VertexShaderInfo& info = m_vertexShaders[i]; if (info.pVertexShader == pVertexShader) { - *pHandle = getShaderHandle(i); + *pHandle = GetShaderHandle(i); return D3D_OK; } } @@ -1961,8 +1618,8 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE D3D8Device::DeleteVertexShader(DWORD Handle) { D3D8DeviceLock lock = LockDevice(); - if (!isFVF(Handle)) { - D3D8VertexShaderInfo* info = getVertexShaderInfo(this, Handle); + if (likely(!IsFVF(Handle))) { + D3D8VertexShaderInfo* info = GetVertexShaderInfo(this, Handle); if (unlikely(!info)) return D3DERR_INVALIDCALL; @@ -1982,7 +1639,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; @@ -2012,7 +1669,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; @@ -2057,13 +1714,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)); @@ -2093,7 +1750,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; @@ -2124,12 +1781,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; @@ -2140,7 +1797,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 b36ad188..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: @@ -442,7 +443,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 +476,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_format.h b/src/d3d8/d3d8_format.h index e90f6730..890f8a8a 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); } } diff --git a/src/d3d8/d3d8_interface.cpp b/src/d3d8/d3d8_interface.cpp index bd36e8b6..c11a2f14 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, @@ -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 ceecc9e8..e88c20c9 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 ); } @@ -87,19 +93,19 @@ 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( 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 ); } @@ -126,19 +132,19 @@ 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( 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) ); } @@ -187,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 88baff6d..dab3dee3 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; @@ -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 63240e3c..641edab2 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_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 ddeae164..4c6247ee 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 @@ -93,10 +92,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 +121,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 +143,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->Size = getSurfaceSize(pSurf8->Format, pSurf9->Width, pSurf9->Height); + 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,11 +157,11 @@ 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->Size = getSurfaceSize(pVol8->Format, pVol9->Width, pVol9->Height) * pVol9->Depth; + 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; pVol8->Depth = pVol9->Depth; @@ -188,15 +187,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/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_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_bridge.cpp b/src/d3d9/d3d9_bridge.cpp index ae5963f7..ce61defc 100644 --- a/src/d3d9/d3d9_bridge.cpp +++ b/src/d3d9/d3d9_bridge.cpp @@ -29,66 +29,288 @@ 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(); + HRESULT DxvkLegacyD3DDeviceBridge::CopyRects( + IDirect3DSurface9* pSourceSurface, + const RECT* pSourceRectsArray, + UINT cRects, + IDirect3DSurface9* pDestinationSurface, + const POINT* pDestPointsArray) { + D3D9DeviceLock lock = m_device->LockDevice(); - D3D9Surface* dst = static_cast(pDestSurface); - D3D9Surface* src = static_cast(pSrcSurface); + // The D3D8 side already checks for NULLs and Src == Dst. - if (unlikely(dst == nullptr || src == nullptr)) - return D3DERR_INVALIDCALL; + D3D9Surface* src = static_cast(pSourceSurface); + D3D9Surface* dst = static_cast(pDestinationSurface); - // CopyRects will not pass a null pSrcRect, but check anyway - if (unlikely(pSrcRect == nullptr)) - return D3DERR_INVALIDCALL; + D3D9CommonTexture* srcTex = src->GetCommonTexture(); + D3D9CommonTexture* dstTex = dst->GetCommonTexture(); - // 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; + VkExtent3D srcTexLevelExtent = srcTex->GetExtentMip(src->GetMipLevel()); + VkExtent3D dstTexLevelExtent = dstTex->GetExtentMip(dst->GetMipLevel()); - // CopyRects will not pass a null pDestPoint, but check anyway - if (unlikely(pDestPoint == nullptr)) + // 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("D3D8Device::CopyRects: Formats that need conversion are only supported on image to image copies"); + return D3DERR_NOTAVAILABLE; + } + + // 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); + + // 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 srcOffset = { pSrcRect->left, - pSrcRect->top, - 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; + } - VkExtent3D extent = { uint32_t(pSrcRect->right - pSrcRect->left), uint32_t(pSrcRect->bottom - pSrcRect->top), 1 }; + 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; + } + } + + bool srcHasImage = srcTex->GetImage() != nullptr; + bool dstHasImage = dstTex->GetImage() != nullptr; + + // 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()); + + 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 = { pDestPoint->x, - pDestPoint->y, - 0u }; + 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 }; - m_device->UpdateTextureFromBuffer( - srcTextureInfo, dstTextureInfo, - src->GetSubresource(), dst->GetSubresource(), - srcOffset, extent, dstOffset - ); + 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; - dstTextureInfo->SetNeedsReadback(dst->GetSubresource(), true); + 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 (dstTextureInfo->IsAutomaticMip()) - m_device->MarkTextureMipsDirty(dstTextureInfo); + 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("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("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. + // 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..dcc178bc 100644 --- a/src/d3d9/d3d9_bridge.h +++ b/src/d3d9/d3d9_bridge.h @@ -29,18 +29,20 @@ IDxvkLegacyD3DDeviceBridge : public IUnknown { #endif /** - * \brief Updates a D3D9 surface from a D3D9 buffer + * \brief D3D8 CopyRects implementation * - * \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 + * \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 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 +77,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 +87,9 @@ namespace dxvk { class D3D9DeviceEx; class D3D9InterfaceEx; + class D3D9CommonTexture; + + struct DxvkFormatInfo; class DxvkLegacyD3DDeviceBridge : public IDxvkLegacyD3DDeviceBridge { @@ -97,16 +105,37 @@ 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_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index 9d5fe38d..111fcda1 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -85,19 +85,20 @@ 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 += GetMipSize(i); + 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); + EnsureBufferExists(false); // No need to initialize here, D3D9Initializer takes care of that. } @@ -174,6 +175,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)) { @@ -208,7 +212,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; } @@ -298,6 +304,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; } @@ -316,12 +326,15 @@ 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; + + 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 = Size; + info.size = GetTotalSize(); info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; @@ -350,10 +363,12 @@ namespace dxvk { std::memset(m_buffer->mapPtr(0), 0, m_totalSize); } m_data = {}; + + return m_buffer; } - 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 @@ -812,13 +827,8 @@ namespace dxvk { } - const Rc& D3D9CommonTexture::GetBuffer() { - return m_buffer; - } - - 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..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 { @@ -412,7 +416,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; } @@ -496,23 +499,24 @@ 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; + // 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); } /** * \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 4ecf356c..df93527e 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(); @@ -272,6 +275,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; } @@ -448,19 +455,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 +493,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 +519,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 +527,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; } @@ -624,9 +630,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 +712,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 +778,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)); @@ -1161,9 +1164,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)) @@ -1176,7 +1177,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); @@ -1232,11 +1233,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 +1261,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 +1311,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 +1328,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 +1366,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. @@ -1407,39 +1401,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; @@ -4481,7 +4470,6 @@ namespace dxvk { HRESULT hr; if (likely(m_deviceType != D3DDEVTYPE_NULLREF)) { hr = m_parent->ValidatePresentationParametersEx(pPresentationParameters, pFullscreenDisplayMode); - if (unlikely(FAILED(hr))) return hr; } @@ -4531,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++; } @@ -5031,88 +5019,48 @@ 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->GetMapMode() == D3D9_COMMON_TEXTURE_MAP_MODE_BACKED || needsReadback)) { - // Create mapping buffer if it doesn't exist yet. (POOL_DEFAULT) - pResource->CreateBuffer(!needsReadback, pResource->GetTotalSize()); - } - - // 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) - // 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) { - 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 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), - cImage = std::move(mappedImage), - cSubresources = subresourceLayers, + cImageBufferSlice = pResource->GetBufferSlice(Subresource), + cImage = pResource->GetImage(), + cSubresources = vk::makeSubresourceLayers(subresource), cLevelExtent = levelExtent, cPackedFormat = packedFormat ] (DxvkContext* ctx) { @@ -5125,7 +5073,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; } @@ -5271,6 +5219,10 @@ namespace dxvk { if (pResource->IsAutomaticMip()) MarkTextureMipsDirty(pResource); + pResource->SetNeedsUpload(Subresource, false); + if (!pResource->NeedsAnyUpload()) + MarkTextureUploaded(pResource); + return D3D_OK; } @@ -5304,14 +5256,19 @@ 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. + 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. @@ -5398,7 +5355,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); @@ -6122,9 +6079,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 +6092,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 +6108,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. @@ -6175,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); }); } @@ -6488,7 +6423,6 @@ namespace dxvk { } pResource->ClearDirtyBoxes(); - pResource->ClearNeedsUpload(); } @@ -6578,6 +6512,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); @@ -8635,81 +8576,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; @@ -8721,7 +8663,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; @@ -8762,7 +8704,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; @@ -8928,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 83d72c2f..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(); @@ -1663,7 +1662,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_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; + } } } 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_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_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; } diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index d672f115..28839592 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)) @@ -281,7 +279,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(); @@ -629,6 +627,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; } @@ -759,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) @@ -841,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; @@ -887,15 +885,18 @@ namespace dxvk { cDevice = m_device, cPresenter = m_wctx->presenter, cBlitter = m_blitter, + cHud = m_hud, cColorSpace = m_colorspace, cSrcView = swapImageView, cSrcRect = srcRect, 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); + // Update back buffer color space as necessary if (cSrcView->image()->info().colorSpace != cColorSpace) { DxvkImageUsageInfo usage = { }; @@ -907,40 +908,21 @@ 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 ctx->synchronizeWsi(cSync); - ctx->flushCommandList(nullptr, nullptr); + ctx->flushCommandList(nullptr, 0u); - cDevice->presentImage(cPresenter, cLatency, cFrameId, 0, nullptr, nullptr); + cDevice->presentImage(cPresenter, cFrameId, 0, 0, nullptr); }); 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(); @@ -1008,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; @@ -1084,22 +1063,18 @@ namespace dxvk { void D3D9SwapChainEx::CreateBlitter() { - Rc hud = hud::Hud::createHud(m_device); - - if (hud) { - m_apiHud = hud->addItem("api", 1, GetApiName()); + m_hud = hud::Hud::createHud(m_device); - if (m_latencyTracking) - m_latencyHud = hud->addItem("latency", 4); - - hud->addItem("swvp", -1, m_parent); + if (m_hud) { + m_apiHud = m_hud->addItem("api", 1, GetApiName()); + 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); } @@ -1114,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 811124d4..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(); @@ -149,6 +148,8 @@ namespace dxvk { Rc m_device; Rc m_blitter; + Rc m_hud; + std::unordered_map< HWND, D3D9WindowContext> m_presenters; @@ -178,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; @@ -200,8 +197,6 @@ namespace dxvk { void CreateBlitter(); - void DestroyLatencyTracker(); - void InitRamp(); void UpdateTargetFrameRate(uint32_t SyncInterval); 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; 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) { 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: 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; 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) 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 108ce649..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; } } @@ -440,30 +432,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); } 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_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_context.cpp b/src/dxvk/dxvk_context.cpp index 6692c215..fb9676c9 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -139,30 +139,9 @@ 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, - 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)); @@ -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, status); - - // 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(); @@ -5033,7 +4996,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 @@ -5420,13 +5383,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 @@ -5902,7 +5865,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; @@ -7700,7 +7663,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; @@ -9114,8 +9077,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()); @@ -10937,7 +10902,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 +10921,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); diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h index 58d16531..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,41 +66,17 @@ 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 * * 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 @@ -1190,7 +1165,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 @@ -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_cs.cpp b/src/dxvk/dxvk_cs.cpp index 06f6aa48..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; } @@ -257,7 +260,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 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; diff --git a/src/dxvk/dxvk_device.cpp b/src/dxvk/dxvk_device.cpp index 02218254..ce957191 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" @@ -179,12 +177,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); } @@ -584,42 +577,21 @@ 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, + uint64_t latencyFrameId, uint32_t rectCount, - const VkRectLayerKHR* rects, - DxvkSubmitStatus* status) { + 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]); - DxvkLatencyInfo latencyInfo; - latencyInfo.tracker = tracker; - latencyInfo.frameId = frameId; - - m_submissionQueue.present(presentInfo, latencyInfo, status); + m_submissionQueue.present(presentInfo); std::lock_guard statLock(m_statLock); m_statCounters.addCtr(DxvkStatCounter::QueuePresentCount, 1); @@ -628,32 +600,24 @@ namespace dxvk { void DxvkDevice::submitCommandList( const Rc& commandList, - const Rc& tracker, - uint64_t frameId, - DxvkSubmitStatus* status) { + uint64_t submissionId) { DxvkSubmitInfo submitInfo = { }; submitInfo.cmdList = commandList; - DxvkLatencyInfo latencyInfo; - latencyInfo.tracker = tracker; - latencyInfo.frameId = frameId; - - m_submissionQueue.submit(submitInfo, latencyInfo, status); + m_submissionQueue.submit(submitInfo, 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::setSubmissionTimeout(uint64_t timeout) { + m_submissionQueue.setSubmissionTimeout(timeout); + } + + + void DxvkDevice::waitForSubmission(uint64_t submissionId) { + m_submissionQueue.synchronizeSubmission(submissionId); } @@ -779,12 +743,15 @@ 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); - if (lowerSinCos) m_shaderOptions.flags.set(DxvkShaderCompileFlag::LowerSinCos); @@ -795,6 +762,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_device.h b/src/dxvk/dxvk_device.h index 7fb070d5..d8cb7d86 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 * @@ -648,19 +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 [out] status Present status */ void presentImage( const Rc& presenter, - const Rc& tracker, uint64_t frameId, + uint64_t latencyFrameId, uint32_t rectCount, - const VkRectLayerKHR* rects, - DxvkSubmitStatus* status); + const VkRectLayerKHR* rects); /** * \brief Submits a command list @@ -668,15 +655,17 @@ 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 [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 Sets queue submission timeout + * \param [in] timeout Timeout, in nanoseconds + */ + void setSubmissionTimeout(uint64_t timeout); /** * \brief Locks submission queue @@ -712,12 +701,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 @@ -772,7 +759,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_device_info.cpp b/src/dxvk/dxvk_device_info.cpp index ab1f5634..04ff33d8 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( @@ -553,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 @@ -1114,6 +1119,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); }; 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; diff --git a/src/dxvk/dxvk_graphics.cpp b/src/dxvk/dxvk_graphics.cpp index 9823bace..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]; @@ -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_image.cpp b/src/dxvk/dxvk_image.cpp index 3b5b7390..5bcb22ab 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; + acquire.pTimeout = milliseconds == INFINITE ? nullptr : &timeout; 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; @@ -334,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); 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 }; 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 b399f99a..00000000 --- a/src/dxvk/dxvk_latency_builtin.cpp +++ /dev/null @@ -1,409 +0,0 @@ -#include - -#include "dxvk_latency_builtin.h" - -#include "../util/log/log.h" - -#include "../util/util_fps_limiter.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_memory.cpp b/src/dxvk/dxvk_memory.cpp index fc18044d..1ee9d589 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" @@ -693,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; @@ -701,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; @@ -1074,9 +1083,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 +1231,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); } } @@ -1306,6 +1314,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 +1335,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; @@ -1346,14 +1358,12 @@ 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)) { - 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; @@ -1371,7 +1381,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; @@ -1583,9 +1593,11 @@ namespace dxvk { } - DxvkResourceAllocation* DxvkMemoryAllocator::createAllocation( + DxvkResourceAllocation* DxvkMemoryAllocator::allocateSparsePageTable( 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; @@ -1722,6 +1734,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, @@ -1771,7 +1815,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; @@ -2639,42 +2683,38 @@ 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; + // 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); - 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 (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 }; - for (uint32_t i = 0u; i < pool.chunks.size(); i++) { - pool.nextEvictChunk += 1u; - pool.nextEvictChunk %= pool.chunks.size(); + pool.nextEvictChunk += 1u; + pool.nextEvictChunk %= pool.chunks.size(); - if (pool.pageAllocator.chunkIsAvailable(pool.nextEvictChunk)) - break; - } + if (chunkIndices[0u] != pool.nextEvictChunk) + chunkIndices[1u] = 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]; @@ -2699,31 +2739,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 8da44ed6..7929166e 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; }; @@ -1132,6 +1131,8 @@ namespace dxvk { DxvkRelocationEntry, RelocationOrdering> m_entries; + DxvkResourceMemoryInfo m_lastKey = {}; + }; @@ -1458,6 +1459,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, @@ -1490,7 +1496,7 @@ namespace dxvk { const DxvkDeviceMemory& memory, const DxvkAllocationInfo& allocationInfo); - DxvkResourceAllocation* createAllocation( + DxvkResourceAllocation* allocateSparsePageTable( DxvkSparsePageTable* sparsePageTable, const DxvkAllocationInfo& allocationInfo); 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)); 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); 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())); } diff --git a/src/dxvk/dxvk_options.cpp b/src/dxvk/dxvk_options.cpp index 52e7b915..ed52368e 100644 --- a/src/dxvk/dxvk_options.cpp +++ b/src/dxvk/dxvk_options.cpp @@ -14,16 +14,15 @@ 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); + 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); - 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); + 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..994fa9ea 100644 --- a/src/dxvk/dxvk_options.h +++ b/src/dxvk/dxvk_options.h @@ -46,15 +46,9 @@ 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; + Tristate enableNvLowLatency2 = Tristate::Auto; // Hides integrated GPUs if dedicated GPUs are // present. May be necessary for some games that @@ -92,6 +86,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_presenter.cpp b/src/dxvk/dxvk_presenter.cpp index 643f7c5d..efb0e410 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); } @@ -146,18 +150,32 @@ 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); } } // 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); @@ -170,13 +188,11 @@ namespace dxvk { VkResult Presenter::presentImage( uint64_t frameId, - const Rc& tracker, + uint64_t latencyFrameId, uint32_t rectCount, 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; @@ -201,35 +217,43 @@ 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; - bool waitForPresent = m_hasPresentWait && isFifoMode; + auto frameTargetTime = PresenterTimingFeedback(); + auto frameIntervalTicks = uint64_t(0); 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); - 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.targetTimeDomainPresentStage = m_timingMode.presentStage; - - if (m_timingDisplayInfo && !m_timingDisplayInfo->isVariableRefresh) - timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_NEAREST_REFRESH_CYCLE_BIT_EXT; + { 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; + } - frameDeadline = timingInfo.targetTime + 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 && frameTargetTime.presentTimeLocal) { + timingInfo.targetTime = frameTargetTime.presentTimeLocal; + timingInfo.targetTimeDomainPresentStage = m_timingMode.presentStage; - // Skip present_wait in fixed refresh mode if the frame is timed - if (!m_timingDisplayInfo || !m_timingDisplayInfo->isVariableRefresh) - waitForPresent = waitForPresent && !timingInfo.targetTime; + if (m_timingDisplayInfo && !m_timingDisplayInfo->isVariableRefresh) + timingInfo.flags |= VK_PRESENT_TIMING_INFO_PRESENT_AT_NEAREST_REFRESH_CYCLE_BIT_EXT; + } } } @@ -250,7 +274,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); } @@ -262,15 +286,14 @@ 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); - // 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; + 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 @@ -283,6 +306,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) { @@ -290,18 +316,21 @@ namespace dxvk { m_frameIndex += 1; m_frameIndex %= m_semaphores.size(); + + if (timingInfo.presentStageQueries) + m_reportQueueAddId = frameId; } // Add frame to waiter queue with current properties PresenterFrame frame; frame.frameId = frameId; - frame.tracker = tracker; frame.mode = m_presentMode; frame.result = status; - frame.targetTime = frameDeadline ? timingInfo.targetTime : 0u; - frame.deadline = frameDeadline; - frame.isTimed = bool(timingInfo.targetTime); - frame.doWait = waitForPresent; + frame.targetTimeHost = frameTargetTime.presentTimeHost; + frame.targetStage = timingInfo.targetTimeDomainPresentStage; + frame.deadlineHost = frameTargetTime.presentTimeHost ? frameTargetTime.presentTimeHost + frameIntervalTicks : 0u; + frame.timingDomainId = timingInfo.timeDomainId; + frame.stageQueries = timingInfo.presentStageQueries; pushFrame(frame); @@ -334,9 +363,7 @@ namespace dxvk { } - void Presenter::signalFrame( - uint64_t frameId, - const Rc& tracker) { + void Presenter::signalFrame(uint64_t frameId) { if (m_signal == nullptr || !frameId) return; @@ -400,8 +427,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) @@ -421,21 +451,23 @@ 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; } - 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) { @@ -443,14 +475,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; @@ -458,46 +485,43 @@ 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; - info.value = ++m_latencySleepCounter; + info.value = m_latencySleepCounter + 1u; m_vkd->vkLatencySleepNV(m_vkd->device(), m_swapchain, &info); lock.unlock(); - auto t0 = dxvk::high_resolution_clock::now(); - VkSemaphoreWaitInfo waitInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO }; waitInfo.semaphoreCount = 1; waitInfo.pSemaphores = &info.signalSemaphore; waitInfo.pValues = &info.value; + auto t0 = dxvk::high_resolution_clock::now(); m_vkd->vkWaitSemaphores(m_vkd->device(), &waitInfo, ~0ull); - auto t1 = dxvk::high_resolution_clock::now(); - return t1 - t0; + + lock.lock(); + + m_latencySleepCounter += 1u; + m_latencySleepDurationUs += std::chrono::duration_cast(t1 - t0).count(); } - uint32_t Presenter::getLatencyTimingsNv( + uint32_t Presenter::getLatencyTimings( uint32_t timingCount, VkLatencyTimingsFrameReportNV* timings) { std::unique_lock lock(m_surfaceMutex); @@ -531,13 +555,20 @@ 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(); + + m_latencySleepModeDirty.store(true, std::memory_order_release); } } @@ -581,8 +612,24 @@ namespace dxvk { PresenterTimingFeedback Presenter::queryPresentTiming() { - std::lock_guard lock(m_timingMutex); - return m_timingFeedback; + return updatePresentTimingFeedback(0u, 0u); + } + + + 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; } @@ -782,10 +829,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) { @@ -875,6 +919,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; @@ -979,16 +1026,15 @@ 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(); updateTimingDomains(); recalibrateTimeDomains(); - - updateTimingMode(); } + updateTimingMode(); return VK_SUCCESS; } @@ -1382,7 +1428,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]; } @@ -1438,15 +1484,19 @@ namespace dxvk { m_timingMode.relativeTiming = false; m_timingMode.absoluteTiming = false; - // Can't meaningfully do timing if we don't know about display timing - if (!m_timingDomains || !m_timingDisplayInfo) - return; + // Reset reference time and frame ID for absolute timing + // so that we don't end up submitting bogus timestamps. + m_timingMode.reference = PresenterTimingFeedback(); // 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 @@ -1460,11 +1510,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 @@ -1479,15 +1531,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; - 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)); @@ -1499,9 +1542,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; @@ -1527,23 +1570,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++) @@ -1552,30 +1584,32 @@ 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; } - bool Presenter::updatePresentTiming(uint64_t frameId) { + PresenterTimingFeedback 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.presentTimeLocal = 0u; + feedback.presentTimeHost = frameId ? dxvk::high_resolution_clock::get_counter() : 0u; if (!m_timingMode.presentStage) { - commitTimingFeedback(feedback); - return false; + const PresenterFrame* frameEntry = findFrameEntry(frameId); + return commitTimingFeedback(feedback, !frameEntry || feedback.presentTimeHost > frameEntry->deadlineHost); } - // Still need to drain the queue even if everything is messed up - small_vector stageTimes; - small_vector reports; + // Query reports in-order so that we can somewhat robustly + // track how many frames are still in the report queue + std::array stageTimes = {}; + std::array reports = {}; - stageTimes.resize(m_timingQueueSize); - reports.resize(m_timingQueueSize); - - 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]; @@ -1593,8 +1627,7 @@ namespace dxvk { if (status) { Logger::warn(str::format("Presenter: Failed to query past present timings: ", status)); - commitTimingFeedback(feedback); - return false; + return commitTimingFeedback(feedback, true); } // Update display timing and time domains as necessary @@ -1609,7 +1642,8 @@ namespace dxvk { updateMode = true; updateTimingDomains(); - recalibrateTimeDomains(); + if (!recalibrateTimeDomains()) + return commitTimingFeedback(feedback, true); } if (updateMode) @@ -1625,91 +1659,123 @@ 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; - uint64_t reportTimeLocal = translateTimestamp( - report.timeDomain, report.timeDomainId, time.time, - VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT, m_timingMode.timeDomainId); + // 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); - uint64_t reportTimeQpc = 0u; + 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 = findFrameEntry(report.presentId); - if (hasQpcDomain()) { - reportTimeQpc = translateTimestamp( - report.timeDomain, report.timeDomainId, time.time, - VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR, 0u); + 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, 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); } if (reportTimeLocal) { - m_timingMode.lastFrameId = report.presentId; - 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.deadline; - } + m_timingMode.lastReport.frameId = report.presentId; + m_timingMode.lastReport.presentTimeLocal = reportTimeLocal; + m_timingMode.lastReport.presentTimeHost = reportTimeHost; + + hasMissedDeadline = reportTimeHost > frameEntry->deadlineHost; } } - 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 the host domain. + if (m_timingMode.lastReport.presentTimeHost && (!frameId || queryStages)) + feedback = m_timingMode.lastReport; - // 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. - if (hasQpcDomain()) { - feedback.frameId = m_timingMode.lastFrameId; - feedback.presentTime = m_timingMode.lastFrameTimeQpc; + if (!hasMissedDeadline) { + const PresenterFrame* frameEntry = findFrameEntry(feedback.frameId); + hasMissedDeadline = frameEntry && feedback.presentTimeHost > frameEntry->deadlineHost; } - commitTimingFeedback(feedback); - return true; + return commitTimingFeedback(feedback, hasMissedDeadline); } - void Presenter::commitTimingFeedback( - const PresenterTimingFeedback& feedback) { + PresenterTimingFeedback Presenter::commitTimingFeedback( + 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; } - void Presenter::waitUntilFrameTargetTime( + PresenterTimingFeedback Presenter::computeFrameTargetTime( + uint64_t frameId) const { + if (!m_timingMode.frameIntervalNs || !m_timingMode.reference.frameId) + return PresenterTimingFeedback(); + + 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; + } + + + bool Presenter::waitUntilFrameTargetTime( const PresenterFrame& frame) { - if (!frame.targetTime) - return; + if (!frame.targetTimeHost) + return false; - uint64_t qpcTargetTime = 0u; + auto now = dxvk::high_resolution_clock::now(); + auto dxvkTargetTime = dxvk::high_resolution_clock::get_time_from_counter(frame.targetTimeHost); - { 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_QUERY_PERFORMANCE_COUNTER_KHR, 0u); - } - } + if (now >= dxvkTargetTime) + return true; - if (qpcTargetTime) { - auto dxvkTargetTime = dxvk::high_resolution_clock::get_time_from_counter(qpcTargetTime); - Sleep::sleepUntil(dxvk::high_resolution_clock::now(), dxvkTargetTime); + // 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; } - 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; } @@ -1746,8 +1812,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. @@ -1798,10 +1864,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 ", @@ -1871,6 +1935,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 @@ -1921,6 +2067,9 @@ namespace dxvk { m_timingDomains = std::nullopt; m_timingDisplayInfo = std::nullopt; m_timingMode = PresenterTimingInfo(); + + m_calibrationFailed = false; + m_calibrationWarned = false; } @@ -1972,6 +2121,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"); @@ -1995,10 +2154,17 @@ 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) { + // 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.targetStage && waitUntilFrameTargetTime(frame); + + // 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) { VkResult vr; if (m_device->features().khrPresentWait2.presentWait2) { @@ -2016,20 +2182,15 @@ 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); - // 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 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) waitUntilFrameTargetTime(frame); - else - m_fpsLimiter.delay(); // 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 d470c2dd..008e4c33 100644 --- a/src/dxvk/dxvk_presenter.h +++ b/src/dxvk/dxvk_presenter.h @@ -8,17 +8,16 @@ #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" #include "../util/sync/sync_signal.h" +#include "../util/sync/sync_ticketlock.h" #include "../vulkan/vulkan_loader.h" #include "dxvk_format.h" #include "dxvk_image.h" -#include "dxvk_latency.h" namespace dxvk { @@ -52,18 +51,27 @@ 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 */ 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; + VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR; + VkResult result = VK_NOT_READY; + uint64_t targetTimeHost = 0u; + VkPresentStageFlagsEXT targetStage = 0u; + uint64_t deadlineHost = 0u; + uint64_t timingDomainId = 0u; + VkPresentStageFlagsEXT stageQueries = 0u; }; /** @@ -80,7 +88,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; @@ -124,19 +131,19 @@ 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 lastFrameTimeQpc = 0u; - uint64_t lastFrameId = 0u; + PresenterTimingFeedback reference = {}; + PresenterTimingFeedback lastReport = {}; }; /** - * \brief Timing info for the last presented frame + * \brief Latency stats + * + * Used for informational purposes only. */ - struct PresenterTimingFeedback { - uint64_t frameId = 0u; - uint64_t presentTime = 0u; + struct PresenterLatencyInfo { + uint64_t frameLatencyUs = 0u; + uint64_t sleepDurationUs = 0u; + uint64_t sleepCount = 0u; }; /** @@ -147,8 +154,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( @@ -193,14 +199,14 @@ namespace dxvk { * * Presents the last successfuly acquired image. * \param [in] frameId Frame number. - * \param [in] tracker Latency tracker + * \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, - const Rc& tracker, + uint64_t latencyFrameId, uint32_t rectCount, const VkRectLayerKHR* rects); @@ -212,11 +218,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 @@ -296,7 +299,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); /** @@ -306,9 +309,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); @@ -317,9 +319,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 @@ -328,7 +329,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); @@ -342,6 +343,12 @@ namespace dxvk { */ PresenterTimingFeedback queryPresentTiming(); + /** + * \brief Queries latency statistics + * \returns Latency statistics + */ + PresenterLatencyInfo queryLatencyStats(); + private: Rc m_device; @@ -369,6 +376,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; @@ -392,8 +402,9 @@ 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; + std::atomic m_latencySleepModeDirty = { false }; bool m_latencySleepSupported = false; alignas(CACHE_LINE_SIZE) @@ -411,20 +422,20 @@ 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; PresenterTimingInfo m_timingMode = { }; PresenterTimingFeedback m_timingFeedback = {}; - uint32_t m_timingQueueSize = FrameQueueSize; double m_frameRateLimit = 0.0; - alignas(CACHE_LINE_SIZE) - FpsLimiter m_fpsLimiter; + uint64_t m_reportQueueAddId = 0u; + uint64_t m_reportQueueTakeId = 0u; bool m_hasGamescopeFenceSignalBug = false; + bool m_hasNvidiaFenceSignalBug = false; static const std::array, 2> s_colorSpaceFallbacks; @@ -478,17 +489,24 @@ namespace dxvk { void updateTimingMode(); - void recalibrateTimeDomains(); + bool recalibrateTimeDomains(); + + PresenterTimingFeedback updatePresentTimingFeedback( + uint64_t frameId, + VkPresentStageFlagsEXT queryStages); - bool updatePresentTiming(uint64_t frameId); + PresenterTimingFeedback commitTimingFeedback( + const PresenterTimingFeedback& feedback, + bool updateReference); - void commitTimingFeedback( - const PresenterTimingFeedback& feedback); + PresenterTimingFeedback computeFrameTargetTime( + uint64_t frameId) const; - void waitUntilFrameTargetTime( + bool waitUntilFrameTargetTime( const PresenterFrame& frame); - bool hasQpcDomain(); + bool hasTimeDomain( + VkTimeDomainKHR timeDomain) const; uint64_t translateTimestamp( VkTimeDomainKHR srcTimeDomain, @@ -501,6 +519,11 @@ namespace dxvk { VkResult createLatencySemaphore(); + VkResult getCalibratedTimestamps( + uint32_t timestampCount, + const VkCalibratedTimestampInfoKHR* pTimestampInfos, + uint64_t* pTimestamps); + void destroySwapchain(); void destroySurface(); @@ -512,6 +535,9 @@ namespace dxvk { void pushFrame(const PresenterFrame& frame); + const PresenterFrame* findFrameEntry( + uint64_t frameId) const; + void runFrameThread(); static VkResult softError( diff --git a/src/dxvk/dxvk_queue.cpp b/src/dxvk/dxvk_queue.cpp index e705ed91..cfb81a5d 100644 --- a/src/dxvk/dxvk_queue.cpp +++ b/src/dxvk/dxvk_queue.cpp @@ -46,8 +46,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,9 +54,8 @@ namespace dxvk { }); DxvkSubmitEntry entry = { }; - entry.status = status; + entry.timeline = submissionId; entry.submit = std::move(submitInfo); - entry.latency = std::move(latencyInfo); m_submitQueue.push(std::move(entry)); m_appendCond.notify_all(); @@ -65,28 +63,19 @@ namespace dxvk { void DxvkSubmissionQueue::present( - DxvkPresentInfo presentInfo, - DxvkLatencyInfo latencyInfo, - DxvkSubmitStatus* status) { + DxvkPresentInfo presentInfo) { std::unique_lock lock(m_mutex); DxvkSubmitEntry entry = { }; - entry.status = status; entry.present = std::move(presentInfo); - entry.latency = std::move(latencyInfo); m_submitQueue.push(std::move(entry)); m_appendCond.notify_all(); } - 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); } @@ -128,12 +117,15 @@ namespace dxvk { } + void DxvkSubmissionQueue::setSubmissionTimeout(uint64_t timeout) { + std::unique_lock lock(m_mutex); + m_submissionTimeout = timeout; + } + + void DxvkSubmissionQueue::submitCmdLists() { env::setThreadName("dxvk-submit"); - uint64_t trackedSubmitId = 0u; - uint64_t trackedPresentId = 0u; - while (!m_stopped.load()) { DxvkSubmitEntry entry; @@ -149,68 +141,49 @@ 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) 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; - } - - entry.result = entry.submit.cmdList->submit( - m_semaphores, m_timelines, trackedSubmitId); + status = entry.submit.cmdList->submit( + 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); - - entry.result = 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); - - trackedPresentId = entry.latency.frameId; - trackedSubmitId = 0u; - } + status = entry.present.presenter->presentImage(entry.present.frameId, + entry.present.latencyFrameId, entry.present.rects.size(), + entry.present.rects.data()); } 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(); } @@ -232,6 +205,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(); @@ -257,18 +231,19 @@ 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(); 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, submissionTimeout); - if (entry.latency.tracker && status == VK_SUCCESS) - entry.latency.tracker->notifyGpuExecutionEnd(entry.latency.frameId); + if (status == VK_TIMEOUT) { + Logger::err("DxvkSubmissionQueue: Timeout occured, GPU likely hung."); + status = VK_ERROR_DEVICE_LOST; + } } if (status == VK_ERROR_DEVICE_LOST && m_checkpoints) @@ -284,7 +259,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 5e056902..817009c9 100644 --- a/src/dxvk/dxvk_queue.h +++ b/src/dxvk/dxvk_queue.h @@ -4,10 +4,11 @@ #include #include +#include "../util/sync/sync_signal.h" + #include "../util/thread.h" #include "dxvk_cmdlist.h" -#include "dxvk_latency.h" #include "dxvk_presenter.h" namespace dxvk { @@ -15,17 +16,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,33 +34,20 @@ namespace dxvk { * a swap chain image on the device. */ struct DxvkPresentInfo { - Rc presenter; - uint64_t frameId; + Rc presenter = nullptr; + uint64_t frameId = 0u; + uint64_t latencyFrameId = 0u; small_vector rects; }; - /** - * \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 */ struct DxvkSubmitEntry { - VkResult result; - DxvkSubmitStatus* status; + uint64_t timeline = 0u; DxvkSubmitInfo submit; DxvkPresentInfo present; - DxvkLatencyInfo latency; DxvkTimelineSemaphoreValues timelines; }; @@ -118,13 +95,11 @@ 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, - DxvkSubmitStatus* status); + uint64_t submissionId); /** * \brief Presents an image synchronously @@ -133,23 +108,18 @@ 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, - DxvkSubmitStatus* status); + DxvkPresentInfo presentInfo); /** * \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 @@ -193,6 +163,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: @@ -221,6 +199,10 @@ namespace dxvk { dxvk::thread m_submitThread; dxvk::thread m_finishThread; + sync::Fence m_submitTimeline; + + uint64_t m_submissionTimeout = 5'000'000'000ull; + void submitCmdLists(); void finishCmdLists(); 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 +} 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 d7619c70..1c6903f2 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; @@ -406,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); @@ -663,6 +665,9 @@ namespace dxvk { SpirvCodeBuffer getShaderCode( VkShaderStageFlagBits stage) const; + DxvkShaderMetadata getShaderMetadata( + VkShaderStageFlagBits stage) const; + void generateModuleIdentifierLocked( VkShaderModuleIdentifierEXT* identifier, const SpirvCodeBuffer& spirvCode); diff --git a/src/dxvk/dxvk_shader_cache.cpp b/src/dxvk/dxvk_shader_cache.cpp index 0bec8399..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)); @@ -668,11 +669,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 +701,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; diff --git a/src/dxvk/dxvk_shader_ir.cpp b/src/dxvk/dxvk_shader_ir.cpp index 85fd0aef..3912549e 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)) @@ -2260,6 +2261,9 @@ namespace dxvk { options.derivativeOptions.hoistDescriptorLoads = true; 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/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 = { }; diff --git a/src/dxvk/dxvk_shader_spirv.cpp b/src/dxvk/dxvk_shader_spirv.cpp index 2e2642e3..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) { @@ -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); 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); } /** 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( diff --git a/src/dxvk/hud/dxvk_hud.cpp b/src/dxvk/hud/dxvk_hud.cpp index 0ef6e85e..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); @@ -35,8 +36,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..e941de4f 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,16 +183,31 @@ namespace dxvk::hud { HudFpsItem::~HudFpsItem() { } - void HudFpsItem::update(dxvk::high_resolution_clock::time_point time) { - m_frameCount += 1; - + void HudFpsItem::update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time) { 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; } } @@ -565,7 +582,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 +649,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 +713,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 +760,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 +843,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 +903,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 +1220,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 +1305,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 +1353,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); @@ -1386,57 +1421,31 @@ namespace dxvk::hud { - 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; - } - } + HudLatencyItem::HudLatencyItem() { } + HudLatencyItem::~HudLatencyItem() { } - 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) { - 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_lastUpdate = time; - m_latencyString = str::format(latency / 10, ".", latency % 10, " ms"); - m_sleepString = str::format(sleep / 10, ".", sleep % 10, " ms"); + auto newStats = presenter.queryLatencyStats(); + auto oldStats = std::exchange(m_stats, newStats); - m_accumStats = { }; - m_accumFrames = 0u; + if (!newStats.sleepCount) + return; - m_invalidUpdates = 0u; - } else { - m_latencyString = "--"; - m_sleepString = "--"; + uint64_t latency = newStats.frameLatencyUs / 100u; + uint64_t sleepTime = (newStats.sleepDurationUs - oldStats.sleepDurationUs) / + (100u * std::max(newStats.sleepCount - oldStats.sleepCount, uint64_t(1u))); - if (m_invalidUpdates < MaxInvalidUpdates) - m_invalidUpdates += 1u; - } - - m_lastUpdate = time; + m_sleep = str::format(sleepTime / 10u, ".", sleepTime % 10, " ms"); + m_latency = str::format(latency / 10u, ".", latency % 10, " ms"); } } @@ -1447,20 +1456,18 @@ namespace dxvk::hud { 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); + 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 += 20; + position.y += 16; + renderer.drawText(16, position, 0xffff60a0u, "Sleep:"); + renderer.drawText(16, { position.x + 108, position.y }, 0xffffffffu, m_sleep); - renderer.drawText(16, position, 0xffff60a0u, "Sleep: "); - renderer.drawText(16, { position.x + 108, position.y }, 0xffffffffu, m_sleepString); + position.y += 8; + } - position.y += 8; return position; } diff --git a/src/dxvk/hud/dxvk_hud_item.h b/src/dxvk/hud/dxvk_hud_item.h index e75c4600..9d0e20b6 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, @@ -235,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(); @@ -360,7 +365,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 +408,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 +448,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 +481,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 +526,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 +559,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 +644,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 +692,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 +729,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, @@ -736,22 +759,16 @@ 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(dxvk::high_resolution_clock::time_point time); + void update( + Presenter& presenter, + dxvk::high_resolution_clock::time_point time); HudPos render( const Rc&ctx, @@ -762,19 +779,14 @@ namespace dxvk::hud { 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(); + std::string m_sleep; + std::string m_latency; + + PresenterLatencyInfo m_stats = {}; + }; } 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', diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp index a92edbd8..a32ef3c3 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" }, @@ -958,7 +962,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 */ @@ -1016,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$)", {{ 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/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 }; 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__) 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/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); - - }; - -} diff --git a/src/util/util_time.h b/src/util/util_time.h index bcc311b6..7d92e8b8 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,95 @@ 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; } + + 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; + } + }; +#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); + } + + 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; + } }; #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(); } + + 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; + } }; #endif -} \ No newline at end of file +} 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; 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); 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); diff --git a/subprojects/dxbc-spirv b/subprojects/dxbc-spirv index c7f06970..d82d9eaa 160000 --- a/subprojects/dxbc-spirv +++ b/subprojects/dxbc-spirv @@ -1 +1 @@ -Subproject commit c7f069701227dcb800a01bd93e42f8001b7543e7 +Subproject commit d82d9eaa6cab5c7277a6839f328e1b4d2f146894