; ; Graphics driver for the 320x200x2 mode on the C128. ; ; Based on Stephen L. Judd's GRLIB code. ; ; 2018-03-13, Sven Klose ; 2018-07-22, Scott Hutter ; 2018-07-28, Greg King ; .include "zeropage.inc" .include "tgi-kernel.inc" .include "tgi-error.inc" .include "c128.inc" .macpack generic .macpack module ; ------------------------------------------------------------------------ ; Header. Includes jump table and constants. module_header _c128_hi_tgi ; First part of the header is a structure that has a magic and defines the ; capabilities of the driver .byte $74, $67, $69 ; "tgi" .byte TGI_API_VERSION ; TGI API version number .addr $0000 ; Library reference .word 320 ; X resolution .word 200 ; Y resolution .byte 2 ; Number of drawing colors .byte 1 ; Number of screens available .byte 8 ; System font X size .byte 8 ; System font Y size .word $00D4 ; Aspect ratio (based on 4/3 display) .byte 0 ; TGI driver flags ; Next comes the jump table. With the exception of IRQ, all entries must be ; valid and may point to an RTS for test versions (function not implemented). .addr INSTALL .addr UNINSTALL .addr INIT .addr DONE .addr GETERROR .addr CONTROL .addr CLEAR .addr SETVIEWPAGE .addr SETDRAWPAGE .addr SETCOLOR .addr SETPALETTE .addr GETPALETTE .addr GETDEFPALETTE .addr SETPIXEL .addr GETPIXEL .addr LINE .addr BAR .addr TEXTSTYLE .addr OUTTEXT ; ------------------------------------------------------------------------ ; Data. ; Variables mapped to the zero page segment variables. Some of these are ; used for passing parameters to the driver. X1 := ptr1 Y1 := ptr2 X2 := ptr3 Y2 := ptr4 TEXT := ptr3 TEMP := tmp4 TEMP2 := sreg POINT := regsave CHUNK := X2 ; Used in the line routine OLDCHUNK := X2+1 ; Ditto ; Absolute variables used in the code .bss ERROR: .res 1 ; Error code PALETTE: .res 2 ; The current palette BITMASK: .res 1 ; $00 = clear, $FF = set pixels ; Line routine stuff DX: .res 2 DY: .res 2 ; BAR variables X1SAVE: .res 2 Y1SAVE: .res 1 X2SAVE: .res 2 Y2SAVE: .res 1 ; Text output stuff TEXTMAGX: .res 1 TEXTMAGY: .res 1 TEXTDIR: .res 1 ; Constants and tables .rodata DEFPALETTE: .byte $00, $01 ; White on black PALETTESIZE = * - DEFPALETTE BITTAB: .byte $80,$40,$20,$10,$08,$04,$02,$01 BITCHUNK: .byte $FF,$7F,$3F,$1F,$0F,$07,$03,$01 CHARROM := $D000 ; Character ROM base address VBASE := $C000 ; Video memory base address CBASE := $E000 ; Color memory base address .code ; ------------------------------------------------------------------------ ; INSTALL routine. Is called after the driver is loaded into memory. May ; initialize anything that has to be done just once. Is probably empty ; most of the time. ; ; Must set an error code: NO ; INSTALL: ; rts ; fall through ; ------------------------------------------------------------------------ ; UNINSTALL routine. Is called before the driver is removed from memory. May ; clean up anything done by INSTALL but is probably empty most of the time. ; ; Must set an error code: NO ; UNINSTALL: rts ; ------------------------------------------------------------------------ ; INIT: Changes an already installed device from text mode to graphics ; mode. ; Note that INIT/DONE may be called multiple times while the driver ; is loaded, while INSTALL is only called once, so any code that is needed ; to initializes variables and so on must go here. Setting palette and ; clearing the screen is not needed because this is called by the graphics ; kernel later. ; The graphics kernel will never call INIT when a graphics mode is already ; active, so there is no need to protect against that. ; ; Must set an error code: YES ; INIT: ; Initialize variables. ldx #$FF ; Foreground color stx BITMASK ; Switch into graphics mode. ; Select a video bank: ; bank 0 = $03 ($0000-$3FFF) (default) ; bank 1 = $02 ($4000-$7FFF) ; bank 2 = $01 ($8000-$BFFF) ; bank 3 = $00 ($C000-$FFFF) (TGI) lda CIA2_PRA and #<~$03 ; Bank 3 sta CIA2_PRA lda #$80 ; color-map at $E000, bitmap at $C000 sta VM2 ; Make the VIC-IIe read RAM instead of the font ROM. lda #%00000100 sta CHARDIS ; Switch to bitmap mode. lda #%00100000 sta GRAPHM lda #TGI_ERR_OK sta ERROR rts ; ------------------------------------------------------------------------ ; DONE: Will be called to switch the graphics device back into text mode. ; The graphics kernel will never call DONE when no graphics mode is active, ; so there is no need to protect against that. ; ; Must set an error code: NO ; DONE: ; Select the text video bank. lda CIA2_PRA ora #$03 ; Bank 0 sta CIA2_PRA ; Make the VIC-IIe read the font ROM instead of RAM. lda #%00000000 sta CHARDIS ;lda #%00000000 ; Switch back to text mode sta GRAPHM ; Restore a value that's needed by BASIC's GRAPHIC 1 statement. lda #$78 ; color-map at $1C00, bitmap at $2000 sta VM2 rts ; ------------------------------------------------------------------------ ; GETERROR: Return the error code in A and clear it. GETERROR: ldx #TGI_ERR_OK lda ERROR stx ERROR rts ; ------------------------------------------------------------------------ ; CONTROL: Platform/driver specific entry point. ; ; Must set an error code: YES ; CONTROL: lda #TGI_ERR_INV_FUNC sta ERROR rts ; ------------------------------------------------------------------------ ; CLEAR: Clears the screen. ; ; Must set an error code: NO ; CLEAR: ldy #$00 tya ldx #MMU_CFG_RAM0 sei stx MMU_CR @L1: sta VBASE+$0000,y sta VBASE+$0100,y sta VBASE+$0200,y sta VBASE+$0300,y sta VBASE+$0400,y sta VBASE+$0500,y sta VBASE+$0600,y sta VBASE+$0700,y sta VBASE+$0800,y sta VBASE+$0900,y sta VBASE+$0A00,y sta VBASE+$0B00,y sta VBASE+$0C00,y sta VBASE+$0D00,y sta VBASE+$0E00,y sta VBASE+$0F00,y sta VBASE+$1000,y sta VBASE+$1100,y sta VBASE+$1200,y sta VBASE+$1300,y sta VBASE+$1400,y sta VBASE+$1500,y sta VBASE+$1600,y sta VBASE+$1700,y sta VBASE+$1800,y sta VBASE+$1900,y sta VBASE+$1A00,y sta VBASE+$1B00,y sta VBASE+$1C00,y sta VBASE+$1D00,y sta VBASE+$1E00,y sta VBASE+$1F00,y iny bne @L1 ldx #MMU_CFG_CC65 stx MMU_CR cli rts ; ------------------------------------------------------------------------ ; SETVIEWPAGE: Set the visible page. Called with the new page in A (0..n). ; The page number is already checked to be valid by the graphics kernel. ; ; Must set an error code: NO (will only be called if page ok) ; SETVIEWPAGE: ; rts ; fall through ; ------------------------------------------------------------------------ ; SETDRAWPAGE: Set the drawable page. Called with the new page in A (0..n). ; The page number is already checked to be valid by the graphics kernel. ; ; Must set an error code: NO (will only be called if page ok) ; SETDRAWPAGE: rts ; ------------------------------------------------------------------------ ; SETCOLOR: Set the drawing color (in A). The new color is already checked ; to be in a valid range (0..maxcolor-1). ; ; Must set an error code: NO (will only be called if color ok) ; SETCOLOR: tax beq @L1 lda #$FF @L1: sta BITMASK rts ; ------------------------------------------------------------------------ ; SETPALETTE: Set the palette (not available with all drivers/hardware). ; A pointer to the palette is passed in ptr1. Must set an error if palettes ; are not supported ; ; Must set an error code: YES ; SETPALETTE: ldy #PALETTESIZE - 1 @L1: lda (ptr1),y ; Copy the palette and #$0F ; Make a valid color sta PALETTE,y dey bpl @L1 ; Get the color entries from the palette lda PALETTE+1 ; Foreground color asl a asl a asl a asl a ora PALETTE ; Background color ; Initialize the color map with the new color settings (it is below the ; Kernal ROM). ldy #$00 ldx #MMU_CFG_RAM0 sei stx MMU_CR @L2: sta CBASE+$0000,y sta CBASE+$0100,y sta CBASE+$0200,y sta CBASE+$02e8,y iny bne @L2 ldx #MMU_CFG_CC65 stx MMU_CR cli ; Done, reset the error code lda #TGI_ERR_OK sta ERROR rts ; ------------------------------------------------------------------------ ; GETPALETTE: Return the current palette in A/X. Even drivers that cannot ; set the palette should return the default palette here, so there's no ; way for this function to fail. ; ; Must set an error code: NO ; GETPALETTE: lda #PALETTE rts ; ------------------------------------------------------------------------ ; GETDEFPALETTE: Return the default palette for the driver in A/X. All ; drivers should return something reasonable here, even drivers that don't ; support palettes, otherwise the caller has no way to determine the colors ; of the (not changeable) palette. ; ; Must set an error code: NO (all drivers must have a default palette) ; GETDEFPALETTE: lda #DEFPALETTE rts ; ------------------------------------------------------------------------ ; SETPIXEL: Draw one pixel at X1/Y1 = ptr1/ptr2 with the current drawing ; color. The coordinates passed to this function are never outside the ; visible screen area, so there is no need for clipping inside this function. ; ; Must set an error code: NO ; SETPIXEL: jsr CALC ; Calculate coordinates lda #MMU_CFG_RAM0 ; Work behind ROMs sei sta MMU_CR lda (POINT),Y eor BITMASK and BITTAB,X eor (POINT),Y sta (POINT),Y ldx #MMU_CFG_CC65 stx MMU_CR cli rts ; ------------------------------------------------------------------------ ; GETPIXEL: Read the color value of a pixel and return it in A/X. The ; coordinates passed to this function are never outside the visible screen ; area, so there is no need for clipping inside this function. GETPIXEL: jsr CALC ; Calculate coordinates lda #MMU_CFG_RAM0 ; Work behind ROMs sei sta MMU_CR lda (POINT),Y and BITTAB,X beq @L1 lda #$01 ; Foreground color @L1: ldy #MMU_CFG_CC65 sty MMU_CR cli ldx #$00 ; Clear high byte rts ; ------------------------------------------------------------------------ ; LINE: Draw a line from X1/Y1 to X2/Y2, where X1/Y1 = ptr1/ptr2 and ; X2/Y2 = ptr3/ptr4 using the current drawing color. ; ; X1,X2 etc. are set up above (x2=LINNUM in particular) ; Format is LINE x2,y2,x1,y1 ; ; Must set an error code: NO ; LINE: @CHECK: lda X2 ;Make sure x1=y1? lda Y1 ;Otherwise dy=y1-y2 sec sbc Y2 tay ldx #$88 ;DEY @DYPOS: sty DY ; 8-bit DY -- FIX ME? stx YINCDEC stx XINCDEC jsr CALC ; Set up .X, .Y, and POINT lda BITCHUNK,X sta OLDCHUNK sta CHUNK lda #MMU_CFG_RAM0 ; Work behind ROMs sei sta MMU_CR ldx DY cpx DX ;Who's bigger: dy or dx? bcc STEPINX ;If dx, then... lda DX+1 bne STEPINX ; ; Big steps in Y ; ; To simplify my life, just use PLOT to plot points. ; ; No more! ; Added special plotting routine -- cool! ; ; X is now counter, Y is y-coordinate ; ; On entry, X=DY=number of loop iterations, and Y= ; Y1 AND #$07 STEPINY: lda #00 sta OLDCHUNK ;So plotting routine will work right lda CHUNK lsr ;Strip the bit eor CHUNK sta CHUNK txa beq YCONT2 ;If dy=0, it's just a point @CONT: lsr ;Init counter to dy/2 ; ; Main loop ; YLOOP: sta TEMP lda (POINT),y eor BITMASK and CHUNK eor (POINT),y sta (POINT),y YINCDEC: iny ;Advance Y coordinate cpy #8 bcc @CONT ;No prob if Y=0..7 jsr FIXY @CONT: lda TEMP ;Restore A sec sbc DX bcc YFIXX YCONT: dex ;X is counter bne YLOOP YCONT2: lda (POINT),y ;Plot endpoint eor BITMASK and CHUNK eor (POINT),y sta (POINT),y ldx #MMU_CFG_CC65 stx MMU_CR cli rts YFIXX: ;x=x+1 adc DY lsr CHUNK bne YCONT ;If we pass a column boundary... ror CHUNK ;then reset CHUNK to $80 sta TEMP2 lda POINT ;And add 8 to POINT adc #8 sta POINT bcc @CONT inc POINT+1 @CONT: lda TEMP2 dex bne YLOOP beq YCONT2 ; ; Big steps in X direction ; ; On entry, X=DY=number of loop iterations, and Y= ; Y1 AND #$07 .bss COUNTHI: .byte $00 ;Temporary counter ;only used once .code STEPINX: ldx DX lda DX+1 sta COUNTHI cmp #$80 ror ;Need bit for initialization sta Y1 ;High byte of counter txa bne @CONT ;Could be $100 dec COUNTHI @CONT: ror ; ; Main loop ; XLOOP: lsr CHUNK beq XFIXC ;If we pass a column boundary... XCONT1: sbc DY bcc XFIXY ;Time to step in Y? XCONT2: dex bne XLOOP dec COUNTHI ;High bits set? bpl XLOOP lsr CHUNK ;Advance to last point jsr LINEPLOT ;Plot the last chunk ldx #MMU_CFG_CC65 stx MMU_CR cli rts ; ; CHUNK has passed a column, so plot and increment pointer ; and fix up CHUNK, OLDCHUNK. ; XFIXC: sta TEMP jsr LINEPLOT lda #$FF sta CHUNK sta OLDCHUNK lda POINT clc adc #8 sta POINT lda TEMP bcc XCONT1 inc POINT+1 jmp XCONT1 ; ; Check to make sure there isn't a high bit, plot chunk, ; and update Y-coordinate. ; XFIXY: dec Y1 ;Maybe high bit set bpl XCONT2 adc DX sta TEMP lda DX+1 adc #$FF ;Hi byte sta Y1 jsr LINEPLOT ;Plot chunk lda CHUNK sta OLDCHUNK lda TEMP XINCDEC: iny ;Y-coord cpy #8 ;0..7 is ok bcc XCONT2 sta TEMP jsr FIXY lda TEMP jmp XCONT2 ; ; Subroutine to plot chunks/points (to save a little ; room, gray hair, etc.) ; LINEPLOT: ; Plot the line chunk lda (POINT),Y eor BITMASK ora CHUNK and OLDCHUNK eor CHUNK eor (POINT),Y sta (POINT),Y rts ; ; Subroutine to fix up pointer when Y decreases through ; zero or increases through 7. ; FIXY: cpy #255 ;Y=255 or Y=8 beq @DECPTR @INCPTR: ;Add 320 to pointer ldy #0 ;Y increased through 7 lda POINT adc #<320 sta POINT lda POINT+1 adc #>320 sta POINT+1 rts @DECPTR: ;Okay, subtract 320 then ldy #7 ;Y decreased through 0 lda POINT sec sbc #<320 sta POINT lda POINT+1 sbc #>320 sta POINT+1 rts ; ------------------------------------------------------------------------ ; BAR: Draw a filled rectangle with the corners X1/Y1, X2/Y2, where ; X1/Y1 = ptr1/ptr2 and X2/Y2 = ptr3/ptr4 using the current drawing color. ; Contrary to most other functions, the graphics kernel will sort and clip ; the coordinates before calling the driver, so on entry the following ; conditions are valid: ; X1 <= X2 ; Y1 <= Y2 ; (X1 >= 0) && (X1 < XRES) ; (X2 >= 0) && (X2 < XRES) ; (Y1 >= 0) && (Y1 < YRES) ; (Y2 >= 0) && (Y2 < YRES) ; ; Must set an error code: NO ; ; Note: This function needs optimization. It's just a cheap translation of ; the original C wrapper and could be written much smaller (besides that, ; calling LINE is not a good idea either). BAR: lda X2 sta X2SAVE lda X2+1 sta X2SAVE+1 lda Y2 sta Y2SAVE lda X1 sta X1SAVE lda X1+1 sta X1SAVE+1 lda Y1 sta Y1SAVE @L1: sta Y2 lda #>200 sta Y1+1 sta Y2+1 jsr LINE lda Y1SAVE cmp Y2SAVE beq @L4 inc Y1SAVE lda X1SAVE sta X1 lda X1SAVE+1 sta X1+1 lda X2SAVE sta X2 lda X2SAVE+1 sta X2+1 lda Y1SAVE sta Y1 jmp @L1 @L4: rts ; ------------------------------------------------------------------------ ; TEXTSTYLE: Set the style used when calling OUTTEXT. Text scaling in X and Y ; direction is passend in X/Y, the text direction is passed in A. ; ; Must set an error code: NO ; TEXTSTYLE: stx TEXTMAGX sty TEXTMAGY sta TEXTDIR rts ; ------------------------------------------------------------------------ ; OUTTEXT: Output text at X/Y = ptr1/ptr2 using the current color and the ; current text style. The text to output is given as a zero terminated ; string with address in ptr3. ; ; Must set an error code: NO ; OUTTEXT: ; Calculate a pointer to the representation of the character in the ; character ROM ldx #((>(CHARROM + $0800)) >> 3) ldy #0 lda (TEXT),y bmi @L1 ldx #((>(CHARROM + $0000)) >> 3) @L1: stx ptr4+1 asl a rol ptr4+1 asl a rol ptr4+1 asl a rol ptr4+1 sta ptr4 rts ; ------------------------------------------------------------------------ ; Calculate all variables to plot the pixel at X1/Y1. CALC: lda Y1 sta TEMP2 and #7 tay lda Y1+1 lsr ; Neg is possible ror TEMP2 lsr ror TEMP2 lsr ror TEMP2 lda #00 sta POINT lda TEMP2 cmp #$80 ror ror POINT cmp #$80 ror ror POINT ; row*64 adc TEMP2 ; +row*256 clc adc #>VBASE ; +bitmap base sta POINT+1 lda X1 tax and #$F8 clc adc POINT ; +(X AND #$F8) sta POINT lda X1+1 adc POINT+1 sta POINT+1 txa and #7 tax rts