BizHawk/quicknes/nes_emu/Nes_Ppu_Rendering.cpp

497 lines
13 KiB
C++

// Nes_Emu 0.7.0. http://www.slack.net/~ant/
#include "Nes_Ppu_Rendering.h"
#include <string.h>
#include <stddef.h>
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
more details. You should have received a copy of the GNU Lesser General
Public License along with this module; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include "blargg_source.h"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
#ifdef __MWERKS__
static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants
#else
const unsigned zero = 0; // compile-time constant on other compilers
#endif
// Nes_Ppu_Impl
inline Nes_Ppu_Impl::cached_tile_t const&
Nes_Ppu_Impl::get_sprite_tile( byte const* sprite ) /*const*/
{
cached_tile_t* tiles = tile_cache;
if ( sprite [2] & 0x40 )
tiles = flipped_tiles;
int index = sprite_tile_index( sprite );
// use index directly, since cached tile is same size as native tile
BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile );
return *(Nes_Ppu_Impl::cached_tile_t*)
((byte*) tiles + map_chr_addr( index * bytes_per_tile ));
}
inline Nes_Ppu_Impl::cached_tile_t const& Nes_Ppu_Impl::get_bg_tile( int index ) /*const*/
{
// use index directly, since cached tile is same size as native tile
BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile );
return *(Nes_Ppu_Impl::cached_tile_t*)
((byte*) tile_cache + map_chr_addr( index * bytes_per_tile ));
}
// Fill
void Nes_Ppu_Rendering::fill_background( int count )
{
ptrdiff_t const next_line = scanline_row_bytes - image_width;
uint32_t* pixels = (uint32_t*) scanline_pixels;
unsigned long fill = palette_offset;
if ( (vram_addr & 0x3f00) == 0x3f00 )
{
// PPU uses current palette entry if addr is within palette ram
int color = vram_addr & 0x1f;
if ( !(color & 3) )
color &= 0x0f;
fill += color * 0x01010101;
}
for ( int n = count; n--; )
{
for ( int n = image_width / 16; n--; )
{
pixels [0] = fill;
pixels [1] = fill;
pixels [2] = fill;
pixels [3] = fill;
pixels += 4;
}
pixels = (uint32_t*) ((byte*) pixels + next_line);
}
}
void Nes_Ppu_Rendering::clip_left( int count )
{
ptrdiff_t next_line = scanline_row_bytes;
byte* p = scanline_pixels;
unsigned long fill = palette_offset;
for ( int n = count; n--; )
{
((uint32_t*) p) [0] = fill;
((uint32_t*) p) [1] = fill;
p += next_line;
}
}
void Nes_Ppu_Rendering::save_left( int count )
{
ptrdiff_t next_line = scanline_row_bytes;
byte* in = scanline_pixels;
uint32_t* out = impl->clip_buf;
for ( int n = count; n--; )
{
unsigned long in0 = ((uint32_t*) in) [0];
unsigned long in1 = ((uint32_t*) in) [1];
in += next_line;
out [0] = in0;
out [1] = in1;
out += 2;
}
}
void Nes_Ppu_Rendering::restore_left( int count )
{
ptrdiff_t next_line = scanline_row_bytes;
byte* out = scanline_pixels;
uint32_t* in = impl->clip_buf;
for ( int n = count; n--; )
{
unsigned long in0 = in [0];
unsigned long in1 = in [1];
in += 2;
((uint32_t*) out) [0] = in0;
((uint32_t*) out) [1] = in1;
out += next_line;
}
}
// Background
void Nes_Ppu_Rendering::draw_background_( int remain )
{
// Draws 'remain' background scanlines. Does not modify vram_addr.
int vram_addr = this->vram_addr & 0x7fff;
byte* row_pixels = scanline_pixels - pixel_x;
int left_clip = (w2001 >> 1 & 1) ^ 1;
row_pixels += left_clip * 8;
do
{
// scanlines until next row
int height = 8 - (vram_addr >> 12);
if ( height > remain )
height = remain;
// handle hscroll change before next scanline
int hscroll_changed = (vram_addr ^ vram_temp) & 0x41f;
int addr = vram_addr;
if ( hscroll_changed )
{
vram_addr ^= hscroll_changed;
height = 1; // hscroll will change after first line
}
remain -= height;
// increment address for next row
vram_addr += height << 12;
assert( vram_addr < 0x10000 );
if ( vram_addr & 0x8000 )
{
int y = (vram_addr + 0x20) & 0x3e0;
vram_addr &= 0x7fff & ~0x3e0;
if ( y == 30 * 0x20 )
y = 0x800; // toggle vertical nametable
vram_addr ^= y;
}
// nametable change usually occurs in middle of row
byte const* nametable = get_nametable( addr );
byte const* nametable2 = get_nametable( addr ^ 0x400 );
int count2 = addr & 31;
int count = 32 - count2 - left_clip;
// this conditional is commented out because of mmc2\4
// normally, the extra row of pixels is only fetched when pixel_ x is not 0, which makes sense
// but here, we need a correct fetch pattern to pick up 0xfd\0xfe tiles off the edge of the display
// this doesn't cause any problems with buffer overflow because the framebuffer we're rendering to is
// already guarded (width = 272)
// this doesn't give us a fully correct ppu fetch pattern, but it's close enough for punch out
//if ( pixel_x )
count2++;
byte const* attr_table = &nametable [0x3c0 | (addr >> 4 & 0x38)];
int bg_bank = (w2000 << 4) & 0x100;
addr += left_clip;
// output pixels
ptrdiff_t const row_bytes = scanline_row_bytes;
byte* pixels = row_pixels;
row_pixels += height * row_bytes;
unsigned long const mask = 0x03030303 + zero;
unsigned long const attrib_factor = 0x04040404 + zero;
if ( height == 8 )
{
// unclipped
assert( (addr >> 12) == 0 );
addr &= 0x03ff;
int const fine_y = 0;
int const clipped = false;
#include "Nes_Ppu_Bg.h"
}
else
{
// clipped
int const fine_y = addr >> 12;
addr &= 0x03ff;
height -= fine_y & 1;
int const clipped = true;
#include "Nes_Ppu_Bg.h"
}
}
while ( remain );
}
// Sprites
void Nes_Ppu_Rendering::draw_sprites_( int begin, int end )
{
// Draws sprites on scanlines begin through end - 1. Handles clipping.
int const sprite_height = this->sprite_height();
int end_minus_one = end - 1;
int begin_minus_one = begin - 1;
int index = 0;
do
{
byte const* sprite = &spr_ram [index];
index += 4;
// find if sprite is visible
int top_minus_one = sprite [0];
int visible = end_minus_one - top_minus_one;
if ( visible <= 0 )
continue; // off bottom
// quickly determine whether sprite is unclipped
int neg_vis = visible - sprite_height;
int neg_skip = top_minus_one - begin_minus_one;
if ( (neg_skip | neg_vis) >= 0 ) // neg_skip >= 0 && neg_vis >= 0
{
// unclipped
#ifndef NDEBUG
int top = sprite [0] + 1;
assert( (top + sprite_height) > begin && top < end );
assert( begin <= top && top + sprite_height <= end );
#endif
int const skip = 0;
int visible = sprite_height;
#define CLIPPED 0
#include "Nes_Ppu_Sprites.h"
}
else
{
// clipped
if ( neg_vis > 0 )
visible -= neg_vis;
if ( neg_skip > 0 )
neg_skip = 0;
visible += neg_skip;
if ( visible <= 0 )
continue; // off top
// visible and clipped
#ifndef NDEBUG
int top = sprite [0] + 1;
assert( (top + sprite_height) > begin && top < end );
assert( top < begin || top + sprite_height > end );
#endif
int skip = -neg_skip;
//dprintf( "begin: %d, end: %d, top: %d, skip: %d, visible: %d\n",
// begin, end, top_minus_one + 1, skip, visible );
#define CLIPPED 1
#include "Nes_Ppu_Sprites.h"
}
}
while ( index < 0x100 );
}
void Nes_Ppu_Rendering::check_sprite_hit( int begin, int end )
{
// Checks for sprite 0 hit on scanlines begin through end - 1.
// Updates sprite_hit_found. Background (but not sprites) must have
// already been rendered for the scanlines.
// clip
int top = spr_ram [0] + 1;
int skip = begin - top;
if ( skip < 0 )
skip = 0;
top += skip;
int visible = end - top;
if ( visible <= 0 )
return; // not visible
int height = sprite_height();
if ( visible >= height )
{
visible = height;
sprite_hit_found = -1; // signal that no more hit checking will take place
}
// pixels
ptrdiff_t next_row = this->scanline_row_bytes;
byte const* bg = this->scanline_pixels + spr_ram [3] + (top - begin) * next_row;
cache_t const* lines = get_sprite_tile( spr_ram );
// left edge clipping
int start_x = 0;
if ( spr_ram [3] < 8 && (w2001 & 0x01e) != 0x1e )
{
if ( spr_ram [3] == 0 )
return; // won't hit
start_x = 8 - spr_ram [3];
}
// vertical flip
int final = skip + visible;
if ( spr_ram [2] & 0x80 )
{
skip += height - 1;
final = skip - visible;
}
// check each line
unsigned long const mask = 0x01010101 + zero;
do
{
// get pixels for line
unsigned long line = lines [skip >> 1];
unsigned long hit0 = ((uint32_t*) bg) [0];
unsigned long hit1 = ((uint32_t*) bg) [1];
bg += next_row;
line >>= skip << 1 & 2;
line |= line >> 1;
// check for hits
hit0 = ((hit0 >> 1) | hit0) & (line >> 4);
hit1 = ((hit1 >> 1) | hit1) & line;
if ( (hit0 | hit1) & mask )
{
// write to memory to avoid endian issues
uint32_t quads [3];
quads [0] = hit0;
quads [1] = hit1;
// find which pixel hit
int x = start_x;
do
{
if ( ((byte*) quads) [x] & 1 )
{
x += spr_ram [3];
if ( x >= 255 )
break; // ignore right edge
if ( spr_ram [2] & 0x80 )
skip = height - 1 - skip; // vertical flip
int y = spr_ram [0] + 1 + skip;
sprite_hit_found = y * scanline_len + x;
return;
}
}
while ( x++ < 7 );
}
if ( skip > final )
skip -= 2;
skip++;
}
while ( skip != final );
}
// Draw scanlines
inline bool Nes_Ppu_Rendering::sprite_hit_possible( int scanline ) const
{
return !sprite_hit_found && spr_ram [0] <= scanline && (w2001 & 0x18) == 0x18;
}
void Nes_Ppu_Rendering::draw_scanlines( int start, int count,
byte* pixels, long pitch, int mode )
{
assert( start + count <= image_height );
assert( pixels );
scanline_pixels = pixels + image_left;
scanline_row_bytes = pitch;
int const obj_mask = 2;
int const bg_mask = 1;
int draw_mode = (w2001 >> 3) & 3;
int clip_mode = (~w2001 >> 1) & draw_mode;
if ( !(draw_mode & bg_mask) )
{
// no background
clip_mode |= bg_mask; // avoid unnecessary save/restore
if ( mode & bg_mask )
fill_background( count );
}
if ( start == 0 && mode & 1 )
memset( sprite_scanlines, max_sprites - sprite_limit, 240 );
if ( (draw_mode &= mode) )
{
// sprites and/or background are being rendered
if ( any_tiles_modified && chr_is_writable )
{
any_tiles_modified = false;
update_tiles( 0 );
}
if ( draw_mode & bg_mask )
{
//dprintf( "bg %3d-%3d\n", start, start + count - 1 );
draw_background_( count );
if ( clip_mode == bg_mask )
clip_left( count );
if ( sprite_hit_possible( start + count ) )
check_sprite_hit( start, start + count );
}
if ( draw_mode & obj_mask )
{
// when clipping just sprites, save left strip then restore after drawing them
if ( clip_mode == obj_mask )
save_left( count );
//dprintf( "obj %3d-%3d\n", start, start + count - 1 );
draw_sprites_( start, start + count );
if ( clip_mode == obj_mask )
restore_left( count );
if ( clip_mode == (obj_mask | bg_mask) )
clip_left( count );
}
}
scanline_pixels = NULL;
}
void Nes_Ppu_Rendering::draw_background( int start, int count )
{
// always capture palette at least once per frame
if ( (start + count >= 240 && !palette_size) || (w2001 & palette_changed) )
{
palette_changed = false;
capture_palette();
}
if ( host_pixels )
{
draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 1 );
}
else if ( sprite_hit_possible( start + count ) )
{
// not rendering, but still handle sprite hit using mini graphics buffer
int y = spr_ram [0] + 1;
int skip = min( count, max( y - start, 0 ) );
int visible = min( count - skip, sprite_height() );
assert( skip + visible <= count );
assert( visible <= mini_offscreen_height );
if ( visible > 0 )
{
run_hblank( skip );
draw_scanlines( start + skip, visible, impl->mini_offscreen, buffer_width, 3 );
}
}
}