optimized palette module

removed palette.set_monochrome(), added start color index to several color set functions
removed mcf example
update gradle wrapper
This commit is contained in:
Irmen de Jong 2024-12-06 22:00:51 +01:00
parent 2e303041c1
commit b2e821755c
15 changed files with 114 additions and 776 deletions

View File

@ -5,55 +5,86 @@
palette {
%option ignore_unused
uword vera_palette_ptr
sub set_color(ubyte index, uword color) {
vera_palette_ptr = $fa00+(index as uword * 2)
cx16.vpoke(1, vera_palette_ptr, lsb(color))
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(color))
cx16.vaddr(1, $fa00+(index as uword * 2), 0, 1)
cx16.VERA_DATA0 = lsb(color)
cx16.VERA_DATA0 = msb(color)
cx16.VERA_ADDR_H &= 1
}
sub get_color(ubyte index) -> uword {
vera_palette_ptr = $fa00+(index as uword * 2)
return mkword(cx16.vpeek(1, vera_palette_ptr+1), cx16.vpeek(1, vera_palette_ptr))
cx16.vaddr(1, $fa00+(index as uword * 2), 0, 1)
cx16.r0L = cx16.VERA_DATA0
cx16.r0H = cx16.VERA_DATA0
cx16.VERA_ADDR_H &= 1
return cx16.r0
}
sub set_rgb_be(uword palette_ptr, uword num_colors) {
sub set_rgb_be(uword palette_ptr, uword num_colors, ubyte startindex) {
; 1 word per color entry, $0rgb in big endian format
vera_palette_ptr = $fa00
cx16.vaddr(1, $fa00+(startindex as uword * 2), 0, 1)
repeat num_colors {
cx16.vpoke(1, vera_palette_ptr+1, @(palette_ptr))
palette_ptr++
cx16.vpoke(1, vera_palette_ptr, @(palette_ptr))
palette_ptr++
vera_palette_ptr+=2
cx16.VERA_DATA0 = @(palette_ptr+1)
cx16.VERA_DATA0 = @(palette_ptr)
palette_ptr += 2
}
cx16.VERA_ADDR_H &= 1
}
sub set_rgb(uword palette_words_ptr, uword num_colors) {
sub set_rgb(uword palette_words_ptr, uword num_colors, ubyte startindex) {
; 1 word per color entry (in little endian format as layed out in video memory, so $gb;$0r)
vera_palette_ptr = $fa00
repeat num_colors*2 {
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
cx16.vaddr(1, $fa00+(startindex as uword * 2), 0, 1)
repeat num_colors {
cx16.VERA_DATA0 = @(palette_words_ptr)
palette_words_ptr++
cx16.VERA_DATA0 = @(palette_words_ptr)
palette_words_ptr++
vera_palette_ptr++
}
cx16.VERA_ADDR_H &= 1
}
sub set_rgb8(uword palette_bytes_ptr, uword num_colors) {
sub set_rgb8(uword palette_bytes_ptr, uword num_colors, ubyte startindex) {
; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel.
vera_palette_ptr = $fa00
cx16.vaddr(1, $fa00+(startindex as uword * 2), 0, 1)
ubyte red
ubyte greenblue
repeat num_colors {
cx16.r1 = color8to4(palette_bytes_ptr)
palette_bytes_ptr+=3
cx16.vpoke(1, vera_palette_ptr, cx16.r1H) ; $GB
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, cx16.r1L) ; $0R
vera_palette_ptr++
cx16.VERA_DATA0 = cx16.r1H ; $GB
cx16.VERA_DATA0 = cx16.r1L ; $0R
}
cx16.VERA_ADDR_H &= 1
}
sub set_all_black() {
cx16.vaddr(1, $fa00, 0, 1)
repeat 256 {
cx16.VERA_DATA0 = 0
cx16.VERA_DATA0 = 0
}
cx16.VERA_ADDR_H &= 1
}
sub set_all_white() {
cx16.vaddr(1, $fa00, 0, 1)
repeat 256 {
cx16.VERA_DATA0 = $ff
cx16.VERA_DATA0 = $0f
}
cx16.VERA_ADDR_H &= 1
}
sub set_grayscale(ubyte startindex) {
; set 16 consecutive colors to a grayscale gradient from black to white
cx16.vaddr(1, $fa00+(startindex as uword * 2), 0, 1)
cx16.r0L=0
repeat 16 {
cx16.VERA_DATA0 = cx16.r0L
cx16.VERA_DATA0 = cx16.r0L
cx16.r0L += $11
}
cx16.VERA_ADDR_H &= 1
}
sub color8to4(uword colorpointer) -> uword {
@ -70,42 +101,6 @@ palette {
return msb(channelvalue * $000f + 135)
}
sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) {
vera_palette_ptr = $fa00
cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB)) ; G,B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(screencolorRGB)) ; R
vera_palette_ptr++
repeat 255 {
cx16.vpoke(1, vera_palette_ptr, lsb(drawcolorRGB)) ; G,B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(drawcolorRGB)) ; R
vera_palette_ptr++
}
}
sub set_all_black() {
set_monochrome($000, $000)
}
sub set_all_white() {
set_monochrome($fff, $fff)
}
sub set_grayscale() {
; set first 16 colors to a grayscale gradient from black to white
vera_palette_ptr = $fa00
cx16.r2L=0
repeat 16 {
cx16.vpoke(1, vera_palette_ptr, cx16.r2L)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, cx16.r2L)
vera_palette_ptr++
cx16.r2L += $11
}
}
sub fade_step_multi(ubyte startindex, ubyte endindex, uword target_rgb) -> bool {
; Perform one color fade step for multiple consecutive palette entries.
; startindex = palette index of first color to fade
@ -210,7 +205,7 @@ palette {
$76e, ; 14 = light blue
$bbb ; 15 = light grey
]
set_rgb(colors, len(colors))
set_rgb(colors, len(colors), 0)
}
sub set_c64ntsc() {
@ -233,7 +228,7 @@ palette {
$36f, ; 14 = light blue
$ccc ; 15 = light grey
]
set_rgb(colors, len(colors))
set_rgb(colors, len(colors), 0)
}
sub set_default16() {
@ -256,6 +251,6 @@ palette {
$08f, ; 14 = light blue
$bbb ; 15 = light grey
]
set_rgb(colors, len(colors))
set_rgb(colors, len(colors), 0)
}
}

