Implement HDR support for Windows (#825)

This commit is contained in:
Cameron Gutman
2023-01-25 19:48:55 -06:00
committed by GitHub
parent 1f2ad8da00
commit bcd5188ac5
9 changed files with 369 additions and 57 deletions

View File

@@ -151,6 +151,7 @@ protected:
}
const char *dxgi_format_to_string(DXGI_FORMAT format);
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
virtual int complete_img(img_t *img, bool dummy) = 0;

View File

@@ -563,6 +563,25 @@ int display_base_t::init(const ::video::config_t &config, const std::string &dis
BOOST_LOG(info) << "Desktop resolution ["sv << dup_desc.ModeDesc.Width << 'x' << dup_desc.ModeDesc.Height << ']';
BOOST_LOG(info) << "Desktop format ["sv << dxgi_format_to_string(dup_desc.ModeDesc.Format) << ']';
dxgi::output6_t output6 {};
status = output->QueryInterface(IID_IDXGIOutput6, (void **)&output6);
if(SUCCEEDED(status)) {
DXGI_OUTPUT_DESC1 desc1;
output6->GetDesc1(&desc1);
BOOST_LOG(info)
<< std::endl
<< "Colorspace : "sv << colorspace_to_string(desc1.ColorSpace) << std::endl
<< "Bits Per Color : "sv << desc1.BitsPerColor << std::endl
<< "Red Primary : ["sv << desc1.RedPrimary[0] << ',' << desc1.RedPrimary[1] << ']' << std::endl
<< "Green Primary : ["sv << desc1.GreenPrimary[0] << ',' << desc1.GreenPrimary[1] << ']' << std::endl
<< "Blue Primary : ["sv << desc1.BluePrimary[0] << ',' << desc1.BluePrimary[1] << ']' << std::endl
<< "White Point : ["sv << desc1.WhitePoint[0] << ',' << desc1.WhitePoint[1] << ']' << std::endl
<< "Min Luminance : "sv << desc1.MinLuminance << " nits"sv << std::endl
<< "Max Luminance : "sv << desc1.MaxLuminance << " nits"sv << std::endl
<< "Max Full Luminance : "sv << desc1.MaxFullFrameLuminance << " nits"sv;
}
// Capture format will be determined from the first call to AcquireNextFrame()
capture_format = DXGI_FORMAT_UNKNOWN;
@@ -598,6 +617,23 @@ bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
DXGI_OUTPUT_DESC1 desc1;
output6->GetDesc1(&desc1);
// The primaries reported here seem to correspond to scRGB (Rec. 709)
// which we then convert to Rec 2020 in our scRGB FP16 -> PQ shader
// prior to encoding. It's not clear to me if we're supposed to report
// the primaries of the original colorspace or the one we've converted
// it to, but let's just report Rec 2020 primaries and D65 white level
// to avoid confusing clients by reporting Rec 709 primaries with a
// Rec 2020 colorspace. It seems like most clients ignore the primaries
// in the metadata anyway (luminance range is most important).
desc1.RedPrimary[0] = 0.708f;
desc1.RedPrimary[1] = 0.292f;
desc1.GreenPrimary[0] = 0.170f;
desc1.GreenPrimary[1] = 0.797f;
desc1.BluePrimary[0] = 0.131f;
desc1.BluePrimary[1] = 0.046f;
desc1.WhitePoint[0] = 0.3127f;
desc1.WhitePoint[1] = 0.3290f;
metadata.displayPrimaries[0].x = desc1.RedPrimary[0] * 50000;
metadata.displayPrimaries[0].y = desc1.RedPrimary[1] * 50000;
metadata.displayPrimaries[1].x = desc1.GreenPrimary[0] * 50000;
@@ -749,6 +785,43 @@ const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
return format_str[format];
}
const char *display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) {
const char *type_str[] = {
"DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709",
"DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709",
"DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709",
"DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020",
"DXGI_COLOR_SPACE_RESERVED",
"DXGI_COLOR_SPACE_YCBCR_FULL_G22_NONE_P709_X601",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601",
"DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709",
"DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020",
"DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020",
"DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020",
"DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020",
"DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020",
"DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020",
"DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P709",
"DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P2020",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P709",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P2020",
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020",
};
if(type < ARRAYSIZE(type_str)) {
return type_str[type];
}
else {
return "UNKNOWN";
}
}
} // namespace platf::dxgi
namespace platf {

View File

@@ -89,9 +89,12 @@ blend_t make_blend(device_t::pointer device, bool enable, bool invert) {
blob_t convert_UV_vs_hlsl;
blob_t convert_UV_ps_hlsl;
blob_t convert_UV_PQ_ps_hlsl;
blob_t scene_vs_hlsl;
blob_t convert_Y_ps_hlsl;
blob_t convert_Y_PQ_ps_hlsl;
blob_t scene_ps_hlsl;
blob_t scene_NW_ps_hlsl;
struct img_d3d_t : public platf::img_t {
std::shared_ptr<platf::display_t> display;
@@ -546,28 +549,39 @@ public:
return -1;
}
status = device->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
if(status) {
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
status = device->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
if(status) {
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
status = device->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs);
if(status) {
BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
if(status) {
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
// If the display is in HDR and we're streaming HDR, we'll be converting scRGB to SMPTE 2084 PQ.
// NB: We can consume scRGB in SDR with our regular shaders because it behaves like UNORM input.
if(format == DXGI_FORMAT_P010 && display->is_hdr()) {
status = device->CreatePixelShader(convert_Y_PQ_ps_hlsl->GetBufferPointer(), convert_Y_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
if(status) {
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
status = device->CreatePixelShader(convert_UV_PQ_ps_hlsl->GetBufferPointer(), convert_UV_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
if(status) {
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
else {
status = device->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
if(status) {
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
status = device->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
if(status) {
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
color_matrix = make_buffer(device.get(), ::video::colors[0]);
@@ -708,7 +722,6 @@ public:
vs_t convert_UV_vs;
ps_t convert_UV_ps;
ps_t convert_Y_ps;
ps_t scene_ps;
vs_t scene_vs;
D3D11_VIEWPORT outY_view;
@@ -978,10 +991,32 @@ int display_vram_t::init(const ::video::config_t &config, const std::string &dis
return -1;
}
status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
if(status) {
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
if(config.dynamicRange && is_hdr()) {
// This shader will normalize scRGB white levels to a user-defined white level
status = device->CreatePixelShader(scene_NW_ps_hlsl->GetBufferPointer(), scene_NW_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
if(status) {
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
// Use a 300 nit target for the mouse cursor. We should really get
// the user's SDR white level in nits, but there is no API that
// provides that information to Win32 apps.
float sdr_multiplier_data[16 / sizeof(float)] { 300.0f / 80.f }; // aligned to 16-byte
auto sdr_multiplier = make_buffer(device.get(), sdr_multiplier_data);
if(!sdr_multiplier) {
BOOST_LOG(warning) << "Failed to create SDR multiplier"sv;
return -1;
}
device_ctx->PSSetConstantBuffers(0, 1, &sdr_multiplier);
}
else {
status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
if(status) {
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
blend_alpha = make_blend(device.get(), true, false);
@@ -1108,7 +1143,25 @@ std::vector<DXGI_FORMAT> display_vram_t::get_supported_sdr_capture_formats() {
}
std::vector<DXGI_FORMAT> display_vram_t::get_supported_hdr_capture_formats() {
return { DXGI_FORMAT_R10G10B10A2_UNORM };
return {
// scRGB FP16 is the desired format for HDR content. This will also handle
// 10-bit SDR displays with the increased precision of FP16 vs 8-bit UNORMs.
DXGI_FORMAT_R16G16B16A16_FLOAT,
// DXGI_FORMAT_R10G10B10A2_UNORM seems like it might give us frames already
// converted to SMPTE 2084 PQ, however it seems to actually just clamp the
// scRGB FP16 values that DWM is using when the desktop format is scRGB FP16.
//
// If there is a case where the desktop format is really SMPTE 2084 PQ, it
// might make sense to support capturing it without conversion to scRGB,
// but we avoid it for now.
// We include the 8-bit modes too for when the display is in SDR mode,
// while the client stream is HDR-capable. These UNORM formats behave
// like a degenerate case of scRGB FP16 with values between 0.0f-1.0f.
DXGI_FORMAT_B8G8R8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM,
};
}
std::shared_ptr<platf::hwdevice_t> display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) {
@@ -1144,11 +1197,21 @@ int init() {
return -1;
}
convert_Y_PQ_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS_PQ.hlsl");
if(!convert_Y_PQ_ps_hlsl) {
return -1;
}
convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl");
if(!convert_UV_ps_hlsl) {
return -1;
}
convert_UV_PQ_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS_PQ.hlsl");
if(!convert_UV_PQ_ps_hlsl) {
return -1;
}
convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl");
if(!convert_UV_vs_hlsl) {
return -1;
@@ -1158,6 +1221,11 @@ int init() {
if(!scene_ps_hlsl) {
return -1;
}
scene_NW_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS_NW.hlsl");
if(!scene_NW_ps_hlsl) {
return -1;
}
BOOST_LOG(info) << "Compiled shaders"sv;
return 0;