mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
build / client-tests (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client-tests (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client-tests (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client-tests (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client-tests (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client-tests (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client-tests (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client-tests (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
Bypass cadence retention when the software render adapter is active. Publish each available source frame immediately with one damage-aware D3D copy into its final IVSHMEM resource. Keep only one software copy in flight so newer frames are dropped instead of queued behind stale work. Retain accumulated damage for the next frame and skip static re-encodes when no image update is pending. Use a row-major IVSHMEM texture when the shared heap supports it and fall back to a direct IVSHMEM buffer copy otherwise. In indirect mode, copy only damaged rows from readback memory into IVSHMEM.
485 lines
14 KiB
C++
485 lines
14 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::CopyToFrameBuffer(
|
|
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;
|
|
if (dst->GetDesc().Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D)
|
|
{
|
|
dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
|
dstLoc.SubresourceIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
|
dstLoc.PlacedFootprint = m_copyLayout;
|
|
}
|
|
|
|
if (fullCopy)
|
|
{
|
|
commandList->CopyTextureRegion(
|
|
&dstLoc, 0, 0, 0, &srcLoc, nullptr);
|
|
return;
|
|
}
|
|
|
|
for (const RECT * rect = dirtyRects;
|
|
rect < dirtyRects + nbDirtyRects; ++rect)
|
|
{
|
|
D3D12_BOX box = {};
|
|
box.left = rect->left;
|
|
box.top = rect->top;
|
|
box.front = 0;
|
|
box.right = rect->right;
|
|
box.bottom = rect->bottom;
|
|
box.back = 1;
|
|
|
|
commandList->CopyTextureRegion(
|
|
&dstLoc, box.left, box.top, 0, &srcLoc, &box);
|
|
}
|
|
}
|
|
|
|
void CPostProcessor::CopyToCandidate(
|
|
const ComPtr<ID3D12GraphicsCommandList>& commandList,
|
|
ID3D12Resource * dst, ID3D12Resource * src) const
|
|
{
|
|
CopyToFrameBuffer(
|
|
commandList, dst, src, nullptr, 0, true);
|
|
}
|
|
|
|
void CPostProcessor::CopyFromCandidate(
|
|
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_PLACED_FOOTPRINT;
|
|
srcLoc.PlacedFootprint = m_copyLayout;
|
|
|
|
D3D12_TEXTURE_COPY_LOCATION dstLoc = {};
|
|
dstLoc.pResource = dst;
|
|
dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
|
dstLoc.PlacedFootprint = m_copyLayout;
|
|
|
|
if (fullCopy)
|
|
{
|
|
commandList->CopyBufferRegion(
|
|
dst, 0, src, 0, m_frameSize);
|
|
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;
|
|
}
|