z80:Drawing Routines

From Learn @ Cemetech
Jump to navigationJump to search

There are a limited number of ways for your programs to communicate with a user. You can use text, graphics, and rarely sound. With text, the OS offers us many tools and it doesn't need to be too fast, but with graphics, the OS routines often do not do the job. Here are a few routines to get the job done.

Circle

A good circle routine is always sure to impress users, especially those accustomed to BASIC. This does not use formulas of the nature you may be used to for drawing circles. As well, this routine uses some SMC, so if you are using this in an app, be aware that the subroutine PlotPixel will need to be in RAM (or at least part of it).

   gBufLSB = 40h     ;different for the 83. Good for 83+/84+ and SE models.
   gBufMSB = 93h    ;different for the 83. Good for 83+/84+ and SE models.
   FastCircle:
   ;Inputs:
   ;     BC = (x,y)
   ;     H  = radius
   ;     A = method
   ;        0 = Pixel Off
   ;        1 = Pixel On
   ;        2 = Pixel Change
        ld de,$A62F     ;2F,A6 = 'cpl \ and (hl)'
        dec a
        jr z,$+5
        ld de,$B600     ;00,B6 = 'nop \ or (hl)'
        dec a
        jr z,$+5
        ld de,$AE00     ;00,B6 = 'nop \ xor (hl)'
        ld (smc_PlotType)
        ld a,h
        or a
        ret z
        ret m
        di
        ld e,0
        ld d,a
        ld l,e
        add hl,hl
        jr Loop+4
   Loop:
        ex af,af'
        call Plot4Pix
        call Plot4Pix
        inc d
        sub l
        jr nc,YIsGood
   DecY:
        dec e
        inc l
        add a,h
   YIsGood:
        ex af,af'
        ld a,e
        sub d
        jp p,Loop
        ret
   
   Plot4Pix:
        inc l
        push af
        push hl
        push bc
        ld a,e
        ld e,d
        ld d,a
        push de
        add a,b
        cp 96
        call c,Plot2Pix       ;DC****
        ld a,b
        sub d
        call p,Plot2Pix
        pop de
        pop bc
        pop hl
        pop af
        ret
   plot2Pix:
        push bc
        ld b,a
        push bc
        ld a,c
        add a,e
        ld c,a
   ;c+e
        call p,PlotPixel
        pop bc
        ld a,c
        sub e
        ld c,a
   ;c-e
        call p,PlotPixel
        pop bc
        ret
   PlotPixel:
   ;Input:
   ;     b is X
   ;     c is y
   ;Output:
   ;     HL points to byte
          cp 64 \ ret nc
          ld a,b
          cp 96 \ ret nc
          ld l,c
          ld h,0
          ld b,h
          add hl,hl
          add hl,bc
          add hl,hl
          add hl,hl
          ld b,a
          rrca \ rrca \ rrca
          and 0Fh
          add a,gBufLSB
          ld c,a
          ld a,b
          ld b,gBufMSB
          add hl,bc
          and 7
          ld b,a
          inc b
          ld a,1
            rrca
            djnz $-1
   PixelType:
          nop
          or (hl)
          ld (hl),a
          ret


Pixel Plotting

