use crate::memory::{ActiveInterrupt, Memory};
use crate::platform::{Color, PlatformProvider, WindowConfig};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
struct VicChipSpeaker {
on: bool,
note: u8,
}
impl VicChipSpeaker {
fn new() -> Self {
Self { on: false, note: 0 }
}
fn read(&self) -> u8 {
self.note | (self.on as u8) << 7
}
fn write(&mut self, value: u8) {
self.on = (value & 0x80) != 0;
self.note = value & 0x7f;
}
fn reset(&mut self) {
self.on = false;
self.note = 0;
}
}
struct VicChipLightPen {
x: u8,
y: u8,
}
impl VicChipLightPen {
fn new() -> Self {
Self { x: 0, y: 0 }
}
fn read_x(&self) -> u8 {
self.x
}
fn read_y(&self) -> u8 {
self.y
}
fn write_x(&mut self, value: u8) {
self.x = value;
}
fn write_y(&mut self, value: u8) {
self.y = value;
}
fn reset(&mut self) {
self.x = 0;
self.y = 0;
}
}
pub struct VicChip {
platform: Arc<dyn PlatformProvider>,
scan_mode: bool,
left_draw_offset: u8,
top_draw_offset: u8,
row_count: u8,
column_count: u8,
double_size_chars: bool,
raster_counter: u16,
vram_address_top: u8,
character_address_top: u8,
color_ram_mapping: bool,
light_pen: VicChipLightPen,
potentiometer_1: u8,
potentiometer_2: u8,
speaker_alto: VicChipSpeaker,
speaker_tenor: VicChipSpeaker,
speaker_soprano: VicChipSpeaker,
speaker_noise: VicChipSpeaker,
speaker_volume: u8,
aux_color: u8,
border_color: u8,
reverse_field: bool,
background_color: u8,
}
impl VicChip {
pub fn new(platform: Arc<dyn PlatformProvider>) -> Self {
let width: u8 = 22;
let height: u8 = 23;
platform.request_window(WindowConfig::new(width as u32 * 8, height as u32 * 8, 2.0));
Self {
platform,
scan_mode: false,
left_draw_offset: 12,
top_draw_offset: 38,
column_count: width,
color_ram_mapping: true,
raster_counter: 0,
row_count: height,
double_size_chars: false,
vram_address_top: 0,
character_address_top: 0,
light_pen: VicChipLightPen::new(),
potentiometer_1: 0xFF,
potentiometer_2: 0xFF,
speaker_alto: VicChipSpeaker::new(),
speaker_tenor: VicChipSpeaker::new(),
speaker_soprano: VicChipSpeaker::new(),
speaker_noise: VicChipSpeaker::new(),
speaker_volume: 0,
aux_color: 0,
border_color: 3,
reverse_field: true,
background_color: 1,
}
}
pub fn reset(&mut self) {
self.scan_mode = false;
self.left_draw_offset = 12;
self.top_draw_offset = 38;
self.column_count = 22;
self.color_ram_mapping = true;
self.raster_counter = 0;
self.row_count = 23;
self.double_size_chars = false;
self.vram_address_top = 15;
self.light_pen.reset();
self.potentiometer_1 = 0xFF;
self.potentiometer_2 = 0xFF;
self.speaker_alto.reset();
self.speaker_tenor.reset();
self.speaker_soprano.reset();
self.speaker_noise.reset();
self.speaker_volume = 0;
self.aux_color = 0;
self.border_color = 3;
self.reverse_field = true;
self.background_color = 1;
self.character_address_top = 0;
}
fn vic_to_cpu_address(address: u16) -> u16 {
#[allow(clippy::identity_op)]
match address & (1 << 13) {
0 => 0x8000 + (address & 0x1fff),
_ => 0x0000 + (address & 0x1fff),
}
}
fn read_vram(&self, address: u16, memory: &mut Box<dyn Memory>) -> u8 {
let mut offset = (self.vram_address_top as u16) << 10;
offset += (self.color_ram_mapping as u16) << 9;
memory.read(VicChip::vic_to_cpu_address(address + offset))
}
fn read_color(&self, address: u16, memory: &mut Box<dyn Memory>) -> u8 {
let offset = if self.color_ram_mapping {
0x1600
} else {
0x1400
};
memory.read(VicChip::vic_to_cpu_address(address + offset))
}
fn read_character(&self, address: u16, memory: &mut Box<dyn Memory>) -> u8 {
let offset = (self.character_address_top as u16) << 10;
memory.read(VicChip::vic_to_cpu_address(address + offset))
}
fn get_character(&mut self, value: u8, memory: &mut Box<dyn Memory>) -> Vec<u8> {
let character_index = (value as u16) * 8;
let mut character = vec![0; 8];
for (i, row) in character.iter_mut().enumerate() {
*row = self.read_character(character_index + i as u16, memory);
}
character
}
fn get_foreground(&mut self, address: u16, memory: &mut Box<dyn Memory>) -> Color {
let value = self.read_color(address, memory);
match value & 0b111 {
0b000 => Color::new(0, 0, 0),
0b001 => Color::new(255, 255, 255),
0b010 => Color::new(255, 0, 0),
0b011 => Color::new(0, 255, 255),
0b100 => Color::new(255, 0, 255),
0b101 => Color::new(0, 255, 0),
0b110 => Color::new(0, 0, 255),
0b111 => Color::new(255, 255, 0),
_ => unreachable!(),
}
}
fn get_background(&self) -> Color {
match self.background_color & 0b1111 {
0b0000 => Color::new(0, 0, 0),
0b0001 => Color::new(255, 255, 255),
0b0010 => Color::new(255, 0, 0),
0b0011 => Color::new(0, 255, 255),
0b0100 => Color::new(255, 0, 255),
0b0101 => Color::new(0, 255, 0),
0b0110 => Color::new(0, 0, 255),
0b0111 => Color::new(255, 255, 0),
0b1000 => Color::new(255, 127, 0),
0b1001 => Color::new(255, 192, 128),
0b1010 => Color::new(255, 128, 128),
0b1011 => Color::new(128, 255, 255),
0b1100 => Color::new(255, 128, 255),
0b1101 => Color::new(128, 255, 128),
0b1110 => Color::new(128, 128, 255),
0b1111 => Color::new(255, 255, 128),
_ => unreachable!(),
}
}
fn get_border_color(&self) -> Color {
match self.border_color & 0b111 {
0b000 => Color::new(0, 0, 0),
0b001 => Color::new(255, 255, 255),
0b010 => Color::new(255, 0, 0),
0b011 => Color::new(0, 255, 255),
0b100 => Color::new(255, 0, 255),
0b101 => Color::new(0, 255, 0),
0b110 => Color::new(0, 0, 255),
0b111 => Color::new(255, 255, 0),
_ => unreachable!(),
}
}
fn get_aux_color(&self) -> Color {
match self.aux_color & 0b1111 {
0b0000 => Color::new(0, 0, 0),
0b0001 => Color::new(255, 255, 255),
0b0010 => Color::new(255, 0, 0),
0b0011 => Color::new(0, 255, 255),
0b0100 => Color::new(255, 0, 255),
0b0101 => Color::new(0, 255, 0),
0b0110 => Color::new(0, 0, 255),
0b0111 => Color::new(255, 255, 0),
0b1000 => Color::new(255, 127, 0),
0b1001 => Color::new(255, 192, 128),
0b1010 => Color::new(255, 128, 128),
0b1011 => Color::new(128, 255, 255),
0b1100 => Color::new(255, 128, 255),
0b1101 => Color::new(128, 255, 128),
0b1110 => Color::new(128, 128, 255),
0b1111 => Color::new(255, 255, 128),
_ => unreachable!(),
}
}
fn redraw(&mut self, address: u16, memory: &mut Box<dyn Memory>, framebuffer: &mut [u8]) {
if address >= (self.row_count as u16 * self.column_count as u16) {
return; }
let column = (address % self.column_count as u16) as u32;
let row = (address / self.column_count as u16) as u32;
let base_column = column * 8;
let base_row = row * 8;
let value = self.read_vram(address, memory);
let color = self.read_color(address, memory);
let character = self.get_character(value, memory);
let char_height = if self.double_size_chars { 16 } else { 8 };
let char_width = if color & 0b1000 == 0 { 8 } else { 4 };
if color & 0b1000 == 0 {
for line in 0..char_height {
let line_data = character[line as usize];
for pixel in 0..char_width {
let color = if line_data & (1 << (char_width - 1 - pixel)) != 0 {
self.get_foreground(address, memory)
} else {
self.get_background()
};
let x = base_column + pixel;
let y = base_row + line;
let index = (y * (self.column_count as u32 * 8) + x) as usize * 4;
let pixel = &mut framebuffer[index..(index + 4)];
pixel.copy_from_slice(&color.to_rgba());
}
}
} else {
for line in 0..char_height {
let line_data = character[line as usize];
for pixel in 0..char_width {
let color_code = line_data >> (2 * (char_width - pixel - 1)) & 0b11;
let color = match color_code {
0b00 => self.get_background(),
0b01 => self.get_border_color(),
0b10 => self.get_foreground(address, memory),
0b11 => self.get_aux_color(),
_ => unreachable!(),
};
let x = column * 8 + (pixel * 2);
let y = row * char_height + line;
let index = ((y * self.column_count as u32 + x) * 4) as usize;
let pixel = &mut framebuffer[index..(index + 4)];
pixel.copy_from_slice(&color.to_rgba());
let index = ((y * self.column_count as u32 + x + 1) * 4) as usize;
let pixel = &mut framebuffer[index..(index + 4)];
pixel.copy_from_slice(&color.to_rgba());
}
}
}
}
pub fn redraw_screen(&mut self, memory: &mut Box<dyn Memory>, framebuffer: &mut [u8]) {
for row in 0..self.row_count {
for column in 0..self.column_count {
let address = (row as u16) * (self.column_count as u16) + (column as u16);
self.redraw(address, memory, framebuffer);
}
}
}
}
pub struct VicChipIO {
chip: Rc<RefCell<VicChip>>,
}
impl VicChipIO {
pub fn new(chip: Rc<RefCell<VicChip>>) -> Self {
Self { chip }
}
}
impl Memory for VicChipIO {
fn read(&mut self, address: u16) -> u8 {
let chip = self.chip.borrow();
match address & 0x0F {
0x0 => chip.left_draw_offset | (chip.scan_mode as u8) << 7,
0x1 => chip.top_draw_offset,
0x2 => chip.column_count | (chip.color_ram_mapping as u8) << 7,
0x3 => {
(chip.double_size_chars as u8)
| (chip.row_count << 1)
| ((chip.raster_counter & 0b1) as u8) << 7
}
0x4 => (chip.raster_counter >> 1) as u8,
0x5 => chip.character_address_top | (chip.vram_address_top << 4),
0x6 => chip.light_pen.read_x(),
0x7 => chip.light_pen.read_y(),
0x8 => chip.potentiometer_1,
0x9 => chip.potentiometer_2,
0xA => chip.speaker_alto.read(),
0xB => chip.speaker_tenor.read(),
0xC => chip.speaker_soprano.read(),
0xD => chip.speaker_noise.read(),
0xE => chip.speaker_volume | (chip.aux_color << 4),
0xF => chip.border_color | (chip.reverse_field as u8) << 3 | (chip.background_color << 4),
_ => unreachable!(),
}
}
fn write(&mut self, address: u16, value: u8) {
let mut chip = self.chip.borrow_mut();
match address & 0x0F {
0x0 => {
chip.scan_mode = (value & 0x80) != 0;
chip.left_draw_offset = value & 0x7F;
}
0x1 => chip.top_draw_offset = value,
0x2 => {
if value & 0x7F != chip.column_count {
chip.platform.request_window(WindowConfig::new(
(value & 0x7F) as u32 * 8,
chip.row_count as u32 * 8,
2.0,
));
}
chip.color_ram_mapping = (value & 0x80) != 0;
chip.column_count = value & 0x7F;
}
0x3 => {
chip.raster_counter = (chip.raster_counter & 0x1FE) | ((value & 0x80) as u16) >> 7;
if ((value >> 1) & 0x3F) != chip.row_count {
chip.platform.request_window(WindowConfig::new(
chip.column_count as u32 * 8,
((value >> 1) & 0x3F) as u32 * 8,
2.0,
));
}
chip.row_count = (value >> 1) & 0x3F;
chip.double_size_chars = (value & 0x01) != 0;
if chip.double_size_chars {
panic!("Double size characters not supported yet");
}
}
0x4 => chip.raster_counter = (chip.raster_counter & 0x1) | ((value as u16) << 1),
0x5 => {
chip.vram_address_top = (value >> 4) & 0x0F;
chip.character_address_top = value & 0x0F;
}
0x6 => chip.light_pen.write_x(value),
0x7 => chip.light_pen.write_y(value),
0x8 => chip.potentiometer_1 = value,
0x9 => chip.potentiometer_2 = value,
0xA => chip.speaker_alto.write(value),
0xB => chip.speaker_tenor.write(value),
0xC => chip.speaker_soprano.write(value),
0xD => chip.speaker_noise.write(value),
0xE => {
chip.speaker_volume = value & 0x0F;
chip.aux_color = (value >> 4) & 0x0F;
}
0xF => {
chip.border_color = value & 0x0F;
chip.reverse_field = (value & 0x08) != 0;
chip.background_color = (value >> 4) & 0x0F;
}
_ => unreachable!(),
}
}
fn reset(&mut self) {
self.chip.borrow_mut().reset();
}
fn poll(&mut self, _cycles_since_poll: u64, _total_cycle_count: u64) -> ActiveInterrupt {
ActiveInterrupt::None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vic_to_cpu_address() {
assert_eq!(0x8000, VicChip::vic_to_cpu_address(0x0000));
assert_eq!(0x8FFF, VicChip::vic_to_cpu_address(0x0FFF));
assert_eq!(0x9000, VicChip::vic_to_cpu_address(0x1000));
assert_eq!(0x9FFF, VicChip::vic_to_cpu_address(0x1FFF));
assert_eq!(0x0000, VicChip::vic_to_cpu_address(0x2000));
assert_eq!(0x0FFF, VicChip::vic_to_cpu_address(0x2FFF));
assert_eq!(0x1000, VicChip::vic_to_cpu_address(0x3000));
assert_eq!(0x1FFF, VicChip::vic_to_cpu_address(0x3FFF));
}
}