godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
87.04k stars 19.54k forks source link

option to dynamically adjust fixed_fps based upon a user's monitor's refresh rate (hz) #9561

Closed ghost closed 6 years ago

ghost commented 7 years ago

Operating system or device - Godot version: 2.1.4, any

Issue description: I just found this nasty issue out after buying a 144hz monitor.

I previewed my game scene and there was a huge jitter when moving. I originally thought it had to do https://github.com/godotengine/godot/issues/2043#issue-85903385

The fix is changing your "fixed_fps" variable under physics to 144. Once you do that, there is ZERO jitter ANYWHERE. It's all gone

The problem is. In godot, there is no way to dynamically and programmatically set this before loading the game.

I'm suggesting maybe a method that is available in the OS class that grabs the refresh rate of the monitor? Maybe that would help? And then godot developers can just call that before loading an extensive physics game world scene, etc.

Calinou commented 7 years ago

For example, if I ship my game out at a fixed_fps variable of 144. Only players who have a 144hz monitor will get NO JITTER. Anyone with a 60hz monitor wll experience massive jitter.

I don't remember fixed_fps values above the monitor's refresh rates causing any issues. That said, it would be helpful to have a function to retreive the monitor's refresh rate, but the implementation is quite OS-specific and would have to be done for every desktop platform.

ghost commented 7 years ago

@Calinou Yeah, I just got done plugging in my older 60hz monitor to test on my 144 fixed_fps game. Jitter is still very apparent. I did test it on my 144hz monitor by just switching it to 60hz in settings, but just wanted to make sure with an actual physically different monitor

Basically i'm curious shouldn't the fixed_fps adjust automatically based on the user's refresh rate already? For the smoothness performance? I mean, if the fixed_fps is defaulted to a delta time of 0.0167 (60fps) I finally get where all the jitter reports are coming from...

