Got first CPU instructions working
+ added interface + added CPU, Bus, and memory implementations + implemented a few CPU instructions
This commit is contained in:
parent
8cc0019dca
commit
012992c4ad
1415
Cargo.lock
generated
1415
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@ -1,8 +1,16 @@
|
|||||||
[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"
|
||||||
|
winit = "0.24.0"
|
||||||
|
mini_gl_fb = "0.9.0"
|
||||||
|
bitflags = "1.3.2"
|
||||||
|
rusttype = "0.9.2"
|
||||||
|
|
||||||
|
|||||||
BIN
fonts/DejaVuSansMono.ttf
Normal file
BIN
fonts/DejaVuSansMono.ttf
Normal file
Binary file not shown.
78380
output.log
Normal file
78380
output.log
Normal file
File diff suppressed because one or more lines are too long
106
src/bus.rs
Normal file
106
src/bus.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::memory::{Memory, Ram};
|
||||||
|
|
||||||
|
pub struct Bus {
|
||||||
|
ram: Ram<0x800>,
|
||||||
|
ppu: Ram<0x8>,
|
||||||
|
apu: Ram<0x1F>,
|
||||||
|
cartridge: Ram<0xBFE0>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Bus {
|
||||||
|
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Bus")
|
||||||
|
.field("ram", &self.ram)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Memory for Bus {
|
||||||
|
|
||||||
|
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.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.write_addr(addr, data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bus {
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
|
||||||
|
let mut ram = Ram::<0x800>::new();
|
||||||
|
|
||||||
|
//TODO temporary for instruction tests
|
||||||
|
ram.buffer[0x01] = 0x0A;
|
||||||
|
ram.buffer[0x02] = 0x90;;
|
||||||
|
ram.buffer[0x03] = (-3 as i8) as u8;
|
||||||
|
ram.buffer[0x04] = 0x90;
|
||||||
|
ram.buffer[0x05] = (-3 as i8) as u8;
|
||||||
|
ram.buffer[0x06] = 0x0A;
|
||||||
|
ram.buffer[0x07] = 0x0A;
|
||||||
|
ram.buffer[0x08] = 0x0A;
|
||||||
|
ram.buffer[0x09] = 0x01;
|
||||||
|
ram.buffer[0x0A] = 0x3D;
|
||||||
|
ram.buffer[0x0B] = 0xFE;
|
||||||
|
ram.buffer[0x0C] = 0x01;
|
||||||
|
ram.buffer[0x0D] = 0x39;
|
||||||
|
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[0x0300] = 0x01; //boundary
|
||||||
|
|
||||||
|
Bus {
|
||||||
|
ram,
|
||||||
|
ppu: Ram::<0x8>::new(),
|
||||||
|
apu: Ram::<0x1F>::new(),
|
||||||
|
cartridge: Ram::<0xBFE0>::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/cpu.precomp.rs
Normal file
0
src/cpu.precomp.rs
Normal file
639
src/cpu.rs
Normal file
639
src/cpu.rs
Normal file
@ -0,0 +1,639 @@
|
|||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bus::Bus,
|
||||||
|
memory::Memory,
|
||||||
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
trait Input {
|
||||||
|
fn get_cycles(&self) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait RInput: Input {
|
||||||
|
fn read(&self, acc: &u8, bus: &Bus) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait WInput: Input {
|
||||||
|
fn write(&mut self, acc: &mut u8, bus: &mut Bus, data: u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
trait RWInput: RInput + WInput {}
|
||||||
|
|
||||||
|
struct Accumulator {}
|
||||||
|
|
||||||
|
impl Input for Accumulator {
|
||||||
|
fn get_cycles(&self) -> u32 { 2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RInput for Accumulator {
|
||||||
|
fn read(&self, acc: &u8, _bus: &Bus) -> u8 { *acc }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WInput for Accumulator {
|
||||||
|
fn write(&mut self, acc: &mut u8, _bus: &mut Bus, data: u8) { *acc = data }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RWInput for Accumulator {}
|
||||||
|
|
||||||
|
struct Data {
|
||||||
|
data: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input for Data {
|
||||||
|
fn get_cycles(&self) -> u32 { 2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RInput for Data {
|
||||||
|
fn read(&self, _acc: &u8, _bus: &Bus) -> u8 { self.data }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MemoryVal {
|
||||||
|
addr: u16,
|
||||||
|
cycles: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input for MemoryVal {
|
||||||
|
fn get_cycles(&self) -> u32 { self.cycles }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RInput for MemoryVal {
|
||||||
|
fn read(&self, _acc: &u8, bus: &Bus) -> u8 {
|
||||||
|
bus.read_addr(self.addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WInput for MemoryVal {
|
||||||
|
fn write(&mut self, _acc: &mut u8, bus: &mut Bus, data: u8) {
|
||||||
|
bus.write_addr(self.addr, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RWInput for MemoryVal {}
|
||||||
|
|
||||||
|
struct MemoryValExtra {
|
||||||
|
addr: u16,
|
||||||
|
cycles: u32,
|
||||||
|
extra_cycle: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input for MemoryValExtra {
|
||||||
|
fn get_cycles(&self) -> u32 {
|
||||||
|
match self.extra_cycle {
|
||||||
|
false => self.cycles,
|
||||||
|
true => self.cycles + 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RInput for MemoryValExtra {
|
||||||
|
fn read(&self, _acc: &u8, bus: &Bus) -> u8 {
|
||||||
|
bus.read_addr(self.addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WInput for MemoryValExtra {
|
||||||
|
fn write(&mut self, _acc: &mut u8, bus: &mut Bus, data: u8) {
|
||||||
|
bus.write_addr(self.addr, data);
|
||||||
|
|
||||||
|
self.extra_cycle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RWInput for MemoryValExtra {}
|
||||||
|
|
||||||
|
macro_rules! parse_opcode {
|
||||||
|
|
||||||
|
($cpu:expr, $opcode:expr, $($code:expr => $inst:ident ($mode:ident)),* $(,)?) => (
|
||||||
|
|
||||||
|
match $opcode {
|
||||||
|
$(
|
||||||
|
$code => {
|
||||||
|
let input = $cpu.$mode();
|
||||||
|
$cpu.$inst(input)
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
_ => panic!("unimplemented opcode: 0x{:x}", $opcode),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
bitflags! {
|
||||||
|
struct StatusReg: u8 {
|
||||||
|
const C = 0x1 << 0;
|
||||||
|
const Z = 0x1 << 1;
|
||||||
|
const I = 0x1 << 2;
|
||||||
|
const D = 0x1 << 3;
|
||||||
|
const B = 0x1 << 4;
|
||||||
|
const U = 0x1 << 5;
|
||||||
|
const V = 0x1 << 6;
|
||||||
|
const N = 0x1 << 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Cpu {
|
||||||
|
a: u8,
|
||||||
|
x: u8,
|
||||||
|
y: u8,
|
||||||
|
pc: u16,
|
||||||
|
s: u8,
|
||||||
|
p: StatusReg,
|
||||||
|
bus: Bus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cpu {
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Cpu {
|
||||||
|
a: 1,
|
||||||
|
x: 1, //TODO temporary
|
||||||
|
y: 2, //TODO temporary
|
||||||
|
pc: 0, //TODO to be verified
|
||||||
|
s: 0x34,
|
||||||
|
p: StatusReg::from_bits(0xFD).unwrap(),
|
||||||
|
bus: Bus::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self) {
|
||||||
|
|
||||||
|
self.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&mut self) {
|
||||||
|
|
||||||
|
let opcode = self.read_pc_addr();
|
||||||
|
let cycles = parse_opcode!(self, opcode,
|
||||||
|
0x69 => adc(mode_immediate),
|
||||||
|
0x65 => adc(mode_zero_page),
|
||||||
|
0x75 => adc(mode_zero_page_x),
|
||||||
|
0x6D => adc(mode_absolute),
|
||||||
|
0x7D => adc(mode_absolute_x),
|
||||||
|
0x79 => adc(mode_absolute_y),
|
||||||
|
0x61 => adc(mode_indirect_x),
|
||||||
|
0x71 => adc(mode_indirect_y),
|
||||||
|
|
||||||
|
0x29 => and(mode_immediate),
|
||||||
|
0x25 => and(mode_zero_page),
|
||||||
|
0x35 => and(mode_zero_page_x),
|
||||||
|
0x2D => and(mode_absolute),
|
||||||
|
0x3D => and(mode_absolute_x),
|
||||||
|
0x39 => and(mode_absolute_y),
|
||||||
|
0x21 => and(mode_indirect_x),
|
||||||
|
0x31 => and(mode_indirect_y),
|
||||||
|
|
||||||
|
0x0A => asl(mode_accumulator),
|
||||||
|
0x06 => asl(mode_zero_page),
|
||||||
|
0x16 => asl(mode_zero_page_x),
|
||||||
|
0x0E => asl(mode_absolute),
|
||||||
|
0x1E => asl(mode_absolute_x),
|
||||||
|
|
||||||
|
0x90 => bcc(mode_relative),
|
||||||
|
0xB0 => bcs(mode_relative),
|
||||||
|
0xF0 => beq(mode_relative),
|
||||||
|
|
||||||
|
0x24 => bit(mode_zero_page),
|
||||||
|
0x2C => bit(mode_absolute),
|
||||||
|
|
||||||
|
0x30 => bmi(mode_relative),
|
||||||
|
0xD0 => bne(mode_relative),
|
||||||
|
0x10 => bpl(mode_relative),
|
||||||
|
|
||||||
|
0x00 => brk(mode_implicit),
|
||||||
|
|
||||||
|
0x50 => bvc(mode_relative),
|
||||||
|
0x70 => bvs(mode_relative),
|
||||||
|
|
||||||
|
0x18 => clc(mode_implicit),
|
||||||
|
0xD8 => cld(mode_implicit),
|
||||||
|
0x58 => cli(mode_implicit),
|
||||||
|
0xB8 => clv(mode_implicit),
|
||||||
|
|
||||||
|
0xC9 => cmp(mode_immediate),
|
||||||
|
0xC5 => cmp(mode_zero_page),
|
||||||
|
0xD5 => cmp(mode_zero_page_x),
|
||||||
|
0xCD => cmp(mode_absolute),
|
||||||
|
0xDD => cmp(mode_absolute_x),
|
||||||
|
0xD9 => cmp(mode_absolute_y),
|
||||||
|
0xC1 => cmp(mode_indirect_x),
|
||||||
|
0xD1 => cmp(mode_indirect_y),
|
||||||
|
|
||||||
|
0xE0 => cpx(mode_immediate),
|
||||||
|
0xE4 => cpx(mode_zero_page),
|
||||||
|
0xEC => cpx(mode_absolute),
|
||||||
|
|
||||||
|
0xC0 => cpy(mode_immediate),
|
||||||
|
0xC4 => cpy(mode_zero_page),
|
||||||
|
0xCC => cpy(mode_absolute),
|
||||||
|
|
||||||
|
0xC6 => dec(mode_zero_page),
|
||||||
|
0xD6 => dec(mode_zero_page_x),
|
||||||
|
0xCE => dec(mode_absolute),
|
||||||
|
0xDE => dec(mode_absolute_x),
|
||||||
|
|
||||||
|
0xCA => dex(mode_implicit),
|
||||||
|
0x88 => dey(mode_implicit),
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("0x{:X}: {} cycles", opcode, cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implicit mode
|
||||||
|
/// instructions that use implied mode don't need inputs
|
||||||
|
fn mode_implicit(&self) -> () {}
|
||||||
|
|
||||||
|
/// Immediate mode "#v":
|
||||||
|
/// use 8bit operand as input
|
||||||
|
fn mode_immediate(&mut self) -> Data {
|
||||||
|
Data {
|
||||||
|
data: self.read_pc_addr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immediate mode "#v":
|
||||||
|
/// use 8bit operand as signed offset for PC
|
||||||
|
fn mode_relative(&mut self) -> Data {
|
||||||
|
self.mode_immediate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zero-page mode "d":
|
||||||
|
/// Fetch value from the zero page (8bit adresses)
|
||||||
|
/// val = PEEK(arg & 0xFF)
|
||||||
|
fn mode_zero_page(&mut self) -> MemoryVal {
|
||||||
|
MemoryVal {
|
||||||
|
addr: self.read_pc_addr() as u16,
|
||||||
|
cycles: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zero-page indexed mode "d,x":
|
||||||
|
/// Fetch value from the zero page (8bit adresses), indexed on X register
|
||||||
|
/// val = PEEK((arg + X) & 0xFF)
|
||||||
|
fn mode_zero_page_x(&mut self) -> MemoryVal {
|
||||||
|
MemoryVal {
|
||||||
|
addr: (self.read_pc_addr() + self.x) as u16 & 0x00FF,
|
||||||
|
cycles: 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zero-page indexed mode "d,y":
|
||||||
|
/// Fetch value from the zero page (8bit adresses), indexed on Y register
|
||||||
|
/// val = PEEK((arg + Y) & 0xFF)
|
||||||
|
fn mode_zero_page_y(&mut self) -> MemoryVal {
|
||||||
|
MemoryVal {
|
||||||
|
addr: (self.read_pc_addr() + self.y) as u16 & 0x00FF,
|
||||||
|
cycles: 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Absolute mode "a"
|
||||||
|
/// Fetch value from anywhere in memory
|
||||||
|
/// val = PEEK(arg)
|
||||||
|
fn mode_absolute(&mut self) -> MemoryVal {
|
||||||
|
|
||||||
|
// compute full address
|
||||||
|
let addr_lo = self.read_pc_addr() as u16;
|
||||||
|
let addr_hi = self.read_pc_addr() as u16;
|
||||||
|
let addr = addr_lo | (addr_hi << 8);
|
||||||
|
|
||||||
|
MemoryVal {
|
||||||
|
addr,
|
||||||
|
cycles: 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Absolute mode "a.x"
|
||||||
|
/// Fetch value from anywhere in memory, indexed on X register
|
||||||
|
/// val = PEEK(arg + X)
|
||||||
|
fn mode_absolute_x(&mut self) -> MemoryValExtra {
|
||||||
|
|
||||||
|
// compute full address
|
||||||
|
let addr_lo = self.read_pc_addr() as u16;
|
||||||
|
let addr_hi = self.read_pc_addr() as u16;
|
||||||
|
let mut addr = addr_lo | (addr_hi << 8);
|
||||||
|
|
||||||
|
// compute index
|
||||||
|
let extra_cycle = (addr_lo + (self.x as u16)) & 0xFF00 != 0;
|
||||||
|
addr += self.x as u16;
|
||||||
|
|
||||||
|
MemoryValExtra {
|
||||||
|
addr,
|
||||||
|
cycles: 4,
|
||||||
|
extra_cycle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Absolute mode "a.Y"
|
||||||
|
/// Fetch value from anywhere in memory, indexed on Y register
|
||||||
|
/// val = PEEK(arg + Y)
|
||||||
|
fn mode_absolute_y(&mut self) -> MemoryValExtra {
|
||||||
|
|
||||||
|
// compute full address
|
||||||
|
let addr_lo = self.read_pc_addr() as u16;
|
||||||
|
let addr_hi = self.read_pc_addr() as u16;
|
||||||
|
let mut addr = addr_lo | (addr_hi << 8);
|
||||||
|
|
||||||
|
// compute index
|
||||||
|
let extra_cycle = (addr_lo + (self.y as u16)) & 0xFF00 != 0;
|
||||||
|
addr += self.y as u16;
|
||||||
|
|
||||||
|
MemoryValExtra {
|
||||||
|
addr,
|
||||||
|
cycles: 4,
|
||||||
|
extra_cycle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indirect mode "(a)"
|
||||||
|
/// Fetch 16bit address from anywhere in memory, only used by JMP. This instruction behaves
|
||||||
|
/// uncorrectly on page boundary on the original 6502 (emulated here)
|
||||||
|
/// addr = PEEK(arg) + PEEK(arg + 1) << 8
|
||||||
|
fn mode_indirect(&mut self) -> MemoryVal {
|
||||||
|
|
||||||
|
// compute full address
|
||||||
|
let addr_ind_lo = self.read_pc_addr() as u16;
|
||||||
|
let addr_ind_hi = self.read_pc_addr() as u16;
|
||||||
|
let addr_ind = addr_ind_lo | (addr_ind_hi << 8);
|
||||||
|
|
||||||
|
// fetch indirect addr to jump to
|
||||||
|
let addr_lo = self.bus.read_addr(addr_ind) as u16;
|
||||||
|
//note: bug of the 6502, the carry isn't applied when incrementing the indirect address
|
||||||
|
let addr_hi = self.bus.read_addr(((addr_ind + 1) & 0xFF) | (addr_ind_hi << 8)) as u16;
|
||||||
|
let addr = addr_lo | (addr_hi << 8);
|
||||||
|
|
||||||
|
MemoryVal {
|
||||||
|
addr,
|
||||||
|
cycles: 6, //only used by JMP that uses cycles - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indirect mode "(d.x)"
|
||||||
|
/// Fetch value from anywhere in memory using an address table built by the user and stored in
|
||||||
|
/// zero page, indexed on X
|
||||||
|
/// val = PEEK(PEEK((arg + X) & 0xFF) + PEEK((arg + X + 1) & 0xFF) << 8)
|
||||||
|
fn mode_indirect_x(&mut self) -> MemoryVal {
|
||||||
|
|
||||||
|
let addr_ind = self.read_pc_addr() as u16 + self.x as u16;
|
||||||
|
|
||||||
|
// fetch indirect addr to jump to
|
||||||
|
let addr_lo = self.bus.read_addr(addr_ind & 0xFF) as u16;
|
||||||
|
let addr_hi = self.bus.read_addr((addr_ind + 1) & 0xFF) as u16;
|
||||||
|
let addr = addr_lo | (addr_hi << 8);
|
||||||
|
|
||||||
|
MemoryVal {
|
||||||
|
addr,
|
||||||
|
cycles: 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indirect mode "(d).y"
|
||||||
|
/// Fetch value from anywhere in memory using a stored address, indexed on Y
|
||||||
|
/// val = PEEK(PEEK(arg) + PEEK((arg + 1) & 0xFF) << 8 + Y)
|
||||||
|
fn mode_indirect_y(&mut self) -> MemoryValExtra {
|
||||||
|
|
||||||
|
let addr_ind = self.read_pc_addr() as u16;
|
||||||
|
|
||||||
|
// fetch indirect addr to jump to
|
||||||
|
let addr_lo = self.bus.read_addr(addr_ind) as u16;
|
||||||
|
let addr_hi = self.bus.read_addr((addr_ind + 1) & 0xFF) as u16;
|
||||||
|
let mut addr = addr_lo | (addr_hi << 8) ;
|
||||||
|
|
||||||
|
// compute index
|
||||||
|
let extra_cycle = (addr_lo + (self.y as u16)) & 0xFF00 != 0;
|
||||||
|
addr += self.y as u16;
|
||||||
|
|
||||||
|
MemoryValExtra {
|
||||||
|
addr,
|
||||||
|
cycles: 5,
|
||||||
|
extra_cycle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accumulator mode "A"
|
||||||
|
/// Directly use the accumulator as input and/or output
|
||||||
|
fn mode_accumulator(&self) -> Accumulator {
|
||||||
|
Accumulator {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increment PC, then read corresponding value in memory
|
||||||
|
fn read_pc_addr(&mut self) -> u8 {
|
||||||
|
|
||||||
|
self.pc += 1;
|
||||||
|
let val = self.bus.read_addr(self.pc);
|
||||||
|
|
||||||
|
val
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adc<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
let val = input.read(&self.a, &self.bus);
|
||||||
|
|
||||||
|
// add acc and value
|
||||||
|
let prev_a = self.a as u16;
|
||||||
|
let mut a = self.a as u16;
|
||||||
|
if self.p.contains(StatusReg::C) {
|
||||||
|
a = a.wrapping_add(1);
|
||||||
|
}
|
||||||
|
a = a.wrapping_add((val & 0xFF) as u16);
|
||||||
|
|
||||||
|
// handle status bits
|
||||||
|
self.p.set(StatusReg::C, a & 0xFF00 != 0);
|
||||||
|
self.p.set(StatusReg::Z, a & 0xFF == 0);
|
||||||
|
if ((prev_a & 0x80) !=0 && (val & 0x80) != 0 && (a & 0x80) == 0)
|
||||||
|
|| ((prev_a & 0x80) == 0 && (val & 0x80) == 0 && (a & 0x80) != 0) {
|
||||||
|
self.p.set(StatusReg::V, true);
|
||||||
|
} else {
|
||||||
|
self.p.set(StatusReg::V, false);
|
||||||
|
}
|
||||||
|
self.p.set(StatusReg::N, a & 0x80 != 0);
|
||||||
|
|
||||||
|
// truncate accumulator to 8 bits
|
||||||
|
self.a = a as u8;
|
||||||
|
|
||||||
|
// handle zero bit
|
||||||
|
|
||||||
|
input.get_cycles()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn and<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
let val = input.read(&self.a, &self.bus);
|
||||||
|
|
||||||
|
// logical AND between acc and value
|
||||||
|
self.a &= val;
|
||||||
|
|
||||||
|
// handle status bits
|
||||||
|
self.p.set(StatusReg::Z, self.a == 0);
|
||||||
|
self.p.set(StatusReg::N, self.a & 0x80 != 0);
|
||||||
|
|
||||||
|
input.get_cycles()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn asl<I: RWInput>(&mut self, mut input: I) -> u32 {
|
||||||
|
let mut target = input.read(&self.a, &self.bus) as u16;
|
||||||
|
|
||||||
|
// shift target one bit left
|
||||||
|
target = target << 1;
|
||||||
|
|
||||||
|
// handle status bits
|
||||||
|
self.p.set(StatusReg::C, target & 0xFF00 != 0);
|
||||||
|
self.p.set(StatusReg::Z, target & 0xFF == 0);
|
||||||
|
self.p.set(StatusReg::N, target & 0x80 != 0);
|
||||||
|
|
||||||
|
// store result
|
||||||
|
input.write(&mut self.a, &mut self.bus, target as u8);
|
||||||
|
input.get_cycles() + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn branch_if<I: RInput>(&mut self, cond: bool, input: I) -> u32 {
|
||||||
|
let offset = input.read(&self.a, &self.bus) as i8;
|
||||||
|
|
||||||
|
match cond {
|
||||||
|
false => input.get_cycles(),
|
||||||
|
true => {
|
||||||
|
let old_pc = self.pc;
|
||||||
|
self.pc = self.pc.wrapping_add(offset as u16);
|
||||||
|
debug!("PC: 0x{:X}", self.pc);
|
||||||
|
match old_pc & 0xFF00 == self.pc & 0xFF00 {
|
||||||
|
true => input.get_cycles() + 1,
|
||||||
|
false => input.get_cycles() + 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bcc<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.branch_if(!self.p.contains(StatusReg::C), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bcs<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.branch_if(self.p.contains(StatusReg::C), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn beq<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.branch_if(self.p.contains(StatusReg::Z), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bit<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
let val = input.read(&self.a, &self.bus);
|
||||||
|
|
||||||
|
// logical AND between acc and value
|
||||||
|
let and = self.a & val;
|
||||||
|
|
||||||
|
// handle status bits
|
||||||
|
self.p.set(StatusReg::Z, and == 0);
|
||||||
|
self.p.set(StatusReg::V, (val & 0x1 << 6) != 0);
|
||||||
|
self.p.set(StatusReg::N, (val & 0x1 << 7) != 0);
|
||||||
|
|
||||||
|
input.get_cycles()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bmi<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.branch_if(self.p.contains(StatusReg::N), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bne<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.branch_if(!self.p.contains(StatusReg::Z), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bpl<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.branch_if(!self.p.contains(StatusReg::N), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn brk(&mut self, _: ()) -> u32 {
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
panic!("BRK not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bvc<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.branch_if(!self.p.contains(StatusReg::V), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bvs<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.branch_if(self.p.contains(StatusReg::V), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clc(&mut self, _: ()) -> u32 {
|
||||||
|
self.p.set(StatusReg::C, false);
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cld(&mut self, _: ()) -> u32 {
|
||||||
|
self.p.set(StatusReg::D, false);
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cli(&mut self, _: ()) -> u32 {
|
||||||
|
self.p.set(StatusReg::I, false);
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clv(&mut self, _: ()) -> u32 {
|
||||||
|
self.p.set(StatusReg::V, false);
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_cmp<I: RInput>(&mut self, reg: u8, input: I) -> u32 {
|
||||||
|
let val = input.read(&self.a, &self.bus) as u8;
|
||||||
|
|
||||||
|
self.p.set(StatusReg::C, reg >= val);
|
||||||
|
self.p.set(StatusReg::Z, reg == val);
|
||||||
|
self.p.set(StatusReg::N, ((reg - val) & (0x1 << 7)) != 0);
|
||||||
|
|
||||||
|
input.get_cycles()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmp<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.compute_cmp(self.a, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cpx<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.compute_cmp(self.x, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cpy<I: RInput>(&mut self, input: I) -> u32 {
|
||||||
|
self.compute_cmp(self.y, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dec<I: RWInput>(&mut self, mut input: I) -> u32 {
|
||||||
|
let mut val = input.read(&self.a, &self.bus);
|
||||||
|
|
||||||
|
// compute
|
||||||
|
val = val.wrapping_sub(1);
|
||||||
|
self.p.set(StatusReg::Z, val == 0);
|
||||||
|
self.p.set(StatusReg::N, (val & (0x1 << 7)) != 0);
|
||||||
|
|
||||||
|
// store result
|
||||||
|
input.write(&mut self.a, &mut self.bus, val);
|
||||||
|
input.get_cycles() + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dex(&mut self, _: ()) -> u32 {
|
||||||
|
|
||||||
|
// compute
|
||||||
|
self.x = self.x.wrapping_sub(1);
|
||||||
|
self.p.set(StatusReg::Z, self.x == 0);
|
||||||
|
self.p.set(StatusReg::N, (self.x & (0x1 << 7)) != 0);
|
||||||
|
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dey(&mut self, _: ()) -> u32 {
|
||||||
|
|
||||||
|
// compute
|
||||||
|
self.y = self.y.wrapping_sub(1);
|
||||||
|
self.p.set(StatusReg::Z, self.y == 0);
|
||||||
|
self.p.set(StatusReg::N, (self.y & (0x1 << 7)) != 0);
|
||||||
|
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
90
src/main.rs
90
src/main.rs
@ -1,3 +1,89 @@
|
|||||||
fn main() {
|
#[allow(unused_imports)]
|
||||||
println!("Hello, world!");
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
mod cpu;
|
||||||
|
use cpu::Cpu;
|
||||||
|
|
||||||
|
mod bus;
|
||||||
|
mod memory;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
fn main() -> Result<(), &'static str> {
|
||||||
|
use utils::{Pixel, PixelBuffer, FontRenderer};
|
||||||
|
use winit::{
|
||||||
|
event_loop::EventLoop,
|
||||||
|
dpi::LogicalSize,
|
||||||
|
};
|
||||||
|
use mini_gl_fb::config;
|
||||||
|
|
||||||
|
// 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(800.0, 600.0),
|
||||||
|
invert_y: false,
|
||||||
|
};
|
||||||
|
let mut fb = mini_gl_fb::get_fancy(config, &event_loop);
|
||||||
|
let mut buffer = PixelBuffer::new(800, 600);
|
||||||
|
let mut renderer = FontRenderer::new(20.0, [0u8, 0, 0]);
|
||||||
|
|
||||||
|
let vec: Vec<[u8; 4]> = (&buffer).into();
|
||||||
|
fb.update_buffer(&vec);
|
||||||
|
|
||||||
|
let mut cpu = Cpu::new();
|
||||||
|
|
||||||
|
// event loop
|
||||||
|
fb.glutin_handle_basic_input(&mut event_loop, |fb, input| {
|
||||||
|
use winit::event::VirtualKeyCode;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
// wait for input before updating
|
||||||
|
input.wait = true;
|
||||||
|
|
||||||
|
// close if escape is pressed
|
||||||
|
if input.key_pressed(VirtualKeyCode::Escape) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// execute next cpu instruction
|
||||||
|
if input.key_pressed(VirtualKeyCode::S) {
|
||||||
|
cpu.tick();
|
||||||
|
|
||||||
|
let mut debug_str = String::new();
|
||||||
|
write!(debug_str, "{:#?}", cpu).unwrap();
|
||||||
|
buffer.fill([20, 20], [760, 560], 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);
|
||||||
|
|
||||||
|
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::Debug)
|
||||||
|
.chain(std::io::stdout())
|
||||||
|
.chain(fern::log_file("output.log")?)
|
||||||
|
.apply()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
54
src/memory.rs
Normal file
54
src/memory.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
pub trait Memory {
|
||||||
|
|
||||||
|
fn read_addr(&self, addr: u16) -> u8;
|
||||||
|
|
||||||
|
fn write_addr(&mut self, addr: u16, data: u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
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 {
|
||||||
|
self.buffer.iter().enumerate()
|
||||||
|
.try_for_each(|(i, elem)| {
|
||||||
|
if i <= 0xFF {
|
||||||
|
if i%0x10 == 0 {
|
||||||
|
f.write_fmt(format_args!("\n {:0>4X}", i))?;
|
||||||
|
}
|
||||||
|
f.write_fmt(format_args!(" {:0>2X}", elem))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const SIZE: usize> Memory 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],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
169
src/utils.rs
Normal file
169
src/utils.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use rusttype::{point, Font, Scale, VMetrics};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/* 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/* PixelBuffer struct */
|
||||||
|
|
||||||
|
pub struct PixelBuffer {
|
||||||
|
buffer: Vec<Pixel>,
|
||||||
|
h: usize,
|
||||||
|
w: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixelBuffer {
|
||||||
|
|
||||||
|
pub fn new(h: usize, w: usize) -> Self {
|
||||||
|
PixelBuffer {
|
||||||
|
buffer: vec![Pixel::rgba(255u8, 255u8, 255u8, 255); h * w],
|
||||||
|
h,
|
||||||
|
w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_pixel(&mut self, x: usize, y: usize, pixel: Pixel) {
|
||||||
|
self.buffer[x + y*self.h] += pixel
|
||||||
|
}
|
||||||
|
|
||||||
|
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.h] += pixel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&PixelBuffer> for Vec<[u8; 4]> {
|
||||||
|
fn from(buffer: &PixelBuffer) -> Self {
|
||||||
|
buffer.buffer.iter()
|
||||||
|
.map(|pixel| pixel.into())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/* FontRenderer struct */
|
||||||
|
|
||||||
|
pub struct FontRenderer {
|
||||||
|
font: Font<'static>, scale: Scale, v_metrics: VMetrics, color: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontRenderer {
|
||||||
|
|
||||||
|
pub fn new(size: f32, color: [u8; 3]) -> Self {
|
||||||
|
|
||||||
|
// Load the font
|
||||||
|
let font = {
|
||||||
|
let font_data = include_bytes!("../fonts/DejaVuSansMono.ttf");
|
||||||
|
// This only succeeds if collection consists of one font
|
||||||
|
Font::try_from_bytes(font_data as &[u8]).expect("Error constructing Font")
|
||||||
|
};
|
||||||
|
|
||||||
|
let scale = Scale::uniform(size);
|
||||||
|
let v_metrics = font.v_metrics(scale);
|
||||||
|
|
||||||
|
FontRenderer {
|
||||||
|
font,
|
||||||
|
scale,
|
||||||
|
v_metrics,
|
||||||
|
color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&mut self, buffer: &mut PixelBuffer, text: &str, origin: [usize; 2],
|
||||||
|
size: [usize; 2]) {
|
||||||
|
|
||||||
|
let line_offset = (self.v_metrics.line_gap + self.v_metrics.ascent
|
||||||
|
- self.v_metrics.descent) as usize;
|
||||||
|
|
||||||
|
// split text along '\n'
|
||||||
|
text.split('\n')
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(id, line)| {
|
||||||
|
|
||||||
|
// layout the glyphs in a line with 20 pixels padding
|
||||||
|
let glyphs: Vec<_> = self.font
|
||||||
|
.layout(line, self.scale, point(origin[0] as f32,
|
||||||
|
origin[1] as f32 + self.v_metrics.ascent))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Loop through the glyphs in the text, positing each one on a line
|
||||||
|
for glyph in glyphs {
|
||||||
|
if let Some(bounding_box) = glyph.pixel_bounding_box() {
|
||||||
|
// Draw the glyph into the image per-pixel by using the draw closure
|
||||||
|
glyph.draw(|x, y, v| {
|
||||||
|
let x_pos = (x + bounding_box.min.x as u32) as usize;
|
||||||
|
let y_pos = (y + bounding_box.min.y as u32) as usize + id * line_offset;
|
||||||
|
|
||||||
|
if x_pos < origin[0] + size[0] && y_pos < origin[1] + size[1] {
|
||||||
|
buffer.put_pixel(
|
||||||
|
x_pos,
|
||||||
|
y_pos,
|
||||||
|
// Turn the coverage into an alpha value
|
||||||
|
Pixel::rgba(
|
||||||
|
self.color[0],
|
||||||
|
self.color[1],
|
||||||
|
self.color[2],
|
||||||
|
(v * 255.0) as u8),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user