Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
331 lines (296 sloc) 12.8 KB
//
// Author: Patrick Walton
//
use sdl2::pixels::PixelFormatEnum::BGR24;
use sdl2::render::{Renderer, Texture, TextureAccess};
use sdl2::{InitBuilder, Sdl};
/// Emulated screen width in pixels
const SCREEN_WIDTH: usize = 256;
/// Emulated screen height in pixels
const SCREEN_HEIGHT: usize = 240;
/// Screen texture size in bytes
const SCREEN_SIZE: usize = SCREEN_WIDTH * SCREEN_HEIGHT * 3;
const FONT_HEIGHT: usize = 10;
const FONT_GLYPH_COUNT: usize = 95;
const FONT_GLYPH_LENGTH: usize = FONT_GLYPH_COUNT * FONT_HEIGHT;
const STATUS_LINE_PADDING: usize = 6;
const STATUS_LINE_X: usize = STATUS_LINE_PADDING;
const STATUS_LINE_Y: usize = SCREEN_HEIGHT - STATUS_LINE_PADDING - FONT_HEIGHT;
const STATUS_LINE_PAUSE_DURATION: usize = 120; // in 1/60 of a second
//
// PT Ronda Seven
//
// (c) Yusuke Kamiyamane, http://pinvoke.com/
//
const FONT_GLYPHS: [u8; FONT_GLYPH_LENGTH] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ' '
0, 64, 64, 64, 64, 64, 0, 64, 0, 0, // '!'
0, 160, 160, 0, 0, 0, 0, 0, 0, 0, // '"'
0, 80, 80, 248, 80, 248, 80, 80, 0, 0, // '#'
32, 112, 168, 160, 112, 40, 168, 112, 32, 0, // '$'
0, 66, 164, 72, 16, 36, 74, 132, 0, 0, // '%'
0, 96, 144, 160, 72, 168, 144, 104, 0, 0, // '&'
0, 128, 128, 0, 0, 0, 0, 0, 0, 0, // '''
32, 64, 128, 128, 128, 128, 128, 64, 32, 0, // '('
128, 64, 32, 32, 32, 32, 32, 64, 128, 0, // ')'
0, 32, 168, 112, 168, 32, 0, 0, 0, 0, // '*'
0, 0, 32, 32, 248, 32, 32, 0, 0, 0, // '+'
0, 0, 0, 0, 0, 0, 0, 64, 64, 128, // ','
0, 0, 0, 0, 0, 224, 0, 0, 0, 0, // '-'
0, 0, 0, 0, 0, 0, 0, 64, 0, 0, // '.'
8, 8, 16, 16, 32, 64, 64, 128, 128, 0, // '/'
0, 112, 136, 136, 136, 136, 136, 112, 0, 0, // '0'
0, 192, 64, 64, 64, 64, 64, 64, 0, 0, // '1'
0, 112, 136, 8, 16, 32, 64, 248, 0, 0, // '2'
0, 112, 136, 8, 48, 8, 136, 112, 0, 0, // '3'
0, 48, 80, 80, 144, 248, 16, 16, 0, 0, // '4'
0, 248, 128, 128, 240, 8, 136, 112, 0, 0, // '5'
0, 112, 136, 128, 240, 136, 136, 112, 0, 0, // '6'
0, 248, 8, 16, 16, 32, 32, 64, 0, 0, // '7'
0, 112, 136, 136, 112, 136, 136, 112, 0, 0, // '8'
0, 112, 136, 136, 120, 8, 136, 112, 0, 0, // '9'
0, 0, 0, 64, 0, 0, 0, 64, 0, 0, // ':'
0, 0, 0, 64, 0, 0, 0, 64, 64, 128, // ';'
0, 0, 32, 64, 128, 64, 32, 0, 0, 0, // '<'
0, 0, 0, 224, 0, 224, 0, 0, 0, 0, // '='
0, 0, 128, 64, 32, 64, 128, 0, 0, 0, // '>'
0, 112, 136, 8, 16, 32, 0, 32, 0, 0, // '?'
60, 66, 157, 165, 165, 173, 149, 66, 56, 0, // '@'
0, 112, 136, 136, 248, 136, 136, 136, 0, 0, // 'A'
0, 240, 136, 136, 240, 136, 136, 240, 0, 0, // 'B'
0, 112, 136, 128, 128, 128, 136, 112, 0, 0, // 'C'
0, 240, 136, 136, 136, 136, 136, 240, 0, 0, // 'D'
0, 248, 128, 128, 240, 128, 128, 248, 0, 0, // 'E'
0, 248, 128, 128, 240, 128, 128, 128, 0, 0, // 'F'
0, 112, 136, 128, 184, 136, 152, 104, 0, 0, // 'G'
0, 136, 136, 136, 248, 136, 136, 136, 0, 0, // 'H'
0, 128, 128, 128, 128, 128, 128, 128, 0, 0, // 'I'
0, 16, 16, 16, 16, 16, 144, 96, 0, 0, // 'J'
0, 136, 144, 160, 192, 160, 144, 136, 0, 0, // 'K'
0, 128, 128, 128, 128, 128, 128, 240, 0, 0, // 'L'
0, 130, 198, 170, 146, 130, 130, 130, 0, 0, // 'M'
0, 136, 200, 168, 168, 168, 152, 136, 0, 0, // 'N'
0, 112, 136, 136, 136, 136, 136, 112, 0, 0, // 'O'
0, 240, 136, 136, 240, 128, 128, 128, 0, 0, // 'P'
0, 112, 136, 136, 136, 136, 136, 112, 8, 0, // 'Q'
0, 240, 136, 136, 240, 160, 144, 136, 0, 0, // 'R'
0, 112, 136, 128, 112, 8, 136, 112, 0, 0, // 'S'
0, 248, 32, 32, 32, 32, 32, 32, 0, 0, // 'T'
0, 136, 136, 136, 136, 136, 136, 112, 0, 0, // 'U'
0, 136, 136, 80, 80, 80, 32, 32, 0, 0, // 'V'
0, 146, 146, 146, 146, 146, 146, 108, 0, 0, // 'W'
0, 136, 136, 80, 32, 80, 136, 136, 0, 0, // 'X'
0, 136, 136, 80, 32, 32, 32, 32, 0, 0, // 'Y'
0, 248, 8, 16, 32, 64, 128, 248, 0, 0, // 'Z'
224, 128, 128, 128, 128, 128, 128, 128, 224, 0, // '['
128, 128, 64, 64, 32, 16, 16, 8, 8, 0, // '\'
224, 32, 32, 32, 32, 32, 32, 32, 224, 0, // ']'
0, 64, 160, 0, 0, 0, 0, 0, 0, 0, // '^'
0, 0, 0, 0, 0, 0, 0, 224, 0, 0, // '_'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // '`'
0, 0, 0, 112, 144, 144, 176, 80, 0, 0, // 'a'
0, 128, 128, 160, 208, 144, 144, 224, 0, 0, // 'b'
0, 0, 0, 96, 144, 128, 144, 96, 0, 0, // 'c'
0, 16, 16, 112, 144, 144, 176, 80, 0, 0, // 'd'
0, 0, 0, 96, 144, 240, 128, 96, 0, 0, // 'e'
0, 96, 128, 192, 128, 128, 128, 128, 0, 0, // 'f'
0, 0, 0, 112, 144, 144, 176, 80, 16, 96, // 'g'
0, 128, 128, 160, 208, 144, 144, 144, 0, 0, // 'h'
0, 128, 0, 128, 128, 128, 128, 128, 0, 0, // 'i'
0, 128, 0, 128, 128, 128, 128, 128, 128, 0, // 'j'
0, 128, 128, 144, 160, 192, 160, 144, 0, 0, // 'k'
0, 128, 128, 128, 128, 128, 128, 128, 0, 0, // 'l'
0, 0, 0, 164, 218, 146, 146, 146, 0, 0, // 'm'
0, 0, 0, 160, 208, 144, 144, 144, 0, 0, // 'n'
0, 0, 0, 96, 144, 144, 144, 96, 0, 0, // 'o'
0, 0, 0, 160, 208, 144, 144, 224, 128, 128, // 'p'
0, 0, 0, 112, 144, 144, 176, 80, 16, 16, // 'q'
0, 0, 0, 160, 192, 128, 128, 128, 0, 0, // 'r'
0, 0, 0, 96, 128, 64, 32, 192, 0, 0, // 's'
0, 64, 64, 224, 64, 64, 64, 64, 0, 0, // 't'
0, 0, 0, 144, 144, 144, 176, 80, 0, 0, // 'u'
0, 0, 0, 144, 144, 144, 144, 96, 0, 0, // 'v'
0, 0, 0, 146, 146, 146, 146, 108, 0, 0, // 'w'
0, 0, 0, 144, 144, 96, 144, 144, 0, 0, // 'x'
0, 0, 0, 144, 144, 144, 176, 80, 16, 96, // 'y'
0, 0, 0, 224, 32, 64, 128, 224, 0, 0, // 'z'
32, 64, 64, 64, 128, 64, 64, 64, 32, 0, // '{'
128, 128, 128, 128, 128, 128, 128, 128, 128, 0, // '|'
128, 64, 64, 64, 32, 64, 64, 64, 128, 0, // '}'
0, 80, 160, 0, 0, 0, 0, 0, 0, 0, // '~'
];
const FONT_ADVANCES: [u8; FONT_GLYPH_COUNT] = [
3 /* */, 3 /* ! */, 4 /* " */, 6 /* # */, 6 /* $ */, 8 /* % */, 6 /* & */, 2 /* ' */,
4 /* ( */, 4 /* ) */, 6 /* * */, 6 /* + */, 3 /* , */, 4 /* - */, 3 /* . */, 5 /* / */,
6 /* 0 */, 3 /* 1 */, 6 /* 2 */, 6 /* 3 */, 6 /* 4 */, 6 /* 5 */, 6 /* 6 */, 6 /* 7 */,
6 /* 8 */, 6 /* 9 */, 3 /* : */, 3 /* ; */, 4 /* < */, 4 /* = */, 4 /* > */, 6 /* ? */,
8 /* @ */, 6 /* A */, 6 /* B */, 6 /* C */, 6 /* D */, 6 /* E */, 6 /* F */, 6 /* G */,
6 /* H */, 2 /* I */, 5 /* J */, 6 /* K */, 5 /* L */, 8 /* M */, 6 /* N */, 6 /* O */,
6 /* P */, 6 /* Q */, 6 /* R */, 6 /* S */, 6 /* T */, 6 /* U */, 6 /* V */, 8 /* W */,
6 /* X */, 6 /* Y */, 6 /* Z */, 4 /* [ */, 6 /* \ */, 4 /* ] */, 4 /* ^ */, 4 /* _ */,
3 /* ` */, 5 /* a */, 5 /* b */, 5 /* c */, 5 /* d */, 5 /* e */, 3 /* f */, 5 /* g */,
5 /* h */, 2 /* i */, 2 /* j */, 5 /* k */, 2 /* l */, 8 /* m */, 5 /* n */, 5 /* o */,
5 /* p */, 5 /* q */, 4 /* r */, 4 /* s */, 4 /* t */, 5 /* u */, 5 /* v */, 8 /* w */,
5 /* x */, 5 /* y */, 4 /* z */, 4 /* { */, 2 /* | */, 4 /* } */, 5 /* ~ */,
];
//
// Text output
//
enum GlyphColor {
White,
Black,
}
fn draw_glyph(pixels: &mut [u8],
surface_width: usize,
x: isize,
y: isize,
color: GlyphColor,
glyph_index: usize) {
let color_byte = match color {
GlyphColor::White => 0xff,
GlyphColor::Black => 0x00,
};
for y_index in 0..10 {
let row = FONT_GLYPHS[glyph_index * 10 + y_index as usize];
for x_index in 0..8 {
if ((row >> (7 - x_index) as usize) & 1) != 0 {
for channel in 0..3 {
let mut index = (y + y_index) * (surface_width as isize) * 3 + (x + x_index) * 3;
index += channel;
if index >= 0 && index < pixels.len() as isize {
pixels[index as usize] = color_byte;
}
}
}
}
}
}
pub fn draw_text(pixels: &mut [u8], surface_width: usize, mut x: isize, y: isize, string: &str) {
for i in 0..string.len() {
let glyph_index = (string.as_bytes()[i] - 32) as usize;
if glyph_index < FONT_ADVANCES.len() {
draw_glyph(pixels, surface_width, x, y + 1, GlyphColor::Black, glyph_index); // Shadow
draw_glyph(pixels, surface_width, x, y, GlyphColor::White, glyph_index); // Main
x += FONT_ADVANCES[glyph_index] as isize;
}
}
}
#[derive(PartialEq, Eq)]
enum StatusLineAnimation {
Idle,
Pausing(usize),
SlidingOut(usize),
}
use self::StatusLineAnimation::*;
struct StatusLineText {
string: String,
animation: StatusLineAnimation,
}
impl StatusLineText {
fn new() -> StatusLineText {
StatusLineText {
string: "".to_string(),
animation: Idle,
}
}
fn set(&mut self, string: String) {
self.string = string;
self.animation = Pausing(STATUS_LINE_PAUSE_DURATION);
}
fn tick(&mut self) {
self.animation = match self.animation {
Idle => Idle,
Pausing(0) => SlidingOut(STATUS_LINE_Y),
Pausing(time) => Pausing(time - 1),
SlidingOut(SCREEN_HEIGHT) => Idle,
SlidingOut(y) => SlidingOut(y + 1),
}
}
fn render(&self, pixels: &mut [u8]) {
if self.animation == Idle {
return;
}
let y = match self.animation {
Idle => panic!(),
SlidingOut(y) => y as isize,
Pausing(_) => STATUS_LINE_Y as isize,
};
draw_text(pixels, SCREEN_WIDTH, STATUS_LINE_X as isize, y, &self.string);
}
}
pub struct StatusLine {
text: StatusLineText,
}
impl StatusLine {
pub fn new() -> StatusLine {
StatusLine {
text: StatusLineText::new(),
}
}
pub fn set(&mut self, new_text: String) {
self.text.set(new_text);
}
pub fn render(&self, pixels: &mut [u8]) {
self.text.render(pixels);
}
}
//
// Screen scaling
//
#[derive(Copy, Clone)]
pub enum Scale {
Scale1x,
Scale2x,
Scale3x,
}
impl Scale {
fn factor(self) -> usize {
match self {
Scale::Scale1x => 1,
Scale::Scale2x => 2,
Scale::Scale3x => 3,
}
}
}
pub struct Gfx<'a> {
pub renderer: Box<Renderer<'a>>,
pub texture: Box<Texture>,
pub scale: Scale,
pub status_line: StatusLine,
}
impl<'a> Gfx<'a> {
pub fn new(scale: Scale) -> (Gfx<'a>, Sdl) {
// FIXME: Handle SDL better
let sdl = InitBuilder::new().video().audio().timer().events().unwrap();
let mut window_builder = sdl.window("sprocketnes",
(SCREEN_WIDTH as usize * scale.factor()) as u32,
(SCREEN_HEIGHT as usize * scale.factor()) as u32);
let window = window_builder.position_centered().build().unwrap();
let renderer = window.renderer().accelerated().present_vsync().build().unwrap();
let texture = renderer.create_texture(BGR24,
TextureAccess::Streaming,
(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32))
.unwrap();
(Gfx {
renderer: Box::new(renderer),
texture: Box::new(texture),
scale: scale,
status_line: StatusLine::new(),
}, sdl)
}
pub fn tick(&mut self) {
self.status_line.text.tick();
}
/// Copies the overlay onto the given screen and displays it to the SDL window.
pub fn composite(&mut self, ppu_screen: &mut [u8; SCREEN_SIZE]) {
self.status_line.render(ppu_screen);
self.blit(ppu_screen);
self.renderer.clear();
self.renderer.copy(&self.texture, None, None);
self.renderer.present();
}
/// Updates the window texture with new screen data.
fn blit(&mut self, ppu_screen: &[u8; SCREEN_SIZE]) {
self.texture.update(None, ppu_screen, SCREEN_WIDTH * 3).unwrap()
}
}