[idd] postprocess: separate calibrated frame branches

This commit is contained in:
Geoffrey McRae
2026-08-13 14:20:01 +10:00
parent 43d918b5f3
commit a8eb49fb17
7 changed files with 177 additions and 68 deletions

View File

@@ -28,6 +28,7 @@ bool Frame::Same(const GraphCfg& left, const GraphCfg& right)
left.srcWidth == right.srcWidth &&
left.srcHeight == right.srcHeight &&
Frame::Same(left.src, right.src) &&
left.transform == right.transform &&
left.width == right.width &&
left.height == right.height &&
Frame::Same(left.checkpoint, right.checkpoint);
@@ -141,60 +142,99 @@ unsigned CFrameGraph::Checkpoint(const FrameProfile& requested)
{
const FrameProfile profile =
Frame::Store(requested, FrameStorage::D3D12_TEXTURE);
FrameProfile current = m_cfg.src;
unsigned parent = 0;
FrameOp op;
const D12ColorTransform * transform = m_cfg.transform.get();
if (transform && transform->matrixEnabled)
{
// The common matrix stage preserves the source signal. A transfer-domain
// LUT is applied after each branch has selected scRGB or HDR10.
if (current.signal == FrameSignal::SRGB &&
current.pixel == FramePixel::BGRA8)
current.pixel = FramePixel::RGBA8;
parent = AddNode(FrameOp::CAL, 0,
m_cfg.srcWidth, m_cfg.srcHeight, current);
if (parent == FRAME_GRAPH_ROOT)
return FRAME_GRAPH_ROOT;
}
// Without calibration the established path filters linear scRGB before
// HDR10 encoding. This scale node is also shared by scRGB and HDR10 leaves.
const bool earlyScale = !transform &&
(m_cfg.srcWidth != m_cfg.width || m_cfg.srcHeight != m_cfg.height);
if (earlyScale)
{
parent = AddNode(FrameOp::SCALE, parent,
m_cfg.width, m_cfg.height, current);
if (parent == FRAME_GRAPH_ROOT)
return FRAME_GRAPH_ROOT;
}
switch (profile.signal)
{
case FrameSignal::SRGB:
op = FrameOp::SDR;
parent = AddNode(FrameOp::SDR, 0, m_cfg.width, m_cfg.height,
m_cfg.checkpoint);
if (parent == FRAME_GRAPH_ROOT)
return FRAME_GRAPH_ROOT;
if (Frame::Same(profile, m_cfg.checkpoint))
return parent;
break;
case FrameSignal::SCRGB_LINEAR:
{
op = FrameOp::SCRGB;
FrameProfile scRGB;
scRGB.storage = FrameStorage::D3D12_TEXTURE;
scRGB.pixel = FramePixel::RGBA16F;
scRGB.signal = FrameSignal::SCRGB_LINEAR;
return AddNode(
op, 0, m_cfg.width, m_cfg.height, scRGB);
}
break;
case FrameSignal::PQ_BT2020:
op = FrameOp::HDR10;
if (m_cfg.src.signal == FrameSignal::SCRGB_LINEAR)
{
FrameProfile scRGB;
scRGB.storage = FrameStorage::D3D12_TEXTURE;
scRGB.pixel = FramePixel::RGBA16F;
scRGB.signal = FrameSignal::SCRGB_LINEAR;
parent = AddNode(
FrameOp::SCRGB, 0, m_cfg.width, m_cfg.height, scRGB);
const unsigned width = earlyScale ? m_cfg.width : m_cfg.srcWidth;
const unsigned height = earlyScale ? m_cfg.height : m_cfg.srcHeight;
parent = AddNode(FrameOp::HDR10, parent,
width, height, profile);
if (parent == FRAME_GRAPH_ROOT)
return FRAME_GRAPH_ROOT;
}
else
{
parent = AddNode(FrameOp::HDR10, 0,
m_cfg.width, m_cfg.height, m_cfg.checkpoint);
if (parent == FRAME_GRAPH_ROOT)
return FRAME_GRAPH_ROOT;
if (Frame::Same(profile, m_cfg.checkpoint))
return parent;
current = profile;
}
break;
default:
return FRAME_GRAPH_ROOT;
}
return AddNode(
op, parent, m_cfg.width, m_cfg.height, profile);
if (transform && transform->lutEnabled)
{
if (current.signal == FrameSignal::SRGB &&
current.pixel == FramePixel::BGRA8)
current.pixel = FramePixel::RGBA8;
parent = AddNode(FrameOp::LUT, parent,
m_cfg.srcWidth, m_cfg.srcHeight, current);
if (parent == FRAME_GRAPH_ROOT)
return FRAME_GRAPH_ROOT;
}
if (!earlyScale &&
(m_cfg.srcWidth != m_cfg.width || m_cfg.srcHeight != m_cfg.height))
{
parent = AddNode(FrameOp::SCALE, parent,
m_cfg.width, m_cfg.height, current);
if (parent == FRAME_GRAPH_ROOT)
return FRAME_GRAPH_ROOT;
}
if (parent != 0 && Frame::Same(current, profile))
return parent;
FrameOp op;
switch (profile.signal)
{
case FrameSignal::SRGB:
op = FrameOp::SDR;
break;
case FrameSignal::SCRGB_LINEAR:
op = FrameOp::SCRGB;
break;
case FrameSignal::PQ_BT2020:
op = FrameOp::HDR10;
break;
default:
return FRAME_GRAPH_ROOT;
}
return AddNode(op, parent, m_cfg.width, m_cfg.height, profile);
}
bool CFrameGraph::Add(BackendId id, uint32_t epoch, bool required,
@@ -251,6 +291,26 @@ bool CFrameGraph::Same(const GraphCfg& cfg) const
return m_sealed && Frame::Same(m_cfg, cfg);
}
bool CFrameGraph::Need(FrameOp op) const
{
if (!m_sealed || op == FrameOp::SRC)
return false;
for (unsigned i = 1; i < m_nodeCount; ++i)
if (m_nodes[i].op == op && m_nodes[i].refs)
return true;
return false;
}
bool CFrameGraph::Want(FrameSignal signal) const
{
if (!m_sealed)
return false;
for (unsigned i = 0; i < m_leafCount; ++i)
if (m_leaves[i].cfg.profile.signal == signal)
return true;
return false;
}
bool CFrameGraph::Desc(unsigned leaf, const FrameDesc& frame,
LeafDesc& desc) const
{

View File

@@ -31,11 +31,14 @@
static const unsigned FRAME_DAMAGE_MAX = 256;
static const unsigned FRAME_GRAPH_ROOT = UINT_MAX;
static const unsigned FRAME_GRAPH_MAX_NODES =
1 + 2 * TRANSPORT_MAX_INSTANCES;
1 + 5 * TRANSPORT_MAX_INSTANCES;
enum class FrameOp : uint8_t
{
SRC,
CAL,
LUT,
SCALE,
SDR,
SCRGB,
HDR10,
@@ -55,6 +58,7 @@ struct GraphCfg
unsigned srcWidth = 0;
unsigned srcHeight = 0;
FrameProfile src;
std::shared_ptr<const D12ColorTransform> transform;
unsigned width = 0;
unsigned height = 0;
FrameProfile checkpoint;
@@ -134,6 +138,8 @@ public:
const FrameCfg& cfg);
bool Seal();
bool Same(const GraphCfg& cfg) const;
bool Want(FrameSignal signal) const;
bool Need(FrameOp op) const;
bool Desc(unsigned leaf, const FrameDesc& frame, LeafDesc& desc) const;
const GraphCfg& Cfg() const { return m_cfg; }

View File

@@ -63,6 +63,7 @@ static bool MakeGraphCfg(const D12FrameFormat& source,
cfg.srcWidth = source.width;
cfg.srcHeight = source.height;
cfg.src = src;
cfg.transform = D12::Transform(source.colorTransform);
cfg.width = checkpoint.width;
cfg.height = checkpoint.height;
cfg.checkpoint = output;

View File

@@ -170,6 +170,13 @@ PostProcessStatus CColorTransformEffect::SetFormat(
if (!transform)
return PostProcessStatus::BYPASS_EFFECT;
const bool matrixEnabled =
m_part != CalPart::LUT && transform->matrixEnabled;
const bool lutEnabled =
m_part != CalPart::MATRIX && transform->lutEnabled;
if (!matrixEnabled && !lutEnabled)
return PostProcessStatus::BYPASS_EFFECT;
DXGI_FORMAT dstFormat;
FrameType frameType;
switch (src.desc.Format)
@@ -184,10 +191,12 @@ PostProcessStatus CColorTransformEffect::SetFormat(
frameType = src.format;
break;
case DXGI_FORMAT_R16G16B16A16_FLOAT:
// The client wire format is HDR10. Perform the XYZ adjustment before
// the BT.2020 rotation, and its LUT after PQ encoding, in one pass.
dstFormat = DXGI_FORMAT_R10G10B10A2_UNORM;
frameType = FRAME_TYPE_RGBA10;
// The legacy chain targets HDR10 in this pass. A graph CAL node keeps
// the linear scRGB signal so its HDR10 child remains independently
// selectable by transport demand.
dstFormat = m_keepSignal ? DXGI_FORMAT_R16G16B16A16_FLOAT :
DXGI_FORMAT_R10G10B10A2_UNORM;
frameType = m_keepSignal ? FRAME_TYPE_RGBA16F : FRAME_TYPE_RGBA10;
break;
default:
DEBUG_ERROR("Unsupported color transform source format %u", src.desc.Format);
@@ -208,11 +217,13 @@ PostProcessStatus CColorTransformEffect::SetFormat(
std::memcpy(m_consts.matrix, transform->matrix,
sizeof(m_consts.matrix));
m_consts.scalar = transform->scalar;
m_consts.matrixEnabled = transform->matrixEnabled;
m_consts.lutEnabled = transform->lutEnabled;
m_consts.inputTransfer = src.hdrPQ ? TRANSFER_PQ :
m_consts.matrixEnabled = matrixEnabled;
m_consts.lutEnabled = lutEnabled;
const UINT inputTransfer = src.hdrPQ ? TRANSFER_PQ :
(src.hdr ? TRANSFER_LINEAR : TRANSFER_SRGB);
m_consts.outputTransfer = src.hdr ? TRANSFER_PQ : TRANSFER_SRGB;
m_consts.inputTransfer = inputTransfer;
m_consts.outputTransfer = m_keepSignal ? inputTransfer :
(src.hdr ? TRANSFER_PQ : TRANSFER_SRGB);
std::memcpy(m_lut, transform->lut, sizeof(m_lut));
m_uploadPending = true;
@@ -225,7 +236,7 @@ PostProcessStatus CColorTransformEffect::SetFormat(
dst.desc = desc;
dst.format = frameType;
if (src.hdr)
dst.hdrPQ = true;
dst.hdrPQ = m_keepSignal ? src.hdrPQ : true;
return PostProcessStatus::SUCCESS;
}

View File

@@ -22,6 +22,13 @@
#include "CComputeEffect.h"
enum class CalPart : uint8_t
{
ALL,
MATRIX,
LUT,
};
class CColorTransformEffect : public CComputeEffect
{
private:
@@ -41,8 +48,14 @@ private:
ComPtr<ID3D12Resource> m_lutBuffer;
DXGI_FORMAT m_srcFormat = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT m_dstFormat = DXGI_FORMAT_UNKNOWN;
CalPart m_part;
bool m_keepSignal;
public:
explicit CColorTransformEffect(CalPart part = CalPart::ALL,
bool keepSignal = false) :
m_part(part), m_keepSignal(keepSignal) {}
const char * GetName() const override { return "ColorTransform"; }
bool Init(const ComPtr<ID3D12Device3>& device);

View File

@@ -96,7 +96,10 @@ const CDownsampleEffect::Rule * CDownsampleEffect::MatchRule(
bool CDownsampleEffect::Init(const ComPtr<ID3D12Device3>& device, bool report)
{
if (!ParseRules(g_settings.ReadStringValue(L"Downsample"), report))
if ((!m_targetX && m_targetY) || (m_targetX && !m_targetY))
return false;
if (!m_targetX &&
!ParseRules(g_settings.ReadStringValue(L"Downsample"), report))
return false;
D3D12_STATIC_SAMPLER_DESC sampler = {};
@@ -154,21 +157,30 @@ PostProcessStatus CDownsampleEffect::SetFormat(
const ComPtr<ID3D12Device3>& 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))
unsigned targetX = m_targetX;
unsigned targetY = m_targetY;
if (!targetX || !targetY)
{
const Rule * rule =
MatchRule((unsigned)src.desc.Width, src.desc.Height);
if (!rule)
return PostProcessStatus::BYPASS_EFFECT;
targetX = rule->targetX;
targetY = rule->targetY;
}
if (targetX == src.desc.Width && targetY == src.desc.Height)
return PostProcessStatus::BYPASS_EFFECT;
D3D12_RESOURCE_DESC desc = src.desc;
desc.Width = rule->targetX;
desc.Height = rule->targetY;
desc.Width = targetX;
desc.Height = 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;
m_consts.width = (float)targetX;
m_consts.height = (float)targetY;
const HRESULT hr = Upload(m_constBuffer, &m_consts, sizeof(m_consts));
if (FAILED(hr))

View File

@@ -50,11 +50,17 @@ private:
double m_scaleY = 1.0;
unsigned m_width = 0;
unsigned m_height = 0;
unsigned m_targetX = 0;
unsigned m_targetY = 0;
bool ParseRules(const std::wstring& value, bool report);
const Rule * MatchRule(unsigned width, unsigned height) const;
public:
CDownsampleEffect() = default;
CDownsampleEffect(unsigned width, unsigned height) :
m_targetX(width), m_targetY(height) {}
const char * GetName() const override { return "Downsample"; }
bool Init(const ComPtr<ID3D12Device3>& device, bool report = true);