Game Programming Patterns: Double Buffer (2014)
gameprogrammingpatterns.com
gameprogrammingpatterns.com
Wasn't too fond of my C# deep copy solution: var serialized = JsonSerializer.Serialize(this); return JsonSerializer.Deserialize<GameState>(serialized);
I took an interested in functional programming, pure functions, immutability, etc. soon after.
If execution of logic is always performed in the same order, then the partially updated world state is not a problem for determinism (though it may be for correctness). Double buffering may let you reorder the execution of logic in different ways and still keep it deterministic. This would likely be needed for logic that executes in parallel threads, which is increasingly important for high-performance game engines.
diff = differential(state)
state.update(diff) # or state = update(state,diff)
# or state += ∂_∂t(state) if you're feeling overly mathyI've been tempted lately to model application state as the integral over time of all events (deltas) that have occurred. For example, imagine a simple game state:
type GameState = {
x: float; // x coordinate
t: float; // current time
v: float; // velocity variable
}
If initial game state = { x: 0,
t: 0,
v: 0, };
and you have some events (deltas): [{t: 1, v:1}, {t:2, v:-1 /*this is deltaV, so back to v: 0*/}, {t: 3, v:2}, {t:5, v:-2}]
You could "sum" them up (integrate over time) and get a game state of x = 5, v = 0 where t >= 5.I've found this approach kinda sucks when you add things like collision detection. Your application would have to emit velocity deltas when objects would collide. If you've got a point that bounces around in a box with perfectly elastic collisions, then over time you'd have an infinite number of these collision/velocity delta events. But as you receive new events, all your precomputed collision events are for nothing (if your player logs out). So the traditional update loop simulate each tick as it occurs seems to work best.
Is there a more general way of thinking about this integration? Maybe with respect to another variable? Perhaps it would address this problem I'm having where I feel forced to quantize my game state onto ticks.
[1] Elliott, Conal; Hudak, Paul (1997), "Functional Reactive Animation" <http://conal.net/papers/icfp97/>
> In their hearts, computers are sequential beasts. [...even with several cores, only a few operations are running concurrently.]
My one small nitpick is that the presentation at the beginning felt very single-core CPU centric, even with the side note on threading. GPUs are both parallel processors, and they’re being used in parallel with the CPU; many games are running things like CPU physics in parallel while the GPU is doing rendering. And to really see this point, many games use triple buffering and are doing some amount of overlapped rendering of two frames at the same time, not just one.
For simpler things, not 3D, sorting objects and a single buffer render still work just fine.
Break the display into whatever elements make sense, and insure timing is all sorted. Run tests to get maximums.
Once it is running, the rest feeds it with pointers to objects, and lists or the objects get generated dynamically at display time.
Various schemes apply to overtime requirements. In old Williams arcade games, one method was to just move baddies to a non displayed region. Aggresive players would fire off a ton of shots to trigger this.
Another is to delay animation or movement.
The kernel can do that, but it gets more complex and slow. Dumber kernels are faster. Higher level code can manage what the kernel has to do.
One thing is smart to do though, and that is to have the display kernel fail gracefully. Use hardware timers, or loop counters to prevent overdraw and the loss of display sync normally associated with that condition.
along with a whole lot of other fascinating stuff
At one point, they were drawing tiles, the walls associated with them, objects, etc...
Moving objects would end up between tiles and the sprite would be cut off by a different tile draw. Worse, there was no sort order that worked for all cases. They ended up creating, what the speaker called "an ai" to move the object away from the draw conflict, get it all drawn, then move the player back, just ahead of the actual motion...
In hindsight, they could have just decoupled the walls from the tiles and it all could have been a lot simpler!
The idea being to have the graphics there for the display device to render them. Once it has done that, they are no longer needed, until next frame.
Doing that actually needs only a small buffer, but is timing intense, also compute and write demanding.
If one has a sprite capability, and or a larger, or full frame buffer, just write from the top down, starting at the beginning of blank from the previous frame.
Drawing behind works simplest assuming a full frame buffer, so the new follows the old once the display has pushed those pixels to eyeballs.
Another scheme, used in DEFENDER and ROBOTRON, among many others, is to divide the screen into top and bottom regions. Or more, depending.
Render into the one the display is not using. This is more coarse, but can be less complex to arrange.
The timing depends. If one is working scanline by scanline, yeah. Need accurate timing.
When working with screen regions, or just a blanking period, the requirement is relaxed. Really, all you need is to understand the speed of the object draw engine and a quick sort to insure the initial part of the frame is drawn in time for the display.
Screen regions also work well when the renderer cannot get a full frame drawn every display frame. There, a priority system will draw what is needed, delay that which is not.
I enjoy doing ths kind of programming. A full double buffer is a luxury :D
It was actually important from another aspect: the video memory was not contiguous. IIRC, it was divided in 3. So the first row was the first scan line, the second row was 1/3 the way down the page and the third row was 2/3 down the page. In order to compose graphics on the screen, you'd have to have this weird algorithm for drawing into memory. Compositing right in the video memory would have resulted in lots of timing problems.
While the Apple ][ had no graphics hardware to speak of, it did have two graphics buffers that you could switch between.
So you would draw into the non-displayed buffer and switch to that.
If you had installed the vertical-blank mod, you could even do that without tearing. (I think the //e came with that).
To swap, just tell write the new frame buffer base address to the appropriate custom chip registers, and you're done.
Since the swap should be synchronized to the beam, the actual write typically happened in a copper list anyway, so that's what you really write to in order to do the swap.
It all brings me back to being a teenager and discovering these things. :) Amiga <3.
The games just made it a visual gimmick for the player to watch the drawing instead of offering a waiting screen, rendering to a backbuffer and then copying the image over.
Frankly, I wouldn't be surprised if the rendering was even made a bit slower so that the player can see the areas fill up in human-scale time. For example, a C64 with 6502 can fill polygons faster than what you see on the screen.
Trust me, everything was flat out then. I posted a link in another reply about something similar. Have a look at this flood fill, this was zipping along at full speed.
> There are a couple of good examples at 4:57 onwards. Link at https://www.youtube.com/watch?v=3f3PFfK-9Gk
Leaderboard had vector graphics so the program knew analytically where the polygon edges are and it could simply write bytes in the bitmap, in most cases without reading back pixels.
Let's say C64 had roughly, in practise, about 15k cycles per frame. Quick calculation shows that filling the entire 320x200 screen in a trivial loop is 7-8k cycles for instructions alone, plus any delays should the graphics chip read the memory at the same time. Add to that vector math and the fact that not the whole screen will be filled and we should be able to count filling the scene in terms of frames, not several seconds.
Leaderboard might have sloppy routines to make it 'watchable' but I suspect C64 can fill faster than that if made to.
Filled vectors were notoriously hard on C64 as both the bandwidth usage and cpu cycles spent on math rack up. Yet games like Rescue in Fractalus had simpler but still filled vectors in real time.
Leaderboard is using a kind of painter's algorithm to do everything, which is flexible but requires using overdraw, and by itself, will produce rendering errors in many scenes because the depth will be sorted per object or per triangle, versus per pixel. Filled triangle rasterization is particularly expensive to compute and requires a fair amount of numeric precision, both of which compound the issue of using overdraw.
Fractalus does not use these techniques for terrain - although the exact implementation is idiosyncratic[1], it's ultimately filling an outline that defines the horizon, and then adds texture to the landscape with dot patterns. Bear in mind, it doesn't render to the full screen either, it's mostly HUD, while Leaderboard is a little more expansive.
[1] A similar technique is described for Captain Blood: http://bringerp.free.fr/RE/CaptainBlood/main.php5
There are a couple of good examples at 4:57 onwards. Link at https://www.youtube.com/watch?v=3f3PFfK-9Gk
Edited to add there is a video here https://www.youtube.com/watch?v=D9qrZC7WUio of the 128k Spectrum version. Looking at the speed I suspect they pre-rendered the images in the background and used the extra memory to store them and then copy the completed image to display when needed.
The images are all hand-drawn art, stored as compressed data and simply decompressed when needed. We don’t render (or prerender) anything.
Nice work, good to see these old systems still being played with and experimented on.
As frames are often similar (only a small portion really changes), on those slow machines many folks preferred to update a small portion of the single buffer at the right time instead of copying the whole thing.
The Spectrum couldn't so you either wrote to the live video display or used some method of copying.
The QL could in theory switch video display between two buffers but they put a section of IO/System memory (I can't remember the details) in the middle of one of the buffers so it was useless.
Some systems also had hardware sprites which weren't part of the main video ram, they were overlaid by hardware. You could also do some fun stuff with those such as rewriting them as the scanline progressed down the screen to fudge a lot more hardware sprites.
I'm out of the industry now but one of the major parts of games programming was pushing the hardware way beyond what it was designed to do. Now, as far as I understand, you write for an API.
He's also writing a new book about creating interpreters: https://craftinginterpreters.com/
AMD has this standard for interacting with the monitor refresh now called FreeSync. Now the game can communicate with the monitor to tell it when the buffer’s ready, to get around the problems caused by a fixed 60hz refresh. A lot of ‘gaming’ branded screens are supporting it now.
(b) you only get judder with vsync=on, vsync=off just flips to the new buffer as soon as it's available. The downside is that you tend to have a visible seam or "tear" in the image when the buffer flips.
(c) the memory for triple-buffering is trivial, that's not the problem. The real problem is that you need to be pushing a framerate at least as high your monitor's refresh rate, and ideally 2x your refresh rate. That's undesirable in a world where you don't have infinite money to spend on hardware.
(d) NVIDIA actually pioneered this technology, AMD came in with a copycat implementation after the fact. AMD has the advantage of not needing special hardware in the monitor, but it usually comes with the disadvantage of only being able to sync over a narrow range, such as 40-60 Hz. Some monitors have as narrow as a 10-hz sync range, and many of them tend to flicker once they get down to the lower end of their sync range. There are currently a total of three FreeSync monitors on the market that don't totally suck.
http://techreport.com/news/25867/amd-could-counter-nvidia-g-...
1) This is not the problem of FreeSync itself but of the panels and their support for VRR.
2) I have 2 FreeSync monitors at home which both do 40-144Hz (Acer XF270HUA and Benq XL2730Z). In addition, more than 2 years ago AMD added LFC to help with the classic case of "FPS drops below FreeSync range" issue. I don't notice any issues with judder when my framerate goes below the range for 1 or 2 frames.
Your info is out of date. Nowadays FreeSync is just as good as GSync if you get a monitor of good quality (and you still pay 150-300$ less just because it isn't GSync).
Despite many claims to the contrary from Freesync supporters, it's still the case that only a handful of Freesync displays will give near the same experience as nearly all GSync displays.
XF270HU/HUA (-A is a later revision of the same model) is one of the handful of good models (wide sync range). The Nixeus EDG has a wide sync range plus is the only FreeSync model known to have Adaptive Overdrive, which is important for preventing ghosting.
There are a very small handful of others, but only about 1/3 of FreeSync monitors even support LFC, and the EDG is the only (!) one that does Adaptive Overdrive, which is a basic feature on all GSync monitors. So no, this is not "out of date" at all, there really are only a handful of FreeSync monitors that are "just as good as GSync".
As a UK developer this caused a real problem writing for the US market. We'd wring the most performance we could developing on the 50Hz refresh rate we had here in the UK and suddenly lost 20% CPU time for the US market. Not always a problem but certainly it was a consideration in how screen refreshes were timed.
Whenever I've used frameworks based on data-binding, my mind tries to rely on an analogy like modular synthesis where elements are connected with wires (https://modularsynthesis.com/DJB-synth.jpg). That works great for tiny demos but poorly for highly dynamic UI's. Maybe I just need a different analogy to properly grasp it.
Is there synchronization between the GPU and the FrameBuffer to tell it that the GPU has finished drawing the current frame? This may be a rare case but I'm getting stuck at the Sample Code.
I'm assuming we're reusing the same FrameBuffer object so it seems like we're using the same two pixel array references. If the GPU can't tell the game it's not done with the current buffer, couldn't the game easily start writing into the buffer the GPU is currently working with if it's just swapping in its own loop? This assumes the game has already drawn the next frame and has swapped back to the former frame.
In the reverse case I guess the game loop could fall behind and we keep writing the same frame and introducing lag.
One of the things I find really interesting about C++ is that idiomatic, modern C++ is really gnarly looking. Lots of `std::` everywhere, tons of templates, etc.
But if you choose to eschew convention and roll your own stuff, you can actually write C++ in a pretty simple, clear style. There are significant downsides to doing that, of course. The prevailing style is prevailing for a reason. But I think it says something interesting about the language itself that it's so accommodating to a wide range of styles and levels of readability.
Lacks all of the clutter I tend to associate with c++.