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::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),
|
||||
..
|
||||
} => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue