The vast majority of games back in the era
of CRT monitors used a method of drawing images to the screen called a raster scan. In this method, a virtual image consisting
of some number of picture elements, or pixels, can be realized on the screen by dividing
the image into lines. These lines are scanned one by one in order
to produce the picture--hence scanlines. However, this isn't the only way the screen
can be illuminated by the monitor's electron beam. The beam can also be swept across the screen
in an arbitrary motion, allowing for brighter, smoother, and sharper graphics. These CRTs were commonly referred to as vector
monitors, or XY monitors. Many companies produced their own versions
of these kinds of monitors, but in this video, we will be looking into Atari's Quadrascan
technology, and the handful of games they made featuring it. First let's take a small visit to the land
of raster graphics, just to see how everything compares. In order to form an image full of pixels,
there are a lot of graphical assets that need to be stored in some sort of video memory,
or VRAM for short. In general, this could include image graphics
data--either raw bitmap data or indexed color graphic data--, color palette data if applicable,
tilemap data to form one or more backgrounds out of these graphics, sprite data to form
objects that move independently from these backgrounds, and other various information
such as scrolling data for backgrounds, position data for sprites, etc. A picture processing unit, or PPU, is responsible
for taking all of this data as input, forming a final picture, and possibly encoding it
into a video signal that a standard CRT television set could understand. The three common standards being NTSC, PAL,
and SECAM. With vector graphics though, everything is
completely different. There are no pixels, so there aren't any graphics
data, backgrounds, sprites, or tilemaps. Everything is just a set of vectors. Static things in the scene are just as much
made of vectors as moving objects are made of vectors. Each vector is just a pair of points, and
the vector generator--the equivalent of a raster system's PPU--is responsible for moving
the electron beam between the two points. Each vector can also have an intensity value
to control the brightness, and eventually a color value in later systems. All of this data is stored in vector memory,
or VMEM, a region of memory that is accessible by both the main CPU that runs the game and
by the vector generator itself. So how exactly is this vector data stored
in memory within Atari's Quadrascan? Well, that's not too easy of a question to
answer because Atari updated and revised their hardware for almost each new game's release. Here is a list of each game that uses the
Quadrascan technology. I'll just be looking at the exact systems
used in Asteroids and Tempest, since those were the best selling games in the monochrome
and color categories. They all had the same basic structure however,
so let's get a broad understanding of everything before getting into the nitty-gritty. A naive approach to storing all of the vector
data would be to just take every vector you wanted to draw for the frame, and store their
properties in order one after the other. Take each one's starting position, ending
position, brightness, and color (if color is available), pack it into some sort of data
structure, and list them off in vector memory. Then the vector generator could use this data
to draw each vector one by one to produce the final image. While this could work, it would be wildly
inefficient, for several different reasons. First, think about how the vectors are actually
used in each of these games. More often than not, they are connected tip
to tail to form some sort of shape. In the case of drawing a simple polygon, if
you specified the start and end points of each vector, you would be stored twice as
much data as needed, since each point would be stored twice--once for each vector it is
part of. Second, objects in the game often use the
same color or brightness for a lot of their vector components. So again, this would be a big waste of data
because you shouldn't really have to specify the color of each vector every time if they
are all going to be the same anyway. The third inefficiency has to do more the
computational complexity than storage space. Think about if a game object moves around
or changes size without changing its shape. Each frame, every single vector's coordinates
would have to be updated. If there are a lot of objects moving around,
and/or if the object was composed of a lot of vectors, this could be a very large amount
of data to update every frame. So it would probably not be a good idea to
store each vector's absolute positions on the screen if possible. This is why vector memory doesn't include
a list of vectors to draw, but instead a list of instructions on *how* to draw all of the
vectors. In this way, the vector generator is a sort
of processor, with its own fairly small instruction set. With just a handful of commands, the amount
of data and computations required to draw all of the vectors on the screen is much less
than if they were all specified one by one. There is a POSITION instruction that can move
the electron beam to an arbitrary absolute position on the screen. The beam would be blanked during this movement,
so no vector would be actually be drawn here. This is the only instruction that allows for
an absolute position, and it should be used sparingly. The DRAW instruction should be used most often
when actually drawing objects onto the screen. The coordinates supplied to this instruction
are relative, so the starting point is where the beam happens to already be, and the end
point is calculated by adding the X and Y operands to this position. This instruction can also be executed when
the beam is blanking, or has a brightness value of zero, to move it around relatively
without drawing as well. Then there are JUMP, CALL, and RETURN instructions
for manipulating the control flow of execution of instructions. The vector generator has its own internal
stack which can only hold a few addresses, so subroutines can't be nested too much. There are no conditional branch instructions,
so there can't be any real logic, but at least routines and subroutines can be utilized. A COLOR instruction was added in later to
modify the color of the vectors being drawn, and to easier control the brightness. And also a SCALE instruction for allowing
more control over the scale of vectors. Finally, a HALT instruction exists for stopping
the vector generator when the current frame is done being drawn. We'll get more into how this is used and how
the main processor and vector generator communicate with one another later on in the video. The vector memory where all of these instructions
resides is accessible by the vector generator and the main processor. The main processor handles the actual game
calculations, and passes along any drawing instructions for the vector generator to deal
with. This means it has to be some sort of random
access memory, or RAM. The main CPU generally does the writing to
this memory, and the vector generator does the reading (though the main CPU can also
read from it if necessary). However, there is usually another part of
vector memory present that is read-only memory, or ROM. This section of vector memory usually holds
constant data that doesn't ever need to change. You can find routines to draw static game
objects and things like text here. Most of the time all of these only use relative
positioning for drawing, so that they can be positioned anywhere on the screen. While in the RAM section of vector memory,
you'll find more procedurally generated graphics, as well as routine calls to things found in
the ROM section. For example, in Asteroids, the shapes of the
asteroids, flying saucer, and text are all in vector ROM. Vector RAM holds the player's ship (since
there is no way to rotate vectors, so to speak), any shots if present, and routine calls to
draw the asteroids and player scores after first positioning them on the screen with
a POSITION instruction. There's a bit of a caveat about how vector
memory is accessed between the two processors. See, the vector generator works with 16-bit
words at a time. So, address $0000 holds the first 16 bits
of vector memory, address $0001 holds the next 16 bits, and so on. However, the main processor (a 6502 of some
sort in these games), works with 8-bit bytes at a time. This means each memory access on the main
CPU will only work with half as many bits. The vector generator reads bytes in little-endian
order as well, so the low byte comes before the high byte. Not only that, but vector memory is memory
mapped to somewhere other than the very start of the address space. In Asteroids, there is 2 kilobytes of vector
RAM present, which is $800 bytes or $400 words. This is mapped to word addresses $0000-$03FF
in the vector generator's memory space, but from $4000-$47FF in the main processor's memory
space. There are also 2 kilobytes of vector ROM,
mapped to $0800-$0BFF in vector generator space, but to $5000-$57FF in main CPU space. In Tempest, there is twice as much of each,
or 4 kilobytes of ROM and RAM each--$1000 bytes or $800 words. These are mapped from $0000-$07FF in vector
generator space, or $2000-$2FFF in main CPU space for RAM. And $0800-$0FFF in vector generator space,
or $3000-$3FFF in main CPU space for ROM. Each game is mapped differently like this,
which can be pretty confusing when trying to work with multiple games at a time. There are three important registers that the
main CPU and vector generator use to talk to each other in conjunction with the vector
memory. Well really, they don't talk to each other,
it's just the main CPU telling the vector generator what to do. The first pair are the GO and RESET registers. When the main CPU writes to the GO register,
the vector generator will start executing its vector code at vector memory word address
$0000. And when the main CPU writes to the RESET
register, the vector generator will stop executing code, as if it just ran into its own HALT
instruction. The last register, the HALT register (not
to be confused with the HALT instruction), can be read by the main CPU to see if the
vector generator is currently running code or if it has halted. These registers are important because care
should be taken to make sure the vector code is not being executed by the vector generator
when the main CPU tries to modify it. This is especially important because the main
CPU can only write 8 bits at a time, which is half the size of the vector generator's
word size. In the worst case, the vector generator could
run a bunch of errant instructions and draw a bunch of garbage to the screen. Let's see how this problem is handled in practice. Most of the Quadrascan games use a sort of
double-buffer technique. In Asteroids, the very first vector instruction
in vector memory is a JUMP instruction that jumps to either address $0001 or $0201. These two addresses hold two nearly identical
list of vector generator instructions, commonly refered to as a video list. One video list is used for even frames, and
the other is used for odd frames. This way one list can be modified or rewritten
while the other list is being drawn to the screen. Once the main CPU is done with all of the
game calculations, it spins in a small loop waiting for the vector generator to finish
its work. Once it's ready, the jump target is swapped
to the other memory location, which will be treated as the active video list. The vector generator is started via the GO
register, and the new list will be drawn. The old area is now ready to be set up for
the next frame. Let's go over how each instruction was encoded, since there were only 7 of them in Asteroids,
and 9 in Tempest. The first is the POSITION instruction. This instruction has the opcode of A and includes
a 10-bit X coordinate and 10-bit Y coordinate to use as the location to move the scanning
beam on the screen. The bottom-left corner of the screen is treated
as the origin, and the top-right corner is the maximum value of (1023,1023). Then, a 4-bit global scale factor is also
specified and applied to any vectors drawn after this instruction. The DRAW instruction has two varieties, a
single word version and a double word version. The double word version has opcodes 0 through
9 and allows specifying an 11-bit, sign-and-magnitude X and Y value for calculating the vector's
coordinates. This is also where the 4-bit intensity is
stored. A brightness of 0 would not produce any light
at all, and a brightness of 15 is the full, white color. Anywhere in between are shades of gray. Another 4-bit local scale factor is found
here as well, which is limited to 0-9, since this is also used as the opcode of the instruction. The global and local scale factors are used
together to calculate the final relative coordinates of the vector using this formula. The X and Y axes both use the same scale values,
and the final result is not limited to an integer value. Also, this intermediate sum of the two scale
factors cannot exceed 9, since this is calculated using binary encoded decimal, and having a
carry here just makes the sum act as if it were zero. It can overflow from 15 back to zero and work
properly again, which is an odd quirk. The single word version of the instruction
has opcode F and only allows for a 3-bit sign-and-magniuted X and Y value, a 4-bit intensity value, and
a 2-bit scale. The final coordinates are calculated a bit
differently when using this shorthand version of the DRAW instruction. Again, this intermediate sum can't exceed
9 for reasons stated before. The JUMP instruction has opcode E, and simply
has a 12-bit value which directly corresponds to the 12-bit word address in vector memory
of the instruction to jump to. The CALL instruction has opcode C, and is
formatted exactly the same. The RETURN instruction uses opcode D, and
has no operands, so all of the other bits are ignored. Similarly with the HALT instruction--it uses
opcode B and has no operands. By the time Tempest was released, the instruction
set was altered slightly, and all the encodings were changed. The biggest changes were the scale being pulled
into its own instruction, as well as the introduction of color. The POSITION instruction was changed into
the CENTER instruction, which plainly returned the scanning beam to the center of screen
without drawing a vector. It has opcode 8 and has no operands so 13
bits go unused here. This is the only absolute positioning instruction
now, so in order to get the same functionality as the POSITION instruction, a DRAW must come
after CENTER, with intensity of zero. The DRAW instruction still has two versions. The double-word version has opcode 0 or 1,
and now has 13-bit X and Y values, this time in 2's complement instead of sign-and-magnitude. The intensity value is still included, but
is now limited to 3 bits. It's a little funky though--the value is doubled
to get the true intensity. Additionally, the value of 001 is special
in that it doesn't result in a brightness of 2, but instead means to use the global
brightness value instead, which we'll get to in a moment. The single-word version of the DRAW instruction
is similar except it uses opcode 4 or 5 and now the X and Y values are only 5 bits wide
each. They are also shifted left once, effectively
doubling the value here. The new SCALE instruction specifies the scale
at which new vectors are drawn. It uses opcode 7 and has two parameters to
control the legacy digital scaling method, as well as a new analog control, which allows
for much finer control over the scaling of vectors. The new formula for calculating the screen
coordinates for a DRAW instruction looks like this. A big difference here is that the scale parameter
now has an inverse effect--the larger the value, the smaller the vectors become. The new COLOR instruction with opcode 6 has
a 4-bit operand for picking one of 7 colors, a 4-bit operand for that global intensity
value that I mentioned earlier, and a single bit to toggle all color. If this bit is cleared, all colors will be
white--otherwise the color from the color operand is used. The JUMP and CALL instructions with opcodes
E and A respectively still have just their 12-bit address operand. And the RETURN instruction now has opcode
C, while HALT has opcode 2, both of which with no operands. Even though this version of the instruction
set has more kinds of instructions, it ultimately resulted in more efficient code, mainly because
the shorter DRAW instruction had two extra bits of precision for the X and Y coordinates
for each vector. The last thing I want to show in this video
is just an example of one frame of Tempest being drawn. In this single frame, 1025 vector generator
instructions are executed, 431 of them being DRAW instructions. Tempest takes the double-buffering method
of managing vector memory to another level, and has a separate buffer for different gameplay
elements. So there's one double-buffer for the score
at the top, one for the playfield, one for player and enemies, one for bullets, etc. This is why it will look like these are done
in groups. It may not be the most efficient in terms
of distance traveled by the scanning beam, but it is still well within the allotted time
for a frame, so it still works. Thank you for watching, and I hope you enjoyed
this video. If you would like to support the channel,
I encourage you to like and share this video with someone who many find it interesting
or fun. Retro Game Mechanics Explained also has a
Patreon and SubscribeStar page, if you are feeling generous. Please stay tuned for more breakdowns and
explanations as we head into the new year!