mirror of
https://github.com/irmen/prog8.git
synced 2025-01-13 10:29:52 +00:00
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:
parent
2e303041c1
commit
b2e821755c
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,6 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
val onlyCx16 = cartesianProduct(
|
||||
listOf(
|
||||
"chunkedfile/demo",
|
||||
"vtui/testvtui",
|
||||
"pcmaudio/play-adpcm",
|
||||
"pcmaudio/stream-wav",
|
||||
|
@ -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?
|
||||
|
@ -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)
|
||||
|
5
examples/cx16/chunkedfile/.gitignore
vendored
5
examples/cx16/chunkedfile/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
testdata/*TITLESCREEN.*
|
||||
*.mcf
|
||||
*.asm
|
||||
*.prg
|
||||
*.vice-mon-list
|
@ -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
|
@ -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
|
||||
}}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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.
|
@ -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.
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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++
|
||||
}
|
||||
}
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user