colour formats

This commit is contained in:
Alex Janka 2023-03-08 11:01:18 +11:00
parent 3fcaca4654
commit 4bae07f165
14 changed files with 114 additions and 77 deletions

View file

@ -19,13 +19,13 @@ struct EmuParams {}
struct EmuVars { struct EmuVars {
rx: AsyncHeapConsumer<[f32; 2]>, rx: AsyncHeapConsumer<[f32; 2]>,
sender: Sender<EmulatorMessage>, sender: Sender<EmulatorMessage>,
emulator_core: EmulatorCore, emulator_core: EmulatorCore<[u8; 4]>,
} }
#[derive(Default)] #[derive(Default)]
pub struct GameboyEmu { pub struct GameboyEmu {
vars: Option<EmuVars>, vars: Option<EmuVars>,
frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>, frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
} }
const ROM: &[u8; 32768] = include_bytes!("../../test-roms/Tetris.gb"); const ROM: &[u8; 32768] = include_bytes!("../../test-roms/Tetris.gb");

View file

@ -14,11 +14,11 @@ use nih_plug::prelude::*;
use pixels::{Pixels, SurfaceTexture}; use pixels::{Pixels, SurfaceTexture};
pub struct Emulator { pub struct Emulator {
frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>, frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
} }
impl Emulator { impl Emulator {
pub fn new(frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>) -> Self { pub fn new(frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>) -> Self {
Self { frame_receiver } Self { frame_receiver }
} }
} }
@ -61,11 +61,14 @@ impl Editor for Emulator {
pub struct EmulatorWindow { pub struct EmulatorWindow {
pix: Pixels, pix: Pixels,
scale: usize, scale: usize,
frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>, frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
} }
impl EmulatorWindow { impl EmulatorWindow {
fn new(window: &mut Window, frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>) -> Self { fn new(
window: &mut Window,
frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
) -> Self {
let info = WindowInfo::from_logical_size(Size::new(WIDTH as f64, HEIGHT as f64), 1.); let info = WindowInfo::from_logical_size(Size::new(WIDTH as f64, HEIGHT as f64), 1.);
let (pix, scale) = init_pixbuf(info, window); let (pix, scale) = init_pixbuf(info, window);
@ -105,7 +108,7 @@ impl WindowHandler for EmulatorWindow {
for (pixel, source) in for (pixel, source) in
self.pix.get_frame_mut().chunks_exact_mut(4).zip(scaled_buf) self.pix.get_frame_mut().chunks_exact_mut(4).zip(scaled_buf)
{ {
pixel.copy_from_slice(&source.to_be_bytes()); pixel.copy_from_slice(&source);
} }
self.pix.render().unwrap(); self.pix.render().unwrap();
} }
@ -124,21 +127,21 @@ impl WindowHandler for EmulatorWindow {
} }
pub struct EmulatorRenderer { pub struct EmulatorRenderer {
tx: Sender<Vec<u32>>, tx: Sender<Vec<[u8; 4]>>,
} }
impl EmulatorRenderer { impl EmulatorRenderer {
pub(super) fn new() -> (Self, Receiver<Vec<u32>>) { pub(super) fn new() -> (Self, Receiver<Vec<[u8; 4]>>) {
let (tx, rx) = mpsc::channel::<Vec<u32>>(); let (tx, rx) = mpsc::channel::<Vec<[u8; 4]>>();
(Self { tx }, rx) (Self { tx }, rx)
} }
} }
impl Renderer for EmulatorRenderer { impl Renderer<[u8; 4]> for EmulatorRenderer {
fn prepare(&mut self, _width: usize, _height: usize) {} fn prepare(&mut self, _width: usize, _height: usize) {}
#[allow(unused_must_use)] #[allow(unused_must_use)]
fn display(&mut self, buffer: &[u32]) { fn display(&mut self, buffer: &[[u8; 4]]) {
self.tx.send(buffer.to_vec()); self.tx.send(buffer.to_vec());
} }

View file

@ -1,3 +1,4 @@
use crate::processor::memory::mmio::gpu::Colour;
pub use crate::processor::memory::mmio::joypad::JoypadState; pub use crate::processor::memory::mmio::joypad::JoypadState;
pub use crate::{HEIGHT, WIDTH}; pub use crate::{HEIGHT, WIDTH};
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb}; use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
@ -12,10 +13,10 @@ pub enum RomFile {
Raw(Vec<u8>), Raw(Vec<u8>),
} }
pub trait Renderer: Send { pub trait Renderer<Format: From<Colour>>: Send {
fn prepare(&mut self, width: usize, height: usize); fn prepare(&mut self, width: usize, height: usize);
fn display(&mut self, buffer: &[u32]); fn display(&mut self, buffer: &[Format]);
fn set_title(&mut self, _title: String) {} fn set_title(&mut self, _title: String) {}

View file

@ -3,13 +3,17 @@
let_chains, let_chains,
slice_flatten, slice_flatten,
async_closure, async_closure,
bigint_helper_methods bigint_helper_methods,
associated_type_defaults
)] )]
use crate::{processor::memory::Memory, util::pause}; use crate::{processor::memory::Memory, util::pause};
use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile}; use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use processor::{memory::Rom, Cpu}; use processor::{
memory::{mmio::gpu::Colour, Rom},
Cpu,
};
use std::{ use std::{
fs::{self}, fs::{self},
io::{stdout, Write}, io::{stdout, Write},
@ -43,20 +47,20 @@ static VERBOSE: OnceCell<bool> = OnceCell::new();
pub const WIDTH: usize = 160; pub const WIDTH: usize = 160;
pub const HEIGHT: usize = 144; pub const HEIGHT: usize = 144;
pub struct EmulatorCore { pub struct EmulatorCore<ColourFormat: From<Colour> + Clone> {
receiver: Receiver<EmulatorMessage>, receiver: Receiver<EmulatorMessage>,
cpu: Cpu, cpu: Cpu<ColourFormat>,
cycle_num: usize, cycle_num: usize,
cycle_count: bool, cycle_count: bool,
} }
impl EmulatorCore { impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
pub fn init( pub fn init(
receiver: Receiver<EmulatorMessage>, receiver: Receiver<EmulatorMessage>,
options: Options, options: Options,
mut window: Box<dyn Renderer>, mut window: Box<dyn Renderer<ColourFormat>>,
output: AudioOutput, output: AudioOutput,
tile_window: Option<Box<dyn Renderer>>, tile_window: Option<Box<dyn Renderer<ColourFormat>>>,
) -> Self { ) -> Self {
VERBOSE.set(options.verbose).unwrap(); VERBOSE.set(options.verbose).unwrap();
@ -116,7 +120,7 @@ impl EmulatorCore {
) )
} }
fn new(receiver: Receiver<EmulatorMessage>, cpu: Cpu, cycle_count: bool) -> Self { fn new(receiver: Receiver<EmulatorMessage>, cpu: Cpu<ColourFormat>, cycle_count: bool) -> Self {
Self { Self {
receiver, receiver,
cpu, cpu,

View file

@ -1,9 +1,9 @@
use crate::{ use crate::{
processor::{Cpu, Direction, Flags, Reg8, SplitRegister}, processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, Reg8, SplitRegister},
util::{clear_bit, get_bit, set_bit}, util::{clear_bit, get_bit, set_bit},
}; };
impl Cpu { impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
pub(crate) fn and(&mut self, first: u8, second: u8) -> u8 { pub(crate) fn and(&mut self, first: u8, second: u8) -> u8 {
let result = first & second; let result = first & second;
self.set_or_clear_flag(Flags::Zero, result == 0x0); self.set_or_clear_flag(Flags::Zero, result == 0x0);

View file

@ -1,10 +1,10 @@
use crate::{ use crate::{
processor::{Cpu, Direction, Flags, SplitRegister}, processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, SplitRegister},
util::{as_signed, get_bit, get_rotation_carry, rotate, Nibbles}, util::{as_signed, get_bit, get_rotation_carry, rotate, Nibbles},
}; };
use std::ops::{BitAnd, BitOr}; use std::ops::{BitAnd, BitOr};
impl Cpu { impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
pub(crate) fn pop_word(&mut self) -> u16 { pub(crate) fn pop_word(&mut self) -> u16 {
let mut word: u16 = 0x0; let mut word: u16 = 0x0;
word.set_low(self.memory.get(self.reg.sp)); word.set_low(self.memory.get(self.reg.sp));

View file

@ -1,4 +1,4 @@
use self::mmio::{Apu, Gpu, Joypad, Serial, Timer}; use self::mmio::{gpu::Colour, Apu, Gpu, Joypad, Serial, Timer};
pub use self::rom::Rom; pub use self::rom::Rom;
use crate::{ use crate::{
connect::{AudioOutput, JoypadState, Renderer}, connect::{AudioOutput, JoypadState, Renderer},
@ -13,7 +13,7 @@ pub(crate) mod rom;
pub(crate) type Address = u16; pub(crate) type Address = u16;
pub struct Memory { pub struct Memory<ColourFormat: From<Colour> + Clone> {
bootrom: Option<Vec<u8>>, bootrom: Option<Vec<u8>>,
rom: Rom, rom: Rom,
ram: [u8; 8192], ram: [u8; 8192],
@ -23,20 +23,20 @@ pub struct Memory {
pub(super) ime_scheduled: u8, pub(super) ime_scheduled: u8,
dma_addr: u8, dma_addr: u8,
joypad: Joypad, joypad: Joypad,
gpu: Gpu, gpu: Gpu<ColourFormat>,
apu: Apu, apu: Apu,
serial: Serial, serial: Serial,
timers: Timer, timers: Timer,
} }
impl Memory { impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
pub fn init( pub fn init(
bootrom: Option<Vec<u8>>, bootrom: Option<Vec<u8>>,
rom: Rom, rom: Rom,
window: Box<dyn Renderer>, window: Box<dyn Renderer<ColourFormat>>,
output: AudioOutput, output: AudioOutput,
connect_serial: bool, connect_serial: bool,
tile_window: Option<Box<dyn Renderer>>, tile_window: Option<Box<dyn Renderer<ColourFormat>>>,
) -> Self { ) -> Self {
let serial = if connect_serial { let serial = if connect_serial {
Serial::default().connected() Serial::default().connected()
@ -228,7 +228,7 @@ impl Memory {
} }
} }
impl Cpu { impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
pub fn increment_timers(&mut self, machine_cycles: u8) { pub fn increment_timers(&mut self, machine_cycles: u8) {
let steps = (machine_cycles as usize) * 4; let steps = (machine_cycles as usize) * 4;

View file

@ -1,8 +1,8 @@
use self::{ use self::{
tile_window::TileWindow, tile_window::TileWindow,
types::{ types::{
Colour, DrawMode, GpuInterrupts, Lcdc, Oam, ObjPalette, ObjSize, Object, ObjectFlags, DrawMode, GpuInterrupts, Lcdc, Oam, ObjPalette, ObjSize, Object, ObjectFlags, Palette,
Palette, Stat, TiledataArea, TilemapArea, Vram, Stat, TiledataArea, TilemapArea, Vram,
}, },
}; };
use crate::{ use crate::{
@ -11,6 +11,7 @@ use crate::{
util::{clear_bit, get_bit}, util::{clear_bit, get_bit},
HEIGHT, WIDTH, HEIGHT, WIDTH,
}; };
pub use types::Colour;
mod addresses; mod addresses;
mod tile_window; mod tile_window;
@ -19,18 +20,18 @@ mod types;
const TILE_WINDOW_WIDTH: usize = 16 * 8; const TILE_WINDOW_WIDTH: usize = 16 * 8;
const TILE_WINDOW_HEIGHT: usize = 24 * 8; const TILE_WINDOW_HEIGHT: usize = 24 * 8;
pub struct Gpu { pub struct Gpu<Format: From<Colour>> {
pub buffer: Vec<u32>, pub buffer: Vec<Format>,
pub vram: Vram, pub vram: Vram,
pub oam: Oam, pub oam: Oam,
pub window: Box<dyn Renderer>, pub window: Box<dyn Renderer<Format>>,
is_bg_zero: Vec<bool>, is_bg_zero: Vec<bool>,
lcdc: Lcdc, lcdc: Lcdc,
stat: Stat, stat: Stat,
mode_clock: usize, mode_clock: usize,
scanline: u8, scanline: u8,
lyc: u8, lyc: u8,
tile_window: Option<TileWindow>, tile_window: Option<TileWindow<Format>>,
window_lc: u8, window_lc: u8,
has_window_been_enabled: bool, has_window_been_enabled: bool,
bg_palette: Palette, bg_palette: Palette,
@ -43,17 +44,21 @@ pub struct Gpu {
prev_stat: bool, prev_stat: bool,
} }
impl Gpu { impl<Format: From<Colour> + Clone> Gpu<Format> {
pub fn new(window: Box<dyn Renderer>, tile_window_renderer: Option<Box<dyn Renderer>>) -> Self { pub fn new(
window: Box<dyn Renderer<Format>>,
tile_window_renderer: Option<Box<dyn Renderer<Format>>>,
) -> Self {
let tile_window = if let Some(mut tile_window_renderer) = tile_window_renderer { let tile_window = if let Some(mut tile_window_renderer) = tile_window_renderer {
tile_window_renderer.prepare(TILE_WINDOW_WIDTH, TILE_WINDOW_HEIGHT); tile_window_renderer.prepare(TILE_WINDOW_WIDTH, TILE_WINDOW_HEIGHT);
Some(TileWindow::new(tile_window_renderer)) Some(TileWindow::new(tile_window_renderer))
} else { } else {
None None
}; };
let buffer = vec![Colour::Error.into(); WIDTH * HEIGHT];
Self { Self {
buffer: vec![0; WIDTH * HEIGHT], buffer,
vram: Vram::default(), vram: Vram::default(),
oam: Oam::default(), oam: Oam::default(),
window, window,
@ -162,7 +167,7 @@ impl Gpu {
*e = true; *e = true;
} }
for x in 0..WIDTH { for x in 0..WIDTH {
self.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 0, 255); self.buffer[(scanline as usize * WIDTH) + x] = Colour::Error.into();
} }
if self.lcdc.bg_window_enable { if self.lcdc.bg_window_enable {
self.render_scanline_bg(scanline); self.render_scanline_bg(scanline);
@ -175,7 +180,7 @@ impl Gpu {
} }
} else { } else {
for x in 0..WIDTH { for x in 0..WIDTH {
self.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 255, 255); self.buffer[(scanline as usize * WIDTH) + x] = Colour::Error.into();
} }
} }
if self.lcdc.obj_enable { if self.lcdc.obj_enable {
@ -295,7 +300,7 @@ impl Gpu {
if x_coord < WIDTH { if x_coord < WIDTH {
let buffer_index = (scanline as usize * WIDTH) + x_coord; let buffer_index = (scanline as usize * WIDTH) + x_coord;
if !object.flags.behind_bg_and_window || self.is_bg_zero[x_coord] { if !object.flags.behind_bg_and_window || self.is_bg_zero[x_coord] {
self.buffer[buffer_index] = colour.as_rgb(); self.buffer[buffer_index] = colour.into();
} }
} }
} }
@ -340,7 +345,7 @@ impl Gpu {
let (colour, is_zero) = self.bg_palette.map_bits(lsb, msb); let (colour, is_zero) = self.bg_palette.map_bits(lsb, msb);
self.is_bg_zero[x] = is_zero; self.is_bg_zero[x] = is_zero;
self.buffer[(scanline as usize * WIDTH) + x] = colour.as_rgb(); self.buffer[(scanline as usize * WIDTH) + x] = colour.into();
} }
} }

View file

@ -2,10 +2,10 @@ use crate::util::{get_bit, set_or_clear_bit};
use super::{ use super::{
types::{DrawMode, ObjSize, Palette, TiledataArea, TilemapArea}, types::{DrawMode, ObjSize, Palette, TiledataArea, TilemapArea},
Gpu, Colour, Gpu,
}; };
impl Gpu { impl<Format: From<Colour>> Gpu<Format> {
pub fn update_lcdc(&mut self, data: u8) { pub fn update_lcdc(&mut self, data: u8) {
self.lcdc.enable = get_bit(data, 7); self.lcdc.enable = get_bit(data, 7);
self.lcdc.window_tilemap = if get_bit(data, 6) { self.lcdc.window_tilemap = if get_bit(data, 6) {

View file

@ -4,23 +4,24 @@ use crate::{
util::get_bit, util::get_bit,
}; };
use super::types::Vram; use super::{types::Vram, Colour};
pub(super) struct TileWindow { pub(super) struct TileWindow<Format: From<Colour>> {
sprite_buffer: Vec<u32>, sprite_buffer: Vec<Format>,
sprite_renderer: Box<dyn Renderer>, sprite_renderer: Box<dyn Renderer<Format>>,
} }
impl TileWindow { impl<Format: From<Colour>> TileWindow<Format> {
pub(super) fn new(window: Box<dyn Renderer>) -> Self { pub(super) fn new(window: Box<dyn Renderer<Format>>) -> Self {
let mut sprite_buffer = Vec::new();
sprite_buffer.reserve_exact(TILE_WINDOW_WIDTH * TILE_WINDOW_HEIGHT);
Self { Self {
sprite_buffer: vec![0; TILE_WINDOW_WIDTH * TILE_WINDOW_HEIGHT], sprite_buffer,
sprite_renderer: window, sprite_renderer: window,
} }
} }
}
impl TileWindow {
pub(super) fn draw_sprite_window(&mut self, palette: Palette, memory: &Vram) { pub(super) fn draw_sprite_window(&mut self, palette: Palette, memory: &Vram) {
for tile_y in 0..16 { for tile_y in 0..16 {
self.draw_row( self.draw_row(
@ -66,7 +67,7 @@ impl TileWindow {
let colour = palette.map_bits(lsb, msb); let colour = palette.map_bits(lsb, msb);
self.sprite_buffer[real_px_x + (real_px_y * TILE_WINDOW_WIDTH)] = self.sprite_buffer[real_px_x + (real_px_y * TILE_WINDOW_WIDTH)] =
colour.0.as_rgb(); colour.0.into();
} }
} }
} }

View file

@ -84,28 +84,40 @@ impl Default for Lcdc {
} }
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub(super) enum Colour { pub enum Colour {
White, White,
LightGray, LightGray,
DarkGray, DarkGray,
Black, Black,
Error,
}
impl From<Colour> for u32 {
fn from(value: Colour) -> Self {
let rgb = value.rgb_bytes();
let (r, g, b) = (rgb.0 as u32, rgb.1 as u32, rgb.2 as u32);
(r << 16) | (g << 8) | b
}
}
impl From<Colour> for [u8; 4] {
fn from(value: Colour) -> Self {
let (r, g, b) = value.rgb_bytes();
[r, g, b, 0xFF]
}
} }
impl Colour { impl Colour {
pub(super) fn as_rgb(&self) -> u32 { fn rgb_bytes(&self) -> (u8, u8, u8) {
match self { match self {
Colour::White => Self::from_u8_rgb(0xFF, 0xFF, 0xFF), Colour::White => (0xFF, 0xFF, 0xFF),
Colour::LightGray => Self::from_u8_rgb(0xAA, 0xAA, 0xAA), Colour::LightGray => (0xAA, 0xAA, 0xAA),
Colour::DarkGray => Self::from_u8_rgb(0x55, 0x55, 0x55), Colour::DarkGray => (0x55, 0x55, 0x55),
Colour::Black => Self::from_u8_rgb(0x00, 0x00, 0x00), Colour::Black => (0x00, 0x00, 0x00),
Colour::Error => (0xFF, 0x00, 0x00),
} }
} }
pub(super) fn from_u8_rgb(r: u8, g: u8, b: u8) -> u32 {
let (r, g, b) = (r as u32, g as u32, b as u32);
(r << 16) | (g << 8) | b
}
pub(super) fn from_bits(first: bool, second: bool) -> Colour { pub(super) fn from_bits(first: bool, second: bool) -> Colour {
match (first, second) { match (first, second) {
(true, true) => Colour::Black, (true, true) => Colour::Black,
@ -121,6 +133,7 @@ impl Colour {
Colour::LightGray => 0b10, Colour::LightGray => 0b10,
Colour::DarkGray => 0b01, Colour::DarkGray => 0b01,
Colour::Black => 0b11, Colour::Black => 0b11,
Colour::Error => 0b00,
} }
} }
} }

View file

@ -1,4 +1,4 @@
use self::memory::{Interrupt, Memory}; use self::memory::{mmio::gpu::Colour, Interrupt, Memory};
use crate::verbose_println; use crate::verbose_println;
mod instructions; mod instructions;
@ -18,8 +18,8 @@ pub(crate) enum Direction {
Right, Right,
} }
pub struct Cpu { pub struct Cpu<ColourFormat: From<Colour> + Clone> {
pub memory: Memory, pub memory: Memory<ColourFormat>,
pub reg: Registers, pub reg: Registers,
pub last_instruction: u8, pub last_instruction: u8,
last_instruction_addr: u16, last_instruction_addr: u16,
@ -27,8 +27,8 @@ pub struct Cpu {
should_halt_bug: bool, should_halt_bug: bool,
} }
impl Cpu { impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
pub fn new(mut memory: Memory, run_bootrom: bool) -> Self { pub fn new(mut memory: Memory<ColourFormat>, run_bootrom: bool) -> Self {
if !run_bootrom { if !run_bootrom {
memory.cpu_ram_init(); memory.cpu_ram_init();
} }

View file

@ -6,7 +6,9 @@ use crate::{
util::as_signed, util::as_signed,
}; };
impl Cpu { use super::memory::mmio::gpu::Colour;
impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
pub fn run_opcode(&mut self, opcode: u8) -> u8 { pub fn run_opcode(&mut self, opcode: u8) -> u8 {
match opcode { match opcode {
0x00 => { 0x00 => {

View file

@ -1,4 +1,7 @@
use crate::{processor::Direction, PAUSE_ENABLED, PAUSE_QUEUED, VERBOSE}; use crate::{
processor::{memory::mmio::gpu::Colour, Direction},
PAUSE_ENABLED, PAUSE_QUEUED, VERBOSE,
};
use std::{io, mem::transmute}; use std::{io, mem::transmute};
#[macro_export] #[macro_export]
@ -122,7 +125,12 @@ impl Nibbles for u8 {
} }
} }
pub fn scale_buffer(buffer: &[u32], width: usize, height: usize, factor: usize) -> Vec<u32> { pub fn scale_buffer<T: From<Colour> + Copy>(
buffer: &[T],
width: usize,
height: usize,
factor: usize,
) -> Vec<T> {
let mut v = vec![]; let mut v = vec![];
for y in 0..height { for y in 0..height {
for _ in 0..factor { for _ in 0..factor {