1

My goal:

  • Implement the game update loop so that it's deterministic (i.e. every frame of updation behaves the same when run multiple times with same frame-input, does not depend on processing speed etc.)
  • Limit the framerate of the game at (maximum) 60fps (can be slower if CPU hangs)

What I don't want to do:

  • I don't want to use a delta_time to compute position updates, because it violates the goal
  • Don't want to introduce any additional input-lag that would come with any sort of buffering (for interpolating time between frames etc.)

What I came up with:

  • I use vsync to guarantee that the game never produces more than 60 fps
  • I disallow any screen-mode that is not running at 60Hz because the game would run at different speeds
  • I allow windowed mode only if the current screen-mode is at 60Hz

Is there a better approach that what I'm doing to achieve what I want, perhaps without limiting the supported refresh rates?

Lake
  • 143
  • 8
  • 2
    The usual advice here is to use a fixed timestep, so your delta time is consistent for every simulation step, regardless of the rendering framerate. Have you tried this? – DMGregory Sep 22 '20 at 11:47
  • So basically that is what my solution is doing, together with vsync and forcing the 60Hz which guarantees that the game will run at the same (max) speed every time. This still won't allow me to support many refresh rates though, hence my question. I suppose there just isn't a good way to target all refresh rates while keeping the update deterministic across platforms.... – Lake Sep 22 '20 at 12:03
  • I suppose I could calculate a fixed time step based on the refresh rate, what I don't like is that the game update will not then be deterministic across platforms. – Lake Sep 22 '20 at 12:05
  • 2
    It sounds like you've misread the article. Supporting a variety of rendering/refresh rates while keeping the simulation tick consistent is exactly what a fixed timestep accomplishes. I explain this at more length here. Your simulation step does not have to be equal to or related in any way to your rendering interval. – DMGregory Sep 22 '20 at 12:06
  • I did misunderstand, thanks for the detailed explaination! It would actually solve my problem (with interpolation for smoothness) but unfortunately it looks like it does add one frame of input delay which I'd love to avoid. I'll add no additional input delay to the goals, but thanks for the interesting read :) – Lake Sep 22 '20 at 12:15
  • 1
    The input delay is actually less than one frame when you're using interpolation, because the target point of the interpolation is moved by the latest input, so you can see a change immediately in the interpolated result, without waiting for that simulation step to move all the way from future to past. You can also apply purely visual/auditory responses to the input instantly in the frame it's received. So you can keep instantaneous response while also getting simulation consistency this way. – DMGregory Sep 22 '20 at 12:19
  • That's actually interesting. For this approach I suppose I would need to implement the update loop in a separate thread, and time it at the desired rate with sleep() like functions? – Lake Sep 22 '20 at 12:26
  • 1
    No, that is not normally how it's done. Since the rendering step needs to wait until the newest game state update is completely finished before interpolating and rendering it, it's not quite as simple as running these two features in parallel. Even single-treaded solutions can get a lot of these benefits, as shown in the Gaffer on Games examples. – DMGregory Sep 22 '20 at 12:30
  • Ok now I start to understand the article, this makes a whole lot of sense now. If you make an answer with the link to the article I'll accept it, this is extremely useful :) – Lake Sep 22 '20 at 12:36
  • I'll be at the office all day — would you care to write an answer describing your understanding of the approach? – DMGregory Sep 22 '20 at 12:58
  • I just feel guilty about not giving you the answer points but I'll credit you in the answer then. :) – Lake Sep 22 '20 at 12:59
  • Actually one thing that still bothers me is that it doesn't seem possible to interpolate frame-based animations in the same way you can interpolate positions or rotations. If I design a frame-based animation to run at 12FPS for example, there is no good multiple at 75Hz and it will be uneven no matter what. This is the only issue I can still think of which probably doesn't have a good solution. – Lake Sep 22 '20 at 13:36

1 Answers1

3

DMGregory guided me towards a good solution explained in this article:

Use a fixed timestep

This solution allows the total decoupling of the simulation and rendering frequency, while allowing for a fixed timestep for the simulation part.

Visual smoothness is preserved by interpolating between the previous and current (last) state of simulation with a blending factor that is given by the total remainder amount of non-yet-simulated time (since simulation happens in fixed step intervals).

This introduces a <1 frame lag between the rendering and the simulation as the rendered state lags behind the simulation, but this does not introduce input lag as the inputs are taken into account in the blending through the current state.

So in terms of lag, responsiveness remains instantaneous while the actual drawn position falls behind by a fraction of an update.

Lake
  • 143
  • 8