﻿//////////////////////////////////////////////////////////////////////////
//                                                                      //
//      NES PPU core                                                    //
//                                                           Norix      //
//                                               written     2001/02/22 //
//                                               last modify ----/--/-- //
//////////////////////////////////////////////////////////////////////////
#define	WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#include "VirtuaNESres.h"

#include "typedef.h"
#include "macro.h"

#include "DebugOut.h"
#include "App.h"

#include "nes.h"
#include "mmu.h"
#include "cpu.h"
#include "ppu.h"
#include "rom.h"
#include "mapper.h"

BYTE	PPU::VSColorMap[5][64] = {
	{	0x35, 0xFF, 0x16, 0x22, 0x1C, 0xFF, 0xFF, 0x15,
		0xFF, 0x00, 0x27, 0x05, 0x04, 0x27, 0x08, 0x30,
		0x21, 0xFF, 0xFF, 0x29, 0x3C, 0xFF, 0x36, 0x12,
		0xFF, 0x2B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
		0xFF, 0x31, 0xFF, 0x2A, 0x2C, 0x0C, 0xFF, 0xFF,
		0xFF, 0x07, 0x34, 0x06, 0x13, 0xFF, 0x26, 0x0F,
		0xFF, 0x19, 0x10, 0x0A, 0xFF, 0xFF, 0xFF, 0x17,
		0xFF, 0x11, 0x09, 0xFF, 0xFF, 0x25, 0x18, 0xFF 
	},
	{	0xFF, 0x27, 0x18, 0xFF, 0x3A, 0x25, 0xFF, 0x31,
////		0x16, 0x13, 0x38, 0x34, 0x20, 0x23, 0xFF, 0x0B,
//		0x16, 0x13, 0x38, 0x34, 0x20, 0x23, 0xFF, 0x1A,
		0x16, 0x13, 0x38, 0x34, 0x20, 0x23, 0x31, 0x1A,
		0xFF, 0x21, 0x06, 0xFF, 0x1B, 0x29, 0xFF, 0x22,
		0xFF, 0x24, 0xFF, 0xFF, 0xFF, 0x08, 0xFF, 0x03,
		0xFF, 0x36, 0x26, 0x33, 0x11, 0xFF, 0x10, 0x02,
		0x14, 0xFF, 0x00, 0x09, 0x12, 0x0F, 0xFF, 0x30,
		0xFF, 0xFF, 0x2A, 0x17, 0x0C, 0x01, 0x15, 0x19,
		0xFF, 0x2C, 0x07, 0x37, 0xFF, 0x05, 0xFF, 0xFF 
	},
#if	1
	{	0xFF, 0xFF, 0xFF, 0x10, 0x1A, 0x30, 0x31, 0x09,
		0x01, 0x0F, 0x36, 0x08, 0x15, 0xFF, 0xFF, 0xF0,
		0x22, 0x1C, 0xFF, 0x12, 0x19, 0x18, 0x17, 0xFF,
		0x00, 0xFF, 0xFF, 0x02, 0x16, 0x06, 0xFF, 0x35,
		0x23, 0xFF, 0x8B, 0xF7, 0xFF, 0x27, 0x26, 0x20,
		0x29, 0xFF, 0x21, 0x24, 0x11, 0xFF, 0xEF, 0xFF,
		0x2C, 0xFF, 0xFF, 0xFF, 0x07, 0xF9, 0x28, 0xFF,
		0x0A, 0xFF, 0x32, 0x37, 0x13, 0xFF, 0xFF, 0x0C 
	},
#else
	{	0xFF, 0xFF, 0xFF, 0x10, 0x0B, 0x30, 0x31, 0x09,	// 00-07
		0x01, 0x0F, 0x36, 0x08, 0x15, 0xFF, 0xFF, 0x3C,	// 08-0F
		0x22, 0x1C, 0xFF, 0x12, 0x19, 0x18, 0x17, 0x1B,	// 10-17
		0x00, 0xFF, 0xFF, 0x02, 0x16, 0x06, 0xFF, 0x35,	// 18-1F
		0x23, 0xFF, 0x8B, 0x3C, 0xFF, 0x27, 0x26, 0x20,	// 20-27
		0x29, 0x04, 0x21, 0x24, 0x11, 0xFF, 0xEF, 0xFF,	// 28-2F
		0x2C, 0xFF, 0xFF, 0xFF, 0x07, 0x39, 0x28, 0xFF,	// 30-37
		0x0A, 0xFF, 0x32, 0x38, 0x13, 0x3B, 0xFF, 0x0C 	// 38-3F
	},
#endif
#if	0
	{	0x18, 0xFF, 0x1C, 0x89, 0x0F, 0xFF, 0x01, 0x17,
		0x10, 0x0F, 0x2A, 0xFF, 0x36, 0x37, 0x1A, 0xFF,
		0x25, 0xFF, 0x12, 0xFF, 0x0F, 0xFF, 0xFF, 0x26,
		0xFF, 0xFF, 0x22, 0xFF, 0xFF, 0x0F, 0x3A, 0x21,
		0x05, 0x0A, 0x07, 0xC2, 0x13, 0xFF, 0x00, 0x15,
		0x0C, 0xFF, 0x11, 0xFF, 0xFF, 0x38, 0xFF, 0xFF,
		0xFF, 0xFF, 0x08, 0x45, 0xFF, 0xFF, 0x30, 0x3C,
		0x0F, 0x27, 0xFF, 0x60, 0x29, 0xFF, 0x30, 0x09 
	},
#else
	{	0x18, 0xFF, 0x1C, 0x89, 0x0F, 0xFF, 0x01, 0x17,	// 00-07
		0x10, 0x0F, 0x2A, 0xFF, 0x36, 0x37, 0x1A, 0xFF,	// 08-0F
		0x25, 0xFF, 0x12, 0xFF, 0x0F, 0xFF, 0xFF, 0x26,	// 10-17
		0xFF, 0xFF, 0x22, 0xFF, 0xFF, 0x0F, 0x3A, 0x21,	// 18-1F
		0x05, 0x0A, 0x07, 0xC2, 0x13, 0xFF, 0x00, 0x15,	// 20-27
		0x0C, 0xFF, 0x11, 0xFF, 0xFF, 0x38, 0xFF, 0xFF,	// 28-2F
		0xFF, 0xFF, 0x08, 0x16, 0xFF, 0xFF, 0x30, 0x3C,	// 30-37
		0x0F, 0x27, 0xFF, 0x60, 0x29, 0xFF, 0x30, 0x09 	// 38-3F
	},
#endif
	{
	// Super Xevious/Gradius
		0x35, 0xFF, 0x16, 0x22, 0x1C, 0x09, 0xFF, 0x15,	// 00-07
		0x20, 0x00, 0x27, 0x05, 0x04, 0x28, 0x08, 0x30,	// 08-0F
		0x21, 0xFF, 0xFF, 0x29, 0x3C, 0xFF, 0x36, 0x12,	// 10-17
		0xFF, 0x2B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,	// 18-1F
		0xFF, 0x31, 0xFF, 0x2A, 0x2C, 0x0C, 0x1B, 0xFF,	// 20-27
		0xFF, 0x07, 0x34, 0x06, 0xFF, 0x25, 0x26, 0x0F,	// 28-2F
		0xFF, 0x19, 0x10, 0x0A, 0xFF, 0xFF, 0xFF, 0x17,	// 30-37
		0xFF, 0x11, 0x1A, 0xFF, 0x38, 0xFF, 0x18, 0x3A,	// 38-3F
	}
};

