From c190aaf5797fe57ea38c12bb8e1edd1d9f7b003c Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sun, 19 Jul 2026 18:37:10 +1000 Subject: [PATCH] [host] hdr: encode legacy captures as HDR10 Convert FP16 scRGB captures to BT.2020 PQ before storing them in the 10-bit wire format. Preserve linear-light downsampling, publish the real SDR white level and display metadata, and advance the format version when those properties change. --- host/include/interface/capture.h | 9 ++ host/platform/Windows/capture/D12/d12.c | 89 ++++++++++- host/platform/Windows/capture/D12/d12.h | 2 + .../Windows/capture/D12/effect/hdr16to10.c | 114 +++----------- host/platform/Windows/capture/DXGI/src/dxgi.c | 106 +++++++++++-- .../capture/DXGI/src/pp/sdrwhitelevel.c | 146 ++++++------------ host/src/app.c | 82 +++++++++- 7 files changed, 324 insertions(+), 224 deletions(-) diff --git a/host/include/interface/capture.h b/host/include/interface/capture.h index 20768d33..5040f133 100644 --- a/host/include/interface/capture.h +++ b/host/include/interface/capture.h @@ -84,8 +84,17 @@ typedef struct CaptureFrame bool truncated; // true if the frame data is truncated bool hdr; // true if the frame format is HDR bool hdrPQ; // true if the frame format is PQ transformed + bool hdrMetadata; // true if the HDR static metadata is valid CaptureRotation rotation; // output rotation of the frame + uint16_t hdrDisplayPrimary[3][2]; + uint16_t hdrWhitePoint[2]; + uint32_t hdrMaxDisplayLuminance; + uint32_t hdrMinDisplayLuminance; + uint32_t hdrMaxContentLightLevel; + uint32_t hdrMaxFrameAverageLightLevel; + uint32_t sdrWhiteLevel; + uint32_t damageRectsCount; FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS]; } diff --git a/host/platform/Windows/capture/D12/d12.c b/host/platform/Windows/capture/D12/d12.c index f8bf3353..2cca61c7 100644 --- a/host/platform/Windows/capture/D12/d12.c +++ b/host/platform/Windows/capture/D12/d12.c @@ -39,6 +39,7 @@ #include #include #include +#include // definitions struct D12Interface @@ -49,6 +50,7 @@ struct D12Interface ID3D12Device3 ** device; DISPLAYCONFIG_PATH_INFO displayPathInfo; + DXGI_OUTPUT_DESC1 outputDesc; ID3D12CommandQueue ** copyQueue; ID3D12CommandQueue ** computeQueue; @@ -110,6 +112,54 @@ ComScope * d12_comScope = NULL; static struct D12Interface * this = NULL; +static bool d12_setHDRMetadata(CaptureFrame * frame, + const DXGI_OUTPUT_DESC1 * desc) +{ + const float coordinates[] = + { + desc->RedPrimary [0], desc->RedPrimary [1], + desc->GreenPrimary[0], desc->GreenPrimary[1], + desc->BluePrimary [0], desc->BluePrimary [1], + desc->WhitePoint [0], desc->WhitePoint [1], + }; + + for (unsigned i = 0; i < ARRAY_LENGTH(coordinates); ++i) + if (coordinates[i] < 0.0f || coordinates[i] > 1.0f) + return false; + + if (desc->RedPrimary[1] == 0.0f || desc->GreenPrimary[1] == 0.0f || + desc->BluePrimary[1] == 0.0f || desc->WhitePoint[1] == 0.0f || + desc->MaxLuminance <= 0.0f) + return false; + + frame->hdrDisplayPrimary[0][0] = (uint16_t)lroundf( + desc->RedPrimary[0] * 50000.0f); + frame->hdrDisplayPrimary[0][1] = (uint16_t)lroundf( + desc->RedPrimary[1] * 50000.0f); + frame->hdrDisplayPrimary[1][0] = (uint16_t)lroundf( + desc->GreenPrimary[0] * 50000.0f); + frame->hdrDisplayPrimary[1][1] = (uint16_t)lroundf( + desc->GreenPrimary[1] * 50000.0f); + frame->hdrDisplayPrimary[2][0] = (uint16_t)lroundf( + desc->BluePrimary[0] * 50000.0f); + frame->hdrDisplayPrimary[2][1] = (uint16_t)lroundf( + desc->BluePrimary[1] * 50000.0f); + frame->hdrWhitePoint[0] = (uint16_t)lroundf( + desc->WhitePoint[0] * 50000.0f); + frame->hdrWhitePoint[1] = (uint16_t)lroundf( + desc->WhitePoint[1] * 50000.0f); + frame->hdrMaxDisplayLuminance = (uint32_t)lroundf(desc->MaxLuminance); + frame->hdrMinDisplayLuminance = desc->MinLuminance > 0.0f ? + (uint32_t)lroundf(desc->MinLuminance * 10000.0f) : 0; + + if ((uint64_t)frame->hdrMaxDisplayLuminance * 10000 <= + frame->hdrMinDisplayLuminance) + return false; + + frame->hdrMetadata = true; + return true; +} + // forwards static bool d12_enumerateDevices( @@ -298,6 +348,7 @@ static bool d12_init(void * ivshmemBase, unsigned * alignSize) DXGI_OUTPUT_DESC1 desc1; IDXGIOutput6_GetDesc1(*output6, &desc1); + this->outputDesc = desc1; if (!display_getPathInfo(desc1.Monitor, &this->displayPathInfo)) { DEBUG_ERROR("Failed to get the display path info"); @@ -546,12 +597,16 @@ static CaptureResult d12_waitFrame(unsigned frameBufferIndex, goto exit; } + const D3D12_RESOURCE_DESC srcDesc = ID3D12Resource_GetDesc(*src); D12FrameFormat srcFormat = { - .desc = ID3D12Resource_GetDesc(*src), + .desc = srcDesc, .colorSpace = desc.colorSpace, - .width = srcFormat.desc.Width, - .height = srcFormat.desc.Height + .width = srcDesc.Width, + .height = srcDesc.Height, + .hdr = desc.colorSpace == + DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, + .hdrPQ = false, }; switch(srcFormat.desc.Format) @@ -577,12 +632,25 @@ static CaptureResult d12_waitFrame(unsigned frameBufferIndex, goto exit; } + if (srcFormat.hdr) + { + if (srcFormat.format == CAPTURE_FMT_RGBA10) + srcFormat.hdrPQ = true; + else if (srcFormat.format != CAPTURE_FMT_RGBA16F) + { + DEBUG_ERROR("HDR capture did not provide an FP16 scRGB or PQ texture"); + goto exit; + } + } + // if the input format changed, reconfigure the effects if (srcFormat.desc.Width == 0 || srcFormat.desc.Width != this->captureFormat.desc.Width || srcFormat.desc.Height != this->captureFormat.desc.Height || srcFormat.desc.Format != this->captureFormat.desc.Format || - srcFormat.colorSpace != this->captureFormat.colorSpace) + srcFormat.colorSpace != this->captureFormat.colorSpace || + srcFormat.hdr != this->captureFormat.hdr || + srcFormat.hdrPQ != this->captureFormat.hdrPQ) { DEBUG_TRACE("Capture format changed"); @@ -621,7 +689,9 @@ static CaptureResult d12_waitFrame(unsigned frameBufferIndex, dstFormat.colorSpace != this->dstFormat.colorSpace || dstFormat.width != this->dstFormat.width || dstFormat.height != this->dstFormat.height || - dstFormat.format != this->dstFormat.format) + dstFormat.format != this->dstFormat.format || + dstFormat.hdr != this->dstFormat.hdr || + dstFormat.hdrPQ != this->dstFormat.hdrPQ) { DEBUG_TRACE("Output format changed"); ++this->formatVer; @@ -655,9 +725,12 @@ static CaptureResult d12_waitFrame(unsigned frameBufferIndex, frame->pitch = this->pitch; frame->stride = this->pitch / this->bpp; frame->format = this->dstFormat.format; - frame->hdr = this->dstFormat.colorSpace == - DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; - frame->hdrPQ = false; + frame->hdr = this->dstFormat.hdr; + frame->hdrPQ = this->dstFormat.hdrPQ; + frame->sdrWhiteLevel = (uint32_t)lroundf( + display_getSDRWhiteLevel(&this->displayPathInfo)); + if (frame->hdr) + d12_setHDRMetadata(frame, &this->outputDesc); frame->rotation = desc.rotation; D12Effect * effect; diff --git a/host/platform/Windows/capture/D12/d12.h b/host/platform/Windows/capture/D12/d12.h index 0fa3e59d..0117cd11 100644 --- a/host/platform/Windows/capture/D12/d12.h +++ b/host/platform/Windows/capture/D12/d12.h @@ -52,6 +52,8 @@ typedef struct D12FrameFormat DXGI_COLOR_SPACE_TYPE colorSpace; unsigned width, height; CaptureFormat format; + bool hdr; + bool hdrPQ; } D12FrameFormat; diff --git a/host/platform/Windows/capture/D12/effect/hdr16to10.c b/host/platform/Windows/capture/D12/effect/hdr16to10.c index 76f06c50..96b0f4f5 100644 --- a/host/platform/Windows/capture/D12/effect/hdr16to10.c +++ b/host/platform/Windows/capture/D12/effect/hdr16to10.c @@ -27,7 +27,6 @@ #include "common/debug.h" #include "common/windebug.h" #include "common/array.h" -#include "common/display.h" #include "common/option.h" #include @@ -36,17 +35,9 @@ typedef struct HDR16to10Inst { D12Effect base; - const DISPLAYCONFIG_PATH_INFO * displayPathInfo; - struct - { - float SDRWhiteLevel; - } - consts; - ID3D12RootSignature ** rootSignature; ID3D12PipelineState ** pso; ID3D12DescriptorHeap ** descHeap; - ID3D12Resource ** constBuffer; unsigned threadsX, threadsY; ID3D12Resource ** dst; @@ -76,6 +67,8 @@ static void d12_effect_hdr16to10InitOptions(void) static D12EffectStatus d12_effect_hdr16to10Create(D12Effect ** instance, ID3D12Device3 * device, const DISPLAYCONFIG_PATH_INFO * displayPathInfo) { + (void)displayPathInfo; + if (!option_get_bool("d12", "HDR16to10")) return D12_EFFECT_STATUS_BYPASS; @@ -90,18 +83,9 @@ static D12EffectStatus d12_effect_hdr16to10Create(D12Effect ** instance, HRESULT hr; comRef_scopePush(10); - this->displayPathInfo = displayPathInfo; - // shader resource view - D3D12_DESCRIPTOR_RANGE descriptorRanges[3] = + D3D12_DESCRIPTOR_RANGE descriptorRanges[2] = { - { - .RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV, - .NumDescriptors = 1, - .BaseShaderRegister = 0, - .RegisterSpace = 0, - .OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND - }, { .RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV, .NumDescriptors = 1, @@ -175,18 +159,27 @@ static D12EffectStatus d12_effect_hdr16to10Create(D12Effect ** instance, // Compile the shader const char * testCode = - "cbuffer Constants : register(b0)\n" - "{\n" - " float SDRWhiteLevel;\n" - "};\n" - "\n" "Texture2D src : register(t0);\n" "RWTexture2D 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" "\n" "[numthreads(" STR(THREADS) ", " STR(THREADS) ", 1)]\n" "void main(uint3 dt : SV_DispatchThreadID)\n" "{\n" - " dst[dt.xy] = src[dt.xy] * SDRWhiteLevel;" + " float4 color = src[dt.xy];\n" + " float3 linear709 = color.rgb * (80.0 / 10000.0);\n" + " float3 linear2020 = float3(\n" + " dot(linear709, float3(0.6274039, 0.3292830, 0.0433131)),\n" + " dot(linear709, float3(0.0690973, 0.9195404, 0.0113623)),\n" + " dot(linear709, float3(0.0163914, 0.0880133, 0.8955953)));\n" + " float3 p = pow(max(linear2020, 0.0), PQ_m1);\n" + " float3 pq = pow((PQ_c1 + PQ_c2 * p) /\n" + " (1.0 + PQ_c3 * p), PQ_m2);\n" + " dst[dt.xy] = float4(pq, color.a);\n" "}\n"; bool debug = false; @@ -240,48 +233,9 @@ static D12EffectStatus d12_effect_hdr16to10Create(D12Effect ** instance, goto exit; } - D3D12_HEAP_PROPERTIES constHeapProps = - { - .Type = D3D12_HEAP_TYPE_UPLOAD, - .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, - .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN - }; - - D3D12_RESOURCE_DESC constBufferDesc = - { - .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, - .Width = ALIGN_TO(sizeof(this->consts), - D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT), - .Height = 1, - .DepthOrArraySize = 1, - .MipLevels = 1, - .Format = DXGI_FORMAT_UNKNOWN, - .SampleDesc.Count = 1, - .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, - .Flags = D3D12_RESOURCE_FLAG_NONE - }; - - comRef_defineLocal(ID3D12Resource, constBuffer); - hr = ID3D12Device3_CreateCommittedResource( - device, - &constHeapProps, - D3D12_HEAP_FLAG_NONE, - &constBufferDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, - NULL, - &IID_ID3D12Resource, - (void **)constBuffer); - - if (FAILED(hr)) - { - DEBUG_WINERROR("Failed to create the constant buffer resource", hr); - goto exit; - } - comRef_toGlobal(this->rootSignature, rootSignature); comRef_toGlobal(this->pso , pso ); comRef_toGlobal(this->descHeap , descHeap ); - comRef_toGlobal(this->constBuffer , constBuffer ); result = D12_EFFECT_STATUS_OK; *instance = &this->base; @@ -313,7 +267,7 @@ static D12EffectStatus d12_effect_hdr16to10SetFormat(D12Effect * effect, HRESULT hr; if (src->desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT || - src->colorSpace != DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) + !src->hdr || src->hdrPQ) { result = D12_EFFECT_STATUS_BYPASS; goto exit; @@ -358,6 +312,8 @@ static D12EffectStatus d12_effect_hdr16to10SetFormat(D12Effect * effect, dst->desc = desc; dst->format = CAPTURE_FMT_RGBA10; + dst->hdr = true; + dst->hdrPQ = true; result = D12_EFFECT_STATUS_OK; exit: @@ -371,21 +327,6 @@ static ID3D12Resource * d12_effect_hdr16to10Run(D12Effect * effect, { HDR16to10Inst * this = UPCAST(HDR16to10Inst, effect); - float nits = 80.0f / display_getSDRWhiteLevel(this->displayPathInfo); - if (nits != this->consts.SDRWhiteLevel) - { - this->consts.SDRWhiteLevel = nits; - - void * data; - D3D12_RANGE readRange = { 0, 0 }; - HRESULT hr = ID3D12Resource_Map(*this->constBuffer, 0, &readRange, &data); - if (SUCCEEDED(hr)) - { - memcpy(data, &this->consts, sizeof(this->consts)); - ID3D12Resource_Unmap(*this->constBuffer, 0, NULL); - } - } - // transition the destination texture to unordered access so we can write to it { D3D12_RESOURCE_BARRIER barrier = @@ -407,19 +348,6 @@ static ID3D12Resource * d12_effect_hdr16to10Run(D12Effect * effect, D3D12_CPU_DESCRIPTOR_HANDLE cpuSrvUavHandle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(*this->descHeap); - // descriptor for input CBV - D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = - { - .BufferLocation = ID3D12Resource_GetGPUVirtualAddress(*this->constBuffer), - .SizeInBytes = ALIGN_TO(sizeof(this->consts), - D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT) - }; - ID3D12Device3_CreateConstantBufferView(device, &cbvDesc, cpuSrvUavHandle); - - // move to the next slot - cpuSrvUavHandle.ptr += ID3D12Device3_GetDescriptorHandleIncrementSize( - device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); - // descriptor for input SRV D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = { diff --git a/host/platform/Windows/capture/DXGI/src/dxgi.c b/host/platform/Windows/capture/DXGI/src/dxgi.c index 57b51acd..f8a99497 100644 --- a/host/platform/Windows/capture/DXGI/src/dxgi.c +++ b/host/platform/Windows/capture/DXGI/src/dxgi.c @@ -22,6 +22,7 @@ #include "interface/platform.h" #include "common/array.h" #include "common/debug.h" +#include "common/display.h" #include "common/windebug.h" #include "common/option.h" #include "common/locking.h" @@ -53,13 +54,13 @@ //post processers extern const DXGIPostProcess DXGIPP_Downsample; -extern const DXGIPostProcess DXGIPP_SDRWhiteLevel; +extern const DXGIPostProcess DXGIPP_HDR16to10; extern const DXGIPostProcess DXGIPP_RGB24; const DXGIPostProcess * postProcessors[] = { &DXGIPP_Downsample, - &DXGIPP_SDRWhiteLevel, + &DXGIPP_HDR16to10, &DXGIPP_RGB24 }; @@ -135,6 +136,10 @@ struct DXGIInterface DXGI_FORMAT dxgiSrcFormat, dxgiFormat; bool hdr; DXGI_COLOR_SPACE_TYPE dxgiColorSpace; + DXGI_OUTPUT_DESC1 outputDesc; + bool outputDescValid; + DISPLAYCONFIG_PATH_INFO displayPathInfo; + bool displayPathInfoValid; ID3D11VertexShader ** vshader; struct DXGICopyBackend * backend; bool backendConfigured; @@ -165,6 +170,54 @@ struct DXGIInterface static struct DXGIInterface * this = NULL; +static bool dxgi_setHDRMetadata(CaptureFrame * frame, + const DXGI_OUTPUT_DESC1 * desc) +{ + const float coordinates[] = + { + desc->RedPrimary [0], desc->RedPrimary [1], + desc->GreenPrimary[0], desc->GreenPrimary[1], + desc->BluePrimary [0], desc->BluePrimary [1], + desc->WhitePoint [0], desc->WhitePoint [1], + }; + + for (unsigned i = 0; i < ARRAY_LENGTH(coordinates); ++i) + if (coordinates[i] < 0.0f || coordinates[i] > 1.0f) + return false; + + if (desc->RedPrimary[1] == 0.0f || desc->GreenPrimary[1] == 0.0f || + desc->BluePrimary[1] == 0.0f || desc->WhitePoint[1] == 0.0f || + desc->MaxLuminance <= 0.0f) + return false; + + frame->hdrDisplayPrimary[0][0] = (uint16_t)lroundf( + desc->RedPrimary[0] * 50000.0f); + frame->hdrDisplayPrimary[0][1] = (uint16_t)lroundf( + desc->RedPrimary[1] * 50000.0f); + frame->hdrDisplayPrimary[1][0] = (uint16_t)lroundf( + desc->GreenPrimary[0] * 50000.0f); + frame->hdrDisplayPrimary[1][1] = (uint16_t)lroundf( + desc->GreenPrimary[1] * 50000.0f); + frame->hdrDisplayPrimary[2][0] = (uint16_t)lroundf( + desc->BluePrimary[0] * 50000.0f); + frame->hdrDisplayPrimary[2][1] = (uint16_t)lroundf( + desc->BluePrimary[1] * 50000.0f); + frame->hdrWhitePoint[0] = (uint16_t)lroundf( + desc->WhitePoint[0] * 50000.0f); + frame->hdrWhitePoint[1] = (uint16_t)lroundf( + desc->WhitePoint[1] * 50000.0f); + frame->hdrMaxDisplayLuminance = (uint32_t)lroundf(desc->MaxLuminance); + frame->hdrMinDisplayLuminance = desc->MinLuminance > 0.0f ? + (uint32_t)lroundf(desc->MinLuminance * 10000.0f) : 0; + + if ((uint64_t)frame->hdrMaxDisplayLuminance * 10000 <= + frame->hdrMinDisplayLuminance) + return false; + + frame->hdrMetadata = true; + return true; +} + extern struct DXGICopyBackend copyBackendD3D11; static struct DXGICopyBackend * backends[] = { ©BackendD3D11, @@ -364,6 +417,15 @@ static bool dxgi_init(void * ivshmemBase, unsigned * alignSize) { DEBUG_ASSERT(this); + // These describe the output selected by this initialization. Do not retain + // metadata from a previous duplication session if the replacement output + // does not expose IDXGIOutput6 or display path information. + this->dxgiColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + this->outputDescValid = false; + this->displayPathInfoValid = false; + memset(&this->outputDesc , 0, sizeof(this->outputDesc )); + memset(&this->displayPathInfo, 0, sizeof(this->displayPathInfo)); + comRef_initGlobalScope(20 + this->maxTextures * 16, this->comScope); comRef_scopePush(20); @@ -725,7 +787,11 @@ static bool dxgi_init(void * ivshmemBase, unsigned * alignSize) { DXGI_OUTPUT_DESC1 desc1; IDXGIOutput6_GetDesc1(*output6, &desc1); - this->dxgiColorSpace = desc1.ColorSpace; + this->dxgiColorSpace = desc1.ColorSpace; + this->outputDesc = desc1; + this->outputDescValid = true; + this->displayPathInfoValid = + display_getPathInfo(desc1.Monitor, &this->displayPathInfo); DEBUG_INFO("Bits Per Color : %u" , desc1.BitsPerColor); DEBUG_INFO("Color Space : %s" , getDXGIColorSpaceTypeStr(this->dxgiColorSpace)); @@ -793,6 +859,12 @@ static bool dxgi_init(void * ivshmemBase, unsigned * alignSize) goto fail; } + if (this->hdr && this->format != CAPTURE_FMT_RGBA16F) + { + DEBUG_ERROR("HDR capture did not provide an FP16 scRGB texture"); + goto fail; + } + this->outputWidth = this->width; this->outputHeight = this->height; @@ -824,23 +896,23 @@ static bool dxgi_init(void * ivshmemBase, unsigned * alignSize) goto fail; bool shareable = this->backend != ©BackendD3D11; - if (this->hdr) - { - //HDR content needs to be corrected and converted to HDR10 - if (!ppInit(&DXGIPP_SDRWhiteLevel, shareable)) - { - DEBUG_ERROR("Failed to initialize the SDRWhiteLevel post processor"); - goto fail; - } - } - - //Downsampling must happen before conversion to RGB24 + // Downsampling must happen in linear light, before HDR conversion or RGB24 + // packing. if (!ppInit(&DXGIPP_Downsample, shareable)) { DEBUG_ERROR("Failed to intiailize the downsample post processor"); goto fail; } + if (this->hdr) + { + if (!ppInit(&DXGIPP_HDR16to10, shareable)) + { + DEBUG_ERROR("Failed to initialize the HDR16to10 post processor"); + goto fail; + } + } + //If not HDR, pack to RGB24 if (!this->hdr && this->allowRGB24) { @@ -1416,7 +1488,11 @@ static CaptureResult dxgi_waitFrame(unsigned frameBufferIndex, frame->stride = this->stride; frame->format = this->outputFormat; frame->hdr = this->hdr; - frame->hdrPQ = false; + frame->hdrPQ = this->hdr; + frame->sdrWhiteLevel = this->displayPathInfoValid ? + (uint32_t)lroundf(display_getSDRWhiteLevel(&this->displayPathInfo)) : 0; + if (frame->hdr && this->outputDescValid) + dxgi_setHDRMetadata(frame, &this->outputDesc); frame->rotation = this->rotation; frame->damageRectsCount = tex->damageRectsCount; diff --git a/host/platform/Windows/capture/DXGI/src/pp/sdrwhitelevel.c b/host/platform/Windows/capture/DXGI/src/pp/sdrwhitelevel.c index c32e87db..e11133ae 100644 --- a/host/platform/Windows/capture/DXGI/src/pp/sdrwhitelevel.c +++ b/host/platform/Windows/capture/DXGI/src/pp/sdrwhitelevel.c @@ -24,11 +24,8 @@ #include "common/debug.h" #include "common/windebug.h" -#include "common/display.h" -#include - -typedef struct SDRWhiteLevel +typedef struct HDR16to10 { ComScope * comScope; @@ -38,13 +35,9 @@ typedef struct SDRWhiteLevel bool shareable; ID3D11PixelShader ** pshader; ID3D11SamplerState ** sampler; - ID3D11Buffer ** buffer; - - DISPLAYCONFIG_PATH_INFO displayPathInfo; - float sdrWhiteLevel; } -SDRWhiteLevel; -static SDRWhiteLevel this = {0}; +HDR16to10; +static HDR16to10 this = {0}; #define comRef_toGlobal(dst, src) \ _comRef_toGlobal(this.comScope, dst, src) @@ -54,23 +47,16 @@ typedef struct ID3D11Texture2D ** tex; ID3D11RenderTargetView ** target; } -SDRWhiteLevelInst; +HDR16to10Inst; -struct ShaderConsts -{ - float sdrWhiteLevel; -} -__attribute__((aligned(16))); - -static void updateConsts(void); - -static bool sdrWhiteLevel_setup( +static bool hdr16to10_setup( ID3D11Device ** device, ID3D11DeviceContext ** context, IDXGIOutput ** output, bool shareable ) { + (void)output; bool result = false; HRESULT status; @@ -78,41 +64,30 @@ static bool sdrWhiteLevel_setup( this.context = context; this.shareable = shareable; - comRef_initGlobalScope(11, this.comScope); - comRef_scopePush(11); - - comRef_defineLocal(IDXGIOutput6, output6); - status = IDXGIOutput_QueryInterface( - *output, &IID_IDXGIOutput6, (void **)output6); - - if (!SUCCEEDED(status)) - { - DEBUG_ERROR("Failed to get the IDXGIOutput6 interface"); - goto exit; - } - - DXGI_OUTPUT_DESC1 desc1; - IDXGIOutput6_GetDesc1(*output6, &desc1); - if (!display_getPathInfo(desc1.Monitor, &this.displayPathInfo)) - { - DEBUG_ERROR("Failed to get the display path info"); - goto exit; - } + comRef_initGlobalScope(10, this.comScope); + comRef_scopePush(10); static const char * pshaderSrc = "Texture2D gInputTexture : register(t0);\n" "SamplerState gSamplerState : register(s0);\n" - "cbuffer gConsts : register(b0)\n" - "{\n" - " float SDRWhiteLevel;" - "};\n" - "\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" "float4 main(\n" " float4 position : SV_POSITION,\n" " float2 texCoord : TEXCOORD0) : SV_TARGET" "{\n" " float4 color = gInputTexture.Sample(gSamplerState, texCoord);\n" - " color.rgb *= SDRWhiteLevel;\n" + " float3 linear709 = color.rgb * (80.0 / 10000.0);\n" + " float3 linear2020 = float3(\n" + " dot(linear709, float3(0.6274039, 0.3292830, 0.0433131)),\n" + " dot(linear709, float3(0.0690973, 0.9195404, 0.0113623)),\n" + " dot(linear709, float3(0.0163914, 0.0880133, 0.8955953)));\n" + " float3 p = pow(max(linear2020, 0.0), PQ_m1);\n" + " color.rgb = pow((PQ_c1 + PQ_c2 * p) /\n" + " (1.0 + PQ_c3 * p), PQ_m2);\n" " return color;\n" "}\n"; @@ -156,29 +131,7 @@ static bool sdrWhiteLevel_setup( goto exit; } - D3D11_BUFFER_DESC bufferDesc = - { - .ByteWidth = sizeof(struct ShaderConsts), - .Usage = D3D11_USAGE_DEFAULT, - .BindFlags = D3D11_BIND_CONSTANT_BUFFER, - }; - - comRef_defineLocal(ID3D11Buffer, buffer); - status = ID3D11Device_CreateBuffer( - *this.device, &bufferDesc, NULL, - buffer); - - if (FAILED(status)) - { - DEBUG_WINERROR("Failed to create the constant buffer", status); - goto exit; - } - comRef_toGlobal(this.sampler, sampler); - comRef_toGlobal(this.buffer , buffer ); - - updateConsts(); - DEBUG_INFO("SDR White Level : %f" , this.sdrWhiteLevel); result = true; @@ -187,15 +140,15 @@ exit: return result; } -static void sdrWhiteLevel_finish(void) +static void hdr16to10_finish(void) { comRef_freeScope(&this.comScope); memset(&this, 0, sizeof(this)); } -static bool sdrWhiteLevel_init(void ** opaque) +static bool hdr16to10_init(void ** opaque) { - SDRWhiteLevelInst * inst = (SDRWhiteLevelInst *)calloc(1, sizeof(*inst)); + HDR16to10Inst * inst = (HDR16to10Inst *)calloc(1, sizeof(*inst)); if (!inst) { DEBUG_ERROR("Failed to allocate memory"); @@ -206,34 +159,26 @@ static bool sdrWhiteLevel_init(void ** opaque) return true; } -static void sdrWhiteLevel_free(void * opaque) +static void hdr16to10_free(void * opaque) { - SDRWhiteLevelInst * inst = (SDRWhiteLevelInst *)opaque; + HDR16to10Inst * inst = (HDR16to10Inst *)opaque; comRef_release(inst->target); comRef_release(inst->tex ); free(inst); } -static void updateConsts(void) -{ - float nits = display_getSDRWhiteLevel(&this.displayPathInfo); - if (nits == this.sdrWhiteLevel) - return; - - this.sdrWhiteLevel = nits; - - struct ShaderConsts consts = { .sdrWhiteLevel = 80.0f / nits }; - ID3D11DeviceContext_UpdateSubresource( - *this.context, *(ID3D11Resource**)this.buffer, - 0, NULL, &consts, 0, 0); -} - -static bool sdrWhiteLevel_configure(void * opaque, +static bool hdr16to10_configure(void * opaque, int * width, int * height, int * cols , int * rows, CaptureFormat * format) { - SDRWhiteLevelInst * inst = (SDRWhiteLevelInst *)opaque; + HDR16to10Inst * inst = (HDR16to10Inst *)opaque; + if (*format != CAPTURE_FMT_RGBA16F) + { + DEBUG_ERROR("HDR16to10 requires an FP16 scRGB source"); + return false; + } + if (inst->tex) return true; @@ -295,12 +240,10 @@ fail: return false; } -static ID3D11Texture2D * sdrWhiteLevel_run(void * opaque, +static ID3D11Texture2D * hdr16to10_run(void * opaque, ID3D11ShaderResourceView * srv) { - SDRWhiteLevelInst * inst = (SDRWhiteLevelInst *)opaque; - - updateConsts(); + HDR16to10Inst * inst = (HDR16to10Inst *)opaque; // set the pixel shader & resource ID3D11DeviceContext_PSSetShader(*this.context, *this.pshader, NULL, 0); @@ -308,7 +251,6 @@ static ID3D11Texture2D * sdrWhiteLevel_run(void * opaque, // set the pixel shader resources ID3D11DeviceContext_PSSetShaderResources(*this.context, 0, 1, &srv ); ID3D11DeviceContext_PSSetSamplers (*this.context, 0, 1, this.sampler); - ID3D11DeviceContext_PSSetConstantBuffers(*this.context, 0, 1, this.buffer ); // set the render target ID3D11DeviceContext_OMSetRenderTargets(*this.context, 1, inst->target, NULL); @@ -316,14 +258,14 @@ static ID3D11Texture2D * sdrWhiteLevel_run(void * opaque, return *inst->tex; } -DXGIPostProcess DXGIPP_SDRWhiteLevel = +DXGIPostProcess DXGIPP_HDR16to10 = { - .name = "SDRWhiteLevel", + .name = "HDR16to10", .earlyInit = NULL, - .setup = sdrWhiteLevel_setup, - .init = sdrWhiteLevel_init, - .free = sdrWhiteLevel_free, - .configure = sdrWhiteLevel_configure, - .run = sdrWhiteLevel_run, - .finish = sdrWhiteLevel_finish + .setup = hdr16to10_setup, + .init = hdr16to10_init, + .free = hdr16to10_free, + .configure = hdr16to10_configure, + .run = hdr16to10_run, + .finish = hdr16to10_finish }; diff --git a/host/src/app.c b/host/src/app.c index 1528abf6..2810b577 100644 --- a/host/src/app.c +++ b/host/src/app.c @@ -107,6 +107,18 @@ struct app unsigned int readIndex; bool frameValid; uint32_t frameSerial; + uint32_t formatVer; + unsigned int captureFormatVer; + bool hdr; + bool hdrPQ; + bool hdrMetadata; + uint16_t hdrDisplayPrimary[3][2]; + uint16_t hdrWhitePoint[2]; + uint32_t hdrMaxDisplayLuminance; + uint32_t hdrMinDisplayLuminance; + uint32_t hdrMaxContentLightLevel; + uint32_t hdrMaxFrameAverageLightLevel; + _Atomic(uint32_t) sdrWhiteLevel; CaptureInterface * iface; bool captureStarted; @@ -278,9 +290,44 @@ static bool sendFrame(CaptureResult result, bool * restart) } KVMFRFrame * fi = app.frame[app.captureIndex]; + const uint32_t sdrWhiteLevel = frame.sdrWhiteLevel ? + frame.sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT; + const bool metadataChanged = + app.hdrMetadata != frame.hdrMetadata || + (frame.hdrMetadata && + (memcmp(app.hdrDisplayPrimary, frame.hdrDisplayPrimary, + sizeof(app.hdrDisplayPrimary)) != 0 || + memcmp(app.hdrWhitePoint, frame.hdrWhitePoint, + sizeof(app.hdrWhitePoint)) != 0 || + app.hdrMaxDisplayLuminance != frame.hdrMaxDisplayLuminance || + app.hdrMinDisplayLuminance != frame.hdrMinDisplayLuminance || + app.hdrMaxContentLightLevel != frame.hdrMaxContentLightLevel || + app.hdrMaxFrameAverageLightLevel != + frame.hdrMaxFrameAverageLightLevel)); + + if (!app.frameValid || app.captureFormatVer != frame.formatVer || + app.hdr != frame.hdr || app.hdrPQ != frame.hdrPQ || + metadataChanged || atomic_load(&app.sdrWhiteLevel) != sdrWhiteLevel) + ++app.formatVer; + + app.captureFormatVer = frame.formatVer; + app.hdr = frame.hdr; + app.hdrPQ = frame.hdrPQ; + app.hdrMetadata = frame.hdrMetadata; + memcpy(app.hdrDisplayPrimary, frame.hdrDisplayPrimary, + sizeof(app.hdrDisplayPrimary)); + memcpy(app.hdrWhitePoint, frame.hdrWhitePoint, + sizeof(app.hdrWhitePoint)); + app.hdrMaxDisplayLuminance = frame.hdrMaxDisplayLuminance; + app.hdrMinDisplayLuminance = frame.hdrMinDisplayLuminance; + app.hdrMaxContentLightLevel = frame.hdrMaxContentLightLevel; + app.hdrMaxFrameAverageLightLevel = frame.hdrMaxFrameAverageLightLevel; + atomic_store(&app.sdrWhiteLevel, sdrWhiteLevel); + KVMFRFrameFlags flags = - (frame.hdr ? FRAME_FLAG_HDR : 0) | - (frame.hdrPQ ? FRAME_FLAG_HDR_PQ : 0); + (frame.hdr ? FRAME_FLAG_HDR : 0) | + (frame.hdrPQ ? FRAME_FLAG_HDR_PQ : 0) | + (frame.hdrMetadata ? FRAME_FLAG_HDR_METADATA : 0); switch(frame.format) { @@ -335,7 +382,7 @@ static bool sendFrame(CaptureResult result, bool * restart) if (frame.truncated) flags |= FRAME_FLAG_TRUNCATED; - fi->formatVer = frame.formatVer; + fi->formatVer = app.formatVer; fi->frameSerial = app.frameSerial++; fi->screenWidth = frame.screenWidth; fi->screenHeight = frame.screenHeight; @@ -347,7 +394,28 @@ static bool sendFrame(CaptureResult result, bool * restart) fi->pitch = frame.pitch; // fi->offset is initialized at startup fi->flags = flags; - fi->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; + fi->sdrWhiteLevel = sdrWhiteLevel; + if (frame.hdrMetadata) + { + memcpy(fi->hdrDisplayPrimary, frame.hdrDisplayPrimary, + sizeof(fi->hdrDisplayPrimary)); + memcpy(fi->hdrWhitePoint, frame.hdrWhitePoint, + sizeof(fi->hdrWhitePoint)); + fi->hdrMaxDisplayLuminance = frame.hdrMaxDisplayLuminance; + fi->hdrMinDisplayLuminance = frame.hdrMinDisplayLuminance; + fi->hdrMaxContentLightLevel = frame.hdrMaxContentLightLevel; + fi->hdrMaxFrameAverageLightLevel = + frame.hdrMaxFrameAverageLightLevel; + } + else + { + memset(fi->hdrDisplayPrimary, 0, sizeof(fi->hdrDisplayPrimary)); + memset(fi->hdrWhitePoint, 0, sizeof(fi->hdrWhitePoint)); + fi->hdrMaxDisplayLuminance = 0; + fi->hdrMinDisplayLuminance = 0; + fi->hdrMaxContentLightLevel = 0; + fi->hdrMaxFrameAverageLightLevel = 0; + } fi->damageRectsCount = frame.damageRectsCount; memcpy(fi->damageRects, frame.damageRects, frame.damageRectsCount * sizeof(FrameDamageRect)); @@ -515,7 +583,7 @@ static void sendPointer(bool newClient) KVMFRCursor *cursor = lgmpHostMemPtr(mem); cursor->x = app.pointerInfo.x; cursor->y = app.pointerInfo.y; - cursor->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; + cursor->sdrWhiteLevel = atomic_load(&app.sdrWhiteLevel); const uint32_t flags = CURSOR_FLAG_POSITION | CURSOR_FLAG_VISIBLE_VALID | (app.pointerShapeValid ? CURSOR_FLAG_SHAPE : 0) | @@ -540,7 +608,7 @@ static void sendPointer(bool newClient) app.pointerIndex = 0; } KVMFRCursor *cursor = lgmpHostMemPtr(mem); - cursor->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; + cursor->sdrWhiteLevel = atomic_load(&app.sdrWhiteLevel); if (app.pointerInfo.positionUpdate || newClient) { @@ -795,6 +863,8 @@ static bool lgmpSetup(struct IVSHMEM * shmDev) app.frameBuffer[i] = (FrameBuffer *)(((uint8_t*)app.frame[i]) + alignOffset); } + atomic_store(&app.sdrWhiteLevel, + KVMFR_SDR_WHITE_LEVEL_DEFAULT); atomic_store(&app.lgmpTimerState, LGMP_TIMER_STATE_OK); if (!lgCreateTimer(10, lgmpTimer, NULL, &app.lgmpTimer)) {