diff --git a/README.md b/README.md index eb192999574..72c02960315 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ A huge thank-you to the following contributors for their invaluable help: - [Device Filter](#device-filter) - [State Cache](#state-cache) - [Shader Compilation (dyasync)](#shader-compilation-dyasync) +- [Frame Pacing (low-latency mode)](#frame-pacing-low-latency-mode) +- [Debugging](#debugging) - [Debugging](#debugging) - [Troubleshooting](#troubleshooting) @@ -178,6 +180,21 @@ Dyasync can be disabled by setting `dxvk.enableDyasync = False` in `dxvk.conf`, `DXVK_ALL_CORES=1` overrides the default way cores are assigned for shader compilation. By default, DXVK-Sarek uses roughly half the available CPU cores for background compilation, leaving the rest free for the game. On CPUs with weak per-core performance that rely on all cores for good throughput, this may cause longer loading times. With `DXVK_ALL_CORES=1`, all available cores are used for both the game and shader compilation. This may cause brief unresponsiveness while compiling shaders but can improve the overall experience on such hardware. +## Frame Pacing (low-latency mode) + +DXVK-Sarek includes an optional frame pacing mode adapted from and inspired by [netborg-afps/dxvk-low-latency](https://github.com/netborg-afps/dxvk-low-latency). It is not a direct port: that project relies on per submission GPU timing and an asynchronous presenter that upstream DXVK has but Sarek Vulkan 1.1/1.2-targeted presenter does not, so the mechanism here has been adapted to what Sarek can actually observe. + +The `dxvk.framePace` option in `dxvk.conf` (or the `DXVK_FRAME_PACE` environment variable) controls it: + +- `"max-frame-latency"` (default) is Sarek's existing, unchanged behaviour. Frame `i` wont start until frame `(i-1)-x` has finished, where `x` is `dxgi.maxFrameLatency` / `d3d9.maxFrameLatency`. +- `"low-latency"` forces the effective frame latency down to the minimum needed for forward progress, and makes a best-effort prediction of when the previous frame will finish (based on a rolling average of recent frame durations) to reduce unnecessary CPU wake-up jitter. The prediction is never load-bearing for correctness; a wrong guess costs microseconds, not a broken frame. +- `"min-latency"` forces the same minimal frame latency without the predictive sleep. Lowest possible latency, usually at a noticeable fps cost. + +`dxvk.lowLatencyOffset` fine-tunes `"low-latency"` mode: positive values (in microseconds) delay a frame's predicted start slightly, negative values start it earlier. Clamped to `-10000`..`10000`, defaults to `0`. + +> [!NOTE] +> Because Sarek presenter has no per-submission GPU progress telemetry the way upstream DXVK's does, `"low-latency"` and `"min-latency"` currently share the same underlying latency-reduction mechanism rather than being two fully distinct algorithms. There's also no VRR-aware pacing mode and no HUD latency display yet, both of which the upstream project has. + ## Debugging The following environment variables can be used for debugging: diff --git a/src/d3d11/d3d11_buffer.cpp b/src/d3d11/d3d11_buffer.cpp index eb72fc1b9db..dd7c710ebe0 100644 --- a/src/d3d11/d3d11_buffer.cpp +++ b/src/d3d11/d3d11_buffer.cpp @@ -5,7 +5,7 @@ #include "../dxvk/dxvk_data.h" namespace dxvk { - + D3D11Buffer::D3D11Buffer( D3D11Device* pDevice, const D3D11_BUFFER_DESC* pDesc) @@ -20,19 +20,19 @@ namespace dxvk { info.stages = VK_PIPELINE_STAGE_TRANSFER_BIT; info.access = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - + if (pDesc->BindFlags & D3D11_BIND_VERTEX_BUFFER) { info.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; info.stages |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; info.access |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; } - + if (pDesc->BindFlags & D3D11_BIND_INDEX_BUFFER) { info.usage |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; info.stages |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; info.access |= VK_ACCESS_INDEX_READ_BIT; } - + if (pDesc->BindFlags & D3D11_BIND_CONSTANT_BUFFER) { info.usage |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; info.stages |= m_parent->GetEnabledShaderStages(); @@ -41,20 +41,20 @@ namespace dxvk { if (m_parent->GetOptions()->constantBufferRangeCheck) info.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; } - + if (pDesc->BindFlags & D3D11_BIND_SHADER_RESOURCE) { info.usage |= VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; info.stages |= m_parent->GetEnabledShaderStages(); info.access |= VK_ACCESS_SHADER_READ_BIT; } - + if (pDesc->BindFlags & D3D11_BIND_STREAM_OUTPUT) { info.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; info.stages |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT; info.access |= VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT; } - + if (pDesc->BindFlags & D3D11_BIND_UNORDERED_ACCESS) { info.usage |= VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; @@ -62,7 +62,7 @@ namespace dxvk { info.access |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } - + if (pDesc->MiscFlags & D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS) { info.usage |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT; info.stages |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; @@ -80,19 +80,19 @@ namespace dxvk { if (pDesc->BindFlags & D3D11_BIND_STREAM_OUTPUT) m_soCounter = CreateSoCounterBuffer(); } - - + + D3D11Buffer::~D3D11Buffer() { } - - + + HRESULT STDMETHODCALLTYPE D3D11Buffer::QueryInterface(REFIID riid, void** ppvObject) { if (ppvObject == nullptr) return E_POINTER; *ppvObject = nullptr; - + if (riid == __uuidof(IUnknown) || riid == __uuidof(ID3D11DeviceChild) || riid == __uuidof(ID3D11Resource) @@ -100,7 +100,7 @@ namespace dxvk { *ppvObject = ref(this); return S_OK; } - + if (riid == __uuidof(ID3D10DeviceChild) || riid == __uuidof(ID3D10Resource) || riid == __uuidof(ID3D10Buffer)) { @@ -115,36 +115,36 @@ namespace dxvk { *ppvObject = ref(&m_resource); return S_OK; } - + Logger::warn("D3D11Buffer::QueryInterface: Unknown interface query"); Logger::warn(str::format(riid)); return E_NOINTERFACE; } - - + + UINT STDMETHODCALLTYPE D3D11Buffer::GetEvictionPriority() { return DXGI_RESOURCE_PRIORITY_NORMAL; } - - + + void STDMETHODCALLTYPE D3D11Buffer::SetEvictionPriority(UINT EvictionPriority) { static bool s_errorShown = false; if (!std::exchange(s_errorShown, true)) Logger::warn("D3D11Buffer::SetEvictionPriority: Stub"); } - - + + void STDMETHODCALLTYPE D3D11Buffer::GetType(D3D11_RESOURCE_DIMENSION* pResourceDimension) { *pResourceDimension = D3D11_RESOURCE_DIMENSION_BUFFER; } - - + + void STDMETHODCALLTYPE D3D11Buffer::GetDesc(D3D11_BUFFER_DESC* pDesc) { *pDesc = m_desc; } - - + + bool D3D11Buffer::CheckViewCompatibility( UINT BindFlags, DXGI_FORMAT Format) const { @@ -173,7 +173,7 @@ namespace dxvk { // We don't support tiled resources if (pDesc->MiscFlags & (D3D11_RESOURCE_MISC_TILE_POOL | D3D11_RESOURCE_MISC_TILED)) return E_INVALIDARG; - + // Constant buffer size must be a multiple of 16 if ((pDesc->BindFlags & D3D11_BIND_CONSTANT_BUFFER) && (pDesc->ByteWidth & 0xF)) @@ -185,7 +185,7 @@ namespace dxvk { || (pDesc->StructureByteStride == 0) || (pDesc->StructureByteStride & 0x3))) return E_INVALIDARG; - + // Basic validation for raw buffers if ((pDesc->MiscFlags & D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS) && (!(pDesc->BindFlags & (D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS)))) @@ -194,10 +194,10 @@ namespace dxvk { // Mip generation obviously doesn't work for buffers if (pDesc->MiscFlags & D3D11_RESOURCE_MISC_GENERATE_MIPS) return E_INVALIDARG; - + if (!(pDesc->MiscFlags & D3D11_RESOURCE_MISC_BUFFER_STRUCTURED)) pDesc->StructureByteStride = 0; - + return S_OK; } @@ -212,7 +212,7 @@ namespace dxvk { VkMemoryPropertyFlags D3D11Buffer::GetMemoryFlags() const { VkMemoryPropertyFlags memoryFlags = 0; - + switch (m_desc.Usage) { case D3D11_USAGE_IMMUTABLE: memoryFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; @@ -231,7 +231,7 @@ namespace dxvk { memoryFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } break; - + case D3D11_USAGE_DYNAMIC: memoryFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; @@ -239,18 +239,19 @@ namespace dxvk { if (m_desc.BindFlags) memoryFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; break; - + case D3D11_USAGE_STAGING: memoryFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; break; } - + bool useCached = (m_parent->GetOptions()->cachedDynamicResources == ~0u) || (m_parent->GetOptions()->cachedDynamicResources & m_desc.BindFlags); if ((memoryFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && useCached) { + memoryFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; memoryFlags |= VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; } @@ -276,6 +277,7 @@ namespace dxvk { | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT | VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT; + return device->createBuffer(info, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); } @@ -285,7 +287,7 @@ namespace dxvk { ? D3D11_COMMON_BUFFER_MAP_MODE_DIRECT : D3D11_COMMON_BUFFER_MAP_MODE_NONE; } - + D3D11Buffer* GetCommonBuffer(ID3D11Resource* pResource) { D3D11_RESOURCE_DIMENSION dimension = D3D11_RESOURCE_DIMENSION_UNKNOWN; diff --git a/src/d3d11/d3d11_context_def.cpp b/src/d3d11/d3d11_context_def.cpp index fd75bf528bf..e6e586691bd 100644 --- a/src/d3d11/d3d11_context_def.cpp +++ b/src/d3d11/d3d11_context_def.cpp @@ -201,7 +201,7 @@ namespace dxvk { : MapImage (pResource, Subresource, &mapInfo); if (unlikely(FAILED(status))) { - pMappedResource->pData = nullptr; + *pMappedResource = D3D11_MAPPED_SUBRESOURCE(); return status; } @@ -214,7 +214,7 @@ namespace dxvk { auto entry = FindMapEntry(pResource, Subresource); if (unlikely(!entry)) { - pMappedResource->pData = nullptr; + *pMappedResource = D3D11_MAPPED_SUBRESOURCE(); return D3D11_ERROR_DEFERRED_CONTEXT_MAP_WITHOUT_INITIAL_DISCARD; } @@ -223,7 +223,7 @@ namespace dxvk { return S_OK; } else { // Not allowed on deferred contexts - pMappedResource->pData = nullptr; + *pMappedResource = D3D11_MAPPED_SUBRESOURCE(); return E_INVALIDARG; } } diff --git a/src/d3d11/d3d11_device.cpp b/src/d3d11/d3d11_device.cpp index 7a72402d755..48cc9cc76e8 100644 --- a/src/d3d11/d3d11_device.cpp +++ b/src/d3d11/d3d11_device.cpp @@ -816,6 +816,8 @@ namespace dxvk { InitReturnPtr(ppGeometryShader); D3D11CommonShader module; + if (!m_dxvkDevice->features().extTransformFeedback.transformFeedback) + return DXGI_ERROR_INVALID_CALL; // Zero-init some counterss so that we can increment // them while walking over the stream output entries diff --git a/src/d3d11/d3d11_gdi.cpp b/src/d3d11/d3d11_gdi.cpp index 0958886eca9..c13750736ef 100644 --- a/src/d3d11/d3d11_gdi.cpp +++ b/src/d3d11/d3d11_gdi.cpp @@ -5,7 +5,7 @@ #include "../util/util_gdi.h" namespace dxvk { - + D3D11GDISurface::D3D11GDISurface( ID3D11Resource* pResource, UINT Subresource) @@ -33,7 +33,7 @@ namespace dxvk { if (D3DKMTCreateDCFromMemory(&desc)) Logger::err(str::format("D3D11: Failed to create GDI DC")); - + m_hdc = desc.hDc; m_hbitmap = desc.hBitmap; } @@ -49,16 +49,16 @@ namespace dxvk { D3DKMTDestroyDCFromMemory(&desc); } - + HRESULT D3D11GDISurface::Acquire(BOOL Discard, HDC* phdc) { if (!phdc) return E_INVALIDARG; - + *phdc = nullptr; if (m_acquired) return DXGI_ERROR_INVALID_CALL; - + if (!Discard) { // Create a staging resource that we can map if (!m_readback && FAILED(CreateReadbackResource())) { @@ -92,13 +92,13 @@ namespace dxvk { context->Unmap(m_readback, 0); } - + m_acquired = true; *phdc = m_hdc; return S_OK; } - + HRESULT D3D11GDISurface::Release(const RECT* pDirtyRect) { if (!m_acquired) return DXGI_ERROR_INVALID_CALL; @@ -140,7 +140,7 @@ namespace dxvk { sizeof(uint32_t) * tex->Width, sizeof(uint32_t) * tex->Width * tex->Height); } - + m_acquired = false; return S_OK; } @@ -149,11 +149,8 @@ namespace dxvk { HRESULT D3D11GDISurface::CreateReadbackResource() { auto tex = GetCommonTexture(m_resource); - Com device; - Com context; - + Com device; m_resource->GetDevice(&device); - device->GetImmediateContext(&context); D3D11_RESOURCE_DIMENSION dim = { }; m_resource->GetType(&dim); @@ -172,7 +169,7 @@ namespace dxvk { desc.BindFlags = 0; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; desc.MiscFlags = 0; - + ID3D11Texture1D* tex1D = nullptr; HRESULT hr = device->CreateTexture1D(&desc, nullptr, &tex1D); m_readback = tex1D; @@ -191,7 +188,7 @@ namespace dxvk { desc.BindFlags = 0; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; desc.MiscFlags = 0; - + ID3D11Texture2D* tex2D = nullptr; HRESULT hr = device->CreateTexture2D(&desc, nullptr, &tex2D); m_readback = tex2D; diff --git a/src/d3d11/d3d11_query.cpp b/src/d3d11/d3d11_query.cpp index b7145739fbd..3a5ee9fdf12 100644 --- a/src/d3d11/d3d11_query.cpp +++ b/src/d3d11/d3d11_query.cpp @@ -2,7 +2,7 @@ #include "d3d11_query.h" namespace dxvk { - + D3D11Query::D3D11Query( D3D11Device* device, const D3D11_QUERY_DESC1& desc) @@ -16,35 +16,35 @@ namespace dxvk { case D3D11_QUERY_EVENT: m_event[0] = dxvkDevice->createGpuEvent(); break; - + case D3D11_QUERY_OCCLUSION: m_query[0] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_OCCLUSION, VK_QUERY_CONTROL_PRECISE_BIT, 0); break; - + case D3D11_QUERY_OCCLUSION_PREDICATE: m_query[0] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_OCCLUSION, 0, 0); break; - + case D3D11_QUERY_TIMESTAMP: m_query[0] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_TIMESTAMP, 0, 0); break; - + case D3D11_QUERY_TIMESTAMP_DISJOINT: for (uint32_t i = 0; i < 2; i++) { m_query[i] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_TIMESTAMP, 0, 0); } break; - + case D3D11_QUERY_PIPELINE_STATISTICS: m_query[0] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_PIPELINE_STATISTICS, 0, 0); break; - + case D3D11_QUERY_SO_STATISTICS: case D3D11_QUERY_SO_STATISTICS_STREAM0: case D3D11_QUERY_SO_OVERFLOW_PREDICATE: @@ -55,42 +55,42 @@ namespace dxvk { m_query[0] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT, 0, 0); break; - + case D3D11_QUERY_SO_STATISTICS_STREAM1: case D3D11_QUERY_SO_OVERFLOW_PREDICATE_STREAM1: m_query[0] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT, 0, 1); break; - + case D3D11_QUERY_SO_STATISTICS_STREAM2: case D3D11_QUERY_SO_OVERFLOW_PREDICATE_STREAM2: m_query[0] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT, 0, 2); break; - + case D3D11_QUERY_SO_STATISTICS_STREAM3: case D3D11_QUERY_SO_OVERFLOW_PREDICATE_STREAM3: m_query[0] = dxvkDevice->createGpuQuery( VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT, 0, 3); break; - + default: throw DxvkError(str::format("D3D11: Unhandled query type: ", desc.Query)); } } - - + + D3D11Query::~D3D11Query() { } - - + + HRESULT STDMETHODCALLTYPE D3D11Query::QueryInterface(REFIID riid, void** ppvObject) { if (ppvObject == nullptr) return E_POINTER; *ppvObject = nullptr; - + if (riid == __uuidof(IUnknown) || riid == __uuidof(ID3D11DeviceChild) || riid == __uuidof(ID3D11Asynchronous) @@ -99,7 +99,7 @@ namespace dxvk { *ppvObject = ref(this); return S_OK; } - + if (riid == __uuidof(IUnknown) || riid == __uuidof(ID3D10DeviceChild) || riid == __uuidof(ID3D10Asynchronous) @@ -107,7 +107,7 @@ namespace dxvk { *ppvObject = ref(&m_d3d10); return S_OK; } - + if (m_desc.Query == D3D11_QUERY_OCCLUSION_PREDICATE) { if (riid == __uuidof(ID3D11Predicate)) { *ppvObject = AsPredicate(ref(this)); @@ -119,40 +119,40 @@ namespace dxvk { return S_OK; } } - + Logger::warn("D3D11Query: Unknown interface query"); Logger::warn(str::format(riid)); return E_NOINTERFACE; } - - + + UINT STDMETHODCALLTYPE D3D11Query::GetDataSize() { switch (m_desc.Query) { case D3D11_QUERY_EVENT: return sizeof(BOOL); - + case D3D11_QUERY_OCCLUSION: return sizeof(UINT64); - + case D3D11_QUERY_TIMESTAMP: return sizeof(UINT64); - + case D3D11_QUERY_TIMESTAMP_DISJOINT: return sizeof(D3D11_QUERY_DATA_TIMESTAMP_DISJOINT); - + case D3D11_QUERY_PIPELINE_STATISTICS: return sizeof(D3D11_QUERY_DATA_PIPELINE_STATISTICS); - + case D3D11_QUERY_OCCLUSION_PREDICATE: return sizeof(BOOL); - + case D3D11_QUERY_SO_STATISTICS: case D3D11_QUERY_SO_STATISTICS_STREAM0: case D3D11_QUERY_SO_STATISTICS_STREAM1: case D3D11_QUERY_SO_STATISTICS_STREAM2: case D3D11_QUERY_SO_STATISTICS_STREAM3: return sizeof(D3D11_QUERY_DATA_SO_STATISTICS); - + case D3D11_QUERY_SO_OVERFLOW_PREDICATE: case D3D11_QUERY_SO_OVERFLOW_PREDICATE_STREAM0: case D3D11_QUERY_SO_OVERFLOW_PREDICATE_STREAM1: @@ -160,23 +160,23 @@ namespace dxvk { case D3D11_QUERY_SO_OVERFLOW_PREDICATE_STREAM3: return sizeof(BOOL); } - + Logger::err("D3D11Query: Failed to query data size"); return 0; } - - + + void STDMETHODCALLTYPE D3D11Query::GetDesc(D3D11_QUERY_DESC* pDesc) { pDesc->Query = m_desc.Query; pDesc->MiscFlags = m_desc.MiscFlags; } - - + + void STDMETHODCALLTYPE D3D11Query::GetDesc1(D3D11_QUERY_DESC1* pDesc) { *pDesc = m_desc; } - - + + void D3D11Query::Begin(DxvkContext* ctx) { switch (m_desc.Query) { case D3D11_QUERY_EVENT: @@ -186,32 +186,32 @@ namespace dxvk { case D3D11_QUERY_TIMESTAMP_DISJOINT: ctx->writeTimestamp(m_query[1]); break; - + default: ctx->beginQuery(m_query[0]); } } - - + + void D3D11Query::End(DxvkContext* ctx) { switch (m_desc.Query) { case D3D11_QUERY_EVENT: ctx->signalGpuEvent(m_event[0]); break; - + case D3D11_QUERY_TIMESTAMP: case D3D11_QUERY_TIMESTAMP_DISJOINT: ctx->writeTimestamp(m_query[0]); break; - + default: ctx->endQuery(m_query[0]); } m_resetCtr.fetch_sub(1, std::memory_order_release); } - - + + bool STDMETHODCALLTYPE D3D11Query::DoBegin() { if (!IsScoped() || m_state == D3D11_VK_QUERY_BEGUN) return false; @@ -246,49 +246,49 @@ namespace dxvk { if (status == DxvkGpuEventStatus::Invalid) return DXGI_ERROR_INVALID_CALL; - + bool signaled = status == DxvkGpuEventStatus::Signaled; if (pData != nullptr) *static_cast(pData) = signaled; - + return signaled ? S_OK : S_FALSE; } else { std::array queryData = { }; - + for (uint32_t i = 0; i < MaxGpuQueries && m_query[i] != nullptr; i++) { DxvkGpuQueryStatus status = m_query[i]->getData(queryData[i]); if (status == DxvkGpuQueryStatus::Invalid || status == DxvkGpuQueryStatus::Failed) return DXGI_ERROR_INVALID_CALL; - + if (status == DxvkGpuQueryStatus::Pending) return S_FALSE; } - + if (pData == nullptr) return S_OK; - + switch (m_desc.Query) { case D3D11_QUERY_OCCLUSION: *static_cast(pData) = queryData[0].occlusion.samplesPassed; return S_OK; - + case D3D11_QUERY_OCCLUSION_PREDICATE: *static_cast(pData) = queryData[0].occlusion.samplesPassed != 0; return S_OK; - + case D3D11_QUERY_TIMESTAMP: *static_cast(pData) = queryData[0].timestamp.time; return S_OK; - + case D3D11_QUERY_TIMESTAMP_DISJOINT: { auto data = static_cast(pData); data->Frequency = GetTimestampQueryFrequency(); data->Disjoint = queryData[0].timestamp.time < queryData[1].timestamp.time; } return S_OK; - + case D3D11_QUERY_PIPELINE_STATISTICS: { auto data = static_cast(pData); data->IAVertices = queryData[0].statistic.iaVertices; @@ -313,7 +313,7 @@ namespace dxvk { data->NumPrimitivesWritten = queryData[0].xfbStream.primitivesWritten; data->PrimitivesStorageNeeded = queryData[0].xfbStream.primitivesNeeded; } return S_OK; - + case D3D11_QUERY_SO_OVERFLOW_PREDICATE: case D3D11_QUERY_SO_OVERFLOW_PREDICATE_STREAM0: case D3D11_QUERY_SO_OVERFLOW_PREDICATE_STREAM1: @@ -330,13 +330,10 @@ namespace dxvk { } } } - - - UINT64 D3D11Query::GetTimestampQueryFrequency() const { - Rc device = m_parent->GetDXVKDevice(); - Rc adapter = device->adapter(); - VkPhysicalDeviceLimits limits = adapter->deviceProperties().limits; + + UINT64 D3D11Query::GetTimestampQueryFrequency() const { + const auto& limits = m_parent->GetDXVKDevice()->properties().core.properties.limits; return uint64_t(1'000'000'000.0f / limits.timestampPeriod); } @@ -345,8 +342,8 @@ namespace dxvk { if (pDesc->Query >= D3D11_QUERY_PIPELINE_STATISTICS && pDesc->ContextType > D3D11_CONTEXT_TYPE_3D) return E_INVALIDARG; - + return S_OK; } - + } diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index 7fc912b4532..7a300c7349c 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -2,6 +2,8 @@ #include "d3d11_device.h" #include "d3d11_swapchain.h" +#include "../dxvk/framepacer/dxvk_framepacer.h" + namespace dxvk { static uint16_t MapGammaControlPoint(float x) { @@ -24,6 +26,7 @@ namespace dxvk { m_context (m_device->createContext()), m_frameLatencyCap(pDevice->GetOptions()->maxFrameLatency) { CreateFrameLatencyEvent(); + m_framePacer = std::make_unique(m_device->config(), m_frameId); if (!pDevice->GetOptions()->deferSurfaceCreation) CreatePresenter(); @@ -266,6 +269,9 @@ namespace dxvk { // Bump our frame id. ++m_frameId; + if (m_framePacer) + m_framePacer->beginFrame(); + UpdateTargetFrameRate(SyncInterval); for (uint32_t i = 0; i < SyncInterval || i < 1; i++) { @@ -304,9 +310,15 @@ namespace dxvk { if (m_hud != nullptr) m_hud->render(m_context, info.format, info.imageExtent); - if (i + 1 >= SyncInterval) + if (i + 1 >= SyncInterval) { m_context->signal(m_frameLatencySignal, m_frameId); + if (m_framePacer && m_framePacer->needsGpuSignal()) { + m_context->signal(m_framePacer->signal(), m_frameId); + m_framePacer->endFrame(m_frameId); + } + } + SubmitPresent(immediateContext, sync, i); } @@ -584,6 +596,10 @@ namespace dxvk { maxFrameLatency = std::min(maxFrameLatency, m_frameLatencyCap); maxFrameLatency = std::min(maxFrameLatency, m_desc.BufferCount + 1); + + if (m_framePacer) + maxFrameLatency = m_framePacer->getEffectiveFrameLatency(maxFrameLatency); + return maxFrameLatency; } diff --git a/src/d3d11/d3d11_swapchain.h b/src/d3d11/d3d11_swapchain.h index 9fff0d00da3..08e11c39113 100644 --- a/src/d3d11/d3d11_swapchain.h +++ b/src/d3d11/d3d11_swapchain.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "d3d11_texture.h" #include "../dxvk/hud/dxvk_hud.h" @@ -12,6 +14,7 @@ namespace dxvk { class D3D11Device; class D3D11DXGIDevice; + class FramePacer; class D3D11SwapChain : public ComObject { constexpr static uint32_t DefaultFrameLatency = 1; @@ -109,6 +112,8 @@ namespace dxvk { HANDLE m_frameLatencyEvent = nullptr; Rc m_frameLatencySignal; + std::unique_ptr m_framePacer; + bool m_dirty = true; bool m_vsync = true; diff --git a/src/d3d11/d3d11_texture.cpp b/src/d3d11/d3d11_texture.cpp index d11c46b3c6a..b34115b5714 100644 --- a/src/d3d11/d3d11_texture.cpp +++ b/src/d3d11/d3d11_texture.cpp @@ -5,7 +5,7 @@ #include "../util/util_shared_res.h" namespace dxvk { - + D3D11CommonTexture::D3D11CommonTexture( ID3D11Resource* pInterface, D3D11Device* pDevice, @@ -54,7 +54,7 @@ namespace dxvk { throw DxvkError(str::format("D3D11: Cannot create shared texture:", "\n MiscFlags: ", m_desc.MiscFlags, "\n FeatureLevel: ", pDevice->GetFeatureLevel())); - + if (m_desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX) Logger::warn("D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX: not supported."); @@ -80,7 +80,7 @@ namespace dxvk { // by a view with a different format. Depth-stencil formats cannot // be reinterpreted in Vulkan, so we'll ignore those. auto formatProperties = imageFormatInfo(formatInfo.Format); - + bool isMutable = formatFamily.FormatCount > 1; bool isMultiPlane = (formatProperties->aspectMask & VK_IMAGE_ASPECT_PLANE_0_BIT) != 0; bool isColorFormat = (formatProperties->aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0; @@ -97,14 +97,14 @@ namespace dxvk { imageInfo.stages |= pDevice->GetEnabledShaderStages(); imageInfo.access |= VK_ACCESS_SHADER_READ_BIT; } - + if (m_desc.BindFlags & D3D11_BIND_RENDER_TARGET) { imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; imageInfo.stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; imageInfo.access |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; } - + if (m_desc.BindFlags & D3D11_BIND_DEPTH_STENCIL) { imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; imageInfo.stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT @@ -112,7 +112,7 @@ namespace dxvk { imageInfo.access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; } - + if (m_desc.BindFlags & D3D11_BIND_UNORDERED_ACCESS) { imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT; imageInfo.stages |= pDevice->GetEnabledShaderStages(); @@ -133,21 +133,21 @@ namespace dxvk { imageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT; } - + // Access pattern for meta-resolve operations if (imageInfo.sampleCount != VK_SAMPLE_COUNT_1_BIT && isColorFormat) { imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT; imageInfo.stages |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; imageInfo.access |= VK_ACCESS_SHADER_READ_BIT; } - + if (m_desc.MiscFlags & D3D11_RESOURCE_MISC_TEXTURECUBE) imageInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; - + if (Dimension == D3D11_RESOURCE_DIMENSION_TEXTURE3D && (m_desc.BindFlags & D3D11_BIND_RENDER_TARGET)) imageInfo.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; - + // Swap chain back buffers need to be shader readable if (DxgiUsage & DXGI_USAGE_BACK_BUFFER) { imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT; @@ -160,10 +160,10 @@ namespace dxvk { // only supported with linear tiling on most GPUs if (!CheckImageSupport(&imageInfo, VK_IMAGE_TILING_OPTIMAL)) imageInfo.tiling = VK_IMAGE_TILING_LINEAR; - + // Determine map mode based on our findings m_mapMode = DetermineMapMode(&imageInfo); - + // If the image is mapped directly to host memory, we need // to enable linear tiling, and DXVK needs to be aware that // the image can be accessed by the host. @@ -171,7 +171,7 @@ namespace dxvk { imageInfo.tiling = VK_IMAGE_TILING_LINEAR; imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; } - + // If necessary, create the mapped linear buffer if (m_mapMode != D3D11_COMMON_TEXTURE_MAP_MODE_NONE) { for (uint32_t i = 0; i < m_desc.ArraySize; i++) { @@ -199,7 +199,7 @@ namespace dxvk { // should in no way affect the default image layout imageInfo.usage |= EnableMetaCopyUsage(imageInfo.format, imageInfo.tiling); imageInfo.usage |= EnableMetaPackUsage(imageInfo.format, m_desc.CPUAccessFlags); - + // Check if we can actually create the image if (!CheckImageSupport(&imageInfo, imageInfo.tiling)) { throw DxvkError(str::format( @@ -214,14 +214,14 @@ namespace dxvk { "\n Usage: ", std::hex, m_desc.BindFlags, "\n Flags: ", std::hex, m_desc.MiscFlags)); } - + // Create the image on a host-visible memory type // in case it is going to be mapped directly. VkMemoryPropertyFlags memoryProperties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - + if (m_mapMode == D3D11_COMMON_TEXTURE_MAP_MODE_DIRECT) memoryProperties = GetMemoryFlags(); - + if (vkImage == VK_NULL_HANDLE) m_image = m_device->GetDXVKDevice()->createImage(imageInfo, memoryProperties); else @@ -230,13 +230,13 @@ namespace dxvk { if (imageInfo.sharing.mode == DxvkSharedHandleMode::Export) ExportImageInfo(); } - - + + D3D11CommonTexture::~D3D11CommonTexture() { - + } - - + + VkDeviceSize D3D11CommonTexture::ComputeMappedOffset(UINT Subresource, UINT Plane, VkOffset3D Offset) const { auto packedFormatInfo = imageFormatInfo(m_packedFormat); @@ -270,8 +270,8 @@ namespace dxvk { result.arrayLayer = Subresource / m_desc.MipLevels; return result; } - - + + D3D11_COMMON_TEXTURE_SUBRESOURCE_LAYOUT D3D11CommonTexture::GetSubresourceLayout( VkImageAspectFlags AspectMask, UINT Subresource) const { @@ -334,14 +334,14 @@ namespace dxvk { DXGI_VK_FORMAT_MODE D3D11CommonTexture::GetFormatMode() const { if (m_desc.BindFlags & D3D11_BIND_RENDER_TARGET) return DXGI_VK_FORMAT_MODE_COLOR; - + if (m_desc.BindFlags & D3D11_BIND_DEPTH_STENCIL) return DXGI_VK_FORMAT_MODE_DEPTH; - + return DXGI_VK_FORMAT_MODE_ANY; } - - + + uint32_t D3D11CommonTexture::GetPlaneCount() const { return vk::getPlaneCount(m_image->formatInfo()->aspectMask); } @@ -358,7 +358,7 @@ namespace dxvk { DXGI_VK_FORMAT_MODE formatMode = GetFormatMode(); DXGI_VK_FORMAT_INFO viewFormat = m_device->LookupFormat(Format, formatMode); DXGI_VK_FORMAT_INFO baseFormat = m_device->LookupFormat(m_desc.Format, formatMode); - + // Check whether the plane index is valid for the given format uint32_t planeCount = GetPlaneCount(); @@ -369,17 +369,17 @@ namespace dxvk { // Check whether the given combination of image // view type and view format is actually supported VkFormatFeatureFlags features = GetImageFormatFeatures(BindFlags); - + if (!CheckFormatFeatureSupport(viewFormat.Format, features)) return false; // Using the image format itself is supported for non-planar formats if (viewFormat.Format == baseFormat.Format && planeCount == 1) return true; - + // If there is a list of compatible formats, the view format must be // included in that list. For planar formats, the list is laid out in - // such a way that the n-th format is supported for the n-th plane. + // such a way that the n-th format is supported for the n-th plane. for (size_t i = Plane; i < imageInfo.viewFormatCount; i += planeCount) { if (imageInfo.viewFormats[i] == viewFormat.Format) { return true; @@ -390,7 +390,7 @@ namespace dxvk { if (imageInfo.viewFormatCount == 0 && planeCount == 1) { auto baseFormatInfo = imageFormatInfo(baseFormat.Format); auto viewFormatInfo = imageFormatInfo(viewFormat.Format); - + return baseFormatInfo->aspectMask == viewFormatInfo->aspectMask && baseFormatInfo->elementSize == viewFormatInfo->elementSize; } @@ -402,15 +402,15 @@ namespace dxvk { return viewFormat.Format == baseFormat.Format && planeCount == 1; } } - - + + HRESULT D3D11CommonTexture::NormalizeTextureProperties(D3D11_COMMON_TEXTURE_DESC* pDesc) { if (pDesc->Width == 0 || pDesc->Height == 0 || pDesc->Depth == 0 || pDesc->ArraySize == 0) return E_INVALIDARG; - + if (FAILED(DecodeSampleCount(pDesc->SampleDesc.Count, nullptr))) return E_INVALIDARG; - + if ((pDesc->MiscFlags & D3D11_RESOURCE_MISC_GDI_COMPATIBLE) && (pDesc->Usage == D3D11_USAGE_STAGING || (pDesc->Format != DXGI_FORMAT_B8G8R8A8_TYPELESS @@ -432,10 +432,10 @@ namespace dxvk { const uint32_t maxMipLevelCount = (pDesc->SampleDesc.Count <= 1) ? util::computeMipLevelCount({ pDesc->Width, pDesc->Height, pDesc->Depth }) : 1u; - + if (pDesc->MipLevels == 0 || pDesc->MipLevels > maxMipLevelCount) pDesc->MipLevels = maxMipLevelCount; - + // Row-major is only supported for textures with one single // subresource and one sample and cannot have bind flags. if (pDesc->TextureLayout == D3D11_TEXTURE_LAYOUT_ROW_MAJOR @@ -448,13 +448,13 @@ namespace dxvk { return S_OK; } - - + + BOOL D3D11CommonTexture::CheckImageSupport( const DxvkImageCreateInfo* pImageInfo, VkImageTiling Tiling) const { const Rc adapter = m_device->GetDXVKDevice()->adapter(); - + VkImageUsageFlags usage = pImageInfo->usage; if (pImageInfo->flags & VK_IMAGE_CREATE_EXTENDED_USAGE_BIT) @@ -464,10 +464,10 @@ namespace dxvk { VkResult status = adapter->imageFormatProperties( pImageInfo->format, pImageInfo->type, Tiling, usage, pImageInfo->flags, formatProps); - + if (status != VK_SUCCESS) return FALSE; - + return (pImageInfo->extent.width <= formatProps.maxExtent.width) && (pImageInfo->extent.height <= formatProps.maxExtent.height) && (pImageInfo->extent.depth <= formatProps.maxExtent.depth) @@ -485,8 +485,8 @@ namespace dxvk { return (properties.linearTilingFeatures & Features) == Features || (properties.optimalTilingFeatures & Features) == Features; } - - + + VkImageUsageFlags D3D11CommonTexture::EnableMetaCopyUsage( VkFormat Format, VkImageTiling Tiling) const { @@ -514,15 +514,15 @@ namespace dxvk { requestedFeatures &= Tiling == VK_IMAGE_TILING_OPTIMAL ? properties.optimalTilingFeatures : properties.linearTilingFeatures; - + VkImageUsageFlags requestedUsage = 0; if (requestedFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) requestedUsage |= VK_IMAGE_USAGE_SAMPLED_BIT; - + if (requestedFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) requestedUsage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - + if (requestedFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) requestedUsage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; @@ -535,7 +535,7 @@ namespace dxvk { UINT CpuAccess) const { if ((CpuAccess & D3D11_CPU_ACCESS_READ) == 0) return 0; - + const auto dsMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; @@ -546,7 +546,7 @@ namespace dxvk { : 0; } - + VkMemoryPropertyFlags D3D11CommonTexture::GetMemoryFlags() const { VkMemoryPropertyFlags memoryFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; @@ -568,7 +568,7 @@ namespace dxvk { // Don't map an image unless the application requests it if (!m_desc.CPUAccessFlags) return D3D11_COMMON_TEXTURE_MAP_MODE_NONE; - + // If the resource cannot be used in the actual rendering pipeline, we // do not need to create an actual image and can instead implement copy // functions as buffer-to-image and image-to-buffer copies. @@ -608,8 +608,8 @@ namespace dxvk { // bugs where games ignore the pitch when mapping the image. return D3D11_COMMON_TEXTURE_MAP_MODE_BUFFER; } - - + + void D3D11CommonTexture::ExportImageInfo() { HANDLE hSharedHandle; @@ -639,8 +639,8 @@ namespace dxvk { if (hSharedHandle != INVALID_HANDLE_VALUE) CloseHandle(hSharedHandle); } - - + + BOOL D3D11CommonTexture::IsR32UavCompatibleFormat( DXGI_FORMAT Format) { return Format == DXGI_FORMAT_R8G8B8A8_TYPELESS @@ -655,7 +655,7 @@ namespace dxvk { D3D11CommonTexture::MappedBuffer D3D11CommonTexture::CreateMappedBuffer(UINT MipLevel) const { const DxvkFormatInfo* formatInfo = imageFormatInfo( m_device->LookupPackedFormat(m_desc.Format, GetFormatMode()).Format); - + DxvkBufferCreateInfo info; info.size = GetSubresourceLayout(formatInfo->aspectMask, MipLevel).Size; info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT @@ -669,22 +669,22 @@ namespace dxvk { | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; - + VkMemoryPropertyFlags memType = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - + bool useCached = m_device->GetOptions()->cachedDynamicResources == ~0u; if (m_desc.Usage == D3D11_USAGE_STAGING || useCached) memType |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; - + MappedBuffer result; result.buffer = m_device->GetDXVKDevice()->createBuffer(info, memType); result.slice = result.buffer->getSliceHandle(); return result; } - - + + VkImageType D3D11CommonTexture::GetImageTypeFromResourceDim(D3D11_RESOURCE_DIMENSION Dimension) { switch (Dimension) { case D3D11_RESOURCE_DIMENSION_TEXTURE1D: return VK_IMAGE_TYPE_1D; @@ -693,40 +693,36 @@ namespace dxvk { default: throw DxvkError("D3D11CommonTexture: Unhandled resource dimension"); } } - - + + VkImageLayout D3D11CommonTexture::OptimizeLayout(VkImageUsageFlags Usage) { const VkImageUsageFlags usageFlags = Usage; - + // Filter out unnecessary flags. Transfer operations // are handled by the backend in a transparent manner. - Usage &= ~(VK_IMAGE_USAGE_TRANSFER_DST_BIT - | VK_IMAGE_USAGE_TRANSFER_SRC_BIT); - + Usage &= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT + | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT + | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + + // Storage images require GENERAL. + if (Usage & VK_IMAGE_USAGE_STORAGE_BIT) + return VK_IMAGE_LAYOUT_GENERAL; + // If the image is used only as an attachment, we never - // have to transform the image back to a different layout + // have to transform the image back to a different layout. if (Usage == VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - + if (Usage == VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - Usage &= ~(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT - | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); - - // If the image is used for reading but not as a storage - // image, we can optimize the image for texture access - if (Usage == VK_IMAGE_USAGE_SAMPLED_BIT) { - return usageFlags & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT - ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL - : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - } - - // Otherwise, we have to stick with the default layout - return VK_IMAGE_LAYOUT_GENERAL; + + // Otherwise, pick a layout that can be used for reading. + return usageFlags & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT + ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL + : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } - - + + D3D11DXGISurface::D3D11DXGISurface( @@ -739,23 +735,23 @@ namespace dxvk { m_gdiSurface = new D3D11GDISurface(m_resource, 0); } - + D3D11DXGISurface::~D3D11DXGISurface() { if (m_gdiSurface) delete m_gdiSurface; } - + ULONG STDMETHODCALLTYPE D3D11DXGISurface::AddRef() { return m_resource->AddRef(); } - + ULONG STDMETHODCALLTYPE D3D11DXGISurface::Release() { return m_resource->Release(); } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::QueryInterface( REFIID riid, void** ppvObject) { @@ -770,7 +766,7 @@ namespace dxvk { return m_resource->GetPrivateData(Name, pDataSize, pData); } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::SetPrivateData( REFGUID Name, UINT DataSize, @@ -778,21 +774,21 @@ namespace dxvk { return m_resource->SetPrivateData(Name, DataSize, pData); } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::SetPrivateDataInterface( REFGUID Name, const IUnknown* pUnknown) { return m_resource->SetPrivateDataInterface(Name, pUnknown); } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::GetParent( REFIID riid, void** ppParent) { return GetDevice(riid, ppParent); } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::GetDevice( REFIID riid, void** ppDevice) { @@ -801,7 +797,7 @@ namespace dxvk { return device->QueryInterface(riid, ppDevice); } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::GetDesc( DXGI_SURFACE_DESC* pDesc) { if (!pDesc) @@ -815,7 +811,7 @@ namespace dxvk { return S_OK; } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::Map( DXGI_MAPPED_RECT* pLockedRect, UINT MapFlags) { @@ -842,7 +838,7 @@ namespace dxvk { mapType = D3D11_MAP_WRITE; else return DXGI_ERROR_INVALID_CALL; - + D3D11_MAPPED_SUBRESOURCE sr; HRESULT hr = context->Map(m_resource, 0, mapType, 0, pLockedRect ? &sr : nullptr); @@ -855,14 +851,14 @@ namespace dxvk { return hr; } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::Unmap() { Com device; Com context; m_resource->GetDevice(&device); device->GetImmediateContext(&context); - + context->Unmap(m_resource, 0); return S_OK; } @@ -873,7 +869,7 @@ namespace dxvk { HDC* phdc) { if (!m_gdiSurface) return DXGI_ERROR_INVALID_CALL; - + return m_gdiSurface->Acquire(Discard, phdc); } @@ -886,18 +882,21 @@ namespace dxvk { return m_gdiSurface->Release(pDirtyRect); } - + HRESULT STDMETHODCALLTYPE D3D11DXGISurface::GetResource( REFIID riid, void** ppParentResource, UINT* pSubresourceIndex) { + if (!ppParentResource) + return E_POINTER; + HRESULT hr = m_resource->QueryInterface(riid, ppParentResource); if (pSubresourceIndex) *pSubresourceIndex = 0; return hr; } - - + + bool D3D11DXGISurface::isSurfaceCompatible() const { auto desc = m_texture->Desc(); @@ -913,62 +912,62 @@ namespace dxvk { D3D11CommonTexture* pTexture) : m_resource(pResource), m_texture (pTexture) { - + } - - + + D3D11VkInteropSurface::~D3D11VkInteropSurface() { - + } - - + + ULONG STDMETHODCALLTYPE D3D11VkInteropSurface::AddRef() { return m_resource->AddRef(); } - - + + ULONG STDMETHODCALLTYPE D3D11VkInteropSurface::Release() { return m_resource->Release(); } - - + + HRESULT STDMETHODCALLTYPE D3D11VkInteropSurface::QueryInterface( REFIID riid, void** ppvObject) { return m_resource->QueryInterface(riid, ppvObject); } - - + + HRESULT STDMETHODCALLTYPE D3D11VkInteropSurface::GetDevice( IDXGIVkInteropDevice** ppDevice) { Com device; m_resource->GetDevice(&device); - + return device->QueryInterface( __uuidof(IDXGIVkInteropDevice), reinterpret_cast(ppDevice)); } - - + + HRESULT STDMETHODCALLTYPE D3D11VkInteropSurface::GetVulkanImageInfo( VkImage* pHandle, VkImageLayout* pLayout, VkImageCreateInfo* pInfo) { const Rc image = m_texture->GetImage(); const DxvkImageCreateInfo& info = image->info(); - + if (pHandle != nullptr) *pHandle = image->handle(); - + if (pLayout != nullptr) *pLayout = info.layout; - + if (pInfo != nullptr) { // We currently don't support any extended structures if (pInfo->sType != VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO || pInfo->pNext != nullptr) return E_INVALIDARG; - + pInfo->flags = 0; pInfo->imageType = info.type; pInfo->format = info.format; @@ -982,11 +981,11 @@ namespace dxvk { pInfo->queueFamilyIndexCount = 0; pInfo->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; } - + return S_OK; } - - + + /////////////////////////////////////////// // D 3 D 1 1 T E X T U R E 1 D D3D11Texture1D::D3D11Texture1D( @@ -998,21 +997,21 @@ namespace dxvk { m_surface (this, &m_texture), m_resource(this), m_d3d10 (this) { - + } - - + + D3D11Texture1D::~D3D11Texture1D() { - + } - - + + HRESULT STDMETHODCALLTYPE D3D11Texture1D::QueryInterface(REFIID riid, void** ppvObject) { if (ppvObject == nullptr) return E_POINTER; *ppvObject = nullptr; - + if (riid == __uuidof(IUnknown) || riid == __uuidof(ID3D11DeviceChild) || riid == __uuidof(ID3D11Resource) @@ -1020,14 +1019,14 @@ namespace dxvk { *ppvObject = ref(this); return S_OK; } - + if (riid == __uuidof(ID3D10DeviceChild) || riid == __uuidof(ID3D10Resource) || riid == __uuidof(ID3D10Texture1D)) { *ppvObject = ref(&m_d3d10); return S_OK; } - + if (m_surface.isSurfaceCompatible() && (riid == __uuidof(IDXGISurface) || riid == __uuidof(IDXGISurface1) @@ -1043,36 +1042,36 @@ namespace dxvk { *ppvObject = ref(&m_resource); return S_OK; } - + if (riid == __uuidof(IDXGIVkInteropSurface)) { *ppvObject = ref(&m_interop); return S_OK; } - + Logger::warn("D3D11Texture1D::QueryInterface: Unknown interface query"); Logger::warn(str::format(riid)); return E_NOINTERFACE; } - - + + void STDMETHODCALLTYPE D3D11Texture1D::GetType(D3D11_RESOURCE_DIMENSION *pResourceDimension) { *pResourceDimension = D3D11_RESOURCE_DIMENSION_TEXTURE1D; } - - + + UINT STDMETHODCALLTYPE D3D11Texture1D::GetEvictionPriority() { return DXGI_RESOURCE_PRIORITY_NORMAL; } - - + + void STDMETHODCALLTYPE D3D11Texture1D::SetEvictionPriority(UINT EvictionPriority) { static bool s_errorShown = false; if (!std::exchange(s_errorShown, true)) Logger::warn("D3D11Texture1D::SetEvictionPriority: Stub"); } - - + + void STDMETHODCALLTYPE D3D11Texture1D::GetDesc(D3D11_TEXTURE1D_DESC *pDesc) { pDesc->Width = m_texture.Desc()->Width; pDesc->MipLevels = m_texture.Desc()->MipLevels; @@ -1083,8 +1082,8 @@ namespace dxvk { pDesc->CPUAccessFlags = m_texture.Desc()->CPUAccessFlags; pDesc->MiscFlags = m_texture.Desc()->MiscFlags; } - - + + /////////////////////////////////////////// // D 3 D 1 1 T E X T U R E 2 D D3D11Texture2D::D3D11Texture2D( @@ -1113,7 +1112,7 @@ namespace dxvk { m_resource (this), m_d3d10 (this), m_swapChain (nullptr) { - + } @@ -1129,15 +1128,15 @@ namespace dxvk { m_resource (this), m_d3d10 (this), m_swapChain (pSwapChain) { - + } - - + + D3D11Texture2D::~D3D11Texture2D() { - + } - - + + ULONG STDMETHODCALLTYPE D3D11Texture2D::AddRef() { uint32_t refCount = D3D11DeviceChild::AddRef(); @@ -1148,7 +1147,7 @@ namespace dxvk { return refCount; } - + ULONG STDMETHODCALLTYPE D3D11Texture2D::Release() { IUnknown* swapChain = m_swapChain; @@ -1168,7 +1167,7 @@ namespace dxvk { return E_POINTER; *ppvObject = nullptr; - + if (riid == __uuidof(IUnknown) || riid == __uuidof(ID3D11DeviceChild) || riid == __uuidof(ID3D11Resource) @@ -1192,7 +1191,7 @@ namespace dxvk { *ppvObject = ref(&m_surface); return S_OK; } - + if (riid == __uuidof(IDXGIObject) || riid == __uuidof(IDXGIDeviceSubObject) || riid == __uuidof(IDXGIResource) @@ -1200,36 +1199,36 @@ namespace dxvk { *ppvObject = ref(&m_resource); return S_OK; } - + if (riid == __uuidof(IDXGIVkInteropSurface)) { *ppvObject = ref(&m_interop); return S_OK; } - + Logger::warn("D3D11Texture2D::QueryInterface: Unknown interface query"); Logger::warn(str::format(riid)); return E_NOINTERFACE; } - - + + void STDMETHODCALLTYPE D3D11Texture2D::GetType(D3D11_RESOURCE_DIMENSION *pResourceDimension) { *pResourceDimension = D3D11_RESOURCE_DIMENSION_TEXTURE2D; } - - + + UINT STDMETHODCALLTYPE D3D11Texture2D::GetEvictionPriority() { return DXGI_RESOURCE_PRIORITY_NORMAL; } - - + + void STDMETHODCALLTYPE D3D11Texture2D::SetEvictionPriority(UINT EvictionPriority) { static bool s_errorShown = false; if (!std::exchange(s_errorShown, true)) Logger::warn("D3D11Texture2D::SetEvictionPriority: Stub"); } - - + + void STDMETHODCALLTYPE D3D11Texture2D::GetDesc(D3D11_TEXTURE2D_DESC* pDesc) { pDesc->Width = m_texture.Desc()->Width; pDesc->Height = m_texture.Desc()->Height; @@ -1242,8 +1241,8 @@ namespace dxvk { pDesc->CPUAccessFlags = m_texture.Desc()->CPUAccessFlags; pDesc->MiscFlags = m_texture.Desc()->MiscFlags; } - - + + void STDMETHODCALLTYPE D3D11Texture2D::GetDesc1(D3D11_TEXTURE2D_DESC1* pDesc) { pDesc->Width = m_texture.Desc()->Width; pDesc->Height = m_texture.Desc()->Height; @@ -1257,8 +1256,8 @@ namespace dxvk { pDesc->MiscFlags = m_texture.Desc()->MiscFlags; pDesc->TextureLayout = m_texture.Desc()->TextureLayout; } - - + + /////////////////////////////////////////// // D 3 D 1 1 T E X T U R E 3 D D3D11Texture3D::D3D11Texture3D( @@ -1269,21 +1268,21 @@ namespace dxvk { m_interop (this, &m_texture), m_resource(this), m_d3d10 (this) { - + } - - + + D3D11Texture3D::~D3D11Texture3D() { - + } - - + + HRESULT STDMETHODCALLTYPE D3D11Texture3D::QueryInterface(REFIID riid, void** ppvObject) { if (ppvObject == nullptr) return E_POINTER; *ppvObject = nullptr; - + if (riid == __uuidof(IUnknown) || riid == __uuidof(ID3D11DeviceChild) || riid == __uuidof(ID3D11Resource) @@ -1292,14 +1291,14 @@ namespace dxvk { *ppvObject = ref(this); return S_OK; } - + if (riid == __uuidof(ID3D10DeviceChild) || riid == __uuidof(ID3D10Resource) || riid == __uuidof(ID3D10Texture3D)) { *ppvObject = ref(&m_d3d10); return S_OK; } - + if (riid == __uuidof(IDXGIObject) || riid == __uuidof(IDXGIDeviceSubObject) || riid == __uuidof(IDXGIResource) @@ -1307,36 +1306,36 @@ namespace dxvk { *ppvObject = ref(&m_resource); return S_OK; } - + if (riid == __uuidof(IDXGIVkInteropSurface)) { *ppvObject = ref(&m_interop); return S_OK; } - + Logger::warn("D3D11Texture3D::QueryInterface: Unknown interface query"); Logger::warn(str::format(riid)); return E_NOINTERFACE; } - - + + void STDMETHODCALLTYPE D3D11Texture3D::GetType(D3D11_RESOURCE_DIMENSION *pResourceDimension) { *pResourceDimension = D3D11_RESOURCE_DIMENSION_TEXTURE3D; } - - + + UINT STDMETHODCALLTYPE D3D11Texture3D::GetEvictionPriority() { return DXGI_RESOURCE_PRIORITY_NORMAL; } - - + + void STDMETHODCALLTYPE D3D11Texture3D::SetEvictionPriority(UINT EvictionPriority) { static bool s_errorShown = false; if (!std::exchange(s_errorShown, true)) Logger::warn("D3D11Texture3D::SetEvictionPriority: Stub"); } - - + + void STDMETHODCALLTYPE D3D11Texture3D::GetDesc(D3D11_TEXTURE3D_DESC* pDesc) { pDesc->Width = m_texture.Desc()->Width; pDesc->Height = m_texture.Desc()->Height; @@ -1348,8 +1347,8 @@ namespace dxvk { pDesc->CPUAccessFlags = m_texture.Desc()->CPUAccessFlags; pDesc->MiscFlags = m_texture.Desc()->MiscFlags; } - - + + void STDMETHODCALLTYPE D3D11Texture3D::GetDesc1(D3D11_TEXTURE3D_DESC1* pDesc) { pDesc->Width = m_texture.Desc()->Width; pDesc->Height = m_texture.Desc()->Height; @@ -1361,25 +1360,25 @@ namespace dxvk { pDesc->CPUAccessFlags = m_texture.Desc()->CPUAccessFlags; pDesc->MiscFlags = m_texture.Desc()->MiscFlags; } - - + + D3D11CommonTexture* GetCommonTexture(ID3D11Resource* pResource) { D3D11_RESOURCE_DIMENSION dimension = D3D11_RESOURCE_DIMENSION_UNKNOWN; pResource->GetType(&dimension); - + switch (dimension) { case D3D11_RESOURCE_DIMENSION_TEXTURE1D: return static_cast(pResource)->GetCommonTexture(); - + case D3D11_RESOURCE_DIMENSION_TEXTURE2D: return static_cast(pResource)->GetCommonTexture(); - + case D3D11_RESOURCE_DIMENSION_TEXTURE3D: return static_cast(pResource)->GetCommonTexture(); - + default: return nullptr; } } - + } diff --git a/src/d3d11/d3d11_video.cpp b/src/d3d11/d3d11_video.cpp index 5fafa8a0c02..29a567b08aa 100644 --- a/src/d3d11/d3d11_video.cpp +++ b/src/d3d11/d3d11_video.cpp @@ -510,6 +510,11 @@ namespace dxvk { ID3D11VideoProcessor* pVideoProcessor, BOOL Enable, const RECT* pRect) { + static bool errorShown = false; + + if (!std::exchange(errorShown, true)) + Logger::warn("D3D11VideoContext::VideoProcessorSetOutputTargetRect: Stub."); + D3D10DeviceLock lock = m_ctx->LockContext(); auto state = static_cast(pVideoProcessor)->GetState(); @@ -517,11 +522,6 @@ namespace dxvk { if (Enable) state->outputTargetRect = *pRect; - - static bool errorShown = false; - - if (!std::exchange(errorShown, true)) - Logger::err("D3D11VideoContext::VideoProcessorSetOutputTargetRect: Stub."); } @@ -529,16 +529,16 @@ namespace dxvk { ID3D11VideoProcessor* pVideoProcessor, BOOL YCbCr, const D3D11_VIDEO_COLOR* pColor) { + static bool errorShown = false; + + if (!std::exchange(errorShown, true)) + Logger::warn("D3D11VideoContext::VideoProcessorSetOutputBackgroundColor: Stub"); + D3D10DeviceLock lock = m_ctx->LockContext(); auto state = static_cast(pVideoProcessor)->GetState(); state->outputBackgroundColorIsYCbCr = YCbCr; state->outputBackgroundColor = *pColor; - - static bool errorShown = false; - - if (!std::exchange(errorShown, true)) - Logger::err("D3D11VideoContext::VideoProcessorSetOutputBackgroundColor: Stub"); } @@ -630,7 +630,9 @@ namespace dxvk { D3D11_VIDEO_PROCESSOR_OUTPUT_RATE Rate, BOOL Repeat, const DXGI_RATIONAL* CustomRate) { - Logger::err("D3D11VideoContext::VideoProcessorSetStreamOutputRate: Stub"); + Logger::warn(str::format("D3D11VideoContext::VideoProcessorSetStreamOutputRate: Stub, Rate ", Rate)); + if (CustomRate) + Logger::warn(str::format("CustomRate ", CustomRate->Numerator, "/", CustomRate->Denominator)); } @@ -639,6 +641,11 @@ namespace dxvk { UINT StreamIndex, BOOL Enable, const RECT* pRect) { + static bool errorShown = false; + + if (!std::exchange(errorShown, true)) + Logger::warn("D3D11VideoContext::VideoProcessorSetStreamSourceRect: Stub."); + D3D10DeviceLock lock = m_ctx->LockContext(); auto state = static_cast(pVideoProcessor)->GetStreamState(StreamIndex); @@ -650,11 +657,6 @@ namespace dxvk { if (Enable) state->srcRect = *pRect; - - static bool errorShown = false; - - if (!std::exchange(errorShown, true)) - Logger::err("D3D11VideoContext::VideoProcessorSetStreamSourceRect: Stub."); } diff --git a/src/d3d9/d3d9_adapter.cpp b/src/d3d9/d3d9_adapter.cpp index d34797b61ee..15731282e35 100644 --- a/src/d3d9/d3d9_adapter.cpp +++ b/src/d3d9/d3d9_adapter.cpp @@ -170,6 +170,9 @@ namespace dxvk { DWORD Usage, D3DRESOURCETYPE RType, D3D9Format CheckFormat) { + if (unlikely(AdapterFormat == D3D9Format::Unknown)) + return D3DERR_INVALIDCALL; + if (!IsSupportedAdapterFormat(AdapterFormat)) return D3DERR_NOTAVAILABLE; @@ -190,7 +193,7 @@ namespace dxvk { if (rt && CheckFormat == D3D9Format::A8 && m_parent->GetOptions().disableA8RT) return D3DERR_NOTAVAILABLE; - if (ds && !IsDepthFormat(CheckFormat)) + if (ds && !IsDepthStencilFormat(CheckFormat)) return D3DERR_NOTAVAILABLE; if (rt && CheckFormat == D3D9Format::NULL_FORMAT && twoDimensional) @@ -208,10 +211,8 @@ namespace dxvk { : D3DERR_NOTAVAILABLE; // I really don't want to support this... - if (dmap) { - Logger::warn("D3D9Adapter::CheckDeviceFormat: D3DUSAGE_DMAP is unsupported"); + if (dmap) return D3DERR_NOTAVAILABLE; - } auto mapping = m_d3d9Formats.GetFormatMapping(CheckFormat); if (mapping.FormatColor == VK_FORMAT_UNDEFINED) @@ -228,7 +229,11 @@ namespace dxvk { return D3D_OK; // Let's actually ask Vulkan now that we got some quirks out the way! - return CheckDeviceVkFormat(mapping.FormatColor, Usage, RType); + VkFormat format = mapping.FormatColor; + if (unlikely(mapping.ConversionFormatInfo.FormatColor != VK_FORMAT_UNDEFINED)) { + format = mapping.ConversionFormatInfo.FormatColor; + } + return CheckDeviceVkFormat(format, Usage, RType); } @@ -316,7 +321,8 @@ namespace dxvk { D3D9Format SourceFormat, D3D9Format TargetFormat) { bool sourceSupported = SourceFormat != D3D9Format::Unknown - && IsSupportedBackBufferFormat(SourceFormat); + && (IsSupportedBackBufferFormat(SourceFormat) + || (IsFourCCFormat(SourceFormat) && !IsVendorFormat(SourceFormat))); bool targetSupported = TargetFormat == D3D9Format::X1R5G5B5 || TargetFormat == D3D9Format::A1R5G5B5 || TargetFormat == D3D9Format::R5G6B5 diff --git a/src/d3d9/d3d9_annotation.cpp b/src/d3d9/d3d9_annotation.cpp index 8949d1e3b6a..6c5ed43e1e4 100644 --- a/src/d3d9/d3d9_annotation.cpp +++ b/src/d3d9/d3d9_annotation.cpp @@ -127,6 +127,8 @@ namespace dxvk { INT STDMETHODCALLTYPE D3D9UserDefinedAnnotation::BeginEvent( D3DCOLOR Color, LPCWSTR Name) { + D3D9DeviceLock lock = m_container->LockDevice(); + m_container->EmitCs([color = Color, labelName = dxvk::str::fromws(Name)](DxvkContext *ctx) { VkDebugUtilsLabelEXT label; label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; @@ -143,6 +145,8 @@ namespace dxvk { INT STDMETHODCALLTYPE D3D9UserDefinedAnnotation::EndEvent() { + D3D9DeviceLock lock = m_container->LockDevice(); + m_container->EmitCs([](DxvkContext *ctx) { ctx->endDebugLabel(); }); @@ -155,6 +159,8 @@ namespace dxvk { void STDMETHODCALLTYPE D3D9UserDefinedAnnotation::SetMarker( D3DCOLOR Color, LPCWSTR Name) { + D3D9DeviceLock lock = m_container->LockDevice(); + m_container->EmitCs([color = Color, labelName = dxvk::str::fromws(Name)](DxvkContext *ctx) { VkDebugUtilsLabelEXT label; label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; diff --git a/src/d3d9/d3d9_common_buffer.cpp b/src/d3d9/d3d9_common_buffer.cpp index 1a6e6c27cd5..23df40ef1be 100644 --- a/src/d3d9/d3d9_common_buffer.cpp +++ b/src/d3d9/d3d9_common_buffer.cpp @@ -104,6 +104,7 @@ namespace dxvk { } if ((memoryFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && m_parent->GetOptions()->cachedDynamicBuffers) { + memoryFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; memoryFlags |= VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; } @@ -126,7 +127,7 @@ namespace dxvk { if (!(m_desc.Usage & D3DUSAGE_WRITEONLY)) info.access |= VK_ACCESS_HOST_READ_BIT; - VkMemoryPropertyFlags memoryFlags = + VkMemoryPropertyFlags memoryFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; @@ -134,4 +135,4 @@ namespace dxvk { return m_parent->GetDXVKDevice()->createBuffer(info, memoryFlags); } -} \ No newline at end of file +} diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index ca42a4a1be3..bf3b7b57c7f 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -1379,6 +1379,8 @@ namespace dxvk { m_flags.set(D3D9DeviceFlag::DirtyViewportScissor); m_state.scissorRect = scissorRect; } + + m_flags.set(D3D9DeviceFlag::DirtyAlphaTestState); } if (m_state.renderTargets[RenderTargetIndex] == rt) @@ -2316,7 +2318,7 @@ namespace dxvk { D3D9DeviceLock lock = LockDevice(); // Only one state block can be recorded at a given time. - if (unlikely(m_recorder != nullptr)) + if (unlikely(ShouldRecord())) return D3DERR_INVALIDCALL; m_recorder = new D3D9StateBlock(this, D3D9StateBlockType::None); @@ -5171,7 +5173,8 @@ namespace dxvk { // Dispatch current chunk so that all commands // recorded prior to this function will be run - FlushCsChunk(); + if (SequenceNumber > m_csSeqNum) + FlushCsChunk(); m_csThread.synchronize(SequenceNumber); } @@ -5772,7 +5775,6 @@ namespace dxvk { void D3D9DeviceEx::MarkTextureMipsDirty(D3D9CommonTexture* pResource) { pResource->SetNeedsMipGen(true); - pResource->MarkAllNeedReadback(); for (uint32_t i : bit::BitMask(m_activeTextures)) { // Guaranteed to not be nullptr... diff --git a/src/d3d9/d3d9_format.cpp b/src/d3d9/d3d9_format.cpp index 55b5d18283e..7cd2d22bc3a 100644 --- a/src/d3d9/d3d9_format.cpp +++ b/src/d3d9/d3d9_format.cpp @@ -507,7 +507,7 @@ namespace dxvk { return D3D9_VK_FORMAT_MAPPING(); if (!m_d24s8Support && mapping.FormatColor == VK_FORMAT_D24_UNORM_S8_UINT) - mapping.FormatColor = VK_FORMAT_D32_SFLOAT_S8_UINT; + mapping.FormatColor = mapping.Aspect & VK_IMAGE_ASPECT_STENCIL_BIT ? VK_FORMAT_D32_SFLOAT_S8_UINT : VK_FORMAT_D32_SFLOAT; if (!m_d16s8Support && mapping.FormatColor == VK_FORMAT_D16_UNORM_S8_UINT) mapping.FormatColor = m_d24s8Support ? VK_FORMAT_D24_UNORM_S8_UINT : VK_FORMAT_D32_SFLOAT_S8_UINT; diff --git a/src/d3d9/d3d9_query.cpp b/src/d3d9/d3d9_query.cpp index 14e46d4293f..9b4f1e341a0 100644 --- a/src/d3d9/d3d9_query.cpp +++ b/src/d3d9/d3d9_query.cpp @@ -254,10 +254,7 @@ namespace dxvk { UINT64 D3D9Query::GetTimestampQueryFrequency() const { - Rc device = m_parent->GetDXVKDevice(); - Rc adapter = device->adapter(); - - VkPhysicalDeviceLimits limits = adapter->deviceProperties().limits; + const auto& limits = m_parent->GetDXVKDevice()->properties().core.properties.limits; return uint64_t(1'000'000'000.0f / limits.timestampPeriod); } diff --git a/src/d3d9/d3d9_surface.cpp b/src/d3d9/d3d9_surface.cpp index 300782e991e..c98ad6d70eb 100644 --- a/src/d3d9/d3d9_surface.cpp +++ b/src/d3d9/d3d9_surface.cpp @@ -91,7 +91,7 @@ namespace dxvk { pDesc->Type = D3DRTYPE_SURFACE; pDesc->Usage = desc.Usage; pDesc->Pool = desc.Pool; - + pDesc->MultiSampleType = desc.MultiSample; pDesc->MultiSampleQuality = desc.MultisampleQuality; pDesc->Width = std::max(1u, desc.Width >> m_mipLevel); @@ -160,7 +160,9 @@ namespace dxvk { createInfo.hBitmap = nullptr; createInfo.hDc = nullptr; - D3DKMTCreateDCFromMemory(&createInfo); + if (D3DKMTCreateDCFromMemory(&createInfo)) + Logger::err("D3D9: Failed to create GDI DC"); + DeleteDC(createInfo.hDeviceDc); // These should now be set... diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 3c7492befba..473c9dbe8a9 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -4,6 +4,8 @@ #include "d3d9_hud.h" +#include "../dxvk/framepacer/dxvk_framepacer.h" + namespace dxvk { @@ -194,6 +196,8 @@ namespace dxvk { , m_frameLatencyCap (pDevice->GetOptions()->maxFrameLatency) , m_frameLatencySignal(new sync::Fence(m_frameId)) , m_dialog (pDevice->GetOptions()->enableDialogMode) { + m_framePacer = std::make_unique(m_device->config(), m_frameId); + this->NormalizePresentParameters(pPresentParams); m_presentParams = *pPresentParams; m_window = m_presentParams.hDeviceWindow; @@ -326,10 +330,10 @@ namespace dxvk { // Copies the front buffer between formats with an implicit resolve. // Oh, and the dest is systemmem... // This is a slow function anyway, it waits for the copy to finish. - // so there's no reason to not just make and throwaway temp images. + // so there no reason to not just make and throwaway temp images. // If extent of dst > src, then we blit to a subrect of the size - // of src onto a temp image of dst's extents, + // of src onto a temp image of dst extents, // then copy buffer back to dst (given dst is subresource) D3D9Surface* dst = static_cast(pDestSurface); @@ -510,6 +514,13 @@ namespace dxvk { return D3DERR_INVALIDCALL; } + if (m_backBuffers.empty()) { + // The backbuffers were destroyed and not recreated. + // This can happen when a call to Reset fails. + *ppBackBuffer = nullptr; + return D3D_OK; + } + *ppBackBuffer = ref(m_backBuffers[iBackBuffer].ptr()); return D3D_OK; } @@ -520,7 +531,7 @@ namespace dxvk { // So... we lie here and make some stuff up // enough that it makes games work. - // Assume there's 20 lines in a vBlank. + // Assume there 20 lines in a vBlank. constexpr uint32_t vBlankLineCount = 20; if (pRasterStatus == nullptr) @@ -587,6 +598,10 @@ namespace dxvk { if (!std::exchange(s_errorShown, true)) Logger::warn("D3D9SwapChainEx::GetLastPresentCount: Stub"); + + if (likely(pLastPresentCount != nullptr)) + *pLastPresentCount = 0; + return D3D_OK; } @@ -832,6 +847,9 @@ namespace dxvk { // Bump our frame id. ++m_frameId; + if (m_framePacer) + m_framePacer->beginFrame(); + UpdateTargetFrameRate(SyncInterval); for (uint32_t i = 0; i < SyncInterval || i < 1; i++) { @@ -870,9 +888,15 @@ namespace dxvk { if (m_hud != nullptr) m_hud->render(m_context, info.format, info.imageExtent); - if (i + 1 >= SyncInterval) + if (i + 1 >= SyncInterval) { m_context->signal(m_frameLatencySignal, m_frameId); + if (m_framePacer && m_framePacer->needsGpuSignal()) { + m_context->signal(m_framePacer->signal(), m_frameId); + m_framePacer->endFrame(m_frameId); + } + } + SubmitPresent(sync, i); } @@ -1131,6 +1155,10 @@ namespace dxvk { maxFrameLatency = std::min(maxFrameLatency, m_frameLatencyCap); maxFrameLatency = std::min(maxFrameLatency, m_presentParams.BackBufferCount + 1); + + if (m_framePacer) + maxFrameLatency = m_framePacer->getEffectiveFrameLatency(maxFrameLatency); + return maxFrameLatency; } @@ -1147,11 +1175,10 @@ namespace dxvk { case D3D9Format::A8R8G8B8: case D3D9Format::X8R8G8B8: - pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; - break; case D3D9Format::A8B8G8R8: case D3D9Format::X8B8G8R8: { pDstFormats[n++] = { VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; + pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; } break; case D3D9Format::A2R10G10B10: diff --git a/src/d3d9/d3d9_swapchain.h b/src/d3d9/d3d9_swapchain.h index 9ea8634c749..e90a9db286a 100644 --- a/src/d3d9/d3d9_swapchain.h +++ b/src/d3d9/d3d9_swapchain.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "d3d9_device_child.h" #include "d3d9_device.h" #include "d3d9_format.h" @@ -15,6 +17,7 @@ namespace dxvk { class D3D9Surface; + class FramePacer; using D3D9SwapChainExBase = D3D9DeviceChild; class D3D9SwapChainEx final : public D3D9SwapChainExBase { @@ -119,6 +122,8 @@ namespace dxvk { uint32_t m_frameLatencyCap = 0; Rc m_frameLatencySignal; + std::unique_ptr m_framePacer; + bool m_dirty = true; bool m_vsync = true; diff --git a/src/d3d9/d3d9_volume.cpp b/src/d3d9/d3d9_volume.cpp index 52886d80bf2..c23db2c5e5b 100644 --- a/src/d3d9/d3d9_volume.cpp +++ b/src/d3d9/d3d9_volume.cpp @@ -94,12 +94,41 @@ namespace dxvk { if (unlikely(pLockedBox == nullptr)) return D3DERR_INVALIDCALL; - return m_parent->LockImage( + // LockBox clears any existing content present in pLockedBox + pLockedBox->pBits = nullptr; + pLockedBox->RowPitch = 0; + pLockedBox->SlicePitch = 0; + + if (unlikely(pBox != nullptr)) { + auto& desc = *(m_texture->Desc()); + + // Negative or zero length dimensions + if ( static_cast(pBox->Right) - static_cast(pBox->Left) <= 0 + || static_cast(pBox->Bottom) - static_cast(pBox->Top) <= 0 + || static_cast(pBox->Back) - static_cast(pBox->Front) <= 0 + // Exceeding surface dimensions + || pBox->Right > std::max(1u, desc.Width >> m_mipLevel) + || pBox->Bottom > std::max(1u, desc.Height >> m_mipLevel) + || pBox->Back > std::max(1u, desc.Depth >> m_mipLevel)) + return D3DERR_INVALIDCALL; + } + + D3DLOCKED_BOX lockedBox; + + HRESULT hr = m_parent->LockImage( m_texture, m_face, m_mipLevel, - pLockedBox, + &lockedBox, pBox, Flags); + + if (FAILED(hr)) return hr; + + pLockedBox->pBits = lockedBox.pBits; + pLockedBox->RowPitch = lockedBox.RowPitch; + pLockedBox->SlicePitch = lockedBox.SlicePitch; + + return hr; } diff --git a/src/dxbc/dxbc_compiler.cpp b/src/dxbc/dxbc_compiler.cpp index d279e8d1039..fe8a64765f8 100644 --- a/src/dxbc/dxbc_compiler.cpp +++ b/src/dxbc/dxbc_compiler.cpp @@ -1517,7 +1517,9 @@ namespace dxvk { m_immConstBuf = m_module.newVarInit( pointerTypeId, spv::StorageClassPrivate, arrayId); + m_module.setDebugName(m_immConstBuf, "icb"); + m_module.decorate(m_immConstBuf, spv::DecorationNonWritable); } @@ -1598,8 +1600,15 @@ namespace dxvk { case DxbcOpcode::Mad: case DxbcOpcode::DFma: - dst.id = m_module.opFFma(typeId, - src.at(0).id, src.at(1).id, src.at(2).id); + if (ins.controls.precise()) { + // FXC only emits precise mad if the shader explicitly uses + // the HLSL mad()/fma() intrinsics, let's preserve that. + dst.id = m_module.opFFma(typeId, + src.at(0).id, src.at(1).id, src.at(2).id); + } else { + dst.id = m_module.opFMul(typeId, src.at(0).id, src.at(1).id); + dst.id = m_module.opFAdd(typeId, dst.id, src.at(2).id); + } break; case DxbcOpcode::Max: @@ -2014,14 +2023,28 @@ namespace dxvk { DxbcRegisterValue dst; dst.type.ctype = ins.dst[0].dataType; dst.type.ccount = 1; + dst.id = 0; - dst.id = m_module.opDot( - getVectorTypeId(dst.type), - src.at(0).id, - src.at(1).id); + uint32_t componentType = getVectorTypeId(dst.type); + uint32_t componentCount = srcMask.popCount(); - if (ins.controls.precise() || m_precise) + for (uint32_t i = 0; i < componentCount; i++) { + if (dst.id) { + dst.id = m_module.opFFma(componentType, + m_module.opCompositeExtract(componentType, src.at(0).id, 1, &i), + m_module.opCompositeExtract(componentType, src.at(1).id, 1, &i), + dst.id); + } else { + dst.id = m_module.opFMul(componentType, + m_module.opCompositeExtract(componentType, src.at(0).id, 1, &i), + m_module.opCompositeExtract(componentType, src.at(1).id, 1, &i)); + } + + // Unconditionally mark as precise since the exact order of operation + // matters for some games, even if the instruction itself is not marked + // as precise. m_module.decorate(dst.id, spv::DecorationNoContraction); + } dst = emitDstOperandModifiers(dst, ins.modifiers); emitRegisterStore(ins.dst[0], dst); @@ -3111,8 +3134,20 @@ namespace dxvk { } break; case DxbcOpcode::EvalSnapped: { - const DxbcRegisterValue offset = emitRegisterLoad( + // The offset is encoded as a 4-bit fixed point value + DxbcRegisterValue offset = emitRegisterLoad( ins.src[1], DxbcRegMask(true, true, false, false)); + offset.id = m_module.opBitFieldSExtract( + getVectorTypeId(offset.type), offset.id, + m_module.consti32(0), m_module.consti32(4)); + + offset.type.ctype = DxbcScalarType::Float32; + offset.id = m_module.opConvertStoF( + getVectorTypeId(offset.type), offset.id); + + offset.id = m_module.opFMul( + getVectorTypeId(offset.type), offset.id, + m_module.constvec2f32(1.0f / 16.0f, 1.0f / 16.0f)); result.id = m_module.opInterpolateAtOffset( getVectorTypeId(result.type), @@ -3154,6 +3189,13 @@ namespace dxvk { DxbcRegisterValue imageSize = emitQueryTextureSize(ins.src[1], mipLod); DxbcRegisterValue imageLevels = emitQueryTextureLods(ins.src[1]); + // If the mip level is out of bounds, D3D requires us to return + // zero before applying modifiers, whereas SPIR-V is undefined, + // so we need to fix it up manually here. + imageSize.id = m_module.opSelect(getVectorTypeId(imageSize.type), + m_module.opULessThan(m_module.defBoolType(), mipLod.id, imageLevels.id), + imageSize.id, emitBuildZeroVector(imageSize.type).id); + // Convert intermediates to the requested type if (returnType == DxbcScalarType::Float32) { imageSize.type.ctype = DxbcScalarType::Float32; @@ -5373,12 +5415,12 @@ namespace dxvk { result.type.ctype = DxbcScalarType::Uint32; result.type.ccount = 1; - if (info.image.sampled == 1) { + if (info.image.ms == 0 && info.image.sampled == 1) { result.id = m_module.opImageQueryLevels( getVectorTypeId(result.type), m_module.opLoad(info.typeId, info.varId)); } else { - // Report one LOD in case of UAVs + // Report one LOD in case of UAVs or multisampled images result.id = m_module.constu32(1); } @@ -6028,7 +6070,7 @@ namespace dxvk { DxbcRegisterValue value = emitValueLoad(ptr); - value.id = m_module.opFClamp( + value.id = m_module.opNClamp( getVectorTypeId(ptr.type), value.id, m_module.constf32(0.0f), @@ -6481,7 +6523,7 @@ namespace dxvk { } DxbcRegisterValue tessValue = emitRegisterExtract(value, mask); - tessValue.id = m_module.opFClamp(getVectorTypeId(tessValue.type), + tessValue.id = m_module.opNClamp(getVectorTypeId(tessValue.type), tessValue.id, m_module.constf32(0.0f), m_module.constf32(maxTessFactor)); @@ -6696,7 +6738,7 @@ namespace dxvk { case DxbcProgramType::GeometryShader: emitGsInit(); break; case DxbcProgramType::PixelShader: emitPsInit(); break; case DxbcProgramType::ComputeShader: emitCsInit(); break; - default: break; + default: throw DxvkError("Invalid shader stage"); } } @@ -7559,6 +7601,7 @@ namespace dxvk { spv::StorageClassPrivate, samplePosArray); m_module.setDebugName(varId, "g_sample_pos"); + m_module.decorate(varId, spv::DecorationNonWritable); return varId; } @@ -7610,7 +7653,9 @@ namespace dxvk { const char* name) { const uint32_t varId = emitNewVariable(info); - m_module.setDebugName(varId, name); + if (name) + m_module.setDebugName(varId, name); + m_module.decorateBuiltIn(varId, builtIn); if (m_programInfo.type() == DxbcProgramType::PixelShader diff --git a/src/dxgi/dxgi_output.cpp b/src/dxgi/dxgi_output.cpp index 5e119667b43..556e61c8031 100644 --- a/src/dxgi/dxgi_output.cpp +++ b/src/dxgi/dxgi_output.cpp @@ -381,14 +381,18 @@ namespace dxvk { if (FAILED(hr)) return hr; - static bool s_errorShown = false; + // Need to acquire swap chain and unlock monitor data, since querying + // frame statistics from the swap chain will also access monitor data. + Com swapChain = monitorInfo->pSwapChain; + m_monitorInfo->ReleaseMonitorData(); - if (!std::exchange(s_errorShown, true)) - Logger::warn("DxgiOutput::GetFrameStatistics: Stub"); + // This API only works if there is a full-screen swap chain active. + if (swapChain == nullptr) { + *pStats = DXGI_FRAME_STATISTICS(); + return S_OK; + } - *pStats = monitorInfo->FrameStats; - m_monitorInfo->ReleaseMonitorData(); - return S_OK; + return swapChain->GetFrameStatistics(pStats); } diff --git a/src/dxgi/dxgi_swapchain.cpp b/src/dxgi/dxgi_swapchain.cpp index 3d1ea152f06..134d38df8f0 100644 --- a/src/dxgi/dxgi_swapchain.cpp +++ b/src/dxgi/dxgi_swapchain.cpp @@ -485,11 +485,9 @@ namespace dxvk { || Height == 0 || Height > m_desc.Height) return E_INVALIDARG; - RECT region; - region.left = 0; - region.top = 0; - region.right = Width; - region.bottom = Height; + std::lock_guard lock(m_lockBuffer); + + RECT region = { 0, 0, LONG(Width), LONG(Height) }; return m_presenter->SetPresentRegion(®ion); } diff --git a/src/dxso/dxso_compiler.cpp b/src/dxso/dxso_compiler.cpp index 640d7dd49cc..a6b5fc0101a 100644 --- a/src/dxso/dxso_compiler.cpp +++ b/src/dxso/dxso_compiler.cpp @@ -1231,8 +1231,7 @@ namespace dxvk { return m_vs.oPSize; default: { - DxsoRegisterPointer nullPointer; - nullPointer.id = 0; + DxsoRegisterPointer nullPointer = { }; return nullPointer; } } @@ -1343,8 +1342,7 @@ namespace dxvk { default: { //Logger::warn(str::format("emitGetOperandPtr: unhandled reg type: ", reg.id.type)); - DxsoRegisterPointer nullPointer; - nullPointer.id = 0; + DxsoRegisterPointer nullPointer = { }; return nullPointer; } } diff --git a/src/dxso/dxso_module.cpp b/src/dxso/dxso_module.cpp index cf32cdaa203..c441889c468 100644 --- a/src/dxso/dxso_module.cpp +++ b/src/dxso/dxso_module.cpp @@ -38,10 +38,14 @@ namespace dxvk { m_constants = compiler->constants(); m_maxDefinedConst = compiler->maxDefinedConstant(); m_usedSamplers = compiler->usedSamplers(); - m_usedRTs = compiler->usedRTs(); compiler->finalize(); + // SM 1 doesn't have explicit output registers and uses R0 instead. + // The shader compiler emits the C0 write in finalize, so we have to get the rt mask + // after that. + m_usedRTs = compiler->usedRTs(); + return compiler->compile(); } @@ -84,4 +88,4 @@ namespace dxvk { decoder.getInstructionContext()); } -} \ No newline at end of file +} diff --git a/src/dxvk/dxvk_options.cpp b/src/dxvk/dxvk_options.cpp index 3f674fe636e..8a4eda35537 100644 --- a/src/dxvk/dxvk_options.cpp +++ b/src/dxvk/dxvk_options.cpp @@ -17,6 +17,8 @@ namespace dxvk { auto budget = config.getOption("dxvk.maxMemoryBudget", 0); maxMemoryBudget = VkDeviceSize(std::max(budget, 0)) << 20u; enableDyasync = config.getOption ("dxvk.enableDyasync", true); + framePace = config.getOption("dxvk.framePace", ""); + lowLatencyOffset = config.getOption ("dxvk.lowLatencyOffset", 0); numDyasyncThreads = config.getOption ("dxvk.numDyasyncThreads", 0); } diff --git a/src/dxvk/dxvk_options.h b/src/dxvk/dxvk_options.h index d225ce5e6fa..2826a83b673 100644 --- a/src/dxvk/dxvk_options.h +++ b/src/dxvk/dxvk_options.h @@ -50,6 +50,14 @@ namespace dxvk { // when using Dyasync int32_t numDyasyncThreads; + /// Frame pacing mode. Supported values: "", "low-latency", "min-latency". + /// Empty (default) preserves Sarek existing behaviour unchanged. + std::string framePace; + + /// Fine-tuning offset for low-latency frame pacing, in microseconds. + /// Clamped to [-10000, 10000]. Defaults to 0. + int32_t lowLatencyOffset = 0; + /// Shader-related options Tristate useRawSsbo; diff --git a/src/dxvk/dxvk_pipemanager.cpp b/src/dxvk/dxvk_pipemanager.cpp index 6eaba084d21..e627a0eca10 100644 --- a/src/dxvk/dxvk_pipemanager.cpp +++ b/src/dxvk/dxvk_pipemanager.cpp @@ -4,75 +4,85 @@ namespace dxvk { - DxvkPipelineManager::DxvkPipelineManager( - DxvkDevice* device, - DxvkRenderPassPool* passManager) - : m_device (device), - m_cache (new DxvkPipelineCache(device->vkd())) { + namespace { + + bool is_dyasync_enabled(const DxvkDevice* device) { + return env::getEnvVar("DXVK_DISABLE_DYASYNC") != "1" + && device->config().enableDyasync; + } - if (env::getEnvVar("DXVK_DISABLE_DYASYNC") != "1" && device->config().enableDyasync) - m_compiler = new DxvkPipelineCompiler(device); + bool is_state_cache_enabled(const DxvkDevice* device) { + return env::getEnvVar("DXVK_STATE_CACHE") != "0" + && device->config().enableStateCache; + } - if (env::getEnvVar("DXVK_STATE_CACHE") != "0" && device->config().enableStateCache) - m_stateCache = new DxvkStateCache(device, this, passManager); } - DxvkPipelineManager::~DxvkPipelineManager() { + DxvkPipelineManager::DxvkPipelineManager( + DxvkDevice* device, + DxvkRenderPassPool* passManager) + : m_device (device) + , m_cache (new DxvkPipelineCache(device->vkd())) + , m_stateCache(is_state_cache_enabled(device) + ? new DxvkStateCache(device, this, passManager) + : nullptr) + , m_compiler (is_dyasync_enabled(device) + ? new DxvkPipelineCompiler(device) + : nullptr) { } + DxvkPipelineManager::~DxvkPipelineManager() = default; + + DxvkComputePipeline* DxvkPipelineManager::createComputePipeline( - const DxvkComputePipelineShaders& shaders) { - if (shaders.cs == nullptr) - return nullptr; + const DxvkComputePipelineShaders& shaders) { + return shaders.cs == nullptr + ? nullptr + : findOrCreateComputePipeline(shaders); + } - std::lock_guard lock(m_mutex); - auto pair = m_computePipelines.find(shaders); - if (pair != m_computePipelines.end()) - return &pair->second; + DxvkComputePipeline* DxvkPipelineManager::findOrCreateComputePipeline( + const DxvkComputePipelineShaders& shaders) { + std::lock_guard lock(m_mutex); - auto iter = m_computePipelines.emplace( - std::piecewise_construct, - std::tuple(shaders), - std::tuple(this, shaders)); - return &iter.first->second; + return &m_computePipelines.try_emplace(shaders, this, shaders).first->second; } DxvkGraphicsPipeline* DxvkPipelineManager::createGraphicsPipeline( - const DxvkGraphicsPipelineShaders& shaders) { - if (shaders.vs == nullptr) - return nullptr; + const DxvkGraphicsPipelineShaders& shaders) { + return shaders.vs == nullptr + ? nullptr + : findOrCreateGraphicsPipeline(shaders); + } - std::lock_guard lock(m_mutex); - auto pair = m_graphicsPipelines.find(shaders); - if (pair != m_graphicsPipelines.end()) - return &pair->second; + DxvkGraphicsPipeline* DxvkPipelineManager::findOrCreateGraphicsPipeline( + const DxvkGraphicsPipelineShaders& shaders) { + std::lock_guard lock(m_mutex); - auto iter = m_graphicsPipelines.emplace( - std::piecewise_construct, - std::tuple(shaders), - std::tuple(this, shaders)); - return &iter.first->second; + return &m_graphicsPipelines.try_emplace(shaders, this, shaders).first->second; } void DxvkPipelineManager::registerShader( - const Rc& shader) { - if (m_stateCache != nullptr) - m_stateCache->registerShader(shader); + const Rc& shader) { + switch (m_stateCache != nullptr) { + case true: m_stateCache->registerShader(shader); break; + case false: break; + } } DxvkPipelineCount DxvkPipelineManager::getPipelineCount() const { - DxvkPipelineCount result; - result.numComputePipelines = m_numComputePipelines.load(); - result.numGraphicsPipelines = m_numGraphicsPipelines.load(); - return result; + return DxvkPipelineCount { + .numGraphicsPipelines = m_numGraphicsPipelines.load(), + .numComputePipelines = m_numComputePipelines.load(), + }; } @@ -83,8 +93,10 @@ namespace dxvk { void DxvkPipelineManager::stopWorkerThreads() const { - if (m_stateCache != nullptr) - m_stateCache->stopWorkerThreads(); + switch (m_stateCache != nullptr) { + case true: m_stateCache->stopWorkerThreads(); break; + case false: break; + } } } diff --git a/src/dxvk/dxvk_pipemanager.h b/src/dxvk/dxvk_pipemanager.h index 33da74c48e5..0d61907d3fa 100644 --- a/src/dxvk/dxvk_pipemanager.h +++ b/src/dxvk/dxvk_pipemanager.h @@ -1,4 +1,3 @@ - #pragma once #include @@ -23,7 +22,6 @@ namespace dxvk { uint32_t numComputePipelines; }; - /** * \brief Pipeline manager * @@ -41,7 +39,6 @@ namespace dxvk { DxvkPipelineManager( DxvkDevice* device, DxvkRenderPassPool* passManager); - ~DxvkPipelineManager(); /** @@ -77,7 +74,7 @@ namespace dxvk { * \param [in] shader Newly compiled shader */ void registerShader( - const Rc& shader); + const Rc& shader); /** * \brief Retrieves total pipeline count @@ -98,13 +95,13 @@ namespace dxvk { private: - DxvkDevice* m_device; + DxvkDevice* m_device = nullptr; Rc m_cache; Rc m_stateCache; Rc m_compiler; - std::atomic m_numComputePipelines = { 0 }; - std::atomic m_numGraphicsPipelines = { 0 }; + std::atomic m_numComputePipelines = { 0 }; + std::atomic m_numGraphicsPipelines = { 0 }; dxvk::mutex m_mutex; @@ -118,6 +115,12 @@ namespace dxvk { DxvkGraphicsPipeline, DxvkHash, DxvkEq> m_graphicsPipelines; + DxvkComputePipeline* findOrCreateComputePipeline( + const DxvkComputePipelineShaders& shaders); + + DxvkGraphicsPipeline* findOrCreateGraphicsPipeline( + const DxvkGraphicsPipelineShaders& shaders); + }; } diff --git a/src/dxvk/framepacer/dxvk_framepacer.cpp b/src/dxvk/framepacer/dxvk_framepacer.cpp new file mode 100644 index 00000000000..93f47306e5e --- /dev/null +++ b/src/dxvk/framepacer/dxvk_framepacer.cpp @@ -0,0 +1,159 @@ +#include "dxvk_framepacer.h" + +#include +#include +#include +#include + +#include "../../util/log/log.h" +#include "../../util/util_env.h" +#include "../../util/util_string.h" + +#include "../dxvk_options.h" + +namespace dxvk { + + namespace { + + constexpr const char* FRAME_PACE_ENV_VAR = "DXVK_FRAME_PACE"; + + constexpr uint32_t MIN_FRAME_LATENCY = 1u; + + constexpr int32_t LOW_LATENCY_OFFSET_MIN_US = -10000; + constexpr int32_t LOW_LATENCY_OFFSET_MAX_US = 10000; + + constexpr int32_t FRAME_DURATION_SMOOTHING_OLD_WEIGHT = 3; + constexpr int32_t FRAME_DURATION_SMOOTHING_NEW_WEIGHT = 1; + constexpr int32_t FRAME_DURATION_SMOOTHING_TOTAL_WEIGHT = + FRAME_DURATION_SMOOTHING_OLD_WEIGHT + FRAME_DURATION_SMOOTHING_NEW_WEIGHT; + + struct FramePaceName { + std::string_view name; + DxvkFramePace mode; + }; + + constexpr std::array FRAME_PACE_NAMES = {{ + { "min-latency", DxvkFramePace::MinLatency }, + { "low-latency", DxvkFramePace::LowLatency }, + }}; + + + DxvkFramePace parse_frame_pace(std::string_view configured) { + auto entry = std::find_if(FRAME_PACE_NAMES.begin(), FRAME_PACE_NAMES.end(), + [configured] (FramePaceName candidate) { + return configured.find(candidate.name) != std::string_view::npos; + }); + + return entry != FRAME_PACE_NAMES.end() ? entry->mode : DxvkFramePace::MaxFrameLatency; + } + + + int32_t clamp_low_latency_offset(int32_t offsetUs) { + return std::clamp(offsetUs, LOW_LATENCY_OFFSET_MIN_US, LOW_LATENCY_OFFSET_MAX_US); + } + + + int32_t blend_frame_duration(int32_t previousAvgUs, int32_t latestDurationUs) { + return previousAvgUs == 0 + ? latestDurationUs + : (previousAvgUs * FRAME_DURATION_SMOOTHING_OLD_WEIGHT + + latestDurationUs * FRAME_DURATION_SMOOTHING_NEW_WEIGHT) + / FRAME_DURATION_SMOOTHING_TOTAL_WEIGHT; + } + + + // Pure prediction only, never sleeps itself. Only LowLatency predicts + // anything: MaxFrameLatency has no pacer behaviour, and MinLatency + // relies entirely on getEffectiveFrameLatency clamp for correctness + // rather than a predicted wake time. + std::optional predict_wake_time( + DxvkFramePace mode, + std::optional lastFrameStart, + int32_t avgFrameDurationUs, + int32_t offsetUs) { + switch (mode) { + case DxvkFramePace::MaxFrameLatency: return std::nullopt; + case DxvkFramePace::MinLatency: return std::nullopt; + case DxvkFramePace::LowLatency: break; + } + + return lastFrameStart.has_value() && avgFrameDurationUs > 0 + ? std::optional(*lastFrameStart + std::chrono::microseconds(avgFrameDurationUs + offsetUs)) + : std::nullopt; + } + + } + + + FramePacer::FramePacer(const DxvkOptions& options, uint64_t firstFrameId) + : m_lowLatencyOffsetUs(clamp_low_latency_offset(options.lowLatencyOffset)) { + auto envValue = env::getEnvVar(FRAME_PACE_ENV_VAR); + + m_mode = parse_frame_pace(envValue.empty() ? options.framePace : envValue); + + switch (m_mode) { + case DxvkFramePace::MaxFrameLatency: + break; + + case DxvkFramePace::MinLatency: + Logger::info("Frame pace: min-latency (frame latency forced to 1)"); + break; + + case DxvkFramePace::LowLatency: + Logger::info(str::format("Frame pace: low-latency (frame latency forced to 1, offset ", + m_lowLatencyOffsetUs, " us)")); + m_signal = new sync::CallbackFence(firstFrameId); + break; + } + } + + + uint32_t FramePacer::getEffectiveFrameLatency(uint32_t configuredLatency) const { + uint32_t effectiveLatency = configuredLatency; + + switch (m_mode) { + case DxvkFramePace::MaxFrameLatency: effectiveLatency = configuredLatency; break; + case DxvkFramePace::LowLatency: effectiveLatency = std::min(configuredLatency, MIN_FRAME_LATENCY); break; + case DxvkFramePace::MinLatency: effectiveLatency = std::min(configuredLatency, MIN_FRAME_LATENCY); break; + } + + return effectiveLatency; + } + + + void FramePacer::beginFrame() { + auto wakeTime = predict_wake_time(m_mode, m_lastFrameStart, + m_avgFrameDurationUs.load(), m_lowLatencyOffsetUs); + + switch (wakeTime.has_value()) { + case true: std::this_thread::sleep_until(*wakeTime); break; + case false: break; + } + + m_lastFrameStart = high_resolution_clock::now(); + } + + + void FramePacer::endFrame(uint64_t frameId) { + switch (m_mode) { + case DxvkFramePace::MaxFrameLatency: return; + case DxvkFramePace::MinLatency: return; + case DxvkFramePace::LowLatency: break; + } + + auto frameStart = *m_lastFrameStart; + + // Kept to one call so no pacing logic runs inside the fence own + // signalling context - the real work happens in recordFrameDuration. + m_signal->setCallback(frameId, [this, frameStart] () { recordFrameDuration(frameStart); }); + } + + + void FramePacer::recordFrameDuration(high_resolution_clock::time_point frameStart) { + auto elapsedUs = int32_t(std::chrono::duration_cast( + high_resolution_clock::now() - frameStart).count()); + + m_avgFrameDurationUs.store(blend_frame_duration(m_avgFrameDurationUs.load(), elapsedUs)); + } + +} diff --git a/src/dxvk/framepacer/dxvk_framepacer.h b/src/dxvk/framepacer/dxvk_framepacer.h new file mode 100644 index 00000000000..c0c9c50e1d7 --- /dev/null +++ b/src/dxvk/framepacer/dxvk_framepacer.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include + +#include "../../util/sync/sync_signal.h" +#include "../../util/util_time.h" + +namespace dxvk { + + struct DxvkOptions; + + // Mirrors netborg-afps/dxvk-low-latency dxvk.framePace modes, scaled down + // to what Sarek synchronous vk::Presenter and per-frame CallbackFence can + // actually observe (no per-submission GPU timestamps, no async present + // completion signal). + enum class DxvkFramePace : uint32_t { + MaxFrameLatency = 0, + LowLatency = 1, + MinLatency = 2, + }; + + class FramePacer { + + public: + + FramePacer(const DxvkOptions& options, uint64_t firstFrameId); + + // LowLatency and MinLatency both clamp to the same minimal buffering + // depth here; Sarek has no per-submission GPU progress signal to tell + // them apart the way upstream predictive low-latency mode does. + uint32_t getEffectiveFrameLatency(uint32_t configuredLatency) const; + + bool needsGpuSignal() const { return m_mode == DxvkFramePace::LowLatency; } + + const Rc& signal() const { return m_signal; } + + void beginFrame(); + + void endFrame(uint64_t frameId); + + private: + + void recordFrameDuration(high_resolution_clock::time_point frameStart); + + DxvkFramePace m_mode = DxvkFramePace::MaxFrameLatency; + int32_t m_lowLatencyOffsetUs = 0; + + std::optional m_lastFrameStart = std::nullopt; + std::atomic m_avgFrameDurationUs = { 0 }; + + Rc m_signal; + + }; + +} diff --git a/src/dxvk/meson.build b/src/dxvk/meson.build index 115f3b9d74f..7addf2d0fb8 100644 --- a/src/dxvk/meson.build +++ b/src/dxvk/meson.build @@ -74,6 +74,9 @@ dxvk_src = [ 'dxvk_fence.cpp', 'dxvk_format.cpp', 'dxvk_framebuffer.cpp', + + 'framepacer/dxvk_framepacer.cpp', + 'dxvk_gpu_event.cpp', 'dxvk_gpu_query.cpp', 'dxvk_graphics.cpp', diff --git a/src/spirv/spirv_code_buffer.cpp b/src/spirv/spirv_code_buffer.cpp index 74d8dd970c6..58d9724431d 100644 --- a/src/spirv/spirv_code_buffer.cpp +++ b/src/spirv/spirv_code_buffer.cpp @@ -4,11 +4,11 @@ #include "spirv_code_buffer.h" namespace dxvk { - + SpirvCodeBuffer:: SpirvCodeBuffer() { } SpirvCodeBuffer::~SpirvCodeBuffer() { } - - + + SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size) : m_ptr(size) { m_code.resize(size); @@ -20,26 +20,26 @@ namespace dxvk { m_code.resize(size); std::memcpy(m_code.data(), data, size * sizeof(uint32_t)); } - - + + SpirvCodeBuffer::SpirvCodeBuffer(std::istream& stream) { stream.ignore(std::numeric_limits::max()); std::streamsize length = stream.gcount(); stream.clear(); stream.seekg(0, std::ios_base::beg); - + std::vector buffer(length); stream.read(buffer.data(), length); buffer.resize(stream.gcount()); - + m_code.resize(buffer.size() / sizeof(uint32_t)); std::memcpy(reinterpret_cast(m_code.data()), buffer.data(), m_code.size() * sizeof(uint32_t)); - + m_ptr = m_code.size(); } - - + + uint32_t SpirvCodeBuffer::allocId() { constexpr size_t BoundIdsOffset = 3; @@ -54,75 +54,86 @@ namespace dxvk { if (other.size() != 0) { const size_t size = m_code.size(); m_code.resize(size + other.m_code.size()); - + uint32_t* dst = this->m_code.data(); const uint32_t* src = other.m_code.data(); - + std::memcpy(dst + size, src, other.size()); m_ptr += other.m_code.size(); } } - - + + void SpirvCodeBuffer::append(const SpirvInstruction& ins) { + const size_t size = m_code.size(); + + m_code.resize(size + ins.length()); + + for (uint32_t i = 0; i < ins.length(); i++) + m_code[size + i] = ins.arg(i); + + m_ptr += ins.length(); + } + + void SpirvCodeBuffer::putWord(uint32_t word) { m_code.insert(m_code.begin() + m_ptr, word); m_ptr += 1; } - - + + void SpirvCodeBuffer::putIns(spv::Op opCode, uint16_t wordCount) { this->putWord( (static_cast(opCode) << 0) | (static_cast(wordCount) << 16)); } - - + + void SpirvCodeBuffer::putInt32(uint32_t word) { this->putWord(word); } - - + + void SpirvCodeBuffer::putInt64(uint64_t value) { this->putWord(value >> 0); this->putWord(value >> 32); } - - + + void SpirvCodeBuffer::putFloat32(float value) { uint32_t tmp; static_assert(sizeof(tmp) == sizeof(value)); std::memcpy(&tmp, &value, sizeof(value)); this->putInt32(tmp); } - - + + void SpirvCodeBuffer::putFloat64(double value) { uint64_t tmp; static_assert(sizeof(tmp) == sizeof(value)); std::memcpy(&tmp, &value, sizeof(value)); this->putInt64(tmp); } - - + + void SpirvCodeBuffer::putStr(const char* str) { uint32_t word = 0; uint32_t nbit = 0; - + for (uint32_t i = 0; str[i] != '\0'; str++) { word |= (static_cast(str[i]) & 0xFF) << nbit; - + if ((nbit += 8) == 32) { this->putWord(word); word = 0; nbit = 0; } } - + // Commit current word this->putWord(word); } - - + + void SpirvCodeBuffer::putHeader(uint32_t version, uint32_t boundIds) { this->putWord(spv::MagicNumber); this->putWord(version); @@ -130,8 +141,8 @@ namespace dxvk { this->putWord(boundIds); this->putWord(0); // Schema } - - + + void SpirvCodeBuffer::erase(size_t size) { m_code.erase( m_code.begin() + m_ptr, @@ -143,12 +154,12 @@ namespace dxvk { // Null-termination plus padding return (std::strlen(str) + 4) / 4; } - - + + void SpirvCodeBuffer::store(std::ostream& stream) const { stream.write( reinterpret_cast(m_code.data()), sizeof(uint32_t) * m_code.size()); } - -} \ No newline at end of file + +} diff --git a/src/spirv/spirv_code_buffer.h b/src/spirv/spirv_code_buffer.h index e919bf4cb82..6dcaa60f3e3 100644 --- a/src/spirv/spirv_code_buffer.h +++ b/src/spirv/spirv_code_buffer.h @@ -7,36 +7,36 @@ #include "spirv_instruction.h" namespace dxvk { - + /** * \brief SPIR-V code buffer - * + * * Helper class for generating SPIR-V shaders. * Stores arbitrary SPIR-V instructions in a * format that can be read by Vulkan drivers. */ class SpirvCodeBuffer { - + public: - + SpirvCodeBuffer(); explicit SpirvCodeBuffer(uint32_t size); SpirvCodeBuffer(uint32_t size, const uint32_t* data); SpirvCodeBuffer(std::istream& stream); - + template SpirvCodeBuffer(const uint32_t (&data)[N]) : SpirvCodeBuffer(N, data) { } - + ~SpirvCodeBuffer(); - + /** * \brief Code data * \returns Code data */ const uint32_t* data() const { return m_code.data(); } uint32_t* data() { return m_code.data(); } - + /** * \brief Code size, in dwords * \returns Code size, in dwords @@ -44,7 +44,7 @@ namespace dxvk { uint32_t dwords() const { return m_code.size(); } - + /** * \brief Code size, in bytes * \returns Code size, in bytes @@ -52,10 +52,10 @@ namespace dxvk { size_t size() const { return m_code.size() * sizeof(uint32_t); } - + /** * \brief Begin instruction iterator - * + * * Points to the first instruction in the instruction * block. The header, if any, will be skipped over. * \returns Instruction iterator @@ -64,17 +64,17 @@ namespace dxvk { return SpirvInstructionIterator( m_code.data(), 0, m_code.size()); } - + /** * \brief End instruction iterator - * + * * Points to the end of the instruction block. * \returns Instruction iterator */ SpirvInstructionIterator end() { return SpirvInstructionIterator(nullptr, 0, 0); } - + /** * \brief Allocates a new ID * @@ -83,26 +83,33 @@ namespace dxvk { * \returns The new SPIR-V ID */ uint32_t allocId(); - + /** * \brief Merges two code buffers - * + * * This is useful to generate declarations or * the SPIR-V header at the same time as the * code when doing so in advance is impossible. * \param [in] other Code buffer to append */ void append(const SpirvCodeBuffer& other); - + + /** + * \brief Appends a single instruction + * + * \param [in] ins Instruction to append + */ + void append(const SpirvInstruction& ins); + /** * \brief Appends an 32-bit word to the buffer * \param [in] word The word to append */ void putWord(uint32_t word); - + /** * \brief Appends an instruction word to the buffer - * + * * Adds a single word containing both the word count * and the op code number for a single instruction. * \param [in] opCode Operand code @@ -115,33 +122,33 @@ namespace dxvk { * \param [in] value The number to add */ void putInt32(uint32_t word); - + /** * \brief Appends a 64-bit integer to the buffer - * + * * A 64-bit integer will take up two 32-bit words. * \param [in] value 64-bit value to add */ void putInt64(uint64_t value); - + /** * \brief Appends a 32-bit float to the buffer * \param [in] value The number to add */ void putFloat32(float value); - + /** * \brief Appends a 64-bit float to the buffer * \param [in] value The number to add */ void putFloat64(double value); - + /** * \brief Appends a literal string to the buffer * \param [in] str String to append to the buffer */ void putStr(const char* str); - + /** * \brief Adds the header to the buffer * @@ -158,27 +165,27 @@ namespace dxvk { * \param [in] size Number of words to remove */ void erase(size_t size); - + /** * \brief Computes length of a literal string - * + * * \param [in] str The string to check * \returns Number of words consumed by a string */ uint32_t strLen(const char* str); - + /** * \brief Stores the SPIR-V module to a stream - * + * * The ability to save modules to a file * exists mostly for debugging purposes. * \param [in] stream Output stream */ void store(std::ostream& stream) const; - + /** * \brief Retrieves current insertion pointer - * + * * Sometimes it may be necessay to insert code into the * middle of the stream rather than appending it. This * retrieves the current function pointer. Note that the @@ -189,10 +196,10 @@ namespace dxvk { size_t getInsertionPtr() const { return m_ptr; } - + /** * \brief Sets insertion pointer to a specific value - * + * * Sets the insertion pointer to a value that was * previously retrieved by \ref getInsertionPtr. * \returns Current instruction pointr @@ -200,10 +207,10 @@ namespace dxvk { void beginInsertion(size_t ptr) { m_ptr = ptr; } - + /** * \brief Sets insertion pointer to the end - * + * * After this call, new instructions will be * appended to the stream. In other words, * this will restore default behaviour. @@ -212,12 +219,12 @@ namespace dxvk { size_t endInsertion() { return std::exchange(m_ptr, m_code.size()); } - + private: - + std::vector m_code; size_t m_ptr = 0; - + }; - + } diff --git a/src/spirv/spirv_module.cpp b/src/spirv/spirv_module.cpp index 0c02acd95e2..8b43e7e9e97 100644 --- a/src/spirv/spirv_module.cpp +++ b/src/spirv/spirv_module.cpp @@ -882,16 +882,7 @@ namespace dxvk { uint32_t SpirvModule::newVar( uint32_t pointerType, spv::StorageClass storageClass) { - uint32_t resultId = this->allocateId(); - - auto& code = storageClass != spv::StorageClassFunction - ? m_variables : m_code; - - code.putIns (spv::OpVariable, 4); - code.putWord (pointerType); - code.putWord (resultId); - code.putWord (storageClass); - return resultId; + return newVarInit(pointerType, storageClass, 0u); } diff --git a/src/util/log/log.cpp b/src/util/log/log.cpp index a163dfb104a..36afbec8045 100644 --- a/src/util/log/log.cpp +++ b/src/util/log/log.cpp @@ -77,10 +77,14 @@ namespace dxvk { std::string adjusted = outstream.str(); if (!adjusted.empty()) { +#ifdef _WIN32 if (m_wineLogOutput) m_wineLogOutput(adjusted.c_str()); else std::cerr << adjusted; +#else + std::cerr << adjusted; +#endif } if (m_fileStream) @@ -96,9 +100,11 @@ namespace dxvk { if (path == "none") return std::string(); +#ifdef _WIN32 // Don't create a log file if we're writing to wine's console output if (path.empty() && m_wineLogOutput) return std::string(); +#endif if (!path.empty() && *path.rbegin() != '/') path += '/'; diff --git a/src/vulkan/vulkan_presenter.cpp b/src/vulkan/vulkan_presenter.cpp index 880f662d2dd..ebb20ba3cdd 100644 --- a/src/vulkan/vulkan_presenter.cpp +++ b/src/vulkan/vulkan_presenter.cpp @@ -26,7 +26,7 @@ namespace dxvk::vk { throw DxvkError("Failed to create swap chain"); } - + Presenter::~Presenter() { destroySwapchain(); destroySurface(); @@ -52,10 +52,10 @@ namespace dxvk::vk { m_swapchain, std::numeric_limits::max(), sync.acquire, VK_NULL_HANDLE, &m_imageIndex); } - + if (m_acquireStatus != VK_SUCCESS && m_acquireStatus != VK_SUBOPTIMAL_KHR) return m_acquireStatus; - + index = m_imageIndex; return m_acquireStatus; } @@ -64,15 +64,12 @@ namespace dxvk::vk { VkResult Presenter::presentImage() { PresenterSync sync = m_semaphores.at(m_frameIndex); - VkPresentInfoKHR info; - info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - info.pNext = nullptr; + VkPresentInfoKHR info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; info.waitSemaphoreCount = 1; info.pWaitSemaphores = &sync.present; info.swapchainCount = 1; info.pSwapchains = &m_swapchain; info.pImageIndices = &m_imageIndex; - info.pResults = nullptr; VkResult status = m_vkd->vkQueuePresentKHR(m_device.queue, &info); @@ -97,11 +94,14 @@ namespace dxvk::vk { return status; } - + VkResult Presenter::recreateSwapChain(const PresenterDesc& desc) { if (m_swapchain) destroySwapchain(); + if (!m_surface) + return VK_ERROR_SURFACE_LOST_KHR; + // Query surface capabilities. Some properties might // have changed, including the size limits and supported // present modes, so we'll just query everything again. @@ -110,7 +110,7 @@ namespace dxvk::vk { std::vector modes; VkResult status; - + if ((status = m_vki->vkGetPhysicalDeviceSurfaceCapabilitiesKHR( m_device.adapter, m_surface, &caps)) != VK_SUCCESS) { if (status == VK_ERROR_SURFACE_LOST_KHR) { @@ -126,10 +126,10 @@ namespace dxvk::vk { return status; } - if ((status = getSupportedFormats(formats, desc)) != VK_SUCCESS) + if ((status = getSupportedFormats(formats, desc.fullScreenExclusive)) != VK_SUCCESS) return status; - if ((status = getSupportedPresentModes(modes, desc)) != VK_SUCCESS) + if ((status = getSupportedPresentModes(modes, desc.fullScreenExclusive)) != VK_SUCCESS) return status; // Select actual swap chain properties and create swap chain @@ -144,15 +144,10 @@ namespace dxvk::vk { return VK_SUCCESS; } - VkSurfaceFullScreenExclusiveInfoEXT fullScreenInfo; - fullScreenInfo.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT; - fullScreenInfo.pNext = nullptr; + VkSurfaceFullScreenExclusiveInfoEXT fullScreenInfo = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT }; fullScreenInfo.fullScreenExclusive = desc.fullScreenExclusive; - VkSwapchainCreateInfoKHR swapInfo; - swapInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - swapInfo.pNext = nullptr; - swapInfo.flags = 0; + VkSwapchainCreateInfoKHR swapInfo = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; swapInfo.surface = m_surface; swapInfo.minImageCount = m_info.imageCount; swapInfo.imageFormat = m_info.format.format; @@ -162,8 +157,6 @@ namespace dxvk::vk { swapInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; swapInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - swapInfo.queueFamilyIndexCount = 0; - swapInfo.pQueueFamilyIndices = nullptr; swapInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; swapInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapInfo.presentMode = m_info.presentMode; @@ -176,21 +169,22 @@ namespace dxvk::vk { Logger::info(str::format( "Presenter: Actual swap chain properties:" "\n Format: ", m_info.format.format, + "\n Color space: ", m_info.format.colorSpace, "\n Present mode: ", m_info.presentMode, "\n Buffer size: ", m_info.imageExtent.width, "x", m_info.imageExtent.height, "\n Image count: ", m_info.imageCount, "\n Exclusive FS: ", desc.fullScreenExclusive)); - + if ((status = m_vkd->vkCreateSwapchainKHR(m_vkd->device(), &swapInfo, nullptr, &m_swapchain)) != VK_SUCCESS) return status; - + // Acquire images and create views std::vector images; if ((status = getSwapImages(images)) != VK_SUCCESS) return status; - + // Update actual image count m_info.imageCount = images.size(); m_images.resize(m_info.imageCount); @@ -198,10 +192,7 @@ namespace dxvk::vk { for (uint32_t i = 0; i < m_info.imageCount; i++) { m_images[i].image = images[i]; - VkImageViewCreateInfo viewInfo; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.pNext = nullptr; - viewInfo.flags = 0; + VkImageViewCreateInfo viewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; viewInfo.image = images[i]; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = m_info.format.format; @@ -211,7 +202,7 @@ namespace dxvk::vk { viewInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - + if ((status = m_vkd->vkCreateImageView(m_vkd->device(), &viewInfo, nullptr, &m_images[i].view)) != VK_SUCCESS) return status; @@ -221,10 +212,7 @@ namespace dxvk::vk { m_semaphores.resize(m_info.imageCount); for (uint32_t i = 0; i < m_semaphores.size(); i++) { - VkSemaphoreCreateInfo semInfo; - semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semInfo.pNext = nullptr; - semInfo.flags = 0; + VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; if ((status = m_vkd->vkCreateSemaphore(m_vkd->device(), &semInfo, nullptr, &m_semaphores[i].acquire)) != VK_SUCCESS) @@ -234,7 +222,7 @@ namespace dxvk::vk { &semInfo, nullptr, &m_semaphores[i].present)) != VK_SUCCESS) return status; } - + // Invalidate indices m_imageIndex = 0; m_frameIndex = 0; @@ -248,21 +236,17 @@ namespace dxvk::vk { } - VkResult Presenter::getSupportedFormats(std::vector& formats, const PresenterDesc& desc) { + VkResult Presenter::getSupportedFormats(std::vector& formats, VkFullScreenExclusiveEXT fullScreenExclusive) const { uint32_t numFormats = 0; - VkSurfaceFullScreenExclusiveInfoEXT fullScreenInfo; - fullScreenInfo.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT; - fullScreenInfo.pNext = nullptr; - fullScreenInfo.fullScreenExclusive = desc.fullScreenExclusive; + VkSurfaceFullScreenExclusiveInfoEXT fullScreenInfo = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT }; + fullScreenInfo.fullScreenExclusive = fullScreenExclusive; - VkPhysicalDeviceSurfaceInfo2KHR surfaceInfo; - surfaceInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR; - surfaceInfo.pNext = &fullScreenInfo; + VkPhysicalDeviceSurfaceInfo2KHR surfaceInfo = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, &fullScreenInfo }; surfaceInfo.surface = m_surface; VkResult status; - + if (m_device.features.fullScreenExclusive) { status = m_vki->vkGetPhysicalDeviceSurfaceFormats2KHR( m_device.adapter, &surfaceInfo, &numFormats, nullptr); @@ -273,11 +257,11 @@ namespace dxvk::vk { if (status != VK_SUCCESS) return status; - + formats.resize(numFormats); if (m_device.features.fullScreenExclusive) { - std::vector tmpFormats(numFormats, + std::vector tmpFormats(numFormats, { VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR, nullptr, VkSurfaceFormatKHR() }); status = m_vki->vkGetPhysicalDeviceSurfaceFormats2KHR( @@ -293,18 +277,14 @@ namespace dxvk::vk { return status; } - - VkResult Presenter::getSupportedPresentModes(std::vector& modes, const PresenterDesc& desc) { + + VkResult Presenter::getSupportedPresentModes(std::vector& modes, VkFullScreenExclusiveEXT fullScreenExclusive) const { uint32_t numModes = 0; - VkSurfaceFullScreenExclusiveInfoEXT fullScreenInfo; - fullScreenInfo.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT; - fullScreenInfo.pNext = nullptr; - fullScreenInfo.fullScreenExclusive = desc.fullScreenExclusive; + VkSurfaceFullScreenExclusiveInfoEXT fullScreenInfo = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT }; + fullScreenInfo.fullScreenExclusive = fullScreenExclusive; - VkPhysicalDeviceSurfaceInfo2KHR surfaceInfo; - surfaceInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR; - surfaceInfo.pNext = &fullScreenInfo; + VkPhysicalDeviceSurfaceInfo2KHR surfaceInfo = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, &fullScreenInfo }; surfaceInfo.surface = m_surface; VkResult status; @@ -319,7 +299,7 @@ namespace dxvk::vk { if (status != VK_SUCCESS) return status; - + modes.resize(numModes); if (m_device.features.fullScreenExclusive) { @@ -339,10 +319,10 @@ namespace dxvk::vk { VkResult status = m_vkd->vkGetSwapchainImagesKHR( m_vkd->device(), m_swapchain, &imageCount, nullptr); - + if (status != VK_SUCCESS) return status; - + images.resize(imageCount); return m_vkd->vkGetSwapchainImagesKHR( @@ -360,7 +340,7 @@ namespace dxvk::vk { // the format, we'll just use the preferred format. if (numSupported == 1 && pSupported[0].format == VK_FORMAT_UNDEFINED) return pDesired[0]; - + // If the preferred format is explicitly listed in // the array of supported surface formats, use it for (uint32_t i = 0; i < numDesired; i++) { @@ -383,7 +363,7 @@ namespace dxvk::vk { return pSupported[j]; } } - + // Otherwise, fall back to the first supported format return pSupported[0]; } @@ -401,7 +381,7 @@ namespace dxvk::vk { return pSupported[j]; } } - + // Guaranteed to be available return VK_PRESENT_MODE_FIFO_KHR; } @@ -412,7 +392,7 @@ namespace dxvk::vk { VkExtent2D desired) { if (caps.currentExtent.width != std::numeric_limits::max()) return caps.currentExtent; - + VkExtent2D actual; actual.width = clamp(desired.width, caps.minImageExtent.width, caps.maxImageExtent.width); actual.height = clamp(desired.height, caps.minImageExtent.height, caps.maxImageExtent.height); @@ -425,16 +405,16 @@ namespace dxvk::vk { VkPresentModeKHR presentMode, uint32_t desired) { uint32_t count = caps.minImageCount; - + if (presentMode != VK_PRESENT_MODE_IMMEDIATE_KHR) count = caps.minImageCount + 1; - + if (count < desired) count = desired; - + if (count > caps.maxImageCount && caps.maxImageCount != 0) count = caps.maxImageCount; - + return count; } @@ -442,26 +422,26 @@ namespace dxvk::vk { VkResult Presenter::createSurface() { HINSTANCE instance = reinterpret_cast( GetWindowLongPtr(m_window, GWLP_HINSTANCE)); - + VkWin32SurfaceCreateInfoKHR info; info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; info.pNext = nullptr; info.flags = 0; info.hinstance = instance; info.hwnd = m_window; - + VkResult status = m_vki->vkCreateWin32SurfaceKHR( m_vki->instance(), &info, nullptr, &m_surface); - + if (status != VK_SUCCESS) return status; - + VkBool32 supportStatus = VK_FALSE; if ((status = m_vki->vkGetPhysicalDeviceSurfaceSupportKHR(m_device.adapter, m_device.queueFamily, m_surface, &supportStatus)) != VK_SUCCESS) return status; - + if (!supportStatus) { m_vki->vkDestroySurfaceKHR(m_vki->instance(), m_surface, nullptr); return VK_ERROR_OUT_OF_HOST_MEMORY; // just abuse this @@ -474,7 +454,7 @@ namespace dxvk::vk { void Presenter::destroySwapchain() { for (const auto& img : m_images) m_vkd->vkDestroyImageView(m_vkd->device(), img.view, nullptr); - + for (const auto& sem : m_semaphores) { m_vkd->vkDestroySemaphore(m_vkd->device(), sem.acquire, nullptr); m_vkd->vkDestroySemaphore(m_vkd->device(), sem.present, nullptr); @@ -491,6 +471,8 @@ namespace dxvk::vk { void Presenter::destroySurface() { m_vki->vkDestroySurfaceKHR(m_vki->instance(), m_surface, nullptr); + + m_surface = VK_NULL_HANDLE; } } diff --git a/src/vulkan/vulkan_presenter.h b/src/vulkan/vulkan_presenter.h index 8df19ea93f4..c90683fe892 100644 --- a/src/vulkan/vulkan_presenter.h +++ b/src/vulkan/vulkan_presenter.h @@ -15,7 +15,7 @@ namespace dxvk::vk { /** * \brief Presenter description - * + * * Contains the desired properties of * the swap chain. This is passed as * an input during swap chain creation. @@ -32,7 +32,7 @@ namespace dxvk::vk { /** * \brief Presenter properties - * + * * Contains the actual properties * of the underlying swap chain. */ @@ -49,7 +49,7 @@ namespace dxvk::vk { struct PresenterFeatures { bool fullScreenExclusive : 1; }; - + /** * \brief Adapter and queue */ @@ -70,7 +70,7 @@ namespace dxvk::vk { /** * \brief Presenter semaphores - * + * * Pair of semaphores used for acquire and present * operations, including the command buffers used * in between. Also stores a fence to signal on @@ -83,7 +83,7 @@ namespace dxvk::vk { /** * \brief Vulkan presenter - * + * * Provides abstractions for some of the * more complicated aspects of Vulkan's * window system integration. @@ -98,7 +98,7 @@ namespace dxvk::vk { const Rc& vkd, PresenterDevice device, const PresenterDesc& desc); - + ~Presenter(); /** @@ -109,7 +109,7 @@ namespace dxvk::vk { /** * \brief Retrieves image by index - * + * * Can be used to create per-image objects. * \param [in] index Image index * \returns Image handle @@ -119,7 +119,7 @@ namespace dxvk::vk { /** * \brief Acquires next image - * + * * Potentially blocks the calling thread. * If this returns an error, the swap chain * must be recreated and a new image must @@ -131,20 +131,20 @@ namespace dxvk::vk { VkResult acquireNextImage( PresenterSync& sync, uint32_t& index); - + /** * \brief Presents current image - * + * * Presents the current image. If this returns * an error, the swap chain must be recreated, * but do not present before acquiring an image. * \returns Status of the operation */ VkResult presentImage(); - + /** * \brief Changes presenter properties - * + * * Recreates the swap chain immediately. Note that * no swap chain resources must be in use by the * GPU at the time this is called. @@ -197,15 +197,15 @@ namespace dxvk::vk { VkResult getSupportedFormats( std::vector& formats, - const PresenterDesc& desc); - + VkFullScreenExclusiveEXT fullScreenExclusive) const; + VkResult getSupportedPresentModes( std::vector& modes, - const PresenterDesc& desc); - + VkFullScreenExclusiveEXT fullScreenExclusive) const; + VkResult getSwapImages( std::vector& images); - + VkSurfaceFormatKHR pickFormat( uint32_t numSupported, const VkSurfaceFormatKHR* pSupported,