Atari's Quadrascan Explained

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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!
Info
Channel: Retro Game Mechanics Explained
Views: 184,913
Rating: undefined out of 5
Keywords: video, game, programming, code, glitch, trick, explain, description, hack, tas, atari, vector, xy, monitor, graphics, quadrascan
Id: smStEPSRKBs
Channel Id: undefined
Length: 20min 57sec (1257 seconds)
Published: Wed Jan 27 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.