mirror of https://github.com/Ivsucram/ivsemu.git
Chip-8: From Rect to Textures
The main contribution of this commit is changing the display rendering method. Before, I was asking the canvas to render rectangles, which represented scaled pixesl. Now I am using a texture, that is automatically stretched to the whole canvas by SDL. The display coding is running faster, but there is still some ways to improve rendering. The main one is to change the cpu display buffer from a multi-dimensional array of booleans (indincating pixels toggling) to a single dimension pixel-color array. In the pixel-color array, 4u8 samples represent a single pixel on screen (R: u8, G: u8, B: u8, A:u8). This refactor will force the CPU to control have knowledge of which color will be printed on the display, letting the display being agnostic to implementation details. This refactor will be added as an issue to the github project kanban and further analyzed. Besides that, other changes to the codebase were mainly in terms of code organization.
This commit is contained in:
parent
d6430f0a2e
commit
03b943eeca
|
@ -1,43 +1,48 @@
|
||||||
use super::cpu::cpu::CPU;
|
use super::cpu::cpu::CPU;
|
||||||
use super::display::Display;
|
use super::display::Display;
|
||||||
|
|
||||||
|
use sdl2::event::Event;
|
||||||
|
use sdl2::keyboard::Keycode;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let mut cpu = CPU::new();
|
let mut cpu = CPU::new();
|
||||||
load_rom("src/chip_8/roms/CAVE.ch8", &mut cpu);
|
load_rom(
|
||||||
|
"src/chip_8/roms/Trip8 Demo (2008) [Revival Studios].ch8",
|
||||||
|
&mut cpu,
|
||||||
|
);
|
||||||
|
|
||||||
let sdl_context = sdl2::init().unwrap();
|
let sdl_context = sdl2::init().unwrap();
|
||||||
let mut display = Display::init(&sdl_context, cpu.get_display_scale());
|
let mut display = Display::init(&sdl_context, cpu.get_display_scale());
|
||||||
let mut event_listener = sdl_context.event_pump().unwrap();
|
let mut event_pump = sdl_context.event_pump().unwrap();
|
||||||
|
|
||||||
'runner: loop {
|
'runner: loop {
|
||||||
for event in event_listener.poll_iter() {
|
for event in event_pump.poll_iter() {
|
||||||
match event {
|
match event {
|
||||||
sdl2::event::Event::Quit { .. } => break 'runner,
|
Event::Quit { .. } => break 'runner,
|
||||||
sdl2::event::Event::KeyDown {
|
Event::KeyDown {
|
||||||
keycode: Some(sdl2::keyboard::Keycode::Escape),
|
keycode: Some(Keycode::Escape),
|
||||||
..
|
..
|
||||||
} => break 'runner,
|
} => break 'runner,
|
||||||
sdl2::event::Event::KeyDown {
|
Event::KeyDown {
|
||||||
keycode: Some(sdl2::keyboard::Keycode::RightBracket),
|
keycode: Some(Keycode::RightBracket),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
cpu.increase_clock(true);
|
cpu.increase_clock(true);
|
||||||
}
|
}
|
||||||
sdl2::event::Event::KeyDown {
|
Event::KeyDown {
|
||||||
keycode: Some(sdl2::keyboard::Keycode::LeftBracket),
|
keycode: Some(Keycode::LeftBracket),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
cpu.decrease_clock(true);
|
cpu.decrease_clock(true);
|
||||||
}
|
}
|
||||||
sdl2::event::Event::KeyDown {
|
Event::KeyDown {
|
||||||
keycode: Some(sdl2::keyboard::Keycode::Backspace),
|
keycode: Some(Keycode::Backspace),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
cpu.reset_rom();
|
cpu.reset_rom();
|
||||||
}
|
}
|
||||||
sdl2::event::Event::KeyDown {
|
Event::KeyDown {
|
||||||
keycode: Some(keycode),
|
keycode: Some(keycode),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
@ -45,7 +50,7 @@ pub fn run() {
|
||||||
cpu.press_key(key_index);
|
cpu.press_key(key_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sdl2::event::Event::KeyUp {
|
Event::KeyUp {
|
||||||
keycode: Some(keycode),
|
keycode: Some(keycode),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
|
|
@ -16,22 +16,16 @@ impl Clock {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self) -> bool {
|
pub fn tick(&mut self) -> bool {
|
||||||
let mut res: bool = false;
|
if let Ok(elapsed) = self.elapsed.elapsed() {
|
||||||
match self.elapsed.elapsed() {
|
if elapsed.as_secs_f64() >= self.clock_hz_as_secs_f64() {
|
||||||
Ok(elapsed) => {
|
if self.tick > 0 {
|
||||||
if elapsed.as_secs_f64() >= self.clock_hz_as_secs_f64() {
|
self.tick -= 1;
|
||||||
if self.tick > 0 {
|
|
||||||
self.tick -= 1;
|
|
||||||
}
|
|
||||||
self.reset_elapsed();
|
|
||||||
res = true;
|
|
||||||
}
|
}
|
||||||
}
|
self.reset_elapsed();
|
||||||
Err(e) => {
|
return true;
|
||||||
println!("Error: {:?}", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_elapsed(&mut self) {
|
fn reset_elapsed(&mut self) {
|
||||||
|
|
|
@ -10,16 +10,16 @@ use sdl2::keyboard::Keycode;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
pub struct CPU {
|
pub struct CPU {
|
||||||
stack: Vec<usize>, // Function Stack
|
stack: Vec<usize>, // Function Stack
|
||||||
dt: Clock, // Delay Timer
|
dt: Clock, // Delay Timer
|
||||||
st: Clock, // Sound Timer
|
st: Clock, // Sound Timer
|
||||||
clock: Clock, // CPU Clock
|
clock: Clock, // CPU Clock
|
||||||
regs: Registers, // Registers
|
regs: Registers, // Registers
|
||||||
ram: RAM, // RAM
|
ram: RAM, // RAM
|
||||||
keypad: Keypad, // Keypad
|
keypad: Keypad, // Keypad
|
||||||
db: DisplayBuffer, // Display Buffer
|
db: DisplayBuffer, // Display Buffer
|
||||||
op: OpCodes, // Operation Code,
|
op: OpCodes, // Operation Code,
|
||||||
pub should_redraw: bool // Boolean indicating Display Buffer update
|
pub should_redraw: bool, // Boolean indicating Display Buffer update
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CPU {
|
impl CPU {
|
||||||
|
@ -37,7 +37,7 @@ impl CPU {
|
||||||
keypad: Keypad::new(),
|
keypad: Keypad::new(),
|
||||||
db: DisplayBuffer::new(10),
|
db: DisplayBuffer::new(10),
|
||||||
op: OpCodes::new(0000),
|
op: OpCodes::new(0000),
|
||||||
should_redraw: false
|
should_redraw: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ impl CPU {
|
||||||
|
|
||||||
pub fn set_delay_timer(&mut self, tick: u8) {
|
pub fn set_delay_timer(&mut self, tick: u8) {
|
||||||
self.dt.tick = tick;
|
self.dt.tick = tick;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sound_timer(&mut self, tick: u8) {
|
pub fn set_sound_timer(&mut self, tick: u8) {
|
||||||
self.st.tick = tick;
|
self.st.tick = tick;
|
||||||
|
@ -343,8 +343,7 @@ fn op_bnnn(cpu: &mut CPU) {
|
||||||
|
|
||||||
fn op_cxnn(cpu: &mut CPU) {
|
fn op_cxnn(cpu: &mut CPU) {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
cpu.regs
|
cpu.regs.set(cpu.op.x, rng.gen_range(0x0..0xFF) & cpu.op.nn);
|
||||||
.set(cpu.op.x, rng.gen_range(0x0..0xFF) & cpu.op.nn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn op_dxyn(cpu: &mut CPU) {
|
fn op_dxyn(cpu: &mut CPU) {
|
||||||
|
@ -438,4 +437,4 @@ fn op_fx65(cpu: &mut CPU) {
|
||||||
for regs in 0x0..(cpu.op.x + 1) {
|
for regs in 0x0..(cpu.op.x + 1) {
|
||||||
cpu.regs.set(regs, cpu.ram.read8(i + regs));
|
cpu.regs.set(regs, cpu.ram.read8(i + regs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,10 +35,10 @@ impl Keypad {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_keycode(&self, keycode: Keycode) -> Option<usize> {
|
pub fn compute_keycode(&self, keycode: Keycode) -> Option<usize> {
|
||||||
match self.keys.get(&keycode) {
|
if let Some(&chip8_key) = self.keys.get(&keycode) {
|
||||||
Some(chip8_key) => Some(*chip8_key),
|
return Some(chip8_key);
|
||||||
None => None,
|
|
||||||
}
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status(&mut self, pos: usize) -> bool {
|
pub fn get_status(&mut self, pos: usize) -> bool {
|
||||||
|
|
|
@ -16,9 +16,9 @@ pub struct Registers {
|
||||||
x_c: u8,
|
x_c: u8,
|
||||||
x_d: u8,
|
x_d: u8,
|
||||||
x_e: u8,
|
x_e: u8,
|
||||||
x_f: u8, // Flag register
|
x_f: u8, // Flag register
|
||||||
pub pc: usize, // Program Counter
|
pub pc: usize, // Program Counter
|
||||||
pub i: usize // Index register
|
pub i: usize, // Index register
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registers {
|
impl Registers {
|
||||||
|
@ -64,7 +64,7 @@ impl Registers {
|
||||||
0xD => self.x_d,
|
0xD => self.x_d,
|
||||||
0xE => self.x_e,
|
0xE => self.x_e,
|
||||||
0xF => self.x_f,
|
0xF => self.x_f,
|
||||||
_ => 0,
|
_ => panic!("Trying to access non-existing register"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ impl Registers {
|
||||||
0xD => self.x_d = value,
|
0xD => self.x_d = value,
|
||||||
0xE => self.x_e = value,
|
0xE => self.x_e = value,
|
||||||
0xF => self.x_f = value,
|
0xF => self.x_f = value,
|
||||||
_ => {}
|
_ => panic!("Trying to access non-existing register"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,56 @@
|
||||||
use sdl2::render::Canvas;
|
|
||||||
use sdl2::pixels::Color;
|
use sdl2::pixels::Color;
|
||||||
use sdl2::rect::Rect;
|
use sdl2::pixels::PixelFormatEnum;
|
||||||
|
use sdl2::render::Canvas;
|
||||||
|
use sdl2::video::Window;
|
||||||
|
|
||||||
const WIDTH: usize = 64;
|
const WIDTH: usize = 64;
|
||||||
const HEIGHT: usize = 32;
|
const HEIGHT: usize = 32;
|
||||||
|
const PITCH_BYTES: usize = std::mem::size_of::<u32>(); // 4 bytes: R G B A, from colors
|
||||||
|
const PITCH: usize = WIDTH * PITCH_BYTES;
|
||||||
|
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
canvas: Canvas<sdl2::video::Window>,
|
canvas: Canvas<Window>,
|
||||||
scale: u32,
|
off_color: [u8; PITCH_BYTES],
|
||||||
off_color: Color,
|
on_color: [u8; PITCH_BYTES],
|
||||||
on_color: Color,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display {
|
impl Display {
|
||||||
pub fn init(sdl_context: &sdl2::Sdl, scale: u32) -> Display {
|
pub fn init(sdl_context: &sdl2::Sdl, scale: u32) -> Display {
|
||||||
let video_subsystem = sdl_context.video().unwrap();
|
let canvas = sdl_context.video().unwrap()
|
||||||
|
|
||||||
let window = video_subsystem
|
|
||||||
.window("Chip-8", WIDTH as u32 * scale, HEIGHT as u32 * scale)
|
.window("Chip-8", WIDTH as u32 * scale, HEIGHT as u32 * scale)
|
||||||
.position_centered()
|
.resizable().position_centered().build().unwrap()
|
||||||
.build()
|
.into_canvas().build().unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let canvas = window.into_canvas().build().unwrap();
|
let off_color = Color::RGBA(0, 0, 0, 255);
|
||||||
|
let on_color = Color::RGBA(255, 255, 255, 255);
|
||||||
|
|
||||||
Display {
|
Display {
|
||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
scale: scale,
|
off_color: [off_color.r, off_color.g, off_color.b, off_color.a],
|
||||||
off_color: Color::RGB(0, 0, 0),
|
on_color: [on_color.r, on_color.g, on_color.b, on_color.a],
|
||||||
on_color: Color::RGB(255, 255, 255),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(self: &mut Display, buffer: &[[bool; HEIGHT]; WIDTH]) {
|
pub fn draw(self: &mut Display, buffer: &[[bool; HEIGHT]; WIDTH]) {
|
||||||
self.canvas.set_draw_color(self.off_color);
|
self.apply_texture(buffer);
|
||||||
self.canvas.clear();
|
self.canvas.present();
|
||||||
|
}
|
||||||
|
|
||||||
self.canvas.set_draw_color(self.on_color);
|
fn apply_texture(&mut self, buffer: &[[bool; HEIGHT]; WIDTH]) {
|
||||||
|
let mut framebuffer: [u8; HEIGHT * PITCH] = [0; HEIGHT * PITCH];
|
||||||
for (x, col) in buffer.iter().enumerate() {
|
for (x, col) in buffer.iter().enumerate() {
|
||||||
for (y, pixel) in col.iter().enumerate() {
|
for (y, pixel) in col.iter().enumerate() {
|
||||||
if *pixel {
|
let offset = x * PITCH_BYTES + y * PITCH;
|
||||||
let x = (x as u32 * self.scale) as i32;
|
let color: &[u8; PITCH_BYTES] = if *pixel { &self.on_color } else { &self.off_color };
|
||||||
let y = (y as u32 * self.scale) as i32;
|
for i in offset..(offset + PITCH_BYTES) { framebuffer[i] = color[i - offset]; }
|
||||||
let width = self.scale;
|
|
||||||
let height = self.scale;
|
|
||||||
self.canvas
|
|
||||||
.fill_rect(Rect::new(x, y, width, height))
|
|
||||||
.expect("Failed to draw pixel");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.canvas.present();
|
let texture_creator = self.canvas.texture_creator();
|
||||||
|
let mut texture = texture_creator
|
||||||
|
.create_texture_streaming(PixelFormatEnum::RGBA32, WIDTH as u32, HEIGHT as u32)
|
||||||
|
.unwrap();
|
||||||
|
texture.update(None, &framebuffer, PITCH).unwrap();
|
||||||
|
self.canvas.copy(&texture, None, None).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue