From 1b7c00dc8203a9f86a7dd705979c9809cd4e9088 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 3 Jun 2026 08:55:18 +1000 Subject: [PATCH] [idd] driver: implement postprocess filters --- idd/LGIdd/CD3D12CommandQueue.h | 5 + idd/LGIdd/CD3D12Device.cpp | 23 ++- idd/LGIdd/CD3D12Device.h | 4 +- idd/LGIdd/CIndirectDeviceContext.cpp | 66 +++--- idd/LGIdd/CIndirectDeviceContext.h | 4 +- idd/LGIdd/CPostProcessor.cpp | 143 +++++++++++++ idd/LGIdd/CPostProcessor.h | 84 ++++++++ idd/LGIdd/CSettings.h | 4 +- idd/LGIdd/CSwapChainProcessor.cpp | 148 +++++++++++--- idd/LGIdd/CSwapChainProcessor.h | 2 + idd/LGIdd/LGIdd.vcxproj | 18 +- idd/LGIdd/LGIdd.vcxproj.filters | 38 +++- idd/LGIdd/effect/CComputeEffect.cpp | 165 +++++++++++++++ idd/LGIdd/effect/CComputeEffect.h | 52 +++++ idd/LGIdd/effect/CDownsampleEffect.cpp | 269 +++++++++++++++++++++++++ idd/LGIdd/effect/CDownsampleEffect.h | 62 ++++++ idd/LGIdd/effect/CHDR16to10Effect.cpp | 152 ++++++++++++++ idd/LGIdd/effect/CHDR16to10Effect.h | 37 ++++ idd/LGIdd/effect/CRGB24Effect.cpp | 123 +++++++++++ idd/LGIdd/effect/CRGB24Effect.h | 30 +++ 20 files changed, 1360 insertions(+), 69 deletions(-) create mode 100644 idd/LGIdd/CPostProcessor.cpp create mode 100644 idd/LGIdd/CPostProcessor.h create mode 100644 idd/LGIdd/effect/CComputeEffect.cpp create mode 100644 idd/LGIdd/effect/CComputeEffect.h create mode 100644 idd/LGIdd/effect/CDownsampleEffect.cpp create mode 100644 idd/LGIdd/effect/CDownsampleEffect.h create mode 100644 idd/LGIdd/effect/CHDR16to10Effect.cpp create mode 100644 idd/LGIdd/effect/CHDR16to10Effect.h create mode 100644 idd/LGIdd/effect/CRGB24Effect.cpp create mode 100644 idd/LGIdd/effect/CRGB24Effect.h diff --git a/idd/LGIdd/CD3D12CommandQueue.h b/idd/LGIdd/CD3D12CommandQueue.h index 5cc8716a..fb8b6064 100644 --- a/idd/LGIdd/CD3D12CommandQueue.h +++ b/idd/LGIdd/CD3D12CommandQueue.h @@ -93,6 +93,11 @@ class CD3D12CommandQueue bool IsReady () const { return !m_pending ; } HANDLE GetEvent() const { return m_event.Get(); } + void WaitFor(CD3D12CommandQueue& queue) + { + m_queue->Wait(queue.m_fence.Get(), queue.m_fenceValue); + } + ComPtr GetCmdQueue() { return m_queue; } ComPtr GetGfxList() { return m_gfxList; } }; diff --git a/idd/LGIdd/CD3D12Device.cpp b/idd/LGIdd/CD3D12Device.cpp index 49c74f18..813c881a 100644 --- a/idd/LGIdd/CD3D12Device.cpp +++ b/idd/LGIdd/CD3D12Device.cpp @@ -132,8 +132,9 @@ CD3D12Device::InitResult CD3D12Device::Init(CIVSHMEM &ivshmem, UINT64 &alignSize m_indirectCopy ? CD3D12CommandQueue::NORMAL : CD3D12CommandQueue::FAST)) return InitResult::FAILURE; - //if (!m_computeQueue.Init(m_device.Get(), D3D12_COMMAND_LIST_TYPE_COMPUTE, L"Compute")) - //return InitResult::FAILURE; + if (!m_computeQueue.Init(m_device.Get(), D3D12_COMMAND_LIST_TYPE_COMPUTE, L"Compute", + CD3D12CommandQueue::FAST)) + return InitResult::FAILURE; DEBUG_INFO("Created CD3D12Device"); return InitResult::SUCCESS; @@ -212,4 +213,20 @@ CD3D12CommandQueue * CD3D12Device::GetCopyQueue() DEBUG_ERROR("Failed to get a copy queue"); return nullptr; -} \ No newline at end of file +} + +CD3D12CommandQueue * CD3D12Device::GetComputeQueue() +{ + for (int c = 0; c < 100; ++c) + { + if (m_computeQueue.IsReady()) + { + m_computeQueue.Reset(); + return &m_computeQueue; + } + Sleep(1); + } + + DEBUG_ERROR("Failed to get a compute queue"); + return nullptr; +} diff --git a/idd/LGIdd/CD3D12Device.h b/idd/LGIdd/CD3D12Device.h index 46d1407f..3e1d0382 100644 --- a/idd/LGIdd/CD3D12Device.h +++ b/idd/LGIdd/CD3D12Device.h @@ -74,5 +74,5 @@ struct CD3D12Device bool IsIndirectCopy() { return m_indirectCopy; } CD3D12CommandQueue * GetCopyQueue(); - CD3D12CommandQueue & GetComputeQueue() { return m_computeQueue; } -}; \ No newline at end of file + CD3D12CommandQueue * GetComputeQueue(); +}; diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index 3b338a9d..33752c66 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -803,7 +803,8 @@ void CIndirectDeviceContext::LGMPTimer() } CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrameBuffer( - unsigned width, unsigned height, unsigned pitch, DXGI_FORMAT format, const RECT * dirtyRects, unsigned nbDirtyRects) + unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, + const RECT * dirtyRects, unsigned nbDirtyRects) { PreparedFrameBuffer result = {}; @@ -811,15 +812,20 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame return result; if (InterlockedCompareExchange(&m_recoverModeUpdateSwapChain, 0, 0) && - width == m_setMode.width && height == m_setMode.height) + srcFormat.width == m_setMode.width && srcFormat.height == m_setMode.height) InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); - if (m_width != width || m_height != height || m_pitch != pitch || m_format != format) + if (m_width != dstFormat.desc.Width || + m_height != dstFormat.desc.Height || + m_pitch != pitch || + m_format != dstFormat.desc.Format || + m_frameType != dstFormat.format) { - m_width = width; - m_height = height; - m_format = format; - m_pitch = pitch; + m_width = (unsigned)dstFormat.desc.Width; + m_height = dstFormat.desc.Height; + m_format = dstFormat.desc.Format; + m_frameType = dstFormat.format; + m_pitch = pitch; ++m_formatVer; } @@ -832,36 +838,38 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame while (lgmpHostQueuePending(m_frameQueue) == LGMP_Q_FRAME_LEN) Sleep(0); - int bpp = 4; - switch (format) + if (dstFormat.format == FRAME_TYPE_INVALID) { - case DXGI_FORMAT_B8G8R8A8_UNORM : fi->type = FRAME_TYPE_BGRA ; break; - case DXGI_FORMAT_R8G8B8A8_UNORM : fi->type = FRAME_TYPE_RGBA ; break; - case DXGI_FORMAT_R10G10B10A2_UNORM : fi->type = FRAME_TYPE_RGBA10 ; break; - - case DXGI_FORMAT_R16G16B16A16_FLOAT: - fi->type = FRAME_TYPE_RGBA16F; - bpp = 8; - break; - - default: - DEBUG_ERROR("Unsuppoted DXGI format 0x%08x", format); - return result; + DEBUG_ERROR("Unsupported frame format, skipping frame"); + return result; } + const unsigned maxRows = (unsigned)(m_maxFrameSize / pitch); + const int bpp = dstFormat.format == FRAME_TYPE_RGBA16F ? 8 : 4; + KVMFRFrameFlags flags = + (dstFormat.hdr ? FRAME_FLAG_HDR : 0) | + (dstFormat.hdrPQ ? FRAME_FLAG_HDR_PQ : 0); + + if (dstFormat.format == FRAME_TYPE_RGBA16F) + flags |= FRAME_FLAG_HDR; + + if (maxRows < dstFormat.desc.Height) + flags |= FRAME_FLAG_TRUNCATED; + fi->formatVer = m_formatVer; fi->frameSerial = m_frameSerial++; - fi->screenWidth = width; - fi->screenHeight = height; - fi->dataWidth = width; - fi->dataHeight = height; - fi->frameWidth = width; - fi->frameHeight = height; + fi->screenWidth = srcFormat.width; + fi->screenHeight = srcFormat.height; + fi->dataWidth = (unsigned)dstFormat.desc.Width; + fi->dataHeight = min(maxRows, dstFormat.desc.Height); + fi->frameWidth = dstFormat.width; + fi->frameHeight = dstFormat.height; fi->stride = pitch / bpp; fi->pitch = pitch; // fi->offset is initialized at startup - fi->flags = 0; + fi->flags = flags; fi->rotation = FRAME_ROT_0; + fi->type = dstFormat.format; fi->damageRectsCount = 0; if (nbDirtyRects <= ARRAYSIZE(fi->damageRects)) { @@ -875,7 +883,7 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame } } - FrameBuffer* fb = m_frameBuffer[m_frameIndex]; + FrameBuffer* fb = m_frameBuffer[m_frameIndex]; fb->wp = 0; lgmpHostQueuePost(m_frameQueue, 0, m_frameMemory[m_frameIndex]); diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index dc2260a4..2a8bf7ec 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -28,6 +28,7 @@ #include "CIVSHMEM.h" #include "CSettings.h" #include "CEdid.h" +#include "CPostProcessor.h" extern "C" { #include "lgmp/host.h" @@ -83,6 +84,7 @@ private: unsigned m_height = 0; unsigned m_pitch = 0; DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN; + FrameType m_frameType = FRAME_TYPE_INVALID; bool m_hasFrame = false; UINT m_iddCxVersion = 0; @@ -148,7 +150,7 @@ public: uint8_t* mem; }; - PreparedFrameBuffer PrepareFrameBuffer(unsigned width, unsigned height, unsigned pitch, DXGI_FORMAT format, const RECT * dirtyRects, unsigned nbDirtyRects); + PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects); void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const; void FinalizeFrameBuffer(unsigned frameIndex) const; diff --git a/idd/LGIdd/CPostProcessor.cpp b/idd/LGIdd/CPostProcessor.cpp new file mode 100644 index 00000000..44f8a61e --- /dev/null +++ b/idd/LGIdd/CPostProcessor.cpp @@ -0,0 +1,143 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include "CPostProcessor.h" + +#include "CD3D12Device.h" +#include "CDebug.h" +#include "effect/CDownsampleEffect.h" +#include "effect/CHDR16to10Effect.h" +#include "effect/CRGB24Effect.h" + +#include + +bool CPostProcessor::Init(std::shared_ptr dx12Device) +{ + m_dx12Device = dx12Device; + m_device = dx12Device->GetDevice(); + m_effects.clear(); + + std::unique_ptr downsample(new CDownsampleEffect()); + if (downsample->Init(m_device)) + { + DEBUG_INFO("Created post-processing effect: %s", downsample->GetName()); + m_effects.push_back(std::move(downsample)); + } + + std::unique_ptr hdr16to10(new CHDR16to10Effect()); + if (hdr16to10->Init(m_device)) + { + DEBUG_INFO("Created post-processing effect: %s", hdr16to10->GetName()); + m_effects.push_back(std::move(hdr16to10)); + } + else + return false; + + std::unique_ptr rgb24(new CRGB24Effect()); + if (rgb24->Init(m_device)) + { + DEBUG_INFO("Created post-processing effect: %s", rgb24->GetName()); + m_effects.push_back(std::move(rgb24)); + } + + return true; +} + +void CPostProcessor::Reset() +{ + m_effects.clear(); + m_dx12Device.reset(); + m_device.Reset(); + m_srcFormat = {}; + m_dstFormat = {}; + m_effectsActive = false; +} + +bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, bool * formatChanged) +{ + if (formatChanged) + *formatChanged = false; + + if (srcFormat.desc.Width == m_srcFormat.desc.Width && + srcFormat.desc.Height == m_srcFormat.desc.Height && + srcFormat.desc.Format == m_srcFormat.desc.Format && + srcFormat.format == m_srcFormat.format && + srcFormat.width == m_srcFormat.width && + srcFormat.height == m_srcFormat.height && + srcFormat.hdr == m_srcFormat.hdr && + srcFormat.hdrPQ == m_srcFormat.hdrPQ) + return true; + + D12FrameFormat oldDst = m_dstFormat; + D12FrameFormat cur = srcFormat; + m_srcFormat = srcFormat; + m_effectsActive = false; + + for (const auto& effect : m_effects) + { + D12FrameFormat dst = cur; + switch (effect->SetFormat(m_device, cur, dst)) + { + case PostProcessStatus::SUCCESS: + effect->Enabled = true; + m_effectsActive = true; + cur = dst; + DEBUG_INFO("Post-processing effect active: %s", effect->GetName()); + break; + + case PostProcessStatus::BYPASS_EFFECT: + effect->Enabled = false; + break; + + case PostProcessStatus::FAILED: + DEBUG_ERROR("Failed to configure post-processing effect: %s", effect->GetName()); + return false; + } + } + + m_dstFormat = cur; + 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; + return true; +} + +void CPostProcessor::AdjustFrameDamage(RECT dirtyRects[], unsigned * nbDirtyRects) +{ + for (const auto& effect : m_effects) + if (effect->Enabled) + effect->AdjustDamage(dirtyRects, nbDirtyRects); +} + +ComPtr CPostProcessor::Run( + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) +{ + ComPtr next = src; + for (const auto& effect : m_effects) + { + if (!effect->Enabled) + continue; + + //DEBUG_TRACE("Run post-processing effect: %s", effect->GetName()); + effect->AdjustDamage(dirtyRects, nbDirtyRects); + next = effect->Run(m_device, commandList, next, dirtyRects, nbDirtyRects); + } + + return next; +} diff --git a/idd/LGIdd/CPostProcessor.h b/idd/LGIdd/CPostProcessor.h new file mode 100644 index 00000000..dcb54289 --- /dev/null +++ b/idd/LGIdd/CPostProcessor.h @@ -0,0 +1,84 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +struct CD3D12Device; + +extern "C" { + #include "common/types.h" +} + +using namespace Microsoft::WRL; + +enum class PostProcessStatus +{ + SUCCESS, + BYPASS_EFFECT, + FAILED +}; + +struct D12FrameFormat +{ + D3D12_RESOURCE_DESC desc = {}; + unsigned width = 0; + unsigned height = 0; + FrameType format = FRAME_TYPE_INVALID; + bool hdr = false; + bool hdrPQ = false; +}; + +class CPostProcessEffect +{ +public: + virtual ~CPostProcessEffect() {} + virtual const char * GetName() const = 0; + 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); } + virtual ComPtr Run(const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) = 0; + bool Enabled = false; +}; + +class CPostProcessor +{ +private: + std::shared_ptr m_dx12Device; + ComPtr m_device; + std::vector> m_effects; + D12FrameFormat m_srcFormat = {}; + D12FrameFormat m_dstFormat = {}; + bool m_effectsActive = false; + +public: + bool Init(std::shared_ptr dx12Device); + void Reset(); + + bool Configure(const D12FrameFormat& srcFormat, bool * formatChanged); + void AdjustFrameDamage(RECT dirtyRects[], unsigned * nbDirtyRects); + ComPtr Run( + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects); + + const D12FrameFormat& GetOutputFormat() const { return m_dstFormat; } + bool HasActiveEffects() const { return m_effectsActive; } +}; diff --git a/idd/LGIdd/CSettings.h b/idd/LGIdd/CSettings.h index 278c22d0..2137a916 100644 --- a/idd/LGIdd/CSettings.h +++ b/idd/LGIdd/CSettings.h @@ -43,8 +43,8 @@ class CSettings bool GetExtraMode(DisplayMode & mode); unsigned GetDefaultRefresh() const; - std::wstring ReadStringValue(const wchar_t* name, const wchar_t* defaultValue); - bool ReadBoolValue(const wchar_t* name, bool defaultValue); + std::wstring ReadStringValue(const wchar_t* name, const wchar_t* defaultValue = nullptr); + bool ReadBoolValue(const wchar_t* name, bool defaultValue = false); private: DisplayModes m_displayModes; diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index a1590992..eccb46ac 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -34,6 +34,8 @@ CSwapChainProcessor::CSwapChainProcessor(IDDCX_MONITOR monitor, CIndirectDeviceC { m_resPool.Init(dx11Device, dx12Device); m_fbPool.Init(this); + if (!m_postProcessor.Init(dx12Device)) + DEBUG_ERROR("Failed to initialize post processor"); m_terminateEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr)); m_thread[0].Attach(CreateThread(nullptr, 0, _SwapChainThread, this, 0, nullptr)); @@ -50,6 +52,7 @@ CSwapChainProcessor::~CSwapChainProcessor() if (m_thread[1].Get()) WaitForSingleObject(m_thread[1].Get(), INFINITE); + m_postProcessor.Reset(); m_resPool.Reset(); m_fbPool.Reset(); delete[] m_shapeBuffer; @@ -268,6 +271,44 @@ static void CopyDirtyRect(ComPtr list, 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; + + if (rect.left < 0 ) rect.left = 0; + if (rect.top < 0 ) rect.top = 0; + if (rect.right > maxRight ) rect.right = maxRight; + if (rect.bottom > maxBottom) rect.bottom = maxBottom; + + return rect.left < rect.right && rect.top < rect.bottom; +} + +static void ClipDirtyRects(RECT dirtyRects[], unsigned * nbDirtyRects, + const D3D12_RESOURCE_DESC& desc) +{ + unsigned out = 0; + for (unsigned i = 0; i < *nbDirtyRects; ++i) + { + RECT rect = dirtyRects[i]; + if (ClipDirtyRect(rect, desc)) + dirtyRects[out++] = rect; + } + *nbDirtyRects = out; +} + +static FrameType GetFrameType(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_B8G8R8A8_UNORM : return FRAME_TYPE_BGRA; + case DXGI_FORMAT_R8G8B8A8_UNORM : return FRAME_TYPE_RGBA; + case DXGI_FORMAT_R10G10B10A2_UNORM : return FRAME_TYPE_RGBA10; + case DXGI_FORMAT_R16G16B16A16_FLOAT: return FRAME_TYPE_RGBA16F; + default : return FRAME_TYPE_INVALID; + } +} + bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount) { ComPtr texture; @@ -314,37 +355,47 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer srcRes->SetDirtyRects(dirtyRects, dirtyOut.DirtyRectOutCount); } - D3D12_RESOURCE_DESC desc = srcRes->GetRes()->GetDesc(); + D3D12_RESOURCE_DESC srcDesc = srcRes->GetRes()->GetDesc(); + D12FrameFormat srcFormat = {}; + srcFormat.desc = srcDesc; + srcFormat.width = (unsigned)srcDesc.Width; + srcFormat.height = srcDesc.Height; + srcFormat.format = GetFrameType(srcDesc.Format); + srcFormat.hdr = srcDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT; + srcFormat.hdrPQ = false; + + bool postProcessFormatChanged = false; + if (!m_postProcessor.Configure(srcFormat, &postProcessFormatChanged)) + return false; + + if (postProcessFormatChanged) + m_nbDirtyRects = 0; + + const D12FrameFormat& dstFormat = m_postProcessor.GetOutputFormat(); + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; m_dx12Device->GetDevice()->GetCopyableFootprints( - &desc, + &dstFormat.desc, 0, 1, 0, &layout, - NULL, + NULL, NULL, NULL); - auto buffer = m_devContext->PrepareFrameBuffer( - (int)desc.Width, - (int)desc.Height, - (int)layout.Footprint.RowPitch, - desc.Format, - srcRes->GetDirtyRects(), - srcRes->GetDirtyRectCount()); - - if (!buffer.mem) - return false; - - CFrameBufferResource * fbRes = m_fbPool.Get(buffer, - (size_t)layout.Footprint.RowPitch * desc.Height); - - if (!fbRes) + RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + RECT frameDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned nbDirtyRects = srcRes->GetDirtyRectCount(); + if (nbDirtyRects > ARRAYSIZE(currentDirtyRects)) + nbDirtyRects = 0; + else { - DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool"); - return false; + memcpy(currentDirtyRects, srcRes->GetDirtyRects(), nbDirtyRects * sizeof(*currentDirtyRects)); + memcpy(frameDirtyRects, currentDirtyRects, nbDirtyRects * sizeof(*frameDirtyRects)); } + unsigned frameDirtyRectCount = nbDirtyRects; + m_postProcessor.AdjustFrameDamage(frameDirtyRects, &frameDirtyRectCount); auto copyQueue = m_dx12Device->GetCopyQueue(); if (!copyQueue) @@ -353,10 +404,53 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer return false; } + ComPtr copySrcResource = srcRes->GetRes(); + CD3D12CommandQueue * computeQueue = nullptr; + if (m_postProcessor.HasActiveEffects()) + { + computeQueue = m_dx12Device->GetComputeQueue(); + if (!computeQueue) + { + DEBUG_ERROR("Failed to get a ComputeQueue"); + return false; + } + + srcRes->Sync(*computeQueue); + copySrcResource = m_postProcessor.Run( + computeQueue->GetGfxList(), copySrcResource, + currentDirtyRects, &nbDirtyRects); + + computeQueue->Execute(); + copyQueue->WaitFor(*computeQueue); + } + else + srcRes->Sync(*copyQueue); + + ClipDirtyRects(currentDirtyRects, &nbDirtyRects, dstFormat.desc); + + auto buffer = m_devContext->PrepareFrameBuffer( + (unsigned)layout.Footprint.RowPitch, + srcFormat, + dstFormat, + frameDirtyRects, + frameDirtyRectCount); + + if (!buffer.mem) + return false; + + CFrameBufferResource * fbRes = m_fbPool.Get(buffer, + (size_t)layout.Footprint.RowPitch * dstFormat.desc.Height); + + if (!fbRes) + { + DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool"); + return false; + } + copyQueue->SetCompletionCallback(&CompletionFunction, this, fbRes); D3D12_TEXTURE_COPY_LOCATION srcLoc = {}; - srcLoc.pResource = srcRes->GetRes().Get(); + srcLoc.pResource = copySrcResource.Get(); srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; srcLoc.SubresourceIndex = 0; @@ -365,11 +459,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; dstLoc.PlacedFootprint = layout; - srcRes->Sync(*copyQueue); - - const RECT * currentDirtyRects = srcRes->GetDirtyRects(); - unsigned nbDirtyRects = srcRes->GetDirtyRectCount(); - if (IsFullDamage(currentDirtyRects, nbDirtyRects, desc) || + if (IsFullDamage(currentDirtyRects, nbDirtyRects, dstFormat.desc) || nbDirtyRects > KVMFR_MAX_DAMAGE_RECTS || m_nbDirtyRects == 0) { copyQueue->GetGfxList()->CopyTextureRegion( @@ -383,7 +473,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer else { for (const RECT * rect = m_dirtyRects; rect < m_dirtyRects + m_nbDirtyRects; ++rect) - CopyDirtyRect(copyQueue->GetGfxList(), &dstLoc, &srcLoc, *rect); + { + RECT clipped = *rect; + if (ClipDirtyRect(clipped, dstFormat.desc)) + CopyDirtyRect(copyQueue->GetGfxList(), &dstLoc, &srcLoc, clipped); + } for (const RECT * rect = currentDirtyRects; rect < currentDirtyRects + nbDirtyRects; ++rect) CopyDirtyRect(copyQueue->GetGfxList(), &dstLoc, &srcLoc, *rect); diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index 78028379..50f9faa4 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -25,6 +25,7 @@ #include "CIndirectDeviceContext.h" #include "CInteropResourcePool.h" #include "CFrameBufferPool.h" +#include "CPostProcessor.h" #include #include @@ -47,6 +48,7 @@ private: CInteropResourcePool m_resPool; CFrameBufferPool m_fbPool; + CPostProcessor m_postProcessor; Wrappers::HandleT m_thread[2]; Wrappers::Event m_terminateEvent; diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index abc81a08..1f18b16f 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -36,6 +36,11 @@ + + + + + @@ -55,6 +60,11 @@ + + + + + @@ -179,7 +189,7 @@ $(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) - %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib + %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib SHA1 @@ -194,7 +204,7 @@ $(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) - %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib + %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib SHA1 @@ -209,7 +219,7 @@ $(SolutionDir)LGCommon;$(SolutionDir)..\repos\LGMP\lgmp\include;$(SolutionDir)..\vendor;$(SolutionDir)..\common\include;%(AdditionalIncludeDirectories) - %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib + %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib SHA1 @@ -224,7 +234,7 @@ $(SolutionDir)LGCommon;$(SolutionDir)..\repos\LGMP\lgmp\include;$(SolutionDir)..\vendor;$(SolutionDir)..\common\include;%(AdditionalIncludeDirectories) - %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib + %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib SHA1 diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index 1ec1c809..a8450d9c 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -17,6 +17,12 @@ {8E41214B-6785-4CFE-B992-037D68949A14} inf;inv;inx;mof;mc; + + {52F1286F-0C64-41B9-92A9-4453C80B1001} + + + {DB623F3B-5D6A-4F8C-8884-83588A1B58D2} + @@ -85,6 +91,21 @@ Header Files + + Header Files + + + Header Files\effect + + + Header Files\effect + + + Header Files\effect + + + Header Files\effect + @@ -139,6 +160,21 @@ Source Files + + Source Files + + + Source Files\effect + + + Source Files\effect + + + Source Files\effect + + + Source Files\effect + - \ No newline at end of file + diff --git a/idd/LGIdd/effect/CComputeEffect.cpp b/idd/LGIdd/effect/CComputeEffect.cpp new file mode 100644 index 00000000..305a0054 --- /dev/null +++ b/idd/LGIdd/effect/CComputeEffect.cpp @@ -0,0 +1,165 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include "CComputeEffect.h" + +#include "CDebug.h" + +#include +#include + +namespace PostProcessUtil +{ + bool CreateDefaultTexture(const ComPtr& device, + const D3D12_RESOURCE_DESC& desc, ComPtr& resource) + { + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + HRESULT hr = device->CreateCommittedResource( + &heapProps, + D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, + &desc, + D3D12_RESOURCE_STATE_COPY_SOURCE, + nullptr, + IID_PPV_ARGS(&resource)); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to create post-processing destination texture"); + return false; + } + + return true; + } +} + +bool CComputeEffect::InitCompute(const ComPtr& device, + const D3D12_DESCRIPTOR_RANGE * ranges, UINT rangeCount, + const D3D12_STATIC_SAMPLER_DESC * samplers, UINT samplerCount, + const char * shader) +{ + D3D12_ROOT_PARAMETER rootParam = {}; + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + rootParam.DescriptorTable.NumDescriptorRanges = rangeCount; + rootParam.DescriptorTable.pDescriptorRanges = ranges; + + D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc = {}; + rootSignatureDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1; + rootSignatureDesc.Desc_1_0.NumParameters = 1; + rootSignatureDesc.Desc_1_0.pParameters = &rootParam; + rootSignatureDesc.Desc_1_0.NumStaticSamplers = samplerCount; + rootSignatureDesc.Desc_1_0.pStaticSamplers = samplers; + rootSignatureDesc.Desc_1_0.Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE; + + ComPtr blob; + ComPtr error; + HRESULT hr = D3D12SerializeVersionedRootSignature( + &rootSignatureDesc, &blob, &error); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to serialize post-processing root signature"); + if (error) + DEBUG_ERROR("%s", (const char *)error->GetBufferPointer()); + return false; + } + + hr = device->CreateRootSignature( + 0, + blob->GetBufferPointer(), + blob->GetBufferSize(), + IID_PPV_ARGS(&m_rootSignature)); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to create post-processing root signature"); + return false; + } + + blob.Reset(); + error.Reset(); + hr = D3DCompile( + shader, + std::strlen(shader), + nullptr, + nullptr, + nullptr, + "main", + "cs_5_0", + 0, + 0, + &blob, + &error); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to compile post-processing shader"); + if (error) + DEBUG_ERROR("%s", (const char *)error->GetBufferPointer()); + return false; + } + + D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {}; + psoDesc.pRootSignature = m_rootSignature.Get(); + psoDesc.CS.pShaderBytecode = blob->GetBufferPointer(); + psoDesc.CS.BytecodeLength = blob->GetBufferSize(); + + hr = device->CreateComputePipelineState(&psoDesc, IID_PPV_ARGS(&m_pso)); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to create post-processing PSO"); + return false; + } + + UINT descriptorCount = 0; + for (UINT i = 0; i < rangeCount; ++i) + descriptorCount += ranges[i].NumDescriptors; + + D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {}; + heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + heapDesc.NumDescriptors = descriptorCount; + heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + + hr = device->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&m_descHeap)); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to create post-processing descriptor heap"); + return false; + } + + return true; +} + +void CComputeEffect::Bind(const ComPtr& commandList) +{ + ID3D12DescriptorHeap * heaps[] = { m_descHeap.Get() }; + commandList->SetDescriptorHeaps(1, heaps); + commandList->SetPipelineState(m_pso.Get()); + commandList->SetComputeRootSignature(m_rootSignature.Get()); + commandList->SetComputeRootDescriptorTable( + 0, m_descHeap->GetGPUDescriptorHandleForHeapStart()); +} + +void CComputeEffect::TransitionDst( + const ComPtr& commandList, + D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) +{ + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = m_dst.Get(); + barrier.Transition.StateBefore = before; + barrier.Transition.StateAfter = after; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + commandList->ResourceBarrier(1, &barrier); +} diff --git a/idd/LGIdd/effect/CComputeEffect.h b/idd/LGIdd/effect/CComputeEffect.h new file mode 100644 index 00000000..ec72c828 --- /dev/null +++ b/idd/LGIdd/effect/CComputeEffect.h @@ -0,0 +1,52 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#pragma once + +#include "../CPostProcessor.h" + + +#define POST_PROCESS_THREADS_STR "8" + +namespace PostProcessUtil +{ + static constexpr unsigned Threads = 8; + + template + static constexpr T AlignTo(T value, T align) + { + return (value + (align - 1)) & ~(align - 1); + } + + bool CreateDefaultTexture(const ComPtr& device, + const D3D12_RESOURCE_DESC& desc, ComPtr& resource); +} + +class CComputeEffect : public CPostProcessEffect +{ +protected: + ComPtr m_rootSignature; + ComPtr m_pso; + ComPtr m_descHeap; + ComPtr m_dst; + unsigned m_threadsX = 0; + unsigned m_threadsY = 0; + + bool InitCompute(const ComPtr& device, + const D3D12_DESCRIPTOR_RANGE * ranges, UINT rangeCount, + const D3D12_STATIC_SAMPLER_DESC * samplers, UINT samplerCount, + const char * shader); + + void Bind(const ComPtr& commandList); + + void TransitionDst(const ComPtr& commandList, + D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after); +}; diff --git a/idd/LGIdd/effect/CDownsampleEffect.cpp b/idd/LGIdd/effect/CDownsampleEffect.cpp new file mode 100644 index 00000000..1fbc83d2 --- /dev/null +++ b/idd/LGIdd/effect/CDownsampleEffect.cpp @@ -0,0 +1,269 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include "CDownsampleEffect.h" + +#include "CDebug.h" +#include "../CSettings.h" + +#include +#include +#include +#include +#include + +using namespace PostProcessUtil; + +bool CDownsampleEffect::ParseRules(const std::wstring& value) +{ + m_rules.clear(); + if (value.empty()) + return false; + + size_t pos = 0; + while (pos < value.size()) + { + size_t comma = value.find(L',', pos); + std::wstring token = value.substr(pos, + comma == std::wstring::npos ? std::wstring::npos : comma - pos); + + while (!token.empty() && std::iswspace(token.front())) + token.erase(token.begin()); + while (!token.empty() && std::iswspace(token.back())) + token.pop_back(); + + if (!token.empty()) + { + Rule rule = {}; + const wchar_t * start = token.c_str(); + if (*start == L'>') + { + rule.greater = true; + ++start; + } + + if (swscanf_s(start, L"%ux%u:%ux%u", + &rule.x, &rule.y, &rule.targetX, &rule.targetY) != 4) + { + DEBUG_ERROR("Unable to parse IDD downsample rule"); + m_rules.clear(); + return false; + } + + DEBUG_INFO("idd:downsample rule: %ux%u -> %ux%u%s", + rule.x, rule.y, rule.targetX, rule.targetY, + rule.greater ? " (greater-than)" : ""); + m_rules.push_back(rule); + } + + if (comma == std::wstring::npos) + break; + pos = comma + 1; + } + + return !m_rules.empty(); +} + +const CDownsampleEffect::Rule * CDownsampleEffect::MatchRule( + unsigned width, unsigned height) const +{ + const Rule * match = nullptr; + for (const auto& rule : m_rules) + if (( rule.greater && (width > rule.x || height > rule.y)) || + (!rule.greater && (width == rule.x && height == rule.y))) + match = &rule; + + return match; +} + +bool CDownsampleEffect::Init(const ComPtr& device) +{ + if (!ParseRules(g_settings.ReadStringValue(L"Downsample"))) + return false; + + D3D12_STATIC_SAMPLER_DESC sampler = {}; + sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; + sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; + sampler.MaxLOD = D3D12_FLOAT32_MAX; + sampler.ShaderRegister = 0; + sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + D3D12_DESCRIPTOR_RANGE ranges[3] = {}; + ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + ranges[0].NumDescriptors = 1; + ranges[0].BaseShaderRegister = 0; + ranges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + ranges[1].NumDescriptors = 1; + ranges[1].BaseShaderRegister = 0; + ranges[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + ranges[2].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + ranges[2].NumDescriptors = 1; + ranges[2].BaseShaderRegister = 0; + ranges[2].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + + const char * shader = + "cbuffer Constants : register(b0)\n" + "{\n" + " float Width;\n" + " float Height;\n" + "};\n" + "Texture2D src : register(t0);\n" + "RWTexture2D dst : register(u0);\n" + "SamplerState ss : register(s0);\n" + "[numthreads(" POST_PROCESS_THREADS_STR ", " POST_PROCESS_THREADS_STR ", 1)]\n" + "void main(uint3 dt : SV_DispatchThreadID)\n" + "{\n" + " dst[dt.xy] = src.SampleLevel(ss,\n" + " float2(\n" + " (float(dt.x) + 0.5f) / Width,\n" + " (float(dt.y) + 0.5f) / Height),\n" + " 0);\n" + "}\n"; + + if (!InitCompute(device, ranges, ARRAYSIZE(ranges), &sampler, 1, shader)) + return false; + + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + + D3D12_RESOURCE_DESC desc = {}; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Width = AlignTo(sizeof(m_consts), + (size_t)D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc.Count = 1; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + + HRESULT hr = device->CreateCommittedResource(&heapProps, + D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, IID_PPV_ARGS(&m_constBuffer)); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to create Downsample constant buffer"); + return false; + } + + return true; +} + +PostProcessStatus CDownsampleEffect::SetFormat( + const ComPtr& device, + const D12FrameFormat& src, D12FrameFormat& dst) +{ + const Rule * rule = MatchRule((unsigned)src.desc.Width, src.desc.Height); + if (!rule || + (rule->targetX == src.desc.Width && rule->targetY == src.desc.Height)) + return PostProcessStatus::BYPASS_EFFECT; + + D3D12_RESOURCE_DESC desc = src.desc; + desc.Width = rule->targetX; + desc.Height = rule->targetY; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + if (!CreateDefaultTexture(device, desc, m_dst)) + return PostProcessStatus::FAILED; + + m_consts.width = (float)rule->targetX; + m_consts.height = (float)rule->targetY; + + void * data = nullptr; + D3D12_RANGE readRange = { 0, 0 }; + HRESULT hr = m_constBuffer->Map(0, &readRange, &data); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to map Downsample constant buffer"); + return PostProcessStatus::FAILED; + } + std::memcpy(data, &m_consts, sizeof(m_consts)); + m_constBuffer->Unmap(0, nullptr); + + m_threadsX = ((unsigned)desc.Width + (Threads - 1)) / Threads; + m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads; + m_format = src.desc.Format; + m_scaleX = (double)desc.Width / src.desc.Width; + m_scaleY = (double)desc.Height / src.desc.Height; + m_width = (unsigned)desc.Width; + m_height = desc.Height; + + dst.desc = desc; + dst.width = (unsigned)desc.Width; + dst.height = desc.Height; + return PostProcessStatus::SUCCESS; +} + +void CDownsampleEffect::AdjustDamage(RECT dirtyRects[], unsigned * nbDirtyRects) +{ + for (RECT * rect = dirtyRects; rect < dirtyRects + *nbDirtyRects; ++rect) + { + unsigned width = (unsigned)std::ceil((double)(rect->right - rect->left) * m_scaleX); + unsigned height = (unsigned)std::ceil((double)(rect->bottom - rect->top ) * m_scaleY); + rect->left = (LONG)max(0.0, std::floor((double)rect->left * m_scaleX)); + rect->right = (LONG)min((double)m_width , (double)rect->left + width); + rect->top = (LONG)max(0.0, std::floor((double)rect->top * m_scaleY)); + rect->bottom = (LONG)min((double)m_height, (double)rect->top + height); + + if (rect->left > 0 ) rect->left -= 1; + if (rect->top > 0 ) rect->top -= 1; + if (rect->right < (LONG)m_width ) rect->right += 1; + if (rect->bottom < (LONG)m_height ) rect->bottom += 1; + } +} + +ComPtr CDownsampleEffect::Run( + const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) +{ + UNREFERENCED_PARAMETER(dirtyRects); + UNREFERENCED_PARAMETER(nbDirtyRects); + + TransitionDst(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + D3D12_CPU_DESCRIPTOR_HANDLE handle = + m_descHeap->GetCPUDescriptorHandleForHeapStart(); + const UINT inc = device->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {}; + cbvDesc.BufferLocation = m_constBuffer->GetGPUVirtualAddress(); + cbvDesc.SizeInBytes = (UINT)AlignTo(sizeof(m_consts), + (size_t)D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + device->CreateConstantBufferView(&cbvDesc, handle); + handle.ptr += inc; + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Format = m_format; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.Texture2D.MipLevels = 1; + device->CreateShaderResourceView(src.Get(), &srvDesc, handle); + handle.ptr += inc; + + D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.Format = m_format; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + 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); + return m_dst; +} diff --git a/idd/LGIdd/effect/CDownsampleEffect.h b/idd/LGIdd/effect/CDownsampleEffect.h new file mode 100644 index 00000000..71eaff14 --- /dev/null +++ b/idd/LGIdd/effect/CDownsampleEffect.h @@ -0,0 +1,62 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#pragma once + +#include "CComputeEffect.h" + +#include +#include + +class CDownsampleEffect : public CComputeEffect +{ +private: + struct Rule + { + bool greater = false; + unsigned x = 0; + unsigned y = 0; + unsigned targetX = 0; + unsigned targetY = 0; + }; + + struct Consts + { + float width; + float height; + } m_consts = {}; + + std::vector m_rules; + ComPtr m_constBuffer; + DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN; + double m_scaleX = 1.0; + double m_scaleY = 1.0; + unsigned m_width = 0; + unsigned m_height = 0; + + bool ParseRules(const std::wstring& value); + const Rule * MatchRule(unsigned width, unsigned height) const; + +public: + const char * GetName() const override { return "Downsample"; } + + bool Init(const ComPtr& device); + + PostProcessStatus SetFormat(const ComPtr& device, + const D12FrameFormat& src, D12FrameFormat& dst) override; + + void AdjustDamage(RECT dirtyRects[], unsigned * nbDirtyRects) override; + + ComPtr Run(const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) override; +}; diff --git a/idd/LGIdd/effect/CHDR16to10Effect.cpp b/idd/LGIdd/effect/CHDR16to10Effect.cpp new file mode 100644 index 00000000..d8f8b348 --- /dev/null +++ b/idd/LGIdd/effect/CHDR16to10Effect.cpp @@ -0,0 +1,152 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include "CHDR16to10Effect.h" + +#include "CDebug.h" + +#include + +using namespace PostProcessUtil; + +bool CHDR16to10Effect::Init(const ComPtr& device) +{ + D3D12_DESCRIPTOR_RANGE ranges[3] = {}; + ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + ranges[0].NumDescriptors = 1; + ranges[0].BaseShaderRegister = 0; + ranges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + ranges[1].NumDescriptors = 1; + ranges[1].BaseShaderRegister = 0; + ranges[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + ranges[2].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + ranges[2].NumDescriptors = 1; + ranges[2].BaseShaderRegister = 0; + ranges[2].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + + const char * shader = + "cbuffer Constants : register(b0)\n" + "{\n" + " float SDRWhiteLevel;\n" + "};\n" + "Texture2D src : register(t0);\n" + "RWTexture2D dst : register(u0);\n" + "[numthreads(" POST_PROCESS_THREADS_STR ", " POST_PROCESS_THREADS_STR ", 1)]\n" + "void main(uint3 dt : SV_DispatchThreadID)\n" + "{\n" + " dst[dt.xy] = float4(src[dt.xy].rgb * SDRWhiteLevel, src[dt.xy].a);\n" + "}\n"; + + if (!InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader)) + return false; + + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + + D3D12_RESOURCE_DESC desc = {}; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Width = AlignTo(sizeof(m_consts), + (size_t)D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc.Count = 1; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + + HRESULT hr = device->CreateCommittedResource(&heapProps, + D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, IID_PPV_ARGS(&m_constBuffer)); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to create HDR16to10 constant buffer"); + return false; + } + + void * data = nullptr; + D3D12_RANGE readRange = { 0, 0 }; + hr = m_constBuffer->Map(0, &readRange, &data); + if (FAILED(hr)) + return false; + std::memcpy(data, &m_consts, sizeof(m_consts)); + m_constBuffer->Unmap(0, nullptr); + + return true; +} + +PostProcessStatus CHDR16to10Effect::SetFormat( + const ComPtr& device, + const D12FrameFormat& src, D12FrameFormat& dst) +{ + if (src.desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT || !src.hdr) + return PostProcessStatus::BYPASS_EFFECT; + + D3D12_RESOURCE_DESC desc = src.desc; + desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + if (!CreateDefaultTexture(device, desc, m_dst)) + return PostProcessStatus::FAILED; + + m_threadsX = ((unsigned)desc.Width + (Threads - 1)) / Threads; + m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads; + + dst.desc = desc; + dst.format = FRAME_TYPE_RGBA10; + dst.hdr = true; + dst.hdrPQ = false; + return PostProcessStatus::SUCCESS; +} + +ComPtr CHDR16to10Effect::Run( + const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) +{ + UNREFERENCED_PARAMETER(dirtyRects); + UNREFERENCED_PARAMETER(nbDirtyRects); + + TransitionDst(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + D3D12_CPU_DESCRIPTOR_HANDLE handle = + m_descHeap->GetCPUDescriptorHandleForHeapStart(); + const UINT inc = device->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {}; + cbvDesc.BufferLocation = m_constBuffer->GetGPUVirtualAddress(); + cbvDesc.SizeInBytes = (UINT)AlignTo(sizeof(m_consts), + (size_t)D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + device->CreateConstantBufferView(&cbvDesc, handle); + handle.ptr += inc; + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.Texture2D.MipLevels = 1; + device->CreateShaderResourceView(src.Get(), &srvDesc, handle); + handle.ptr += inc; + + D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + 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); + return m_dst; +} diff --git a/idd/LGIdd/effect/CHDR16to10Effect.h b/idd/LGIdd/effect/CHDR16to10Effect.h new file mode 100644 index 00000000..45001522 --- /dev/null +++ b/idd/LGIdd/effect/CHDR16to10Effect.h @@ -0,0 +1,37 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#pragma once + +#include "CComputeEffect.h" + +class CHDR16to10Effect : public CComputeEffect +{ +private: + struct Consts + { + float SDRWhiteLevel; + } m_consts = { 1.0f }; + ComPtr m_constBuffer; + +public: + const char * GetName() const override { return "HDR16to10"; } + + bool Init(const ComPtr& device); + + PostProcessStatus SetFormat(const ComPtr& device, + const D12FrameFormat& src, D12FrameFormat& dst) override; + + ComPtr Run(const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) override; +}; diff --git a/idd/LGIdd/effect/CRGB24Effect.cpp b/idd/LGIdd/effect/CRGB24Effect.cpp new file mode 100644 index 00000000..3999823b --- /dev/null +++ b/idd/LGIdd/effect/CRGB24Effect.cpp @@ -0,0 +1,123 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include "CRGB24Effect.h" + +#include "../CSettings.h" + +using namespace PostProcessUtil; + +bool CRGB24Effect::Init(const ComPtr& device) +{ + if (!g_settings.ReadBoolValue(L"AllowRGB24", false)) + 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].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + 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" + "[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 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" + "}\n"; + + return InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader); +} + +PostProcessStatus CRGB24Effect::SetFormat(const ComPtr& device, + const D12FrameFormat& src, D12FrameFormat& dst) +{ + if (src.desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) + 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 (!CreateDefaultTexture(device, desc, m_dst)) + return PostProcessStatus::FAILED; + + m_threadsX = ((unsigned)desc.Width + (Threads - 1)) / Threads; + m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads; + + dst.desc = desc; + dst.format = FRAME_TYPE_BGR_32; + return PostProcessStatus::SUCCESS; +} + +ComPtr CRGB24Effect::Run(const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) +{ + UNREFERENCED_PARAMETER(dirtyRects); + UNREFERENCED_PARAMETER(nbDirtyRects); + + TransitionDst(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + D3D12_CPU_DESCRIPTOR_HANDLE handle = + m_descHeap->GetCPUDescriptorHandleForHeapStart(); + const UINT inc = device->GetDescriptorHandleIncrementSize( + 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.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + 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; + 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) + { + unsigned width = rect->right - rect->left; + rect->left = (rect->left * 3) / 4; + rect->right = rect->left + (width * 3 + 3) / 4; + } + + return m_dst; +} diff --git a/idd/LGIdd/effect/CRGB24Effect.h b/idd/LGIdd/effect/CRGB24Effect.h new file mode 100644 index 00000000..51ca22bc --- /dev/null +++ b/idd/LGIdd/effect/CRGB24Effect.h @@ -0,0 +1,30 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#pragma once + +#include "CComputeEffect.h" + +class CRGB24Effect : public CComputeEffect +{ +public: + const char * GetName() const override { return "RGB24"; } + + bool Init(const ComPtr& device); + + PostProcessStatus SetFormat(const ComPtr& device, + const D12FrameFormat& src, D12FrameFormat& dst) override; + + ComPtr Run(const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) override; +};