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] [dependencies]
sdl2 = "0.32" sdl2 = "0.32"
cpuprofiler = "0.0.3" cpuprofiler = "0.0.3"
[profile.release]
debug = true

View File

@ -9,24 +9,33 @@ mod input;
use cpu::Cpu; use cpu::Cpu;
use ppu::Ppu; use ppu::Ppu;
use cartridge::Cartridge; use cartridge::Cartridge;
use screen::Screen; use screen::{init_window, draw_pixel, draw_to_window};
use input::poll_buttons; use input::poll_buttons;
use sdl2::keyboard::Keycode; use sdl2::keyboard::Keycode;
use sdl2::event::Event; use sdl2::event::Event;
use sdl2::pixels::PixelFormatEnum;
// use cpuprofiler::PROFILER; // use cpuprofiler::PROFILER;
fn main() -> Result<(), String> { fn main() -> Result<(), String> {
// Set up screen
let sdl_context = sdl2::init()?; let sdl_context = sdl2::init()?;
let mut event_pump = sdl_context.event_pump()?; 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 cart = Cartridge::new();
let ppu = Ppu::new(&cart); let ppu = Ppu::new(&cart);
let mut cpu = Cpu::new(&cart, ppu); let mut cpu = Cpu::new(&cart, ppu);
// For throttling to 60 FPS
let mut timer = Instant::now(); let mut timer = Instant::now();
let mut fps_timer = Instant::now(); let mut fps_timer = Instant::now();
let mut fps = 0; let mut fps = 0;
@ -39,12 +48,12 @@ fn main() -> Result<(), String> {
for _i in 0..num_cycles * 3 { for _i in 0..num_cycles * 3 {
let (pixel, end_of_frame) = cpu.ppu.step(); let (pixel, end_of_frame) = cpu.ppu.step();
match pixel { match pixel {
Some((x, y, color)) => screen.draw_pixel(x, y, color), Some((x, y, color)) => draw_pixel(&mut screen_buffer, x, y, color),
None => Ok(()), None => (),
}?; };
if end_of_frame { if end_of_frame {
fps += 1; fps += 1;
screen.canvas.present(); draw_to_window(&mut texture, &mut canvas, &screen_buffer)?;
let now = Instant::now(); let now = Instant::now();
if now < timer + Duration::from_millis(1000/60) { if now < timer + Duration::from_millis(1000/60) {
std::thread::sleep(timer + Duration::from_millis(1000/60) - now); std::thread::sleep(timer + Duration::from_millis(1000/60) - now);

View File

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