View File

@ -106,7 +106,6 @@ class TestCompilerOnExamplesCx16: FunSpec({
val onlyCx16 = cartesianProduct(
listOf(
"chunkedfile/demo",
"vtui/testvtui",
"pcmaudio/play-adpcm",
"pcmaudio/stream-wav",

View File

@ -12,7 +12,6 @@ Future Things and Ideas
- a syntax to access specific bits in a variable, to avoid manually shifts&ands, something like variable[4:8] ? (or something else this may be too similar to regular array indexing)
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
- on the C64: make the floating point routines @banked so that basic can be permanently banked out even if you use floats? But this will crash when the call is done from program code at $a000+
- Libraries: improve ability to create library files in prog8; for instance there's still stuff injected into the start of the start() routine AND there is separate setup logic going on before calling it.
Make up our mind! Maybe all setup does need to be put into start() ? because the program cannot function correctly when the variables aren't initialized properly bss is not cleared etc. etc.
Add a -library $xxxx command line option to preselect every setting that is required to make a library at $xxxx rather than a normal loadable and runnable program?

View File

@ -11,8 +11,7 @@ main {
gfx_hires.graphics_mode() ; select 640*480 mode, 4 colors
mouse.set_pointer_image()
cx16.mouse_config(-1, 640/8, 240/8)
uword[4] amigacolors = [$aaa, $000, $fff, $68c] ; gray, black, white, lightblue
palette.set_rgb(amigacolors, len(amigacolors))
palette.set_rgb([$aaa, $000, $fff, $68c], 4, 0) ; set Amiga's workbench 2.0 gray, black, white, lightblue colors
cx16.VERA_DC_VSCALE = 64 ; have the vertical resolution so it is 640*240 - more or less Amiga's default non interlaced mode
gfx_hires.text_charset(1)

View File

@ -1,5 +0,0 @@
testdata/*TITLESCREEN.*
*.mcf
*.asm
*.prg
*.vice-mon-list

View File

@ -1,300 +0,0 @@
import struct
from typing import Sequence
# Chunk types:
# user types: 0 - 239
# reserved: 240 - 248
CHUNK_DUMMY = 249
CHUNK_BONKRAM = 250
CHUNK_SYSTEMRAM = 251
CHUNK_VIDEORAM = 252
CHUNK_PAUSE = 253
CHUNK_EOF = 254
CHUNK_IGNORE = 255
ChunksWithData = {CHUNK_DUMMY, CHUNK_SYSTEMRAM, CHUNK_VIDEORAM, CHUNK_BONKRAM}
class LoadList:
def __init__(self):
self.data = bytearray([CHUNK_IGNORE] * 256)
self.data[0:4] = struct.pack("ccBB", b'L', b'L', 1, 0)
self.index = 4
def __eq__(self, other) -> bool:
return isinstance(other, LoadList) and self.data == other.data
def add_chunk(self, chunktype: int, size: int, bank: int = 0, address: int = 0) -> None:
if chunktype < 0 or chunktype > 255:
raise ValueError("chunktype must be 0 - 255")
if size < 0 or size > 65535:
raise ValueError(f"size must be 0 - 65535 bytes")
if bank < 0 or bank > 255:
raise ValueError("bank must be 0 - 255")
if address < 0 or address > 65535:
raise ValueError("address must be 0 - 65535")
data = struct.pack("<BHBH", chunktype, size, bank, address)
if self.index + len(data) > 255:
raise IndexError("too many chunks")
self.data[self.index: self.index + len(data)] = data
self.index += len(data)
def read(self, data: bytes) -> None:
if len(data) != 256:
raise ValueError("data must be 256 bytes long")
self.data[0:256] = data
self.index = 256
def parse(self) -> Sequence[tuple]:
if self.data[0] != ord('L') or self.data[1] != ord('L') or self.data[2] != 1:
raise ValueError("invalid loadlist identifier", self.data[:4])
index = 4
chunks = []
while index < 256:
chunktype = self.data[index]
if chunktype == CHUNK_IGNORE:
index += 1 # pad byte
elif chunktype == CHUNK_EOF:
chunks.append((CHUNK_EOF, 0, 0, 0))
return chunks
else:
size, bank, address = struct.unpack("<HBH", self.data[index + 1:index + 6])
chunks.append((chunktype, size, bank, address))
index += 6
return chunks
def is_empty(self) -> bool:
return self.index <= 4
class MultiChunkFile:
def __init__(self):
self.chunks = []
self.loadlist = LoadList()
self.datachunks = []
def read(self, filename: str) -> None:
num_loadlist = 0
num_data = 0
data_total = 0
file_total = 0
with open(filename, "rb") as inf:
while True:
data = inf.read(256)
if len(data) == 0:
break
loadlist = LoadList()
loadlist.read(data)
self.chunks.append(loadlist)
num_loadlist += 1
file_total += len(data)
for chunk in loadlist.parse():
if chunk[0] == CHUNK_EOF:
break
elif chunk[0] in ChunksWithData or chunk[0] < 240:
data = inf.read(chunk[1])
self.chunks.append(data)
num_data += 1
data_total += len(data)
file_total += len(data)
self.validate()
print("Read", filename)
print(f" {num_loadlist} loadlists, {num_data} data chunks")
print(f" total data size: {data_total} (${data_total:x})")
print(f" total file size: {file_total} (${file_total:x})")
def write(self, filename: str) -> None:
self.flush_ll()
self.validate()
num_loadlist = 0
num_data = 0
data_total = 0
file_total = 0
with open(filename, "wb") as out:
for chunk in self.chunks:
if isinstance(chunk, LoadList):
num_loadlist += 1
out.write(chunk.data)
file_total += len(chunk.data)
elif isinstance(chunk, bytes):
num_data += 1
out.write(chunk)
file_total += len(chunk)
data_total += len(chunk)
else:
raise TypeError("invalid chunk type")
print("Written", filename)
print(f" {num_loadlist} loadlists, {num_data} data chunks")
print(f" total data size: {data_total} (${data_total:x})")
print(f" total file size: {file_total} (${file_total:x})")
def validate(self) -> None:
if len(self.chunks) < 2:
raise ValueError("must be at least 2 chunks", self.chunks)
chunk_iter = iter(self.chunks)
eof_found = False
while not eof_found:
try:
chunk = next(chunk_iter)
except StopIteration:
raise ValueError("missing EOF chunk")
else:
if isinstance(chunk, LoadList):
for lc in chunk.parse():
if lc[0] == CHUNK_EOF:
eof_found = True
break
elif lc[0] in ChunksWithData or lc[0] < 240:
size, bank, address = lc[1:]
data = next(chunk_iter)
if isinstance(data, bytes):
if len(data) != size:
raise ValueError("data chunk size mismatch")
else:
raise TypeError("expected data chunk")
else:
raise TypeError("expected LoadList chunk")
try:
next(chunk_iter)
except StopIteration:
pass
else:
raise ValueError("trailing chunks")
def add_Dummy(self, size: int) -> None:
self.add_chunk(CHUNK_DUMMY, data=bytearray(size))
def add_SystemRam(self, bank: int, address: int, data: bytes, chunksize: int = 0xfe00) -> None:
if address >= 0xa000 and address < 0xc000:
raise ValueError("use add_BankedRam instead to load chunks into banked ram $a000-$c000")
if bank < 0 or bank > 31:
raise ValueError("bank must be 0 - 31")
while data:
if address >= 65536:
raise ValueError("data too large for system ram")
self.add_chunk(CHUNK_SYSTEMRAM, bank, address, data[:chunksize])
data = data[chunksize:]
address += chunksize
def add_BankedRam(self, bank: int, address: int, data: bytes, chunksize: int = 0x2000) -> None:
if address < 0xa000 or address >= 0xc000:
raise ValueError("use add_SystemRam instead to load chunks into normal system ram")
if chunksize > 0x2000:
raise ValueError("chunksize too large for banked ram, max 8K")
while data:
if address >= 0xc000:
address -= 0x2000
bank += 1
if bank >= 32:
raise ValueError("data too large for banked ram")
self.add_chunk(CHUNK_SYSTEMRAM, bank, address, data[:chunksize])
data = data[chunksize:]
address += chunksize
def add_BonkRam(self, bonk: int, address: int, data: bytes, chunksize: int = 0x4000) -> None:
if bonk < 32 or bonk > 255:
raise ValueError("bank for bonk ram (cartridge ram) must be 32 - 255")
if chunksize > 0x4000:
raise ValueError("chunksize too large for bonk ram (cartridge ram), max 16K")
if address < 0xc000 or address > 0xffff:
raise ValueError("use add_SystemRam instead to load chunks into normal system ram")
while data:
if address > 0xffff:
address -= 0x4000
bonk += 1
if bonk > 255:
raise ValueError("data too large for bonk ram (cartridge ram)")
self.add_chunk(CHUNK_BONKRAM, bonk, address, data[:chunksize])
data = data[chunksize:]
address += chunksize
def add_VideoRam(self, bank: int, address: int, data: bytes, chunksize: int = 0xfe00) -> None:
if bank < 0 or bank > 1:
raise ValueError("bank for videoram must be 0 or 1")
while data:
if address >= 65536:
address -= 65536
bank += 1
if bank >= 2:
raise ValueError("data too large for video ram")
self.add_chunk(CHUNK_VIDEORAM, bank, address, data[:chunksize])
data = data[chunksize:]
address += chunksize
def add_User(self, chunktype: int, data: bytes) -> None:
if chunktype < 0 or chunktype > 239:
raise ValueError("user chunk type must be 0 - 239")
if len(data) > 65535:
raise ValueError("data too large to fit in a single chunk")
self.add_chunk(chunktype, data=data)
def add_Pause(self, code: int) -> None:
self.add_chunk_nodata(CHUNK_PAUSE, code)
def add_EOF(self) -> None:
self.add_chunk_nodata(CHUNK_EOF, 0)
def add_chunk(self, chunktype: int, bank: int = 0, address: int = 0, data: bytes = b"") -> None:
try:
self.loadlist.add_chunk(chunktype, len(data), bank, address)
except IndexError:
# loadlist is full, flush everything out and create a new one and put it in there.
self.flush_ll()
self.loadlist.add_chunk(chunktype, len(data), bank, address)
if data:
self.datachunks.append(bytes(data))
def add_chunk_nodata(self, chunktype: int, code: int = 0) -> None:
try:
self.loadlist.add_chunk(chunktype, code, 0, 0)
except IndexError:
# loadlist is full, flush everything out and create a new one and put it in there.
self.flush_ll()
self.loadlist.add_chunk(chunktype, code, 0, 0)
def flush_ll(self) -> None:
if not self.loadlist.is_empty():
self.chunks.append(self.loadlist)
self.chunks.extend(self.datachunks)
self.loadlist = LoadList()
self.datachunks.clear()
if __name__ == "__main__":
try:
bitmap1 = open("testdata/ME-TITLESCREEN.BIN", "rb").read()
bitmap2 = open("testdata/DS-TITLESCREEN.BIN", "rb").read()
palette1 = open("testdata/ME-TITLESCREEN.PAL", "rb").read()
palette2 = open("testdata/DS-TITLESCREEN.PAL", "rb").read()
except IOError:
print("""ERROR: cannot load the demo data files.
You'll need to put the titlescreen data files from the 'musicdemo' project into the testdata directory.
The musicdemo is on github: https://github.com/irmen/cx16musicdemo
The four files are the two ME- and the two DS- TITLESCREEN.BIN and .PAL files, and are generated by running the makefile in that project.""")
raise SystemExit
program = bytearray(3333)
textdata = b"hello this is a demo text.... \x00"
mcf = MultiChunkFile()
for _ in range(4):
mcf.add_User(42, bytearray(999))
mcf.add_SystemRam(0, 0x4000, program)
mcf.add_BankedRam(10, 0xa000, textdata)
mcf.add_Pause(444)
for _ in range(4):
mcf.add_User(42, bytearray(999))
mcf.add_VideoRam(1, 0xfa00, palette1)
mcf.add_VideoRam(0, 0, bitmap1)
mcf.add_Pause(333)
mcf.add_VideoRam(1, 0xfa00, palette2)
mcf.add_VideoRam(0, 0, bitmap2)
mcf.add_Pause(222)
mcf.add_BonkRam(32, 0xc000, bytearray(32768))
mcf.add_Pause(111)
mcf.add_EOF()
mcf.write("demo.mcf")
print("Verifying file...")
mcf2 = MultiChunkFile()
mcf2.read("demo.mcf")
assert mcf2.chunks == mcf.chunks

View File

@ -1,69 +0,0 @@
%import diskio
%import textio
%import mcf
%zeropage basicsafe
main {
sub start() {
uword duration
ubyte[256] bonkbuffer
;;diskio.fastmode(1)
set_screen()
cbm.SETTIM(0,0,0)
mcf.set_callbacks(mcf_get_buffer, mcf_process_chunk) ; not needed if the stream has no custom chunk types
mcf.set_bonkbuffer(bonkbuffer)
if mcf.open("demo.mcf", 8, 2) {
repeat {
mcf.stream()
if_cs {
break ; EOF reached, stop the streaming loop
} else {
; PAUSE chunk encountered, code is in cx16.r0
}
}
mcf.close()
}
duration = cbm.RDTIM16()
cbm.CINT()
txt.print("done. ")
txt.print_uw(duration)
txt.print(" jiffies.\n")
}
sub set_screen() {
; 640x400 16 colors
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_CTRL = %00000010
cx16.VERA_DC_VSTART = 20
cx16.VERA_DC_VSTOP = 400 /2 -1 + 20 ; clip off screen that overflows vram
cx16.VERA_L1_CONFIG = %00000110 ; 16 colors bitmap mode
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001 ; hires
}
asmsub mcf_get_buffer(ubyte chunktype @A, uword size @XY) -> ubyte @A, uword @XY, bool @Pc {
%asm {{
ldx #<$a000
ldy #>$a000
lda #10
clc
rts
}}
}
asmsub mcf_process_chunk() -> bool @Pc {
; process the chunk that was loaded in the location returned by the previous call to mcf_get_buffer()
%asm {{
clc
rts
}}
}
}

View File

@ -1,206 +0,0 @@
%import syslib
%import strings
; Streaming routine for MCF files (multipurpose chunk format):
; 1. call open()
; 2. set callbacks if needed; set_callbacks()
; 3. set bonk ram (cartridge ram) load buffer, if needed; set_bonkbuffer()
; 4. call stream() in a loop
; 5. call close() if you want to cleanup halfway through for some reason
mcf {
uword loadlist_buf = memory("loadlist", 256, 0)
uword @zp loadlist_ptr
uword bonkbuffer
bool needs_loadlist
ubyte file_channel
sub open(str filename, ubyte drive, ubyte channel) -> bool {
file_channel = channel
cbm.SETNAM(strings.length(filename), filename)
cbm.SETLFS(channel, drive, 2)
void cbm.OPEN()
if_cc {
if cbm.READST()==0 {
void cbm.CHKIN(channel)
loadlist_ptr = loadlist_buf
needs_loadlist = true
return true
}
}
close()
return false
}
sub close() {
cbm.CLRCHN()
cbm.CLOSE(file_channel)
}
asmsub set_callbacks(uword getbuffer_routine @R0, uword processchunk_routine @R1) {
%asm {{
lda cx16.r0
ldy cx16.r0+1
sta p8b_mcf.p8s_stream.getbuffer_call+1
sty p8b_mcf.p8s_stream.getbuffer_call+2
lda cx16.r1
ldy cx16.r1+1
sta p8b_mcf.p8s_stream.processchunk_call+1
sty p8b_mcf.p8s_stream.processchunk_call+2
rts
}}
}
sub set_bonkbuffer(uword buffer) {
; needs to be a buffer of 256 bytes (1 page)
bonkbuffer = buffer
}
sub stream() {
repeat {
if needs_loadlist {
if not read_loadlist() {
sys.set_carry()
return
}
needs_loadlist = false
}
while (loadlist_ptr-loadlist_buf)<256 {
when @(loadlist_ptr) {
255 -> {
; simply ignore this byte
loadlist_ptr++
}
254 -> {
; End of File
close()
sys.set_carry()
return
}
253 -> {
; pause streaming
cx16.r0 = peekw(loadlist_ptr+1)
loadlist_ptr+=6
sys.clear_carry()
return
}
252 -> {
; load into vram
blockload_vram(peekw(loadlist_ptr+1), peek(loadlist_ptr+3), peekw(loadlist_ptr+4))
loadlist_ptr+=6
}
251 -> {
; load into system ram
blockload_ram(peekw(loadlist_ptr+1), peek(loadlist_ptr+3), peekw(loadlist_ptr+4))
loadlist_ptr+=6
}
250 -> {
; bonk ram (cartridge ram)
; This cannot use MACPTR (because the kernal rom isn't banked in)
; so we have to load it into a buffer and copy it manually.
; Because this will be rarely used, the buffer is not allocated here to save memory, and instead
; the user has to set it with the config routine when the program wants to use this chunk type.
blockload_bonkram(peekw(loadlist_ptr+1), peek(loadlist_ptr+3), peekw(loadlist_ptr+4))
loadlist_ptr+=6
}
249 -> {
; dummy chunk
blockload_dummy(peekw(loadlist_ptr+1))
loadlist_ptr+=6
}
else -> {
; custom chunk
uword @shared chunksize = peekw(loadlist_ptr+1)
%asm {{
lda (p8b_mcf.p8v_loadlist_ptr)
ldx p8v_chunksize
ldy p8v_chunksize+1
getbuffer_call jsr $ffff ; modified
bcc +
rts ; fail - exit as if EOF
+ sta cx16.r1L
stx cx16.r0
sty cx16.r0+1
}}
blockload_ram(chunksize, cx16.r1L, cx16.r0)
loadlist_ptr += 6
%asm {{
processchunk_call jsr $ffff ; modified
bcc +
rts
+
}}
}
}
}
needs_loadlist = true
}
}
sub read_loadlist() -> bool {
blockload_ram(256, cx16.getrambank(), loadlist_buf)
if loadlist_buf[0]!=sc:'L' or loadlist_buf[1]!=sc:'L' or loadlist_buf[2]!=1
return false ; header error
loadlist_ptr = loadlist_buf+4
}
sub blockload_vram(uword size, ubyte bank, uword address) {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_L = lsb(address)
cx16.VERA_ADDR_M = msb(address)
cx16.VERA_ADDR_H = bank | %00010000 ; enable vera auto increment
while size!=0 {
size -= readblock(size, &cx16.VERA_DATA0, true)
}
}
sub blockload_dummy(uword size) {
ubyte buffer
while size!=0 {
size -= readblock(size, &buffer, true)
}
}
sub blockload_ram(uword size, ubyte bank, uword address) {
ubyte orig_ram_bank = cx16.getrambank()
cx16.rambank(bank)
cx16.r3 = address
while size!=0 {
cx16.r2 = readblock(size, cx16.r3, false)
size -= cx16.r2
cx16.r3 += cx16.r2
}
cx16.rambank(orig_ram_bank)
}
sub blockload_bonkram(uword size, ubyte bonk, uword address) {
ubyte orig_rom_bank = cx16.getrombank()
cx16.r3 = address
while size!=0 {
ubyte readsize = 255
if msb(size)==0
readsize = lsb(size)
void, cx16.r2 = cx16.MACPTR(readsize, bonkbuffer, false) ; can't MACPTR directly to bonk ram
cx16.rombank(bonk)
sys.memcopy(bonkbuffer, cx16.r3, cx16.r2) ; copy to bonk ram
cx16.rombank(orig_rom_bank)
size -= cx16.r2
cx16.r3 += cx16.r2
}
}
sub readblock(uword size, uword address, bool dontAdvance) -> uword {
if msb(size)>=2 {
void, cx16.r0 = cx16.MACPTR(0, address, dontAdvance) ; read 512 bytes
return cx16.r0
}
if msb(size)!=0 {
void, cx16.r0 = cx16.MACPTR(255, address, dontAdvance) ; read 255 bytes
return cx16.r0
}
void, cx16.r0 = cx16.MACPTR(lsb(size), address, dontAdvance) ; read remaining number of bytes
return cx16.r0
}
}

View File

@ -1,99 +0,0 @@
# Multipurpose Chunked File
## Goals
- meant for the Commander X16 system, depends on working MACPTR kernal call for fast loading.
- single file, possibly megabytes in size, that contains various kinds of data to be loaded or streamed into different locations.
- simple file format with few special cases.
- no random access, only sequential reading/streaming.
- simple user code that doesn't have to bother with the I/O at all.
- a few chunk types that can be handled automatically to load data into system ram, banked ram, or video ram.
- custom chunk types for flexibility or extensibility.
Theoretical optimal chunk size is 512 bytes but actual size may be different. (In practice there seems to be no significant speed impact)
MCF files are meant to be created using a tool on PC, and only being read on the X16.
A Python tool is provided to create a demo MCF file.
A proof of concept Prog8 library module and example program is provided to consume that demo MCF file on the X16.
## File Format
This is the MCF file format:
| Chunk | size (bytes) |
|------------|--------------|
| LoadList | 256 |
| Data | 1 - 65535 |
| Data | 1 - 65535 |
| ... | |
| LoadList | 256 |
| Data | 1 - 65535 |
| Data | 1 - 65535 |
| ... | |
and so on.
There is no limit to the number of chunks and the size of the file, as long as it fits on the disk.
### LoadList chunk
This chunk is a list of what kinds of chunks occur in the file after it.
It starts with a small identification header:
| data | meaning |
|---------|---------------------|
| 2 bytes | 'LL' (76, 76) |
| byte | version (1 for now) |
| byte | reserved (0) |
Then a sequence of 1 or more chunk specs (6 bytes each), as long as it still fits in 256 bytes:
| data | meaning |
|----------------------|--------------------------------------------|
| byte | chunk type |
| word (little-endian) | chunk size |
| byte | bank number (used for some chunk types) |
| word (little-endian) | memory address (used for some chunk types) |
Total 6 bytes per occurrence. Any remaining unused bytes in the 256 bytes LoadList chunk are to be padded with byte 255.
If there are more chunks in the file than fit in a single loadlist, we simply add another loadlist block and continue there.
(The file only ends once an End Of File chunk type is encountered in a loadlist.)
### Chunk types
| chunk type | meaning |
|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 0 - 239 | custom chunk types. See below. |
| 240 - 248 | reserved for future system chunk types. |
| 249 | dummy chunk: read a chunk of the specified number of bytes but don't do anything with it. Useful to realign the file I/O on disk block size. |
| 250 | bonk ram load: use banknumber + address to set the Cartridge RAM ('bonk' RAM) bank and load address and loads the chunk there, then continue streaming. Note this is slower than other types of ram. Rquires 1 page of user chosen buffer area. |
| 251 | system ram load: use banknumber + address to set the RAM bank and load address and loads the chunk there, then continue streaming. |
| 252 | video ram load: use banknumber + address to set the Vera VRAM bank (hi byte) and load address (mid+lo byte) and loads the chunk into video ram there, then continue streaming. |
| 253 | pause streaming. Returns from stream routine with pause status: Carry=clear. And reg.r0=size. until perhaps the program calls the stream routine again to resume. |
| 254 | end of file. Closes the file and stops streaming: returns from stream routine with exit status: Carry=set. |
| 255 | ignore this byte. Used to pad out the loadlist chunk to 256 bytes. |
### Custom chunk types (0 - 239)
When such a custom chunk type is encountered, a user routine is called to get the load address for the chunk data,
then the chunk is loaded into that buffer, and finally a user routine is called to process the chunk data.
The size can be zero, so that the chunk type acts like a simple notification flag for the main program to do something.
The first routine has the following signature:
**get_buffer()**:
Arguments: reg.A = chunk type, reg.XY = chunksize.
Returns: Carry flag=success (set = fail, clear = ok), ram bank in reg.A, memory address in reg.XY.
The second routine has the following signature:
**process_chunk()**:
Arguments: none (save them from the get_buffer call if needed).
Returns: Carry flag=success (set = fail, clear = ok).
These routines are provided to the streaming routine as callback addresses (ram bank number + address to call).
If any of these routines returns Carry set (error status) the streaming routine halts, otherwise it keeps on going.
The streaming continues until an End of File chunk type is encountered in the loadlist.

View File

@ -1,3 +0,0 @@
You'll need to put the titlescreen data files from the 'musicdemo' project into this directory.
The musicdemo is on GitHub: https://github.com/irmen/cx16musicdemo
The four files are the two ME- and the two DS- TITLESCREEN.BIN and .PAL files, and are generated by running the makefile in that project.

View File

@ -21,7 +21,7 @@ main {
$312, $211, $100
]
palette.set_rgb(&colors, len(colors))
palette.set_rgb(&colors, len(colors), 0)
gfx_lores.graphics_mode() ; lores 256 colors
cx16.VERA_DC_VSCALE = 0 ; display trick spoiler.......: stretch 1 line of display all the way to the bottom
cx16.enable_irq_handlers(true)

View File

@ -33,7 +33,7 @@ main {
; (you could do a soft fade-in effect with this for instance)
bmx.palette_buffer_ptr = memory("palette", 512, 0)
sys.memset(bmx.palette_buffer_ptr, 512, 0)
palette.set_rgb(bmx.palette_buffer_ptr, 256)
palette.set_rgb(bmx.palette_buffer_ptr, 256, 0)
; switch to bitmap screen mode and color depth: 320*240
void cx16.screen_mode($80, false) ; we're lazy and just use a kernal routine to set up the basics

View File

@ -1,26 +1,51 @@
%import textio
%import palette
%zeropage basicsafe
%option no_sysinit
main {
uword[] @shared colors = [
$000, ; 0 = black
$fff, ; 1 = white
$800, ; 2 = red
$afe, ; 3 = cyan
$c4c, ; 4 = purple
$0c5, ; 5 = green
$00a, ; 6 = blue
$ee7, ; 7 = yellow
$d85, ; 8 = orange
$640, ; 9 = brown
$f77, ; 10 = light red
$333, ; 11 = dark grey
$777, ; 12 = medium grey
$af6, ; 13 = light green
$08f, ; 14 = light blue
$bbb ; 15 = light grey
]
ubyte[] @shared colors_b = [
$00, $00, ; 0 = black
$0f, $ff, ; 1 = white
$08, $00, ; 2 = red
$0a, $fe, ; 3 = cyan
$0c, $4c, ; 4 = purple
$00, $c5, ; 5 = green
$00, $0a, ; 6 = blue
$0e, $e7, ; 7 = yellow
$0d, $85, ; 8 = orange
$06, $40, ; 9 = brown
$0f, $77, ; 10 = light red
$03, $33, ; 11 = dark grey
$07, $77, ; 12 = medium grey
$0a, $f6, ; 13 = light green
$00, $8f, ; 14 = light blue
$0b, $bb ; 15 = light grey
]
sub start() {
if (cx16.r0 & 1) as bool == false
cx16.r1++
else
cx16.r2++
if (cx16.r0 & 1) as bool == true
cx16.r1++
else
cx16.r2++
if not((cx16.r0 & 1) as bool)
cx16.r1++
else
cx16.r2++
if (cx16.r0 & 1) as bool
cx16.r1++
else
cx16.r2++
palette.set_grayscale(0)
sys.wait(60)
palette.set_rgb_be(colors_b, 16, 0)
}
}

View File

@ -1,7 +1,10 @@
%import textio
; The "Byte Sieve" test. https://en.wikipedia.org/wiki/Byte_Sieve
%import textio
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
@ -24,8 +27,8 @@ main {
flags_ptr[k] = 0 ; false
k += prime
}
txt.print_uw(prime)
txt.nl()
; txt.print_uw(prime)
; txt.nl()
count++
}
}

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME