2009-07-17 19:27:04 +02:00
/* FCE Ultra - NES/Famicom Emulator
*
* Copyright notice for this file :
* Copyright ( C ) 1998 BERO
* Copyright ( C ) 2003 Xodnizel
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program 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 General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <string.h>
# include <stdio.h>
# include <stdlib.h>
# include "types.h"
# include "x6502.h"
# include "fceu.h"
# include "ppu.h"
# include "nsf.h"
# include "sound.h"
# include "file.h"
# include "utils/endian.h"
# include "utils/memory.h"
# include "cart.h"
# include "palette.h"
# include "state.h"
# include "video.h"
# include "input.h"
# include "driver.h"
2009-09-15 10:20:48 +02:00
2009-07-17 19:27:04 +02:00
# define VBlankON (PPU[0]&0x80) //Generate VBlank NMI
# define Sprite16 (PPU[0]&0x20) //Sprites 8x16/8x8
# define BGAdrHI (PPU[0]&0x10) //BG pattern adr $0000/$1000
# define SpAdrHI (PPU[0]&0x08) //Sprite pattern adr $0000/$1000
# define INC32 (PPU[0]&0x04) //auto increment 1/32
# define SpriteON (PPU[1]&0x10) //Show Sprite
# define ScreenON (PPU[1]&0x08) //Show screen
# define PPUON (PPU[1]&0x18) //PPU should operate
2009-07-18 00:54:58 +02:00
# define GRAYSCALE (PPU[1]&0x01) //Grayscale (AND palette entries with 0x30)
2009-07-17 19:27:04 +02:00
# define SpriteLeft8 (PPU[1]&0x04)
# define BGLeft8 (PPU[1]&0x02)
# define PPU_status (PPU[2])
# define Pal (PALRAM)
static void FetchSpriteData ( void ) ;
static void RefreshLine ( int lastpixel ) ;
static void RefreshSprites ( void ) ;
static void CopySprites ( uint8 * target ) ;
static void Fixit1 ( void ) ;
static uint32 ppulut1 [ 256 ] ;
static uint32 ppulut2 [ 256 ] ;
static uint32 ppulut3 [ 128 ] ;
int test = 0 ;
template < typename T , int BITS >
struct BITREVLUT {
T * lut ;
BITREVLUT ( ) {
int bits = BITS ;
int n = 1 < < BITS ;
lut = new T [ n ] ;
int m = 1 ;
int a = n > > 1 ;
int j = 2 ;
lut [ 0 ] = 0 ;
lut [ 1 ] = a ;
while ( - - bits ) {
m < < = 1 ;
a > > = 1 ;
for ( int i = 0 ; i < m ; i + + )
lut [ j + + ] = lut [ i ] + a ;
}
}
T operator [ ] ( int index ) { return lut [ index ] ; }
} ;
BITREVLUT < uint8 , 8 > bitrevlut ;
2009-07-18 00:54:58 +02:00
struct PPUSTATUS
{
2009-09-15 10:20:48 +02:00
int32 sl ;
int32 cycle , end_cycle ;
2009-07-18 00:54:58 +02:00
} ;
struct SPRITE_READ
{
2009-09-15 10:20:48 +02:00
int32 num ;
int32 count ;
int32 fetch ;
int32 found ;
int32 found_pos [ 8 ] ;
int32 ret ;
int32 last ;
int32 mode ;
void reset ( ) {
num = count = fetch = found = ret = last = mode = 0 ;
found_pos [ 0 ] = found_pos [ 1 ] = found_pos [ 2 ] = found_pos [ 3 ] = 0 ;
found_pos [ 4 ] = found_pos [ 5 ] = found_pos [ 6 ] = found_pos [ 7 ] = 0 ;
}
void start_scanline ( )
{
num = 1 ;
found = 0 ;
fetch = 1 ;
count = 0 ;
last = 64 ;
mode = 0 ;
found_pos [ 0 ] = found_pos [ 1 ] = found_pos [ 2 ] = found_pos [ 3 ] = 0 ;
found_pos [ 4 ] = found_pos [ 5 ] = found_pos [ 6 ] = found_pos [ 7 ] = 0 ;
}
2009-07-18 00:54:58 +02:00
} ;
2009-09-15 10:20:48 +02:00
//doesn't need to be savestated as it is just a reflection of the current position in the ppu loop
PPUPHASE ppuphase ;
//this needs to be savestated since a game may be trying to read from this across vblanks
SPRITE_READ spr_read ;
//definitely needs to be savestated
uint8 idleSynch = 1 ;
2009-07-18 00:54:58 +02:00
2009-07-17 19:27:04 +02:00
//uses the internal counters concept at http://nesdev.icequake.net/PPU%20addressing.txt
struct PPUREGS {
2009-09-15 10:20:48 +02:00
//normal clocked regs. as the game can interfere with these at any time, they need to be savestated
2009-07-17 19:27:04 +02:00
uint32 fv ; //3
uint32 v ; //1
uint32 h ; //1
uint32 vt ; //5
uint32 ht ; //5
2009-09-15 10:20:48 +02:00
//temp unlatched regs (need savestating, can be written to at any time)
2009-07-18 00:54:58 +02:00
uint32 _fv , _v , _h , _vt , _ht ;
2009-09-15 10:20:48 +02:00
//other regs that need savestating
uint32 fh ; //3 (horz scroll)
uint32 s ; //1 ($2000 bit 4: "Background pattern table address (0: $0000; 1: $1000)")
2009-07-17 19:27:04 +02:00
2009-09-15 10:20:48 +02:00
//other regs that don't need saving
uint32 par ; //8 (sort of a hack, just stored in here, but not managed by this system)
//cached state data. these are always reset at the beginning of a frame and don't need saving
//but just to be safe, we're gonna save it
PPUSTATUS status ;
void reset ( )
{
fv = v = h = vt = ht = 0 ;
fh = par = s = 0 ;
_fv = _v = _h = _vt = _ht = 0 ;
status . cycle = 0 ;
status . end_cycle = 341 ;
status . sl = 241 ;
}
2009-07-17 19:27:04 +02:00
void install_latches ( ) {
fv = _fv ;
v = _v ;
h = _h ;
vt = _vt ;
ht = _ht ;
}
void install_h_latches ( ) {
ht = _ht ;
h = _h ;
}
void clear_latches ( ) {
_fv = _v = _h = _vt = _ht = 0 ;
fh = 0 ;
}
void increment_hsc ( ) {
//The first one, the horizontal scroll counter, consists of 6 bits, and is
//made up by daisy-chaining the HT counter to the H counter. The HT counter is
//then clocked every 8 pixel dot clocks (or every 8/3 CPU clock cycles).
ht + + ;
h + = ( ht > > 5 ) ;
ht & = 31 ;
h & = 1 ;
}
void increment_vs ( ) {
fv + + ;
vt + = ( fv > > 3 ) ;
2009-10-22 04:44:03 +02:00
vt & = 31 ; //fixed tecmo super bowl
2009-07-17 19:27:04 +02:00
v + = ( vt = = 30 ) ? 1 : 0 ;
fv & = 7 ;
if ( vt = = 30 ) vt = 0 ;
v & = 1 ;
}
uint32 get_ntread ( ) {
return 0x2000 | ( v < < 0xB ) | ( h < < 0xA ) | ( vt < < 5 ) | ht ;
}
uint32 get_2007access ( ) {
return ( ( fv & 3 ) < < 0xC ) | ( v < < 0xB ) | ( h < < 0xA ) | ( vt < < 5 ) | ht ;
}
//The PPU has an internal 4-position, 2-bit shifter, which it uses for
//obtaining the 2-bit palette select data during an attribute table byte
//fetch. To represent how this data is shifted in the diagram, letters a..c
//are used in the diagram to represent the right-shift position amount to
//apply to the data read from the attribute data (a is always 0). This is why
//you only see bits 0 and 1 used off the read attribute data in the diagram.
uint32 get_atread ( ) {
return 0x2000 | ( v < < 0xB ) | ( h < < 0xA ) | 0x3C0 | ( ( vt & 0x1C ) < < 1 ) | ( ( ht & 0x1C ) > > 2 ) ;
}
//address line 3 relates to the pattern table fetch occuring (the PPU always makes them in pairs).
uint32 get_ptread ( ) {
return ( s < < 0xC ) | ( par < < 0x4 ) | fv ;
}
void increment2007 ( bool by32 ) {
//If the VRAM address increment bit (2000.2) is clear (inc. amt. = 1), all the
//scroll counters are daisy-chained (in the order of HT, VT, H, V, FV) so that
//the carry out of each counter controls the next counter's clock rate. The
//result is that all 5 counters function as a single 15-bit one. Any access to
//2007 clocks the HT counter here.
//
//If the VRAM address increment bit is set (inc. amt. = 32), the only
//difference is that the HT counter is no longer being clocked, and the VT
//counter is now being clocked by access to 2007.
if ( by32 ) {
vt + + ;
} else {
ht + + ;
vt + = ( ht > > 5 ) & 1 ;
}
h + = ( vt > > 5 ) ;
v + = ( h > > 1 ) ;
fv + = ( v > > 1 ) ;
ht & = 31 ;
vt & = 31 ;
h & = 1 ;
v & = 1 ;
fv & = 7 ;
}
} ppur ;
static void makeppulut ( void )
{
int x ;
int y ;
int cc , xo , pixel ;
for ( x = 0 ; x < 256 ; x + + )
{
ppulut1 [ x ] = 0 ;
for ( y = 0 ; y < 8 ; y + + )
{
ppulut1 [ x ] | = ( ( x > > ( 7 - y ) ) & 1 ) < < ( y * 4 ) ;
}
ppulut2 [ x ] = ppulut1 [ x ] < < 1 ;
}
for ( cc = 0 ; cc < 16 ; cc + + )
{
for ( xo = 0 ; xo < 8 ; xo + + )
{
ppulut3 [ xo | ( cc < < 3 ) ] = 0 ;
for ( pixel = 0 ; pixel < 8 ; pixel + + )
{
int shiftr ;
shiftr = ( pixel + xo ) / 8 ;
shiftr * = 2 ;
ppulut3 [ xo | ( cc < < 3 ) ] | = ( ( cc > > shiftr ) & 3 ) < < ( 2 + pixel * 4 ) ;
}
// printf("%08x\n",ppulut3[xo|(cc<<3)]);
}
}
}
static int ppudead = 1 ;
static int kook = 0 ;
int fceuindbg = 0 ;
//mbg 6/23/08
//make the no-bg fill color configurable
//0xFF shall indicate to use palette[0]
uint8 gNoBGFillColor = 0xFF ;
int MMC5Hack = 0 ;
uint32 MMC5HackVROMMask = 0 ;
uint8 * MMC5HackExNTARAMPtr = 0 ;
uint8 * MMC5HackVROMPTR = 0 ;
uint8 MMC5HackCHRMode = 0 ;
uint8 MMC5HackSPMode = 0 ;
2009-07-18 00:54:58 +02:00
uint8 MMC50x5130 = 0 ;
2009-07-17 19:27:04 +02:00
uint8 MMC5HackSPScroll = 0 ;
uint8 MMC5HackSPPage = 0 ;
uint8 VRAMBuffer = 0 , PPUGenLatch = 0 ;
uint8 * vnapage [ 4 ] ;
uint8 PPUNTARAM = 0 ;
uint8 PPUCHRRAM = 0 ;
//Color deemphasis emulation. Joy...
static uint8 deemp = 0 ;
static int deempcnt [ 8 ] ;
void ( * GameHBIRQHook ) ( void ) , ( * GameHBIRQHook2 ) ( void ) ;
void ( * PPU_hook ) ( uint32 A ) ;
uint8 vtoggle = 0 ;
uint8 XOffset = 0 ;
uint32 TempAddr = 0 , RefreshAddr = 0 ;
static int maxsprites = 8 ;
//scanline is equal to the current visible scanline we're on.
int scanline ;
static uint32 scanlines_per_frame ;
uint8 PPU [ 4 ] ;
uint8 PPUSPL ;
uint8 NTARAM [ 0x800 ] , PALRAM [ 0x20 ] , SPRAM [ 0x100 ] , SPRBUF [ 0x100 ] ;
2009-07-18 00:54:58 +02:00
uint8 UPALRAM [ 0x03 ] ; //for 0x4/0x8/0xC addresses in palette, the ones in
//0x20 are 0 to not break fceu rendering.
2009-07-17 19:27:04 +02:00
# define MMC5SPRVRAMADR(V) &MMC5SPRVPage[(V)>>10][(V)]
# define VRAMADR(V) &VPage[(V)>>10][(V)]
//mbg 8/6/08 - fix a bug relating to
//"When in 8x8 sprite mode, only one set is used for both BG and sprites."
//in mmc5 docs
uint8 * MMC5BGVRAMADR ( uint32 V ) {
if ( ! Sprite16 ) {
extern uint8 mmc5ABMode ; /* A=0, B=1 */
if ( mmc5ABMode = = 0 )
return MMC5SPRVRAMADR ( V ) ;
else
return & MMC5BGVPage [ ( V ) > > 10 ] [ ( V ) ] ;
} else return & MMC5BGVPage [ ( V ) > > 10 ] [ ( V ) ] ;
}
//this duplicates logic which is embedded in the ppu rendering code
//which figures out where to get CHR data from depending on various hack modes
//mostly involving mmc5.
//this might be incomplete.
uint8 * FCEUPPU_GetCHR ( uint32 vadr , uint32 refreshaddr ) {
if ( MMC5Hack ) {
if ( MMC5HackCHRMode = = 1 ) {
uint8 * C = MMC5HackVROMPTR ;
C + = ( ( ( MMC5HackExNTARAMPtr [ refreshaddr & 0x3ff ] ) & 0x3f & MMC5HackVROMMask ) < < 12 ) + ( vadr & 0xfff ) ;
2009-07-18 00:54:58 +02:00
C + = ( MMC50x5130 & 0x3 ) < < 18 ; //11-jun-2009 for kuja_killer
2009-07-17 19:27:04 +02:00
return C ;
} else {
return MMC5BGVRAMADR ( vadr ) ;
}
}
else return VRAMADR ( vadr ) ;
}
//likewise for ATTR
int FCEUPPU_GetAttr ( int ntnum , int xt , int yt ) {
int attraddr = 0x3C0 + ( ( yt > > 2 ) < < 3 ) + ( xt > > 2 ) ;
int temp = ( ( ( yt & 2 ) < < 1 ) + ( xt & 2 ) ) ;
int refreshaddr = xt + yt * 32 ;
if ( MMC5Hack & & MMC5HackCHRMode = = 1 )
return ( MMC5HackExNTARAMPtr [ refreshaddr & 0x3ff ] & 0xC0 ) > > 6 ;
else
return ( vnapage [ ntnum ] [ attraddr ] & ( 3 < < temp ) ) > > temp ;
}
//new ppu-----
inline void FFCEUX_PPUWrite_Default ( uint32 A , uint8 V ) {
uint32 tmp = A ;
2009-09-15 10:20:48 +02:00
if ( PPU_hook ) PPU_hook ( A ) ;
2009-07-18 00:54:58 +02:00
if ( tmp < 0x2000 )
{
if ( PPUCHRRAM & ( 1 < < ( tmp > > 10 ) ) )
VPage [ tmp > > 10 ] [ tmp ] = V ;
}
else if ( tmp < 0x3F00 )
{
if ( PPUNTARAM & ( 1 < < ( ( tmp & 0xF00 ) > > 10 ) ) )
vnapage [ ( ( tmp & 0xF00 ) > > 10 ) ] [ tmp & 0x3FF ] = V ;
}
else
{
if ( ! ( tmp & 3 ) )
{
if ( ! ( tmp & 0xC ) )
PALRAM [ 0x00 ] = PALRAM [ 0x04 ] =
PALRAM [ 0x08 ] = PALRAM [ 0x0C ] = V & 0x3F ;
else
UPALRAM [ ( ( tmp & 0xC ) > > 2 ) - 1 ] = V & 0x3F ;
}
else
PALRAM [ tmp & 0x1F ] = V & 0x3F ;
}
2009-07-17 19:27:04 +02:00
}
2009-09-15 10:20:48 +02:00
uint8 FASTCALL FFCEUX_PPURead_Default ( uint32 A ) {
2009-07-17 19:27:04 +02:00
uint32 tmp = A ;
2009-09-15 10:20:48 +02:00
if ( PPU_hook ) PPU_hook ( A ) ;
2009-07-17 19:27:04 +02:00
if ( tmp < 0x2000 )
{
return VPage [ tmp > > 10 ] [ tmp ] ;
}
2009-07-18 00:54:58 +02:00
else if ( tmp < 0x3F00 )
2009-07-17 19:27:04 +02:00
{
return vnapage [ ( tmp > > 10 ) & 0x3 ] [ tmp & 0x3FF ] ;
}
2009-07-18 00:54:58 +02:00
else
{
uint8 ret ;
if ( ! ( tmp & 3 ) )
{
if ( ! ( tmp & 0xC ) )
ret = PALRAM [ 0x00 ] ;
else
ret = UPALRAM [ ( ( tmp & 0xC ) > > 2 ) - 1 ] ;
}
else
ret = PALRAM [ tmp & 0x1F ] ;
if ( GRAYSCALE )
ret & = 0x30 ;
return ret ;
}
2009-07-17 19:27:04 +02:00
}
2009-09-15 10:20:48 +02:00
uint8 ( FASTCALL * FFCEUX_PPURead ) ( uint32 A ) = 0 ;
2009-07-17 19:27:04 +02:00
void ( * FFCEUX_PPUWrite ) ( uint32 A , uint8 V ) = 0 ;
2009-09-15 10:20:48 +02:00
# define CALL_PPUREAD(A) (FFCEUX_PPURead(A))
2009-07-17 19:27:04 +02:00
# define CALL_PPUWRITE(A,V) (FFCEUX_PPUWrite?FFCEUX_PPUWrite(A,V):FFCEUX_PPUWrite_Default(A,V))
//whether to use the new ppu (new PPU doesn't handle MMC5 extra nametables at all
int newppu = 0 ;
//---------------
static DECLFR ( A2002 )
{
if ( newppu )
{
//once we thought we clear latches here, but that caused midframe glitches.
//i think we should only reset the state machine for 2005/2006
//ppur.clear_latches();
}
uint8 ret ;
FCEUPPU_LineUpdate ( ) ;
ret = PPU_status ;
ret | = PPUGenLatch & 0x1F ;
# ifdef FCEUDEF_DEBUGGER
if ( ! fceuindbg )
# endif
{
vtoggle = 0 ;
PPU_status & = 0x7F ;
PPUGenLatch = ret ;
}
return ret ;
}
2009-07-18 00:54:58 +02:00
static DECLFR ( A2004 )
{
if ( newppu )
{
if ( ( ppur . status . sl < 241 ) & & PPUON )
{
/* from cycles 0 to 63, the
* 32 byte OAM buffer gets init
* to 0xFF */
if ( ppur . status . cycle < 64 )
return spr_read . ret = 0xFF ;
else
{
for ( int i = spr_read . last ;
i ! = ppur . status . cycle ; + + i )
{
if ( i < 256 )
{
switch ( spr_read . mode )
{
case 0 :
if ( spr_read . count < 2 )
spr_read . ret = ( PPU [ 3 ] & 0xF8 )
+ ( spr_read . count < < 2 ) ;
else
spr_read . ret = spr_read . count < < 2 ;
spr_read . found_pos [ spr_read . found ] =
spr_read . ret ;
spr_read . ret = SPRAM [ spr_read . ret ] ;
if ( i & 1 ) //odd cycle
{
//see if in range
if ( ! ( ( ppur . status . sl - 1 -
spr_read . ret )
& ~ ( Sprite16 ? 0xF : 0x7 ) ) )
{
+ + spr_read . found ;
spr_read . fetch = 1 ;
spr_read . mode = 1 ;
}
else
{
if ( + + spr_read . count = = 64 )
{
spr_read . mode = 4 ;
spr_read . count = 0 ;
}
else if ( spr_read . found = = 8 )
{
spr_read . fetch = 0 ;
spr_read . mode = 2 ;
}
}
}
break ;
case 1 : //sprite is in range fetch next 3 bytes
if ( i & 1 )
{
+ + spr_read . fetch ;
if ( spr_read . fetch = = 4 )
{
spr_read . fetch = 1 ;
if ( + + spr_read . count = = 64 )
{
spr_read . count = 0 ;
spr_read . mode = 4 ;
}
else if ( spr_read . found = = 8 )
{
spr_read . fetch = 0 ;
spr_read . mode = 2 ;
}
else
spr_read . mode = 0 ;
}
}
if ( spr_read . count < 2 )
spr_read . ret = ( PPU [ 3 ] & 0xF8 )
+ ( spr_read . count < < 2 ) ;
else
spr_read . ret = spr_read . count < < 2 ;
spr_read . ret = SPRAM [ spr_read . ret |
spr_read . fetch ] ;
break ;
case 2 : //8th sprite fetched
spr_read . ret = SPRAM [ ( spr_read . count < < 2 )
| spr_read . fetch ] ;
if ( i & 1 )
{
if ( ! ( ( ppur . status . sl - 1 -
SPRAM [ ( ( spr_read . count < < 2 )
| spr_read . fetch ) ] )
& ~ ( ( Sprite16 ) ? 0xF : 0x7 ) ) )
{
spr_read . fetch = 1 ;
spr_read . mode = 3 ;
}
else
{
if ( + + spr_read . count = = 64 )
{
spr_read . count = 0 ;
spr_read . mode = 4 ;
}
spr_read . fetch =
( spr_read . fetch + 1 ) & 3 ;
}
}
spr_read . ret = spr_read . count ;
break ;
case 3 : //9th sprite overflow detected
spr_read . ret = SPRAM [ spr_read . count
| spr_read . fetch ] ;
if ( i & 1 )
{
if ( + + spr_read . fetch = = 4 )
{
spr_read . count = ( spr_read . count
+ 1 ) & 63 ;
spr_read . mode = 4 ;
}
}
break ;
case 4 : //read OAM[n][0] until hblank
if ( i & 1 )
spr_read . count =
( spr_read . count + 1 ) & 63 ;
spr_read . fetch = 0 ;
spr_read . ret = SPRAM [ spr_read . count < < 2 ] ;
break ;
}
}
else if ( i < 320 )
{
spr_read . ret = ( i & 0x38 ) > > 3 ;
if ( spr_read . found < ( spr_read . ret + 1 ) )
{
if ( spr_read . num )
{
spr_read . ret = SPRAM [ 252 ] ;
spr_read . num = 0 ;
}
else
spr_read . ret = 0xFF ;
}
else if ( ( i & 7 ) < 4 )
{
spr_read . ret =
SPRAM [ spr_read . found_pos [ spr_read . ret ]
| spr_read . fetch + + ] ;
if ( spr_read . fetch = = 4 )
spr_read . fetch = 0 ;
}
else
spr_read . ret = SPRAM [ spr_read . found_pos
[ spr_read . ret | 3 ] ] ;
}
else
{
if ( ! spr_read . found )
spr_read . ret = SPRAM [ 252 ] ;
else
spr_read . ret = SPRAM [ spr_read . found_pos [ 0 ] ] ;
break ;
}
}
spr_read . last = ppur . status . cycle ;
return spr_read . ret ;
}
}
else
return SPRAM [ PPU [ 3 ] ] ;
}
else
{
FCEUPPU_LineUpdate ( ) ;
return PPUGenLatch ;
}
}
2009-07-17 19:27:04 +02:00
static DECLFR ( A200x ) /* Not correct for $2004 reads. */
{
FCEUPPU_LineUpdate ( ) ;
return PPUGenLatch ;
}
/*
static DECLFR ( A2004 )
{
uint8 ret ;
FCEUPPU_LineUpdate ( ) ;
ret = SPRAM [ PPU [ 3 ] ] ;
if ( PPUSPL > = 8 )
{
if ( PPU [ 3 ] > = 8 )
ret = SPRAM [ PPU [ 3 ] ] ;
}
else
{
//printf("$%02x:$%02x\n",PPUSPL,V);
ret = SPRAM [ PPUSPL ] ;
}
PPU [ 3 ] + + ;
PPUSPL + + ;
PPUGenLatch = ret ;
printf ( " %d, %02x \n " , scanline , ret ) ;
return ( ret ) ;
}
*/
static DECLFR ( A2007 )
{
uint8 ret ;
uint32 tmp = RefreshAddr & 0x3FFF ;
if ( newppu ) {
2009-07-18 00:54:58 +02:00
ret = VRAMBuffer ;
RefreshAddr = ppur . get_2007access ( ) & 0x3FFF ;
if ( ( RefreshAddr & 0x3F00 ) = = 0x3F00 )
{
//if it is in the palette range bypass the
//delayed read, and what gets filled in the temp
//buffer is the address - 0x1000, also
//if grayscale is set then the return is AND with 0x30
//to get a gray color reading
if ( ! ( tmp & 3 ) )
{
if ( ! ( tmp & 0xC ) )
ret = PALRAM [ 0x00 ] ;
else
ret = UPALRAM [ ( ( tmp & 0xC ) > > 2 ) - 1 ] ;
}
else
ret = PALRAM [ tmp & 0x1F ] ;
if ( GRAYSCALE )
ret & = 0x30 ;
VRAMBuffer = CALL_PPUREAD ( RefreshAddr - 0x1000 ) ;
}
else
VRAMBuffer = CALL_PPUREAD ( RefreshAddr ) ;
2009-07-17 19:27:04 +02:00
ppur . increment2007 ( INC32 ! = 0 ) ;
RefreshAddr = ppur . get_2007access ( ) ;
2009-07-18 00:54:58 +02:00
return ret ;
} else {
2009-07-17 19:27:04 +02:00
FCEUPPU_LineUpdate ( ) ;
ret = VRAMBuffer ;
# ifdef FCEUDEF_DEBUGGER
if ( ! fceuindbg )
# endif
{
if ( PPU_hook ) PPU_hook ( tmp ) ;
PPUGenLatch = VRAMBuffer ;
if ( tmp < 0x2000 )
{
VRAMBuffer = VPage [ tmp > > 10 ] [ tmp ] ;
}
2009-07-18 00:54:58 +02:00
else if ( tmp < 0x3F00 )
2009-07-17 19:27:04 +02:00
{
VRAMBuffer = vnapage [ ( tmp > > 10 ) & 0x3 ] [ tmp & 0x3FF ] ;
}
}
# ifdef FCEUDEF_DEBUGGER
if ( ! fceuindbg )
# endif
{
if ( INC32 ) RefreshAddr + = 32 ;
else RefreshAddr + + ;
if ( PPU_hook ) PPU_hook ( RefreshAddr & 0x3fff ) ;
}
return ret ;
}
}
static DECLFW ( B2000 )
{
// FCEU_printf("%04x:%02x, (%d) %02x, %02x\n",A,V,scanline,PPU[0],PPU_status);
FCEUPPU_LineUpdate ( ) ;
PPUGenLatch = V ;
if ( ! ( PPU [ 0 ] & 0x80 ) & & ( V & 0x80 ) & & ( PPU_status & 0x80 ) )
{
// FCEU_printf("Trigger NMI, %d, %d\n",timestamp,ppudead);
TriggerNMI2 ( ) ;
}
PPU [ 0 ] = V ;
TempAddr & = 0xF3FF ;
TempAddr | = ( V & 3 ) < < 10 ;
ppur . _h = V & 1 ;
ppur . _v = ( V > > 1 ) & 1 ;
ppur . s = ( V > > 4 ) & 1 ;
}
static DECLFW ( B2001 )
{
//printf("%04x:$%02x, %d\n",A,V,scanline);
FCEUPPU_LineUpdate ( ) ;
PPUGenLatch = V ;
PPU [ 1 ] = V ;
if ( V & 0xE0 )
deemp = V > > 5 ;
}
2009-07-18 00:54:58 +02:00
//
2009-07-17 19:27:04 +02:00
static DECLFW ( B2002 )
{
PPUGenLatch = V ;
}
static DECLFW ( B2003 )
{
//printf("$%04x:$%02x, %d, %d\n",A,V,timestamp,scanline);
PPUGenLatch = V ;
PPU [ 3 ] = V ;
PPUSPL = V & 0x7 ;
}
static DECLFW ( B2004 )
{
//printf("Wr: %04x:$%02x\n",A,V);
2009-07-18 00:54:58 +02:00
PPUGenLatch = V ;
if ( newppu )
{
//the attribute upper bits are not connected
//so AND them out on write, since reading them
//should return 0 in those bits.
if ( ( PPU [ 3 ] & 3 ) = = 2 )
V & = 0xE3 ;
SPRAM [ PPU [ 3 ] ] = V ;
PPU [ 3 ] = ( PPU [ 3 ] + 1 ) & 0xFF ;
}
else
{
if ( PPUSPL > = 8 )
{
if ( PPU [ 3 ] > = 8 )
SPRAM [ PPU [ 3 ] ] = V ;
}
else
{
//printf("$%02x:$%02x\n",PPUSPL,V);
SPRAM [ PPUSPL ] = V ;
}
PPU [ 3 ] + + ;
PPUSPL + + ;
}
2009-07-17 19:27:04 +02:00
}
static DECLFW ( B2005 )
{
uint32 tmp = TempAddr ;
FCEUPPU_LineUpdate ( ) ;
PPUGenLatch = V ;
if ( ! vtoggle )
{
tmp & = 0xFFE0 ;
tmp | = V > > 3 ;
XOffset = V & 7 ;
ppur . _ht = V > > 3 ;
ppur . fh = V & 7 ;
}
else
{
tmp & = 0x8C1F ;
tmp | = ( ( V & ~ 0x7 ) < < 2 ) ;
tmp | = ( V & 7 ) < < 12 ;
ppur . _vt = V > > 3 ;
ppur . _fv = V & 7 ;
}
TempAddr = tmp ;
vtoggle ^ = 1 ;
}
static DECLFW ( B2006 )
{
if ( ! newppu )
FCEUPPU_LineUpdate ( ) ;
PPUGenLatch = V ;
if ( ! vtoggle )
{
TempAddr & = 0x00FF ;
TempAddr | = ( V & 0x3f ) < < 8 ;
ppur . _vt & = 0x07 ;
ppur . _vt | = ( V & 0x3 ) < < 3 ;
ppur . _h = ( V > > 2 ) & 1 ;
ppur . _v = ( V > > 3 ) & 1 ;
ppur . _fv = ( V > > 4 ) & 3 ;
}
else
{
TempAddr & = 0xFF00 ;
TempAddr | = V ;
RefreshAddr = TempAddr ;
if ( PPU_hook )
PPU_hook ( RefreshAddr ) ;
//printf("%d, %04x\n",scanline,RefreshAddr);
ppur . _vt & = 0x18 ;
ppur . _vt | = ( V > > 5 ) ;
ppur . _ht = V & 31 ;
ppur . install_latches ( ) ;
}
vtoggle ^ = 1 ;
}
static DECLFW ( B2007 )
{
uint32 tmp = RefreshAddr & 0x3FFF ;
if ( newppu ) {
2009-07-18 00:54:58 +02:00
RefreshAddr = ppur . get_2007access ( ) & 0x3FFF ;
2009-07-17 19:27:04 +02:00
CALL_PPUWRITE ( RefreshAddr , V ) ;
//printf("%04x ",RefreshAddr);
ppur . increment2007 ( INC32 ! = 0 ) ;
RefreshAddr = ppur . get_2007access ( ) ;
}
else
{
//printf("%04x ",tmp);
PPUGenLatch = V ;
if ( tmp > = 0x3F00 )
{
// hmmm....
if ( ! ( tmp & 0xf ) )
PALRAM [ 0x00 ] = PALRAM [ 0x04 ] = PALRAM [ 0x08 ] = PALRAM [ 0x0C ] = V & 0x3F ;
else if ( tmp & 3 ) PALRAM [ ( tmp & 0x1f ) ] = V & 0x3f ;
}
else if ( tmp < 0x2000 )
{
if ( PPUCHRRAM & ( 1 < < ( tmp > > 10 ) ) )
VPage [ tmp > > 10 ] [ tmp ] = V ;
}
else
{
if ( PPUNTARAM & ( 1 < < ( ( tmp & 0xF00 ) > > 10 ) ) )
vnapage [ ( ( tmp & 0xF00 ) > > 10 ) ] [ tmp & 0x3FF ] = V ;
}
// FCEU_printf("ppu (%04x) %04x:%04x %d, %d\n",X.PC,RefreshAddr,PPUGenLatch,scanline,timestamp);
if ( INC32 ) RefreshAddr + = 32 ;
else RefreshAddr + + ;
if ( PPU_hook ) PPU_hook ( RefreshAddr & 0x3fff ) ;
}
}
static DECLFW ( B4014 )
{
uint32 t = V < < 8 ;
int x ;
for ( x = 0 ; x < 256 ; x + + )
X6502_DMW ( 0x2004 , X6502_DMR ( t + x ) ) ;
}
# define PAL(c) ((c)+cc)
# define GETLASTPIXEL (PAL?((timestamp*48-linestartts) / 15) : ((timestamp*48-linestartts)>>4) )
static uint8 * Pline , * Plinef ;
static int firsttile ;
int linestartts ; //no longer static so the debugger can see it
static int tofix = 0 ;
static void ResetRL ( uint8 * target )
{
memset ( target , 0xFF , 256 ) ;
InputScanlineHook ( 0 , 0 , 0 , 0 ) ;
Plinef = target ;
Pline = target ;
firsttile = 0 ;
linestartts = timestamp * 48 + X . count ;
tofix = 0 ;
FCEUPPU_LineUpdate ( ) ;
tofix = 1 ;
}
static uint8 sprlinebuf [ 256 + 8 ] ;
void FCEUPPU_LineUpdate ( void )
{
# ifdef FCEUDEF_DEBUGGER
if ( ! fceuindbg )
# endif
if ( Pline )
{
int l = GETLASTPIXEL ;
RefreshLine ( l ) ;
}
}
static bool rendersprites = true , renderbg = true ;
void FCEUI_SetRenderPlanes ( bool sprites , bool bg )
{
rendersprites = sprites ;
renderbg = bg ;
}
void FCEUI_GetRenderPlanes ( bool & sprites , bool & bg )
{
sprites = rendersprites ;
bg = renderbg ;
}
//mbg 6/21/08 - tileview is being ripped out since i dont know how long its been since it worked
//static int tileview=1;
//void FCEUI_ToggleTileView(void)
//{
// tileview^=1;
//}
//mbg 6/21/08 - tileview is being ripped out since i dont know how long its been since it worked
//static void TileView(void)
//{
// uint8 *P=XBuf+16*256;
// int bgh;
// int y;
// int X1;
// for(bgh=0;bgh<2;bgh++)
// for(y=0;y<16*8;y++)
// for(P=XBuf+bgh*128+(16+y)*256,X1=16;X1;X1--,P+=8)
// {
// uint8 *C;
// register uint8 cc;
// uint32 vadr;
//
// vadr=((((16-X1)|((y>>3)<<4))<<4)|(y&7))+bgh*0x1000;
// //C= ROM+vadr+turt*8192;
// C = VRAMADR(vadr);
// //if((vadr+turt*8192)>=524288)
// //printf("%d ",vadr+turt*8192);
// cc=0;
// //#include "pputile.inc"
// }
//}
static void CheckSpriteHit ( int p ) ;
static void EndRL ( void )
{
RefreshLine ( 272 ) ;
if ( tofix )
Fixit1 ( ) ;
CheckSpriteHit ( 272 ) ;
Pline = 0 ;
}
static int32 sphitx ;
static uint8 sphitdata ;
static void CheckSpriteHit ( int p )
{
int l = p - 16 ;
int x ;
if ( sphitx = = 0x100 ) return ;
for ( x = sphitx ; x < ( sphitx + 8 ) & & x < l ; x + + )
{
if ( ( sphitdata & ( 0x80 > > ( x - sphitx ) ) ) & & ! ( Plinef [ x ] & 64 ) )
{
PPU_status | = 0x40 ;
//printf("Ha: %d, %d, Hita: %d, %d, %d, %d, %d\n",p,p&~7,scanline,GETLASTPIXEL-16,&Plinef[x],Pline,Pline-Plinef);
//printf("%d\n",GETLASTPIXEL-16);
//if(Plinef[x] == 0xFF)
//printf("PL: %d, %02x\n",scanline, Plinef[x]);
sphitx = 0x100 ;
break ;
}
}
}
//spork the world. Any sprites on this line? Then this will be set to 1.
//Needed for zapper emulation and *gasp* sprite emulation.
static int spork = 0 ;
// lasttile is really "second to last tile."
static void RefreshLine ( int lastpixel )
{
static uint32 pshift [ 2 ] ;
static uint32 atlatch ;
uint32 smorkus = RefreshAddr ;
# define RefreshAddr smorkus
uint32 vofs ;
int X1 ;
register uint8 * P = Pline ;
int lasttile = lastpixel > > 3 ;
int numtiles ;
static int norecurse = 0 ; /* Yeah, recursion would be bad.
PPU_hook ( ) functions can call
mirroring / chr bank switching functions ,
which call FCEUPPU_LineUpdate , which call this
function . */
if ( norecurse ) return ;
if ( sphitx ! = 0x100 & & ! ( PPU_status & 0x40 ) )
{
if ( ( sphitx < ( lastpixel - 16 ) ) & & ! ( sphitx < ( ( lasttile - 2 ) * 8 ) ) )
{
//printf("OK: %d\n",scanline);
lasttile + + ;
}
}
if ( lasttile > 34 ) lasttile = 34 ;
numtiles = lasttile - firsttile ;
if ( numtiles < = 0 ) return ;
P = Pline ;
vofs = 0 ;
vofs = ( ( PPU [ 0 ] & 0x10 ) < < 8 ) | ( ( RefreshAddr > > 12 ) & 7 ) ;
if ( ! ScreenON & & ! SpriteON )
{
uint32 tem ;
tem = Pal [ 0 ] | ( Pal [ 0 ] < < 8 ) | ( Pal [ 0 ] < < 16 ) | ( Pal [ 0 ] < < 24 ) ;
tem | = 0x40404040 ;
FCEU_dwmemset ( Pline , tem , numtiles * 8 ) ;
P + = numtiles * 8 ;
Pline = P ;
firsttile = lasttile ;
# define TOFIXNUM (272-0x4)
if ( lastpixel > = TOFIXNUM & & tofix )
{
Fixit1 ( ) ;
tofix = 0 ;
}
if ( ( lastpixel - 16 ) > = 0 )
{
InputScanlineHook ( Plinef , spork ? sprlinebuf : 0 , linestartts , lasttile * 8 - 16 ) ;
}
return ;
}
//Priority bits, needed for sprite emulation.
Pal [ 0 ] | = 64 ;
Pal [ 4 ] | = 64 ;
Pal [ 8 ] | = 64 ;
Pal [ 0xC ] | = 64 ;
//This high-level graphics MMC5 emulation code was written for MMC5 carts in "CL" mode.
//It's probably not totally correct for carts in "SL" mode.
# define PPUT_MMC5
if ( MMC5Hack & & geniestage ! = 1 )
{
if ( MMC5HackCHRMode = = 0 & & ( MMC5HackSPMode & 0x80 ) )
{
int tochange = MMC5HackSPMode & 0x1F ;
tochange - = firsttile ;
for ( X1 = firsttile ; X1 < lasttile ; X1 + + )
{
if ( ( tochange < = 0 & & MMC5HackSPMode & 0x40 ) | | ( tochange > 0 & & ! ( MMC5HackSPMode & 0x40 ) ) )
{
# define PPUT_MMC5SP
# include "pputile.inc"
# undef PPUT_MMC5SP
}
else
{
# include "pputile.inc"
}
tochange - - ;
}
}
else if ( MMC5HackCHRMode = = 1 & & ( MMC5HackSPMode & 0x80 ) )
{
int tochange = MMC5HackSPMode & 0x1F ;
tochange - = firsttile ;
# define PPUT_MMC5SP
# define PPUT_MMC5CHR1
for ( X1 = firsttile ; X1 < lasttile ; X1 + + )
{
# include "pputile.inc"
}
# undef PPUT_MMC5CHR1
# undef PPUT_MMC5SP
}
else if ( MMC5HackCHRMode = = 1 )
{
# define PPUT_MMC5CHR1
for ( X1 = firsttile ; X1 < lasttile ; X1 + + )
{
# include "pputile.inc"
}
# undef PPUT_MMC5CHR1
}
else
{
for ( X1 = firsttile ; X1 < lasttile ; X1 + + )
{
# include "pputile.inc"
}
}
}
# undef PPUT_MMC5
else if ( PPU_hook )
{
norecurse = 1 ;
# define PPUT_HOOK
for ( X1 = firsttile ; X1 < lasttile ; X1 + + )
{
# include "pputile.inc"
}
# undef PPUT_HOOK
norecurse = 0 ;
}
else
{
for ( X1 = firsttile ; X1 < lasttile ; X1 + + )
{
# include "pputile.inc"
}
}
# undef vofs
# undef RefreshAddr
//Reverse changes made before.
Pal [ 0 ] & = 63 ;
Pal [ 4 ] & = 63 ;
Pal [ 8 ] & = 63 ;
Pal [ 0xC ] & = 63 ;
RefreshAddr = smorkus ;
if ( firsttile < = 2 & & 2 < lasttile & & ! ( PPU [ 1 ] & 2 ) )
{
uint32 tem ;
tem = Pal [ 0 ] | ( Pal [ 0 ] < < 8 ) | ( Pal [ 0 ] < < 16 ) | ( Pal [ 0 ] < < 24 ) ;
tem | = 0x40404040 ;
* ( uint32 * ) Plinef = * ( uint32 * ) ( Plinef + 4 ) = tem ;
}
if ( ! ScreenON )
{
uint32 tem ;
int tstart , tcount ;
tem = Pal [ 0 ] | ( Pal [ 0 ] < < 8 ) | ( Pal [ 0 ] < < 16 ) | ( Pal [ 0 ] < < 24 ) ;
tem | = 0x40404040 ;
tcount = lasttile - firsttile ;
tstart = firsttile - 2 ;
if ( tstart < 0 )
{
tcount + = tstart ;
tstart = 0 ;
}
if ( tcount > 0 )
FCEU_dwmemset ( Plinef + tstart * 8 , tem , tcount * 8 ) ;
}
if ( lastpixel > = TOFIXNUM & & tofix )
{
//puts("Fixed");
Fixit1 ( ) ;
tofix = 0 ;
}
//CheckSpriteHit(lasttile*8); //lasttile*8); //lastpixel);
//This only works right because of a hack earlier in this function.
CheckSpriteHit ( lastpixel ) ;
if ( ( lastpixel - 16 ) > = 0 )
{
InputScanlineHook ( Plinef , spork ? sprlinebuf : 0 , linestartts , lasttile * 8 - 16 ) ;
}
Pline = P ;
firsttile = lasttile ;
}
static INLINE void Fixit2 ( void )
{
if ( ScreenON | | SpriteON )
{
uint32 rad = RefreshAddr ;
rad & = 0xFBE0 ;
rad | = TempAddr & 0x041f ;
RefreshAddr = rad ;
//PPU_hook(RefreshAddr);
//PPU_hook(RefreshAddr,-1);
}
}
static void Fixit1 ( void )
{
if ( ScreenON | | SpriteON )
{
uint32 rad = RefreshAddr ;
if ( ( rad & 0x7000 ) = = 0x7000 )
{
rad ^ = 0x7000 ;
if ( ( rad & 0x3E0 ) = = 0x3A0 )
{
rad ^ = 0x3A0 ;
rad ^ = 0x800 ;
}
else
{
if ( ( rad & 0x3E0 ) = = 0x3e0 )
rad ^ = 0x3e0 ;
else rad + = 0x20 ;
}
}
else
rad + = 0x1000 ;
RefreshAddr = rad ;
//PPU_hook(RefreshAddr); //,-1);
}
}
void MMC5_hb ( int ) ; //Ugh ugh ugh.
static void DoLine ( void )
{
int x ;
uint8 * target = XBuf + ( scanline < < 8 ) ;
if ( MMC5Hack & & ( ScreenON | | SpriteON ) ) MMC5_hb ( scanline ) ;
X6502_Run ( 256 ) ;
EndRL ( ) ;
if ( ! renderbg ) // User asked to not display background data.
{
uint32 tem ;
uint8 col ;
if ( gNoBGFillColor = = 0xFF )
col = Pal [ 0 ] ;
else col = gNoBGFillColor ;
tem = col | ( col < < 8 ) | ( col < < 16 ) | ( col < < 24 ) ;
tem | = 0x40404040 ;
FCEU_dwmemset ( target , tem , 256 ) ;
}
if ( SpriteON )
CopySprites ( target ) ;
if ( ScreenON | | SpriteON ) // Yes, very el-cheapo.
{
if ( PPU [ 1 ] & 0x01 )
{
for ( x = 63 ; x > = 0 ; x - - )
* ( uint32 * ) & target [ x < < 2 ] = ( * ( uint32 * ) & target [ x < < 2 ] ) & 0x30303030 ;
}
}
if ( ( PPU [ 1 ] > > 5 ) = = 0x7 )
{
for ( x = 63 ; x > = 0 ; x - - )
* ( uint32 * ) & target [ x < < 2 ] = ( ( * ( uint32 * ) & target [ x < < 2 ] ) & 0x3f3f3f3f ) | 0xc0c0c0c0 ;
}
else if ( PPU [ 1 ] & 0xE0 )
for ( x = 63 ; x > = 0 ; x - - )
* ( uint32 * ) & target [ x < < 2 ] = ( * ( uint32 * ) & target [ x < < 2 ] ) | 0x40404040 ;
else
for ( x = 63 ; x > = 0 ; x - - )
* ( uint32 * ) & target [ x < < 2 ] = ( ( * ( uint32 * ) & target [ x < < 2 ] ) & 0x3f3f3f3f ) | 0x80808080 ;
sphitx = 0x100 ;
if ( ScreenON | | SpriteON )
FetchSpriteData ( ) ;
if ( GameHBIRQHook & & ( ScreenON | | SpriteON ) & & ( ( PPU [ 0 ] & 0x38 ) ! = 0x18 ) )
{
X6502_Run ( 6 ) ;
Fixit2 ( ) ;
X6502_Run ( 4 ) ;
GameHBIRQHook ( ) ;
X6502_Run ( 85 - 16 - 10 ) ;
}
else
{
X6502_Run ( 6 ) ; // Tried 65, caused problems with Slalom(maybe others)
Fixit2 ( ) ;
X6502_Run ( 85 - 6 - 16 ) ;
// A semi-hack for Star Trek: 25th Anniversary
if ( GameHBIRQHook & & ( ScreenON | | SpriteON ) & & ( ( PPU [ 0 ] & 0x38 ) ! = 0x18 ) )
GameHBIRQHook ( ) ;
}
DEBUG ( FCEUD_UpdateNTView ( scanline , 0 ) ) ;
if ( SpriteON )
RefreshSprites ( ) ;
if ( GameHBIRQHook2 & & ( ScreenON | | SpriteON ) )
GameHBIRQHook2 ( ) ;
scanline + + ;
if ( scanline < 240 )
{
ResetRL ( XBuf + ( scanline < < 8 ) ) ;
}
X6502_Run ( 16 ) ;
}
# define V_FLIP 0x80
# define H_FLIP 0x40
# define SP_BACK 0x20
typedef struct {
uint8 y , no , atr , x ;
} SPR ;
typedef struct {
uint8 ca [ 2 ] , atr , x ;
} SPRB ;
void FCEUI_DisableSpriteLimitation ( int a )
{
maxsprites = a ? 64 : 8 ;
}
static uint8 numsprites , SpriteBlurp ;
static void FetchSpriteData ( void )
{
uint8 ns , sb ;
SPR * spr ;
uint8 H ;
int n ;
int vofs ;
uint8 P0 = PPU [ 0 ] ;
spr = ( SPR * ) SPRAM ;
H = 8 ;
ns = sb = 0 ;
vofs = ( unsigned int ) ( P0 & 0x8 & ( ( ( P0 & 0x20 ) ^ 0x20 ) > > 2 ) ) < < 9 ;
H + = ( P0 & 0x20 ) > > 2 ;
if ( ! PPU_hook )
for ( n = 63 ; n > = 0 ; n - - , spr + + )
{
if ( ( unsigned int ) ( scanline - spr - > y ) > = H ) continue ;
//printf("%d, %u\n",scanline,(unsigned int)(scanline-spr->y));
if ( ns < maxsprites )
{
if ( n = = 63 ) sb = 1 ;
{
SPRB dst ;
uint8 * C ;
int t ;
unsigned int vadr ;
t = ( int ) scanline - ( spr - > y ) ;
if ( Sprite16 )
vadr = ( ( spr - > no & 1 ) < < 12 ) + ( ( spr - > no & 0xFE ) < < 4 ) ;
else
vadr = ( spr - > no < < 4 ) + vofs ;
if ( spr - > atr & V_FLIP )
{
vadr + = 7 ;
vadr - = t ;
vadr + = ( P0 & 0x20 ) > > 1 ;
vadr - = t & 8 ;
}
else
{
vadr + = t ;
vadr + = t & 8 ;
}
/* Fix this geniestage hack */
if ( MMC5Hack & & geniestage ! = 1 ) C = MMC5SPRVRAMADR ( vadr ) ;
else C = VRAMADR ( vadr ) ;
dst . ca [ 0 ] = C [ 0 ] ;
dst . ca [ 1 ] = C [ 8 ] ;
dst . x = spr - > x ;
dst . atr = spr - > atr ;
* ( uint32 * ) & SPRBUF [ ns < < 2 ] = * ( uint32 * ) & dst ;
}
ns + + ;
}
else
{
PPU_status | = 0x20 ;
break ;
}
}
else
for ( n = 63 ; n > = 0 ; n - - , spr + + )
{
if ( ( unsigned int ) ( scanline - spr - > y ) > = H ) continue ;
if ( ns < maxsprites )
{
if ( n = = 63 ) sb = 1 ;
{
SPRB dst ;
uint8 * C ;
int t ;
unsigned int vadr ;
t = ( int ) scanline - ( spr - > y ) ;
if ( Sprite16 )
vadr = ( ( spr - > no & 1 ) < < 12 ) + ( ( spr - > no & 0xFE ) < < 4 ) ;
else
vadr = ( spr - > no < < 4 ) + vofs ;
if ( spr - > atr & V_FLIP )
{
vadr + = 7 ;
vadr - = t ;
vadr + = ( P0 & 0x20 ) > > 1 ;
vadr - = t & 8 ;
}
else
{
vadr + = t ;
vadr + = t & 8 ;
}
if ( MMC5Hack ) C = MMC5SPRVRAMADR ( vadr ) ;
else C = VRAMADR ( vadr ) ;
dst . ca [ 0 ] = C [ 0 ] ;
if ( ns < 8 )
{
PPU_hook ( 0x2000 ) ;
PPU_hook ( vadr ) ;
}
dst . ca [ 1 ] = C [ 8 ] ;
dst . x = spr - > x ;
dst . atr = spr - > atr ;
* ( uint32 * ) & SPRBUF [ ns < < 2 ] = * ( uint32 * ) & dst ;
}
ns + + ;
}
else
{
PPU_status | = 0x20 ;
break ;
}
}
//if(ns>=7)
//printf("%d %d\n",scanline,ns);
//Handle case when >8 sprites per scanline option is enabled.
if ( ns > 8 ) PPU_status | = 0x20 ;
else if ( PPU_hook )
{
for ( n = 0 ; n < ( 8 - ns ) ; n + + )
{
PPU_hook ( 0x2000 ) ;
PPU_hook ( vofs ) ;
}
}
numsprites = ns ;
SpriteBlurp = sb ;
}
static void RefreshSprites ( void )
{
int n ;
SPRB * spr ;
spork = 0 ;
if ( ! numsprites ) return ;
FCEU_dwmemset ( sprlinebuf , 0x80808080 , 256 ) ;
numsprites - - ;
spr = ( SPRB * ) SPRBUF + numsprites ;
for ( n = numsprites ; n > = 0 ; n - - , spr - - )
{
uint32 pixdata ;
uint8 J , atr ;
int x = spr - > x ;
uint8 * C ;
uint8 * VB ;
pixdata = ppulut1 [ spr - > ca [ 0 ] ] | ppulut2 [ spr - > ca [ 1 ] ] ;
J = spr - > ca [ 0 ] | spr - > ca [ 1 ] ;
atr = spr - > atr ;
if ( J )
{
if ( n = = 0 & & SpriteBlurp & & ! ( PPU_status & 0x40 ) )
{
sphitx = x ;
sphitdata = J ;
if ( atr & H_FLIP )
sphitdata = ( ( J < < 7 ) & 0x80 ) |
( ( J < < 5 ) & 0x40 ) |
( ( J < < 3 ) & 0x20 ) |
( ( J < < 1 ) & 0x10 ) |
( ( J > > 1 ) & 0x08 ) |
( ( J > > 3 ) & 0x04 ) |
( ( J > > 5 ) & 0x02 ) |
( ( J > > 7 ) & 0x01 ) ;
}
C = sprlinebuf + x ;
VB = ( PALRAM + 0x10 ) + ( ( atr & 3 ) < < 2 ) ;
if ( atr & SP_BACK )
{
if ( atr & H_FLIP )
{
if ( J & 0x80 ) C [ 7 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x40 ) C [ 6 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x20 ) C [ 5 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x10 ) C [ 4 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x08 ) C [ 3 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x04 ) C [ 2 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x02 ) C [ 1 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x01 ) C [ 0 ] = VB [ pixdata ] | 0x40 ;
} else {
if ( J & 0x80 ) C [ 0 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x40 ) C [ 1 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x20 ) C [ 2 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x10 ) C [ 3 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x08 ) C [ 4 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x04 ) C [ 5 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x02 ) C [ 6 ] = VB [ pixdata & 3 ] | 0x40 ;
pixdata > > = 4 ;
if ( J & 0x01 ) C [ 7 ] = VB [ pixdata ] | 0x40 ;
}
} else {
if ( atr & H_FLIP )
{
if ( J & 0x80 ) C [ 7 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x40 ) C [ 6 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x20 ) C [ 5 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x10 ) C [ 4 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x08 ) C [ 3 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x04 ) C [ 2 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x02 ) C [ 1 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x01 ) C [ 0 ] = VB [ pixdata ] ;
} else {
if ( J & 0x80 ) C [ 0 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x40 ) C [ 1 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x20 ) C [ 2 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x10 ) C [ 3 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x08 ) C [ 4 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x04 ) C [ 5 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x02 ) C [ 6 ] = VB [ pixdata & 3 ] ;
pixdata > > = 4 ;
if ( J & 0x01 ) C [ 7 ] = VB [ pixdata ] ;
}
}
}
}
SpriteBlurp = 0 ;
spork = 1 ;
}
static void CopySprites ( uint8 * target )
{
uint8 n = ( ( PPU [ 1 ] & 4 ) ^ 4 ) < < 1 ;
uint8 * P = target ;
if ( ! spork ) return ;
spork = 0 ;
if ( ! rendersprites ) return ; //User asked to not display sprites.
loopskie :
{
uint32 t = * ( uint32 * ) ( sprlinebuf + n ) ;
if ( t ! = 0x80808080 )
{
# ifdef LSB_FIRST
if ( ! ( t & 0x80 ) )
{
if ( ! ( t & 0x40 ) | | ( P [ n ] & 0x40 ) ) // Normal sprite || behind bg sprite
P [ n ] = sprlinebuf [ n ] ;
}
if ( ! ( t & 0x8000 ) )
{
if ( ! ( t & 0x4000 ) | | ( P [ n + 1 ] & 0x40 ) ) // Normal sprite || behind bg sprite
P [ n + 1 ] = ( sprlinebuf + 1 ) [ n ] ;
}
if ( ! ( t & 0x800000 ) )
{
if ( ! ( t & 0x400000 ) | | ( P [ n + 2 ] & 0x40 ) ) // Normal sprite || behind bg sprite
P [ n + 2 ] = ( sprlinebuf + 2 ) [ n ] ;
}
if ( ! ( t & 0x80000000 ) )
{
if ( ! ( t & 0x40000000 ) | | ( P [ n + 3 ] & 0x40 ) ) // Normal sprite || behind bg sprite
P [ n + 3 ] = ( sprlinebuf + 3 ) [ n ] ;
}
# else
/* TODO: Simplify */
if ( ! ( t & 0x80000000 ) )
{
if ( ! ( t & 0x40000000 ) ) // Normal sprite
P [ n ] = sprlinebuf [ n ] ;
else if ( P [ n ] & 64 ) // behind bg sprite
P [ n ] = sprlinebuf [ n ] ;
}
if ( ! ( t & 0x800000 ) )
{
if ( ! ( t & 0x400000 ) ) // Normal sprite
P [ n + 1 ] = ( sprlinebuf + 1 ) [ n ] ;
else if ( P [ n + 1 ] & 64 ) // behind bg sprite
P [ n + 1 ] = ( sprlinebuf + 1 ) [ n ] ;
}
if ( ! ( t & 0x8000 ) )
{
if ( ! ( t & 0x4000 ) ) // Normal sprite
P [ n + 2 ] = ( sprlinebuf + 2 ) [ n ] ;
else if ( P [ n + 2 ] & 64 ) // behind bg sprite
P [ n + 2 ] = ( sprlinebuf + 2 ) [ n ] ;
}
if ( ! ( t & 0x80 ) )
{
if ( ! ( t & 0x40 ) ) // Normal sprite
P [ n + 3 ] = ( sprlinebuf + 3 ) [ n ] ;
else if ( P [ n + 3 ] & 64 ) // behind bg sprite
P [ n + 3 ] = ( sprlinebuf + 3 ) [ n ] ;
}
# endif
}
}
n + = 4 ;
if ( n ) goto loopskie ;
}
void FCEUPPU_SetVideoSystem ( int w )
{
if ( w )
{
scanlines_per_frame = 312 ;
FSettings . FirstSLine = FSettings . UsrFirstSLine [ 1 ] ;
FSettings . LastSLine = FSettings . UsrLastSLine [ 1 ] ;
}
else
{
scanlines_per_frame = 262 ;
FSettings . FirstSLine = FSettings . UsrFirstSLine [ 0 ] ;
FSettings . LastSLine = FSettings . UsrLastSLine [ 0 ] ;
}
}
//Initializes the PPU
void FCEUPPU_Init ( void )
{
makeppulut ( ) ;
}
2009-09-15 10:20:48 +02:00
void PPU_ResetHooks ( )
{
FFCEUX_PPURead = FFCEUX_PPURead_Default ;
}
2009-07-17 19:27:04 +02:00
void FCEUPPU_Reset ( void )
{
VRAMBuffer = PPU [ 0 ] = PPU [ 1 ] = PPU_status = PPU [ 3 ] = 0 ;
PPUSPL = 0 ;
PPUGenLatch = 0 ;
RefreshAddr = TempAddr = 0 ;
vtoggle = 0 ;
ppudead = 2 ;
kook = 0 ;
2009-09-15 10:20:48 +02:00
idleSynch = 1 ;
2009-07-17 19:27:04 +02:00
// XOffset=0;
2009-09-15 10:20:48 +02:00
ppur . reset ( ) ;
spr_read . reset ( ) ;
2009-07-17 19:27:04 +02:00
}
void FCEUPPU_Power ( void )
{
int x ;
memset ( NTARAM , 0x00 , 0x800 ) ;
2009-07-18 00:54:58 +02:00
memset ( PALRAM , 0x00 , 0x20 ) ;
memset ( UPALRAM , 0x00 , 0x03 ) ;
2009-07-17 19:27:04 +02:00
memset ( SPRAM , 0x00 , 0x100 ) ;
FCEUPPU_Reset ( ) ;
for ( x = 0x2000 ; x < 0x4000 ; x + = 8 )
{
ARead [ x ] = A200x ;
BWrite [ x ] = B2000 ;
ARead [ x + 1 ] = A200x ;
BWrite [ x + 1 ] = B2001 ;
ARead [ x + 2 ] = A2002 ;
BWrite [ x + 2 ] = B2002 ;
ARead [ x + 3 ] = A200x ;
BWrite [ x + 3 ] = B2003 ;
2009-07-18 00:54:58 +02:00
ARead [ x + 4 ] = A2004 ; //A2004;
2009-07-17 19:27:04 +02:00
BWrite [ x + 4 ] = B2004 ;
ARead [ x + 5 ] = A200x ;
BWrite [ x + 5 ] = B2005 ;
ARead [ x + 6 ] = A200x ;
BWrite [ x + 6 ] = B2006 ;
ARead [ x + 7 ] = A2007 ;
BWrite [ x + 7 ] = B2007 ;
}
BWrite [ 0x4014 ] = B4014 ;
}
int FCEUPPU_Loop ( int skip )
{
2009-09-15 10:20:48 +02:00
if ( ( newppu ) & & ( GameInfo - > type ! = GIT_NSF ) ) {
2009-07-17 19:27:04 +02:00
int FCEUX_PPU_Loop ( int skip ) ;
return FCEUX_PPU_Loop ( skip ) ;
}
//Needed for Knight Rider, possibly others.
if ( ppudead )
{
memset ( XBuf , 0x80 , 256 * 240 ) ;
X6502_Run ( scanlines_per_frame * ( 256 + 85 ) ) ;
ppudead - - ;
}
else
{
X6502_Run ( 256 + 85 ) ;
PPU_status | = 0x80 ;
//Not sure if this is correct. According to Matt Conte and my own tests, it is.
//Timing is probably off, though.
//NOTE: Not having this here breaks a Super Donkey Kong game.
PPU [ 3 ] = PPUSPL = 0 ;
//I need to figure out the true nature and length of this delay.
X6502_Run ( 12 ) ;
if ( GameInfo - > type = = GIT_NSF )
DoNSFFrame ( ) ;
else
{
if ( VBlankON )
TriggerNMI ( ) ;
}
X6502_Run ( ( scanlines_per_frame - 242 ) * ( 256 + 85 ) - 12 ) ; //-12);
PPU_status & = 0x1f ;
X6502_Run ( 256 ) ;
{
int x ;
if ( ScreenON | | SpriteON )
{
if ( GameHBIRQHook & & ( ( PPU [ 0 ] & 0x38 ) ! = 0x18 ) )
GameHBIRQHook ( ) ;
if ( PPU_hook )
for ( x = 0 ; x < 42 ; x + + ) { PPU_hook ( 0x2000 ) ; PPU_hook ( 0 ) ; }
if ( GameHBIRQHook2 )
GameHBIRQHook2 ( ) ;
}
X6502_Run ( 85 - 16 ) ;
if ( ScreenON | | SpriteON )
{
RefreshAddr = TempAddr ;
if ( PPU_hook ) PPU_hook ( RefreshAddr & 0x3fff ) ;
}
//Clean this stuff up later.
spork = numsprites = 0 ;
ResetRL ( XBuf ) ;
X6502_Run ( 16 - kook ) ;
kook ^ = 1 ;
}
if ( GameInfo - > type = = GIT_NSF )
X6502_Run ( ( 256 + 85 ) * 240 ) ;
# ifdef FRAMESKIP
else if ( skip )
{
int y ;
y = SPRAM [ 0 ] ;
y + + ;
PPU_status | = 0x20 ; // Fixes "Bee 52". Does it break anything?
if ( GameHBIRQHook )
{
X6502_Run ( 256 ) ;
for ( scanline = 0 ; scanline < 240 ; scanline + + )
{
if ( ScreenON | | SpriteON )
GameHBIRQHook ( ) ;
if ( scanline = = y & & SpriteON ) PPU_status | = 0x40 ;
X6502_Run ( ( scanline = = 239 ) ? 85 : ( 256 + 85 ) ) ;
}
}
else if ( y < 240 )
{
X6502_Run ( ( 256 + 85 ) * y ) ;
if ( SpriteON ) PPU_status | = 0x40 ; // Quick and very dirty hack.
X6502_Run ( ( 256 + 85 ) * ( 240 - y ) ) ;
}
else
X6502_Run ( ( 256 + 85 ) * 240 ) ;
}
# endif
else
{
int x , max , maxref ;
deemp = PPU [ 1 ] > > 5 ;
for ( scanline = 0 ; scanline < 240 ; ) //scanline is incremented in DoLine. Evil. :/
{
deempcnt [ deemp ] + + ;
DEBUG ( FCEUD_UpdatePPUView ( scanline , 1 ) ) ;
DoLine ( ) ;
}
if ( MMC5Hack & & ( ScreenON | | SpriteON ) ) MMC5_hb ( scanline ) ;
for ( x = 1 , max = 0 , maxref = 0 ; x < 7 ; x + + )
{
if ( deempcnt [ x ] > max )
{
max = deempcnt [ x ] ;
maxref = x ;
}
deempcnt [ x ] = 0 ;
}
//FCEU_DispMessage("%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x %d",deempcnt[0],deempcnt[1],deempcnt[2],deempcnt[3],deempcnt[4],deempcnt[5],deempcnt[6],deempcnt[7],maxref);
//memset(deempcnt,0,sizeof(deempcnt));
SetNESDeemph ( maxref , 0 ) ;
}
} //else... to if(ppudead)
# ifdef FRAMESKIP
if ( skip )
{
FCEU_PutImageDummy ( ) ;
return ( 0 ) ;
}
else
# endif
{
//mbg 6/21/08 - tileview is being ripped out since i dont know how long its been since it worked
//if(tileview) TileView();
FCEU_PutImage ( ) ;
return ( 1 ) ;
}
}
int ( * PPU_MASTER ) ( int skip ) = FCEUPPU_Loop ;
static uint16 TempAddrT , RefreshAddrT ;
void FCEUPPU_LoadState ( int version )
{
TempAddr = TempAddrT ;
RefreshAddr = RefreshAddrT ;
}
SFORMAT FCEUPPU_STATEINFO [ ] = {
{ NTARAM , 0x800 , " NTAR " } ,
{ PALRAM , 0x20 , " PRAM " } ,
{ SPRAM , 0x100 , " SPRA " } ,
{ PPU , 0x4 , " PPUR " } ,
{ & kook , 1 , " KOOK " } ,
{ & ppudead , 1 , " DEAD " } ,
{ & PPUSPL , 1 , " PSPL " } ,
{ & XOffset , 1 , " XOFF " } ,
{ & vtoggle , 1 , " VTOG " } ,
{ & RefreshAddrT , 2 | FCEUSTATE_RLSB , " RADD " } ,
{ & TempAddrT , 2 | FCEUSTATE_RLSB , " TADD " } ,
{ & VRAMBuffer , 1 , " VBUF " } ,
{ & PPUGenLatch , 1 , " PGEN " } ,
{ 0 }
} ;
2009-09-15 10:20:48 +02:00
SFORMAT FCEU_NEWPPU_STATEINFO [ ] = {
{ & idleSynch , 1 , " IDLS " } ,
{ & spr_read . num , 4 | FCEUSTATE_RLSB , " SR_0 " } ,
{ & spr_read . count , 4 | FCEUSTATE_RLSB , " SR_1 " } ,
{ & spr_read . fetch , 4 | FCEUSTATE_RLSB , " SR_2 " } ,
{ & spr_read . found , 4 | FCEUSTATE_RLSB , " SR_3 " } ,
{ & spr_read . found_pos [ 0 ] , 4 | FCEUSTATE_RLSB , " SRx0 " } ,
{ & spr_read . found_pos [ 0 ] , 4 | FCEUSTATE_RLSB , " SRx1 " } ,
{ & spr_read . found_pos [ 0 ] , 4 | FCEUSTATE_RLSB , " SRx2 " } ,
{ & spr_read . found_pos [ 0 ] , 4 | FCEUSTATE_RLSB , " SRx3 " } ,
{ & spr_read . found_pos [ 0 ] , 4 | FCEUSTATE_RLSB , " SRx4 " } ,
{ & spr_read . found_pos [ 0 ] , 4 | FCEUSTATE_RLSB , " SRx5 " } ,
{ & spr_read . found_pos [ 0 ] , 4 | FCEUSTATE_RLSB , " SRx6 " } ,
{ & spr_read . found_pos [ 0 ] , 4 | FCEUSTATE_RLSB , " SRx7 " } ,
{ & spr_read . ret , 4 | FCEUSTATE_RLSB , " SR_4 " } ,
{ & spr_read . last , 4 | FCEUSTATE_RLSB , " SR_5 " } ,
{ & spr_read . mode , 4 | FCEUSTATE_RLSB , " SR_6 " } ,
{ & ppur . fv , 4 | FCEUSTATE_RLSB , " PFVx " } ,
{ & ppur . v , 4 | FCEUSTATE_RLSB , " PVxx " } ,
{ & ppur . h , 4 | FCEUSTATE_RLSB , " PHxx " } ,
{ & ppur . vt , 4 | FCEUSTATE_RLSB , " PVTx " } ,
{ & ppur . ht , 4 | FCEUSTATE_RLSB , " PHTx " } ,
{ & ppur . _fv , 4 | FCEUSTATE_RLSB , " P_FV " } ,
{ & ppur . _v , 4 | FCEUSTATE_RLSB , " P_Vx " } ,
{ & ppur . _h , 4 | FCEUSTATE_RLSB , " P_Hx " } ,
{ & ppur . _vt , 4 | FCEUSTATE_RLSB , " P_VT " } ,
{ & ppur . _ht , 4 | FCEUSTATE_RLSB , " P_HT " } ,
{ & ppur . fh , 4 | FCEUSTATE_RLSB , " PFHx " } ,
{ & ppur . s , 4 | FCEUSTATE_RLSB , " PSxx " } ,
{ & ppur . status . sl , 4 | FCEUSTATE_RLSB , " PST0 " } ,
{ & ppur . status . cycle , 4 | FCEUSTATE_RLSB , " PST1 " } ,
{ & ppur . status . end_cycle , 4 | FCEUSTATE_RLSB , " PST2 " } ,
{ 0 }
} ;
2009-07-17 19:27:04 +02:00
void FCEUPPU_SaveState ( void )
{
TempAddrT = TempAddr ;
RefreshAddrT = RefreshAddr ;
}
//---------------------
int pputime = 0 ;
int totpputime = 0 ;
const int kLineTime = 341 ;
const int kFetchTime = 2 ;
void runppu ( int x ) {
//pputime+=x;
//if(cputodo<200) return;
2009-09-15 10:20:48 +02:00
2009-07-18 00:54:58 +02:00
ppur . status . cycle = ( ppur . status . cycle + x ) %
ppur . status . end_cycle ;
2009-09-15 10:20:48 +02:00
2009-07-17 19:27:04 +02:00
X6502_Run ( x ) ;
//pputime -= cputodo<<2;
}
//todo - consider making this a 3 or 4 slot fifo to keep from touching so much memory
struct BGData {
struct Record {
uint8 nt , at , pt [ 2 ] ;
2009-09-15 10:20:48 +02:00
INLINE void Read ( ) {
2009-07-17 19:27:04 +02:00
RefreshAddr = ppur . get_ntread ( ) ;
nt = CALL_PPUREAD ( RefreshAddr ) ;
runppu ( kFetchTime ) ;
RefreshAddr = ppur . get_atread ( ) ;
at = CALL_PPUREAD ( RefreshAddr ) ;
2009-07-18 00:54:58 +02:00
2009-07-17 19:27:04 +02:00
//modify at to get appropriate palette shift
if ( ppur . vt & 2 ) at > > = 4 ;
if ( ppur . ht & 2 ) at > > = 2 ;
at & = 0x03 ;
at < < = 2 ;
2009-07-18 00:54:58 +02:00
//horizontal scroll clocked at cycle 3 and then
//vertical scroll at 251
runppu ( 1 ) ;
if ( PPUON )
{
ppur . increment_hsc ( ) ;
if ( ppur . status . cycle = = 251 )
ppur . increment_vs ( ) ;
}
runppu ( 1 ) ;
ppur . par = nt ;
2009-07-17 19:27:04 +02:00
RefreshAddr = ppur . get_ptread ( ) ;
pt [ 0 ] = CALL_PPUREAD ( RefreshAddr ) ;
runppu ( kFetchTime ) ;
RefreshAddr | = 8 ;
pt [ 1 ] = CALL_PPUREAD ( RefreshAddr ) ;
runppu ( kFetchTime ) ;
}
} ;
Record main [ 34 ] ; //one at the end is junk, it can never be rendered
} bgdata ;
int framectr = 0 ;
int FCEUX_PPU_Loop ( int skip ) {
//262 scanlines
2009-07-18 00:54:58 +02:00
if ( ppudead )
{
/* not quite emulating all the NES power up behavior
* since it is known that the NES ignores writes to some
* register before around a full frame , but no games
* should write to those regs during that time , it needs
* to wait for vblank */
ppur . status . sl = 241 ;
if ( PAL )
runppu ( 70 * kLineTime ) ;
else
runppu ( 20 * kLineTime ) ;
ppur . status . sl = 0 ;
runppu ( 242 * kLineTime ) ;
2009-09-15 10:20:48 +02:00
- - ppudead ;
2009-07-18 00:54:58 +02:00
goto finish ;
}
2009-07-17 19:27:04 +02:00
{
PPU_status | = 0x80 ;
ppuphase = PPUPHASE_VBL ;
//Not sure if this is correct. According to Matt Conte and my own tests, it is.
//Timing is probably off, though.
//NOTE: Not having this here breaks a Super Donkey Kong game.
2009-07-18 00:54:58 +02:00
PPU [ 3 ] = PPUSPL = 0 ;
2009-07-17 19:27:04 +02:00
const int delay = 20 ; //fceu used 12 here but I couldnt get it to work in marble madness and pirates.
2009-07-18 00:54:58 +02:00
ppur . status . sl = 241 ; //for sprite reads
runppu ( delay ) ; //X6502_Run(12);
2009-07-17 19:27:04 +02:00
if ( VBlankON ) TriggerNMI ( ) ;
2009-07-18 00:54:58 +02:00
if ( PAL )
runppu ( 70 * ( kLineTime ) - delay ) ;
else
runppu ( 20 * ( kLineTime ) - delay ) ;
2009-07-17 19:27:04 +02:00
//this seems to run just before the dummy scanline begins
2009-07-18 00:54:58 +02:00
PPU_status = 0 ;
2009-07-17 19:27:04 +02:00
//this early out caused metroid to fail to boot. I am leaving it here as a reminder of what not to do
//if(!PPUON) { runppu(kLineTime*242); goto finish; }
//There are 2 conditions that update all 5 PPU scroll counters with the
//contents of the latches adjacent to them. The first is after a write to
//2006/2. The second, is at the beginning of scanline 20, when the PPU starts
//rendering data for the first time in a frame (this update won't happen if
//all rendering is disabled via 2001.3 and 2001.4).
2009-07-18 00:54:58 +02:00
//if(PPUON)
// ppur.install_latches();
2009-07-17 19:27:04 +02:00
2009-09-15 10:20:48 +02:00
static uint8 oams [ 2 ] [ 64 ] [ 8 ] ; //[7] turned to [8] for faster indexing
static int oamcounts [ 2 ] = { 0 , 0 } ;
static int oamslot = 0 ;
static int oamcount ;
2009-07-17 19:27:04 +02:00
//capture the initial xscroll
//int xscroll = ppur.fh;
//render 241 scanlines (including 1 dummy at beginning)
for ( int sl = 0 ; sl < 241 ; sl + + ) {
2009-09-15 10:20:48 +02:00
spr_read . start_scanline ( ) ;
2009-07-18 00:54:58 +02:00
ppur . status . sl = sl ;
2009-07-17 19:27:04 +02:00
2009-09-15 10:20:48 +02:00
const int yp = sl - 1 ;
2009-07-17 19:27:04 +02:00
ppuphase = PPUPHASE_BG ;
if ( sl ! = 0 ) {
DEBUG ( FCEUD_UpdatePPUView ( scanline = yp , 1 ) ) ;
DEBUG ( FCEUD_UpdateNTView ( scanline = yp , 1 ) ) ;
}
if ( sl ! = 0 ) if ( MMC5Hack & & PPUON ) MMC5_hb ( yp ) ;
//twiddle the oam buffers
2009-09-15 10:20:48 +02:00
const int scanslot = oamslot ^ 1 ;
const int renderslot = oamslot ;
2009-07-17 19:27:04 +02:00
oamslot ^ = 1 ;
oamcount = oamcounts [ renderslot ] ;
//the main scanline rendering loop:
//32 times, we will fetch a tile and then render 8 pixels.
//two of those tiles were read in the last scanline.
for ( int xt = 0 ; xt < 32 ; xt + + ) {
bgdata . main [ xt + 2 ] . Read ( ) ;
2009-09-15 10:20:48 +02:00
2009-07-18 00:54:58 +02:00
//ok, we're also going to draw here.
2009-07-17 19:27:04 +02:00
//unless we're on the first dummy scanline
if ( sl ! = 0 ) {
int xstart = xt < < 3 ;
oamcount = oamcounts [ renderslot ] ;
2009-09-15 10:20:48 +02:00
uint8 * const target = XBuf + ( yp < < 8 ) + xstart ;
2009-07-17 19:27:04 +02:00
uint8 * ptr = target ;
int rasterpos = xstart ;
//check all the conditions that can cause things to render in these 8px
2009-09-15 10:20:48 +02:00
const bool renderspritenow = SpriteON & & rendersprites & & ( xt > 0 | | SpriteLeft8 ) ;
const bool renderbgnow = ScreenON & & renderbg & & ( xt > 0 | | BGLeft8 ) ;
2009-07-17 19:27:04 +02:00
for ( int xp = 0 ; xp < 8 ; xp + + , rasterpos + + ) {
//bg pos is different from raster pos due to its offsetability.
//so adjust for that here
2009-09-15 10:20:48 +02:00
const int bgpos = rasterpos + ppur . fh ;
const int bgpx = bgpos & 7 ;
const int bgtile = bgpos > > 3 ;
2009-07-17 19:27:04 +02:00
uint8 pixel = 0 , pixelcolor ;
//generate the BG data
if ( renderbgnow )
{
uint8 * pt = bgdata . main [ bgtile ] . pt ;
pixel = ( ( pt [ 0 ] > > ( 7 - bgpx ) ) & 1 ) | ( ( ( pt [ 1 ] > > ( 7 - bgpx ) ) & 1 ) < < 1 ) | bgdata . main [ bgtile ] . at ;
}
pixelcolor = PALRAM [ pixel ] ;
//look for a sprite to be drawn
bool havepixel = false ;
for ( int s = 0 ; s < oamcount ; s + + ) {
uint8 * oam = oams [ renderslot ] [ s ] ;
int x = oam [ 3 ] ;
if ( rasterpos > = x & & rasterpos < x + 8 ) {
//build the pixel.
//fetch the LSB of the patterns
uint8 spixel = oam [ 4 ] & 1 ;
spixel | = ( oam [ 5 ] & 1 ) < < 1 ;
//shift down the patterns so the next pixel is in the LSB
oam [ 4 ] > > = 1 ;
oam [ 5 ] > > = 1 ;
if ( ! renderspritenow ) continue ;
//bail out if we already have a pixel from a higher priority sprite
if ( havepixel ) continue ;
//transparent pixel bailout
if ( spixel = = 0 ) continue ;
//spritehit:
//1. is it sprite#0?
//2. is the bg pixel nonzero?
//then, it is spritehit.
if ( oam [ 6 ] = = 0 & & pixel ! = 0 )
2009-10-22 04:44:03 +02:00
{
2009-07-17 19:27:04 +02:00
PPU_status | = 0x40 ;
2009-10-22 04:44:03 +02:00
}
2009-07-17 19:27:04 +02:00
havepixel = true ;
//priority handling
if ( oam [ 2 ] & 0x20 ) {
//behind background:
if ( ( pixel & 3 ) ! = 0 ) continue ;
}
//bring in the palette bits and palettize
spixel | = ( oam [ 2 ] & 3 ) < < 2 ;
pixelcolor = PALRAM [ 0x10 + spixel ] ;
}
}
//fceu rendering system requires that this be set
//(so that it knows there is a valid pixel there?)
pixelcolor | = 0x80 ;
* ptr + + = pixelcolor ;
}
}
}
//look for sprites (was supposed to run concurrent with bg rendering)
oamcounts [ scanslot ] = 0 ;
oamcount = 0 ;
2009-09-15 10:20:48 +02:00
const int spriteHeight = Sprite16 ? 16 : 8 ;
2009-07-17 19:27:04 +02:00
for ( int i = 0 ; i < 64 ; i + + ) {
uint8 * spr = SPRAM + i * 4 ;
if ( yp > = spr [ 0 ] & & yp < spr [ 0 ] + spriteHeight ) {
//if we already have maxsprites, then this new one causes an overflow,
//set the flag and bail out.
if ( oamcount = = maxsprites ) {
PPU_status | = 0x20 ;
break ;
}
//just copy some bytes into the internal sprite buffer
for ( int j = 0 ; j < 4 ; j + + )
oams [ scanslot ] [ oamcount ] [ j ] = spr [ j ] ;
//note that we stuff the oam index into [6].
//i need to turn this into a struct so we can have fewer magic numbers
oams [ scanslot ] [ oamcount ] [ 6 ] = ( uint8 ) i ;
oamcount + + ;
}
}
oamcounts [ scanslot ] = oamcount ;
//FV is clocked by the PPU's horizontal blanking impulse, and therefore will increment every scanline.
//well, according to (which?) tests, maybe at the end of hblank.
//but, according to what it took to get crystalis working, it is at the beginning of hblank.
2009-07-18 00:54:58 +02:00
//this is done at cycle 251
//rendering scanline, it doesn't need to be scanline 0,
//because on the first scanline when the increment is 0, the vs_scroll is reloaded.
//if(PPUON && sl != 0)
// ppur.increment_vs();
2009-07-17 19:27:04 +02:00
//todo - think about clearing oams to a predefined value to force deterministic behavior
//so.. this is the end of hblank. latch horizontal scroll values
2009-07-18 00:54:58 +02:00
//do it cycle at 251
if ( PPUON & & sl ! = 0 )
ppur . install_h_latches ( ) ;
2009-07-17 19:27:04 +02:00
ppuphase = PPUPHASE_OBJ ;
//fetch sprite patterns
for ( int s = 0 ; s < maxsprites ; s + + ) {
//if we have hit our eight sprite pattern and we dont have any more sprites, then bail
if ( s = = oamcount & & s > = 8 )
break ;
//if this is a real sprite sprite, then it is not above the 8 sprite limit.
//this is how we support the no 8 sprite limit feature.
//not that at some point we may need a virtual CALL_PPUREAD which just peeks and doesnt increment any counters
//this could be handy for the debugging tools also
2009-09-15 10:20:48 +02:00
const bool realSprite = ( s < 8 ) ;
2009-07-17 19:27:04 +02:00
2009-09-15 10:20:48 +02:00
uint8 * const oam = oams [ scanslot ] [ s ] ;
2009-07-17 19:27:04 +02:00
uint32 line = yp - oam [ 0 ] ;
if ( oam [ 2 ] & 0x80 ) //vflip
line = spriteHeight - line - 1 ;
uint32 patternNumber = oam [ 1 ] ;
uint32 patternAddress ;
//8x16 sprite handling:
if ( Sprite16 ) {
uint32 bank = ( patternNumber & 1 ) < < 12 ;
patternNumber = patternNumber & ~ 1 ;
patternNumber | = ( line > > 3 ) ;
patternAddress = ( patternNumber < < 4 ) | bank ;
} else {
patternAddress = ( patternNumber < < 4 ) | ( SpAdrHI < < 9 ) ;
}
//offset into the pattern for the current line.
//tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above.
//so we just need the line offset for the second pattern
patternAddress + = line & 7 ;
//garbage nametable fetches
2009-07-18 00:54:58 +02:00
//reset the scroll counter, happens at cycle 304
if ( realSprite )
{
if ( ( sl = = 0 ) & & PPUON )
{
if ( ppur . status . cycle = = 304 )
{
runppu ( 1 ) ;
ppur . install_latches ( ) ;
runppu ( 1 ) ;
}
else
runppu ( kFetchTime ) ;
}
else
runppu ( kFetchTime ) ;
}
2009-10-22 04:44:03 +02:00
//Dragon's Lair (Europe version mapper 4)
//does not set SpriteON in the beginning but it does
//set the bg on so if using the conditional SpriteON the MMC3 counter
//the counter will never count and no IRQs will be fired so use PPUON
if ( ( ( PPU [ 0 ] & 0x38 ) ! = 0x18 ) & & s = = 2 & & PPUON ) { //SpriteON ) {
2009-07-17 19:27:04 +02:00
//(The MMC3 scanline counter is based entirely on PPU A12, triggered on rising edges (after the line remains low for a sufficiently long period of time))
//http://nesdevwiki.org/wiki/index.php/Nintendo_MMC3
//test cases for timing: SMB3, Crystalis
//crystalis requires deferring this til somewhere in sprite [1,3]
//kirby requires deferring this til somewhere in sprite [2,5..
2009-10-22 04:44:03 +02:00
//if (PPUON && GameHBIRQHook) {
if ( GameHBIRQHook ) {
2009-07-17 19:27:04 +02:00
GameHBIRQHook ( ) ;
}
}
if ( realSprite ) runppu ( kFetchTime ) ;
2009-07-18 00:54:58 +02:00
2009-07-17 19:27:04 +02:00
//pattern table fetches
RefreshAddr = patternAddress ;
oam [ 4 ] = CALL_PPUREAD ( RefreshAddr ) ;
if ( realSprite ) runppu ( kFetchTime ) ;
2009-07-18 00:54:58 +02:00
2009-07-17 19:27:04 +02:00
RefreshAddr + = 8 ;
oam [ 5 ] = CALL_PPUREAD ( RefreshAddr ) ;
if ( realSprite ) runppu ( kFetchTime ) ;
//hflip
if ( ! ( oam [ 2 ] & 0x40 ) ) {
oam [ 4 ] = bitrevlut [ oam [ 4 ] ] ;
oam [ 5 ] = bitrevlut [ oam [ 5 ] ] ;
}
}
ppuphase = PPUPHASE_BG ;
//fetch BG: two tiles for next line
for ( int xt = 0 ; xt < 2 ; xt + + )
bgdata . main [ xt ] . Read ( ) ;
//I'm unclear of the reason why this particular access to memory is made.
//The nametable address that is accessed 2 times in a row here, is also the
//same nametable address that points to the 3rd tile to be rendered on the
//screen (or basically, the first nametable address that will be accessed when
//the PPU is fetching background data on the next scanline).
2009-07-18 00:54:58 +02:00
//(not implemented yet)
2009-07-17 19:27:04 +02:00
runppu ( kFetchTime ) ;
2009-07-18 00:54:58 +02:00
if ( sl = = 0 )
{
if ( idleSynch & & PPUON & & ! PAL )
ppur . status . end_cycle = 340 ;
else
ppur . status . end_cycle = 341 ;
idleSynch ^ = 1 ;
}
else
ppur . status . end_cycle = 341 ;
2009-07-17 19:27:04 +02:00
runppu ( kFetchTime ) ;
2009-07-18 00:54:58 +02:00
//After memory access 170, the PPU simply rests for 4 cycles (or the
2009-07-17 19:27:04 +02:00
//equivelant of half a memory access cycle) before repeating the whole
//pixel/scanline rendering process. If the scanline being rendered is the very
//first one on every second frame, then this delay simply doesn't exist.
2009-07-18 00:54:58 +02:00
if ( ppur . status . end_cycle = = 341 )
runppu ( 1 ) ;
}
2009-07-17 19:27:04 +02:00
if ( MMC5Hack & & PPUON ) MMC5_hb ( 240 ) ;
//idle for one line
runppu ( kLineTime ) ;
framectr + + ;
}
finish :
FCEU_PutImage ( ) ;
return 0 ;
}