etc
This commit is contained in:
parent
5ad3aeeea4
commit
37b36dc8c0
452
src/ppu.cpp
452
src/ppu.cpp
|
@ -1805,284 +1805,286 @@ int FCEUX_PPU_Loop(int skip) {
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
//vblank
|
{
|
||||||
PPU_status |= 0x80;
|
//vblank
|
||||||
if(VBlankON) TriggerNMI();
|
PPU_status |= 0x80;
|
||||||
|
if(VBlankON) TriggerNMI();
|
||||||
|
|
||||||
//TRICKY:
|
//TRICKY:
|
||||||
//even though the timing doc says that every scanline is 1364 except for the first dummy scanline,
|
//even though the timing doc says that every scanline is 1364 except for the first dummy scanline,
|
||||||
//i need these to be 1360 in order to get marble madness and pirates! to work.
|
//i need these to be 1360 in order to get marble madness and pirates! to work.
|
||||||
runppu(20*1360);
|
runppu(20*1360);
|
||||||
|
|
||||||
//no longer in vblank
|
//no longer in vblank
|
||||||
PPU_status &= ~0x80;
|
PPU_status &= ~0x80;
|
||||||
//fceu cleared the spritehit and spriteoverflow flags at the same time.. it is reasonable here. or, it is resaonable at vblank interrupt time
|
//fceu cleared the spritehit and spriteoverflow flags at the same time.. it is reasonable here. or, it is resaonable at vblank interrupt time
|
||||||
PPU_status &= ~0x40;
|
PPU_status &= ~0x40;
|
||||||
PPU_status &= ~0x20;
|
PPU_status &= ~0x20;
|
||||||
|
|
||||||
//----------
|
//----------
|
||||||
//this early out caused metroid to fail to boot. I am leaving it here as a reminder of what not to do
|
//this early out caused metroid to fail to boot. I am leaving it here as a reminder of what not to do
|
||||||
//if(!PPUON) {
|
//if(!PPUON) {
|
||||||
// runppu(kLineTime*242);
|
// runppu(kLineTime*242);
|
||||||
// goto finish;
|
// goto finish;
|
||||||
//}
|
//}
|
||||||
//----------
|
//----------
|
||||||
|
|
||||||
//There are 2 conditions that update all 5 PPU scroll counters with the
|
//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
|
//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
|
//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
|
//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).
|
//all rendering is disabled via 2001.3 and 2001.4).
|
||||||
|
|
||||||
if(PPUON)
|
if(PPUON)
|
||||||
ppur.install_latches();
|
ppur.install_latches();
|
||||||
|
|
||||||
uint8 oams[2][8][7];
|
uint8 oams[2][8][7];
|
||||||
int oamcounts[2]={0,0};
|
int oamcounts[2]={0,0};
|
||||||
int oamslot=0;
|
int oamslot=0;
|
||||||
int oamcount;
|
int oamcount;
|
||||||
|
|
||||||
//capture the initial xscroll
|
//capture the initial xscroll
|
||||||
int xscroll = ppur.fh;
|
int xscroll = ppur.fh;
|
||||||
|
|
||||||
//render 241 scanlines (including 1 dummy at beginning)
|
//render 241 scanlines (including 1 dummy at beginning)
|
||||||
for(int sl=0;sl<241;sl++) {
|
for(int sl=0;sl<241;sl++) {
|
||||||
int yp = sl-1;
|
int yp = sl-1;
|
||||||
|
|
||||||
if(sl != 0) {
|
|
||||||
FCEUD_UpdatePPUView(scanline=yp,1);
|
|
||||||
FCEUD_UpdateNTView(scanline=yp,1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//twiddle the oam buffers
|
|
||||||
int scanslot = oamslot^1;
|
|
||||||
int renderslot = oamslot;
|
|
||||||
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(true);
|
|
||||||
|
|
||||||
//ok, we're also going to draw here.
|
|
||||||
//unless we're on the first dummy scanline
|
|
||||||
if(sl != 0) {
|
if(sl != 0) {
|
||||||
int xstart = xt<<3;
|
FCEUD_UpdatePPUView(scanline=yp,1);
|
||||||
oamcount = oamcounts[renderslot];
|
FCEUD_UpdateNTView(scanline=yp,1);
|
||||||
uint8 *target=XBuf+(yp<<8)+xstart;
|
}
|
||||||
uint8 *ptr = target;
|
|
||||||
int rasterpos = xstart;
|
|
||||||
|
|
||||||
//check all the conditions that can cause things to render in these 8px
|
//twiddle the oam buffers
|
||||||
bool renderspritenow = SpriteON && rendersprites && (xt>0 || SpriteLeft8);
|
int scanslot = oamslot^1;
|
||||||
bool renderbgnow = ScreenON && renderbg && (xt>0 || BGLeft8);
|
int renderslot = oamslot;
|
||||||
|
oamslot ^= 1;
|
||||||
|
|
||||||
for(int xp=0;xp<8;xp++,rasterpos++) {
|
oamcount = oamcounts[renderslot];
|
||||||
|
|
||||||
//bg pos is different from raster pos due to its offsetability.
|
//the main scanline rendering loop:
|
||||||
//so adjust for that here
|
//32 times, we will fetch a tile and then render 8 pixels.
|
||||||
int bgpos = rasterpos + xscroll;
|
//two of those tiles were read in the last scanline.
|
||||||
int bgpx = bgpos&7;
|
for(int xt=0;xt<32;xt++) {
|
||||||
int bgtile = bgpos>>3;
|
bgdata.main[xt+2].Read(true);
|
||||||
|
|
||||||
uint8 pixel=0, pixelcolor;
|
//ok, we're also going to draw here.
|
||||||
|
//unless we're on the first dummy scanline
|
||||||
|
if(sl != 0) {
|
||||||
|
int xstart = xt<<3;
|
||||||
|
oamcount = oamcounts[renderslot];
|
||||||
|
uint8 *target=XBuf+(yp<<8)+xstart;
|
||||||
|
uint8 *ptr = target;
|
||||||
|
int rasterpos = xstart;
|
||||||
|
|
||||||
//generate the BG data
|
//check all the conditions that can cause things to render in these 8px
|
||||||
if(renderbgnow)
|
bool renderspritenow = SpriteON && rendersprites && (xt>0 || SpriteLeft8);
|
||||||
{
|
bool renderbgnow = ScreenON && renderbg && (xt>0 || BGLeft8);
|
||||||
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
|
for(int xp=0;xp<8;xp++,rasterpos++) {
|
||||||
if(renderspritenow) {
|
|
||||||
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
|
//bg pos is different from raster pos due to its offsetability.
|
||||||
oam[4] >>= 1;
|
//so adjust for that here
|
||||||
oam[5] >>= 1;
|
int bgpos = rasterpos + xscroll;
|
||||||
|
int bgpx = bgpos&7;
|
||||||
|
int bgtile = bgpos>>3;
|
||||||
|
|
||||||
//bail out if we already have a pixel from a higher priority sprite
|
uint8 pixel=0, pixelcolor;
|
||||||
if(havepixel) continue;
|
|
||||||
|
|
||||||
//transparent pixel bailout
|
//generate the BG data
|
||||||
if(spixel==0) continue;
|
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];
|
||||||
|
|
||||||
//spritehit:
|
//look for a sprite to be drawn
|
||||||
//1. is it sprite#0?
|
if(renderspritenow) {
|
||||||
//2. is the bg pixel nonzero?
|
bool havepixel = false;
|
||||||
//then, it is spritehit.
|
for(int s=0;s<oamcount;s++) {
|
||||||
if(oam[6] == 0 && pixel != 0)
|
uint8* oam = oams[renderslot][s];
|
||||||
PPU_status |= 0x40;
|
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;
|
||||||
|
|
||||||
havepixel = true;
|
//shift down the patterns so the next pixel is in the LSB
|
||||||
|
oam[4] >>= 1;
|
||||||
|
oam[5] >>= 1;
|
||||||
|
|
||||||
//priority handling
|
//bail out if we already have a pixel from a higher priority sprite
|
||||||
if(oam[2]&0x20) {
|
if(havepixel) continue;
|
||||||
//behind background:
|
|
||||||
if((pixel&3)!=0) 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)
|
||||||
|
PPU_status |= 0x40;
|
||||||
|
|
||||||
|
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
//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;
|
||||||
|
int spriteHeight = Sprite16?16:8;
|
||||||
|
for(int i=0;i<64;i++) {
|
||||||
|
uint8* spr = SPRAM+i*4;
|
||||||
|
if(yp >= spr[0] && yp < spr[0]+spriteHeight) {
|
||||||
|
//if we already have 8 sprites, then this new one causes an overflow,
|
||||||
|
//set the flag and bail out.
|
||||||
|
if(oamcount == 8) {
|
||||||
|
PPU_status |= 0x20;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//fceu rendering system requires that this be set
|
//just copy some bytes into the internal sprite buffer
|
||||||
//(so that it knows there is a valid pixel there?)
|
for(int j=0;j<4;j++)
|
||||||
pixelcolor |= 0x80;
|
oams[scanslot][oamcount][j] = spr[j];
|
||||||
*ptr++ = pixelcolor;
|
|
||||||
|
//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;
|
||||||
|
|
||||||
//look for sprites (was supposed to run concurrent with bg rendering)
|
//todo - think about clearing oams to a predefined value to force deterministic behavior
|
||||||
oamcounts[scanslot] = 0;
|
|
||||||
oamcount=0;
|
//fetch sprite patterns
|
||||||
int spriteHeight = Sprite16?16:8;
|
for(int s=0;s<8;s++) {
|
||||||
for(int i=0;i<64;i++) {
|
|
||||||
uint8* spr = SPRAM+i*4;
|
if(s==1 && sl != 0) {
|
||||||
if(yp >= spr[0] && yp < spr[0]+spriteHeight) {
|
//begin hblank!
|
||||||
//if we already have 8 sprites, then this new one causes an overflow,
|
//the screen is always behind the ppu operation so we need to wait a little longer before triggering it
|
||||||
//set the flag and bail out.
|
//NOTE: SMB3 is very sensitive about this timing, for the statusbar
|
||||||
if(oamcount == 8) {
|
if(PPUON) {
|
||||||
PPU_status |= 0x20;
|
if(GameHBIRQHook)
|
||||||
break;
|
GameHBIRQHook();
|
||||||
|
} else {
|
||||||
|
int zzz=9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//just copy some bytes into the internal sprite buffer
|
uint8* oam = oams[scanslot][s];
|
||||||
for(int j=0;j<4;j++)
|
uint32 line = yp - oam[0];
|
||||||
oams[scanslot][oamcount][j] = spr[j];
|
if(oam[2]&0x80) //vflip
|
||||||
|
line = spriteHeight-line-1;
|
||||||
|
|
||||||
//note that we stuff the oam index into [6].
|
uint32 patternNumber = oam[1];
|
||||||
//i need to turn this into a struct so we can have fewer magic numbers
|
uint32 patternAddress;
|
||||||
oams[scanslot][oamcount][6] = (uint8)i;
|
|
||||||
oamcount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
oamcounts[scanslot] = oamcount;
|
|
||||||
|
|
||||||
//todo - think about clearing oams to a predefined value to force deterministic behavior
|
//8x16 sprite handling:
|
||||||
|
if(Sprite16) {
|
||||||
//fetch sprite patterns
|
uint32 bank = (patternNumber&1)<<12;
|
||||||
for(int s=0;s<8;s++) {
|
patternNumber = patternNumber&~1;
|
||||||
|
patternNumber |= (line>>3);
|
||||||
if(s==1 && sl != 0) {
|
patternAddress = (patternNumber<<4) | bank;
|
||||||
//begin hblank!
|
|
||||||
//the screen is always behind the ppu operation so we need to wait a little longer before triggering it
|
|
||||||
//NOTE: SMB3 is very sensitive about this timing, for the statusbar
|
|
||||||
if(PPUON) {
|
|
||||||
if(GameHBIRQHook)
|
|
||||||
GameHBIRQHook();
|
|
||||||
} else {
|
} else {
|
||||||
int zzz=9;
|
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
|
||||||
|
runppu(kFetchTime);
|
||||||
|
runppu(kFetchTime);
|
||||||
|
|
||||||
|
//pattern table fetches
|
||||||
|
RefreshAddr = patternAddress;
|
||||||
|
oam[4] = FFCEUX_PPURead(RefreshAddr);
|
||||||
|
runppu(kFetchTime);
|
||||||
|
RefreshAddr += 8;
|
||||||
|
oam[5] = FFCEUX_PPURead(RefreshAddr);
|
||||||
|
runppu(kFetchTime);
|
||||||
|
|
||||||
|
//hflip
|
||||||
|
if(!(oam[2]&0x40)) {
|
||||||
|
oam[4] = bitrevlut[oam[4]];
|
||||||
|
oam[5] = bitrevlut[oam[5]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8* oam = oams[scanslot][s];
|
// FV is clocked by the PPU's horizontal blanking impulse, and therefore will increment every scanline.
|
||||||
uint32 line = yp - oam[0];
|
//well, according to my tests, maybe at the end of hblank.
|
||||||
if(oam[2]&0x80) //vflip
|
//but where is the end of hblank?
|
||||||
line = spriteHeight-line-1;
|
//well, i imagine it needs to be before the BG fetches for the next line.
|
||||||
|
if(PPUON && sl != 0)
|
||||||
|
ppur.increment_vs();
|
||||||
|
|
||||||
uint32 patternNumber = oam[1];
|
//so.. this is the end of hblank. latch horizontal scroll values
|
||||||
uint32 patternAddress;
|
if(PPUON && sl != 0)
|
||||||
|
ppur.install_h_latches();
|
||||||
|
|
||||||
//8x16 sprite handling:
|
//capture the next xscroll
|
||||||
if(Sprite16) {
|
xscroll = ppur.fh;
|
||||||
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.
|
//fetch BG: two tiles for next line
|
||||||
//tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above.
|
for(int xt=0;xt<2;xt++)
|
||||||
//so we just need the line offset for the second pattern
|
bgdata.main[xt].Read(true);
|
||||||
patternAddress += line&7;
|
|
||||||
|
|
||||||
//garbage nametable fetches
|
//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).
|
||||||
|
//(not implemented yet)
|
||||||
runppu(kFetchTime);
|
runppu(kFetchTime);
|
||||||
runppu(kFetchTime);
|
runppu(kFetchTime);
|
||||||
|
|
||||||
//pattern table fetches
|
|
||||||
RefreshAddr = patternAddress;
|
|
||||||
oam[4] = FFCEUX_PPURead(RefreshAddr);
|
|
||||||
runppu(kFetchTime);
|
|
||||||
RefreshAddr += 8;
|
|
||||||
oam[5] = FFCEUX_PPURead(RefreshAddr);
|
|
||||||
runppu(kFetchTime);
|
|
||||||
|
|
||||||
//hflip
|
//After memory access 170, the PPU simply rests for 4 cycles (or the
|
||||||
if(!(oam[2]&0x40)) {
|
//equivelant of half a memory access cycle) before repeating the whole
|
||||||
oam[4] = bitrevlut[oam[4]];
|
//pixel/scanline rendering process. If the scanline being rendered is the very
|
||||||
oam[5] = bitrevlut[oam[5]];
|
//first one on every second frame, then this delay simply doesn't exist.
|
||||||
}
|
if(sl==0 && idleSynch==0)
|
||||||
|
{}
|
||||||
|
else
|
||||||
|
runppu(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FV is clocked by the PPU's horizontal blanking impulse, and therefore will increment every scanline.
|
idleSynch ++;
|
||||||
//well, according to my tests, maybe at the end of hblank.
|
if(idleSynch==2) idleSynch = 0;
|
||||||
//but where is the end of hblank?
|
|
||||||
//well, i imagine it needs to be before the BG fetches for the next line.
|
|
||||||
if(PPUON && sl != 0)
|
|
||||||
ppur.increment_vs();
|
|
||||||
|
|
||||||
//so.. this is the end of hblank. latch horizontal scroll values
|
//idle for one line
|
||||||
if(PPUON && sl != 0)
|
//why 1360? see the note up top at vblank
|
||||||
ppur.install_h_latches();
|
runppu(1360);
|
||||||
|
|
||||||
//capture the next xscroll
|
framectr++;
|
||||||
xscroll = ppur.fh;
|
|
||||||
|
|
||||||
//fetch BG: two tiles for next line
|
|
||||||
for(int xt=0;xt<2;xt++)
|
|
||||||
bgdata.main[xt].Read(true);
|
|
||||||
|
|
||||||
//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).
|
|
||||||
//(not implemented yet)
|
|
||||||
runppu(kFetchTime);
|
|
||||||
runppu(kFetchTime);
|
|
||||||
|
|
||||||
|
|
||||||
//After memory access 170, the PPU simply rests for 4 cycles (or the
|
|
||||||
//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.
|
|
||||||
if(sl==0 && idleSynch==0)
|
|
||||||
{}
|
|
||||||
else
|
|
||||||
runppu(4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
idleSynch ++;
|
|
||||||
if(idleSynch==2) idleSynch = 0;
|
|
||||||
|
|
||||||
//idle for one line
|
|
||||||
//why 1360? see the note up top at vblank
|
|
||||||
runppu(1360);
|
|
||||||
|
|
||||||
framectr++;
|
|
||||||
|
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
FCEU_PutImage();
|
FCEU_PutImage();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue