Foreword
Snooping
Now we can inspect a couple of registers inside _regs. The first thing we can inspect is in what screen mode the VIC-II is. This is set in memory location D011, i.e. register 0x11 in _regs.
Next, it would be interesting to find out where in memory, this display data is stored. To start this investigation, lets pause a bit, and ponder on how the VIC-II sees memory.
- %00, 0: Bank #3, $C000-$FFFF, 49152-65535.
- %01, 1: Bank #2, $8000-$BFFF, 32768-49151.
- %10, 2: Bank #1, $4000-$7FFF, 16384-32767.
- %11, 3: Bank #0, $0000-$3FFF, 0-16383.
This time, DD00 evaluates 4, meaning botht the lower two bits are zero. Doing the inversion we get to 11, which is the last page of memory at C000-FFFF.
- Bit 7 - 4: Bit 13 to 10 of the VIC-II address bus, for screen memory accesses.
- Bit 3 - 1: Bit 13 to 11 of the VIC-II address bus, for character data access. When in bitmap mode, only bit 13 is used, so within a bank the bitmap data can either be within 0 - 7999, or from 8192 - 16191.
Implementing VIC-II memory mapping
... var vicBank = 0; final type_data.ByteData _colorRam = type_data.ByteData(1024); ...The vicBank variable is a mirror of the lower two bits of the register DD00.
setMem(int value, int address ) {
var addressHigh = address >> 8;
var addressLow = address & 0xff;
if (addressHigh == 0xDC) {
cia1.setMem(address, value);
} else if (addressHigh == 0xD0) {
vic.setReg(address, value);
} else if (address >= 0xD800 && address <= 0xDBFF) {
_colorRam.setInt8(address & 0x3FF, value);
} else if (address == 0xDD00) {
vicBank = value & 3;
} else if (address == 1) {
_ram.setInt8(address, value);
_tape.setMotor((value & 0x20) == 0 );
_kernelEnabled = (value & 2) != 0;
} else {
_ram.setInt8(address, value);
}
}
We just added functionality for setting Color RAM and vicBank. int getMem(int address) {
_readCount++;
if (address >= 0xA000 && address <= 0xBFFF) {
return _basic.getUint8(address & 0x1fff);
} else if (address >= 0xE000 && address <= 0xFFFF && _kernelEnabled) {
return _kernal.getUint8(address & 0x1fff);
} else if (address == 0xD012) {
return (_readCount & 1024) == 0 ? 1 : 0;
} else if ((address >> 8) == 0xDC ) {
return cia1.getMem(address);
} else if ((address >> 8) == 0xD0) {
return vic.getReg(address);
} else if (address >= 0xD800 && address <= 0xDBFF) {
return _colorRam.getInt8(address & 0x3FF);
} else if (address == 0xDD00) {
return vicBank;
} else if (address == 1) {
var value = _ram.getUint8(address) & 0xef;
return value | _tape.getCassetteSense();
} else {
return _ram.getUint8(address);
}
}
Next, in this class, we make some modifications in the method for reading from memory for the VIC:We have implemented something similar here than a physical VIC-II chip. on a real VIC-II chip the data bus is not 8 bits, but 12 bits. Four of the bits is part of data coming from the Color RAM.(type_data.Uint8List, type_data.Uint8List) readVicRange(int address, int count) {var (storage, resolvedAddress) = _resolveVicAddress(address); return (storage.buffer.asUint8List(resolvedAddress, count), _colorRam.buffer.asUint8List(address & 0x3FF, count)); }
(type_data.ByteData, int) _resolveVicAddress(address) {
address = address | ((~vicBank & 3) << 14);
if (address >= 0x1000 && ((vicBank & 1) == 1)) {
return (_character, address & 0xfff);
}
return (_ram, address);
}
Here we use the vicBank to assemble the final resolved VIC-II address. Also, the extra extra check added, vicBank & 1, we only return content of character ROM at address 0x1000, with even banks 0 and 2. This is how it works on a real C64.Rendering Bitmap Mode
if (visibleVerticalRegion && displayEnabled) {
if (charLine == 0) {
var screenMemBase = (_regs.getInt8(0x18) & 0xf0) << 6;
var result = memory.readVicRange(videoMatrixPos | screenMemBase, 40);
_charCodeBuffer = result.$1;
_colorRamBuffer = result.$2;
}
Here we get a line worth of character codes of screen memory and color codes from color RAM. var bitmapMode = (_regs.getInt8(0x11) & 0x20) != 0;
if (!bitmapMode) {
drawStandardTextLine();
} else if (bitmapMode) {
drawBitmapModeLine();
}
Here we first determine if we are in bitmap mode as discussed previously, and either render standard text mode or bitmap mode. void drawStandardTextLine() {
var charDrawPointer = currentPosStartLine + 40;
for (int i = 0; i < _charCodeBuffer.length; i++) {
int charCode = _charCodeBuffer[i];
int colorCode = _colorRamBuffer[i];
var bitmapRow = memory.readVic((charCode << 3) | charLine | 0x1000);
if (bitmapRow & 0x80 != 0) {
c64Buffer[charDrawPointer] = colorCode;
}
if (bitmapRow & 0x40 != 0) {
c64Buffer[charDrawPointer + 1] = colorCode;
}
if (bitmapRow & 0x20 != 0) {
c64Buffer[charDrawPointer + 2] = colorCode;
}
if (bitmapRow & 0x10 != 0) {
c64Buffer[charDrawPointer + 3] = colorCode;
}
if (bitmapRow & 0x08 != 0) {
c64Buffer[charDrawPointer + 4] = colorCode;
}
if (bitmapRow & 0x04 != 0) {
c64Buffer[charDrawPointer + 5] = colorCode;
}
if (bitmapRow & 0x02 != 0) {
c64Buffer[charDrawPointer + 6] = colorCode;
}
if (bitmapRow & 0x01 != 0) {
c64Buffer[charDrawPointer + 7] = colorCode;
}
charDrawPointer = charDrawPointer + 8;
}
}
This is the same code more or less than our old except that we don't hard code the foreground color of Light Blue (14) anymore. We now properly get the color code from color RAM. void drawBitmapModeLine () {
var charDrawPointer = currentPosStartLine + 40;
var bitmapOffset = (_regs.getInt8(0x18) & 8) == 8 ? 0x2000 : 0;
for (int i = 0, currentBlock = videoMatrixPos; i < _charCodeBuffer.length; i++, currentBlock++) {
int charCode = _charCodeBuffer[i];
int foregroundColor = charCode >> 4;
int backgroundColor = charCode & 0xf;
var bitmapRow = memory.readVic((currentBlock << 3) | charLine | bitmapOffset);
c64Buffer[charDrawPointer] = (bitmapRow & 0x80 != 0) ? foregroundColor : backgroundColor;
c64Buffer[charDrawPointer + 1] = (bitmapRow & 0x40 != 0) ? foregroundColor : backgroundColor;
c64Buffer[charDrawPointer + 2] = (bitmapRow & 0x20 != 0) ? foregroundColor : backgroundColor;
c64Buffer[charDrawPointer + 3] = (bitmapRow & 0x10 != 0) ? foregroundColor : backgroundColor;
c64Buffer[charDrawPointer + 4] = (bitmapRow & 0x8 != 0) ? foregroundColor : backgroundColor;
c64Buffer[charDrawPointer + 5] = (bitmapRow & 0x4 != 0) ? foregroundColor : backgroundColor;
c64Buffer[charDrawPointer + 6] = (bitmapRow & 0x2 != 0) ? foregroundColor : backgroundColor;
c64Buffer[charDrawPointer + 7] = (bitmapRow & 0x1 != 0) ? foregroundColor : backgroundColor;
charDrawPointer = charDrawPointer + 8;
}
}
You will notice that the mechanism for retrieving bitmap data is almost the same mechanism as fetching data from Character ROM when rendering a line in Standard Text Mode. The only difference is that we don't use a character code as an index for fetching the data, but rather the current position in Screen memory act as the index for fetching the bitmap data.Attempt #1
Implementing Multicolor Bitmap Mode
var bitmapMode = (_regs.getInt8(0x11) & 0x20) != 0;
var multiColorMode = (_regs.getInt8(0x16) & 0x10) != 0;
if (!bitmapMode) {
drawStandardTextLine();
} else if (bitmapMode && !multiColorMode) {
drawBitmapModeLine();
} else if (bitmapMode && multiColorMode) {
drawBitmapMcmModeLine();
}
Now, every pixel in this mode is two bits wide. Each one of the four values for each two bits, represents the following:- 00: Uses the Global Background Color from VIC-II register $D021. This color is shared across the entire screen.
- 01: Uses Color 1, defined by the upper 4 bits (nibble) of the corresponding byte in Screen RAM ($0400–07FF$).
- 10: Uses Color 2, defined by the lower 4 bits (nibble) of the corresponding byte in Screen RAM ($0400–07FF$).
- 11: Uses Color 3, which comes from the corresponding byte in Color RAM ($D800–DBFF$).
void drawBitmapMcmModeLine () {
var charDrawPointer = currentPosStartLine + 40;
var bitmapOffset = (_regs.getInt8(0x18) & 8) == 8 ? 0x2000 : 0;
// %00 uses the global background color register ($D021)
int globalBgColor = _regs.getInt8(0x21) & 0x0f;
for (int i = 0, currentBlock = videoMatrixPos; i < _charCodeBuffer.length; i++, currentBlock++) {
int charCode = _charCodeBuffer[i];
// Extract the two nibbles from Screen RAM for this 8x8 block
int color0 = charCode >> 4; // High nibble -> maps to code %01
int color1 = charCode & 0x0f; // Low nibble -> maps to code %10
int color2 = _colorRamBuffer[i];
var bitmapRow = memory.readVic((currentBlock << 3) | charLine | bitmapOffset);
int resolvedColor;
// Pair 1 (Bits 7-6)
resolvedColor = _resolveMultiColorColor((bitmapRow >> 6) & 0x03, globalBgColor, color0, color1, color2);
c64Buffer[charDrawPointer] = resolvedColor;
c64Buffer[charDrawPointer + 1] = resolvedColor;
// Pair 2 (Bits 5-4)
resolvedColor = _resolveMultiColorColor((bitmapRow >> 4) & 0x03, globalBgColor, color0, color1, color2);
c64Buffer[charDrawPointer + 2] = resolvedColor;
c64Buffer[charDrawPointer + 3] = resolvedColor;
// Pair 3 (Bits 3-2)
resolvedColor = _resolveMultiColorColor((bitmapRow >> 2) & 0x03, globalBgColor, color0, color1, color2);
c64Buffer[charDrawPointer + 4] = resolvedColor;
c64Buffer[charDrawPointer + 5] = resolvedColor;
// Pair 4 (Bits 1-0)
resolvedColor = _resolveMultiColorColor((bitmapRow >> 0) & 0x03, globalBgColor, color0, color1, color2);
c64Buffer[charDrawPointer + 6] = resolvedColor;
c64Buffer[charDrawPointer + 7] = resolvedColor;
charDrawPointer = charDrawPointer + 8;
}
}
int _resolveMultiColorColor(int pair, int bg0, int c0, int c1, int c2) {
switch (pair) {
case 0: return bg0; // %00: Global background ($D021)
case 1: return c0; // %01: Screen RAM high nibble
case 2: return c1; // %10: Screen RAM low nibble
case 3: return c2; // %11: Color RAM lower nibble
default: return 0;
}
}
For each two pair, we have the method _resolveMultiColorColor, which resolve it to the actual color of the pixel.







