diff --git a/src/fill.s b/src/fill.s index 6f3a78a..fbadb0f 100644 --- a/src/fill.s +++ b/src/fill.s @@ -6,22 +6,21 @@ vera_data0 := $9f23 .import popa .segment "CODE" _fill_vera: - sta vera_fill_data - jsr popa - sta max - lda max - cmp #0 - beq @end + sta vera_fill_data ; Save the value to fill the vera with + jsr popa ; Get the length parameter + sta max ; Store the max value + cmp #0 ; Make sure it's not 0 + beq @end ; If it is, we're already done @fill: - ldx #0 - lda vera_fill_data + ldx #0 ; Initialize X + lda vera_fill_data ; Load the fill value @loop: - sta vera_data0 - inx - cpx max - bne @loop + sta vera_data0 ; Store the fill value + inx ; Increment X + cpx max ; Compare it against the maximum value + bne @loop ; If it's not the max yet, do it again @end: - rts + rts ; We're done! .segment "BSS" max: .res 1 diff --git a/src/macptr.s b/src/macptr.s index 1d7216e..9e3ea6d 100644 --- a/src/macptr.s +++ b/src/macptr.s @@ -7,30 +7,31 @@ r1 := $04 r1l:= $04 r1h:= $05 .segment "CODE" +; ASM wrapper for MACPTR _cx16_k_macptr: - sta r0l + sta r0l ; Store pointer in ZP stx r0h - jsr popa + jsr popa ; Store increment boolean sta r1l - jsr popa + jsr popa ; Store size byte sta r1h - ldx r0l - ldy r0h + ldx r0l ; Load parameters + ldy r0h lda r1l - beq @increment - clc - jmp @loadregs + beq @increment ; If A is 0, set the carry flag + clc ; Otherwise clear it + jmp @loadregs ; And don't set it again @increment: - sec + sec ; Set the carry flag @loadregs: - lda r1h - jsr $FF44 - bcs @error - txa + lda r1h ; Load the size parameter + jsr $FF44 ; Call MACPTR + bcs @error ; If there was an error, return 0 + txa ; Otherwise, set up registers to hold the return value sty r0 ldx r0 rts @error: - lda #0 + lda #0 ; Return 0 in case of error ldx #0 rts diff --git a/src/main.c b/src/main.c index 609323b..7131872 100644 --- a/src/main.c +++ b/src/main.c @@ -5,98 +5,138 @@ #include #include #include +#include #include "vera.h" #include "macptr.h" #include "debug.h" +#include "memory_decompress.h" #include "fill.h" uint8_t palette[512]; uint8_t vera_bit_depth; uint8_t bitdepth; uint16_t width, height; +bool compress; +bool direct_to_vera; +uint16_t imgdatastart; uint32_t imgdatabytes; uint8_t pixels_per_byte; -uint8_t filleridx = 255; +uint8_t borderidx = 255; bool over320; uint8_t oldhscale, oldvscale; uint8_t significant_palette_entries; uint8_t significant_palette_start; +uint8_t *image_start = BANK_RAM; +/// @brief Reads a 16 bit little endian value from LFN 2 +/// @return The 16 bit value uint16_t read16() { cbm_k_chkin(2); return ((cbm_k_chrin())) | ((uint16_t)cbm_k_chrin() << 8); } +/// @brief Reads an 8 bit value and returns it as a character +/// @return The character char readchar() { cbm_k_chkin(2); return cbm_k_chrin(); } +/// @brief Reads an unsigned 8 bit value and returns it as is +/// @return The unsigned 8 bit value uint8_t read8() { cbm_k_chkin(2); return cbm_k_getin(); } +/// @brief Checks if the header in LFN 2 is correct +/// @return True if it is, false otherwise bool checkheader() { - char header[4] = {0x42, 0x4D, 0x58, 0}; // X16BM - char tested[4] = {0, 0, 0, 0}; + char header[4] = {0x42, 0x4D, 0x58, 0}; // BMX + char tested[4] = {0, 0, 0, 0}; // The tested values, for debugging uint8_t i = 0; - bool valid = true; + bool valid = true; // Set to true so that if it's wrong, the loop can set it to false, but if it's correct, the loop doesn't have to set it char chr = 0; printf("Reading magic bytes...\n"); + // Check the magic bytes one-by-one for (; i < 3; i++) { - chr = readchar(); - tested[i] = chr; + chr = readchar(); // Get a character + tested[i] = chr; // Put it in the tested value list printf("%2x%s", chr, i >= 4 ? "\n" : ", "); - if (chr != header[i]) { + if (chr != header[i]) { // Check the value valid = false; } } + // Make sure the tested values don't contain any unprintable characters for (i = 0; i < 3; i++) { if (tested[i] < 0x20 || tested[i] >= 0x7F) { tested[i] = '.'; } } - printf("%s%c=%s\n", tested, valid ? '=' : '!', header); + printf("%s%c=%s\n", tested, valid ? '=' : '!', header); // Display the comparison return valid; } +/// @brief Updates the scale of the VERA based on the minimum required width/height to display the image void updatescale() { uint8_t hscale; uint8_t vscale; + // Set DCSEL to 0 VERA.control &= 0b10000001; + // Get the scale to check against and later set back to hscale = VERA.display.hscale; vscale = VERA.display.vscale; + // Back up the current scale oldhscale = hscale; oldvscale = vscale; + // If it's over 320, the scale's probably correct. if (over320) return; + // Make sure the scale doesn't end up showing garbage data. if (hscale > 64) VERA.display.hscale = 64; if (vscale > 64) VERA.display.vscale = 64; } +/// @brief Restores the scale to what it was before updating it to fit the image void restorescale() { VERA.control &= 0b10000001; VERA.display.hscale = oldhscale; VERA.display.vscale = oldvscale; } +/// @brief Loads and uploads an image to the VERA +/// @param filename The file to load +/// @return 0 on success, exit code on failure int uploadimage(const char *filename) { + // Used for decompression. + uint8_t *image_end = BANK_RAM; + // 16 bit variables size_t i = 0, j = 0, x = 0, y = 0, value = 0, bitmask = 0; + // Variables for the VERA width and height values size_t vera_w = 320, vera_h = 240; + // The max address of the VERA uint16_t vera_max = 0; + // 32 bit variables, used sparingly uint32_t tmp = 0, vera_max_32 = 0; - uint16_t vera_adr_bak; - uint8_t vera_adr_h_bak; + // The bank the VERA will be at when the operation finishes bool vera_max_bank = 1; + // Base layer config uint8_t config = 0b00000100; + // The counter for the amount of bytes read uint16_t bytes_read; + // The version of the BMX format uint8_t version; + // True if all palette entries are significant bool all_significant; + // Open the file cbm_k_setnam(filename); cbm_k_setlfs(2, 8, 2); cbm_k_open(); + // Check if the open was successful if (cbm_k_readst()) { printf("Error opening file.\n"); return 1; } + // Make sure it's a BMX file if (!checkheader()) { printf("Invalid file!\n"); return 1; } + // Read the header version = read8(); bitdepth = read8(); + // Verify the bitdepth if (bitdepth == 0) { printf("Error: bitdepth was 0.\n"); return 1; @@ -104,19 +144,18 @@ int uploadimage(const char *filename) { vera_bit_depth = read8(); vera_bit_depth &= 0b11; config |= vera_bit_depth; - if (vera_bit_depth > 3) { - printf("Error: VERA bit depth was invalid!\n"); - } + // Calculate the amount of pixels that can fit in a byte, used later pixels_per_byte = 8 / bitdepth; - printf("Bit depth: %u => %u pixels per byte\n", bitdepth, pixels_per_byte); - width = read16(); + printf("Bit depth: %u => %u pixels per byte\n", bitdepth, pixels_per_byte); // Debugging + width = read16(); // Get the image dimensions height = read16(); printf("Width: %u, height: %u\n", width, height); + // Make sure the later update_scale call sets the correct scale over320 = width > 320 || height > 240; - filleridx = read8(); - printf("Border color: %02x\n", filleridx); + // Calculate the amount of bytes used within the image imgdatabytes = ((uint32_t)width * (uint32_t)height) / (uint32_t)pixels_per_byte; printf("Bytes: %lu\n", imgdatabytes); + // Get the list of significant palette entires significant_palette_entries = read8(); significant_palette_start = read8(); if (significant_palette_entries == 0) { @@ -126,61 +165,115 @@ int uploadimage(const char *filename) { } else { printf("%u palette entries significant\nstarting at %u.\n", significant_palette_entries, significant_palette_start); } + imgdatastart = read16(); + --imgdatastart; + printf("Image starts at 0-indexed offset %4x\n", imgdatastart); + direct_to_vera = width == vera_w && height == vera_h; + if ((int8_t)read8() == -1) { + compress = true; + }; printf("Skipping reserved bytes...\n"); - for (i = 0; i < 19; i++) { + for (i = 0; i < 16; i++) { read8(); } + // Load the border color + borderidx = read8(); + printf("Border color: %02x\n", borderidx); printf("Reading palette entries...\n"); - for (i = 0; i < 512; i++) { + for (i = 0; i < (uint16_t)significant_palette_entries*2; i++) { j = i >> 1; - if (all_significant || j >= significant_palette_start && j <= significant_palette_entries) { + // Only load the required palette entries + if (all_significant || j >= significant_palette_start && j < significant_palette_start+significant_palette_entries || j == borderidx) { palette[i] = read8(); } else { read8(); palette[i] = get_from_backed_up_palette(i); } } + // If the VERA was set up for a 640p image, make sure to keep track of that if (over320) { vera_w *= 2; vera_h *= 2; } + // Calculate the maximum address and bank vera_max_32 = ((uint32_t)vera_w * (uint32_t)vera_h)/(uint32_t)pixels_per_byte; + // Get just the max address vera_max = vera_max_32; + // ... and the max bank all by itself vera_max_bank = vera_max_32 >> 16; + // Update the VERA layer config VERA.layer0.config = config; + // Make sure the TILEW value is set up correctly VERA.layer0.tilebase = over320 ? 1 : 0; - printf("Loading image bytes: $%lx\n", imgdatabytes); + // Debugging + printf("Loading %simage bytes: $%lx\n", compress ? "compressed " : "", imgdatabytes); printf("Image width: %u, image height: %u\npixels per byte: %u\n", width, height, pixels_per_byte); + i = 0; + // Calculate the border value. + value = 0; + bitmask = (1 << bitdepth) - 1; + + if (compress && !direct_to_vera) { + printf("Error: The image cannot be loaded due to limitations in memory_decompress."); + return 1; + } + for (i = 0; i < pixels_per_byte; i++) { + value |= (borderidx & bitmask) << (i * bitdepth); + } + if (compress) { + RAM_BANK = 1; + do { + i = cx16_k_macptr(0, true, image_end); + image_end += i; + if (RAM_BANK > 1) { + printf("Error: Cannot decompress memory because compressed data is too large.\n"); + return 1; + } + if (i == 0) { + break; + } + } while (i != 0); + } + // Set up the VERA's data0 address VERA.control &= ~0b1; VERA.address = 0; VERA.address_hi = 0b00010000; - i = 0; - // Calculate the filler value. - value = 0; - bitmask = (1 << bitdepth) - 1; - for (i = 0; i < pixels_per_byte; i++) { - value |= (filleridx & bitmask) << (i * bitdepth); - } - // Upload the bitmap - for (x = 0; VERA.address < vera_max || (vera_max_bank & 0b1) != (VERA.address_hi & 0b1);) { - tmp = ((((uint32_t)VERA.address) + ((uint32_t)(VERA.address_hi & 0b1) << 16))); - x = (tmp % (uint32_t)(vera_w/pixels_per_byte))*pixels_per_byte; - y = tmp / (uint32_t)(vera_w/pixels_per_byte); - #if 0 - vera_adr_bak = VERA.address; - vera_adr_h_bak = VERA.address_hi; - printf("Y: %u, X: %u, tmp: %lu,\nvera_w: %u\nvera_adr: %u, vera_adr_h: %s\n", y, x, tmp, vera_w, vera_adr_bak, (vera_adr_h_bak & 1) ? "1" : "0"); - VERA.address = vera_adr_bak; - VERA.address_hi = vera_adr_h_bak; - #endif - if (y >= height) { - break; - } - if (x < width && y < height) { - bytes_read = cx16_k_macptr((((width) - x))/pixels_per_byte, false, &VERA.data0); + if (direct_to_vera) { + if (compress) { + RAM_BANK = 1; + image_start = BANK_RAM; + memory_decompress(image_start, &VERA.data0); + DEBUG_PORT1 = 0; } else { - //VERA.data0 = value; - tmp = (vera_w-x)/pixels_per_byte; + do { + bytes_read = cx16_k_macptr(0, false, &VERA.data0); + } while (bytes_read > 0); + } + } else { + // Upload the bitmap + for (x = 0; VERA.address < vera_max || (vera_max_bank & 0b1) != (VERA.address_hi & 0b1);) { + tmp = ((((uint32_t)VERA.address) + ((uint32_t)(VERA.address_hi & 0b1) << 16))); + x = (tmp % (uint32_t)(vera_w/pixels_per_byte))*pixels_per_byte; + y = tmp / (uint32_t)(vera_w/pixels_per_byte); + if (y >= height) { + break; + } + if (x < width && y < height) { + // Load the image's bytes, up to the bytes per row - the x value in bytes + bytes_read = cx16_k_macptr((((width) - x))/pixels_per_byte, false, &VERA.data0); + } else { + // Fill the remaining pixels in the row with the border color using a fast assembly routine + tmp = (vera_w-x)/pixels_per_byte; + if (tmp >= ((uint32_t)1 << 8)) { + fill_vera(0xFF, value); + } else { + fill_vera(tmp, value); + } + } + } + // Fill the rest of the image with the border color using the same fast assembly routine + for (; VERA.address < vera_max || (vera_max_bank & 0b1) != (VERA.address_hi & 0b1);) { + tmp = vera_max_32 - (((uint32_t)VERA.address) | ((uint32_t)(VERA.address_hi & 0b1) << 16)); if (tmp >= ((uint32_t)1 << 8)) { fill_vera(0xFF, value); } else { @@ -188,17 +281,11 @@ int uploadimage(const char *filename) { } } } - for (; VERA.address < vera_max || (vera_max_bank & 0b1) != (VERA.address_hi & 0b1);) { - tmp = vera_max_32 - (((uint32_t)VERA.address) | ((uint32_t)(VERA.address_hi & 0b1) << 16)); - if (tmp >= ((uint32_t)1 << 8)) { - fill_vera(0xFF, value); - } else { - fill_vera(tmp, value); - } - } + // Set up the VERA to display the bitmap upload_palette(palette); updatescale(); vera_layer_enable(0b11); + // Close the file cbm_k_clrch(); cbm_k_close(2); return 0; diff --git a/src/memory_decompress.h b/src/memory_decompress.h new file mode 100644 index 0000000..b8966a2 --- /dev/null +++ b/src/memory_decompress.h @@ -0,0 +1,6 @@ +#ifndef MEMORY_DECOMPRESS_H +#define MEMORY_DECOMPRESS_H + +extern void * __cdecl__ memory_decompress(const void *input, void *output); + +#endif \ No newline at end of file diff --git a/src/memory_decompress.s b/src/memory_decompress.s new file mode 100644 index 0000000..860c5ef --- /dev/null +++ b/src/memory_decompress.s @@ -0,0 +1,19 @@ +.export _memory_decompress +.import popax +r0 := $02 +r0l := $02 +r0h := $03 +r1l := $04 +r1h := $05 +.segment "CODE" +_memory_decompress: + jsr popax + sta r1l + stx r1h + jsr popax + sta r0l + stx r0h + jsr $FEED + lda r1l + ldx r1h + rts \ No newline at end of file