From 6109501eeddb9c130d091e2a532273500014edaf Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 3 Aug 2026 20:35:49 +1000 Subject: [PATCH] [idd] select linear RGB24 packing automatically Replace the texture-shaped RGB24 output with a linear raw buffer and keep its packing, damage translation, and buffer copies inside the RGB24 effect. Benchmark full-frame native and packed processing and retain the faster path for each source format. Preserve logical damage rectangles for client updates and alternating framebuffer repair. Return compute outputs to COMMON for COPY queue handoff and refresh cached framebuffer sizes when switching packed and native layouts. --- idd/LGIdd/CFrameBufferPool.cpp | 10 +- idd/LGIdd/CFrameBufferResource.cpp | 9 +- idd/LGIdd/CFrameBufferResource.h | 34 +- idd/LGIdd/CIndirectDeviceContext.cpp | 35 +- idd/LGIdd/CIndirectDeviceContext.h | 21 +- idd/LGIdd/CPostProcessor.cpp | 209 +++++++- idd/LGIdd/CPostProcessor.h | 87 +++- idd/LGIdd/CSwapChainProcessor.cpp | 206 ++++---- idd/LGIdd/CSwapChainProcessor.h | 6 +- idd/LGIdd/effect/CColorTransformEffect.cpp | 4 +- idd/LGIdd/effect/CComputeEffect.cpp | 27 +- idd/LGIdd/effect/CComputeEffect.h | 2 + idd/LGIdd/effect/CDownsampleEffect.cpp | 4 +- idd/LGIdd/effect/CHDR16to10Effect.cpp | 4 +- idd/LGIdd/effect/CRGB24Effect.cpp | 538 +++++++++++++++++++-- idd/LGIdd/effect/CRGB24Effect.h | 23 + 16 files changed, 987 insertions(+), 232 deletions(-) diff --git a/idd/LGIdd/CFrameBufferPool.cpp b/idd/LGIdd/CFrameBufferPool.cpp index a52ee67d..43752899 100644 --- a/idd/LGIdd/CFrameBufferPool.cpp +++ b/idd/LGIdd/CFrameBufferPool.cpp @@ -41,13 +41,9 @@ CFrameBufferResource * CFrameBufferPool::Get( if (buffer.frameIndex > ARRAYSIZE(m_buffers) - 1) return nullptr; - CFrameBufferResource* fbr = &m_buffers[buffer.frameIndex]; - if (!fbr->IsValid() || fbr->GetBase() != buffer.mem || fbr->GetSize() < minSize) - { - fbr->Reset(); - if (!fbr->Init(m_swapChain, buffer.frameIndex, buffer.mem, minSize)) - return nullptr; - } + CFrameBufferResource * fbr = &m_buffers[buffer.frameIndex]; + if (!fbr->Init(m_swapChain, buffer.frameIndex, buffer.mem, minSize)) + return nullptr; return fbr; } diff --git a/idd/LGIdd/CFrameBufferResource.cpp b/idd/LGIdd/CFrameBufferResource.cpp index 8d9bcc04..3f28fffb 100644 --- a/idd/LGIdd/CFrameBufferResource.cpp +++ b/idd/LGIdd/CFrameBufferResource.cpp @@ -28,7 +28,8 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameI if (size > swapChain->GetDevice()->GetMaxFrameSize()) { - DEBUG_ERROR("Frame size of %lu is too large to fit in available shared ram"); + DEBUG_ERROR("Frame size of %llu is too large to fit in shared ram", + (unsigned long long)size); return false; } @@ -97,7 +98,7 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameI swapChain->GetD3D12Device()->GetHeap().Get(), (uintptr_t)base - (uintptr_t)swapChain->GetDevice()->GetIVSHMEM().GetMem(), &desc, - D3D12_RESOURCE_STATE_COPY_DEST, + D3D12_RESOURCE_STATE_COMMON, NULL, IID_PPV_ARGS(&m_res) ); @@ -115,7 +116,6 @@ bool CFrameBufferResource::Init(CSwapChainProcessor * swapChain, unsigned frameI m_base = base; m_size = size; m_frameSize = size; - m_valid = true; return true; } @@ -131,5 +131,4 @@ void CFrameBufferResource::Reset() m_size = 0; m_frameSize = 0; m_res.Reset(); - m_valid = false; -} \ No newline at end of file +} diff --git a/idd/LGIdd/CFrameBufferResource.h b/idd/LGIdd/CFrameBufferResource.h index 2126f5c4..e0c3ea09 100644 --- a/idd/LGIdd/CFrameBufferResource.h +++ b/idd/LGIdd/CFrameBufferResource.h @@ -33,25 +33,24 @@ using namespace Microsoft::WRL; class CFrameBufferResource { private: - bool m_valid = false; - unsigned m_frameIndex = 0; - uint8_t * m_base = nullptr; - size_t m_size = 0; - size_t m_frameSize = 0; - uint64_t m_captureTime = 0; - uint64_t m_postProcessStart = 0; - uint64_t m_copyStart = 0; + unsigned m_frameIndex = 0; + uint8_t * m_base = nullptr; + size_t m_size = 0; + size_t m_frameSize = 0; + uint64_t m_captureTime = 0; + uint64_t m_postProcessStart = 0; + uint64_t m_copyStart = 0; + unsigned m_timingEffectIndex = 0; + uint64_t m_timingToken = 0; + bool m_fullCopy = false; ComPtr m_res; - void * m_map = nullptr; + void * m_map = nullptr; public: bool Init(CSwapChainProcessor * swapChain, unsigned frameIndex, uint8_t * base, size_t size); void Reset(); - bool IsValid() { return m_valid; } unsigned GetFrameIndex() { return m_frameIndex; } - uint8_t * GetBase() { return m_base; } - size_t GetSize() { return m_size; } size_t GetFrameSize() { return m_frameSize; } void * GetMap() { return m_map; } @@ -66,5 +65,16 @@ class CFrameBufferResource uint64_t GetPostProcessStart() const { return m_postProcessStart; } uint64_t GetCopyStart () const { return m_copyStart; } + void SetPostProcessSample( + unsigned effectIndex, uint64_t token, bool fullCopy) + { + m_timingEffectIndex = effectIndex; + m_timingToken = token; + m_fullCopy = fullCopy; + } + unsigned GetTimingEffectIndex() const { return m_timingEffectIndex; } + uint64_t GetTimingToken () const { return m_timingToken; } + bool IsFullCopy () const { return m_fullCopy; } + ComPtr Get() { return m_res; } }; diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index a83cdcde..1110fd8e 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -1249,6 +1249,11 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame { PreparedFrameBuffer result = {}; + const unsigned dataWidth = dstFormat.dataWidth ? + dstFormat.dataWidth : (unsigned)dstFormat.desc.Width; + const unsigned dataHeight = dstFormat.dataHeight ? + dstFormat.dataHeight : dstFormat.desc.Height; + if (!FrameBufferAvailable()) return result; @@ -1268,17 +1273,21 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame expected, true, std::memory_order_acq_rel)) return result; - if (m_width != dstFormat.desc.Width || - m_height != dstFormat.desc.Height || - m_pitch != pitch || - m_format != dstFormat.desc.Format || - m_frameType != dstFormat.format) + if (m_width != dataWidth || + m_height != dataHeight || + m_frameWidth != dstFormat.width || + m_frameHeight != dstFormat.height || + m_pitch != pitch || + m_format != dstFormat.desc.Format || + m_frameType != dstFormat.format) { - m_width = (unsigned)dstFormat.desc.Width; - m_height = dstFormat.desc.Height; - m_format = dstFormat.desc.Format; - m_frameType = dstFormat.format; - m_pitch = pitch; + m_width = dataWidth; + m_height = dataHeight; + m_frameWidth = dstFormat.width; + m_frameHeight = dstFormat.height; + m_pitch = pitch; + m_format = dstFormat.desc.Format; + m_frameType = dstFormat.format; ++m_formatVer; } @@ -1326,15 +1335,15 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame (dstFormat.hdrPQ ? FRAME_FLAG_HDR_PQ : 0) | (dstFormat.hdrMetadata ? FRAME_FLAG_HDR_METADATA : 0); - if (maxRows < dstFormat.desc.Height) + if (maxRows < dataHeight) flags |= FRAME_FLAG_TRUNCATED; fi->formatVer = m_formatVer; fi->frameSerial = m_frameSerial++; fi->screenWidth = srcFormat.width; fi->screenHeight = srcFormat.height; - fi->dataWidth = (unsigned)dstFormat.desc.Width; - fi->dataHeight = min(maxRows, dstFormat.desc.Height); + fi->dataWidth = dataWidth; + fi->dataHeight = min(maxRows, dataHeight); fi->frameWidth = dstFormat.width; fi->frameHeight = dstFormat.height; fi->stride = pitch / bpp; diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index a0924fcf..a402989c 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -113,15 +113,18 @@ private: KVMFRFrame * m_frame [LGMP_Q_FRAME_LEN] = {}; FrameBuffer * m_frameBuffer[LGMP_Q_FRAME_LEN] = {}; - unsigned m_width = 0; - unsigned m_height = 0; - unsigned m_pitch = 0; - DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN; - FrameType m_frameType = FRAME_TYPE_INVALID; - UINT m_iddCxVersion = 0; - bool m_hasIddCx110DDIs = false; - bool m_canProcessFP16 = false; - bool m_softwareMode = true; + unsigned m_width = 0; + unsigned m_height = 0; + unsigned m_frameWidth = 0; + unsigned m_frameHeight = 0; + unsigned m_pitch = 0; + DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN; + FrameType m_frameType = FRAME_TYPE_INVALID; + + UINT m_iddCxVersion = 0; + bool m_hasIddCx110DDIs = false; + bool m_canProcessFP16 = false; + bool m_softwareMode = true; // Previous HDR metadata used to detect changes for formatVer bumps uint16_t m_lastHDRDisplayPrimary[3][2] = {}; diff --git a/idd/LGIdd/CPostProcessor.cpp b/idd/LGIdd/CPostProcessor.cpp index 98138f2d..3d7f4eaf 100644 --- a/idd/LGIdd/CPostProcessor.cpp +++ b/idd/LGIdd/CPostProcessor.cpp @@ -28,6 +28,7 @@ #include "effect/CRGB24Effect.h" #include +#include #include namespace @@ -82,12 +83,16 @@ static void CopyHDRMetadata(D12FrameFormat& dst, const D12FrameFormat& src) dst.maxFrameAverageLightLevel = src.maxFrameAverageLightLevel; } -bool CPostProcessor::Init(std::shared_ptr dx12Device) +bool CPostProcessor::Init(std::shared_ptr dx12Device, + bool enableEffects) { m_dx12Device = dx12Device; - m_device = dx12Device->GetDevice(); + m_device = dx12Device->GetDevice(); m_effects.clear(); + if (!enableEffects) + return true; + std::unique_ptr colorTransform(new CColorTransformEffect()); if (colorTransform->Init(m_device)) { @@ -130,6 +135,10 @@ void CPostProcessor::Reset() m_device.Reset(); m_srcFormat = {}; m_dstFormat = {}; + m_copyLayout = {}; + m_copyEffect = nullptr; + m_frameSize = 0; + m_pitch = 0; m_effectsActive = false; m_configured = false; } @@ -147,9 +156,26 @@ bool CPostProcessor::HasSameEffectChain(const CPostProcessor& other) const return true; } +bool CPostProcessor::ShareEffectState(const CPostProcessor& other) +{ + if (!HasSameEffectChain(other)) + return false; + + for (size_t i = 0; i < m_effects.size(); ++i) + m_effects[i]->ShareState(*other.m_effects[i]); + + return true; +} + +void CPostProcessor::Update(const D12FrameFormat& srcFormat) +{ + for (const auto& effect : m_effects) + effect->Update(srcFormat); +} + bool CPostProcessor::NeedsReconfigure(const D12FrameFormat& srcFormat) const { - return !m_configured || + if (!m_configured || srcFormat.desc.Width != m_srcFormat.desc.Width || srcFormat.desc.Height != m_srcFormat.desc.Height || srcFormat.desc.Format != m_srcFormat.desc.Format || @@ -158,7 +184,23 @@ bool CPostProcessor::NeedsReconfigure(const D12FrameFormat& srcFormat) const srcFormat.height != m_srcFormat.height || srcFormat.hdr != m_srcFormat.hdr || srcFormat.hdrPQ != m_srcFormat.hdrPQ || - srcFormat.colorTransform != m_srcFormat.colorTransform; + srcFormat.colorTransform != m_srcFormat.colorTransform) + return true; + + for (const auto& effect : m_effects) + if (effect->NeedsReconfigure()) + return true; + + return false; +} + +bool CPostProcessor::RequiresFullDamage() const +{ + for (const auto& effect : m_effects) + if (effect->RequiresFullDamage()) + return true; + + return false; } bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, @@ -170,15 +212,16 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, if (!NeedsReconfigure(srcFormat)) { // Static HDR metadata may change independently of the resource format. - // Propagate it without recreating textures or post-processing state. + // Propagate it without recreating resources or post-processing state. CopyHDRMetadata(m_srcFormat, srcFormat); CopyHDRMetadata(m_dstFormat, srcFormat); return true; } - D12FrameFormat oldDst = m_dstFormat; - D12FrameFormat cur = srcFormat; - bool effectsActive = false; + D12FrameFormat oldDst = m_dstFormat; + D12FrameFormat cur = srcFormat; + CPostProcessEffect * outputEffect = nullptr; + bool effectsActive = false; for (const auto& effect : m_effects) { @@ -189,6 +232,7 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, effect->Enabled = true; effectsActive = true; cur = dst; + outputEffect = effect.get(); DEBUG_INFO("Post-processing effect active: %s", effect->GetName()); break; @@ -202,25 +246,156 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, } } + D3D12_PLACED_SUBRESOURCE_FOOTPRINT copyLayout = {}; + CPostProcessEffect * copyEffect = nullptr; + unsigned pitch = 0; + unsigned dataHeight = 0; + if (outputEffect && outputEffect->GetCopyLayout(&pitch, &dataHeight)) + copyEffect = outputEffect; + else + { + if (cur.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D) + { + DEBUG_ERROR("Post-processing output has no copy implementation"); + return false; + } + + m_device->GetCopyableFootprints( + &cur.desc, + 0, + 1, + 0, + ©Layout, + nullptr, + nullptr, + nullptr); + pitch = copyLayout.Footprint.RowPitch; + dataHeight = cur.desc.Height; + } + + if (!pitch || !dataHeight || + pitch > (std::numeric_limits::max)() / dataHeight) + { + DEBUG_ERROR("Invalid post-processing output layout"); + return false; + } + + const size_t frameSize = (size_t)pitch * dataHeight; + if (copyEffect && cur.desc.Width < frameSize) + { + DEBUG_ERROR("Post-processing output buffer is too small"); + return false; + } + m_srcFormat = srcFormat; m_dstFormat = cur; + m_copyLayout = copyLayout; + m_copyEffect = copyEffect; + m_frameSize = frameSize; + m_pitch = pitch; m_effectsActive = effectsActive; m_configured = true; if (formatChanged) *formatChanged = - oldDst.desc.Width != m_dstFormat.desc.Width || - oldDst.desc.Height != m_dstFormat.desc.Height || - oldDst.desc.Format != m_dstFormat.desc.Format || - oldDst.format != m_dstFormat.format || - oldDst.width != m_dstFormat.width || - oldDst.height != m_dstFormat.height || - oldDst.hdr != m_dstFormat.hdr || - oldDst.hdrPQ != m_dstFormat.hdrPQ || - oldDst.sdrWhiteLevel != m_dstFormat.sdrWhiteLevel || + oldDst.desc.Width != m_dstFormat.desc.Width || + oldDst.desc.Height != m_dstFormat.desc.Height || + oldDst.desc.Format != m_dstFormat.desc.Format || + oldDst.dataWidth != m_dstFormat.dataWidth || + oldDst.dataHeight != m_dstFormat.dataHeight || + oldDst.pitch != m_dstFormat.pitch || + oldDst.format != m_dstFormat.format || + oldDst.width != m_dstFormat.width || + oldDst.height != m_dstFormat.height || + oldDst.hdr != m_dstFormat.hdr || + oldDst.hdrPQ != m_dstFormat.hdrPQ || + oldDst.sdrWhiteLevel != m_dstFormat.sdrWhiteLevel || oldDst.colorTransform != m_dstFormat.colorTransform; return true; } +void CPostProcessor::GetTimingToken( + unsigned * effectIndex, uint64_t * token) const +{ + if (effectIndex) + *effectIndex = 0; + if (token) + *token = 0; + + for (size_t i = 0; i < m_effects.size(); ++i) + { + const uint64_t value = m_effects[i]->GetTimingToken(); + if (!value) + continue; + + if (effectIndex) + *effectIndex = (unsigned)i; + if (token) + *token = value; + return; + } +} + +void CPostProcessor::RecordTiming( + unsigned effectIndex, uint64_t token, bool fullCopy, uint64_t totalTime) +{ + if (!token || effectIndex >= m_effects.size()) + return; + + m_effects[effectIndex]->RecordTiming(token, fullCopy, totalTime); +} + +bool CPostProcessor::ShouldCopyFully( + const RECT dirtyRects[], unsigned nbDirtyRects) const +{ + return m_copyEffect && + m_copyEffect->ShouldCopyFully(dirtyRects, nbDirtyRects); +} + +void CPostProcessor::CopyFrame( + const ComPtr& commandList, + ID3D12Resource * dst, ID3D12Resource * src, + const RECT dirtyRects[], unsigned nbDirtyRects, bool fullCopy) const +{ + if (m_copyEffect) + { + m_copyEffect->CopyFrame( + commandList, dst, src, dirtyRects, nbDirtyRects, fullCopy); + return; + } + + D3D12_TEXTURE_COPY_LOCATION srcLoc = {}; + srcLoc.pResource = src; + srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + srcLoc.SubresourceIndex = 0; + + D3D12_TEXTURE_COPY_LOCATION dstLoc = {}; + dstLoc.pResource = dst; + dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + dstLoc.PlacedFootprint = m_copyLayout; + + 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::AdjustFrameDamage(RECT dirtyRects[], unsigned * nbDirtyRects) { for (const auto& effect : m_effects) diff --git a/idd/LGIdd/CPostProcessor.h b/idd/LGIdd/CPostProcessor.h index 04472c4f..2ad57778 100644 --- a/idd/LGIdd/CPostProcessor.h +++ b/idd/LGIdd/CPostProcessor.h @@ -55,15 +55,18 @@ bool IsIdentityColorTransform(const D12ColorTransform& transform); struct D12FrameFormat { - D3D12_RESOURCE_DESC desc = {}; - unsigned width = 0; - unsigned height = 0; - FrameType format = FRAME_TYPE_INVALID; - bool hdr = false; - bool hdrPQ = false; - bool hdrMetadata = false; - uint32_t sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT; - std::shared_ptr colorTransform; + D3D12_RESOURCE_DESC desc = {}; + unsigned dataWidth = 0; + unsigned dataHeight = 0; + unsigned pitch = 0; + unsigned width = 0; + unsigned height = 0; + FrameType format = FRAME_TYPE_INVALID; + bool hdr = false; + bool hdrPQ = false; + bool hdrMetadata = false; + uint32_t sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT; + std::shared_ptr colorTransform; // HDR static metadata (SMPTE ST 2086) // Display color primaries in 0.00002 units (xy coordinates) @@ -84,6 +87,51 @@ class CPostProcessEffect public: virtual ~CPostProcessEffect() {} virtual const char * GetName() const = 0; + virtual void ShareState(const CPostProcessEffect& other) + { + UNREFERENCED_PARAMETER(other); + } + virtual void Update(const D12FrameFormat& format) + { + UNREFERENCED_PARAMETER(format); + } + virtual bool NeedsReconfigure() const { return false; } + virtual bool RequiresFullDamage() const { return false; } + virtual uint64_t GetTimingToken() const { return 0; } + virtual void RecordTiming( + uint64_t token, bool fullCopy, uint64_t totalTime) + { + UNREFERENCED_PARAMETER(token); + UNREFERENCED_PARAMETER(fullCopy); + UNREFERENCED_PARAMETER(totalTime); + } + // A final effect with a non-texture output owns its framebuffer copy. + virtual bool GetCopyLayout( + unsigned * pitch, unsigned * dataHeight) const + { + UNREFERENCED_PARAMETER(pitch); + UNREFERENCED_PARAMETER(dataHeight); + return false; + } + virtual bool ShouldCopyFully( + const RECT dirtyRects[], unsigned nbDirtyRects) const + { + UNREFERENCED_PARAMETER(dirtyRects); + UNREFERENCED_PARAMETER(nbDirtyRects); + return false; + } + virtual void CopyFrame( + const ComPtr& commandList, + ID3D12Resource * dst, ID3D12Resource * src, + const RECT dirtyRects[], unsigned nbDirtyRects, bool fullCopy) const + { + UNREFERENCED_PARAMETER(commandList); + UNREFERENCED_PARAMETER(dst); + UNREFERENCED_PARAMETER(src); + UNREFERENCED_PARAMETER(dirtyRects); + UNREFERENCED_PARAMETER(nbDirtyRects); + UNREFERENCED_PARAMETER(fullCopy); + } virtual PostProcessStatus SetFormat(const ComPtr& device, const D12FrameFormat& src, D12FrameFormat& dst) = 0; virtual void AdjustDamage(RECT dirtyRects[], unsigned * nbDirtyRects) { UNREFERENCED_PARAMETER(dirtyRects); UNREFERENCED_PARAMETER(nbDirtyRects); } @@ -102,15 +150,23 @@ private: std::vector> m_effects; D12FrameFormat m_srcFormat = {}; D12FrameFormat m_dstFormat = {}; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT m_copyLayout = {}; + CPostProcessEffect * m_copyEffect = nullptr; + size_t m_frameSize = 0; + unsigned m_pitch = 0; bool m_effectsActive = false; bool m_configured = false; public: - bool Init(std::shared_ptr dx12Device); + bool Init(std::shared_ptr dx12Device, + bool enableEffects); void Reset(); bool HasSameEffectChain(const CPostProcessor& other) const; + bool ShareEffectState(const CPostProcessor& other); + void Update(const D12FrameFormat& srcFormat); bool NeedsReconfigure(const D12FrameFormat& srcFormat) const; + bool RequiresFullDamage() const; bool Configure(const D12FrameFormat& srcFormat, bool * formatChanged); void AdjustFrameDamage(RECT dirtyRects[], unsigned * nbDirtyRects); ComPtr Run( @@ -120,4 +176,15 @@ public: const D12FrameFormat& GetOutputFormat() const { return m_dstFormat; } bool HasActiveEffects() const { return m_effectsActive; } + void GetTimingToken(unsigned * effectIndex, uint64_t * token) const; + void RecordTiming(unsigned effectIndex, uint64_t token, + bool fullCopy, uint64_t totalTime); + unsigned GetOutputPitch() const { return m_pitch; } + size_t GetOutputSize () const { return m_frameSize; } + bool ShouldCopyFully( + const RECT dirtyRects[], unsigned nbDirtyRects) const; + void CopyFrame( + const ComPtr& commandList, + ID3D12Resource * dst, ID3D12Resource * src, + const RECT dirtyRects[], unsigned nbDirtyRects, bool fullCopy) const; }; diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 9b488e66..7a0f0f04 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -79,33 +79,37 @@ CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContex { m_resPool.Init(dx11Device, dx12Device); m_fbPool.Init(this); - if (m_dx11Device->IsSoftware()) + const bool enableEffects = !m_dx11Device->IsSoftware(); + if (!enableEffects) DEBUG_INFO("Software render adapter: post-processing disabled"); - else - { - bool initialized = true; - for (CPostProcessor& postProcessor : m_postProcessors) - if (!postProcessor.Init(dx12Device)) + + bool initialized = true; + for (CPostProcessor& postProcessor : m_postProcessors) + if (!postProcessor.Init(dx12Device, enableEffects)) + { + initialized = false; + break; + } + + if (initialized) + for (unsigned i = 1; i < ARRAYSIZE(m_postProcessors); ++i) + if (!m_postProcessors[i].ShareEffectState(m_postProcessors[0])) { + DEBUG_ERROR("Post processor effect chains do not match"); initialized = false; break; } - if (initialized) - for (unsigned i = 1; i < ARRAYSIZE(m_postProcessors); ++i) - if (!m_postProcessors[0].HasSameEffectChain(m_postProcessors[i])) - { - DEBUG_ERROR("Post processor effect chains do not match"); - initialized = false; - break; - } - - if (!initialized) + if (!initialized) + { + for (CPostProcessor& postProcessor : m_postProcessors) { - for (CPostProcessor& postProcessor : m_postProcessors) - postProcessor.Reset(); - DEBUG_ERROR("Failed to initialize post processors"); + postProcessor.Reset(); + if (!postProcessor.Init(dx12Device, false)) + DEBUG_ERROR("Failed to initialize post processor copy support"); } + DEBUG_WARN( + "Failed to initialize post-processing effects; effects disabled"); } // Manual-reset: both worker threads wait on this, so it must stay signalled @@ -363,7 +367,7 @@ void CSwapChainProcessor::CompletionFunction( fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); // Queue waits execute before the start timestamp. The end timestamp follows - // the last CopyTextureRegion, separating GPU work from readiness dispatch. + // the last copy command, separating GPU work from readiness dispatch. const bool gpuTimingValid = slot->GetGPUTimes(gpuCopyStart, gpuCopyEnd); @@ -384,6 +388,9 @@ void CSwapChainProcessor::CompletionFunction( readyTime = readyEnd - gpuCopyEnd; } + sc->m_postProcessors[fbRes->GetFrameIndex()].RecordTiming( + fbRes->GetTimingEffectIndex(), fbRes->GetTimingToken(), + fbRes->IsFullCopy(), postProcessTime + copyTime + readyTime); sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(), fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime); sc->m_devContext->CompleteFrameBuffer(fbRes->GetFrameIndex()); @@ -391,14 +398,14 @@ void CSwapChainProcessor::CompletionFunction( static bool IsFullDamage(const RECT * dirtyRects, unsigned nbDirtyRects, - const D3D12_RESOURCE_DESC& desc) + unsigned width, unsigned height) { for (const RECT * rect = dirtyRects; rect < dirtyRects + nbDirtyRects; ++rect) - if (rect->left == 0 && - rect->top == 0 && - rect->right == (LONG)desc.Width && - rect->bottom == (LONG)desc.Height) + if (rect->left == 0 && + rect->top == 0 && + rect->right == (LONG)width && + rect->bottom == (LONG)height) return true; return false; @@ -470,9 +477,9 @@ static bool AddCopyDirtyRect(RECT dirtyRects[], unsigned capacity, } static bool CopyAreaCoversFrame(const RECT * dirtyRects, - unsigned nbDirtyRects, const D3D12_RESOURCE_DESC& desc) + unsigned nbDirtyRects, unsigned width, unsigned height) { - const uint64_t frameArea = (uint64_t)desc.Width * desc.Height; + const uint64_t frameArea = (uint64_t)width * height; uint64_t copyArea = 0; for (const RECT * rect = dirtyRects; @@ -487,26 +494,10 @@ static bool CopyAreaCoversFrame(const RECT * dirtyRects, return false; } -static void CopyDirtyRect(ComPtr list, - D3D12_TEXTURE_COPY_LOCATION * dstLoc, - D3D12_TEXTURE_COPY_LOCATION * srcLoc, - const RECT& rect) +static bool ClipDirtyRect(RECT& rect, unsigned width, unsigned height) { - 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; - - list->CopyTextureRegion(dstLoc, box.left, box.top, 0, srcLoc, &box); -} - -static bool ClipDirtyRect(RECT& rect, const D3D12_RESOURCE_DESC& desc) -{ - const LONG maxRight = (LONG)desc.Width; - const LONG maxBottom = (LONG)desc.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; @@ -517,13 +508,13 @@ static bool ClipDirtyRect(RECT& rect, const D3D12_RESOURCE_DESC& desc) } static void ClipDirtyRects(RECT dirtyRects[], unsigned * nbDirtyRects, - const D3D12_RESOURCE_DESC& desc) + unsigned width, unsigned height) { unsigned out = 0; for (unsigned i = 0; i < *nbDirtyRects; ++i) { RECT rect = dirtyRects[i]; - if (ClipDirtyRect(rect, desc)) + if (ClipDirtyRect(rect, width, height)) dirtyRects[out++] = rect; } *nbDirtyRects = out; @@ -816,6 +807,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer break; } + m_postProcessors[0].Update(srcFormat); + const bool frameMetadataChanged = noImageUpdate && FrameMetadataChanged(m_postProcessors[0].GetOutputFormat(), srcFormat); @@ -836,18 +829,40 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer m_dx12Device->WaitForIdle(); } + // An optional adaptive effect can reject its candidate while configuring + // either slot. Both queues are already idle, so allow one convergence pass + // to return both effect chains to the same mode. bool postProcessFormatChanged = false; - for (unsigned i = 0; i < ARRAYSIZE(m_postProcessors); ++i) + bool configurationStable = false; + for (unsigned pass = 0; pass < 2 && !configurationStable; ++pass) { - bool formatChanged = false; - if (!m_postProcessors[i].Configure(srcFormat, &formatChanged)) + for (unsigned i = 0; i < ARRAYSIZE(m_postProcessors); ++i) { - SetFullPendingDamage(); - return false; + bool formatChanged = false; + if (!m_postProcessors[i].Configure(srcFormat, &formatChanged)) + { + SetFullPendingDamage(); + return false; + } + + if (i == 0) + postProcessFormatChanged |= formatChanged; } - if (i == 0) - postProcessFormatChanged = formatChanged; + configurationStable = true; + for (const CPostProcessor& postProcessor : m_postProcessors) + if (postProcessor.NeedsReconfigure(srcFormat)) + { + configurationStable = false; + break; + } + } + + if (!configurationStable) + { + DEBUG_ERROR("Post processor configuration did not stabilize"); + SetFullPendingDamage(); + return false; } if (postProcessFormatChanged) @@ -858,37 +873,34 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer else if (frameMetadataChanged) SetFullPendingDamage(); + // Adaptive effects need comparable full-frame samples until they lock. + if (m_postProcessors[0].RequiresFullDamage()) + SetFullPendingDamage(); + if (noImageUpdate && !m_hasPendingDamage) return true; const D12FrameFormat& dstFormat = m_postProcessors[0].GetOutputFormat(); + const unsigned pitch = m_postProcessors[0].GetOutputPitch(); + const size_t frameSize = m_postProcessors[0].GetOutputSize(); - D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; - m_dx12Device->GetDevice()->GetCopyableFootprints( - &dstFormat.desc, - 0, - 1, - 0, - &layout, - NULL, - NULL, - NULL); - - RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; - RECT frameDirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbDirtyRects = m_nbPendingDirtyRects; + RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + RECT frameDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned nbDirtyRects = m_nbPendingDirtyRects; + unsigned frameDirtyRectCount = nbDirtyRects; if (nbDirtyRects) { - memcpy(currentDirtyRects, m_pendingDirtyRects, nbDirtyRects * sizeof(*currentDirtyRects)); - memcpy(frameDirtyRects, currentDirtyRects, nbDirtyRects * sizeof(*frameDirtyRects)); + memcpy(currentDirtyRects, m_pendingDirtyRects, + nbDirtyRects * sizeof(*currentDirtyRects)); + memcpy(frameDirtyRects, currentDirtyRects, + nbDirtyRects * sizeof(*frameDirtyRects)); } - unsigned frameDirtyRectCount = nbDirtyRects; m_postProcessors[0].AdjustFrameDamage( frameDirtyRects, &frameDirtyRectCount); auto buffer = m_devContext->PrepareFrameBuffer( - (unsigned)layout.Footprint.RowPitch, + pitch, srcFormat, dstFormat, frameDirtyRects, @@ -899,8 +911,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (!buffer.mem) return true; - CFrameBufferResource * fbRes = m_fbPool.Get(buffer, - (size_t)layout.Footprint.RowPitch * dstFormat.desc.Height); + CFrameBufferResource * fbRes = m_fbPool.Get(buffer, frameSize); if (!fbRes) { @@ -986,23 +997,14 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer return false; } - ClipDirtyRects(currentDirtyRects, &nbDirtyRects, dstFormat.desc); + ClipDirtyRects(currentDirtyRects, &nbDirtyRects, + dstFormat.width, dstFormat.height); const uint64_t copyStart = Nanotime(); fbRes->SetTiming(captureTime, postProcessStart, copyStart); copySlot->SetCompletionCallback(&CompletionFunction, this, fbRes); - D3D12_TEXTURE_COPY_LOCATION srcLoc = {}; - srcLoc.pResource = copySrcResource.Get(); - srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - srcLoc.SubresourceIndex = 0; - - D3D12_TEXTURE_COPY_LOCATION dstLoc = {}; - dstLoc.pResource = fbRes->Get().Get(); - dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; - dstLoc.PlacedFootprint = layout; - /* Each destination is reused every other frame, so repair both the prior * and current damage. Coalesce them first to avoid copying overlapping * regions, especially a prior full frame, more than once. */ @@ -1017,7 +1019,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer rect < m_dirtyRects + m_nbDirtyRects && !fullCopy; ++rect) { RECT clipped = *rect; - if (ClipDirtyRect(clipped, dstFormat.desc) && + if (ClipDirtyRect(clipped, dstFormat.width, dstFormat.height) && !AddCopyDirtyRect(copyDirtyRects, ARRAYSIZE(copyDirtyRects), &nbCopyDirtyRects, clipped)) fullCopy = true; @@ -1031,25 +1033,29 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (!fullCopy) fullCopy = IsFullDamage( - copyDirtyRects, nbCopyDirtyRects, dstFormat.desc) || + copyDirtyRects, nbCopyDirtyRects, + dstFormat.width, dstFormat.height) || CopyAreaCoversFrame( - copyDirtyRects, nbCopyDirtyRects, dstFormat.desc); + copyDirtyRects, nbCopyDirtyRects, + dstFormat.width, dstFormat.height); + + if (!fullCopy) + fullCopy = postProcessor.ShouldCopyFully( + copyDirtyRects, nbCopyDirtyRects); } + unsigned timingEffectIndex = 0; + uint64_t timingToken = 0; + postProcessor.GetTimingToken(&timingEffectIndex, &timingToken); + fbRes->SetPostProcessSample( + timingEffectIndex, timingToken, fullCopy); + // Source/compute waits are submitted immediately before this command list. // The timestamp therefore marks the first actual copy operation. copySlot->BeginTiming(); - if (fullCopy) - { - copySlot->GetGfxList()->CopyTextureRegion( - &dstLoc, 0, 0, 0, &srcLoc, NULL); - } - else - { - for (const RECT * rect = copyDirtyRects; - rect < copyDirtyRects + nbCopyDirtyRects; ++rect) - CopyDirtyRect(copySlot->GetGfxList(), &dstLoc, &srcLoc, *rect); - } + postProcessor.CopyFrame( + copySlot->GetGfxList(), fbRes->Get().Get(), copySrcResource.Get(), + copyDirtyRects, nbCopyDirtyRects, fullCopy); copySlot->EndTiming(); if (!copySlot->Execute()) diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index 1ea3a4af..83135190 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -63,9 +63,9 @@ private: DWORD m_lastShapeId = 0; std::atomic m_sdrWhiteLevel { KVMFR_SDR_WHITE_LEVEL_DEFAULT }; - // 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. + // 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; diff --git a/idd/LGIdd/effect/CColorTransformEffect.cpp b/idd/LGIdd/effect/CColorTransformEffect.cpp index 381ca680..6bdd7059 100644 --- a/idd/LGIdd/effect/CColorTransformEffect.cpp +++ b/idd/LGIdd/effect/CColorTransformEffect.cpp @@ -292,7 +292,7 @@ ComPtr CColorTransformEffect::Run( m_uploadPending = false; } - TransitionDst(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE, + TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); D3D12_CPU_DESCRIPTOR_HANDLE handle = @@ -332,6 +332,6 @@ ComPtr CColorTransformEffect::Run( commandList->Dispatch(m_threadsX, m_threadsY, 1); TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, - D3D12_RESOURCE_STATE_COPY_SOURCE); + D3D12_RESOURCE_STATE_COMMON); return m_dst; } diff --git a/idd/LGIdd/effect/CComputeEffect.cpp b/idd/LGIdd/effect/CComputeEffect.cpp index b2c8f4ee..6d50cfda 100644 --- a/idd/LGIdd/effect/CComputeEffect.cpp +++ b/idd/LGIdd/effect/CComputeEffect.cpp @@ -27,7 +27,7 @@ namespace PostProcessUtil { - bool CreateDefaultTexture(const ComPtr& device, + static bool CreateDefaultResource(const ComPtr& device, const D3D12_RESOURCE_DESC& desc, ComPtr& resource) { D3D12_HEAP_PROPERTIES heapProps = {}; @@ -41,17 +41,38 @@ namespace PostProcessUtil &heapProps, D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, &desc, - D3D12_RESOURCE_STATE_COPY_SOURCE, + D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&resource)); if (FAILED(hr)) { - DEBUG_ERROR_HR(hr, "Failed to create post-processing destination texture"); + DEBUG_ERROR_HR(hr, "Failed to create post-processing destination resource"); return false; } return true; } + + bool CreateDefaultTexture(const ComPtr& device, + const D3D12_RESOURCE_DESC& desc, ComPtr& resource) + { + return CreateDefaultResource(device, desc, resource); + } + + bool CreateDefaultBuffer(const ComPtr& device, + UINT64 size, ComPtr& resource) + { + D3D12_RESOURCE_DESC desc = {}; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Width = size; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc.Count = 1; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + return CreateDefaultResource(device, desc, resource); + } } bool CComputeEffect::InitCompute(const ComPtr& device, diff --git a/idd/LGIdd/effect/CComputeEffect.h b/idd/LGIdd/effect/CComputeEffect.h index 45ef8d99..188dbb2a 100644 --- a/idd/LGIdd/effect/CComputeEffect.h +++ b/idd/LGIdd/effect/CComputeEffect.h @@ -37,6 +37,8 @@ namespace PostProcessUtil bool CreateDefaultTexture(const ComPtr& device, const D3D12_RESOURCE_DESC& desc, ComPtr& resource); + bool CreateDefaultBuffer(const ComPtr& device, + UINT64 size, ComPtr& resource); } class CComputeEffect : public CPostProcessEffect diff --git a/idd/LGIdd/effect/CDownsampleEffect.cpp b/idd/LGIdd/effect/CDownsampleEffect.cpp index 8d7bc1b0..df56d001 100644 --- a/idd/LGIdd/effect/CDownsampleEffect.cpp +++ b/idd/LGIdd/effect/CDownsampleEffect.cpp @@ -241,7 +241,7 @@ ComPtr CDownsampleEffect::Run( UNREFERENCED_PARAMETER(dirtyRects); UNREFERENCED_PARAMETER(nbDirtyRects); - TransitionDst(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE, + TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); D3D12_CPU_DESCRIPTOR_HANDLE handle = @@ -273,6 +273,6 @@ ComPtr CDownsampleEffect::Run( commandList->Dispatch(m_threadsX, m_threadsY, 1); TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, - D3D12_RESOURCE_STATE_COPY_SOURCE); + D3D12_RESOURCE_STATE_COMMON); return m_dst; } diff --git a/idd/LGIdd/effect/CHDR16to10Effect.cpp b/idd/LGIdd/effect/CHDR16to10Effect.cpp index 4cfe8ede..3a0f60cd 100644 --- a/idd/LGIdd/effect/CHDR16to10Effect.cpp +++ b/idd/LGIdd/effect/CHDR16to10Effect.cpp @@ -155,7 +155,7 @@ ComPtr CHDR16to10Effect::Run( UNREFERENCED_PARAMETER(dirtyRects); UNREFERENCED_PARAMETER(nbDirtyRects); - TransitionDst(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE, + TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); D3D12_CPU_DESCRIPTOR_HANDLE handle = @@ -187,6 +187,6 @@ ComPtr CHDR16to10Effect::Run( commandList->Dispatch(m_threadsX, m_threadsY, 1); TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, - D3D12_RESOURCE_STATE_COPY_SOURCE); + D3D12_RESOURCE_STATE_COMMON); return m_dst; } diff --git a/idd/LGIdd/effect/CRGB24Effect.cpp b/idd/LGIdd/effect/CRGB24Effect.cpp index 1a92c862..127a37f9 100644 --- a/idd/LGIdd/effect/CRGB24Effect.cpp +++ b/idd/LGIdd/effect/CRGB24Effect.cpp @@ -20,69 +20,517 @@ #include "CRGB24Effect.h" +#include "CDebug.h" #include "../CSettings.h" +#include "common/LGMPConfig.h" + +#include +#include +#include +#include using namespace PostProcessUtil; +static_assert(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT == 256, + "RGB24 shader row alignment must match D3D12"); + +struct CRGB24Effect::State +{ + enum class Phase + { + DISABLED, + NATIVE_WARMUP, + NATIVE_SAMPLE, + PACKED_WARMUP, + PACKED_SAMPLE, + LOCKED_NATIVE, + LOCKED_PACKED, + }; + + struct FormatKey + { + D3D12_RESOURCE_DIMENSION resourceDimension = D3D12_RESOURCE_DIMENSION_UNKNOWN; + UINT64 resourceWidth = 0; + UINT resourceHeight = 0; + DXGI_FORMAT resourceFormat = DXGI_FORMAT_UNKNOWN; + unsigned width = 0; + unsigned height = 0; + FrameType format = FRAME_TYPE_INVALID; + bool hdr = false; + bool hdrPQ = false; + std::shared_ptr colorTransform; + }; + + static const unsigned WarmupCount = LGMP_Q_FRAME_LEN; + static const unsigned SampleCount = 64; + static const unsigned TrimCount = SampleCount / 8; + + SRWLOCK lock = SRWLOCK_INIT; + Phase phase = Phase::DISABLED; + FormatKey format = {}; + bool formatValid = false; + uint64_t generation = 0; + unsigned warmups = 0; + unsigned sampleCount = 0; + uint64_t nativeMean = 0; + uint64_t samples[SampleCount] = {}; + + static bool IsEligible(const D12FrameFormat& format) + { + if (format.hdr || + format.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D || + format.desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) + return false; + + if (!format.colorTransform || + (!format.colorTransform->matrixEnabled && + !format.colorTransform->lutEnabled)) + return true; + + return IsIdentityColorTransform(*format.colorTransform); + } + + bool WantsPackedLocked() const + { + return phase == Phase::PACKED_WARMUP || + phase == Phase::PACKED_SAMPLE || + phase == Phase::LOCKED_PACKED; + } + + bool IsBenchmarkingLocked() const + { + return phase == Phase::NATIVE_WARMUP || + phase == Phase::NATIVE_SAMPLE || + phase == Phase::PACKED_WARMUP || + phase == Phase::PACKED_SAMPLE; + } + + void ResetStageLocked() + { + warmups = 0; + sampleCount = 0; + std::memset(samples, 0, sizeof(samples)); + } + + uint64_t TrimmedMeanLocked() + { + std::sort(samples, samples + SampleCount); + + uint64_t total = 0; + for (unsigned i = TrimCount; i < SampleCount - TrimCount; ++i) + total += samples[i]; + + return total / (SampleCount - TrimCount * 2); + } + + void Update(const D12FrameFormat& next) + { + AcquireSRWLockExclusive(&lock); + + const bool formatChanged = !formatValid || + format.resourceDimension != next.desc.Dimension || + format.resourceWidth != next.desc.Width || + format.resourceHeight != next.desc.Height || + format.resourceFormat != next.desc.Format || + format.width != next.width || + format.height != next.height || + format.format != next.format || + format.hdr != next.hdr || + format.hdrPQ != next.hdrPQ || + format.colorTransform != next.colorTransform; + + if (formatChanged) + { + format.resourceDimension = next.desc.Dimension; + format.resourceWidth = next.desc.Width; + format.resourceHeight = next.desc.Height; + format.resourceFormat = next.desc.Format; + format.width = next.width; + format.height = next.height; + format.format = next.format; + format.hdr = next.hdr; + format.hdrPQ = next.hdrPQ; + format.colorTransform = next.colorTransform; + formatValid = true; + + phase = IsEligible(next) ? + Phase::NATIVE_WARMUP : Phase::DISABLED; + nativeMean = 0; + ++generation; + ResetStageLocked(); + } + + switch (phase) + { + case Phase::NATIVE_WARMUP: + if (warmups >= WarmupCount) + { + phase = Phase::NATIVE_SAMPLE; + ++generation; + ResetStageLocked(); + } + break; + + case Phase::NATIVE_SAMPLE: + if (sampleCount >= SampleCount) + { + nativeMean = TrimmedMeanLocked(); + phase = Phase::PACKED_WARMUP; + ++generation; + ResetStageLocked(); + } + break; + + case Phase::PACKED_WARMUP: + if (warmups >= WarmupCount) + { + phase = Phase::PACKED_SAMPLE; + ++generation; + ResetStageLocked(); + } + break; + + case Phase::PACKED_SAMPLE: + if (sampleCount >= SampleCount) + { + const uint64_t packedMean = TrimmedMeanLocked(); + const uint64_t relativeThreshold = nativeMean / 20; + const uint64_t threshold = relativeThreshold > 50000ULL ? + relativeThreshold : 50000ULL; + // Prefer the bandwidth saving unless native is meaningfully faster. + const bool usePacked = packedMean <= nativeMean || + packedMean - nativeMean <= threshold; + + DEBUG_INFO( + "RGB24 benchmark: native=%llu us, packed=%llu us, selected=%s", + (unsigned long long)(nativeMean / 1000), + (unsigned long long)(packedMean / 1000), + usePacked ? "packed" : "native"); + + phase = usePacked ? + Phase::LOCKED_PACKED : Phase::LOCKED_NATIVE; + ++generation; + ResetStageLocked(); + } + break; + + default: + break; + } + + ReleaseSRWLockExclusive(&lock); + } + + bool WantsPacked() + { + AcquireSRWLockShared(&lock); + const bool result = WantsPackedLocked(); + ReleaseSRWLockShared(&lock); + return result; + } + + bool IsBenchmarking() + { + AcquireSRWLockShared(&lock); + const bool result = IsBenchmarkingLocked(); + ReleaseSRWLockShared(&lock); + return result; + } + + uint64_t GetTimingToken(bool packed) + { + AcquireSRWLockShared(&lock); + + const uint64_t result = IsBenchmarkingLocked() && + packed == WantsPackedLocked() ? generation : 0; + + ReleaseSRWLockShared(&lock); + return result; + } + + void Reject() + { + AcquireSRWLockExclusive(&lock); + if (WantsPackedLocked()) + { + phase = Phase::LOCKED_NATIVE; + ++generation; + ResetStageLocked(); + } + ReleaseSRWLockExclusive(&lock); + } + + void RecordTiming(uint64_t token, bool fullCopy, uint64_t totalTime) + { + AcquireSRWLockExclusive(&lock); + + if (token == generation && fullCopy) + switch (phase) + { + case Phase::NATIVE_WARMUP: + case Phase::PACKED_WARMUP: + ++warmups; + break; + + case Phase::NATIVE_SAMPLE: + case Phase::PACKED_SAMPLE: + if (sampleCount < SampleCount) + samples[sampleCount++] = totalTime; + break; + + default: + break; + } + + ReleaseSRWLockExclusive(&lock); + } +}; + bool CRGB24Effect::Init(const ComPtr& device) { - if (!g_settings.ReadBoolValue(L"AllowRGB24", false)) + if (!g_settings.ReadBoolValue(L"AllowRGB24", true)) return false; D3D12_DESCRIPTOR_RANGE ranges[2] = {}; - ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; - ranges[0].NumDescriptors = 1; - ranges[0].BaseShaderRegister = 0; + ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + ranges[0].NumDescriptors = 1; + ranges[0].BaseShaderRegister = 0; ranges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; - ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; - ranges[1].NumDescriptors = 1; - ranges[1].BaseShaderRegister = 0; + ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + ranges[1].NumDescriptors = 1; + ranges[1].BaseShaderRegister = 0; ranges[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; const char * shader = - "Texture2D src : register(t0);\n" - "RWTexture2D dst : register(u0);\n" + "Texture2D src : register(t0);\n" + "RWByteAddressBuffer dst : register(u0);\n" "[numthreads(" POST_PROCESS_THREADS_STR ", " POST_PROCESS_THREADS_STR ", 1)]\n" "void main(uint3 dt : SV_DispatchThreadID)\n" "{\n" - " uint fstInputX = (dt.x * 4) / 3;\n" - " float4 color0 = src[uint2(fstInputX, dt.y)];\n" - " uint sndInputX = fstInputX + 1;\n" - " float4 color3 = src[uint2(sndInputX, dt.y)];\n" + " uint width, height;\n" + " src.GetDimensions(width, height);\n" + " uint rowBytes = width * 3;\n" + " uint dataWidth = ((rowBytes + 255) & ~255u) / 4;\n" + " if (dt.x >= dataWidth || dt.y >= height)\n" + " return;\n" + " uint rowOffset = dt.x * 4;\n" + " if (rowOffset >= rowBytes)\n" + " {\n" + " dst.Store((dt.y * dataWidth + dt.x) * 4, 0);\n" + " return;\n" + " }\n" + " uint firstX = (dt.x * 4) / 3;\n" + " uint secondX = firstX + 1;\n" + " float4 color0 = src[uint2(firstX, dt.y)];\n" + " float4 color3 = secondX < width ?\n" + " src[uint2(secondX, dt.y)] : 0.0f;\n" " uint xmod3 = dt.x % 3;\n" " float4 color1 = xmod3 <= 1 ? color0 : color3;\n" " float4 color2 = xmod3 == 0 ? color0 : color3;\n" - " float b = color0.bgr[xmod3];\n" - " float g = color1.grb[xmod3];\n" - " float r = color2.rbg[xmod3];\n" - " float a = color3.bgr[xmod3];\n" - " dst[dt.xy] = float4(r, g, b, a);\n" + " float4 packed = float4(\n" + " color0.bgr[xmod3], color1.grb[xmod3],\n" + " color2.rbg[xmod3], color3.bgr[xmod3]);\n" + " uint4 bytes = (uint4)(saturate(packed) * 255.0f + 0.5f);\n" + " uint value = bytes.x | (bytes.y << 8) |\n" + " (bytes.z << 16) | (bytes.w << 24);\n" + " dst.Store((dt.y * dataWidth + dt.x) * 4, value);\n" "}\n"; - return InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader); + if (!InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader)) + return false; + + m_state = std::make_shared(); + return true; +} + +void CRGB24Effect::ShareState(const CPostProcessEffect& other) +{ + m_state = static_cast(other).m_state; +} + +void CRGB24Effect::Update(const D12FrameFormat& format) +{ + m_state->Update(format); +} + +bool CRGB24Effect::NeedsReconfigure() const +{ + return m_state->WantsPacked() != Enabled; +} + +bool CRGB24Effect::RequiresFullDamage() const +{ + return m_state->IsBenchmarking(); +} + +uint64_t CRGB24Effect::GetTimingToken() const +{ + return m_state->GetTimingToken(Enabled); +} + +void CRGB24Effect::RecordTiming( + uint64_t token, bool fullCopy, uint64_t totalTime) +{ + m_state->RecordTiming(token, fullCopy, totalTime); +} + +bool CRGB24Effect::GetCopyLayout( + unsigned * pitch, unsigned * dataHeight) const +{ + *pitch = m_pitch; + *dataHeight = m_height; + return true; +} + +static void GetCopySpan(const RECT& rect, unsigned width, + unsigned pitch, UINT64 * left, UINT64 * right) +{ + // Damage stays in logical pixels until the packed copy is recorded. + *left = ((UINT64)rect.left * 3) & ~3ULL; + if (rect.left == 0 && rect.right == (LONG)width) + *right = pitch; + else + *right = ((UINT64)rect.right * 3 + 3) & ~3ULL; +} + +bool CRGB24Effect::ShouldCopyFully( + const RECT dirtyRects[], unsigned nbDirtyRects) const +{ + static const unsigned commandLimit = 256; + + const uint64_t frameSize = (uint64_t)m_pitch * m_height; + uint64_t copiedBytes = 0; + unsigned commands = 0; + for (const RECT * rect = dirtyRects; + rect < dirtyRects + nbDirtyRects; ++rect) + { + UINT64 left; + UINT64 right; + GetCopySpan(*rect, m_width, m_pitch, &left, &right); + + const unsigned rows = (unsigned)(rect->bottom - rect->top); + if (left == 0 && right == m_pitch) + { + ++commands; + copiedBytes += (uint64_t)rows * m_pitch; + } + else + { + commands += rows; + copiedBytes += (right - left) * rows; + } + + if (commands > commandLimit || copiedBytes >= frameSize) + return true; + } + + return false; +} + +void CRGB24Effect::CopyFrame( + const ComPtr& commandList, + ID3D12Resource * dst, ID3D12Resource * src, + const RECT dirtyRects[], unsigned nbDirtyRects, bool fullCopy) const +{ + if (fullCopy) + { + commandList->CopyBufferRegion( + dst, 0, src, 0, (UINT64)m_pitch * m_height); + return; + } + + for (const RECT * rect = dirtyRects; + rect < dirtyRects + nbDirtyRects; ++rect) + { + UINT64 left; + UINT64 right; + GetCopySpan(*rect, m_width, m_pitch, &left, &right); + + if (left == 0 && right == m_pitch) + { + const UINT64 offset = (UINT64)rect->top * m_pitch; + const UINT64 size = + (UINT64)(rect->bottom - rect->top) * m_pitch; + commandList->CopyBufferRegion(dst, offset, src, offset, size); + continue; + } + + for (LONG y = rect->top; y < rect->bottom; ++y) + { + const UINT64 offset = (UINT64)y * m_pitch + left; + commandList->CopyBufferRegion( + dst, offset, src, offset, right - left); + } + } } PostProcessStatus CRGB24Effect::SetFormat(const ComPtr& device, const D12FrameFormat& src, D12FrameFormat& dst) { - if (src.desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) + if (!m_state->WantsPacked()) + { + m_dst.Reset(); return PostProcessStatus::BYPASS_EFFECT; + } - const unsigned packedPitch = AlignTo((unsigned)src.desc.Width * 3, 4u); - D3D12_RESOURCE_DESC desc = src.desc; - desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - desc.Width = AlignTo(packedPitch / 4, 64u); - desc.Height = ((unsigned)src.desc.Width * src.desc.Height) / (packedPitch / 3); - desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + if (src.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D || + src.desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM || + src.hdr) + { + DEBUG_WARN("RGB24 packing is unavailable for the current format"); + m_state->Reject(); + m_dst.Reset(); + return PostProcessStatus::BYPASS_EFFECT; + } - if (!CreateDefaultTexture(device, desc, m_dst)) - return PostProcessStatus::FAILED; + if (src.desc.Width > + (UINT64_MAX - (D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1)) / 3) + { + m_state->Reject(); + m_dst.Reset(); + return PostProcessStatus::BYPASS_EFFECT; + } - m_threadsX = ((unsigned)desc.Width + (Threads - 1)) / Threads; - m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads; + const UINT64 packedPitch = AlignTo( + src.desc.Width * 3, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); + if (!src.desc.Height || packedPitch > LONG_MAX || + packedPitch > UINT64_MAX / src.desc.Height) + { + m_state->Reject(); + m_dst.Reset(); + return PostProcessStatus::BYPASS_EFFECT; + } - dst.desc = desc; - dst.format = FRAME_TYPE_BGR_32; + const UINT64 bufferSize = packedPitch * src.desc.Height; + const UINT64 maxUAVSize = + (1ULL << D3D12_REQ_BUFFER_RESOURCE_TEXEL_COUNT_2_TO_EXP) * + sizeof(uint32_t); + if (bufferSize > UINT32_MAX || bufferSize > maxUAVSize) + { + m_state->Reject(); + m_dst.Reset(); + return PostProcessStatus::BYPASS_EFFECT; + } + + if (!CreateDefaultBuffer(device, bufferSize, m_dst)) + { + m_state->Reject(); + m_dst.Reset(); + return PostProcessStatus::BYPASS_EFFECT; + } + + const unsigned dataWidth = (unsigned)(packedPitch / 4); + m_threadsX = (dataWidth + (Threads - 1)) / Threads; + m_threadsY = (src.desc.Height + (Threads - 1)) / Threads; + m_width = (unsigned)src.desc.Width; + m_height = src.desc.Height; + m_pitch = (unsigned)packedPitch; + + dst.desc = m_dst->GetDesc(); + dst.dataWidth = dataWidth; + dst.dataHeight = src.desc.Height; + dst.pitch = m_pitch; + dst.format = FRAME_TYPE_BGR_32; return PostProcessStatus::SUCCESS; } @@ -94,7 +542,7 @@ ComPtr CRGB24Effect::Run(const ComPtr& device, UNREFERENCED_PARAMETER(dirtyRects); UNREFERENCED_PARAMETER(nbDirtyRects); - TransitionDst(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE, + TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); D3D12_CPU_DESCRIPTOR_HANDLE handle = @@ -103,31 +551,27 @@ ComPtr CRGB24Effect::Run(const ComPtr& device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; - srvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - srvDesc.Texture2D.MipLevels = 1; + srvDesc.Texture2D.MipLevels = 1; device->CreateShaderResourceView(src.Get(), &srvDesc, handle); handle.ptr += inc; D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; - uavDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + uavDesc.Format = DXGI_FORMAT_R32_TYPELESS; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + uavDesc.Buffer.NumElements = (UINT)(m_dst->GetDesc().Width / 4); + uavDesc.Buffer.StructureByteStride = 0; + uavDesc.Buffer.CounterOffsetInBytes = 0; + uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW; device->CreateUnorderedAccessView(m_dst.Get(), nullptr, &uavDesc, handle); Bind(commandList); commandList->Dispatch(m_threadsX, m_threadsY, 1); TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, - D3D12_RESOURCE_STATE_COPY_SOURCE); - - for (RECT * rect = dirtyRects; rect < dirtyRects + *nbDirtyRects; ++rect) - { - const LONG left = rect->left; - const LONG right = rect->right; - rect->left = (left * 3) / 4; - rect->right = (right * 3 + 3) / 4; - } + D3D12_RESOURCE_STATE_COMMON); return m_dst; } diff --git a/idd/LGIdd/effect/CRGB24Effect.h b/idd/LGIdd/effect/CRGB24Effect.h index 594fb4ca..70e29604 100644 --- a/idd/LGIdd/effect/CRGB24Effect.h +++ b/idd/LGIdd/effect/CRGB24Effect.h @@ -24,10 +24,33 @@ class CRGB24Effect : public CComputeEffect { +private: + struct State; + std::shared_ptr m_state; + unsigned m_width = 0; + unsigned m_height = 0; + unsigned m_pitch = 0; + public: const char * GetName() const override { return "RGB24"; } bool Init(const ComPtr& device); + void ShareState(const CPostProcessEffect& other) override; + void Update(const D12FrameFormat& format) override; + bool NeedsReconfigure() const override; + bool RequiresFullDamage() const override; + uint64_t GetTimingToken() const override; + void RecordTiming( + uint64_t token, bool fullCopy, uint64_t totalTime) override; + bool GetCopyLayout( + unsigned * pitch, unsigned * dataHeight) const override; + bool ShouldCopyFully( + const RECT dirtyRects[], unsigned nbDirtyRects) const override; + void CopyFrame( + const ComPtr& commandList, + ID3D12Resource * dst, ID3D12Resource * src, + const RECT dirtyRects[], unsigned nbDirtyRects, + bool fullCopy) const override; PostProcessStatus SetFormat(const ComPtr& device, const D12FrameFormat& src, D12FrameFormat& dst) override;