fixed performance issue on windows. got profiling with visual studio working, found that calling canvas.fill_rect() every pixel was way too slow. now writing pixels to one raw RGB buffer, and between frames, updating a texture with that buffer and copying it to the canvas.

This commit is contained in:
Theron 2019-11-12 17:19:25 -06:00
parent 4edcbdd73c
commit 28b371a5ae
3 changed files with 58 additions and 34 deletions

View File

@ -7,3 +7,6 @@ edition = "2018"
[dependencies]
sdl2 = "0.32"
cpuprofiler = "0.0.3"
[profile.release]
debug = true

View File

@ -9,24 +9,33 @@ mod input;
use cpu::Cpu;
use ppu::Ppu;
use cartridge::Cartridge;
use screen::Screen;
use screen::{init_window, draw_pixel, draw_to_window};
use input::poll_buttons;
use sdl2::keyboard::Keycode;
use sdl2::event::Event;
use sdl2::pixels::PixelFormatEnum;
// use cpuprofiler::PROFILER;
fn main() -> Result<(), String> {
// Set up screen
let sdl_context = sdl2::init()?;
let mut event_pump = sdl_context.event_pump()?;
let mut screen = Screen::new(&sdl_context)?;
let (mut canvas, texture_creator) = init_window(&sdl_context).expect("Could not create window");
let mut texture = texture_creator.create_texture_streaming(
PixelFormatEnum::RGB24, 256*screen::SCALE_FACTOR as u32, 240*screen::SCALE_FACTOR as u32)
.map_err(|e| e.to_string())?;
let byte_width = 256 * 3 * screen::SCALE_FACTOR; // 256 NES pixels, 3 bytes for each pixel (RGB 24-bit), and NES-to-SDL scale factor
let byte_height = 240 * screen::SCALE_FACTOR; // NES image is 240 pixels tall, multiply by scale factor for total number of rows needed
let mut screen_buffer = vec![0; byte_width * byte_height]; // contains raw RGB data for the screen
// Initialize hardware components
let cart = Cartridge::new();
let ppu = Ppu::new(&cart);
let mut cpu = Cpu::new(&cart, ppu);
// For throttling to 60 FPS
let mut timer = Instant::now();
let mut fps_timer = Instant::now();
let mut fps = 0;
@ -39,12 +48,12 @@ fn main() -> Result<(), String> {
for _i in 0..num_cycles * 3 {
let (pixel, end_of_frame) = cpu.ppu.step();
match pixel {
Some((x, y, color)) => screen.draw_pixel(x, y, color),
None => Ok(()),
}?;
Some((x, y, color)) => draw_pixel(&mut screen_buffer, x, y, color),
None => (),
};
if end_of_frame {
fps += 1;
screen.canvas.present();
draw_to_window(&mut texture, &mut canvas, &screen_buffer)?;
let now = Instant::now();
if now < timer + Duration::from_millis(1000/60) {
std::thread::sleep(timer + Duration::from_millis(1000/60) - now);

View File

@ -2,38 +2,50 @@ extern crate sdl2;
use sdl2::Sdl;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::Canvas;
use sdl2::video::Window;
use sdl2::render::{Canvas, Texture, TextureCreator};
use sdl2::video::{Window, WindowContext};
const SCALE_FACTOR: usize = 4;
pub const SCALE_FACTOR: usize = 3;
const BYTES_IN_COL: usize = SCALE_FACTOR * 3; // 3 bytes per pixel in RGB24. This represents a thick, SCALE_FACTOR-pixel-wide column.
const BYTES_IN_ROW: usize = BYTES_IN_COL * 256; // 256 = screen width in NES pixels. This represents a thin, one-SDL-pixel-tall row.
type RGBColor = (u8, u8, u8);
pub struct Screen {
pub canvas: Canvas<Window>,
pub fn init_window(context: &Sdl) -> Result<(Canvas<Window>, TextureCreator<WindowContext>), String> {
let video_subsystem = context.video()?;
let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32)
.position_centered()
.opengl()
.build()
.map_err(|e| e.to_string())?;
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
let texture_creator = canvas.texture_creator();
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
canvas.present();
Ok((canvas, texture_creator))
}
impl Screen {
pub fn new(context: &Sdl) -> Result<Self, String> {
let video_subsystem = context.video()?;
let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32)
.position_centered()
.opengl()
.build()
.map_err(|e| e.to_string())?;
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
canvas.present();
Ok(Screen{canvas})
pub fn draw_pixel(buffer: &mut Vec<u8>, x: usize, y: usize, color: RGBColor) {
let (r, g, b) = color;
let nes_y_offset = y * BYTES_IN_ROW * SCALE_FACTOR; // find offset for thick, SCALE_FACTOR-pixel tall row
for sdl_row_num in 0..SCALE_FACTOR { // looping over one-pixel tall rows up to SCALE_FACTOR
let row_offset = nes_y_offset + (sdl_row_num * BYTES_IN_ROW); // row_offset is the offset within buffer of the thin row we're on
let nes_x_offset = x * BYTES_IN_COL; // find horizontal offset within row (in byte terms) of NES x-coordinate
for sdl_col_num in 0..SCALE_FACTOR { // for pixels up to SCALE_FACTOR, moving horizontally
let col_offset = nes_x_offset + (sdl_col_num * 3); // skip 3 bytes at a time, R/G/B for each pixel
let offset = row_offset + col_offset;
buffer[offset + 0] = r;
buffer[offset + 1] = g;
buffer[offset + 2] = b;
}
}
}
pub fn draw_pixel(&mut self, x: usize, y: usize, color: RGBColor) -> Result<(), String> {
let (r, g, b) = color;
self.canvas.set_draw_color(Color::RGB(r, g, b));
self.canvas.fill_rect(Rect::new((x * SCALE_FACTOR) as i32, (y * SCALE_FACTOR) as i32,
SCALE_FACTOR as u32, SCALE_FACTOR as u32))?;
Ok(())
}
}
pub fn draw_to_window(texture: &mut Texture, canvas: &mut Canvas<Window>, buffer: &Vec<u8>) -> Result<(), String> {
texture.update(None, buffer, 256*3*SCALE_FACTOR)
.map_err(|e| e.to_string())?;
canvas.copy(&texture, None, None)?;
canvas.present();
Ok(())
}