It is currently Mon Oct 23, 2017 4:55 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 5 posts ] 
Author Message
 Post subject: timing (again me...)
PostPosted: Wed Oct 19, 2005 7:52 pm 
Offline
User avatar

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)
       {
         ReloadFromPPUTemp();   // -> Counters
         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.

Thanks in advance.

_________________
ANes


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 20, 2005 1:59 am 
Offline

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
 Profile  
 
 Post subject:
PostPosted: Thu Oct 20, 2005 6:11 am 
Offline
User avatar

Joined: Wed Nov 10, 2004 6:47 pm
Posts: 1845
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)
{
  ReloadFromPPUTemp();   // -> Counters
  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;
   u16 c, d, ad;

   // scanline -1 -- prerender
   //
   //  **NOTE** prerender scanline stuff removed for sake
   // of this example... it looks just like the other scanlines only I
   // don't render pixels... and I reload nPPUAddr (LoopyV) at
   // 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)
         {
            LOAD_BG_TILE(nScanCyc - 3 + 16);
            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();
         PPU_LoadSpriteLine();
         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)
      {
         LOAD_BG_TILE(nScanCyc - 323);
         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)
         pVidOut += nVidPitchAdd;

      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
 Profile  
 
 Post subject:
PostPosted: Thu Oct 20, 2005 6:40 am 
Offline
User avatar

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
 Profile  
 
 Post subject:
PostPosted: Thu Oct 20, 2005 8:58 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19116
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
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: Google [Bot] and 8 guests


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

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group