Because once I set the fixed_fps to the same as my monitor's refresh rate, it all disappeared and Godot's renderer feels as smooth as butter... (never thought I'd say that lol)

e: This is with vsync on btw

tamkcr commented 7 years ago

this is the physics iteration per second step right? If so this defeats the purpose which is to let you have a fixed stepped iteration, interpolate/extrapolate the node's physics state to obtain a visual position in frame update or move your stuff there manually.

reduz commented 7 years ago

I am pretty impressed anyone can see jitter beyond 60fps, would love to have a 144hz monitor to test :P

On Sat, Jul 8, 2017 at 12:32 PM, tamkcr notifications@github.com wrote:

this is the physics iteration per second step right? If so this defeats the purpose which is to let you have a fixed stepped iteration, interpolate/extrapolate the node's physics state to obtain a visual position in frame update or move your stuff there manually.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/9561#issuecomment-313863096, or mute the thread https://github.com/notifications/unsubscribe-auth/AF-Z2x0D7bqJonb2q94qz-myoo--AZsSks5sL6EBgaJpZM4ORui1 .

quinnyo commented 7 years ago

Is this the fixed timestep jitter when more/less fixed steps happen compared to the previous frame? If so, the issue is that Godot doesn't interpolate between the previous<->new states when there is a fixed/idle time deficit -- how's that going by the way? :P

Changing fixed FPS per user/machine is probably not a good idea. Different deltas will make physics and your code behave differently, causing an inconsistent experience and limiting your test coverage. Fixed timesteps are and should be completely independent of the monitor refresh rate.

ghost commented 7 years ago

Okay, thank you guys.

So here's the deal after I did some testing. I am now using a KinematicBody2D that utilizes _process instead of _fixed_process to move the character. This works flawlessly on both 60hz and 144hz with no jitter whatsoever.

The only problem now is when your character on a 144hz monitor pushes against a collision shape. The KinematicBody2D goes back n forth and creates a jitter.

To fix that, you need to set fixed_fps to 144

Also, another way to fix that, set the force_fps to 60

But if you set the force_fps to 60, then there is jitter when you move. (if you're on a 144hz monitor)

And if you set the fixed_fps to 144, the godot user who doesn't have a 144hz monitor will experience jitter.

We need a way to programmatically get the user's monitor refresh rate and use it to set settings in godot. Or games cannot be shipped and work with 144hz and 60hz monitors. That's a big problem!!

reduz commented 7 years ago

One suggested solution was using fixed frame interpolation, but honestly without trying or seeing the problem myself you can imagine it's a little difficult..

ghost commented 7 years ago

Np reduz, thanks for the info.

I found a temporary solution.

The only problem now is when your character on a 144hz monitor pushes against a collision shape. The KinematicBody2D goes back n forth and creates a jitter.

Go to physics 2d -> motion_fix_enabled and set this to true. This fixes that issue.

And, looking at the camera class, line 141, it uses fixed updated (physics).

Sadly that means using a native camera node it will create jitter if updating your kinematic body with _process.

The best solution I found is to roll some custom camera code. Use this inside your _process function right after your character is moving.

Create 2 variables

var cameraPos = Vector2()
var smoothed_camera_pos = Vector2()

Then stick this underneath your move()

var canvas_transform = get_viewport().get_canvas_transform()
var width = OS.get_window_size().width
var height = OS.get_window_size().height

cameraPos.x = -get_global_pos().x + (width/2)  
cameraPos.y = -get_global_pos().y + (height/2)

var c = 4 * delta
smoothed_camera_pos = ((cameraPos - smoothed_camera_pos) * c) + smoothed_camera_pos
canvas_transform[2] = smoothed_camera_pos
get_viewport().set_canvas_transform(canvas_transform)

No jitter on 144hz, or 60hz.

This is a big deal juan because a lot of users are transitioning over to 144hz monitors, and when someone with a 144hz monitor starts playing a godot game that was developed using the native camera node, and moving charcters with _fixed_update, they'll notice a lot of jitter. So it's essential!

raymoo commented 7 years ago

Maybe there should be engine support for interpolating visuals?

reduz commented 7 years ago

It's a possibility, but it's really difficult without being able to see the problem. My intuition tells me jitter should not be visible, and that it might be something else that is at fault

On Sun, Jul 9, 2017 at 2:12 PM, raymoo notifications@github.com wrote:

Maybe there should be engine support for interpolating visuals?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/9561#issuecomment-313932610, or mute the thread https://github.com/notifications/unsubscribe-auth/AF-Z26FvOZrIFnzhZhOmOwZ6uFSGky2Uks5sMQn6gaJpZM4ORui1 .

raymoo commented 7 years ago

I get very little / no jitter when running at 144 fixed FPS on my 60Hz monitor. I can't test the other way around since I don't have a high refresh rate monitor.

ghost commented 7 years ago

Okay. I need someone to test this for me please. Sorry for the big download:

https://drive.google.com/file/d/0B0bKTHkpn7NZZGRQdWt4dDVoNGs/view?usp=sharing

It's shipped at 144 fixed_fps with vSync on. I tried it on my 60hz monitor, and there is jitter, but it's only because of the human eye phenomena (from going to 144 to 60).

On my 144hz monitor though, there is no jitter so that's good.

I need someone who is using a 60hz monitor, to test this and move around with WASD and see if there is any jitter.

If there is jitter = something bad

If there is no jitter for 60hz monitor users. Then that means you must ship godot with fixed_fps to be a value of whatever your gamedev machine's monitor is running at AND a value you think is the highest used for your playerbase?

raymoo commented 7 years ago

@Dillybob92 Could you export a Linux build? I don't have a Windows machine.

ghost commented 7 years ago

@raymoo yep np, done:

https://drive.google.com/file/d/0B0bKTHkpn7NZUE5GWVVWRFhLNlU/view?usp=sharing

raymoo commented 7 years ago

@Dillybob92 It is a bit jittery for me. Could you export a 60FPS version too so I can make sure it's because of the FPS difference?

ghost commented 7 years ago

Yep. Here is fixed_fps set to 60:

https://drive.google.com/file/d/0B0bKTHkpn7NZaGxDV2lnYmt2OUU/view?usp=sharing

raymoo commented 7 years ago

I get lag spikes in both but the 60 fixed_fps one is definitely smoother when there are no lag spikes.

ghost commented 7 years ago

Thank you raymoo for testing them.. I am unfortunately stumped lol. I don't know how ship a godot game that works with all monitor refresh rates..

It seems like if I change it to 144 fixed_fps, it's smooth for 144hz monitor users, but jittery for 60hz users.

If I change it to a fixed_fps to 60, it's smooth for 60hz users, but jittery for 144hz users

There has to be something going on internally?

raymoo commented 7 years ago

Well I'm guessing at least that 60FPS on 144Hz will be smoother than 144FPS on 60Hz since with 60->144 at least you won't be skipping any fixed frames when displaying.

ghost commented 7 years ago

@reduz, this would be a good solution, right?

Use Globals.set("physics/fixed_fps", MONITOR_REFRESH_RATE) . -- Then, we won't have to worry for each individual user's refresh rate?

And if using delta for movement in _fixed_process, it will be the same experience for all.

Win Win?

raymoo commented 7 years ago

And if using delta for movement in _fixed_process, it will be the same experience for all.

Not really true, especially for second-order movement effects like acceleration, if implemented with ordinary Euler integration. Euler's method becomes more accurate (closer to how an object experiencing that acceleration would actually move) with smaller timesteps, so you will have different behavior on 60Hz and 144Hz. For example, a ball on a ballistic trajectory will travel farther with a larger timestep.

There is also the issue of anything that is supposed to happen on an interval not divided evenly by both 1/60 and 1/144. The frames they happen on will be uneven in different ways on both setups.

ghost commented 7 years ago

Yeah good point, you are right

With that said, I'm about done with this though, I have no idea what I am talking about half of the time because this shit is so far beyond my scope of knowledge. All I know is that I cannot find a way to ship my game that works smoothly on all monitor refresh rates. I'm getting really frustrated not at you guys, but at myself. I'm just stumped and honestly I'm giving up with all of this.

tamkcr commented 7 years ago

that is just how physics works, solutions adopted by other engines:

i) separate the entity's physics node and view node (ie. make them siblings) and use a script to set the rendered node's position through interpolation/extrapolation in _process (the Unity way...) ii) make them kinematic and move manually in _process (UCharacterMovementComponent...)

