Compare commits

...

1 Commits

Author SHA1 Message Date
mars e945e026d8 Cruddy NES emulator 2022-09-22 11:55:02 -06:00
3 changed files with 324 additions and 0 deletions

View File

@ -1,6 +1,7 @@
[workspace]
members = [
"crates/egui",
"crates/nes-emu",
"crates/sao-ui-rs",
"crates/script",
"crates/types",

13
crates/nes-emu/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "nes-emu"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
glam = "^0.21"
canary_script = { path = "../script" }
ludus = "0.2"
wee_alloc = "^0.4"

310
crates/nes-emu/src/lib.rs Normal file
View File

@ -0,0 +1,310 @@
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
use canary_script::*;
use ludus::Console;
const FRAME_DURATION: f32 = 1.0 / 60.0;
const PIX_WIDTH: usize = 256;
const PIX_HEIGHT: usize = 240;
const PIX_NUM: usize = PIX_WIDTH * PIX_HEIGHT;
static ROM: &[u8] = include_bytes!("rom.nes");
export_abi!(NesPanel);
pub struct Config {
pub fb_pix_size: f32,
pub fb_origin_x: f32,
pub fb_origin_y: f32,
}
impl Default for Config {
fn default() -> Self {
Self {
fb_pix_size: 0.004,
fb_origin_x: -0.5,
fb_origin_y: -0.5,
}
}
}
pub struct NesPanel {
panel: Panel,
fb: Framebuffer,
input: Input,
console: Console,
audio: DummyAudioDevice,
step: f32,
}
impl BindPanel for NesPanel {
fn bind(panel: Panel, message: Message) -> Box<dyn PanelImpl> {
let config = Config::default();
let cart = ludus::Cart::from_bytes(ROM).expect("Failed to decode rom");
let panel = Self {
panel,
fb: Framebuffer::new(&config),
input: Input::new(),
console: Console::new(cart, 8000),
audio: DummyAudioDevice,
step: 0.0,
};
Box::new(panel)
}
}
impl PanelImpl for NesPanel {
fn update(&mut self, dt: f32) {
self.step += dt;
self.input.update();
while self.step > FRAME_DURATION {
self.step -= FRAME_DURATION;
self.console.update_controller(self.input.get_state());
self.console.step_frame(&mut self.audio, &mut self.fb);
}
}
fn draw(&mut self) {
self.fb.draw(&self.panel);
self.input.draw(&self.panel);
}
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {
for button in self.input.buttons.iter_mut() {
button.on_cursor_event(kind, at);
}
}
fn on_message(&mut self, message: Message) {}
}
pub struct DummyAudioDevice;
impl ludus::ports::AudioDevice for DummyAudioDevice {
fn push_sample(&mut self, _sample: f32) {}
}
pub struct Framebuffer {
pub vertices: Vec<MeshVertex>,
pub indices: Vec<MeshIndex>,
pub double_buffer: Vec<u32>,
pub dirty: bool,
}
impl Framebuffer {
pub fn new(config: &Config) -> Self {
let mut vertices = Vec::with_capacity(PIX_NUM * 4);
let mut indices = Vec::with_capacity(PIX_NUM * 6);
let color = Color::BLACK;
for y in 0..PIX_HEIGHT {
for x in 0..PIX_WIDTH {
let index = vertices.len() as MeshIndex;
let y = PIX_HEIGHT - y;
let x = x as f32 * config.fb_pix_size + config.fb_origin_x;
let y = y as f32 * config.fb_pix_size + config.fb_origin_y;
vertices.push(MeshVertex {
position: Vec2 { x, y },
color,
});
vertices.push(MeshVertex {
position: Vec2 {
x: x + config.fb_pix_size,
y,
},
color,
});
vertices.push(MeshVertex {
position: Vec2 {
x,
y: y + config.fb_pix_size,
},
color,
});
vertices.push(MeshVertex {
position: Vec2 {
x: x + config.fb_pix_size,
y: y + config.fb_pix_size,
},
color,
});
indices.extend_from_slice(&[
index,
index + 1,
index + 2,
index + 1,
index + 2,
index + 3,
]);
}
}
Self {
vertices,
indices,
double_buffer: Vec::new(),
dirty: false,
}
}
pub fn draw(&mut self, panel: &Panel) {
if self.dirty {
self.dirty = false;
for (pixel, vertices) in self
.double_buffer
.iter()
.zip(self.vertices.chunks_exact_mut(4))
{
let r = ((pixel >> 16) & 0xff) as f32 / 255.0;
let g = ((pixel >> 8) & 0xff) as f32 / 255.0;
let b = (pixel & 0xff) as f32 / 255.0;
let color = Color::new(r, g, b, 1.0);
for vertex in vertices {
vertex.color = color;
}
}
}
panel.draw_indexed(&self.vertices, &self.indices);
}
}
impl ludus::ports::VideoDevice for Framebuffer {
fn blit_pixels(&mut self, pixels: &ludus::PixelBuffer) {
let pixels = pixels.as_ref();
self.double_buffer.resize(pixels.len(), 0);
self.double_buffer.copy_from_slice(pixels);
self.dirty = true;
}
}
pub struct Input {
pub buttons: [Button; 8],
pub state: ludus::ButtonState,
}
impl Input {
pub fn new() -> Self {
let button_size = Vec2 { x: 0.1, y: 0.1 };
let button_positions = [
Vec2 { x: -0.4, y: -0.3 },
Vec2 { x: -0.3, y: -0.4 },
Vec2 { x: -0.4, y: -0.5 },
Vec2 { x: -0.5, y: -0.4 },
Vec2 { x: -0.15, y: -0.5 },
Vec2 { x: 0.15, y: -0.5 },
Vec2 { x: 0.4, y: -0.4 },
Vec2 { x: 0.55, y: -0.4 },
];
Self {
buttons: button_positions.map(|pos| Button::new(pos, button_size)),
state: Default::default(),
}
}
pub fn get_state(&self) -> ludus::ButtonState {
ludus::ButtonState { ..self.state }
}
pub fn update(&mut self) {
self.state.up = self.buttons[0].pressed;
self.state.right = self.buttons[1].pressed;
self.state.down = self.buttons[2].pressed;
self.state.left = self.buttons[3].pressed;
self.state.select = self.buttons[4].pressed;
self.state.start = self.buttons[5].pressed;
self.state.b = self.buttons[6].pressed;
self.state.a = self.buttons[7].pressed;
}
pub fn draw(&self, panel: &Panel) {
for button in self.buttons.iter() {
button.draw(panel);
}
}
}
pub struct Button {
pos: Vec2,
size: Vec2,
pressed: bool,
}
impl Button {
pub fn new(pos: Vec2, size: Vec2) -> Self {
Self {
pos,
size,
pressed: false,
}
}
pub fn draw(&self, panel: &Panel) {
let alpha = if self.pressed { 1.0 } else { 0.8 };
let color = Color::new(1.0, 1.0, 1.0, alpha);
let pos = self.pos;
let corner = Vec2 {
x: pos.x + self.size.x,
y: pos.y + self.size.y,
};
let vertices = &[
MeshVertex {
position: pos,
color,
},
MeshVertex {
position: Vec2 {
x: corner.x,
y: pos.y,
},
color,
},
MeshVertex {
position: Vec2 {
x: pos.x,
y: corner.y,
},
color,
},
MeshVertex {
position: corner,
color,
},
];
let indices = &[0, 1, 2, 1, 2, 3];
panel.draw_indexed(vertices.as_slice(), indices.as_slice());
}
pub fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {
if kind == CursorEventKind::Deselect {
self.pressed = false;
} else if kind == CursorEventKind::Select
&& at.x > self.pos.x
&& at.y > self.pos.y
&& at.x < self.pos.x + self.size.x
&& at.y < self.pos.y + self.size.y
{
self.pressed = true;
}
}
}