PPU::PPU( NES* parent ) : nes(parent)
{
	lpScreen = NULL;
	lpColormode = NULL;

	bVSMode = FALSE;
	nVSColorMap = -1;
	VSSecurityData = 0;

	// 左右反転マスクテーブル
	for( INT i = 0; i < 256; i++ ) {
		BYTE	m = 0x80;
		BYTE	c = 0;
		for( INT j = 0; j < 8; j++ ) {
			if( i&(1<<j) ) {
				c |= m;
			}
			m >>= 1;
		}
		Bit2Rev[i] = c;
	}
}

PPU::~PPU()
{
}

void	PPU::Reset()
{
	bExtLatch = FALSE;
	bChrLatch = FALSE;
	bExtNameTable = FALSE;
	bExtMono = FALSE;

	PPUREG[0] = PPUREG[1] = 0;

	PPU56Toggle = 0;

	PPU7_Temp = 0xFF;	// VS Excitebikeでおかしくなる($2006を読みに行くバグがある)
//	PPU7_Temp = 0;

	loopy_v = loopy_t = 0;
	loopy_x = loopy_y = 0;
	loopy_shift = 0;

	if( lpScreen )
		::memset( lpScreen, 0x3F, SCREEN_WIDTH*SCREEN_HEIGHT*sizeof(BYTE) );

	if( lpColormode )
		::memset( lpColormode, 0, SCREEN_HEIGHT*sizeof(BYTE) );
}

BYTE	PPU::Read( WORD addr )
{
BYTE	data = 0x00;

	switch( addr ) {
		// Write only Register
		case	0x2000: // PPU Control Register #1(W)
		case	0x2001: // PPU Control Register #2(W)
		case	0x2003: // SPR-RAM Address Register(W)
		case	0x2005: // PPU Scroll Register(W2)
		case	0x2006: // VRAM Address Register(W2)
			data = PPU7_Temp;	// 多分
			break;
		// Read/Write Register
		case	0x2002: // PPU Status Register(R)
//DEBUGOUT( "2002 RD L:%3d C:%8d\n", ScanlineNo, nes->cpu->GetTotalCycles() );
			data = PPUREG[2] | VSSecurityData;
			PPU56Toggle = 0;
			PPUREG[2] &= ~PPU_VBLANK_FLAG;
			break;
		case	0x2004: // SPR_RAM I/O Register(RW)
			data = SPRAM[ PPUREG[3]++ ];
			break;
		case	0x2007: // VRAM I/O Register(RW)
			WORD	addr = loopy_v & 0x3FFF;
			data = PPU7_Temp;
			if( PPUREG[0] & PPU_INC32_BIT ) loopy_v+=32;
			else				loopy_v++;
			if( addr >= 0x3000 ) {
				if( addr >= 0x3F00 ) {
//					data &= 0x3F;
					if( !(addr&0x0010) ) {
						return	BGPAL[addr&0x000F];
					} else {
						return	SPPAL[addr&0x000F];
					}
				}
				addr &= 0xEFFF;
			}
			PPU7_Temp = PPU_MEM_BANK[addr>>10][addr&0x03FF];
	}

	return	data;
}

