mirror of
https://github.com/FranLMSP/snes.git
synced 2026-10-03 16:05:18 -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;
|
use crate::rom::lo_rom::LoROM;
|
||||||
|
|
||||||
pub struct Emulator {
|
pub struct Emulator {
|
||||||
cpu: CPU,
|
pub cpu: CPU,
|
||||||
bus: Bus,
|
pub bus: Bus,
|
||||||
pub rom: Box<dyn ROM>,
|
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 TMW: u16 = 0x212E; // Window Area Main Screen Disable (W)
|
||||||
pub const TSW: u16 = 0x212F; // Window Area Sub 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 {
|
pub struct PPURegisters {
|
||||||
data: [u8; 256],
|
data: [u8; 256],
|
||||||
}
|
}
|
||||||
@@ -78,4 +128,63 @@ impl PPURegisters {
|
|||||||
pub fn write(&mut self, address: u16, value: u8) {
|
pub fn write(&mut self, address: u16, value: u8) {
|
||||||
self.data[(address as usize) - 0x2100] = value;
|
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;
|
pub mod state;
|
||||||
@@ -16,6 +16,7 @@ use winit::{
|
|||||||
use snes_frontend::state::State;
|
use snes_frontend::state::State;
|
||||||
extern crate snes_core;
|
extern crate snes_core;
|
||||||
use snes_core::emulator::Emulator;
|
use snes_core::emulator::Emulator;
|
||||||
|
use snes_frontend::ppu as ppu_render;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Windowing state
|
// Windowing state
|
||||||
@@ -138,6 +139,12 @@ fn main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
let texture_id = renderer.textures.insert(texture);
|
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
|
// Event loop
|
||||||
@@ -238,6 +245,15 @@ fn main() {
|
|||||||
.size([300.0, 100.0], Condition::FirstUseEver)
|
.size([300.0, 100.0], Condition::FirstUseEver)
|
||||||
.opened(&mut state.debug_options.show_debug_window)
|
.opened(&mut state.debug_options.show_debug_window)
|
||||||
.build(&ui, || {
|
.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(
|
ui.checkbox(
|
||||||
"Show CPU registers",
|
"Show CPU registers",
|
||||||
&mut state.debug_options.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
|
// Render emulator framebuffer
|
||||||
{
|
{
|
||||||
let tex = renderer.textures.get_mut(texture_id).unwrap();
|
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 struct DebugOptions {
|
||||||
|
pub is_enabled: bool,
|
||||||
pub show_debug_window: bool,
|
pub show_debug_window: bool,
|
||||||
pub show_cpu_registers: bool,
|
pub show_cpu_registers: bool,
|
||||||
pub show_spc700_registers: bool,
|
pub show_spc700_registers: bool,
|
||||||
@@ -8,7 +17,8 @@ pub struct DebugOptions {
|
|||||||
impl DebugOptions {
|
impl DebugOptions {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
show_debug_window: false,
|
is_enabled: true,
|
||||||
|
show_debug_window: true,
|
||||||
show_cpu_registers: true,
|
show_cpu_registers: true,
|
||||||
show_spc700_registers: true,
|
show_spc700_registers: true,
|
||||||
show_cpu_memory: 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 struct State {
|
||||||
pub debug_options: DebugOptions,
|
pub debug_options: DebugOptions,
|
||||||
pub error_message: ErrorMessage,
|
pub error_message: ErrorMessage,
|
||||||
|
pub ppudebug: PPUDebug,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
@@ -40,6 +88,7 @@ impl State {
|
|||||||
Self {
|
Self {
|
||||||
debug_options: DebugOptions::new(),
|
debug_options: DebugOptions::new(),
|
||||||
error_message: ErrorMessage::new(),
|
error_message: ErrorMessage::new(),
|
||||||
|
ppudebug: PPUDebug::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user