[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.srcWidth == right.srcWidth &&
left.srcHeight == right.srcHeight && left.srcHeight == right.srcHeight &&
Frame::Same(left.src, right.src) && Frame::Same(left.src, right.src) &&
left.transform == right.transform &&
left.width == right.width && left.width == right.width &&
left.height == right.height && left.height == right.height &&
Frame::Same(left.checkpoint, right.checkpoint); Frame::Same(left.checkpoint, right.checkpoint);
@@ -141,60 +142,99 @@ unsigned CFrameGraph::Checkpoint(const FrameProfile& requested)
{ {
const FrameProfile profile = const FrameProfile profile =
Frame::Store(requested, FrameStorage::D3D12_TEXTURE); Frame::Store(requested, FrameStorage::D3D12_TEXTURE);
FrameProfile current = m_cfg.src;
unsigned parent = 0; 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) switch (profile.signal)
{ {
case FrameSignal::SRGB: 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; break;
case FrameSignal::SCRGB_LINEAR: case FrameSignal::SCRGB_LINEAR:
{ break;
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);
}
case FrameSignal::PQ_BT2020: case FrameSignal::PQ_BT2020:
op = FrameOp::HDR10;
if (m_cfg.src.signal == FrameSignal::SCRGB_LINEAR) if (m_cfg.src.signal == FrameSignal::SCRGB_LINEAR)
{ {
FrameProfile scRGB; const unsigned width = earlyScale ? m_cfg.width : m_cfg.srcWidth;
scRGB.storage = FrameStorage::D3D12_TEXTURE; const unsigned height = earlyScale ? m_cfg.height : m_cfg.srcHeight;
scRGB.pixel = FramePixel::RGBA16F; parent = AddNode(FrameOp::HDR10, parent,
scRGB.signal = FrameSignal::SCRGB_LINEAR; width, height, profile);
parent = AddNode(
FrameOp::SCRGB, 0, m_cfg.width, m_cfg.height, scRGB);
if (parent == FRAME_GRAPH_ROOT) if (parent == FRAME_GRAPH_ROOT)
return FRAME_GRAPH_ROOT; return FRAME_GRAPH_ROOT;
} current = profile;
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;
} }
break; break;
default: default:
return FRAME_GRAPH_ROOT; 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, 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); 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, bool CFrameGraph::Desc(unsigned leaf, const FrameDesc& frame,
LeafDesc& desc) const LeafDesc& desc) const
{ {

View File

@@ -31,11 +31,14 @@
static const unsigned FRAME_DAMAGE_MAX = 256; static const unsigned FRAME_DAMAGE_MAX = 256;
static const unsigned FRAME_GRAPH_ROOT = UINT_MAX; static const unsigned FRAME_GRAPH_ROOT = UINT_MAX;
static const unsigned FRAME_GRAPH_MAX_NODES = static const unsigned FRAME_GRAPH_MAX_NODES =
1 + 2 * TRANSPORT_MAX_INSTANCES; 1 + 5 * TRANSPORT_MAX_INSTANCES;
enum class FrameOp : uint8_t enum class FrameOp : uint8_t
{ {
SRC, SRC,
CAL,
LUT,
SCALE,
SDR, SDR,
SCRGB, SCRGB,
HDR10, HDR10,
@@ -50,14 +53,15 @@ enum class FrameDamage : uint8_t
struct GraphCfg struct GraphCfg
{ {
GpuMode mode = GpuMode::HARDWARE; GpuMode mode = GpuMode::HARDWARE;
LUID adapter = {}; LUID adapter = {};
unsigned srcWidth = 0; unsigned srcWidth = 0;
unsigned srcHeight = 0; unsigned srcHeight = 0;
FrameProfile src; FrameProfile src;
unsigned width = 0; std::shared_ptr<const D12ColorTransform> transform;
unsigned height = 0; unsigned width = 0;
FrameProfile checkpoint; unsigned height = 0;
FrameProfile checkpoint;
}; };
namespace Frame namespace Frame
@@ -134,6 +138,8 @@ public:
const FrameCfg& cfg); const FrameCfg& cfg);
bool Seal(); bool Seal();
bool Same(const GraphCfg& cfg) const; 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; bool Desc(unsigned leaf, const FrameDesc& frame, LeafDesc& desc) const;
const GraphCfg& Cfg() const { return m_cfg; } const GraphCfg& Cfg() const { return m_cfg; }

View File

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

View File

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

View File

@@ -22,6 +22,13 @@
#include "CComputeEffect.h" #include "CComputeEffect.h"
enum class CalPart : uint8_t
{
ALL,
MATRIX,
LUT,
};
class CColorTransformEffect : public CComputeEffect class CColorTransformEffect : public CComputeEffect
{ {
private: private:
@@ -41,8 +48,14 @@ private:
ComPtr<ID3D12Resource> m_lutBuffer; ComPtr<ID3D12Resource> m_lutBuffer;
DXGI_FORMAT m_srcFormat = DXGI_FORMAT_UNKNOWN; DXGI_FORMAT m_srcFormat = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT m_dstFormat = DXGI_FORMAT_UNKNOWN; DXGI_FORMAT m_dstFormat = DXGI_FORMAT_UNKNOWN;
CalPart m_part;
bool m_keepSignal;
public: public:
explicit CColorTransformEffect(CalPart part = CalPart::ALL,
bool keepSignal = false) :
m_part(part), m_keepSignal(keepSignal) {}
const char * GetName() const override { return "ColorTransform"; } const char * GetName() const override { return "ColorTransform"; }
bool Init(const ComPtr<ID3D12Device3>& device); 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) 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; return false;
D3D12_STATIC_SAMPLER_DESC sampler = {}; D3D12_STATIC_SAMPLER_DESC sampler = {};
@@ -154,21 +157,30 @@ PostProcessStatus CDownsampleEffect::SetFormat(
const ComPtr<ID3D12Device3>& device, const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) const D12FrameFormat& src, D12FrameFormat& dst)
{ {
const Rule * rule = MatchRule((unsigned)src.desc.Width, src.desc.Height); unsigned targetX = m_targetX;
if (!rule || unsigned targetY = m_targetY;
(rule->targetX == src.desc.Width && rule->targetY == src.desc.Height)) 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; return PostProcessStatus::BYPASS_EFFECT;
D3D12_RESOURCE_DESC desc = src.desc; D3D12_RESOURCE_DESC desc = src.desc;
desc.Width = rule->targetX; desc.Width = targetX;
desc.Height = rule->targetY; desc.Height = targetY;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
if (!CreateDefaultTexture(device, desc, m_dst)) if (!CreateDefaultTexture(device, desc, m_dst))
return PostProcessStatus::FAILED; return PostProcessStatus::FAILED;
m_consts.width = (float)rule->targetX; m_consts.width = (float)targetX;
m_consts.height = (float)rule->targetY; m_consts.height = (float)targetY;
const HRESULT hr = Upload(m_constBuffer, &m_consts, sizeof(m_consts)); const HRESULT hr = Upload(m_constBuffer, &m_consts, sizeof(m_consts));
if (FAILED(hr)) if (FAILED(hr))

View File

@@ -43,18 +43,24 @@ private:
float height; float height;
} m_consts = {}; } m_consts = {};
std::vector<Rule> m_rules; std::vector<Rule> m_rules;
ComPtr<ID3D12Resource> m_constBuffer; ComPtr<ID3D12Resource> m_constBuffer;
DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN; DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN;
double m_scaleX = 1.0; double m_scaleX = 1.0;
double m_scaleY = 1.0; double m_scaleY = 1.0;
unsigned m_width = 0; unsigned m_width = 0;
unsigned m_height = 0; unsigned m_height = 0;
unsigned m_targetX = 0;
unsigned m_targetY = 0;
bool ParseRules(const std::wstring& value, bool report); bool ParseRules(const std::wstring& value, bool report);
const Rule * MatchRule(unsigned width, unsigned height) const; const Rule * MatchRule(unsigned width, unsigned height) const;
public: public:
CDownsampleEffect() = default;
CDownsampleEffect(unsigned width, unsigned height) :
m_targetX(width), m_targetY(height) {}
const char * GetName() const override { return "Downsample"; } const char * GetName() const override { return "Downsample"; }
bool Init(const ComPtr<ID3D12Device3>& device, bool report = true); bool Init(const ComPtr<ID3D12Device3>& device, bool report = true);