Jul 6

FPS investigations


I run a 100Hz monitor, and after recent work in other bugs, I was a little perplexed as to why in amosball, every other animation frame was ~16ms, then ~4ms. In a very predicatable pattern, it was always 2 frames totalling ~20ms. This makes sense for an average of 10ms per frame, or 100Hz, but the every other frame being non-existent was puzzling me further.

Digging into this a bit more, I found that this is due to the wait vbl command, which is causing this to skip a frame, but then this raises an issue.

On 50Hz displays, your effective FPS would be 25fps (Half PAL)

On 60Hz displays, your effective FPS would be 30fps (Half NTSC)

On 100Hz displays, your effective FPS would be 50fps (Full PAL)



This comes down to a more fundamental question... if we are looking to be backwards compatible with old AMOS programs, which FPS should we target in a wait vbl call? Should this be tunable by the compiler in the manifest? Using the previous times, we should be able to work out if we need to double a frame every now and then (i.e. if we're targetting 50fps, and we're on a 60Hz screen, then we would need to add 10 skipped frames, every 6ms or so). We could use the previous n frames to gauge the current average frame length, and compute whether we need to skip or not.

I'll look to add a patch in the next week to see how this looks, and fire it over to you Francois!

Jul 6Edited: Jul 6

Thinking out loud, using a 50fps target on a 60Hz monitor Each frame should take a total of 20ms, but we get a RAF every 16.66666ms. We could look at the past n frame times, say 3. We know that this should have taken 60ms for 3 frames, but we've actually taken 50ms. We could look at the delta between ideal frame time and total time. Where this exceeds half the RAF time, we skip a frame. This would mean that Completed Frame:

1, ideal time 20ms, actual 16.66666ms - ~3ms difference, no skip

2, ideal time 40ms, actual 33.33333ms - ~7ms difference, no skip

3, ideal time 60ms, actual 50ms - 10ms difference, greater than 16.6666/2 (8.3333ms). skip next

4. skip a frame. ideal time 60ms (we're skipping a frame so we don't increase ideal time), actual 66.6666ms

5. ideal time 80ms, actual 83.33333ms, no skip

6. ideal time 100ms, actual 100ms, no skip (and the next frame loops back to 1)


This would also cater for any frames which take longer than the RAF time to render, as it will only ever skip a frame to try to lead the RAF time, when it is half a RAF time ahead.


25fps (40ms) on a 60hz monitor

1. Ideal time 40ms, actual 16.66666ms, - 23.333ms difference, greater than half a RAF time, skip

2. skip frame. ideal time still 40ms, actual 33.333ms - difference ~7ms, no skip

3. ideal time 80ms, actual 50ms, - 30ms difference, greater than half a RAF time, skip next

4. skip frame. ideal time 80ms, actual 66.666ms, greater than half a RAF time, skip next

5. skip frame. ideal time 80ms, actual 83.33, difference 3.33ms no skip

6. ideal time 120ms, actual 100ms, difference 20ms, skip next

7. skip frame. ideal time 120ms, actual 116.666ms, difference ~3ms, no skip

8. ideal time 160ms, actual 133.333ms, difference ~27ms, skip next

9. skip frame ideal time 160ms, actual 150ms, difference 10ms, skip next

10. skip frame. ideal time 160ms, actual ~167ms, difference ~7ms, no skip

11. ideal time 200ms, actual 183.33ms, difference ~17ms, skip next

12. skip frame. ideal time 200ms, actual 200ms. no skip (and the next frame loops back to 2).

Thinking about the reverse, where the ideal time minus the actual time is actually negative (we can't keep up with the requested FPS), we could have a flag to allow the designer to decide what to do.

1) Allow the drift to continue. Thus if we end up say 100 frames behind, and suddenly the code runs significantly faster, then we just run the frames as fast as we can to catch up... in games this would be like the game suddenly hit a turbo boost. This is useful if we need to perform FPS dependant calculations or have an FPS sync with an external resource like music.