void	PPU::Write( WORD addr, BYTE data )
{
	if( bVSMode && VSSecurityData ) {
		if( addr == 0x2000 ) {
			addr = 0x2001;
		} else if( addr == 0x2001 ){
			addr = 0x2000;
		}
	}

	switch( addr ) {
		// Read only Register
		case	0x2002: // PPU Status register(R)
			break;
		// Write Register
		case	0x2000: // PPU Control Register #1(W)
			// NameTable select
			// t:0000110000000000=d:00000011
			loopy_t = (loopy_t & 0xF3FF)|(((WORD)data & 0x03)<<10);

			if( (data & 0x80) && !(PPUREG[0] & 0x80) && (PPUREG[2] & 0x80) ) {
				nes->cpu->NMI();	// hmm..
			}
//DEBUGOUT( "W2000 %02X O:%02X S:%02X L:%3d C:%8d\n", data, PPUREG[0], PPUREG[2], ScanlineNo, nes->cpu->GetTotalCycles() );

			PPUREG[0] = data;
			break;
		case	0x2001: // PPU Control Register #2(W)
//DEBUGOUT( "W2001 %02X L:%3d C:%8d\n", data, ScanlineNo, nes->cpu->GetTotalCycles() );
			PPUREG[1] = data;
			break;
		case	0x2003: // SPR-RAM Address Register(W)
			PPUREG[3] = data;
			break;
		case	0x2004: // SPR_RAM I/O Register(RW)
			SPRAM[ PPUREG[3]++ ] = data;
			break;

		case	0x2005: // PPU Scroll Register(W2)
//DEBUGOUT( "SCR WRT L:%3d C:%8d\n", ScanlineNo, nes->cpu->GetTotalCycles() );
			if( !PPU56Toggle ) {
			// First write
				// tile X t:0000000000011111=d:11111000
				loopy_t = (loopy_t & 0xFFE0)|(((WORD)data)>>3);
				// scroll offset X x=d:00000111
				loopy_x = data & 0x07;
			} else {
			// Second write
				// tile Y t:0000001111100000=d:11111000
				loopy_t = (loopy_t & 0xFC1F)|((((WORD)data) & 0xF8)<<2);
				// scroll offset Y t:0111000000000000=d:00000111
				loopy_t = (loopy_t & 0x8FFF)|((((WORD)data) & 0x07)<<12);
			}
			PPU56Toggle = !PPU56Toggle;
			break;
		case	0x2006: // VRAM Address Register(W2)
			if( !PPU56Toggle ) {
			// First write
				// t:0011111100000000=d:00111111
				// t:1100000000000000=0
				loopy_t = (loopy_t & 0x00FF)|((((WORD)data) & 0x3F)<<8);
			} else {
			// Second write
				// t:0000000011111111=d:11111111
				loopy_t = (loopy_t & 0xFF00)|(WORD)data;
				// v=t
				loopy_v = loopy_t;

				nes->mapper->PPU_Latch( loopy_v );
			}
			PPU56Toggle = !PPU56Toggle;
			break;

		case	0x2007: // VRAM I/O Register(RW)
			WORD	vaddr = loopy_v & 0x3FFF;
			if( PPUREG[0] & PPU_INC32_BIT ) loopy_v+=32;
			else				loopy_v++;

			if( vaddr >= 0x3000 ) {
				if( vaddr >= 0x3F00 ) {
					data &= 0x3F;
					if( bVSMode && nVSColorMap != -1 ) {
						BYTE	temp = VSColorMap[nVSColorMap][data];
						if( temp != 0xFF ) {
							data = temp & 0x3F;
						}
					}

					if( !(vaddr&0x000F) ) {
						BGPAL[0] = SPPAL[0] = data;
					} else if( !(vaddr&0x0010) ) {
						BGPAL[vaddr&0x000F] = data;
					} else {
						SPPAL[vaddr&0x000F] = data;
					}
					BGPAL[0x04] = BGPAL[0x08] = BGPAL[0x0C] = BGPAL[0x00];
					SPPAL[0x00] = SPPAL[0x04] = SPPAL[0x08] = SPPAL[0x0C] = BGPAL[0x00];
					return;
				}
				vaddr &= 0xEFFF;
			}
			if( PPU_MEM_TYPE[vaddr>>10] != BANKTYPE_VROM ) {
				PPU_MEM_BANK[vaddr>>10][vaddr&0x03FF] = data;
			}
			break;
	}
}

void	PPU::DMA( BYTE data )
{
WORD	addr = data<<8;

	for( INT i = 0; i < 256; i++ ) {
		SPRAM[i] = nes->Read( addr+i );
	}
}

