use display_types::cea861::HdmiForumFrl;
use display_types::{ColorBitDepth, ColorFormat, VideoMode};
use crate::types::{CableCapabilities, CandidateConfig, SinkCapabilities, SourceCapabilities};
pub trait CandidateEnumerator {
type Iter<'a>: Iterator<Item = CandidateConfig<'a>>
where
Self: 'a;
fn enumerate<'a>(
&'a self,
sink: &'a SinkCapabilities,
source: &'a SourceCapabilities,
cable: &'a CableCapabilities,
) -> Self::Iter<'a>;
}
#[derive(Debug)]
pub struct EnumeratorIter<'a> {
modes: &'a [VideoMode],
encodings: [ColorFormat; 4],
enc_len: usize,
depths: [[ColorBitDepth; 4]; 4],
dep_lens: [usize; 4],
frl_rates: [HdmiForumFrl; 7],
frl_len: usize,
dsc: [bool; 2],
dsc_len: usize,
mode_idx: usize,
enc_idx: usize,
dep_idx: usize,
frl_idx: usize,
dsc_idx: usize,
}
impl<'a> EnumeratorIter<'a> {
fn remaining(&self) -> usize {
if self.mode_idx >= self.modes.len() {
return 0;
}
let dsc_rem = self.dsc_len - self.dsc_idx;
let frl_rem = (self.frl_len - self.frl_idx - 1) * self.dsc_len;
let dep_rem =
(self.dep_lens[self.enc_idx] - self.dep_idx - 1) * self.frl_len * self.dsc_len;
let enc_rem: usize = (self.enc_idx + 1..self.enc_len)
.map(|e| self.dep_lens[e] * self.frl_len * self.dsc_len)
.sum();
let per_mode: usize = (0..self.enc_len)
.map(|e| self.dep_lens[e] * self.frl_len * self.dsc_len)
.sum();
let mode_rem = (self.modes.len() - self.mode_idx - 1) * per_mode;
dsc_rem + frl_rem + dep_rem + enc_rem + mode_rem
}
}
impl<'a> Iterator for EnumeratorIter<'a> {
type Item = CandidateConfig<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.mode_idx >= self.modes.len() {
return None;
}
let candidate = CandidateConfig {
mode: &self.modes[self.mode_idx],
color_encoding: self.encodings[self.enc_idx],
bit_depth: self.depths[self.enc_idx][self.dep_idx],
frl_rate: self.frl_rates[self.frl_idx],
dsc_enabled: self.dsc[self.dsc_idx],
};
self.dsc_idx += 1;
if self.dsc_idx < self.dsc_len {
return Some(candidate);
}
self.dsc_idx = 0;
self.frl_idx += 1;
if self.frl_idx < self.frl_len {
return Some(candidate);
}
self.frl_idx = 0;
self.dep_idx += 1;
if self.dep_idx < self.dep_lens[self.enc_idx] {
return Some(candidate);
}
self.dep_idx = 0;
self.enc_idx += 1;
if self.enc_idx < self.enc_len {
return Some(candidate);
}
self.enc_idx = 0;
self.mode_idx += 1;
Some(candidate)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let n = self.remaining();
(n, Some(n))
}
}
impl ExactSizeIterator for EnumeratorIter<'_> {}
fn build_iter<'a>(
modes: &'a [VideoMode],
sink: &SinkCapabilities,
source: &SourceCapabilities,
cable: &CableCapabilities,
) -> EnumeratorIter<'a> {
const ALL_FORMATS: [ColorFormat; 4] = [
ColorFormat::Rgb444,
ColorFormat::YCbCr444,
ColorFormat::YCbCr422,
ColorFormat::YCbCr420,
];
const ALL_DEPTHS: [ColorBitDepth; 4] = [
ColorBitDepth::Depth8,
ColorBitDepth::Depth10,
ColorBitDepth::Depth12,
ColorBitDepth::Depth16,
];
const ALL_FRL_DESC: [HdmiForumFrl; 7] = [
HdmiForumFrl::Rate12Gbps4Lanes,
HdmiForumFrl::Rate10Gbps4Lanes,
HdmiForumFrl::Rate8Gbps4Lanes,
HdmiForumFrl::Rate6Gbps4Lanes,
HdmiForumFrl::Rate6Gbps3Lanes,
HdmiForumFrl::Rate3Gbps3Lanes,
HdmiForumFrl::NotSupported,
];
let mut encodings = [ColorFormat::Rgb444; 4];
let mut enc_len = 0usize;
for &fmt in &ALL_FORMATS {
let supported = sink.color_capabilities.for_format(fmt);
if ALL_DEPTHS.iter().any(|&d| supported.supports(d)) {
encodings[enc_len] = fmt;
enc_len += 1;
}
}
let mut depths = [[ColorBitDepth::Depth8; 4]; 4];
let mut dep_lens = [0usize; 4];
for i in 0..enc_len {
let supported = sink.color_capabilities.for_format(encodings[i]);
let mut d = 0usize;
for &depth in &ALL_DEPTHS {
if supported.supports(depth) {
depths[i][d] = depth;
d += 1;
}
}
dep_lens[i] = d;
}
let sink_frl_ceil = sink
.hdmi_forum
.as_ref()
.map_or(HdmiForumFrl::NotSupported, |hf| hf.max_frl_rate);
let effective_ceil = sink_frl_ceil
.min(source.max_frl_rate)
.min(cable.max_frl_rate);
let mut frl_rates = [HdmiForumFrl::NotSupported; 7];
let mut frl_len = 0usize;
for &rate in &ALL_FRL_DESC {
if rate <= effective_ceil {
frl_rates[frl_len] = rate;
frl_len += 1;
}
}
let dsc_supported = source.dsc.is_some_and(|d| d.dsc_1p2)
&& sink
.hdmi_forum
.as_ref()
.is_some_and(|hf| hf.dsc.as_ref().is_some_and(|d| d.dsc_1p2));
let dsc_len = if dsc_supported { 2 } else { 1 };
let mode_idx = if enc_len == 0 { modes.len() } else { 0 };
EnumeratorIter {
modes,
encodings,
enc_len,
depths,
dep_lens,
frl_rates,
frl_len,
dsc: [false, true],
dsc_len,
mode_idx,
enc_idx: 0,
dep_idx: 0,
frl_idx: 0,
dsc_idx: 0,
}
}
#[derive(Debug)]
pub struct SliceEnumerator<'modes> {
modes: &'modes [VideoMode],
}
impl<'modes> SliceEnumerator<'modes> {
pub fn new(modes: &'modes [VideoMode]) -> Self {
Self { modes }
}
}
impl<'modes> CandidateEnumerator for SliceEnumerator<'modes> {
type Iter<'a>
= EnumeratorIter<'a>
where
Self: 'a;
fn enumerate<'a>(
&'a self,
sink: &'a SinkCapabilities,
source: &'a SourceCapabilities,
cable: &'a CableCapabilities,
) -> Self::Iter<'a> {
build_iter(self.modes, sink, source, cable)
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[derive(Debug, Default)]
pub struct DefaultEnumerator;
#[cfg(any(feature = "alloc", feature = "std"))]
impl CandidateEnumerator for DefaultEnumerator {
type Iter<'a> = EnumeratorIter<'a>;
fn enumerate<'a>(
&'a self,
sink: &'a SinkCapabilities,
source: &'a SourceCapabilities,
cable: &'a CableCapabilities,
) -> Self::Iter<'a> {
build_iter(sink.supported_modes.as_slice(), sink, source, cable)
}
}
#[cfg(all(test, any(feature = "alloc", feature = "std")))]
mod tests {
use super::*;
use display_types::cea861::{HdmiDscMaxSlices, HdmiForumDsc, HdmiForumSinkCap};
use display_types::{ColorBitDepths, VideoMode};
fn mode(refresh_rate: u16) -> VideoMode {
VideoMode::new(1920, 1080, refresh_rate, false)
}
fn hf_sink(max_frl_rate: HdmiForumFrl) -> HdmiForumSinkCap {
HdmiForumSinkCap::new(
1,
0,
false,
false,
false,
false,
false,
false,
false,
false,
max_frl_rate,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
None,
None,
None,
)
}
fn hf_sink_dsc(max_frl_rate: HdmiForumFrl) -> HdmiForumSinkCap {
let dsc = HdmiForumDsc::new(
true,
false,
false,
false,
false,
false,
false,
HdmiForumFrl::NotSupported,
HdmiDscMaxSlices::Slices4,
0,
);
HdmiForumSinkCap::new(
1,
0,
false,
false,
false,
false,
false,
false,
false,
false,
max_frl_rate,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
None,
None,
Some(dsc),
)
}
fn rgb8_sink() -> SinkCapabilities {
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8;
SinkCapabilities {
color_capabilities: caps,
..Default::default()
}
}
fn frl6_sink() -> SinkCapabilities {
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8;
SinkCapabilities {
color_capabilities: caps,
hdmi_forum: Some(hf_sink(HdmiForumFrl::Rate6Gbps4Lanes)),
..Default::default()
}
}
fn frl6_source() -> SourceCapabilities {
SourceCapabilities {
max_frl_rate: HdmiForumFrl::Rate6Gbps4Lanes,
..Default::default()
}
}
fn dsc_sink() -> SinkCapabilities {
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8;
SinkCapabilities {
color_capabilities: caps,
hdmi_forum: Some(hf_sink_dsc(HdmiForumFrl::Rate6Gbps4Lanes)),
..Default::default()
}
}
fn dsc_source() -> SourceCapabilities {
use crate::types::source::DscCapabilities;
SourceCapabilities {
max_frl_rate: HdmiForumFrl::Rate6Gbps4Lanes,
dsc: Some(DscCapabilities {
dsc_1p2: true,
max_slices: 4,
max_bpp_x16: 128,
}),
..Default::default()
}
}
fn collect_from<'a>(
modes: &'a [VideoMode],
sink: &'a SinkCapabilities,
source: &'a SourceCapabilities,
cable: &'a CableCapabilities,
) -> alloc::vec::Vec<CandidateConfig<'a>> {
build_iter(modes, sink, source, cable).collect()
}
#[test]
fn empty_mode_list_yields_nothing() {
let sink = rgb8_sink();
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
assert!(collect_from(&[], &sink, &source, &cable).is_empty());
}
#[test]
fn no_usable_encoding_yields_nothing() {
let modes = [mode(60)];
let sink = SinkCapabilities::default();
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
assert!(collect_from(&modes, &sink, &source, &cable).is_empty());
}
#[test]
fn tmds_only_single_mode_rgb8() {
let modes = [mode(60)];
let sink = rgb8_sink();
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let candidates = collect_from(&modes, &sink, &source, &cable);
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].color_encoding, ColorFormat::Rgb444);
assert_eq!(candidates[0].bit_depth, ColorBitDepth::Depth8);
assert_eq!(candidates[0].frl_rate, HdmiForumFrl::NotSupported);
assert!(!candidates[0].dsc_enabled);
}
#[test]
fn frl_rates_highest_first() {
let modes = [mode(60)];
let sink = frl6_sink();
let source = frl6_source();
let cable = CableCapabilities::unconstrained();
let candidates = collect_from(&modes, &sink, &source, &cable);
let frl_rates: alloc::vec::Vec<_> = candidates.iter().map(|c| c.frl_rate).collect();
for i in 1..frl_rates.len() {
assert!(
frl_rates[i - 1] >= frl_rates[i],
"expected descending FRL order at position {i}: {:?} < {:?}",
frl_rates[i - 1],
frl_rates[i],
);
}
}
#[test]
fn dsc_false_before_true() {
let modes = [mode(60)];
let sink = dsc_sink();
let source = dsc_source();
let cable = CableCapabilities::unconstrained();
let candidates = collect_from(&modes, &sink, &source, &cable);
assert!(
!candidates[0].dsc_enabled,
"first candidate should have dsc=false"
);
assert!(
candidates[1].dsc_enabled,
"second candidate should have dsc=true"
);
}
#[test]
fn dsc_absent_when_source_lacks_it() {
let modes = [mode(60)];
let sink = dsc_sink();
let source = frl6_source();
let cable = CableCapabilities::unconstrained();
let candidates = collect_from(&modes, &sink, &source, &cable);
assert!(candidates.iter().all(|c| !c.dsc_enabled));
}
#[test]
fn dsc_absent_when_sink_lacks_it() {
let modes = [mode(60)];
let sink = frl6_sink();
let source = dsc_source();
let cable = CableCapabilities::unconstrained();
let candidates = collect_from(&modes, &sink, &source, &cable);
assert!(candidates.iter().all(|c| !c.dsc_enabled));
}
#[test]
fn candidate_count_matches_product() {
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8.with(ColorBitDepth::Depth10);
let sink = SinkCapabilities {
color_capabilities: caps,
hdmi_forum: Some(hf_sink(HdmiForumFrl::Rate6Gbps4Lanes)),
..Default::default()
};
let modes = [mode(60), mode(30)];
let source = frl6_source();
let cable = CableCapabilities::unconstrained();
let candidates = collect_from(&modes, &sink, &source, &cable);
assert_eq!(candidates.len(), 16);
}
#[test]
fn encoding_order_is_rgb_ycbcr444_ycbcr422_ycbcr420() {
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8;
caps.ycbcr444 = ColorBitDepths::BPC_8;
caps.ycbcr422 = ColorBitDepths::BPC_8;
caps.ycbcr420 = ColorBitDepths::BPC_8;
let sink = SinkCapabilities {
color_capabilities: caps,
..Default::default()
};
let modes = [mode(60)];
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let candidates = collect_from(&modes, &sink, &source, &cable);
let mut seen_order = alloc::vec::Vec::new();
for c in &candidates {
if seen_order.last() != Some(&c.color_encoding) {
seen_order.push(c.color_encoding);
}
}
assert_eq!(
seen_order,
alloc::vec![
ColorFormat::Rgb444,
ColorFormat::YCbCr444,
ColorFormat::YCbCr422,
ColorFormat::YCbCr420,
]
);
}
#[test]
fn no_hf_scdb_yields_only_tmds() {
let modes = [mode(60)];
let sink = rgb8_sink(); let source = frl6_source();
let cable = CableCapabilities::unconstrained();
let candidates = collect_from(&modes, &sink, &source, &cable);
assert!(
candidates
.iter()
.all(|c| c.frl_rate == HdmiForumFrl::NotSupported),
"expected only TMDS candidates when sink has no HF-SCDB"
);
}
#[test]
fn cable_is_binding_frl_ceiling() {
let modes = [mode(60)];
let sink = frl6_sink();
let source = frl6_source();
let cable = CableCapabilities {
max_frl_rate: HdmiForumFrl::Rate3Gbps3Lanes,
..CableCapabilities::unconstrained()
};
let candidates = collect_from(&modes, &sink, &source, &cable);
let max_frl = candidates.iter().map(|c| c.frl_rate).max().unwrap();
assert_eq!(max_frl, HdmiForumFrl::Rate3Gbps3Lanes);
}
#[test]
fn all_seven_frl_tiers_when_ceiling_is_max() {
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8;
let sink = SinkCapabilities {
color_capabilities: caps,
hdmi_forum: Some(hf_sink(HdmiForumFrl::Rate12Gbps4Lanes)),
..Default::default()
};
let source = SourceCapabilities {
max_frl_rate: HdmiForumFrl::Rate12Gbps4Lanes,
..Default::default()
};
let modes = [mode(60)];
let cable = CableCapabilities::unconstrained();
let candidates = collect_from(&modes, &sink, &source, &cable);
let mut frl_rates: alloc::vec::Vec<_> = candidates.iter().map(|c| c.frl_rate).collect();
frl_rates.dedup();
assert_eq!(frl_rates.len(), 7);
}
#[test]
fn multiple_encodings_all_emitted() {
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8;
caps.ycbcr420 = ColorBitDepths::BPC_8;
let sink = SinkCapabilities {
color_capabilities: caps,
..Default::default()
};
let modes = [mode(60)];
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let candidates = collect_from(&modes, &sink, &source, &cable);
let has_rgb = candidates
.iter()
.any(|c| c.color_encoding == ColorFormat::Rgb444);
let has_y420 = candidates
.iter()
.any(|c| c.color_encoding == ColorFormat::YCbCr420);
assert!(has_rgb, "expected RGB candidates");
assert!(has_y420, "expected YCbCr420 candidates");
}
#[test]
fn per_encoding_depths_are_independent() {
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8.with(ColorBitDepth::Depth10);
caps.ycbcr420 = ColorBitDepths::BPC_8;
let sink = SinkCapabilities {
color_capabilities: caps,
..Default::default()
};
let modes = [mode(60)];
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let candidates = collect_from(&modes, &sink, &source, &cable);
let rgb_depths: alloc::vec::Vec<_> = candidates
.iter()
.filter(|c| c.color_encoding == ColorFormat::Rgb444)
.map(|c| c.bit_depth)
.collect();
let y420_depths: alloc::vec::Vec<_> = candidates
.iter()
.filter(|c| c.color_encoding == ColorFormat::YCbCr420)
.map(|c| c.bit_depth)
.collect();
assert!(
rgb_depths.contains(&ColorBitDepth::Depth10),
"RGB should include Depth10"
);
assert!(
!y420_depths.contains(&ColorBitDepth::Depth10),
"YCbCr420 should not include Depth10"
);
}
#[test]
fn odometer_sequence_within_mode() {
use crate::types::source::DscCapabilities;
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8.with(ColorBitDepth::Depth10);
let sink = SinkCapabilities {
color_capabilities: caps,
hdmi_forum: Some(hf_sink_dsc(HdmiForumFrl::Rate3Gbps3Lanes)),
..Default::default()
};
let source = SourceCapabilities {
max_frl_rate: HdmiForumFrl::Rate3Gbps3Lanes,
dsc: Some(DscCapabilities {
dsc_1p2: true,
max_slices: 4,
max_bpp_x16: 128,
}),
..Default::default()
};
let modes = [mode(60)];
let cable = CableCapabilities::unconstrained();
let candidates = collect_from(&modes, &sink, &source, &cable);
type T = (ColorBitDepth, HdmiForumFrl, bool);
let expected: &[T] = &[
(ColorBitDepth::Depth8, HdmiForumFrl::Rate3Gbps3Lanes, false),
(ColorBitDepth::Depth8, HdmiForumFrl::Rate3Gbps3Lanes, true),
(ColorBitDepth::Depth8, HdmiForumFrl::NotSupported, false),
(ColorBitDepth::Depth8, HdmiForumFrl::NotSupported, true),
(ColorBitDepth::Depth10, HdmiForumFrl::Rate3Gbps3Lanes, false),
(ColorBitDepth::Depth10, HdmiForumFrl::Rate3Gbps3Lanes, true),
(ColorBitDepth::Depth10, HdmiForumFrl::NotSupported, false),
(ColorBitDepth::Depth10, HdmiForumFrl::NotSupported, true),
];
assert_eq!(candidates.len(), expected.len());
for (i, (c, &(depth, frl, dsc))) in candidates.iter().zip(expected).enumerate() {
assert_eq!(c.bit_depth, depth, "depth mismatch at {i}");
assert_eq!(c.frl_rate, frl, "frl mismatch at {i}");
assert_eq!(c.dsc_enabled, dsc, "dsc mismatch at {i}");
}
}
#[test]
fn mode_is_slowest_dimension() {
let modes = [mode(60), mode(30)];
let sink = rgb8_sink();
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let candidates = collect_from(&modes, &sink, &source, &cable);
let switch = candidates
.windows(2)
.position(|w| !core::ptr::eq(w[0].mode, w[1].mode));
if let Some(i) = switch {
assert!(
candidates[i + 1..]
.iter()
.all(|c| !core::ptr::eq(c.mode, &modes[0])),
"mode[0] appeared again after the switch at position {i}"
);
}
}
#[test]
fn all_candidates_borrow_from_mode_slice() {
let modes = [mode(60), mode(30)];
let sink = rgb8_sink();
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let candidates = collect_from(&modes, &sink, &source, &cable);
let mid = candidates.len() / 2;
assert!(
candidates[..mid]
.iter()
.all(|c| core::ptr::eq(c.mode, &modes[0]))
);
assert!(
candidates[mid..]
.iter()
.all(|c| core::ptr::eq(c.mode, &modes[1]))
);
}
#[test]
fn slice_enumerator_uses_provided_slice_not_sink_modes() {
use crate::types::sink::SupportedModes;
let sink_mode = VideoMode::new(3840, 2160, 60, false);
let slice_mode = VideoMode::new(1920, 1080, 60, false);
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8;
let (supported_modes, _) = SupportedModes::from_vec(alloc::vec![sink_mode]);
let sink = SinkCapabilities {
color_capabilities: caps,
supported_modes,
..Default::default()
};
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let enumerator = SliceEnumerator::new(core::slice::from_ref(&slice_mode));
let candidates: alloc::vec::Vec<_> =
CandidateEnumerator::enumerate(&enumerator, &sink, &source, &cable).collect();
assert!(!candidates.is_empty());
assert!(
candidates.iter().all(|c| c.mode.width == 1920),
"expected only the slice mode (1920-wide), not the sink mode (3840-wide)"
);
}
#[test]
fn slice_enumerator_enumerate_is_repeatable() {
let modes = [mode(60), mode(30)];
let sink = rgb8_sink();
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let enumerator = SliceEnumerator::new(&modes);
let first: alloc::vec::Vec<_> =
CandidateEnumerator::enumerate(&enumerator, &sink, &source, &cable).collect();
let second: alloc::vec::Vec<_> =
CandidateEnumerator::enumerate(&enumerator, &sink, &source, &cable).collect();
assert_eq!(first, second);
}
#[test]
fn default_enumerator_empty_supported_modes_yields_nothing() {
let sink = rgb8_sink(); let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let candidates: alloc::vec::Vec<_> =
CandidateEnumerator::enumerate(&DefaultEnumerator, &sink, &source, &cable).collect();
assert!(candidates.is_empty());
}
#[test]
fn default_enumerator_matches_slice_enumerator() {
use crate::types::sink::SupportedModes;
let mut caps = display_types::ColorCapabilities::default();
caps.rgb444 = ColorBitDepths::BPC_8.with(ColorBitDepth::Depth10);
let (supported_modes, _) =
SupportedModes::from_vec(alloc::vec![mode(60), mode(30), mode(24)]);
let sink = SinkCapabilities {
color_capabilities: caps,
supported_modes,
hdmi_forum: Some(hf_sink(HdmiForumFrl::Rate6Gbps4Lanes)),
..Default::default()
};
let source = frl6_source();
let cable = CableCapabilities::unconstrained();
let from_default: alloc::vec::Vec<_> =
CandidateEnumerator::enumerate(&DefaultEnumerator, &sink, &source, &cable).collect();
let slice_enumerator = SliceEnumerator::new(sink.supported_modes.as_slice());
let from_slice: alloc::vec::Vec<_> =
CandidateEnumerator::enumerate(&slice_enumerator, &sink, &source, &cable).collect();
assert_eq!(from_default, from_slice);
}
#[test]
fn size_hint_equals_collect_len() {
let modes = [mode(60), mode(30)];
let sink = rgb8_sink();
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let mut iter = build_iter(&modes, &sink, &source, &cable);
let (lo, hi) = iter.size_hint();
let collected: alloc::vec::Vec<_> = iter.by_ref().collect();
assert_eq!(lo, collected.len());
assert_eq!(hi, Some(collected.len()));
}
#[test]
fn size_hint_decrements_on_each_next() {
let modes = [mode(60)];
let sink = rgb8_sink();
let source = SourceCapabilities::default();
let cable = CableCapabilities::default();
let mut iter = build_iter(&modes, &sink, &source, &cable);
let total = iter.len();
for consumed in 1..=total {
iter.next();
assert_eq!(
iter.len(),
total - consumed,
"wrong len after {consumed} calls"
);
}
assert_eq!(iter.len(), 0);
assert!(iter.next().is_none());
}
}