Loading Cookies...
2024-01-28
Apple II - How DHR works

In the previous part, we learned about the basics of the Apple II Hi-Res graphics mode, allowing us to display up to six colors on a 280x192 pixel screen. Today, we will explore the Double Hi-Res mode, its 16 colors and doubled resolution, how it works, the memory it occupies and what are its advantages and challenges.

If you missed the previous part, you can read it here: Apple II - Hi-Res Fundamentals

Let's get started!

Credits: BattleStations - a Retrocomputing, Computer Science, Algorithms and Education magazine, originally published on Wednesday, April 12, 2017
Copyright Jonathan Graham - All Rights Reserved

Apple II - Double Hi-Res From The Ground Up
Part 2: How DHR works

Bank switched memory:


One limitation that 8 bit machines hit early on was memory. If you haven't noticed by now all of the addresses we've referenced here use two bytes $2000,$4000,etc... This is because the 6502 internally stores addresses using no more than two bytes. Two bytes is sixteen bits which means 216 possible memory locations. In other words 65536 bytes or 64 Kilobytes.
To get around this, many systems used a technique called bank switching. This involves having circuitry which allows you to take some part of the memory in the computer and switch it with another piece of memory of the same size. Effectively you can take what's stored at say memory location $3000-$3FFF - flip a switch - and now those same memory locations point to a completely different block of memory. Flip the switch again and you've got your original memory block back.

With the release of the Apple //e and the Extended 80 Column Card users now owned machines with 128K of memory primarily existing as two bank switched blocks of 64K. The memory that the Apple II uses when turned on is called the MAIN bank and the other 64K is referred to as the auxiliary bank or simply AUX memory.

Double hi-res works by having the Apple //e video circuitry read the memory in both the MAIN and AUX banks at the same time. This extra memory doubles the number of pixels available in monochrome hi-res and doubles the number of bits per pixel on a colour screen. There's no documentation that I can find on who decided to make this change but evidence suggests it was an afterthought and possibly done by someone who worked on the Apple /// or understood it's video circuitry. As this function replicates the Apple ///'s graphic modes almost exactly. In any case once this was realized, Apple made this change to their production line resulting in the Revision B motherboard.
Under DHR, pixels are read first from AUX memory and then from MAIN memory. So the screen layout for the first eight lines of DHR PAGE1 looks something like this


Let's take a closer look at just the first four bytes. Again we will use letters to represent pixels.



As you can see, we have seven pixels spread across four bytes of memory BUT using only two addresses $2000 (8192) and $2001 (8193). Some of these pixels reside in a single byte and others are split between two bytes - one in MAIN and the other in AUX.

Perhaps you've noticed that the palette bit is still there. Does that mean we can use it to get even more colours? Nope! When DHR is active the palette bit simply gets ignored (in the vast majority of cases).

So how do we activate this magical mode? You guessed it! Softswitches! You need FIVE - in no particular order. Here they are:

$C050 (49232) - Graphics
$C057 (49239) - Hires
$C052 (49234) - Fullscreen
$C05E (49246) - DHR
$C00D (49165) - 80 Column mode

POKE something into each one of those locations and you will be on the DHR screen. At this point you usually see a kind of stripe pattern. This is due to the fact that the main hi-res page is, on power up filled with garbage and the AUX page gets cleared.

Drawing on the DHR screen


Ok let's put together everything we've learned and get some pixels up on the DHR screen. If you recall our diagram...



...and keeping in mind that currently we don't yet know how to access the AUX memory. The first memory location we have access to is 8192 in MAIN memory. As you can see, it has one bit from pixel B, all four from pixel C and two from pixel D. So the only pixel we can fully draw by changing this byte alone is C! In order to know what to put in this byte we need to decide what colour pixel we are going to draw. Here is full list of DHR colours and their associated bit patterns:


Lets say we want to colour pixel C yellow. The bit pattern for yellow is 1110 and putting that into the spot where pixel C is in our diagram we get PDD1110B. For now lets assume that all the other pixels around this are black so we can set all the other bits in this byte to zero and the result is 00011100 or 28! There's probably a lot of junk on the screen so let's write a simple program to flicker the pixel between yellow and black which as you can see from the chart above the bit pattern is 0000.

