Einstein 256 Primer cover: Graphics 7's 256 colours on the Einstein 256's screen
← Back to Modules
module
13

Flicker-Free

Introduction

On the TC-01 everything that moved was a sprite. The video chip drew sprites over the screen for you, so a moving ship never left a mess, and S22's animation was a matter of changing one byte. Anything drawn into the screen itself had to be rubbed out and drawn again, while the picture was being shown.

The 256's bitmap modes invite you to draw much more than 32 sprites' worth: whole ships, explosions, scenery, in sixteen colours at every dot. Draw them on the screen the player is watching, and the player sees them half-drawn. This section draws them where the player can't see.

Two Pages

In S5 and S11 you saw that what the CPU writes (register 14) and what the screen shows (register 2) are separate. In Graphics 4 there are four pages. Take two:

Shown by R#2 =Written with R#14 =
page A, 10000h5Fh4, 5
page B, 18000h7Fh6, 7

While page A is on screen you draw the next picture on page B. When it is finished you write 7Fh to register 2, and page B appears complete. Then you draw on page A, and so on. The player only ever sees finished pictures.

Both pages are above 10000h, so MOS's screen is safe, and the sprite table from S11 sits in page B's unseen lines at 1FA00h.

When to Flip

A change to register 2 takes effect from the next line the chip draws. Write it while the picture is half shown and the top of the screen comes from one page and the bottom from the other, for one frame.

So flip from the frame interrupt. The main loop finishes a page, sets a flag and waits. onframe (S7) sees the flag, writes register 2 and clears the flag. The switch lands between two pictures, never in the middle of one:

onframe:
        LD A, (flip)
        OR A
        RET Z           ; nothing new: keep showing the same page
        LD A, (draw)
        LD (shown), A
        OR A
        LD A, 5Fh       ; page A
        JR Z, show
        LD A, 7Fh       ; page B
show:   LD B, 2
        CALL setreg     ; R#2: the finished page
        XOR A
        LD (flip), A    ; done: the main loop may carry on
        RET

No MOS call in sight, as S7 requires.

The Two Rules

Page flipping has two rules, and breaking either one brings back the mess you were avoiding.

Never draw on a page until the flip that hid it has happened. After asking for a flip, the main loop waits for onframe to clear the flag. Until then the page it just drew is not yet on screen, and the other one still is. Start drawing at once and you are drawing on the page the player can see.

Rub out where this page last had the object. Each page has its own copy of the picture. When you come back to page A, it holds the ship where you drew it on page A, two frames ago, not where page B has it now. So keep one old position for each page:

pos:    DEFB 0,0, 0,0   ; where each page has the ship: x, y for A, then B

Rub out at this page's entry, move, draw, and store the new position in the same entry. Use one position for both and each page rubs out the wrong place: the ship leaves a trail.

