[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.
This commit is contained in:
Geoffrey McRae
2026-07-19 18:37:10 +10:00
parent 4abbf33e9f
commit c190aaf579
7 changed files with 324 additions and 224 deletions

View File

@@ -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[] = {
&copyBackendD3D11,
@@ -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 != &copyBackendD3D11;
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;

View File

@@ -24,11 +24,8 @@
#include "common/debug.h"
#include "common/windebug.h"
#include "common/display.h"
#include <dxgi1_6.h>
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
};