2) Allow n number of frames to be drifted before we ditch and reset the counter. Basically, run the program as fast as we can normally, and when we get to a point where we have spare CPU, we either catch up on n frames, or reset the counter (n can be zero)

3) Allow X amount of time to be drifted before we ditch and reset the counter. Again, run the program as fast as we can normally, and when we get to a point where we have spare CPU cycles, we either catch up on X time, or reset the counter (again X can be zero)

Jul 24

Hey David. Sorry for not responding to all you posts, just catching up after leaving Q-Loc. I will be more present here from now on. About Wait VBL. yes you are right, I was waiting for 2 VBL instead of one. This bug is corrected. Yet, it is not that simple in Javascript. To get a proper display, it is mandatory that you are synchronized with the monitor. In JS, you only have one way of doing it, "requestAnimationFrame" (find it in runtime/amos.js at the beginning). And that is all, it is the browser that calls you. Having already tried to skip frames as you suggest for other projects, although it might work for static applications, it is not applicable for games with the movements of the bobs and sprites, they look chopped and it is ugly. And in Javascript, you do not have high-precision timers (up to the micro second in Windows), that would be fundamental for a synchronization 'on the fly'... Always wondered why... So there is no real choice I am afraid. To stabilize the speed of an app in Amiga compatible mode, I will add waiting loops between the instructions to match as best as possible the speed of the Amiga, so that the games which are not stabilized by Wait Vbl (not many) can also run fine.

New Posts
  • With my AMOS Code, when I want to compile it with AMOS2 Compiler, I have errors on the "Shared" statement. V1=23:X=0:Y=0:LIFE=5 Dim MAP(121) Shared V1,MAP(),X,Y,LIFE >> Syntax error here But on Amiga and AMOS Pro, that's works very well. Will "Shared"statement be available? Thanks.
  • A new version is out! It has the wrong date; it says 05/05/19 - Now fixed with 07/07/19 version. ------------ I've copied the A1200 manifest into it and compiled a program. It's giving a syntax error on an input line: locate 19,22:input pw$ ------------ It looks like co-ords are working better, as pixel co-ords now match the bob/sprite co-ords. Bobs are now showing much smaller. Possibly too small, but I'll do some testing on that. - Now fixed. The scaling was wrong. ------------ main.amos:181:18: error: syntax error main.amos:182:18: error: syntax error main.amos:184:14: error: syntax error main.amos:185:47: error: syntax error for check=1 to bobson a#=abs(x#-( bpos #(check,1)))^2 b#=abs(y#-( bpos #(check,2)))^2 dist#=sqr(a#+b#) area#=63* bz #(check) if (dist#<area# and dist#<>0) and (abs(z#- bz #(check))<0.05) then hit=1 next check ------------ Not sure if PAL screen should work yet, but I'm still having to use the tiny NTSC 200 pixel high one. I noticed PAL mentioned in the manifest, and I tried changing the screen to 256 high in there. - PAL screen seems to work now
  • Hi there, I downloaded the most recent build (2019-07-13), and followed the Quick Start instructions, but I needed to manually install the pngjs library. If you hit F5 in the Code workspace without doing this, you get: Error: Cannot find module 'pngjs' Require stack: - c:\Development\amos-2\compiler\utilities.js - c:\Development\amos-2\compiler\messages.js - c:\Development\amos-2\compiler\amosc.js at Function.Module._resolveFilename (internal/modules/cjs/loader.js:625:15) at Function.Module._load (internal/modules/cjs/loader.js:527:27) at Module.require (internal/modules/cjs/loader.js:683:19) at require (internal/modules/cjs/helpers.js:16:16) at Object.<anonymous> (c:\Development\amos-2\compiler\utilities.js:21:7) at Module._compile (internal/modules/cjs/loader.js:776:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10) at Module.load (internal/modules/cjs/loader.js:643:32) at Function.Module._load (internal/modules/cjs/loader.js:556:12) at Module.require (internal/modules/cjs/loader.js:683:19) I fixed this by entering the amos-2 folder and running: npm install pngjs I'm running NodeJS v12.6.0.