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

The Blitter: Copying

Introduction

S13 drew a ship into the bitmap and rubbed it out again each frame. Over a plain sky that is a fill. Over scenery it isn't: whatever the ship covered has to come back exactly as it was, every frame, and doing that with the CPU means reading and writing every byte under it.

The command engine copies. One command puts a rectangle of background back from a clean copy kept out of sight; another draws a shape over it with its colour-0 dots left transparent. Together they make a software sprite: as many as you like, as big as you like, in all sixteen colours, over any picture. The TC-01's chip could draw nothing over the screen but its own sprites.

The Copy Commands

All three take a source (SX, SY), a destination (DX, DY) and a size (NX, NY), in S16's registers, with S16's lines numbered on through the pages.

HMMM (CMR = D0h) copies a rectangle byte for byte. In Graphics 4 a byte is two dots, so it ignores the lowest bit of SX, DX and NX: copies start and end on even dots.

YMMM (CMR = E0h) copies from DX to the right-hand edge of the screen, NY lines from line SY to line DY. There is no SX and no NX: the strip is always as wide as the space from DX to the edge. With DX = 0 that is whole lines, which makes it the simplest way to say "copy these lines there".

LMMM (CMR = 90h + a logical operation) copies dot by dot through S16's logical operations. With TIMP (98h), dots of colour 0 in the source leave the destination as it was: the shape is drawn, its background isn't. With IMP (90h) the 0s are copied too.

HMMM and YMMM move bytes, LMMM moves dots. Use the byte copies where the rectangle starts on an even dot and needs no logical operation, and LMMM where it doesn't.

Where to Keep the Graphics

