A lot has been said and written on the Apple ]\[ hires screens. How colors work, how it's organized in RAM, how to animate sprites, how to clear the screen faster than the HGR/HGR2 Applesoft commands, how to draw faster lines than HPLOT, etc.
This article does not have the pretention to uncover anything new regarding hires pages: if you know how to program your Apple ]\[ then most of the information here will be old news to you. Nonetheless there might be a trick or two that I learned the hard way that still might be useful to you.
I've written this as if you didn't know much about hires on Apple ]\[ except maybe a few Applesoft commands like `HGR/HGR2`, `HCOLOR` and `HPLOT`. Maybe even `DRAW/XDRAW` ... and yet you don't know how it works behind all this.
So this article will first cover the basics: structure of the hires pages in RAM, pixels and colors (even in those sections you might find some rare info) and then will dive into specific techniques and tools I've encountered or developed.
I'll try to make you understand how it works by using Applesoft most of the time, so I expect you know mostly how to program in Applesoft. I won't explain the Applesoft code much except using some `REM`s in the code.
Some parts of this article will feature 6502 code. If you're not comfortable with 6502, don't worry, just skip the section.
A `POKE 8192,255`will plot 7 pixels on the top left corner of the hires screen (page 1). Poking the next memory address (8193), will plot 7 more pixels on line 0 of the hires screen.
So to draw the entire line 0 we could `RUN` this code
In fact, all lines between 128 and 191 in RAM have 8 unused bytes at their end. Those 8x64 lines represent 512 bytes. Those are the missing bytes in first computation.
Now that we now that, we could slightly modify the above code so that after having drawn 3 lines, we add 8 to `A`so that it points to the next line location in memory. Let's do it and plot 3 times 3 lines.
* the first 3 lines of 40 bytes delimit the three A-zones and represent lines 0, 64 and 128 of the screen. Incidentally, these are too the first B-zones of each A-zone.
* then 8 bytes are wasted
* the next 3 lines of 40 bytes represent line 8 of each of the A-zones and the 2nd B-zone of each A-zone (that is the base line of each A-zone + 8, so we have lines 0+8, 64+8 and 128+8)
* This continues until we've arrived at line 56 relative to each A-zone, which is also the 8th B-zone for each A-zone. That is line 0+56=56, line 64+56=120 and line 128+56=184.
So what happens next ? Well, a `POKE 9216,255` will show you that you're plotting on line 1 ! And once line 1 has been filled, you'll plot on line 65 ! And then on line 129 ! Then back to first section of 64-lines but on line 8+1=9, then on line 16+1=17, etc.
|<sub>Line</sub>|<sub> Start </sub>|<sub> End </sub>|<sub> Line </sub>|<sub> Start </sub>|<sub> End </sub>|<sub> Line </sub>|<sub> Start </sub>|<sub> End </sub>|<sub> Line </sub>|<sub> Start </sub>|<sub> End </sub>|<sub>Line </sub>|<sub> Start </sub>|<sub> End </sub>|<sub> Line </sub>|<sub> Start </sub>|<sub> End </sub>|<sub>Line </sub>|<sub> Start </sub>|<sub> End </sub>|<sub> Line </sub>|<sub> Start </sub>|<sub> End </sub>|
This structure might seem confusing and it's true that most of the time programmers will use lookup tables to find the starting address of a line instead of using the above formula.
For example, if we're on a line that's a multiple of 8 (that's the first 3 columns in the table above), all we have to do to find the address of the next 8 lines is to add 4 to the most significant byte (MSB) of the address. In 6502 that's only one instruction (after you've cleared the carry), which might be cycle-saving. In Applesoft it means adding 1024 to the base address.
For instance, if we draw bitmaps starting from a line that is a multiple of 8, like it might be the case when displaying 8-lines high tiles in a game , we only need the address of the first line, while the address of the other lines have the same LSB but an MSB that is incremented by four each time.
Another example is when you write a fast routine to clear the hires screen. You'll want to skip the hires holes for two reasons:
1. It's 512 bytes that don't need to be cleared and that will waste cycles
2. You may want to use these 512 bytes to store data and so you don't want to erase it
The position of the screen holes is also very regular. First they are all within the third section of the screen. Then their address range is either `$xx78-$xx7F` or `$xxF8-$xxFF`.
We make use of this information by looping down from `#$F7` (thus skipping the second kind of hole area) to `#$00` but skipping to `#$77` once we reach `#$7F`.
If you closely watch the above table, you'll notice that the last A-zone starting addresses all end with either `$xx50` or `$xxD0`. It means that if you want to address this zone, all you have to do is cycle from `#$20` to `#$3F` for the MSB and flip between `#$50` and `#$D0` for the LSB, for instance by using an `EOR #$80`.
This could be used for instance in a game where the screen scrolls only on the lower third of the screen. You know, in airplanes fighting games, this is usually where the ground and the enemies are (hint, hint).
The same is in fact true the other two A-zones. For the first one, addresses end with either `$00` or `$80`. For the second one, it's `$28` and `$A8`. But maybe the use cases are less obvious ?
Now imagine you want scroll only the last 32 lines of the screen, then the MSB of the baseline will be either `#$22` or `#$23` to which you add 4 for each of the next seven lines while the LSB flips between `#$50` and `#$D0`. And you have many options to unroll the loops if you want to speed things up a little bit further.
For instance this code would copy bytes 1-39 of each line of the 32 last lines of the screen to bytes 0-38 effectively scrolling that part of the screen one byte to the left.
`POKE`ing 1 in the next line produced a violet dot in x=0, while `POKE`ing 2 resulted in a green dot in x=1 and finally, POKEing 3 created two white dots, one in position 0 and the other in position 1.
* if the pixel is off, it's rendered black except if both its neighbours are on, in which case it's rendered using the color of its neighbours' columns
#### 6. It's impossible to plot more than one pair of consecutive colored pixels: colored pixels are always odd in number
#### 7. To plot only two consecutive colored pixels, they must be surrounded by two white pixels on one side and two black pixels on the other side
#### 8. Single dot (then colored) pixels must be surrounded by two pairs of black pixels but the minimum distance between two single dot pixels of the same color is 3 black pixels. The minimum distance between two single dot pixels of different colors is 2 pixels.
So, the 7th bit switches to a different color palette. Pixels in this palette follow the same rules as the previous palette. But we can add more observations.
#### 9. A second palette is selected when the 7th bit (AKA the "hi-bit") is ON
#### 10. Blue is on even columns and orange/red is on odd columns ... HEY WAIT !! LOOK CLOSELY !
#### 11. Blue pixels are displayed in-between the columns of the violet/green pixels while red pixels are displayed in-between the columns of the green/violet pixels.
How weird is that ?
Let's try this:
10 HGR: N=128: YY=0
20 FOR Y = 0 TO 127
30 A = INT(Y/64): REM A-ZONE
40 B = INT( (Y - 64 * A) / 8): REM B-ZONE
50 C = INT(Y - 64 * A - 8 * B): REM C-ZONE
60 P = 8192 + A * 40 + B * 128 + C * 1024: REM STARTING ADDRESS IN RAM
As you can see, not only are the color pixels of the other palette slightly shifted but the whites and blacks too !
Ok, let's try something else using HPLOT and HCOLOR this time ..
NEW
10 HGR
20 X=0: C = 3
30 FOR Y = 0 TO 159
40 HCOLOR = C
50 HPLOT X,Y
60 C=10-C
70 IF C = 3 THEN X=X+1
90 NEXT
![screenshot](img/apple2_hires_hplot560.png)
What a beautiful colored line ... looks so sharp !
What we did was plot one dot with the first palette, go down one line, plot a dot with the second palette in the same X-coord, go down one line, plot a dot in the next X-coord with the first palette, and so on.
This is why sometimes you can read that the Apple ]\[ has a hires resolution of 560x192 (and I'm not talking about double hi-res which is an entirely different topic !). It's possible to plot "between" columns of the other palette making it look like the resolution is 560 pixels wide. But practically, this is not useable because on one byte you may activate only one palette (using the 7th bit). So it can be used mostly only if you're turn on only one bit in the 7-pixels byte. Since you need at least 2 black pixels between single dot pixels and that you can't use the "in-between" columns until 7 pixels further, the illusion of 560 pixels horizontally will quickly vanish.
Is the Apple ]\[ hires screen 280 pixels wide ? Yes, if you consider a monochrome display, it is. If you're counting on colors, it's more like a 140 pixels wide screen since you need two pixels to render white. And as we've seen there are a lot of limitations on the use of colors.
Let's speak of two others ... yes, the nightmare is far from finished !