It is currently Mon Oct 15, 2018 9:50 am

 All times are UTC - 7 hours

 Page 1 of 1 [ 5 posts ]
 Print view Previous topic | Next topic
Author Message
 Post subject: timing (again me...)Posted: Wed Oct 19, 2005 7:52 pm

Joined: Tue Dec 21, 2004 8:35 pm
Posts: 600
Location: Argentina
i really cant get working the method of "catch up" the ppu or cpu i dont remember, what it says that ppu should run x number of cycles until something that changes the ppu (drawing, etc) happens.

My question is (and i know have been answered to me before and is written in docs) how to make a simple ppu timing routine, i mean without catch up and those things (which are better becouse speed up emulation).

Tell me please if what i know is correctly:

- 1 cpu cycles == 3 ppu cycles
So when an instructions that takes 2 cycles the ppu should run for 2 * 3 = 6 cycles. Is that right?
- a scanline takes 341 ppu cycles which equals 341/3 = 113.6~ cpu cycles. So we have 256 / 3 = 85.3 cpu cycles for scanline pixels and we have more or less 28 cycles for hblank. again is that right?

Now i will write the code how i implement my ppu emu and want someone please tell me which points im failing or someting:

Code:

void EmulatePPU(int CurrentCPUCycle)
{
static WORD cc_ppu = 0;
static WORD sl_number = 0;
int i;
BYTE BkPixel, BYTE SprPixel;

for (i = 0; i < CurrentCPUCycle * 3; i++)
{
if (cc_ppu == 340)
{
//what i do here is increment the scanline number
//since ppu cc has reached the end

if (scanline == 242)
{

SetVBlankFlag()

if (bNMIEnable)
SetNMIPending();
}

if (scanline == 261)
scanline = 0;
else
scanline++;

//when it is the last scanline pixel i clock V Counters
ClockVCounters();

//reset ppu cc
cc_ppu = 0;
}
else
{
if (sl_number == 0)
{
Clear2002Bits();
}
else if (sl_number >= 1 && sl_number <= 240)
{
//Render 239 scanlines, here i have a GetBKPixel() func
//that gets the current scanline and ppu cc pixel
//I have here too a GetSprPixel which get the current spr pixel
//and i have a EmulateMultiplexer() func that takes both pixels
//and emulate sprite 0 hit too

if (SprVisible)
SprPixel = GetSprPixel(...);
else
SprPixel = 0;

if (BkVisible)
BkPixel = GetBKPixel(...);
else
BkPixel = 0;

EmulateMultiplexer(BkPixel, SprPixel, ...);

//and a func that clocks the H counters
ClockHCounters();
}

cc_ppu++;
}
}

}

Well its not everything what i do in that EmulatePPU() func, but that gives the idea how im implementing it. Please tell me what is wrong/bad, etc with this method. If someone has a simple method for emulating please let me know.

_________________
ANes

Top

 Post subject: Posted: Thu Oct 20, 2005 1:59 am

Joined: Thu Sep 15, 2005 9:23 am
Posts: 1194
Location: Behind you with a knife!
1 CPU cycle = 3 PPU Cycles, unless you are in PAL mode then it is 3.2 PPU Cycles. 85.3 Cpu Cycles per Scanline, 28 CPU Cycles for HBlank. Those figures are about accurate.

_________________
http://www.jamesturner.de/

Top

 Post subject: Posted: Thu Oct 20, 2005 6:11 am

Joined: Wed Nov 10, 2004 6:47 pm
Posts: 1849
Yes, on NTSC, 1 CPU cycle = 3 PPU cycles as WedNESday said.

