From b5255444cdd5e8819dd079e3d736c4cd92b8b41f Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 22 May 2023 20:36:33 +0200 Subject: [PATCH 1/7] irq-safe irqd handling for RDTIM16 --- compiler/res/prog8lib/cx16/syslib.p8 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/res/prog8lib/cx16/syslib.p8 b/compiler/res/prog8lib/cx16/syslib.p8 index 37542bc08..d8353606c 100644 --- a/compiler/res/prog8lib/cx16/syslib.p8 +++ b/compiler/res/prog8lib/cx16/syslib.p8 @@ -70,9 +70,10 @@ asmsub RDTIM16() -> uword @AY { ; -- like RDTIM() but only returning the lower 16 bits in AY for convenience %asm {{ phx + php sei jsr c64.RDTIM - cli + plp pha txa tay @@ -927,6 +928,7 @@ sys { asmsub wait(uword jiffies @AY) { ; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1) ; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock + ; note: this routine cannot be used from inside a irq handler %asm {{ phx sta P8ZP_SCRATCH_W1 From 8bffd7672d3b88bc36be7927c98ac77a2d7cc3bc Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 22 May 2023 21:13:20 +0200 Subject: [PATCH 2/7] added sys.irqsafe_set_irqd()/irqsafe_clear_irqd() --- compiler/res/prog8lib/atari/syslib.p8 | 13 +++++++++++++ compiler/res/prog8lib/c128/syslib.p8 | 13 +++++++++++++ compiler/res/prog8lib/c64/syslib.p8 | 13 +++++++++++++ compiler/res/prog8lib/cx16/psg.p8 | 18 ++++-------------- compiler/res/prog8lib/cx16/syslib.p8 | 13 +++++++++++++ docs/source/libraries.rst | 10 ++++++++++ 6 files changed, 66 insertions(+), 14 deletions(-) diff --git a/compiler/res/prog8lib/atari/syslib.p8 b/compiler/res/prog8lib/atari/syslib.p8 index 3bd31b5aa..26629c9ba 100644 --- a/compiler/res/prog8lib/atari/syslib.p8 +++ b/compiler/res/prog8lib/atari/syslib.p8 @@ -188,6 +188,19 @@ _longcopy }} } + inline asmsub irqsafe_set_irqd() { + %asm {{ + php + sei + }} + } + + inline asmsub irqsafe_clear_irqd() { + %asm {{ + plp + }} + } + inline asmsub exit(ubyte returnvalue @A) { ; -- immediately exit the program with a return code in the A register ; TODO diff --git a/compiler/res/prog8lib/c128/syslib.p8 b/compiler/res/prog8lib/c128/syslib.p8 index 949c73c36..978ce877f 100644 --- a/compiler/res/prog8lib/c128/syslib.p8 +++ b/compiler/res/prog8lib/c128/syslib.p8 @@ -729,6 +729,19 @@ _longcopy }} } + inline asmsub irqsafe_set_irqd() { + %asm {{ + php + sei + }} + } + + inline asmsub irqsafe_clear_irqd() { + %asm {{ + plp + }} + } + inline asmsub exit(ubyte returnvalue @A) { ; -- immediately exit the program with a return code in the A register %asm {{ diff --git a/compiler/res/prog8lib/c64/syslib.p8 b/compiler/res/prog8lib/c64/syslib.p8 index 6796fa634..dc437f119 100644 --- a/compiler/res/prog8lib/c64/syslib.p8 +++ b/compiler/res/prog8lib/c64/syslib.p8 @@ -695,6 +695,19 @@ _longcopy }} } + inline asmsub irqsafe_set_irqd() { + %asm {{ + php + sei + }} + } + + inline asmsub irqsafe_clear_irqd() { + %asm {{ + plp + }} + } + inline asmsub exit(ubyte returnvalue @A) { ; -- immediately exit the program with a return code in the A register %asm {{ diff --git a/compiler/res/prog8lib/cx16/psg.p8 b/compiler/res/prog8lib/cx16/psg.p8 index 0d5a0c850..783539567 100644 --- a/compiler/res/prog8lib/cx16/psg.p8 +++ b/compiler/res/prog8lib/cx16/psg.p8 @@ -22,10 +22,7 @@ psg { ; waveform = one of PULSE,SAWTOOTH,TRIANGLE,NOISE. ; pulsewidth = 0-63. Specifies the pulse width for waveform=PULSE. envelope_states[voice_num] = 255 - %asm {{ - php - sei - }} + sys.irqsafe_set_irqd() cx16.r0 = $f9c2 + voice_num * 4 cx16.VERA_CTRL = 0 cx16.VERA_ADDR_L = lsb(cx16.r0) @@ -36,9 +33,7 @@ psg { cx16.VERA_DATA0 = waveform | pulsewidth envelope_volumes[voice_num] = mkword(volume, 0) envelope_maxvolumes[voice_num] = volume - %asm {{ - plp - }} + sys.irqsafe_clear_irqd() } ; sub freq_hz(ubyte voice_num, float hertz) { @@ -53,10 +48,7 @@ psg { ; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation. ; (https://github.com/x16community/x16-docs/blob/master/VERA%20Programmer's%20Reference.md) ; Write freq MSB first and then LSB to reduce the chance on clicks - %asm {{ - php - sei - }} + sys.irqsafe_set_irqd() cx16.r0 = $f9c1 + voice_num * 4 cx16.VERA_CTRL = 0 cx16.VERA_ADDR_L = lsb(cx16.r0) @@ -65,9 +57,7 @@ psg { cx16.VERA_DATA0 = msb(vera_freq) cx16.VERA_ADDR_L-- cx16.VERA_DATA0 = lsb(vera_freq) - %asm {{ - plp - }} + sys.irqsafe_clear_irqd() } sub volume(ubyte voice_num, ubyte vol) { diff --git a/compiler/res/prog8lib/cx16/syslib.p8 b/compiler/res/prog8lib/cx16/syslib.p8 index 63bc21a97..387b626e3 100644 --- a/compiler/res/prog8lib/cx16/syslib.p8 +++ b/compiler/res/prog8lib/cx16/syslib.p8 @@ -1084,6 +1084,19 @@ _longcopy }} } + inline asmsub irqsafe_set_irqd() { + %asm {{ + php + sei + }} + } + + inline asmsub irqsafe_clear_irqd() { + %asm {{ + plp + }} + } + inline asmsub exit(ubyte returnvalue @A) { ; -- immediately exit the program with a return code in the A register %asm {{ diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index e798c88d9..cd41c3094 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -87,6 +87,16 @@ sys (part of syslib) ``clear_irqd ()`` Clears the CPU status register Interrupt Disable flag. +``irqsafe_set_irqd ()`` + Sets the CPU status register Interrupt Disable flag, in a way that is safe to be used inside a IRQ handler. + Pair with ``irqsafe_clear_irqd()``. + +``irqsafe_clear_irqd ()`` + Clears the CPU status register Interrupt Disable flag, in a way that is safe to be used inside a IRQ handler. + Pair with ``irqsafe_set_irqd()``. Inside an IRQ handler this makes sure it doesn't inadvertently + clear the irqd status bit, and it can still be used inside normal code as well (where it *does* clear + the irqd status bit if it was cleared before entering). + ``progend ()`` Returns the last address of the program in memory + 1. Can be used to load dynamic data after the program, instead of hardcoding something. From e15bc68c9b3a9505e4f3e8c36362030f0bbff920 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 23 May 2023 00:27:42 +0200 Subject: [PATCH 3/7] added gfx2.fill() flood fill routine --- compiler/res/prog8lib/cx16/gfx2.p8 | 119 ++++++++++++++++++++++++++++- docs/source/libraries.rst | 1 + docs/source/todo.rst | 3 +- examples/cx16/testgfx2.p8 | 17 ++++- 4 files changed, 137 insertions(+), 3 deletions(-) diff --git a/compiler/res/prog8lib/cx16/gfx2.p8 b/compiler/res/prog8lib/cx16/gfx2.p8 index 21e33abf7..f87c280aa 100644 --- a/compiler/res/prog8lib/cx16/gfx2.p8 +++ b/compiler/res/prog8lib/cx16/gfx2.p8 @@ -547,7 +547,7 @@ _done } } - sub plot(uword @zp x, uword y, ubyte color) { + sub plot(uword @zp x, uword @zp y, ubyte @zp color) { ubyte[8] @shared bits = [128, 64, 32, 16, 8, 4, 2, 1] ubyte[4] @shared mask4c = [%00111111, %11001111, %11110011, %11111100] ubyte[4] @shared shift4c = [6,4,2,0] @@ -750,6 +750,123 @@ _done } } + sub fill(word @zp x, word @zp y, ubyte new_color) { + ; 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 = 48 + word[MAXDEPTH] @shared stack_xl + word[MAXDEPTH] @shared stack_xr + word[MAXDEPTH] @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 {{ + lda cx16.r12L + asl a + tay + lda sxl + sta stack_xl,y + lda sxl+1 + sta stack_xl+1,y + lda sxr + sta stack_xr,y + lda sxr+1 + sta stack_xr+1,y + lda sy + sta stack_y,y + lda sy+1 + sta stack_y+1,y + ldy cx16.r12L + lda sdy + sta 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] +;; y += dy + %asm {{ + dec cx16.r12L + lda cx16.r12L + asl a + tay + lda stack_xl,y + sta x1 + lda stack_xl+1,y + sta x1+1 + lda stack_xr,y + sta x2 + lda stack_xr+1,y + sta x2+1 + lda stack_y,y + sta y + lda stack_y+1,y + sta y+1 + ldy cx16.r12L + lda stack_dy,y + sta dy + }} + y+=dy + } + cx16.r11L = pget(x as uword, y as uword) ; old_color + if cx16.r11L == cx16.r10L + return + if x<0 or x > width-1 or y<0 or y > height-1 + return + push_stack(x, x, y, 1) + push_stack(x, x, y + 1, -1) + word left = 0 + while cx16.r12L { + pop_stack() + x = x1 + while x >= 0 and pget(x as uword, y as uword) == cx16.r11L { + plot(x as uword, y as uword, cx16.r10L) + x-- + } + if x>= x1 + goto skip + + left = x + 1 + if left < x1 + push_stack(left, x1 - 1, y, -dy) + x = x1 + 1 + + do { + while x <= width-1 and pget(x as uword, y as uword) == cx16.r11L { + plot(x as uword, y as uword, cx16.r10L) + x++ + } + push_stack(left, x - 1, y, dy) + if x > x2 + 1 + push_stack(x2 + 1, x - 1, y, -dy) +skip: + x++ + while x <= x2 and pget(x as uword, y as uword) != cx16.r11L + x++ + left = x + } until x>x2 + } + } + sub position(uword @zp x, uword y) { ubyte bank when active_mode { diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index cd41c3094..15bc9fef3 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -381,6 +381,7 @@ Full-screen multicolor bitmap graphics routines, available on the Cx16 machine o - clearing screen, switching screen mode, also back to text mode is possible. - drawing and reading individual pixels - drawing lines, rectangles, filled rectangles, circles, discs +- flood fill - drawing text inside the bitmap - in monochrome mode, it's possible to use a stippled drawing pattern to simulate a shade of gray. diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 2b661be60..bf51aae79 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -14,6 +14,8 @@ For 9.0 major changes - DONE: for loops now skip the whole loop if from value already outside the loop range (this is what all other programming languages also do) - DONE: asmsub params or return values passed in cpu flags (like carry) now must be declared as booleans (previously ubyte was still accepted). - DONE: (on cx16) added diskio.save_raw() to save without the 2 byte prg header +- DONE: added sys.irqsafe_xxx irqd routines +- DONE: added gfx2.fill() flood fill routine - [much work:] add special (u)word array type (or modifier such as @fast or @split? ) that puts the array into memory as 2 separate byte-arrays 1 for LSB 1 for MSB -> allows for word arrays of length 256 and faster indexing this is an enormous amout of work, if this type is to be treated equally as existing (u)word , because all expression / lookup / assignment routines need to know about the distinction.... @@ -61,7 +63,6 @@ Libraries: - c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking) - optimize several inner loops in gfx2 even further? - add modes 3 and perhaps even 2 to gfx2 (lores 16 color and 4 color)? -- add a flood fill (span fill/scanline fill) routine to gfx2? Expressions: diff --git a/examples/cx16/testgfx2.p8 b/examples/cx16/testgfx2.p8 index 5263e3b33..01a35c9aa 100644 --- a/examples/cx16/testgfx2.p8 +++ b/examples/cx16/testgfx2.p8 @@ -9,8 +9,10 @@ main { sub start() { + gfx2.screen_mode(4) + demofill() + sys.wait(2*60) gfx2.screen_mode(5) - demo1() sys.wait(2*60) demo2() @@ -19,6 +21,19 @@ main { txt.print("done!\n") } + sub demofill() { + + gfx2.circle(160, 120, 110, 1) + gfx2.rect(180, 5, 25, 190, 1) + gfx2.line(100, 150, 240, 10, 1) + gfx2.line(101, 150, 241, 10, 1) + ;gfx2.monochrome_stipple(true) + sys.wait(60) + gfx2.fill(100,100,2) + ;gfx2.monochrome_stipple(false) + gfx2.fill(182,140,4) + } + sub demo1() { uword yy = 10 uword xx From cdbccad21eccf012b1df1df33d5f0e11ce539fcf Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 23 May 2023 20:10:01 +0200 Subject: [PATCH 4/7] optimized gfx2 plot and horizontal_line a bit more --- compiler/res/prog8lib/cx16/gfx2.p8 | 46 ++++++++++++++++++++---------- examples/cx16/testgfx2.p8 | 4 ++- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/compiler/res/prog8lib/cx16/gfx2.p8 b/compiler/res/prog8lib/cx16/gfx2.p8 index f87c280aa..d98f614cd 100644 --- a/compiler/res/prog8lib/cx16/gfx2.p8 +++ b/compiler/res/prog8lib/cx16/gfx2.p8 @@ -155,6 +155,9 @@ gfx2 { } sub horizontal_line(uword x, uword y, uword length, ubyte color) { + ubyte[9] masked_ends = [ 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110, %11111111] + ubyte[9] masked_starts = [ 0, %00000001, %00000011, %00000111, %00001111, %00011111, %00111111, %01111111, %11111111] + if length==0 return when active_mode { @@ -163,12 +166,13 @@ gfx2 { ubyte separate_pixels = (8-lsb(x)) & 7 if separate_pixels as uword > length separate_pixels = lsb(length) - repeat separate_pixels { - ; TODO optimize this by writing a masked byte in 1 go - plot(x, y, color) - x++ + if separate_pixels { + position(x,y) + cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off + cx16.VERA_DATA0 = cx16.VERA_DATA0 | masked_starts[separate_pixels] + length -= separate_pixels + x += separate_pixels } - length -= separate_pixels if length { position(x, y) separate_pixels = lsb(length) & 7 @@ -205,11 +209,9 @@ _loop lda length bra _loop _done }} - repeat separate_pixels { - ; TODO optimize this by writing a masked byte in 1 go - plot(x, y, color) - x++ - } + + cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off + cx16.VERA_DATA0 = cx16.VERA_DATA0 | masked_ends[separate_pixels] } cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again } @@ -366,8 +368,14 @@ _done return position2(x,y,true) set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode - color &= 3 - color <<= gfx2.plot.shift4c[lsb(x) & 3] ; TODO with lookup table + ;; color &= 3 + ;; color <<= gfx2.plot.shift4c[lsb(x) & 3] + cx16.r2L = lsb(x) & 3 + when color & 3 { + 1 -> color = gfx2.plot.shiftedleft_4c_1[cx16.r2L] + 2 -> color = gfx2.plot.shiftedleft_4c_2[cx16.r2L] + 3 -> color = gfx2.plot.shiftedleft_4c_3[cx16.r2L] + } ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3] repeat lheight { %asm {{ @@ -551,6 +559,9 @@ _done ubyte[8] @shared bits = [128, 64, 32, 16, 8, 4, 2, 1] ubyte[4] @shared mask4c = [%00111111, %11001111, %11110011, %11111100] ubyte[4] @shared shift4c = [6,4,2,0] + ubyte[4] shiftedleft_4c_1 = [1<<6, 1<<4, 1<<2, 1<<0] + ubyte[4] shiftedleft_4c_2 = [2<<6, 2<<4, 2<<2, 2<<0] + ubyte[4] shiftedleft_4c_3 = [3<<6, 3<<4, 3<<2, 3<<0] when active_mode { 1 -> { @@ -643,8 +654,13 @@ _done ; TODO also mostly usable for lores 4c? void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte) cx16.r2L = lsb(x) & 3 ; xbits - color &= 3 - color <<= shift4c[cx16.r2L] ; TODO with lookup table + ; color &= 3 + ; color <<= shift4c[cx16.r2L] + when color & 3 { + 1 -> color = shiftedleft_4c_1[cx16.r2L] + 2 -> color = shiftedleft_4c_2[cx16.r2L] + 3 -> color = shiftedleft_4c_3[cx16.r2L] + } %asm {{ stz cx16.VERA_CTRL lda cx16.r1L @@ -743,7 +759,7 @@ _done sta cx16.r0L }} cx16.r1L = lsb(x) & 3 - cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L] ; TODO with lookup table + cx16.r0L >>= gfx2.plot.shift4c[cx16.r1L] return cx16.r0L & 3 } else -> return 0 diff --git a/examples/cx16/testgfx2.p8 b/examples/cx16/testgfx2.p8 index 01a35c9aa..3f72fc70f 100644 --- a/examples/cx16/testgfx2.p8 +++ b/examples/cx16/testgfx2.p8 @@ -31,7 +31,9 @@ main { sys.wait(60) gfx2.fill(100,100,2) ;gfx2.monochrome_stipple(false) - gfx2.fill(182,140,4) + gfx2.fill(182,140,3) + gfx2.fill(182,40,1) + } sub demo1() { From 48864ad6cfe6036ce2bda02dd5d6fe20b67b3df9 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 23 May 2023 20:42:36 +0200 Subject: [PATCH 5/7] add a unit test that checks for 64tass availability --- codeGenCpu6502/test/TestCodegen.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/codeGenCpu6502/test/TestCodegen.kt b/codeGenCpu6502/test/TestCodegen.kt index 92b1b541b..f65522269 100644 --- a/codeGenCpu6502/test/TestCodegen.kt +++ b/codeGenCpu6502/test/TestCodegen.kt @@ -1,5 +1,6 @@ package prog8tests.codegencpu6502 +import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import prog8.code.SymbolTableMaker @@ -102,5 +103,14 @@ class TestCodegen: FunSpec({ result.name shouldBe "test" Files.deleteIfExists(Path("${result.name}.asm")) } + + test("64tass assembler available? - if this fails you need to install 64tass in the path") { + val command = mutableListOf("64tass", "--version") + shouldNotThrowAny { + val proc = ProcessBuilder(command).inheritIO().start() + val result = proc.waitFor() + result.shouldBe(0) + } + } }) From 5c75b19c5da4816184ad0cfd8d87cf7c6ec4fbdd Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 26 May 2023 19:13:21 +0200 Subject: [PATCH 6/7] fix kotlin version IDE warning --- .idea/kotlinc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index bd5867845..ee8249b2b 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -4,6 +4,6 @@