Compare commits
1 Commits
main
...
nes-emulat
Author | SHA1 | Date |
---|---|---|
mars | e945e026d8 |
|
@ -1,6 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/egui",
|
||||
"crates/nes-emu",
|
||||
"crates/sao-ui-rs",
|
||||
"crates/script",
|
||||
"crates/types",
|
||||
|
|
|
@ -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"
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue