Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/platform/windows/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,13 @@ namespace platf::dxgi {

virtual bool is_hdr() override;
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override;
// Returns true when the capture source delivers FP16 storage with already
// gamma-encoded (sRGB) values, as happens with Windows 11 Auto Color
// Management (ACM) when an SDR application is displayed on an SDR monitor.
// In this case the linear shader path must NOT apply ApplySRGBCurve again
// (otherwise highlights are doubly encoded → white-out).
// Distinct from is_hdr() which checks for PQ HDR (G2084).
bool is_source_gamma_encoded_fp16();

const char *dxgi_format_to_string(DXGI_FORMAT format);
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
Expand Down
21 changes: 21 additions & 0 deletions src/platform/windows/display_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,27 @@ namespace platf::dxgi {
return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
}

bool display_base_t::is_source_gamma_encoded_fp16() {
dxgi::output6_t output6 {};

auto status = output->QueryInterface(IID_IDXGIOutput6, (void **) &output6);
if (FAILED(status)) {
return false;
}

DXGI_OUTPUT_DESC1 desc1;
output6->GetDesc1(&desc1);

// Windows 11 Auto Color Management (ACM) composes SDR desktops into an
// FP16 framebuffer but the pixel values remain gamma-encoded (G22).
// The "linear" shader path assumes linear scRGB FP16 (G10) and applies
// ApplySRGBCurve, which double-encodes the already-sRGB G22 values,
// crushing highlights to white. Use this method to route G22+FP16 input
// through the non-linear shader path (saturate only, no curve).
return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
|| desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020;
}

bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
dxgi::output6_t output6 {};

Expand Down
39 changes: 35 additions & 4 deletions src/platform/windows/display_vram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,15 +507,28 @@ namespace platf::dxgi {
// Semi-planar 8-bit YUV 4:2:0
create_vertex_shader_helper(convert_yuv420_planar_y_vs_hlsl, convert_Y_or_YUV_vs);
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_ps);
create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
if (display->is_source_gamma_encoded_fp16()) {
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding (would cause highlight white-out)
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
}
if (downscaling) {
create_vertex_shader_helper(convert_yuv420_packed_uv_type0s_vs_hlsl, convert_UV_vs);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_ps);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps);
if (display->is_source_gamma_encoded_fp16()) {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps);
}
} else {
create_vertex_shader_helper(convert_yuv420_packed_uv_type0_vs_hlsl, convert_UV_vs);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps);
if (display->is_source_gamma_encoded_fp16()) {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps);
}
}
break;

Expand All @@ -525,6 +538,9 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_ps);
if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
} else if (display->is_source_gamma_encoded_fp16()) {
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
}
Expand All @@ -533,6 +549,8 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_ps);
if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps);
} else if (display->is_source_gamma_encoded_fp16()) {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps);
}
Expand All @@ -541,6 +559,8 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps);
if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps);
} else if (display->is_source_gamma_encoded_fp16()) {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps);
}
Expand All @@ -553,6 +573,9 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv444_planar_ps_hlsl, convert_Y_or_YUV_ps);
if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv444_planar_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
} else if (display->is_source_gamma_encoded_fp16()) {
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding
create_pixel_shader_helper(convert_yuv444_planar_ps_hlsl, convert_Y_or_YUV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv444_planar_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
}
Expand All @@ -562,7 +585,12 @@ namespace platf::dxgi {
// Packed 8-bit YUV 4:4:4
create_vertex_shader_helper(convert_yuv444_packed_vs_hlsl, convert_Y_or_YUV_vs);
create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_hlsl, convert_Y_or_YUV_ps);
create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
if (display->is_source_gamma_encoded_fp16()) {
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding
create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_hlsl, convert_Y_or_YUV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
}
break;

case DXGI_FORMAT_Y410:
Expand All @@ -571,6 +599,9 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_hlsl, convert_Y_or_YUV_ps);
if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
} else if (display->is_source_gamma_encoded_fp16()) {
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_hlsl, convert_Y_or_YUV_fp16_ps);
} else {
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
}
Expand Down