feat: emu slowly draws scanline to pixelbuffer
This commit is contained in:
parent
4663e8c960
commit
fb38ef3f68
|
@ -33,7 +33,7 @@ fn main() -> Result<()> {
|
||||||
let ppu = game_boy.get_ppu();
|
let ppu = game_boy.get_ppu();
|
||||||
let frame = pixels.get_frame();
|
let frame = pixels.get_frame();
|
||||||
|
|
||||||
ppu.draw(frame);
|
ppu.copy_to_gui(frame);
|
||||||
|
|
||||||
if pixels
|
if pixels
|
||||||
.render()
|
.render()
|
||||||
|
@ -57,7 +57,7 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
// Emulation
|
// Emulation
|
||||||
let _cycles = game_boy.step();
|
let _cycles = game_boy.step();
|
||||||
// window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
120
src/ppu.rs
120
src/ppu.rs
|
@ -17,11 +17,11 @@ pub struct Ppu {
|
||||||
|
|
||||||
impl Ppu {
|
impl Ppu {
|
||||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||||
unimplemented!()
|
self.vram[addr as usize - 0x8000]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_byte(&mut self, addr: u16, byte: u8) {
|
pub fn write_byte(&mut self, addr: u16, byte: u8) {
|
||||||
unimplemented!()
|
self.vram[addr as usize - 0x8000] = byte;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ impl Ppu {
|
||||||
match self.stat.mode() {
|
match self.stat.mode() {
|
||||||
Mode::OamScan => {
|
Mode::OamScan => {
|
||||||
if self.cycles >= 80.into() {
|
if self.cycles >= 80.into() {
|
||||||
|
self.cycles %= 80;
|
||||||
self.stat.set_mode(Mode::Drawing);
|
self.stat.set_mode(Mode::Drawing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,15 +47,19 @@ impl Ppu {
|
||||||
|
|
||||||
// TODO: This 172 needs to be variable somehow?
|
// TODO: This 172 needs to be variable somehow?
|
||||||
if self.cycles >= 172.into() {
|
if self.cycles >= 172.into() {
|
||||||
|
self.cycles %= 172;
|
||||||
|
|
||||||
self.stat.set_mode(Mode::HBlank);
|
self.stat.set_mode(Mode::HBlank);
|
||||||
|
self.draw_scanline();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::HBlank => {
|
Mode::HBlank => {
|
||||||
// We've reached the end of a scanline
|
// We've reached the end of a scanline
|
||||||
if self.cycles >= 456.into() {
|
if self.cycles >= 200.into() {
|
||||||
|
self.cycles %= 200;
|
||||||
self.pos.line_y += 1;
|
self.pos.line_y += 1;
|
||||||
|
|
||||||
let next_mode = if self.pos.line_y >= 143 {
|
let next_mode = if self.pos.line_y >= 144 {
|
||||||
Mode::VBlank
|
Mode::VBlank
|
||||||
} else {
|
} else {
|
||||||
Mode::OamScan
|
Mode::OamScan
|
||||||
|
@ -70,7 +75,7 @@ impl Ppu {
|
||||||
self.cycles %= 456;
|
self.cycles %= 456;
|
||||||
self.pos.line_y += 1;
|
self.pos.line_y += 1;
|
||||||
|
|
||||||
if self.pos.line_y >= 153 {
|
if self.pos.line_y == 154 {
|
||||||
self.stat.set_mode(Mode::OamScan);
|
self.stat.set_mode(Mode::OamScan);
|
||||||
self.pos.line_y = 0;
|
self.pos.line_y = 0;
|
||||||
}
|
}
|
||||||
|
@ -79,7 +84,53 @@ impl Ppu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&self, frame: &mut [u8]) {
|
fn draw_scanline(&mut self) {
|
||||||
|
let mut scanline: [u8; GB_WIDTH * 4] = [0; GB_WIDTH * 4];
|
||||||
|
|
||||||
|
let scroll_y = self.pos.scroll_y;
|
||||||
|
let scroll_x = self.pos.scroll_x;
|
||||||
|
// let window_y = self.pos.window_y;
|
||||||
|
// let window_x = self.pos.window_x - 7;
|
||||||
|
|
||||||
|
let tile_map_addr = match self.lcd_control.bg_tile_map_addr() {
|
||||||
|
TileMapAddress::X9800 => 0x9800,
|
||||||
|
TileMapAddress::X9C00 => 0x9C00,
|
||||||
|
};
|
||||||
|
|
||||||
|
let y_pos: usize = scroll_y as usize + self.pos.line_y as usize;
|
||||||
|
let tile_row: usize = (y_pos as usize / 8) * 32;
|
||||||
|
|
||||||
|
for (i, chunk) in scanline.chunks_mut(4).enumerate() {
|
||||||
|
let x_pos = i as u8 + scroll_x;
|
||||||
|
let tile_column = x_pos / 8;
|
||||||
|
|
||||||
|
let tile_addr = tile_map_addr + tile_row as u16 + tile_column as u16;
|
||||||
|
let tile_number = self.read_byte(tile_addr);
|
||||||
|
|
||||||
|
let tile_data_addr = match self.lcd_control.tile_data_addr() {
|
||||||
|
TileDataAddress::X8800 => (0x9000 as i32 + (tile_number as i32 * 16)) as u16,
|
||||||
|
TileDataAddress::X8000 => 0x8000 + (tile_number as u16 * 16),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the correct vertical line we're on
|
||||||
|
let line = (y_pos % 8) * 2; // *2 since each vertical line takes up 2 bytes
|
||||||
|
|
||||||
|
let higher = self.read_byte(tile_data_addr + line as u16);
|
||||||
|
let lower = self.read_byte(tile_data_addr + line as u16 + 1);
|
||||||
|
// println!("Hi: {:#010b} | Lo: {:#010b}", higher, lower);
|
||||||
|
|
||||||
|
let bit = x_pos % 8;
|
||||||
|
let colour = ((higher >> bit) & 0x01) << 1 | ((lower >> bit) & 0x01);
|
||||||
|
let shade: GrayShade = colour.into();
|
||||||
|
|
||||||
|
chunk.copy_from_slice(&shade.into_rgba());
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = (GB_WIDTH * 4) * self.pos.line_y as usize;
|
||||||
|
self.frame_buf[i..(i + scanline.len())].copy_from_slice(&scanline);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_to_gui(&self, frame: &mut [u8]) {
|
||||||
frame.copy_from_slice(&self.frame_buf);
|
frame.copy_from_slice(&self.frame_buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,11 +237,11 @@ bitfield! {
|
||||||
pub struct LCDControl(u8);
|
pub struct LCDControl(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
lcd_enabled, set_lcd_enabled: 7;
|
lcd_enabled, set_lcd_enabled: 7;
|
||||||
from into TileMapRegister, win_tile_map_area, set_win_tile_map_area: 6;
|
from into TileMapAddress, win_tile_map_addr, set_win_tile_map_addr: 6, 6;
|
||||||
window_enabled, set_window_enabled: 5;
|
window_enabled, set_window_enabled: 5;
|
||||||
from into TileDataRegister, tile_data_area, set_tile_data_area: 4;
|
from into TileDataAddress, tile_data_addr, set_tile_data_addr: 4, 4;
|
||||||
from into TileMapRegister, gb_tile_map_area, set_gb_tile_map_area: 3;
|
from into TileMapAddress, bg_tile_map_addr, set_bg_tile_map_addr: 3, 3;
|
||||||
from into OBJSize, obg_size, set_obj_size: 2;
|
from into ObjectSize, obg_size, set_obj_size: 2, 2;
|
||||||
obj_enabled, set_obj_enabled: 1;
|
obj_enabled, set_obj_enabled: 1;
|
||||||
bg_win_enabled, set_bg_win_enabled: 0;
|
bg_win_enabled, set_bg_win_enabled: 0;
|
||||||
}
|
}
|
||||||
|
@ -221,12 +272,12 @@ impl From<LCDControl> for u8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum TileMapRegister {
|
enum TileMapAddress {
|
||||||
X9800 = 0,
|
X9800 = 0,
|
||||||
X9C00 = 1,
|
X9C00 = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u8> for TileMapRegister {
|
impl From<u8> for TileMapAddress {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte {
|
match byte {
|
||||||
0b00 => Self::X9800,
|
0b00 => Self::X9800,
|
||||||
|
@ -236,19 +287,25 @@ impl From<u8> for TileMapRegister {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TileMapRegister {
|
impl From<TileMapAddress> for u8 {
|
||||||
|
fn from(reg: TileMapAddress) -> Self {
|
||||||
|
reg as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TileMapAddress {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::X9800
|
Self::X9800
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum TileDataRegister {
|
enum TileDataAddress {
|
||||||
X8800 = 0,
|
X8800 = 0,
|
||||||
X8000 = 1,
|
X8000 = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u8> for TileDataRegister {
|
impl From<u8> for TileDataAddress {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte {
|
match byte {
|
||||||
0b00 => Self::X8800,
|
0b00 => Self::X8800,
|
||||||
|
@ -258,19 +315,25 @@ impl From<u8> for TileDataRegister {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TileDataRegister {
|
impl From<TileDataAddress> for u8 {
|
||||||
|
fn from(reg: TileDataAddress) -> Self {
|
||||||
|
reg as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TileDataAddress {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::X8800
|
Self::X8800
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum ObjSize {
|
enum ObjectSize {
|
||||||
EightByEight = 0,
|
EightByEight = 0,
|
||||||
EightBySixteen = 1,
|
EightBySixteen = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u8> for ObjSize {
|
impl From<u8> for ObjectSize {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte {
|
match byte {
|
||||||
0b00 => Self::EightByEight,
|
0b00 => Self::EightByEight,
|
||||||
|
@ -280,7 +343,13 @@ impl From<u8> for ObjSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ObjSize {
|
impl From<ObjectSize> for u8 {
|
||||||
|
fn from(size: ObjectSize) -> Self {
|
||||||
|
size as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectSize {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::EightByEight
|
Self::EightByEight
|
||||||
}
|
}
|
||||||
|
@ -294,6 +363,17 @@ pub enum GrayShade {
|
||||||
Black = 3,
|
Black = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GrayShade {
|
||||||
|
pub fn into_rgba(self) -> [u8; 4] {
|
||||||
|
match self {
|
||||||
|
GrayShade::White => [0xFF, 0xFF, 0xFF, 0xFF],
|
||||||
|
GrayShade::LightGray => [0xCC, 0xCC, 0xCC, 0xFF],
|
||||||
|
GrayShade::DarkGray => [0x77, 0x77, 0x77, 0xFF],
|
||||||
|
GrayShade::Black => [0x00, 0x00, 0x00, 0x00],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for GrayShade {
|
impl Default for GrayShade {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::White
|
Self::White
|
||||||
|
@ -391,3 +471,5 @@ impl From<ObjectPalette> for u8 {
|
||||||
palette.0
|
palette.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BackgroundMap([u8; 32]);
|
||||||
|
|
Loading…
Reference in New Issue