Posts: 4
Threads: 1
Joined: Nov 2023
Reputation:
0
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
Posts: 1,277
Threads: 120
Joined: Apr 2022
Reputation:
100
Remove the two wait statements in your main loop and enable the _LIMIT 60 line.
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
Posts: 4
Threads: 1
Joined: Nov 2023
Reputation:
0
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
Posts: 3,925
Threads: 175
Joined: Apr 2022
Reputation:
211
11-18-2023, 12:29 PM
(This post was last modified: 11-18-2023, 12:37 PM by bplus.)
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 + ...
Posts: 3,925
Threads: 175
Joined: Apr 2022
Reputation:
211
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 + ...
Posts: 1,277
Threads: 120
Joined: Apr 2022
Reputation:
100
(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.
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
Posts: 2,686
Threads: 326
Joined: Apr 2022
Reputation:
215
_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!
Posts: 4
Threads: 1
Joined: Nov 2023
Reputation:
0
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
Posts: 303
Threads: 10
Joined: Apr 2022
Reputation:
44
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.
Posts: 1,277
Threads: 120
Joined: Apr 2022
Reputation:
100
11-19-2023, 03:05 AM
(This post was last modified: 11-19-2023, 03:16 AM by TerryRitchie.)
(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?
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
|