Sometimes, we need to plot pixels. This routine returns a pointer to the byte the pixel is located on, as well as a mask for the pixel. If it is out of bounds, it returns the c flag reset, else it is set. To use this routine, you can do:

   ;turn the pixel ON
        call GetPixelLoc
        ret nc
        or (hl)
        ld (hl),a
        ret
   ;turn the pixel OFF
        call GetPixelLoc
        ret nc
        cpl         ;invert the bits of A
        and (hl)
        ld (hl),a
        ret
   ;invert the pixel
        call GetPixelLoc
        ret nc
        xor (hl)
        ret
   ;test the pixel (nz=ON, z=Off)
        call GetPixelLoc
        ret nc            ;also happens to return nz if c is reset
        and (hl)
        ret


   gBufLSB = 40h     ;different for the 83. Good for 83+/84+ and SE models.
   gBufMSB = 93h    ;different for the 83. Good for 83+/84+ and SE models.
   GetPixelLoc:
   ;Input:
   ;     b is X
   ;     c is y
   ;Output:
   ;     HL points to byte
   ;     A is the mask
   ;     nc if not computed, c if computed
          cp 64 \ ret nc
          ld a,b
          cp 96 \ ret nc
          ld l,c
          ld h,0
          ld b,h
          add hl,hl
          add hl,bc
          add hl,hl
          add hl,hl
          ld b,a
          rrca \ rrca \ rrca
          and 0Fh
          add a,gBufLSB
          ld c,a
          ld a,b
          ld b,gBufMSB
          add hl,bc
          and 7
          ld b,a
          ld a,1
          inc b
          rrca
          djnz $-1
          scf
          ret


Rectangle

The OS routines automatically update the LCD area that it is drawn to, which can be a pain to assembly programmers. It also slows it down. Here is a quick routine, riddled with SMCL

   gBufLSB = 40h     ;different for the 83. Good for 83+/84+ and SE models.
   gBufMSB = 93h    ;different for the 83. Good for 83+/84+ and SE models.
   RectData   equ OP1
   Rectangle:
   ;Inputs:
   ;     D = x
   ;     E = y
   ;     B = height
   ;     C = width
   ;     A = Method
   ;         0=Erase
   ;         1=OR
   ;         2=XOR
        dec a
        jr nz,$+6
          ld a,$B6   ;B6 = 'or (hl)'
          jr RectPattern
        dec a
        jr nz,$+6
          ld a,$AE   ;AE = 'xor (hl)'
          jr RectPattern
        ld a,-1
        ld (smc_Erase0),a
        ld a,2Fh     ;2F = CPL
        ld (smc_Erase1),a
        ld a,0Ch     ;0C = INC C
        ld (smc_Erase2),a
        ld a,$A6     ;A6 = 'and (hl)'
   RectPattern:
        ld (smc_Logic),a
        push bc
        push de
        push bc
   ; First I want to clear the RectData buffer
        ld hl,RectData
        .db 1
   smc_Erase0:
        .dw 0C00h      ;changed to 0CFF for Erase
          ld (hl),c
          inc l
          djnz $-2
   
        ld l,78h
   ; Now I want to make the rectangle pattern.
   ; Since D is the x coordinate, we need to find which bit
   ; the edge starts on and set each bit after that as well.
        ld a,d
        and 7
        ld b,a
        ld a,80h
        jr z,$+5
          rrca
          djnz $-1
        add a,a
        dec a
   smc_Erase1:
        nop
        ld (hl),a
   ; Now we need to fill in the rest of the buffer
   ; First we need to know how many bits need to be set 
        ex (sp),hl
        ld a,d
        neg
        and 7
        ld b,a
        ld a,l
   smc_Erase2:
        dec c      ;changed to 'inc c' for Erase
        sub b
        ex (sp),hl
        jr z,PatternFinished
        jr c,LoadLastByte
        inc l
        ld (hl),c
        sub 8
        jr nc,$-4
   LoadLastByte:
        and 7
        ld b,a
        ld a,80h
        jr z,$+5
          rrca
          djnz $-1
        add a,a
        dec a
        xor (hl)
   smc_Erase3:
        nop
        ld (hl),a
   PatternFinished:
   ; At this point, the whole pattern is filled in. Bien.
   ; Now we can worry about where to start drawing the pattern.
   ; For this, we use DE for (x,y)
   ; B is 0, so I cheat.
        ld a,d
        ld d,b
        ld h,d
        ld l,e
        add hl,hl
        add hl,de
        add hl,hl
        add hl,hl
        and %11111000
        rrca \ rrca \ rrca
        add a,gBufLSB         ;plotSScreen = 9340h
        ld e,a
        ld d,gBufMSB
        add hl,de
   
   ; Now HL points to where to draw in the graph buffer.
   ; All we do now is treat the pattern as a 12-byte wide sprite with
   ; the same bytes repeated over and over.
        pop bc     ;b is the height.
        ld d,84h   ;RectData = 8478h
   DrawLoopStart:
        ld e,78h
        ld c,12
   DrawLoop:
        ld a,(de)
   smc_Logic:
        or (hl)
        ld (hl),a
        inc hl
        inc e
        dec c
        jr nz,DrawLoop
        djnz DrawLoopStart
        pop de
        pop bc
        ret

