mirror of
https://github.com/henrikpersson/potatis.git
synced 2024-06-01 11:07:23 -04:00
Pico
This commit is contained in:
parent
511fbea69f
commit
d328521df7
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -2,4 +2,6 @@
|
|||
.DS_Store
|
||||
/legal-roms
|
||||
Cargo.lock
|
||||
dhat*.json
|
||||
dhat*.json
|
||||
profile*.json
|
||||
node_modules
|
||||
|
|
|
@ -5,9 +5,11 @@ members = [
|
|||
"nes",
|
||||
"nes-sdl",
|
||||
"nes-wasm",
|
||||
"nes-android",
|
||||
"nes-android"
|
||||
]
|
||||
exclude = [
|
||||
"nes-embedded",
|
||||
"bench",
|
||||
"profile"
|
||||
]
|
||||
|
||||
[profile.test]
|
||||
|
|
29
README.md
29
README.md
|
@ -1,5 +1,4 @@
|
|||
# 🥔 Potatis
|
||||
|
||||
<img width="400" alt="smb" src="screenshots/smb.png"><img width="400" alt="smb3" src="screenshots/smb3.png">
|
||||
<img width="400" alt="bb" src="screenshots/bb.png"><img width="400" alt="dr" src="screenshots/dr.png">
|
||||
|
||||
|
@ -7,6 +6,7 @@
|
|||
- `/nes` - A very incomplete NES emulator.
|
||||
- `/nes-sdl` - Native target using SDL.
|
||||
- `/nes-wasm` - Browser target using WASM.
|
||||
- `/nes-embedded` - Embedded target for RP-2040 (Raspberry Pi Pico).
|
||||
- `/nes-android` - Android target using JNI.
|
||||
|
||||
## /mos6502
|
||||
|
@ -19,7 +19,7 @@ let mut machine = Mos6502::new(cpu);
|
|||
|
||||
loop {
|
||||
machine.tick()
|
||||
println!("{}", machine); // Will print nestest-like output
|
||||
println!("{}", machine); // Prints nestest-like output
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -45,7 +45,7 @@ Supported mappers:
|
|||
- MMC3 (mapper 4)
|
||||
|
||||
```rust
|
||||
impl nes::HostSystem for MyHost {
|
||||
impl nes::HostPlatform for MyHost {
|
||||
fn render(&mut self, frame: &RenderFrame) {
|
||||
// frame.pixels() == 256 * 240 * 3 RGB array
|
||||
}
|
||||
|
@ -80,9 +80,28 @@ loop {
|
|||
|
||||
Try it here: https://henrikpersson.github.io/nes/index.html
|
||||
|
||||
## /nes-android
|
||||
|
||||
<img height="300" alt="bb" src="screenshots/android.png" align="right">
|
||||
## /nes-embedded
|
||||
It also runs on a RP-Pico with only 264kB available RAM! Without any optimizations it started out at ~0.5 FPS. But after some overclocking, and offloading the display rendering to the second CPU core, it now runs at a steady 5 FPS.
|
||||
|
||||
Total heap usage, single-core: 135kB
|
||||
<br>
|
||||
Total heap usage, multi-core: 243kB (2x frame buffers)
|
||||
|
||||
<img width="600" alt="smb" src="screenshots/pico.jpg">
|
||||
|
||||
|
||||
_The second Pico on the picture is wired up as a SWD debugger/flasher. The display is a [ST7789 by Adafruit](https://www.adafruit.com/product/4311)_.
|
||||
<br>
|
||||
|
||||
```
|
||||
cd nes-embedded
|
||||
ROM=/path/to/rom.nes cargo run --release
|
||||
```
|
||||
|
||||
If you don't have a debug-probe setup, change the runner in `.cargo/config` to use a normal `elf2uf2`.
|
||||
|
||||
## /nes-android
|
||||
|
||||
1. Download Android NDK and `rustup target add [target]`
|
||||
2. Configure your target(s) in `~/.cargo/config` with the linker(s) provided by the Android NDK
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "bench"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nes = { path = "../nes", default-features = false }
|
||||
dhat = "0.3.2"
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
|
@ -1,53 +0,0 @@
|
|||
#![no_std]
|
||||
|
||||
use nes::{cartridge::{Cartridge}, nes::Nes};
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: dhat::Alloc = dhat::Alloc;
|
||||
|
||||
struct FakeHost;
|
||||
|
||||
impl nes::nes::HostSystem for FakeHost {
|
||||
fn elapsed_millis(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn delay(&self, _: core::time::Duration) {
|
||||
|
||||
}
|
||||
|
||||
fn render(&mut self, _: &nes::frame::RenderFrame) {
|
||||
|
||||
}
|
||||
|
||||
fn poll_events(&mut self, _: &mut nes::joypad::Joypad) -> nes::nes::Shutdown {
|
||||
nes::nes::Shutdown::No
|
||||
}
|
||||
|
||||
fn display_region(&self) -> nes::nes::HostDisplayRegion {
|
||||
nes::nes::HostDisplayRegion::Ntsc
|
||||
}
|
||||
|
||||
fn pixel_format(&self) -> nes::nes::HostPixelFormat {
|
||||
nes::nes::HostPixelFormat::Rgb565
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
fn main() {
|
||||
#[cfg(debug_assertions)]
|
||||
panic!("run bench with --release");
|
||||
|
||||
let _profiler = dhat::Profiler::new_heap();
|
||||
|
||||
// let rom = include_bytes!("../../test-roms/nestest/nestest.nes");
|
||||
let rom = include_bytes!("../../legal-roms/drmario.nes");
|
||||
// let rom = include_bytes!("../../legal-roms/smb.nes");
|
||||
// let rom = EmbeddedRom { full: rom };
|
||||
let cart = Cartridge::blow_dust_no_heap(rom).unwrap();
|
||||
let mut nes = Nes::insert(cart, FakeHost{});
|
||||
|
||||
for _ in 0..100000000 {
|
||||
nes.tick();
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ use crate::{cpu::Cpu, memory::Bus, debugger::Debugger};
|
|||
|
||||
pub struct Mos6502 {
|
||||
cpu: Cpu,
|
||||
#[allow(dead_code)]
|
||||
debugger: Debugger,
|
||||
total_cycles: usize,
|
||||
total_ticks: usize,
|
||||
|
|
|
@ -80,7 +80,7 @@ impl AndroidHost {
|
|||
}
|
||||
}
|
||||
|
||||
impl nes::nes::HostSystem for AndroidHost {
|
||||
impl nes::nes::HostPlatform for AndroidHost {
|
||||
fn elapsed_millis(&self) -> usize {
|
||||
self.time.elapsed().as_millis() as usize
|
||||
}
|
||||
|
@ -90,8 +90,9 @@ impl nes::nes::HostSystem for AndroidHost {
|
|||
}
|
||||
|
||||
fn render(&mut self, frame: &nes::frame::RenderFrame) {
|
||||
let pixels: Vec<u8> = frame.pixels_ntsc().collect();
|
||||
unsafe {
|
||||
let jpixels: jbyteArray = self.env.byte_array_from_slice(frame.pixels()).unwrap();
|
||||
let jpixels: jbyteArray = self.env.byte_array_from_slice(&pixels).unwrap();
|
||||
let jobj = JObject::from_raw(jpixels);
|
||||
|
||||
// TODO: Is it possible/good for perf to cache the method lookup? call_method_unchecked.
|
||||
|
|
21
nes-embedded/.cargo/config
Normal file
21
nes-embedded/.cargo/config
Normal file
|
@ -0,0 +1,21 @@
|
|||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# Convert elf > uf2, copy to mounted pico
|
||||
#runner = "elf2uf2-rs -d"
|
||||
|
||||
# SWD probe
|
||||
runner = "probe-run --chip RP2040"
|
||||
|
||||
rustflags = [
|
||||
"-C", "linker=flip-link",
|
||||
"-C", "link-arg=--nmagic",
|
||||
"-C", "link-arg=-Tlink.x", # link.x pulled from cortex-m-rt crate
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
"-C", "inline-threshold=5",
|
||||
"-C", "no-vectorize-loops",
|
||||
]
|
||||
|
||||
[build]
|
||||
target = "thumbv6m-none-eabi"
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "debug"
|
2
nes-embedded/.gitignore
vendored
Normal file
2
nes-embedded/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
.vscode
|
|
@ -3,9 +3,28 @@ name = "nes-embedded"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
embedded-alloc = "0.5.0"
|
||||
rp-pico = "0.5.0" # rp-hal-boards, not needed! manually create the board here.
|
||||
nes = { path = "../nes", default-features = false }
|
||||
nes = { path = "../nes", default-features = false }
|
||||
embedded-alloc = "0.5.0" # Heap, linked_list allocator
|
||||
embedded-graphics = "0.7.1" # HAL Display drivers, drawing
|
||||
embedded-hal = { version = "0.2.5", features = ["unproven"] } # The HAL. Generic pins IO etc..
|
||||
cortex-m = { version = "0.7.2", features = ["critical-section"] } # low level access to cpu, delays etc.
|
||||
cortex-m-rt = "0.7" # Runtime, vectors, .bss init etc.
|
||||
fugit = "0.3.6" # Time, instant, delay, Hertz
|
||||
rp2040-hal = { version = "0.8.0", features = ["rt", "rom-func-cache", "critical-section-impl"] }
|
||||
st7789 = "0.7.0"
|
||||
rp2040-boot2 = "0.2.0"
|
||||
display-interface-spi = "0.4.1"
|
||||
critical-section = "1.1.1"
|
||||
defmt = "0.3"
|
||||
defmt-rtt = "0.4"
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
18
nes-embedded/build.rs
Normal file
18
nes-embedded/build.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Copy memory.x from crate-root/nes-embedded/memory.x to target output
|
||||
// By default linker looks in crate-root, but that doesn't work with workspace setup
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.expect("output path")
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.expect("memory.x path");
|
||||
|
||||
// Tell linker to look in target output
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
15
nes-embedded/memory.x
Normal file
15
nes-embedded/memory.x
Normal file
|
@ -0,0 +1,15 @@
|
|||
MEMORY
|
||||
{
|
||||
BOOT_LOADER : ORIGIN = 0x10000000, LENGTH = 0x100
|
||||
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 264K
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
|
||||
.boot_loader ORIGIN(BOOT_LOADER) :
|
||||
{
|
||||
KEEP(*(.boot_loader*));
|
||||
} > BOOT_LOADER
|
||||
|
||||
} INSERT BEFORE .text;
|
104
nes-embedded/src/board.rs
Normal file
104
nes-embedded/src/board.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use rp2040_hal as hal;
|
||||
use hal::pac as pac;
|
||||
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use hal::gpio::{Pin, PushPullOutput, FunctionSpi};
|
||||
use hal::gpio::bank0::{Gpio16, Gpio17};
|
||||
use hal::sio::SioGpioBank0;
|
||||
use hal::{Spi, spi::Enabled};
|
||||
use hal::gpio::bank0::Gpio25;
|
||||
use hal::gpio::PinId;
|
||||
|
||||
use fugit::RateExtU32;
|
||||
use display_interface_spi::SPIInterface;
|
||||
use embedded_hal::{blocking::delay::DelayUs, spi::MODE_0};
|
||||
use st7789::ST7789;
|
||||
|
||||
mod all_pins {
|
||||
rp2040_hal::bsp_pins!(
|
||||
Gpio16 {
|
||||
name: lcd_dc,
|
||||
aliases: { FunctionSpi: LcdDc }
|
||||
},
|
||||
Gpio17 {
|
||||
name: lcd_cs,
|
||||
aliases: { FunctionSpi: LcdCs }
|
||||
},
|
||||
Gpio18 {
|
||||
name: spi_sclk,
|
||||
aliases: { FunctionSpi: Sclk }
|
||||
},
|
||||
Gpio19 {
|
||||
name: spi_mosi,
|
||||
aliases: { FunctionSpi: Mosi }
|
||||
},
|
||||
Gpio25 { name: led },
|
||||
);
|
||||
}
|
||||
|
||||
pub type Screen = ST7789<
|
||||
SPIInterface<Spi<Enabled, pac::SPI0, 8>, Pin<Gpio16, PushPullOutput>, Pin<Gpio17, PushPullOutput>>,
|
||||
DummyPin,
|
||||
DummyPin
|
||||
>;
|
||||
|
||||
pub struct Pins {
|
||||
pub led: Pin<Gpio25, <Gpio25 as PinId>::Reset>
|
||||
}
|
||||
|
||||
pub struct Board {
|
||||
pub screen: Screen,
|
||||
}
|
||||
|
||||
pub struct DummyPin;
|
||||
|
||||
impl OutputPin for DummyPin {
|
||||
type Error = ();
|
||||
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn new(
|
||||
io: pac::IO_BANK0,
|
||||
pads: pac::PADS_BANK0,
|
||||
sio: SioGpioBank0,
|
||||
spi0: pac::SPI0,
|
||||
resets: &mut pac::RESETS,
|
||||
delay: &mut impl DelayUs<u32>,
|
||||
) -> (Self, Pins) {
|
||||
let pins = all_pins::Pins::new(io, pads, sio, resets);
|
||||
|
||||
let dc = pins.lcd_dc.into_push_pull_output();
|
||||
let cs = pins.lcd_cs.into_push_pull_output();
|
||||
let sck = pins.spi_sclk.into_mode::<FunctionSpi>();
|
||||
let mosi = pins.spi_mosi.into_mode::<FunctionSpi>();
|
||||
|
||||
let spi_screen = Spi::<_, _, 8>::new(spi0).init(
|
||||
resets,
|
||||
125u32.MHz(),
|
||||
16u32.MHz(),
|
||||
&MODE_0
|
||||
);
|
||||
|
||||
let spii_screen = SPIInterface::new(spi_screen, dc, cs);
|
||||
let mut screen = ST7789::new(
|
||||
spii_screen,
|
||||
None,
|
||||
None,
|
||||
320,
|
||||
240,
|
||||
);
|
||||
|
||||
screen.init(delay).unwrap();
|
||||
screen
|
||||
.set_orientation(st7789::Orientation::LandscapeSwapped)
|
||||
.unwrap();
|
||||
|
||||
(Self { screen }, Pins { led: pins.led })
|
||||
}
|
||||
}
|
79
nes-embedded/src/clocks.rs
Normal file
79
nes-embedded/src/clocks.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use rp2040_hal as hal;
|
||||
|
||||
use fugit::RateExtU32;
|
||||
use hal::{pac as pac, Watchdog, clocks::ClocksManager};
|
||||
use hal::clocks::ClockSource;
|
||||
use hal::Clock;
|
||||
|
||||
pub const XOSC_CRYSTAL_FREQ: u32 = 12_000_000;
|
||||
|
||||
pub fn configure_normal(
|
||||
xosc_dev: pac::XOSC,
|
||||
clocks_dev: pac::CLOCKS,
|
||||
pll_sys_dev: pac::PLL_SYS,
|
||||
pll_usb_dev: pac::PLL_USB,
|
||||
resets: &mut pac::RESETS,
|
||||
watchdog: &mut Watchdog,
|
||||
) {
|
||||
hal::clocks::init_clocks_and_plls(
|
||||
XOSC_CRYSTAL_FREQ,
|
||||
xosc_dev,
|
||||
clocks_dev,
|
||||
pll_sys_dev,
|
||||
pll_usb_dev,
|
||||
resets,
|
||||
watchdog,
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn configure_overclock(
|
||||
xosc_dev: pac::XOSC,
|
||||
clocks_dev: pac::CLOCKS,
|
||||
pll_sys_dev: pac::PLL_SYS,
|
||||
pll_usb_dev: pac::PLL_USB,
|
||||
resets: &mut pac::RESETS,
|
||||
watchdog: &mut Watchdog,
|
||||
) -> ClocksManager {
|
||||
let xosc = hal::xosc::setup_xosc_blocking(
|
||||
xosc_dev,
|
||||
XOSC_CRYSTAL_FREQ.Hz()
|
||||
).unwrap();
|
||||
|
||||
watchdog.enable_tick_generation((XOSC_CRYSTAL_FREQ / 1_000_000) as u8);
|
||||
|
||||
let mut clocks = ClocksManager::new(clocks_dev);
|
||||
|
||||
let pll_sys = hal::pll::setup_pll_blocking(
|
||||
pll_sys_dev,
|
||||
xosc.operating_frequency(),
|
||||
hal::pll::PLLConfig {
|
||||
vco_freq: fugit::HertzU32::MHz(1500),
|
||||
refdiv: 1,
|
||||
post_div1: 3,
|
||||
post_div2: 2,
|
||||
},
|
||||
&mut clocks,
|
||||
resets,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let pll_usb = hal::pll::setup_pll_blocking(
|
||||
pll_usb_dev,
|
||||
xosc.operating_frequency(),
|
||||
hal::pll::common_configs::PLL_USB_48MHZ,
|
||||
&mut clocks,
|
||||
resets,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
clocks
|
||||
.system_clock
|
||||
.configure_clock(&pll_sys, pll_sys.get_freq())
|
||||
.unwrap();
|
||||
|
||||
clocks.init_default(&xosc, &pll_sys, &pll_usb).unwrap();
|
||||
|
||||
clocks
|
||||
}
|
|
@ -1,32 +1,163 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(alloc_error_handler)]
|
||||
|
||||
use pimoroni_pico_explorer::entry;
|
||||
use nes::{cartridge::Cartridge, nes::Nes};
|
||||
use rp_pico::entry;
|
||||
use rp2040_hal as hal;
|
||||
use hal::{multicore::Stack, sio::SioFifo, pac, Sio, Watchdog};
|
||||
|
||||
// PicoHost?
|
||||
struct EmbeddedHost {
|
||||
mod clocks;
|
||||
mod board;
|
||||
|
||||
use board::Board;
|
||||
use critical_section::Mutex;
|
||||
use defmt::*;
|
||||
use defmt_rtt as _;
|
||||
use panic_probe as _;
|
||||
use embedded_alloc::Heap;
|
||||
use nes::{cartridge::Cartridge, nes::{Nes, HostPixelFormat}, frame::PixelFormat};
|
||||
use core::{alloc::Layout, cell::RefCell};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
pixelcolor::Rgb565, image::{ImageRaw, Image},
|
||||
};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use hal::multicore::Multicore;
|
||||
use hal::clocks::Clock;
|
||||
|
||||
#[link_section = ".boot_loader"]
|
||||
#[used]
|
||||
pub static BOOT_LOADER: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
|
||||
|
||||
#[global_allocator]
|
||||
static HEAP: Heap = Heap::empty();
|
||||
|
||||
const FRAME_BUF_SIZE: usize = nes::frame::NTSC_WIDTH * nes::frame::NTSC_HEIGHT * nes::frame::PixelFormatRGB565::BYTES_PER_PIXEL;
|
||||
static FRAME_BUF: Mutex<RefCell<[u8; FRAME_BUF_SIZE]>> = Mutex::new(RefCell::new([0; FRAME_BUF_SIZE]));
|
||||
|
||||
static mut CORE1_STACK: Stack<2048> = Stack::new();
|
||||
|
||||
#[alloc_error_handler]
|
||||
fn oom(_: Layout) -> ! {
|
||||
defmt::panic!("OOM");
|
||||
}
|
||||
|
||||
impl nes::nes::HostSystem for EmbeddedHost {
|
||||
struct EmbeddedHost {
|
||||
start: bool,
|
||||
core1: SioFifo,
|
||||
}
|
||||
|
||||
impl EmbeddedHost {
|
||||
fn new(core1: SioFifo) -> Self {
|
||||
Self {
|
||||
start: false,
|
||||
core1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl nes::nes::HostPlatform for EmbeddedHost {
|
||||
fn pixel_format(&self) -> HostPixelFormat {
|
||||
HostPixelFormat::Rgb565
|
||||
}
|
||||
|
||||
fn render(&mut self, frame: &nes::frame::RenderFrame) {
|
||||
todo!()
|
||||
critical_section::with(|cs| {
|
||||
let mut framebuf = FRAME_BUF.borrow_ref_mut(cs);
|
||||
for (i, p) in frame.pixels_ntsc().enumerate() {
|
||||
framebuf[i] = p;
|
||||
}
|
||||
});
|
||||
|
||||
self.core1.write(1);
|
||||
}
|
||||
|
||||
fn poll_events(&mut self, joypad: &mut nes::joypad::Joypad) -> nes::nes::Shutdown {
|
||||
todo!()
|
||||
nes::nes::Shutdown::No
|
||||
}
|
||||
}
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let rom = include_bytes!("../../legal-roms/smb.nes");
|
||||
let cart = Cartridge::load(rom).unwrap();
|
||||
let nes = Nes::insert(cart, EmbeddedHost {});
|
||||
fn core1_render(sys_freq: u32) -> ! {
|
||||
let mut pac = unsafe { pac::Peripherals::steal() };
|
||||
let core = unsafe { pac::CorePeripherals::steal() };
|
||||
|
||||
let mut sio = Sio::new(pac.SIO);
|
||||
let mut delay = cortex_m::delay::Delay::new(core.SYST, sys_freq);
|
||||
|
||||
let (mut board, pins) = Board::new(
|
||||
pac.IO_BANK0,
|
||||
pac.PADS_BANK0,
|
||||
sio.gpio_bank0,
|
||||
pac.SPI0,
|
||||
&mut pac.RESETS,
|
||||
&mut delay,
|
||||
);
|
||||
|
||||
board.screen.clear(Rgb565::BLACK).unwrap();
|
||||
|
||||
let mut led_pin = pins.led.into_push_pull_output();
|
||||
let mut frame_n = 0;
|
||||
|
||||
info!("core 1 loopin");
|
||||
loop {
|
||||
nes.tick()
|
||||
let _ = sio.fifo.read_blocking();
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let frame = FRAME_BUF.borrow(cs).borrow();
|
||||
|
||||
if frame_n % 2 == 0 {
|
||||
led_pin.set_high().unwrap();
|
||||
} else {
|
||||
led_pin.set_low().unwrap();
|
||||
}
|
||||
|
||||
let raw_image = ImageRaw::<Rgb565>::new(&frame[..], nes::frame::NTSC_WIDTH as u32);
|
||||
let image = Image::new(&raw_image, Point::zero());
|
||||
image.draw(&mut board.screen).unwrap();
|
||||
});
|
||||
|
||||
frame_n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hal::entry]
|
||||
fn main() -> ! {
|
||||
{
|
||||
use core::mem::MaybeUninit;
|
||||
const HEAP_SIZE: usize = 140000;
|
||||
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
|
||||
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
|
||||
}
|
||||
|
||||
let mut pac = pac::Peripherals::take().unwrap();
|
||||
let mut watchdog = Watchdog::new(pac.WATCHDOG);
|
||||
let clocks = clocks::configure_overclock(
|
||||
pac.XOSC,
|
||||
pac.CLOCKS,
|
||||
pac.PLL_SYS,
|
||||
pac.PLL_USB,
|
||||
&mut pac.RESETS,
|
||||
&mut watchdog
|
||||
);
|
||||
|
||||
let mut sio = Sio::new(pac.SIO);
|
||||
|
||||
let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo);
|
||||
let cores = mc.cores();
|
||||
let core1 = &mut cores[1];
|
||||
|
||||
info!("booting core1");
|
||||
let sys_freq = clocks.system_clock.freq().to_Hz();
|
||||
core1.spawn(unsafe { &mut CORE1_STACK.mem }, move || {
|
||||
core1_render(sys_freq);
|
||||
})
|
||||
.expect("core1 failed");
|
||||
|
||||
let rom = include_bytes!(env!("ROM"));
|
||||
let cart = Cartridge::blow_dust_no_heap(rom).unwrap();
|
||||
let host = EmbeddedHost::new(sio.fifo);
|
||||
let mut nes = Nes::insert(cart, host);
|
||||
|
||||
loop {
|
||||
nes.tick();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use structopt::StructOpt;
|
|||
use common::utils;
|
||||
|
||||
mod sdl;
|
||||
use crate::sdl::SdlHostSystem;
|
||||
use crate::sdl::SdlHostPlatform;
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
struct Cli {
|
||||
|
@ -26,7 +26,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let cartridge = Cartridge::blow_dust(args.path)?;
|
||||
println!("Loaded! {}", cartridge);
|
||||
|
||||
let mut nes = Nes::insert(cartridge, SdlHostSystem::new());
|
||||
let mut nes = Nes::insert(cartridge, SdlHostPlatform::new());
|
||||
nes.show_fps(std::env::var("SHOW_FPS").is_ok());
|
||||
|
||||
let debugger = nes.debugger();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use nes::{joypad::{Joypad, JoypadEvent, JoypadButton}, frame::{RenderFrame, DisplayRegion, DisplayRegionNTSC, DisplayRegionPAL}, nes::{HostSystem, Shutdown, HostDisplayRegion}};
|
||||
use nes::{joypad::{Joypad, JoypadEvent, JoypadButton}, frame::{RenderFrame}, nes::{HostPlatform, Shutdown}};
|
||||
use sdl2::{pixels::PixelFormatEnum, event::Event, keyboard::Keycode, Sdl, render::{Texture, Canvas, TextureCreator}, video::{Window, WindowContext}};
|
||||
|
||||
pub struct SdlHostSystem<'a> {
|
||||
pub struct SdlHostPlatform<'a> {
|
||||
context: Sdl,
|
||||
canvas: Canvas<Window>,
|
||||
texture: Texture<'a>,
|
||||
|
@ -11,12 +11,12 @@ pub struct SdlHostSystem<'a> {
|
|||
time: Instant,
|
||||
}
|
||||
|
||||
impl SdlHostSystem<'_> {
|
||||
impl SdlHostPlatform<'_> {
|
||||
pub fn new() -> Self {
|
||||
// TODO: Inject
|
||||
let scale = 4;
|
||||
let w = DisplayRegionNTSC::WIDTH as u32;
|
||||
let h = DisplayRegionNTSC::HEIGHT as u32;
|
||||
let w = nes::frame::NTSC_WIDTH as u32;
|
||||
let h = nes::frame::NTSC_HEIGHT as u32;
|
||||
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
|
@ -49,9 +49,10 @@ impl SdlHostSystem<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl HostSystem for SdlHostSystem<'_> {
|
||||
impl HostPlatform for SdlHostPlatform<'_> {
|
||||
fn render(&mut self, frame: &RenderFrame) {
|
||||
self.texture.update(None, frame.pixels(), frame.pitch()).unwrap();
|
||||
let pixels: Vec<u8> = frame.pixels_ntsc().collect();
|
||||
self.texture.update(None, &pixels, frame.pitch_ntsc()).unwrap();
|
||||
self.canvas.copy(&self.texture, None, None).unwrap();
|
||||
self.canvas.present();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use nes::{cartridge::Cartridge, nes::{Nes, HostSystem, Shutdown}, joypad::{JoypadButton, JoypadEvent}, frame::{PixelFormat, SetPixel, DisplayRegionNTSC}};
|
||||
use nes::{cartridge::Cartridge, nes::{Nes, HostPlatform, Shutdown}, joypad::{JoypadButton, JoypadEvent}, frame::{PixelFormat, SetPixel}};
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
|
||||
pub struct PixelFormatRGBA8888;
|
||||
|
@ -47,19 +47,21 @@ pub fn main() -> Result<(), JsValue> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
struct WasmHostSystem {
|
||||
struct WasmHostPlatform {
|
||||
browser: BrowserNes,
|
||||
keyboard: KeyboardState,
|
||||
time: wasm_timer::Instant
|
||||
}
|
||||
|
||||
impl HostSystem for WasmHostSystem {
|
||||
impl HostPlatform for WasmHostPlatform {
|
||||
fn alloc_render_frame(&self) -> nes::frame::RenderFrame {
|
||||
nes::frame::RenderFrame::new::<DisplayRegionNTSC, PixelFormatRGBA8888>()
|
||||
nes::frame::RenderFrame::new::<PixelFormatRGBA8888>()
|
||||
}
|
||||
|
||||
fn render(&mut self, frame: &nes::frame::RenderFrame) {
|
||||
self.browser.on_frame_ready(frame.pixels().as_ptr(), frame.pixels().len());
|
||||
let pixels: Vec<u8> = frame.pixels_ntsc().collect();
|
||||
// assert_eq!(pixels.len(), 224 * 240 * 4);
|
||||
self.browser.on_frame_ready(pixels.as_ptr(), pixels.len());
|
||||
}
|
||||
|
||||
fn poll_events(&mut self, joypad: &mut nes::joypad::Joypad) -> Shutdown {
|
||||
|
@ -99,7 +101,7 @@ impl HostSystem for WasmHostSystem {
|
|||
}
|
||||
}
|
||||
|
||||
impl WasmHostSystem {
|
||||
impl WasmHostPlatform {
|
||||
pub fn new(browser: BrowserNes) -> Self {
|
||||
Self { browser, keyboard: KeyboardState::default(), time: wasm_timer::Instant::now() }
|
||||
}
|
||||
|
@ -121,7 +123,7 @@ impl NesWasm {
|
|||
};
|
||||
|
||||
log(format!("nes init! {}", cart).as_str());
|
||||
let nes = Nes::insert(cart, WasmHostSystem::new(browser));
|
||||
let nes = Nes::insert(cart, WasmHostPlatform::new(browser));
|
||||
Self { nes }
|
||||
}
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ impl Rom for EmbeddedRom {
|
|||
}
|
||||
|
||||
fn get(&self) -> &[u8] {
|
||||
&self.0
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +240,7 @@ impl<R : Rom> Cartridge<R> {
|
|||
Ok(Cartridge {
|
||||
prg: prg_start..prg_end,
|
||||
chr: chr_range,
|
||||
rom: rom,
|
||||
rom,
|
||||
mirroring,
|
||||
mapper,
|
||||
format,
|
||||
|
@ -254,12 +254,13 @@ impl<R : Rom> Cartridge<R> {
|
|||
}
|
||||
|
||||
pub fn prg(&self) -> &[u8] {
|
||||
// TODO: expensive to slice each r/w? have slice refs ready?
|
||||
// TODO: Perf, expensive to slice each r/w? have slice refs ready?
|
||||
&self.rom.get()[self.prg.start..self.prg.end]
|
||||
}
|
||||
|
||||
pub fn chr(&self) -> &[u8] {
|
||||
if let Some(chr_ram) = &self.chr_ram {
|
||||
// TODO: Perf, get rid of this branch
|
||||
if let Some(chr_ram) = &self.chr_ram {
|
||||
&chr_ram[..]
|
||||
} else {
|
||||
&self.rom.get()[self.chr.start..self.chr.end]
|
||||
|
|
|
@ -44,79 +44,64 @@ impl SetPixel for PixelFormatRGB565 {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait DisplayRegion {
|
||||
const WIDTH: usize;
|
||||
const HEIGHT: usize;
|
||||
const OVERSCAN_PIXELS: usize;
|
||||
}
|
||||
// Also PAL res
|
||||
pub const NES_WIDTH: usize = 256;
|
||||
pub const NES_HEIGHT: usize = 240;
|
||||
|
||||
pub struct DisplayRegionNTSC;
|
||||
|
||||
impl DisplayRegion for DisplayRegionNTSC {
|
||||
const WIDTH: usize = 240;
|
||||
const HEIGHT: usize = 224;
|
||||
const OVERSCAN_PIXELS: usize = 8;
|
||||
}
|
||||
|
||||
pub struct DisplayRegionPAL;
|
||||
|
||||
impl DisplayRegion for DisplayRegionPAL {
|
||||
const WIDTH: usize = 256;
|
||||
const HEIGHT: usize = 240;
|
||||
const OVERSCAN_PIXELS: usize = 0;
|
||||
}
|
||||
pub const NTSC_WIDTH: usize = 240;
|
||||
pub const NTSC_HEIGHT: usize = 224;
|
||||
const NTSC_OVERSCAN_PIXELS: usize = 8;
|
||||
|
||||
pub struct RenderFrame {
|
||||
width: usize,
|
||||
bytes_per_pixel: usize,
|
||||
overscan_pixels: usize,
|
||||
buf: Vec<u8>,
|
||||
set_pixel_fn: SetPixelFn
|
||||
set_pixel_fn: SetPixelFn,
|
||||
pitch_ntsc: usize,
|
||||
pitch_pal: usize,
|
||||
}
|
||||
|
||||
impl RenderFrame {
|
||||
pub fn new<DISPLAY, FORMAT>() -> Self
|
||||
pub fn new<FORMAT>() -> Self
|
||||
where
|
||||
DISPLAY : DisplayRegion,
|
||||
FORMAT : PixelFormat + SetPixel + 'static
|
||||
{
|
||||
Self {
|
||||
width: DISPLAY::WIDTH,
|
||||
bytes_per_pixel: FORMAT::BYTES_PER_PIXEL,
|
||||
overscan_pixels: DISPLAY::OVERSCAN_PIXELS,
|
||||
buf: vec![0; DISPLAY::WIDTH * DISPLAY::HEIGHT * FORMAT::BYTES_PER_PIXEL],
|
||||
set_pixel_fn: FORMAT::set_pixel
|
||||
buf: vec![0; NES_WIDTH * NES_HEIGHT * FORMAT::BYTES_PER_PIXEL],
|
||||
set_pixel_fn: FORMAT::set_pixel,
|
||||
pitch_ntsc: NTSC_WIDTH * FORMAT::BYTES_PER_PIXEL,
|
||||
pitch_pal: NES_WIDTH * FORMAT::BYTES_PER_PIXEL,
|
||||
}
|
||||
}
|
||||
|
||||
fn overscan(&self, x: &mut usize, y: &mut usize) {
|
||||
// 0 == no draw
|
||||
// width - 8 == no draw
|
||||
// 8 == 0
|
||||
if *x >= self.overscan_pixels {
|
||||
*x -= self.overscan_pixels;
|
||||
}
|
||||
|
||||
if *y >= self.overscan_pixels {
|
||||
*y -= self.overscan_pixels;
|
||||
}
|
||||
// The NES PPU always generates a 256x240 pixel picture.
|
||||
pub fn set_pixel_xy(&mut self, x: usize, y: usize, rgb: (u8, u8, u8)) {
|
||||
let i = ((y * NES_WIDTH) + x) * self.bytes_per_pixel;
|
||||
(self.set_pixel_fn)(&mut self.buf, i, rgb);
|
||||
}
|
||||
|
||||
pub fn set_pixel_xy(&mut self, mut x: usize, mut y: usize, rgb: (u8, u8, u8)) {
|
||||
self.overscan(&mut x, &mut y);
|
||||
|
||||
let i = ((y * self.width) + x) * self.bytes_per_pixel;
|
||||
if i < self.buf.len() {
|
||||
(self.set_pixel_fn)(&mut self.buf, i, rgb);
|
||||
}
|
||||
pub fn pixels_pal(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
|
||||
pub fn pixels(&self) -> &[u8] {
|
||||
&self.buf[..]
|
||||
// This is an iter to avoid allocations
|
||||
pub fn pixels_ntsc(&self) -> impl Iterator<Item = u8> + '_ {
|
||||
let buf_pitch = NES_WIDTH * self.bytes_per_pixel;
|
||||
let overscan_pitch = NTSC_OVERSCAN_PIXELS * self.bytes_per_pixel;
|
||||
self.buf.chunks(buf_pitch) // Chunk as rows
|
||||
.skip(NTSC_OVERSCAN_PIXELS) // Skip first X rows
|
||||
.map(move |row| &row[overscan_pitch..buf_pitch - overscan_pitch]) // Skip col edges
|
||||
.take(NTSC_HEIGHT)
|
||||
.flatten()
|
||||
.copied()
|
||||
}
|
||||
|
||||
pub fn pitch(&self) -> usize {
|
||||
self.width * self.bytes_per_pixel
|
||||
pub fn pitch_ntsc(&self) -> usize {
|
||||
self.pitch_ntsc
|
||||
}
|
||||
|
||||
pub fn pitch_pal(&self) -> usize {
|
||||
self.pitch_pal
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ pub(crate) struct CNROM<R : Rom> {
|
|||
is_16kb: bool,
|
||||
}
|
||||
|
||||
impl<R : Rom> Mapper for CNROM<R> {}
|
||||
|
||||
impl<R: Rom> CNROM<R> {
|
||||
pub fn new(cart: Cartridge<R>) -> Self {
|
||||
let is_16kb = match cart.prg().len() {
|
||||
|
@ -30,12 +32,6 @@ impl<R: Rom> CNROM<R> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R : Rom> Mapper for CNROM<R> {
|
||||
fn mirroring(&self) -> crate::cartridge::Mirroring {
|
||||
self.cart.mirroring()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R : Rom> Bus for CNROM<R> {
|
||||
fn read8(&self, address: u16) -> u8 {
|
||||
match address {
|
||||
|
|
|
@ -46,6 +46,8 @@ pub struct MMC1<R : Rom> {
|
|||
shift_register: u8,
|
||||
}
|
||||
|
||||
impl<R : Rom> Mapper for MMC1<R> {}
|
||||
|
||||
impl<R : Rom> MMC1<R> {
|
||||
pub fn new(cart: Cartridge<R>) -> Self {
|
||||
let mirroring = cart.mirroring();
|
||||
|
@ -178,12 +180,6 @@ impl<R : Rom> MMC1<R> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R : Rom> Mapper for MMC1<R> {
|
||||
fn mirroring(&self) -> crate::cartridge::Mirroring {
|
||||
self.mirroring
|
||||
}
|
||||
}
|
||||
|
||||
impl <R : Rom>Bus for MMC1<R> {
|
||||
fn read8(&self, address: u16) -> u8 {
|
||||
// println!("Read: {:#06x}", address);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use core::panic;
|
||||
use common::kilobytes;
|
||||
use mos6502::memory::Bus;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use crate::cartridge::{Cartridge, Mirroring, Rom};
|
||||
|
||||
use super::Mapper;
|
||||
|
@ -25,7 +25,7 @@ pub struct MMC3<R : Rom> {
|
|||
prg_rom_bank_mode: PrgBankMode,
|
||||
|
||||
chr_rom_bank_mode: ChrBankMode,
|
||||
mirroring: Option<Mirroring>,
|
||||
mirroring_cb: Option<Box<dyn FnMut(&Mirroring)>>,
|
||||
|
||||
registers: [u8; 8],
|
||||
register_to_update: u8, // 3 bits
|
||||
|
@ -43,7 +43,7 @@ impl<R : Rom> MMC3<R> {
|
|||
cart,
|
||||
prg_rom_bank_mode: PrgBankMode::Swap8000FixC000_0,
|
||||
chr_rom_bank_mode: ChrBankMode::TwoKbAt0000_0,
|
||||
mirroring: None,
|
||||
mirroring_cb: None,
|
||||
registers: [0; 8],
|
||||
register_to_update: 0,
|
||||
irq_enabled: false,
|
||||
|
@ -106,13 +106,8 @@ impl<R : Rom> MMC3<R> {
|
|||
}
|
||||
|
||||
impl<R : Rom> Mapper for MMC3<R> {
|
||||
fn mirroring(&self) -> crate::cartridge::Mirroring {
|
||||
// This bit has no effect on cartridges with hardwired 4-screen VRAM.
|
||||
// In the iNES and NES 2.0 formats, this can be identified through bit 3 of byte $06 of the header.
|
||||
if self.cart.mirroring() == Mirroring::HardwiredFourScreen || self.mirroring.is_none() {
|
||||
return self.cart.mirroring()
|
||||
}
|
||||
self.mirroring.unwrap()
|
||||
fn on_runtime_mirroring(&mut self, cb: Box<dyn FnMut(&Mirroring)>) {
|
||||
self.mirroring_cb = Some(cb);
|
||||
}
|
||||
|
||||
fn irq(&mut self) -> bool {
|
||||
|
@ -171,8 +166,18 @@ impl<R : Rom> Bus for MMC3<R> {
|
|||
}
|
||||
0xa000..=0xbfff => {
|
||||
if even {
|
||||
// Mirroring
|
||||
self.mirroring = if val & 1 == 1 { Some(Mirroring::Horizontal) } else { Some(Mirroring::Vertical) };
|
||||
let runtime_mirroring = if val & 1 == 1 {
|
||||
Mirroring::Horizontal
|
||||
} else {
|
||||
Mirroring::Vertical
|
||||
};
|
||||
|
||||
if self.cart.mirroring() != runtime_mirroring {
|
||||
let cb = self.mirroring_cb
|
||||
.as_mut()
|
||||
.expect("mirroring changed, no one to tell");
|
||||
(*cb)(&runtime_mirroring)
|
||||
}
|
||||
}
|
||||
// Odd: PRG RAM protect
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use core::cell::RefCell;
|
||||
use alloc::rc::Rc;
|
||||
use mos6502::memory::Bus;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use crate::cartridge::{Cartridge, Mirroring, Rom};
|
||||
|
||||
mod mmc1;
|
||||
|
@ -11,7 +11,7 @@ mod mmc3;
|
|||
mod uxrom;
|
||||
|
||||
pub trait Mapper : Bus {
|
||||
fn mirroring(&self) -> Mirroring;
|
||||
fn on_runtime_mirroring(&mut self, _: Box<dyn FnMut(&Mirroring)>) {}
|
||||
fn irq(&mut self) -> bool { false }
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ pub struct NROM<R : Rom> {
|
|||
is_16kb: bool
|
||||
}
|
||||
|
||||
impl<R : Rom> Mapper for NROM<R> {}
|
||||
|
||||
impl<R : Rom> NROM<R> {
|
||||
pub fn new(cart: Cartridge<R>) -> Self {
|
||||
let is_16kb = match cart.prg().len() {
|
||||
|
@ -26,12 +28,6 @@ impl<R : Rom> NROM<R> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R : Rom> Mapper for NROM<R> {
|
||||
fn mirroring(&self) -> crate::cartridge::Mirroring {
|
||||
self.cart.mirroring()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R : Rom> Bus for NROM<R> {
|
||||
fn read8(&self, address: u16) -> u8 {
|
||||
match address {
|
||||
|
|
|
@ -11,6 +11,8 @@ pub struct UxROM<R : Rom> {
|
|||
num_banks: usize,
|
||||
}
|
||||
|
||||
impl<R : Rom> Mapper for UxROM<R> {}
|
||||
|
||||
impl<R : Rom> UxROM<R> {
|
||||
pub fn new(cart: Cartridge<R>) -> Self {
|
||||
Self {
|
||||
|
@ -21,12 +23,6 @@ impl<R : Rom> UxROM<R> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R : Rom> Mapper for UxROM<R> {
|
||||
fn mirroring(&self) -> crate::cartridge::Mirroring {
|
||||
self.cart.mirroring()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R : Rom> Bus for UxROM<R> {
|
||||
fn read8(&self, address: u16) -> u8 {
|
||||
let address = address as usize;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use core::{cell::RefCell, time::Duration};
|
||||
use alloc::{rc::Rc, boxed::Box, string::ToString};
|
||||
#[allow(unused_imports)]
|
||||
use mos6502::{mos6502::Mos6502, memory::Bus, cpu::{Cpu, Reg}, debugger::Debugger};
|
||||
use crate::{cartridge::{Cartridge, Rom}, nesbus::NesBus, ppu::ppu::{Ppu, TickEvent}, joypad::Joypad, frame::{RenderFrame, PixelFormatRGB888, DisplayRegionNTSC, PixelFormatRGB565, DisplayRegionPAL}, trace, fonts};
|
||||
use crate::{cartridge::{Cartridge, Rom}, nesbus::NesBus, ppu::ppu::{Ppu, TickEvent}, joypad::Joypad, frame::{RenderFrame, PixelFormatRGB888, PixelFormatRGB565}, trace, fonts};
|
||||
|
||||
const DEFAULT_FPS_MAX: usize = 60;
|
||||
|
||||
|
@ -14,49 +15,35 @@ impl From<bool> for Shutdown {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Default)]
|
||||
pub enum HostDisplayRegion { #[default] Ntsc, Pal }
|
||||
|
||||
#[derive(PartialEq, Default)]
|
||||
pub enum HostPixelFormat { #[default] Rgb888, Rgb565 }
|
||||
|
||||
pub trait HostSystem {
|
||||
pub trait HostPlatform {
|
||||
fn render(&mut self, frame: &RenderFrame);
|
||||
fn poll_events(&mut self, joypad: &mut Joypad) -> Shutdown;
|
||||
|
||||
fn elapsed_millis(&self) -> usize {
|
||||
// Not required. Up to platform to implement for FPS control.
|
||||
return 0;
|
||||
0
|
||||
}
|
||||
|
||||
fn delay(&self, _: Duration) {
|
||||
// Not required. Up to platform to implement for FPS control.
|
||||
}
|
||||
|
||||
fn display_region(&self) -> HostDisplayRegion { HostDisplayRegion::default() }
|
||||
fn pixel_format(&self) -> HostPixelFormat { HostPixelFormat::default() }
|
||||
|
||||
fn alloc_render_frame(&self) -> RenderFrame {
|
||||
match (self.display_region(), self.pixel_format()) {
|
||||
(HostDisplayRegion::Ntsc, HostPixelFormat::Rgb888) => {
|
||||
RenderFrame::new::<DisplayRegionNTSC, PixelFormatRGB888>()
|
||||
}
|
||||
(HostDisplayRegion::Ntsc, HostPixelFormat::Rgb565) => {
|
||||
RenderFrame::new::<DisplayRegionNTSC, PixelFormatRGB565>()
|
||||
}
|
||||
(HostDisplayRegion::Pal, HostPixelFormat::Rgb888) => {
|
||||
RenderFrame::new::<DisplayRegionPAL, PixelFormatRGB888>()
|
||||
}
|
||||
(HostDisplayRegion::Pal, HostPixelFormat::Rgb565) => {
|
||||
RenderFrame::new::<DisplayRegionPAL, PixelFormatRGB565>()
|
||||
}
|
||||
match self.pixel_format() {
|
||||
HostPixelFormat::Rgb888 => RenderFrame::new::<PixelFormatRGB888>(),
|
||||
HostPixelFormat::Rgb565 => RenderFrame::new::<PixelFormatRGB565>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct HeadlessHost;
|
||||
impl HostSystem for HeadlessHost {
|
||||
impl HostPlatform for HeadlessHost {
|
||||
fn render(&mut self, _: &RenderFrame) {}
|
||||
fn poll_events(&mut self, _: &mut Joypad) -> Shutdown { Shutdown::No }
|
||||
fn elapsed_millis(&self) -> usize { 0 }
|
||||
|
@ -66,7 +53,7 @@ impl HostSystem for HeadlessHost {
|
|||
pub struct Nes {
|
||||
machine: Mos6502,
|
||||
ppu: Rc<RefCell<Ppu>>,
|
||||
host: Box<dyn HostSystem>,
|
||||
host: Box<dyn HostPlatform>,
|
||||
joypad: Rc<RefCell<Joypad>>,
|
||||
timing: FrameTiming,
|
||||
show_fps: bool,
|
||||
|
@ -74,11 +61,12 @@ pub struct Nes {
|
|||
}
|
||||
|
||||
impl Nes {
|
||||
pub fn insert<H : HostSystem + 'static, R : Rom + 'static>(cartridge: Cartridge<R>, host: H) -> Self {
|
||||
pub fn insert<H : HostPlatform + 'static, R : Rom + 'static>(cartridge: Cartridge<R>, host: H) -> Self {
|
||||
let mirroring = cartridge.mirroring();
|
||||
let rom_mapper = crate::mappers::for_cart(cartridge);
|
||||
|
||||
let frame = host.alloc_render_frame();
|
||||
let ppu = Rc::new(RefCell::new(Ppu::new(rom_mapper.clone(), frame)));
|
||||
let ppu = Rc::new(RefCell::new(Ppu::new(rom_mapper.clone(), mirroring, frame)));
|
||||
let joypad = Rc::new(RefCell::new(Joypad::default()));
|
||||
let bus = NesBus::new(rom_mapper.clone(), ppu.clone(), joypad.clone());
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ impl Bus for NesBus {
|
|||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
MappedDevice::CpuTest => (), // TODO
|
||||
MappedDevice::CpuTest => (),
|
||||
MappedDevice::Cartridge => self.rom.borrow_mut().write8(val, address),
|
||||
}
|
||||
}
|
||||
|
@ -98,16 +98,12 @@ impl Bus for NesBus {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::frame::{RenderFrame, DisplayRegionNTSC, PixelFormatRGB888};
|
||||
use crate::{frame::{RenderFrame, PixelFormatRGB888}, cartridge::Mirroring};
|
||||
use super::*;
|
||||
|
||||
struct TestBus{}
|
||||
|
||||
impl Mapper for TestBus {
|
||||
fn mirroring(&self) -> crate::cartridge::Mirroring {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
impl Mapper for TestBus {}
|
||||
|
||||
impl Bus for TestBus {
|
||||
fn read8(&self, _: u16) -> u8 {
|
||||
|
@ -122,10 +118,10 @@ mod tests {
|
|||
fn sut() -> NesBus {
|
||||
let bus = Rc::new(RefCell::new(TestBus{}));
|
||||
let joypad = Joypad::default();
|
||||
let frame = RenderFrame::new::<DisplayRegionNTSC, PixelFormatRGB888>();
|
||||
let frame = RenderFrame::new::<PixelFormatRGB888>();
|
||||
NesBus::new(
|
||||
bus.clone(),
|
||||
Rc::new(RefCell::new(Ppu::new(bus, frame))),
|
||||
Rc::new(RefCell::new(Ppu::new(bus, Mirroring::Horizontal, frame))),
|
||||
Rc::new(RefCell::new(joypad))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,74 @@ pub const BLARRG_PALETTE: [u8; PALETTE_SIZE] = [
|
|||
0x00,0x20,0x2C,0x08
|
||||
];
|
||||
|
||||
// https://www.nesdev.org/wiki/PPU_palettes
|
||||
static PALETTE_RGB: [(u8, u8, u8); 64] = [
|
||||
(101, 101, 101),
|
||||
(0 , 45, 105),
|
||||
(19, 31, 127),
|
||||
(69 , 19, 124),
|
||||
(96 , 11, 98),
|
||||
(115, 10, 55),
|
||||
(113, 15, 7),
|
||||
(90 , 26, 0),
|
||||
(52 , 40, 0),
|
||||
(11 , 52, 0),
|
||||
(0, 60, 0),
|
||||
(0, 61, 16),
|
||||
(0, 56, 64),
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
(174,174 ,174),
|
||||
(15 , 99,179),
|
||||
(64 , 81, 208),
|
||||
(120, 65, 204),
|
||||
(167, 54, 169),
|
||||
(192, 52, 112),
|
||||
(189, 60, 48),
|
||||
(159, 74, 0),
|
||||
(109, 92, 0),
|
||||
(54 , 109 , 0),
|
||||
(7 , 119 , 4),
|
||||
(0 , 121 , 61),
|
||||
(0, 114 ,125),
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
(254, 254, 255),
|
||||
(93, 179, 255),
|
||||
(143, 161, 255),
|
||||
(200, 144, 255),
|
||||
(247, 133, 250),
|
||||
(255, 131, 192),
|
||||
(255, 139, 127),
|
||||
(239, 154, 73),
|
||||
(189, 172, 44),
|
||||
(133, 188, 47),
|
||||
(85, 199, 83),
|
||||
(60, 201, 140),
|
||||
(62, 194, 205),
|
||||
(78, 78, 78),
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
(254, 254, 255),
|
||||
(188, 223, 255),
|
||||
(209, 216, 255),
|
||||
(232, 209, 255),
|
||||
(251, 205, 253),
|
||||
(255, 204, 229),
|
||||
(255, 207, 202),
|
||||
(248, 213, 180),
|
||||
(228, 220, 168),
|
||||
(204, 227, 169),
|
||||
(185, 232, 184),
|
||||
(174, 232, 208),
|
||||
(175, 229, 234),
|
||||
(182, 182, 182),
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
];
|
||||
|
||||
pub struct Palette {
|
||||
data: [u8; PALETTE_SIZE]
|
||||
}
|
||||
|
@ -34,7 +102,7 @@ impl Palette {
|
|||
}
|
||||
|
||||
pub fn rgb_from_index(&self, index: u8) -> (u8, u8, u8) {
|
||||
palette_to_rgb(self.data[index as usize])
|
||||
PALETTE_RGB[self.data[index as usize] as usize % 64]
|
||||
}
|
||||
|
||||
// 0x3f00..=0x3fff
|
||||
|
@ -75,78 +143,4 @@ mod tests {
|
|||
assert_eq!(Palette::mirror(0x3f18), 0x3f08);
|
||||
assert_eq!(Palette::mirror(0x3f1c), 0x3f0c);
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.nesdev.org/wiki/PPU_palettes
|
||||
pub fn palette_to_rgb(value: u8) -> (u8, u8, u8) {
|
||||
match value {
|
||||
0x00 => (101, 101, 101),
|
||||
0x01 => (0 , 45, 105),
|
||||
0x02 => (19, 31, 127),
|
||||
0x03 => (69 , 19, 124),
|
||||
0x04 => (96 , 11, 98),
|
||||
0x05 => (115, 10, 55),
|
||||
0x06 => (113, 15, 7),
|
||||
0x07 => (90 , 26, 0),
|
||||
0x08 => (52 , 40, 0),
|
||||
0x09 => (11 , 52, 0),
|
||||
0x0a => (0, 60, 0),
|
||||
0x0b => (0, 61, 16),
|
||||
0x0c => (0, 56, 64),
|
||||
0x0d => (0, 0, 0),
|
||||
0x0e => (0, 0, 0),
|
||||
0x0f => (0, 0, 0),
|
||||
0x10 => (174,174 ,174),
|
||||
0x11 => (15 , 99,179),
|
||||
0x12 => (64 , 81, 208),
|
||||
0x13 => (120, 65, 204),
|
||||
0x14 => (167, 54, 169),
|
||||
0x15 => (192, 52, 112),
|
||||
0x16 => (189, 60, 48),
|
||||
0x17 => (159, 74, 0),
|
||||
0x18 => (109, 92, 0),
|
||||
0x19 => (54 , 109 , 0),
|
||||
0x1a => (7 , 119 , 4),
|
||||
0x1b => (0 , 121 , 61),
|
||||
0x1c => (0, 114 ,125),
|
||||
0x1d => (0, 0, 0),
|
||||
0x1e => (0, 0, 0),
|
||||
0x1f => (0, 0, 0),
|
||||
0x20 => (254, 254, 255),
|
||||
0x21 => (93, 179, 255),
|
||||
0x22 => (143, 161, 255),
|
||||
0x23 => (200, 144, 255),
|
||||
0x24 => (247, 133, 250),
|
||||
0x25 => (255, 131, 192),
|
||||
0x26 => (255, 139, 127),
|
||||
0x27 => (239, 154, 73),
|
||||
0x28 => (189, 172, 44),
|
||||
0x29 => (133, 188, 47),
|
||||
0x2a => (85, 199, 83),
|
||||
0x2b => (60, 201, 140),
|
||||
0x2c => (62, 194, 205),
|
||||
0x2d => (78, 78, 78),
|
||||
0x2e => (0, 0, 0),
|
||||
0x2f => (0, 0, 0),
|
||||
0x30 => (254, 254, 255),
|
||||
0x31 => (188, 223, 255),
|
||||
0x32 => (209, 216, 255),
|
||||
0x33 => (232, 209, 255),
|
||||
0x34 => (251, 205, 253),
|
||||
0x35 => (255, 204, 229),
|
||||
0x36 => (255, 207, 202),
|
||||
0x37 => (248, 213, 180),
|
||||
0x38 => (228, 220, 168),
|
||||
0x39 => (204, 227, 169),
|
||||
0x3a => (185, 232, 184),
|
||||
0x3b => (174, 232, 208),
|
||||
0x3c => (175, 229, 234),
|
||||
0x3d => (182, 182, 182),
|
||||
0x3e => (0, 0, 0),
|
||||
0x3f => (0, 0, 0),
|
||||
_ => {
|
||||
// println!("eh");
|
||||
(0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
use core::cell::RefCell;
|
||||
use alloc::rc::Rc;
|
||||
use crate::{frame::RenderFrame, trace, ppu::state::{Phase, Rendering}, mappers::Mapper};
|
||||
use alloc::vec::Vec;
|
||||
use crate::{frame::RenderFrame, trace, ppu::state::{Phase, Rendering}, mappers::Mapper, cartridge::Mirroring};
|
||||
use super::{palette::Palette, vram::Vram, state::State};
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
|
@ -46,7 +47,7 @@ pub struct Ppu {
|
|||
|
||||
oam: [u8; 256],
|
||||
oam_address: u8,
|
||||
sprites: [Option<Sprite>; 8], // AKA secondary OAM
|
||||
sprites: Vec<Sprite>, // AKA secondary OAM
|
||||
|
||||
v: u16, // Current VRAM address (15 bits)
|
||||
t: u16, // Temporary VRAM address (15 bits); can also be thought of as the address of the top left onscreen tile.
|
||||
|
@ -74,9 +75,13 @@ pub struct Ppu {
|
|||
|
||||
#[allow(dead_code)]
|
||||
impl Ppu {
|
||||
pub fn new(mapper: Rc<RefCell<dyn Mapper>>, frame: RenderFrame) -> Ppu {
|
||||
pub fn new(
|
||||
mapper: Rc<RefCell<dyn Mapper>>,
|
||||
cart_mirroring: Mirroring,
|
||||
frame: RenderFrame,
|
||||
) -> Ppu {
|
||||
Ppu {
|
||||
vram: Vram::new(mapper.clone()),
|
||||
vram: Vram::new(mapper.clone(), cart_mirroring),
|
||||
rom_mapper: mapper,
|
||||
palette: Palette::new(),
|
||||
frame,
|
||||
|
@ -84,7 +89,7 @@ impl Ppu {
|
|||
|
||||
oam: [0; 256],
|
||||
oam_address: 0,
|
||||
sprites: [None; 8],
|
||||
sprites: Vec::with_capacity(8),
|
||||
|
||||
v: 0,
|
||||
t: 0,
|
||||
|
@ -340,11 +345,12 @@ impl Ppu {
|
|||
fn render_sprite_pixel(&mut self, x: usize, y: usize, bg_pixel_drawn: bool) {
|
||||
let x = x as u8;
|
||||
|
||||
let sprites_in_bounds = self.sprites.iter()
|
||||
.filter_map(|s| s.as_ref())
|
||||
.filter(|s| x >= s.x && x < (s.x + 8));
|
||||
for sprite in self.sprites.iter() {
|
||||
if x < sprite.x || x >= (sprite.x.saturating_add(8)) {
|
||||
// Not in bounds
|
||||
continue;
|
||||
}
|
||||
|
||||
for sprite in sprites_in_bounds {
|
||||
let pixel = 7 - (x - sprite.x);
|
||||
let entry = sprite.pixels[pixel as usize];
|
||||
let transparent = (entry & 0x03) == 0;
|
||||
|
@ -367,7 +373,7 @@ impl Ppu {
|
|||
|
||||
fn load_sprites_for_next_scanline(&mut self) {
|
||||
// Clear current sprites
|
||||
self.sprites = [None; 8];
|
||||
self.sprites.clear();
|
||||
|
||||
let sprite_height = if self.sprite_size_16 { 16 } else { 8 };
|
||||
let next_line = self.state.scanline() as u8 + 1;
|
||||
|
@ -427,7 +433,7 @@ impl Ppu {
|
|||
*p = ((first_plane >> i) & 0x1) | ((second_plane >> i & 0x1) << 1) | ((attr & 0x3) << 2);
|
||||
}
|
||||
|
||||
self.sprites[sprite_n] = Some(Sprite {
|
||||
self.sprites.push(Sprite {
|
||||
pixels,
|
||||
priority: (attr & 0x20) == 0x20,
|
||||
x,
|
||||
|
|
|
@ -63,6 +63,7 @@ impl State {
|
|||
self.cycle
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn clock(&self) -> usize {
|
||||
self.clock
|
||||
}
|
||||
|
|
|
@ -1,28 +1,47 @@
|
|||
use core::cell::RefCell;
|
||||
use core::cell::{RefCell};
|
||||
use alloc::rc::Rc;
|
||||
use common::kilobytes;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use crate::{cartridge::Mirroring, mappers::Mapper};
|
||||
|
||||
pub(crate) struct Vram {
|
||||
nametables: [[u8; kilobytes::KB1]; 2], // AKA CIRAM
|
||||
mapper: Rc<RefCell<dyn Mapper>>,
|
||||
mirror_map: Rc<RefCell<[u8; 4]>>,
|
||||
}
|
||||
|
||||
impl Vram {
|
||||
pub fn new(mapper: Rc<RefCell<dyn Mapper>>) -> Self {
|
||||
Self { nametables: [[0; kilobytes::KB1]; 2], mapper }
|
||||
pub fn new(mapper: Rc<RefCell<dyn Mapper>>, cart_mirroring: Mirroring) -> Self {
|
||||
let mirror_map = Self::setup_mirror_map(&cart_mirroring);
|
||||
let mirror_map = Rc::new(RefCell::new(mirror_map));
|
||||
|
||||
let m = mirror_map.clone();
|
||||
mapper.borrow_mut().on_runtime_mirroring(Box::new( move |new_mirroring| {
|
||||
*m.borrow_mut() = Self::setup_mirror_map(new_mirroring);
|
||||
}));
|
||||
|
||||
Self {
|
||||
nametables: [[0; kilobytes::KB1]; 2],
|
||||
mirror_map
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> Mirroring {
|
||||
self.mapper.borrow().mirroring()
|
||||
pub fn setup_mirror_map(new_mirroring: &Mirroring) -> [u8; 4] {
|
||||
// https://www.nesdev.org/wiki/PPU_nametables
|
||||
// https://www.nesdev.org/wiki/Mirroring#Nametable_Mirroring
|
||||
match new_mirroring {
|
||||
Mirroring::Vertical => [0, 1, 0, 1],
|
||||
Mirroring::Horizontal => [0, 0, 1, 1],
|
||||
Mirroring::SingleScreenLower => [0, 0, 0, 0],
|
||||
Mirroring::SingleScreenUpper => [1, 1, 1, 1],
|
||||
_ => panic!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self, address: u16) -> u8 {
|
||||
assert!((0x2000..=0x2fff).contains(&address), "Invalid vram read: {:#06x}", address);
|
||||
|
||||
let virtual_index = Self::get_virtual_nametable_index(address);
|
||||
let index = Self::map(&self.mode(), virtual_index);
|
||||
let index = self.mirror_map.borrow()[virtual_index] as usize;
|
||||
let offset = address & 0x3ff; // Only lower 9 bits, higher are indexing
|
||||
self.nametables[index][offset as usize]
|
||||
}
|
||||
|
@ -31,44 +50,20 @@ impl Vram {
|
|||
assert!((0x2000..=0x2fff).contains(&address), "Invalid vram write: {:#06x}", address);
|
||||
|
||||
let virtual_index = Self::get_virtual_nametable_index(address);
|
||||
let index = Self::map(&self.mode(), virtual_index);
|
||||
let index = self.mirror_map.borrow()[virtual_index] as usize;
|
||||
let offset = address & 0x3ff; // Only lower 9 bits, higher are indexing
|
||||
self.nametables[index][offset as usize] = val;
|
||||
}
|
||||
|
||||
pub fn read_indexed(&self, virtual_index: u16, offset: u16) -> u8 {
|
||||
let index = Self::map(&self.mode(), virtual_index);
|
||||
let index = self.mirror_map.borrow()[virtual_index as usize] as usize;
|
||||
self.nametables[index][offset as usize]
|
||||
}
|
||||
|
||||
fn get_virtual_nametable_index(address: u16) -> u16 {
|
||||
fn get_virtual_nametable_index(address: u16) -> usize {
|
||||
// (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
|
||||
// Start == 0x2000, bit 11 & 10 selects the nametable index.
|
||||
(address >> 10) & 0b11
|
||||
}
|
||||
|
||||
// TODO: This mapping could probably be setup in the constructor already,
|
||||
// instead of calculated for EVERY read/write..
|
||||
fn map(mode: &Mirroring, virtual_index: u16) -> usize {
|
||||
assert!(virtual_index <= 3);
|
||||
|
||||
// https://www.nesdev.org/wiki/PPU_nametables
|
||||
// https://www.nesdev.org/wiki/Mirroring#Nametable_Mirroring
|
||||
let physical_index = match (mode, virtual_index) {
|
||||
(Mirroring::Vertical, 0) => 0,
|
||||
(Mirroring::Vertical, 1) => 1,
|
||||
(Mirroring::Vertical, 2) => 0,
|
||||
(Mirroring::Vertical, 3) => 1,
|
||||
(Mirroring::Horizontal, 0) => 0,
|
||||
(Mirroring::Horizontal, 1) => 0,
|
||||
(Mirroring::Horizontal, 2) => 1,
|
||||
(Mirroring::Horizontal, 3) => 1,
|
||||
(Mirroring::SingleScreenLower, _) => 0,
|
||||
(Mirroring::SingleScreenUpper, _) => 1,
|
||||
_ => panic!("nametable mirroring? {} {:?}", virtual_index, mode),
|
||||
};
|
||||
|
||||
physical_index
|
||||
((address >> 10) & 0b11) as usize
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ const NESTEST_SUCCESS: u16 = 0xc68b; // Here it starts writing to APU, which is
|
|||
const NESTEST_RES_BYTE2: u16 = 0x0002;
|
||||
const NESTEST_RES_BYTE3: u16 = 0x0003;
|
||||
|
||||
const ENABLE_TEST_CYCLES: bool = true;
|
||||
const ENABLE_TEST_CYCLES: bool = false;
|
||||
|
||||
#[test]
|
||||
fn nestest() {
|
||||
|
|
24
profile/Cargo.toml
Normal file
24
profile/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "profile"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nes = { path = "../nes", default-features = false }
|
||||
#linked_list_allocator = { version = "0.10.4", optional = true }
|
||||
dhat = { version = "0.3.2", optional = true }
|
||||
|
||||
[features]
|
||||
profile_heap = ["dhat"]
|
||||
profile_cpu_no_std = ["profile_heap"]
|
||||
profile_cpu_std = ["nes/default"]
|
||||
default = []
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
59
profile/src/main.rs
Normal file
59
profile/src/main.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
#![cfg_attr(not(feature = "profile_cpu_std"), no_std)]
|
||||
// #![feature(alloc_error_handler)]
|
||||
#![feature(iter_collect_into)]
|
||||
// #![feature(default_alloc_error_handler)]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
use nes::{cartridge::Cartridge, nes::Nes};
|
||||
|
||||
#[cfg(feature = "profile_heap")]
|
||||
#[global_allocator]
|
||||
static ALLOC: dhat::Alloc = dhat::Alloc;
|
||||
|
||||
struct FakeHost;
|
||||
|
||||
const EXPECTED_FRAME_SIZE: usize = 240 * 224 * 2;
|
||||
|
||||
impl nes::nes::HostPlatform for FakeHost {
|
||||
fn elapsed_millis(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn delay(&self, _: core::time::Duration) {
|
||||
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn render(&mut self, f: &nes::frame::RenderFrame) {
|
||||
let mut buf = Vec::with_capacity(EXPECTED_FRAME_SIZE);
|
||||
f.pixels_ntsc().collect_into(&mut buf);
|
||||
assert_eq!(buf.len(), EXPECTED_FRAME_SIZE);
|
||||
assert_eq!(buf.capacity(), EXPECTED_FRAME_SIZE);
|
||||
}
|
||||
|
||||
fn poll_events(&mut self, _: &mut nes::joypad::Joypad) -> nes::nes::Shutdown {
|
||||
nes::nes::Shutdown::No
|
||||
}
|
||||
|
||||
fn pixel_format(&self) -> nes::nes::HostPixelFormat {
|
||||
nes::nes::HostPixelFormat::Rgb565
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
fn main() {
|
||||
#[cfg(debug_assertions)]
|
||||
panic!("run profile with --release");
|
||||
|
||||
#[cfg(feature = "profile_heap")]
|
||||
let _profiler = dhat::Profiler::new_heap();
|
||||
|
||||
let rom = include_bytes!(env!("PROF_ROM"));
|
||||
let cart = Cartridge::blow_dust_no_heap(rom).unwrap();
|
||||
let mut nes = Nes::insert(cart, FakeHost{});
|
||||
|
||||
for _ in 0..10_000_000 {
|
||||
nes.tick();
|
||||
}
|
||||
}
|
4
profile_mem.sh
Executable file
4
profile_mem.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
cd profile &&
|
||||
RUSTFLAGS=-g cargo run --features profile_heap --release &&
|
||||
open 'https://nnethercote.github.io/dh_view/dh_view.html'
|
4
profile_perf.sh
Executable file
4
profile_perf.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
cd profile &&
|
||||
RUSTFLAGS=-g cargo build --release --features profile_cpu_no_std &&
|
||||
samply record ../target/release/profile
|
BIN
screenshots/pico.jpg
Normal file
BIN
screenshots/pico.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 MiB |
Loading…
Reference in a new issue