This is also my main pet peeve on the web. You can't measure a precise frame duration (made much worse by Spectre/Meltdown mitigations), but you also can't query the display refresh rate.
In the 80's we took perfectly smooth scrolling and animations for granted, because most 80's home computers and game consoles were proper hard-realtime systems. Counter-intuitively this is harder to achieve on modern PCs that are many thousand times faster.
1. Derive ideal number of elapsed frames using elapsed time from start divided by framerate.
2. Run updates to "catch up" to ideal frames.
3. Observe "rubberbanding" artifact as game oscillates between too slow and too fast.
4. Add limits to how many catchup frames are allowed per render frame to stop the rubberbanding. Implement a notion of "dropped frames" instead, so that our ideal time accounting has a release valve when too much catchup is needed.
5. Now apply a low-pass filter to the amount of catchup frames over time, e.g. if you have a tick of 120hz but your monitor is at 60hz you are likely to see an occasional high frequency oscillation in how many tick frames are supplied per render frame, like [2,3,2,3,2,3]. Filtering can make this [2,2,2,3,3,3]. (It's been a while since I did this, so I don't recall my exact algorithm)
The end result of this effort is that wall-clock time is obeyed over the long-run(minus dropped frames) but the supplied frames are also able to maintain the same pacing for longer periods, which makes the moment-to-moment experience very predictable, hence smooth.
While I haven't tried it, I think the equivalent thing when interpolating would be to freeze a certain issued delta time pace for some number of frames.
Ultimately I had to fill the screen as best I could, if the paint happened ahead of the deadline I had to decide on painting ahead on the next couple of frames, or doing cleanup work. There was never enough time for both.
Proper hard realtime means the software is designed to meet stringent time deadlines. If a deadline is missed then the system has failed.
Soft real time means you tolerate missing one or more deadlines if the system is designed to handle it.
The 80's hardware only ran the game code so there was never any CPU contention. There was no kernel, scheduler, threads or processes. The programmers could wrap their heads round the simpler hardware and use all available tricks to optimize every clock tick to do useful work.
Nowadays we have stupid cheap multicore GHz CPU's for a few dollars with GB of RAM so you brute force your way through everything on a general purpose OS like Linux.
OTH making the hardware components "asynchronous" and the timings "unpredictable" enabled today's performance (e.g. by introducing caches and pipelines).
Vulkan has extensions for measuring frame timings. I suspect DirectX 12 does too, given how similar it is to Vulkan.
See: https://www.khronos.org/registry/vulkan/specs/1.2-extensions...
https://blog.unity.com/technology/fixing-time-deltatime-in-u...
Converting nominal cycles to time can be unreliable in some cases but not impossible.
Is it very well known in the game industry? I have trouble finding anything about it.
I wrote about it in a thread here called: "Why bugs might feel “impossible”" about two months ago and someone commented that the game TerraNova (from 1996) had a fully deterministic engine.
BTW TFA say StarCraft 2 (2007) used the technique but Warcraft 3 (2003) was already using a deterministic engine. Save files for Warcraft 3 games (including multiplayer ones over the Internet) consisted in only recording the player inputs and the "tick" at which they happened. This make for tiny savefiles, even for very long games.
So basically: several of us independently discovered that technique in the nineties. There may have been games in the eighties already using such a technique but I don't know any.
Fabien calls it "fixed timeslice". I find Fabien's solution much simpler. It is quite different to Fiedler's one.
Has anyone investigated the idea of sacrificing an entire high priority thread to a timer loop that busy waits over a collection of pending timers? Most gaming PCs these days have more than enough cores to get away with the idea of a full-time high-precision timer thread.
In my experiments of this idea, I have had no trouble getting jitter under 100uS in managed languages (C#/Java). Millions of timers scheduled seems to be no problem, assuming mild optimizations are employed (i.e. order by next desired execution tick). With all of this magic turned on, I still have 31 other cores to play with. Task.Delay looks like a preschool art project by comparison to the precision I get out of my (arguably dumb-as-hell) DIY timer impl.
I have used this approach to schedule client frame draws in a custom UI framework, and it is indistinguishable from butter on my computer and across reasonable networks. In my prototypes, the client input events are entirely decoupled from the frame draws by way of a separate ringbuffer/batching abstraction. There is actually no locking in my architecture. Draws back to the client use immutable snapshots of state, so current updates can continue unfettered. The state is only ever mutated from a single thread.
Technically, I sacrifice ~2 threads in my architecture, as the ringbuffer also uses a busy wait technique.
All of this said, if you are willing to burn more power, use more cores, and think outside of the box just a bit, you can do some pretty amazing shit.
Consider these ideas at broader scale too - What if you could amortize the cost of that power-hungry timer thread for thousands of gamers instead of just 1? Are there maybe some additional benefits to moving 100% of the game state to the server and shipping x265 frames to the end users?
In the end, the only important event is when your new frame shows up on screen, and this is (a) very precise, and (b) out of your control anyway, at least for traditional fixed-refresh-rate displays.
It doesn't really matter where exactly within a frame-time-slice your per-frame-code runs, or how long it takes as long as it fits comfortably within one display refresh interval, the basic timer resolution that matters is the display refresh rate.
PS: it's more difficult if you also need to minimize input- and networking-latency, that's were it may make sense to to use separate threads I guess. But on desktop PCs (versus game consoles), so much also depends on the operating system playing ball...
PPS: busy looping probably isn't a good idea when power consumption matters (e.g. on mobile)
I don't know of any online game that doesn't track the official game state on the server. This is required for anti-cheat reasons, if nothing else. However, networking would typically be optimized to send small packets of information, and typically over UDP. If a packet is missed, the next packet will just be overwriting it anyway, so no big deal. Clients would basically just locally simulate game state and then reconcile that against the game state being received from the server.
Also, rendering has to be done per-client anyway, since each one has a different camera state. So there's no economy of scale for doing that on the server rather than the client. In fact, there's probably anti-economy: Servers that aren't rendering don't need expensive video cards unless they're using them for physics.
I have the understanding that more modern games do sometimes use TCP sockets. And obviously modern bandwidth has made streaming frames realistically possible. Hence the recent emergence of streaming game services.
A lot of titles are more or less serverless peer-to-peer.
[0] https://github.com/Syn-Nine/mgfw
[1] https://github.com/Syn-Nine/rust-mini-games/tree/main/2d-gam...
Ideally the 'render clock' would query the display refresh rate from the operating system, but this is harder than it sounds. The next best option is to measure the frame time, and then round to the next "common" refresh rate (AFAIK that's the only way it works on the web for instance).
I thought a game engine doing this figured out the collisions when the trajectories changed so then it's just a matter of rendering those known effects (rather than the more naive approach of checking each tick or frame whether anything's collided). Note that doing this at impulse can cause the game to slow down when many objects are moved at the same time, like an explosion. I think it works pretty well because most game objects are stationary, or even immovable; also moving objects are rarely subject to new accelerations—they stay ballistic.
But IANAGameDev, so do correct me if you know something about this technique.
t=0
cube1 p=5,0 v=0,0 m/s
cube2 p=1,0 v=2,0 m/s
// Game knows cube1 and 2 will collide at t=2
t=1
cube1 p=0,0 v=0,0 m/s
cube2 p=3,0 v=2,0 m/s
cube3 p=0,0 v=5,0 m/s
// Game knows cube1 and 2 AND 3 will collide at t=2
t=2
all cubes hit at 5,0This makes me want a French Vanilla from Tim Hortons.
const TIMESTEP = 1/60;
let since_last_frame = 0.0;
function update(dt) {
since_last_frame += dt;
while (since_last_frame > TIMESTEP) {
tick();
since_last_frame -= TIMESTEP;
}
}
function tick() {
// handle game logic at a fixed timestep
}