mirror of
https://github.com/FranLMSP/snes.git
synced 2026-10-03 16:05:18 -04:00
Compute 2bpp background framebuffer (#4)
This commit is contained in:
@@ -24,7 +24,7 @@ impl CPUInstruction for BRL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mnemonic(&self, registers: &Registers, bus: &Bus, opcode: u8) -> String {
|
fn mnemonic(&self, registers: &Registers, bus: &Bus, opcode: u8) -> String {
|
||||||
decoder_common::mnemonic_absolute(opcode, INSTR_NAME, registers, bus)
|
decoder_common::mnemonic_absolute_16bit(opcode, INSTR_NAME, registers, bus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ pub fn mnemonic_arithmetic(is_16bit: bool, opcode: u8, instr_name: &str, address
|
|||||||
true => mnemonic_16bit_immediate(opcode, instr_name, registers, bus),
|
true => mnemonic_16bit_immediate(opcode, instr_name, registers, bus),
|
||||||
false => mnemonic_8bit_immediate(opcode, instr_name, registers, bus),
|
false => mnemonic_8bit_immediate(opcode, instr_name, registers, bus),
|
||||||
},
|
},
|
||||||
A::Absolute => mnemonic_absolute(opcode, instr_name, registers, bus),
|
A::Absolute => match is_16bit {
|
||||||
|
true => mnemonic_absolute_16bit(opcode, instr_name, registers, bus),
|
||||||
|
false => mnemonic_absolute_8bit(opcode, instr_name, registers, bus),
|
||||||
|
},
|
||||||
A::AbsoluteLong => mnemonic_absolute_long(opcode, instr_name, registers, bus),
|
A::AbsoluteLong => mnemonic_absolute_long(opcode, instr_name, registers, bus),
|
||||||
A::DirectPage => mnemonic_direct_page(opcode, instr_name, registers, bus),
|
A::DirectPage => mnemonic_direct_page(opcode, instr_name, registers, bus),
|
||||||
A::DirectPageIndirect => mnemonic_direct_page_indirect(opcode, instr_name, registers, bus),
|
A::DirectPageIndirect => mnemonic_direct_page_indirect(opcode, instr_name, registers, bus),
|
||||||
@@ -38,14 +41,21 @@ pub fn mnemonic_8bit_immediate(opcode: u8, instr_name: &str, registers: &Registe
|
|||||||
pub fn mnemonic_16bit_immediate(opcode: u8, instr_name: &str, registers: &Registers, bus: &Bus) -> String {
|
pub fn mnemonic_16bit_immediate(opcode: u8, instr_name: &str, registers: &Registers, bus: &Bus) -> String {
|
||||||
let next_byte = bus.read_external(registers.get_pc_address() + 1);
|
let next_byte = bus.read_external(registers.get_pc_address() + 1);
|
||||||
let next_second_byte = bus.read_external(registers.get_pc_address() + 2);
|
let next_second_byte = bus.read_external(registers.get_pc_address() + 2);
|
||||||
let word = (next_byte as u16) | ((next_byte as u16) << 8);
|
let word = (next_byte as u16) | ((next_second_byte as u16) << 8);
|
||||||
format!("{:02X} {:02X} {:02X} __ | {} #${:04X}", opcode, next_byte, next_second_byte, instr_name, word)
|
format!("{:02X} {:02X} {:02X} __ | {} #${:04X}", opcode, next_byte, next_second_byte, instr_name, word)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mnemonic_absolute(opcode: u8, instr_name: &str, registers: &Registers, bus: &Bus) -> String {
|
pub fn mnemonic_absolute_8bit(opcode: u8, instr_name: &str, registers: &Registers, bus: &Bus) -> String {
|
||||||
let next_byte = bus.read_external(registers.get_pc_address() + 1);
|
let next_byte = bus.read_external(registers.get_pc_address() + 1);
|
||||||
let next_second_byte = bus.read_external(registers.get_pc_address() + 2);
|
let next_second_byte = bus.read_external(registers.get_pc_address() + 2);
|
||||||
let word = (next_byte as u16) | ((next_byte as u16) << 8);
|
let word = (next_byte as u16) | ((next_second_byte as u16) << 8);
|
||||||
|
format!("{:02X} {:02X} {:02X} __ | {} ${:04X}", opcode, next_byte, next_second_byte, instr_name, word)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mnemonic_absolute_16bit(opcode: u8, instr_name: &str, registers: &Registers, bus: &Bus) -> String {
|
||||||
|
let next_byte = bus.read_external(registers.get_pc_address() + 1);
|
||||||
|
let next_second_byte = bus.read_external(registers.get_pc_address() + 2);
|
||||||
|
let word = (next_byte as u16) | ((next_second_byte as u16) << 8);
|
||||||
format!("{:02X} {:02X} {:02X} __ | {} ${:04X}", opcode, next_byte, next_second_byte, instr_name, word)
|
format!("{:02X} {:02X} {:02X} __ | {} ${:04X}", opcode, next_byte, next_second_byte, instr_name, word)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ impl CPUInstruction for PEA {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mnemonic(&self, registers: &Registers, bus: &Bus, opcode: u8) -> String {
|
fn mnemonic(&self, registers: &Registers, bus: &Bus, opcode: u8) -> String {
|
||||||
decoder_common::mnemonic_absolute(opcode, INSTR_NAME, registers, bus)
|
decoder_common::mnemonic_absolute_16bit(opcode, INSTR_NAME, registers, bus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ impl CPUInstruction for PER {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mnemonic(&self, registers: &Registers, bus: &Bus, opcode: u8) -> String {
|
fn mnemonic(&self, registers: &Registers, bus: &Bus, opcode: u8) -> String {
|
||||||
decoder_common::mnemonic_absolute(opcode, INSTR_NAME, registers, bus)
|
decoder_common::mnemonic_absolute_16bit(opcode, INSTR_NAME, registers, bus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,9 +57,11 @@ impl InternalRegisters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn read_vblank_nmi_mut(&self, ppu_registers: &mut PPURegisters) -> u8 {
|
fn read_vblank_nmi_mut(&self, ppu_registers: &mut PPURegisters) -> u8 {
|
||||||
let byte = self._read(RDNMI);
|
let result = self.read_vblank_nmi(ppu_registers);
|
||||||
|
if result == 0x80 {
|
||||||
|
println!("nmi set");
|
||||||
|
}
|
||||||
// When register is read, bit 7 is cleared
|
// When register is read, bit 7 is cleared
|
||||||
let result = (byte & 0x7F) | ((ppu_registers.vblank_nmi as u8) << 7);
|
|
||||||
ppu_registers.vblank_nmi = false;
|
ppu_registers.vblank_nmi = false;
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ pub enum BgSize {
|
|||||||
T32x32,
|
T32x32,
|
||||||
T64x32, // V Mirror
|
T64x32, // V Mirror
|
||||||
T32x64, // H Mirror
|
T32x64, // H Mirror
|
||||||
T64x64, // H Mirror
|
T64x64, // H/V Mirror
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BgSize {
|
impl BgSize {
|
||||||
@@ -266,10 +266,10 @@ impl PPURegisters {
|
|||||||
pub fn get_bg_tile_size(&self, background: Background) -> TileSize {
|
pub fn get_bg_tile_size(&self, background: Background) -> TileSize {
|
||||||
let byte = self._read(BGMODE);
|
let byte = self._read(BGMODE);
|
||||||
let bit = match background {
|
let bit = match background {
|
||||||
Background::Bg1 => byte >> 3 & 0b1 == 1, // Bit 4
|
Background::Bg1 => byte >> 4 & 0b1 == 1, // Bit 4
|
||||||
Background::Bg2 => byte >> 4 & 0b1 == 1, // Bit 5
|
Background::Bg2 => byte >> 5 & 0b1 == 1, // Bit 5
|
||||||
Background::Bg3 => byte >> 5 & 0b1 == 1, // Bit 6
|
Background::Bg3 => byte >> 6 & 0b1 == 1, // Bit 6
|
||||||
Background::Bg4 => byte >> 6 & 0b1 == 1, // Bit 7
|
Background::Bg4 => byte >> 7 & 0b1 == 1, // Bit 7
|
||||||
};
|
};
|
||||||
match bit {
|
match bit {
|
||||||
true => TileSize::P16x16,
|
true => TileSize::P16x16,
|
||||||
@@ -393,9 +393,9 @@ mod ppu_registers_test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_get_bg_tile_size() {
|
fn test_get_bg_tile_size() {
|
||||||
let mut registers = PPURegisters::new();
|
let mut registers = PPURegisters::new();
|
||||||
registers.write(BGMODE, 0b00000100);
|
registers.write(BGMODE, 0b00000000);
|
||||||
assert_eq!(registers.get_bg_tile_size(Background::Bg1), TileSize::P8x8);
|
assert_eq!(registers.get_bg_tile_size(Background::Bg1), TileSize::P8x8);
|
||||||
registers.write(BGMODE, 0b00001100);
|
registers.write(BGMODE, 0b00010000);
|
||||||
assert_eq!(registers.get_bg_tile_size(Background::Bg1), TileSize::P16x16);
|
assert_eq!(registers.get_bg_tile_size(Background::Bg1), TileSize::P16x16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ impl ROM for LoROM {
|
|||||||
let address = LoROM::adjust_address(address);
|
let address = LoROM::adjust_address(address);
|
||||||
match self.data.get(address as usize) {
|
match self.data.get(address as usize) {
|
||||||
Some(byte) => *byte,
|
Some(byte) => *byte,
|
||||||
None => 0xFF,
|
None => 0x00,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ edition = "2021"
|
|||||||
snes-core = { path = "../snes-core" }
|
snes-core = { path = "../snes-core" }
|
||||||
|
|
||||||
# Frontend stuff
|
# Frontend stuff
|
||||||
eframe = "0.24.1"
|
eframe = "0.25.0"
|
||||||
env_logger = "0.10.1"
|
env_logger = "0.10.1"
|
||||||
rfd = "0.12.1"
|
rfd = "0.12.1"
|
||||||
regex = "1.10.2"
|
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_registers: bool,
|
||||||
pub show_vram: bool,
|
pub show_vram: bool,
|
||||||
pub vram_inputs: VramInputs,
|
pub vram_inputs: VramInputs,
|
||||||
|
pub vram_inputs_result: VramInputs,
|
||||||
pub backgrounds: [BgDebug; 4],
|
pub backgrounds: [BgDebug; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +114,7 @@ impl PPUDebugControlOptions {
|
|||||||
show_registers: true,
|
show_registers: true,
|
||||||
show_vram: true,
|
show_vram: true,
|
||||||
vram_inputs: VramInputs::new(),
|
vram_inputs: VramInputs::new(),
|
||||||
|
vram_inputs_result: VramInputs::new(),
|
||||||
backgrounds: [
|
backgrounds: [
|
||||||
BgDebug::new(PPUBg::Bg1),
|
BgDebug::new(PPUBg::Bg1),
|
||||||
BgDebug::new(PPUBg::Bg2),
|
BgDebug::new(PPUBg::Bg2),
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
pub struct EmulationState {
|
pub struct EmulationState {
|
||||||
pub is_paused: bool,
|
pub is_paused: bool,
|
||||||
|
pub one_tick_per_frame: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmulationState {
|
impl EmulationState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
is_paused: true,
|
is_paused: true,
|
||||||
|
one_tick_per_frame: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,12 @@ pub fn build_cpu_debug_controls(ctx: &egui::Context, cpu_debug_options: &mut CPU
|
|||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.monospace("Controls:");
|
ui.monospace("Controls:");
|
||||||
ui.horizontal(|ui| {
|
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"};
|
let pause_text = if emulation_state.is_paused {"Resume"} else {"Pause"};
|
||||||
if ui.button(pause_text).clicked() {
|
if ui.button(pause_text).clicked() {
|
||||||
emulation_state.is_paused = !emulation_state.is_paused;
|
emulation_state.is_paused = !emulation_state.is_paused;
|
||||||
|
|||||||
@@ -164,13 +164,15 @@ fn build_vram_window(ctx: &egui::Context, ppu_debug_options: &mut PPUDebugContro
|
|||||||
if ui.button("Search").clicked() {
|
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_start, false);
|
||||||
sanitize_input(&mut ppu_debug_options.vram_inputs.address_end, 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();
|
ui.separator();
|
||||||
|
|
||||||
egui::ScrollArea::both().show(ui, |ui| {
|
egui::ScrollArea::both().show(ui, |ui| {
|
||||||
let address_start = u16::from_str_radix(&ppu_debug_options.vram_inputs.address_start, 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.address_end, 16).unwrap();
|
let address_end = u16::from_str_radix(&ppu_debug_options.vram_inputs_result.address_end, 16).unwrap();
|
||||||
let mut header = String::from(" | ");
|
let mut header = String::from(" | ");
|
||||||
for page in 0x00..=0x0F {
|
for page in 0x00..=0x0F {
|
||||||
header = format!("{} {:02X} ", header, page);
|
header = format!("{} {:02X} ", header, page);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use eframe::epaint::{Vec2, TextureHandle};
|
|||||||
use eframe::{egui::Context, epaint::ColorImage};
|
use eframe::{egui::Context, epaint::ColorImage};
|
||||||
|
|
||||||
use eframe::egui::{self, TextureOptions, Ui};
|
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;
|
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");
|
ui.label("Charset");
|
||||||
compute_2bpp_bg_char_framebuffer(bgdebug.background, &mut bgdebug.char_framebuffer, registers);
|
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);
|
paint_texture(ui, &mut bgdebug.char_texture, &bgdebug.char_framebuffer, 16 * 8, 8 * 8);
|
||||||
// ui.label("Background");
|
ui.label("Background");
|
||||||
// paint_texture(ui, &mut bgdebug.bg_texture, &mut bgdebug.bg_framebuffer, MAX_BG_WIDTH, MAX_BG_HEIGHT);
|
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) {
|
fn paint_texture(ui: &mut Ui, texture: &mut Option<TextureHandle>, framebuffer: &[u8], width: usize, height: usize) {
|
||||||
if let Some(txt) = texture {
|
if let Some(txt) = texture {
|
||||||
txt.set(
|
txt.set(
|
||||||
@@ -129,7 +226,7 @@ fn paint_texture(ui: &mut Ui, texture: &mut Option<TextureHandle>, framebuffer:
|
|||||||
TextureOptions::default(),
|
TextureOptions::default(),
|
||||||
);
|
);
|
||||||
let (whole_rect, _) =
|
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((
|
egui::Image::new((
|
||||||
txt.id(),
|
txt.id(),
|
||||||
txt.size_vec2(),
|
txt.size_vec2(),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use snes_core::emulator::Emulator;
|
use snes_core::emulator::Emulator;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
mod emu_ui;
|
mod emu_ui;
|
||||||
mod emu_state;
|
mod emu_state;
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ mod emu_state;
|
|||||||
struct SnesEmulatorApp {
|
struct SnesEmulatorApp {
|
||||||
emulator: Emulator,
|
emulator: Emulator,
|
||||||
state: emu_state::AppState,
|
state: emu_state::AppState,
|
||||||
|
frame_limit: utils::frame_limiter::FrameLimiter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SnesEmulatorApp {
|
impl SnesEmulatorApp {
|
||||||
@@ -25,14 +27,24 @@ impl eframe::App for SnesEmulatorApp {
|
|||||||
// ui::game::build_game_window(ctx);
|
// ui::game::build_game_window(ctx);
|
||||||
});
|
});
|
||||||
if !self.state.emulation_state.is_paused {
|
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);
|
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<()> {
|
fn main() -> eframe::Result<()> {
|
||||||
let native_options = eframe::NativeOptions::default();
|
let native_options = eframe::NativeOptions {
|
||||||
|
vsync: false,
|
||||||
|
..eframe::NativeOptions::default()
|
||||||
|
};
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"SNES Emulator",
|
"SNES Emulator",
|
||||||
native_options,
|
native_options,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod frame_limiter;
|
||||||
Reference in New Issue
Block a user