Compute 2bpp background framebuffer (#4)

This commit is contained in:
2024-01-21 23:59:58 +00:00
committed by GitHub
parent e2446514db
commit 01b3493547
16 changed files with 199 additions and 26 deletions
+5 -1
View File
@@ -9,7 +9,11 @@ edition = "2021"
snes-core = { path = "../snes-core" }
# Frontend stuff
eframe = "0.24.1"
eframe = "0.25.0"
env_logger = "0.10.1"
rfd = "0.12.1"
regex = "1.10.2"
wgpu = "0.18.0"
[features]
wgpu = ["eframe/wgpu"]
@@ -103,6 +103,7 @@ pub struct PPUDebugControlOptions {
pub show_registers: bool,
pub show_vram: bool,
pub vram_inputs: VramInputs,
pub vram_inputs_result: VramInputs,
pub backgrounds: [BgDebug; 4],
}
@@ -113,6 +114,7 @@ impl PPUDebugControlOptions {
show_registers: true,
show_vram: true,
vram_inputs: VramInputs::new(),
vram_inputs_result: VramInputs::new(),
backgrounds: [
BgDebug::new(PPUBg::Bg1),
BgDebug::new(PPUBg::Bg2),
+2
View File
@@ -1,11 +1,13 @@
pub struct EmulationState {
pub is_paused: bool,
pub one_tick_per_frame: bool,
}
impl EmulationState {
pub fn new() -> Self {
Self {
is_paused: true,
one_tick_per_frame: false,
}
}
}
+6
View File
@@ -16,6 +16,12 @@ pub fn build_cpu_debug_controls(ctx: &egui::Context, cpu_debug_options: &mut CPU
.show(ctx, |ui| {
ui.monospace("Controls:");
ui.horizontal(|ui| {
if ui.selectable_label(
emulation_state.one_tick_per_frame,
"Tick per frame"
).clicked() {
emulation_state.one_tick_per_frame = !emulation_state.one_tick_per_frame;
}
let pause_text = if emulation_state.is_paused {"Resume"} else {"Pause"};
if ui.button(pause_text).clicked() {
emulation_state.is_paused = !emulation_state.is_paused;
+4 -2
View File
@@ -164,13 +164,15 @@ fn build_vram_window(ctx: &egui::Context, ppu_debug_options: &mut PPUDebugContro
if ui.button("Search").clicked() {
sanitize_input(&mut ppu_debug_options.vram_inputs.address_start, false);
sanitize_input(&mut ppu_debug_options.vram_inputs.address_end, false);
ppu_debug_options.vram_inputs_result.address_start = ppu_debug_options.vram_inputs.address_start.to_string();
ppu_debug_options.vram_inputs_result.address_end = ppu_debug_options.vram_inputs.address_end.to_string();
}
ui.separator();
egui::ScrollArea::both().show(ui, |ui| {
let address_start = u16::from_str_radix(&ppu_debug_options.vram_inputs.address_start, 16).unwrap();
let address_end = u16::from_str_radix(&ppu_debug_options.vram_inputs.address_end, 16).unwrap();
let address_start = u16::from_str_radix(&ppu_debug_options.vram_inputs_result.address_start, 16).unwrap();
let address_end = u16::from_str_radix(&ppu_debug_options.vram_inputs_result.address_end, 16).unwrap();
let mut header = String::from(" | ");
for page in 0x00..=0x0F {
header = format!("{} {:02X} ", header, page);
+101 -4
View File
@@ -2,7 +2,7 @@ use eframe::epaint::{Vec2, TextureHandle};
use eframe::{egui::Context, epaint::ColorImage};
use eframe::egui::{self, TextureOptions, Ui};
use snes_core::ppu::registers::{PPURegisters, Background};
use snes_core::ppu::registers::{PPURegisters, Background, MAX_BG_WIDTH, MAX_BG_HEIGHT};
use crate::emu_state::debug_options::BgDebug;
@@ -25,8 +25,9 @@ fn build_bg_preview_window(ctx: &Context, bgdebug: &mut BgDebug, registers: &PPU
ui.label("Charset");
compute_2bpp_bg_char_framebuffer(bgdebug.background, &mut bgdebug.char_framebuffer, registers);
paint_texture(ui, &mut bgdebug.char_texture, &bgdebug.char_framebuffer, 16 * 8, 8 * 8);
// ui.label("Background");
// paint_texture(ui, &mut bgdebug.bg_texture, &mut bgdebug.bg_framebuffer, MAX_BG_WIDTH, MAX_BG_HEIGHT);
ui.label("Background");
compute_2bpp_bg_background_framebuffer(bgdebug.background, &mut bgdebug.bg_framebuffer, registers);
paint_texture(ui, &mut bgdebug.bg_texture, &bgdebug.bg_framebuffer, MAX_BG_WIDTH, MAX_BG_HEIGHT);
});
});
}
@@ -122,6 +123,102 @@ fn compute_2bpp_bg_char_framebuffer(background: Background, framebuffer: &mut [u
}
}
fn compute_2bpp_bg_background_framebuffer(background: Background, framebuffer: &mut [u8], registers: &PPURegisters) {
let tileset_vram_base_address = registers.get_bg_tile_base_address(background) as usize;
let charset_vram_base_address = registers.get_bg_char_base_address(background) as usize;
let (bg_size_width, bg_size_height) = registers.get_bg_size(background).to_usize();
let (tile_size_width, tile_size_height) = registers.get_bg_tile_size(background).to_usize();
let vram = registers.vram();
let width: usize = bg_size_width * tile_size_width;
let height: usize = bg_size_height * tile_size_height;
for y in 0..height {
for x in 0..width {
let current_tile = (x / tile_size_width) + ((y / tile_size_height) * bg_size_width);
let tile_byte = vram[tileset_vram_base_address + current_tile];
let char_index = tile_byte & 0b11_11111111;
let current_char_column = x.rem_euclid(tile_size_width);
let current_char_row = y.rem_euclid(tile_size_height);
// 8x8 pixels, 2 bitplanes, each word (16bit) holds 8 pixels
// so 1 char is 8 bytes x 2
let effective_vram_address =
charset_vram_base_address +
((char_index as usize) * tile_size_width) +
current_char_row;
let vram_word = vram[effective_vram_address];
let lsb_bitplane= vram_word as u8;
let msb_bitplane= (vram_word >> 8) as u8;
let pixels = [
(
(lsb_bitplane >> 7) |
((msb_bitplane >> 7) << 1)
),
(
((lsb_bitplane >> 6) & 1) |
(((msb_bitplane >> 6) & 1) << 1)
),
(
((lsb_bitplane >> 5) & 1) |
(((msb_bitplane >> 5) & 1) << 1)
),
(
((lsb_bitplane >> 4) & 1) |
(((msb_bitplane >> 4) & 1) << 1)
),
(
((lsb_bitplane >> 3) & 1) |
(((msb_bitplane >> 3) & 1) << 1)
),
(
((lsb_bitplane >> 2) & 1) |
(((msb_bitplane >> 2) & 1) << 1)
),
(
((lsb_bitplane >> 1) & 1) |
(((msb_bitplane >> 1) & 1) << 1)
),
(
(lsb_bitplane & 1) |
((msb_bitplane & 1) << 1)
),
];
let effective_pixel = pixels[current_char_column];
let current_fb_pixel = x + (y * MAX_BG_WIDTH);
let fb_index = current_fb_pixel * 4;
let r = fb_index;
let g = fb_index + 1;
let b = fb_index + 2;
let a = fb_index + 3;
framebuffer[a] = 0xFF;
match effective_pixel {
0b00 => {
framebuffer[r] = 0xFF;
framebuffer[g] = 0xFF;
framebuffer[b] = 0xFF;
},
0b01 => {
framebuffer[r] = 0xAC;
framebuffer[g] = 0xAC;
framebuffer[b] = 0xAC;
},
0b10 => {
framebuffer[r] = 0x56;
framebuffer[g] = 0x56;
framebuffer[b] = 0x56;
},
0b11 => {
framebuffer[r] = 0x00;
framebuffer[g] = 0x00;
framebuffer[b] = 0x00;
},
_ => unreachable!(),
};
}
}
}
fn paint_texture(ui: &mut Ui, texture: &mut Option<TextureHandle>, framebuffer: &[u8], width: usize, height: usize) {
if let Some(txt) = texture {
txt.set(
@@ -129,7 +226,7 @@ fn paint_texture(ui: &mut Ui, texture: &mut Option<TextureHandle>, framebuffer:
TextureOptions::default(),
);
let (whole_rect, _) =
ui.allocate_exact_size(Vec2::from([width as f32, height as f32]), egui::Sense::focusable_noninteractive());
ui.allocate_exact_size(Vec2::from([(width * 2) as f32, (height * 2) as f32]), egui::Sense::focusable_noninteractive());
egui::Image::new((
txt.id(),
txt.size_vec2(),
+14 -2
View File
@@ -1,6 +1,7 @@
use eframe::egui;
use snes_core::emulator::Emulator;
mod utils;
mod emu_ui;
mod emu_state;
@@ -9,6 +10,7 @@ mod emu_state;
struct SnesEmulatorApp {
emulator: Emulator,
state: emu_state::AppState,
frame_limit: utils::frame_limiter::FrameLimiter,
}
impl SnesEmulatorApp {
@@ -25,14 +27,24 @@ impl eframe::App for SnesEmulatorApp {
// ui::game::build_game_window(ctx);
});
if !self.state.emulation_state.is_paused {
self.emulator.loop_frame();
if self.state.emulation_state.one_tick_per_frame {
self.emulator.tick();
} else {
self.emulator.loop_frame();
}
}
emu_ui::debug::build_all_debug_options(ctx, &mut self.state.debug_options, &mut self.state.emulation_state, &mut self.emulator);
ctx.request_repaint();
self.frame_limit.limit();
self.frame_limit.reset_timer();
}
}
fn main() -> eframe::Result<()> {
let native_options = eframe::NativeOptions::default();
let native_options = eframe::NativeOptions {
vsync: false,
..eframe::NativeOptions::default()
};
eframe::run_native(
"SNES Emulator",
native_options,
+35
View File
@@ -0,0 +1,35 @@
use std::{thread, time};
use std::time::Instant;
pub struct FrameLimiter {
timer: Instant,
fps: u128,
}
impl FrameLimiter {
pub fn new() -> Self {
Self {
timer: Instant::now(),
fps: 16600,
}
}
pub fn reset_timer(&mut self) {
self.timer = Instant::now();
}
pub fn limit(&self) {
let elapsed = self.timer.elapsed().as_micros();
if elapsed > self.fps {
return;
}
let wait = (self.fps - elapsed).try_into().unwrap();
thread::sleep(time::Duration::from_micros(wait));
}
}
impl Default for FrameLimiter {
fn default() -> Self {
Self::new()
}
}
+1
View File
@@ -0,0 +1 @@
pub mod frame_limiter;