2024-09-17 19:01:01 +00:00
|
|
|
; optimized graphics routines for just the single screen mode: lores 320*240, 256c (8bpp)
|
2024-09-14 12:27:45 +00:00
|
|
|
; bitmap image needs to start at VRAM addres $00000.
|
|
|
|
; This is compatible with the CX16's screen mode 128. (void cx16.set_screen_mode(128))
|
|
|
|
|
2024-10-01 20:18:03 +00:00
|
|
|
|
2024-10-30 18:17:58 +00:00
|
|
|
%import syslib
|
|
|
|
%import verafx
|
2024-09-14 12:27:45 +00:00
|
|
|
|
|
|
|
gfx_lores {
|
2024-09-18 20:04:25 +00:00
|
|
|
%option ignore_unused
|
|
|
|
|
2024-10-30 18:17:58 +00:00
|
|
|
const uword WIDTH = 320
|
|
|
|
const ubyte HEIGHT = 240
|
|
|
|
|
|
|
|
sub graphics_mode() {
|
|
|
|
; enable 320x240 256c bitmap graphics mode
|
2024-09-17 19:01:01 +00:00
|
|
|
cx16.VERA_CTRL=0
|
|
|
|
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
|
|
|
cx16.VERA_DC_HSCALE = 64
|
|
|
|
cx16.VERA_DC_VSCALE = 64
|
|
|
|
cx16.VERA_L1_CONFIG = %00000111
|
|
|
|
cx16.VERA_L1_MAPBASE = 0
|
|
|
|
cx16.VERA_L1_TILEBASE = 0
|
|
|
|
clear_screen(0)
|
2024-10-31 00:28:29 +00:00
|
|
|
drawmode_eor(false)
|
2024-09-17 19:01:01 +00:00
|
|
|
}
|
|
|
|
|
2024-10-30 18:17:58 +00:00
|
|
|
sub text_mode() {
|
|
|
|
; back to normal text mode
|
|
|
|
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
|
|
|
|
cbm.CINT()
|
|
|
|
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11111000) | cx16.r15L
|
|
|
|
}
|
|
|
|
|
2024-10-31 00:28:29 +00:00
|
|
|
sub drawmode_eor(bool enabled) {
|
|
|
|
; with EOR drawing mode you can have non destructive drawing (2*EOR=restore original)
|
|
|
|
eor_mode = enabled
|
|
|
|
}
|
|
|
|
|
|
|
|
bool eor_mode
|
|
|
|
|
2024-09-17 19:01:01 +00:00
|
|
|
sub clear_screen(ubyte color) {
|
2024-10-01 20:18:03 +00:00
|
|
|
if verafx.available() {
|
2024-10-01 21:43:50 +00:00
|
|
|
; use verafx cache writes to quicly clear the screen
|
|
|
|
const ubyte vbank = 0
|
|
|
|
const uword vaddr = 0
|
|
|
|
cx16.VERA_CTRL = 0
|
|
|
|
cx16.VERA_ADDR_H = vbank | %00110000 ; 4-byte increment
|
|
|
|
cx16.VERA_ADDR_M = msb(vaddr)
|
|
|
|
cx16.VERA_ADDR_L = lsb(vaddr)
|
|
|
|
cx16.VERA_CTRL = 6<<1 ; dcsel = 6, fill the 32 bits cache
|
|
|
|
cx16.VERA_FX_CACHE_L = color
|
|
|
|
cx16.VERA_FX_CACHE_M = color
|
|
|
|
cx16.VERA_FX_CACHE_H = color
|
|
|
|
cx16.VERA_FX_CACHE_U = color
|
|
|
|
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
|
|
|
|
cx16.VERA_FX_MULT = 0
|
|
|
|
cx16.VERA_FX_CTRL = %01000000 ; cache write enable
|
|
|
|
repeat 320/4/4 {
|
|
|
|
%asm {{
|
|
|
|
ldy #240
|
|
|
|
- stz cx16.VERA_DATA0
|
|
|
|
stz cx16.VERA_DATA0
|
|
|
|
stz cx16.VERA_DATA0
|
|
|
|
stz cx16.VERA_DATA0
|
|
|
|
dey
|
|
|
|
bne -
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
cx16.VERA_FX_CTRL = 0 ; cache write disable
|
|
|
|
cx16.VERA_CTRL = 0
|
2024-10-01 20:18:03 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
; fallback to cpu clear
|
2024-09-17 19:01:01 +00:00
|
|
|
cx16.VERA_CTRL=0
|
|
|
|
cx16.VERA_ADDR=0
|
|
|
|
cx16.VERA_ADDR_H = 1<<4 ; 1 pixel auto increment
|
2024-10-30 18:17:58 +00:00
|
|
|
repeat HEIGHT {
|
2024-10-01 20:18:03 +00:00
|
|
|
%asm {{
|
|
|
|
lda p8v_color
|
2024-10-30 18:17:58 +00:00
|
|
|
ldy #p8c_WIDTH/8
|
2024-10-01 20:18:03 +00:00
|
|
|
- .rept 8
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
.endrept
|
|
|
|
dey
|
|
|
|
bne -
|
|
|
|
}}
|
|
|
|
}
|
2024-09-17 19:01:01 +00:00
|
|
|
cx16.VERA_ADDR=0
|
|
|
|
cx16.VERA_ADDR_H = 0
|
|
|
|
}
|
|
|
|
|
2024-10-30 18:17:58 +00:00
|
|
|
sub rect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
|
|
|
|
if rwidth==0 or rheight==0
|
|
|
|
return
|
|
|
|
horizontal_line(xx, yy, rwidth, color)
|
|
|
|
if rheight==1
|
|
|
|
return
|
|
|
|
horizontal_line(xx, yy+rheight-1, rwidth, color)
|
|
|
|
vertical_line(xx, yy+1, rheight-2, color)
|
|
|
|
if rwidth==1
|
|
|
|
return
|
|
|
|
vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
|
|
|
|
}
|
|
|
|
|
|
|
|
sub safe_rect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
|
|
|
|
; does bounds checking and clipping
|
|
|
|
safe_horizontal_line(xx, yy, rwidth, color)
|
|
|
|
if rheight==1
|
|
|
|
return
|
|
|
|
uword bottomyy = yy as uword + rheight -1
|
|
|
|
if bottomyy<HEIGHT
|
|
|
|
safe_horizontal_line(xx, lsb(bottomyy), rwidth, color)
|
|
|
|
safe_vertical_line(xx, yy+1, rheight-2, color)
|
|
|
|
if rwidth==1
|
|
|
|
return
|
|
|
|
safe_vertical_line(xx+rwidth-1, yy+1, rheight-2, color)
|
|
|
|
}
|
|
|
|
|
|
|
|
sub fillrect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
|
|
|
|
; Draw a filled rectangle of the given size and color.
|
|
|
|
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
|
|
|
if rwidth==0
|
|
|
|
return
|
|
|
|
repeat rheight {
|
|
|
|
horizontal_line(xx, yy, rwidth, color)
|
|
|
|
yy++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub safe_fillrect(uword xx, ubyte yy, uword rwidth, ubyte rheight, ubyte color) {
|
|
|
|
; Draw a filled rectangle of the given size and color.
|
|
|
|
; To fill the whole screen, use clear_screen(color) instead - it is much faster.
|
|
|
|
; This safe version does bounds checking and clipping.
|
|
|
|
if xx>=WIDTH or yy>=HEIGHT
|
|
|
|
return
|
|
|
|
if msb(xx)&$80!=0 {
|
|
|
|
rwidth += xx
|
|
|
|
xx = 0
|
|
|
|
}
|
|
|
|
if xx>=WIDTH
|
|
|
|
return
|
|
|
|
if xx+rwidth>WIDTH
|
|
|
|
rwidth = WIDTH-xx
|
|
|
|
if rwidth>WIDTH
|
|
|
|
return
|
|
|
|
|
|
|
|
if yy as uword + rheight > HEIGHT
|
|
|
|
rheight = HEIGHT-yy
|
|
|
|
if rheight>HEIGHT
|
|
|
|
return
|
|
|
|
|
|
|
|
repeat rheight {
|
|
|
|
horizontal_line(xx, yy, rwidth, color)
|
|
|
|
yy++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub horizontal_line(uword xx, ubyte yy, uword length, ubyte color) {
|
|
|
|
if length==0
|
|
|
|
return
|
2024-10-31 00:28:29 +00:00
|
|
|
position(xx, yy)
|
2024-10-30 18:17:58 +00:00
|
|
|
; set vera auto-increment to 1 pixel
|
|
|
|
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (1<<4)
|
2024-10-31 00:28:29 +00:00
|
|
|
if eor_mode {
|
|
|
|
cx16.vaddr_clone(0) ; also setup port 1, for reading
|
|
|
|
%asm {{
|
|
|
|
ldx p8v_length+1
|
|
|
|
beq +
|
|
|
|
ldy #0
|
|
|
|
- lda p8v_color
|
|
|
|
eor cx16.VERA_DATA1
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
iny
|
|
|
|
bne -
|
|
|
|
dex
|
|
|
|
bne -
|
|
|
|
+ ldy p8v_length ; remaining
|
|
|
|
beq +
|
|
|
|
- lda p8v_color
|
|
|
|
eor cx16.VERA_DATA1
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
dey
|
|
|
|
bne -
|
2024-10-30 18:17:58 +00:00
|
|
|
+
|
2024-10-31 00:28:29 +00:00
|
|
|
}}
|
|
|
|
} else {
|
|
|
|
%asm {{
|
|
|
|
lda p8v_color
|
|
|
|
ldx p8v_length+1
|
|
|
|
beq +
|
|
|
|
ldy #0
|
|
|
|
- sta cx16.VERA_DATA0
|
|
|
|
iny
|
|
|
|
bne -
|
|
|
|
dex
|
|
|
|
bne -
|
|
|
|
+ ldy p8v_length ; remaining
|
|
|
|
beq +
|
|
|
|
- sta cx16.VERA_DATA0
|
|
|
|
dey
|
|
|
|
bne -
|
|
|
|
+
|
|
|
|
}}
|
|
|
|
}
|
2024-10-01 20:18:03 +00:00
|
|
|
}
|
|
|
|
|
2024-10-30 18:17:58 +00:00
|
|
|
sub safe_horizontal_line(uword xx, ubyte yy, uword length, ubyte color) {
|
|
|
|
; does bounds checking and clipping
|
|
|
|
if yy>=HEIGHT
|
|
|
|
return
|
|
|
|
if msb(xx)&$80!=0 {
|
|
|
|
length += xx
|
|
|
|
xx = 0
|
|
|
|
}
|
|
|
|
if xx>=WIDTH
|
|
|
|
return
|
|
|
|
if xx+length>WIDTH
|
|
|
|
length = WIDTH-xx
|
|
|
|
if length>WIDTH
|
|
|
|
return
|
|
|
|
|
|
|
|
horizontal_line(xx, yy, length, color)
|
|
|
|
}
|
|
|
|
|
|
|
|
sub vertical_line(uword xx, ubyte yy, ubyte lheight, ubyte color) {
|
|
|
|
if lheight==0
|
|
|
|
return
|
2024-10-31 00:28:29 +00:00
|
|
|
position(xx, yy)
|
2024-10-30 18:17:58 +00:00
|
|
|
; set vera auto-increment to 320 pixel increment (=next line)
|
|
|
|
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
|
2024-10-31 00:28:29 +00:00
|
|
|
if eor_mode {
|
|
|
|
cx16.vaddr_clone(0) ; also setup port 1, for reading
|
|
|
|
%asm {{
|
|
|
|
ldy p8v_lheight
|
|
|
|
beq +
|
|
|
|
- lda p8v_color
|
|
|
|
eor cx16.VERA_DATA1
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
dey
|
|
|
|
bne -
|
|
|
|
+
|
|
|
|
}}
|
|
|
|
} else {
|
|
|
|
%asm {{
|
|
|
|
ldy p8v_lheight
|
|
|
|
lda p8v_color
|
|
|
|
- sta cx16.VERA_DATA0
|
|
|
|
dey
|
|
|
|
bne -
|
|
|
|
}}
|
|
|
|
}
|
2024-10-30 18:17:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub safe_vertical_line(uword xx, ubyte yy, ubyte lheight, ubyte color) {
|
|
|
|
; does bounds checking and clipping
|
|
|
|
if yy>=HEIGHT
|
|
|
|
return
|
|
|
|
if msb(xx)&$80!=0 or xx>=WIDTH
|
|
|
|
return
|
|
|
|
if yy as uword + lheight > HEIGHT
|
|
|
|
lheight = HEIGHT-yy
|
|
|
|
if lheight>HEIGHT
|
|
|
|
return
|
|
|
|
|
|
|
|
vertical_line(xx, yy, lheight, color)
|
|
|
|
}
|
|
|
|
|
2024-09-14 12:27:45 +00:00
|
|
|
sub line(uword x1, ubyte y1, uword x2, ubyte y2, ubyte color) {
|
|
|
|
; Bresenham algorithm.
|
|
|
|
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
|
|
|
; NOTE: this is about twice as fast as the kernal routine GRAPH_draw_line, and ~3-4 times as fast as gfx2.line()
|
|
|
|
; it trades memory for speed (uses inline plot routine and multiplication lookup tables)
|
|
|
|
;
|
|
|
|
; NOTE: is currently still a regular 6502 routine, could likely be made much faster with the VeraFX line helper.
|
|
|
|
|
|
|
|
cx16.r3L = y2 ; ensure zeropage
|
|
|
|
cx16.r1L = y1 ; ensure zeropage
|
|
|
|
|
|
|
|
if cx16.r1L > cx16.r3L {
|
|
|
|
; make sure dy is always positive to have only 4 instead of 8 special cases
|
|
|
|
cx16.r0 = x1
|
|
|
|
x1 = x2
|
|
|
|
x2 = cx16.r0
|
|
|
|
cx16.r0L = cx16.r1L
|
|
|
|
cx16.r1L = cx16.r3L
|
|
|
|
cx16.r3L = cx16.r0L
|
|
|
|
}
|
|
|
|
word @zp dx = x2 as word
|
|
|
|
word @zp dy = cx16.r3L
|
|
|
|
dx -= x1
|
|
|
|
dy -= cx16.r1L
|
|
|
|
|
|
|
|
if dx==0 {
|
|
|
|
vertical_line(x1, cx16.r1L, lsb(dy)+1, color)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dy==0 {
|
|
|
|
if x1>x2
|
|
|
|
x1=x2
|
|
|
|
horizontal_line(x1, cx16.r1L, abs(dx) as uword +1, color)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
word @zp d = 0
|
|
|
|
bool positive_ix = true
|
|
|
|
if dx < 0 {
|
|
|
|
dx = -dx
|
|
|
|
positive_ix = false
|
|
|
|
}
|
|
|
|
word @zp dx2 = dx*2
|
|
|
|
word @zp dy2 = dy*2
|
|
|
|
|
|
|
|
cx16.r0 = x1 ; ensure zeropage
|
|
|
|
cx16.r2 = x2 ; ensure zeropage
|
|
|
|
|
|
|
|
cx16.VERA_CTRL = 0
|
|
|
|
if dx >= dy {
|
|
|
|
if positive_ix {
|
|
|
|
repeat {
|
|
|
|
plot()
|
|
|
|
if cx16.r0==cx16.r2
|
|
|
|
return
|
|
|
|
cx16.r0++
|
|
|
|
d += dy2
|
|
|
|
if d > dx {
|
|
|
|
cx16.r1L++
|
|
|
|
d -= dx2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
repeat {
|
|
|
|
plot()
|
|
|
|
if cx16.r0==cx16.r2
|
|
|
|
return
|
|
|
|
cx16.r0--
|
|
|
|
d += dy2
|
|
|
|
if d > dx {
|
|
|
|
cx16.r1L++
|
|
|
|
d -= dx2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if positive_ix {
|
|
|
|
repeat {
|
|
|
|
plot()
|
|
|
|
if cx16.r1L == cx16.r3L
|
|
|
|
return
|
|
|
|
cx16.r1L++
|
|
|
|
d += dx2
|
|
|
|
if d > dy {
|
|
|
|
cx16.r0++
|
|
|
|
d -= dy2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
repeat {
|
|
|
|
plot()
|
|
|
|
if cx16.r1L == cx16.r3L
|
|
|
|
return
|
|
|
|
cx16.r1L++
|
|
|
|
d += dx2
|
|
|
|
if d > dy {
|
|
|
|
cx16.r0--
|
|
|
|
d -= dy2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
asmsub plot() {
|
2024-10-01 20:18:03 +00:00
|
|
|
; internal plot routine for the line algorithm: x in r0, y in r1, color in variable.
|
2024-09-14 12:27:45 +00:00
|
|
|
%asm {{
|
|
|
|
ldy cx16.r1L
|
|
|
|
clc
|
|
|
|
lda times320_lo,y
|
|
|
|
adc cx16.r0L
|
|
|
|
sta cx16.VERA_ADDR_L
|
|
|
|
lda times320_mid,y
|
|
|
|
adc cx16.r0H
|
|
|
|
sta cx16.VERA_ADDR_M
|
|
|
|
lda #0
|
|
|
|
adc times320_hi,y
|
|
|
|
sta cx16.VERA_ADDR_H
|
2024-10-31 00:28:29 +00:00
|
|
|
|
|
|
|
lda p8v_eor_mode
|
|
|
|
bne +
|
2024-09-14 12:27:45 +00:00
|
|
|
lda p8v_color
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
rts
|
2024-10-31 00:28:29 +00:00
|
|
|
+ lda p8v_color
|
|
|
|
eor cx16.VERA_DATA0
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
rts
|
2024-09-14 12:27:45 +00:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-30 18:17:58 +00:00
|
|
|
sub circle(uword @zp xcenter, ubyte @zp ycenter, ubyte radius, ubyte color) {
|
|
|
|
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
|
|
|
; Midpoint algorithm.
|
|
|
|
if radius==0
|
2024-09-14 12:27:45 +00:00
|
|
|
return
|
|
|
|
|
2024-10-30 18:17:58 +00:00
|
|
|
ubyte @zp xx = radius
|
|
|
|
ubyte @zp yy = 0
|
|
|
|
word @zp decisionOver2 = (1 as word)-xx
|
|
|
|
; R14 = internal plot X
|
|
|
|
; R15 = internal plot Y
|
|
|
|
|
|
|
|
while xx>=yy {
|
|
|
|
cx16.r14 = xcenter + xx
|
|
|
|
cx16.r15 = ycenter + yy
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter - xx
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter + xx
|
|
|
|
cx16.r15 = ycenter - yy
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter - xx
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter + yy
|
|
|
|
cx16.r15 = ycenter + xx
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter - yy
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter + yy
|
|
|
|
cx16.r15 = ycenter - xx
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter - yy
|
|
|
|
plotq()
|
|
|
|
|
|
|
|
yy++
|
|
|
|
if decisionOver2>=0 {
|
|
|
|
xx--
|
|
|
|
decisionOver2 -= xx*$0002
|
|
|
|
}
|
|
|
|
decisionOver2 += yy*$0002
|
|
|
|
decisionOver2++
|
|
|
|
}
|
|
|
|
|
|
|
|
sub plotq() {
|
|
|
|
; cx16.r14 = x, cx16.r15 = y, color=color.
|
|
|
|
plot(cx16.r14, cx16.r15L, color)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub safe_circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
|
|
|
|
; This version does bounds checks and clipping, but is a lot slower.
|
|
|
|
; Midpoint algorithm.
|
|
|
|
if radius==0
|
|
|
|
return
|
|
|
|
|
|
|
|
ubyte @zp xx = radius
|
|
|
|
ubyte @zp yy = 0
|
|
|
|
word @zp decisionOver2 = (1 as word)-xx
|
|
|
|
; R14 = internal plot X
|
|
|
|
; R15 = internal plot Y
|
|
|
|
|
|
|
|
while xx>=yy {
|
|
|
|
cx16.r14 = xcenter + xx
|
|
|
|
cx16.r15 = ycenter + yy
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter - xx
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter + xx
|
|
|
|
cx16.r15 = ycenter - yy
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter - xx
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter + yy
|
|
|
|
cx16.r15 = ycenter + xx
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter - yy
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter + yy
|
|
|
|
cx16.r15 = ycenter - xx
|
|
|
|
plotq()
|
|
|
|
cx16.r14 = xcenter - yy
|
|
|
|
plotq()
|
|
|
|
|
|
|
|
yy++
|
|
|
|
if decisionOver2>=0 {
|
|
|
|
xx--
|
|
|
|
decisionOver2 -= xx*$0002
|
|
|
|
}
|
|
|
|
decisionOver2 += yy*$0002
|
|
|
|
decisionOver2++
|
|
|
|
}
|
|
|
|
|
|
|
|
sub plotq() {
|
|
|
|
; cx16.r14 = x, cx16.r15 = y, color=color.
|
|
|
|
if cx16.r15 < HEIGHT
|
|
|
|
safe_plot(cx16.r14, cx16.r15L, color)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub disc(uword @zp xcenter, ubyte @zp ycenter, ubyte @zp radius, ubyte color) {
|
|
|
|
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
|
|
|
; Midpoint algorithm, filled
|
|
|
|
if radius==0
|
|
|
|
return
|
|
|
|
ubyte @zp yy = 0
|
|
|
|
word @zp decisionOver2 = (1 as word)-radius
|
|
|
|
ubyte last_y3 = ycenter+radius
|
|
|
|
ubyte last_y4 = ycenter-radius
|
|
|
|
ubyte new_y3, new_y4
|
|
|
|
|
|
|
|
while radius>=yy {
|
|
|
|
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
|
|
|
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
|
|
|
|
|
|
|
new_y3 = ycenter+radius
|
|
|
|
if new_y3 != last_y3 {
|
|
|
|
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
|
|
|
|
last_y3 = new_y3
|
|
|
|
}
|
|
|
|
new_y4 = ycenter-radius
|
|
|
|
if new_y4 != last_y4 {
|
|
|
|
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
|
|
|
|
last_y4 = new_y4
|
|
|
|
}
|
|
|
|
|
|
|
|
yy++
|
|
|
|
if decisionOver2>=0 {
|
|
|
|
radius--
|
|
|
|
decisionOver2 -= radius*$0002
|
|
|
|
}
|
|
|
|
decisionOver2 += yy*$0002
|
|
|
|
decisionOver2++
|
|
|
|
}
|
|
|
|
|
|
|
|
; draw the final two spans
|
|
|
|
yy--
|
|
|
|
horizontal_line(xcenter-yy, last_y3, yy*$0002+1, color)
|
|
|
|
horizontal_line(xcenter-yy, last_y4, yy*$0002+1, color)
|
|
|
|
}
|
|
|
|
|
|
|
|
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
|
|
|
; This version does bounds checks and clipping, but is a lot slower.
|
|
|
|
; Midpoint algorithm, filled
|
|
|
|
if radius==0
|
|
|
|
return
|
|
|
|
ubyte @zp yy = 0
|
|
|
|
word @zp decisionOver2 = (1 as word)-radius
|
|
|
|
uword last_y3 = ycenter+radius
|
|
|
|
uword last_y4 = ycenter-radius
|
|
|
|
uword new_y3, new_y4
|
|
|
|
|
|
|
|
while radius>=yy {
|
|
|
|
uword liney = ycenter+yy
|
|
|
|
if msb(liney)==0
|
|
|
|
safe_horizontal_line(xcenter-radius, lsb(ycenter+yy), radius*$0002+1, color)
|
|
|
|
liney = ycenter-yy
|
|
|
|
if msb(liney)==0
|
|
|
|
safe_horizontal_line(xcenter-radius, lsb(ycenter-yy), radius*$0002+1, color)
|
|
|
|
new_y3 = ycenter+radius
|
|
|
|
if new_y3 != last_y3 {
|
|
|
|
if msb(last_y3)==0
|
|
|
|
safe_horizontal_line(xcenter-yy, lsb(last_y3), yy*$0002+1, color)
|
|
|
|
last_y3 = new_y3
|
|
|
|
}
|
|
|
|
new_y4 = ycenter-radius
|
|
|
|
if new_y4 != last_y4 {
|
|
|
|
if msb(last_y4)==0
|
|
|
|
safe_horizontal_line(xcenter-yy, lsb(last_y4), yy*$0002+1, color)
|
|
|
|
last_y4 = new_y4
|
|
|
|
}
|
|
|
|
yy++
|
|
|
|
if decisionOver2>=0 {
|
|
|
|
radius--
|
|
|
|
decisionOver2 -= radius*$0002
|
|
|
|
}
|
|
|
|
decisionOver2 += yy*$0002
|
|
|
|
decisionOver2++
|
|
|
|
}
|
|
|
|
; draw the final two spans
|
|
|
|
yy--
|
|
|
|
if msb(last_y3)==0
|
|
|
|
safe_horizontal_line(xcenter-yy, lsb(last_y3), yy*$0002+1, color)
|
|
|
|
if msb(last_y4)==0
|
|
|
|
safe_horizontal_line(xcenter-yy, lsb(last_y4), yy*$0002+1, color)
|
|
|
|
}
|
|
|
|
|
|
|
|
asmsub plot(uword x @AX, ubyte y @Y, ubyte color @R0) {
|
|
|
|
; x in r0, y in r1, color.
|
2024-09-14 12:27:45 +00:00
|
|
|
%asm {{
|
2024-10-30 18:17:58 +00:00
|
|
|
clc
|
|
|
|
adc times320_lo,y
|
|
|
|
sta cx16.VERA_ADDR_L
|
|
|
|
txa
|
|
|
|
adc times320_mid,y
|
|
|
|
sta cx16.VERA_ADDR_M
|
|
|
|
lda #0
|
|
|
|
adc times320_hi,y
|
|
|
|
sta cx16.VERA_ADDR_H
|
2024-10-31 00:28:29 +00:00
|
|
|
|
|
|
|
lda p8v_eor_mode
|
|
|
|
bne +
|
2024-10-30 18:17:58 +00:00
|
|
|
lda cx16.r0L
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
rts
|
2024-10-31 00:28:29 +00:00
|
|
|
+ lda cx16.r0L
|
|
|
|
eor cx16.VERA_DATA0
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
rts
|
2024-10-30 18:17:58 +00:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub safe_plot(uword xx, ubyte yy, ubyte color) {
|
|
|
|
; A plot that does bounds checks to see if the pixel is inside the screen.
|
|
|
|
if msb(xx)&$80!=0
|
|
|
|
return
|
|
|
|
if xx >= WIDTH or yy >= HEIGHT
|
|
|
|
return
|
|
|
|
plot(xx, yy, color)
|
|
|
|
}
|
|
|
|
|
|
|
|
asmsub pget(uword x @AX, ubyte y @Y) -> ubyte @A {
|
|
|
|
; returns the color of the pixel
|
|
|
|
%asm {{
|
|
|
|
jsr p8s_position
|
|
|
|
lda cx16.VERA_DATA0
|
|
|
|
rts
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub fill(uword x, ubyte y, ubyte new_color) {
|
|
|
|
; reuse a few virtual registers in ZP for variables
|
|
|
|
&ubyte fillm = &cx16.r7L
|
|
|
|
&ubyte seedm = &cx16.r8L
|
|
|
|
&ubyte cmask = &cx16.r8H
|
|
|
|
&ubyte vub = &cx16.r13L
|
|
|
|
&ubyte nvub = &cx16.r13H
|
|
|
|
ubyte[4] amask = [$c0,$30,$0c,$03] ; array of cmask bytes
|
|
|
|
|
|
|
|
; Non-recursive scanline flood fill.
|
|
|
|
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
|
|
|
|
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
|
|
|
|
const ubyte MAXDEPTH = 100
|
|
|
|
word @zp xx = x as word
|
|
|
|
word @zp yy = y as word
|
|
|
|
word[MAXDEPTH] @split @shared stack_xl
|
|
|
|
word[MAXDEPTH] @split @shared stack_xr
|
|
|
|
word[MAXDEPTH] @split @shared stack_y
|
|
|
|
byte[MAXDEPTH] @shared stack_dy
|
|
|
|
cx16.r12L = 0 ; stack pointer
|
|
|
|
word x1
|
|
|
|
word x2
|
|
|
|
byte dy
|
|
|
|
cx16.r10L = new_color
|
|
|
|
|
|
|
|
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
|
|
|
|
if cx16.r12L==MAXDEPTH
|
|
|
|
return
|
|
|
|
cx16.r0s = sy+sdy
|
|
|
|
if cx16.r0s>=0 and cx16.r0s<=HEIGHT-1 {
|
|
|
|
;; stack_xl[cx16.r12L] = sxl
|
|
|
|
;; stack_xr[cx16.r12L] = sxr
|
|
|
|
;; stack_y[cx16.r12L] = sy
|
|
|
|
;; stack_dy[cx16.r12L] = sdy
|
|
|
|
;; cx16.r12L++
|
|
|
|
%asm {{
|
|
|
|
ldy cx16.r12L
|
|
|
|
lda p8v_sxl
|
|
|
|
sta p8v_stack_xl_lsb,y
|
|
|
|
lda p8v_sxl+1
|
|
|
|
sta p8v_stack_xl_msb,y
|
|
|
|
lda p8v_sxr
|
|
|
|
sta p8v_stack_xr_lsb,y
|
|
|
|
lda p8v_sxr+1
|
|
|
|
sta p8v_stack_xr_msb,y
|
|
|
|
lda p8v_sy
|
|
|
|
sta p8v_stack_y_lsb,y
|
|
|
|
lda p8v_sy+1
|
|
|
|
sta p8v_stack_y_msb,y
|
|
|
|
ldy cx16.r12L
|
|
|
|
lda p8v_sdy
|
|
|
|
sta p8v_stack_dy,y
|
|
|
|
inc cx16.r12L
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub pop_stack() {
|
|
|
|
;; cx16.r12L--
|
|
|
|
;; x1 = stack_xl[cx16.r12L]
|
|
|
|
;; x2 = stack_xr[cx16.r12L]
|
|
|
|
;; y = stack_y[cx16.r12L]
|
|
|
|
;; dy = stack_dy[cx16.r12L]
|
|
|
|
%asm {{
|
|
|
|
dec cx16.r12L
|
|
|
|
ldy cx16.r12L
|
|
|
|
lda p8v_stack_xl_lsb,y
|
|
|
|
sta p8v_x1
|
|
|
|
lda p8v_stack_xl_msb,y
|
|
|
|
sta p8v_x1+1
|
|
|
|
lda p8v_stack_xr_lsb,y
|
|
|
|
sta p8v_x2
|
|
|
|
lda p8v_stack_xr_msb,y
|
|
|
|
sta p8v_x2+1
|
|
|
|
lda p8v_stack_y_lsb,y
|
|
|
|
sta p8v_yy
|
|
|
|
lda p8v_stack_y_msb,y
|
|
|
|
sta p8v_yy+1
|
|
|
|
ldy cx16.r12L
|
|
|
|
lda p8v_stack_dy,y
|
|
|
|
sta p8v_dy
|
|
|
|
}}
|
|
|
|
yy+=dy
|
|
|
|
}
|
|
|
|
cx16.r11L = pget(xx as uword, lsb(yy)) ; old_color
|
|
|
|
if cx16.r11L == cx16.r10L
|
|
|
|
return
|
|
|
|
if xx<0 or xx>WIDTH-1 or yy<0 or yy>HEIGHT-1
|
|
|
|
return
|
|
|
|
push_stack(xx, xx, yy, 1)
|
|
|
|
push_stack(xx, xx, yy + 1, -1)
|
|
|
|
word left = 0
|
|
|
|
while cx16.r12L!=0 {
|
|
|
|
pop_stack()
|
|
|
|
xx = x1
|
|
|
|
if fill_scanline_left_8bpp() goto skip
|
|
|
|
left = xx + 1
|
|
|
|
if left < x1
|
|
|
|
push_stack(left, x1 - 1, yy, -dy)
|
|
|
|
xx = x1 + 1
|
|
|
|
|
|
|
|
do {
|
|
|
|
fill_scanline_right_8bpp()
|
|
|
|
push_stack(left, xx - 1, yy, dy)
|
|
|
|
if xx > x2 + 1
|
|
|
|
push_stack(x2 + 1, xx - 1, yy, -dy)
|
|
|
|
skip:
|
|
|
|
xx++
|
|
|
|
while xx <= x2 {
|
|
|
|
if pget(xx as uword, lsb(yy)) == cx16.r11L
|
|
|
|
break
|
|
|
|
xx++
|
|
|
|
}
|
|
|
|
left = xx
|
|
|
|
} until xx>x2
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_vera_address(bool decr) {
|
|
|
|
; set both data0 and data1 addresses
|
|
|
|
position(xx as uword, lsb(yy))
|
|
|
|
cx16.r0 = cx16.VERA_ADDR
|
|
|
|
cx16.r1L = cx16.VERA_ADDR_H & 1 | if decr %00011000 else %00010000
|
|
|
|
cx16.VERA_ADDR_H = cx16.r1L
|
|
|
|
cx16.VERA_CTRL = 1
|
|
|
|
cx16.VERA_ADDR = cx16.r0
|
|
|
|
cx16.VERA_ADDR_H = cx16.r1L
|
|
|
|
cx16.VERA_CTRL = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
sub fill_scanline_left_8bpp() -> bool {
|
|
|
|
set_vera_address(true)
|
|
|
|
cx16.r9s = xx
|
|
|
|
while xx >= 0 {
|
|
|
|
if cx16.VERA_DATA0 != cx16.r11L
|
|
|
|
break
|
|
|
|
cx16.VERA_DATA1 = cx16.r10L
|
|
|
|
xx--
|
|
|
|
}
|
|
|
|
return xx==cx16.r9s
|
|
|
|
}
|
|
|
|
|
|
|
|
sub fill_scanline_right_8bpp() {
|
|
|
|
set_vera_address(false)
|
|
|
|
while xx <= WIDTH-1 {
|
|
|
|
if cx16.VERA_DATA0 != cx16.r11L
|
|
|
|
break
|
|
|
|
cx16.VERA_DATA1 = cx16.r10L
|
|
|
|
xx++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub text_charset(ubyte charset) {
|
|
|
|
; -- select the text charset to use with the text() routine
|
|
|
|
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
|
|
|
|
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase etc. etc.
|
|
|
|
cx16.screen_set_charset(charset, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
const ubyte charset_bank = $1
|
|
|
|
const uword charset_addr = $f000 ; in bank 1, so $1f000
|
|
|
|
|
|
|
|
sub text(uword @zp xx, uword yy, ubyte color, uword textptr) {
|
|
|
|
; -- Write some text at the given pixel position. The text string must be in an encoding approprite for the charset.
|
|
|
|
; You must also have called text_charset() first to select and prepare the character set to use.
|
|
|
|
uword chardataptr
|
|
|
|
ubyte[8] @shared char_bitmap_bytes_left
|
|
|
|
ubyte[8] @shared char_bitmap_bytes_right
|
|
|
|
|
|
|
|
while @(textptr)!=0 {
|
|
|
|
chardataptr = charset_addr + (@(textptr) as uword)*8
|
|
|
|
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
|
|
|
repeat 8 {
|
|
|
|
position(xx,lsb(yy))
|
|
|
|
yy++
|
|
|
|
%asm {{
|
|
|
|
ldx p8v_color
|
|
|
|
lda cx16.VERA_DATA1
|
|
|
|
sta P8ZP_SCRATCH_B1
|
|
|
|
ldy #8
|
|
|
|
- asl P8ZP_SCRATCH_B1
|
|
|
|
bcc +
|
|
|
|
stx cx16.VERA_DATA0 ; write a pixel
|
|
|
|
bra ++
|
|
|
|
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
|
|
|
|
+ dey
|
|
|
|
bne -
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
xx+=8
|
|
|
|
yy-=8
|
|
|
|
textptr++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
asmsub position(uword x @AX, ubyte y @Y) {
|
|
|
|
%asm {{
|
|
|
|
clc
|
|
|
|
adc times320_lo,y
|
|
|
|
sta cx16.VERA_ADDR_L
|
|
|
|
txa
|
|
|
|
adc times320_mid,y
|
|
|
|
sta cx16.VERA_ADDR_M
|
|
|
|
lda #%00010000 ; auto increment on
|
|
|
|
adc times320_hi,y
|
|
|
|
sta cx16.VERA_ADDR_H
|
|
|
|
rts
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline asmsub next_pixel(ubyte color @A) {
|
|
|
|
; -- sets the next pixel byte to the graphics chip.
|
|
|
|
; for 8 bpp screens this will plot 1 pixel.
|
|
|
|
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
|
|
|
|
%asm {{
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, X, Y) {
|
|
|
|
; -- sets the next bunch of pixels from a prepared array of bytes.
|
|
|
|
; for 8 bpp screens this will plot 1 pixel per byte.
|
|
|
|
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
|
|
|
|
%asm {{
|
|
|
|
sta P8ZP_SCRATCH_W1
|
|
|
|
sty P8ZP_SCRATCH_W1+1
|
|
|
|
ldx cx16.r0+1
|
2024-09-14 12:27:45 +00:00
|
|
|
beq +
|
|
|
|
ldy #0
|
2024-10-30 18:17:58 +00:00
|
|
|
- lda (P8ZP_SCRATCH_W1),y
|
|
|
|
sta cx16.VERA_DATA0
|
2024-09-14 12:27:45 +00:00
|
|
|
iny
|
|
|
|
bne -
|
2024-10-30 18:17:58 +00:00
|
|
|
inc P8ZP_SCRATCH_W1+1 ; next page of 256 pixels
|
2024-09-14 12:27:45 +00:00
|
|
|
dex
|
|
|
|
bne -
|
2024-10-30 18:17:58 +00:00
|
|
|
|
|
|
|
+ ldx cx16.r0 ; remaining pixels
|
2024-09-14 12:27:45 +00:00
|
|
|
beq +
|
2024-10-30 18:17:58 +00:00
|
|
|
ldy #0
|
|
|
|
- lda (P8ZP_SCRATCH_W1),y
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
iny
|
|
|
|
dex
|
2024-09-14 12:27:45 +00:00
|
|
|
bne -
|
2024-10-30 18:17:58 +00:00
|
|
|
+ rts
|
2024-09-14 12:27:45 +00:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2024-10-30 18:17:58 +00:00
|
|
|
asmsub set_8_pixels_from_bits(ubyte bits @R0, ubyte oncolor @A, ubyte offcolor @Y) clobbers(X) {
|
|
|
|
; this is only useful in 256 color mode where one pixel equals one byte value.
|
2024-09-14 12:27:45 +00:00
|
|
|
%asm {{
|
2024-10-30 18:17:58 +00:00
|
|
|
ldx #8
|
|
|
|
- asl cx16.r0
|
|
|
|
bcc +
|
|
|
|
sta cx16.VERA_DATA0
|
|
|
|
bra ++
|
|
|
|
+ sty cx16.VERA_DATA0
|
|
|
|
+ dex
|
2024-09-14 12:27:45 +00:00
|
|
|
bne -
|
2024-10-30 18:17:58 +00:00
|
|
|
rts
|
2024-09-14 12:27:45 +00:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2024-10-01 20:18:03 +00:00
|
|
|
%asm {{
|
|
|
|
; multiplication by 320 lookup table
|
|
|
|
times320 := 320*range(240)
|
2024-09-17 19:01:01 +00:00
|
|
|
|
2024-10-01 20:18:03 +00:00
|
|
|
times320_lo .byte <times320
|
|
|
|
times320_mid .byte >times320
|
|
|
|
times320_hi .byte `times320
|
|
|
|
}}
|
2024-09-14 12:27:45 +00:00
|
|
|
}
|