void	PPU::VBlankStart()
{
	PPUREG[2] |= PPU_VBLANK_FLAG;
//	PPUREG[2] |= PPU_SPHIT_FLAG;	// VBlank突入時に必ずON？
}

void	PPU::VBlankEnd()
{
	PPUREG[2] &= ~PPU_VBLANK_FLAG;
	// VBlank脱出時にクリアされる
	// エキサイトバイクで重要
	PPUREG[2] &= ~PPU_SPHIT_FLAG;
}

void	PPU::FrameStart()
{
	if( PPUREG[1] & (PPU_SPDISP_BIT|PPU_BGDISP_BIT) ) {
		loopy_v = loopy_t;
		loopy_shift = loopy_x;
		loopy_y = (loopy_v&0x7000)>>12;
	}

	if( lpScreen ) {
		::memset( lpScreen, 0x3F, SCREEN_WIDTH*sizeof(BYTE) );
	}
	if( lpColormode ) {
		lpColormode[0] = 0;
	}
}

void	PPU::FrameEnd()
{
}

void	PPU::SetRenderScanline( INT scanline )
{
	ScanlineNo = scanline;
	if( scanline < 240 ) {
		lpScanline = lpScreen+SCREEN_WIDTH*scanline;
	}
}

void	PPU::ScanlineStart()
{
	if( PPUREG[1] & (PPU_BGDISP_BIT|PPU_SPDISP_BIT) ) {
		loopy_v = (loopy_v & 0xFBE0)|(loopy_t & 0x041F);
		loopy_shift = loopy_x;
		loopy_y = (loopy_v&0x7000)>>12;
		nes->mapper->PPU_Latch( 0x2000 + (loopy_v & 0x0FFF) );
	}
}

void	PPU::ScanlineNext()
{
	if( PPUREG[1] & (PPU_BGDISP_BIT|PPU_SPDISP_BIT) ) {
		if( (loopy_v & 0x7000) == 0x7000 ) {
			loopy_v &= 0x8FFF;
			if( (loopy_v & 0x03E0) == 0x03A0 ) {
				loopy_v ^= 0x0800;
				loopy_v &= 0xFC1F;
			} else {
				if( (loopy_v & 0x03E0) == 0x03E0 ) {
					loopy_v &= 0xFC1F;
				} else {
					loopy_v += 0x0020;
				}
			}
		} else {
			loopy_v += 0x1000;
		}
		loopy_y = (loopy_v&0x7000)>>12;
	}
}

