WIP PPU registers and background rendering

This commit is contained in:
2022-12-14 00:13:42 -05:00
parent a3f12e61b4
commit ac4c749e37
6 changed files with 280 additions and 3 deletions
+2 -2
View File
@@ -4,8 +4,8 @@ use crate::rom::ROM;
use crate::rom::lo_rom::LoROM;
pub struct Emulator {
cpu: CPU,
bus: Bus,
pub cpu: CPU,
pub bus: Bus,
pub rom: Box<dyn ROM>,
}
+109
View File
@@ -60,6 +60,56 @@ pub const WOBJLOG: u16 = 0x212B; // Window 2 Mask Logic (W)
pub const TMW: u16 = 0x212E; // Window Area Main Screen Disable (W)
pub const TSW: u16 = 0x212F; // Window Area Sub Screen Disable (W)
pub const MAX_BG_WIDTH: usize = 16 * 64;
pub const MAX_BG_HEIGHT: usize = 16 * 64;
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum TileSize {
P8x8,
P16x16,
P16x8,
}
impl TileSize {
pub fn to_usize(&self) -> (usize, usize) {
match self {
Self::P8x8 => (8, 8),
Self::P16x16 => (16, 16),
Self::P16x8 => (16, 8),
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum BgSize {
T32x32,
T64x32, // V Mirror
T32x64, // H Mirror
T64x64, // H Mirror
}
impl BgSize {
pub fn to_usize(&self) -> (usize, usize) {
match self {
Self::T32x32 => (32, 32),
Self::T64x32 => (64, 32),
Self::T32x64 => (32, 64),
Self::T64x64 => (64, 64),
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum Background {
Bg1,
Bg2,
Bg3,
Bg4,
}
pub struct PPURegisters {
data: [u8; 256],
}
@@ -78,4 +128,63 @@ impl PPURegisters {
pub fn write(&mut self, address: u16, value: u8) {
self.data[(address as usize) - 0x2100] = value;
}
/// 7 BG4 Tile Size (0=8x8, 1=16x16) ;\(BgMode0..4: variable 8x8 or 16x16)
/// 6 BG3 Tile Size (0=8x8, 1=16x16) ; (BgMode5: 8x8 acts as 16x8)
/// 5 BG2 Tile Size (0=8x8, 1=16x16) ; (BgMode6: fixed 16x8?)
/// 4 BG1 Tile Size (0=8x8, 1=16x16) ;/(BgMode7: fixed 8x8)
/// 3 BG3 Priority in Mode 1 (0=Normal, 1=High)
/// 2-0 BG Screen Mode (0..7 = see below)
pub fn get_bg_tile_size(&self, background: Background) -> TileSize {
let byte = self.read(BGMODE);
let bit = match background {
Background::Bg1 => byte >> 3 & 0b1 == 1, // Bit 4
Background::Bg2 => byte >> 4 & 0b1 == 1, // Bit 5
Background::Bg3 => byte >> 5 & 0b1 == 1, // Bit 6
Background::Bg4 => byte >> 6 & 0b1 == 1, // Bit 7
};
match bit {
true => TileSize::P16x16,
false => TileSize::P8x8,
}
}
pub fn get_bg_size(&self, background: Background) -> BgSize {
let byte = match background {
Background::Bg1 => self.read(BG1SC),
Background::Bg2 => self.read(BG2SC),
Background::Bg3 => self.read(BG3SC),
Background::Bg4 => self.read(BG4SC),
};
match byte & 0b11 {
0 => BgSize::T32x32,
1 => BgSize::T64x32,
2 => BgSize::T32x64,
3 => BgSize::T64x64,
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod ppu_registers_test {
use super::*;
#[test]
fn test_get_bg_tile_size() {
let mut registers = PPURegisters::new();
registers.write(BGMODE, 0b00000100);
assert_eq!(registers.get_bg_tile_size(Background::Bg1), TileSize::P8x8);
registers.write(BGMODE, 0b00001100);
assert_eq!(registers.get_bg_tile_size(Background::Bg1), TileSize::P16x16);
}
#[test]
fn test_get_bg_size() {
let mut registers = PPURegisters::new();
registers.write(BG1SC, 2);
assert_eq!(registers.get_bg_size(Background::Bg1), BgSize::T32x64);
}
}
+1
View File
@@ -1 +1,2 @@
pub mod ppu;
pub mod state;
+44
View File
@@ -16,6 +16,7 @@ use winit::{
use snes_frontend::state::State;
extern crate snes_core;
use snes_core::emulator::Emulator;
use snes_frontend::ppu as ppu_render;
fn main() {
// Windowing state
@@ -138,6 +139,12 @@ fn main() {
},
);
let texture_id = renderer.textures.insert(texture);
for bgdebug in state.ppudebug.backgrounds.iter_mut() {
bgdebug.texture_id = Some(
ppu_render::background_texture(&device, &mut renderer, bgdebug.background)
);
}
// Event loop
@@ -238,6 +245,15 @@ fn main() {
.size([300.0, 100.0], Condition::FirstUseEver)
.opened(&mut state.debug_options.show_debug_window)
.build(&ui, || {
ui.checkbox(
"Enable debugging",
&mut state.debug_options.is_enabled,
);
ui.separator();
ui.checkbox(
"Show PPU debugging options",
&mut state.ppudebug.is_enabled,
);
ui.checkbox(
"Show CPU registers",
&mut state.debug_options.show_cpu_registers,
@@ -245,6 +261,34 @@ fn main() {
});
}
// Render all debugging stuff
if state.debug_options.is_enabled {
if state.ppudebug.is_enabled {
let window = imgui::Window::new("PPU Debugging options");
window
.size([300.0, 400.0], Condition::FirstUseEver)
.build(&ui, || {
ui.text("Backgrounds:");
for bgdebug in state.ppudebug.backgrounds.iter_mut() {
ui.checkbox(
format!("Show {:?}", bgdebug.background),
&mut bgdebug.is_enabled,
);
}
});
for bgdebug in state.ppudebug.backgrounds.iter_mut() {
ppu_render::background_window(
bgdebug,
&emulator.bus.ppu.registers,
&ui,
&mut renderer,
&queue,
)
}
}
}
// Render emulator framebuffer
{
let tex = renderer.textures.get_mut(texture_id).unwrap();
+74
View File
@@ -0,0 +1,74 @@
extern crate snes_core;
use snes_core::ppu::registers::{
MAX_BG_WIDTH,
MAX_BG_HEIGHT,
PPURegisters,
Background as PPUBg,
};
use imgui_wgpu::Renderer;
use imgui::TextureId;
use wgpu::{Device, Queue};
use super::state::BgDebug;
pub fn background_texture(
device: &Device,
renderer: &mut Renderer,
background: PPUBg
) -> TextureId {
let texture = imgui_wgpu::Texture::new(
device,
renderer,
imgui_wgpu::TextureConfig {
label: Some(&format!("Background {:?} texture", background)),
size: wgpu::Extent3d {
width: MAX_BG_WIDTH as u32,
height: MAX_BG_HEIGHT as u32,
depth_or_array_layers: 1,
},
format: Some(wgpu::TextureFormat::Rgba8Unorm),
..imgui_wgpu::TextureConfig::default()
},
);
renderer.textures.insert(texture)
}
fn render_background(bgdebug: &mut BgDebug, registers: &PPURegisters) {
let (tile_width, tile_height) = registers.get_bg_tile_size(bgdebug.background).to_usize();
let (bg_width, bg_height) = registers.get_bg_size(bgdebug.background).to_usize();
let width = tile_width * bg_width;
let height = tile_height * bg_height;
for x in 0..width {
for y in 0..height {
let index = ((y * MAX_BG_WIDTH) + x) * 4;
let r = index;
let g = index + 1;
let b = index + 2;
let a = index + 3;
bgdebug.framebuffer[r] = 0xFF;
bgdebug.framebuffer[g] = 0xFF;
bgdebug.framebuffer[b] = 0xFF;
bgdebug.framebuffer[a] = 0xFF;
}
}
}
pub fn background_window(bgdebug: &mut BgDebug, registers: &PPURegisters, ui: &imgui::Ui, renderer: &mut Renderer, queue: &Queue) {
if !bgdebug.is_enabled {
return;
}
let texture_id = bgdebug.texture_id.unwrap();
render_background(bgdebug, registers);
let tex = renderer.textures.get_mut(texture_id).unwrap();
tex.write(queue, &bgdebug.framebuffer, MAX_BG_WIDTH as u32, MAX_BG_HEIGHT as u32);
let game_window = imgui::Window::new(format!("BG: {:?}", bgdebug.background));
game_window
.size([MAX_BG_WIDTH as f32, MAX_BG_HEIGHT as f32], imgui::Condition::FirstUseEver)
.opened(&mut bgdebug.is_enabled)
.build(ui, || {
let game_image = imgui::Image::new(texture_id, [MAX_BG_WIDTH as f32, MAX_BG_HEIGHT as f32]);
game_image.build(&ui);
});
}
+50 -1
View File
@@ -1,4 +1,13 @@
extern crate snes_core;
use snes_core::ppu::registers::{
Background as PPUBg,
MAX_BG_WIDTH,
MAX_BG_HEIGHT,
};
pub struct DebugOptions {
pub is_enabled: bool,
pub show_debug_window: bool,
pub show_cpu_registers: bool,
pub show_spc700_registers: bool,
@@ -8,7 +17,8 @@ pub struct DebugOptions {
impl DebugOptions {
pub fn new() -> Self {
Self {
show_debug_window: false,
is_enabled: true,
show_debug_window: true,
show_cpu_registers: true,
show_spc700_registers: true,
show_cpu_memory: true,
@@ -30,9 +40,47 @@ impl ErrorMessage {
}
}
pub struct BgDebug {
pub background: PPUBg,
pub is_enabled: bool,
pub texture_id: Option<imgui::TextureId>,
pub framebuffer: Vec<u8>,
}
impl BgDebug {
pub fn new(background: PPUBg) -> Self {
Self {
background: background,
is_enabled: false,
texture_id: None,
framebuffer: vec![0x00; MAX_BG_WIDTH * MAX_BG_HEIGHT * 4],
}
}
}
pub struct PPUDebug {
pub is_enabled: bool,
pub backgrounds: [BgDebug; 4],
}
impl PPUDebug {
pub fn new() -> Self {
Self {
is_enabled: true,
backgrounds: [
BgDebug::new(PPUBg::Bg1),
BgDebug::new(PPUBg::Bg2),
BgDebug::new(PPUBg::Bg3),
BgDebug::new(PPUBg::Bg4),
],
}
}
}
pub struct State {
pub debug_options: DebugOptions,
pub error_message: ErrorMessage,
pub ppudebug: PPUDebug,
}
impl State {
@@ -40,6 +88,7 @@ impl State {
Self {
debug_options: DebugOptions::new(),
error_message: ErrorMessage::new(),
ppudebug: PPUDebug::new(),
}
}
}