10 POKE 8192,0
20 POKE 8192,28
30 GOTO 10

Accessing AUX memory


The last thing we need in order to have complete control of every pixel on the DHR screen is to figure out how to write to AUX memory. To help with that the Apple II provides a special softswitch called 80STOREON $C001 (49153). When invoked it changes the behavior of the PAGE1 and PAGE2 softswitches. Instead of switching which page is displayed. The PAGE2 softswitch now swaps the MAIN hi-res page 1 ($2000-$3FFF) with the hi-res page in in AUX memory and the PAGE1 softswitch swaps them back.
Looking back at our diagram again to the first byte of PAGE1 in aux memory. We see: PBBBAAAA and we recall the bit pattern for yellow is 1110. To set pixel A to yellow we again insert it into our diagram PBBB1110 and zero the other bits 00001110 which gives us 14. So starting from the beginning our program for writing looks like this:

10 POKE 49232,100: REM TURN ON GRAPHICS
20 POKE 49239,100: REM TURN ON HIRES
30 POKE 49234,100: REM TURN ON FULLSCREEN
40 POKE 49246,100: REM TURN ON DHR
50 POKE 49165,100: REM TURN ON 80COLUMNS
60 POKE 49153,100: REM TURN ON 80STORE
70 POKE 49237,100: REM TURN ON PAGE2
80 POKE 8192,14: REM WRITE YELLOW PIXEL RUN

As this is probably the last bit of BASIC code in this series. Here is that same program in assembly code:

     STA $C050 ;TURN ON GRAPHICS
     STA $C057 ;TURN ON HIRES
     STA FULLSCREEN ;TURN ON FULLSCREEN
     STA $C05E ;TURN ON DHR
     STA $C00D ;TURN ON 80 COLUMNS
     STA $C001 ;TURN ON 80STORE
     STA $C055 ;TURN ON PAGE2
     LDA #$0E ;LOAD 14
     STA $2000 ;Put in first location on HIRES PAGE 1 in AUX RAM.

...and here's a binary output that you can paste into any Apple II emulator.

CALL -151
6000:8d 50 c0 8d 57 c0 8d 52 c0 8d 5e c0 8d 0d c0 8d 01 c0 8d 55 c0 a9 0e 8d 00 20 60
6000g

Now you have everything you need to activate the DHR screen and turn on any DHR pixel in any DHR color. But there is more than one way to access AUX memory, and therefore the DHR pages. We have already talked about using the 80STOREON softswitch $C001 to change the function of the PAGE1/PAGE2 softswitches so they map $2000-$3FFF (hi-res page 1) between MAIN and AUX memory banks respectively. The effect of which you can see here:


One limitation you might notice is that, for whatever reason (remember that DHR was an afterthought) these switches ONLY work for hi-res page 1. In other words even with 80STOREON and PAGE2 selected, hi-res page 2 ($4000-$5FFF) still maps to main memory.

So what do we do? Well first we will use another softtswitch 80STOREOFF $C000 this returns the PAGE1/PAGE2 function to normal. Once we do that we can use a different set of softswitches: RAMWRTON ($C005) and RAMWRTOFF ($C004) to allow us to write to AUX memory.

These switches act in a fundamentally different way from what 80STOREON gives us. When $C005 is invoked all memory WRITE operations (e.g. STA $2222) go to AUX memory and when $C004 is invoked all memory WRITE operations go to main memory. READ operations (including fetching the next instruction in your program) always go to MAIN memory.



In this configuration we can use the PAGE1/PAGE2 softswitches to display the DHR pages 1 and 2 respectively.

To demonstrate this I've provided a program below. It stores alternating black (bit pattern 0000) and then white (bit pattern 1111) pixels in the first position of the first eight lines of DHR PAGE1. Which starts at AUX memory location $2000. From there we store the opposite pattern, white and then black on DHR PAGE2. Which is at AUX memory location $4000. Finally we enter into a loop which just flips endlessly between PAGE1 and PAGE2.

