Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
55.6 frames per second
#1
Hello,

How I can draw smooth animation?
In the attached code, the X jumps or stutters as it moves from left to right.

Things I tried that have not improved the smoothness:

use _Limit 60 instead of the Wait function
Re-arrange Waits to be before or after _Display

The FPS measurement timer settles on 55.6 fps, which I find to be strange.
This is regardless of how I set the Vertical Sync mode in my GPU drivers.
My display refresh rate is set to 60.0 fps.

Code: (Select All)

'Issue I'm seeing is that I am not getting an expected 60 fps screen update.
' Defocus your eyes by looking at the caret: ^
' and watch the X trail across the screen as it misses or stutters.
' For some strange reason I get 55.6 frames per second regardless of vsync on or off in graphics drivers.

Dim Shared fps As Single
Dim Shared fpsLast As Single
Dim Shared X As Integer

_Title "Framerate check"
Screen _NewImage(640, 480, 32)

timerFps = _FreeTimer
On Timer(timerFps, 10) UpdateFps

fps = 0
Timer(timerFps) On

_Display 'this causes autodisplay=off

X = 1
Do:
    Cls , _RGB32(28, 28, 22)
    Call Render

    Locate 1, 60: Print "FPS:";
    If fpsLast = 0.0 Then
        Print "wait"
    Else
        Print fpsLast
    End If

    '_Limit 60

    Wait &H3DA, 8, 8
    _Display
    Wait &H3DA, 8

    fps = fps + 1
Loop Until _KeyHit = 27
End


Sub Render
    If X < 1 Then X = 1
    If X > _Width / 8 Then X = 1

    Locate 10, X
    Print "X"
    X = X + 1

    Locate 11, _Width / 16
    Print "^"
End Sub

Sub UpdateFps
    fpsLast = fps / 10.0
    fps = 0.0
End Sub


[Image: 55-6-fps.png]
Reply
#2
Remove the two wait statements in your main loop and enable the _LIMIT 60 line.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#3
Yes, I get 60.0 fps, but the animation is still not smooth.

The X still jumps and stutters if I track it across the screen.

If I use persistence of vision and stare in one spot, entire frames are missed:

looks kind of like this with gaps:
XXXXXX XXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXX XXXXXXXXXXX
Reply
#4
Do you know about Interference patterns?

When the program is binking a screen at one rate and the screen hardware updates at a different rate you are going to develope interference patterns which will look like stutters.

Look here at all stutters in the yellow box:
Code: (Select All)
Screen _NewImage(1200, 600, 32)
_ScreenMove 50, 60
For i = 1 To 100
    r1 = r1 + 13
    r2 = r2 + 5
    Circle (300, 300), r1
    Circle (900, 300), r2
Next
Line (300, 280)-(900, 320), &HFFFFFF00, B

You can get a really sweet pattern of regularity if the two rates are mutliples of each other like so:
Code: (Select All)
Screen _NewImage(1200, 600, 32)
_ScreenMove 50, 60
For i = 1 To 100
    r1 = r1 + 4
    r2 = r2 + 8
    Circle (300, 300), r1
    Circle (900, 300), r2
Next
Line (300, 280)-(900, 320), &HFFFFFF00, B

So a solution would be to fine tune the times you CLS to your hardware refresh rate.
b = b + ...
Reply
#5
Every time you press a key the LPS = Loops Per Second is reduced by .5 there might be a Goldilocks LPS between 80 and 60 better chance between 80 and 30?

Code: (Select All)
_Title "Framerate check"
Screen _NewImage(640, 480, 32)
Color &HFFFFFFFF, _RGB32(28, 28, 22)
X = 1
lps = 80
While _KeyDown(27) = 0
    Do
        Cls
        If Len(InKey$) Then lps = lps - .5
        Print "LPS:"; lps
        Locate 10, X: Print "X"
        Locate 11, 40: Print "^"
        _Display
        _Limit lps
        X = X + 1
        If X > 80 Then X = 1
    Loop Until _KeyHit = 27
Wend
b = b + ...
Reply
#6
(11-18-2023, 05:47 AM)Haggarman Wrote: Yes, I get 60.0 fps, but the animation is still not smooth.

The X still jumps and stutters if I track it across the screen.

If I use persistence of vision and stare in one spot, entire frames are missed:

looks kind of like this with gaps:
XXXXXX XXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXX XXXXXXXXXXX

Smooth as butter on my system, no stutters at all.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#7
_DISPLAY basically says to only update the buffer when the video card is ready for it.  This stops screen tearing or printing of partial screens.

_LIMIT says to pause the program so it runs a maximum of X number of times in a second.

Now nothing in Display says that it's going to limit or affect the speed of your program.  It just affects how often the screen refreshes.

And nothing in Limit has any control over how often the screen refreshes.  It just limits the program speed.

The problem here is that the two won't always be in perfect sync with each other.  

Even with a limit of 60, you're dealing with floating point math and rounding.  At some point, there's going to be a value of x seconds when the LIMIT does it thing and moves the character once in that loop, and then the next time it runs that loop is going to be 0.999999999999999 seconds later.   It's effectively running the loop TWICE before the _DISPLAY ever gets called, which results in that character "skip" that you're noticing.

