The pixel of a thousand masks
Last time we came up with a program that can determine which byte(s) we need to alter to draw our pixel. The main drawback with our code was that it would overwrite the whole byte not just the bits we needed to colour a pixel. Today we look at how to remedy that.
Most of you who have used computers are familiar with the three fundamental logical operations: AND, NOT and OR. You may not realize it but these are the key to drawing a pixel non-destructively. First lets look at all the different outputs of the OR function. We call this it's TRUTH TABLE!
INPUT 1 | INPUT 2 | OUTPUT |
---|---|---|
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
0 | 0 | 0 |
Let's also reexamine our first four bytes of the DHR screen with pixels A-G:
Now lets suppose that we want to draw a green pixel in position C. Lets also assume that that byte contains the value 01100001. A green pixel requires a pattern of 0110. If we padded this value with zeros to get it to line up with position C like so 00001100. Then it seems all we need to do is OR these two bytes together. Like so:
01100001
OR 00001100
--------
01101101
But what if that byte contained 01111111? Would the OR work? Nope we would just end up with 01111111. So OR works but it only handles cases where there are already zeros in the bit positions we want to change. What we need then, is a way to "erase" - that is set to 0 - just the few bits that we are interested in. The solution to this is to use the AND operation The truth table for AND is as follows:
As you can see AND always returns 0 unless both inputs bits are 1. We can use this to 'clear' a series of bits to 0 by doing the following:
01111111
AND 01100001
------------
01100001
Notice that by carefully choosing the value that we are ANDing against so that there are zeros in the bits we want to clear. We guarantee that those bits will be set to zero. We also guarantee, by setting all other bits to 1 that the rest of the byte will be undisturbed. This process is called bit masking.
So to answer to our original question. We need to use both these operations, first AND the bits we are interested in to 0 then OR our pixel pattern into the space we created.
01111111
AND 01100001
------------
01100001
OR 00001100
------------
01101101
...we see here that pixel A exists in the last four bits of AUX $2000 and therefore needs a mask of 01110000 to clear out those bits. Pixel B, on the other hand needs a mask of 00001111 at AUX $2000 AND a mask of 011111110 at MAIN $2000
So what do we do? Why we use another lookup table! Two actually. One for the AUX mask and one for the MAIN mask. We can write it out like this:
We can turn these into code for the assembler in this way:
01100001
OR 00001100
--------
01101101
But what if that byte contained 01111111? Would the OR work? Nope we would just end up with 01111111. So OR works but it only handles cases where there are already zeros in the bit positions we want to change. What we need then, is a way to "erase" - that is set to 0 - just the few bits that we are interested in. The solution to this is to use the AND operation The truth table for AND is as follows:
INPUT 1 | INPUT 2 | OUTPUT |
---|---|---|
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
0 | 0 | 0 |
As you can see AND always returns 0 unless both inputs bits are 1. We can use this to 'clear' a series of bits to 0 by doing the following:
01111111
AND 01100001
------------
01100001
So to answer to our original question. We need to use both these operations, first AND the bits we are interested in to 0 then OR our pixel pattern into the space we created.
01111111
AND 01100001
------------
01100001
OR 00001100
------------
01101101
Implementation:
Looking at our first four bytes again...
...we see here that pixel A exists in the last four bits of AUX $2000 and therefore needs a mask of 01110000 to clear out those bits. Pixel B, on the other hand needs a mask of 00001111 at AUX $2000 AND a mask of 011111110 at MAIN $2000
So what do we do? Why we use another lookup table! Two actually. One for the AUX mask and one for the MAIN mask. We can write it out like this:
Pixel | AUX mask | MAIN mask |
---|---|---|
A | 01110000 | 01111111 |
B | 00001111 | 01111110 |
C | 01111111 | 01100001 |
D | 01111100 | 00011111 |
E | 01000011 | 01111111 |
F | 00111111 | 01111000 |
G | 01111111 | 00000111 |
We can turn these into code for the assembler in this way:
MAINAND DB %01111111,%01111110,%01100001,%00011111 DB %01111111,%01111000,%00000111 AUXAND DB %01110000,%00001111,%01111111,%01111100 DB %01000011,%00111111,%01111111The percent sign tells the assembler that these numbers are in binary. All our code has to do is find the byte we want to draw our pixel on, load that byte into the accumulator and then AND it against the appropriate bitmask value in the table. We can implement this by altering our code into something as follows: (NOTE:I've only written the part that uses MAIN memory for now):
LDY MBOFFSET,X BMI AUX STA PAGE1 LDA $2000,Y AND MAINAND,XFinally we need to DRAW the bit pattern we want into the "hole" we made using AND. This is accomplished by using an OR. If we assume, for now that the pixel we want to draw is green then the four-bit pattern we want to draw is 0110. If you look again at our diagram of the first four bytes of memory you will see that we need to draw a different pattern depending on which pixel we want to write. Yet, again the solution to this is a table. It looks like this:
Pixel | AUX pattern | MAIN pattern |
---|---|---|
A | 00000110 | 00000000 |
B | 01100000 | 00000000 |
C | 00000000 | 00001100 |
D | 00000001 | 01000000 |
E | 00011000 | 00000000 |
F | 00000000 | 00000011 |
G | 00000000 | 00110000 |
Translating that into assembler we get:
MAINGR DB %00000000,%00000000,%00001100,%01000000 DB %00000000,%00000011,%00110000 AUXGR DB %00000110,%01100000,%00000000,%000000001 DB %00011000,%00000000,%00000000I've called these MAINGR and AUXGR to indicate that these values are for ORing a green pixel into screen memory. The addition of an ORA instruction into our code snippet looks something like this:
LDY MBOFFSET,X BMI AUX STA PAGE1 LDA $2000,Y AND MAINAND,X ORA MAINGR,XSo here's the whole program:
ORG $6000 ;Start program at $6000 GRAPHON EQU $C050 HIRESON EQU $C057 FULLON EQU $C052 DHRON EQU $C05E ON80STOR EQU $C001 ON80COL EQU $C00D PAGE1 EQU $C054 PAGE2 EQU $C055 SCRN_LO EQU $1D SCRN_HI EQU $1E INIT STA GRAPHON ;Turn on graphics STA HIRESON ;Turn on hi-res STA FULLON ;Turn on fullscreen STA DHRON ;Turn on DHR STA ON80COL ;Turn on 80 columns STA ON80STOR ;Use PAGE1/PAGE2 to switch between MAIN and AUX memory LDX #00 DPLOT LDY MBOFFSET,X ;Find what byte if any in MAIN we are working in BMI AUX ;If pixel has not bits in MAIN memory - go to aux routine STA PAGE1 ;Map $2000 to MAIN memory LDA $2000,Y ;Load screen data AND MAINAND,X ;Erase pixel bits ORA MAINGR,X ;Draw green pixel STA $2000,Y ;Write back to screen AUX LDY ABOFFSET,X ;Find what byte if any in MAIN we are working in BMI END ;If no part of the pixel is in AUX - end the program STA PAGE2 ;Map $2000 to AUX memory LDA $2000,Y ;Load screen data AND AUXAND,X ;Erase pixel bits ORA AUXGR,X ;Draw green pixel STA $2000,Y ;Write back to screen END RTS MBOFFSET DB 255,0,0,0,255,1,1 ABOFFSET DB 0,0,255,1,1,1,255 MAINGR DB %00000000,%00000000,%00001100,%01000000 DB %00000000,%00000011,%00110000 AUXGR DB %00000110,%01100000,%00000000,%000000001 DB %00011000,%00000000,%00000000 MAINAND DB %01111111,%01111110,%01100001,%00011111 DB %01111111,%01111000,%00000111 AUXAND DB %01110000,%00001111,%01111111,%01111100 DB %01000011,%00111111,%01111111That probably seems like a lot of code for just plotting seven pixels. Which invites the question: What do we need to change to draw across the whole line? The answer is easier than you think. All three of these tables REPEAT in some way after the seventh pixel. If we assume we're starting at the beginning of a line then the MBOFFSET table the pattern is:
Unused,X,X,X,Unused,X+1,X+1,Unused,X+2,X+2,X+2,Unused,X+3,X+3..
and the ABOFFSET table the pattern is:
X,X,Unused,X+1,X+1,X+1,Unused,X+2,X+2,Unused,X+3,X+3,X+3...
So the full tables for both of those are:
MBOFFSET DB 255,0,0,0,255,1,1,255,2,2,2,255,3,3 DB 255,4,4,4,255,5,5,255,6,6,6,255,7,7 DB 255,8,8,8,255,9,9,255,10,10,10,255,11,11 DB 255,12,12,12,255,13,13,255,14,14,14,255,15,15 DB 255,16,16,16,255,17,17,255,18,18,18,255,19,19 DB 255,20,20,20,255,21,21,255,22,22,22,255,23,23 DB 255,24,24,24,255,25,25,255,26,26,26,255,27,27 DB 255,28,28,28,255,29,29,255,30,30,30,255,31,31 DB 255,32,32,32,255,33,33,255,34,34,34,255,35,35 DB 255,36,36,36,255,37,37,255,38,38,38,255,39,39 ABOFFSET DB 0,0,255,1,1,1,255,2,2,255,3,3,3,255 DB 4,4,255,5,5,5,255,6,6,255,7,7,7,255 DB 8,8,255,9,9,9,255,10,10,255,11,11,11,255 DB 12,12,255,13,13,13,255,14,14,255,15,15,15,255 DB 16,16,255,17,17,17,255,18,18,255,19,19,19,255 DB 20,20,255,21,21,21,255,22,22,255,23,23,23,255 DB 24,24,255,25,25,25,255,26,26,255,27,27,27,255 DB 28,28,255,29,29,29,255,30,30,255,31,31,31,255 DB 32,32,255,33,33,33,255,34,34,255,35,35,35,255 DB 36,36,244,37,37,37,255,38,38,255,39,39,39,255The data in MAINAND,AUXAND,MAINGR,AUXGR simply repeats without change for all 140 pixel positions. Which means the implementation is easy. We simply type the DB portions of each table twenty times! Thankfully the Merlin Pro assembler has an instruction called LUP which makes this dull task rather trivial. Here we've repeated all four tables twenty times each using that instruction:
MAINAND LUP 20 DB %01111111,%01111110,%01100001,%00011111 DB %01111111,%01111000,%00000111 --^ AUXAND LUP 20 DB %01110000,%00001111,%01111111,%01111100 DB %01000011,%00111111,%01111111 --^ MAINGR LUP 20 DB %00000000,%00000000,%00001100,%01000000 DB %00000000,%00000011,%00110000 --^ AUXGR LUP 20 DB %00000110,%01100000,%00000000,%000000001 DB %00011000,%00000000,%00000000 --^LUP, as you can see is followed by a number telling the assembler the number of times you want to repeat a section. The section you want to repeat is delineated by the LUP opcode and the three characters: --^ which need to be on a line by themselves.
Ok that's all we have for this post!
Tune in next time for the last piece in developing our plotting function: Changing colour, and handling changes on the Y axis!
Cheers!
No comments:
Post a Comment