Grayscale LCD Update

This code uses SMC, so it should be used in RAM. However, the SMC used can easily be removed for use in an app. This requires the use of a minor buffer and major buffer and it can use different levels of saturation from each buffer by changing GrayMask. This is designed to handle 2,3, or 4 levels of grayscale and each of the grayscale bit masks is meant to display each pixel for a certain percentage of the frames displayed. This routine must be continuously called to update the LCD in order for it to appear gray.

   ;===============================================================
   BufferToLCD:
   ;===============================================================
   ;Inputs:
   ;     MajBuf points to the main buffer to use. Probably plotSScreen.
   ;     MinBuf points to the backgfor round buffer to use. Probably saveSScreen.
   ;     GrayMask holds the gray mask. Zero takes from the major,
   ;       buffer, one takes from the minor buffer.
   ;Outputs:
   ;
   ;Defines:
   
   GrayMask50    equ %1010101010101010   ;1   1/2   1/2   0
   GrayMask67    equ %1001001001001001   ;1   2/3   1/3   0
   GrayMask75    equ %1000100010001000   ;1   3/4   1/4   0
   GrayMask83    equ %1000001000001000   ;1   5/6   1/6   0
   GrayMask92    equ %1000000000001000   ;1  11/12  1/12  0
   Graymask100   equ -1
   GrayMask0     equ 0
   GrayMask8     equ 1-GrayMaks92
   GrayMask17    equ 1-GrayMask83
   GrayMask25    equ 1-GrayMask75
   GrayMask33    equ 1-GrayMask67
   #define     LCDDelay()    in a,(16) \ rlca \ jr c,$-3
   ;===============================================================
        .db 21h           ;ld hl,**
   GrayMask:
        .dw GrayMask50        ;replace with whatever you like
        add hl,hl
        jr nc,$+4
          set 4,l
        ld (GrayMask),hl
        ld hl,MajBuf
        ld ix,MinBuf
   setrow:
        LCDDelay()
        ld a,$80
        out (16),a
        ld de,12
        ld a,$20
   col:
        ld (smc_Var2),a
        LCDDelay()
        ld a,(smc_Var2)
        out (10h),a
        ld b,64
   row:
        ld (smc_Var1),hl       ;16
        .db 21h            ;10        ld hl,**
   GrayMask:               ;  
        .dw GrayMask50     ;--
        add hl,hl          ;11
        jr nc,$+4          ;12|15
          set 4,l          ;--
        ld (GrayMask),hl   ;16
        ld a,(ix)          ;19
        .db 21h            ;10        ld hl,**
   smc_Var1:                   ;  
        .dw 0              ;--
        xor (hl)           ;7
        and c              ;4
        xor (hl)           ;7
        add hl,de          ;11
        add ix,de          ;15
   
   ;========================================================
   ;Not needed because the stuff before takes >130 cycles
   ;        push af
   ;        LCDDelay()
   ;        pop af
   ;========================================================
        out ($11),a
        djnz row
        .db 3Eh             ;ld a,*
   smc_Var2:
        .db 20h
        inc a
        dec h
        dec h
        dec h
        inc hl
        dec ixh
        dec ixh
        dec ixh
        inc ix
        cp $2c
        jp nz,col
        ret