Area536 :: Rebooted

Oct 30, 2023 - 8 minute read - C64 gamedev

Larry 64 core - drawing the screen

As mentioned earlier, I’m not porting AGI but writing Larry again from scratch to cater to the specific strengths and weaknesses of the C64 as a platform. I’m doing this in 6502 Assembler as that’s the only way for me to gain the required speed and control over the hardware that is needed to pull all the tricks I need. In this post I’ll provide more detail on how the screen actually gets drawn.

The Commodore 64 contains a VIC-II chip to drive the display. This is one of the most famous and well understood video controller chips in the history of personal computing. As users of modern systems we should recall that the C64 was originally always paired with a cathode ray tube (CRT) based monitor or a similarly configured TV set. In order to understand how the VIC-II works, and what tricks we’re pulling, you need to know a bit about CRT’s.

A CRT continuously generates a very strong but very thin beam at the back of the tube. This beam gets strobed at dizzying speed across the screen, starting at the top left corner and moving to the right very fast, then down a line and repeat until the lower right corner is reached and the process starts again. Along the way, the beam strikes a phosphorous screen at the front of the display. The particles light up for a short while under the influence of the beam. They dim again very quickly, though, so the beam needs to race across the screen dozens of times per second to keep up the illusion of a stable image in our sluggish human eyeball/brain combo.

Video modes of the VIC-II

What does this have to do with Larry? Well, the Commodore’s VIC-II can only do so many things at the same time. It can either show a raster of 40x25 characters of text. Very useful when writing code or playing ancient text adventure games. Clever programmers use this mode to draw pictures using what are, effectively, characters of text.

Sometimes they use the characters Commodore provides, called PETSCII, but more often they adjust the VIC-II so that it gets its information from RAM where the programmer can adjust what every individual character looks like. By doing this and cleverly combining characters, which are just blocks of 8x8 pixels, you can draw very elaborate graphics very quickly: most of the heavy lifting is done by the VIC-II, which is pretty good at reading character definitions from memory and drawing them onto the screen very quickly. Most shoot-em-up scrolling games have background graphics that are drawn in this way.

Since I’m porting an existing game that had graphics that were drawn onto a 16-colour bitmapped area with no regard for the 8x8 blocks of pixels that Commodore characters are composed of, the original Larry graphics don’t carry over very well to this mode at all.

Instead, I’m using the VIC-II’s mulicolour bitmap mode. The C64 can also display 16 colours in a resolution identical to the one EGA Larry was originally coded in: 160x200 pixels. The colours of the C64 are a bit different from EGA’s pallette, but that can be lived with. More interesting is the way the VIC-II draws bitmaps compared to EGA, but that’s a topic for a future post.

For now, it’s important to know that the VIC-II in bitmapped mode can not display text of any kind whatsoever. Sure, you could “draw” text as part of your bitmap but doing that is extremely slow and cumbersome to deal with. The resolution would also be very coarse in the multicoloured mode we need for Larry. Regular text is drawn on a 320x200 pixel matrix, bt the bitmapped screen can only do 160x200 so we get characters that are stretched to double their size horizontally. We could only display 20 of those before filling an entire line.

Raster interrupts

We’re actually tricking the computer into switching display modes at very precise spots on the screen. This works because the VIC-II chip exposes to the CPU exactly which line of the screen the CRT’s scanning beam is drawing. If we connect these two, we can interrupt whatever the CPU is doing just in time to tell VIC-II to switch from text mode to bitmapped mode and back, and the resume whatever it was doing before that. This trick is called a raster interrupt and it provides a way for us to split the screen horizontally into multiple parts.

In the case of Larry we have a top bar that holds the game’s title and score against a white background. This is done in the C64’s standard text mode. Just before the VIC-II is about to finish drawing the last white pixel of this top bar, we need to interrupt the CPU and gear it up to tell VIC-II to change modes ASAP. If we time this just right, this switching of video modes happens while the CRT’s scanning beam is busy drawing the C64’s fat screen border somewhere on the left or right of the screen. The result will be a sharp transition between the top bar and the multicoloured bitmap below it, as if the screen didn’t change modes at all.

