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 {
rx: AsyncHeapConsumer<[f32; 2]>,
sender: Sender<EmulatorMessage>,
emulator_core: EmulatorCore,
emulator_core: EmulatorCore<[u8; 4]>,
}
#[derive(Default)]
pub struct GameboyEmu {
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");

View file

@ -14,11 +14,11 @@ use nih_plug::prelude::*;
use pixels::{Pixels, SurfaceTexture};
pub struct Emulator {
frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>,
frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
}
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 }
}
}
@ -61,11 +61,14 @@ impl Editor for Emulator {
pub struct EmulatorWindow {
pix: Pixels,
scale: usize,
frame_receiver: Arc<Mutex<Option<Receiver<Vec<u32>>>>>,
frame_receiver: Arc<Mutex<Option<Receiver<Vec<[u8; 4]>>>>>,
}
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 (pix, scale) = init_pixbuf(info, window);
@ -105,7 +108,7 @@ impl WindowHandler for EmulatorWindow {
for (pixel, source) in
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();
}
@ -124,21 +127,21 @@ impl WindowHandler for EmulatorWindow {
}
pub struct EmulatorRenderer {
tx: Sender<Vec<u32>>,
tx: Sender<Vec<[u8; 4]>>,
}
impl EmulatorRenderer {
pub(super) fn new() -> (Self, Receiver<Vec<u32>>) {
let (tx, rx) = mpsc::channel::<Vec<u32>>();
pub(super) fn new() -> (Self, Receiver<Vec<[u8; 4]>>) {
let (tx, rx) = mpsc::channel::<Vec<[u8; 4]>>();
(Self { tx }, rx)
}
}
impl Renderer for EmulatorRenderer {
impl Renderer<[u8; 4]> for EmulatorRenderer {
fn prepare(&mut self, _width: usize, _height: usize) {}
#[allow(unused_must_use)]
fn display(&mut self, buffer: &[u32]) {
fn display(&mut self, buffer: &[[u8; 4]]) {
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::{HEIGHT, WIDTH};
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
@ -12,10 +13,10 @@ pub enum RomFile {
Raw(Vec<u8>),
}
pub trait Renderer: Send {
pub trait Renderer<Format: From<Colour>>: Send {
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) {}

View file

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

View file

@ -1,9 +1,9 @@
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},
};
impl Cpu {
impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
pub(crate) fn and(&mut self, first: u8, second: u8) -> u8 {
let result = first & second;
self.set_or_clear_flag(Flags::Zero, result == 0x0);

View file

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

View file

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

View file

@ -4,23 +4,24 @@ use crate::{
util::get_bit,
};
use super::types::Vram;
use super::{types::Vram, Colour};
pub(super) struct TileWindow {
sprite_buffer: Vec<u32>,
sprite_renderer: Box<dyn Renderer>,
pub(super) struct TileWindow<Format: From<Colour>> {
sprite_buffer: Vec<Format>,
sprite_renderer: Box<dyn Renderer<Format>>,
}
impl TileWindow {
pub(super) fn new(window: Box<dyn Renderer>) -> Self {
impl<Format: From<Colour>> TileWindow<Format> {
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 {
sprite_buffer: vec![0; TILE_WINDOW_WIDTH * TILE_WINDOW_HEIGHT],
sprite_buffer,
sprite_renderer: window,
}
}
}
impl TileWindow {
pub(super) fn draw_sprite_window(&mut self, palette: Palette, memory: &Vram) {
for tile_y in 0..16 {
self.draw_row(
@ -66,7 +67,7 @@ impl TileWindow {
let colour = palette.map_bits(lsb, msb);
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)]
pub(super) enum Colour {
pub enum Colour {
White,
LightGray,
DarkGray,
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 {
pub(super) fn as_rgb(&self) -> u32 {
fn rgb_bytes(&self) -> (u8, u8, u8) {
match self {
Colour::White => Self::from_u8_rgb(0xFF, 0xFF, 0xFF),
Colour::LightGray => Self::from_u8_rgb(0xAA, 0xAA, 0xAA),
Colour::DarkGray => Self::from_u8_rgb(0x55, 0x55, 0x55),
Colour::Black => Self::from_u8_rgb(0x00, 0x00, 0x00),
Colour::White => (0xFF, 0xFF, 0xFF),
Colour::LightGray => (0xAA, 0xAA, 0xAA),
Colour::DarkGray => (0x55, 0x55, 0x55),
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 {
match (first, second) {
(true, true) => Colour::Black,
@ -121,6 +133,7 @@ impl Colour {
Colour::LightGray => 0b10,
Colour::DarkGray => 0b01,
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;
mod instructions;
@ -18,8 +18,8 @@ pub(crate) enum Direction {
Right,
}
pub struct Cpu {
pub memory: Memory,
pub struct Cpu<ColourFormat: From<Colour> + Clone> {
pub memory: Memory<ColourFormat>,
pub reg: Registers,
pub last_instruction: u8,
last_instruction_addr: u16,
@ -27,8 +27,8 @@ pub struct Cpu {
should_halt_bug: bool,
}
impl Cpu {
pub fn new(mut memory: Memory, run_bootrom: bool) -> Self {
impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
pub fn new(mut memory: Memory<ColourFormat>, run_bootrom: bool) -> Self {
if !run_bootrom {
memory.cpu_ram_init();
}

View file

@ -6,7 +6,9 @@ use crate::{
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 {
match opcode {
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};
#[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![];
for y in 0..height {
for _ in 0..factor {