
// 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( uint8_t const* sprite )
{
	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*)
			((uint8_t*) tiles + map_chr_addr( index * bytes_per_tile ));
}

inline Nes_Ppu_Impl::cached_tile_t const& Nes_Ppu_Impl::get_bg_tile( int index )
{
	// 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*)
			((uint8_t*) 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*) ((uint8_t*) pixels + next_line);
	}
}

void Nes_Ppu_Rendering::clip_left( int count )
{
	ptrdiff_t next_line = scanline_row_bytes;
	uint8_t* 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;
	uint8_t* 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;
	uint8_t* 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;
	uint8_t* 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;
		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
		uint8_t const* nametable = get_nametable( addr );
		uint8_t const* nametable2 = get_nametable( addr ^ 0x400 );
		int count2 = addr & 31;
		int count = 32 - count2 - left_clip;
		//if ( pixel_x )
			count2++;
		
		uint8_t 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;
		uint8_t* 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
			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
	{
		uint8_t 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
		{
			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
			
			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;
	uint8_t 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 = ((unaligned_uint32_t*) bg) [0].val;
		unsigned long hit1 = ((unaligned_uint32_t*) bg) [1].val;
		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 ( ((uint8_t*) 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,
		uint8_t* pixels, long pitch, int mode )
{
	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() );
		
		if ( visible > 0 )
		{
			run_hblank( skip );
			draw_scanlines( start + skip, visible, impl->mini_offscreen, buffer_width, 3 );
		}
	}
}