void	PPU::Scanline( INT scanline, BOOL bMax, BOOL bLeftClip )
{
BYTE	BGwrite[33+1];
BYTE	BGmono[33+1];

	ZEROMEMORY( BGwrite, sizeof(BGwrite) );
	ZEROMEMORY( BGmono, sizeof(BGmono) );

	// Linecolor mode
	lpColormode[scanline] = ((PPUREG[1]&PPU_BGCOLOR_BIT)>>5)|((PPUREG[1]&PPU_COLORMODE_BIT)<<7);

	// Render BG
	if( !(PPUREG[1]&PPU_BGDISP_BIT) ) {
		::memset( lpScanline, BGPAL[0], SCREEN_WIDTH );
		if( nes->GetRenderMethod() == NES::TILE_RENDER ) {
			nes->EmulationCPU( FETCH_CYCLES*4*32 );
		}
	} else {
		if( nes->GetRenderMethod() != NES::TILE_RENDER ) {
			if( !bExtLatch ) {
				// Without Extension Latch
				LPBYTE	pScn = lpScanline+(8-loopy_shift);
				LPBYTE	pBGw = BGwrite;

				INT	tileofs = (PPUREG[0]&PPU_BGTBL_BIT)<<8;
				INT	ntbladr = 0x2000+(loopy_v&0x0FFF);
				INT	attradr = 0x23C0+(loopy_v&0x0C00)+((loopy_v&0x0380)>>4);
				INT	ntbl_x  = ntbladr&0x001F;
				INT	attrsft = (ntbladr&0x0040)>>4;
				LPBYTE	pNTBL = PPU_MEM_BANK[ntbladr>>10];

				INT	tileadr;
				INT	cache_tile = 0xFFFF0000;
				BYTE	cache_attr = 0xFF;

				BYTE	chr_h, chr_l, attr;

				attradr &= 0x3FF;

				for( INT i = 0; i < 33; i++ ) {
					tileadr = tileofs+pNTBL[ntbladr&0x03FF]*0x10+loopy_y;
					attr = ((pNTBL[attradr+(ntbl_x>>2)]>>((ntbl_x&2)+attrsft))&3)<<2;

					if( cache_tile == tileadr && cache_attr == attr ) {
						*(LPDWORD)(pScn+0) = *(LPDWORD)(pScn-8);
						*(LPDWORD)(pScn+4) = *(LPDWORD)(pScn-4);
						*(pBGw+0) = *(pBGw-1);
					} else {
						cache_tile = tileadr;
						cache_attr = attr;
						chr_l = PPU_MEM_BANK[tileadr>>10][ tileadr&0x03FF   ];
						chr_h = PPU_MEM_BANK[tileadr>>10][(tileadr&0x03FF)+8];
						*pBGw = chr_h|chr_l;

						LPBYTE	pBGPAL = &BGPAL[attr];
						{
						register INT	c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA);
						register INT	c2 = (chr_l&0x55)|((chr_h<<1)&0xAA);
						pScn[0] = pBGPAL[(c1>>6)];
						pScn[4] = pBGPAL[(c1>>2)&3];
						pScn[1] = pBGPAL[(c2>>6)];
						pScn[5] = pBGPAL[(c2>>2)&3];
						pScn[2] = pBGPAL[(c1>>4)&3];
						pScn[6] = pBGPAL[c1&3];
						pScn[3] = pBGPAL[(c2>>4)&3];
						pScn[7] = pBGPAL[c2&3];
						}
					}
					pScn+=8;
					pBGw++;

					// Character latch(For MMC2/MMC4)
					if( bChrLatch ) {
						nes->mapper->PPU_ChrLatch( tileadr );
					}

					if( ++ntbl_x == 32 ) {
						ntbl_x = 0;
						ntbladr ^= 0x41F;
						attradr = 0x03C0+((ntbladr&0x0380)>>4);
						pNTBL = PPU_MEM_BANK[ntbladr>>10];
					} else {
						ntbladr++;
					}
				}
			} else {
				// With Extension Latch(For MMC5)
				LPBYTE	pScn = lpScanline+(8-loopy_shift);
				LPBYTE	pBGw = BGwrite;

				INT	ntbladr = 0x2000+(loopy_v&0x0FFF);
				INT	ntbl_x  = ntbladr & 0x1F;

				INT	cache_tile = 0xFFFF0000;
				BYTE	cache_attr = 0xFF;

				BYTE	chr_h, chr_l, attr, exattr;

				for( INT i = 0; i < 33; i++ ) {
					nes->mapper->PPU_ExtLatchX( i );
					nes->mapper->PPU_ExtLatch( ntbladr, chr_l, chr_h, exattr );
					attr = exattr&0x0C;

					if( cache_tile != (((INT)chr_h<<8)+(INT)chr_l) || cache_attr != attr ) {
						cache_tile = (((INT)chr_h<<8)+(INT)chr_l);
						cache_attr = attr;
						*pBGw = chr_h|chr_l;

						LPBYTE	pBGPAL = &BGPAL[attr];
						{
						register INT	c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA);
						register INT	c2 = (chr_l&0x55)|((chr_h<<1)&0xAA);
						pScn[0] = pBGPAL[(c1>>6)];
						pScn[4] = pBGPAL[(c1>>2)&3];
						pScn[1] = pBGPAL[(c2>>6)];
						pScn[5] = pBGPAL[(c2>>2)&3];
						pScn[2] = pBGPAL[(c1>>4)&3];
						pScn[6] = pBGPAL[c1&3];
						pScn[3] = pBGPAL[(c2>>4)&3];
						pScn[7] = pBGPAL[c2&3];
						}
					} else {
						*(DWORD*)(pScn+0) = *(DWORD*)(pScn-8);
						*(DWORD*)(pScn+4) = *(DWORD*)(pScn-4);
						*(pBGw+0) = *(pBGw-1);
					}
					pScn+=8;
					pBGw++;

					if( ++ntbl_x == 32 ) {
						ntbl_x = 0;
						ntbladr ^= 0x41F;
					} else {
						ntbladr++;
					}
				}
			}
		} else {
			if( !bExtLatch ) {
				// Without Extension Latch
				if( !bExtNameTable ) {
					LPBYTE	pScn = lpScanline+(8-loopy_shift);
					LPBYTE	pBGw = BGwrite;

					INT	ntbladr = 0x2000+(loopy_v&0x0FFF);
					INT	attradr = 0x03C0+((loopy_v&0x0380)>>4);
					INT	ntbl_x  = ntbladr&0x001F;
					INT	attrsft = (ntbladr&0x0040)>>4;
					LPBYTE	pNTBL = PPU_MEM_BANK[ntbladr>>10];

					INT	tileadr;
					INT	cache_tile = 0xFFFF0000;
					BYTE	cache_attr = 0xFF;
					BYTE	cache_mono = 0x00;

					BYTE	chr_h, chr_l, attr;

					for( INT i = 0; i < 33; i++ ) {
						tileadr = ((PPUREG[0]&PPU_BGTBL_BIT)<<8)+pNTBL[ntbladr&0x03FF]*0x10+loopy_y;

						if( i != 0 ) {
							nes->EmulationCPU( FETCH_CYCLES*4 );
						}

						attr = ((pNTBL[attradr+(ntbl_x>>2)]>>((ntbl_x&2)+attrsft))&3)<<2;

						if( cache_tile != tileadr || cache_attr != attr ) {
							cache_tile = tileadr;
							cache_attr = attr;

							chr_l = PPU_MEM_BANK[tileadr>>10][ tileadr&0x03FF   ];
							chr_h = PPU_MEM_BANK[tileadr>>10][(tileadr&0x03FF)+8];
							*pBGw = chr_l|chr_h;

							LPBYTE	pBGPAL = &BGPAL[attr];
							{
							register INT	c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA);
							register INT	c2 = (chr_l&0x55)|((chr_h<<1)&0xAA);
							pScn[0] = pBGPAL[(c1>>6)];
							pScn[4] = pBGPAL[(c1>>2)&3];
							pScn[1] = pBGPAL[(c2>>6)];
							pScn[5] = pBGPAL[(c2>>2)&3];
							pScn[2] = pBGPAL[(c1>>4)&3];
							pScn[6] = pBGPAL[c1&3];
							pScn[3] = pBGPAL[(c2>>4)&3];
							pScn[7] = pBGPAL[c2&3];
							}
						} else {
							*(DWORD*)(pScn+0) = *(DWORD*)(pScn-8);
							*(DWORD*)(pScn+4) = *(DWORD*)(pScn-4);
							*(pBGw+0) = *(pBGw-1);
						}
						pScn+=8;
						pBGw++;

						// Character latch(For MMC2/MMC4)
						if( bChrLatch ) {
							nes->mapper->PPU_ChrLatch( tileadr );
						}

						if( ++ntbl_x == 32 ) {
							ntbl_x = 0;
							ntbladr ^= 0x41F;
							attradr = 0x03C0+((ntbladr&0x0380)>>4);
							pNTBL = PPU_MEM_BANK[ntbladr>>10];
						} else {
							ntbladr++;
						}
					}
				} else {
					LPBYTE	pScn = lpScanline+(8-loopy_shift);
					LPBYTE	pBGw = BGwrite;

					INT	ntbladr;
					INT	tileadr;
					INT	cache_tile = 0xFFFF0000;
					BYTE	cache_attr = 0xFF;
					BYTE	cache_mono = 0x00;

					BYTE	chr_h, chr_l, attr;

					WORD	loopy_v_tmp = loopy_v;

					for( INT i = 0; i < 33; i++ ) {
						if( i != 0 ) {
							nes->EmulationCPU( FETCH_CYCLES*4 );
						}

						ntbladr = 0x2000+(loopy_v&0x0FFF);
						tileadr = ((PPUREG[0]&PPU_BGTBL_BIT)<<8)+PPU_MEM_BANK[ntbladr>>10][ntbladr&0x03FF]*0x10+((loopy_v&0x7000)>>12);
						attr = ((PPU_MEM_BANK[ntbladr>>10][0x03C0+((ntbladr&0x0380)>>4)+((ntbladr&0x001C)>>2)]>>(((ntbladr&0x40)>>4)+(ntbladr&0x02)))&3)<<2;

						if( cache_tile != tileadr || cache_attr != attr ) {
							cache_tile = tileadr;
							cache_attr = attr;

							chr_l = PPU_MEM_BANK[tileadr>>10][ tileadr&0x03FF   ];
							chr_h = PPU_MEM_BANK[tileadr>>10][(tileadr&0x03FF)+8];
							*pBGw = chr_l|chr_h;

							LPBYTE	pBGPAL = &BGPAL[attr];
							{
							register INT	c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA);
							register INT	c2 = (chr_l&0x55)|((chr_h<<1)&0xAA);
							pScn[0] = pBGPAL[(c1>>6)];
							pScn[4] = pBGPAL[(c1>>2)&3];
							pScn[1] = pBGPAL[(c2>>6)];
							pScn[5] = pBGPAL[(c2>>2)&3];
							pScn[2] = pBGPAL[(c1>>4)&3];
							pScn[6] = pBGPAL[c1&3];
							pScn[3] = pBGPAL[(c2>>4)&3];
							pScn[7] = pBGPAL[c2&3];
							}
						} else {
							*(DWORD*)(pScn+0) = *(DWORD*)(pScn-8);
							*(DWORD*)(pScn+4) = *(DWORD*)(pScn-4);
							*(pBGw+0) = *(pBGw-1);
						}
						pScn+=8;
						pBGw++;

						// Character latch(For MMC2/MMC4)
						if( bChrLatch ) {
							nes->mapper->PPU_ChrLatch( tileadr );
						}

						if( (loopy_v & 0x1F) == 0x1F ) {
							loopy_v ^= 0x041F;
						} else {
							loopy_v++;
						}
					}
					loopy_v = loopy_v_tmp;
				}
			} else {
				// With Extension Latch(For MMC5)
				LPBYTE	pScn = lpScanline+(8-loopy_shift);
				LPBYTE	pBGw = BGwrite;

				INT	ntbladr = 0x2000+(loopy_v&0x0FFF);
				INT	ntbl_x  = ntbladr & 0x1F;

				INT	cache_tile = 0xFFFF0000;
				BYTE	cache_attr = 0xFF;

				BYTE	chr_h, chr_l, attr, exattr;

				for( INT i = 0; i < 33; i++ ) {
					if( i != 0 ) {
						nes->EmulationCPU( FETCH_CYCLES*4 );
					}
					nes->mapper->PPU_ExtLatchX( i );
					nes->mapper->PPU_ExtLatch( ntbladr, chr_l, chr_h, exattr );
					attr = exattr&0x0C;

					if( cache_tile != (((INT)chr_h<<8)+(INT)chr_l) || cache_attr != attr ) {
						cache_tile = (((INT)chr_h<<8)+(INT)chr_l);
						cache_attr = attr;
						*pBGw = chr_l|chr_h;

						LPBYTE	pBGPAL   = &BGPAL[attr];
						{
						register INT	c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA);
						register INT	c2 = (chr_l&0x55)|((chr_h<<1)&0xAA);
						pScn[0] = pBGPAL[(c1>>6)];
						pScn[4] = pBGPAL[(c1>>2)&3];
						pScn[1] = pBGPAL[(c2>>6)];
						pScn[5] = pBGPAL[(c2>>2)&3];
						pScn[2] = pBGPAL[(c1>>4)&3];
						pScn[6] = pBGPAL[c1&3];
						pScn[3] = pBGPAL[(c2>>4)&3];
						pScn[7] = pBGPAL[c2&3];
						}
					} else {
						*(DWORD*)(pScn+0) = *(DWORD*)(pScn-8);
						*(DWORD*)(pScn+4) = *(DWORD*)(pScn-4);
						*(pBGw+0) = *(pBGw-1);
					}
					pScn+=8;
					pBGw++;

					if( ++ntbl_x == 32 ) {
						ntbl_x = 0;
						ntbladr ^= 0x41F;
					} else {
						ntbladr++;
					}
				}
			}
		}
		if( !(PPUREG[1]&PPU_BGCLIP_BIT) && bLeftClip ) {
			LPBYTE	pScn = lpScanline+8;
			for( INT i = 0; i < 8; i++ ) {
				pScn[i] = BGPAL[0];
			}
		}
	}

	// Render sprites
	PPUREG[2] &= ~PPU_SPMAX_FLAG;

	// 表示期間外であればキャンセル
	if( scanline > 239 )
		return;

	if( !(PPUREG[1]&PPU_SPDISP_BIT) ) {
		return;
	}

	BYTE	SPwrite[33+1];
	INT	spmax;
	INT	spraddr, sp_y, sp_h;
	BYTE	chr_h, chr_l;
	LPSPRITE sp;

	LPBYTE	pBGw = BGwrite;
	LPBYTE	pSPw = SPwrite;
	LPBYTE	pBit2Rev = Bit2Rev;

	ZEROMEMORY( SPwrite, sizeof(SPwrite) );

	spmax = 0;
	sp = (LPSPRITE)SPRAM;
	sp_h = (PPUREG[0]&PPU_SP16_BIT)?15:7;

	// Left clip
	if( !(PPUREG[1]&PPU_SPCLIP_BIT) && bLeftClip ) {
		SPwrite[0] = 0xFF;
	}

	for( INT i = 0; i < 64; i++, sp++ ) {
		sp_y = scanline - (sp->y+1);
		// スキャンライン内にSPRITEが存在するかをチェック
		if( sp_y != (sp_y & sp_h) )
			continue;

		if( !(PPUREG[0]&PPU_SP16_BIT) ) {
		// 8x8 Sprite
			spraddr = (((INT)PPUREG[0]&PPU_SPTBL_BIT)<<9)+((INT)sp->tile<<4);
			if( !(sp->attr&SP_VMIRROR_BIT) )
				spraddr += sp_y;
			else
				spraddr += 7-sp_y;
		} else {
		// 8x16 Sprite
			spraddr = (((INT)sp->tile&1)<<12)+(((INT)sp->tile&0xFE)<<4);
			if( !(sp->attr&SP_VMIRROR_BIT) )
				spraddr += ((sp_y&8)<<1)+(sp_y&7);
			else
				spraddr += ((~sp_y&8)<<1)+(7-(sp_y&7));
		}
		// Character pattern
		chr_l = PPU_MEM_BANK[spraddr>>10][ spraddr&0x3FF   ];
		chr_h = PPU_MEM_BANK[spraddr>>10][(spraddr&0x3FF)+8];

		// Character latch(For MMC2/MMC4)
		if( bChrLatch ) {
			nes->mapper->PPU_ChrLatch( spraddr );
		}

		// pattern mask
		if( sp->attr&SP_HMIRROR_BIT ) {
			chr_l = pBit2Rev[chr_l];
			chr_h = pBit2Rev[chr_h];
		}
		BYTE	SPpat = chr_l|chr_h;

		// Sprite hitcheck
		if( i == 0 && !(PPUREG[2]&PPU_SPHIT_FLAG) ) {
			INT	BGpos = ((sp->x&0xF8)+((loopy_shift+(sp->x&7))&8))>>3;
			INT	BGsft = 8-((loopy_shift+sp->x)&7);
			BYTE	BGmsk = (((WORD)pBGw[BGpos+0]<<8)|(WORD)pBGw[BGpos+1])>>BGsft;

			if( SPpat & BGmsk ) {
				PPUREG[2] |= PPU_SPHIT_FLAG;
			}
		}

		// Sprite mask
		INT	SPpos = sp->x/8;
		INT	SPsft = 8-(sp->x&7);
		BYTE	SPmsk = (((WORD)pSPw[SPpos+0]<<8)|(WORD)pSPw[SPpos+1])>>SPsft;
		WORD	SPwrt = (WORD)SPpat<<SPsft;
		pSPw[SPpos+0] |= SPwrt >> 8;
		pSPw[SPpos+1] |= SPwrt & 0xFF;
		SPpat &= ~SPmsk;

		if( sp->attr&SP_PRIORITY_BIT ) {
		// BG > SP priority
			INT	BGpos = ((sp->x&0xF8)+((loopy_shift+(sp->x&7))&8))>>3;
			INT	BGsft = 8-((loopy_shift+sp->x)&7);
			BYTE	BGmsk = (((WORD)pBGw[BGpos+0]<<8)|(WORD)pBGw[BGpos+1])>>BGsft;

			SPpat &= ~BGmsk;
		}

		// Attribute
		LPBYTE	pSPPAL = &SPPAL[(sp->attr&SP_COLOR_BIT)<<2];
		// Ptr
		LPBYTE	pScn   = lpScanline+sp->x+8;

		if( !bExtMono ) {
			register INT	c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA);
			register INT	c2 = (chr_l&0x55)|((chr_h<<1)&0xAA);
			if( SPpat&0x80 ) pScn[0] = pSPPAL[(c1>>6)];
			if( SPpat&0x08 ) pScn[4] = pSPPAL[(c1>>2)&3];
			if( SPpat&0x40 ) pScn[1] = pSPPAL[(c2>>6)];
			if( SPpat&0x04 ) pScn[5] = pSPPAL[(c2>>2)&3];
			if( SPpat&0x20 ) pScn[2] = pSPPAL[(c1>>4)&3];
			if( SPpat&0x02 ) pScn[6] = pSPPAL[c1&3];
			if( SPpat&0x10 ) pScn[3] = pSPPAL[(c2>>4)&3];
			if( SPpat&0x01 ) pScn[7] = pSPPAL[c2&3];
		} else {
		// Monocrome effect (for Final Fantasy)
			BYTE	mono = BGmono[((sp->x&0xF8)+((loopy_shift+(sp->x&7))&8))>>3];

			register INT	c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA);
			register INT	c2 = (chr_l&0x55)|((chr_h<<1)&0xAA);
			if( SPpat&0x80 ) pScn[0] = pSPPAL[c1>>6]	|mono;
			if( SPpat&0x08 ) pScn[4] = pSPPAL[(c1>>2)&3]	|mono;
			if( SPpat&0x40 ) pScn[1] = pSPPAL[c2>>6]	|mono;
			if( SPpat&0x04 ) pScn[5] = pSPPAL[(c2>>2)&3]	|mono;
			if( SPpat&0x20 ) pScn[2] = pSPPAL[(c1>>4)&3]	|mono;
			if( SPpat&0x02 ) pScn[6] = pSPPAL[c1&3]		|mono;
			if( SPpat&0x10 ) pScn[3] = pSPPAL[(c2>>4)&3]	|mono;
			if( SPpat&0x01 ) pScn[7] = pSPPAL[c2&3]		|mono;
		}

		if( ++spmax > 8-1 ) {
			if( !bMax )
				break;
		}
	}
	if( spmax > 8-1 ) {
		PPUREG[2] |= PPU_SPMAX_FLAG;
	}
}

