Timers and frame rate

Yesterday I wanted to try out if it was possible to simulate different levels of grayscale on the screen by using the trick Nintendo used to use to achieve semi transparency on NES games.

The trick is simple. On even frames the pixel should be on, and on odd frames the pixel should be off. If you look at games like Metroid on the NES, when you spawn in at the very beginning Samus’ sprite is semi transparent. It is achieved by turning the sprite on and off every other frame. The TV screen, being unresponsive and interlaced is trying to keep up with what the hardware is trying to show, but it can’t and in the end the sprite comes out semi transparent.

When I did an earlier test I wrote a scrolling text and I noticed that the screen was quite unresponsive. When pixels where fading from on to off was clearly visible. So I figured that if I would flicker the pixel on and off the screen, being so unresponsive, should end up stuck in the fade between on and off.

So I wrote this little test

vmthread  MAIN
{
  DATA8 Flag
  DATA8 Draw_1of2

  MOVE8_8(0, Draw_1of2)

Loop:
  UI_DRAW( FILLWINDOW,0x00,0,0 )                  // Clear screen
  UI_DRAW( FILLRECT, FG_COLOR, 0, 0, 10, 10)      // Draw reference rect, fully opaque


  //
  // Draw 1 of 2
  //
  JR_NEQ8( Draw_1of2, 0, DrawDone_1of2 )          // if( Draw_1of2 != 0 )
  UI_DRAW( FILLRECT, FG_COLOR, 10, 0, 10, 10)     //     Draw full rect (every second frame)
DrawDone_1of2:
  ADD8( Draw_1of2, 1, Draw_1of2 )                 //
  JR_LT8( Draw_1of2, 2, Done_1of2 )               // if( Draw_1of2 >= 2 )
  MOVE8_8( 0, Draw_1of2 )                         //     Draw_1of2 = 0
Done_1of2:
  UI_DRAW( UPDATE )                               //  Show the stuff

  // Check for key to exit
  UI_BUTTON( SHORTPRESS, BACK_BUTTON, Flag )      // IF backbutton pressed THEN Flag=1
  JR_FALSE( Flag, Loop )                          // IF Flag==1 then JR Loop
}

But instead of seeing a semi transparent rectangle I saw a rectangle that was flickering out of control. The interval of the flickering wasn’t steady but seemed to vary. Some intervals the rectangle was shown multiple frames and some intervals it was only shown a single frame. It is obviously quite difficult to see exactly what is going on and for how long the rect was shown and hidden, but I could tell it was not flickering at a fixed and steady interval.

There where some timing issues.

I wanted the app to run in 60 FPS (old habit) and I didn’t know if my app was running faster or slower than that. In fact, I don’t even know the refresh rate of the screen. Now that I think about it, I guess what I really want to do is to sync my app with the refresh rate of the screen.

In any case, I wanted to take control of the timing of my app, so I looked into the TIMER functions.

The timer function would let me setup a timer and then stall the app until the timer had been reached. So I could say that, at this point in my code I want a timer that runs no longer than X milliseconds, and at another point in the code I could ask to wait for the timer to catch up with X.

So I added this:

vmthread  MAIN
{
  DATA8 Flag
  DATA8 Draw_1of2
  DATA32  Timer

  MOVE8_8(0, Draw_1of2)

Loop:
  TIMER_WAIT( 16, Timer )                         // Aim for 60 FPS

  UI_DRAW( FILLWINDOW,0x00,0,0 )                  // Clear screen
  UI_DRAW( FILLRECT, FG_COLOR, 0, 0, 10, 10)      // Draw reference rect, fully opaque


  //
  // Draw 1 of 2
  //
  JR_NEQ8( Draw_1of2, 0, DrawDone_1of2 )          // if( Draw_1of2 != 0 )
  UI_DRAW( FILLRECT, FG_COLOR, 10, 0, 10, 10)     //     Draw full rect (every second frame)
DrawDone_1of2:
  ADD8( Draw_1of2, 1, Draw_1of2 )                 //
  JR_LT8( Draw_1of2, 2, Done_1of2 )               // if( Draw_1of2 >= 2 )
  MOVE8_8( 0, Draw_1of2 )                         //     Draw_1of2 = 0
Done_1of2:
  UI_DRAW( UPDATE )                               //  Show the stuff

  TIMER_READY( Timer )                            // Wait for 60 FPS

  // Check for key to exit
  UI_BUTTON( SHORTPRESS, BACK_BUTTON, Flag )      // IF backbutton pressed THEN Flag=1
  JR_FALSE( Flag, Loop )                          // IF Flag==1 then JR Loop
}

In the added code the TIMER_WAIT( 16, Timer ) will store the current time + 16 in the variable Timer and TIMER_READY( Timer ) will wait until the current time have reached Timer. Like so:

/*! \page cTimer Timer
 *  <hr size="1"/>
 *  <b>     opTIMER_WAIT (TIME, TIMER)  </b>
 *
 *- Setup timer to wait TIME mS\n
 *- Dispatch status unchanged
 *
 *  \param  (DATA32)  TIME    - Time to wait [mS]
 *  \param  (DATA32)  TIMER   - Variable used for timing
 */
/*! \brief  opTIMER_WAIT byte code
 *
 */
void      cTimerWait(void)
{
  ULONG   Time;

  Time  =  *(ULONG*)PrimParPointer();

  *(ULONG*)PrimParPointer()  =  cTimerGetmS() + Time;
}


/*! \page cTimer
 *  <hr size="1"/>
 *  <b>     opTIMER_READY (TIMER) </b>
 *
 *- Wait for timer ready (wait for timeout)\n
 *- Dispatch status can change to BUSYBREAK
 *
 *  \param  (DATA32)  TIMER   - Variable used for timing
 */
/*! \brief  opTIMER_READY byte code
 *
 */
void      cTimerReady(void)
{
  IP      TmpIp;
  DSPSTAT DspStat = BUSYBREAK;

  TmpIp   =  GetObjectIp();

  if (*(ULONG*)PrimParPointer() <= cTimerGetmS())
  {
    DspStat  =  NOBREAK;
  }
  if (DspStat == BUSYBREAK)
  { // Rewind IP

    SetObjectIp(TmpIp - 1);
  }
  SetDispatchStatus(DspStat);

}

But even with that code the flickering wasn’t smooth 60 FPS. So I played around with the desired frame rate. I only achieved a stead flickering when I got as far down as 10 FPS. And at 10 FPS the flickering didn’t look like semi transparency, it was just flickering.

I guess at least that makes it easy for my future projects, because since I won’t be able to have semi transparency or gradients I won’t have to care about hitting a solid 60 FPS for my projects. :)

Leave a Reply