Skip to content

Commit 5bbf9d2

Browse files
committed
fix(windows): correct shader selection for sRGB-encoded FP16 framebuffer (Win11 SDR)
1 parent 0709990 commit 5bbf9d2

3 files changed

Lines changed: 63 additions & 4 deletions

File tree

src/platform/windows/display.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ namespace platf::dxgi {
232232

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

236243
const char *dxgi_format_to_string(DXGI_FORMAT format);
237244
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);

src/platform/windows/display_base.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,27 @@ namespace platf::dxgi {
766766
return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
767767
}
768768

769+
bool display_base_t::is_source_gamma_encoded_fp16() {
770+
dxgi::output6_t output6 {};
771+
772+
auto status = output->QueryInterface(IID_IDXGIOutput6, (void **) &output6);
773+
if (FAILED(status)) {
774+
return false;
775+
}
776+
777+
DXGI_OUTPUT_DESC1 desc1;
778+
output6->GetDesc1(&desc1);
779+
780+
// Windows 11 Auto Color Management (ACM) composes SDR desktops into an
781+
// FP16 framebuffer but the pixel values remain gamma-encoded (G22).
782+
// The "linear" shader path assumes linear scRGB FP16 (G10) and applies
783+
// ApplySRGBCurve, which double-encodes the already-sRGB G22 values,
784+
// crushing highlights to white. Use this method to route G22+FP16 input
785+
// through the non-linear shader path (saturate only, no curve).
786+
return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
787+
|| desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020;
788+
}
789+
769790
bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
770791
dxgi::output6_t output6 {};
771792

src/platform/windows/display_vram.cpp

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -507,15 +507,28 @@ namespace platf::dxgi {
507507
// Semi-planar 8-bit YUV 4:2:0
508508
create_vertex_shader_helper(convert_yuv420_planar_y_vs_hlsl, convert_Y_or_YUV_vs);
509509
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_ps);
510-
create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
510+
if (display->is_source_gamma_encoded_fp16()) {
511+
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding (would cause highlight white-out)
512+
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_fp16_ps);
513+
} else {
514+
create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
515+
}
511516
if (downscaling) {
512517
create_vertex_shader_helper(convert_yuv420_packed_uv_type0s_vs_hlsl, convert_UV_vs);
513518
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_ps);
514-
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps);
519+
if (display->is_source_gamma_encoded_fp16()) {
520+
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_fp16_ps);
521+
} else {
522+
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps);
523+
}
515524
} else {
516525
create_vertex_shader_helper(convert_yuv420_packed_uv_type0_vs_hlsl, convert_UV_vs);
517526
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps);
518-
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps);
527+
if (display->is_source_gamma_encoded_fp16()) {
528+
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_fp16_ps);
529+
} else {
530+
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps);
531+
}
519532
}
520533
break;
521534

@@ -525,6 +538,9 @@ namespace platf::dxgi {
525538
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_ps);
526539
if (display->is_hdr()) {
527540
create_pixel_shader_helper(convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
541+
} else if (display->is_source_gamma_encoded_fp16()) {
542+
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding
543+
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_fp16_ps);
528544
} else {
529545
create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
530546
}
@@ -533,6 +549,8 @@ namespace platf::dxgi {
533549
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_ps);
534550
if (display->is_hdr()) {
535551
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps);
552+
} else if (display->is_source_gamma_encoded_fp16()) {
553+
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_fp16_ps);
536554
} else {
537555
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps);
538556
}
@@ -541,6 +559,8 @@ namespace platf::dxgi {
541559
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps);
542560
if (display->is_hdr()) {
543561
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps);
562+
} else if (display->is_source_gamma_encoded_fp16()) {
563+
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_fp16_ps);
544564
} else {
545565
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps);
546566
}
@@ -553,6 +573,9 @@ namespace platf::dxgi {
553573
create_pixel_shader_helper(convert_yuv444_planar_ps_hlsl, convert_Y_or_YUV_ps);
554574
if (display->is_hdr()) {
555575
create_pixel_shader_helper(convert_yuv444_planar_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
576+
} else if (display->is_source_gamma_encoded_fp16()) {
577+
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding
578+
create_pixel_shader_helper(convert_yuv444_planar_ps_hlsl, convert_Y_or_YUV_fp16_ps);
556579
} else {
557580
create_pixel_shader_helper(convert_yuv444_planar_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
558581
}
@@ -562,7 +585,12 @@ namespace platf::dxgi {
562585
// Packed 8-bit YUV 4:4:4
563586
create_vertex_shader_helper(convert_yuv444_packed_vs_hlsl, convert_Y_or_YUV_vs);
564587
create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_hlsl, convert_Y_or_YUV_ps);
565-
create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
588+
if (display->is_source_gamma_encoded_fp16()) {
589+
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding
590+
create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_hlsl, convert_Y_or_YUV_fp16_ps);
591+
} else {
592+
create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
593+
}
566594
break;
567595

568596
case DXGI_FORMAT_Y410:
@@ -571,6 +599,9 @@ namespace platf::dxgi {
571599
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_hlsl, convert_Y_or_YUV_ps);
572600
if (display->is_hdr()) {
573601
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
602+
} else if (display->is_source_gamma_encoded_fp16()) {
603+
// ACM-SDR FP16: input is already sRGB-encoded; reuse non-linear shader to avoid double sRGB encoding
604+
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_hlsl, convert_Y_or_YUV_fp16_ps);
574605
} else {
575606
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
576607
}

0 commit comments

Comments
 (0)