mirror of
https://github.com/FranLMSP/snes.git
synced 2026-08-03 08:30:01 -04:00
WIP PPU registers and background rendering
This commit is contained in:
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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 +1,2 @@
|
||||
pub mod ppu;
|
||||
pub mod state;
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user