Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: configurable refresh-rate and change default to 60fps #8680

merged 8 commits into from Feb 19, 2021


Copy link

@TrueBrain TrueBrain commented Feb 16, 2021

Motivation / Problem

While working on OpenGL for SDL, I noticed a few things with the video driver that stood out. It made me wonder if they could be changed .. and so I tried them out. These combined with OpenGL give to me a much better experience with SDL.

While doing SDL, I found that doing all drivers is pretty easy if (and this is a big if) std::chrono::steady_clock has a high enough resolution on all our targets. Turns out, they do!


There are several, rather unrelated, changes in this PR, but combined gives a good total experience.

  1. draw-ticks and game-ticks are separated. This means that during fast-forward, the speed of drawing is no longer influenced. This leaves more time for the game-ticks, making fast-foward a lot faster. Additionally, draw-ticks demand time, making it hit its mark a lot better.

  2. make refresh-rate configurable, and 60 by default. Our 33.33 was always a bit weird, and most modern monitors do 60Hz. 60 fps is a lot smoother for the eye. Users can change it to 30, or, in my case, to 144 if they like. The higher the number, the less time there is available for simulation!

  3. switched all video-drivers to std::chrono, and applied many fixes to smooth out how often we (nearly) hit our deadline. This makes the fps counter a lot more stable.

There is one TODO in the code, as it depends on another PR to be merged first (startup-configuration-entries).

And as you can see in the diff, there is a lot of duplication between the drivers now. We could abstract this, but I was unsure we should go that route. They are the same now, as we worked a lot on them lately, but there is no guarantee that works in the future. So I left that out for now .. but creating a "base" driver with the mainloop in there is very much possible.

Tested it on SDL2, SDL1, Allegro, Cocoa, Win32, dedicated server and emscripten; they all work as I would expect.


  • Drawing at 60fps or 144fps has an impact on performance. Mostly, on SDL it takes 5ms to draw a frame. This means that if we run at 60fps, 30% of the time is needed to draw. OpenGL PR mostly fixes this, as that reduces this time to almost nothing.
  • Things like "new game" and "abandon game" now sometimes have their window disappear before the command is executed, giving for a few frames the illusion nothing happened. During those times, the mouse hangs, so it is not like they can do anything anyway.

What issues does this solve?

  • Sluggish mouse behaviour (and window behaviour); mostly we have reports from macOS users about this, and it drove me crazy personally.
  • When the game is taking more than 30ms to run (mostly, the GameLoop), it would still sleep (which takes ~6ms). This is time we could have spend trying to play the game.
  • Fast-forward is much faster, as it doesn't waste time on drawing frames nobody is going to see anyway.
  • More decoupling of game-state from GUI.

Checklist for review

Some things are not automated, and forgotten often. This list is a reminder for the reviewers.

  • The bug fix is important enough to be backported? (label: 'backport requested')
  • This PR affects the save game format? (label 'savegame upgrade')
  • This PR affects the GS/AI API? (label 'needs review: Script API')
    • ai_changelog.hpp, gs_changelog.hpp need updating.
    • The compatibility wrappers (compat_*.nut) need updating.
  • This PR affects the NewGRF API? (label 'needs review: NewGRF')

src/video/sdl2_v.cpp Outdated Show resolved Hide resolved
src/video/sdl2_v.cpp Outdated Show resolved Hide resolved
@TrueBrain TrueBrain changed the title Several improvements to SDL2 video driver Improvements for all video drivers Feb 17, 2021
@TrueBrain TrueBrain changed the title Improvements for all video drivers Feature: configurable refresh-rate and default to 60fps Feb 17, 2021
@TrueBrain TrueBrain changed the title Feature: configurable refresh-rate and default to 60fps Feature: configurable refresh-rate and change default to 60fps Feb 17, 2021
@TrueBrain TrueBrain added the preview This PR is receiving preview builds label Feb 17, 2021
@DorpsGek DorpsGek temporarily deployed to preview-pr-8680 February 17, 2021 15:09 Inactive
@TrueBrain TrueBrain marked this pull request as ready for review February 17, 2021 15:12
Adding to _realtime_ticks in a random place is a bit of a hack,
and by using modern C++, we can avoid this hack.
On all OSes we tested the std::chrono::steady_clock is of a high
enough resolution to do millisecond measurements, which is all we

By accident, this fixes a Win32 driver bug, where we would never
hit our targets, as the resolution of the clock was too low to
do accurate millisecond measurements with (it was ~16ms resolution
_realtime_tick was reset every time the diff was calculated. This
means if it would trigger, say, every N.9 milliseconds, it would
after two iterations already drift a millisecond. This adds up
pretty quick.
During fast-forward, the game was drawing as fast as it could. This
means that the fast-forward was limited also by how fast we could
draw, something that people in general don't expect.

To give an extreme case, if you are fully zoomed out on a busy
map, fast-forward would be mostly limited because of the time it
takes to draw the screen.

By decoupling the draw-tick and game-tick, we can keep the pace
of the draw-tick the same while speeding up the game-tick. To use
the extreme case as example again, if you are fully zoomed out
now, the screen only redraws 33.33 times per second, fast-forwarding
or not. This means fast-forward is much more likely to go at the
same speed, no matter what you are looking at.
Before, every next frame was calculated from the current time.
If for some reason the current frame was drifting a bit, the
next would too, and the next more, etc etc. This meant we rarely
hit the targets we would like, like 33.33fps.

Instead, allow video-drivers to drift slightly, and schedule the
next frame based on the time the last should have happened. Only
if the drift gets too much, that deadlines are missed for longer
period of times, schedule the next frame based on the current

This makes the FPS a lot smoother, as sleeps aren't as exact as
you might think.
Sleep for 1ms (which is always (a lot) more than 1ms) is just
randomly guessing and hoping you hit your deadline, give or take.

But given we can calculate when our next frame is happening, we
can just sleep for that exact amount. As these values are often
a bit larger, it is also more likely the OS can schedule us back
in close to our requested target. This means it is more likely we
hit our deadlines, which makes the FPS a lot more stable.
@DorpsGek DorpsGek temporarily deployed to preview-pr-8680 February 18, 2021 09:34 Inactive
Most modern games run on 60 fps, and for good reason. This gives
a much smoother experiences.

As some people have monitors that can do 144Hz or even 240Hz, allow
people to configure the refresh rate. Of course, the higher you
set the value, the more time the game spends on drawing pixels
instead of simulating the game, which has an effect on simulation

The simulation will still always run at 33.33 fps, and is not
influences by this setting.
@DorpsGek DorpsGek temporarily deployed to preview-pr-8680 February 18, 2021 10:18 Inactive
@DorpsGek DorpsGek temporarily deployed to preview-pr-8680 February 18, 2021 12:36 Inactive
… that shouldn't be closed yet

The higher your refresh-rate, the more likely this is. Mostly you
notice this when creating a new game or when abandoning a game.

This is a bit of a hack to keep the old behaviour, as before this
patch the game was already freezing your mouse while it was changing
game-mode, and it does this too after this patch. Just now it
freezes too a few frames earlier, to prevent not drawing windows
people still expect to see.
Copy link

@LordAro LordAro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues.

Would be much easier to review if you deduplicated the main loop ;)

@TrueBrain TrueBrain merged commit c4df0f9 into OpenTTD:master Feb 19, 2021
@TrueBrain TrueBrain deleted the sdl-performance branch February 19, 2021 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
preview This PR is receiving preview builds
None yet

Successfully merging this pull request may close these issues.

None yet

4 participants