[idd/client] apply the Windows display color transform

Honor the IddCx 3x4 XYZ matrix and post-transfer LUT in the IDD
conversion pipeline. Carry transform changes to the client so EGL applies
the same calibration to hardware cursors, and invalidate the full frame
whenever calibration changes.
This commit is contained in:
Geoffrey McRae
2026-07-19 03:11:42 +10:00
parent ad5d7753e8
commit 26ca90893a
17 changed files with 766 additions and 48 deletions

View File

@@ -868,6 +868,20 @@ bool CIndirectDeviceContext::SetupLGMP(size_t alignSize)
memset(lgmpHostMemPtr(m_pointerShapeMemory[i]), 0, MAX_POINTER_SIZE);
}
for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
{
if ((status = lgmpHostMemAlloc(m_lgmp,
sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform),
&m_pointerTransformMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Transform): %s",
lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerTransformMemory[i]), 0,
sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform));
}
m_maxFrameSize = lgmpHostMemAvail(m_lgmp);
m_maxFrameSize = (m_maxFrameSize -(m_alignSize - 1)) & ~(m_alignSize - 1);
m_maxFrameSize /= LGMP_Q_FRAME_LEN;
@@ -951,6 +965,9 @@ void CIndirectDeviceContext::DeInitLGMP()
lgmpHostMemFree(&m_pointerMemory[i]);
for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
lgmpHostMemFree(&m_pointerShapeMemory[i]);
for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
lgmpHostMemFree(&m_pointerTransformMemory[i]);
lgmpHostFree(&m_lgmp);
}
@@ -1017,7 +1034,10 @@ void CIndirectDeviceContext::LGMPTimer()
}
if (lgmpHostQueueNewSubs(m_pointerQueue))
{
ResendCursor();
SendColorTransform();
}
}
bool CIndirectDeviceContext::FrameBufferAvailable() const
@@ -1272,6 +1292,24 @@ void CIndirectDeviceContext::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, co
}
}
void CIndirectDeviceContext::SetColorTransform(
std::shared_ptr<const D12ColorTransform> transform)
{
AcquireSRWLockExclusive(&m_colorTransformLock);
m_colorTransform = std::move(transform);
ReleaseSRWLockExclusive(&m_colorTransformLock);
SendColorTransform();
}
std::shared_ptr<const D12ColorTransform>
CIndirectDeviceContext::GetColorTransform() const
{
AcquireSRWLockShared(&m_colorTransformLock);
auto transform = m_colorTransform;
ReleaseSRWLockShared(&m_colorTransformLock);
return transform;
}
#ifdef HAS_IDDCX_110
void CIndirectDeviceContext::SetHDRActive(const struct IDDCX_HDR10_METADATA * hdrMeta)
{
@@ -1325,7 +1363,49 @@ bool CIndirectDeviceContext::GetHDRMetadata(D12FrameFormat & format) const
return true;
}
void CIndirectDeviceContext::ResendCursor() const
void CIndirectDeviceContext::SendColorTransform()
{
if (!m_pointerQueue || !m_pointerTransformMemory[0])
return;
PLGMPMemory mem = m_pointerTransformMemory[m_pointerTransformIndex];
if (++m_pointerTransformIndex == COLOR_TRANSFORM_BUFFERS)
m_pointerTransformIndex = 0;
KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
KVMFRColorTransform * output =
(KVMFRColorTransform *)(cursor + 1);
const auto transform = GetColorTransform();
output->flags = 0;
if (transform)
{
if (transform->matrixEnabled)
output->flags |= KVMFR_COLOR_TRANSFORM_MATRIX;
if (transform->lutEnabled)
output->flags |= KVMFR_COLOR_TRANSFORM_LUT;
memcpy(output->matrix, transform->matrix, sizeof(output->matrix));
output->scalar = transform->scalar;
memcpy(output->lut, transform->lut, sizeof(output->lut));
}
LGMP_STATUS status;
while ((status = lgmpHostQueuePost(m_pointerQueue,
CURSOR_FLAG_COLOR_TRANSFORM, mem)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_FULL)
{
Sleep(1);
continue;
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer Transform): %s",
lgmpStatusString(status));
break;
}
}
void CIndirectDeviceContext::ResendCursor()
{
PLGMPMemory mem = m_pointerShape;
if (!mem)

View File

@@ -45,6 +45,7 @@ extern "C" {
#endif
#define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (512 * 512 * 4))
#define COLOR_TRANSFORM_BUFFERS 3
#define POINTER_SHAPE_BUFFERS 3
//FIXME: this should not really be done here, this is a hack
@@ -90,9 +91,11 @@ private:
PLGMPHostQueue m_pointerQueue = nullptr;
PLGMPMemory m_pointerMemory [LGMP_Q_POINTER_LEN ] = {};
PLGMPMemory m_pointerShapeMemory[POINTER_SHAPE_BUFFERS] = {};
PLGMPMemory m_pointerTransformMemory[COLOR_TRANSFORM_BUFFERS] = {};
PLGMPMemory m_pointerShape = nullptr;
int m_pointerMemoryIndex = 0;
int m_pointerShapeIndex = 0;
int m_pointerTransformIndex = 0;
bool m_cursorVisible = false;
int m_cursorX = 0, m_cursorY = 0;
@@ -137,6 +140,12 @@ private:
bool m_lastHDRActive = false;
bool m_lastHDRMetadata = false;
// Windows display calibration transform. The callback publishes immutable
// snapshots so the swap-chain thread never observes a partially updated
// matrix or LUT.
mutable SRWLOCK m_colorTransformLock = SRWLOCK_INIT;
std::shared_ptr<const D12ColorTransform> m_colorTransform;
void QueryIddCxCapabilities();
bool CanUseIddCx110DDIs() const { return m_canProcessFP16; }
@@ -145,7 +154,8 @@ private:
void DeInitLGMP();
void LGMPTimer();
void ResendCursor() const;
void ResendCursor();
void SendColorTransform();
void InitializeEdid();
// Guards m_displayModes and m_edid. The mode list is rebuilt on the LGMP
@@ -220,6 +230,9 @@ public:
// Tracks HDR state from EvtIddCxMonitorSetDefaultHdrMetadata
void SetHDRActive(const struct IDDCX_HDR10_METADATA * hdrMeta);
void SetColorTransform(std::shared_ptr<const D12ColorTransform> transform);
std::shared_ptr<const D12ColorTransform> GetColorTransform() const;
// Returns true if the display is currently in HDR mode
bool IsHDRActive() const
{

View File

@@ -13,6 +13,7 @@
#include "CD3D12Device.h"
#include "CDebug.h"
#include "effect/CColorTransformEffect.h"
#include "effect/CDownsampleEffect.h"
#include "effect/CHDR16to10Effect.h"
#include "effect/CRGB24Effect.h"
@@ -38,6 +39,15 @@ bool CPostProcessor::Init(std::shared_ptr<CD3D12Device> dx12Device)
m_device = dx12Device->GetDevice();
m_effects.clear();
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))
{
@@ -86,7 +96,8 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, bool * formatCha
srcFormat.width == m_srcFormat.width &&
srcFormat.height == m_srcFormat.height &&
srcFormat.hdr == m_srcFormat.hdr &&
srcFormat.hdrPQ == m_srcFormat.hdrPQ)
srcFormat.hdrPQ == m_srcFormat.hdrPQ &&
srcFormat.colorTransform == m_srcFormat.colorTransform)
{
// Static HDR metadata may change independently of the resource format.
// Propagate it without recreating textures or post-processing state.
@@ -132,7 +143,8 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, bool * formatCha
oldDst.height != m_dstFormat.height ||
oldDst.hdr != m_dstFormat.hdr ||
oldDst.hdrPQ != m_dstFormat.hdrPQ ||
oldDst.sdrWhiteLevel != m_dstFormat.sdrWhiteLevel;
oldDst.sdrWhiteLevel != m_dstFormat.sdrWhiteLevel ||
oldDst.colorTransform != m_dstFormat.colorTransform;
return true;
}

