From 3ddc199bec1842bda58481647f45a7a2852d4de1 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 7 Aug 2026 13:23:19 +1000 Subject: [PATCH] [idd] capture: split software and hardware frame processors Move cadence-aware hardware capture and immediate software capture behind a common frame processor interface. Keep shared damage tracking and frame-buffer ownership in the base processor, and move stateless format, resource, and rectangle helpers into CFrameProcessorUtil. Decouple frame-buffer resources from CSwapChainProcessor by passing the device dependencies they use directly. --- idd/LGIdd/CD3D11Device.h | 4 +- idd/LGIdd/CFrameBufferPool.cpp | 9 +- idd/LGIdd/CFrameBufferPool.h | 9 +- idd/LGIdd/CFrameBufferResource.cpp | 35 +- idd/LGIdd/CFrameBufferResource.h | 7 +- idd/LGIdd/CFrameProcessor.cpp | 175 +++ idd/LGIdd/CFrameProcessor.h | 99 ++ idd/LGIdd/CFrameProcessorUtil.cpp | 270 +++++ idd/LGIdd/CFrameProcessorUtil.h | 63 + idd/LGIdd/CHardwareFrameProcessor.cpp | 821 +++++++++++++ idd/LGIdd/CHardwareFrameProcessor.h | 105 ++ idd/LGIdd/CIndirectDeviceContext.cpp | 2 +- idd/LGIdd/CSoftwareFrameProcessor.cpp | 364 ++++++ idd/LGIdd/CSoftwareFrameProcessor.h | 46 + idd/LGIdd/CSwapChainProcessor.cpp | 1559 +------------------------ idd/LGIdd/CSwapChainProcessor.h | 113 +- idd/LGIdd/LGIdd.vcxproj | 8 + idd/LGIdd/LGIdd.vcxproj.filters | 24 + 18 files changed, 2051 insertions(+), 1662 deletions(-) create mode 100644 idd/LGIdd/CFrameProcessor.cpp create mode 100644 idd/LGIdd/CFrameProcessor.h create mode 100644 idd/LGIdd/CFrameProcessorUtil.cpp create mode 100644 idd/LGIdd/CFrameProcessorUtil.h create mode 100644 idd/LGIdd/CHardwareFrameProcessor.cpp create mode 100644 idd/LGIdd/CHardwareFrameProcessor.h create mode 100644 idd/LGIdd/CSoftwareFrameProcessor.cpp create mode 100644 idd/LGIdd/CSoftwareFrameProcessor.h diff --git a/idd/LGIdd/CD3D11Device.h b/idd/LGIdd/CD3D11Device.h index e8020f4f..163f8a0e 100644 --- a/idd/LGIdd/CD3D11Device.h +++ b/idd/LGIdd/CD3D11Device.h @@ -36,7 +36,7 @@ private: ComPtr m_adapter; ComPtr m_device; ComPtr m_context; - bool m_isSoftware; + bool m_isSoftware = false; public: CD3D11Device(LUID adapterLuid) : @@ -54,4 +54,4 @@ public: ComPtr GetContext() { return m_context; } bool IsSoftware() { return m_isSoftware; } -}; \ No newline at end of file +}; diff --git a/idd/LGIdd/CFrameBufferPool.cpp b/idd/LGIdd/CFrameBufferPool.cpp index 183d39bb..80461408 100644 --- a/idd/LGIdd/CFrameBufferPool.cpp +++ b/idd/LGIdd/CFrameBufferPool.cpp @@ -19,13 +19,14 @@ */ #include "CFrameBufferPool.h" -#include "CSwapChainProcessor.h" #include -void CFrameBufferPool::Init(CSwapChainProcessor * swapChain) +void CFrameBufferPool::Init( + CIndirectDeviceContext * device, CD3D12Device * dx12) { - m_swapChain = swapChain; + m_device = device; + m_dx12 = dx12; } void CFrameBufferPool::Reset() @@ -42,7 +43,7 @@ CFrameBufferResource * CFrameBufferPool::Get( return nullptr; CFrameBufferResource * fbr = &m_buffers[buffer.frameIndex]; - if (!fbr->Init(m_swapChain, buffer.frameIndex, buffer.mem, + if (!fbr->Init(m_device, m_dx12, buffer.frameIndex, buffer.mem, minSize, textureDesc)) return nullptr; diff --git a/idd/LGIdd/CFrameBufferPool.h b/idd/LGIdd/CFrameBufferPool.h index ae528516..3a0b15e6 100644 --- a/idd/LGIdd/CFrameBufferPool.h +++ b/idd/LGIdd/CFrameBufferPool.h @@ -24,19 +24,20 @@ #include "CIndirectDeviceContext.h" #include "common/KVMFR.h" -//class CSwapChainProcessor; +struct CD3D12Device; class CFrameBufferPool { - CSwapChainProcessor * m_swapChain; + CIndirectDeviceContext * m_device = nullptr; + CD3D12Device * m_dx12 = nullptr; CFrameBufferResource m_buffers[LGMP_Q_FRAME_BUFFER_LEN]; public: - void Init(CSwapChainProcessor * swapChain); + void Init(CIndirectDeviceContext * device, CD3D12Device * dx12); void Reset(); - CFrameBufferResource* CFrameBufferPool::Get( + CFrameBufferResource * Get( const CIndirectDeviceContext::PreparedFrameBuffer& buffer, size_t minSize, const D3D12_RESOURCE_DESC * textureDesc = nullptr); diff --git a/idd/LGIdd/CFrameBufferResource.cpp b/idd/LGIdd/CFrameBufferResource.cpp index 677c7187..65bbfa09 100644 --- a/idd/LGIdd/CFrameBufferResource.cpp +++ b/idd/LGIdd/CFrameBufferResource.cpp @@ -19,42 +19,26 @@ */ #include "CFrameBufferResource.h" -#include "CSwapChainProcessor.h" +#include "CFrameProcessorUtil.h" +#include "CD3D12Device.h" +#include "CIndirectDeviceContext.h" #include "CDebug.h" #include -static bool ResourceDescMatches( - const D3D12_RESOURCE_DESC& left, const D3D12_RESOURCE_DESC& right) -{ - return - left.Dimension == right.Dimension && - left.Alignment == right.Alignment && - left.Width == right.Width && - left.Height == right.Height && - left.DepthOrArraySize == right.DepthOrArraySize && - left.MipLevels == right.MipLevels && - left.Format == right.Format && - left.SampleDesc.Count == right.SampleDesc.Count && - left.SampleDesc.Quality == right.SampleDesc.Quality && - left.Layout == right.Layout && - left.Flags == right.Flags; -} - -bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, - unsigned frameIndex, uint8_t * base, size_t size, +bool CFrameBufferResource::Init(CIndirectDeviceContext * device, + CD3D12Device * dx12, unsigned frameIndex, uint8_t * base, size_t size, const D3D12_RESOURCE_DESC * textureDesc) { m_frameIndex = frameIndex; - if (size > swapChain->GetDevice()->GetMaxFrameSize()) + if (size > device->GetMaxFrameSize()) { DEBUG_ERROR("Frame size of %llu is too large to fit in shared ram", (unsigned long long)size); return false; } - const auto dx12 = swapChain->GetD3D12Device(); const bool indirect = dx12->IsIndirectCopy(); const ResourceType type = textureDesc ? RESOURCE_TEXTURE : RESOURCE_BUFFER; @@ -99,7 +83,8 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, // Nothing to do if the resource already represents this allocation. if (m_base == base && m_type == type && ((type == RESOURCE_BUFFER && m_size >= size) || - (type == RESOURCE_TEXTURE && ResourceDescMatches(m_desc, desc)))) + (type == RESOURCE_TEXTURE && + CFrameProcessorUtil::ResourceDescMatches(m_desc, desc)))) { m_frameSize = size; return true; @@ -146,14 +131,14 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, { const UINT64 heapOffset = (uintptr_t)base - - (uintptr_t)swapChain->GetDevice()->GetIVSHMEM().GetMem(); + (uintptr_t)device->GetIVSHMEM().GetMem(); const D3D12_RESOURCE_ALLOCATION_INFO allocation = dx12->GetDevice()->GetResourceAllocationInfo(0, 1, &desc); allocationSize = allocation.SizeInBytes; const D3D12_HEAP_DESC heapDesc = dx12->GetHeap()->GetDesc(); if (!allocation.Alignment || heapOffset % allocation.Alignment || - allocation.SizeInBytes > swapChain->GetDevice()->GetMaxFrameSize() || + allocation.SizeInBytes > device->GetMaxFrameSize() || heapOffset > heapDesc.SizeInBytes || allocation.SizeInBytes > heapDesc.SizeInBytes - heapOffset) { diff --git a/idd/LGIdd/CFrameBufferResource.h b/idd/LGIdd/CFrameBufferResource.h index 082ff33b..c14e4478 100644 --- a/idd/LGIdd/CFrameBufferResource.h +++ b/idd/LGIdd/CFrameBufferResource.h @@ -30,7 +30,8 @@ #include "CFrameScheduler.h" #include "CInteropResource.h" -class CSwapChainProcessor; +struct CD3D12Device; +class CIndirectDeviceContext; using namespace Microsoft::WRL; @@ -69,8 +70,8 @@ class CFrameBufferResource void * m_map = nullptr; public: - bool Init(CSwapChainProcessor * swapChain, unsigned frameIndex, - uint8_t * base, size_t size, + bool Init(CIndirectDeviceContext * device, CD3D12Device * dx12, + unsigned frameIndex, uint8_t * base, size_t size, const D3D12_RESOURCE_DESC * textureDesc = nullptr); void Reset(); diff --git a/idd/LGIdd/CFrameProcessor.cpp b/idd/LGIdd/CFrameProcessor.cpp new file mode 100644 index 00000000..3d62257b --- /dev/null +++ b/idd/LGIdd/CFrameProcessor.cpp @@ -0,0 +1,175 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "CFrameProcessor.h" +#include "CFrameProcessorUtil.h" +#include "CHardwareFrameProcessor.h" +#include "CSoftwareFrameProcessor.h" + +#include +#include +#include + +CFrameProcessor::CFrameProcessor(CIndirectDeviceContext * device, + std::shared_ptr dx12, + CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], + SRWLOCK * pipelineLock, HANDLE terminateEvent) : + m_device(device), + m_dx12(std::move(dx12)), + m_postProcessors(postProcessors), + m_pipelineLock(pipelineLock), + m_terminateEvent(terminateEvent) +{ + m_readyEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr)); + m_frameBuffers.Init(m_device, m_dx12.get()); +} + +bool CFrameProcessor::IsValid() const +{ + return m_readyEvent.Get() != nullptr; +} + +void CFrameProcessor::Reset() +{ + m_frameBuffers.Reset(); +} + +void CFrameProcessor::Invalidate() +{ + AcquireSRWLockExclusive(&m_damageLock); + m_previousDamageCount = 0; + m_hasPendingDamage = true; + m_pendingDamageCount = 0; + SetFullDamageLocked(); + ReleaseSRWLockExclusive(&m_damageLock); +} + +void CFrameProcessor::ResetPipeline() +{ + Invalidate(); +} + +void CFrameProcessor::AccumulateDamage( + const RECT dirtyRects[], unsigned count) +{ + AcquireSRWLockExclusive(&m_damageLock); + CFrameProcessorUtil::AccumulateDamage( + m_pendingDamage, &m_pendingDamageCount, &m_hasPendingDamage, + dirtyRects, count); + AccumulateDamageLocked(dirtyRects, count); + ReleaseSRWLockExclusive(&m_damageLock); +} + +void CFrameProcessor::SetFullDamage() +{ + AcquireSRWLockExclusive(&m_damageLock); + m_hasPendingDamage = true; + m_pendingDamageCount = 0; + SetFullDamageLocked(); + ReleaseSRWLockExclusive(&m_damageLock); +} + +void CFrameProcessor::AccumulateDamageLocked( + const RECT[], unsigned) +{ +} + +void CFrameProcessor::SetFullDamageLocked() +{ +} + +bool CFrameProcessor::HasPendingDamage() const +{ + AcquireSRWLockShared(&m_damageLock); + const bool result = m_hasPendingDamage; + ReleaseSRWLockShared(&m_damageLock); + return result; +} + +bool CFrameProcessor::TakePendingDamage( + RECT dirtyRects[], unsigned * count) +{ + AcquireSRWLockExclusive(&m_damageLock); + const bool hasDamage = m_hasPendingDamage; + *count = hasDamage ? m_pendingDamageCount : 0; + if (*count) + memcpy(dirtyRects, m_pendingDamage, + *count * sizeof(*dirtyRects)); + m_hasPendingDamage = false; + m_pendingDamageCount = 0; + ReleaseSRWLockExclusive(&m_damageLock); + return hasDamage; +} + +void CFrameProcessor::RestorePendingDamage( + const RECT dirtyRects[], unsigned count, bool hasDamage) +{ + if (!hasDamage) + return; + + AcquireSRWLockExclusive(&m_damageLock); + CFrameProcessorUtil::AccumulateDamage( + m_pendingDamage, &m_pendingDamageCount, &m_hasPendingDamage, + dirtyRects, count); + ReleaseSRWLockExclusive(&m_damageLock); +} + +void CFrameProcessor::CommitDamage( + const RECT dirtyRects[], unsigned count) +{ + AcquireSRWLockExclusive(&m_damageLock); + m_previousDamageCount = count; + if (count) + memcpy(m_previousDamage, dirtyRects, + count * sizeof(*m_previousDamage)); + ReleaseSRWLockExclusive(&m_damageLock); +} + +void CFrameProcessor::GetPreviousDamage( + RECT dirtyRects[], unsigned * count) const +{ + AcquireSRWLockShared(&m_damageLock); + *count = m_previousDamageCount; + if (*count) + memcpy(dirtyRects, m_previousDamage, + *count * sizeof(*dirtyRects)); + ReleaseSRWLockShared(&m_damageLock); +} + +std::unique_ptr CreateFrameProcessor( + bool software, CIndirectDeviceContext * device, + std::shared_ptr dx12, + CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], + SRWLOCK * pipelineLock, HANDLE terminateEvent) +{ + std::unique_ptr processor; + if (software) + processor.reset(new (std::nothrow) CSoftwareFrameProcessor( + device, std::move(dx12), postProcessors, + pipelineLock, terminateEvent)); + else + processor.reset(new (std::nothrow) CHardwareFrameProcessor( + device, std::move(dx12), postProcessors, + pipelineLock, terminateEvent)); + + if (!processor || !processor->IsValid()) + return nullptr; + return processor; +} diff --git a/idd/LGIdd/CFrameProcessor.h b/idd/LGIdd/CFrameProcessor.h new file mode 100644 index 00000000..dd421234 --- /dev/null +++ b/idd/LGIdd/CFrameProcessor.h @@ -0,0 +1,99 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "CD3D12Device.h" +#include "CFrameBufferPool.h" +#include "CIndirectDeviceContext.h" +#include "CInteropResource.h" +#include "CPostProcessor.h" + +#include +#include + +using namespace Microsoft::WRL; + +struct FrameSubmission +{ + CInteropResource * source; + D12FrameFormat sourceFormat; + uint64_t captureTime; + uint64_t postProcessStart; + unsigned timingEffectIndex; + uint64_t timingToken; + bool noImageUpdate; +}; + +class CFrameProcessor +{ +protected: + CIndirectDeviceContext * m_device; + std::shared_ptr m_dx12; + CPostProcessor * m_postProcessors; + SRWLOCK * m_pipelineLock; + HANDLE m_terminateEvent; + CFrameBufferPool m_frameBuffers; + Wrappers::Event m_readyEvent; + + mutable SRWLOCK m_damageLock = SRWLOCK_INIT; + RECT m_previousDamage[LG_MAX_DIRTY_RECTS] = {}; + unsigned m_previousDamageCount = 0; + RECT m_pendingDamage[LG_MAX_DIRTY_RECTS] = {}; + unsigned m_pendingDamageCount = 0; + bool m_hasPendingDamage = true; + + bool HasPendingDamage() const; + bool TakePendingDamage(RECT dirtyRects[], unsigned * count); + void RestorePendingDamage( + const RECT dirtyRects[], unsigned count, bool hasDamage); + void CommitDamage(const RECT dirtyRects[], unsigned count); + void GetPreviousDamage(RECT dirtyRects[], unsigned * count) const; + virtual void AccumulateDamageLocked( + const RECT dirtyRects[], unsigned count); + virtual void SetFullDamageLocked(); + +public: + CFrameProcessor(CIndirectDeviceContext * device, + std::shared_ptr dx12, + CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], + SRWLOCK * pipelineLock, HANDLE terminateEvent); + virtual ~CFrameProcessor() = default; + + virtual bool IsValid() const; + virtual bool Submit(const FrameSubmission& submission) = 0; + virtual bool HasReadyFrame() const = 0; + virtual bool Publish(const CFrameScheduler::Schedule& schedule, + bool periodic, uint64_t publishStart) = 0; + virtual bool UsesCadence() const = 0; + virtual void Reset(); + virtual void Invalidate(); + virtual void ResetPipeline(); + void AccumulateDamage(const RECT dirtyRects[], unsigned count); + void SetFullDamage(); + + HANDLE GetReadyEvent() const { return m_readyEvent.Get(); } +}; + +std::unique_ptr CreateFrameProcessor( + bool software, CIndirectDeviceContext * device, + std::shared_ptr dx12, + CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], + SRWLOCK * pipelineLock, HANDLE terminateEvent); diff --git a/idd/LGIdd/CFrameProcessorUtil.cpp b/idd/LGIdd/CFrameProcessorUtil.cpp new file mode 100644 index 00000000..b3d0b0f6 --- /dev/null +++ b/idd/LGIdd/CFrameProcessorUtil.cpp @@ -0,0 +1,270 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "CFrameProcessorUtil.h" + +#include + +bool CFrameProcessorUtil::FrameMetadataChanged( + const D12FrameFormat& previous, const D12FrameFormat& current) +{ + return + previous.hdrMetadata != current.hdrMetadata || + previous.sdrWhiteLevel != current.sdrWhiteLevel || + (current.hdrMetadata && + (memcmp(previous.displayPrimary, current.displayPrimary, + sizeof(current.displayPrimary)) != 0 || + memcmp(previous.whitePoint, current.whitePoint, + sizeof(current.whitePoint)) != 0 || + previous.maxDisplayLuminance != current.maxDisplayLuminance || + previous.minDisplayLuminance != current.minDisplayLuminance || + previous.maxContentLightLevel != current.maxContentLightLevel || + previous.maxFrameAverageLightLevel != current.maxFrameAverageLightLevel)); +} + +FrameType CFrameProcessorUtil::GetFrameType(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_B8G8R8A8_UNORM : return FRAME_TYPE_BGRA; + case DXGI_FORMAT_R8G8B8A8_UNORM : return FRAME_TYPE_RGBA; + case DXGI_FORMAT_R10G10B10A2_UNORM : return FRAME_TYPE_RGBA10; + case DXGI_FORMAT_R16G16B16A16_FLOAT: return FRAME_TYPE_RGBA16F; + default : return FRAME_TYPE_INVALID; + } +} + +bool CFrameProcessorUtil::ResourceDescMatches( + const D3D12_RESOURCE_DESC& left, const D3D12_RESOURCE_DESC& right, + bool compareAlignment) +{ + // GetDesc may report a resolved alignment when resource creation requested + // automatic alignment, so callers comparing creation descriptors can omit + // this allocation metadata. + return + left.Dimension == right.Dimension && + (!compareAlignment || left.Alignment == right.Alignment) && + left.Width == right.Width && + left.Height == right.Height && + left.DepthOrArraySize == right.DepthOrArraySize && + left.MipLevels == right.MipLevels && + left.Format == right.Format && + left.SampleDesc.Count == right.SampleDesc.Count && + left.SampleDesc.Quality == right.SampleDesc.Quality && + left.Layout == right.Layout && + left.Flags == right.Flags; +} + +static bool IsFullDamage(const RECT * dirtyRects, unsigned nbDirtyRects, + unsigned width, unsigned height) +{ + for (const RECT * rect = dirtyRects; + rect < dirtyRects + nbDirtyRects; ++rect) + if (rect->left == 0 && + rect->top == 0 && + rect->right == (LONG)width && + rect->bottom == (LONG)height) + return true; + + return false; +} + +static bool DirtyRectContains(const RECT& outer, const RECT& inner) +{ + return outer.left <= inner.left && + outer.top <= inner.top && + outer.right >= inner.right && + outer.bottom >= inner.bottom; +} + +static bool DirtyRectsTouchOrIntersect(const RECT& a, const RECT& b) +{ + return a.left <= b.right && a.right >= b.left && + a.top <= b.bottom && a.bottom >= b.top; +} + +static RECT MergeDirtyRects(const RECT& a, const RECT& b) +{ + RECT result; + result.left = min(a.left , b.left ); + result.top = min(a.top , b.top ); + result.right = max(a.right , b.right ); + result.bottom = max(a.bottom, b.bottom); + return result; +} + +static uint64_t DirtyRectArea(const RECT& rect) +{ + const uint64_t width = (uint64_t)((int64_t)rect.right - rect.left); + const uint64_t height = (uint64_t)((int64_t)rect.bottom - rect.top ); + return width * height; +} + +static bool AddCopyDirtyRect(RECT dirtyRects[], unsigned capacity, + unsigned * nbDirtyRects, const RECT& dirtyRect) +{ + RECT candidate = dirtyRect; + for (unsigned i = 0; i < *nbDirtyRects;) + { + if (DirtyRectContains(dirtyRects[i], candidate)) + return true; + + const RECT merged = MergeDirtyRects(dirtyRects[i], candidate); + if (DirtyRectContains(candidate, dirtyRects[i]) || + (DirtyRectsTouchOrIntersect(dirtyRects[i], candidate) && + DirtyRectArea(merged) <= + DirtyRectArea(dirtyRects[i]) + DirtyRectArea(candidate))) + { + candidate = merged; + --(*nbDirtyRects); + dirtyRects[i] = dirtyRects[*nbDirtyRects]; + i = 0; + continue; + } + + ++i; + } + + if (*nbDirtyRects >= capacity) + return false; + + dirtyRects[(*nbDirtyRects)++] = candidate; + return true; +} + +static bool CopyAreaCoversFrame(const RECT * dirtyRects, + unsigned nbDirtyRects, unsigned width, unsigned height) +{ + const uint64_t frameArea = (uint64_t)width * height; + uint64_t copyArea = 0; + + for (const RECT * rect = dirtyRects; + rect < dirtyRects + nbDirtyRects; ++rect) + { + const uint64_t area = DirtyRectArea(*rect); + if (area >= frameArea - copyArea) + return true; + copyArea += area; + } + + return false; +} + +static bool ClipDirtyRect(RECT& rect, unsigned width, unsigned height) +{ + const LONG maxRight = (LONG)width; + const LONG maxBottom = (LONG)height; + + if (rect.left < 0 ) rect.left = 0; + if (rect.top < 0 ) rect.top = 0; + if (rect.right > maxRight ) rect.right = maxRight; + if (rect.bottom > maxBottom) rect.bottom = maxBottom; + + return rect.left < rect.right && rect.top < rect.bottom; +} + +void CFrameProcessorUtil::ClipDirtyRects( + RECT dirtyRects[], unsigned * nbDirtyRects, + unsigned width, unsigned height) +{ + unsigned out = 0; + for (unsigned i = 0; i < *nbDirtyRects; ++i) + { + RECT rect = dirtyRects[i]; + if (ClipDirtyRect(rect, width, height)) + dirtyRects[out++] = rect; + } + *nbDirtyRects = out; +} + +bool CFrameProcessorUtil::BuildCopyDamage( + const CPostProcessor& postProcessor, bool destinationNeedsFullCopy, + const RECT previousDirtyRects[], unsigned nbPreviousDirtyRects, + const RECT currentDirtyRects[], unsigned nbCurrentDirtyRects, + unsigned width, unsigned height, + RECT copyDirtyRects[], unsigned * nbCopyDirtyRects) +{ + *nbCopyDirtyRects = 0; + bool fullCopy = destinationNeedsFullCopy || + nbCurrentDirtyRects == 0 || nbPreviousDirtyRects == 0; + + if (fullCopy) + return true; + + for (const RECT * rect = previousDirtyRects; + rect < previousDirtyRects + nbPreviousDirtyRects && !fullCopy; + ++rect) + { + RECT clipped = *rect; + if (ClipDirtyRect(clipped, width, height) && + !AddCopyDirtyRect(copyDirtyRects, LG_MAX_DIRTY_RECTS * 2, + nbCopyDirtyRects, clipped)) + fullCopy = true; + } + + for (const RECT * rect = currentDirtyRects; + rect < currentDirtyRects + nbCurrentDirtyRects && !fullCopy; + ++rect) + if (!AddCopyDirtyRect(copyDirtyRects, LG_MAX_DIRTY_RECTS * 2, + nbCopyDirtyRects, *rect)) + fullCopy = true; + + if (!fullCopy) + fullCopy = IsFullDamage(copyDirtyRects, *nbCopyDirtyRects, + width, height) || + CopyAreaCoversFrame(copyDirtyRects, *nbCopyDirtyRects, + width, height); + + if (!fullCopy) + fullCopy = postProcessor.ShouldCopyFully( + copyDirtyRects, *nbCopyDirtyRects); + + return fullCopy; +} + +void CFrameProcessorUtil::AccumulateDamage( + RECT pendingDirtyRects[], unsigned * nbPendingDirtyRects, + bool * hasPendingDamage, const RECT dirtyRects[], + unsigned nbDirtyRects) +{ + if (nbDirtyRects > LG_MAX_DIRTY_RECTS) + nbDirtyRects = 0; + + if (!*hasPendingDamage) + { + *hasPendingDamage = true; + *nbPendingDirtyRects = nbDirtyRects; + if (nbDirtyRects) + memcpy(pendingDirtyRects, dirtyRects, + nbDirtyRects * sizeof(*pendingDirtyRects)); + return; + } + + if (*nbPendingDirtyRects == 0 || nbDirtyRects == 0 || + *nbPendingDirtyRects + nbDirtyRects > LG_MAX_DIRTY_RECTS) + { + *nbPendingDirtyRects = 0; + return; + } + + memcpy(pendingDirtyRects + *nbPendingDirtyRects, dirtyRects, + nbDirtyRects * sizeof(*pendingDirtyRects)); + *nbPendingDirtyRects += nbDirtyRects; +} diff --git a/idd/LGIdd/CFrameProcessorUtil.h b/idd/LGIdd/CFrameProcessorUtil.h new file mode 100644 index 00000000..687a3c3e --- /dev/null +++ b/idd/LGIdd/CFrameProcessorUtil.h @@ -0,0 +1,63 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "CInteropResource.h" +#include "CPostProcessor.h" + +class CFrameProcessorSharedLock +{ +private: + SRWLOCK * m_lock; + +public: + explicit CFrameProcessorSharedLock(SRWLOCK * lock) : m_lock(lock) + { + AcquireSRWLockShared(m_lock); + } + + ~CFrameProcessorSharedLock() + { + ReleaseSRWLockShared(m_lock); + } +}; + +class CFrameProcessorUtil +{ +public: + static bool FrameMetadataChanged(const D12FrameFormat& previous, + const D12FrameFormat& current); + static FrameType GetFrameType(DXGI_FORMAT format); + static bool ResourceDescMatches(const D3D12_RESOURCE_DESC& left, + const D3D12_RESOURCE_DESC& right, bool compareAlignment = true); + static void ClipDirtyRects(RECT dirtyRects[], unsigned * nbDirtyRects, + unsigned width, unsigned height); + static bool BuildCopyDamage(const CPostProcessor& postProcessor, + bool destinationNeedsFullCopy, + const RECT previousDirtyRects[], unsigned nbPreviousDirtyRects, + const RECT currentDirtyRects[], unsigned nbCurrentDirtyRects, + unsigned width, unsigned height, + RECT copyDirtyRects[], unsigned * nbCopyDirtyRects); + static void AccumulateDamage( + RECT pendingDirtyRects[], unsigned * nbPendingDirtyRects, + bool * hasPendingDamage, const RECT dirtyRects[], + unsigned nbDirtyRects); +}; diff --git a/idd/LGIdd/CHardwareFrameProcessor.cpp b/idd/LGIdd/CHardwareFrameProcessor.cpp new file mode 100644 index 00000000..258a90f9 --- /dev/null +++ b/idd/LGIdd/CHardwareFrameProcessor.cpp @@ -0,0 +1,821 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "CHardwareFrameProcessor.h" +#include "CFrameProcessorUtil.h" +#include "CDebug.h" + +#include +#include + +using namespace Microsoft::WRL; + +static_assert(LGMP_Q_FRAME_LEN == 2, + "IDD candidate pipeline assumes two slots"); + +class CPublishPending +{ +private: + SRWLOCK * m_lock; + bool * m_pending; + HANDLE m_event; + bool m_active = true; + +public: + CPublishPending(SRWLOCK * lock, bool * pending, HANDLE event) : + m_lock(lock), + m_pending(pending), + m_event(event) + { + AcquireSRWLockExclusive(m_lock); + *m_pending = true; + ResetEvent(m_event); + ReleaseSRWLockExclusive(m_lock); + } + + ~CPublishPending() + { + Clear(); + } + + void Clear() + { + if (!m_active) + return; + + AcquireSRWLockExclusive(m_lock); + *m_pending = false; + SetEvent(m_event); + ReleaseSRWLockExclusive(m_lock); + m_active = false; + } +}; + +CHardwareFrameProcessor::CHardwareFrameProcessor( + CIndirectDeviceContext * device, std::shared_ptr dx12, + CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], + SRWLOCK * pipelineLock, HANDLE terminateEvent) : + CFrameProcessor(device, std::move(dx12), postProcessors, + pipelineLock, terminateEvent) +{ + m_candidateAvailableEvent.Attach( + CreateEvent(nullptr, FALSE, FALSE, nullptr)); + m_copySubmitEvent.Attach(CreateEvent(nullptr, TRUE, TRUE, nullptr)); +} + +bool CHardwareFrameProcessor::IsValid() const +{ + return CFrameProcessor::IsValid() && + m_candidateAvailableEvent.Get() && m_copySubmitEvent.Get(); +} + +void CHardwareFrameProcessor::SignalCandidateState() +{ + SetEvent(m_readyEvent.Get()); + SetEvent(m_candidateAvailableEvent.Get()); +} + +void CHardwareFrameProcessor::SetFullDamageLocked() +{ + for (CandidateDamageTail& tail : m_candidateDamageTail) + if (tail.active) + { + tail.hasDamage = true; + tail.nbDirtyRects = 0; + } +} + +void CHardwareFrameProcessor::AccumulateDamageLocked( + const RECT dirtyRects[], unsigned count) +{ + for (CandidateDamageTail& tail : m_candidateDamageTail) + if (tail.active) + CFrameProcessorUtil::AccumulateDamage( + tail.dirtyRects, &tail.nbDirtyRects, &tail.hasDamage, + dirtyRects, count); +} + +void CHardwareFrameProcessor::ResetCandidates() +{ + AcquireSRWLockExclusive(&m_candidateLock); + for (FrameCandidate& candidate : m_candidates) + candidate = {}; + ReleaseSRWLockExclusive(&m_candidateLock); + + AcquireSRWLockExclusive(&m_damageLock); + for (CandidateDamageTail& tail : m_candidateDamageTail) + tail = {}; + ReleaseSRWLockExclusive(&m_damageLock); + SignalCandidateState(); +} + +void CHardwareFrameProcessor::Reset() +{ + ResetCandidates(); + CFrameProcessor::Reset(); +} + +void CHardwareFrameProcessor::ResetPipeline() +{ + ResetCandidates(); + CFrameProcessor::Invalidate(); +} + +bool CHardwareFrameProcessor::HasReadyFrame() const +{ + bool ready = false; + AcquireSRWLockShared(&m_candidateLock); + for (const FrameCandidate& candidate : m_candidates) + if (candidate.state == CANDIDATE_READY) + { + ready = true; + break; + } + ReleaseSRWLockShared(&m_candidateLock); + return ready; +} + +int CHardwareFrameProcessor::AcquireCandidate( + bool exclusiveSample, bool allowSupersede) +{ + int selected = -1; + uint64_t oldest = UINT64_MAX; + bool superseded = false; + bool idle = true; + bool publishing = false; + + AcquireSRWLockExclusive(&m_candidateLock); + for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) + { + if (m_candidates[i].state != CANDIDATE_FREE) + { + idle = false; + if (m_candidates[i].state == CANDIDATE_PUBLISHING) + publishing = true; + } + else if (selected < 0) + selected = static_cast(i); + } + + if (exclusiveSample && !idle) + selected = -1; + + unsigned readyCount = 0; + for (const FrameCandidate& candidate : m_candidates) + if (candidate.state == CANDIDATE_READY) + ++readyCount; + + if (allowSupersede && !exclusiveSample && selected < 0 && + readyCount > (publishing ? 0U : 1U)) + for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) + if (m_candidates[i].state == CANDIDATE_READY && + m_candidates[i].sequence < oldest) + { + selected = static_cast(i); + oldest = m_candidates[i].sequence; + } + + if (selected >= 0) + { + FrameCandidate& candidate = + m_candidates[static_cast(selected)]; + superseded = candidate.state == CANDIDATE_READY; + candidate.state = CANDIDATE_PREPARING; + candidate.sequence = ++m_candidateSequence; + } + ReleaseSRWLockExclusive(&m_candidateLock); + + if (superseded) + m_device->FrameSuperseded(); + return selected; +} + +void CHardwareFrameProcessor::ReleaseCandidate(unsigned candidateIndex) +{ + if (candidateIndex >= ARRAYSIZE(m_candidates)) + return; + + AcquireSRWLockExclusive(&m_candidateLock); + m_candidates[candidateIndex].state = CANDIDATE_FREE; + ReleaseSRWLockExclusive(&m_candidateLock); + SignalCandidateState(); +} + +bool CHardwareFrameProcessor::EnsureCandidateResource( + unsigned candidateIndex, size_t frameSize) +{ + FrameCandidate& candidate = m_candidates[candidateIndex]; + + D3D12_RESOURCE_DESC desc = {}; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Width = frameSize; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = D3D12_RESOURCE_FLAG_NONE; + + if (candidate.resource && + CFrameProcessorUtil::ResourceDescMatches( + candidate.resource->GetDesc(), desc, false)) + return true; + + candidate.resource.Reset(); + + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + const HRESULT hr = m_dx12->GetDevice()->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, + nullptr, IID_PPV_ARGS(&candidate.resource)); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to create retained frame candidate"); + return false; + } + + static const WCHAR * names[] = + { + L"Frame Candidate 0", + L"Frame Candidate 1", + }; + candidate.resource->SetName(names[candidateIndex]); + return true; +} + +bool CHardwareFrameProcessor::ExecuteCandidateCopy( + CD3D12CommandSlot * copySlot) +{ + HANDLE waitHandles[] = + { + m_terminateEvent, + m_copySubmitEvent.Get(), + }; + + for (;;) + { + AcquireSRWLockExclusive(&m_copySubmitLock); + if (!m_publishPending) + { + const bool result = copySlot->Execute(); + ReleaseSRWLockExclusive(&m_copySubmitLock); + return result; + } + ReleaseSRWLockExclusive(&m_copySubmitLock); + + const DWORD result = WaitForMultipleObjects( + ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); + if (result == WAIT_OBJECT_0 + 1) + continue; + + copySlot->Cancel(); + if (result != WAIT_OBJECT_0) + DEBUG_ERROR_HR(HRESULT_FROM_WIN32(GetLastError()), + "Failed while waiting to submit a frame candidate"); + return false; + } +} + +void CHardwareFrameProcessor::CandidateCompletionFunction( + CD3D12CommandSlot * slot, bool result, void * param1, void * param2) +{ + auto processor = static_cast(param1); + auto candidate = static_cast(param2); + + uint64_t gpuStart = 0; + uint64_t gpuEnd = 0; + const bool timingValid = result && slot->GetGPUTimes(gpuStart, gpuEnd); + + bool forceFrame = false; + AcquireSRWLockExclusive(&processor->m_candidateLock); + if (candidate->state == CANDIDATE_PREPARING) + { + candidate->prepareReady = CFrameScheduler::Nanotime(); + candidate->prepareGPUStart = gpuStart; + candidate->prepareGPUEnd = gpuEnd; + candidate->prepareTimingValid = timingValid; + candidate->state = + result ? CANDIDATE_READY : CANDIDATE_FREE; + forceFrame = result && candidate->timingToken != 0; + } + ReleaseSRWLockExclusive(&processor->m_candidateLock); + + if (!result) + { + processor->SetFullDamage(); + processor->m_device->ForceFrame(); + } + else if (forceFrame) + processor->m_device->ForceFrame(); + processor->SignalCandidateState(); +} + +void CHardwareFrameProcessor::CompletionFunction( + CD3D12CommandSlot * slot, bool result, void * param1, void * param2) +{ + auto processor = static_cast(param1); + auto fbRes = static_cast(param2); + const unsigned candidateIndex = fbRes->GetCandidateIndex(); + + if (!result) + { + processor->m_device->FailFrameBuffer(fbRes->GetFrameIndex()); + processor->SetFullDamage(); + processor->m_device->ForceFrame(); + processor->ReleaseCandidate(candidateIndex); + return; + } + + uint64_t prepareCopyStart; + uint64_t prepareReady; + uint64_t prepareGPUStart; + uint64_t prepareGPUEnd; + uint64_t timingStart; + bool prepareTimingValid; + AcquireSRWLockShared(&processor->m_candidateLock); + const FrameCandidate& candidate = + processor->m_candidates[candidateIndex]; + prepareCopyStart = candidate.prepareCopyStart; + prepareReady = candidate.prepareReady; + prepareGPUStart = candidate.prepareGPUStart; + prepareGPUEnd = candidate.prepareGPUEnd; + timingStart = candidate.timingStart; + prepareTimingValid = candidate.prepareTimingValid; + ReleaseSRWLockShared(&processor->m_candidateLock); + + const uint64_t publishStart = fbRes->GetCopyStart(); + uint64_t gpuCopyStart = 0; + uint64_t gpuCopyEnd = 0; + uint64_t indirectCopyTime = 0; + if (processor->m_dx12->IsIndirectCopy()) + { + const uint64_t indirectCopyStart = CFrameScheduler::Nanotime(); + processor->m_device->WriteFrameBuffer( + fbRes->GetFrameIndex(), fbRes->GetMap(), 0, + fbRes->GetFrameSize(), false); + indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart; + } + + const bool gpuTimingValid = + slot->GetGPUTimes(gpuCopyStart, gpuCopyEnd); + const uint64_t copyReady = CFrameScheduler::Nanotime(); + + const uint64_t postProcessStart = fbRes->GetPostProcessStart(); + uint64_t postProcessTime = prepareCopyStart - postProcessStart; + uint64_t prepareCopyTime = prepareReady - prepareCopyStart; + if (prepareTimingValid && prepareGPUStart >= postProcessStart && + prepareGPUEnd >= prepareGPUStart && prepareGPUEnd <= prepareReady) + { + postProcessTime = prepareGPUStart - postProcessStart; + prepareCopyTime = prepareGPUEnd - prepareGPUStart; + } + + uint64_t publishCopyTime = copyReady - publishStart; + if (gpuTimingValid && gpuCopyStart >= publishStart && + gpuCopyEnd >= gpuCopyStart && gpuCopyEnd <= copyReady) + publishCopyTime = gpuCopyEnd - gpuCopyStart + indirectCopyTime; + + const uint64_t copyTime = prepareCopyTime + publishCopyTime; + + processor->m_device->FinalizeFrameBuffer(fbRes->GetFrameIndex()); + const uint64_t publishedAt = CFrameScheduler::Nanotime(); + const uint64_t prepareElapsed = prepareReady >= postProcessStart ? + prepareReady - postProcessStart : 0; + const uint64_t prepareMeasured = postProcessTime + prepareCopyTime; + const uint64_t prepareReadyTime = prepareElapsed > prepareMeasured ? + prepareElapsed - prepareMeasured : 0; + const uint64_t publishElapsed = publishedAt >= publishStart ? + publishedAt - publishStart : 0; + const uint64_t publishReadyTime = publishElapsed > publishCopyTime ? + publishElapsed - publishCopyTime : 0; + const uint64_t readyTime = prepareReadyTime + publishReadyTime; + const uint64_t holdTime = publishStart >= prepareReady ? + publishStart - prepareReady : 0; + + processor->m_device->SetFrameTiming(fbRes->GetFrameIndex(), + fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, holdTime, + fbRes->GetSchedule(), publishedAt); + processor->m_device->TryRecordFrameTiming(publishedAt - publishStart); + + const uint64_t timingToken = fbRes->GetTimingToken(); + if (timingToken && timingStart && prepareReady >= timingStart && + copyReady >= publishStart) + { + const uint64_t totalTime = + (prepareReady - timingStart) + (copyReady - publishStart); + processor->m_postProcessors[candidateIndex].RecordTiming( + fbRes->GetTimingEffectIndex(), timingToken, + fbRes->IsFullCopy(), totalTime); + } + + processor->m_device->CompleteFrameBuffer(fbRes->GetFrameIndex(), true); + processor->ReleaseCandidate(candidateIndex); +} + +bool CHardwareFrameProcessor::Publish( + const CFrameScheduler::Schedule& schedule, bool periodic, + uint64_t publishStart) +{ + CPublishPending publishPending( + &m_copySubmitLock, &m_publishPending, m_copySubmitEvent.Get()); + CFrameProcessorSharedLock pipelineLock(m_pipelineLock); + + int selectedCandidate = -1; + uint64_t newestSequence = 0; + + AcquireSRWLockExclusive(&m_candidateLock); + for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) + if (m_candidates[i].state == CANDIDATE_READY && + (selectedCandidate < 0 || + m_candidates[i].sequence > newestSequence)) + { + selectedCandidate = static_cast(i); + newestSequence = m_candidates[i].sequence; + } + + if (selectedCandidate >= 0) + m_candidates[static_cast(selectedCandidate)].state = + CANDIDATE_PUBLISHING; + ReleaseSRWLockExclusive(&m_candidateLock); + + if (selectedCandidate < 0) + return false; + const unsigned candidateIndex = + static_cast(selectedCandidate); + + const auto restoreCandidate = [this, candidateIndex]() + { + AcquireSRWLockExclusive(&m_candidateLock); + if (m_candidates[candidateIndex].state == CANDIDATE_PUBLISHING) + m_candidates[candidateIndex].state = CANDIDATE_READY; + ReleaseSRWLockExclusive(&m_candidateLock); + SignalCandidateState(); + }; + + AcquireSRWLockShared(&m_candidateLock); + const bool candidateValid = + m_candidates[candidateIndex].state == CANDIDATE_PUBLISHING && + m_candidates[candidateIndex].resource.Get(); + ReleaseSRWLockShared(&m_candidateLock); + if (!candidateValid) + { + restoreCandidate(); + return false; + } + + FrameCandidate& candidate = m_candidates[candidateIndex]; + CPostProcessor& postProcessor = m_postProcessors[candidateIndex]; + const uint64_t candidateSequence = candidate.sequence; + + auto buffer = m_device->PrepareFrameBuffer( + candidate.pitch, candidate.srcFormat, candidate.dstFormat, + candidate.dirtyRects, candidate.nbDirtyRects, schedule); + if (!buffer.mem) + { + restoreCandidate(); + return false; + } + + CFrameBufferResource * fbRes = + m_frameBuffers.Get(buffer, candidate.frameSize); + if (!fbRes) + { + m_device->AbortFrameBuffer(buffer.frameIndex); + restoreCandidate(); + DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool"); + SetFullDamage(); + return false; + } + + CD3D12CommandSlot * copySlot = m_dx12->GetCopySlot(candidateIndex); + if (!copySlot) + { + m_device->AbortFrameBuffer(buffer.frameIndex); + restoreCandidate(); + DEBUG_ERROR("Failed to get a copy CommandSlot for publication"); + SetFullDamage(); + return false; + } + + RECT previousDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned nbPreviousDirtyRects = 0; + GetPreviousDamage(previousDirtyRects, &nbPreviousDirtyRects); + + RECT copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {}; + unsigned nbCopyDirtyRects = 0; + const bool fullCopy = CFrameProcessorUtil::BuildCopyDamage( + postProcessor, buffer.fullCopy, + previousDirtyRects, nbPreviousDirtyRects, + candidate.dirtyRects, candidate.nbDirtyRects, + candidate.dstFormat.width, candidate.dstFormat.height, + copyDirtyRects, &nbCopyDirtyRects); + + fbRes->SetTiming( + candidate.captureTime, candidate.postProcessStart, publishStart); + fbRes->SetCandidateIndex(candidateIndex); + fbRes->SetPostProcessSample( + candidate.timingEffectIndex, candidate.timingToken, fullCopy); + copySlot->SetCompletionCallback(&CompletionFunction, this, fbRes); + + copySlot->BeginTiming(); + postProcessor.CopyFromCandidate( + copySlot->GetGfxList(), fbRes->Get().Get(), candidate.resource.Get(), + copyDirtyRects, nbCopyDirtyRects, fullCopy); + copySlot->EndTiming(); + + bool deliveredToOwner; + if (!m_device->PublishFrameBuffer( + buffer.frameIndex, schedule, deliveredToOwner)) + { + copySlot->Cancel(); + m_device->AbortFrameBuffer(buffer.frameIndex); + restoreCandidate(); + return false; + } + CFrameScheduler::Schedule frameSchedule = schedule; + if (!deliveredToOwner || + !m_device->TryFrameSubmitted(buffer.frameIndex, schedule)) + frameSchedule.phaseEligible = false; + fbRes->SetSchedule(frameSchedule); + + AcquireSRWLockExclusive(&m_damageLock); + if (candidate.nbDirtyRects) + memcpy(m_previousDamage, candidate.dirtyRects, + candidate.nbDirtyRects * sizeof(*m_previousDamage)); + m_previousDamageCount = candidate.nbDirtyRects; + CandidateDamageTail& tail = m_candidateDamageTail[candidateIndex]; + if (tail.active && tail.ownerSequence == candidateSequence) + { + m_hasPendingDamage = tail.hasDamage; + m_pendingDamageCount = tail.nbDirtyRects; + if (tail.hasDamage && tail.nbDirtyRects) + memcpy(m_pendingDamage, tail.dirtyRects, + tail.nbDirtyRects * sizeof(*m_pendingDamage)); + tail.ownerSequence = 0; + tail.active = false; + } + ReleaseSRWLockExclusive(&m_damageLock); + + const bool submitted = copySlot->Execute(); + publishPending.Clear(); + if (!submitted) + { + SetFullDamage(); + AcquireSRWLockShared(&m_candidateLock); + const bool callbackPending = + candidate.state == CANDIDATE_PUBLISHING; + ReleaseSRWLockShared(&m_candidateLock); + if (callbackPending && !copySlot->HasSubmittedWork()) + { + m_device->FailFrameBuffer(buffer.frameIndex); + ReleaseCandidate(candidateIndex); + } + m_device->ForceFrame(); + SignalCandidateState(); + return false; + } + + m_device->CommitFrameBuffer( + buffer.frameIndex, schedule, periodic, deliveredToOwner); + + unsigned superseded = 0; + AcquireSRWLockExclusive(&m_candidateLock); + for (FrameCandidate& ready : m_candidates) + if (ready.state == CANDIDATE_READY && + ready.sequence < candidateSequence) + { + ready.state = CANDIDATE_FREE; + ++superseded; + } + ReleaseSRWLockExclusive(&m_candidateLock); + for (unsigned i = 0; i < superseded; ++i) + m_device->FrameSuperseded(); + SignalCandidateState(); + return true; +} + +bool CHardwareFrameProcessor::Submit(const FrameSubmission& submission) +{ + int selectedCandidate = AcquireCandidate( + submission.timingToken != 0, !submission.noImageUpdate); + while (selectedCandidate < 0 && submission.noImageUpdate) + { + HANDLE waitHandles[] = + { + m_terminateEvent, + m_candidateAvailableEvent.Get(), + }; + const DWORD waitResult = WaitForMultipleObjects( + ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); + if (waitResult == WAIT_OBJECT_0) + return true; + if (waitResult != WAIT_OBJECT_0 + 1) + { + DEBUG_ERROR_HR(HRESULT_FROM_WIN32(GetLastError()), + "Failed while waiting for a frame candidate"); + return false; + } + + selectedCandidate = AcquireCandidate( + submission.timingToken != 0, false); + } + if (selectedCandidate < 0) + { + m_device->FrameSuperseded(); + return true; + } + const unsigned candidateIndex = + static_cast(selectedCandidate); + FrameCandidate& candidate = m_candidates[candidateIndex]; + + CFrameProcessorSharedLock pipelineLock(m_pipelineLock); + CPostProcessor& postProcessor = m_postProcessors[candidateIndex]; + const D12FrameFormat& dstFormat = postProcessor.GetOutputFormat(); + + RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned nbDirtyRects = 0; + AcquireSRWLockExclusive(&m_damageLock); + if (m_hasPendingDamage) + { + nbDirtyRects = m_pendingDamageCount; + if (nbDirtyRects) + memcpy(currentDirtyRects, m_pendingDamage, + nbDirtyRects * sizeof(*currentDirtyRects)); + } + CandidateDamageTail& tail = m_candidateDamageTail[candidateIndex]; + tail.ownerSequence = candidate.sequence; + tail.nbDirtyRects = 0; + tail.hasDamage = false; + tail.active = true; + ReleaseSRWLockExclusive(&m_damageLock); + + CD3D12CommandSlot * copySlot = m_dx12->GetCopySlot(candidateIndex); + if (!copySlot) + { + ReleaseCandidate(candidateIndex); + DEBUG_ERROR("Failed to get a copy CommandSlot"); + SetFullDamage(); + return false; + } + const uint64_t timingStart = submission.timingToken ? + CFrameScheduler::Nanotime() : 0; + + ComPtr copySrcResource = + submission.source->GetRes(); + CD3D12CommandSlot * computeSlot = nullptr; + if (postProcessor.HasActiveEffects()) + { + computeSlot = m_dx12->GetComputeSlot(candidateIndex); + if (!computeSlot) + { + copySlot->Cancel(); + ReleaseCandidate(candidateIndex); + DEBUG_ERROR("Failed to get a compute CommandSlot"); + SetFullDamage(); + return false; + } + } + + if (!submission.source->Signal()) + { + if (computeSlot) + computeSlot->Cancel(); + copySlot->Cancel(); + ReleaseCandidate(candidateIndex); + SetFullDamage(); + return false; + } + + if (computeSlot) + { + if (!submission.source->Sync(*computeSlot)) + { + computeSlot->Cancel(); + copySlot->Cancel(); + ReleaseCandidate(candidateIndex); + SetFullDamage(); + return false; + } + + copySrcResource = postProcessor.Run( + computeSlot->GetGfxList(), copySrcResource, + currentDirtyRects, &nbDirtyRects); + if (!copySrcResource) + { + computeSlot->Cancel(); + copySlot->Cancel(); + ReleaseCandidate(candidateIndex); + DEBUG_ERROR("Post processor returned no output resource"); + SetFullDamage(); + return false; + } + + if (!computeSlot->Execute()) + { + copySlot->Cancel(); + m_dx12->WaitForIdle(); + ReleaseCandidate(candidateIndex); + SetFullDamage(); + return false; + } + + if (!copySlot->WaitFor(*computeSlot)) + { + copySlot->Cancel(); + m_dx12->WaitForIdle(); + ReleaseCandidate(candidateIndex); + DEBUG_ERROR("Failed to queue compute synchronization"); + SetFullDamage(); + return false; + } + } + else if (!submission.source->Sync(*copySlot)) + { + copySlot->Cancel(); + ReleaseCandidate(candidateIndex); + DEBUG_ERROR("Failed to queue source synchronization"); + SetFullDamage(); + return false; + } + + CFrameProcessorUtil::ClipDirtyRects( + currentDirtyRects, &nbDirtyRects, + dstFormat.width, dstFormat.height); + + const size_t frameSize = postProcessor.GetOutputSize(); + if (!EnsureCandidateResource(candidateIndex, frameSize)) + { + copySlot->Cancel(); + if (computeSlot) + m_dx12->WaitForIdle(); + ReleaseCandidate(candidateIndex); + SetFullDamage(); + return false; + } + + candidate.srcFormat = submission.sourceFormat; + candidate.dstFormat = dstFormat; + candidate.nbDirtyRects = nbDirtyRects; + candidate.pitch = postProcessor.GetOutputPitch(); + candidate.frameSize = frameSize; + candidate.captureTime = submission.captureTime; + candidate.postProcessStart = submission.postProcessStart; + candidate.prepareCopyStart = CFrameScheduler::Nanotime(); + candidate.prepareReady = 0; + candidate.prepareGPUStart = 0; + candidate.prepareGPUEnd = 0; + candidate.timingStart = timingStart; + candidate.prepareTimingValid = false; + if (nbDirtyRects) + memcpy(candidate.dirtyRects, currentDirtyRects, + nbDirtyRects * sizeof(*candidate.dirtyRects)); + candidate.timingEffectIndex = submission.timingEffectIndex; + candidate.timingToken = submission.timingToken; + + copySlot->SetCompletionCallback( + &CandidateCompletionFunction, this, &candidate); + copySlot->BeginTiming(); + postProcessor.CopyToCandidate( + copySlot->GetGfxList(), candidate.resource.Get(), + copySrcResource.Get()); + copySlot->EndTiming(); + + if (!ExecuteCandidateCopy(copySlot)) + { + if (!copySlot->HasSubmittedWork()) + { + if (computeSlot) + m_dx12->WaitForIdle(); + ReleaseCandidate(candidateIndex); + } + SetFullDamage(); + m_device->ForceFrame(); + return false; + } + + return true; +} diff --git a/idd/LGIdd/CHardwareFrameProcessor.h b/idd/LGIdd/CHardwareFrameProcessor.h new file mode 100644 index 00000000..d240880a --- /dev/null +++ b/idd/LGIdd/CHardwareFrameProcessor.h @@ -0,0 +1,105 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "CFrameProcessor.h" + +class CHardwareFrameProcessor final : public CFrameProcessor +{ +private: + enum CandidateState + { + CANDIDATE_FREE, + CANDIDATE_PREPARING, + CANDIDATE_READY, + CANDIDATE_PUBLISHING, + }; + + struct FrameCandidate + { + CandidateState state = CANDIDATE_FREE; + ComPtr resource; + D12FrameFormat srcFormat = {}; + D12FrameFormat dstFormat = {}; + RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned nbDirtyRects = 0; + unsigned pitch = 0; + size_t frameSize = 0; + uint64_t sequence = 0; + uint64_t captureTime = 0; + uint64_t postProcessStart = 0; + uint64_t prepareCopyStart = 0; + uint64_t prepareReady = 0; + uint64_t prepareGPUStart = 0; + uint64_t prepareGPUEnd = 0; + uint64_t timingStart = 0; + unsigned timingEffectIndex = 0; + uint64_t timingToken = 0; + bool prepareTimingValid = false; + }; + + struct CandidateDamageTail + { + uint64_t ownerSequence = 0; + RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned nbDirtyRects = 0; + bool hasDamage = false; + bool active = false; + }; + + FrameCandidate m_candidates[LGMP_Q_FRAME_LEN]; + CandidateDamageTail m_candidateDamageTail[LGMP_Q_FRAME_LEN]; + mutable SRWLOCK m_candidateLock = SRWLOCK_INIT; + SRWLOCK m_copySubmitLock = SRWLOCK_INIT; + uint64_t m_candidateSequence = 0; + bool m_publishPending = false; + Wrappers::Event m_candidateAvailableEvent; + Wrappers::Event m_copySubmitEvent; + + static void CandidateCompletionFunction( + CD3D12CommandSlot * slot, bool result, void * param1, void * param2); + static void CompletionFunction( + CD3D12CommandSlot * slot, bool result, void * param1, void * param2); + int AcquireCandidate(bool exclusiveSample, bool allowSupersede); + void ReleaseCandidate(unsigned candidateIndex); + bool EnsureCandidateResource(unsigned candidateIndex, size_t frameSize); + void ResetCandidates(); + void SignalCandidateState(); + bool ExecuteCandidateCopy(CD3D12CommandSlot * copySlot); + void AccumulateDamageLocked( + const RECT dirtyRects[], unsigned count) override; + void SetFullDamageLocked() override; + +public: + CHardwareFrameProcessor(CIndirectDeviceContext * device, + std::shared_ptr dx12, + CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], + SRWLOCK * pipelineLock, HANDLE terminateEvent); + + bool IsValid() const override; + bool Submit(const FrameSubmission& submission) override; + bool HasReadyFrame() const override; + bool Publish(const CFrameScheduler::Schedule& schedule, + bool periodic, uint64_t publishStart) override; + bool UsesCadence() const override { return true; } + void Reset() override; + void ResetPipeline() override; +}; diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index 3769b0be..fd6d9c98 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -2562,7 +2562,7 @@ std::shared_ptr CIndirectDeviceContext::GetColorTransform() const { AcquireSRWLockShared(&m_colorTransformLock); - auto transform = m_colorTransform; + std::shared_ptr transform = m_colorTransform; ReleaseSRWLockShared(&m_colorTransformLock); return transform; } diff --git a/idd/LGIdd/CSoftwareFrameProcessor.cpp b/idd/LGIdd/CSoftwareFrameProcessor.cpp new file mode 100644 index 00000000..f34ef95b --- /dev/null +++ b/idd/LGIdd/CSoftwareFrameProcessor.cpp @@ -0,0 +1,364 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "CSoftwareFrameProcessor.h" +#include "CFrameProcessorUtil.h" +#include "CDebug.h" + +#include + +CSoftwareFrameProcessor::CSoftwareFrameProcessor( + CIndirectDeviceContext * device, std::shared_ptr dx12, + CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], + SRWLOCK * pipelineLock, HANDLE terminateEvent) : + CFrameProcessor(device, std::move(dx12), postProcessors, + pipelineLock, terminateEvent), + m_directTexture( + !m_dx12->IsIndirectCopy() && m_dx12->CanUseIVSHMEMTexture()) +{ +} + +void CSoftwareFrameProcessor::CompletionFunction( + CD3D12CommandSlot * slot, bool result, void * param1, void * param2) +{ + auto processor = static_cast(param1); + auto fbRes = static_cast(param2); + fbRes->MarkCompletion(); + + if (!result) + { + processor->m_device->FailFrameBuffer(fbRes->GetFrameIndex()); + processor->SetFullDamage(); + processor->m_device->ForceFrame(); + return; + } + + uint64_t indirectCopyTime = 0; + if (processor->m_dx12->IsIndirectCopy()) + { + const uint64_t indirectCopyStart = CFrameScheduler::Nanotime(); + if (fbRes->IsFullCopy()) + processor->m_device->WriteFrameBuffer(fbRes->GetFrameIndex(), + fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); + else + { + const unsigned pitch = fbRes->GetCopyPitch(); + const unsigned bytesPerPixel = fbRes->GetCopyBytesPerPixel(); + const RECT * dirtyRects = fbRes->GetCopyDirtyRects(); + const unsigned count = fbRes->GetCopyDirtyRectCount(); + for (const RECT * rect = dirtyRects; rect < dirtyRects + count; ++rect) + { + const size_t rowOffset = + (size_t)rect->top * pitch + + (size_t)rect->left * bytesPerPixel; + const size_t rowBytes = + (size_t)(rect->right - rect->left) * bytesPerPixel; + processor->m_device->WriteFrameBufferRows(fbRes->GetFrameIndex(), + fbRes->GetMap(), rowOffset, rowBytes, pitch, + (unsigned)(rect->bottom - rect->top)); + } + } + indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart; + } + + uint64_t gpuStart = 0; + uint64_t gpuEnd = 0; + const uint64_t copyReady = CFrameScheduler::Nanotime(); + const bool gpuTimingValid = slot->GetGPUTimes(gpuStart, gpuEnd); + + processor->m_device->FinalizeFrameBuffer(fbRes->GetFrameIndex()); + const uint64_t publishedAt = CFrameScheduler::Nanotime(); + const uint64_t postProcessStart = fbRes->GetPostProcessStart(); + const uint64_t copyStart = fbRes->GetCopyStart(); + uint64_t postProcessTime = copyStart >= postProcessStart ? + copyStart - postProcessStart : 0; + uint64_t copyTime = copyReady >= copyStart ? + copyReady - copyStart : 0; + if (gpuTimingValid && gpuStart >= postProcessStart && + gpuEnd >= gpuStart && gpuEnd <= copyReady) + { + postProcessTime = gpuStart - postProcessStart; + copyTime = gpuEnd - gpuStart + indirectCopyTime; + } + + const uint64_t elapsed = publishedAt >= postProcessStart ? + publishedAt - postProcessStart : 0; + const uint64_t measured = postProcessTime + copyTime; + const uint64_t readyTime = elapsed > measured ? elapsed - measured : 0; + + processor->m_device->SetFrameTiming(fbRes->GetFrameIndex(), + fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, 0, + fbRes->GetSchedule(), publishedAt); + processor->m_device->CompleteFrameBuffer(fbRes->GetFrameIndex(), true); +} + +bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) +{ + CFrameProcessorSharedLock pipelineLock(m_pipelineLock); + CPostProcessor& postProcessor = m_postProcessors[0]; + const D12FrameFormat& dstFormat = postProcessor.GetOutputFormat(); + + D3D12_RESOURCE_DESC textureDesc = {}; + const D3D12_RESOURCE_DESC * textureDescPtr = nullptr; + unsigned pitch = postProcessor.GetOutputPitch(); + size_t frameSize = postProcessor.GetOutputSize(); + if (m_directTexture && + dstFormat.desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && + dstFormat.desc.Width && dstFormat.desc.Height && + dstFormat.desc.Format != DXGI_FORMAT_UNKNOWN) + { + textureDesc = dstFormat.desc; + textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + textureDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + textureDesc.DepthOrArraySize = 1; + textureDesc.MipLevels = 1; + textureDesc.SampleDesc.Count = 1; + textureDesc.SampleDesc.Quality = 0; + textureDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + textureDesc.Flags = + D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER; + + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {}; + m_dx12->GetDevice()->GetCopyableFootprints( + &textureDesc, 0, 1, 0, &layout, nullptr, nullptr, nullptr); + const unsigned texturePitch = layout.Footprint.RowPitch; + if (texturePitch && textureDesc.Height <= + m_device->GetMaxFrameSize() / texturePitch) + { + pitch = texturePitch; + frameSize = (size_t)pitch * textureDesc.Height; + textureDescPtr = &textureDesc; + } + else + { + m_directTexture = false; + DEBUG_WARN("IVSHMEM texture layout does not fit the framebuffer"); + } + } + else if (m_directTexture) + { + m_directTexture = false; + DEBUG_WARN("Post-processor output cannot use an IVSHMEM texture"); + } + + if (!pitch || !frameSize || frameSize > m_device->GetMaxFrameSize()) + { + DEBUG_ERROR("Software frame does not fit in shared memory"); + SetFullDamage(); + return false; + } + + if (submission.noImageUpdate && !HasPendingDamage()) + return true; + + for (;;) + { + CFrameScheduler::Schedule commitSchedule = {}; + CFrameScheduler::Schedule deliverySchedule = {}; + CIndirectDeviceContext::PreparedFrameBuffer buffer = {}; + CD3D12CommandSlot * copySlot = nullptr; + RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned nbDirtyRects = 0; + bool hasDamage = false; + + uint64_t ignoredTarget = 0; + bool ignoredPeriodic = false; + bool ignoredRepublish = false; + m_device->GetPublishTarget(CFrameScheduler::Nanotime(), + ignoredTarget, commitSchedule, ignoredPeriodic, ignoredRepublish); + deliverySchedule = commitSchedule; + deliverySchedule.deliveryDeadlineSerial = 0; + deliverySchedule.phaseEligible = false; + + m_device->ProcessFrameQueue(); + if (!m_device->FrameBufferAvailable( + deliverySchedule, submission.noImageUpdate)) + { + if (!submission.noImageUpdate) + { + m_device->FrameSuperseded(); + return true; + } + + if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0) + return true; + continue; + } + + copySlot = m_dx12->GetCopySlot(); + if (!copySlot) + { + if (!submission.noImageUpdate) + { + m_device->FrameSuperseded(); + return true; + } + + if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0) + return true; + continue; + } + + hasDamage = TakePendingDamage(currentDirtyRects, &nbDirtyRects); + CFrameProcessorUtil::ClipDirtyRects( + currentDirtyRects, &nbDirtyRects, + dstFormat.width, dstFormat.height); + buffer = m_device->PrepareFrameBuffer( + pitch, submission.sourceFormat, dstFormat, + currentDirtyRects, nbDirtyRects, deliverySchedule, + submission.noImageUpdate); + if (!buffer.mem) + { + copySlot->Cancel(); + RestorePendingDamage( + currentDirtyRects, nbDirtyRects, hasDamage); + if (!submission.noImageUpdate) + { + m_device->FrameSuperseded(); + return true; + } + + if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0) + return true; + continue; + } + + CFrameBufferResource * fbRes = nullptr; + if (textureDescPtr) + { + fbRes = m_frameBuffers.Get(buffer, frameSize, textureDescPtr); + if (!fbRes) + { + const HRESULT deviceStatus = + m_dx12->GetDevice()->GetDeviceRemovedReason(); + if (FAILED(deviceStatus)) + { + copySlot->Cancel(); + m_device->AbortFrameBuffer(buffer.frameIndex); + RestorePendingDamage( + currentDirtyRects, nbDirtyRects, hasDamage); + DEBUG_ERROR_HR(deviceStatus, + "D3D12 device removed while creating an IVSHMEM texture"); + SetFullDamage(); + return false; + } + + m_directTexture = false; + textureDescPtr = nullptr; + DEBUG_WARN( + "IVSHMEM textures unavailable; using a direct buffer copy"); + } + } + + if (!fbRes) + fbRes = m_frameBuffers.Get(buffer, frameSize); + if (!fbRes) + { + copySlot->Cancel(); + m_device->AbortFrameBuffer(buffer.frameIndex); + RestorePendingDamage( + currentDirtyRects, nbDirtyRects, hasDamage); + DEBUG_ERROR("Failed to get a framebuffer for software capture"); + SetFullDamage(); + return false; + } + + if (!submission.source->Signal() || + !submission.source->Sync(*copySlot)) + { + copySlot->Cancel(); + m_device->AbortFrameBuffer(buffer.frameIndex); + RestorePendingDamage( + currentDirtyRects, nbDirtyRects, hasDamage); + SetFullDamage(); + return false; + } + + RECT previousDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned nbPreviousDirtyRects = 0; + GetPreviousDamage(previousDirtyRects, &nbPreviousDirtyRects); + + RECT copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {}; + unsigned nbCopyDirtyRects = 0; + const bool fullCopy = CFrameProcessorUtil::BuildCopyDamage( + postProcessor, buffer.fullCopy, + previousDirtyRects, nbPreviousDirtyRects, + currentDirtyRects, nbDirtyRects, + dstFormat.width, dstFormat.height, + copyDirtyRects, &nbCopyDirtyRects); + + const unsigned bytesPerPixel = + dstFormat.format == FRAME_TYPE_RGBA16F ? 8 : 4; + const uint64_t copyStart = CFrameScheduler::Nanotime(); + fbRes->SetTiming(submission.captureTime, + submission.postProcessStart, copyStart); + fbRes->SetSchedule(deliverySchedule); + fbRes->SetCopyDamage(copyDirtyRects, nbCopyDirtyRects, + fullCopy, pitch, bytesPerPixel); + fbRes->ResetCompletion(); + copySlot->SetCompletionCallback( + &CompletionFunction, this, fbRes); + copySlot->BeginTiming(); + postProcessor.CopyToFrameBuffer(copySlot->GetGfxList(), + fbRes->Get().Get(), submission.source->GetRes().Get(), + copyDirtyRects, nbCopyDirtyRects, fullCopy); + copySlot->EndTiming(); + + bool deliveredToOwner; + if (!m_device->PublishFrameBuffer( + buffer.frameIndex, deliverySchedule, deliveredToOwner)) + { + copySlot->Cancel(); + m_device->AbortFrameBuffer(buffer.frameIndex); + RestorePendingDamage( + currentDirtyRects, nbDirtyRects, hasDamage); + if (!submission.noImageUpdate) + { + m_device->FrameSuperseded(); + return true; + } + + if (WaitForSingleObject(m_terminateEvent, 1) == WAIT_OBJECT_0) + return true; + continue; + } + + CommitDamage(currentDirtyRects, nbDirtyRects); + if (!copySlot->Execute()) + { + const bool submittedWork = copySlot->HasSubmittedWork(); + const bool completionHandled = fbRes->CompletionHandled(); + if (!submittedWork && !completionHandled) + m_device->FailFrameBuffer(buffer.frameIndex); + RestorePendingDamage( + currentDirtyRects, nbDirtyRects, hasDamage); + if (!submittedWork && !completionHandled) + { + SetFullDamage(); + m_device->ForceFrame(); + } + return false; + } + + m_device->CommitFrameBuffer( + buffer.frameIndex, commitSchedule, false, deliveredToOwner); + return true; + } +} diff --git a/idd/LGIdd/CSoftwareFrameProcessor.h b/idd/LGIdd/CSoftwareFrameProcessor.h new file mode 100644 index 00000000..21a9082d --- /dev/null +++ b/idd/LGIdd/CSoftwareFrameProcessor.h @@ -0,0 +1,46 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "CFrameProcessor.h" + +class CSoftwareFrameProcessor final : public CFrameProcessor +{ +private: + bool m_directTexture; + + static void CompletionFunction( + CD3D12CommandSlot * slot, bool result, void * param1, void * param2); + +public: + CSoftwareFrameProcessor(CIndirectDeviceContext * device, + std::shared_ptr dx12, + CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], + SRWLOCK * pipelineLock, HANDLE terminateEvent); + + bool Submit(const FrameSubmission& submission) override; + bool HasReadyFrame() const override { return false; } + bool Publish(const CFrameScheduler::Schedule&, bool, uint64_t) override + { + return false; + } + bool UsesCadence() const override { return false; } +}; diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 5276d1ca..caf606a5 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -19,6 +19,7 @@ */ #include "CSwapChainProcessor.h" +#include "CFrameProcessorUtil.h" #include "CIndirectMonitorContext.h" #include "CPlatformInfo.h" @@ -35,9 +36,6 @@ static const uint32_t HDR_PQ_MIN_LUMINANCE = 50; static const uint32_t HDR_PQ_MAX_LUMINANCE = 10000; static const uint64_t PUBLISH_RETRY_NS = 1000000ULL; -static_assert(LGMP_Q_FRAME_LEN == 2, - "IDD candidate pipeline assumes two slots"); - class CSRWExclusiveLock { private: @@ -55,78 +53,6 @@ public: } }; -class CSRWSharedLock -{ -private: - SRWLOCK * m_lock; - -public: - explicit CSRWSharedLock(SRWLOCK * lock) : m_lock(lock) - { - AcquireSRWLockShared(m_lock); - } - - ~CSRWSharedLock() - { - ReleaseSRWLockShared(m_lock); - } -}; - -class CPublishPending -{ -private: - SRWLOCK * m_lock; - bool * m_pending; - HANDLE m_event; - bool m_active = true; - -public: - CPublishPending(SRWLOCK * lock, bool * pending, HANDLE event) : - m_lock(lock), - m_pending(pending), - m_event(event) - { - AcquireSRWLockExclusive(m_lock); - *m_pending = true; - ResetEvent(m_event); - ReleaseSRWLockExclusive(m_lock); - } - - ~CPublishPending() - { - Clear(); - } - - void Clear() - { - if (!m_active) - return; - - AcquireSRWLockExclusive(m_lock); - *m_pending = false; - SetEvent(m_event); - ReleaseSRWLockExclusive(m_lock); - m_active = false; - } -}; - -static bool FrameMetadataChanged(const D12FrameFormat& previous, - const D12FrameFormat& current) -{ - return - previous.hdrMetadata != current.hdrMetadata || - previous.sdrWhiteLevel != current.sdrWhiteLevel || - (current.hdrMetadata && - (memcmp(previous.displayPrimary, current.displayPrimary, - sizeof(current.displayPrimary)) != 0 || - memcmp(previous.whitePoint, current.whitePoint, - sizeof(current.whitePoint)) != 0 || - previous.maxDisplayLuminance != current.maxDisplayLuminance || - previous.minDisplayLuminance != current.minDisplayLuminance || - previous.maxContentLightLevel != current.maxContentLightLevel || - previous.maxFrameAverageLightLevel != current.maxFrameAverageLightLevel)); -} - CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration, IDDCX_MONITOR monitor, CIndirectDeviceContext * devContext, IDDCX_SWAPCHAIN hSwapChain, @@ -144,10 +70,6 @@ CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContex // Manual-reset: all worker threads wait on this, so it must stay signalled // once set or only one thread would ever observe termination. m_terminateEvent.Attach(CreateEvent(nullptr, TRUE, FALSE, nullptr)); - m_candidateEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr)); - m_candidateAvailableEvent.Attach( - CreateEvent(nullptr, FALSE, FALSE, nullptr)); - m_copySubmitEvent.Attach(CreateEvent(nullptr, TRUE, TRUE, nullptr)); m_publishTimer.Attach(CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS)); if (!m_publishTimer.Get()) @@ -159,9 +81,8 @@ CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContex bool CSwapChainProcessor::Start() { - if (!m_terminateEvent.Get() || !m_candidateEvent.Get() || - !m_candidateAvailableEvent.Get() || !m_copySubmitEvent.Get() || - !m_publishTimer.Get() || !m_cursorDataEvent.Get() || !m_shapeBuffer) + if (!m_terminateEvent.Get() || !m_publishTimer.Get() || + !m_cursorDataEvent.Get() || !m_shapeBuffer) { DEBUG_ERROR("Failed to initialize swap chain worker resources"); return false; @@ -212,10 +133,6 @@ bool CSwapChainProcessor::InitializePipeline() } m_dx12Device = std::move(dx12Device); - m_directSoftwareTexture = - m_dx11Device->IsSoftware() && - !m_dx12Device->IsIndirectCopy() && - m_dx12Device->CanUseIVSHMEMTexture(); break; } @@ -224,7 +141,6 @@ bool CSwapChainProcessor::InitializePipeline() return false; m_resPool.Init(m_dx11Device, m_dx12Device); - m_fbPool.Init(this); const bool enableEffects = !m_dx11Device->IsSoftware(); if (!enableEffects) DEBUG_INFO("Software render adapter: post-processing disabled"); @@ -258,6 +174,15 @@ bool CSwapChainProcessor::InitializePipeline() "Failed to initialize post-processing effects; effects disabled"); } + m_frameProcessor = CreateFrameProcessor(m_dx11Device->IsSoftware(), + m_devContext, m_dx12Device, m_postProcessors, + &m_pipelineLock, m_terminateEvent.Get()); + if (!m_frameProcessor) + { + DEBUG_ERROR("Failed to create the frame processor"); + return false; + } + if (!m_monitorContext->IsAssignmentCurrent(m_assignmentGeneration) || WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0) return false; @@ -288,13 +213,14 @@ CSwapChainProcessor::~CSwapChainProcessor() if (m_dx12Device) { m_dx12Device->WaitForIdle(); - ResetCandidates(); + if (m_frameProcessor) + m_frameProcessor->Reset(); } for (CPostProcessor& postProcessor : m_postProcessors) postProcessor.Reset(); + m_frameProcessor.reset(); m_resPool.Reset(); - m_fbPool.Reset(); delete[] m_shapeBuffer; } @@ -322,20 +248,6 @@ DWORD CALLBACK CSwapChainProcessor::_PublisherThread(LPVOID arg) return 0; } -bool CSwapChainProcessor::HasReadyCandidate() -{ - bool ready = false; - AcquireSRWLockShared(&m_candidateLock); - for (const FrameCandidate& candidate : m_candidates) - if (candidate.state == CANDIDATE_READY) - { - ready = true; - break; - } - ReleaseSRWLockShared(&m_candidateLock); - return ready; -} - void CSwapChainProcessor::PublisherThread() { DWORD avTask = 0; @@ -349,20 +261,17 @@ void CSwapChainProcessor::PublisherThread() HANDLE idleHandles[] = { m_terminateEvent.Get(), - m_candidateEvent.Get(), + m_frameProcessor->GetReadyEvent(), scheduleEvent, }; HANDLE timerHandles[] = { m_terminateEvent.Get(), - m_candidateEvent.Get(), + m_frameProcessor->GetReadyEvent(), scheduleEvent, m_publishTimer.Get(), }; - // Software capture publishes source frames immediately. Keep this thread - // available for transport delivery and reconnects, but do not wake it for - // deadlines the software path cannot reliably meet. - const bool cadenceEnabled = !m_dx11Device->IsSoftware(); + const bool cadenceEnabled = m_frameProcessor->UsesCadence(); for (;;) { @@ -374,11 +283,11 @@ void CSwapChainProcessor::PublisherThread() m_devContext->GetPublishTarget( now, target, schedule, periodic, republish); - const bool ready = HasReadyCandidate(); + const bool ready = m_frameProcessor->HasReadyFrame(); if (!ready) { m_devContext->ProcessFrameQueue(); - if (HasReadyCandidate()) + if (m_frameProcessor->HasReadyFrame()) continue; uint64_t current = CFrameScheduler::Nanotime(); @@ -547,8 +456,7 @@ void CSwapChainProcessor::PublisherThread() const uint64_t publishStart = CFrameScheduler::Nanotime(); m_devContext->ProcessFrameQueue(); if (!m_devContext->FrameBufferAvailable(schedule) || - !PublishNewestCandidate( - schedule, periodic, publishStart)) + !m_frameProcessor->Publish(schedule, periodic, publishStart)) { ArmPublishTimer(m_publishTimer.Get(), PUBLISH_RETRY_NS); if (WaitForMultipleObjects( @@ -765,913 +673,6 @@ void CSwapChainProcessor::SwapChainThreadCore() } -void CSwapChainProcessor::CandidateCompletionFunction( - CD3D12CommandSlot * slot, bool result, void * param1, void * param2) -{ - auto sc = static_cast(param1); - auto candidate = static_cast(param2); - - uint64_t gpuStart = 0; - uint64_t gpuEnd = 0; - const bool timingValid = result && slot->GetGPUTimes(gpuStart, gpuEnd); - - bool forceFrame = false; - AcquireSRWLockExclusive(&sc->m_candidateLock); - if (candidate->state == CANDIDATE_PREPARING) - { - candidate->prepareReady = CFrameScheduler::Nanotime(); - candidate->prepareGPUStart = gpuStart; - candidate->prepareGPUEnd = gpuEnd; - candidate->prepareTimingValid = timingValid; - candidate->state = - result ? CANDIDATE_READY : CANDIDATE_FREE; - forceFrame = result && candidate->timingToken != 0; - } - ReleaseSRWLockExclusive(&sc->m_candidateLock); - - if (!result) - { - sc->SetFullPendingDamage(); - sc->m_devContext->ForceFrame(); - } - else if (forceFrame) - sc->m_devContext->ForceFrame(); - sc->SignalCandidateState(); -} - -void CSwapChainProcessor::SoftwareCompletionFunction( - CD3D12CommandSlot * slot, bool result, void * param1, void * param2) -{ - auto sc = static_cast(param1); - auto fbRes = static_cast(param2); - fbRes->MarkCompletion(); - - if (!result) - { - sc->m_devContext->FailFrameBuffer(fbRes->GetFrameIndex()); - sc->SetFullPendingDamage(); - sc->m_devContext->ForceFrame(); - return; - } - - uint64_t indirectCopyTime = 0; - if (sc->m_dx12Device->IsIndirectCopy()) - { - const uint64_t indirectCopyStart = CFrameScheduler::Nanotime(); - if (fbRes->IsFullCopy()) - sc->m_devContext->WriteFrameBuffer(fbRes->GetFrameIndex(), - fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); - else - { - const unsigned pitch = fbRes->GetCopyPitch(); - const unsigned bytesPerPixel = fbRes->GetCopyBytesPerPixel(); - const RECT * dirtyRects = fbRes->GetCopyDirtyRects(); - const unsigned count = fbRes->GetCopyDirtyRectCount(); - for (const RECT * rect = dirtyRects; rect < dirtyRects + count; ++rect) - { - const size_t rowOffset = - (size_t)rect->top * pitch + - (size_t)rect->left * bytesPerPixel; - const size_t rowBytes = - (size_t)(rect->right - rect->left) * bytesPerPixel; - sc->m_devContext->WriteFrameBufferRows(fbRes->GetFrameIndex(), - fbRes->GetMap(), rowOffset, rowBytes, pitch, - (unsigned)(rect->bottom - rect->top)); - } - } - indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart; - } - - uint64_t gpuStart = 0; - uint64_t gpuEnd = 0; - const uint64_t copyReady = CFrameScheduler::Nanotime(); - const bool gpuTimingValid = - slot->GetGPUTimes(gpuStart, gpuEnd); - - sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex()); - const uint64_t publishedAt = CFrameScheduler::Nanotime(); - const uint64_t postProcessStart = fbRes->GetPostProcessStart(); - const uint64_t copyStart = fbRes->GetCopyStart(); - uint64_t postProcessTime = copyStart >= postProcessStart ? - copyStart - postProcessStart : 0; - uint64_t copyTime = copyReady >= copyStart ? - copyReady - copyStart : 0; - if (gpuTimingValid && gpuStart >= postProcessStart && - gpuEnd >= gpuStart && gpuEnd <= copyReady) - { - postProcessTime = gpuStart - postProcessStart; - copyTime = gpuEnd - gpuStart + indirectCopyTime; - } - - const uint64_t elapsed = publishedAt >= postProcessStart ? - publishedAt - postProcessStart : 0; - const uint64_t measured = postProcessTime + copyTime; - const uint64_t readyTime = elapsed > measured ? - elapsed - measured : 0; - - sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(), - fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, 0, - fbRes->GetSchedule(), publishedAt); - sc->m_devContext->CompleteFrameBuffer(fbRes->GetFrameIndex(), true); -} - -void CSwapChainProcessor::CompletionFunction( - CD3D12CommandSlot * slot, bool result, void * param1, void * param2) -{ - auto sc = static_cast(param1); - auto fbRes = static_cast(param2); - const unsigned candidateIndex = fbRes->GetCandidateIndex(); - - if (!result) - { - // The frame was reserved in LGMP before GPU submission. Make the message - // releasable even though its contents failed. - sc->m_devContext->FailFrameBuffer(fbRes->GetFrameIndex()); - sc->SetFullPendingDamage(); - sc->m_devContext->ForceFrame(); - sc->ReleaseCandidate(candidateIndex); - return; - } - - uint64_t prepareCopyStart; - uint64_t prepareReady; - uint64_t prepareGPUStart; - uint64_t prepareGPUEnd; - uint64_t timingStart; - bool prepareTimingValid; - AcquireSRWLockShared(&sc->m_candidateLock); - const FrameCandidate& candidate = sc->m_candidates[candidateIndex]; - prepareCopyStart = candidate.prepareCopyStart; - prepareReady = candidate.prepareReady; - prepareGPUStart = candidate.prepareGPUStart; - prepareGPUEnd = candidate.prepareGPUEnd; - timingStart = candidate.timingStart; - prepareTimingValid = candidate.prepareTimingValid; - ReleaseSRWLockShared(&sc->m_candidateLock); - - const uint64_t publishStart = fbRes->GetCopyStart(); - uint64_t gpuCopyStart = 0; - uint64_t gpuCopyEnd = 0; - uint64_t indirectCopyTime = 0; - if (sc->m_dx12Device->IsIndirectCopy()) - { - // GPU timestamps end at the readback copy. Track the following CPU copy - // separately for frame metrics; benchmark wall time includes it directly. - const uint64_t indirectCopyStart = CFrameScheduler::Nanotime(); - sc->m_devContext->WriteFrameBuffer( - fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); - indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart; - } - - // Queue waits execute before the start timestamp. The end timestamp follows - // the last copy command, separating GPU work from readiness dispatch. - const bool gpuTimingValid = - slot->GetGPUTimes(gpuCopyStart, gpuCopyEnd); - - const uint64_t copyReady = CFrameScheduler::Nanotime(); - - const uint64_t postProcessStart = fbRes->GetPostProcessStart(); - uint64_t postProcessTime = prepareCopyStart - postProcessStart; - uint64_t prepareCopyTime = prepareReady - prepareCopyStart; - if (prepareTimingValid && prepareGPUStart >= postProcessStart && - prepareGPUEnd >= prepareGPUStart && prepareGPUEnd <= prepareReady) - { - postProcessTime = prepareGPUStart - postProcessStart; - prepareCopyTime = prepareGPUEnd - prepareGPUStart; - } - - uint64_t publishCopyTime = copyReady - publishStart; - if (gpuTimingValid && gpuCopyStart >= publishStart && - gpuCopyEnd >= gpuCopyStart && gpuCopyEnd <= copyReady) - publishCopyTime = gpuCopyEnd - gpuCopyStart + indirectCopyTime; - - const uint64_t copyTime = prepareCopyTime + publishCopyTime; - - // Make the framebuffer readable before phase bookkeeping. If the scheduler - // lock is busy, the frame is still delivered and only this phase sample is - // discarded. - sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex()); - const uint64_t publishedAt = CFrameScheduler::Nanotime(); - const uint64_t prepareElapsed = prepareReady >= postProcessStart ? - prepareReady - postProcessStart : 0; - const uint64_t prepareMeasured = postProcessTime + prepareCopyTime; - const uint64_t prepareReadyTime = prepareElapsed > prepareMeasured ? - prepareElapsed - prepareMeasured : 0; - const uint64_t publishElapsed = publishedAt >= publishStart ? - publishedAt - publishStart : 0; - const uint64_t publishReadyTime = publishElapsed > publishCopyTime ? - publishElapsed - publishCopyTime : 0; - const uint64_t readyTime = prepareReadyTime + publishReadyTime; - const uint64_t holdTime = publishStart >= prepareReady ? - publishStart - prepareReady : 0; - - sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(), - fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, holdTime, - fbRes->GetSchedule(), publishedAt); - sc->m_devContext->TryRecordFrameTiming( - publishedAt - publishStart); - - // Use matching wall-clock boundaries for both modes. The split excludes the - // cadence hold while including the indirect CPU copy only when it occurs. - const uint64_t timingToken = fbRes->GetTimingToken(); - if (timingToken && timingStart && prepareReady >= timingStart && - copyReady >= publishStart) - { - const uint64_t totalTime = - (prepareReady - timingStart) + (copyReady - publishStart); - sc->m_postProcessors[candidateIndex].RecordTiming( - fbRes->GetTimingEffectIndex(), timingToken, - fbRes->IsFullCopy(), totalTime); - } - - sc->m_devContext->CompleteFrameBuffer(fbRes->GetFrameIndex(), true); - sc->ReleaseCandidate(candidateIndex); -} - - -static bool IsFullDamage(const RECT * dirtyRects, unsigned nbDirtyRects, - unsigned width, unsigned height) -{ - for (const RECT * rect = dirtyRects; - rect < dirtyRects + nbDirtyRects; ++rect) - if (rect->left == 0 && - rect->top == 0 && - rect->right == (LONG)width && - rect->bottom == (LONG)height) - return true; - - return false; -} - -static bool DirtyRectContains(const RECT& outer, const RECT& inner) -{ - return outer.left <= inner.left && - outer.top <= inner.top && - outer.right >= inner.right && - outer.bottom >= inner.bottom; -} - -static bool DirtyRectsTouchOrIntersect(const RECT& a, const RECT& b) -{ - return a.left <= b.right && a.right >= b.left && - a.top <= b.bottom && a.bottom >= b.top; -} - -static RECT MergeDirtyRects(const RECT& a, const RECT& b) -{ - RECT result; - result.left = min(a.left , b.left ); - result.top = min(a.top , b.top ); - result.right = max(a.right , b.right ); - result.bottom = max(a.bottom, b.bottom); - return result; -} - -static uint64_t DirtyRectArea(const RECT& rect) -{ - const uint64_t width = (uint64_t)((int64_t)rect.right - rect.left); - const uint64_t height = (uint64_t)((int64_t)rect.bottom - rect.top ); - return width * height; -} - -static bool AddCopyDirtyRect(RECT dirtyRects[], unsigned capacity, - unsigned * nbDirtyRects, const RECT& dirtyRect) -{ - RECT candidate = dirtyRect; - for (unsigned i = 0; i < *nbDirtyRects;) - { - if (DirtyRectContains(dirtyRects[i], candidate)) - return true; - - const RECT merged = MergeDirtyRects(dirtyRects[i], candidate); - // Reduce command and overlap cost without copying more pixels than the - // two original rectangles would have copied. - if (DirtyRectContains(candidate, dirtyRects[i]) || - (DirtyRectsTouchOrIntersect(dirtyRects[i], candidate) && - DirtyRectArea(merged) <= - DirtyRectArea(dirtyRects[i]) + DirtyRectArea(candidate))) - { - candidate = merged; - --(*nbDirtyRects); - dirtyRects[i] = dirtyRects[*nbDirtyRects]; - i = 0; - continue; - } - - ++i; - } - - if (*nbDirtyRects >= capacity) - return false; - - dirtyRects[(*nbDirtyRects)++] = candidate; - return true; -} - -static bool CopyAreaCoversFrame(const RECT * dirtyRects, - unsigned nbDirtyRects, unsigned width, unsigned height) -{ - const uint64_t frameArea = (uint64_t)width * height; - uint64_t copyArea = 0; - - for (const RECT * rect = dirtyRects; - rect < dirtyRects + nbDirtyRects; ++rect) - { - const uint64_t area = DirtyRectArea(*rect); - if (area >= frameArea - copyArea) - return true; - copyArea += area; - } - - return false; -} - -static bool ClipDirtyRect(RECT& rect, unsigned width, unsigned height) -{ - const LONG maxRight = (LONG)width; - const LONG maxBottom = (LONG)height; - - if (rect.left < 0 ) rect.left = 0; - if (rect.top < 0 ) rect.top = 0; - if (rect.right > maxRight ) rect.right = maxRight; - if (rect.bottom > maxBottom) rect.bottom = maxBottom; - - return rect.left < rect.right && rect.top < rect.bottom; -} - -static void ClipDirtyRects(RECT dirtyRects[], unsigned * nbDirtyRects, - unsigned width, unsigned height) -{ - unsigned out = 0; - for (unsigned i = 0; i < *nbDirtyRects; ++i) - { - RECT rect = dirtyRects[i]; - if (ClipDirtyRect(rect, width, height)) - dirtyRects[out++] = rect; - } - *nbDirtyRects = out; -} - -static bool BuildCopyDamage(const CPostProcessor& postProcessor, - bool destinationNeedsFullCopy, - const RECT previousDirtyRects[], unsigned nbPreviousDirtyRects, - const RECT currentDirtyRects[], unsigned nbCurrentDirtyRects, - unsigned width, unsigned height, - RECT copyDirtyRects[], unsigned * nbCopyDirtyRects) -{ - *nbCopyDirtyRects = 0; - bool fullCopy = destinationNeedsFullCopy || - nbCurrentDirtyRects == 0 || nbPreviousDirtyRects == 0; - - if (fullCopy) - return true; - - for (const RECT * rect = previousDirtyRects; - rect < previousDirtyRects + nbPreviousDirtyRects && !fullCopy; - ++rect) - { - RECT clipped = *rect; - if (ClipDirtyRect(clipped, width, height) && - !AddCopyDirtyRect(copyDirtyRects, LG_MAX_DIRTY_RECTS * 2, - nbCopyDirtyRects, clipped)) - fullCopy = true; - } - - for (const RECT * rect = currentDirtyRects; - rect < currentDirtyRects + nbCurrentDirtyRects && !fullCopy; - ++rect) - if (!AddCopyDirtyRect(copyDirtyRects, LG_MAX_DIRTY_RECTS * 2, - nbCopyDirtyRects, *rect)) - fullCopy = true; - - if (!fullCopy) - fullCopy = IsFullDamage(copyDirtyRects, *nbCopyDirtyRects, - width, height) || - CopyAreaCoversFrame(copyDirtyRects, *nbCopyDirtyRects, - width, height); - - if (!fullCopy) - fullCopy = postProcessor.ShouldCopyFully( - copyDirtyRects, *nbCopyDirtyRects); - - return fullCopy; -} - -static FrameType GetFrameType(DXGI_FORMAT format) -{ - switch (format) - { - case DXGI_FORMAT_B8G8R8A8_UNORM : return FRAME_TYPE_BGRA; - case DXGI_FORMAT_R8G8B8A8_UNORM : return FRAME_TYPE_RGBA; - case DXGI_FORMAT_R10G10B10A2_UNORM : return FRAME_TYPE_RGBA10; - case DXGI_FORMAT_R16G16B16A16_FLOAT: return FRAME_TYPE_RGBA16F; - default : return FRAME_TYPE_INVALID; - } -} - -static void AccumulatePendingDamage( - RECT pendingDirtyRects[], unsigned * nbPendingDirtyRects, - bool * hasPendingDamage, const RECT dirtyRects[], unsigned nbDirtyRects) -{ - if (nbDirtyRects > LG_MAX_DIRTY_RECTS) - nbDirtyRects = 0; - - if (!*hasPendingDamage) - { - *hasPendingDamage = true; - *nbPendingDirtyRects = nbDirtyRects; - if (nbDirtyRects) - memcpy(pendingDirtyRects, dirtyRects, - nbDirtyRects * sizeof(*pendingDirtyRects)); - return; - } - - // Zero dirty rectangles represents full-frame damage. Once an accumulated - // set is full, no later rectangles can narrow that same set again. - if (*nbPendingDirtyRects == 0 || nbDirtyRects == 0 || - *nbPendingDirtyRects + nbDirtyRects > LG_MAX_DIRTY_RECTS) - { - *nbPendingDirtyRects = 0; - return; - } - - memcpy(pendingDirtyRects + *nbPendingDirtyRects, dirtyRects, - nbDirtyRects * sizeof(*pendingDirtyRects)); - *nbPendingDirtyRects += nbDirtyRects; -} - -void CSwapChainProcessor::SetFullPendingDamage() -{ - AcquireSRWLockExclusive(&m_damageLock); - m_hasPendingDamage = true; - m_nbPendingDirtyRects = 0; - for (CandidateDamageTail& tail : m_candidateDamageTail) - if (tail.active) - { - tail.hasDamage = true; - tail.nbDirtyRects = 0; - } - ReleaseSRWLockExclusive(&m_damageLock); -} - -void CSwapChainProcessor::AccumulateFrameDamage( - const RECT * dirtyRects, unsigned nbDirtyRects) -{ - AcquireSRWLockExclusive(&m_damageLock); - AccumulatePendingDamage( - m_pendingDirtyRects, &m_nbPendingDirtyRects, &m_hasPendingDamage, - dirtyRects, nbDirtyRects); - for (CandidateDamageTail& tail : m_candidateDamageTail) - if (tail.active) - AccumulatePendingDamage( - tail.dirtyRects, &tail.nbDirtyRects, &tail.hasDamage, - dirtyRects, nbDirtyRects); - ReleaseSRWLockExclusive(&m_damageLock); -} - -bool CSwapChainProcessor::HasPendingDamage() -{ - AcquireSRWLockShared(&m_damageLock); - const bool result = m_hasPendingDamage; - ReleaseSRWLockShared(&m_damageLock); - return result; -} - -bool CSwapChainProcessor::TakePendingDamage( - RECT dirtyRects[], unsigned * nbDirtyRects) -{ - AcquireSRWLockExclusive(&m_damageLock); - const bool hasDamage = m_hasPendingDamage; - *nbDirtyRects = hasDamage ? m_nbPendingDirtyRects : 0; - if (*nbDirtyRects) - memcpy(dirtyRects, m_pendingDirtyRects, - *nbDirtyRects * sizeof(*dirtyRects)); - m_hasPendingDamage = false; - m_nbPendingDirtyRects = 0; - ReleaseSRWLockExclusive(&m_damageLock); - return hasDamage; -} - -void CSwapChainProcessor::RestorePendingDamage( - const RECT dirtyRects[], unsigned nbDirtyRects, bool hasDamage) -{ - if (!hasDamage) - return; - - AcquireSRWLockExclusive(&m_damageLock); - AccumulatePendingDamage( - m_pendingDirtyRects, &m_nbPendingDirtyRects, &m_hasPendingDamage, - dirtyRects, nbDirtyRects); - ReleaseSRWLockExclusive(&m_damageLock); -} - -void CSwapChainProcessor::CommitFrameDamage( - const RECT dirtyRects[], unsigned nbDirtyRects) -{ - AcquireSRWLockExclusive(&m_damageLock); - m_nbDirtyRects = nbDirtyRects; - if (nbDirtyRects) - memcpy(m_dirtyRects, dirtyRects, - nbDirtyRects * sizeof(*m_dirtyRects)); - ReleaseSRWLockExclusive(&m_damageLock); -} - -int CSwapChainProcessor::AcquireCandidate( - bool exclusiveSample, bool allowSupersede) -{ - int selected = -1; - uint64_t oldest = UINT64_MAX; - bool superseded = false; - bool idle = true; - bool publishing = false; - - AcquireSRWLockExclusive(&m_candidateLock); - for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) - { - if (m_candidates[i].state != CANDIDATE_FREE) - { - idle = false; - if (m_candidates[i].state == CANDIDATE_PUBLISHING) - publishing = true; - } - else if (selected < 0) - selected = static_cast(i); - } - - // Effect timing samples must not queue behind work which can later be - // superseded, otherwise that discarded work contaminates the sample. - if (exclusiveSample && !idle) - selected = -1; - - unsigned readyCount = 0; - for (const FrameCandidate& candidate : m_candidates) - if (candidate.state == CANDIDATE_READY) - ++readyCount; - - // Preserve one completed fallback unless another candidate is already - // publishing. In that case its peer must remain available for new source - // frames instead of being frozen for the duration of the transport copy. - if (allowSupersede && !exclusiveSample && selected < 0 && - readyCount > (publishing ? 0U : 1U)) - for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) - if (m_candidates[i].state == CANDIDATE_READY && - m_candidates[i].sequence < oldest) - { - selected = static_cast(i); - oldest = m_candidates[i].sequence; - } - - if (selected >= 0) - { - FrameCandidate& candidate = - m_candidates[static_cast(selected)]; - superseded = candidate.state == CANDIDATE_READY; - candidate.state = CANDIDATE_PREPARING; - candidate.sequence = ++m_candidateSequence; - } - ReleaseSRWLockExclusive(&m_candidateLock); - - if (superseded) - m_devContext->FrameSuperseded(); - return selected; -} - -void CSwapChainProcessor::ReleaseCandidate(unsigned candidateIndex) -{ - if (candidateIndex >= ARRAYSIZE(m_candidates)) - return; - - AcquireSRWLockExclusive(&m_candidateLock); - m_candidates[candidateIndex].state = CANDIDATE_FREE; - ReleaseSRWLockExclusive(&m_candidateLock); - SignalCandidateState(); -} - -static bool ResourceDescMatches( - const D3D12_RESOURCE_DESC& left, const D3D12_RESOURCE_DESC& right) -{ - // Alignment is allocation metadata. GetDesc may report the resolved value - // when the creation descriptor requested automatic alignment. - return - left.Dimension == right.Dimension && - left.Width == right.Width && - left.Height == right.Height && - left.DepthOrArraySize == right.DepthOrArraySize && - left.MipLevels == right.MipLevels && - left.Format == right.Format && - left.SampleDesc.Count == right.SampleDesc.Count && - left.SampleDesc.Quality == right.SampleDesc.Quality && - left.Layout == right.Layout && - left.Flags == right.Flags; -} - -bool CSwapChainProcessor::EnsureCandidateResource( - unsigned candidateIndex, size_t frameSize) -{ - FrameCandidate& candidate = m_candidates[candidateIndex]; - - // Keep the transport layout in local GPU memory so publication does not - // combine texture detiling with the IVSHMEM or readback transfer. - D3D12_RESOURCE_DESC desc = {}; - desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - desc.Width = frameSize; - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.Format = DXGI_FORMAT_UNKNOWN; - desc.SampleDesc.Count = 1; - desc.SampleDesc.Quality = 0; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - desc.Flags = D3D12_RESOURCE_FLAG_NONE; - - if (candidate.resource && - ResourceDescMatches(candidate.resource->GetDesc(), desc)) - return true; - - candidate.resource.Reset(); - - D3D12_HEAP_PROPERTIES heapProps = {}; - heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; - heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - heapProps.CreationNodeMask = 1; - heapProps.VisibleNodeMask = 1; - - const HRESULT hr = m_dx12Device->GetDevice()->CreateCommittedResource( - &heapProps, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, - nullptr, IID_PPV_ARGS(&candidate.resource)); - if (FAILED(hr)) - { - DEBUG_ERROR_HR(hr, "Failed to create retained frame candidate"); - return false; - } - - static const WCHAR * names[] = - { - L"Frame Candidate 0", - L"Frame Candidate 1", - }; - candidate.resource->SetName(names[candidateIndex]); - return true; -} - -void CSwapChainProcessor::ResetCandidates() -{ - AcquireSRWLockExclusive(&m_candidateLock); - for (FrameCandidate& candidate : m_candidates) - candidate = {}; - ReleaseSRWLockExclusive(&m_candidateLock); - - AcquireSRWLockExclusive(&m_damageLock); - for (CandidateDamageTail& tail : m_candidateDamageTail) - tail = {}; - ReleaseSRWLockExclusive(&m_damageLock); - SignalCandidateState(); -} - -void CSwapChainProcessor::SignalCandidateState() -{ - SetEvent(m_candidateEvent.Get()); - SetEvent(m_candidateAvailableEvent.Get()); -} - -bool CSwapChainProcessor::ExecuteCandidateCopy( - CD3D12CommandSlot * copySlot) -{ - HANDLE waitHandles[] = - { - m_terminateEvent.Get(), - m_copySubmitEvent.Get(), - }; - - for (;;) - { - AcquireSRWLockExclusive(&m_copySubmitLock); - if (!m_publishPending) - { - const bool result = copySlot->Execute(); - ReleaseSRWLockExclusive(&m_copySubmitLock); - return result; - } - ReleaseSRWLockExclusive(&m_copySubmitLock); - - const DWORD result = WaitForMultipleObjects( - ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); - if (result == WAIT_OBJECT_0 + 1) - continue; - - copySlot->Cancel(); - if (result != WAIT_OBJECT_0) - DEBUG_ERROR_HR(HRESULT_FROM_WIN32(GetLastError()), - "Failed while waiting to submit a frame candidate"); - return false; - } -} - -bool CSwapChainProcessor::PublishNewestCandidate( - const CFrameScheduler::Schedule& schedule, bool periodic, - uint64_t publishStart) -{ - // Once a deadline is due, prevent newly recorded preparation work from - // being submitted ahead of the transport copy. The short submission gate - // allows a preparation which is already submitting to finish first. - CPublishPending publishPending( - &m_copySubmitLock, &m_publishPending, m_copySubmitEvent.Get()); - CSRWSharedLock pipelineLock(&m_pipelineLock); - - int selectedCandidate = -1; - uint64_t newestSequence = 0; - - AcquireSRWLockExclusive(&m_candidateLock); - for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) - if (m_candidates[i].state == CANDIDATE_READY && - (selectedCandidate < 0 || - m_candidates[i].sequence > newestSequence)) - { - selectedCandidate = static_cast(i); - newestSequence = m_candidates[i].sequence; - } - - if (selectedCandidate >= 0) - m_candidates[static_cast(selectedCandidate)].state = - CANDIDATE_PUBLISHING; - ReleaseSRWLockExclusive(&m_candidateLock); - - if (selectedCandidate < 0) - return false; - const unsigned candidateIndex = - static_cast(selectedCandidate); - - const auto restoreCandidates = [this, candidateIndex]() - { - AcquireSRWLockExclusive(&m_candidateLock); - if (m_candidates[candidateIndex].state == CANDIDATE_PUBLISHING) - m_candidates[candidateIndex].state = CANDIDATE_READY; - ReleaseSRWLockExclusive(&m_candidateLock); - SignalCandidateState(); - }; - - AcquireSRWLockShared(&m_candidateLock); - const bool candidateValid = - m_candidates[candidateIndex].state == CANDIDATE_PUBLISHING && - m_candidates[candidateIndex].resource.Get(); - ReleaseSRWLockShared(&m_candidateLock); - if (!candidateValid) - { - restoreCandidates(); - return false; - } - - FrameCandidate& candidate = m_candidates[candidateIndex]; - CPostProcessor& postProcessor = m_postProcessors[candidateIndex]; - const uint64_t candidateSequence = candidate.sequence; - - auto buffer = m_devContext->PrepareFrameBuffer( - candidate.pitch, - candidate.srcFormat, - candidate.dstFormat, - candidate.dirtyRects, - candidate.nbDirtyRects, - schedule); - if (!buffer.mem) - { - restoreCandidates(); - return false; - } - - CFrameBufferResource * fbRes = - m_fbPool.Get(buffer, candidate.frameSize); - if (!fbRes) - { - m_devContext->AbortFrameBuffer(buffer.frameIndex); - restoreCandidates(); - DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool"); - SetFullPendingDamage(); - return false; - } - - CD3D12CommandSlot * copySlot = - m_dx12Device->GetCopySlot(candidateIndex); - if (!copySlot) - { - m_devContext->AbortFrameBuffer(buffer.frameIndex); - restoreCandidates(); - DEBUG_ERROR("Failed to get a copy CommandSlot for publication"); - SetFullPendingDamage(); - return false; - } - - RECT previousDirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbPreviousDirtyRects = 0; - AcquireSRWLockShared(&m_damageLock); - nbPreviousDirtyRects = m_nbDirtyRects; - if (nbPreviousDirtyRects) - memcpy(previousDirtyRects, m_dirtyRects, - nbPreviousDirtyRects * sizeof(*previousDirtyRects)); - ReleaseSRWLockShared(&m_damageLock); - - RECT copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {}; - unsigned nbCopyDirtyRects = 0; - const bool fullCopy = BuildCopyDamage(postProcessor, buffer.fullCopy, - previousDirtyRects, nbPreviousDirtyRects, - candidate.dirtyRects, candidate.nbDirtyRects, - candidate.dstFormat.width, candidate.dstFormat.height, - copyDirtyRects, &nbCopyDirtyRects); - - fbRes->SetTiming( - candidate.captureTime, candidate.postProcessStart, publishStart); - fbRes->SetCandidateIndex(candidateIndex); - fbRes->SetPostProcessSample( - candidate.timingEffectIndex, candidate.timingToken, fullCopy); - copySlot->SetCompletionCallback(&CompletionFunction, this, fbRes); - - copySlot->BeginTiming(); - postProcessor.CopyFromCandidate( - copySlot->GetGfxList(), fbRes->Get().Get(), candidate.resource.Get(), - copyDirtyRects, nbCopyDirtyRects, fullCopy); - copySlot->EndTiming(); - - // Reserve the LGMP delivery or retained-frame slot before submitting the - // copy. This makes failure recoverable without racing a very fast GPU - // completion callback. - bool deliveredToOwner; - if (!m_devContext->PublishFrameBuffer( - buffer.frameIndex, schedule, deliveredToOwner)) - { - copySlot->Cancel(); - m_devContext->AbortFrameBuffer(buffer.frameIndex); - restoreCandidates(); - return false; - } - CFrameScheduler::Schedule frameSchedule = schedule; - // Phase accounting must never hold up D3D submission. Keep the immutable - // delivery identity and discard only this feedback sample on contention. - if (!deliveredToOwner || - !m_devContext->TryFrameSubmitted(buffer.frameIndex, schedule)) - frameSchedule.phaseEligible = false; - fbRes->SetSchedule(frameSchedule); - - // Retire the candidate damage before submission. The completion callback - // may run before Execute returns and make this candidate reusable. - AcquireSRWLockExclusive(&m_damageLock); - if (candidate.nbDirtyRects) - memcpy(m_dirtyRects, candidate.dirtyRects, - candidate.nbDirtyRects * sizeof(*m_dirtyRects)); - m_nbDirtyRects = candidate.nbDirtyRects; - CandidateDamageTail& tail = m_candidateDamageTail[candidateIndex]; - if (tail.active && tail.ownerSequence == candidateSequence) - { - m_hasPendingDamage = tail.hasDamage; - m_nbPendingDirtyRects = tail.nbDirtyRects; - if (tail.hasDamage && tail.nbDirtyRects) - memcpy(m_pendingDirtyRects, tail.dirtyRects, - tail.nbDirtyRects * sizeof(*m_pendingDirtyRects)); - tail.ownerSequence = 0; - tail.active = false; - } - ReleaseSRWLockExclusive(&m_damageLock); - - const bool submitted = copySlot->Execute(); - publishPending.Clear(); - if (!submitted) - { - // The logical damage state was advanced before submission. Force a full - // repair whether submission failed or its callback reported the failure. - SetFullPendingDamage(); - AcquireSRWLockShared(&m_candidateLock); - const bool callbackPending = - candidate.state == CANDIDATE_PUBLISHING; - ReleaseSRWLockShared(&m_candidateLock); - if (callbackPending && !copySlot->HasSubmittedWork()) - { - m_devContext->FailFrameBuffer(buffer.frameIndex); - ReleaseCandidate(candidateIndex); - } - m_devContext->ForceFrame(); - - SignalCandidateState(); - return false; - } - - m_devContext->CommitFrameBuffer( - buffer.frameIndex, schedule, periodic, deliveredToOwner); - - unsigned superseded = 0; - AcquireSRWLockExclusive(&m_candidateLock); - for (FrameCandidate& ready : m_candidates) - if (ready.state == CANDIDATE_READY && - ready.sequence < candidateSequence) - { - ready.state = CANDIDATE_FREE; - ++superseded; - } - ReleaseSRWLockExclusive(&m_candidateLock); - for (unsigned i = 0; i < superseded; ++i) - m_devContext->FrameSuperseded(); - SignalCandidateState(); - return true; -} - #ifdef HAS_IDDCX_110 void CSwapChainProcessor::UpdateHDRMetadata(const IDDCX_METADATA2& metadata) { @@ -1737,271 +738,6 @@ bool CSwapChainProcessor::GetContentHDRMetadata(D12FrameFormat& format) const #endif } -bool CSwapChainProcessor::PublishSoftwareFrame(CInteropResource * srcRes, - const D12FrameFormat& srcFormat, uint64_t captureTime, - uint64_t postProcessStart, bool noImageUpdate) -{ - CSRWSharedLock pipelineLock(&m_pipelineLock); - CPostProcessor& postProcessor = m_postProcessors[0]; - const D12FrameFormat& dstFormat = postProcessor.GetOutputFormat(); - - D3D12_RESOURCE_DESC textureDesc = {}; - const D3D12_RESOURCE_DESC * textureDescPtr = nullptr; - unsigned pitch = postProcessor.GetOutputPitch(); - size_t frameSize = postProcessor.GetOutputSize(); - if (m_directSoftwareTexture && - dstFormat.desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && - dstFormat.desc.Width && dstFormat.desc.Height && - dstFormat.desc.Format != DXGI_FORMAT_UNKNOWN) - { - textureDesc = dstFormat.desc; - textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; - textureDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; - textureDesc.DepthOrArraySize = 1; - textureDesc.MipLevels = 1; - textureDesc.SampleDesc.Count = 1; - textureDesc.SampleDesc.Quality = 0; - textureDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - textureDesc.Flags = - D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER; - - D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {}; - m_dx12Device->GetDevice()->GetCopyableFootprints( - &textureDesc, 0, 1, 0, &layout, nullptr, nullptr, nullptr); - const unsigned texturePitch = layout.Footprint.RowPitch; - if (texturePitch && textureDesc.Height <= - m_devContext->GetMaxFrameSize() / texturePitch) - { - pitch = texturePitch; - frameSize = (size_t)pitch * textureDesc.Height; - textureDescPtr = &textureDesc; - } - else - { - m_directSoftwareTexture = false; - DEBUG_WARN("IVSHMEM texture layout does not fit the framebuffer"); - } - } - else if (m_directSoftwareTexture) - { - m_directSoftwareTexture = false; - DEBUG_WARN("Post-processor output cannot use an IVSHMEM texture"); - } - - if (!pitch || !frameSize || frameSize > m_devContext->GetMaxFrameSize()) - { - DEBUG_ERROR("Software frame does not fit in shared memory"); - SetFullPendingDamage(); - return false; - } - - // Static-desktop re-encodes carry no new image. The retained frame can be - // republished without spending GPU or memory bandwidth on another copy. A - // pending full-damage request still passes through so startup, format - // changes, and failed copies can establish a valid replacement. - if (noImageUpdate && !HasPendingDamage()) - return true; - - for (;;) - { - CFrameScheduler::Schedule commitSchedule = {}; - CFrameScheduler::Schedule deliverySchedule = {}; - CIndirectDeviceContext::PreparedFrameBuffer buffer = {}; - CD3D12CommandSlot * copySlot = nullptr; - RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbDirtyRects = 0; - bool hasDamage = false; - - uint64_t ignoredTarget = 0; - bool ignoredPeriodic = false; - bool ignoredRepublish = false; - m_devContext->GetPublishTarget(CFrameScheduler::Nanotime(), - ignoredTarget, commitSchedule, ignoredPeriodic, ignoredRepublish); - deliverySchedule = commitSchedule; - deliverySchedule.deliveryDeadlineSerial = 0; - deliverySchedule.phaseEligible = false; - - m_devContext->ProcessFrameQueue(); - // Ordinary frames never displace the retained fallback. A must-not-drop - // static replacement may reuse it once every transport reference is gone. - if (!m_devContext->FrameBufferAvailable( - deliverySchedule, noImageUpdate)) - { - if (!noImageUpdate) - { - m_devContext->FrameSuperseded(); - return true; - } - - if (WaitForSingleObject(m_terminateEvent.Get(), 1) == WAIT_OBJECT_0) - return true; - continue; - } - - copySlot = m_dx12Device->GetCopySlot(); - if (!copySlot) - { - if (!noImageUpdate) - { - m_devContext->FrameSuperseded(); - return true; - } - - if (WaitForSingleObject(m_terminateEvent.Get(), 1) == WAIT_OBJECT_0) - return true; - continue; - } - - hasDamage = TakePendingDamage( - currentDirtyRects, &nbDirtyRects); - ClipDirtyRects(currentDirtyRects, &nbDirtyRects, - dstFormat.width, dstFormat.height); - buffer = m_devContext->PrepareFrameBuffer( - pitch, srcFormat, dstFormat, - currentDirtyRects, nbDirtyRects, deliverySchedule, - noImageUpdate); - if (!buffer.mem) - { - copySlot->Cancel(); - RestorePendingDamage( - currentDirtyRects, nbDirtyRects, hasDamage); - if (!noImageUpdate) - { - m_devContext->FrameSuperseded(); - return true; - } - - if (WaitForSingleObject(m_terminateEvent.Get(), 1) == WAIT_OBJECT_0) - return true; - continue; - } - - CFrameBufferResource * fbRes = nullptr; - if (textureDescPtr) - { - fbRes = m_fbPool.Get(buffer, frameSize, textureDescPtr); - if (!fbRes) - { - const HRESULT deviceStatus = - m_dx12Device->GetDevice()->GetDeviceRemovedReason(); - if (FAILED(deviceStatus)) - { - copySlot->Cancel(); - m_devContext->AbortFrameBuffer(buffer.frameIndex); - RestorePendingDamage( - currentDirtyRects, nbDirtyRects, hasDamage); - DEBUG_ERROR_HR(deviceStatus, - "D3D12 device removed while creating an IVSHMEM texture"); - SetFullPendingDamage(); - return false; - } - - m_directSoftwareTexture = false; - textureDescPtr = nullptr; - DEBUG_WARN( - "IVSHMEM textures unavailable; using a direct buffer copy"); - } - } - - if (!fbRes) - fbRes = m_fbPool.Get(buffer, frameSize); - if (!fbRes) - { - copySlot->Cancel(); - m_devContext->AbortFrameBuffer(buffer.frameIndex); - RestorePendingDamage( - currentDirtyRects, nbDirtyRects, hasDamage); - DEBUG_ERROR("Failed to get a framebuffer for software capture"); - SetFullPendingDamage(); - return false; - } - - if (!srcRes->Signal() || !srcRes->Sync(*copySlot)) - { - copySlot->Cancel(); - m_devContext->AbortFrameBuffer(buffer.frameIndex); - RestorePendingDamage( - currentDirtyRects, nbDirtyRects, hasDamage); - SetFullPendingDamage(); - return false; - } - - RECT previousDirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbPreviousDirtyRects = 0; - AcquireSRWLockShared(&m_damageLock); - nbPreviousDirtyRects = m_nbDirtyRects; - if (nbPreviousDirtyRects) - memcpy(previousDirtyRects, m_dirtyRects, - nbPreviousDirtyRects * sizeof(*previousDirtyRects)); - ReleaseSRWLockShared(&m_damageLock); - - RECT copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {}; - unsigned nbCopyDirtyRects = 0; - const bool fullCopy = BuildCopyDamage( - postProcessor, buffer.fullCopy, - previousDirtyRects, nbPreviousDirtyRects, - currentDirtyRects, nbDirtyRects, - dstFormat.width, dstFormat.height, - copyDirtyRects, &nbCopyDirtyRects); - - const unsigned bytesPerPixel = - dstFormat.format == FRAME_TYPE_RGBA16F ? 8 : 4; - const uint64_t copyStart = CFrameScheduler::Nanotime(); - fbRes->SetTiming(captureTime, postProcessStart, copyStart); - fbRes->SetSchedule(deliverySchedule); - fbRes->SetCopyDamage(copyDirtyRects, nbCopyDirtyRects, - fullCopy, pitch, bytesPerPixel); - fbRes->ResetCompletion(); - copySlot->SetCompletionCallback( - &SoftwareCompletionFunction, this, fbRes); - copySlot->BeginTiming(); - postProcessor.CopyToFrameBuffer(copySlot->GetGfxList(), - fbRes->Get().Get(), srcRes->GetRes().Get(), - copyDirtyRects, nbCopyDirtyRects, fullCopy); - copySlot->EndTiming(); - - bool deliveredToOwner; - if (!m_devContext->PublishFrameBuffer( - buffer.frameIndex, deliverySchedule, deliveredToOwner)) - { - copySlot->Cancel(); - m_devContext->AbortFrameBuffer(buffer.frameIndex); - RestorePendingDamage( - currentDirtyRects, nbDirtyRects, hasDamage); - if (!noImageUpdate) - { - m_devContext->FrameSuperseded(); - return true; - } - - if (WaitForSingleObject(m_terminateEvent.Get(), 1) == WAIT_OBJECT_0) - return true; - continue; - } - - CommitFrameDamage(currentDirtyRects, nbDirtyRects); - if (!copySlot->Execute()) - { - const bool submittedWork = copySlot->HasSubmittedWork(); - const bool completionHandled = fbRes->CompletionHandled(); - if (!submittedWork && !completionHandled) - m_devContext->FailFrameBuffer(buffer.frameIndex); - RestorePendingDamage( - currentDirtyRects, nbDirtyRects, hasDamage); - if (!submittedWork && !completionHandled) - { - SetFullPendingDamage(); - m_devContext->ForceFrame(); - } - return false; - } - - m_devContext->CommitFrameBuffer( - buffer.frameIndex, commitSchedule, false, deliveredToOwner); - return true; - } -} - bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, unsigned moveRegionCount, DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel, @@ -2057,7 +793,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer { DEBUG_ERROR_HR(hr, "Failed to obtain the ID3D11Texture2D from the acquiredBuffer"); - SetFullPendingDamage(); + m_frameProcessor->SetFullDamage(); return false; } @@ -2065,7 +801,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (!srcRes) { DEBUG_ERROR("Failed to get a CInteropResource from the pool"); - SetFullPendingDamage(); + m_frameProcessor->SetFullDamage(); return false; } @@ -2078,7 +814,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (!noImageUpdate) { m_devContext->ObserveFrame(postProcessStart); - AccumulateFrameDamage( + m_frameProcessor->AccumulateDamage( srcRes->GetDirtyRects(), srcRes->GetDirtyRectCount()); } @@ -2086,7 +822,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer srcFormat.desc = srcDesc; srcFormat.width = (unsigned)srcDesc.Width; srcFormat.height = srcDesc.Height; - srcFormat.format = GetFrameType(srcDesc.Format); + srcFormat.format = CFrameProcessorUtil::GetFrameType(srcDesc.Format); srcFormat.sdrWhiteLevel = sdrWhiteLevel; srcFormat.colorTransform = m_devContext->GetColorTransform(); @@ -2173,7 +909,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer m_postProcessors[0].Update(srcFormat); frameMetadataChanged = noImageUpdate && - FrameMetadataChanged( + CFrameProcessorUtil::FrameMetadataChanged( m_postProcessors[0].GetOutputFormat(), srcFormat); for (const CPostProcessor& postProcessor : m_postProcessors) @@ -2183,16 +919,12 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer break; } - // A format change can replace resources referenced by either retained - // candidate. Stop publication, drain both queues, then invalidate them. + // A format change can replace resources referenced by in-flight work. + // Drain both queues before invalidating the selected frame processor. if (needsReconfigure) { - AcquireSRWLockExclusive(&m_damageLock); - m_nbDirtyRects = 0; - ReleaseSRWLockExclusive(&m_damageLock); - SetFullPendingDamage(); m_dx12Device->WaitForIdle(); - ResetCandidates(); + m_frameProcessor->ResetPipeline(); } bool configurationStable = false; @@ -2203,7 +935,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer bool formatChanged = false; if (!m_postProcessors[i].Configure(srcFormat, &formatChanged)) { - SetFullPendingDamage(); + m_frameProcessor->SetFullDamage(); return false; } @@ -2223,23 +955,18 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (!configurationStable) { DEBUG_ERROR("Post processor configuration did not stabilize"); - SetFullPendingDamage(); + m_frameProcessor->SetFullDamage(); return false; } if (postProcessFormatChanged) - { - AcquireSRWLockExclusive(&m_damageLock); - m_nbDirtyRects = 0; - ReleaseSRWLockExclusive(&m_damageLock); - SetFullPendingDamage(); - } + m_frameProcessor->Invalidate(); else if (frameMetadataChanged) - SetFullPendingDamage(); + m_frameProcessor->SetFullDamage(); requiresFullDamage = m_postProcessors[0].RequiresFullDamage(); if (requiresFullDamage) - SetFullPendingDamage(); + m_frameProcessor->SetFullDamage(); m_postProcessors[0].GetTimingToken( &timingEffectIndex, &timingToken); @@ -2248,215 +975,17 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged) m_devContext->ForceFrame(); - if (m_dx11Device->IsSoftware()) - return PublishSoftwareFrame(srcRes, srcFormat, - captureTime, postProcessStart, noImageUpdate); - - // Always prepare the requested static-desktop re-encode. An older - // publication can still fail after this frame is acquired, so deciding - // solely from the current pending-damage state can lose the final update. - int selectedCandidate = AcquireCandidate(timingToken != 0, !noImageUpdate); - while (selectedCandidate < 0 && noImageUpdate) + const FrameSubmission submission = { - HANDLE waitHandles[] = - { - m_terminateEvent.Get(), - m_candidateAvailableEvent.Get(), - }; - const DWORD waitResult = WaitForMultipleObjects( - ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); - if (waitResult == WAIT_OBJECT_0) - return true; - if (waitResult != WAIT_OBJECT_0 + 1) - { - DEBUG_ERROR_HR(HRESULT_FROM_WIN32(GetLastError()), - "Failed while waiting for a frame candidate"); - return false; - } - - selectedCandidate = AcquireCandidate(timingToken != 0, false); - } - if (selectedCandidate < 0) - { - m_devContext->FrameSuperseded(); - return true; - } - const unsigned candidateIndex = - static_cast(selectedCandidate); - - FrameCandidate& candidate = m_candidates[candidateIndex]; - - CSRWSharedLock pipelineLock(&m_pipelineLock); - CPostProcessor& postProcessor = m_postProcessors[candidateIndex]; - const D12FrameFormat& dstFormat = postProcessor.GetOutputFormat(); - - RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbDirtyRects = 0; - AcquireSRWLockExclusive(&m_damageLock); - if (m_hasPendingDamage) - { - nbDirtyRects = m_nbPendingDirtyRects; - if (nbDirtyRects) - memcpy(currentDirtyRects, m_pendingDirtyRects, - nbDirtyRects * sizeof(*currentDirtyRects)); - } - CandidateDamageTail& tail = m_candidateDamageTail[candidateIndex]; - tail.ownerSequence = candidate.sequence; - tail.nbDirtyRects = 0; - tail.hasDamage = false; - tail.active = true; - ReleaseSRWLockExclusive(&m_damageLock); - - CD3D12CommandSlot * copySlot = - m_dx12Device->GetCopySlot(candidateIndex); - if (!copySlot) - { - ReleaseCandidate(candidateIndex); - DEBUG_ERROR("Failed to get a copy CommandSlot"); - SetFullPendingDamage(); - return false; - } - // Candidate and copy-slot acquisition are common to both benchmark modes. - const uint64_t timingStart = timingToken ? - CFrameScheduler::Nanotime() : 0; - - ComPtr copySrcResource = srcRes->GetRes(); - CD3D12CommandSlot * computeSlot = nullptr; - if (postProcessor.HasActiveEffects()) - { - computeSlot = m_dx12Device->GetComputeSlot(candidateIndex); - if (!computeSlot) - { - copySlot->Cancel(); - ReleaseCandidate(candidateIndex); - DEBUG_ERROR("Failed to get a compute CommandSlot"); - SetFullPendingDamage(); - return false; - } - } - - /** - * Even though we have not performed any copy/draw operations we still need - * to use a fence. Because we share this texture with DirectX12 it is able to - * read from it before IddCx has finished updating it. - */ - if (!srcRes->Signal()) - { - if (computeSlot) - computeSlot->Cancel(); - copySlot->Cancel(); - ReleaseCandidate(candidateIndex); - SetFullPendingDamage(); - return false; - } - - if (computeSlot) - { - if (!srcRes->Sync(*computeSlot)) - { - computeSlot->Cancel(); - copySlot->Cancel(); - ReleaseCandidate(candidateIndex); - SetFullPendingDamage(); - return false; - } - - copySrcResource = postProcessor.Run( - computeSlot->GetGfxList(), copySrcResource, - currentDirtyRects, &nbDirtyRects); - if (!copySrcResource) - { - computeSlot->Cancel(); - copySlot->Cancel(); - ReleaseCandidate(candidateIndex); - DEBUG_ERROR("Post processor returned no output resource"); - SetFullPendingDamage(); - return false; - } - - if (!computeSlot->Execute()) - { - copySlot->Cancel(); - m_dx12Device->WaitForIdle(); - ReleaseCandidate(candidateIndex); - SetFullPendingDamage(); - return false; - } - - if (!copySlot->WaitFor(*computeSlot)) - { - copySlot->Cancel(); - m_dx12Device->WaitForIdle(); - ReleaseCandidate(candidateIndex); - DEBUG_ERROR("Failed to queue compute synchronization"); - SetFullPendingDamage(); - return false; - } - } - else if (!srcRes->Sync(*copySlot)) - { - copySlot->Cancel(); - ReleaseCandidate(candidateIndex); - DEBUG_ERROR("Failed to queue source synchronization"); - SetFullPendingDamage(); - return false; - } - - ClipDirtyRects(currentDirtyRects, &nbDirtyRects, - dstFormat.width, dstFormat.height); - - const size_t frameSize = postProcessor.GetOutputSize(); - if (!EnsureCandidateResource(candidateIndex, frameSize)) - { - copySlot->Cancel(); - if (computeSlot) - m_dx12Device->WaitForIdle(); - ReleaseCandidate(candidateIndex); - SetFullPendingDamage(); - return false; - } - - candidate.srcFormat = srcFormat; - candidate.dstFormat = dstFormat; - candidate.nbDirtyRects = nbDirtyRects; - candidate.pitch = postProcessor.GetOutputPitch(); - candidate.frameSize = frameSize; - candidate.captureTime = captureTime; - candidate.postProcessStart = postProcessStart; - candidate.prepareCopyStart = CFrameScheduler::Nanotime(); - candidate.prepareReady = 0; - candidate.prepareGPUStart = 0; - candidate.prepareGPUEnd = 0; - candidate.timingStart = timingStart; - candidate.prepareTimingValid = false; - if (nbDirtyRects) - memcpy(candidate.dirtyRects, currentDirtyRects, - nbDirtyRects * sizeof(*candidate.dirtyRects)); - candidate.timingEffectIndex = timingEffectIndex; - candidate.timingToken = timingToken; - - copySlot->SetCompletionCallback( - &CandidateCompletionFunction, this, &candidate); - copySlot->BeginTiming(); - postProcessor.CopyToCandidate( - copySlot->GetGfxList(), candidate.resource.Get(), - copySrcResource.Get()); - copySlot->EndTiming(); - - if (!ExecuteCandidateCopy(copySlot)) - { - if (!copySlot->HasSubmittedWork()) - { - if (computeSlot) - m_dx12Device->WaitForIdle(); - ReleaseCandidate(candidateIndex); - } - SetFullPendingDamage(); - m_devContext->ForceFrame(); - return false; - } - - return true; + srcRes, + srcFormat, + captureTime, + postProcessStart, + timingEffectIndex, + timingToken, + noImageUpdate, + }; + return m_frameProcessor->Submit(submission); } DWORD CALLBACK CSwapChainProcessor::_CursorThread(LPVOID arg) diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index 82d4e885..c6524b40 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -24,7 +24,7 @@ #include "CD3D12Device.h" #include "CIndirectDeviceContext.h" #include "CInteropResourcePool.h" -#include "CFrameBufferPool.h" +#include "CFrameProcessor.h" #include "CPostProcessor.h" #include @@ -35,8 +35,6 @@ using namespace Microsoft::WRL; -#define STAGING_TEXTURES 3 - class CIndirectMonitorContext; class CSwapChainProcessor @@ -52,69 +50,14 @@ private: std::shared_ptr m_dx12Device; HANDLE m_newFrameEvent; - CInteropResourcePool m_resPool; - CFrameBufferPool m_fbPool; - CPostProcessor m_postProcessors[LGMP_Q_FRAME_LEN]; - - enum CandidateState - { - CANDIDATE_FREE, - CANDIDATE_PREPARING, - CANDIDATE_READY, - CANDIDATE_PUBLISHING, - }; - - struct FrameCandidate - { - CandidateState state = CANDIDATE_FREE; - ComPtr resource; - D12FrameFormat srcFormat = {}; - D12FrameFormat dstFormat = {}; - RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbDirtyRects = 0; - unsigned pitch = 0; - size_t frameSize = 0; - uint64_t sequence = 0; - uint64_t captureTime = 0; - uint64_t postProcessStart = 0; - uint64_t prepareCopyStart = 0; - uint64_t prepareReady = 0; - uint64_t prepareGPUStart = 0; - uint64_t prepareGPUEnd = 0; - uint64_t timingStart = 0; - unsigned timingEffectIndex = 0; - uint64_t timingToken = 0; - bool prepareTimingValid = false; - }; - - struct CandidateDamageTail - { - uint64_t ownerSequence = 0; - RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbDirtyRects = 0; - bool hasDamage = false; - bool active = false; - }; - - // An active tail records only damage received after its candidate snapshot. - FrameCandidate m_candidates[LGMP_Q_FRAME_LEN]; - CandidateDamageTail m_candidateDamageTail[LGMP_Q_FRAME_LEN]; - SRWLOCK m_candidateLock = SRWLOCK_INIT; - SRWLOCK m_damageLock = SRWLOCK_INIT; + CInteropResourcePool m_resPool; + CPostProcessor m_postProcessors[LGMP_Q_FRAME_LEN]; + std::unique_ptr m_frameProcessor; // Reconfiguration is exclusive while per-candidate recording is shared. - SRWLOCK m_pipelineLock = SRWLOCK_INIT; - // Capture holds this only across submission; the publisher uses it to - // close the gate before recording deadline work. - SRWLOCK m_copySubmitLock = SRWLOCK_INIT; - uint64_t m_candidateSequence = 0; - bool m_publishPending = false; - bool m_directSoftwareTexture = false; + SRWLOCK m_pipelineLock = SRWLOCK_INIT; Wrappers::HandleT m_thread[3]; Wrappers::Event m_terminateEvent; - Wrappers::Event m_candidateEvent; - Wrappers::Event m_candidateAvailableEvent; - Wrappers::Event m_copySubmitEvent; Wrappers::HandleT m_publishTimer; Wrappers::Event m_cursorDataEvent; @@ -122,20 +65,6 @@ private: DWORD m_lastShapeId = 0; std::atomic m_sdrWhiteLevel { KVMFR_SDR_WHITE_LEVEL_DEFAULT }; - // Logical output-space damage from the previous published frame. The - // shared-memory frame buffers alternate, so this must be copied along with - // the current damage to bring the older target buffer up to date. - RECT m_dirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned m_nbDirtyRects = 0; - - // Source-space damage accumulated since the last published frame. Frames - // can be dropped while the LGMP queue is full, but their damage must be - // included in the next frame sent to the client. A count of zero represents - // full-frame damage when m_hasPendingDamage is set. - RECT m_pendingDirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned m_nbPendingDirtyRects = 0; - bool m_hasPendingDamage = true; - #ifdef HAS_IDDCX_110 // The per-frame metadata stream can select the monitor default, provide a // replacement block, or retain the selection from the previous frame. @@ -151,43 +80,14 @@ private: static DWORD CALLBACK _PublisherThread(LPVOID arg); void PublisherThread(); - bool PublishNewestCandidate( - const CFrameScheduler::Schedule& schedule, bool periodic, - uint64_t publishStart); - bool HasReadyCandidate(); - int AcquireCandidate(bool exclusiveSample, bool allowSupersede); - void ReleaseCandidate(unsigned candidateIndex); - bool EnsureCandidateResource( - unsigned candidateIndex, size_t frameSize); - void ResetCandidates(); - void SignalCandidateState(); - bool ExecuteCandidateCopy(CD3D12CommandSlot * copySlot); - static DWORD CALLBACK _CursorThread(LPVOID arg); bool QueryHWCursor(); void CursorThread(); - static void CompletionFunction( - CD3D12CommandSlot * slot, bool result, void * param1, void * param2); - static void CandidateCompletionFunction( - CD3D12CommandSlot * slot, bool result, void * param1, void * param2); - static void SoftwareCompletionFunction( - CD3D12CommandSlot * slot, bool result, void * param1, void * param2); - void AccumulateFrameDamage(const RECT * dirtyRects, unsigned nbDirtyRects); - bool HasPendingDamage(); - bool TakePendingDamage(RECT dirtyRects[], unsigned * nbDirtyRects); - void RestorePendingDamage(const RECT dirtyRects[], - unsigned nbDirtyRects, bool hasDamage); - void CommitFrameDamage( - const RECT dirtyRects[], unsigned nbDirtyRects); - void SetFullPendingDamage(); #ifdef HAS_IDDCX_110 void UpdateHDRMetadata(const IDDCX_METADATA2& metadata); #endif bool GetContentHDRMetadata(D12FrameFormat& format) const; - bool PublishSoftwareFrame(CInteropResource * srcRes, - const D12FrameFormat& srcFormat, uint64_t captureTime, - uint64_t postProcessStart, bool noImageUpdate); bool SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, unsigned moveRegionCount, DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel, @@ -200,7 +100,4 @@ public: HANDLE newFrameEvent); ~CSwapChainProcessor(); bool Start(); - - CIndirectDeviceContext * GetDevice() { return m_devContext; } - std::shared_ptr GetD3D12Device() { return m_dx12Device; } }; diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index a2c36b3d..26f5df8d 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -29,7 +29,10 @@ + + + @@ -44,6 +47,7 @@ + @@ -56,7 +60,10 @@ + + + @@ -70,6 +77,7 @@ + diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index ee0c33c8..0e8524ac 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -82,6 +82,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + Header Files @@ -97,6 +106,9 @@ Header Files + + Header Files + Header Files @@ -161,6 +173,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + Source Files @@ -172,6 +193,9 @@ Source Files + + Source Files + Source Files