It has been a while since I wrote a chapter for this project. I wrote the code for this chapter in the summer of 2008 but I wasn't completely happy with it. So I published the executable as a development version. Then I lost interest. Recently I am receiving emails asking me to continue with this project. So I decided to bring this chapter presenting the code as it is.
In the two last chapters we extended the VIC emulation. But we still can only display text. Here we will introduce other video modes: Multicolor mode, extended color mode, bitmap mode and multicolor bitmap mode.
In the previous chapter we used screenAddress and charRomAddress. In other screenmodes this naming will be confusing. We will talk here about the VideoMatrix (VM), the PixelMatrix (PM) and the ColorMatrix (CM). The begin address of this matrices will be VMbase, PMbase and CMbase. These addresses will be absolute addresses and not relative to the current videobank. So we replace:
bankAddress+screenAddress by VMbase
bankAddress+charRomAddress by PMbase
CMbase is a constant ($D800)
in unit memio we will rename watchVicFrom, watchVicTo and setVicWatchRange to watchVicVMFrom, watchVicVMTo and setVicVMWatchRange.
Since not only the VM should be monitored, we will provide the same functionality for the PM. (watchVicPMFrom, watchVicPMTo and setVicPMWatchRange)
At last we will rename the procedurecall vicText to vicVMupdate. A new procedure vicPMupdate will be added to the VIC interface. See memio.pas for the changes in the memio unit.
a. In chapter 9 we didn't watch the PM. An update of the PM (= the characterset in chap 9) did not change any characters already displayed on the screen.
b. It is not necessary to monitor the CM since an access is already dispatched by colorWrite.
The videomode of the c64 is determinated by bit 6 en 7 of register $D011 and bit 5 of reg $D016. Since these bit's aren't overlapping each other we can easely generate a unique code for the videomode by isolating this bits and combining them in one byte:
vidMode := (reg[$D011] and $60) or (reg[$D016] and $10);
The valid combinations for vidMode are:
$00 : SCM Simple Character Mode
$10 : MCCM Multi Color Character Mode
$40 : ECCM Extended Color Character Mode
$20 : SBMM Simple BitMap Mode
$30 : MCBMM Multi Color BitMap Mode
we will define these as constants.
Bit 6 selects bitmapmode. We define the boolean:
bmm := boolean (vidMode and $20);
The lenght of the PM is variable: we will store it in PMlenght. In a charactermode PMlenght is 1999, in bitmapmode it is 7999.
Every poke to a vic register will change the state of the VIC. We can intercept them the usual way using a case instruction in vicWrite, changing only what is necessary. To make code simple I chose here to make all changes in one procedure called setVicValues. This procedure is called every time a change to the state of the VIC is provoked. Again, this is not performant, but will produce simple code.
procedure setVicValues;
begin
vidMode := (reg[$D011] and $60) or (reg[$D016] and $10);
bmm := boolean (vidmode and $20);
VMbase := bankAddress + (reg[$D018] shr 4)*$400;
if bmm then begin
PMbase := bankAddress + (reg[$D018] and $08)*$400;
PMlenght := 7999
end
else begin
PMbase := bankAddress + (reg[$D018] and $0E)*$400;
PMlenght := 1999
end;
setVicVMwatchRange(VMbase,VMbase+999);
setVicPMwatchRange(PMbase,PMbase+PMlenght);
// refresh
end;when the state of the VIC is changed, the screen should be refreshed to reflect the new state:
procedure refresh;
var i : word;
begin
for i := 0 to 999 do poke (VMbase+i,peek(VMbase+i))
end;The refresh slows down the emulator. Especially at startup. So I disabled it by putting it in a comment. Most programs have no problem with that. But it should be reactivated for a exact emulation. Remark the difference between the behavior of PRINT CHR$(14) with this refresh enabled or disabled.
when a poke to the VM occurs, vicVMupdate is called. This routine dispatches the call in function of vidMode:
procedure vicVMupdate(pos: word; val: byte);
begin
case vidMode of
SCM : updateSCM (pos,val);
MCCM : updateMCCM(pos,val);
ECCM : updateECCM(pos,val);
SBMM : updateSBMM(pos,val);
MCBMM: updateMCBMM(pos,val);
else
aWriteLn ('WARNING: Undefined Video Mode !')
end
end;updateSCM is simply the old vicText procedure:
procedure updateSCM(pos: word; val: byte);
var x0,y0,x,y:integer;
begin
x0:=(pos mod 40)*8;
y0:=(pos div 40)*8;
for x:=0 to 7 do
for y := 0 to 7 do
if boolean(vicPeek(PMbase+val*8+y) and ($80 shr x))
then setPixel(x0+x,y0+y,color_ram[CMbase+pos])
else setPixel(x0+x,y0+y,reg[$D021])
end;In this mode only the lower 6 bits determinate the character (limiting the possible characters to 64). The upper two bits determinate the background color (defined in VIC register $D021 to $D024). The code is straightforward:
procedure updateECCM(pos: word; val: byte);
var x0,y0,x,y:integer;
bg : byte;
begin
x0:=(pos mod 40)*8;
y0:=(pos div 40)*8;
bg := (val and $C0) shr 6;
val := val and $3F;
for x:=0 to 7 do
for y := 0 to 7 do
if boolean(vicPeek(PMbase+val*8+y) and ($80 shr x))
then setPixel(x0+x,y0+y,color_ram[CMbase+pos])
else setPixel(x0+x,y0+y,reg[$D021+bg])
end;if bit 4 of the colorram is 0, SCM is used (limiting the number of colors to 8). If it is one, a 4 pixel characterpattern is used. For each pixels two bits allow to chose between the colors taken from:
00 : reg[$D021]
01 : reg[$D022]
10 : reg[$D023]
11 : the three lower bits of the colorram.
Remark that we should still put 8 pixels on the screen (4 times 2 equal pixels). This is done by shifting right by (2*((7-x) div 2)). div is an integer divide so the result of this formula is always even. The same value is calculated for x=i and x=i+1, i being an even number.
procedure updateMCCM(pos: word; val: byte);
var x0,y0,x,y:integer;
begin
if boolean(color_ram[CMbase+pos] and $8)
then
begin
x0:=(pos mod 40)*8;
y0:=(pos div 40)*8;
for x:=0 to 7 do
for y := 0 to 7 do
case (vicPeek(PMbase+val*8+y) shr (2*((7-x) div 2))) and 3 of
0 : setPixel(x0+x,y0+y,reg[$D021]);
1 : setPixel(x0+x,y0+y,reg[$D022]);
2 : setPixel(x0+x,y0+y,reg[$D023]);
3 : setPixel(x0+x,y0+y,color_ram[CMbase+pos] and $7)
end
end
else updateSCM (pos,val)
end;This mode is very simular to SCM. The first difference is that it is not the value of the character that is used as an index in the PM, but the position in the VM. The second difference is that the VM is used instead of the CM to generate a color. The lower 4 bits are used as foreground and the higher 4 bits as backgroundcolor.
procedure updateSBMM(pos: word; val: byte);
var x0,y0,x,y:integer;
begin
x0:=(pos mod 40)*8;
y0:=(pos div 40)*8;
for x:=0 to 7 do
for y := 0 to 7 do
if boolean(vicPeek(PMbase+pos*8+y) and ($80 shr x))
then setPixel(x0+x,y0+y,(val and $F0) shr 4)
else setPixel(x0+x,y0+y,val and $0F)
end;If you understand SBMM and MCCM then this mode will be clear to you: it is a bitmapmode where two consecutive bits are used to generate one of four colors taken from:
00 : reg[$D021]
01 : the upper nibble of the byte in the VM
10 : the lower nibble of the byte in the VM
11 : The CM
procedure updateMCBMM(pos: word; val: byte);
var x0,y0,x,y:integer;
begin
x0:=(pos mod 40)*8;
y0:=(pos div 40)*8;
for x:=0 to 7 do
for y := 0 to 7 do
case (vicPeek(PMbase+pos*8+y) shr (2*((7-x) div 2))) and 3 of
0 : setPixel(x0+x,y0+y,reg[$D021]);
1 : setPixel(x0+x,y0+y,(val and $F0) shr 4);
2 : setPixel(x0+x,y0+y,val and $0F);
3 : setPixel(x0+x,y0+y,color_ram[CMbase+pos])
end
end;when a write to the PM area occurs in bitmapmode, the screen should be updated. This can be done very simple but very inefficient by calculating the corresponding position in the VM, and call the update routine for the VM.
A write to the PM in charactermode is less simple: we simply do not know what to update since we do not know if and where that character is already used on the screen. The only solution here is a complete refresh of the screen.
procedure vicPMupdate(pos: word; val: byte);
begin
if bmm then vicVMupdate(pos div 8, vicPeek(VMbase+(pos div 8)))
// else refresh;
end;It will be clear that these refreshes will be too heavy. In fact the pascal program crashes for an unknown reason when trying to execute. So the refresh has been disabled.
a. without this refresh a modified character will be correct if used after the update of it's bitpattern. Characters already on the screen will not be updated. (like it was in chapter 9)
b. A possible solution could be setting a 'refresh needed' flag when refresh is needed, and check this flag at regular times (e.g. once in a second). If set a refresh is executed.
a. We implemented the different screen modes, but there is (much) room for optimization. Especially the PM update is too heavy: each poke to a PM location provokes an update of the complete square (8 bytes). Things should be done the other way around: a PM update should modify one byte, and a VM update should call the PM update 8 times.
b. I didn't test all the modes since I have no example c64 programs for them all. Mail me if you find any bugs (or if you have some simple c64 test program). I included a hires testprogram (hires.prg). Also Simon's Basic runs on the emulator. I included a Simon's Basic hires program (circle.prg).
c. The next logical step should be the implementation of sprites. But this will be very heavy. Probably too heavy for this emulator model. Next chapter will be an evaluation of this project.
See program files: ed64.pas,
memio.pas,vic.pas
and SystemAbstraction.pas
.