View File

@@ -34,6 +34,15 @@ enum class PostProcessStatus
FAILED
};
struct D12ColorTransform
{
bool matrixEnabled = false;
float matrix[3][4] = {};
float scalar = 1.0f;
bool lutEnabled = false;
float lut[4096][4] = {};
};
struct D12FrameFormat
{
D3D12_RESOURCE_DESC desc = {};
@@ -44,6 +53,7 @@ struct D12FrameFormat
bool hdrPQ = false;
bool hdrMetadata = false;
uint32_t sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT;
std::shared_ptr<const D12ColorTransform> colorTransform;
// HDR static metadata (SMPTE ST 2086)
// Display color primaries in 0.00002 units (xy coordinates)

View File

@@ -538,6 +538,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
srcFormat.height = srcDesc.Height;
srcFormat.format = GetFrameType(srcDesc.Format);
srcFormat.sdrWhiteLevel = sdrWhiteLevel;
srcFormat.colorTransform = m_devContext->GetColorTransform();
switch (colorSpace)
{

View File

@@ -28,6 +28,8 @@
#include <IddCx.h>
#include <avrt.h>
#include <wrl.h>
#include <memory>
#include <utility>
#include "CDebug.h"
#include "CIndirectDeviceContext.h"
@@ -192,8 +194,51 @@ NTSTATUS LGIddMonitorSetDefaultHdrMetadata(IDDCX_MONITOR monitor,
NTSTATUS LGIddMonitorSetGammaRamp(IDDCX_MONITOR monitor, const IDARG_IN_SET_GAMMARAMP* inArgs)
{
UNREFERENCED_PARAMETER(monitor);
UNREFERENCED_PARAMETER(inArgs);
auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor);
auto* ctx = wrapper->context->GetDeviceContext();
if (inArgs->Type == IDDCX_GAMMARAMP_TYPE_DEFAULT)
{
ctx->SetColorTransform(nullptr);
DEBUG_INFO("Display color transform reset to default");
return STATUS_SUCCESS;
}
if (inArgs->Type != IDDCX_GAMMARAMP_TYPE_3x4_COLORSPACE_TRANSFORM)
{
DEBUG_WARN("Unsupported gamma ramp type %u", inArgs->Type);
return STATUS_NOT_SUPPORTED;
}
if (!inArgs->pGammaRampData ||
inArgs->GammaRampSizeInBytes <
sizeof(IDDCX_GAMMARAMP_3X4_COLORSPACE_TRANSFORM))
{
DEBUG_ERROR("Invalid 3x4 color transform buffer (%u bytes)",
inArgs->GammaRampSizeInBytes);
return STATUS_INVALID_PARAMETER;
}
const auto* input = static_cast<
const IDDCX_GAMMARAMP_3X4_COLORSPACE_TRANSFORM*>(
inArgs->pGammaRampData);
auto transform = std::make_shared<D12ColorTransform>();
transform->matrixEnabled = !!input->MatrixEnabled;
transform->scalar = input->ScalarMultiplier;
transform->lutEnabled = !!input->LutEnabled;
memcpy(transform->matrix, input->ColorMatrix3x4,
sizeof(transform->matrix));
for (unsigned i = 0; i < 4096; ++i)
{
transform->lut[i][0] = input->LookupTable1D[i].Red;
transform->lut[i][1] = input->LookupTable1D[i].Green;
transform->lut[i][2] = input->LookupTable1D[i].Blue;
transform->lut[i][3] = 1.0f;
}
ctx->SetColorTransform(std::move(transform));
DEBUG_INFO("Display color transform updated (matrix:%d lut:%d)",
input->MatrixEnabled, input->LutEnabled);
return STATUS_SUCCESS;
}

View File

@@ -37,6 +37,7 @@
<ClCompile Include="CPipeServer.cpp" />
<ClCompile Include="CPlatformInfo.cpp" />
<ClCompile Include="CPostProcessor.cpp" />
<ClCompile Include="effect\CColorTransformEffect.cpp" />
<ClCompile Include="effect\CComputeEffect.cpp" />
<ClCompile Include="effect\CDownsampleEffect.cpp" />
<ClCompile Include="effect\CHDR16to10Effect.cpp" />
@@ -61,6 +62,7 @@
<ClInclude Include="CPipeServer.h" />
<ClInclude Include="CPlatformInfo.h" />
<ClInclude Include="CPostProcessor.h" />
<ClInclude Include="effect\CColorTransformEffect.h" />
<ClInclude Include="effect\CComputeEffect.h" />
<ClInclude Include="effect\CDownsampleEffect.h" />
<ClInclude Include="effect\CHDR16to10Effect.h" />
@@ -274,4 +276,4 @@
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
</Project>
</Project>

View File

@@ -94,6 +94,9 @@
<ClInclude Include="CPostProcessor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="effect\CColorTransformEffect.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="effect\CComputeEffect.h">
<Filter>Header Files\effect</Filter>
</ClInclude>
@@ -163,6 +166,9 @@
<ClCompile Include="CPostProcessor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="effect\CColorTransformEffect.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="effect\CComputeEffect.cpp">
<Filter>Source Files\effect</Filter>
</ClCompile>

View File

@@ -0,0 +1,328 @@
/**
* 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 "CColorTransformEffect.h"
#include "CDebug.h"
#include <cstring>
using namespace PostProcessUtil;
namespace
{
enum TransferFunction : UINT
{
TRANSFER_LINEAR,
TRANSFER_SRGB,
TRANSFER_PQ,
};
bool CreateUploadBuffer(const ComPtr<ID3D12Device3>& device, size_t size,
ComPtr<ID3D12Resource>& resource)
{
D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = size;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
const HRESULT hr = device->CreateCommittedResource(&heapProps,
D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr, IID_PPV_ARGS(&resource));
return SUCCEEDED(hr);
}
bool Upload(const ComPtr<ID3D12Resource>& resource,
const void * data, size_t size)
{
void * dst = nullptr;
const D3D12_RANGE readRange = { 0, 0 };
if (FAILED(resource->Map(0, &readRange, &dst)))
return false;
std::memcpy(dst, data, size);
resource->Unmap(0, nullptr);
return true;
}
}
bool CColorTransformEffect::Init(const ComPtr<ID3D12Device3>& device)
{
D3D12_DESCRIPTOR_RANGE ranges[4] = {};
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_SRV;
ranges[2].NumDescriptors = 1;
ranges[2].BaseShaderRegister = 1;
ranges[2].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
ranges[3].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
ranges[3].NumDescriptors = 1;
ranges[3].BaseShaderRegister = 0;
ranges[3].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
const char * shader =
"cbuffer Constants : register(b0)\n"
"{\n"
" float4 ColorMatrix[3];\n"
" float Scalar;\n"
" uint MatrixEnabled;\n"
" uint LutEnabled;\n"
" uint InputTransfer;\n"
" uint OutputTransfer;\n"
"};\n"
"Texture2D<float4> src : register(t0);\n"
"Buffer<float4> lut : register(t1);\n"
"RWTexture2D<float4> dst : register(u0);\n"
"static const float PQ_m1 = 0.1593017578125;\n"
"static const float PQ_m2 = 78.84375;\n"
"static const float PQ_c1 = 0.8359375;\n"
"static const float PQ_c2 = 18.8515625;\n"
"static const float PQ_c3 = 18.6875;\n"
"float3 decode(float3 value)\n"
"{\n"
" if (InputTransfer == 1)\n"
" return lerp(value / 12.92, pow((value + 0.055) / 1.055, 2.4),\n"
" step(0.04045, value));\n"
" if (InputTransfer == 2)\n"
" {\n"
" float3 p = pow(max(value, 0.0), 1.0 / PQ_m2);\n"
" return pow(max(p - PQ_c1, 0.0) / max(PQ_c2 - PQ_c3 * p, 1e-6),\n"
" 1.0 / PQ_m1);\n"
" }\n"
" return value;\n"
"}\n"
"float3 encode(float3 value)\n"
"{\n"
" if (OutputTransfer == 1)\n"
" return lerp(value * 12.92, 1.055 * pow(max(value, 0.0), 1.0 / 2.4) - 0.055,\n"
" step(0.0031308, value));\n"
" if (OutputTransfer == 2)\n"
" {\n"
" float3 p = pow(max(value, 0.0), PQ_m1);\n"
" return pow((PQ_c1 + PQ_c2 * p) / (1.0 + PQ_c3 * p), PQ_m2);\n"
" }\n"
" return value;\n"
"}\n"
"float3 rgbToXYZ(float3 rgb)\n"
"{\n"
" if (InputTransfer == 2)\n"
" return float3(\n"
" dot(rgb, float3(0.6369580, 0.1446169, 0.1688810)),\n"
" dot(rgb, float3(0.2627002, 0.6779981, 0.0593017)),\n"
" dot(rgb, float3(0.0000000, 0.0280727, 1.0609851)));\n"
" return float3(\n"
" dot(rgb, float3(0.4123908, 0.3575843, 0.1804808)),\n"
" dot(rgb, float3(0.2126390, 0.7151687, 0.0721923)),\n"
" dot(rgb, float3(0.0193308, 0.1191948, 0.9505322)));\n"
"}\n"
"float3 xyzToRGB(float3 xyz)\n"
"{\n"
" if (OutputTransfer == 2)\n"
" return float3(\n"
" dot(xyz, float3( 1.7166512, -0.3556708, -0.2533663)),\n"
" dot(xyz, float3(-0.6666844, 1.6164812, 0.0157685)),\n"
" dot(xyz, float3( 0.0176399, -0.0427706, 0.9421031)));\n"
" return float3(\n"
" dot(xyz, float3( 3.2409699, -1.5373832, -0.4986108)),\n"
" dot(xyz, float3(-0.9692436, 1.8759675, 0.0415551)),\n"
" dot(xyz, float3( 0.0556301, -0.2039770, 1.0569715)));\n"
"}\n"
"float3 applyLut(float3 value)\n"
"{\n"
" float3 pos = saturate(value) * 4095.0;\n"
" uint3 lo = (uint3)floor(pos);\n"
" uint3 hi = min(lo + 1, 4095);\n"
" float3 f = frac(pos);\n"
" return float3(\n"
" lerp(lut[lo.r].r, lut[hi.r].r, f.r),\n"
" lerp(lut[lo.g].g, lut[hi.g].g, f.g),\n"
" lerp(lut[lo.b].b, lut[hi.b].b, f.b));\n"
"}\n"
"[numthreads(" POST_PROCESS_THREADS_STR ", " POST_PROCESS_THREADS_STR ", 1)]\n"
"void main(uint3 dt : SV_DispatchThreadID)\n"
"{\n"
" float4 pixel = src[dt.xy];\n"
" float3 value = decode(pixel.rgb);\n"
" if (MatrixEnabled != 0 || InputTransfer != OutputTransfer)\n"
" {\n"
" float3 xyz = rgbToXYZ(value);\n"
" if (MatrixEnabled != 0)\n"
" xyz = float3(dot(float4(xyz, 1.0), ColorMatrix[0]),\n"
" dot(float4(xyz, 1.0), ColorMatrix[1]),\n"
" dot(float4(xyz, 1.0), ColorMatrix[2])) * Scalar;\n"
" value = xyzToRGB(xyz);\n"
" }\n"
" if (InputTransfer == 0 && OutputTransfer == 2)\n"
" value *= 80.0 / 10000.0;\n"
" value = encode(value);\n"
" if (LutEnabled != 0)\n"
" value = applyLut(value);\n"
" dst[dt.xy] = float4(value, pixel.a);\n"
"}\n";
if (!InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader))
return false;
const size_t constSize = AlignTo(sizeof(m_consts),
(size_t)D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT);
if (!CreateUploadBuffer(device, constSize, m_constBuffer) ||
!CreateUploadBuffer(device, sizeof(float) * 4096 * 4, m_lutBuffer))
{
DEBUG_ERROR("Failed to create color transform buffers");
return false;
}
return true;
}
PostProcessStatus CColorTransformEffect::SetFormat(
const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst)
{
if (!src.colorTransform ||
(!src.colorTransform->matrixEnabled && !src.colorTransform->lutEnabled))
return PostProcessStatus::BYPASS_EFFECT;
DXGI_FORMAT dstFormat;
FrameType frameType;
switch (src.desc.Format)
{
case DXGI_FORMAT_B8G8R8A8_UNORM:
dstFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
frameType = FRAME_TYPE_RGBA;
break;
case DXGI_FORMAT_R8G8B8A8_UNORM:
case DXGI_FORMAT_R10G10B10A2_UNORM:
dstFormat = src.desc.Format;
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;
break;
default:
DEBUG_ERROR("Unsupported color transform source format %u", src.desc.Format);
return PostProcessStatus::FAILED;
}
D3D12_RESOURCE_DESC desc = src.desc;
desc.Format = dstFormat;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
if (!m_dst || m_dst->GetDesc().Width != desc.Width ||
m_dst->GetDesc().Height != desc.Height ||
m_dst->GetDesc().Format != desc.Format)
{
if (!CreateDefaultTexture(device, desc, m_dst))
return PostProcessStatus::FAILED;
}
std::memcpy(m_consts.matrix, src.colorTransform->matrix,
sizeof(m_consts.matrix));
m_consts.scalar = src.colorTransform->scalar;
m_consts.matrixEnabled = src.colorTransform->matrixEnabled;
m_consts.lutEnabled = src.colorTransform->lutEnabled;
m_consts.inputTransfer = src.hdrPQ ? TRANSFER_PQ :
(src.hdr ? TRANSFER_LINEAR : TRANSFER_SRGB);
m_consts.outputTransfer = src.hdr ? TRANSFER_PQ : TRANSFER_SRGB;
std::memcpy(m_lut, src.colorTransform->lut, sizeof(m_lut));
m_uploadPending = true;
m_srcFormat = src.desc.Format;
m_dstFormat = dstFormat;
m_threadsX = ((unsigned)desc.Width + (Threads - 1)) / Threads;
m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads;
dst.desc = desc;
dst.format = frameType;
if (src.hdr)
dst.hdrPQ = true;
return PostProcessStatus::SUCCESS;
}
ComPtr<ID3D12Resource> CColorTransformEffect::Run(
const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects)
{
UNREFERENCED_PARAMETER(dirtyRects);
UNREFERENCED_PARAMETER(nbDirtyRects);
// GetComputeQueue waits for the previous submission before Run is called,
// so this is the first point where the shared upload buffers are guaranteed
// not to be in use by the GPU.
if (m_uploadPending)
{
if (!Upload(m_constBuffer, &m_consts, sizeof(m_consts)) ||
!Upload(m_lutBuffer, m_lut, sizeof(m_lut)))
DEBUG_ERROR("Failed to upload display color transform");
else
m_uploadPending = false;
}
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_srcFormat;
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_SHADER_RESOURCE_VIEW_DESC lutDesc = {};
lutDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
lutDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
lutDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
lutDesc.Buffer.NumElements = 4096;
device->CreateShaderResourceView(m_lutBuffer.Get(), &lutDesc, handle);
handle.ptr += inc;
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
uavDesc.Format = m_dstFormat;
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;
}

View File

@@ -0,0 +1,48 @@
/**
* 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 CColorTransformEffect : public CComputeEffect
{
private:
struct Consts
{
float matrix[3][4];
float scalar;
UINT matrixEnabled;
UINT lutEnabled;
UINT inputTransfer;
UINT outputTransfer;
} m_consts = {};
float m_lut[4096][4] = {};
bool m_uploadPending = false;
ComPtr<ID3D12Resource> m_constBuffer;
ComPtr<ID3D12Resource> m_lutBuffer;
DXGI_FORMAT m_srcFormat = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT m_dstFormat = DXGI_FORMAT_UNKNOWN;
public:
const char * GetName() const override { return "ColorTransform"; }
bool Init(const ComPtr<ID3D12Device3>& device);
PostProcessStatus SetFormat(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) override;
ComPtr<ID3D12Resource> Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects) override;
};