mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 06:12:04 +00:00
Replace the texture-shaped RGB24 output with a linear raw buffer and keep its packing, damage translation, and buffer copies inside the RGB24 effect. Benchmark full-frame native and packed processing and retain the faster path for each source format. Preserve logical damage rectangles for client updates and alternating framebuffer repair. Return compute outputs to COMMON for COPY queue handoff and refresh cached framebuffer sizes when switching packed and native layouts.
424 lines
12 KiB
C++
424 lines
12 KiB
C++
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "CPostProcessor.h"
|
|
|
|
#include "CD3D12Device.h"
|
|
#include "CDebug.h"
|
|
#include "effect/CColorTransformEffect.h"
|
|
#include "effect/CDownsampleEffect.h"
|
|
#include "effect/CHDR16to10Effect.h"
|
|
#include "effect/CRGB24Effect.h"
|
|
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
namespace
|
|
{
|
|
bool NearlyEqual(float a, float b, float tolerance)
|
|
{
|
|
const float delta = a - b;
|
|
return delta >= -tolerance && delta <= tolerance;
|
|
}
|
|
}
|
|
|
|
bool IsIdentityColorTransform(const D12ColorTransform& transform)
|
|
{
|
|
static const float matrixTolerance = 1.0f / 1048576.0f;
|
|
static const float lutTolerance = 1.0f / 65535.0f;
|
|
|
|
if (transform.matrixEnabled)
|
|
{
|
|
for (unsigned row = 0; row < 3; ++row)
|
|
for (unsigned column = 0; column < 4; ++column)
|
|
{
|
|
const float expected = row == column ? 1.0f : 0.0f;
|
|
const float effective =
|
|
transform.matrix[row][column] * transform.scalar;
|
|
if (!NearlyEqual(effective, expected, matrixTolerance))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (transform.lutEnabled)
|
|
for (unsigned i = 0; i < 4096; ++i)
|
|
{
|
|
const float expected = (float)i / 4095.0f;
|
|
if (!NearlyEqual(transform.lut[i][0], expected, lutTolerance) ||
|
|
!NearlyEqual(transform.lut[i][1], expected, lutTolerance) ||
|
|
!NearlyEqual(transform.lut[i][2], expected, lutTolerance))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void CopyHDRMetadata(D12FrameFormat& dst, const D12FrameFormat& src)
|
|
{
|
|
dst.hdrMetadata = src.hdrMetadata;
|
|
dst.sdrWhiteLevel = src.sdrWhiteLevel;
|
|
std::memcpy(dst.displayPrimary, src.displayPrimary, sizeof(dst.displayPrimary));
|
|
std::memcpy(dst.whitePoint, src.whitePoint, sizeof(dst.whitePoint));
|
|
dst.maxDisplayLuminance = src.maxDisplayLuminance;
|
|
dst.minDisplayLuminance = src.minDisplayLuminance;
|
|
dst.maxContentLightLevel = src.maxContentLightLevel;
|
|
dst.maxFrameAverageLightLevel = src.maxFrameAverageLightLevel;
|
|
}
|
|
|
|
bool CPostProcessor::Init(std::shared_ptr<CD3D12Device> dx12Device,
|
|
bool enableEffects)
|
|
{
|
|
m_dx12Device = dx12Device;
|
|
m_device = dx12Device->GetDevice();
|
|
m_effects.clear();
|
|
|
|
if (!enableEffects)
|
|
return true;
|
|
|
|
std::unique_ptr<CColorTransformEffect> colorTransform(new CColorTransformEffect());
|
|
if (colorTransform->Init(m_device))
|
|
{
|
|
DEBUG_INFO("Created post-processing effect: %s", colorTransform->GetName());
|
|
m_effects.push_back(std::move(colorTransform));
|
|
}
|
|
else
|
|
return false;
|
|
|
|
std::unique_ptr<CDownsampleEffect> 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<CHDR16to10Effect> 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<CRGB24Effect> 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_copyLayout = {};
|
|
m_copyEffect = nullptr;
|
|
m_frameSize = 0;
|
|
m_pitch = 0;
|
|
m_effectsActive = false;
|
|
m_configured = false;
|
|
}
|
|
|
|
bool CPostProcessor::HasSameEffectChain(const CPostProcessor& other) const
|
|
{
|
|
if (m_effects.size() != other.m_effects.size())
|
|
return false;
|
|
|
|
for (size_t i = 0; i < m_effects.size(); ++i)
|
|
if (std::strcmp(m_effects[i]->GetName(),
|
|
other.m_effects[i]->GetName()) != 0)
|
|
return false;
|
|
|
|
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
|
|
{
|
|
if (!m_configured ||
|
|
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 ||
|
|
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,
|
|
bool * formatChanged)
|
|
{
|
|
if (formatChanged)
|
|
*formatChanged = false;
|
|
|
|
if (!NeedsReconfigure(srcFormat))
|
|
{
|
|
// Static HDR metadata may change independently of the resource format.
|
|
// 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;
|
|
CPostProcessEffect * outputEffect = nullptr;
|
|
bool effectsActive = false;
|
|
|
|
for (const auto& effect : m_effects)
|
|
{
|
|
D12FrameFormat dst = cur;
|
|
switch (effect->SetFormat(m_device, cur, dst))
|
|
{
|
|
case PostProcessStatus::SUCCESS:
|
|
effect->Enabled = true;
|
|
effectsActive = true;
|
|
cur = dst;
|
|
outputEffect = effect.get();
|
|
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;
|
|
}
|
|
}
|
|
|
|
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<size_t>::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.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<ID3D12GraphicsCommandList>& 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)
|
|
if (effect->Enabled)
|
|
effect->AdjustDamage(dirtyRects, nbDirtyRects);
|
|
}
|
|
|
|
ComPtr<ID3D12Resource> CPostProcessor::Run(
|
|
const ComPtr<ID3D12GraphicsCommandList>& commandList,
|
|
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
|
|
unsigned * nbDirtyRects)
|
|
{
|
|
ComPtr<ID3D12Resource> 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;
|
|
}
|