use crate::model::capabilities::{ModeSink, RefreshRate, StereoMode, SyncDefinition, VideoMode};
pub(super) fn decode_type_i_descriptor(d: &[u8; 20], sink: &mut dyn ModeSink) {
if let Some(mode) = decode_type_1_7_descriptor_body(d, 10) {
sink.push_mode(mode);
}
}
fn decode_type_1_7_descriptor_body(d: &[u8; 20], pixel_clock_unit_khz: u32) -> Option<VideoMode> {
let pixel_clock_raw = u32::from(d[0]) | (u32::from(d[1]) << 8) | (u32::from(d[2]) << 16);
if pixel_clock_raw == 0 {
return None; }
let pixel_clock_khz = (pixel_clock_raw + 1) * pixel_clock_unit_khz;
let options = d[3];
let interlaced = (options & 0x10) != 0;
let h_active = u16::from_le_bytes([d[4], d[5]]).checked_add(1)?;
let h_blank = u16::from_le_bytes([d[6], d[7]]).checked_add(1)?;
let h_fp_word = u16::from_le_bytes([d[8], d[9]]);
let h_front_porch = (h_fp_word & 0x7FFF) + 1;
let h_sync_positive = (h_fp_word >> 15) != 0;
let h_sync_width = u16::from_le_bytes([d[10], d[11]]).checked_add(1)?;
let v_active = u16::from_le_bytes([d[12], d[13]]).checked_add(1)?;
let v_blank = u16::from_le_bytes([d[14], d[15]]).checked_add(1)?;
let v_fp_word = u16::from_le_bytes([d[16], d[17]]);
let v_front_porch = (v_fp_word & 0x7FFF) + 1;
let v_sync_positive = (v_fp_word >> 15) != 0;
let v_sync_width = u16::from_le_bytes([d[18], d[19]]).checked_add(1)?;
let h_total = u64::from(h_active) + u64::from(h_blank);
let v_total = u64::from(v_active) + u64::from(v_blank);
let total_pixels = h_total * v_total;
if total_pixels == 0 {
return None;
}
let pixel_clock_hz = u64::from(pixel_clock_khz) * 1000;
let refresh_rate = RefreshRate::from_ratio(pixel_clock_hz, total_pixels)?;
Some(
VideoMode::new(h_active, v_active, refresh_rate, interlaced).with_detailed_timing(
pixel_clock_khz,
h_front_porch,
h_sync_width,
v_front_porch,
v_sync_width,
0,
0,
StereoMode::None,
Some(SyncDefinition::DigitalSeparate {
v_sync_positive,
h_sync_positive,
}),
),
)
}
pub(super) fn decode_type_ii_descriptor(d: &[u8; 11], sink: &mut dyn ModeSink) {
let raw_pixel_clock = (d[0] as u32) | ((d[1] as u32) << 8) | ((d[2] as u32) << 16);
let pixel_clock_10khz = 1u64 + raw_pixel_clock as u64;
let flags = d[3];
let interlaced = (flags & 0x10) != 0;
let h_sync_positive = (flags & 0x08) != 0;
let v_sync_positive = (flags & 0x04) != 0;
let ha_raw = (d[4] as u16) | (((d[5] & 0x01) as u16) << 8);
let h_active = 8u16 + 8 * ha_raw;
let hb_raw = ((d[5] >> 1) & 0x7F) as u16;
let h_blank = 8u16 + 8 * hb_raw;
let h_front_porch = 8u16 + 8 * ((d[6] >> 4) as u16);
let h_sync_width = 8u16 + 8 * ((d[6] & 0x0F) as u16);
let va_raw = (d[7] as u16) | (((d[8] & 0x0F) as u16) << 8);
let v_active = 1u16 + va_raw;
let v_blank = 1u16 + d[9] as u16;
let v_front_porch = 1u16 + ((d[9] >> 4) as u16);
let v_sync_width = 1u16 + ((d[9] & 0x0F) as u16);
let h_total = h_active as u64 + h_blank as u64;
let v_total = v_active as u64 + v_blank as u64;
if h_total == 0 || v_total == 0 {
return;
}
let pixel_clock_hz = pixel_clock_10khz * 10_000;
let Some(refresh_rate) = RefreshRate::from_ratio(pixel_clock_hz, h_total * v_total) else {
return;
};
sink.push_mode(
VideoMode::new(h_active, v_active, refresh_rate, interlaced).with_detailed_timing(
(pixel_clock_10khz * 10) as u32,
h_front_porch,
h_sync_width,
v_front_porch,
v_sync_width,
0,
0,
StereoMode::None,
Some(SyncDefinition::DigitalSeparate {
v_sync_positive,
h_sync_positive,
}),
),
);
}
pub(crate) fn decode_type_vii_descriptor_to_mode(d: &[u8; 20], revision: u8) -> Option<VideoMode> {
let mode = decode_type_1_7_descriptor_body(d, 1)?;
if revision >= 2 && (d[3] & 0x80) != 0 {
Some(mode.with_y420(true))
} else {
Some(mode)
}
}
pub(super) fn decode_type_vii_descriptor(d: &[u8; 20], revision: u8, sink: &mut dyn ModeSink) {
if let Some(mode) = decode_type_vii_descriptor_to_mode(d, revision) {
sink.push_mode(mode);
}
}
pub(super) fn decode_type_vi_descriptor(d: &[u8], sink: &mut dyn ModeSink) -> usize {
if d.len() < 14 {
return 0;
}
let raw = (d[0] as u32) | ((d[1] as u32) << 8) | ((d[2] as u32) << 16);
let pixel_clock_khz = raw & 0x003F_FFFF; let has_aspect = (raw >> 22) & 1 != 0;
let descriptor_size = if has_aspect { 17 } else { 14 };
if pixel_clock_khz == 0 {
return descriptor_size;
}
let h_word = u16::from_le_bytes([d[3], d[4]]);
let h_active = h_word & 0x7FFF;
let h_sync_positive = (h_word >> 15) != 0;
let v_word = u16::from_le_bytes([d[5], d[6]]);
let v_active = v_word & 0x7FFF;
let v_sync_positive = (v_word >> 15) != 0;
let h_blank = (d[7] as u16) | (((d[9] & 0x0F) as u16) << 8);
let h_fp = (d[8] as u16) | (((d[9] >> 4) as u16) << 8);
let h_sync_width = d[10] as u16;
let v_blank = d[11] as u16;
let v_fp = d[12] as u16;
let v_sync_width = (d[13] & 0x0F) as u16;
let interlaced = (d[13] >> 7) != 0;
let h_total = h_active as u64 + h_blank as u64;
let v_total = v_active as u64 + v_blank as u64;
if h_total == 0 || v_total == 0 {
return descriptor_size;
}
let pixel_clock_hz = pixel_clock_khz as u64 * 1000;
let Some(refresh_rate) = RefreshRate::from_ratio(pixel_clock_hz, h_total * v_total) else {
return descriptor_size;
};
sink.push_mode(
VideoMode::new(h_active, v_active, refresh_rate, interlaced).with_detailed_timing(
pixel_clock_khz,
h_fp,
h_sync_width,
v_fp,
v_sync_width,
0,
0,
StereoMode::None,
Some(SyncDefinition::DigitalSeparate {
h_sync_positive,
v_sync_positive,
}),
),
);
descriptor_size
}
#[cfg(test)]
#[cfg(any(feature = "alloc", feature = "std"))]
mod tests {
use super::*;
use crate::model::capabilities::{DisplayCapabilities, RefreshRate, SyncDefinition};
#[allow(clippy::too_many_arguments)]
fn make_type_i_descriptor(
pixel_clock_10khz: u32,
h_active: u16,
h_blank: u16,
h_fp: u16,
h_sw: u16,
v_active: u16,
v_blank: u16,
v_fp: u16,
v_sw: u16,
options_byte3: u8,
) -> [u8; 20] {
let mut d = [0u8; 20];
let raw_pclk = pixel_clock_10khz - 1;
d[0] = (raw_pclk & 0xFF) as u8;
d[1] = ((raw_pclk >> 8) & 0xFF) as u8;
d[2] = ((raw_pclk >> 16) & 0xFF) as u8;
d[3] = options_byte3;
d[4..6].copy_from_slice(&(h_active - 1).to_le_bytes());
d[6..8].copy_from_slice(&(h_blank - 1).to_le_bytes());
d[8..10].copy_from_slice(&(h_fp - 1).to_le_bytes());
d[10..12].copy_from_slice(&(h_sw - 1).to_le_bytes());
d[12..14].copy_from_slice(&(v_active - 1).to_le_bytes());
d[14..16].copy_from_slice(&(v_blank - 1).to_le_bytes());
d[16..18].copy_from_slice(&(v_fp - 1).to_le_bytes());
d[18..20].copy_from_slice(&(v_sw - 1).to_le_bytes());
d
}
#[allow(clippy::too_many_arguments)]
fn make_type_ii_descriptor(
pixel_clock_10khz: u32,
ha_raw: u16,
hb_raw: u8,
hfp_raw: u8,
hsw_raw: u8,
va_raw: u16,
v_blank_byte: u8,
flags: u8,
) -> [u8; 11] {
let mut d = [0u8; 11];
d[0] = (pixel_clock_10khz & 0xFF) as u8;
d[1] = ((pixel_clock_10khz >> 8) & 0xFF) as u8;
d[2] = ((pixel_clock_10khz >> 16) & 0xFF) as u8;
d[3] = flags;
d[4] = (ha_raw & 0xFF) as u8;
d[5] = (((ha_raw >> 8) & 0x01) as u8) | ((hb_raw & 0x7F) << 1);
d[6] = ((hfp_raw & 0x0F) << 4) | (hsw_raw & 0x0F);
d[7] = (va_raw & 0xFF) as u8;
d[8] = ((va_raw >> 8) & 0x0F) as u8;
d[9] = v_blank_byte;
d[10] = 0x00;
d
}
#[test]
fn test_type_i_timing_decoded() {
let d = make_type_i_descriptor(14850, 1920, 280, 88, 44, 1080, 45, 4, 5, 0x00);
let mut caps = DisplayCapabilities::default();
decode_type_i_descriptor(&d, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
let mode = &caps.supported_modes[0];
assert_eq!(mode.width, 1920);
assert_eq!(mode.height, 1080);
assert_eq!(mode.refresh_rate, Some(RefreshRate::integral(60)));
assert!(!mode.interlaced);
}
#[test]
fn test_type_i_null_descriptor_skipped() {
let null_descriptor = [0u8; 20];
let mut caps = DisplayCapabilities::default();
decode_type_i_descriptor(&null_descriptor, &mut caps);
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_type_i_interlaced_flag_decoded() {
let d = make_type_i_descriptor(14850, 1920, 280, 88, 44, 1080, 45, 4, 5, 0x10);
let mut caps = DisplayCapabilities::default();
decode_type_i_descriptor(&d, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert!(caps.supported_modes[0].interlaced);
}
#[test]
fn test_type_i_sync_polarity_decoded_from_fp_high_bit() {
let mut d = make_type_i_descriptor(14850, 1920, 280, 88, 44, 1080, 45, 4, 5, 0x00);
d[9] |= 0x80; d[17] |= 0x80; let mut caps = DisplayCapabilities::default();
decode_type_i_descriptor(&d, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(
caps.supported_modes[0].sync,
Some(SyncDefinition::DigitalSeparate {
h_sync_positive: true,
v_sync_positive: true,
})
);
assert_eq!(caps.supported_modes[0].h_front_porch, 88);
assert_eq!(caps.supported_modes[0].v_front_porch, 4);
}
#[test]
fn test_type_i_negative_polarities_default() {
let d = make_type_i_descriptor(14850, 1920, 280, 88, 44, 1080, 45, 4, 5, 0x00);
let mut caps = DisplayCapabilities::default();
decode_type_i_descriptor(&d, &mut caps);
assert_eq!(
caps.supported_modes[0].sync,
Some(SyncDefinition::DigitalSeparate {
h_sync_positive: false,
v_sync_positive: false,
})
);
}
#[test]
fn test_type_ii_timing_decoded() {
let d = make_type_ii_descriptor(14849, 239, 34, 10, 5, 1079, 0x2C, 0x0C);
let mut caps = DisplayCapabilities::default();
decode_type_ii_descriptor(&d, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
let mode = &caps.supported_modes[0];
assert_eq!(mode.width, 1920);
assert_eq!(mode.height, 1080);
assert_eq!(mode.refresh_rate, Some(RefreshRate::integral(60)));
assert_eq!(mode.h_front_porch, 88);
assert_eq!(mode.h_sync_width, 48);
assert_eq!(mode.v_front_porch, 3);
assert_eq!(mode.v_sync_width, 13);
assert!(!mode.interlaced);
assert_eq!(
mode.sync,
Some(SyncDefinition::DigitalSeparate {
h_sync_positive: true,
v_sync_positive: true,
})
);
}
#[test]
fn test_type_ii_interlaced_flag() {
let d = make_type_ii_descriptor(14849, 239, 34, 10, 5, 1079, 0x2C, 0x10);
let mut caps = DisplayCapabilities::default();
decode_type_ii_descriptor(&d, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert!(caps.supported_modes[0].interlaced);
}
#[test]
fn test_type_ii_multiple_descriptors() {
let desc1 = make_type_ii_descriptor(14849, 239, 34, 10, 5, 1079, 0x2C, 0x0C);
let desc2 = make_type_ii_descriptor(26819, 319, 54, 10, 4, 1439, 0x31, 0x0C);
let mut caps = DisplayCapabilities::default();
decode_type_ii_descriptor(&desc1, &mut caps);
decode_type_ii_descriptor(&desc2, &mut caps);
assert_eq!(caps.supported_modes.len(), 2);
assert!(
caps.supported_modes
.iter()
.any(|m| m.width == 1920 && m.height == 1080)
);
assert!(
caps.supported_modes
.iter()
.any(|m| m.width == 2560 && m.height == 1440)
);
}
#[allow(clippy::too_many_arguments)]
fn make_type_vi_descriptor(
pixel_clock_khz: u32,
h_active: u16,
h_sync_positive: bool,
v_active: u16,
v_sync_positive: bool,
h_blank: u16,
h_fp: u16,
h_sync_width: u8,
v_blank: u8,
v_fp: u8,
v_sync_width: u8,
interlaced: bool,
) -> [u8; 14] {
let mut d = [0u8; 14];
let pc = pixel_clock_khz & 0x3F_FFFF;
d[0] = (pc & 0xFF) as u8;
d[1] = ((pc >> 8) & 0xFF) as u8;
d[2] = ((pc >> 16) & 0x3F) as u8; let h_word = (h_active & 0x7FFF) | ((h_sync_positive as u16) << 15);
d[3..5].copy_from_slice(&h_word.to_le_bytes());
let v_word = (v_active & 0x7FFF) | ((v_sync_positive as u16) << 15);
d[5..7].copy_from_slice(&v_word.to_le_bytes());
d[7] = (h_blank & 0xFF) as u8;
d[8] = (h_fp & 0xFF) as u8;
d[9] = ((h_blank >> 8) & 0x0F) as u8 | (((h_fp >> 8) & 0x0F) << 4) as u8;
d[10] = h_sync_width;
d[11] = v_blank;
d[12] = v_fp;
d[13] = (v_sync_width & 0x0F) | ((interlaced as u8) << 7);
d
}
#[test]
fn test_type_vi_1920x1080_decoded() {
let d = make_type_vi_descriptor(
148_500, 1920, true, 1080, true, 280, 88, 44, 45, 4, 5, false,
);
let mut caps = DisplayCapabilities::default();
let consumed = decode_type_vi_descriptor(&d, &mut caps);
assert_eq!(consumed, 14);
assert_eq!(caps.supported_modes.len(), 1);
let mode = &caps.supported_modes[0];
assert_eq!(mode.width, 1920);
assert_eq!(mode.height, 1080);
assert_eq!(mode.refresh_rate, Some(RefreshRate::integral(60)));
assert!(!mode.interlaced);
assert_eq!(mode.h_front_porch, 88);
assert_eq!(mode.h_sync_width, 44);
assert_eq!(mode.v_front_porch, 4);
assert_eq!(mode.v_sync_width, 5);
assert_eq!(
mode.sync,
Some(SyncDefinition::DigitalSeparate {
h_sync_positive: true,
v_sync_positive: true,
})
);
}
#[test]
fn test_type_vi_interlaced_flag() {
let d =
make_type_vi_descriptor(148_500, 1920, true, 1080, true, 280, 88, 44, 45, 4, 5, true);
let mut caps = DisplayCapabilities::default();
decode_type_vi_descriptor(&d, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert!(caps.supported_modes[0].interlaced);
}
#[test]
fn test_type_vi_null_descriptor_skipped() {
let d = [0u8; 14];
let mut caps = DisplayCapabilities::default();
let consumed = decode_type_vi_descriptor(&d, &mut caps);
assert_eq!(consumed, 14);
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_type_vi_with_aspect_bytes_returns_17() {
let mut d = [0u8; 17];
let pc = 148_500u32;
d[0] = (pc & 0xFF) as u8;
d[1] = ((pc >> 8) & 0xFF) as u8;
d[2] = (((pc >> 16) & 0x3F) | 0x40) as u8; let h_word: u16 = 1920;
d[3..5].copy_from_slice(&h_word.to_le_bytes());
let v_word: u16 = 1080;
d[5..7].copy_from_slice(&v_word.to_le_bytes());
d[7] = 280u16 as u8;
d[9] = ((280u16 >> 8) & 0x0F) as u8;
d[11] = 45; let mut caps = DisplayCapabilities::default();
let consumed = decode_type_vi_descriptor(&d, &mut caps);
assert_eq!(consumed, 17);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 1920);
}
#[test]
fn test_type_vi_too_short_returns_zero() {
let d = [0u8; 13]; let mut caps = DisplayCapabilities::default();
let consumed = decode_type_vi_descriptor(&d, &mut caps);
assert_eq!(consumed, 0);
assert!(caps.supported_modes.is_empty());
}
#[allow(clippy::too_many_arguments)]
fn make_type_vii_descriptor(
pixel_clock_khz: u32,
h_active: u16,
h_blank: u16,
h_fp: u16,
h_sync_positive: bool,
h_sw: u16,
v_active: u16,
v_blank: u16,
v_fp: u16,
v_sync_positive: bool,
v_sw: u16,
interlaced: bool,
) -> [u8; 20] {
let mut d = [0u8; 20];
let raw_pclk = pixel_clock_khz - 1;
d[0] = (raw_pclk & 0xFF) as u8;
d[1] = ((raw_pclk >> 8) & 0xFF) as u8;
d[2] = ((raw_pclk >> 16) & 0xFF) as u8;
d[3] = (interlaced as u8) << 4;
d[4..6].copy_from_slice(&(h_active - 1).to_le_bytes());
d[6..8].copy_from_slice(&(h_blank - 1).to_le_bytes());
let h_fp_word = ((h_fp - 1) & 0x7FFF) | ((h_sync_positive as u16) << 15);
d[8..10].copy_from_slice(&h_fp_word.to_le_bytes());
d[10..12].copy_from_slice(&(h_sw - 1).to_le_bytes());
d[12..14].copy_from_slice(&(v_active - 1).to_le_bytes());
d[14..16].copy_from_slice(&(v_blank - 1).to_le_bytes());
let v_fp_word = ((v_fp - 1) & 0x7FFF) | ((v_sync_positive as u16) << 15);
d[16..18].copy_from_slice(&v_fp_word.to_le_bytes());
d[18..20].copy_from_slice(&(v_sw - 1).to_le_bytes());
d
}
#[test]
fn test_type_vii_1080p60_decoded() {
let d = make_type_vii_descriptor(
148_500, 1920, 280, 88, true, 44, 1080, 45, 4, true, 5, false,
);
let mut caps = DisplayCapabilities::default();
decode_type_vii_descriptor(&d, 0, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
let mode = &caps.supported_modes[0];
assert_eq!(mode.width, 1920);
assert_eq!(mode.height, 1080);
assert_eq!(mode.refresh_rate, Some(RefreshRate::integral(60)));
assert!(!mode.interlaced);
assert_eq!(mode.h_front_porch, 88);
assert_eq!(mode.h_sync_width, 44);
assert_eq!(mode.v_front_porch, 4);
assert_eq!(mode.v_sync_width, 5);
assert_eq!(
mode.sync,
Some(SyncDefinition::DigitalSeparate {
h_sync_positive: true,
v_sync_positive: true,
})
);
}
#[test]
fn test_type_vii_rational_rate_preserved() {
let d = make_type_vii_descriptor(120_120, 1000, 1, 1, false, 1, 1, 1, 1, false, 1, false);
let mut caps = DisplayCapabilities::default();
decode_type_vii_descriptor(&d, 0, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(
caps.supported_modes[0].refresh_rate,
Some(RefreshRate::integral(60_000))
);
}
#[test]
fn test_type_vii_polarity_negative() {
let d = make_type_vii_descriptor(
148_500, 1920, 280, 88, false, 44, 1080, 45, 4, false, 5, false,
);
let mut caps = DisplayCapabilities::default();
decode_type_vii_descriptor(&d, 0, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(
caps.supported_modes[0].sync,
Some(SyncDefinition::DigitalSeparate {
h_sync_positive: false,
v_sync_positive: false,
})
);
}
#[test]
fn test_type_vii_interlaced_flag() {
let d =
make_type_vii_descriptor(148_500, 1920, 280, 88, true, 44, 1080, 45, 4, true, 5, true);
let mut caps = DisplayCapabilities::default();
decode_type_vii_descriptor(&d, 0, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert!(caps.supported_modes[0].interlaced);
}
#[test]
fn test_type_vii_null_descriptor_skipped() {
let d = [0u8; 20];
let mut caps = DisplayCapabilities::default();
decode_type_vii_descriptor(&d, 0, &mut caps);
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_type_vii_h_active_overflow_skipped() {
let mut d = make_type_vii_descriptor(
148_500, 1920, 280, 88, true, 44, 1080, 45, 4, true, 5, false,
);
d[4] = 0xFF;
d[5] = 0xFF;
let mut caps = DisplayCapabilities::default();
decode_type_vii_descriptor(&d, 0, &mut caps);
assert!(caps.supported_modes.is_empty());
}
}