In this section, we will be covering a couple of important things in order to achieve full screen smooth scrolling with colors:
- Bank switching & double buffering
- Raster interrupt timing to handle color copying
- Avoiding flickering and stops
- Screen copying routines
In the end, this is what it all comes down to:
The idea is simple, but the implementation somewhat tedious.
- For each frame, use the vertical scroll register and scroll y between 0 and 7
- When the scroll register is zero, copy all image & color data one row down
- Render a new line of text & color at the top of the screen
Sounds easy? Yeah but man this is C64 and nothing is that simple. As mentioned in this blogpost, there are a number of things we need to address before being able to achieve god-like smooth scrolling (as opposed to hellish flickering):
- Copying of screen data is slow, and cannot be performed in a single pass. Therefore, we have to split the copying into two parts: copy upper and lower screen data separately at different values for the scroll register.
- In addition, to prevent flickering, we implement double buffering. This means that we are displaying one screen while writing to another, before switching the screen memory pointer when the scroll register hits 0. This is done via bank switching – we continuously switch between bank 0 and bank 1 in order to prevent screen artifacts.
- As the C64’s color data located at $D800 is completely static, it is impossible to double buffer colors. This means that the color copying must be “live”, and requires that we perform the copying synchronized with the current raste line. Again, this needs to be performed in two passes.
The good news
Turbo Rascal SE contains a lot of built-in functions, which are hard-coded drunkenly composed assembler functions that can be called directly from TRSE. In addition, we will now encounter the first use of the Turbo Rascal SE Ras library, or RasLib for short. RasLib is a collection of include files that contain bunch of useful implementations that is written in Rascal, so you don’t have to delve into the technical parts. However, we will be covering the more advanced implementations further down in this tutorial, just because.
program Tutorial8;
var
index, time, a, val, color, colorShift,i,j,k : byte;
fade : array [16] of byte = (11,6,6,12,4,14,15,1,1,15,14,4,12,6,6,11);
mainChar: IncBin("test.bin","$27FF");
// Include methods for banking
@include "../RasLib/verticalbanking.ras"
// include vscroll methods
@include "../RasLib/verticalscrolling.ras"
In the variable declaration, we have our old fade function and a custom character set loaded at $2800 (yeah there is an extra rogue byte there somewhere). For the case of scrolling & banking, we only need to include two RasLib files in order to get the vertical scrolling up and running : “verticalbanking.ras“, which contains banking & copying methods, and “verticalscrolling.ras” which contains exactly what it sounds like. These RasLib files are located in the main project folder and can be edited just like regular files.
The main routine looks as follows:
procedure Setup();
begin
copyfullscreen(^$27FF,^$27FF+^$4000);
poke(SCREEN_BG_COL, 0, 0);
poke(SCREEN_FG_COL, 0, 0);
hideBorderY(0);
end;
begin
time:=0;
Setup();
disableinterrupts();
RasterIRQ(Update(), 1);
enableinterrupts();
end.
Note here some new functions:
- CopyFullScreen is a built-in method that will copy exactly 1000 bytes from location A to B. In this case, we are copying 1000 bytes from the custom caracter set from bank 0 to bank 1 (+$4000 bytes)
- HideBorderY extends the border (bad naming perhaps) by hiding line 0 and 24 on the screen. This enables the illusion of continuous smooth scrolling by preventing the user from seeing the actual writing on line 0.
Again, the main method is hooked up to a raster IRQ:
// Raster update
interrupt Update();
begin
time:=time+1;
a:=(time*4)&1;
if a=0 then begin
VerticalScroll();
// Only print a new color line when scrolling index is 0
if g_vscroll=0 then
printColor();
// Only print a new line when scrolling index is 7
if g_vscroll=7 then
PrintLine();
end;
kernalinterrupt();
end;
The Update() interrupt increases a counter, and slows it down by letting the vertical scroll method only be called on every 4th frame. The VerticalScroll() method is implemented in the RasLib file “verticalscrolling.ras”, and performs all the copying, banking and raster timing you need. For now. The only left needing implementation is what to actually display during scrolling. The PrintLine outputs characters when the (global) scroll register index i 7, while color data is added when the scroll register is at 0. The implementations are as follows:
// Print a single line at the top of the screen on the current bank
procedure PrintLine();
begin
j:=sine[time*1]/8;
k:=sine[j*time/64]/2;
for i:=0 to 40 do begin
val:=val/32+64 - 4;
// Chose the center of a nice sine functions so that the background looks
// like a canal
val:=sine[(i*4 + j+k)+30]/16 + 64 - 4;
if val<64 then
val:=$20;
if g_currentBank=1 then
poke(^$0400, i, val)
else
poke(^$4400, i, val);
end;
end;
// Print a line of colors at $D800
procedure printColor();
begin
colorShift:=0;
color:=fade[(time/16 + colorShift)&15];
for i:=0 to 40 do begin
poke(^$D800,i,color);
end;
end;
Again, these two methods are quite arbitrary : printColor just prints a indexed color from the fade function, while PrintLine outputs characters 64-80 as the shifted center of a sine function. This particular character set has graded characters from empty to filled on this particular location (64-80), hand-drawn by me, and was also used in the Plasma Effect tutorial.
The gritty parts
If you just want to get vertical scrolling up and running, you can stop reading here. However, if you are going to use this method in a full-scale project, you had better understand how this routine really works, because you will probably have to modify it.
The deal with VIC banks:
The C64 has 64K of memory, but the VIC graphics chip can only address 12 bits, meaning that
- The C64 can "see" all memory simultaneously
- The VIC chip han only "see" 16K, or $4000 bytes of memory
- Color data is different, and is located globally at $D800, no matter what
When double-buffering, we are copying data from the current bank to the next bank in line, which is fine and well. However, since we loaded the custom character set at $2800, this memory address is invisible to the VIC when we switch from bank 0 to bank 1. We therefore have to copy the entire page of character sets to the "mirrored" position that bank 1 can access - meaning that we copy data from $2800 to $2800 + $4000. For future tutorials, this will also have to be performed when displaying sprites while bank switching.
Anyway, here's the Vertical Scroll method included from RasLib:
procedure VerticalScroll();
var
g_vscroll : byte;
begin
g_vscroll:=(g_vscroll+1)&7;
if g_vscroll=0 then begin
WaitForRaster(150);
CopyColor(0);
end;
waitForRaster(251);
if g_vscroll=0 then SwitchBank();
if g_vscroll=1 then CopyScreen(1);
if g_vscroll=4 then CopyScreen(0);
if g_vscroll=0 then begin
CopyColor(1);
//printColor();
end;
scrolly(g_vscroll);
//if scroll=7 then PrintLine();
end;
Whenever this method is called, a global scroll register "g_vscroll" is increased and kept between values 0-7. Breakdown:
- If a bank switching (displaying the next buffer) is going to happen (at g_vscroll=0), we need to copy color data as well. However, color data is fixed at $D800, so the trick here is to start copying *after* the raster line has passed the current position that we are copying from. In addition, we only have time enough to copy half of the color data - or the whole process ends up with severe flickering. We therefore time the copying routime to wait for rasterline 150 before starting the copying of the upper half of the color data.
- We then wait for the raster to near the end of the screen, so flickering is avoided (about at position 250)
Next, to spread the workload and decrease chances of flickering, we perform various tasks at various stages of the scroll register:
On g_vscroll = 0, we perform the actual bank switching, presenting the next buffer and switching the current bank index
On g_vscroll =1, we copy the lower half of character data from the current bank to the next bank in line
On g_vscroll=4, the upper half of character data is copied to the next bank in line
If we are at g_vscroll=0, when color copying should happen, we are now guaranteed to be at a rasterline position near the bottom, so we can proceeed by copying the lower half of the color data without getting flickering.
Finally, at rasterlines>250, we shift the actual hardware vertical scroll register. If we did this at <250, it would result in severe flickering.
Finally, we turn our attention to the bank switching methods. Here are all definitions from the verticalbanking.ras RasLib include file:
procedure Banking_Vars();
var
g_currentBank:byte;
temp_colorCopy : array[40] of byte;
begin
g_currentBank:=0;
end;
procedure CopyScreen(ul_:byte);
begin
if g_currentBank=0 then begin
if ul_=0 then
copyhalfscreen(^$0400 + ^520, ^$4400 + ^40 + ^520,12, 1)
else
copyhalfscreen(^$0400, ^$4400 + ^40, 13, 1);
end;
if g_currentBank=1 then begin
if ul_=0 then
copyhalfscreen(^$4400+^520, ^$0400 + ^40 + ^520, 12, 1)
else
copyhalfscreen(^$4400, ^$0400 + ^40,13, 1);
end;
end;
procedure CopyColor( ul2_copycolor:byte );
begin
if ul2_copycolor=0 then begin
// First, copy the missing line
memcpy(^$DA08, 0, temp_colorCopy, 40);
copyhalfscreen(^$D800, ^$D800+^40,13, 1);
end
else begin
copyhalfscreen(^$D800+^14*^40 , ^$D800+^15*^40,10,1);
memcpy(temp_colorCopy, 0, ^$D800 + 14*40, 40);
end;
end;
procedure SwitchBank();
begin
if g_currentBank=0 then
SetBank(VIC_BANK1)
else
SetBank(VIC_BANK0);
poke(VIC_DATA_LOC, 0, $1A);
g_currentBank:=(g_currentBank+1)&1;
end;
These methods should be quite self-explanatory, but anyway here's a small breakdown:
- These methods assume that we are switching between bank 0 and 1. If you need different banks, you need different methods (fo now).
- The CopyScreenVerticalShift method copies either the upper or lower half of the screen to the next bank in line with a vertical shift
- The CopyColorVerticalShift copies eiher the upper or lower half of the screen to the next bank in line with a vertical shift
- SwitchBank alternates g_currentBank between 0 and 1, while making sure that the current character location is set properly to $2800 ($A * $400 = $2800) on the local VIC bank.