Foreword
In the previous post we added some color to our C64 Emulator. We could also kind of emulate the effect of flashing borders when the game Dan Dare loaded from a tape image in our emulator.
However, when border colors change midway in a scan line we couldnt see the effect, since we only use one border color per scan line.
In this post we will attempt to render the mid-scaline border changes more accurately.
A brief peek into the Vice source code
In the previous post we looked a bit into the source code of Vice C64 emulator, the gold standard of C64 emulators, in order to get tips for rendering. We will do the same in this section to get some tips on how cater for mid-scanline border color changes.
Now, I am probably making it sound more complex than it is to do mid-scanline border changes. One could just with every emulated CPU instruction render a part of the scanline and use the border color at that point in time. However, I tried this previously with a JavaScript C64 emulator, and I hit a terrible performance hit.
It turns out that doing it this way, you get very bad cache hit performance on a CPU. Vice does something efficient here. It emulates a scanline worth of CPU instructions at a time. Any border changes happening gets added to a list. Afterwards, you just render the list for the scanline. By emulating as much subsequent CPU instructions as possible at a time, you cache hits will be far more predictable, rather than haphazardly switching between emulating a few CPU instruction, then rendering a bit of a border here, and then fetching a character code here, with its bitmap and so.
Let us briefly skim through the code on how Vice render border color changes on a scanline. All this happen in the Vice source code in the file raster-line.c.
Firstly, it is catered for when drawing scanline, where the border spans the whole line. This is when the screen is blanked, or a line above or below the screen area:
So, on these lines we take the pixel position where the border changed and drawing line segments. with the last change, we extend from that pixel position to the end of the line. The raster_changes_apply method make sure we always have a handle on the last border color change.
Now, in the screen area, we draw a border on the left of the visible screen, and on the right. Drawing these borders with changes, make it a bit more tricky, but not impossible. Those cases are handled in the else part, which we omitted above.
Let start with drawing the left border:
This process is similar as with the previous snippet. On the scanline, however, we stop just before the screen area.
Let us look at drawing the right border:
A similar process follows to draw the right border, but skipping the border changes that happens in the visible screen area.
We now have enough ideas to start coding the rendering mid-scanline border changes, in our Flutter C64 Emulator.
Storing Border Color changes
Now, when our Flutter C64 emulator execute emulate instructions, and it changes the border color, we need to make a note of the change, so by the end of a scanline, we can render the line with the changed border colors.
Let us start by defining the structures for storing these changes. Firstly, lets define a structure for a border change itself:
class RasterChange {
int where = 0;
int value = 0;
void reset() {
where = 0;
value = 0;
}
}
The where field indicates where on the scanline the border change occured and value is the new color.Next, let us look at a structure for storing border changes on a scanline:
import 'package:c64_flutter/raster_change.dart';
class RasterChangePool {
final List<RasterChange> actions =
List.generate(
100,
(_) => RasterChange(),
);
int count = 0;
RasterChange allocate() {
return actions[count++];
}
void clear() {
count = 0;
}
}
Here we pre-define 100 instances of RasterChange, which we reuse trough the entire life of the Emulator. Now, looking at this, might seem a bit counter-intuitive, since a Flutter List can grow and shrink dynamically. However, in this emulator showing 60 frames a second and hundreds of lines per frame, using the dynamic nature of List, will cause constant garbage collection, which can make the emulator appear to stutter.
Therefore, keeping a static list, we avoid the possibility of stuttering. We have a count variable where we keep track of the current top of the list.
Now, within our Vicii class, we will be utilising this structure. We modify the setReg method as follows:
setReg(int address, int value) {
switch (address & 0x3f) {
case 0x20:
int cycle = 63 - _vicAlarm!.getRemainingTicks();
int posX = (cycle - 17) * 8 + 32;
if (posX <= 0) {
currentRasterBorder = value;
} else if (posX > 0 && posX < 384) {
var rasterElement = rasterPool.allocate();
rasterElement.value = value;
rasterElement.where = (cycle - 17) * 8 + 32;
} else {
nextLineBorderColor = value;
}
default:
}
_regs.setInt8(address & 0x3f, value);
}
Here we see that a border change only get qued with posX between 0 and 384. Outside this range, i.e. outside the visible range, the color either gets assigned to currentRasterBorderColor or nextLineBorderColor. The function of these two variables will become clear later on.Rendering Border Changes
Let us now move onto actually rendering the actual border changes. A key function for this is in processVicAlarm, which is triggered at the end of every scan line. When this method triggers, Our RasterPool will contain all the Border color changes our virtual CPU has set in the scan line that just finished:
processVicAlarm(int remaining) {
_vicAlarm!.setTicks(63 + remaining);
// process borders
// Exit if v-blanking
if (yReg >= 17 && yReg <= 300) {
drawScanLine();
currentPosStartLine = currentPosStartLine + 400;
}
currentRasterBorder = nextLineBorderColor ?? currentRasterBorder;
nextLineBorderColor = null;
rasterPool.clear();
yReg++;
if (yReg == 312) {
_frameFinished = true;
yReg = 0;
currentPosStartLine = 0;
videoMatrixPos = 0;
charLine = 0;
}
}
drawScanline() does the drawing of the actual scanline. It is in this methos we will later make use of the border color changes.I have bolded a section in the snippet above where we do some housekeeping. You will remember a bit earlier I introduced the variable nextLineBorderColor, which basically contains the color of a border change after the scan line rendered, but before the beginning of the next scan line. We dont want this change to get lost, because this is the color to use in fwolling scan lines, should there happen no further changes in the border color. So, we set currentRasterBorder to that color, and then we clear nextLineBorderColor.
Finally we clear all raster changes, so we can start with a fresh list on the next scan line.
Next, us have a look at the changes required in the method drawScanLine(). We look first at the top part of the border:
if (yReg < 51) {
int xs;
if (rasterPool.count > 0) {
for (var i = xs = 0; i < rasterPool.count; i++) {
var xe = rasterPool.actions[i].where;
if (xs < xe) {
c64Buffer.fillRange(
currentPosStartLine + xs, currentPosStartLine + xe,
currentRasterBorder);
xs = xe;
}
currentRasterBorder = rasterPool.actions[i].value;
}
c64Buffer.fillRange(
currentPosStartLine + xs, currentPosStartLine + 400,
currentRasterBorder);
} else {
c64Buffer.fillRange(currentPosStartLine, currentPosStartLine + 400, currentRasterBorder);
}
return;
}
Since the top part of the border occuspies the whole scan line, we process all the changes on such a line. We code this in a very similar way as our peek into the Vice source code. In the case where there was no border color changes on the scan line, we just draw the whole line in the current border color.
Next, we focus on drawing the left and the right border on the visible screen area. We will have an if statement for drawing both. First in the if statement, we start with drawing the left border:
if (visibleVerticalRegion && displayEnabled) {
...
for (i = xs = 0; i < rasterPool.count && rasterPool.actions[i].where < 40; i++) {
var xe = rasterPool.actions[i].where;
if (xs < xe) {
c64Buffer.fillRange(
currentPosStartLine + xs, currentPosStartLine + xe,
currentRasterBorder);
xs = xe;
}
currentRasterBorder = rasterPool.actions[i].value;
}
c64Buffer.fillRange(
currentPosStartLine + xs, currentPosStartLine + 40,
currentRasterBorder);
...
}
Further down in the if statement we skip the border changes that happen in the visible area, making sure we keep the current border color up to date: for (; i < rasterPool.count && rasterPool.actions[i].where <= 320+40; i++) {
currentRasterBorder = rasterPool.actions[i].value;
}
You will notice we don't initialise the variable i, but we continue from the where our previous loop stopped.Finally, further down in the if statement, we draw the right border:
for (xs = 320+40; i < rasterPool.count; i++) {
int xe = rasterPool.actions[i].where;
if (xs < xe) {
c64Buffer.fillRange(
currentPosStartLine + xs, currentPosStartLine + xe -1,
currentRasterBorder);
xs = xe;
}
currentRasterBorder = rasterPool.actions[i].value;
}
c64Buffer.fillRange(
currentPosStartLine + xs, currentPosStartLine + 400 -1,
currentRasterBorder);
The Result
With the code changes the loading screen look like this:
And this time we can see the rugged edges of the flashing borders!
In Summary
In this post we implement mid scanline border change rendering using the same technique as in the Vice Emulator. This enabled us to emulate the flashing borders while loading a classic game title from a tape image more accurately.
In the next post we will be implementing high resolution rendering in our emulator. This will enable us to see the splash screen while the game loads from the tape image.
Until next time!
