use crate::model::capabilities::{
DisplayCapabilities, ModeSink, StereoMode, SyncDefinition, VideoMode,
};
use crate::model::diagnostics::EdidWarning;
pub(super) fn decode_established_timings(base: &[u8; 128], sink: &mut dyn ModeSink) {
const TIMINGS: &[(usize, u8, u16, u16, u8)] = &[
(0x23, 0x80, 720, 400, 70),
(0x23, 0x40, 720, 400, 88),
(0x23, 0x20, 640, 480, 60),
(0x23, 0x10, 640, 480, 67),
(0x23, 0x08, 640, 480, 72),
(0x23, 0x04, 640, 480, 75),
(0x23, 0x02, 800, 600, 56),
(0x23, 0x01, 800, 600, 60),
(0x24, 0x80, 800, 600, 72),
(0x24, 0x40, 800, 600, 75),
(0x24, 0x20, 832, 624, 75),
(0x24, 0x10, 1024, 768, 87),
(0x24, 0x08, 1024, 768, 60),
(0x24, 0x04, 1024, 768, 70),
(0x24, 0x02, 1024, 768, 75),
(0x24, 0x01, 1280, 1024, 75),
(0x25, 0x80, 1152, 870, 75), ];
for &(byte_off, mask, w, h, rate) in TIMINGS {
if base[byte_off] & mask != 0 {
sink.push_mode(VideoMode::new(w, h, rate, false));
}
}
}
pub(super) fn decode_standard_timing_entry(b1: u8, b2: u8) -> Option<VideoMode> {
if (b1 == 0x01 && b2 == 0x01) || b1 == 0x00 {
return None;
}
let w = (b1 as u16 + 31) * 8;
let h = match (b2 >> 6) & 0x03 {
0x00 => (w * 10) / 16, 0x01 => (w * 3) / 4, 0x02 => (w * 4) / 5, _ => (w * 9) / 16, };
Some(VideoMode::new(w, h, (b2 & 0x3F) + 60, false))
}
pub(super) fn decode_standard_timings(base: &[u8; 128], sink: &mut dyn ModeSink) {
for i in 0..8 {
let offset = 0x26 + (i * 2);
if let Some(mode) = decode_standard_timing_entry(base[offset], base[offset + 1]) {
sink.push_mode(mode);
}
}
}
fn build_dtd_mode(dtd: &[u8]) -> Result<Option<VideoMode>, EdidWarning> {
if dtd.len() < 18 {
return Err(EdidWarning::DtdSlotTooShort);
}
if dtd[0] == 0x00 && dtd[1] == 0x00 {
return Ok(None); }
let pixel_clock = ((dtd[1] as u32) << 8) | (dtd[0] as u32);
if pixel_clock == 0 {
return Ok(None);
}
let hactive = (((dtd[4] as u16) & 0xF0) << 4) | (dtd[2] as u16);
let hblank = (((dtd[4] as u16) & 0x0F) << 8) | (dtd[3] as u16);
let vactive = (((dtd[7] as u16) & 0xF0) << 4) | (dtd[5] as u16);
let vblank = (((dtd[7] as u16) & 0x0F) << 8) | (dtd[6] as u16);
if hactive == 0 || vactive == 0 || hblank == 0 || vblank == 0 {
return Ok(None);
}
let total_pixels = (hactive + hblank) as u32 * (vactive + vblank) as u32;
if total_pixels == 0 {
return Ok(None);
}
let Some(refresh_rate) = pixel_clock
.checked_mul(10_000)
.and_then(|scaled| u8::try_from(scaled / total_pixels).ok())
else {
return Err(EdidWarning::DtdPixelClockOverflow);
};
let interlaced = dtd[17] & 0x80 != 0;
let h_front_porch = (((dtd[11] as u16) >> 6) << 8) | (dtd[8] as u16);
let h_sync_width = ((((dtd[11] as u16) >> 4) & 0x03) << 8) | (dtd[9] as u16);
let v_front_porch = ((((dtd[11] as u16) >> 2) & 0x03) << 4) | (((dtd[10] as u16) >> 4) & 0x0F);
let v_sync_width = (((dtd[11] as u16) & 0x03) << 4) | ((dtd[10] as u16) & 0x0F);
let h_border = dtd[15];
let v_border = dtd[16];
let stereo = match ((dtd[17] >> 5) & 0x03, dtd[17] & 0x01) {
(0b00, _) => StereoMode::None,
(0b01, 0) => StereoMode::FieldSequentialRightFirst,
(0b10, 0) => StereoMode::FieldSequentialLeftFirst,
(0b01, 1) => StereoMode::TwoWayInterleavedRightEven,
(0b10, 1) => StereoMode::TwoWayInterleavedLeftEven,
(0b11, 0) => StereoMode::FourWayInterleaved,
_ => StereoMode::SideBySideInterleaved,
};
let sync = Some(if dtd[17] & 0x10 == 0 {
let serrations = dtd[17] & 0x04 != 0;
let sync_on_all_rgb = dtd[17] & 0x02 != 0;
if dtd[17] & 0x08 == 0 {
SyncDefinition::AnalogComposite {
serrations,
sync_on_all_rgb,
}
} else {
SyncDefinition::BipolarAnalogComposite {
serrations,
sync_on_all_rgb,
}
}
} else {
let h_sync_positive = dtd[17] & 0x02 != 0;
if dtd[17] & 0x08 == 0 {
SyncDefinition::DigitalComposite {
serrations: dtd[17] & 0x04 != 0,
h_sync_positive,
}
} else {
SyncDefinition::DigitalSeparate {
v_sync_positive: dtd[17] & 0x04 != 0,
h_sync_positive,
}
}
});
Ok(Some(
VideoMode::new(hactive, vactive, refresh_rate, interlaced).with_detailed_timing(
pixel_clock * 10,
h_front_porch,
h_sync_width,
v_front_porch,
v_sync_width,
h_border,
v_border,
stereo,
sync,
),
))
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub(crate) fn decode_dtd_slot(dtd: &[u8], caps: &mut DisplayCapabilities) {
let mode = match build_dtd_mode(dtd) {
Err(w) => {
caps.push_warning(w);
return;
}
Ok(None) => return,
Ok(Some(m)) => m,
};
let h_mm = (((dtd[14] as u16) & 0xF0) << 4) | (dtd[12] as u16);
let v_mm = (((dtd[14] as u16) & 0x0F) << 8) | (dtd[13] as u16);
if h_mm != 0 && v_mm != 0 && caps.preferred_image_size_mm.is_none() {
caps.preferred_image_size_mm = Some((h_mm, v_mm));
}
if let Some(existing) = caps.supported_modes.iter_mut().find(|m| {
m.width == mode.width
&& m.height == mode.height
&& m.refresh_rate == mode.refresh_rate
&& m.interlaced == mode.interlaced
}) {
*existing = mode;
} else {
caps.supported_modes.push(mode);
}
}
pub(crate) fn decode_dtd_slot_into_sink(dtd: &[u8], sink: &mut dyn ModeSink) {
match build_dtd_mode(dtd) {
Err(w) => sink.push_warning(w),
Ok(None) => {}
Ok(Some(mode)) => sink.push_mode(mode),
}
}
#[allow(dead_code)]
pub(super) fn decode_detailed_timings(base: &[u8; 128], sink: &mut dyn ModeSink) {
for i in 0..4 {
let offset = 0x36 + (i * 18);
decode_dtd_slot_into_sink(&base[offset..offset + 18], sink);
}
}
#[cfg(test)]
#[cfg(any(feature = "alloc", feature = "std"))]
mod tests {
use crate::capabilities::base::BaseBlockHandler;
use crate::model::capabilities::{DisplayCapabilities, VideoMode};
use crate::model::extension::ExtensionHandler;
use crate::model::prelude::Vec;
#[test]
fn test_established_timings() {
let mut base = [0u8; 128];
base[0x23] = 0x20; base[0x23] |= 0x01; base[0x24] = 0x08; base[0x24] |= 0x01;
let mut caps = DisplayCapabilities::default();
BaseBlockHandler.process(&[&base], &mut caps, &mut Vec::new());
assert_eq!(caps.supported_modes.len(), 4);
assert!(
caps.supported_modes
.contains(&VideoMode::new(640, 480, 60, false))
);
assert!(
caps.supported_modes
.contains(&VideoMode::new(800, 600, 60, false))
);
assert!(
caps.supported_modes
.contains(&VideoMode::new(1024, 768, 60, false))
);
assert!(
caps.supported_modes
.contains(&VideoMode::new(1280, 1024, 75, false))
);
}
#[test]
fn test_standard_timings() {
let mut base = [0u8; 128];
base[0x26] = 209;
base[0x27] = 0xC0;
base[0x28] = 129;
base[0x29] = 0x8F;
let mut caps = DisplayCapabilities::default();
BaseBlockHandler.process(&[&base], &mut caps, &mut Vec::new());
assert_eq!(caps.supported_modes.len(), 2);
assert_eq!(caps.supported_modes[0].width, 1920);
assert_eq!(caps.supported_modes[0].height, 1080);
assert_eq!(caps.supported_modes[0].refresh_rate, 60);
assert_eq!(caps.supported_modes[1].width, 1280);
assert_eq!(caps.supported_modes[1].height, 1024);
assert_eq!(caps.supported_modes[1].refresh_rate, 75);
}
#[test]
fn test_detailed_timing_and_range_limits() {
let mut base = [0u8; 128];
base[0x36] = 0x02;
base[0x37] = 0x3A;
base[0x38] = 0x80; base[0x39] = 0x18; base[0x3A] = 0x71; base[0x3B] = 0x38; base[0x3C] = 0x2D; base[0x3D] = 0x40;
base[0x48..0x4D].copy_from_slice(&[0x00, 0x00, 0x00, 0xFD, 0x00]);
base[0x4D] = 48; base[0x4E] = 75; base[0x4F] = 30; base[0x50] = 83; base[0x51] = 17;
let mut caps = DisplayCapabilities::default();
BaseBlockHandler.process(&[&base], &mut caps, &mut Vec::new());
assert_eq!(caps.supported_modes.len(), 1);
assert_eq!(caps.supported_modes[0].width, 1920);
assert_eq!(caps.supported_modes[0].height, 1080);
assert_eq!(caps.supported_modes[0].refresh_rate, 60);
assert_eq!(caps.min_v_rate, Some(48));
assert_eq!(caps.max_v_rate, Some(75));
assert_eq!(caps.min_h_rate_khz, Some(30));
assert_eq!(caps.max_h_rate_khz, Some(83));
assert_eq!(caps.max_pixel_clock_mhz, Some(170));
}
#[test]
fn test_dtd_sync_timing() {
let mut base = [0u8; 128];
base[0x36] = 0x02;
base[0x37] = 0x3A;
base[0x38] = 0x80;
base[0x39] = 0x18;
base[0x3A] = 0x71; base[0x3B] = 0x38;
base[0x3C] = 0x2D;
base[0x3D] = 0x40; base[0x3E] = 88; base[0x3F] = 44; base[0x40] = (4 << 4) | 5; base[0x41] = 0x00;
base[0x41] = 0b10_01_11_10;
let mut caps = DisplayCapabilities::default();
BaseBlockHandler.process(&[&base], &mut caps, &mut Vec::new());
assert_eq!(caps.supported_modes.len(), 1);
let mode = &caps.supported_modes[0];
assert_eq!(mode.h_front_porch, 600);
assert_eq!(mode.h_sync_width, 300);
assert_eq!(mode.v_front_porch, 52);
assert_eq!(mode.v_sync_width, 37);
}
#[test]
fn test_dtd_upgrades_standard_timing_entry() {
let mut base = [0u8; 128];
base[0x26] = 209;
base[0x27] = 0xC0;
base[0x36] = 0x02;
base[0x37] = 0x3A;
base[0x38] = 0x80;
base[0x39] = 0x18;
base[0x3A] = 0x71;
base[0x3B] = 0x38;
base[0x3C] = 0x2D;
base[0x3D] = 0x40;
base[0x3E] = 88;
base[0x3F] = 44;
base[0x40] = (4 << 4) | 5;
base[0x41] = 0x00;
let mut caps = DisplayCapabilities::default();
BaseBlockHandler.process(&[&base], &mut caps, &mut Vec::new());
assert_eq!(
caps.supported_modes
.iter()
.filter(|m| m.width == 1920 && m.height == 1080 && m.refresh_rate == 60)
.count(),
1
);
let mode = caps
.supported_modes
.iter()
.find(|m| m.width == 1920 && m.height == 1080)
.unwrap();
assert_eq!(mode.h_front_porch, 88);
assert_eq!(mode.v_front_porch, 4);
}
#[test]
fn test_dtd_interlace_and_image_size() {
let mut base = [0u8; 128];
base[0x36] = 0x29; base[0x37] = 0x1D; base[0x38] = 0x80;
base[0x39] = 0x18;
base[0x3A] = 0x71;
base[0x3B] = 0x1C;
base[0x3C] = 0x16;
base[0x3D] = 0x20;
base[0x3E] = 0x00;
base[0x3F] = 0x00;
base[0x40] = 0x00;
base[0x41] = 0x00;
base[0x42] = 0x0F; base[0x43] = 0x28; base[0x44] = 0x21; base[0x45] = 0x00;
base[0x46] = 0x00;
base[0x47] = 0x80;
let mut caps = DisplayCapabilities::default();
BaseBlockHandler.process(&[&base], &mut caps, &mut Vec::new());
assert_eq!(caps.supported_modes.len(), 1);
assert!(caps.supported_modes[0].interlaced);
assert_eq!(caps.preferred_image_size_mm, Some((527, 296)));
}
#[test]
fn test_dtd_progressive_no_image_size() {
let mut base = [0u8; 128];
base[0x36] = 0x02;
base[0x37] = 0x3A;
base[0x38] = 0x80;
base[0x39] = 0x18;
base[0x3A] = 0x71;
base[0x3B] = 0x38;
base[0x3C] = 0x2D;
base[0x3D] = 0x40;
base[0x47] = 0x00;
let mut caps = DisplayCapabilities::default();
BaseBlockHandler.process(&[&base], &mut caps, &mut Vec::new());
assert!(!caps.supported_modes[0].interlaced);
assert_eq!(caps.preferred_image_size_mm, None);
}
#[cfg(any(feature = "alloc", feature = "std"))]
fn dtd_with_byte17(flags: u8) -> VideoMode {
let mut base = [0u8; 128];
base[0x36] = 0x02;
base[0x37] = 0x3A;
base[0x38] = 0x80;
base[0x39] = 0x18;
base[0x3A] = 0x71;
base[0x3B] = 0x38;
base[0x3C] = 0x2D;
base[0x3D] = 0x40;
base[0x45] = 3; base[0x46] = 2; base[0x47] = flags;
let mut caps = DisplayCapabilities::default();
BaseBlockHandler.process(&[&base], &mut caps, &mut Vec::new());
caps.supported_modes.into_iter().next().unwrap()
}
#[test]
fn test_dtd_border() {
let mode = dtd_with_byte17(0x1E); assert_eq!(mode.h_border, 3);
assert_eq!(mode.v_border, 2);
}
#[test]
fn test_dtd_stereo() {
use crate::model::capabilities::StereoMode;
assert_eq!(dtd_with_byte17(0x1E).stereo, StereoMode::None);
assert_eq!(
dtd_with_byte17(0x3E).stereo,
StereoMode::FieldSequentialRightFirst
);
assert_eq!(
dtd_with_byte17(0x5E).stereo,
StereoMode::FieldSequentialLeftFirst
);
assert_eq!(
dtd_with_byte17(0x3F).stereo,
StereoMode::TwoWayInterleavedRightEven
);
assert_eq!(
dtd_with_byte17(0x5F).stereo,
StereoMode::TwoWayInterleavedLeftEven
);
assert_eq!(dtd_with_byte17(0x7E).stereo, StereoMode::FourWayInterleaved);
assert_eq!(
dtd_with_byte17(0x7F).stereo,
StereoMode::SideBySideInterleaved
);
}
#[test]
fn test_dtd_sync_types() {
use crate::model::capabilities::SyncDefinition;
assert_eq!(
dtd_with_byte17(0x00).sync,
Some(SyncDefinition::AnalogComposite {
serrations: false,
sync_on_all_rgb: false
})
);
assert_eq!(
dtd_with_byte17(0x06).sync,
Some(SyncDefinition::AnalogComposite {
serrations: true,
sync_on_all_rgb: true
})
);
assert_eq!(
dtd_with_byte17(0x0A).sync,
Some(SyncDefinition::BipolarAnalogComposite {
serrations: false,
sync_on_all_rgb: true
})
);
assert_eq!(
dtd_with_byte17(0x10).sync,
Some(SyncDefinition::DigitalComposite {
serrations: false,
h_sync_positive: false
})
);
assert_eq!(
dtd_with_byte17(0x16).sync,
Some(SyncDefinition::DigitalComposite {
serrations: true,
h_sync_positive: true
})
);
assert_eq!(
dtd_with_byte17(0x18).sync,
Some(SyncDefinition::DigitalSeparate {
v_sync_positive: false,
h_sync_positive: false
})
);
assert_eq!(
dtd_with_byte17(0x1E).sync,
Some(SyncDefinition::DigitalSeparate {
v_sync_positive: true,
h_sync_positive: true
})
);
}
}