SuperTux / supertux

SuperTux source code
https://supertux.org
GNU General Public License v3.0
2.48k stars 474 forks source link

Camera is jittery #1117

Open Alzter opened 5 years ago

Alzter commented 5 years ago

SuperTux version: master branch System information: Windows 10

Expected behavior

Camera moves smoothly when walking

Actual behavior

Camera (and Tux) jitters when walking 2019-06-03_10-10-20

HybridDog commented 5 years ago

related: #1055

Does it happen to you with and/or without vsync?

Alzter commented 4 years ago

This issue still happens. It's less noticable when Vsync is set to adaptive but it still happens.

Rusty-Box commented 4 years ago

@Alzter is this issue still a thing? If not I would close this.

HybridDog commented 4 years ago

Currently the camera still jitters a bit because the screen displays 60 FPS and the logical game steps happen at ca. 64 FPS; I think this is difficult to fix because double buffering may offset the frames. A problem similar to this also happens when viewing a 24 FPS video (almost all videos are ca. 24 FPS) on a 60 fps screen.

Alzter commented 4 years ago

Currently the camera still jitters a bit because the screen displays 60 FPS and the logical game steps happen at ca. 64 FPS; I think this is difficult to fix because double buffering may offset the frames. A problem similar to this also happens when viewing a 24 FPS video (almost all videos are ca. 24 FPS) on a 60 fps screen.

Sounds tricky. Maybe reverse engineer how the older versions of SuperTux had a smooth camera (like 0.5.1) and apply that? We don't want to be making steps backwards and making the game clunkier :P

HybridDog commented 4 years ago

Maybe reverse engineer how the older versions of SuperTux had a smooth camera (like 0.5.1) and apply that?

I think supertux 1 didn't have this jittering. It is possible to change the logical fps to 60, but this could subtly change some gameplay features.

mamarley commented 4 years ago

This is still happening in 0.6.1 as well. It also happens on Linux, in addition to Windows as originally reported.

EDIT: For what it is worth, I recompiled after changing LOGICAL_FPS to 60 in src/supertux/constants.hpp but I still get the juddering.

tobbi commented 2 years ago

Is this still an issue with the latest master from http://download.supertux.org ?

mstoeckl commented 1 month ago

Is this still an issue with the latest master from http://download.supertux.org/ ?

Not the original poster, but I observe jittering like that which was shown in the first post's video, when using SuperTux built from current git master.

EDIT: For what it is worth, I recompiled after changing LOGICAL_FPS to 60 in src/supertux/constants.hpp but I still get the juddering.

Doing this doesn't actually set the logic rate to 60 fps. Due to conversions to/from SDL ticks (milliseconds), the game state actually updates at 1000/floor(1000/LOGICAL_FPS) Hz. Thus the current LOGICAL_FPS = 64.0f produces 66.66 game ticks/sec, and LOGICAL_FPS = 60.0f produces 62.5 game ticks/sec. Actually implementing a 60 ticks/sec logic rate would probably require changing ScreenManager::loop_iter to measure time with higher precision; for example, using std::chrono instead of SDL_GetTicks .

HybridDog commented 1 month ago

Using 60 Hz for the logical steps sounds like a bad solution to me since it only works with 60 Hz displays.

#

The video looks like this when I move every frame ca. 21.4 pixels horizontally: https://github.com/SuperTux/supertux/assets/3192173/ef689f43-4e4c-4668-a02d-59b26fb86727 Difference between successive frames: https://github.com/SuperTux/supertux/assets/3192173/a111a315-e1b7-40d0-96d9-8696ba1bd202

mstoeckl commented 1 month ago

Using 60 Hz for the logical steps sounds like a bad solution to me since it only works with 60 Hz displays.

If you haven't seen it already, I made a PR (https://github.com/SuperTux/supertux/pull/2962) adding a option that can make rendering at frame rates >> logical rate look smoother. With that (or a similar approach) a 60 Hz logical rate would be feasible. Despite this, I don't think switching to 60Hz would be a good idea, for the different reason that it would slightly change physics behavior and risk breaking existing levels. One of the goals of my comment was to warn that, with the current game loop, it is not possible to get 60Hz behavior just by changing a constant.

Also: some more investigation of jittering suggests that part of it is caused by thread scheduling. Even though the screen updates at almost exactly 60 Hz, the time between successive calls to SDL_GetTicks can vary by +/-.3 milliseconds (for me), which makes some frames slightly draw slightly ahead of where they should be, and others slightly behind, although on average things render OK.

mstoeckl commented 2 weeks ago

Also: some more investigation of jittering suggests that part of it is caused by thread scheduling.

A very small part of the jittering is caused by the rounding of timestamps to millisecond precision in SDL_GetTicks , so I've made #2983 to use a higher resolution counter. It certainly does not fix the entire problem but the change is simple and has other benefits.

Also: I made an experimental branch which adds an option to assume nominal fps which should avoid timing jitter entirely if SuperTux never misses a frame, and SDL gets the monitor framerate right (which it doesn't always). See: https://github.com/mstoeckl/supertux/tree/match-screen . I don't think it is yet good enough to make an PR out of, and entirely different approaches may prove better.

HybridDog commented 6 days ago

Maybe it is possible to synchronise frames to the display refresh rate with an additional thread which manages two buffers:

Loop of thread 1:

  1. Handle game logic if needed
  2. Get the last time a vblank happened (This is the time which thread 2 remembers in its loop.)
  3. Set target_time to the time of the last vblank time plus 1/display_refresh. target_time the time when the next vblank happens.
  4. Render a frame where the camera movement prediction uses target_time
  5. Get the last time a vblank happened. Let time_last_blank be this time. If target_time = time_last_blank + 1/display_refresh holds, save the rendered frame to buffer C and sleep until the next vblank happens. Otherwise target_time <= time_last_blank (usually target_time = time_last_blank) holds, and we save the rendered frame to buffer B.
  6. goto 1

Loop of thread 2:

  1. Wait for the next vblank, i.e. wait until the window system is ready to receive a new frame with vertical synchronisation
  2. Remember the current time as the last time a vblank happened
  3. Send the frame in buffer B to the window system
  4. Copy the frame in buffer C to buffer B
  5. goto 1

If rendering is fast enough, i.e. rendering a frame takes less than 1/display_refresh time, the frames should be aligned to the display refresh rate without jittering: