Graphics programming for 8-bit 6502-based computers in C using CC65

Published on: December 22, 2024

Programming for 8-bit computers can be quite fun, CC65 is a C compiler for 8-bit 6502-based computers. Along with CC65 is a library called TGI or Tiny Graphics Interface. This library makes it easier to draw graphics on the screen, and also allows for use of vector fonts.

I decided to have a go at drawing graphics on the Commodore 64 using this library.

The Commodore 64 has a native maximum resolution of 320x200 with a 16 color palette.

Here's a color table:

#       Name        RGB Value (Hex) CC65 Color Definition   TGI Color Definition
--------------------------------------------------------------------------------
0       Black       #000000         COLOR_BLACK             TGI_COLOR_BLACK
1       White       #FFFFFF         COLOR_WHITE             TGI_COLOR_WHITE
2       Red         #9F4E44         COLOR_RED               TGI_COLOR_RED
3       Cyan        #6ABFC6         COLOR_CYAN              TGI_COLOR_CYAN
4       Purple      #A057A3         COLOR_PURPLE            TGI_COLOR_PURPLE
5       Green       #5CAB5E         COLOR_GREEN             TGI_COLOR_GREEN
6       Blue        #50459B         COLOR_BLUE              TGI_COLOR_BLUE
7       Yellow      #C9D487         COLOR_YELLOW            TGI_COLOR_YELLOW
8       Orange      #A1683C         COLOR_ORANGE            TGI_COLOR_ORANGE
9       Brown       #6D5412         COLOR_BROWN             TGI_COLOR_BROWN
10      Light Red   #CB7E75         COLOR_LIGHTRED          TGI_COLOR_LIGHTRED
11      Dark Gray   #626262         COLOR_GRAY1             TGI_COLOR_GRAY1
12      Mid Gray    #898989         COLOR_GRAY2             TGI_COLOR_GRAY2
13      Light Green #9AE29B         COLOR_LIGHTGREEN        TGI_COLOR_LIGHTGREEN
14      Light Blue  #887ECB         COLOR_LIGHTBLUE         TGI_COLOR_LIGHTBLUE
15      Light Gray  #ADADAD         COLOR_GRAY3             TGI_COLOR_GRAY3

The TGI graphics library (tgi.h) has functions for various things, like drawing lines and shapes. For now I'm interested in the following:

tgi_install(const void *driver); // Install a driver that's been linked at compile time
tgi_init(); // Initialize the driver
tgi_getmaxx(); // Returns the max value on the x-axis of the pixel grid
tgi_getmaxy(); // Returns the max value on the y-axis of the pixel grid
tgi_getaspectratio(); // Returns the aspect ratio of the display
tgi_setpalette(const unsigned char* palette); // Sets the color palette to the values set in an array
tgi_clear(); // Clears the screen
tgi_setdrawpage(unsigned char page); // Sets the page (buffer) which drawing will happen on
tgi_setviewpage(unsigned char page); // Sets the page (buffer) which is currently being viewed
tgi_setcolor(unsigned char color); // Set the color to be used for drawing
tgi_clear(); // Clears the screen
tgi_uninstall(); // Uninstalls and unloads the loaded driver

A function that's part of conio.h used for setting the color of the border:

bordercolor(unsigned char color);

The tgi_stat_stddrv.s driver only supports displaying two colors out of the 16 color palette at once.

The graphics driver needs to be compiled, tgi_stat_stddrv.s from the cc65 source tree libsrc/c64/tgi_stat_stddrv.s can be compiled by running:

$ ca65 tgi_stat_stddrv.s

Now, some headers need to be included in your program. For the Commodore 64 the ca64.h header is needed. The tgi.h header is the header for the graphics library, it also needs to be included. conio.h (console I/O) needs to be included because many things will be used from there. The cc65.h header contains functions specific to cc65.

#include <cc65.h>
#include <conio.h>
#include <tgi.h>
#include <c64.h>

Next, some variables should be defined:

static unsigned max_x;
static unsigned max_y;
static unsigned aspect_ratio;

unsigned char border;

static const unsigned char palette[2] = {
    /* The TGI COLOR definitions for the 2 colors you want to use */
};

Then the driver and library need to be installed and initialized:

tgi_install(tgi_static_stddrv);
tgi_init();

Now it's time to set the maximum value on the x and y axis of the pixel grid, as well as the aspect ratio and border color:

max_x = tgi_getmaxx();
max_y = tgi_getmaxy();
aspect_ratio = tgi_getaspectratio();

border = bordercolor(/* CC65 color definition */);

Now time to set the pallette, draw color, clear the screen, and set the drawpage:

tgi_setpalette(palette);
tgi_setcolor(/* TGI color definition */);
tgi_setdrawpage(0); // Sets it to the 0th draw page

Now we can start drawing pixels! There are some functions for doing this:

tgi_line(int x1, int y1, int x2, int y2); // Draws a line on the set coordinates
tgi_lineto(int x2, int y2); // Draws a line from the current position of the cursor to a specified endpoint
tgi_outtext(const char* s); // Outputs text at the current position of the cursor
tgi_outtextxy(int x, int y, const char* s); // Outputs text at the given coordinates
tgi_setpixel(int x, int y); // Plot a pixel at the given coordinates