reduz commented 7 years ago

Again, I think this should not be too difficult to add to Godot so it happens automatically on most objects (fixed step interpolation) problem is, I have no test hardware.

On Fri, Jul 14, 2017 at 8:23 PM, tamkcr notifications@github.com wrote:

that is just how physics works, solutions adopted by other engines:

i) separate the entity's physics node and view node (ie. make them siblings) and use a script to set the rendered node's position through interpolation/extrapolation in _process (the Unity way...) ii) make them kinematic and move manually in _process (UCharacterMovementComponent...)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/9561#issuecomment-315490003, or mute the thread https://github.com/notifications/unsubscribe-auth/AF-Z23OLUdRCGLL3DwwO3JznggBWEOuBks5sN_iNgaJpZM4ORui1 .

tamkcr commented 7 years ago

on a 60hz monitor a physics fps of ~25 (or any lower than 60 number) should recreate the effect (amplified)

ghost commented 7 years ago

@tamkcr thanks i just implemented your option #2.

@reduz, understood. if I could ship u a 144hz monitor i would.

@raymoo, here is the movement with a kinematic body using move with delta inside _process.

It's smooth as butter on my 144hz monitor. if you have a chance, please try this on your 60hz monitor

https://drive.google.com/file/d/0B7tVcnnVmWAWdk9sdmwzbjJkZmc/view?usp=sharing

