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:
parent
4edcbdd73c
commit
28b371a5ae
|
@ -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
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -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);
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue