use crate::core::chips::kr580vg75::Kr580Vg75;
use serde::{Deserialize, Serialize};
pub const CHAR_WIDTH: usize = 6;
const DEFAULT_CHARS_PER_ROW: usize = 78;
const DEFAULT_ROWS_PER_SCREEN: usize = 30;
const DEFAULT_LINES_PER_ROW: usize = 10;
const FONT_ALT_BANK_OFFSET: usize = 128;
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ColorMode {
Color,
Grayscale,
Bw,
}
const GRAYSCALE_PALETTE: [[u8; 4]; 8] = [
[0x00, 0x00, 0x00, 255],
[0x82, 0x82, 0x82, 255],
[0xC5, 0xC5, 0xC5, 255],
[0xEE, 0xEE, 0xEE, 255],
[0x58, 0x58, 0x58, 255],
[0xAE, 0xAE, 0xAE, 255],
[0xDF, 0xDF, 0xDF, 255],
[0xFF, 0xFF, 0xFF, 255],
];
pub struct VideoRenderer {
pub chargen_rom: Vec<u8>,
pub color_mode: ColorMode,
gigascreen: bool,
width: u32,
height: u32,
frame_buffer: Vec<u8>,
prev_frame_buffer: Vec<u8>,
}
impl VideoRenderer {
pub fn new(chargen_rom: Vec<u8>, color_mode: ColorMode, gigascreen: bool) -> Self {
let width = (DEFAULT_CHARS_PER_ROW * CHAR_WIDTH) as u32;
let height = (DEFAULT_ROWS_PER_SCREEN * DEFAULT_LINES_PER_ROW) as u32;
Self {
chargen_rom,
color_mode,
gigascreen,
width,
height,
frame_buffer: vec![0; (width * height * 4) as usize],
prev_frame_buffer: vec![0; (width * height * 4) as usize],
}
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn frame_buffer(&self) -> &[u8] {
&self.frame_buffer
}
pub fn render_frame(&mut self, vg75: &Kr580Vg75, font_banks: &[bool; 64]) -> bool {
let parsed_frame = vg75.parsed_frame();
let max_rows = parsed_frame.len();
let max_chars = parsed_frame[0].len();
let n_rows = (vg75.n_rows() as usize).min(max_rows);
let n_chars = (vg75.n_chars() as usize).min(max_chars);
let n_lines = vg75.n_lines() as usize;
let new_width = ((n_chars * CHAR_WIDTH) as u32).max(CHAR_WIDTH as u32);
let new_height = ((n_rows * n_lines) as u32).max(1);
let mut size_changed = false;
if new_width != self.width || new_height != self.height {
self.width = new_width;
self.height = new_height;
let buf_size = (self.width * self.height * 4) as usize;
self.frame_buffer.resize(buf_size, 0);
self.prev_frame_buffer.resize(buf_size, 0);
size_changed = true;
}
let bg_color = [0, 0, 0, 255];
for px in self.frame_buffer.chunks_exact_mut(4) {
px.copy_from_slice(&bg_color);
}
for (row, frame_row) in parsed_frame.iter().enumerate().take(n_rows) {
let chargen = if font_banks[row % 64] {
FONT_ALT_BANK_OFFSET
} else {
0
};
for ln in 0..n_lines {
let lc = if vg75.font_down() {
if ln != 0 {
ln - 1
} else {
n_lines.saturating_sub(1)
}
} else {
ln
};
let py = row * n_lines + ln;
if py >= self.height as usize {
continue;
}
let px_base_y = py * (self.width as usize);
for x in 0..n_chars {
let sym = &frame_row[x];
let vsp = sym.get_vsp(ln);
let lten = sym.get_lten(ln);
let is_bw = self.color_mode == ColorMode::Bw;
let attr_sym = if is_bw && x < n_chars - 1 {
&frame_row[x + 1]
} else {
sym
};
let hglt = attr_sym.hglt();
let gpa0 = attr_sym.gpa0();
let gpa1 = attr_sym.gpa1();
let rvv = attr_sym.rvv();
let fg_color = match self.color_mode {
ColorMode::Grayscale => {
let mut index = 0;
if !hglt {
index |= 1;
}
if !gpa1 {
index |= 2;
}
if !gpa0 {
index |= 4;
}
GRAYSCALE_PALETTE[index]
}
ColorMode::Bw => {
if hglt {
[0xFF, 0xFF, 0xFF, 255]
} else {
[0xC0, 0xC0, 0xC0, 255]
}
}
ColorMode::Color => [
if hglt { 0x00 } else { 0xFF },
if gpa1 { 0x00 } else { 0xFF },
if gpa0 { 0x00 } else { 0xFF },
255,
],
};
let char_idx = ((sym.chr as usize) + chargen) & 0xFF;
let row_data = if !vsp {
self.chargen_rom
.get(char_idx * 8 + (lc & 7))
.copied()
.unwrap_or(0xFF)
} else {
0xFF
};
let px_base = x * CHAR_WIDTH;
for col in 0..CHAR_WIDTH {
let px = px_base + col;
if px >= self.width as usize {
continue;
}
let pixel_bit = (row_data >> (5 - col)) & 1;
let mut pixel_on = pixel_bit == 0;
if lten {
pixel_on = true;
}
if rvv {
pixel_on = !pixel_on;
}
if pixel_on {
let px_idx = (px_base_y + px) * 4;
self.frame_buffer[px_idx..px_idx + 4].copy_from_slice(&fg_color);
}
}
}
}
}
if self.gigascreen {
if size_changed {
self.prev_frame_buffer.copy_from_slice(&self.frame_buffer);
} else {
for (curr, prev) in self
.frame_buffer
.chunks_exact_mut(4)
.zip(self.prev_frame_buffer.chunks_exact_mut(4))
{
let r = ((curr[0] as u16 + prev[0] as u16) >> 1) as u8;
let g = ((curr[1] as u16 + prev[1] as u16) >> 1) as u8;
let b = ((curr[2] as u16 + prev[2] as u16) >> 1) as u8;
prev[0..3].copy_from_slice(&curr[0..3]);
curr[0] = r;
curr[1] = g;
curr[2] = b;
}
}
}
size_changed
}
}