gekk.info « articles

Advantages of stone-age graphics

Please note: This article contains some speculation about how MSFS renders internally.

Not long ago, I published a video about a quirky Taiwanese compact PC rebranded as the Headstart Explorer which possessed the unusual-to-me feature that it could run in both CGA and Hercules modes. It was a very convenient feature... but, unfortunately, not implemented well.

I used Microsoft Flight Simulator 3 as an example of a "game" that had Hercules graphics support, and moreover, to demonstrate that the Headstart Explorer (mine, at least) has a totally broken Hercules implementation.

This is MSFS3 in Hercules mode, running in an emulator. Notably, this runs at what looks like 60FPS, and it looks kind of incredible - I would very much like to try putting a Herc card into a late ISA machine, like a P3, and see if it can do this IRL.

On my Headstart machine, which is a clock-doubled 8088, it runs at only a couple FPS. This is actually spritely: on the real Hercules card in my original IBM 5150 PC, it runs at One Frame Per Second, because this is a tremendous amount of pixel-plotting being done all on the CPU. So the Headstart would be preferable, except for one problem.

Above, the actual output from the Headstart. In addition to being slow, one of the two video memory pages in the Headstart's Hercules implementation appears to be missing or broken, so every other rendered frame is just gibberish.

I tested this with two other apps, a Hercules test suite and Windows 2.0. The former showed gibberish whenever it tried to show page two. Windows 2.0 was nothing but gibberish, and a friend looked at the EXE and confirmed is hardcoded to draw all video to page two. That's weird, and both these problems are way outside my expertise to investigate, short of shotgun-replacing all the VRAM in the Headstart.

A few days after releasing that video, a comment popped up pointing out that, while it did look like the second video page was corrupted, the screen was still being updated more than once a second. Specifically, the dashboard gauges could be seen to move in sequential frames.

This is a pair of subsequent frames captured from my Headstart video. Open them in two tabs and flip back and forth - the gauge is moving, and there's no corrupted frame showing up in between positions. So, is it really "every other frame" that's corrupted? What's going on here?

Many readers will already have guessed what's going on here, but for the rest: you have to understand that the way graphics were rendered in these days was very barbarian.

Generally speaking, prior to Windows taking over in about '97, games on the PC had direct access to video memory. There was no "rendering API" and no "GPU." You calculated the whole display with CPU instructions, produced a bitmap, and poked that bitmap into video RAM. This is why it took so long for the PC to get reasonable action games - the 8088 simply could not poke pixels very fast, acceleration hardware was virtually nonexistent, and the deployed base of 286-486 machines was minimal compared to original 8088 and clock-doubled variants.

Screen tearing was a problem then as now, and made even worse by the incredibly slow speed at which the CPU plotted pixels. It was also solved the same way then as it is now: Instead of plotting pixels to the framebuffer that the graphics card is outputting from, plot them instead into a separate chunk of RAM, so the card keeps outputting the last good frame while you're slowly plotting pixels. Once a frame is done rendering, tell the graphics card to output from that other memory region, instantly swapping from the last good frame to the newest good frame. Then, start drawing your next frame to that original memory region, and repeat.

This technique is known as "page flipping" and "double buffering." I believe the only difference is that with page flipping, you're telling the video card to draw from a different region of RAM, but with double buffering, you always output from the same memory, but when you finish drawing a frame you just quickly copy it from the temporary buffer into the active one.

Nowadays, as far as I know, these features are both implemented at a very low level - but in the mid 80s you were doing everything by hand, with no help from an API. If you wanted to flip pages, you reprogrammed the CRT Controller chip on the graphics card by poking a new memory address into some registers. If you wanted to double buffer, you programmed the CPU to copy every single bit from one memory region to another. It was tedious - but it also meant that you weren't really married to any given technique.

So, how are the gauges moving without a page flip? The answer is: Who cares? Who said a pageflip had to happen to update the screen? It's just a tool in your toolbox.

Pageflipping IS used in this games rendering, but not all the time. At any given moment, maybe you're staring at page 0, seeing a frame from 800msec ago, while page 1 is slowly being drawn by the CPU, offscreen. It'll be another 200ms before it's ready to flip pages - but nothing says it HAS to finish rendering the next frame before it can update what's on screen. It's just RAM.

The game doesn't render the world in the tightest possible loop, because it has other concerns. Yes, it has to render the world, and that takes up 90%+ of its CPU time, but it also needs to calculate how the plane has moved, read user inputs, play sounds, and update the dashboard. If nothing else, it *has* to pump the keyboard buffer, otherwise a user who's madly holding down an arrow in order to pull out of a stall would start getting infuriating beeps.

Most likely, the game interrupts the rendering loop every n pixels to do all those housekeeping activities. This is probably why the simulation continues to run at realtime no matter how fast the screen is updating. The actual "simulation" step, updating the position of your plane and control surfaces and so on, probably happens at precisely fixed intervals, unrelated to graphical framerate.

The dashboard is, relatively speaking, just a handful of pixels, and very cheap to draw compared to rendering a 3D world. The gauges are 2D shapes, consisting of four simple trigonometric calculations fed into four line-drawing functions. The game has enough CPU cycles to update these pretty much effortlessly.

In addition, because the dashboard is largely static - the only parts that change are the dials - the game only draws it one time, at the beginning of a session. After that, it just leaves those pixels in place. They're RAM - whatever you put in them will stay there until you remove it. So as you fly, the pixels in your dashboard never get redrawn, you're looking at a stale bitmap for hours.

To update the gauges, the sim (probably) just redraws each needle in black - effectively erasing it - and then plots the new position in white. It does this right on top of the existing data, it doesn't wipe the screen first.

And so, while the 3D portion of the screen is rendered using pageflipping, the dashboard is not. When your session starts, the game renders the static parts of the dashboard once, into each page, and then every time it updates the gauges, it renders them to both pages as well, so whenever it does flip, the gauges in the other page will be up to date.

So the game may be rendering at one or two frames per second, but the gauges have their own, totally independent framerate. I don't know what it is - you'd have to disassemble the game to find out, but it's pretty quick, and it's very easy to prove this.

download video

In this video, I am running the game in an emulator (DOSbox.) I start the game at a high CPU speed (cycle count) so it runs very smoothly. I fly for a bit, and then slow the CPU down to sub-8088 speeds. It begins rendering more slowly than the original IBM PC, at <1FPS - but the gauges continue updating in realtime.

If you aren't certain, watch the little arrows between the artificial horizon and altitude gauge. They represent my flight surfaces. As I steer the plane, you'll see them move very smoothly.

Intriguingly, this isn't consistent. Airspeed, yoke position, vertical speed, time and attitude indicators all update in realtime; most other critical gauges do not. This is a shame, because if *all* gauges were realtime, you could fly on instruments even if your framerate was unplayably slow. The idea of switching to IFR due to rendering speed interfering with VFR is an incredibly beautiful cyberpunk idea.

It seems to reveal some interesting details about how the game calculates your position - perhaps the 3D engine is calculating where you really are, which only happens every few ticks, but simpler estimates are used for values like airspeed and attitude, allowing them to update more quickly.

A source code dive on MSFS would be fascinating, but for now, the delightful takeaway is that "FPS", at this point in history, was not really meaningful, since the rendered image could be, and probably often was, assembled asynchronously.

List of Articles