diff --git a/idd/LGIdd/CD3D12CommandQueue.cpp b/idd/LGIdd/CD3D12CommandQueue.cpp index 98cff4a3..f8693696 100644 --- a/idd/LGIdd/CD3D12CommandQueue.cpp +++ b/idd/LGIdd/CD3D12CommandQueue.cpp @@ -538,7 +538,22 @@ CD3D12CommandSlot * CD3D12CommandQueue::Acquire(UINT slotIndex) CD3D12CommandSlot * CD3D12CommandQueue::Acquire() { - return Acquire(0); + if (!m_slotCount) + return nullptr; + + // The unindexed path is used for immediate software publication. Keep at + // most one copy in flight so a newer frame is dropped instead of queued + // behind bandwidth-bound work which is already stale. + CD3D12CommandSlot& slot = m_slots[0]; + if (slot.Acquire()) + return &slot; + + // Complete already-fenced work without waiting for callback dispatch. + slot.OnCompletion(false); + if (slot.Acquire()) + return &slot; + + return nullptr; } void CD3D12CommandQueue::WaitForIdle() diff --git a/idd/LGIdd/CD3D12Device.cpp b/idd/LGIdd/CD3D12Device.cpp index bdb5fb99..1ac701de 100644 --- a/idd/LGIdd/CD3D12Device.cpp +++ b/idd/LGIdd/CD3D12Device.cpp @@ -116,6 +116,9 @@ CD3D12Device::InitResult CD3D12Device::Init(CIVSHMEM &ivshmem, D3D12_HEAP_DESC heapDesc = m_ivshmemHeap->GetDesc(); alignSize = heapDesc.Alignment; + m_ivshmemTextureSupported = + (heapDesc.Flags & D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER) && + !(heapDesc.Flags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES); // test that the heap is usable if (!HeapTest()) @@ -128,6 +131,8 @@ CD3D12Device::InitResult CD3D12Device::Init(CIVSHMEM &ivshmem, } DEBUG_INFO("Using IVSHMEM as a D3D12Heap"); + if (!m_ivshmemTextureSupported) + DEBUG_WARN("IVSHMEM heap does not support placed textures"); } if (!m_copyQueue.Init(m_device.Get(), D3D12_COMMAND_LIST_TYPE_COPY, @@ -207,6 +212,11 @@ CD3D12CommandSlot * CD3D12Device::GetCopySlot(unsigned frameIndex) return m_copyQueue.Acquire(frameIndex); } +CD3D12CommandSlot * CD3D12Device::GetCopySlot() +{ + return m_copyQueue.Acquire(); +} + CD3D12CommandSlot * CD3D12Device::GetComputeSlot(unsigned frameIndex) { if (!m_computeEnabled) diff --git a/idd/LGIdd/CD3D12Device.h b/idd/LGIdd/CD3D12Device.h index 60ae39ef..f91e5377 100644 --- a/idd/LGIdd/CD3D12Device.h +++ b/idd/LGIdd/CD3D12Device.h @@ -51,7 +51,8 @@ struct CD3D12Device CD3D12CommandQueue m_copyQueue; CD3D12CommandQueue m_computeQueue; - bool m_computeEnabled = false; + bool m_computeEnabled = false; + bool m_ivshmemTextureSupported = false; bool HeapTest(); @@ -78,7 +79,9 @@ struct CD3D12Device ComPtr GetDevice() { return m_device; } ComPtr GetHeap() { return m_ivshmemHeap; } bool IsIndirectCopy() { return m_indirectCopy; } + bool CanUseIVSHMEMTexture() { return m_ivshmemTextureSupported; } + CD3D12CommandSlot * GetCopySlot (); CD3D12CommandSlot * GetCopySlot (unsigned frameIndex); CD3D12CommandSlot * GetComputeSlot(unsigned frameIndex); }; diff --git a/idd/LGIdd/CFrameBufferPool.cpp b/idd/LGIdd/CFrameBufferPool.cpp index 43752899..183d39bb 100644 --- a/idd/LGIdd/CFrameBufferPool.cpp +++ b/idd/LGIdd/CFrameBufferPool.cpp @@ -36,13 +36,14 @@ void CFrameBufferPool::Reset() CFrameBufferResource * CFrameBufferPool::Get( const CIndirectDeviceContext::PreparedFrameBuffer& buffer, - size_t minSize) + size_t minSize, const D3D12_RESOURCE_DESC * textureDesc) { if (buffer.frameIndex > ARRAYSIZE(m_buffers) - 1) return nullptr; CFrameBufferResource * fbr = &m_buffers[buffer.frameIndex]; - if (!fbr->Init(m_swapChain, buffer.frameIndex, buffer.mem, minSize)) + if (!fbr->Init(m_swapChain, buffer.frameIndex, buffer.mem, + minSize, textureDesc)) return nullptr; return fbr; diff --git a/idd/LGIdd/CFrameBufferPool.h b/idd/LGIdd/CFrameBufferPool.h index acef28da..ae528516 100644 --- a/idd/LGIdd/CFrameBufferPool.h +++ b/idd/LGIdd/CFrameBufferPool.h @@ -38,5 +38,6 @@ class CFrameBufferPool CFrameBufferResource* CFrameBufferPool::Get( const CIndirectDeviceContext::PreparedFrameBuffer& buffer, - size_t minSize); + size_t minSize, + const D3D12_RESOURCE_DESC * textureDesc = nullptr); }; diff --git a/idd/LGIdd/CFrameBufferResource.cpp b/idd/LGIdd/CFrameBufferResource.cpp index 3f28fffb..677c7187 100644 --- a/idd/LGIdd/CFrameBufferResource.cpp +++ b/idd/LGIdd/CFrameBufferResource.cpp @@ -22,7 +22,28 @@ #include "CSwapChainProcessor.h" #include "CDebug.h" -bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameIndex, uint8_t * base, size_t size) +#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, + const D3D12_RESOURCE_DESC * textureDesc) { m_frameIndex = frameIndex; @@ -33,8 +54,52 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameI return false; } - // nothing to do if the resource already exists and is large enough - if (m_base == base && m_size >= size) + const auto dx12 = swapChain->GetD3D12Device(); + const bool indirect = dx12->IsIndirectCopy(); + const ResourceType type = textureDesc ? + RESOURCE_TEXTURE : RESOURCE_BUFFER; + + D3D12_RESOURCE_DESC desc = {}; + if (textureDesc) + { + if (indirect || !dx12->CanUseIVSHMEMTexture()) + return false; + desc = *textureDesc; + if (desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D || + !desc.Width || + !desc.Height || + desc.DepthOrArraySize != 1 || + desc.MipLevels != 1 || + desc.SampleDesc.Count != 1 || + desc.SampleDesc.Quality || + desc.Format == DXGI_FORMAT_UNKNOWN || + desc.Layout != D3D12_TEXTURE_LAYOUT_ROW_MAJOR || + desc.Flags != D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER) + return false; + } + else + { + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Width = size; + 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 (!indirect) + { + desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER; + } + } + + // 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)))) { m_frameSize = size; return true; @@ -42,22 +107,11 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameI Reset(); - D3D12_RESOURCE_DESC desc = {}; - desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - desc.Width = size; - 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; - - HRESULT hr; + HRESULT hr; const WCHAR * resName; + UINT64 allocationSize = size; - if (swapChain->GetD3D12Device()->IsIndirectCopy()) + if (indirect) { DEBUG_TRACE("Creating standard resource for %p", base); D3D12_HEAP_PROPERTIES heapProps = {}; @@ -67,7 +121,7 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameI heapProps.CreationNodeMask = 1; heapProps.VisibleNodeMask = 1; - hr = swapChain->GetD3D12Device()->GetDevice()->CreateCommittedResource( + hr = dx12->GetDevice()->CreateCommittedResource( &heapProps, D3D12_HEAP_FLAG_NONE, &desc, @@ -90,19 +144,52 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameI } else { - DEBUG_TRACE("Creating ivshmem resource for %p", base); - desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; - desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER; + const UINT64 heapOffset = + (uintptr_t)base - + (uintptr_t)swapChain->GetDevice()->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() || + heapOffset > heapDesc.SizeInBytes || + allocation.SizeInBytes > heapDesc.SizeInBytes - heapOffset) + { + DEBUG_ERROR("IVSHMEM resource does not fit its framebuffer allocation"); + return false; + } - hr = swapChain->GetD3D12Device()->GetDevice()->CreatePlacedResource( - swapChain->GetD3D12Device()->GetHeap().Get(), - (uintptr_t)base - (uintptr_t)swapChain->GetDevice()->GetIVSHMEM().GetMem(), + if (type == RESOURCE_TEXTURE) + { + D3D12_FEATURE_DATA_FORMAT_SUPPORT support = {}; + support.Format = desc.Format; + hr = dx12->GetDevice()->CheckFeatureSupport( + D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support)); + if (FAILED(hr) || + !(support.Support1 & D3D12_FORMAT_SUPPORT1_TEXTURE2D)) + { + DEBUG_ERROR("IVSHMEM texture format is unsupported"); + return false; + } + DEBUG_TRACE("Creating IVSHMEM texture for %p", base); + resName = L"IVSHMEM Texture"; + } + else + { + DEBUG_TRACE("Creating IVSHMEM buffer for %p", base); + resName = L"IVSHMEM"; + } + + hr = dx12->GetDevice()->CreatePlacedResource( + dx12->GetHeap().Get(), + heapOffset, &desc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_PPV_ARGS(&m_res) ); - resName = L"IVSHMEM"; } if (FAILED(hr)) @@ -114,8 +201,11 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameI m_res->SetName(resName); m_base = base; - m_size = size; + m_size = type == RESOURCE_TEXTURE ? + (size_t)allocationSize : size; m_frameSize = size; + m_type = type; + m_desc = desc; return true; } @@ -127,8 +217,27 @@ void CFrameBufferResource::Reset() m_map = NULL; } - m_base = nullptr; - m_size = 0; - m_frameSize = 0; + m_base = nullptr; + m_size = 0; + m_frameSize = 0; + m_type = RESOURCE_NONE; + m_desc = {}; + m_fullCopy = false; + m_nbCopyDirtyRects = 0; + m_copyPitch = 0; + m_copyBytesPerPixel = 0; m_res.Reset(); } + +void CFrameBufferResource::SetCopyDamage(const RECT dirtyRects[], + unsigned nbDirtyRects, bool fullCopy, unsigned pitch, + unsigned bytesPerPixel) +{ + m_fullCopy = fullCopy; + m_nbCopyDirtyRects = fullCopy ? 0 : nbDirtyRects; + m_copyPitch = pitch; + m_copyBytesPerPixel = bytesPerPixel; + if (m_nbCopyDirtyRects) + memcpy(m_copyDirtyRects, dirtyRects, + m_nbCopyDirtyRects * sizeof(*m_copyDirtyRects)); +} diff --git a/idd/LGIdd/CFrameBufferResource.h b/idd/LGIdd/CFrameBufferResource.h index 7b6437e0..082ff33b 100644 --- a/idd/LGIdd/CFrameBufferResource.h +++ b/idd/LGIdd/CFrameBufferResource.h @@ -24,9 +24,11 @@ #include #include #include +#include #include #include "CFrameScheduler.h" +#include "CInteropResource.h" class CSwapChainProcessor; @@ -35,6 +37,13 @@ using namespace Microsoft::WRL; class CFrameBufferResource { private: + enum ResourceType + { + RESOURCE_NONE, + RESOURCE_BUFFER, + RESOURCE_TEXTURE, + }; + unsigned m_frameIndex = 0; uint8_t * m_base = nullptr; size_t m_size = 0; @@ -48,12 +57,21 @@ class CFrameBufferResource unsigned m_timingEffectIndex = 0; uint64_t m_timingToken = 0; bool m_fullCopy = false; + RECT m_copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {}; + unsigned m_nbCopyDirtyRects = 0; + unsigned m_copyPitch = 0; + unsigned m_copyBytesPerPixel = 0; unsigned m_candidateIndex = 0; + ResourceType m_type = RESOURCE_NONE; + D3D12_RESOURCE_DESC m_desc = {}; + std::atomic m_completionHandled = false; ComPtr m_res; void * m_map = nullptr; public: - bool Init(CSwapChainProcessor * swapChain, unsigned frameIndex, uint8_t * base, size_t size); + bool Init(CSwapChainProcessor * swapChain, unsigned frameIndex, + uint8_t * base, size_t size, + const D3D12_RESOURCE_DESC * textureDesc = nullptr); void Reset(); unsigned GetFrameIndex() { return m_frameIndex; } @@ -89,6 +107,27 @@ class CFrameBufferResource unsigned GetTimingEffectIndex() const { return m_timingEffectIndex; } uint64_t GetTimingToken () const { return m_timingToken; } bool IsFullCopy () const { return m_fullCopy; } + void SetCopyDamage(const RECT dirtyRects[], unsigned nbDirtyRects, + bool fullCopy, unsigned pitch, unsigned bytesPerPixel); + const RECT * GetCopyDirtyRects() const { return m_copyDirtyRects; } + unsigned GetCopyDirtyRectCount() const { return m_nbCopyDirtyRects; } + unsigned GetCopyPitch () const { return m_copyPitch; } + unsigned GetCopyBytesPerPixel () const + { + return m_copyBytesPerPixel; + } + void ResetCompletion() + { + m_completionHandled.store(false, std::memory_order_release); + } + void MarkCompletion() + { + m_completionHandled.store(true, std::memory_order_release); + } + bool CompletionHandled() const + { + return m_completionHandled.load(std::memory_order_acquire); + } void SetCandidateIndex(unsigned index) { m_candidateIndex = index; } unsigned GetCandidateIndex() const { return m_candidateIndex; } diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index df4bd12b..3769b0be 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -1804,7 +1804,8 @@ int CIndirectDeviceContext::FindNewestCompletedFrame( } bool CIndirectDeviceContext::FrameBufferAvailable( - const CFrameScheduler::Schedule& schedule) + const CFrameScheduler::Schedule& schedule, + bool allowReadyReplacement) { if (!m_lgmp || !m_frameQueue) return false; @@ -1823,7 +1824,8 @@ bool CIndirectDeviceContext::FrameBufferAvailable( const bool ownerBlocked = CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN; const bool ownerQueuesBlocked = FindAvailableOwnerQueue(0) < 0; - allowReady = ownerBlocked || ownerQueuesBlocked; + allowReady = allowReadyReplacement && + (ownerBlocked || ownerQueuesBlocked); } else if (lgmpHostQueuePending(m_frameQueue) != 0) { @@ -1911,7 +1913,8 @@ bool CIndirectDeviceContext::ReplaySharedFrame(uint64_t now, bool& retry) CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrameBuffer( unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, - unsigned nbDirtyRects, const CFrameScheduler::Schedule& schedule) + unsigned nbDirtyRects, const CFrameScheduler::Schedule& schedule, + bool allowReadyReplacement) { PreparedFrameBuffer result = {}; @@ -1929,8 +1932,9 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame AcquireSRWLockExclusive(&m_framePublishLock); const bool ownerBlocked = schedule.clientID && CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN; - const bool allowReady = ownerBlocked || - (schedule.clientID && FindAvailableOwnerQueue(0) < 0); + const bool allowReady = allowReadyReplacement && + (ownerBlocked || + (schedule.clientID && FindAvailableOwnerQueue(0) < 0)); const int availableFrameIndex = FindAvailableFrameBuffer(allowReady); bool expected = false; @@ -2450,6 +2454,21 @@ void CIndirectDeviceContext::WriteFrameBuffer(unsigned frameIndex, void* src, si fb->wp = (uint32_t)(offset + len); } +void CIndirectDeviceContext::WriteFrameBufferRows(unsigned frameIndex, + void * src, size_t offset, size_t rowBytes, size_t pitch, + unsigned rows) const +{ + FrameBuffer * fb = m_frameBuffer[frameIndex]; + uint8_t * dst = fb->data + offset; + uint8_t * source = static_cast(src) + offset; + for (unsigned row = 0; row < rows; ++row) + { + memcpy(dst, source, rowBytes); + dst += pitch; + source += pitch; + } +} + void CIndirectDeviceContext::FinalizeFrameBuffer(unsigned frameIndex) const { const KVMFRFrame * frame = m_frame[frameIndex]; diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index 9dbaae1b..b8a1ffc9 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -280,7 +280,8 @@ public: bool fullCopy; }; - bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule); + bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule, + bool allowReadyReplacement = true); bool HasPublishedFrame() const { return m_readyFrameIndex.load(std::memory_order_acquire) >= 0; @@ -291,7 +292,8 @@ public: PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects, - const CFrameScheduler::Schedule& schedule); + const CFrameScheduler::Schedule& schedule, + bool allowReadyReplacement = true); bool PublishFrameBuffer(unsigned frameIndex, const CFrameScheduler::Schedule& schedule, bool& deliveredToOwner); bool RepublishFrameBuffer(const CFrameScheduler::Schedule& schedule); @@ -308,6 +310,8 @@ public: uint64_t holdTime, const CFrameScheduler::Schedule& schedule, uint64_t completedAt); void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const; + void WriteFrameBufferRows(unsigned frameIndex, void * src, + size_t offset, size_t rowBytes, size_t pitch, unsigned rows) const; void FinalizeFrameBuffer(unsigned frameIndex) const; void ObserveFrame(uint64_t now); diff --git a/idd/LGIdd/CPostProcessor.cpp b/idd/LGIdd/CPostProcessor.cpp index 9b61aa7d..e321c6ec 100644 --- a/idd/LGIdd/CPostProcessor.cpp +++ b/idd/LGIdd/CPostProcessor.cpp @@ -351,14 +351,15 @@ bool CPostProcessor::ShouldCopyFully( m_copyEffect->ShouldCopyFully(dirtyRects, nbDirtyRects); } -void CPostProcessor::CopyToCandidate( +void CPostProcessor::CopyToFrameBuffer( const ComPtr& commandList, - ID3D12Resource * dst, ID3D12Resource * src) const + ID3D12Resource * dst, ID3D12Resource * src, + const RECT dirtyRects[], unsigned nbDirtyRects, bool fullCopy) const { if (m_copyEffect) { m_copyEffect->CopyFrame( - commandList, dst, src, nullptr, 0, true); + commandList, dst, src, dirtyRects, nbDirtyRects, fullCopy); return; } @@ -368,12 +369,47 @@ void CPostProcessor::CopyToCandidate( srcLoc.SubresourceIndex = 0; D3D12_TEXTURE_COPY_LOCATION dstLoc = {}; - dstLoc.pResource = dst; - dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; - dstLoc.PlacedFootprint = m_copyLayout; + dstLoc.pResource = dst; + if (dst->GetDesc().Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D) + { + dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLoc.SubresourceIndex = 0; + } + else + { + dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + dstLoc.PlacedFootprint = m_copyLayout; + } - commandList->CopyTextureRegion( - &dstLoc, 0, 0, 0, &srcLoc, nullptr); + if (fullCopy) + { + commandList->CopyTextureRegion( + &dstLoc, 0, 0, 0, &srcLoc, nullptr); + return; + } + + for (const RECT * rect = dirtyRects; + rect < dirtyRects + nbDirtyRects; ++rect) + { + D3D12_BOX box = {}; + box.left = rect->left; + box.top = rect->top; + box.front = 0; + box.right = rect->right; + box.bottom = rect->bottom; + box.back = 1; + + commandList->CopyTextureRegion( + &dstLoc, box.left, box.top, 0, &srcLoc, &box); + } +} + +void CPostProcessor::CopyToCandidate( + const ComPtr& commandList, + ID3D12Resource * dst, ID3D12Resource * src) const +{ + CopyToFrameBuffer( + commandList, dst, src, nullptr, 0, true); } void CPostProcessor::CopyFromCandidate( diff --git a/idd/LGIdd/CPostProcessor.h b/idd/LGIdd/CPostProcessor.h index 52e933fa..ac80f037 100644 --- a/idd/LGIdd/CPostProcessor.h +++ b/idd/LGIdd/CPostProcessor.h @@ -183,6 +183,10 @@ public: size_t GetOutputSize () const { return m_frameSize; } bool ShouldCopyFully( const RECT dirtyRects[], unsigned nbDirtyRects) const; + void CopyToFrameBuffer( + const ComPtr& commandList, + ID3D12Resource * dst, ID3D12Resource * src, + const RECT dirtyRects[], unsigned nbDirtyRects, bool fullCopy) const; void CopyToCandidate( const ComPtr& commandList, ID3D12Resource * dst, ID3D12Resource * src) const; diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 6aa6e90a..5276d1ca 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -212,6 +212,10 @@ bool CSwapChainProcessor::InitializePipeline() } m_dx12Device = std::move(dx12Device); + m_directSoftwareTexture = + m_dx11Device->IsSoftware() && + !m_dx12Device->IsIndirectCopy() && + m_dx12Device->CanUseIVSHMEMTexture(); break; } @@ -355,6 +359,10 @@ void CSwapChainProcessor::PublisherThread() 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(); for (;;) { @@ -375,7 +383,7 @@ void CSwapChainProcessor::PublisherThread() uint64_t current = CFrameScheduler::Nanotime(); uint64_t cadenceTarget = 0; - if (schedule.deliveryDeadlineSerial && periodic) + if (cadenceEnabled && schedule.deliveryDeadlineSerial && periodic) { if (schedule.deadline <= current) { @@ -791,6 +799,82 @@ void CSwapChainProcessor::CandidateCompletionFunction( 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) { @@ -1028,6 +1112,51 @@ static void ClipDirtyRects(RECT dirtyRects[], unsigned * nbDirtyRects, *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) @@ -1100,6 +1229,53 @@ void CSwapChainProcessor::AccumulateFrameDamage( 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) { @@ -1396,42 +1572,11 @@ bool CSwapChainProcessor::PublishNewestCandidate( RECT copyDirtyRects[LG_MAX_DIRTY_RECTS * 2] = {}; unsigned nbCopyDirtyRects = 0; - bool fullCopy = buffer.fullCopy || - candidate.nbDirtyRects == 0 || nbPreviousDirtyRects == 0; - - if (!fullCopy) - { - for (const RECT * rect = previousDirtyRects; - rect < previousDirtyRects + nbPreviousDirtyRects && !fullCopy; - ++rect) - { - RECT clipped = *rect; - if (ClipDirtyRect(clipped, - candidate.dstFormat.width, candidate.dstFormat.height) && - !AddCopyDirtyRect(copyDirtyRects, ARRAYSIZE(copyDirtyRects), - &nbCopyDirtyRects, clipped)) - fullCopy = true; - } - - for (const RECT * rect = candidate.dirtyRects; - rect < candidate.dirtyRects + candidate.nbDirtyRects && !fullCopy; - ++rect) - if (!AddCopyDirtyRect(copyDirtyRects, ARRAYSIZE(copyDirtyRects), - &nbCopyDirtyRects, *rect)) - fullCopy = true; - - if (!fullCopy) - fullCopy = IsFullDamage( - copyDirtyRects, nbCopyDirtyRects, - candidate.dstFormat.width, candidate.dstFormat.height) || - CopyAreaCoversFrame( - copyDirtyRects, nbCopyDirtyRects, - candidate.dstFormat.width, candidate.dstFormat.height); - - if (!fullCopy) - fullCopy = postProcessor.ShouldCopyFully( - copyDirtyRects, nbCopyDirtyRects); - } + 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); @@ -1592,6 +1737,271 @@ 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, @@ -1838,6 +2248,10 @@ 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. diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index 0695fafb..82d4e885 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -106,8 +106,9 @@ private: // 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; + uint64_t m_candidateSequence = 0; + bool m_publishPending = false; + bool m_directSoftwareTexture = false; Wrappers::HandleT m_thread[3]; Wrappers::Event m_terminateEvent; @@ -170,12 +171,23 @@ private: 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,