From Google'ing countless times to find the shortest, sweetest, and easiest example code or tutorial on how to open the borders on the C64, I found myself countless times being disappointed or let down by overcomplicated methods and obscure explanations. It's a pretty common effect these days and it's very cool, but how's it done?
First I'll explain why it works and how to do it, restrictions for drawing in the border, and finally, give some example code. This tutorial, however, will NOT
cover opening the side borders, though it may include some information to help you reach that goal. Conceptually, it's equally as simple, but the timing must be imaculate in order to achieve it. The top/bottom borders don't require such intricate and sensitive timing routines, and can be done by really anyone who can code some machine code, even in a monitor. So sit back, fire up your monitor/assembler, grab a drink, and enjoy
.How it Works and How to Do it
Before you can code anything, you need to know how that thing works, whether it's a plasma, a line routine, a twistor, a poly fill, or a scroller; anything.
To open the borders on the C64, we trick the VIC chip to think that it's drawing the borders, when really it isn't
The way we do this is actually very simple.
The VIC chip has two major control registers: $d011 and $d016. The one we're concerned with when opening the top/bottom borders is $d011 (and likewise, $d016 for the side borders). In both of these, we're looking for a single bit - bit 3.
In $d011, bit 3 controls whether the VIC should display 24 or 25 rows of text. If it's 0, the VIC will draw 24 rows. If it's 1, 25 rows will be drawn. By default, this bit (in both registers) is set to 1.How is this useful to us? How is this relevant to our goal here?
The VIC has a border state for the top/bottom and the side borders. Each border type is either on or off. Here's what happens regarding the top/bottom border state: - 1.
When the C64 starts up, the border state is on. - 2.
Each frame, when the scanline reaches line 50 (line 50 if it will draw 25 rows of text; line 54 if it will draw 24 rows), the border state is turned off and the VIC begins drawing the screen contents. - 3.
The VIC continues its current operation until it finishes the last scanline of the character screen. If bit 3 of $d011 has directed the VIC to draw 25 rows, this is row 250, else it's row 246. At this point, the border state is set to on again. - 4.
The VIC continues happily and the border state is not checked or switched again until the next screen refresh.
This is how the VIC normally operates. But look again at #3 and #4: "...the border state is set to on. 4. The VIC continues happily and the border state is not checked or switched again until the next screen refresh.
One more time: "...the border state is not checked or switched again until the next screen refresh."
If you haven't figured it out yet, this is the key to opening the borders.
What we're going to do is simply trick the VIC to make sure that the border state never actually gets set to on. And because it's only switched at the end of the last character screen scanline, this is actually very easy.
First, at the beginning of each frame, we're going to set bit 3 of $d011, so the VIC should draw 25 rows of text. This is the default, and it's probably set anyways. If not, we'll reset the bit after each frame, so you probably won't even have to do this step. Next, we'll do everything else we would normally do per frame, as long as this activity doesn't occur from scanlines 249 to 255. Then, we'll either set an interrupt to occur at line 249 or just poll $d012 until it returns this value (the timing here isn't too critical so either method would work). Once we've reached this line, we'll either delay until the VIC has finished drawing the current character line, or if there's nothing important there, then it doesn't matter. This is where we clear bit 3 of $d011, so that the VIC will draw 24 lines of text.
Wait a second here. We just set the VIC to draw 24 rows of text on the last scanline of the 25th row
. Doesn't this seem a bit weird? Well, actually, it is
But this has done everything needed to open the border. This is because now, the VIC won't check the border state at line 250, because it already assumed that it did so on line 246. This means the border won't be turned on for this frame.
But why did I tell you to make sure not to do anything from line 249 to line 255? Well, we need to set the VIC back to 25 rows for the next frame so this will work properly. If you do so at the beginning of each frame, then you won't have to worry. Otherwise, set a raster interrupt or poll for line 255, at which point you'll reset bit 3 of $d011.Restrictions for Drawing in the Border
Since the borders are drawn outside of the main/character screen, there's no way to draw any character data outside of the borders. Luckily, the C64 provides more than just the main screen for graphics. You all know what I'm talking about: Sprites
. Sprites, in all their forms, work perfectly when drawn over the border.
You may also notice (and this isn't always the case, so maybe not) that the open borders are filled with solid black or black garbage. Why is this?? Well, since the border state is off, then the VIC will continue to attempt to draw character graphics. Problem is, there's no more screen or color RAM to read from, so the VIC will default to the last byte in the current VIC bank ($3fff for bank 0, $7fff for bank 1, etc.) and draw it over and over, in *only* black. How to fix this? Obviously, you can just set that byte in memory to 0 (as long as it doesn't overwrite any data you may have stored there!!). On the other hand, since you can set the bit to whatever you want, and it acts like black bitmap graphics, does it really need to be set to 0? Example Code
Finally, some code
This code is the exact code I use for the effect, plus some very, very simple startup code. It's simple, small, fast, and as accurate as it needs to be to do this great effect.
; Start of frame.Conclusion & Thanks
; Assume that none of the video registers/memory has
; been changed and there's nothing important stored at $3fff.
; Make sure there'll be no garbage in the open borders.
; Wait until scanline 249
bne * - 3
; Trick the VIC and open the border!!
; Wait until scanline 255
bne * - 3
; Reset bit 3 for the next frame
And that's everything
Hope you enjoyed this tutorial and can get something good out of it. Thanks to HCL/Booze Design for his example code on CodeBase64, because without that I wouldn't've realized how ridiculously simple this effect can be to code. Cheers and happy coding