If we do not get this right, there will be visible shimmering artifacts between the top bar and the bitmap below it. Getting this just right is something of a dark art that demo coders have honed to perfection. Me? Not so much.

Screen layout in text input mode.

Below is the code that sets up the raster interrupt for Larry.

/* Setup the system for interrupts from VIC */
core_setup_raster_irq:
    lda #$C0                                // Pick some random line somewhere mid-screen
csri_wait:
    cmp $D012
    bne csri_wait                           // Wait for mid-screen to avoid weird crashes
    sei                                     // Disable interrupts
    lda #%01111111
    sta $DC0D                               // Disable interrupts from CIA-1

    and $D011                               // Clear bit on VIC's raster register
    sta $D011

    lda $DC0D                               // Ack pending interrupts from CIA-1
    lda $DD0D                               // Ack pending interrupts from CIA-2

    lda #$3A                                // Scanline just below the top bar
    sta $D012
    lda #<core_irq_topline                  // IRQ handler low byte
    sta $0314
    lda #>core_irq_topline                  // IRQ handler high byte
    sta $0315

    lda #%00000001
    sta $D01A                               // Enable IRQ signals from VIC

    cli                                     // Restore interrupts
    rts

What this does, is something I learned through trial and error. Apparently it’s not a good idea to just kick this process off whenever you feel like it. The game would crash with a garbled screen. So what the first line does is pick some more-or-less random line somewhere mid-screen, $C0 specifically, to prevent these crashes from happening. The number is loaded into the accumulator and we compare location $D012 (the VIC-II’s scanline register) to hit this number before proceeding. This is the only time in the game that we hold code execution while waiting for a specific scanline to be hit.

Once line $C0 is hit, we disable all other interrupts with sei and perform the needed actions that disable the timing interrupts that the CPU receives from the CIA-1 chip by default. We acknowledge any pending interrupts from the CIA chips so they’ll be gone when we exit this routine.

Scanline $3A is the scanline that gave me the most stable result for triggering the switch to bitmapped mode. The actual switching code is not in this fragment. We’re loading the address of core_irq_topline into the vector at $0314 and $0315, essentially constructing what languages like C call a pointer.

Our last step is to set $D01A to 1 to have VIC-II send interrupts to the CPU. The CLI instruction re-enables interrupts system-wide and our newly installed handler will run from now on out. The code that actually does handle the interrupt lives in a different routine:

/* This is where the scene's background starts */
core_irq_topline:
    jsr core_bitmap_mode
    lda #$00                                // Screen background is black
    sta $D021
    jsr scene_io
    lda $0B                                 // Load current game mode
    cmp #$00                                // Game mode is default
    bne cit_dialog                          // Jump to test for dialogue mode if not in default
cit_default:
    lda #$F1                                // Next interrupt just above the screen editor
    sta $D012
    lda #<core_irq_editor
    sta $0314
    lda #>core_irq_editor
    sta $0315
    jmp cit_wrapup
cit_dialog:
    lda $0B
    cmp #$01                                // Game mode $01 is dialogue mode
    bne cit_system                          // Jump to system mode if this isn't it
    lda #$D2                                // Next interrupt just above the dialogue window
    sta $D012
    lda #<core_irq_dialog
    sta $0314
    lda #>core_irq_dialog
    sta $0315
    jmp cit_wrapup
cit_system:
    lda $0B
    cmp #$02
    bne cit_wrapup
    nop                                     // System mode isn't present yet.
cit_wrapup:
    lda #$FF                                // Ack the interrupt
    sta $D019
    pla                                     // Recover the stack
    tay
    pla
    tax
    pla
    rti                                     // Back to Kansas

This is core_irq_topline which is where the CPU gets sent when VIC-II fires just below the game’s title bar. The routine immediately sets the screen’s background to black (value $00 in $D021) before doing anything else. The next step is to figure out what game-mode we’re in and jump to the appropriate label to act as needed. The difference mainly being the installation of the next interrupt handler at another scanline further down the screen to either display the screen editor, a dialogue window or the as-yet unimplemented system-mode. The cit_wrapup part acknowledges that the interrupt took place and recovers the stack so that we can return the CPU to what it was doing as quickly as possible.