Skip to navigation


Line buffers

Caching line information for faster erasing

The whole Aviator codebase boils down to one thing: working out which lines to draw on the screen. Without any lines, there is no simulation, there is no outside world, and there is no dashboard information. Although this sounds like an abstraction, this is a literal interpretation of the code, as once the dashboard image has been loaded and the canopy edges drawn, the only things that get updated on-screen are the lines.

There are two main types of line: the lines that make up 3D objects in the canopy view (which I refer to as "canopy lines"), and the lines on the dashboard ("indicator lines"). Of the lines on the dashboard, some are relatively quick to draw, such as the vertical bars on the rudder and thrust indicators, but most of them involve quite a few calculations, with the trickiest being the clock-based dials and the radar (see the deep dive on clock hands and dial indicators for details). Meanwhile, all of the lines in the canopy are a challenge to calculate; see the deep dives on lines and points and 3D objects to find out why.

Aviator erases lines from the screen by redrawing them, using EOR logic to cancel out the old line. So, to save time recalculating each line when we want to erase it, we cache the line details in the line buffers. Let's take a look at how this works.

The line buffers
----------------

There are two separate sets of line buffers, one for the dashboard indicators and one for the canopy lines, but they work in the same way. Each line in Aviator is defined by a set of parameters that gets passed to the relevant line-drawing routine. For clock-based indicator lines (including the line on the artificial horizon), that routine is DrawVectorLine, while for canopy lines it's DrawCanopyLine.

Both of these routines expect the following information to be passed to them:

  • The start point's x-coordinate
  • The start point's y-coordinate
  • The magnitude of the x-coordinate of the line's vector, i.e. |x-delta|
  • The magnitude of the y-coordinate of the line's vector, i.e. |y-delta|
  • The direction of the line's vector:
    • Bit 7 is the direction of the x-delta
    • Bit 6 is the direction of the y-delta
    • The direction is like a clock, so positive (clear) is up and right

For canopy lines, the DrawCanopyLine routine also expects the following additional information, which enables us to implement line-clipping (which we don't need to do for the dashboard):

  • Max/min x-coordinate for the end of the line
  • Max/min y-coordinate for the end of the line
  • Extra information stored in the direction of the line's vector:
    • Bit 1 is set if this is the horizon line
    • Bit 0 is set if the line has been clipped

The information above is what we cache in the line buffers. There is one set of line buffers for the dashboard lines, and another for the canopy lines, so let's take a look at how they are structured.

The dashboard line buffers
--------------------------

Both line buffers work in the same way: for each line, they simply cache all of the parameters that we need to pass to that line's line-drawing routine. In the case of the dashboard lines, there are eight indicator lines that get cached:

  • Indicator 0: Compass
  • Indicator 1: Airspeed indicator
  • Indicator 2: Altimeter, small "hour" hand
  • Indicator 3: Altimeter, large "minute" hand
  • Indicator 4: Vertical speed indicator
  • Indicator 5: Turn indicator
  • Indicator 6: Slip indicator
  • Indicator 7: Artificial horizon

There are five parameters that define each line in the DrawVectorLine routine, as mentioned above, and there are five corresponding line buffers, one for each parameter. Each line buffer has eight entries, one for each of the dashboard lines that we want to cache. The buffers are named after the variables that are used to pass the parameters to the DrawVectorLine routine, so because the start coordinate is passed to the routine in (I, J), the two buffers that store the coordinate values are called indicatorLineI and indicatorLineJ.

Here's a list of all the line buffers for the dashboard lines.

ParameterDetailsBuffer
IStart point x-coordinateindicatorLineI
JStart point y-coordinateindicatorLineJ
TMagnitude of line's vector |x-delta|indicatorLineT
UMagnitude of line's vector |y-delta|indicatorLineU
VDirection of line's vector in (T, U)indicatorLineV

So the compass line on indicator 0 is buffered in the following locations:

  • indicatorLineI+0
  • indicatorLineJ+0
  • indicatorLineT+0
  • indicatorLineU+0
  • indicatorLineV+0

while the slip line on indicator 6 is buffered here:

  • indicatorLineI+6
  • indicatorLineJ+6
  • indicatorLineT+6
  • indicatorLineU+6
  • indicatorLineV+6

The code to erase and draw dashboard lines is in the DrawIndicatorLine routine, which first erases the existing line that's in the cache, and then draws the new one, caching the line's details in the process.

The canopy line buffers
-----------------------

The line buffers for the canopy lines use the same principal as the dashboard lines, except there are more line parameters and more lines (there's a fixed set of 193 lines in Aviator - see the deep dive on lines and points for more information). Also, the parameters are passed to the DrawCanopyLine routine using slightly different variables, so the buffer names are also different.

Here's a list of all the line buffers for the canopy lines.

ParameterDetailsBuffer
RStart point x-coordinatelineBufferR
SStart point y-coordinatelineBufferS
TMagnitude of line's vector |x-delta|lineBufferT
UMagnitude of line's vector |y-delta|lineBufferU
VDirection of line's vector in (T, U)lineBufferV
WMax/min x-coordinate for the end of the linelineBufferW
GMax/min y-coordinate for the end of the linelineBufferG

The above information is stored when a line is drawn by the DrawClippedLine routine, and is read by the EraseCanopyLines routine when the line is erased, so that works in a similar way to above. However, these caches are organised differently to the dashboard caches. We don't buffer all 193 lines, and we also need to support the colour-cycling system (see the deep dive on flicker-free animation through colour cycling for details of the latter).

Each of the above buffers contains room to cache the details of 96 lines. This is split in half to support the two colours used in the colour cycling system: one half is used to buffer up to 48 lines in colour 1, while the other half is used to buffer up to 48 lines in colour 2. As all the lines are erased in one batch when clearing the invisible screen, it doesn't matter in which order they are added to the cache, so each cache is a simple sequential list, with the list sizes stored in the lineBuffer1Count and lineBuffer2Count variables.

This means that the maximum number of lines we can show on-screen at any one time is 48 lines out of the 193 lines defined in the world.

The radar buffer
----------------

There is one more buffer, for the radar lines (i.e. the runway and the attacking alien, if there is one). The start point for each of these lines is stored in the (xRadarBuffer, yRadarBuffer) table, but that's the only information that is cached: the line vector is recalculated every time one of these lines needs to be erased.

Then again, the radar is only updated once every eight iterations of the main loop, so this isn't an issue. See the deep dive on scheduling tasks in the main loop for more details.