PutTile

putTile.bas

This subroutine takes a 2X2 tile of data from the given address and copies it to the screen co-ordinates at (x, y) - x and y in character addresses, where 0 <= x <= 31 and 0 <= y < =23.

Note that this uses pushes and pops to move the data, using the fastest known data moving algorithm for the Z80. As a consequence, while active it uses ALL the registers, including alternates and IY and IX as well as the Stack Pointer SP. It is kind enough to put these back for the purposes of exiting the subroutine though - ZX BASIC uses that register quite extensively.

Also, interrupts are disabled while the copying is happening. Considering that the stack pointer is likely pointing either at the screen or the tile data, an interrupt would be disastrous. If interrupts were enabled when the SUB is called, it should re-enable them again on exit.

Note the data format is across the tile - 2 bytes for the top row, then 2 bytes for the second row...and so on until there are 2 bytes for the 16th row. Then two bytes for the top two attributes, and 2 for the bottom. It uses 36 bytes of data, starting at the address given.

' Routine to place a 16 pixel by 16 pixel "Tile" onto the screen at character position x,y from adddress given.
' Data must be in the format of 16 bit rows, followed by attribute data.
' (c) 2010 Britlion, donated to the ZX BASIC project.
' Thanks to Boriel, LCD and Na_than for inspiration behind this.

' This routine could be used as the basis for a fast sprite system, provided all sprites can be in 4 character blocks.
' It can also be used to clean up dirty background (erase sprites), or put backgrounds from tiled blocks onto a screen.

' Note the comments about Self Modifying code should be ignored. This has been updated with IX+n methods, which overall are faster than accessing and changing the code.
' (They would have to be accessed to change the memory anyway - may as well just access them directly.)

SUB putTile(x as uByte, y as uByte, graphicsAddr as uInteger)

ASM
JP pt_start

ptstackSave:
defb 0,0

pt_start:
ld a,i
push af ; Save interrupt status.

; Routine to save the background to the buffer

         DI ; we really, really, REALLY can NOT be having interrupts while the stack and IX and IY are pointed elsewhere.

         PUSH IX
         PUSH IY
         ;ld      HL, 65535 ; Self modifying code should load this with the graphics address.
         LD D,(IX+9)
         LD E,(IX+8)
         EX DE,HL

;; Print sprites routine
         LD (ptstackSave), SP ; Save Stack Pointer

         LD SP,HL   ; now SP points at the start of the graphics.

       ; This function returns the address into HL of the screen address
         ld      a,(IX+5) ; Load in x - note the Self Modifying value
         ld      IYH, a ; save it
         ld      l,a
         ld      a,(IX+7) ; Load in y - note the Self Modifying value
         ld      IYL, a ; save it
         ld      d,a
         and     24
         add     a,64
         ld      h,a
         ld      a,d
         and     7
         rrca
         rrca
         rrca
         or      l
         add     a,2   ; Need to be to the right so backwards writing pushes land properly.
         ld      l,a

         ; SO now, HL -> Screen address, and SP -> Graphics. Time to start loading.

         POP BC    ; Row 0
         POP DE    ; row 1
         EX AF,AF'
         POP AF    ; row 2
         EX AF,AF'
         EXX
         POP BC    ; row 3
         POP DE    ; row 4
         POP HL    ; row 5
         EXX

         ; All right. We're loaded. Time to dump!

         LD IX,0
         ADD IX,SP  ; Save our stack pointer into IX

         LD SP,HL  ; point at the screen.
         PUSH BC   ; row 0

         INC H
         LD SP,HL
         PUSH DE   ; row 1

         INC H
         LD SP,HL
         EX AF,AF'
         PUSH AF   ; row 2

         INC H
         LD SP,HL
         EXX
         PUSH BC   ; row 3
         EXX

         INC H
         LD SP,HL
         EXX
         PUSH DE   ; row 4
         EXX

         INC H
         LD SP,HL
         EXX
         PUSH HL   ; ROW 5
         EXX

         ; We're empty. Time to load up again.

         LD SP,IX
         POP BC    ; ROW 6
         POP DE    ; ROW 7
         EX AF,AF'
         POP AF    ; ROW 8
         EX AF,AF'
         EXX
         POP BC    ; ROW 9
         POP DE    ; ROW 10
         POP HL    ; ROW 11
         EXX

         ; and we're loaded up again! Time to dump this graphic on the screen.

         LD IX,0
         ADD IX,SP ; save SP in IX

         INC H
         LD SP,HL
         PUSH BC   ; ROW 6

         INC H
         LD SP,HL
         PUSH DE   ; ROW 7

         DEC HL
         DEC HL

         ; Aha. Snag. We're at the bottom of a character. What's the next address down?
         ld   a,l
         and  224
         cp   224
         jp   z,ptSameThird3

ptNextThird3:
         ld   de,1760
         and  a
         sbc  hl,de
         jp ptAddrDone3

ptSameThird3:

         ld   de,32
         and  a
         adc  hl,de

ptAddrDone3:

          INC HL
          INC HL

          LD SP,HL
          EX AF,AF'
          PUSH AF  ; ROW 8

          INC H
          LD SP,HL
          EXX
          PUSH BC  ; ROW 9
          EXX

          INC H
          LD SP,HL
          EXX
          PUSH DE  ; ROW 10
          EXX

          INC H
          LD SP,HL
          EXX
          PUSH HL  ; ROW 11
          EXX

         ; Okay. Registers empty. Reload time!
         LD SP,IX
         POP BC    ; ROW 12
         POP DE    ; ROW 13

         EXX
         POP BC    ; ROW 14
         POP DE    ; ROW 15
         POP HL    ; Top Attrs
         EXX

         EX AF,AF'
         POP AF    ; Bottom Attrs
         EX AF,AF'

         ; and the last dump to screen

         INC H
         LD SP,HL
         PUSH BC

         INC H
         LD SP,HL
         PUSH DE

         INC H
         LD SP,HL
         EXX
         PUSH BC
         EXX

         INC H
         LD SP,HL
         EXX
         PUSH DE
         EXX

         ; Pixels done. Just need to do the attributes.
         ; So set HL to the attr address:

         ld      a,IYL        ;ypos

         rrca
         rrca
         rrca               ; Multiply by 32
         ld      l,a        ; Pass to L
         and     3          ; Mask with 00000011
         add     a,88       ; 88 * 256 = 22528 - start of attributes.
         ld      h,a        ; Put it in the High Byte
         ld      a,l        ; We get y value *32
         and     224        ; Mask with 11100000
         ld      l,a        ; Put it in L
         ld      a,IYH      ; xpos
         adc     a,l        ; Add it to the Low byte
         ld      l,a        ; Put it back in L, and we're done. HL=Address.
         INC HL             ; we need to be to the right of the ATTR point as pushes write backwards.
         INC HL

         ; attr
         LD SP,HL
         EXX
         PUSH HL            ; top row
         EXX

         LD HL,34           ; we need to move down to the next row. We already backed up 2, so we add 34.
         ADD HL,SP
         LD SP,HL
         EX AF,AF'          ; bottom row
         PUSH AF

ptNextSprite2:
         ; done. Cleanup.
         LD SP,(ptstackSave) ;  put our stack back together.

         ; done all 4 final clean up

         POP IY
         POP IX

         POP AF  ; recover interrupt status
         JP PO, pt_nointerrupts
         EI      ; Okay. We put everything back. If you need interrupts, you can go with em.

pt_nointerrupts:
END ASM
END SUB

Usage

Example:

putTile (10,10,@sprite)

Will copy a tile of data to print position 10,10 from address at label sprite.