i just tested this on a 23hz monitor and if i hold down D and wait 3 seconds I am still in the same place as playing on a 144hz monitor. Why is that, when there is still different delta times?

I thought different delta times is a bad thing. How can the character be in the EXACT same place when it's multiplying by different deltas?

raymoo commented 7 years ago

@Dillybob92 It's smooth for me too (except for lag spikes)

I would say it's behaving similarly because all movement is linear. Euler integration works perfectly if you have a fixed velocity. Maybe you could try making some motion with an acceleration that has a strong effect (no capped velocity, high acceleration).

ghost commented 7 years ago

@raymoo

Okay, it seems like the solution is to use a kinematic body and move under _process as tamkcr alluded to.

---VERY IMPORTANT---

I also found that using the Camera2D when moving a kinematic body under _process WITH a 144hz monitor will cause jitter. This is because I believe Camera2D is updated at a fixed_fps of 60 by default. If I change fixed_fps to 144, the Camera2D is smooth. However, that means for 60hz monitor users it will be jittery. Thus, if anyone sees this issue, use my code above and you will see no jitter on 144hz and 60hz monitors

Thank you so much raymoo for the testing.. We need to get reduz a 144hz monitor fast lol

e: I got the viewsonic xg2401 for only $189 off Amazon refurbished. Cheapest one that I think that is out there

raymoo commented 7 years ago

@Dillybob92 Have you tested it with nonlinear movement?

ghost commented 7 years ago

@raymoo Like diagonal? Yep just tested that now, held down S+D for 2 seconds. Reached the same spot on the map for both 25hz and 144hz monitors

Code:

var direction = Vector2()
var speed = 15
func _process(delta):

    if player_states.right:
        direction.x = speed  

    if player_states.down:
        direction.y = speed  

    if player_states.left:
        direction.x = -speed 

    if player_states.up:
        direction.y = -speed  

    if is_colliding():
        var n = get_collision_normal()
        direction = n.slide( direction )

    velocity =  (direction * speed) * delta
    if get_global_pos().x > 0 and get_global_pos().y > 0:
        move(velocity)

    direction.x = 0
    direction.y = 0
raymoo commented 7 years ago

No, non-linear meaning the velocity changes over time, like a ball that accelerates downward.

tamkcr commented 7 years ago

not much reason to test that, when its kinematic only the scripts are moving it and the consistency becomes purely implementation specific, i.e. not related to this issue. (sometimes networked games faces a somewhat similar problem as net code updates at a different, variable rate)

raymoo commented 7 years ago

@tamkcr Any code that does

velocity += acceleration * delta
move(velocity * delta)

etc will be affected.

tamkcr commented 7 years ago

that's the point, we already know such implementation will be affected and is not related the issue concerned, so let's not derail.

raymoo commented 7 years ago

@tamkcr What implementation of acceleration (controlled by input) would not be affected? It's not unrelated to this issue because you get consistency if you have a fixed timestep.

tamkcr commented 7 years ago

