ED64 - HOW TO WRITE A COMMODORE 64 EMULATOR
Copyright 2007 by ir. Marc Dendooven


Chapter 6 : Using a graphic interface.

  1. Introduction

    In the previous chapters we created a working c64 emulator. It used the console for its output. Only a limited number of characters can be displayed. In this chapter we will use a graphic interface. The characters will be generated pixel by pixel using the c64 character rom.

  2. Choosing a graphic library.

    There are a lot of usable graphic libraries. Most of them are OS specific. To make abstraction of these libraries we will create an abstract unit that will call the real library for us. When someone chooses to use another graphic library, only the implementation of the abstract unit should be changed. Freepascal comes with the Graph unit. When developing under windows, a library called WinGraph is recommended. It can be found on the internet.

  3. Accessing the character rom.

    In the memio unit, the character rom should be emulated: the char rom can be fond in the file char.rom

var char_rom : array [$D000..$DFFF] of byte;
...
initialization
begin
    ...
    loadrom(char_rom,'char.rom')
end;

    in standard startup mode, the char rom is not visible for the processor, so we shouldn't update the peek instruction (at this moment).

    But the vic chip emulation should be able to access the char rom. The vic 'sees' memory in a different way as the processor: The vic sees the ram memory, and two images of the character rom at $1000 and $9000. We can implement this with a alternative peek function (vicPeek).

Interface
...
function vicPeek(address : word) : byte;

Implementation
...
function vicPeek(address : word) : byte;
begin
    case address of
        $1000..$1FFF : vicPeek := char_rom[address+$C000];
        $9000..$9FFF : vicPeek := char_rom[address+$4000]
    else
            vicPeek := ram[address]
    end
end;

  1. The package 'SystemAbstraction'

    This package makes abstraction of the graphics library. The first thing we need is to open a graphic window or screen. After the emulator has finished, the window/screen should be closed. We should also be able to set pixels in the window to a certain color. At last, most graphic windows contain a close button. We should be able to check if this button has been pressed. This gives the following interface:

interface

type nibble = 0..15;

procedure openScreen;
procedure closeScreen;
procedure setPixel(x,y : word; color: nibble);
function closeScreenRequest : Boolean;

  1. Implementation

    Using the WinGraph package, the implementation could be:

implementation

uses WinGraph, memio;

var gd,gm : integer;

procedure openscreen;
begin
    gd := D8bit;
    gm := m320x200;
    initgraph(gd,gm,'ED64');
    if graphresult <> grOK then
    begin
        writeln('graphic mode not supported');
        halt(1)
    end;
    setPalette(0,black);
    setPalette(1,white);
    setPalette(2,red);
    setPalette(3,cyan);
    setPalette(4,purple);
    setPalette(5,green);
    setPalette(6,blue);
    setPalette(7,yellow);
    setPalette(8,orange);
    setPalette(9,brown);
    setPalette(10,lightred);
    setPalette(11,darkgray);
    setPalette(12,gray);
    setPalette(13,lightgreen);
    setPalette(14,lightblue);
    setPalette(15,lightgray)
end;

procedure closeScreen;
begin
    closeGraph
end;

procedure setPixel(x,y : word; color : nibble);
begin
    putpixel(x,y,color)
end;

function closeScreenRequest : Boolean;
begin
    closeScreenRequest := closeGraphRequest
end;

  1. Using the abstract package.

    In the main program, openScreen should be called at startup. CloseScreen should be called when the program is ended. This is always done by a call to the error procedure, so this is the place to call the closeScreen. Checking the close button should be done at regular times. The only place we could use is the main loop when an interrupt is emulated. (See ED64.pas). The implementation of vicText in the vic package is very simple:

procedure vicText(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($1000+val*8+y) and ($80 shr x))
                then setPixel(x0+x,y0+y,14) //light blue
                else setPixel(x0+x,y0+y,6) //blue
end;

    x0 and y0 are the topleft position of the character to display. x and y present the pixel relative to this position. If we use the image of the character rom at $1000 then $1000+val*8 is points to the image of he character we want to display. Adding y gives the correct byte, and the correct pixel is read by masking with ($80 shr x). This routine can be optimised, but is very simple and will do for now.

  1. Using higher reolutions than 320x200

    If we want to use a higher resolution then 320x200, a c64 pixel should be displayed as a filled rectangle. We could implement setPixel for a certain pixelSize as:

procedure setPixel(x,y : word; color : nibble);
begin
    setFillStyle(solidFill,color);
    bar (x*pixelSize,y*pixelSize,(x+1)*pixelSize-1,(y+1)*pixelSize-1);
end;

    look at SystemAbstraction.pas. A more complex implementation of openScreen accepts a number of resolutions.

  1. Calls to the CRT package

    Although the console has been replaced by a graphic screen, there are a number of console operations left in the program. Start and finish messages and error reporting using writeln will be left unchanged. They will be send to standard output. Remark that the trace can be reactivated since emulator screen output goes immediately to the graphic screen. For reading the keyboard and writing text to the graphical screen there is a package called WinCRT that replaces CRT when using WinGraph. But since we want to be independent of that or other packages, we will access this functionality through the SystemAbstaction unit. Getting a filename for the user will also be implemented in the SystemAbstraction unit. Although this is not necessary, it will permit to use file access widgets. But we will not do this in this manual to keep the code simple. (See SystemAbstraction.pas)

  2. Conclusion

    This chapter provides a c64 emulator using pixel based characters and the complete characterset (for the moment only characterset one). There are a lot of old C64 programs in characterset mode that work already with this version of the emulator. You can find lots of them on the internet. These programs should not try to alter the memory map. In the next chapter we will introduce memory management for our emulator.
    files : SystemAbstraction.pas, vic.pas, memio.pas, ed64.pas