First we have the assembly code:

     STA $C050 ;Turn on GRAPHICS
     STA $C057 ;Turn on Hi-res
     STA $C052 ;Turn on Full screen
     STA $C05E ;Turn on DHR
     STA $C00D ;Turn on 80 Columns
     STA $C000 ;Turn OFF 80STORE
     STA $C005 ;Turn ON RAMWRT
     LDA #$00 ;Load bitpattern 000000
     STA $2000 ;Store on DHR PAGE 1, line 1
     STA $4400 ;Store on DHR PAGE 2, line 2
     STA $2800 ;Store on DHR PAGE 1, line 3
     STA $4C00 ;Store on DHR PAGE 2, line 4
     STA $3000 ;Store on DHR PAGE 1, line 5
     STA $5400 ;Store on DHR PAGE 2, line 6
     STA $3800 ;Store on DHR PAGE 1, line 7
     STA $5C00 ;Store on DHR PAGE 2, line 8
     LDA #$0F ;Load bitpattern 001111
     STA $4000 ;Store on DHR PAGE 2, line 1
     STA $2400 ;Store on DHR PAGE 1, line 2
     STA $4800 ;Store on DHR PAGE 2, line 3
     STA $2C00 ;Store on DHR PAGE 1, line 4
     STA $5000 ;Store on DHR PAGE 2, line 5
     STA $3400 ;Store on DHR PAGE 1, line 6
     STA $5800 ;Store on DHR PAGE 2, line 7
     STA $3C00 ;Store on DHR PAGE 1, line 8
LOOP STA $C055 ;Show PAGE 2
     STA $C054 ;Show PAGE 1
     JMP LOOP

I'll assume, for now that everyone understands the insane VERTICAL structure of all the Apple II hi-res screens. If you don't then just take it for granted that each line of pixels in the first eight lines of either DHR page are $400 bytes apart. Anyone who wants a tutorial on this just mention it the comments section!

Now here's the program in binary form. You can paste it into your favorite emulator.

call -151
6000: 8d 50 c0 8d 57 c0 8d 52 c0 8d 5e c0 8d 0d c0 8d 00 c0 8d 05 c0 a9 00 8d 00 20 8d 00 44 8d 00 28 8d 00 4c 8d 00 30 8d 00 54 8d 00 38 8d 00 5c a9 0f 8d 00 40 8d 00 24 8d 00 48 8d 00 2c 8d 00 50 8d 00 34 8d 00 58 8d 00 3c 8d 55 c0 8d 54 c0 4c 46 60
6000g

So why would we ever use 80STOREON?


Virtually every graphic mode on every computer involves trade-offs. 80STOREON is no different. When 80STOREON is active we can read and write from DHR PAGE1. When 80STOREOFF is invoked RAMWRT only lets us WRITE to DHR PAGE1 and PAGE2. If we wanted to read the screen we would be out of luck. This would be problematic if, for example you were drawing a spaceship on a complicated background. In this case it would be common to copy the small part of the background you are going to draw the ship on top of to some area in memory. That way, later when you need to erase the spaceship. You can simply copy that small portion of background back. However using RAMWRT alone, you couldn't do this.

So what happens if we want to read from both DHR pages?


My linear algebra professor had this understated way of telling the class that he didn't want to explain something because it would require too much time or effort. He would simply say: "It's non-trivial". It didn't take long for us to understand that meant: "Here be Dragons!"
Fact
Early on, the DHR softswitches must have been rather poorly documented because on page 89 of the manual for Beagle Graphics, a popular set of DHR graphical utilities it actually says that DHR Page 2 "doesn't really exist"

Reading from both DHR pages is...non-trivial. It requires the use oftwo more softswitches RAMRDOFF ($C002) and RAMRDON ($C003). Using them though, is tricky. Why? Remember when I said that even when RAMWRT is on our program is still being read from MAIN memory? Well as soon as you invoke $C003 - Immediately the 6502 program counter starts reading the next program instruction from AUX memory. If you don't have a program sitting in AUX memory at that location the computer will probably just crash ( more correctly it will execute random bytes as instructions until it encounters a 00 which is a BRK instruction)

To use this feature successfully we need to employ RAMWRT to write a program into the memory location the program counter will be at the instant we invoke RAMRDON. We also need a program sitting in MAIN memory exactly where we expect our program to be when we invoke RAMRDOFF to bring us back into MAIN memory again.
If that sounds like a pain in the ass? You'd be right.

