use serde::{Deserialize, Serialize};
use super::{base_system::BaseSystem, graphics::GraphicsDriver};
use super::graphics::GraphicsType;
pub(crate) const SCREEN0: u8 = 0;
pub(crate) const SCREEN1: u8 = 1;
pub(crate) const SCREEN2: u8 = 2;
pub(crate) const SCREEN3: u8 = 3;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct VdpData {
screen_enabled: bool,
screen_mode: u8,
value_read: u8,
write_state: bool,
enabled_interrupts: bool,
registers: [u8; 10],
write_to_vram: bool,
vram: Vec<u8>, pointer_vram: u16,
status_reg: u8,
magnif_num: u8,
}
impl Default for VdpData {
fn default() -> Self {
Self::new()
}
}
impl VdpData {
pub fn new() -> Self {
Self {
screen_enabled: false,
screen_mode: 0,
value_read: 0,
write_state: false,
enabled_interrupts: false,
registers: [0; 10],
write_to_vram: false,
vram: vec![0; 0x10000],
pointer_vram: 0,
status_reg: 0,
magnif_num: 0,
}
}
}
pub struct Vdp<'a> {
pub(crate) data: VdpData,
graphics: GraphicsDriver<'a>,
}
impl Vdp<'_> {
pub fn new(graphics_type: GraphicsType, quality: bool) -> Self {
let graphics = graphics_type.create(quality);
Self {
data: VdpData::default(),
graphics,
}
}
pub fn has_enabled_interrupts(&self) -> bool {
self.data.enabled_interrupts
}
pub fn set_frame_flag(&mut self) {
self.data.status_reg |= 0x80;
}
pub fn update_registers(&mut self) {
self.data.screen_enabled = self.data.registers[1] & 0x40 != 0;
self.data.enabled_interrupts = self.data.registers[1] & 0x20 != 0;
let m1 = self.data.registers[1] & 0x10 != 0;
let m2 = self.data.registers[1] & 0x08 != 0;
let m3 = self.data.registers[0] & 0x02 != 0;
let m4 = self.data.registers[0] & 0x04 != 0;
let m5 = self.data.registers[0] & 0x08 != 0;
let scm = self.data.screen_mode;
if !m4 && !m5 {
if !m1 && !m2 && !m3 {
self.data.screen_mode = SCREEN1;
}
if m1 && !m2 && !m3 {
self.data.screen_mode = SCREEN0;
}
if !m1 && m2 && !m3 {
self.data.screen_mode = SCREEN3;
}
if !m1 && !m2 && m3 {
self.data.screen_mode = SCREEN2;
}
}
if scm != self.data.screen_mode {
log::info!("Change screen mode: {}", self.data.screen_mode);
self.graphics.set_logical_resolution(self.data.screen_mode);
}
}
pub fn write_port(&mut self, ad: u8, mut val: u8) {
match ad {
0x99 => {
if !self.data.write_state {
self.data.value_read = val;
self.data.write_state = true;
} else {
self.data.write_state = false;
if val & 0x80 != 0 {
let regn = val - 128;
self.data.registers[regn as usize] = self.data.value_read;
self.update_registers();
} else {
self.data.write_to_vram = val & 0x40 != 0;
val &= 0xBF;
self.data.pointer_vram = 0;
self.data.pointer_vram |= self.data.value_read as u16;
self.data.pointer_vram |= (val as u16) << 8;
}
}
}
0x98 => {
self.data.vram[self.data.pointer_vram as usize] = val;
self.data.pointer_vram += 1;
}
_ => {
log::error!("Not implemented: VDP: Out({:02x}, {:02x})", ad, val);
unimplemented!()
}
}
}
pub fn read_port(&mut self, ad: u8) -> u8 {
match ad {
0x98 => {
let r = self.data.vram[self.data.pointer_vram as usize];
self.data.pointer_vram += 1;
r
}
0x99 => {
let r = self.data.status_reg;
self.data.status_reg &= 0x7F; r
}
_ => {
log::error!("Not implemented: VDP: In({:02x})", ad);
0
}
}
}
pub fn graphics_render(&mut self, sys: &mut BaseSystem) {
self.graphics.render(sys);
}
pub fn update_buffer(&mut self) {
if !self.data.screen_enabled {
return;
}
let name_table_addr = (self.data.registers[2] as usize) << 10;
let pat_table_addr = (self.data.registers[4] as usize) << 11;
let color_table_addr = (self.data.registers[3] as usize) << 6;
match self.data.screen_mode {
SCREEN0 => {
let color1 = (self.data.registers[7] & 0xF0) >> 4;
let color2 = self.data.registers[7] & 0x0F;
for y in 0..24 {
for x in 0..40 {
let name_table = &self.data.vram[name_table_addr..];
let pt = name_table[(x + y * 40) as usize] as u16 * 8;
self.draw_patterns_s0(x * 8, y * 8, pt, pat_table_addr, color1, color2);
}
}
}
SCREEN1 => {
for y in 0..24 {
for x in 0..32 {
let name_table = &self.data.vram[name_table_addr..];
let pat = name_table[(x + y * 32) as usize];
let color_table = &self.data.vram[color_table_addr..];
let color = color_table[(pat / 8) as usize];
self.draw_patterns_s1(x * 8, y * 8, pat as u16 * 8, pat_table_addr, color);
}
}
self.draw_sprites();
}
SCREEN2 => {
let pat_table_addr = ((self.data.registers[4] & 0x04) as usize) << 11;
let color_table_addr = ((self.data.registers[3] & 0x80) as usize) << 6;
for y in 0..24 {
for x in 0..32 {
let name_table = &self.data.vram[name_table_addr..];
let pat = name_table[(x + y * 32) as usize];
self.draw_patterns_s2(
x * 8,
y * 8,
pat as u16 * 8,
pat_table_addr,
color_table_addr,
);
}
}
self.draw_sprites();
}
SCREEN3 => {
log::error!("Drawing in screen3 not implemented yet");
}
_ => {
panic!("RenderScreen: impossible mode");
}
}
}
pub fn get_vram_data(&self, base_addr: usize, offset: usize) -> u8 {
self.data.vram[base_addr + offset]
}
pub fn draw_patterns_s0(
&mut self,
x: u16,
y: u16,
pt: u16,
pat_table_addr: usize,
color1: u8,
color2: u8,
) {
for i in 0..8 {
let b = self.get_vram_data(pat_table_addr, (i + pt) as usize);
let mut xx = 0;
let mut mask = 0x80;
while mask > 0 {
if mask & b != 0 {
self.graphics
.draw_pixel((x + xx).into(), (y + i).into(), color1.into());
} else {
self.graphics
.draw_pixel((x + xx).into(), (y + i).into(), color2.into());
}
xx += 1;
mask >>= 1;
}
}
}
fn draw_patterns_s1(&mut self, x: u16, y: u16, pt: u16, pat_table_addr: usize, color: u8) {
let color1 = (color & 0xF0) as usize >> 4;
let color2 = (color & 0x0F) as usize;
for i in 0..8 {
let b = self.get_vram_data(pat_table_addr, (i + pt) as usize);
let mut xx = 0;
let mut mask = 0x80;
while mask > 0 {
if mask & b != 0 {
self.graphics
.draw_pixel((x + xx).into(), (y + i).into(), color1);
} else {
self.graphics
.draw_pixel((x + xx).into(), (y + i).into(), color2);
}
xx += 1;
mask >>= 1;
}
}
}
fn draw_patterns_s2(
&mut self,
x: u16,
y: u16,
pt: u16,
pat_table_addr: usize,
color_table_addr: usize,
) {
for i in 0..8 {
let idx = if y < 64 {
(i + pt) as usize
} else if y < 128 {
(i + pt + 2048) as usize
} else {
(i + pt + 2048 * 2) as usize
};
let b = self.get_vram_data(pat_table_addr, idx);
let color = self.get_vram_data(color_table_addr, idx);
let color1 = (color & 0xF0) >> 4;
let color2 = color & 0x0F;
let mut xx = 0;
let mut mask = 0x80;
while mask > 0 {
if mask & b != 0 {
self.graphics
.draw_pixel((x + xx).into(), (y + i).into(), color1.into());
} else {
self.graphics
.draw_pixel((x + xx).into(), (y + i).into(), color2.into());
}
xx += 1;
mask >>= 1;
}
}
}
fn draw_sprites(&mut self) {
let spr_table_addr = (self.data.registers[5] as usize) << 7;
let spr_pat_table_addr = (self.data.registers[6] as usize) << 11;
let magnif = (self.data.registers[1] & 0x01) != 0;
let spr16x16 = (self.data.registers[1] & 0x02) != 0;
self.data.magnif_num = 0;
let mut i = 0;
let mut j = 0;
while i < 32 {
let ypos = self.data.vram[spr_table_addr + j] as u16;
if ypos == 0xd0 {
return;
}
let mut xpos = self.data.vram[spr_table_addr + j + 1] as u16;
let pattern = self.data.vram[spr_table_addr + j + 2];
let ec = (self.data.vram[spr_table_addr + j + 3] & 0x80) != 0;
if ec {
xpos -= 32
}
let color = self.data.vram[spr_table_addr + j + 3] & 0x0F;
if !spr16x16 {
let pattern_addr = spr_pat_table_addr + (pattern as usize) * 8;
self.draw_spr(magnif, xpos, ypos, pattern_addr, color);
} else {
let pattern_addr = spr_pat_table_addr + ((pattern >> 2) as usize) * 8 * 4;
self.draw_spr(magnif, xpos, ypos, pattern_addr, color);
self.draw_spr(magnif, xpos, ypos + 8, pattern_addr + 8, color);
self.draw_spr(magnif, xpos + 8, ypos, pattern_addr + 16, color);
self.draw_spr(magnif, xpos + 8, ypos + 8, pattern_addr + 24, color);
}
i += 1;
j += 4;
}
}
fn draw_spr(
&mut self,
magnif: bool,
xpos: u16,
ypos: u16,
pattern_addr: usize,
color: u8,
) {
if ypos > 191 {
return;
}
for y in 0..8 {
let b = self.get_vram_data(pattern_addr, y as usize);
let mut x = 0;
let mut mask = 0x80;
while mask > 0 {
if magnif && x == 0 && y == 0 {
if self.data.magnif_num == 4 {
self.data.magnif_num = 1;
} else {
self.data.magnif_num += 1;
}
}
if mask & b != 0 {
if magnif {
let mut x = x * 2;
let mut y = y * 2;
if self.data.magnif_num == 2 || self.data.magnif_num == 4 {
y += 8;
}
if self.data.magnif_num == 3 || self.data.magnif_num == 4 {
x += 8;
}
self.graphics.draw_pixel(
(xpos + x).into(),
(ypos + y).into(),
color.into(),
);
self.graphics.draw_pixel(
(xpos + x + 1).into(),
(ypos + y).into(),
color.into(),
);
self.graphics.draw_pixel(
(xpos + x).into(),
(ypos + y + 1).into(),
color.into(),
);
self.graphics.draw_pixel(
(xpos + x + 1).into(),
(ypos + y + 1).into(),
color.into(),
);
} else {
self.graphics.draw_pixel(
(xpos + x).into(),
(ypos + y).into(),
color.into(),
);
}
}
x += 1;
mask >>= 1;
}
}
}
pub fn get_data(&self) -> VdpData {
self.data.clone()
}
pub fn set_data(&mut self, data: VdpData) {
self.data = data;
}
}