What It Costs

  • Video memory: two Graphics 4 pages, 64K of the 128K. That leaves the pages at 00000h (MOS's) and 08000h. In Graphics 6 or 7 the two pages are the whole 128K (S12).
  • Drawing: every object is drawn twice over its life on screen, once on each page, and each frame's work is a rub-out and a redraw. The ship here is 8 bytes by 16 lines: 128 bytes to rub out and 128 to draw, 256 bytes a frame.
  • The background: both pages need it. The listing draws the sky and ground on each page at the start. A rub-out restores the background, so over a plain sky it is a fill; over scenery it has to copy the scenery back (S17 does that with the blitter).
  • Time: the picture changes once for every pass of the main loop. If a pass takes longer than a frame, the old page simply stays up a frame longer, still whole. The game slows; it never tears.

The Code

A ship drawn into the bitmap, bouncing round a night sky above the ground, two dots across and one line down every frame. Its picture is held one hex digit per dot, so you can see it in the source: F white, 7 cyan, E grey, 8 and 9 reds, 1 the black sky. The rub-out is the same rectangle of sky. After 250 frames, MOS gets its screen back.

; S13: flicker-free movement in Graphics 4 by page flipping.
;
; Two Graphics 4 pages, both above MOS's screen:
;   page A at 10000h: shown by R#2 = 5Fh, drawn with R#14 = 4 and 5
;   page B at 18000h: shown by R#2 = 7Fh, drawn with R#14 = 6 and 7
; The player always sees one page while the program draws on the other.
; Each pass of the main loop, on the hidden page:
;   1. rub the ship out where THIS page last had it
;   2. move the ship
;   3. draw it at the new place, and remember the place for this page
;   4. ask for a flip, and wait until the frame interrupt has done it
; The frame interrupt (frame.asm, S7) shows the finished page with R#2.
; After 250 frames (5 seconds) MOS gets its screen back.

        ORG 256

        DI
        ; --- Graphics 4 first, so writes run on past each 16K
        LD A, 06h
        LD B, 0
        CALL setreg     ; R#0 = 06h: Graphics 4

        ; --- the sprites' own attribute table at 1FA00h: only Y = 216
        LD A, 7
        LD B, 14
        CALL setreg
        XOR A
        OUT (9), A
        LD A, 3Ah + 40h
        OUT (9), A
        LD A, 216
        OUT (8), A
        LD A, 0F7h
        LD B, 5
        CALL setreg     ; R#5 = F7h
        LD A, 03h
        LD B, 11
        CALL setreg     ; R#11 = 03h

        ; --- the same background on both pages: black, with ground
        LD A, 4         ; page A
        CALL scene
        LD A, 6         ; page B
        CALL scene

        LD A, 5Fh
        LD B, 2
        CALL setreg     ; R#2 = 5Fh: show page A

        CALL irqon
        LD A, 0E0h
        LD B, 1
        CALL setreg     ; display on, frame interrupt on
        EI

; --- the main loop
main:   LD A, (shown)
        XOR 1
        LD (draw), A    ; draw on the page that is not shown

        ; 1. rub out the ship where this page last had it
        CALL oldpos     ; HL -> this page's saved position (x, y)
        LD E, (HL)
        INC HL
        LD D, (HL)
        LD HL, blank
        CALL put

        ; 2. move it
        CALL move       ; E = new x (bytes), D = new y (lines)

        ; 3. draw it there, and remember where, for this page
        PUSH DE
        CALL oldpos
        POP DE
        LD (HL), E
        INC HL
        LD (HL), D
        LD HL, ship
        CALL put

        ; 4. ask for the flip, and wait for it
        LD A, 1
        LD (flip), A
wait:   LD A, (flip)
        OR A
        JR NZ, wait     ; never touch a page until the flip has hidden it

        LD HL, (TICKS)
        LD A, H
        OR A
        JR NZ, done
        LD A, L
        CP 250
        JR C, main

done:   LD A, 0C0h
        CALL irqoff     ; frame interrupt off, MOS's table back
        CALL tomos      ; MOS's screen back
        RET

; --- onframe: called every frame. If a page is finished, show it.
onframe:
        LD A, (flip)
        OR A
        RET Z           ; nothing new: keep showing the same page
        LD A, (draw)
        LD (shown), A
        OR A
        LD A, 5Fh       ; page A
        JR Z, show
        LD A, 7Fh       ; page B
show:   LD B, 2
        CALL setreg     ; R#2: the finished page
        XOR A
        LD (flip), A    ; done: the main loop may carry on
        RET

; --- oldpos: HL -> the saved (x, y) for page (draw)
oldpos: LD A, (draw)
        ADD A, A
        LD E, A
        LD D, 0
        LD HL, pos
        ADD HL, DE
        RET

; --- move: step x by dx (one byte = 2 dots) and y by dy, bouncing.
;     Returns E = x, D = y.
move:   LD A, (x)
        LD B, A
        LD A, (dx)
        ADD A, B
        LD (x), A
        OR A
        JR Z, mx
        CP 120          ; 128 bytes across, less the ship's 8
        JR NZ, my
mx:     LD A, (dx)
        NEG
        LD (dx), A
my:     LD A, (y)
        LD B, A
        LD A, (dy)
        ADD A, B
        LD (y), A
        OR A
        JR Z, mz
        CP 160          ; the ship's 16 lines end at the ground (176)
        JR NZ, mdone
mz:     LD A, (dy)
        NEG
        LD (dy), A
mdone:  LD A, (x)
        LD E, A
        LD A, (y)
        LD D, A
        RET

; --- put: copy 16 lines of 8 bytes from (HL) to byte x = E, line y = D
;     of page (draw)
put:    LD A, (draw)
        ADD A, A
        ADD A, 4        ; R#14 base: 4 for page A, 6 for page B
        LD C, A
        LD B, 16        ; 16 lines
put1:   PUSH BC
        CALL setw
        LD B, 8         ; 8 bytes = 16 dots
put2:   LD A, (HL)
        OUT (8), A
        INC HL
        DJNZ put2
        INC D           ; next line
        POP BC
        DJNZ put1
        RET

; --- setw: write address for byte E of line D, page base C.
;     y*128 + x = (y/2)*256 + (y's bit 0)*128 + x
setw:   PUSH HL
        LD A, D
        SRL A
        LD H, A         ; H = y/2, carry = y's bit 0
        LD A, 0
        RRA
        OR E
        LD L, A         ; HL = y*128 + x
        DI              ; no interrupt between the bytes of an address
        LD A, H
        RLCA
        RLCA
        AND 3
        ADD A, C        ; which 16K of the page
        OUT (9), A
        LD A, 80h + 14
        OUT (9), A      ; R#14
        LD A, L
        OUT (9), A
        LD A, H
        AND 3Fh
        OR 40h
        OUT (9), A      ; address, for writing
        EI
        POP HL
        RET

; --- scene: clear the page whose R#14 base is A to black (192 lines),
;     then lay the ground on lines 176-191
scene:  LD B, 14
        CALL setreg
        XOR A
        OUT (9), A
        LD A, 40h
        OUT (9), A      ; the page's first byte, for writing
        LD D, 176       ; 176 black lines: 176 x 128 bytes
        LD A, 11h
        CALL rows
        LD D, 1         ; one line of grass
        LD A, 22h
        CALL rows
        LD D, 15        ; and earth to line 191
        LD A, 0CCh
        CALL rows
        RET

; --- rows: D lines of byte A, carrying on from the current address
rows:   LD B, 128
rw1:    OUT (8), A
        DJNZ rw1
        DEC D
        JR NZ, rows
        RET

; --- the ship, one hex digit a dot: colour 1 is the black sky
ship:   DEFB 011h,011h,011h,01Fh,0F1h,011h,011h,011h
        DEFB 011h,011h,011h,0FFh,0FFh,011h,011h,011h
        DEFB 011h,011h,011h,0F7h,07Fh,011h,011h,011h
        DEFB 011h,011h,01Fh,0F7h,07Fh,0F1h,011h,011h
        DEFB 011h,011h,01Fh,077h,077h,0F1h,011h,011h
        DEFB 011h,011h,0FFh,077h,077h,0FFh,011h,011h
        DEFB 011h,011h,0FEh,0EEh,0EEh,0EFh,011h,011h
        DEFB 011h,01Fh,0FEh,0EEh,0EEh,0EFh,0F1h,011h
        DEFB 011h,0FFh,0FFh,0EEh,0EEh,0FFh,0FFh,011h
        DEFB 01Fh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0F1h
        DEFB 0FFh,0F1h,01Fh,0FFh,0FFh,0F1h,01Fh,0FFh
        DEFB 0F1h,011h,01Fh,0FFh,0FFh,0F1h,011h,01Fh
        DEFB 011h,011h,01Fh,088h,088h,0F1h,011h,011h
        DEFB 011h,011h,011h,089h,098h,011h,011h,011h
        DEFB 011h,011h,011h,019h,091h,011h,011h,011h
        DEFB 011h,011h,011h,011h,011h,011h,011h,011h

; --- the rub-out: the same 16 x 8 bytes, all sky
blank:  DEFS 128, 11h

shown:  DEFB 0          ; the page on screen: 0 = A, 1 = B
draw:   DEFB 0          ; the page being drawn
flip:   DEFB 0          ; 1 = show the drawn page at the next frame
pos:    DEFB 0,0, 0,0   ; where each page has the ship: x, y for A, then B
x:      DEFB 0
y:      DEFB 0
dx:     DEFB 1
dy:     DEFB 1

        INCLUDE "frame.asm"

Build and run

Save it as s13-flicker-free.asm next to frame.asm. Build and run it as in S2.

What you should see

A black sky over a strip of green ground, and a small white ship with a cyan canopy and a red flame. It flies diagonally down and to the right, two dots across and one line down every frame, and bounces off the edges. In every single frame the ship is whole: never half-drawn, never doubled, and it leaves nothing behind.

A white ship with a cyan canopy and red flame over a black sky and green ground

After five seconds MOS's screen comes back, cleared, with the register display and the prompt.

Change One Thing

  • In oldpos, replace LD A, (draw) with XOR A, so both pages share page A's old position. Before you run it: what will be left behind, and how many trails will there be?
  • In the main loop, replace XOR 1 with NOP, so the program always draws on the page being shown. What do you expect to see? Which of the two rules does it break?
  • Change dx to 2. How far does the ship move each frame, and does the rub-out still work? Why?

When It Goes Wrong

SymptomCause
Two dotted trails follow the ship across the screenBoth pages rubbed out at one shared old position. Each page needs its own

Two dotted trails left behind the ship

Summary

You can animate anything drawn into a bitmap without flicker: draw on the hidden page, flip with register 2 from the frame interrupt, wait for the flip before touching a page again, and rub out where that page last had the object. It costs a second page and a redraw on each.

Next

S14, Scrolling the Screen: register 23 moves the whole picture, and a game only has to draw the lines that scroll into view.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.