tweak c64 balloonflight example etc.

This commit is contained in:
Irmen de Jong 2024-09-22 12:20:28 +02:00
parent 90b8a22a71
commit 1d1d6b3d98
6 changed files with 146 additions and 62 deletions

View File

@ -152,10 +152,16 @@ The IRQ handler routine must return a boolean value (0 or 1) in the A register:
**CommanderX16 specific notes** **CommanderX16 specific notes**
.. sidebar::
X16 specific routines
For the X16 there are also some specialized IRQ handling routines, see :ref:`x16-specific-irq` below.
Note that for the CommanderX16 the set_rasterirq() will disable VSYNC irqs and never call the system IRQ handler regardless Note that for the CommanderX16 the set_rasterirq() will disable VSYNC irqs and never call the system IRQ handler regardless
of the return value of the user handler routine. This also means the default sys.wait() routine won't work anymore, of the return value of the user handler routine. This also means the default sys.wait() routine won't work anymore,
when using this handler. when using this handler.
These two helper routines are not particularly suited to handle multiple IRQ sources on the Commander X16. These two helper routines are not particularly suited to handle multiple IRQ sources on the Commander X16.
It's possible but it requires correct fiddling with IRQ enable bits, acknowledging the IRQs, and properly calling It's possible but it requires correct fiddling with IRQ enable bits, acknowledging the IRQs, and properly calling
or not calling the system IRQ handler routine. See the section below for perhaps a better and easier solution that or not calling the system IRQ handler routine. See the section below for perhaps a better and easier solution that
@ -192,6 +198,8 @@ will corrupt any Vera operations that were going on in the main program. The rou
and restored at the end of the handler, further increasing its execution time... and restored at the end of the handler, further increasing its execution time...
.. _x16-specific-irq:
Commander X16 specific IRQ handling Commander X16 specific IRQ handling
=================================== ===================================

View File

@ -1,6 +1,14 @@
TODO TODO
==== ====
Can we move the asm init code that is injected into the start() subroutine, to init_system_phase2 instead?
Doc improvements: some short overview for people coming from other programming languages like C:
tell something about prog8 not having function overloading, max 16 bit (u)word integer as native type (and floats sometimes),
static variable allocations, no dynamic memory allocation in the language itself (although possible via user written libraries),
etc ...
Improve register load order in subroutine call args assignments: Improve register load order in subroutine call args assignments:
in certain situations, the "wrong" order of evaluation of function call arguments is done which results in certain situations, the "wrong" order of evaluation of function call arguments is done which results
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!) in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)

View File