Going forward we will be using both approaches. For doing single pixel plots we will be using 80STOREON as we will need to read screen memory in order to add pixels to the screen without disturbing other bytes. As we start looking at bitmap graphics we will be using 80STOREOFF. Since, at first we will be doing things like character generation. Which can be done by blindly writing to screen memory and later when we start writing arcade games we will do operations like collision detection without reading screen memory at all...
Part 2 / 9


Update: just found the original blog series that were published on BattleStations, still available on Blogger.

So that concludes the Apple II Double Hi-Res tutorial series for now, as the source written by Jonathan Graham is still available on internet today, although Blogger is quite bad in terms of SEO and I really had troubles finding the source.

Also after some digging I found the Forum where these tutorials originated from. Enjoy!

2023-06-12

Flash was once the dominant web technology for creating interactive and multimedia content. It powered countless websites, games, animations, and videos that entertained and educated millions of users. However, Flash also had many drawbacks, such as security issues, performance problems, and compatibility challenges. As newer web standards like HTML5 emerged, Flash became obsolete and was eventually discontinued by Adobe in 2020...

Read more
2023-04-11

Have you ever stumbled upon an old blog post that you wish you could read again? What if that blog post was about something you're passionate about, like retro computers and programming? Unfortunately, many old blog posts are no longer available online today. But don't worry! Thanks to the WayBackMachine - an internet archive that provides backups of webpages throughout the years - you can still access these precious internet resources. In fact, it's important that we make sure these resources are available to the public again so that we can continue to learn from them and appreciate their value. That's why I'm excited to share with you a blog post about Apple II programming, especially about its Double Hi-Res graphics mode, originally published in BattleStations...

Read more
2019-10-24

JS13K games is Javascript golfing competition that runs for a month between August 13th and September 13th every year. The game jam has one main characteristic - the entire game (source code, graphics, sounds etc.) must be contained in a 13 kilobytes ZIP archive, as constrained by the creator of the compo, Andrzej Mazur (@end3r)...

Read more
2018-07-26

The Puzzle of Life was created during Castle Game Jam 2018 by Noncho Savov (me, programming) and Jurgita Raynite (art). The Game Jam was organized by James Newnorth (Spelkollektivet) and was held in a medieval castle located in Örebro, Sweden between the 7th and 15th of July. The main theme of the jam was "Capricious"...

Read more
2018-06-01

My upcoming rogue-like role playing game RogueVerse Dungeon was showcased at Retrospelsmässan - one of the largest retro game conventions in the world! The convention is held every year in Gothenburg Sweden and I presented my game as part of Spelkollektivet exhibition. It was an amazing experience - I managed to collect a great portion of feedback from attendees of all ages.. and it was incredible to see a little girl beating my game:..

Read more
2017-09-09

Check out my new game Formation Absent - a turn-based tactical puzzle, which is easy to win but hard to master. A game that is only 13 kilobytes, submission in JS13K competition. Theme of the competition this year is "Lost". As per Compo rules the game is contained in 13kb archive and includes 13 stages and a level editor. The game also keeps your game progress and score through utilizing URL vars. Here is an example of the gameplay:..

Read more
2017-05-01

Each year, a unique JavaScript game development competition takes place within one month, starting on August 13th, named JS13KGames. What's interesting about this competition is that there is a filesize limit requirement on the submitted game source - the entire game must be packed in 13kB ZIP archive. In 2016, I participated with Skip'n Glitch, a two player competitive logical game involving strategy and arithmetics. The game has a single player mode set against a simple and predictable but challenging AI opponent. Two players can play against each other on the same device...

Read more
2016-11-23

As the web tech is evolving, the flash player for web browsers is getting more and more obsolete. For example the biggest advertising platform - Google's DoubleClick dropped support for flash ads. I'm not speaking against the flash technology, which is still viable for game development (it can be easily compiled for mobile or wrapped into Steam). However in web there are new heroes and one of them is the HTML Canvas. I've already shared some lightweight JavaScript libraries used in #js13kgames and today I'm sharing another class - Lens Flare effect!..

Read more
2015-02-28
Archived
2014-07-03
Archived
2013-05-24
Archived
2013-04-10
Archived
2012-10-23
Archived