Frame rate in mode 15

Using mode 15 (220x176, 16 colors) I noticed a strange behavior with frame rate. If I set the frame rate at 30, the FPS counter displays 25. If I set the frame rate at 60, the FPS counter displays 50. I tried to look a bit in the Pokitto lib but nothing obvious explains that.

My guess is that the display is 50Hz and there is some vsync?

I guess its that the frame rate counter is not so accurate. It was made by @Hanski, maybe he can comment?

I am under the impression that it’s the frame rate limiter that’s inaccurate, not the counter.

This behavior can be seen in any screen mode and gets worse with a higher limiter. My guess is the the delay has some overhead. :thinking:

The way I have implemented the FPS counter should be quite accurate. I have also verified the result visually, so it should be ok.

This. The frame rate limit is set by a simple division of integers. Its probably a rounding error.

1 Like

It could be because in the update-function there is nextFrameMillis = getTime()+timePerFrame (and not nextFrameMillis += timePerFrame). So the next frame gets rendered timePerFrame milliseconds from when the previous update actually started, which could be a few milliseconds late.

2 Likes

Indeed there is a rounding error but in the ‘other direction’. At 60 FPS the interval should be 16.666666 but because of the integer division the interval is 16. So it should be even a bit faster.

Indeed this seems to be the issue. Applying your proposed fix the FPS is correct.

I do believe it’s important to have the correct frame rate so I think it should be fixed. The issue is that this breaks compatibility fix existing games as they will run faster :frowning: My proposal is to use a per-processor variable so that in My_settings.h it’s possible to add something like ‘#define ACCURATE_FPS’ to have the correct behavior. If you guys think it’s worth it I could do a pull request for that.

2 Likes

I think it’s worth recompiling some old games to see what the difference is before assuming anything.
I suspect for most games it won’t be much of an issue.

Ideally games shouldn’t be locking their behaviour to the framerate anyway,
they should be measuring actual time.

1 Like

I also suspect that is not a big issue for compatibility. Please make a PR, and, if possible, fix that integer rounding problem too.

There is at least one thing to consider before doing this change.

When the game starts, (nextFrameMillis initialized to 0) if it takes for example 5 seconds for the game to start running the update-loop, then the first 150+ frames are rendered in one burst until nextFrameMillis catches up with the timer. Same thing later during excecution if the game pauses updates while loading resources from sd card or something.

Adding a simple check might be sufficient:
if nextFrameMillis is lagging more than one frame, then use the current method and set its value to now+timePerFrame.

2 Likes

I tend to disagree. This is mostly true for ‘modern’ games that use a floating point delta time. Personally even in this case I sometime makes assumption based on the frame, for example when applying friction to an entity (e.g. multiply velocity by 0.9 each frame).

But for retro game based on integer I think it’s critical to have known frame rate. If the players moves by 1 pixel every frame it should always result to the same movement speed. Another issue is related to the low resolution. Generally you need to move the entities at a constant speed (e.g. 2 pixel each frame) to prevent jaggerness. If you take delta time (that is constant between frames) into consideration the multiplication might result in entities not moving constantly (e.g. 2 pixel, 2 pixel, 3 pixel, 2 pixel, 2 pixel).

Not sure it can be fixed. The issue is that it seems we only have time in milliseconds, so we need to round the interval into a number of milliseconds. Oh do you mean rounding 16.666… into 17 instead of 16?

This is the first thing I noticed after patching the code. When the game started, it was super fast for few seconds before going to normal. To solve that I currently use a flag to know the first frame. When the first frame is played nextFrameMillis is set to current time.

Last issue, which I’m not sure about,it feels like after the patch movement is a bit jaggier than before. I will need to investigate further. I think what happens is the delta slowly drifts and suddenly two frames are played to catch up. I’m afraid this is not the end of the story.

2 Likes