// スプライト０がヒットするかも知れないライン？
BOOL	PPU::IsSprite0( INT scanline )
{
	// スプライトorBG非表示はキャンセル(ヒットしない)
	if( (PPUREG[1]&(PPU_SPDISP_BIT|PPU_BGDISP_BIT)) != (PPU_SPDISP_BIT|PPU_BGDISP_BIT) )
		return	FALSE;

	// 既にヒットしていたらキャンセル
	if( PPUREG[2]&PPU_SPHIT_FLAG )
		return	FALSE;

	if( !(PPUREG[0]&PPU_SP16_BIT) ) {
	// 8x8
		if( (scanline < (INT)SPRAM[0]+1) || (scanline > ((INT)SPRAM[0]+7+1)) )
			return	FALSE;
	} else {
	// 8x16
		if( (scanline < (INT)SPRAM[0]+1) || (scanline > ((INT)SPRAM[0]+15+1)) )
			return	FALSE;
	}

	return	TRUE;
}

void	PPU::DummyScanline( INT scanline )
{
INT	i;
INT	spmax;
INT	sp_h;
LPSPRITE sp;

	PPUREG[2] &= ~PPU_SPMAX_FLAG;

	// スプライト非表示はキャンセル
	if( !(PPUREG[1]&PPU_SPDISP_BIT) )
		return;

	// 表示期間外であればキャンセル
	if( scanline < 0 || scanline > 239 )
		return;

	sp = (LPSPRITE)SPRAM;
	sp_h = (PPUREG[0]&PPU_SP16_BIT)?15:7;

	spmax = 0;
	// Sprite Max check
	for( i = 0; i < 64; i++, sp++ ) {
		// スキャンライン内にSPRITEが存在するかをチェック
		if( (scanline < (INT)sp->y+1) || (scanline > ((INT)sp->y+sp_h+1)) ) {
			continue;
		}

		if( ++spmax > 8-1 ) {
			PPUREG[2] |= PPU_SPMAX_FLAG;
			break;
		}
	}
}

