use crate::capabilities::cea861::{dmt_to_mode, vic_to_mode};
use crate::model::capabilities::{ModeSink, VideoMode};
fn resolve_coded_timing(code_type: u8, code: u16) -> Option<VideoMode> {
match code_type {
0 => dmt_to_mode(code),
1 => u8::try_from(code).ok().and_then(vic_to_mode),
2 => u8::try_from(code).ok().and_then(hdmi_vic_to_mode),
_ => None,
}
}
pub(super) fn decode_type_iv_block(payload: &[u8], code_type: u8, sink: &mut dyn ModeSink) {
for &code in payload {
if let Some(m) = resolve_coded_timing(code_type, code as u16) {
sink.push_mode(m);
}
}
}
pub(super) fn decode_type_viii_block(payload: &[u8], revision: u8, sink: &mut dyn ModeSink) {
let code_type = (revision >> 6) & 0x03;
let two_byte = (revision >> 3) & 1 != 0;
let stride = if two_byte { 2 } else { 1 };
let mut i = 0;
while i + stride <= payload.len() {
let code = if two_byte {
u16::from_le_bytes([payload[i], payload[i + 1]])
} else {
payload[i] as u16
};
if let Some(m) = resolve_coded_timing(code_type, code) {
sink.push_mode(m);
}
i += stride;
}
}
fn hdmi_vic_to_mode(code: u8) -> Option<VideoMode> {
let (w, h, r): (u16, u16, u16) = match code {
1 => (3840, 2160, 30),
2 => (3840, 2160, 25),
3 => (3840, 2160, 24),
4 => (4096, 2160, 24),
_ => return None,
};
Some(VideoMode::new(w, h, r, false))
}
pub(super) fn decode_vesa_video_timing_block(payload: &[u8], sink: &mut dyn ModeSink) {
for (byte_idx, &byte) in payload.iter().take(10).enumerate() {
for bit in 0u16..8 {
if byte & (1 << bit) != 0 {
let dmt_id = (byte_idx as u16) * 8 + bit + 1;
if let Some(mode) = dmt_to_mode(dmt_id) {
sink.push_mode(mode);
}
}
}
}
}
pub(super) fn decode_cta_video_timing_block(payload: &[u8], sink: &mut dyn ModeSink) {
for (byte_idx, &byte) in payload.iter().take(8).enumerate() {
for bit in 0u8..8 {
if byte & (1 << bit) != 0 {
let vic = (byte_idx as u8) * 8 + bit + 1;
if let Some(mode) = vic_to_mode(vic) {
sink.push_mode(mode);
}
}
}
}
}
#[cfg(test)]
#[cfg(any(feature = "alloc", feature = "std"))]
mod tests {
use super::*;
use crate::model::capabilities::{DisplayCapabilities, RefreshRate, SyncDefinition};
#[test]
fn test_type_iv_dmt_decoded() {
let mut caps = DisplayCapabilities::default();
decode_type_iv_block(&[0x52], 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_iv_dmt_interlaced() {
let mut caps = DisplayCapabilities::default();
decode_type_iv_block(&[0x0F], 0, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
let mode = &caps.supported_modes[0];
assert_eq!(mode.width, 1024);
assert_eq!(mode.height, 768);
assert_eq!(mode.refresh_rate, Some(RefreshRate::integral(43)));
assert!(mode.interlaced);
}
#[test]
fn test_type_iv_vic_decoded() {
let mut caps = DisplayCapabilities::default();
decode_type_iv_block(&[1], 1, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 640);
assert_eq!(caps.supported_modes[0].height, 480);
assert_eq!(
caps.supported_modes[0].refresh_rate,
Some(RefreshRate::integral(60))
);
}
#[test]
fn test_type_iv_hdmi_vic_decoded() {
let mut caps = DisplayCapabilities::default();
decode_type_iv_block(&[1], 2, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 3840);
assert_eq!(caps.supported_modes[0].height, 2160);
assert_eq!(
caps.supported_modes[0].refresh_rate,
Some(RefreshRate::integral(30))
);
}
#[test]
fn test_type_iv_unknown_dmt_skipped() {
let mut caps = DisplayCapabilities::default();
decode_type_iv_block(&[0xFF], 0, &mut caps);
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_type_iv_multiple_codes() {
let mut caps = DisplayCapabilities::default();
decode_type_iv_block(&[0x10, 0x23, 0x52], 0, &mut caps);
assert_eq!(caps.supported_modes.len(), 3);
assert!(
caps.supported_modes
.iter()
.any(|m| m.width == 1024 && m.height == 768)
);
assert!(
caps.supported_modes
.iter()
.any(|m| m.width == 1280 && m.height == 1024)
);
assert!(
caps.supported_modes
.iter()
.any(|m| m.width == 1920 && m.height == 1080)
);
}
#[test]
fn test_type_iv_mixed_known_unknown_dmt_codes() {
let mut caps = DisplayCapabilities::default();
decode_type_iv_block(&[0xFE, 0x23, 0xFF], 0, &mut caps); assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 1280);
assert_eq!(caps.supported_modes[0].height, 1024);
assert_eq!(
caps.supported_modes[0].refresh_rate,
Some(RefreshRate::integral(60))
);
}
#[test]
fn test_type_iv_reserved_code_type_skipped() {
let mut caps = DisplayCapabilities::default();
decode_type_iv_block(&[0x52], 3, &mut caps);
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_vesa_timing_single_bit_set() {
let mut caps = DisplayCapabilities::default();
decode_vesa_video_timing_block(&[0b0000_1000], &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
let mode = &caps.supported_modes[0];
assert_eq!(mode.width, 640);
assert_eq!(mode.height, 480);
assert_eq!(mode.refresh_rate, Some(RefreshRate::integral(60)));
assert_eq!(mode.h_front_porch, 16);
assert_eq!(mode.h_sync_width, 96);
assert_eq!(mode.v_front_porch, 10);
assert_eq!(mode.v_sync_width, 2);
assert_eq!(
mode.sync,
Some(SyncDefinition::DigitalSeparate {
h_sync_positive: false,
v_sync_positive: false,
})
);
}
#[test]
fn test_vesa_timing_multiple_modes() {
let mut caps = DisplayCapabilities::default();
decode_vesa_video_timing_block(&[0x01, 0x01], &mut caps);
assert_eq!(caps.supported_modes.len(), 2);
assert!(
caps.supported_modes
.iter()
.any(|m| m.width == 640 && m.height == 350)
);
assert!(
caps.supported_modes
.iter()
.any(|m| m.width == 800 && m.height == 600)
);
}
#[test]
fn test_vesa_timing_empty_payload() {
let mut caps = DisplayCapabilities::default();
decode_vesa_video_timing_block(&[], &mut caps);
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_vesa_timing_payload_truncated_at_10_bytes() {
let mut bitmap = vec![0u8; 11];
bitmap[10] = 0xFF; bitmap[0] = 0x01; let mut caps = DisplayCapabilities::default();
decode_vesa_video_timing_block(&bitmap, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 640);
assert_eq!(caps.supported_modes[0].height, 350);
}
#[test]
fn test_cta_timing_single_bit_set() {
let mut caps = DisplayCapabilities::default();
decode_cta_video_timing_block(&[0x01], &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
let mode = &caps.supported_modes[0];
assert_eq!(mode.width, 640);
assert_eq!(mode.height, 480);
assert_eq!(mode.refresh_rate, Some(RefreshRate::integral(60)));
assert!(mode.h_front_porch != 0 || mode.h_sync_width != 0);
}
#[test]
fn test_cta_timing_multiple_modes() {
let mut caps = DisplayCapabilities::default();
decode_cta_video_timing_block(&[0x03], &mut caps);
assert_eq!(caps.supported_modes.len(), 2);
}
#[test]
fn test_cta_timing_empty_payload() {
let mut caps = DisplayCapabilities::default();
decode_cta_video_timing_block(&[], &mut caps);
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_cta_timing_payload_truncated_at_8_bytes() {
let mut bitmap = vec![0u8; 9];
bitmap[8] = 0xFF; bitmap[0] = 0x01; let mut caps = DisplayCapabilities::default();
decode_cta_video_timing_block(&bitmap, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 640);
}
#[test]
fn test_type_viii_dmt_one_byte() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0x52], 0x00, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 1920);
assert_eq!(caps.supported_modes[0].height, 1080);
}
#[test]
fn test_type_viii_dmt_two_byte() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0x52, 0x00], 0x08, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 1920);
assert_eq!(caps.supported_modes[0].height, 1080);
}
#[test]
fn test_type_viii_vic_one_byte() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0x01], 0x40, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 640);
assert_eq!(caps.supported_modes[0].height, 480);
}
#[test]
fn test_type_viii_hdmi_vic() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0x01], 0x80, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 3840);
assert_eq!(
caps.supported_modes[0].refresh_rate,
Some(RefreshRate::integral(30))
);
}
#[test]
fn test_type_viii_reserved_code_type_skipped() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0x52], 0xC0, &mut caps);
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_type_viii_two_byte_vic_high_byte_nonzero_skipped() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0x01, 0x01], 0x48, &mut caps); assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_type_viii_two_byte_trailing_odd_byte_ignored() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0x52, 0x00, 0x01], 0x08, &mut caps);
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 1920);
}
#[test]
fn test_type_viii_multiple_one_byte_codes() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0x10, 0x23, 0x52], 0x00, &mut caps);
assert_eq!(caps.supported_modes.len(), 3);
}
#[test]
fn test_type_viii_unknown_dmt_skipped() {
let mut caps = DisplayCapabilities::default();
decode_type_viii_block(&[0xFF, 0x00], 0x08, &mut caps); assert!(caps.supported_modes.is_empty());
}
}