HuC6260 VCE (video color encoder)
11pub const MAX_LINES_PER_FRAME: usize = 263; 12 13pub const CRAM_LEN_WORDS: usize = 512; 14 15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 16pub enum DotClockDivider { 17 #[default] 18 Four = 4, // ~5.37 MHz, commonly H256px 19 Three = 3, // ~7.13 MHz, commonly H304px to H352px 20 Two = 2, // ~10.69 MHz, commonly H512px (not used by commercial releases) 21} 22 23impl DotClockDivider { 24 fn from_bits(bits: u8) -> Self { 25 match bits & 3 { 26 0 => Self::Four, 27 1 => Self::Three, 28 2 | 3 => Self::Two, 29 _ => unreachable!("value & 3 is always <= 3"), 30 } 31 } 32 33 pub fn divide_difference(self, cycles: u64, prev_cycles: u64) -> u64 { 34 debug_assert!(cycles >= prev_cycles); 35 36 // This is faster than dividing by u64::from(self) because it avoids div instructions and 37 // dot clock divider rarely changes 38 match self { 39 Self::Four => (cycles >> 2) - (prev_cycles >> 2), 40 Self::Three => (cycles / 3) - (prev_cycles / 3), 41 Self::Two => (cycles >> 1) - (prev_cycles >> 1), 42 } 43 } 44 45 pub fn divide(self, cycles: u64) -> u64 { 46 // Same as in divide_difference(), avoids div instructions 47 match self { 48 Self::Four => cycles >> 2, 49 Self::Three => cycles / 3, 50 Self::Two => cycles >> 1, 51 } 52 } 53} 54 55impl From<DotClockDivider> for u64 { 56 fn from(value: DotClockDivider) -> Self { 57 match value { 58 DotClockDivider::Four => 4, 59 DotClockDivider::Three => 3, 60 DotClockDivider::Two => 2, 61 } 62 } 63} 64 65#[derive(Debug, Clone, Encode, Decode)] 66pub struct Vce { 67 cram: BoxedWordArray<CRAM_LEN_WORDS>, 68 dot_clock_divider: DotClockDivider, 69 extra_line_per_frame: bool, 70 greyscale: bool, 71 color_table_address: u16, 72} 73 74impl Vce { 75 pub fn new() -> Self { 76 Self { 77 cram: BoxedWordArray::new(), 78 dot_clock_divider: DotClockDivider::default(), 79 extra_line_per_frame: false, 80 greyscale: false, 81 color_table_address: 0, 82 } 83 } 84 85 pub fn overscan_color(&self) -> u16 { 86 // Sprite color 0 87 self.cram[0x100] 88 } 89 90 pub fn read_color(&self, color_idx: u16) -> u16 { 91 self.cram[(color_idx as usize) & (CRAM_LEN_WORDS - 1)] 92 } 93 94 pub fn dot_clock_divider(&self) -> DotClockDivider { 95 self.dot_clock_divider 96 } 97 98 pub fn lines_per_frame(&self) -> u16 { 99 (MAX_LINES_PER_FRAME as u16) - 1 + u16::from(self.extra_line_per_frame) 100 } 101 102 pub fn greyscale(&self) -> bool { 103 self.greyscale 104 } 105 106 // $1FE400: CR (Control register) 107 pub fn write_control(&mut self, value: u8) { 108 self.dot_clock_divider = DotClockDivider::from_bits(value); 109 self.extra_line_per_frame = value.bit(2); 110 self.greyscale = value.bit(7); 111 112 log::trace!("CR write: {value:02X}"); 113 log::trace!(" Dot clock divider: {}", u64::from(self.dot_clock_divider)); 114 log::trace!(" Lines per frame: {}", if self.extra_line_per_frame { 263 } else { 262 }); 115 log::trace!(" Monochrome: {}", self.greyscale); 116 } 117 118 // $1FE402-$1FE403: CTA (Color table address register) 119 pub fn write_color_address(&mut self, value: u8, byte: WordByte) { 120 match byte { 121 WordByte::Low => self.color_table_address.set_lsb(value), 122 WordByte::High => self.color_table_address.set_msb(value & 1), 123 } 124 125 log::trace!("CTA {byte:?} write: {value:02X}"); 126 log::trace!(" Color table address: {:03X}", self.color_table_address); 127 } 128 129 // $1FE404-$1FE405: CTR (Color table data read register) 130 pub fn read_color_data(&mut self, byte: WordByte) -> u8 { 131 log::trace!("CTR {byte:?} read (current address {:03X})", self.color_table_address); 132 133 // Highest 7 bits always read 1 134 let color = self.cram[self.color_table_address as usize] | !0x1FF; 135 136 if byte == WordByte::High { 137 self.increment_color_table_address(); 138 } 139 140 byte.get(color) 141 } 142 143 // $1FE404-$1FE405: CTW (Color table data write register) 144 pub fn write_color_data(&mut self, value: u8, byte: WordByte) { 145 log::trace!( 146 "CTW {byte:?} write: {value:02X} (current address {:03X})", 147 self.color_table_address 148 ); 149 150 let color = &mut self.cram[self.color_table_address as usize]; 151 match byte { 152 WordByte::Low => color.set_lsb(value), 153 WordByte::High => color.set_msb(value & 1), 154 } 155 156 if byte == WordByte::High { 157 self.increment_color_table_address(); 158 } 159 } 160 161 fn increment_color_table_address(&mut self) { 162 self.color_table_address = (self.color_table_address + 1) & (CRAM_LEN_WORDS - 1) as u16; 163 } 164 165 pub fn dump_palettes(&self, out: &mut [Color], palette: &PcePalette) { 166 for (cram_color, out_color) in 167 iter::zip(self.cram.iter().copied(), &mut out[..CRAM_LEN_WORDS]) 168 { 169 let (r, g, b) = palette[(cram_color & 0x1FF) as usize]; 170 *out_color = Color::rgb(r, g, b); 171 } 172 } 173}