There's not much really that you can do with such minor glitches.   Floating point math is always going to be fuzzy and off by a minute fraction, and its effects will showcase themselves in various little ways like this one.

The one real thing that you can do, however, is to minimize the appearance of the glitch as much as possible.   Instead of using LOCATE and PRINT, which moves one character (8 pixels) at a time across the screen, change that to a _PRINTSTRING statement and move pixel by pixel.   Will you still see those skips?  Surely -- by they'll be 1/8th as large as before, and you're not really likely to even notice that they're going on with the program!  A guy who jumps 8 feet is noticable; a guy who jumps one is...  Ehh... Who??

Code: (Select All)
DO
    LOCATE 1, 1: PRINT x
    _LIMIT 600
    _DISPLAY
    x = x + 1
LOOP

Take the above program and run it, for consideration.

My display is 60FPS.  With a limit of 600, the program should run 10 times before the _DISPLAY updates.  In this case, the last digit should always be a ZERO in the number printed.  Unfortunately, they're not synced that perfectly in line with each other.  As you can see, that last digit changes constantly as the program is ran.  There's got to be times where it processes 9 loops before a refresh, or times where it processes 11 loops before a refresh, because it's certainly not keeping a perfect 10 loop per refresh going on!
Reply
#8
Thanks for the replies, I really do appreciate the help.

@bplus, good thinking on adjusting the _Limit duration at program run. I can't seem to find a sweet spot however.

It feels like there is something lurking beneath that has to do with _Display having a high variability (10~20 ms) in when the actual memory copy occurs.
I realize _Display needs to call a graphics copy function at the OS level, but it's actually waiting for something too? What would that be?

As for 55.6 fps, is the wait &h3da, 8 command unsupported at this point? I know from original QB that this waited for vsync (vertical refresh) back in the VGA compatible days.

The process on Old QBASIC was draw to the working screen page, wait until vsync, flip the screen page, clear the new working screen page, wait until not in vsync, repeat.

For QB64, I wish there was a command to essentially wait for vsync and copy over the image buffer to the OS at that point and return. And then if it was called again while in the same vsync period, it would actually just wait until the next vsync to do the copy.

thanks
Reply
#9
An important thing to understand is that `_Display` does not actually update what is drawn in the window, rather it renders a frame of the program and then queues that frame to be drawn on the next display refresh (this is how it can get away with not pausing execution of the program and still ensure no screen tearing). The queue itself is effectively 2 frames deep.

The display refresh happens at 60fps* and the program is synced to draw the frames at that rate, but there is no perfect way to call `_Display` the right number of times to ensure there's always one frame in the queue when the display refresh happens. `_Limit 60` or faster is close, but you're still going to get either some moments where the queue was empty when drawing the screen, or moments where the queue drops a frame due to becoming full, and those situations cause the stutter.

We could definitely provide a command that waits for the next frame to actually be drawn on the screen (without going into too much explanation, that `Wait` you're doing is emulated and doesn't do that properly). I just tried it out a very crude solution to make `_Display` do the waiting and was able to get an extremely smooth result at exactly 60 FPS, so that's promising at least.

* Supposed to happen at 60 FPS, I am also noticing it actually happens at 55.6 FPS like you mentioned. It looks like the timer function that is supposed to trigger the redraw at 60FPS is doing the calculation incorrectly, resulting in it being slightly slow. I'll make a bug report about this. Funny enough, the code that does the `Wait` you're using has a similar bug, causing it to also be slow.
Reply
#10
(11-18-2023, 10:09 PM)Haggarman Wrote: Thanks for the replies, I really do appreciate the help.

@bplus, good thinking on adjusting the _Limit duration at program run. I can't seem to find a sweet spot however.

It feels like there is something lurking beneath that has to do with _Display having a high variability (10~20 ms) in when the actual memory copy occurs.
I realize _Display needs to call a graphics copy function at the OS level, but it's actually waiting for something too? What would that be?

As for 55.6 fps, is the wait &h3da, 8 command unsupported at this point? I know from original QB that this waited for vsync (vertical refresh) back in the VGA compatible days.

The process on Old QBASIC was draw to the working screen page, wait until vsync, flip the screen page, clear the new working screen page, wait until not in vsync, repeat.

For QB64, I wish there was a command to essentially wait for vsync and copy over the image buffer to the OS at that point and return. And then if it was called again while in the same vsync period, it would actually just wait until the next vsync to do the copy.

thanks
Are you working on something that requires less than 20ms accuracy for screen updates? I've been writing games in QB64 since 2010 and have never once run into an issue where screen updates have hiccuped in the slightest using _DISPLAY and _LIMIT together.

I ran your example code with the changes I suggested for a good 5 minutes. It locked onto to 60FPS and stayed there. I watched intensely for any frame skipping and never saw one. It may boil down to the combination of CPU, GPU, RAM, and OS causing these issues.

Has anyone else run the example code with the changes I suggested and noticed any frame skipping?

My system specs are old but considered pretty good when new.

i7 6700K @ 4GHz, 32GB dual channel RAM @ 1GHz, nVidia GeForce GTX 960 4GB, Windows 7 Pro 64bit.

Update: DSMan posted while I was writing this. So there is an issue with QB64? Why am I getting a rock solid 60FPS then?
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply




Users browsing this thread: 1 Guest(s)