Every scanline is 341 PPU cycles except for scanline -1 (the prerender scanline)... which alternates between 341 and 340 cycles every other frame on NTSC systems (on PAL, it's always 341)

If you want to be anal... you could say you have less VBlank time than you think -- since the PPU is loading tiles for the next scanline near the end of the scanline... effectively giving you about 320-256= only 64 PPU cycles of HBlank (during which time the PPU is doing only sprite related things). Although like I said I'm just being anal... the only game I know of where this really makes a difference is Micro Machines... and even then if you don't follow things this closely the only thing that'll go wrong is half a scanline on the title screen will be the wrong color.

The only thing that stood out to me in your code is the following:

Quote:
Code:
if (sl_number == 0)
{
Clear2002Bits();
}

This looks like the start of your pre-render scanline.. which I believe is the right time to clear \$2002... however this is way too early to reload Loopy_V (this accually occurs near the end of the scanline -- cycle 304 I believe). If you do this here, some games will act funky... most notably Megaman 2 which will look very ugly when the game scrolls vertically.

Also, I don't know if this is related to your problems, but it doesn't appear as though you have a global PPU timestamp. 'cc_ppu' seems to only be the cycle within the scanline. This is fine.. if CurrentCPUCycle resets itself between calls to this function.

For example... If you call this function with a CurrentCPUCycle of 15 -- the PPU will run for 15*3=45 cycles. After that if you call with a CurrentCPUCycle of 20 -- the PPU will run for 20*3=60 more cycles (not 20-15 * 3 = 15 cycles like you might think?)

I don't really know how you have it set up... and this way would work just fine... as long as CurrentCPUCycle is adjusted accordingly between calls to this function.

And because I'm bored... here's a chopped up version of my current emu's PPU emulator (some things removed, some comments added)

Code:
void CNES::RunPPU(s32 cyc)
{
//don't need to catch up
if(nPPUCycle >= cyc)      return;

//Idle scanline
if(nPPUCycle < (341 * PPU_CYCBASE))
{
nPPUCycle = (341 * PPU_CYCBASE);
if(nPPUCycle >= cyc)
return;
}
//after the idle scanline, VBlank flag is raised, and VBlank
if(nPPUCycle == (341 * PPU_CYCBASE))
{
memset(nSpRender,0,256);
n2002Status |= 0x80;
nPPUCycle = nEndOfVBlank;
nScanline = -1;
nScanCyc = 0;
if(nPPUCycle >= cyc)
return;
}

//just after VBlank, \$2002 gets cleared
if(nPPUCycle == nEndOfVBlank)
n2002Status = 0;

//
// here, I check to see if full scanlines can be rendered, and if
// so, I render as many full scanlines as I can.
// but for the sake of this example, that part is removed
// assume you can't run any full scanlines:
//

if(bPPUOn)         RunPPU_On_Fine(cyc);
else            RunPPU_Off_Fine(cyc);
}

void CNES::RunPPU_On_Fine(s32 cyc)
{
if(nPPUCycle >= cyc)
return;

u8* namepage;
u8* pattern;
RESET_NAME_PAGE();

u8 a, at;

// scanline -1 -- prerender
//
//  **NOTE** prerender scanline stuff removed for sake
// of this example... it looks just like the other scanlines only I
// cycle 304

// rest of scanlines
while(nScanline < 240)
{
if(!nScanCyc)   // **NOTE** for MMC5 IRQs
if(MapperScanlineStart)      (this->*MapperScanlineStart)(nScanline);

//cycles 0-255 .. render pixels, load tiles
while(nScanCyc < 256)
{
//render a pixel...
a = nBGRender[nXScroll + nScanCyc];
at = nSpRender[nScanCyc];
if(nScanCyc < nBGClip)   a = 0;  /* nBGClip is either 0 (no clipping), 8 (clipping), or 256 (BG disabled)  */
if(nScanCyc < nSpClip)   at = 0; // ditto for nSpClip

if(a && (at & 0x40) && (nScanCyc != 255))   n2002Status |= 0x40;   //sprite 0 hit

if(pVidOut)
{
if(at & 0x80)  // low sprite priority
{
if(!a)   a = at & 0x1F;
}
else if(at)
a = at & 0x1F;

OUTPUT_PIXEL(a);
}

//load a tile (on 3rd cycle)
if((nScanCyc & 7) == 3)
{
INC_PPU_X();
if(nScanCyc == 251)
{
INC_PPU_Y();
}
}

nScanCyc++;
nPPUCycle += PPU_CYCBASE;
if(nPPUCycle >= cyc)
return;
}

// cycle 256 does nothing
if(nScanCyc == 256)
{
nScanCyc++;
nPPUCycle += PPU_CYCBASE;
if(nPPUCycle >= cyc)
return;
}

//cycle 257 -- reset X scroll, load sprite crap
if(nScanCyc == 257)
{
nScanCyc = 260;
nPPUCycle += (260 - 257) * PPU_CYCBASE;
RESET_PPU_X();
if(nPPUCycle >= cyc)
return;
}

// at 260, rising edge   **NOTE** for MMC3 IRQs... see below
if(nScanCyc == 260)
{
if(MapperA12Edge)
(this->*MapperA12Edge)(1);
nScanCyc = 323;
nPPUCycle += (323 - 260) * PPU_CYCBASE;
if(nPPUCycle >= cyc)
return;
}

//323, 331, load first two tiles for next line
while(nScanCyc < 339)
{
INC_PPU_X();
nScanCyc += 8;
nPPUCycle += 8 * PPU_CYCBASE;
if(nPPUCycle >= cyc)
return;
}

//burn the rest of the scanline
nScanline++;
nScanCyc = 0;
nPPUCycle += 2 * PPU_CYCBASE;
if(pVidOut)

if(nPPUCycle >= cyc)
return;
}
}

LOAD_BG_TILE() fills my 'nBGRender' buffer with pixel data... which is later drawn to the screen.

PPU_LoadSpriteLine() does the same kinda thing, but for sprites (nSpRender).

Sprite pixels in nSpRender are 0 if transparent, otherwise they're the palette color to output for this pixel (pattern + attribute bits + 0x10 = between 0x11-0x1F). Additionally, if the sprite pixel is non-transparent and has background priority, bit 7 (0x80) is flipped on. If the pixels in non-transparent and it belongs to sprite 0, bit 6 (0x40) is flipped on.

I know how I do MMC3 IRQs isn't terribly accurate. I tried doing them the proper way but it was just too much work and too much slowdown for too little gain. This way is "good enough" and runs every game I've tried just fine.

Top

 Post subject: Posted: Thu Oct 20, 2005 6:40 am

Joined: Tue Dec 21, 2004 8:35 pm
Posts: 600
Location: Argentina
well... that gave me an idea of a different way of rendering and not my ugly engine.

_________________
ANes

Top

 Post subject: Posted: Thu Oct 20, 2005 8:58 am

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20656
Location: NE Indiana, USA (NTSC)
Disch wrote:
I know how I do MMC3 IRQs isn't terribly accurate. I tried doing them the proper way but it was just too much work and too much slowdown for too little gain. This way is "good enough" and runs every game I've tried just fine.

Watch out. Some disgruntled pro-hardware anti-emulator-testing militant (or some developer of a "refreshingly accurate" emulator) might make a ROM that looks like a simple game but does some stress testing in the cut scenes.

Top

 Display posts from previous: All posts1 day7 days2 weeks1 month3 months6 months1 year Sort by AuthorPost timeSubject AscendingDescending
 Page 1 of 1 [ 5 posts ]

 All times are UTC - 7 hours

#### Who is online

Users browsing this forum: No registered users and 4 guests

 You cannot post new topics in this forumYou cannot reply to topics in this forumYou cannot edit your posts in this forumYou cannot delete your posts in this forumYou cannot post attachments in this forum

Search for:
 Jump to:  Select a forum ------------------ NES / Famicom    NESdev    NESemdev    NES Graphics    NES Music    Homebrew Projects       2018 NESdev Competition       2017 NESdev Competition       2016 NESdev Competition       2014 NESdev Competition       2011 NESdev Competition    Newbie Help Center    NES Hardware and Flash Equipment       Reproduction    NESdev International       FCdev       NESdev China       NESdev Middle East Other    General Stuff    Membler Industries    Other Retro Dev       SNESdev       GBDev    Test Forum Site Issues    phpBB Issues    Web Issues    nesdevWiki