There are also functions for drawing shapes, those are more complicated and won't be covered in this post which I'm focusing on the basics.

After drawing some pixels the viewpage needs to be set, which will display what you've drawn on the drawpage: tgi_setviewpage(0);

After you're done with the tgi library, you need to invoke tgi_uninstall(); at the end of your program, and set the border color:

tgi_uninstall();
(void) bordercolor(Border);

I will show some examples below of basic programs I've written to draw some 2d and 3d objects, as well as screenshots:

Pyramid

#include <stdio.h>
#include <cc65.h>
#include <conio.h>
#include <tgi.h>
#include <c64.h>

int main(void) {
    static unsigned max_x;
    static unsigned max_y;
    static unsigned aspect_ratio;

    unsigned char border;

    static const unsigned char palette[2] = {
        TGI_COLOR_BLACK,
        TGI_COLOR_WHITE,
    };

    tgi_install(tgi_static_stddrv);
    tgi_init();

    max_x = tgi_getmaxx();
    max_y = tgi_getmaxy();
    aspect_ratio = tgi_getaspectratio();

    border = bordercolor(COLOR_BLACK);

    tgi_setpalette(palette);
    tgi_setcolor(TGI_COLOR_WHITE);
    tgi_clear();
    tgi_setdrawpage(0);

    tgi_line(100, 150, 150, 50);
    tgi_line(200, 150, 150, 50);
    tgi_line(100, 150, 125, 175);
    tgi_line(125, 175, 200, 150);
    tgi_line(125, 175, 150, 50);
    tgi_line(100, 150, 165, 125);
    tgi_line(165, 125, 200, 150);
    tgi_line(165, 125, 150, 50);

    tgi_setviewpage(0);

    cgetc();

    tgi_uninstall();

    (void) bordercolor(border);
    return 0;
}

Pyramid

Cube

#include <stdio.h>
#include <conio.h>
#include <cc65.h>
#include <tgi.h>
#include <c64.h>

static unsigned max_x;
static unsigned max_y;
static unsigned aspect_ratio;

int main(void) {
    unsigned char border;

    static const unsigned char palette[2] = {
	TGI_COLOR_BLACK,
	TGI_COLOR_WHITE,
    };

    tgi_install(tgi_static_stddrv);
    tgi_init();

    max_x = tgi_getmaxx();
    max_y = tgi_getmaxy();
    aspect_ratio = tgi_getaspectratio();

    border = bordercolor(COLOR_BLACK);

    tgi_setpalette(palette);
    tgi_setcolor(TGI_COLOR_WHITE);
    tgi_clear();
    tgi_setdrawpage(0);

    tgi_line(100, 150, 100, 50);
    tgi_line(100, 150, 200, 150);
    tgi_line(200, 150, 200, 50);
    tgi_line(100, 50, 200, 50);
    tgi_line(100, 50, 125, 25);
    tgi_line(125, 25, 225, 25);
    tgi_line(200, 50, 225, 25);
    tgi_line(225, 25, 225, 125);
    tgi_line(225, 125, 200, 150);
    tgi_line(100, 150, 125, 125);
    tgi_line(125, 125, 125, 25);
    tgi_line(125, 125, 225, 125);

    tgi_setviewpage(0);

    cgetc();

    tgi_uninstall();

    (void) bordercolor(border);
    return 0;
}

Cube

Cool-S

#include <stdio.h>
#include <conio.h>
#include <cc65.h>
#include <tgi.h>
#include <c64.h>

static unsigned max_x;
static unsigned max_y;
static unsigned aspect_ratio;

int main(void) {
    unsigned char border;

    static const unsigned char palette[2] = {
	TGI_COLOR_BLACK,
	TGI_COLOR_WHITE,
    };

    tgi_install(tgi_static_stddrv);
    tgi_init();

    max_x = tgi_getmaxx();
    max_y = tgi_getmaxy();
    aspect_ratio = tgi_getaspectratio();

    border = bordercolor(COLOR_BLACK);

    tgi_setpalette(palette);
    tgi_setcolor(TGI_COLOR_WHITE);
    tgi_clear();
    tgi_setdrawpage(0);

    tgi_line(100, 100, 100, 75);
    tgi_line(100, 150, 100, 125);
    tgi_line(150, 150, 150, 125);
    tgi_line(150, 100, 150, 75);
    tgi_line(200, 150, 200, 125);
    tgi_line(200, 100, 200, 75);

    tgi_line(100, 100, 150, 125);
    tgi_line(150, 100, 200, 125);

    tgi_line(100, 150, 150, 200);
    tgi_line(150, 200, 200, 150);
    tgi_line(100, 75, 150, 25);
    tgi_line(200, 75, 150, 25);

    tgi_line(100, 125, 125, 112);
    tgi_line(200, 100, 175, 112);

    tgi_setviewpage(0);

    cgetc();

    tgi_uninstall();

    (void) bordercolor(border);
    return 0;
}

Cool-S