At the end of the last article, you could program graphics in colour using the built in MOVE and DRAW commands in BASIC and could program fast, detailed graphics in black and white using user defined characters. But there was a problem. If we wanted graphics in both colour and detail, as by both methods the production of such graphics would be incredibly slow.
There is a solution to this problem which involves taking advantage of the way in which the computer's video system operates. The colour of each pixel on your monitor is determined by the data sent to the monitor by your computer - that's fairly obvious. But if you have drawn a very complex set of shapes on the computer screen, perhaps using a BASIC program which took several hours to calculate and draw the result you see on the screen, how does the computer remember the colour of every pixel on the screen, and know what data to send to your monitor? The answer is simple: within your computer's memory there is an area set aside to store the colour of every pixel on the screen, and when the monitor requests the colour of a particular pixel, the computer simply reads the colour of the pixel from this area of memory. The area's name is "Screen Memory". It can be considered as a sort of giant table, holding data for the colours of all of the pixels in a huge matrix of memory locations.
But of what use is this to the programmer, you might be wondering. The answer is that when you use user defined characters, MOVE and DRAW commands, or any other method of putting anything on the screen, the computer simply alters a few values in this giant table. But the computer takes time to process your MOVE and DRAW commands to work out which values in the table to alter. If you could bypass this stage, and set the values in this giant table yourself without using the computer's general purpose graphics commands, you can get exactly the result that you want, and much faster than you would achieve if you were doing the operation through the computer's commands. These have to process the parameters which you give, and then calculate whereabouts in the giant table you wish to alter, before they can actually do any useful work. Thus a lot of computer time is wasted in performing calculations, which can actually be skipped if the programmer is careful.
There are two possible ways of achieving this idea, both of which are useful in different situations:
1. Save screen memory to disk
This idea is fairly simple to understand. You, as the programmer, write a program to plot the artwork for your program onto the screen, taking however many hours it may take to complete all of the calculations required to plot the graphics. You run this program once on your computer, so that your masterpiece is stored in the screen memory of your computer (ie. is on the monitor of your computer). You than transfer the entire contents of the screen memory on your computer to floppy disk.
In the final program which you want the user to operate, you include a command to load this data back from floppy disk into the screen memory of their computer. In a matter of a couple of seconds, graphics which took possible hours to calculate will appear on their screen. This is a technique which I have used in several of my programs. The main menu of SHIPWRECKED II: JUPITER 3, where you select Play, Instructions, Game Complete or Cheat with the starfield backdrop was done in this way - the screen took nearly an hour for my computer to calculate, but I was able to make it appear in seconds on your screen. I should also mention the title screen to Shipwrecked with the animated sea, not to mention the endless EUG title screens I've done in this way - the Christmas EUG #35 screen with the swirly blue background, for example - took two whole days to plot!
In order to achieve this, you simply need to understand two BASIC commands:
*SAVE Filename nnnn mmmm *LOAD Filename nnnnThese load and save areas of the computer's memory to floppy disk (or cassette) with the filename given in place of the work Filename, starting at memory location nnnn, and continuing until memory location mmmm (where nnnn and mmmm are both in hexidecimal - base 16). Don't worry if you don't entirely understand this - it is not necessary to entirely understand the workings of these commands, or of hexidecimal, to use them to store and retrieve screen memory. All you need to do is substitute nnnn and mmmm for the addresses of the start and finish of screen memory. The location of these within your computer varies depending upon which graphics mode you are using (selected using the MODE command - see previous tutorial if you're unsure of what this is), but is always constant for any particular Mode. The values for the different modes are given below:
Mode nnnn mmmm NOTES: 0 3000 7FFF 1 3000 7FFF - nnnn and mmmm values given to the left are 2 3000 7FFF all in hexidecimal. Therefore they can be 3 4000 7FFF substituted directly into the *LOAD and *SAVE 4 5800 7FFF commands with no conversion required. 5 5800 7FFF 6 6000 7FFF - Values are not valid for the BBC model A in 7 7C00 7FFF modes 4,5,6 or 7.Examples:
Suppose you are in Mode 2, and want to store the screen on a floppy. From the table, nnnn is 3000 and mmmm is 7FFF for Mode 2. Therefore, to save the screen under the filename "Screen", you would type:
>*SAVE Screen 3000 7FFFNow, if you want to restore the screen again, and put your graphics back on the screen again, you use *LOAD. Notice that this time only a value for nnnn is required, and not for mmmm:
>*LOAD Screen 3000You can try this out now, if you switch on your Electron, and insert a work disk into your floppy drive. If you use ADFS, you may need to use *MOUNT to select the floppy disk, and be able to write to it. Now enter Mode 2 by typing:
>MODE 2Type some text on the screen which will represent the graphics which you want to save. When you have finished, save the contents of screen memory to floppy disk as shown above, by typing:
>*SAVE Screen 3000 7FFFWhen the disk drive finishes, clear the text off the screen by typing:
>CLSThe text which you typed onto the screen has now been wiped from the computer's memory, but should still be stored on the floppy disk. We can show that this is so by typing:
>*LOAD Screen 3000Your text should now reappear on the screen. You can repeat this with any kind of complex graphics on screen, and it will still work. The same can be done in any other graphics mode, simply by changing the values of nnnn and mmmm to those given in the table above. For example in Mode 5, you would use:
>*SAVE Screen 5800 7FFFfollowed by:
>*LOAD Screen 5800 7FFFThere are, however, a few precautionary points which should be mentioned about this technique of saving screen memory:
- As you probably realise, in four colour modes, the four colours you can use can be changed from the default black, red, yellow and white by using VDU 19 to change the palette. The colours in the palette are not saved when this technique is used, and must be restored by repeating the VDU 19 commands again. If you define the palette to, for example, black, blue, cyan and white, and create a picture which looks good in this colour scheme, you must remember that unless you define the palette to these four colours before performing *LOAD to restore the picture, it may appear rather odd when reloaded, as it will appear in black, red, yellow and white - the default colour scheme.
- The exact reason for the following disadvantage is rather complicated. If you allow the cursor to go down off the bottom line of the screen and the screen scrolls up a line, the technique stops working until you perform another MODE command or clear the screen using CLS. Basically, what happens is that the computer does not physically move every line of text on the screen up one line, but instead makes a note that every line in screen memory is stored one line lower than it should be. When you re-load from disk, this note is lost - and the computer draws every line lower than it should be! Each time that CLS or MODE is executed, screen memory is cleared and re-initiated, so that every line is stored in memory where you would expect it to be. Simply remember not to allow the screen to scroll between clearing it and saving the contents of screen memory to disk.
- The files created when you save the contents of screen memory to disk are fairly large - so large, in fact, that you can often only fit seven on one side of a DFS disk and fifteen on the ADFS M format (the exact number depends upon what modes the images are in). The technique therefore is not feasible for a large number of images. Each screen in a game like Shipwrecked could not be stored in this way - if I'd stored every screen of the game like this, it would have required six floppy disks to store the data for the game! This means that saved screens must be reserved for the purposes of title screens only.
So, we have a technique which allows us to store a small number of very detailed screens on floppy, and recall then at very great speed. But what if we want to use this method for a large number of screens? And what if we want a situation like a game, where the user can move sprites on the screen around. This can also be done by altering the values in screen memory directly, but in a different way...
2. Editing screen memory
The basic idea is this: in any program, you only usually require a certain, small number of little 'blocks' to make up a whole picture. In a word processor, for example, you only need the letters of the alphabet to appear on the screen. These letters are small blocks of graphics that appear on your screen (called characters). Because only these 26 blocks are required, the word processor does not need to store every letter as all of the pixels which make it up (ie. as a bitmap), but just as a code for that particular letter. When the word processor wants to display a letter on the screen, it looks up the letter code, and then copies the graphic data for what that letter looks like onto the screen. Therefore, the graphic data for what colour all of the pixels should be for that particular letter (which might take several bytes) need only be stored once, and that letter can be stored in your script in just one byte, for the letter code. Thus, the memory taken up by your script is reduced dramatically.
The same principle can be used in graphics. Look at two brick walls in an arcade adventure such as Shipwrecked - can you see the difference between them? If they are the same, surely you only need to store the graphic data for a brick wall once, and then copy it onto the screen whenever you want a brick wall. In Shipwrecked, for example, the graphic data to describe what a brick wall looks like took 128 bytes. Yet every time I wanted that brick wall (which was over a hundred times) I could simply use one byte to "put a brick wall here!".
The easiest way to use this block idea, as you may already have realised, is using user defined characters. You redefine the letters of the alphabet as different pictures, and then use the standard BASIC PRINT command to output these pictures to the screen as and when they are required. But, as we already realised in the last article, this only allows you to use one colour in each character. Even so, this is a reasonable technique if you don't mind the lack of colour. Many commercial games actually never addressed this problem, and had graphics that somewhat lacked colour. Even DIZZY (for those who have never heard of it, a great 1980's arcade adventure which has quite a cult following on the Spectrum and C64 but never quite reached the Elk) did not have more than one colour (other than black) per block. Different blocks were, however, different colours - trees were green and the ground was red - but there was never any overlap between the two colour areas.
There is, however, a way of creating an effect just like user defined characters but in full colour and the principle is very similar to that used before in saving screen memory. Basically you design your small block on the screen, using whatever technique you like to draw it, and then save the small part of screen memory which contains your design. This time, however, you will not save it to floppy disk, but to another area of memory. You can then copy this block back to screen memory whenever and wherever you want it. If you want it say, in the fifth row down and twelve characters along, you can calculate whereabouts this corresponds to in screen memory and copy the block to this location. As the block is stored in your computer's RAM, as opposed to on floppy, this transfer can be done by machine code at very great speed (taking only a small fraction of a second).
For those programmers who are familiar with machine code, a module is included with this tutorial allowing you to do this. The code for this module is actually taken directly from Shipwrecked.
If you are not familiar with machine code then you can probably skip the next section on how to directly access screen memory, as it is impossible to achieve any speed by accessing it directly in BASIC - your program will crawl along incredibly slowly. You could still use such a technique in preparing a screen which you intend to save to floppy using the technique described before with *SAVE and *LOAD.
Firstly, it is important to know how screen memory is laid out in memory. The exact position of each character within the memory of your computer varies from one graphics mode to another, but the basic principles are the same:
- The screen area is divided up into characters. These are blocks of 8 x 8 pixels, and are the same size as the rectangular block of space taken up by one letter when you print text to the screen. Screen memory is divided up into blocks, with each the characters being defined by one of these blocks.
- The screen is stored row by row, with the top row first and the bottom row last.
- Within each of these rows, characters are stored one by one, from left to right.
- In all modes, screen memory ends at &7FFF (ie. at the end of RAM)
N.B. By convention, memory addresses are specified in hexidecimal. I use the standard Acorn '&' prefix to indicate hexidecimal values. Those used to dealing with PCs are any other computers may be used to the American convention of using a dollar prefix in its place. The Elk expects & and will not accept $.
Example:
Suppose I want to draw a blob in character square (6,7) in Mode 1. What is the address of this square? (given that each character takes 16 bytes of screen memory to define in mode 1 - we'll come to how to calculate this in a minute).
Well - looking at the table earlier, screen memory starts at &3000 in Mode 1. We want to find the start of row 7 in screen memory. This row has seven rows of characters above it, and which will be stored before it. Each of these rows consists of 40 characters, which take up 16 bytes bytes each. Therefore the total memory taken up by these rows is 7 * 40 * 16=4480 bytes. (Each character is 16 bytes, each row consists of 40 of these characters so we multiply by 40 and, as there are seven of these complete rows, me multiply by 7 to get the value for seven of these rows.)
Now we have that the starting address of row 7 is at:
&3000 + 4480=&4180Now we've found whereabouts in memory this row starts, we need to locate character 6. This should be fairly easy - we know that characters are stored from left to right, so just add on the memory required to store six characters to the left of the one we want, i.e. 16 * 6=96 bytes (Six characters, each taking 16 bytes to store).
Therefore the address of character (6,7) will be:
&4180 + 96=&41E0Answer: Character (6,7) is stored from &41E0 to &41EF
Just to check the answer is correct, type this simple loop:
>10 MODE 1 >20 FOR ad=&41E0 TO &41EF >30 ?ad=255 >40 NEXTAnd yes, a white splodge does appear in square (6,7).
This whole procedure can actually be refined down, and expressed as a simple formula:
Address=Base + ( y * No. characters per line + x ) * Memory per character
where:
Address | = | The memory location of the start of the character wanted. |
Base | = | Start of screen memory in current graphics mode. |
(x,y) | = | Co-ordinate of square you want to access (same as PRINT TAB). |
Since the number of characters per line, the number of bytes per character and the base address of screen memory are all constant for any particular graphics mode, the values for the graphics mode you are using can be substituted into the equation in your program. For Mode 1, for example, you would use:
Address=&3000 + (40 * y + x) * 16Before continuing, one note should be borne in mind. You may remember that a while back I mentioned that when you type text which overflows off the bottom of the screen, the computer makes a note that each line is stored in memory one line lower than it should be, but does not actually move anything around in screen memory. The same effect can cause problems with direct access to screen memory as well. The simple rule for beginners is never attempt to use direct screen memory access when there is even the slightest possibility that the screen might have been allowed to scroll up at all (e.g. after unsing INPUT to get input from the user - how do you know that they didn't type so much text that it went off the bottom of the screen?).
If you know what you are doing, this rule can be ignored and high-speed scrolling can be achieved as a result, but that is not for the beginner! I can tell you from first hand experience that creating the scrolling in JUPITER III with this technique was a nightmare - and especially when the Operating System calls you need to make to achieve this are completely different on the BBC to the Elk. You need to find out which machine the game is operating on, and then make the relevant calls for that machine!
Now that you can locate the memory occupied by a character in screen memory, you need to know how the bytes within this block are laid out, so that you can have pixel by pixel control over your monitor screen. This is also required if you are to be able to calculate the amount of memory taken up by each character's definition in a particular mode.
The graphics modes can be divided into three categories: monochrome modes (ie. two colours), four colour modes, and sixteen colour modes. All of the Elk's modes fall into these three groupings. Which of these colours each pixel of the screen is depends upon the status of a number of binary bits. These have two states, 0 or 1. Now, in a monochrome mode, one bit is sufficient to define the colour of one pixel (bits have two states, 0 or 1 and the pixels also have two states, colour 0 or 1). In a four colour mode on the other hand, one bit will not be sufficient because four different states are required. Two bits will be enough to store the colour of one pixel however:
Bit 1 state Bit 2 state Colour 0 0 0 0 1 1 1 0 2 1 1 3Those familiar with binary will spot the pattern straight away - simply convert the binary value of the two bits into decimal, and you have the logical colour number. A very similar pattern applies to MODE 2 - the Elk's 16 colour mode. Each pixel requires 4 bits to store its colour (as 2^4=16). Once again, the colour of the pixel is the logical colour whose number is given when four bits are put together to form a binary value:
Bit 1 state Bit 2 state Bit 3 state Bit 4 state Colour Default 0 0 0 0 0 BLK 1 0 0 0 1 RED 0 1 0 0 2 GRN 1 1 0 0 3 YEL 0 0 1 0 4 BLE 1 0 1 0 5 MGN 0 1 1 0 6 CYN 1 1 1 0 7 WHT 0 0 0 1 8 BLK/WHT 1 0 0 1 9 RED/CYN 0 1 0 1 10 GRN/MGN 1 1 0 1 11 YEL/BLE 0 0 1 1 12 BLE/YEL 1 0 1 1 13 MGN/GRN 0 1 1 1 14 CYN/RED 1 1 1 1 15 WHT/BLK(Default: Default setting of this logical colour when MODE 2 is first entered)
So... the Electron has modes where the definition of the colour of a certain pixel can take 1, 2 or 4 bits. As each byte of the Electron's memory consists of 8 seperate bits, this means that in monochrome modes we can fit 8 pixels into each byte (8 / 1=8). In four colour modes (where each pixel requires two bits) we can fit 4 pixels into each byte (8 / 2=4). Finally in MODE 2 (the sixteen colour mode where each pixel requires 4 bits) we can fit two pixels into each byte. Let's now look at how each is laid out in more depth:
Monochrome Modes
These are laid out in the simplest way, as eight pixels fit into each byte. The character, remember, consists of an 8-by-8 square of pixels. The data is stored in the same way as user defined characters are defined using VDU 23. The first byte of the definition defines the top row of eight pixels; the second defines the second row down, etc. Thus the entire character can be defined in eight bytes, and this is the number of bytes of the Elk's memory which is used by each character's definition.
Note the most significant bit of each byte defines the left-most pixel of the row, and the least significant bit defines the right-most pixel (just as with VDU 23 definitions).
Four Colour Modes
This time each byte can only hold four pixels. Once again, each byte holds a number of pixels which are in a horizontal line. This time, as before, the first eight bytes run down in a vertical line. The difference this time though is that not all of the pixels in the character are being defined by the one sweep, and instead only the left-most four pixels can be defined. The right-most four pixels are defined in a second downward vertical sweep. Thus the total memory allocation for the definition of each character is sixteen bytes. The layout of these is shown more clearly by the diagram below. This shows the eight-by-eight block of pixels in a character, and each has the offset of the memory location where this pixel is stored from the start of the character definition. Offsets are given in hexadecimal:Pixel-by-pixel layout Memory Flow 00008888 0 8 11119999 | | 2222AAAA | | 3333BBBB | | 4444CCCC | | 5555DDDD | | 6666EEEE \ / \ / 7777FFFF 7 FEach of these sixteen bytes, as already discussed, contains four pixel definitions. But within each byte's eight bits, they are not laid out as you might at first imagine. If we call the four pixels A, B, C and D (A is the left-most, D is the right-most), the layout is:
[MOST SIGNIFICANT] A1 B1 C1 D1 A0 B0 C0 D0 [LEAST SIGNIFICANT]Where: A1: Most significant bit of A's definition } Two bit definition A0: Least significant bit of A's definition } of pixel A
A similar pattern holds for B, C and D.
For example:
Let's suppose we want to set the colour of the left-most pixel in a byte of a four-colour mode to be colour 3. We would use:
?address=(?address AND &77) OR &88Explanation:
The (?address AND &77) part clears the two bits defining the pixel that we want to change. The OR &88 then sets the two bits that we want to set. This way we ensure that when we are going say from colour 1 to colour 2, the least significant bit of the definition is cleared as well as the most significant bit being set.
It is important to notice the pattern in these bits - each pixel requires a two bit definition, but these two bits are not next to each other in the byte as one might expect - instead all of the high-bytes are stored together, and all of the low-bytes are stored together.
Sixteen Colour Modes
The pattern for sixteen colour modes is very similar to that found in four colour modes, except that each byte can only store two pixels (each occupies four bites). As with previous cases, the pixels are stored in a number of vertical sweeps starting at the left of the character, and moving right. This time four vertical sweeps are required to cover all of the character (there are eight pixels across, and each sweep covers two of them). The layout is shown below:
PIXEL-BY-PIXEL LAYOUT MEMORY FLOW 00880088 0 8 10 18 11991199 | | | | 22AA22AA | | | | 33BB33BB | | | | 44CC44CC | | | | 55DD55DD | | | | 66EE66EE \ / \ / \ / \ / 77FF77FF 7 F 17 1F ****NB: Offsets given in hexadecinal. Values in the right half of the above diagram (ie. the right four pixels, which have * underneath) should have &10 added to them - only the least significant character of each hexadecimal value is given. I am sure you can see my problem - the hexadecimal values are two digits long, and I've only got one character in which to put them!
The pattern of the layout of the pixel data in each of the bytes is also similar to that for the four colour modes (the two pixels are A and B, and each comprises of four bits 0-3, where 0 is least significant and 3 is most significant).
[MOST SIGNIFICANT] A3 B3 A2 B2 A1 B1 A0 B0 [LEAST SIGNIFICANT]Notice once again that bits defining the pixels A and B are not stored together, but those defining the least/most significant bits are stored together.
There will be more on the subject of screen memory in EUG #39!
Dominic Ford, EUG #38