godot
godot copied to clipboard
Forward Plus Renderer causes frameskips/jitter/judder/stutter, GPU frames aren't sorted correctly on Windows
Godot version
4.1.3
System information
Windows 11, NVIDIA GeForce GTX 1070, i7-7700K CPU 4.20GHz, 1920x1080 60Hz IPS monitor & a 2560x1440 adaptive 100Hz monitor
Issue description
The forward plus renderer, causes jitters in movement. It's especially visible when V-Sync is switched on. This happens in 2D and 3D Those jitters are more or less pronounced, depending on which screen you use, they might not be very visible on a 60HZ screen but are still there.
when i talk about "jitter" i am talking about this effect happening in the video above or clearly observable in this older video here at 5, 8, 11 and 13 seconds: Video
The problem seems to be tha tthe renderer has a cache of 4 GPU frames and that the sorting of those frames is jumbled up. So basically by design we seem to have an reoccuring order of 0, 1, 3, 2, 3, ... 4, 5, 8, 7, 8... This means some frames don't get shown, others get shown twice and the judder is caused by a step back
Currently it's not clear if it's a problem with the way how the frames are put together, or if it's a driver related issue related to memory. It could be 2 bugs, as comment sin the thread show other bugs play into this
Steps to reproduce
- Simply use forward plus renderer.
- Preferably use a screen with a low refresh rate, as frames are displayed longer and the jumbles are easier to see.
- If you want to experience it very clearly, switch off camera smoothing and switch on V-Sync.
- set your game to a very low resolution e.g. 160x90 scaled by 12 something like this makes the steps between images way bigger and therefore the issue is clearer visible.
Can this be circumvented?
No. It's a very critical and very deep sitting bug. With the forward plus renderer there is no way to circumvent this and it will happen on any hardware. THe higher the resolution, the less obvious the bug is, however it's always there. The forward plus renderer is simply broken and can't be used in the current state.
You could Use the gl_compatibility renderer, which doesn't have this problem, but this is not a good solution either.
Minimal reproduction project:
I made a simple project in which the character can only run left and right per key input. That's literally all the code we need to test the issue properly, the setup described above is key for making it visible.
Conclusion
- Only by enabling the gl_compatibility renderer the jitter seems to be solved. The visibility of the problem and the intensity the skips are happening is however different based on the settings.
- It happens in 2D and 3D
- Disabling V-Sync with the forward plus renderer makes issues less pronounced than with having V-Sync enabled...
- ...especially on a 60Hz screen on windows disabling the V-Sync leads to way less experienced jitter, it's still there though
- The problem happens with process function and with physics_process() function.
Hypothesis
I think the core of the problem lies with the forward plus renderer (There however might be additional problems with V_Sync, Camera or Physics)
Minimal reproduction project
Project Download This includes a small platformer project. Project settings need to be adjusted according to my tests to reproduce the issues.
Would it be possible for you to use your phone filming your monitors to record all those various tests and post the videos here under each setting description?
We all have different hardware and it is close to impossible to fully replicate your result, or be able to tell if we have replicated the same result without seeing yours. If you record your monitors with your phone we would have the best chance to see what you are seeing as screen recording software would likely alter the result as well.
Initial Tests (Outdated, lower in the thread are actual videos, much better than anything written here
-Forward Plus renderer was used -The camera is conneced to the character. -you can see the positioning issues just with moving the character -If we connect the camera to the character and have bunch of sprites in the game it's easier to see if there are visual positioning problems. -sprite scaling set to nearest neighbor -Display -> Window -> Size: Window Width Override: 0 -Display -> Window -> Size: Window Height Override: 0
Test 2.1: Basic window size, physics loop, 100%, v-sync on
-Display -> Window -> Size: Viewport Width: 640 -Display -> Window -> Size: Viewport Height: 360 -Display -> Window -> Stretch: Mode: viewport -Display -> Window -> Stretch: Scale: 1 -Display -> Window -> V-Sync: enabled -character code with func _physics_process(delta): -display scaling: viewport
Here the character displays the jitter It however is barely visible on the 60hz monitor and very visible on the 100 Hz monitor
Test 2.2: Basic window size, process loop, 100%, v-sync on
-Display -> Window -> Size: Viewport Width: 640 -Display -> Window -> Size: Viewport Height: 360 -Display -> Window -> Stretch: Mode: viewport -Display -> Window -> Stretch: Scale: 1 -Display -> Window -> V-Sync: enabled -character code with func_process(delta): -display scaling: viewport
Here the character displays the jitter It however is slightly more visible on the 60hz monitor than without phsics process and disturbingly well visible on the 100 Hz monitor
Test 2.3: zoomed window size, process loop, 300%, v-sync on
-Display -> Window -> Size: Viewport Width: 1920 -Display -> Window -> Size: Viewport Height: 1080 -Display -> Window -> Stretch: Mode: viewport -Display -> Window -> Stretch: Scale: 3 -Display -> Window -> V-Sync: enabled -character code with func_process(delta): -display scaling: viewport
Here the character displays the jitter, very clearly
Test 2.4: zoomed window size, physics process loop, 300%, v-sync on
-Display -> Window -> Size: Viewport Width: 1920 -Display -> Window -> Size: Viewport Height: 1080 -Display -> Window -> Stretch: Mode: viewport -Display -> Window -> Stretch: Scale: 3 -Display -> Window -> V-Sync: enabled -character code with func_physics_process(delta): -display scaling: viewport
Here the character displays the jitter, very clearly
Test 2.5: Basic window size, physics loop, 100%, v-sync off
-Display -> Window -> Size: Viewport Width: 640 -Display -> Window -> Size: Viewport Height: 360 -Display -> Window -> Stretch: Mode: viewport -Display -> Window -> Stretch: Scale: 1 -Display -> Window -> V-Sync: disabled -character code with func _physics_process(delta): -display scaling: viewport
Here the character displays no jitter
Test 2.2: Basic window size, process loop, 100%, v-sync off
-Display -> Window -> Size: Viewport Width: 640 -Display -> Window -> Size: Viewport Height: 360 -Display -> Window -> Stretch: Mode: viewport -Display -> Window -> Stretch: Scale: 1 -Display -> Window -> V-Sync: disabled -character code with func_process(delta): -display scaling: viewport
Here the character displays no jitter
Test 2.3: zoomed window size, process loop, 300%, v-sync off
-Display -> Window -> Size: Viewport Width: 1920 -Display -> Window -> Size: Viewport Height: 1080 -Display -> Window -> Stretch: Mode: viewport -Display -> Window -> Stretch: Scale: 3 -Display -> Window -> V-Sync: disabled -character code with func_process(delta): -display scaling: viewport
Here the character displays no jitter
Test 2.4: zoomed window size, physics process loop, 300%, v-sync off
-Display -> Window -> Size: Viewport Width: 1920 -Display -> Window -> Size: Viewport Height: 1080 -Display -> Window -> Stretch: Mode: viewport -Display -> Window -> Stretch: Scale: 3 -Display -> Window -> V-Sync: disabled -character code with func_physics_process(delta): -display scaling: viewport
Here the character displays no jitter
The whole issue also comes up in a ton of other issue reports with physics, sprite 2d and whatever. It#s not only me experiencing those issues, I also have it on my laptop and found a bunch of similar bug reports, but I found out that it's the renderer
If I use the compatibility renderer all is smooth
So by sheer logic that's where the issue must lie.
I am not familiar with the code, but I hypothesize that the swapchain is using the wrong monitor hz for the window.
Also because it came up: changing the project settings to fullscreen also produces the jitter, having fullscreen without forward plus v-sync runs smooth again.
I got another very interesting observation: After I was switching the project settings to fullscreen mode and then setting them back to windowed the game suddenly ran smooth. Just before it did not run smooth in the fullscreen.
So it also could be that the V-Sync is fine, but just something with the initial initialization of the windowed mode Hz might be wrong. I started this as a clean project and did not touch the window mode, just let it run in windowed. Now for this project it seems to be fixed (not the uploaded one, but the one I had on my HD)
I quickly created another new project and the issue again applies
My main system monitor is the 100Hz monitor, my secondar ymonitor is the 60Hz monitor.
Exact steps which accidentally seem to solve this issue: 1.) switching the renderer to compatibility renderer, save project, restart 2.) switching the render back to forward_plus renderer, save project, restart 3.) set window mode in the project settings to fullscreen, again choose viewport in the stretch mode settings, playtest 4.) set window mode in the project settings back to windowed, playtest (now it ran smooth for me)
Hi @Cyangmou , I have just run the demo and got jittered whenever stretch mode set to viewport
no matter v-sync is enabled or not. After changing to canvas_item
there is no jitter anymore. I only have a 60 Hz monitor so this could be a multiple monitors problem.
Btw, I'm usingSubviewport
in a top-down game to deal with jittering because canvas_item
will not work with diagonal movement. This is the demo:
JitterTest_Subviewport.zip
I'm kinda concerning about the multiple monitors issue but I could not verify it because I don't have the setup to test myself so your result on this would be much appreciated!
I just ran that linked demo how it came out of the box. I performed all tests on my 100Hz 2560x1440 monitor
Test 1: out of the box jitter
what is different here: Window Stretch Mode: disabled Renderer: forward plus Window Height Override: 1920 Window Width Override: 1080 V-Sync: enabled
Test 1: disabling V-Sync very soft, barely noticeable jitter, jitter became a lot softer than with v-sync enabled but is not gone
Test 2: v sync enabled, but using process instead of physics process solved the jitter, I could not detect any jitter
Test 3: v sync diabled, physics-process a few stutters could be observed
So with this viewport override it seems to make a difference if you use a physics process or no physics process As with the physics process the jitter is way more pronounced than with a normal process But in both cases the jitter can be observed!
Also I additionally tried then to set the stretch mode to viewport and to canvas items in both cases I would neithe rsee the gameworld correctly positioned nor the character, only the numbers running you added in the output.
@Cyangmou Thanks so much!
So with Subviewport
I can still achieve pixel perfect on multiple monitors setup with v-sync enabled by using _process
for both player and camera as you mentioned in Test 2 right? Don't know if this is still a bug or not because I expect movement code with physics to happen in _physics_process
. But at least there is a workaround for it.
The point is that not using the physics process further in dev could drag down the game, as running physics operations in frames is costly. I mean smoothness could be the case with this small project on my machine, which is probably more powerful than the machine most people play those small indiegames on. But if the game scales up and you put all of your stuff in the process loop, it's literally in terms of performance an insanely bad idea.
I would not call it a solution, rather something which unexpectedly worked. The important parts would be the physics ticks.
@Cyangmou I don't know how costly physics operations is but the way Godot handle pixel perfect is very similar to GameMaker to me. There is no _physics_process
function in GameMaker, only Step
event that is very much like _process
, and I need to do almost the same things in GameMaker to make the game pixel perfect (scale the application surface, rounding the camera position). Also _process
in Godot run like 60 times per second so I don't think there will be problem if we put all the code in _process
.
You can of course do it. It's just neither professional nor smart to do it. And yes I agree about GameMaker, but GameMaker really scales badly for big projects and that's why it's uninteresting for any medium to bigger sized game. There is a reason why a physics tick exists and why both Unreal and Unity which have the bigge rshare on the market make use of this optimization.
Oh @Cyangmou wait... In my demo I forgot to change player to _physics_process
which results in player and camera running in different update function. Did you test the case where both of them using _physics_process
yet?
Edit: Nevermind, player is using _physics_process
already, just my mistake after messing things around.
I am also experiencing this issue and did some testing and may have found a hint to the issue. It appears that when changing the Max FPS setting to be 60 to bump up against VSync's max stepping, the issue seems to disappear. I'm not 100% sure if it actually disappears after looking at it for so long, but, the jitter is 99% reduced in conjunction with the Physics Jitter Fix setting at 0.5 (default).
Monitor 1: 2560x1440p @ 60hz Monitor 2: 2560x1440p @ 60hz Godot: v4.1.2.stable.mono.official [399c9dc39]
Settings changed: Project > Project Settings > General Tab > Application > Run > Max FPS Project > Project Settings > General Tab > Display > Window > V-Sync > V-Sync Mode
When VSync is enabled and Max FPS is set to 0, the jitter is there. When VSync is enabled and Max FPS is set to 60, the jitter disappears When VSync is enabled and Max FPS is set to 61, the jitter is there
When VSync is disabled and Max FPS is set to 0 (goes to around 3k+), the jitter disappears When VSync is disabled and Max FPS is set to 90, the jitter disappears
Yep, if you lock a 60Hz game on a 60Hz screen and your hardware can blow the specs, for sure it's running smooth. I mean that's within the expectation. But then it's running smooth for you only and it's not an overarching solution for releasing a game for a lot of different hardware setups. This is not even a bandaid, it's just that with this specific setting and setup the issue would be greatly reduced.
Ok, I decided to run other tests with setups with 4 different (close to) 60HZ screens
On Windows you can check in the Advanced display settings the exact Hz the screen runs.
Adding those 2 lines of code in godot should give us a good idea about the return
print(DisplayServer.screen_get_refresh_rate(0))
print(DisplayServer.screen_get_refresh_rate(1))
Setup 1:
screen1: 1920x1080, on Windows 11: 59,95Hz screen2: 1920x1080 on Windows 11: 60Hz
results in: 59,95 Hz screen results in a value Godot returns of 59 60.00 Hz screen results in a value godot returns of 60
Setup 2:
59,97 Hz screen results in a value godot returns of 60 59,95 Hz screen results in a value godot returns of 59
Conclusion:
Jitter could be experienced on all screens What's interesting about the return number of the engine is that it's an integer. It's not aligning with the system information. The rounding when sorted also looks totally weird: 60.00 Hz screen results in a value godot returns of 60 59,97 Hz screen results in a value godot returns of 60 59,95 Hz screen results in a value godot returns of 59
Something is really really off here This might or might not have an impact on the issue, i don't know if the output of the DisplayServer.screen_get_refresh_rate() is rounded or not, but I have the feleing it should align with the OS information, which it doesn't
I did some tests on my own. I can confirm that there is definitely something very broken here.
I have a lot of experience with pixelart games in Godot 3 and never had the feeling that anything was problematic there and so far have not worked with pixelart in Godot 4.
I "ported" the MRP to Godot 3 and used settings that were as close as possible to the MRP. I disabled camera smoothing on both projects as I found it makes the issue much more obvious.
Here is footage that is slowed down to 1000% from both versions:
Testing it in Godot 4 I had "position skips" very, very regulary where the sprite skipped a pixel and jumped to the next pixel immediately. And even when not skipping, it didn't feel as smooth as the test in Godot 3.
It didn't always skip a pixel of course...
In Godot 3 I was not able to reproduce any of these problem - it was super smooth.
My screens are both 144 fps and I disabled VSync for both tests.
⚡⚡⚡ EDIT: Sorry, my bad. I enabled pixel snapping for transforms which led to these bad stutters, without this setting I have smooth movement. ⚡⚡⚡
Here is a lossless recording with camera smoothing switched off There are multiple framelosses, which are very hard to see unless your eye is trained and a 2 frame loss at around 15 seconds, when the character is mid jump - focus on the environment and this will be really easy to spot.
60hz screen forward plus renderer 1920x1080, 300% windowed mode, stretch mode viewport v-sync enabled
Edited the name that the description of the issue is in line with all known test results.
Regarding the issue of refresh rate appearing incorrectly, it looks like that Godot uses DEVMODEW here and this structure seems to used a DWORD for the storage of the refresh rate, which means that it can't deal with fractional refresh rates.
Instead, the structure DWM_TIMING_INFO seems to provide "UNSIGNED_RATIO rateRefresh;" which supports fractional refresh rates, so this looks like it should be used instead
Here is a lossless recording with camera smoothing switched off There are multiple framelosses, which are very hard to see unless your eye is trained and a 2 frame loss at around 15 seconds, when the character is mid jump - focus on the environment and this will be really easy to spot.
60hz screen forward plus renderer 1920x1080, 300% windowed mode, stretch mode viewport v-sync enabled
I watched the footage very carefully... had to do it a couple of times to spot it. But I put my observation into a short gif so it's very obvious.
Frame 7 is dropped/skipped
it looks like that Godot uses DEVMODEW here and this structure seems to used a DWORD for the storage of the refresh rate, which means that it can't deal with fractional refresh rates.
Godot 3.5.3 similarly uses DEVMODW, and I note that _MonitorEnumProcRefreshRate is unchanged between 3.5.3 and 4. Does 3.x have this issue too?
Can't say as the first Godot version I use is 4.x All tests I performed were on 4.x Somebody else would need to confirm this.
I can confirm that it's also the case for 3D games. @RPicster built a level in the project for 3d I tested the setups. Same problems as for 2D apply. It's a renderer / screenrate refresh issue.
Project Lossless Video In the video you can see a bad stutter right at the start and one at the end, there are a few single frame losses inbetween. So exact same behaviour as 2D
For further tests anyone wants to perform, I personally think that testing with a low resolution is the best. My recommendation would be something like 160*80 scaled by 12, as something like this would make the frameloss mistake way better visible. The higher the resolution and the smaller the steps between frames, the more this mistake gets hidden. It's however always there.
I checked the footage and again could spot one very, very obvious frame drop.
I highlighted the motion difference with red.
Regarding the issue of refresh rate appearing incorrectly, it looks like that Godot uses DEVMODEW here and this structure seems to used a DWORD for the storage of the refresh rate, which means that it can't deal with fractional refresh rates.
Instead, the structure DWM_TIMING_INFO seems to provide "UNSIGNED_RATIO rateRefresh;" which supports fractional refresh rates, so this looks like it should be used instead
That's an interesting finding, but it shouldn't be related to this bug in the end. DisplayServer::screen_get_refresh_rate()
is a convenience method for users, but it's not used anywhere internally in the engine. So if it reports a wrong refresh rate, that's worth tracking in a separate bug report but it shouldn't influence the issue with VSync in the RenderingDevice backends.
Regarding the issue of refresh rate appearing incorrectly, it looks like that Godot uses DEVMODEW here and this structure seems to used a DWORD for the storage of the refresh rate, which means that it can't deal with fractional refresh rates.
Instead, the structure DWM_TIMING_INFO seems to provide "UNSIGNED_RATIO rateRefresh;" which supports fractional refresh rates, so this looks like it should be used instead
This seems to be correct, DEVMODEW
can't return a fractional rate. DwmGetCompositionTimingInfo
/ DWM_TIMING_INFO
is only usable for the main display (window argument was removed since Windows 8.1), so getting a proper fractional rate will likely require use of CCD API (DISPLAYCONFIG_PATH_TARGET_INFO).
@RPicster What did you use to record the footage? Are you 100% certain there were no frame drops or jitter incurred due to the recording software?
@Calinou the videos I made were recorded with OBS screen recording, set to lossless, framerate same as my screen on a 60hz monitor to ensure consistency and reproduction for the broadest amount of users. Also the video is way more soft, than the perception of the frameskips on the actual screen.
I just learned of another problem which might have also derailed the 2D tests a bit. As framejumps however were experienced during motion, I don' tthink this interferes with the renderer, but is another thing which needs fixing for smooth rendering.
apparently the camera is rendering 1 frame too late:, this issue here: issue #74203 NOTE: I just foun dout that this project uses the mobile renderer, so might or might not be an issue with the forward plus renderer I also assume the same 1 frame delay is happening with the 3D camera, for the reason that it probably is way harder to see in 3D, this we would need to test with a low-res 3D setup to be 100% sure.
@RPicster What did you use to record the footage? Are you 100% certain there were no frame drops or jitter incurred due to the recording software?
I didn't record this footage - I can't reproduce the bug here. I just looked through the footage very closely and tried to spot it. It was hard for me to see the problem on the video, so I looked at it frame by frame in After Effects and tried to make it very clear where it happens.
Sorry if this is a stupid/invalid question, but does the jitter still occur when using Godot's built-in video recorder?