A copy needs a source that the player never sees. You have three places for one, from S5:

  • Below the picture. A Graphics 4 page is 256 lines and shows 192. The 64 lines underneath, 8K, are out of sight.
  • Another page. In Graphics 4, the pages you aren't showing are 32K each.
  • The expansion RAM. 64K that is never on screen. Set MXS (R#45 bit 4, ARG in the command) and the command reads its source from the expansion instead of main video memory. HMMM and LMMM both do. The CPU fills it with R#45 bit 6, as S5 showed; the command engine draws into it with MXD (bit 5).

The listing keeps its clean background in page 2 and the ball's picture in the expansion.

Getting Graphics Into Video Memory

Before the command engine can copy a graphic, it has to be in video memory. Put it there the way S5 did: set the address and write the bytes to port 8. Do it once, at the start, into a page you never show. From then on, every copy is the command engine's work, not your program's.

Building a Background by Doubling

Copies can build a picture as well as move one. Draw one 16 x 16 tile with three HMMV fills. Then copy it to its right: 32 wide. Copy those 32 to their right: 64. Four HMMM copies and the row is 256 dots wide. Then YMMM, from DX = 0, doubles it downwards the same way: 16 lines, 32, 64, 128, 256. Eleven commands fill a whole 32K page.

A Software Sprite

Three pages of Graphics 4 do the job:

PageLines (y)Holds
1, 08000h256-511the screen (R#2 = 3Fh)
2, 10000h512-767a clean copy of the background, never shown
expansion0-the ball's picture, 16 x 16

Each frame, for each sprite:

  1. Restore. HMMM 16 x 16 from the clean copy, (x, 512 + y), to the screen, (x, 256 + y). Where the ball was is background again.
  2. Move it.
  3. Draw. LMMM with TIMP, the picture from the expansion (MXS set) to the screen at (x, 256 + y). The ball's colour-0 dots leave the background showing through.

With two sprites or more, restore them all, then draw them all. Restore one, draw it, then restore the next, and the second restore can copy clean background over part of the first sprite where they overlap.

Keep x even, because the restore is a byte copy. At an odd x HMMM still copies from the even dot below it, and whenever the ball moves left a column of the old ball's dots is left behind. Either move in steps of two, or restore with LMMM (IMP) instead, which works at any dot.

What it costs: the clean copy is a page of video memory, and a sprite is two commands a frame. The CPU only writes 30 bytes to set them off and polls CE; it never touches the picture's bytes.

The Code: Two Balls

; S17: software sprites made by the command engine, in Graphics 4.
; Two balls bounce over a checkerboard. Each frame, for each ball, HMMM
; copies the background back from a clean copy, and LMMM with TIMP
; draws the ball at its new place with colour 0 left transparent.
; Runs for 500 frames (10 seconds).
;
; Where everything is, as the command engine counts lines (256 a page):
;   page 1, lines 256-511 (VRAM 8000h, R#2 = 3Fh): the screen
;   page 2, lines 512-767 (10000h): the clean background, never shown
;   the expansion RAM, at (0,0): the ball's picture
; MOS's own screen, in the first 16K, is left alone.

        ORG 256

        DI
        ; --- Graphics 4, showing page 1
        LD A, 06h
        LD B, 0
        CALL setreg
        LD A, 3Fh
        LD B, 2
        CALL setreg

        ; --- sprites out of the way (attribute table at 1FA00h: 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
        LD A, 03h
        LD B, 11
        CALL setreg
        EI

        ; --- the background, built in page 2 by the command engine
        LD HL, tile1    ; a 16x16 tile: colour 4 ...
        CALL send
        LD HL, tile2    ; ... with two squares of colour 5
        CALL send
        LD HL, tile3
        CALL send
        LD HL, 16       ; double it across: 16, 32, 64, 128 -> 256 wide
across: LD (wide+4), HL         ; DX = the width so far
        LD (wide+8), HL         ; NX = the same
        PUSH HL
        LD HL, wide
        CALL send
        POP HL
        ADD HL, HL
        BIT 0, H        ; until 256
        JR Z, across
        LD HL, 16       ; then down with YMMM: 16, 32, 64, 128 -> 256 lines
down:   PUSH HL
        LD DE, 512
        ADD HL, DE
        LD (deep+6), HL         ; DY = 512 + the depth so far
        POP HL
        LD (deep+10), HL        ; NY = the same
        PUSH HL
        LD HL, deep
        CALL send
        POP HL
        ADD HL, HL
        BIT 0, H
        JR Z, down

        ; --- the screen: the whole background copied to page 1
        LD HL, copybg
        CALL send

        ; --- the ball's picture into the expansion RAM
        CALL upload

        ; --- the frame interrupt (S7)
        CALL irqon
        LD A, 0E0h
        LD B, 1
        CALL setreg
        EI

; --- the game loop: every frame, both balls
main:   CALL waitf
        LD IX, ball1    ; 1. the background back under both balls
        CALL restore
        LD IX, ball2
        CALL restore
        LD IX, ball1    ; 2. move them
        CALL move
        LD IX, ball2
        CALL move
        LD IX, ball1    ; 3. draw them at their new places
        CALL draw
        LD IX, ball2
        CALL draw
        LD HL, (TICKS)
        LD DE, 500
        OR A
        SBC HL, DE
        JR C, main

        ; --- put the machine back for MOS
        LD A, 0C0h
        CALL irqoff
        CALL tomos
        RET

onframe: RET            ; nothing to do in the interrupt

; --- restore: HMMM 16x16 from the clean copy, at the ball IX's place
restore:
        LD A, (IX+0)    ; x
        LD (rest+0), A          ; SX
        LD (rest+4), A          ; DX
        LD A, (IX+1)    ; y
        LD (rest+2), A          ; SY = 512 + y
        LD (rest+6), A          ; DY = 256 + y
        LD HL, rest
        JP send

; --- draw: LMMM with TIMP, the ball's picture to the ball IX's place
draw:   LD A, (IX+0)
        LD (put+4), A           ; DX
        LD A, (IX+1)
        LD (put+6), A           ; DY = 256 + y
        LD HL, put
        JP send

; --- move: the ball IX steps (IX+2) across and (IX+3) down, bouncing
move:   LD A, (IX+0)
        ADD A, (IX+2)
        LD (IX+0), A
        OR A            ; x = 0 or 240: turn round
        JR Z, mv1
        CP 240
        JR NZ, mv2
mv1:    LD A, (IX+2)
        NEG
        LD (IX+2), A
mv2:    LD A, (IX+1)
        ADD A, (IX+3)
        LD (IX+1), A
        OR A            ; y = 0 or 176: turn round
        JR Z, mv3
        CP 176
        RET NZ
mv3:    LD A, (IX+3)
        NEG
        LD (IX+3), A
        RET

;       x, y, step across, step down - x and every step across even
ball1:  DEFB 0, 0, 2, 1
ball2:  DEFB 200, 100, -2, -1

; --- upload: the ball's picture into the expansion RAM at (0,0), by the
;     CPU: R#45 bit 6 switches the CPU's VRAM access to the expansion.
;     16 lines of 8 bytes, each line 128 bytes on from the last (S11).
upload: DI
        LD A, 40h
        LD B, 45
        CALL setreg     ; R#45 = 40h: the CPU reaches the expansion
        XOR A
        LD B, 14
        CALL setreg     ; its first 16K
        LD HL, 0        ; HL = the line's offset
        LD DE, ballpic
        LD C, 16        ; 16 lines
up1:    LD A, L
        OUT (9), A
        LD A, H
        OR 40h
        OUT (9), A      ; the line's address, for writing
        LD B, 8
up2:    LD A, (DE)
        OUT (8), A
        INC DE
        DJNZ up2
        PUSH DE
        LD DE, 128
        ADD HL, DE      ; the next line
        POP DE
        DEC C
        JR NZ, up1
        XOR A
        LD B, 45
        CALL setreg     ; R#45 = 0: main VRAM again
        EI
        RET

; --- send: the 15 bytes at HL to R#32-R#46 through port 0Bh, with
;     R#17 auto-incrementing; writing R#46 starts the command. Then wait.
send:   DI
        LD A, 32
        LD B, 17
        CALL setreg
        LD BC, 15*256 + 0Bh
        OTIR
        EI
cwait:  DI              ; CE, one read at a time, R#15 back to 0 each time
        LD A, 2
        LD B, 15
        CALL setreg
        IN A, (9)
        LD C, A
        XOR A
        LD B, 15
        CALL setreg
        EI
        BIT 0, C
        JR NZ, cwait
        RET

; Command blocks: SX, SY, DX, DY, NX, NY (words), then CLR, ARG, command.
;               SX  SY   DX  DY       NX   NY
tile1:  DEFW    0,  0,   0,  512,     16,  16
        DEFB    44h, 0, 0C0h            ; HMMV colour 4
tile2:  DEFW    0,  0,   8,  512,     8,   8
        DEFB    55h, 0, 0C0h            ; HMMV colour 5
tile3:  DEFW    0,  0,   0,  520,     8,   8
        DEFB    55h, 0, 0C0h            ; HMMV colour 5
wide:   DEFW    0,  512, 0,  512,     0,   16
        DEFB    0,  0,  0D0h            ; HMMM (DX, NX set above)
deep:   DEFW    0,  512, 0,  0,       0,   0
        DEFB    0,  0,  0E0h            ; YMMM from DX = 0: whole lines
copybg: DEFW    0,  512, 0,  256,     256, 192
        DEFB    0,  0,  0D0h            ; HMMM: page 2 to page 1
rest:   DEFW    0,  512, 0,  256,     16,  16
        DEFB    0,  0,  0D0h            ; HMMM (x, y set above)
put:    DEFW    0,  0,   0,  256,     16,  16
        DEFB    0,  10h, 98h            ; LMMM, TIMP, source in the expansion (MXS)

; the ball, one row a line, one hex digit a dot: A (colour 10) inside,
; F (15) round the edge, 0 (transparent) outside
ballpic:
        DEFB 000h,000h,000h,00Fh,0F0h,000h,000h,000h
        DEFB 000h,000h,0FFh,0FFh,0FFh,0FFh,000h,000h
        DEFB 000h,00Fh,0FFh,0AAh,0AAh,0FFh,0F0h,000h
        DEFB 000h,0FFh,0AAh,0AAh,0AAh,0AAh,0FFh,000h
        DEFB 00Fh,0FAh,0AAh,0AAh,0AAh,0AAh,0AFh,0F0h
        DEFB 00Fh,0FAh,0AAh,0AAh,0AAh,0AAh,0AFh,0F0h
        DEFB 00Fh,0AAh,0AAh,0AAh,0AAh,0AAh,0AAh,0F0h
        DEFB 0FFh,0AAh,0AAh,0AAh,0AAh,0AAh,0AAh,0FFh
        DEFB 0FFh,0AAh,0AAh,0AAh,0AAh,0AAh,0AAh,0FFh
        DEFB 00Fh,0AAh,0AAh,0AAh,0AAh,0AAh,0AAh,0F0h
        DEFB 00Fh,0FAh,0AAh,0AAh,0AAh,0AAh,0AFh,0F0h
        DEFB 00Fh,0FAh,0AAh,0AAh,0AAh,0AAh,0AFh,0F0h
        DEFB 000h,0FFh,0AAh,0AAh,0AAh,0AAh,0FFh,000h
        DEFB 000h,00Fh,0FFh,0AAh,0AAh,0FFh,0F0h,000h
        DEFB 000h,000h,0FFh,0FFh,0FFh,0FFh,000h,000h
        DEFB 000h,000h,000h,00Fh,0F0h,000h,000h,000h

        INCLUDE "frame.asm"

Build and run

Save it as s17-copying.asm next to frame.asm. Build and run it as in S2.

What you should see

A checkerboard of blue and light blue 8 x 8 squares filling the screen, and two yellow balls with white rims bouncing round it, two dots across and one line down or up every frame. The squares show through the corners of each ball, and behind them the checkerboard is perfect: no trail, no smudge. After ten seconds, MOS's screen and the prompt.

Two yellow balls with white rims over a blue and light blue checkerboard

Change One Thing

  • In ball1, change the step across from 2 to 1. What do you expect to see behind that ball, and why only behind that one?
  • In put, change ARG from 10h to 00h. Where does LMMM read the ball's picture from now, and what is there?
  • In the main loop, reorder the calls so each ball is restored, moved and drawn before the next. When will you see the difference, and what will it look like?

When It Goes Wrong

SymptomCause
A dotted trail behind the sprite, in its rim's colourThe sprite is at an odd x and the restore is a byte copy (HMMM). Keep x even, or restore with LMMM
Small blobs of other shapes where the balls should beThe draw read main video memory instead of the expansion: MXS (10h) missing from ARG

Summary

You can copy rectangles of video memory in bytes (HMMM), in whole lines (YMMM) and in dots through a logical operation (LMMM, with TIMP for transparency), from any page or from the expansion with MXS. A software sprite is a restore from a clean copy and a transparent draw: restore every sprite, then draw every sprite, at even x.

Next

S18, Eighty Columns: the 256's text mode, for menus and status screens.

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.