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;