@ -1,38 +1,32 @@
%import syslib %import syslib
%import textio %import textio
%import math %import math
%import test_stack
%zeropage basicsafe %zeropage basicsafe
; note: The flickering in the scrolling is caused by the CPU requiring
; too long to scroll the characters + the colors in course scroll.
; This takes nearly a full frame to accomplish, and causes tearing.
; It's very difficult to remove this flicker: it requires double buffering
; and splitting the coarse character scrolling on multiple phases...
main { main {
bool perform_scroll = false bool do_char_scroll = false
sub start() { sub start() {
uword moon_x = 310
c64.set_sprite_ptr(0, $0f00) ; alternatively, set directly: c64.SPRPTR[0] = $0f00 / 64 c64.set_sprite_ptr(0, $0f00) ; alternatively, set directly: c64.SPRPTR[0] = $0f00 / 64
c64.SPENA = 1 c64.set_sprite_ptr(1, $0f00+64) ; alternatively, set directly: c64.SPRPTR[0] = $0f00 / 64
c64.SPENA = %00000011
c64.SP0COL = 14 c64.SP0COL = 14
c64.SP1COL = 7
c64.SPXY[0] = 80 c64.SPXY[0] = 80
c64.SPXY[1] = 100 c64.SPXY[1] = 100
set_moon_pos(moon_x)
c64.SCROLX &= %11110111 ; 38 column mode c64.SCROLX &= %11110111 ; 38 column mode
sys.set_rasterirq(&irq.irqhandler, 250) ; enable animation via raster interrupt
sys.set_rasterirq(&irq.irqhandler, 200) ; enable animation via raster interrupt
ubyte target_height = 10 ubyte target_height = 10
ubyte active_height = 24 ubyte active_height = 25
bool upwards = true bool upwards = true
repeat { repeat {
;txt.plot(0,0)
;test_stack.test()
ubyte mountain = 223 ; slope upwards ubyte mountain = 223 ; slope upwards
if active_height < target_height { if active_height < target_height {
active_height++ active_height++
@ -42,66 +36,124 @@ main {
active_height-- active_height--
upwards = false upwards = false
} else { } else {
target_height = 8 + math.rnd() % 16 ; determine new height for next mountain
target_height = 9 + math.rnd() % 15
if upwards if upwards
mountain = 233 mountain = 233
else else
mountain = 223 mountain = 223
} }
while not perform_scroll { while not do_char_scroll {
; let the raster irq do its timing job ; let the raster irq do its timing job
} }
perform_scroll = false do_char_scroll = false
txt.scroll_left(true) scroll_characters_left()
; float the balloon ; float the balloon and the moon sprites
if math.rnd() & %10000 !=0 if math.rnd() & 1 !=0
c64.SPXY[1] ++ c64.SPXY[1] ++
else else
c64.SPXY[1] -- c64.SPXY[1] --
moon_x--
if msb(moon_x)==255
moon_x = 340
set_moon_pos(moon_x)
; draw new mountain etc.
const ubyte RIGHT_COLUMN = 39
ubyte yy ubyte yy
for yy in 0 to active_height-1 { for yy in 0 to active_height-1 {
txt.setcc(39, yy, 32, 2) ; clear top of screen txt.setcc(RIGHT_COLUMN, yy, 32, 2) ; clear top of screen
} }
txt.setcc(39, active_height, mountain, 8) ; mountain edge txt.setcc(RIGHT_COLUMN, active_height, mountain, 8) ; mountain edge
for yy in active_height+1 to 24 { for yy in active_height+1 to 24 {
txt.setcc(39, yy, 160, 8) ; draw mountain txt.setcc(RIGHT_COLUMN, yy, 160, 8) ; draw filled mountain
} }
yy = math.rnd() ubyte clutter = math.rnd()
if yy > 100 { if clutter > 100 {
; draw a star ; draw a star
txt.setcc(39, yy % (active_height-1), '.', math.rnd()) txt.setcc(RIGHT_COLUMN, clutter % (active_height-1), sc:'.', math.rnd())
} }
if yy > 200 { if clutter > 200 {
; draw a tree ; draw a tree
ubyte tree = 30 ubyte tree = sc:'↑'
ubyte treecolor = 5 ubyte treecolor = 5
if yy & %01000000 != 0 if clutter & %00010000 != 0
tree = 88 tree = sc:'♣'
else if yy & %00100000 != 0 else if clutter & %00100000 != 0
tree = 65 tree = sc:'♠'
if math.rnd() > 130 if math.rnd() > 130
treecolor = 13 treecolor = 13
txt.setcc(39, active_height, tree, treecolor) txt.setcc(RIGHT_COLUMN, active_height, tree, treecolor)
} }
if yy > 235 { if clutter > 235 {
; draw a camel ; draw a camel
txt.setcc(39, active_height, 94, 9) txt.setcc(RIGHT_COLUMN, active_height, sc:'π', 9)
} }
} }
} }
sub set_moon_pos(uword x) {
c64.SPXY[2] = lsb(x)
c64.SPXY[3] = 55
if msb(x)!=0
c64.MSIGX |= %00000010
else
c64.MSIGX &= %11111101
}
sub scroll_characters_left () {
; Scroll the bottom half (approx.) of the character screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Without clever split-screen tricks, the C64 is not fast enough to scroll the whole
; screen smootly without tearing. So for simplicity it's constrained to less rows
; such that what is scrolled, *does* scrolls smoothly.
; For maximum performance the scrolling is done in unrolled assembly code.
%asm {{
ldx #0
ldy #38
-
.for row=10, row<=24, row+=1
lda cbm.Screen + 40*row + 1,x
sta cbm.Screen + 40*row + 0,x
lda cbm.Colors + 40*row + 1,x
sta cbm.Colors + 40*row + 0,x
.next
inx
dey
bpl -
rts
}}
}
}
irq {
; does the smooth scrolling immediately after the visible screen area,
; so there is no screen tearing. The main loop does the "big" character
; scrolling when the soft-scroll runs out after 8 pixels
ubyte smoothx=0
sub irqhandler() -> bool {
smoothx = (smoothx-1) & 7
main.do_char_scroll = smoothx==7
c64.SCROLX = (c64.SCROLX & %11111000) | smoothx
return false
}
} }
spritedata $0f00 { spritedata $0f00 {
; this memory block contains the sprite data ; this memory block contains the sprite data. it must start on an address aligned to 64 bytes.
; it must start on an address aligned to 64 bytes. ; for simplicity, it's currently statically located at $0f00 (not far in memory after the program code),
; but there are ways to do this more dynamically.
%option force_output ; make sure the data in this block appears in the resulting program %option force_output ; make sure the data in this block appears in the resulting program
ubyte[] balloonsprite = [ %00000000,%01111111,%00000000, ubyte[] balloonsprite = [ %00000000,%01111111,%00000000,
@ -124,17 +176,29 @@ spritedata $0f00 {
%00000000,%00111110,%00000000, %00000000,%00111110,%00000000,
%00000000,%00111110,%00000000, %00000000,%00111110,%00000000,
%00000000,%00111110,%00000000, %00000000,%00111110,%00000000,
%00000000,%00011100,%00000000 ] %00000000,%00011100,%00000000,
} 0]
ubyte[] moonsprite = [ %00000000,%00000110,%00000000,
irq { %00000000,%00011100,%00000000,
ubyte smoothx=0 %00000000,%01111000,%00000000,
sub irqhandler() -> bool { %00000000,%11111000,%00000000,
smoothx = (smoothx-1) & 7 %00000001,%11110000,%00000000,
main.perform_scroll = smoothx==7 %00000011,%11110000,%00000000,
c64.SCROLX = (c64.SCROLX & %11111000) | smoothx %00000011,%11110000,%00000000,
return false %00000111,%11100000,%00000000,
%00000111,%11100000,%00000000,
%00000111,%11100000,%00000000,
%00000111,%11100000,%00000000,
%00000111,%11100000,%00000000,
%00000111,%11100000,%00000000,
%00000111,%11100000,%00000000,
%00000011,%11110000,%00000000,
%00000011,%11110000,%00000000,
%00000001,%11110000,%00000000,
%00000000,%11111000,%00000000,
%00000000,%01111000,%00000000,
%00000000,%00011100,%00000000,
%00000000,%00000110,%00000000,
0]
} }
}

View File

@ -92,15 +92,18 @@ main {
for y in 0 to 24 { for y in 0 to 24 {
ubyte @zp @shared yvalue = ybuf[y] ubyte @zp @shared yvalue = ybuf[y]
for x in 0 to 39 { for x in 0 to 39 {
; @(screen+x) = xbuf[x] + yvalue @(screen+x) = xbuf[x] + yvalue
; max optimized asm is this: (achieving ~21 fps on the C64):
%asm {{ ; optimized asm for the line above is this:
lda p8v_yvalue ; (achieving ~23 fps on the C64, about 1 or 2 fps more than with the pure prog8 code):
ldy p8v_x ; %asm {{
clc ; lda p8v_yvalue
adc p8v_xbuf,y ; ldy p8v_x
sta (p8v_screen),y ; clc
}} ; adc p8v_xbuf,y
; sta (p8v_screen),y
; }}
} }
screen += 40 screen += 40
} }

View File

@ -57,8 +57,9 @@ irq {
uword @zp x = math.sin8u(angle1-spri*16) as uword + 50 uword @zp x = math.sin8u(angle1-spri*16) as uword + 50
ubyte @zp y = math.sin8u(angle2-spri*16) / 2 + 70 ubyte @zp y = math.sin8u(angle2-spri*16) / 2 + 70
c64.SPXYW[spri] = mkword(y, lsb(x)) c64.SPXYW[spri] = mkword(y, lsb(x))
c64.MSIGX <<= 1 if msb(x)!=0
if msb(x)!=0 c64.MSIGX++ sys.set_carry()
rol(c64.MSIGX)
} }
c64.EXTCOL-=8 c64.EXTCOL-=8
return true return true

View File

@ -5,4 +5,4 @@ org.gradle.daemon=true
kotlin.code.style=official kotlin.code.style=official
javaVersion=11 javaVersion=11
kotlinVersion=2.0.20 kotlinVersion=2.0.20
version=10.4.1 version=10.4.2-SNAPSHOT