Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dec36156df | |||
| 5e6df7d7cc | |||
| 3c05bfdcaa | |||
| 27585d6440 | |||
| 7a13325eb0 | |||
| 50ba57db74 | |||
| f916f77641 | |||
| 123ef70487 | |||
| 1b9c73c432 | |||
| 051cfe6611 | |||
| 012992c4ad |
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[*.rs]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 100
|
||||||
|
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
@ -1 +1,15 @@
|
|||||||
/target
|
/target
|
||||||
|
/cgmath
|
||||||
|
*.log
|
||||||
|
*.bin
|
||||||
|
*.bin.*
|
||||||
|
*.bkp
|
||||||
|
*.perf
|
||||||
|
*.svg
|
||||||
|
*.lock
|
||||||
|
perf.*
|
||||||
|
tags
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1859
Cargo.lock
generated
1859
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
@ -1,8 +1,19 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nes_emulator"
|
name = "nes_emulator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
log = "0.4.14"
|
||||||
|
chrono = "0.4.19"
|
||||||
|
fern = "0.6.0"
|
||||||
|
bitflags = "1.3.2"
|
||||||
|
rusttype = "0.9.2"
|
||||||
|
canvas = "0.1.0"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
canvas = { git = "https://git.steins7.ovh/Steins7/canvas.git", branch = "dev" }
|
||||||
|
cgmath = { path = "cgmath" }
|
||||||
|
|
||||||
|
|||||||
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2022 Steins7
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
1
doc/nes_architecture
Normal file
1
doc/nes_architecture
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="Electron" modified="2022-05-26T11:39:44.986Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/17.4.2 Chrome/96.0.4664.174 Electron/16.2.2 Safari/537.36" etag="qDAsjD8odGcVAzsI0HnM" version="17.4.2" type="device" pages="2"><diagram id="ump8jJu03wSbJk_0SnHp" name="Page-1">7Vtbc5s6EP41zLQP6XA3fvQl6ZlpfE4mTk9OHxUj2zQYuUJO7P76I0AyCGGCbTBkmqdYyyLE7rff7kpEMUar7VcM1ssJcqGv6Kq7VYyxouuaqTr0TyTZJRKnpyWCBfZcppQKpt5vyIQqk248F4aCIkHIJ95aFM5QEMAZEWQAY/Qqqs2RLz51DRZQEkxnwJelj55Llkyq2f30wl/QWyzZox29l1xYAa7M3iRcAhe9ZkTGtWKMMEIk+bXajqAfGY/bJbnv5sDV/cIwDEiVG/xH23nx/168wmA3/DkFt89DdBXdEC+O7PgbQ5cagA0RJku0QAHwr1PpEKNN4MJoWpWOUp1bhNZUqFHhT0jIjnkTbAiioiVZ+ewq3Hrkv+j2L7rFhj/YbNHv8TY72PFBQPAue1c0/pG9mN4Xj/iNIQGYDCIsUEGAAshlN57v73Uwet57mPpmKJuXWTxEGzyDJTblMAV4AUmZ7RO9yN6ZBzDnfYVoBelbUAUMfUC8FxGQgOF6sddjt9IXBbuMwhp5AQkzM99FAqrAQtRh8GTxaedAlNM2+2Xq9EfyeD7KvEcqinF5DEaND4y2hVHtPYLUUS8PUsNMHvkC/A0zAnBdXIjcW/BEs6SANuB7i4D+nlFPQkwFLxATj6ahAbuw8lw3ATYMvd/gKZ4vQgUzHZ3cGirWuBAVpWEVPQluBb+xZMqeIuQrwensLgp207QFk3OAHQeL1JNcBc3nIQWo6L56HPaR+VpjFesdkorety5OKprEKaO77xJsw1dv5YPYuQddJwX5wWA2RKtonEtf0+pX57JlpvDdm7PIP0LwHh2pkhUG5VaIIc5CkLJcZUY82lZmzlambCtNL7DVXli7rXSzXVY7idRO57SZD8LQm+VoTTuLxPSqpVEnSMzKYdApZzHNKFNvqH6328SklkVkmnXfAqUAyUza7UimrQzS2uv38+jJ+YBCW1BwOsFXdq7q0qxywsrrm0avVL+nl+o31PzZUonS9ebPOVDrnND8aVb3mz210Bet8M5ptHMU6xwui0TiuUyh1BHisUVi4E3MQeLJ6ZtW/wJE0pOIxAUEdJhIeGTVQSSqY4k9tt51XtEld9013qCb+a6zcoeuNtV18sO6jB2u6JDaYvRwf6votk+XMHyiCdFekNgKicT1XvKicA0CLmNzTAbTb/wyXV5WIyMumKva9NOHwcP3aYMPGA8eBg1OPxiP74+cPodPCjxSTCQ+nJPzaCTKimGSINP8M0I+wml1PKf5KSeqWsYcGzyaGDuGHDr9gshpbmtLk5zxZ27X1FmXWBXrkto3cM6CgiVR6OP9YEIln/Tnzw2nFN1+O6XsNzezgWE3FRiOZI1/L2YNqYXUTNkadoE1+o3lV/kgQMgRGVvYvzbRByXDiNOvGI3T7KPGTL6/WpZWAkAXHXF6eHrSqnVBgBDsPW0IjIO6QwtbA5/y7NHLqZx96yjjm82/h3YRDsaWWN8XpF+tqHK1GjsusT7y75nZlu8wv7N0a8j5tuOdNsfq+Z32lfrF4CmF5zil6512r9VIVU7YwdOUru/gVY7d2k+R6tnCY0FcdQsvt+XX0Bae/LnCLGw3ywifGmnVwHtOmql06HUh5Na++XxeIS9vGI6okbAXLe+yXU1Rj2cUfayi9xpra+RvJu8wWmCwosL7fybHWaTc4NV74VzzV/GTnuZ6P/lskUIGzAi30Q1rjJ1jG+NmDLbvjNvrluVDlOZ6QOL5UV+a1I6d6Er12DW6qlt2DIut81lJ/k/lD25U91FU405xIQ8YjaFa3hLLBr76Ca2JFxUI7bCAlj8urbiD2BgLcOdk7DUB6zWFaSskqb3NkkUnDyeYhw7TfypLqtr0X/OM6/8B</diagram><diagram id="rIACK3iZetm4nWkpG2b9" name="Page-2">7Vtdb9owFP01PDLhhPDxSGjXVqMaK5P28VK5iSFunThzTIH++tnBIaQJlHYQV6slJPDJdXx9z73OsYGGPQyXFwzGwTX1EWlYLX/ZsM8algXarZ54k8hqjfS6YA3MGPaVUQ5M8BNSYEuhc+yjpGDIKSUcx0XQo1GEPF7AIGN0UTSbUlIcNYYzVAImHiRl9Af2eaBQ0OnnFy4RngVq6J7VXV8IYWasZpIE0KeLLcg+b9hDRilffwqXQ0Rk8LK4rPt93nF14xhDET+kQ3QJXfjEJqM/36+a3gTcXE0HTTWLR0jmasLKWb7KIoB8ERDVpIwHdEYjSM5z1GV0HvlIDtMSrdxmRGksQCDAe8T5SrEL55wKKOAhUVfXY8qBds5NQQmdMw/tmVCWI5DNEN9j52wYEKmLaIg4W4l+DBHI8WPRD6hyaLaxy8MsPqhIvyLqoBT1YTwvBT5Z4JDASEZ4SiOecSBj7AWY+CO4onM5x4RD7yFruQFl+EnYwyy64jLjKvZWR94NEzKkhDIBRDQdIO80kTdTwzCUiG7jjAbwDLqGy4LhCCY8c5ASAuME36Uuy46hYARHLuWchspIRQExjpb7iS8TlXWwVXWp5QV0VXuxVaxthQVbdQpa7RORa1WUlCuXHozk2jOQ2bmKUYluEQSessXoA3pGTwVjkOBZJJoETWU3GUUslq2BgrksPTeJoYej2Si1OWvnyI2KhISo6Dsl6dIUYN9HUVrSHHJ4t0m4mOKIp5FyXPESAR22PjkNRzg+FG2Qt8VLmjM+pJGYC8QpkUikxgLJ9DiM9d1lU04FRb1I7YOYz+yOTrxtiNdCvGNpJr5tiNdCfLdXH/FfuccnvY6HV63J7Zef4/hxcdvs/m/qySmrp8qJWzWpp8rBgfNRww5a/xj3tOuAMbjaMlCVnt95LIFcYzmgqLEs29nm8EV7u+8843ztQZ4Bm6kclBRX4cL1+uyx/Xuw/HbRXNxH8a9m5wPkROXEdyyR7zslHOe4KbEvglsp4c6TUlaYXdbLuyzbeesuK9ueHV1zVdW70VzH0Fw7TkY07LKqn4GGeC3Ea99lVYltQ/zpia9zl1XpXs8Qr4X4zRGptrW+fJR6A0Oj4N6g4JyuTgVXSa45Lj1CWb/mdOa9KDhzXKqH+DoVXKV7Ved2hvjTE6/9nNxs1vUQr1/BZed+W9SPzU8djiLhbFu7hAPmMOZUhb3rq573IuJAeW9mqK+Feu0yDpidmybqtQs5YPZumqivVcrt/qK5QP3AKLmjKLl2v04lV0mueZofoa5f8/sMDTqu0j3zLNdDfJ0qrtI98yTXQ3ydGq7SPXMKq4f4Uyo40cz/SLb+/Vz+dzz7/C8=</diagram></mxfile>
|
||||||
BIN
fonts/DejaVuSansMono.ttf
Normal file
BIN
fonts/DejaVuSansMono.ttf
Normal file
Binary file not shown.
165
src/bus.rs
Normal file
165
src/bus.rs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
rc::Rc,
|
||||||
|
cell::RefCell,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
utils::DisplayBuffer,
|
||||||
|
peripherals::{Peripheral, Ram, Mapper, Ppu},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Bus<M: Mapper> {
|
||||||
|
ram: Ram<0x800>,
|
||||||
|
ppu: Ppu<M>,
|
||||||
|
apu: Ram<0x1F>,//TODO
|
||||||
|
cartridge: Rc<RefCell<M>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Mapper> fmt::Debug for Bus<M> {
|
||||||
|
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Bus")
|
||||||
|
.field("ram", &self.ram)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Mapper> Peripheral for Bus<M> {
|
||||||
|
|
||||||
|
fn read_addr(&self, addr: u16) -> u8 {
|
||||||
|
|
||||||
|
match addr {
|
||||||
|
0x0000..=0x1FFF => self.ram.read_addr(addr % 0x0800), //RAM is mirrored 3 times
|
||||||
|
0x2000..=0x3FFF => self.ppu.read_addr(addr % 0x8), //PPU is mirrored every 8 bytes
|
||||||
|
0x4000..=0x401F => self.apu.read_addr(addr),
|
||||||
|
0x4020..=0xFFFF => self.cartridge.borrow().read_addr(addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_addr(&mut self, addr: u16, data: u8) {
|
||||||
|
|
||||||
|
match addr {
|
||||||
|
0x0000..=0x1FFF => self.ram.write_addr(addr % 0x0800, data),
|
||||||
|
0x2000..=0x3FFF => self.ppu.write_addr(addr % 0x8, data),
|
||||||
|
0x4000..=0x401F => self.apu.write_addr(addr, data),
|
||||||
|
0x4020..=0xFFFF => self.cartridge.borrow_mut().write_addr(addr, data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Mapper> Bus<M> {
|
||||||
|
|
||||||
|
pub fn new(mapper: Rc<RefCell<M>>,
|
||||||
|
screen: Rc<RefCell<DisplayBuffer>>,
|
||||||
|
pattern_table: Rc<RefCell<DisplayBuffer>>)
|
||||||
|
-> Self {
|
||||||
|
|
||||||
|
let mut ram = Ram::<0x800>::new();
|
||||||
|
|
||||||
|
//TODO temporary for instruction tests
|
||||||
|
ram.buffer[0x01] = 0xE9; //sbc
|
||||||
|
ram.buffer[0x02] = (-3 as i8) as u8;
|
||||||
|
ram.buffer[0x03] = 0x09;
|
||||||
|
ram.buffer[0x04] = 0x00;
|
||||||
|
ram.buffer[0x05] = 0xA9; //lda
|
||||||
|
ram.buffer[0x06] = 0x01;
|
||||||
|
ram.buffer[0x07] = 0x4C; //jmp
|
||||||
|
ram.buffer[0x08] = 0x00;
|
||||||
|
ram.buffer[0x09] = 0x00;
|
||||||
|
ram.buffer[0x0A] = 0x2A; //fn rol
|
||||||
|
ram.buffer[0x0B] = 0x90; //bcc
|
||||||
|
ram.buffer[0x0C] = (-3 as i8) as u8;
|
||||||
|
ram.buffer[0x0D] = 0x60; //rts
|
||||||
|
ram.buffer[0x0E] = 0xFD;
|
||||||
|
ram.buffer[0x0F] = 0x01;
|
||||||
|
ram.buffer[0x10] = 0x21;
|
||||||
|
ram.buffer[0x11] = 0x9F;
|
||||||
|
ram.buffer[0x12] = 0x31;
|
||||||
|
ram.buffer[0x13] = 0xB0;
|
||||||
|
|
||||||
|
// boundary
|
||||||
|
ram.buffer[0x14] = 0x3D;
|
||||||
|
ram.buffer[0x15] = 0xFF;
|
||||||
|
ram.buffer[0x16] = 0x02;
|
||||||
|
ram.buffer[0x17] = 0x39;
|
||||||
|
ram.buffer[0x18] = 0xFE;
|
||||||
|
ram.buffer[0x19] = 0x02;
|
||||||
|
ram.buffer[0x1A] = 0x31;
|
||||||
|
ram.buffer[0x1B] = 0xB2;
|
||||||
|
|
||||||
|
|
||||||
|
// ptr to val
|
||||||
|
ram.buffer[0xA0] = 0xFF;
|
||||||
|
ram.buffer[0xA1] = 0x01;
|
||||||
|
|
||||||
|
// ptr to val as table
|
||||||
|
ram.buffer[0xB0] = 0xFD;
|
||||||
|
ram.buffer[0xB1] = 0x01;
|
||||||
|
ram.buffer[0xB2] = 0xFE; //boundary
|
||||||
|
ram.buffer[0xB3] = 0x02;
|
||||||
|
|
||||||
|
// zero page val
|
||||||
|
ram.buffer[0xFF] = 0x01;
|
||||||
|
|
||||||
|
// val
|
||||||
|
ram.buffer[0x01FF] = 0x01;
|
||||||
|
ram.buffer[0x02FF] = 0xFF; //boundary
|
||||||
|
ram.buffer[0x0300] = 0x03;
|
||||||
|
|
||||||
|
// ptr to test
|
||||||
|
ram.buffer[0x030F] = 0xFF;
|
||||||
|
ram.buffer[0x0310] = 0x03;
|
||||||
|
|
||||||
|
// jmp test
|
||||||
|
ram.buffer[0x0400] = 0xE6;
|
||||||
|
|
||||||
|
Bus {
|
||||||
|
ram,
|
||||||
|
ppu: Ppu::new(mapper.clone(), screen, pattern_table),
|
||||||
|
apu: Ram::<0x1F>::new(),
|
||||||
|
cartridge: mapper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self) {
|
||||||
|
self.ppu.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// special Bus only used for testing the emulator
|
||||||
|
pub struct FileBus {
|
||||||
|
mem: Ram<0x10000>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for FileBus {
|
||||||
|
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Bus")
|
||||||
|
.field("mem", &self.mem)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Peripheral for FileBus {
|
||||||
|
|
||||||
|
fn read_addr(&self, addr: u16) -> u8 {
|
||||||
|
self.mem.read_addr(addr) //RAM is mirrored 3 times
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_addr(&mut self, addr: u16, data: u8) {
|
||||||
|
self.mem.write_addr(addr % 0x0800, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileBus {
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
FileBus {
|
||||||
|
mem: Ram::from_file("6502_functional_test.bin"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
1149
src/cpu.rs
Normal file
1149
src/cpu.rs
Normal file
File diff suppressed because it is too large
Load Diff
262
src/main.rs
262
src/main.rs
@ -1,3 +1,261 @@
|
|||||||
fn main() {
|
#[allow(unused_imports)]
|
||||||
println!("Hello, world!");
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
rc::Rc,
|
||||||
|
cell::RefCell,
|
||||||
|
time::Instant,
|
||||||
|
fmt::Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod cpu;
|
||||||
|
use cpu::Cpu;
|
||||||
|
|
||||||
|
mod bus;
|
||||||
|
mod peripherals;
|
||||||
|
use peripherals::mapper::Nrom128;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use canvas::{
|
||||||
|
Application,
|
||||||
|
Canvas,
|
||||||
|
sprite::{Sprite, TextureSprite, TextSprite, Center},
|
||||||
|
utils::{Size, Color, Position},
|
||||||
|
};
|
||||||
|
use utils::DisplayBuffer;
|
||||||
|
|
||||||
|
struct EmulatorState {
|
||||||
|
pub screen: Rc<RefCell<DisplayBuffer>>,
|
||||||
|
pub screen_sprite: TextureSprite,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub pattern_table: Rc<RefCell<DisplayBuffer>>,
|
||||||
|
pub pattern_sprite: TextureSprite,
|
||||||
|
|
||||||
|
pub fps_text: TextSprite,
|
||||||
|
pub debug_text: TextSprite,
|
||||||
|
|
||||||
|
pub cpu: Cpu<Nrom128>,
|
||||||
|
|
||||||
|
pub start: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NesEmulator {}
|
||||||
|
|
||||||
|
impl Application<EmulatorState> for NesEmulator {
|
||||||
|
|
||||||
|
fn init(canvas: &mut Canvas) -> Result<EmulatorState, &'static str> {
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
let screen_texture = canvas
|
||||||
|
.create_texture(Size {w: 256, h: 240}, Some(Color::RED.into()))
|
||||||
|
.unwrap();
|
||||||
|
let mut screen_sprite = canvas.create_texture_sprite(Size {w: 256 * 2, h: 240 * 2});
|
||||||
|
screen_sprite.set_texture(screen_texture.clone(), None, 0.5);
|
||||||
|
screen_sprite.set_center(Center::BotLeft);
|
||||||
|
screen_sprite.set_position(Position {x: 100, y: 200});
|
||||||
|
let screen = Rc::new(RefCell::new(DisplayBuffer::from_texture(screen_texture)));
|
||||||
|
|
||||||
|
let pattern_texture = canvas
|
||||||
|
.create_texture(Size {w: 256, h: 128}, Some(Color::BLUE.into()))
|
||||||
|
.unwrap();
|
||||||
|
let mut pattern_sprite = canvas.create_texture_sprite(Size {w: 256, h: 128});
|
||||||
|
pattern_sprite.set_texture(pattern_texture.clone(), None, 1.0);
|
||||||
|
pattern_sprite.set_center(Center::BotLeft);
|
||||||
|
pattern_sprite.set_position(Position {x: 100, y: 50});
|
||||||
|
let pattern_table = Rc::new(RefCell::new(DisplayBuffer::from_texture(pattern_texture)));
|
||||||
|
|
||||||
|
let mut fps_text = canvas.create_text_sprite("00", Size {w: 30, h: 20}, 20.0);
|
||||||
|
fps_text.set_center(Center::TopLeft);
|
||||||
|
fps_text.set_position(Position {x:0, y:720});
|
||||||
|
|
||||||
|
let mut debug_text = canvas.create_text_sprite("00", Size {w: 300, h: 600}, 20.0);
|
||||||
|
debug_text.set_center(Center::BotLeft);
|
||||||
|
debug_text.set_position(Position {x:700, y:50});
|
||||||
|
|
||||||
|
let mapper = Rc::new(RefCell::new(Nrom128::new(false)));
|
||||||
|
let cpu = Cpu::new(mapper, screen.clone(), pattern_table.clone());
|
||||||
|
|
||||||
|
canvas.clear();
|
||||||
|
canvas.update();
|
||||||
|
|
||||||
|
Ok(EmulatorState {
|
||||||
|
screen,
|
||||||
|
screen_sprite,
|
||||||
|
pattern_table,
|
||||||
|
pattern_sprite,
|
||||||
|
fps_text,
|
||||||
|
debug_text,
|
||||||
|
cpu,
|
||||||
|
start,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(state: &mut EmulatorState, canvas: &mut Canvas) -> Result<(), &'static str> {
|
||||||
|
|
||||||
|
let frame_time = state.start.elapsed().as_secs_f32();
|
||||||
|
state.start = Instant::now();
|
||||||
|
canvas.clear();
|
||||||
|
state.cpu.tick();
|
||||||
|
|
||||||
|
if state.screen.borrow_mut().is_ready {
|
||||||
|
state.screen.borrow_mut().is_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.draw(&mut state.screen_sprite);
|
||||||
|
canvas.draw(&mut state.pattern_sprite);
|
||||||
|
|
||||||
|
let mut fps_str = String::new();
|
||||||
|
write!(fps_str, "{} fps", 1.0/frame_time).unwrap();
|
||||||
|
state.fps_text.set_text(&fps_str);
|
||||||
|
canvas.draw(&mut state.fps_text);
|
||||||
|
|
||||||
|
let mut debug_str = String::new();
|
||||||
|
write!(debug_str, "{:#?}", state.cpu).unwrap();
|
||||||
|
state.debug_text.set_text(&debug_str);
|
||||||
|
canvas.draw(&mut state.debug_text);
|
||||||
|
|
||||||
|
canvas.update();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), &'static str> {
|
||||||
|
|
||||||
|
setup_logger()
|
||||||
|
.map_err(|_| "Failed to setup logger")?;
|
||||||
|
canvas::run_canvas("NES emulator", Size {w: 1280, h: 720}, NesEmulator {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// use utils::{Pixel, PixelBuffer, FontRenderer, DisplayBuffer};
|
||||||
|
// use glutin::{
|
||||||
|
// dpi::LogicalSize,
|
||||||
|
// event_loop::EventLoop,
|
||||||
|
// };
|
||||||
|
// use mini_gl_fb::config;
|
||||||
|
// use std::{
|
||||||
|
// fmt::Write,
|
||||||
|
// rc::Rc,
|
||||||
|
// cell::RefCell,
|
||||||
|
// time::Instant,
|
||||||
|
// };
|
||||||
|
// use peripherals::mapper::Nrom128;
|
||||||
|
// use std::ops::Deref;
|
||||||
|
//
|
||||||
|
// // setup
|
||||||
|
// setup_logger().map_err(|_| "Failed to setup logger")?;
|
||||||
|
//
|
||||||
|
// let mut event_loop = EventLoop::new();
|
||||||
|
// let config = config! {
|
||||||
|
// window_title: "Test".to_string(),
|
||||||
|
// window_size: LogicalSize::new(1280.0, 720.0),
|
||||||
|
// invert_y: false,
|
||||||
|
// };
|
||||||
|
// let mut fb = mini_gl_fb::get_fancy(config, event_loop.deref());
|
||||||
|
// let mut buffer = PixelBuffer::new(1280, 720);
|
||||||
|
// let mut renderer = FontRenderer::new(20.0, [0u8, 0, 0]);
|
||||||
|
//
|
||||||
|
// // first image
|
||||||
|
// let mapper = Rc::new(RefCell::new(Nrom128::new(false)));
|
||||||
|
// let mut screen = Rc::new(RefCell::new(DisplayBuffer::new(256, 240)));
|
||||||
|
// let pattern_table = Rc::new(RefCell::new(DisplayBuffer::new(256, 128)));
|
||||||
|
// let mut cpu = Cpu::new(mapper, screen.clone(), pattern_table.clone());
|
||||||
|
// let mut debug_str = String::new();
|
||||||
|
// //write!(debug_str, "{:#?}", cpu).unwrap();
|
||||||
|
// buffer.fill([0, 0], [1280, 720], Pixel::rgba(255, 255, 255, 255));
|
||||||
|
// //renderer.draw(&mut buffer, &debug_str, [20, 20], [760, 560]);
|
||||||
|
//
|
||||||
|
// let vec: Vec<[u8; 4]> = (&buffer).into();
|
||||||
|
// fb.update_buffer(&vec);
|
||||||
|
// let mut update = false;
|
||||||
|
// let mut refresh = true;
|
||||||
|
// let mut start = Instant::now();
|
||||||
|
//
|
||||||
|
// // event loop
|
||||||
|
// fb.glutin_handle_basic_input(&mut event_loop, |fb, input| {
|
||||||
|
// use glutin::event::VirtualKeyCode;
|
||||||
|
//
|
||||||
|
// // wait for input before updating
|
||||||
|
//
|
||||||
|
// // close if escape is pressed
|
||||||
|
// if input.key_pressed(VirtualKeyCode::Escape) {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // execute next cpu instruction (step mode)
|
||||||
|
// if input.key_pressed(VirtualKeyCode::S) {
|
||||||
|
// cpu.tick();
|
||||||
|
// refresh = true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if input.key_pressed(VirtualKeyCode::R) {
|
||||||
|
// update = true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if input.key_pressed(VirtualKeyCode::C) {
|
||||||
|
// update = false;
|
||||||
|
// refresh = true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if update {
|
||||||
|
// cpu.tick();
|
||||||
|
//
|
||||||
|
// if screen.borrow_mut().is_ready {
|
||||||
|
// screen.borrow_mut().is_ready = false;
|
||||||
|
// refresh = true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// input.wait = false;
|
||||||
|
// } else {
|
||||||
|
// input.wait = true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if refresh {
|
||||||
|
// refresh = false;
|
||||||
|
//
|
||||||
|
// let frame_time = start.elapsed().as_secs_f32();
|
||||||
|
// start = Instant::now();
|
||||||
|
//
|
||||||
|
// let mut debug_str = String::new();
|
||||||
|
// buffer.fill([0, 0], [1280, 720], Pixel::rgba(255, 255, 255, 255));
|
||||||
|
// write!(debug_str, "{} fps", 1.0/frame_time);
|
||||||
|
// renderer.draw(&mut buffer, &debug_str, [1, 1], [20, 20]);
|
||||||
|
// debug_str = String::new();
|
||||||
|
// write!(debug_str, "{:#?}", cpu).unwrap();
|
||||||
|
// renderer.draw(&mut buffer, &debug_str, [600, 20], [760, 560]);
|
||||||
|
// buffer.embbed([20, 20], &screen.borrow().buffer, 2);
|
||||||
|
//
|
||||||
|
// let vec: Vec<[u8; 4]> = (&buffer).into();
|
||||||
|
// fb.update_buffer(&vec);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// true
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// Ok(())
|
||||||
|
//}
|
||||||
|
|
||||||
|
fn setup_logger() -> Result<(), fern::InitError> {
|
||||||
|
|
||||||
|
fern::Dispatch::new()
|
||||||
|
.format(|out, message, record| {
|
||||||
|
out.finish(format_args!(
|
||||||
|
"{}[{}][{}] {}",
|
||||||
|
chrono::Local::now().format("[%H:%M:%S]"),
|
||||||
|
record.target(),
|
||||||
|
//record.file(),
|
||||||
|
record.level(),
|
||||||
|
message
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.level(log::LevelFilter::Error)
|
||||||
|
.chain(std::io::stdout())
|
||||||
|
.chain(fern::log_file("output.log")?)
|
||||||
|
.apply()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
17
src/peripherals.rs
Normal file
17
src/peripherals.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
mod ram;
|
||||||
|
mod ppu;
|
||||||
|
pub mod mapper;
|
||||||
|
|
||||||
|
pub use ram::Ram;
|
||||||
|
pub use ppu::Ppu;
|
||||||
|
pub use mapper::Mapper;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
pub trait Peripheral {
|
||||||
|
|
||||||
|
fn read_addr(&self, addr: u16) -> u8;
|
||||||
|
|
||||||
|
fn write_addr(&mut self, addr: u16, data: u8);
|
||||||
|
}
|
||||||
|
|
||||||
17
src/peripherals/mapper.rs
Normal file
17
src/peripherals/mapper.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use super::Peripheral;
|
||||||
|
|
||||||
|
mod nrom;
|
||||||
|
pub use nrom::Nrom128;
|
||||||
|
|
||||||
|
//--Mapper struct-----------------------------------------------------------------------------------
|
||||||
|
///A trait used as API for the different implementations of mappers in cartridges
|
||||||
|
///
|
||||||
|
///A Mapper is a special `Peripheral` that can also be read by the `Ppu`. The exact memory layout
|
||||||
|
///is defined by the implementation
|
||||||
|
pub trait Mapper: Peripheral {
|
||||||
|
|
||||||
|
///read the specified 16 bits address using the ppu bus
|
||||||
|
fn ppu_read_addr(&self, addr: u16) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
71
src/peripherals/mapper/nrom.rs
Normal file
71
src/peripherals/mapper/nrom.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use super::{
|
||||||
|
Mapper,
|
||||||
|
super::{
|
||||||
|
Peripheral,
|
||||||
|
Ram,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Nrom128 {
|
||||||
|
prg_rom: Ram<0x4000>, //16KiB
|
||||||
|
prg_ram: Ram<0x2000>, //8KiB
|
||||||
|
chr_rom: Ram<0x2000>, //8KiB
|
||||||
|
v_ram: Ram<0x0800>, //2KiB
|
||||||
|
vertical: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Peripheral for Nrom128 {
|
||||||
|
|
||||||
|
fn read_addr(&self, addr: u16) -> u8 {
|
||||||
|
|
||||||
|
match addr {
|
||||||
|
0x6000..=0x7FFF => self.prg_ram.read_addr(addr - 0x6000),
|
||||||
|
0x8000..=0xFFFF => self.prg_rom.read_addr((addr - 0x8000) % 0x4000),
|
||||||
|
//mirrored every 16KiB
|
||||||
|
_ => panic!("{}: Invalid address", addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_addr(&mut self, addr: u16, data: u8) {
|
||||||
|
|
||||||
|
match addr {
|
||||||
|
0x6000..=0x7FFF => self.prg_ram.write_addr(addr - 0x6000, data),
|
||||||
|
_ => (), //writes are ignored for rom or invald addresses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mapper for Nrom128 {
|
||||||
|
|
||||||
|
fn ppu_read_addr(&self, addr: u16) -> u8 {
|
||||||
|
|
||||||
|
match addr {
|
||||||
|
0x0000..=0x1FFF => self.chr_rom.read_addr(addr),
|
||||||
|
0x2000..=0x3EFF => {
|
||||||
|
match self.vertical {
|
||||||
|
false => match (addr - 0x2000) % 0x0FFF {
|
||||||
|
0x0000..=0x07FF => self.v_ram.read_addr(addr % 0x0400),
|
||||||
|
0x0800..=0x0FFF => self.v_ram.read_addr((addr % 0x0400) + 0x0800),
|
||||||
|
_ => panic!("Unexpected behaviour"),
|
||||||
|
},
|
||||||
|
true => self.v_ram.read_addr(addr % 0x0800),
|
||||||
|
}},
|
||||||
|
_ => panic!("{}: Invalid ppu address", addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Nrom128 {
|
||||||
|
|
||||||
|
pub fn new(vertical: bool) -> Self {
|
||||||
|
|
||||||
|
Self {
|
||||||
|
prg_rom: Ram::<0x4000>::new(),
|
||||||
|
prg_ram: Ram::<0x2000>::new(),
|
||||||
|
chr_rom: Ram::<0x2000>::new(),
|
||||||
|
v_ram: Ram::<0x0800>::new(),
|
||||||
|
vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
174
src/peripherals/ppu.rs
Normal file
174
src/peripherals/ppu.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
rc::Rc,
|
||||||
|
cell::RefCell,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
peripherals::{Peripheral, Ram, Mapper},
|
||||||
|
utils::DisplayBuffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
bitflags! {
|
||||||
|
struct PpuCtrl: u8 {
|
||||||
|
const Nl = 0x1 << 0;
|
||||||
|
const Nh = 0x1 << 1;
|
||||||
|
const I = 0x1 << 2;
|
||||||
|
const S = 0x1 << 3;
|
||||||
|
const B = 0x1 << 4;
|
||||||
|
const H = 0x1 << 5;
|
||||||
|
const P = 0x1 << 6;
|
||||||
|
const V = 0x1 << 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
struct PpuMask: u8 {
|
||||||
|
const Gr = 0x1 << 0;
|
||||||
|
const m = 0x1 << 1;
|
||||||
|
const M = 0x1 << 2;
|
||||||
|
const b = 0x1 << 3;
|
||||||
|
const s = 0x1 << 4;
|
||||||
|
const R = 0x1 << 5;
|
||||||
|
const G = 0x1 << 6;
|
||||||
|
const B = 0x1 << 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
struct PpuStatus: u8 {
|
||||||
|
const O = 0x1 << 5;
|
||||||
|
const S = 0x1 << 6;
|
||||||
|
const V = 0x1 << 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Ppu<M: Mapper> {
|
||||||
|
vbus: Rc<RefCell<M>>,
|
||||||
|
ppu_ctrl: PpuCtrl,
|
||||||
|
ppu_mask: PpuMask,
|
||||||
|
ppu_status: PpuStatus,
|
||||||
|
scroll: u8,
|
||||||
|
addr: u8,
|
||||||
|
data: u8,
|
||||||
|
oam_addr: u8,
|
||||||
|
oam_data: u8,
|
||||||
|
oam_dma: u8,
|
||||||
|
oam: Ram<0x100>,
|
||||||
|
blanking: bool,
|
||||||
|
line: u16,
|
||||||
|
dot: u16,
|
||||||
|
nmi: bool,
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
screen: Rc<RefCell<DisplayBuffer>>,
|
||||||
|
pattern_table: Rc<RefCell<DisplayBuffer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Mapper> Peripheral for Ppu<M> {
|
||||||
|
|
||||||
|
fn read_addr(&self, _addr: u16) -> u8 {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_addr(&mut self, _addr: u16, _data: u8) {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Mapper> Ppu<M> {
|
||||||
|
|
||||||
|
pub fn new(mapper: Rc<RefCell<M>>,
|
||||||
|
screen: Rc<RefCell<DisplayBuffer>>,
|
||||||
|
pattern_table: Rc<RefCell<DisplayBuffer>>)
|
||||||
|
-> Self {
|
||||||
|
|
||||||
|
Self {
|
||||||
|
vbus: mapper,
|
||||||
|
ppu_ctrl: PpuCtrl::from_bits(0).unwrap(),
|
||||||
|
ppu_mask: PpuMask::from_bits(0).unwrap(),
|
||||||
|
ppu_status: PpuStatus::from_bits(0).unwrap(),
|
||||||
|
scroll: 0,
|
||||||
|
addr: 0,
|
||||||
|
data: 0,
|
||||||
|
oam_addr: 0,
|
||||||
|
oam_data: 0,
|
||||||
|
oam_dma: 0,
|
||||||
|
oam: Ram::<0x100>::new(),
|
||||||
|
blanking: false,
|
||||||
|
line: 0,
|
||||||
|
dot: 0,
|
||||||
|
nmi: false,
|
||||||
|
|
||||||
|
screen,
|
||||||
|
pattern_table,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_nmi(&mut self) -> bool {
|
||||||
|
let ret = self.nmi;
|
||||||
|
if self.nmi {
|
||||||
|
self.nmi = false;
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self) {
|
||||||
|
use crate::utils::Pixel;
|
||||||
|
|
||||||
|
self.screen.borrow_mut()
|
||||||
|
.set_pixel(
|
||||||
|
self.dot.into(),
|
||||||
|
self.line.into(),
|
||||||
|
Pixel::rgba(self.data.into(), 0, 0, 255));
|
||||||
|
|
||||||
|
self.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&mut self) {
|
||||||
|
|
||||||
|
match self.line {
|
||||||
|
0..=239 => self.render_frame(),
|
||||||
|
241 => {
|
||||||
|
if self.dot == 1 {
|
||||||
|
self.ppu_status.set(PpuStatus::V, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
261 => {
|
||||||
|
if self.dot == 1 {
|
||||||
|
self.ppu_status.set(PpuStatus::V | PpuStatus::S | PpuStatus::O, false);
|
||||||
|
}
|
||||||
|
self.render_frame();
|
||||||
|
|
||||||
|
//last line, got back to first line
|
||||||
|
self.line = 0;
|
||||||
|
}
|
||||||
|
240..=260 => (), //vertical blank
|
||||||
|
_ => panic!("{}: Invalid line number", self.line),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dot = (self.dot + 1) % 256;
|
||||||
|
if self.dot == 0 {
|
||||||
|
self.line = (self.line + 1) % 240;
|
||||||
|
if self.line == 0 {
|
||||||
|
self.screen.borrow_mut().is_ready = true;
|
||||||
|
self.data = (self.data + 1) % 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_frame(&mut self) {
|
||||||
|
|
||||||
|
match self.dot {
|
||||||
|
0 => (),
|
||||||
|
33..=40 => (),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/peripherals/ram.rs
Normal file
59
src/peripherals/ram.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use super::Peripheral;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
pub struct Ram<const SIZE: usize> {
|
||||||
|
pub buffer: [u8; SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const SIZE: usize> fmt::Debug for Ram<SIZE> {
|
||||||
|
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
for i in 0x100..=0x1FF {
|
||||||
|
if i%0x10 == 0 {
|
||||||
|
f.write_fmt(format_args!("\n {:0>4X}", i))?;
|
||||||
|
}
|
||||||
|
f.write_fmt(format_args!(" {:0>2X}", self.buffer[i]))?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const SIZE: usize> Peripheral for Ram<SIZE> {
|
||||||
|
|
||||||
|
fn read_addr(&self, addr: u16) -> u8 {
|
||||||
|
self.buffer[addr as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_addr(&mut self, addr: u16, data: u8) {
|
||||||
|
self.buffer[addr as usize] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const SIZE: usize> Ram<SIZE> {
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
|
||||||
|
Ram {
|
||||||
|
buffer: [0u8; SIZE],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_file(file: &str) -> Self {
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::Read,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut f = File::open(file).unwrap();
|
||||||
|
let mut buffer = [0u8; SIZE];
|
||||||
|
f.read_exact(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
Ram {
|
||||||
|
buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
124
src/utils.rs
Normal file
124
src/utils.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
mod display_buffer;
|
||||||
|
pub use display_buffer::DisplayBuffer;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/* Pixel struct */
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Pixel {
|
||||||
|
r: u8,
|
||||||
|
g: u8,
|
||||||
|
b: u8,
|
||||||
|
a: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pixel {
|
||||||
|
|
||||||
|
pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
|
Pixel {r, g, b, a, }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for Pixel {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, other: Self) -> Self {
|
||||||
|
|
||||||
|
let alpha = other.a as f32 / 255.0;
|
||||||
|
let inv_alpha = 1.0 - alpha;
|
||||||
|
|
||||||
|
Pixel {
|
||||||
|
r: ((self.r as f32)*inv_alpha + (other.r as f32)*alpha) as u8,
|
||||||
|
g: ((self.g as f32)*inv_alpha + (other.g as f32)*alpha) as u8,
|
||||||
|
b: ((self.b as f32)*inv_alpha + (other.b as f32)*alpha) as u8,
|
||||||
|
a: 255,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::AddAssign for Pixel {
|
||||||
|
|
||||||
|
fn add_assign(&mut self, other: Self) {
|
||||||
|
|
||||||
|
let alpha = other.a as f32 / 255.0;
|
||||||
|
let inv_alpha = 1.0 - alpha;
|
||||||
|
|
||||||
|
self.r = ((self.r as f32)*inv_alpha + (other.r as f32)*alpha) as u8;
|
||||||
|
self.g = ((self.g as f32)*inv_alpha + (other.g as f32)*alpha) as u8;
|
||||||
|
self.b = ((self.b as f32)*inv_alpha + (other.b as f32)*alpha) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Pixel> for [u8; 4] {
|
||||||
|
fn from(pixel: &Pixel) -> Self {
|
||||||
|
[pixel.r, pixel.g, pixel.b, pixel.a]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[u8; 4]> for Pixel {
|
||||||
|
fn from(array: [u8; 4]) -> Self {
|
||||||
|
Pixel::rgba(array[0], array[1], array[2], array[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/* PixelBuffer struct */
|
||||||
|
|
||||||
|
pub struct PixelBuffer {
|
||||||
|
buffer: Vec<Pixel>,
|
||||||
|
w: usize,
|
||||||
|
h: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixelBuffer {
|
||||||
|
|
||||||
|
pub fn new(w: usize, h: usize) -> Self {
|
||||||
|
PixelBuffer {
|
||||||
|
buffer: vec![Pixel::rgba(255u8, 255u8, 255u8, 255); h * w],
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_pixel(&mut self, x: usize, y: usize, pixel: Pixel) {
|
||||||
|
self.buffer[x + y*self.w] += pixel
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pixel(&self, x: usize, y: usize) -> Pixel {
|
||||||
|
self.buffer[x + y*self.w]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill(&mut self, origin: [usize; 2], size: [usize; 2], pixel: Pixel) {
|
||||||
|
for x in origin[0]..(origin[0] + size[0]) {
|
||||||
|
for y in origin[1]..(origin[1] + size[1]) {
|
||||||
|
self.buffer[x + y*self.w] = pixel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn embbed(&mut self, origin: [usize; 2], buffer: &Self, scale: usize) {
|
||||||
|
let [w, h] = buffer.get_dimensions();
|
||||||
|
|
||||||
|
for y in 0..(h*scale) {
|
||||||
|
for x in 0..(w*scale) {
|
||||||
|
self.put_pixel(x + origin[0], y + origin[1], buffer.get_pixel(x/scale, y/scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dimensions(&self) -> [usize; 2] {
|
||||||
|
[self.w, self.h]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&PixelBuffer> for Vec<[u8; 4]> {
|
||||||
|
fn from(buffer: &PixelBuffer) -> Self {
|
||||||
|
buffer.buffer.iter()
|
||||||
|
.map(|pixel| pixel.into())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
33
src/utils/display_buffer.rs
Normal file
33
src/utils/display_buffer.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use super::Pixel;
|
||||||
|
|
||||||
|
use canvas::texture::TextureHandle;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
pub struct DisplayBuffer {
|
||||||
|
pub is_ready: bool,
|
||||||
|
|
||||||
|
texture: TextureHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayBuffer {
|
||||||
|
|
||||||
|
pub fn from_texture(texture: TextureHandle) -> Self {
|
||||||
|
|
||||||
|
Self {
|
||||||
|
texture,
|
||||||
|
is_ready: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pixel(&mut self, x: usize, y: usize, pixel: Pixel) {
|
||||||
|
self.texture.set_pixel(
|
||||||
|
canvas::utils::Position {x: x.try_into().unwrap(), y: y.try_into().unwrap()},
|
||||||
|
canvas::utils::Pixel {
|
||||||
|
r: pixel.r,
|
||||||
|
g: pixel.g,
|
||||||
|
b: pixel.b,
|
||||||
|
a: pixel.a,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user