e.g.(pseudo code) if W_is_pressed: set_pos(last_still_position + move_direction * (acceleration * (min(current_time - time_W_is_pressed, time_needed_to_max_speed)^2) / 2 + max_speed * max(0, (current_time - time_W_is_pressed - time_needed_to_max_speed))), this is an analogy to how some networked games implement movement abilities as packets arrive at different and variable rates. in single player there will still be slight error from when time stamps can be polled but it would not accumulate as much.

I say this is not related because the issue concerns on how position is read during the update loop for nodes managed by the physics engine, which I believe is provided "as is" currently. If you move nodes manually in _process the script should have full control and so bear responsibility to tackle these problems (you can recreate a fixed loop in it, ue4 does that for character movement IIRC).

raymoo commented 7 years ago

@tamkcr Ok. It doesn't handle instantaneous velocity changes or if the player was moving already when they hit a key, but I see how it could be extended to cover those cases.

This issue though doesn't concern specifically how position is read, it is looking for a way to have a game look smooth on both 60Hz and 144Hz monitors. The reason I brought up differences caused by different timesteps was to argue that "just move movement to _process" would cause different behavior on different configurations. I had not considered an exact solution to movement, though, so thank you for bringing that up. However, doing it that way is significantly more complicated for the programmer, so interpolation between fixed frames would still be useful to have.

tamkcr commented 7 years ago

good movement code is bound to be complicated, that is why the physics system is there to assist us. yes optional interpolation between fixed frames is likely the best out-of-the-box solution, but it should be there only for movements happened in fixed loops.

if we write a script to move something in a jittery / inconsistent manner directly it should be jittery / inconsistent, as intended. if you dont want to face the complexity of moving things objects then use the physics tools, the solution is already there (fails in certain setups currently which is why this issue is reported).

raymoo commented 7 years ago

@tamkcr Okay, it doesn't seem like we disagree.

Ranoller commented 7 years ago

reduz:

Probably you can change your monitor refresh rate to 75 hz or other rate in driver configuration and see the problem.

I have 2 screens, one at 60 hz other at 75 hz, when i execute my prototype and init the profiler it shows that godot leave empty frames when the game is in the 75 hz monitor, if the game screen is in the 60 hz monitor it´s ok. In 75hz the camera travelings are jerky. Animations and particle systems with no camera movements look equally and do not have perceptibly effect. This is at 75hz, I suppose that 144 hz should do more jittering.

raymoo commented 7 years ago

@davarrcal Maybe just camera interpolation is enough then?

Ranoller commented 7 years ago

I don´t think so.... it´s something deeper. In my game probably camera interpolation it´s ok, because there is a lot of movement and particles, but if you execute "car demo", that doesn´t have camera, with the car movement at physics fixed at 60 fps, in the 75 hz monitor there is a continuous nauseating shake in the cars. Their turn fuzzy and jerky. I´m not expert, but i read other similar engines problems and one solution was frame interpolate (but i don´t understand deeply this concept, so I can´t suggest that).

Edit: This problem is not attached to the "stuttering" problem releated to the "car demo", this stuttering happens in both monitors. Maybe the cause is close to this problem and can have a jointly aprroach to the solution... who know´s.

Zireael07 commented 6 years ago

Why close? This is actually a legitimate issue, not the OP doing something wrong.

akien-mga commented 6 years ago

Bug triage note: The original issue reporter closed all their issues before deleting their account. If you are affected by a similar issue/want a similar feature, please file a new ticket (referencing this one if it contains useful information) so that it can be further looked at and eventually resolved.

fossegutten commented 6 years ago

I still had this problem with a 100hz monitor in godot 3.0.2 stable. However, adding a late update method that updates camera position after the player has moved fixed the issue for me. This also made the whole game feel smoother 👍

Ranoller commented 6 years ago

Can you explain your camera "late update" aproach? Would be useful for all....

fossegutten commented 6 years ago

screenshot 20 Basically my latest solution is adding the camera to the bottom of the node tree in every level. Camera is self contained with its own script, so it runs after all other nodes are finished calculating their positions. Would be really easy if godot had several game loops called at different times, like unity, gamemaker studio and monogame to name a few.

ejnij commented 5 years ago

For me camera smoothing causes jitters if physics fps < monitor hz (and actual fps is higher than physics). If physics fps is higher than monitor hz then it's fine, but then it's kind of a waste of resources? Being able to adjust physics fps in a similar way to what Juan said here - https://twitter.com/reduzio/status/984783032459685890 would be great. Not sure when it would be implemented though.

Ranoller commented 5 years ago

I think you can do that now in gdscript... methods are exposed. You can detect engine fps... if you have enabled vsync, godot report to you current fps of current screen... so you can adjust physics fps to match that... i don't know if current proposal involves some other workarounds, but adjust physics fps in runtime is not new thing to the engine, it's only not documented.