From 71e03d7883eaf3fb16bfc394779054d5fa91465c Mon Sep 17 00:00:00 2001 From: Ryan Bateman Date: Sun, 1 Nov 2026 20:42:30 -0800 Subject: [PATCH 1/3] fix(encoder): only convert RGB input where the driver sites chroma as the stream implies Streams don't signal a chroma location, so players assume type 0 (cosited left, midway vertically). A driver that couldn't site chroma there got the other offset, shifting color edges without the stream saying so. Now it gets no RGB input, and callers convert instead. RADV supports type 0. --- src/encoder/rgb.rs | 85 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/src/encoder/rgb.rs b/src/encoder/rgb.rs index fe1910bb..a29aee40 100644 --- a/src/encoder/rgb.rs +++ b/src/encoder/rgb.rs @@ -129,23 +129,22 @@ impl RgbConversionCaps { // and a player that is not told assumes chroma sits on the left luma // sample of each pair, and midway between the two rows (H.264 and // H.265 chroma location type 0, which most AV1 players follow as well). - // Asking the hardware for exactly that keeps colour edges where the - // player will put them. A driver that cannot gets the other option. - let pick = |supported: Offset, preferred: Offset| { - if supported.contains(preferred) { - preferred - } else if supported.contains(Offset::COSITED_EVEN) { - Offset::COSITED_EVEN - } else { - Offset::MIDPOINT - } - }; + // The hardware has to produce exactly that: a driver that can only + // site chroma elsewhere would shift color edges without the stream + // saying so, so it gets no RGB input and the caller converts instead. + let (x, y) = (Offset::COSITED_EVEN, Offset::MIDPOINT); + if !self.x_chroma_offsets.contains(x) || !self.y_chroma_offsets.contains(y) { + return Err(PixelForgeError::NoSuitableDevice(format!( + "RGB input: the driver sites chroma at {:?} x {:?}, not {x:?} x {y:?}, which the stream implies", + self.x_chroma_offsets, self.y_chroma_offsets + ))); + } Ok( vk::VideoEncodeSessionRgbConversionCreateInfoVALVE::default() .rgb_model(model) .rgb_range(range) - .x_chroma_offset(pick(self.x_chroma_offsets, Offset::COSITED_EVEN)) - .y_chroma_offset(pick(self.y_chroma_offsets, Offset::MIDPOINT)), + .x_chroma_offset(x) + .y_chroma_offset(y), ) } } @@ -221,3 +220,63 @@ impl VideoContext { true } } + +#[cfg(test)] +mod tests { + use super::*; + use vk::VideoEncodeRgbChromaOffsetFlagsVALVE as Offset; + use vk::VideoEncodeRgbModelConversionFlagsVALVE as Model; + use vk::VideoEncodeRgbRangeCompressionFlagsVALVE as Range; + + /// What RADV reports (Mesa radv_video.c): chroma cosited horizontally, + /// either way vertically. + fn radv() -> RgbConversionCaps { + RgbConversionCaps { + models: Model::YCBCR_709 | Model::YCBCR_2020, + ranges: Range::FULL_RANGE | Range::NARROW_RANGE, + x_chroma_offsets: Offset::COSITED_EVEN, + y_chroma_offsets: Offset::MIDPOINT | Offset::COSITED_EVEN, + } + } + + #[test] + fn sites_chroma_where_the_stream_implies() { + let info = radv().session_info(&ColorDescription::bt709()).unwrap(); + assert_eq!(info.x_chroma_offset, Offset::COSITED_EVEN); + assert_eq!(info.y_chroma_offset, Offset::MIDPOINT); + assert_eq!(info.rgb_model, Model::YCBCR_709); + assert_eq!(info.rgb_range, Range::NARROW_RANGE); + } + + #[test] + fn refuses_other_chroma_siting() { + let midpoint_x = RgbConversionCaps { + x_chroma_offsets: Offset::MIDPOINT, + ..radv() + }; + assert!(midpoint_x.session_info(&ColorDescription::bt709()).is_err()); + let cosited_y = RgbConversionCaps { + y_chroma_offsets: Offset::COSITED_EVEN, + ..radv() + }; + assert!(cosited_y.session_info(&ColorDescription::bt709()).is_err()); + } + + #[test] + fn refuses_an_unsupported_matrix_or_range() { + let no_2020 = RgbConversionCaps { + models: Model::YCBCR_709, + ..radv() + }; + assert!( + no_2020 + .session_info(&ColorDescription::bt2020_pq()) + .is_err() + ); + let full_only = RgbConversionCaps { + ranges: Range::FULL_RANGE, + ..radv() + }; + assert!(full_only.session_info(&ColorDescription::bt709()).is_err()); + } +} From ee449d252083aaba00c8f63d45328366669bdd82 Mon Sep 17 00:00:00 2001 From: Hans Gaiser Date: Wed, 4 Nov 2026 19:13:42 +0100 Subject: [PATCH 2/3] feat(encoder): signal type 0 chroma location in the stream --- src/encoder/av1/session_params.rs | 8 +++++++- src/encoder/h264/session_params.rs | 6 ++++++ src/encoder/h265/session_params.rs | 4 +++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/encoder/av1/session_params.rs b/src/encoder/av1/session_params.rs index 2e1448b6..5034d68e 100644 --- a/src/encoder/av1/session_params.rs +++ b/src/encoder/av1/session_params.rs @@ -69,7 +69,13 @@ impl Av1 { color_primaries: av1_color_primaries, transfer_characteristics: av1_transfer, matrix_coefficients: av1_matrix, - chroma_sample_position: ash::vk::native::StdVideoAV1ChromaSamplePosition_STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_UNKNOWN, + // 4:2:0 chroma sits on the left luma sample, midway between the + // two rows. Only 4:2:0 writes a position at all. + chroma_sample_position: if subsampling_x == 1 && subsampling_y == 1 { + ash::vk::native::StdVideoAV1ChromaSamplePosition_STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_VERTICAL + } else { + ash::vk::native::StdVideoAV1ChromaSamplePosition_STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_UNKNOWN + }, }; let profile = match config.pixel_format { diff --git a/src/encoder/h264/session_params.rs b/src/encoder/h264/session_params.rs index d20ccd75..01aae437 100644 --- a/src/encoder/h264/session_params.rs +++ b/src/encoder/h264/session_params.rs @@ -75,6 +75,12 @@ impl H264 { vui_flags.set_video_signal_type_present_flag(1); vui_flags.set_video_full_range_flag(if desc.full_range { 1 } else { 0 }); vui_flags.set_color_description_present_flag(1); + // 4:2:0 chroma sits on the left luma sample, midway between the two + // rows (chroma_sample_loc_type 0), and the stream says so. + vui_flags + .set_chroma_loc_info_present_flag( + matches!(config.pixel_format, PixelFormat::Yuv420) as u32 + ); // Avoid HRD with zeroed bitrates (device loss on some drivers). vui_flags.set_nal_hrd_parameters_present_flag(0); vui_flags.set_bitstream_restriction_flag(1); diff --git a/src/encoder/h265/session_params.rs b/src/encoder/h265/session_params.rs index 4deca2fd..e6f8cabb 100644 --- a/src/encoder/h265/session_params.rs +++ b/src/encoder/h265/session_params.rs @@ -128,7 +128,9 @@ impl H265 { 1, if desc.full_range { 1 } else { 0 }, 1, - 0, + // chroma_loc_info_present_flag: 4:2:0 chroma sits on the left + // luma sample, midway between the two rows (type 0). + matches!(config.pixel_format, PixelFormat::Yuv420) as u32, 0, 0, 0, From 75de03bbc9fe554a21b46ad51953f2d530fd7d6c Mon Sep 17 00:00:00 2001 From: Hans Gaiser Date: Wed, 4 Nov 2026 19:17:14 +0100 Subject: [PATCH 3/3] fix(shader): site 4:2:0 chroma at type 0 --- shader/color_convert.comp | 43 ++++++++++++++++++++------------------ shader/color_convert.spv | Bin 25968 -> 33156 bytes tests/color_conversion.rs | 2 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/shader/color_convert.comp b/shader/color_convert.comp index c9f2c928..c425de04 100644 --- a/shader/color_convert.comp +++ b/shader/color_convert.comp @@ -169,6 +169,23 @@ uint q10_c(float c) { return (val << 6u) & 0xFFC0u; } +// 4:2:0 chroma for the block whose top-left sample is (x, y), sited where a +// player puts it when the stream says nothing else: on the left luma column, +// midway between the two rows (chroma_sample_loc_type 0). That is a [1 2 1] +// filter centred on column x, averaged over both rows. A sample past an edge +// repeats the edge. `yuv` is the sample at (x, y), already converted. +vec2 chroma_420(uint x, uint y, vec3 yuv) { + uint xl = x > 0u ? x - 1u : x; + uint xr = min(x + 1u, params.width - 1u); + uint yb = min(y + 1u, params.height - 1u); + vec2 top = rgb_to_yuv(read_rgb(ivec2(xl, y))).yz + 2.0 * yuv.yz + + rgb_to_yuv(read_rgb(ivec2(xr, y))).yz; + vec2 bottom = rgb_to_yuv(read_rgb(ivec2(xl, yb))).yz + + 2.0 * rgb_to_yuv(read_rgb(ivec2(x, yb))).yz + + rgb_to_yuv(read_rgb(ivec2(xr, yb))).yz; + return (top + bottom) / 8.0; +} + void main() { uint x = gl_GlobalInvocationID.x; uint y = gl_GlobalInvocationID.y; @@ -222,16 +239,9 @@ void main() { uint uv_width = params.width / 2u; uint uv_idx = uv_y * uv_width + uv_x; - vec3 yuv00 = yuv; - vec3 yuv10 = (x + 1u < params.width) ? - rgb_to_yuv(read_rgb(ivec2(x + 1u, y))) : yuv00; - vec3 yuv01 = (y + 1u < params.height) ? - rgb_to_yuv(read_rgb(ivec2(x, y + 1u))) : yuv00; - vec3 yuv11 = (x + 1u < params.width && y + 1u < params.height) ? - rgb_to_yuv(read_rgb(ivec2(x + 1u, y + 1u))) : yuv00; - - float avg_u = (yuv00.y + yuv10.y + yuv01.y + yuv11.y) / 4.0; - float avg_v = (yuv00.z + yuv10.z + yuv01.z + yuv11.z) / 4.0; + vec2 uv = chroma_420(x, y, yuv); + float avg_u = uv.x; + float avg_v = uv.y; uint uv_base_words = pixel_count / 2u; uint uv_word_idx = uv_base_words + uv_idx; @@ -251,16 +261,9 @@ void main() { uint uv_width = params.width / 2u; uint uv_idx = uv_y * uv_width + uv_x; - vec3 yuv00 = yuv; - vec3 yuv10 = (x + 1u < params.width) ? - rgb_to_yuv(read_rgb(ivec2(x + 1u, y))) : yuv00; - vec3 yuv01 = (y + 1u < params.height) ? - rgb_to_yuv(read_rgb(ivec2(x, y + 1u))) : yuv00; - vec3 yuv11 = (x + 1u < params.width && y + 1u < params.height) ? - rgb_to_yuv(read_rgb(ivec2(x + 1u, y + 1u))) : yuv00; - - float avg_u = (yuv00.y + yuv10.y + yuv01.y + yuv11.y) / 4.0; - float avg_v = (yuv00.z + yuv10.z + yuv01.z + yuv11.z) / 4.0; + vec2 uv = chroma_420(x, y, yuv); + float avg_u = uv.x; + float avg_v = uv.y; if (params.output_format == 0u) { // NV12: Interleaved UV after Y plane. diff --git a/shader/color_convert.spv b/shader/color_convert.spv index 6abab7f8d3e3d6f68c7a291ef25c52825d753979..ef2895e7c92851511fef6966d45cda245e44777c 100644 GIT binary patch literal 33156 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 Jn2q&l{|5siPOty~ diff --git a/tests/color_conversion.rs b/tests/color_conversion.rs index 3cea06f6..8069164f 100644 --- a/tests/color_conversion.rs +++ b/tests/color_conversion.rs @@ -14,7 +14,7 @@ //! target rather than from the shader's branches, so a wrong branch shows up as //! a large disagreement. //! -//! Luma only, since chroma is averaged over 2x2 blocks and that averaging would +//! Luma only, since chroma is filtered over neighbouring pixels and that filter would //! muddy the comparison. The test frame is deliberately not grey: grey converts //! to exact values and would hide the very errors being looked for. //!