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:
Marcus Vinicius de Carvalho 2021-07-30 15:02:08 +08:00
parent d6430f0a2e
commit 03b943eeca
6 changed files with 77 additions and 79 deletions

View File

@ -1,43 +1,48 @@
use super::cpu::cpu::CPU;
use super::display::Display;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use std::io::Read;
pub fn run() {
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 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 {
for event in event_listener.poll_iter() {
for event in event_pump.poll_iter() {
match event {
sdl2::event::Event::Quit { .. } => break 'runner,
sdl2::event::Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::Escape),
Event::Quit { .. } => break 'runner,
Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => break 'runner,
sdl2::event::Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::RightBracket),
Event::KeyDown {
keycode: Some(Keycode::RightBracket),
..
} => {
cpu.increase_clock(true);
}
sdl2::event::Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::LeftBracket),
Event::KeyDown {
keycode: Some(Keycode::LeftBracket),
..
} => {
cpu.decrease_clock(true);
}
sdl2::event::Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::Backspace),
Event::KeyDown {
keycode: Some(Keycode::Backspace),
..
} => {
cpu.reset_rom();
}
sdl2::event::Event::KeyDown {
Event::KeyDown {
keycode: Some(keycode),
..
} => {
@ -45,7 +50,7 @@ pub fn run() {
cpu.press_key(key_index);
}
}
sdl2::event::Event::KeyUp {
Event::KeyUp {
keycode: Some(keycode),
..
} => {

View File

@ -16,22 +16,16 @@ impl Clock {
}
pub fn tick(&mut self) -> bool {
let mut res: bool = false;
match self.elapsed.elapsed() {
Ok(elapsed) => {
if elapsed.as_secs_f64() >= self.clock_hz_as_secs_f64() {
if self.tick > 0 {
self.tick -= 1;
}
self.reset_elapsed();
res = true;
if let Ok(elapsed) = self.elapsed.elapsed() {
if elapsed.as_secs_f64() >= self.clock_hz_as_secs_f64() {
if self.tick > 0 {
self.tick -= 1;
}
}
Err(e) => {
println!("Error: {:?}", e);
self.reset_elapsed();
return true;
}
}
res
false
}
fn reset_elapsed(&mut self) {

View File

@ -10,16 +10,16 @@ use sdl2::keyboard::Keycode;
use rand::Rng;
pub struct CPU {
stack: Vec<usize>, // Function Stack
dt: Clock, // Delay Timer
st: Clock, // Sound Timer
clock: Clock, // CPU Clock
regs: Registers, // Registers
ram: RAM, // RAM
keypad: Keypad, // Keypad
db: DisplayBuffer, // Display Buffer
op: OpCodes, // Operation Code,
pub should_redraw: bool // Boolean indicating Display Buffer update
stack: Vec<usize>, // Function Stack
dt: Clock, // Delay Timer
st: Clock, // Sound Timer
clock: Clock, // CPU Clock
regs: Registers, // Registers
ram: RAM, // RAM
keypad: Keypad, // Keypad
db: DisplayBuffer, // Display Buffer
op: OpCodes, // Operation Code,
pub should_redraw: bool, // Boolean indicating Display Buffer update
}
impl CPU {
@ -37,7 +37,7 @@ impl CPU {
keypad: Keypad::new(),
db: DisplayBuffer::new(10),
op: OpCodes::new(0000),
should_redraw: false
should_redraw: false,
}
}
@ -343,8 +343,7 @@ fn op_bnnn(cpu: &mut CPU) {
fn op_cxnn(cpu: &mut CPU) {
let mut rng = rand::thread_rng();
cpu.regs
.set(cpu.op.x, rng.gen_range(0x0..0xFF) & cpu.op.nn);
cpu.regs.set(cpu.op.x, rng.gen_range(0x0..0xFF) & cpu.op.nn);
}
fn op_dxyn(cpu: &mut CPU) {

View File

@ -35,10 +35,10 @@ impl Keypad {
}
pub fn compute_keycode(&self, keycode: Keycode) -> Option<usize> {
match self.keys.get(&keycode) {
Some(chip8_key) => Some(*chip8_key),
None => None,
if let Some(&chip8_key) = self.keys.get(&keycode) {
return Some(chip8_key);
}
None
}
pub fn get_status(&mut self, pos: usize) -> bool {

View File

@ -16,9 +16,9 @@ pub struct Registers {
x_c: u8,
x_d: u8,
x_e: u8,
x_f: u8, // Flag register
pub pc: usize, // Program Counter
pub i: usize // Index register
x_f: u8, // Flag register
pub pc: usize, // Program Counter
pub i: usize, // Index register
}
impl Registers {
@ -64,7 +64,7 @@ impl Registers {
0xD => self.x_d,
0xE => self.x_e,
0xF => self.x_f,
_ => 0,
_ => panic!("Trying to access non-existing register"),
}
}
@ -87,7 +87,7 @@ impl Registers {
0xD => self.x_d = value,
0xE => self.x_e = value,
0xF => self.x_f = value,
_ => {}
_ => panic!("Trying to access non-existing register"),
}
}

View File

@ -1,56 +1,56 @@
use sdl2::render::Canvas;
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 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 {
canvas: Canvas<sdl2::video::Window>,
scale: u32,
off_color: Color,
on_color: Color,
canvas: Canvas<Window>,
off_color: [u8; PITCH_BYTES],
on_color: [u8; PITCH_BYTES],
}
impl Display {
pub fn init(sdl_context: &sdl2::Sdl, scale: u32) -> Display {
let video_subsystem = sdl_context.video().unwrap();
let window = video_subsystem
let canvas = sdl_context.video().unwrap()
.window("Chip-8", WIDTH as u32 * scale, HEIGHT as u32 * scale)
.position_centered()
.build()
.unwrap();
.resizable().position_centered().build().unwrap()
.into_canvas().build().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 {
canvas: canvas,
scale: scale,
off_color: Color::RGB(0, 0, 0),
on_color: Color::RGB(255, 255, 255),
off_color: [off_color.r, off_color.g, off_color.b, off_color.a],
on_color: [on_color.r, on_color.g, on_color.b, on_color.a],
}
}
pub fn draw(self: &mut Display, buffer: &[[bool; HEIGHT]; WIDTH]) {
self.canvas.set_draw_color(self.off_color);
self.canvas.clear();
self.apply_texture(buffer);
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 (y, pixel) in col.iter().enumerate() {
if *pixel {
let x = (x as u32 * self.scale) as i32;
let y = (y as u32 * self.scale) as i32;
let width = self.scale;
let height = self.scale;
self.canvas
.fill_rect(Rect::new(x, y, width, height))
.expect("Failed to draw pixel");
}
let offset = x * PITCH_BYTES + y * PITCH;
let color: &[u8; PITCH_BYTES] = if *pixel { &self.on_color } else { &self.off_color };
for i in offset..(offset + PITCH_BYTES) { framebuffer[i] = color[i - offset]; }
}
}
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();
}
}