The correct rounding to milliseconds would improve the situation. But to get rid of the cumulative error (or reducenit significantly) in the calculation “nextFrameMillis += timePerFrame” would require to save those times in microseconds. Thought, I cannot say how much it really affects to FPS accuracy which however is in integer resolution.

2 Likes

You don’t need to avoid float on the Pokitto,
unless you’ve measured and it definitely isn’t fast enough for your game.
Don’t forget the Cortex-M0 is a 32-bit processor,
and memory isn’t as much of an issue as on certain other systems.

But even then you only need it to accumulate time,
you don’t have to use it for physics calculations.
You can have a fixed-time update step, e.g. 1 update every 10 milliseconds),
accumulate the delta time and only run an update step every time 10ms has elapsed.
(If 20ms has accumulated, do two update steps.)

(This is the ‘semi-fixed timestep’ approach described in the famous fix your timestep article.)

Fixed point arithmetic is also an option if floating point won’t suffice.

2 Likes

That exactly the solution I’m aiming for. Do anybody know how to get microseconds time?

Looking at the code currently getTime() use systick interrupt to increment an internal counter. The actual precision is not even milliseconds. The interrupt rate is 100Hz and the counter is increased by 10 each tick. Not sure why.

Simple answer: its using the mbed lib calls that have way too much overhead. Making a higher precision timer has been on my todo list since 2016 :wink:

Mmh. Yeah it seems that getting microseconds with mbed is quite an issue…

I tried to increase the systick rate to 1000Hz in order to fix the milliseconds precision of getTime(). There doesn’t seem to be any performance issue. And moreover:

Having correct getTime() precision seems to fix this issue.

2 Likes

While studying PokittoLib and how the timer works, I found that it’s possible to get millisecond and even microsecond precision without having to increase the SysTick rate.

This is a simplified PokittoClock.cpp with interrupts at 100 ms interval. For precision it uses SysTick->VAL which is a counter value that counts down from 4800000-1 and overflows every 100 millisecs.

volatile uint32_t pt_count = 0;

extern "C" {
  void SysTick_Handler(void) {
    pt_count += 100;   // increment by 100 milliseconds
  }
}

void Core::initClock() {
  // Reload value of 24 bit down counter. 4800000-1 gives 100 ms interval on a 48 MHz system
  SysTick->LOAD = 4800000-1;
  SysTick->VAL  = 0;
  SysTick->CTRL = 4 | 2 | 1;
  pt_count = 0;
}

uint32_t Core::getTime() {
  // Convert SysTick counter value to milliseconds since last interrupt:
  // systick_ms = (4800000-1-SysTick->VAL)/48000
  uint32_t systick_ms = ((((4800000-1-SysTick->VAL)>>9)*699)>>16); // div by 48000 => mul by 699>>25

  return pt_count + systick_ms;
}

uint32_t Core::getTime_us() {
  // Convert SysTick counter value to microseconds since last interrupt:
  // systick_us = (4800000-1-SysTick->VAL)/48
  uint32_t systick_us = ((((4800000-1-SysTick->VAL)>>5)*21845)>>15); // div by 48 => mul by 21845>>20

  return pt_count*1000 + systick_us;
}

This works on hardware, but on emulator my test program halts just before reaching 90 seconds. Probably the timer gives some weird values that make the update() function stop updating. Also with the unmodified PokittoClock.cpp there is some hiccup at 90 seconds on emulator but it continues after a while.

1 Like

Awesome! :smiley:

I’d expect timers to break at about 91 seconds. At 45mhz, that’s when uint32 overflows and all of a sudden clocks rewind. I’ll switch to uint64.

Is extern "C" needed?
I thought the interrupts could handle name-mangled functions.

Also, some of these magic numbers could do with being changed to constexpr variables with nice names so it’s clearer what they’re supposed to mean, and perhaps some comments to explain how the numbers are derived and what’s actually happening here.

(I’m presuming you’re treating the numbers as Q16.16 fixed points or something.)