Skip to content

Instantly share code, notes, and snippets.

@unbibium
Created October 24, 2020 05:17
Show Gist options
  • Save unbibium/c58a6e9dd372d88b539f8e5177448b7c to your computer and use it in GitHub Desktop.
Save unbibium/c58a6e9dd372d88b539f8e5177448b7c to your computer and use it in GitHub Desktop.
Cool screen hack for Commodore 64 in BASIC

Although I grew up with the Commodore 64, I didn't do very much graphics programming on it. So when I actually tried to, I was surprised by the quirk of the VIC-II chip that there were blocks of RAM that were essentially invisible to the VIC-II chip because it always mapped the character set ROM there. This ROM only made sense as a character set, but you could also see it in bitmap mode, in sprites, and even in screen memory.

I'd never seen it in screen memory, but today I decided to see if I could. I looked up what I needed to online, typed POKE 53272,69, and there it was, five columns of strange patterns, with 125 PETSCII character glyphs each rendered as eight PETSCII glyphs. You could kind of work out how the top row must map to @ABCD, how the right side or that row kind of showed the same symmetry as the letters B, C, and D did. It was a tenuous resemblance at best, but it was there.

Then I wondered, what if I made a custom character set, where each character was its own screen code, rendered into bits? So character 0 would be blank, character 255 would be filled, character 24 would have the middle two rows filled, etc. Then it would kind of look like a bitmap again, and you'd be able to see the letters all stretched out and sideways.

Well, I managed to bang out a BASIC program to do it. The algorithm went like this:

for each character X from 0 to 255
    for each bit/row Y from 0 to 7
        if the number X has bit Y set,
            set row Y of character X to all ones
        else
            set row Y of character X to all zeroes

If I tried to translate this directly into BASIC, and used TI to benchmark it, it would look like this:

0 ti$="000000"
10 poke 53281,14:print"{lblu}{clr}":poke 53281,6
20 poke 53272,72:sc=8192:ff=255
30 for x=0 to ff
40 for y=0 to 7
50 poke sc,0
55 if x and 2^y then poke sc,ff
60 sc=sc+1
70 next y,x
75 print ti
80 get a$:if a$=""then 80
90 poke 53272,21

This takes about 83 seconds (5033 frames) to finish the loop, despite the obvious optimizations of incrementing the variable SC instead of computing 8192+X*8+Y every time, and of using the constant FF to hold the number 255 so that BASIC doesn't have to convert it to floating point thousands of times.

The version in the source code takes just over 36 seconds, due to understanding which things are fast or slow in CBM BASIC. The caltulation 2^Y uses a very slow and complicated floating point power algorithm. So instead, it stores the bit mask in the variable B and multiplies it by 2 for each row. The floating point routines can multiply by 2 very quickly. And instead of using an IF statement, it uses some clever logic to turn the bitmask into 0 or 255. (X AND B) is either 0 or a positive integer, but (X AND B)>0 is either 0 or -1. And a fast way to turn -1 into any integer up to 32768 is with AND. I used to do things like ((X AND B)>0)*-255, but multiplication is slower than AND.

Change the poke statement in line 20 from 72 to 104 to see the lowercase letters instead.

I was surprised how little code it took, but 36 seconds is still a long time to wait for a render. so I wrote an assembly language program that does the same thing. It compiles to an 80-byte PRG file.

0000000 01 08 0b 08 0a 00 9e 32 30 36 31 00 00 00 a9 00
0000010 85 fb a9 01 a8 25 fb f0 02 a9 ff 8d 00 20 ee 1b
0000020 08 d0 03 ee 1c 08 98 2a 90 ea e6 fb 18 d0 e3 a9
0000030 20 8d 1c 08 a9 48 8d 18 d0 a5 c6 f0 fc c6 c6 a9
0000040 68 8d 18 d0 a5 c6 f0 fc c6 c6 a9 15 8d 18 d0 60
0000050

I wonder if there's any other way to do anything cool by pointing the VIC-II to the character set ROM out of context?

; This is a cool screen hack for the Commodore 64.
; Compiles with dasm
processor 6502
CHAR equ 251
ADDR equ STORE+1
FONT equ $2000
org $0801
.word BASICEND ; end of basic
.word 10 ; line number
.byte $9E ; SYS token
.byte $30 + (BEGIN % 10000)/1000
.byte $30 + (BEGIN % 1000)/100
.byte $30 + (BEGIN % 100)/10
.byte $30 + (BEGIN % 10)
.byte 0
BASICEND: .byte 0,0
MAC WAITKEY
.wait
lda 198
beq .wait
dec 198
ENDM
MAC POKE
lda #{2}
sta {1}
ENDM
BEGIN:
POKE CHAR,0
CHLOOP:
lda #1
BITLOOP:
tay
and CHAR
beq STORE
lda #255
STORE:
sta FONT
inc ADDR
bne II
inc ADDR+1
II: tya
rol
bcc BITLOOP
inc CHAR
clc
bne CHLOOP
POKE ADDR+1,>FONT ;reset loop
POKE 53272,72
WAITKEY
POKE 53272,104
WAITKEY
POKE 53272,21
rts
5 rem display something cool on the c64
10 poke 53281,14:print"{lblu}{clr}":poke 53281,6
20 poke 53272,72:sc=8192:ff=255
30 for x=0 to ff
40 b=1:for y=0 to 7
50 poke sc,(x and b)>0 and ff
60 sc=sc+1:b=b*2
70 next y,x
80 get a$:if a$=""then 80
90 poke 53272,21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment