diff --git a/src/test/kc/complex/new_30_years_low_resolution/byteboozer.c b/src/test/kc/complex/new_30_years_low_resolution/byteboozer.c new file mode 100644 index 000000000..fb2de1d1f --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/byteboozer.c @@ -0,0 +1,18 @@ +// ByteBoozer decruncher +// https://github.com/p-a/kickass-cruncher-plugins + +// Decrunch crunched data using ByteBoozer +// - crunched: Pointer to the start of the crunched data +void byteboozer_decrunch(char* crunched) { + asm { + ldy crunched + ldx crunched+1 + jsr b2.Decrunch + } +} + +// The byteboozer decruncher +export char BYTEBOOZER[] = kickasm(resource "byteboozer_decrunch.asm") {{ + .const B2_ZP_BASE = $fc + #import "byteboozer_decrunch.asm" +}}; \ No newline at end of file diff --git a/src/test/kc/complex/new_30_years_low_resolution/byteboozer.h b/src/test/kc/complex/new_30_years_low_resolution/byteboozer.h new file mode 100644 index 000000000..7fabd299e --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/byteboozer.h @@ -0,0 +1,6 @@ +// ByteBoozer decruncher +// https://github.com/p-a/kickass-cruncher-plugins + +// Decrunch crunched data using ByteBoozer +// - crunched: Pointer to the start of the crunched data +void byteboozer_decrunch(char* crunched); diff --git a/src/test/kc/complex/new_30_years_low_resolution/byteboozer_decrunch.asm b/src/test/kc/complex/new_30_years_low_resolution/byteboozer_decrunch.asm new file mode 100644 index 000000000..a5ae4daca --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/byteboozer_decrunch.asm @@ -0,0 +1,182 @@ +// ByteBoozer Decruncher /HCL May.2003 +// B2 Decruncher December 2014 + +.importonce +.filenamespace b2 + +// You must set .const B2_ZP_BASE prior the import of this file +.if (B2_ZP_BASE > $ff) { + .error "B2_ZP_BASE must be in zeropage. Was $" + toHexString(B2_ZP_BASE,4) +} + + +.label zp_base = B2_ZP_BASE +.label bits = zp_base +.label put = zp_base + 2 + +.macro @B2_DECRUNCH(addr) { + ldy #addr + jsr b2.Decrunch +} + +.macro GetNextBit() { + asl bits + bne DgEnd + jsr GetNewBits +DgEnd: +} + +.macro GetLen() { + lda #1 +GlLoop: + :GetNextBit() + bcc GlEnd + :GetNextBit() + rol + bpl GlLoop +GlEnd: +} + +Decrunch: + sty Get1+1 + sty Get2+1 + sty Get3+1 + stx Get1+2 + stx Get2+2 + stx Get3+2 + + ldx #0 + jsr GetNewBits + sty put-1,x + cpx #2 + bcc *-7 + lda #$80 + sta bits +DLoop: + :GetNextBit() + bcs Match +Literal: + // Literal run.. get length. + :GetLen() + sta LLen+1 + + ldy #0 +LLoop: +Get3: + lda $feed,x + inx + bne *+5 + jsr GnbInc +L1: sta (put),y + iny +LLen: + cpy #0 + bne LLoop + + clc + tya + adc put + sta put + bcc *+4 + inc put+1 + + iny + beq DLoop + + // Has to continue with a match.. + +Match: + // Match.. get length. + :GetLen() + sta MLen+1 + + // Length 255 -> EOF + cmp #$ff + beq End + + // Get num bits + cmp #2 + lda #0 + rol + :GetNextBit() + rol + :GetNextBit() + rol + tay + lda Tab,y + beq M8 + + // Get bits < 8 +M_1: + :GetNextBit() + rol + bcs M_1 + bmi MShort +M8: + // Get byte + eor #$ff + tay +Get2: + lda $feed,x + inx + bne *+5 + jsr GnbInc + jmp Mdone +MShort: + ldy #$ff +Mdone: + //clc + adc put + sta MLda+1 + tya + adc put+1 + sta MLda+2 + + ldy #$ff +MLoop: + iny +MLda: + lda $beef,y + sta (put),y +MLen: + cpy #0 + bne MLoop + + //sec + tya + adc put + sta put + bcc *+4 + inc put+1 + + jmp DLoop + +End: + rts + +GetNewBits: +Get1: + ldy $feed,x + sty bits + rol bits + inx + bne GnbEnd +GnbInc: + inc Get1+2 + inc Get2+2 + inc Get3+2 +GnbEnd: + rts + +Tab: + // Short offsets + .byte %11011111 // 3 + .byte %11111011 // 6 + .byte %00000000 // 8 + .byte %10000000 // 10 + // Long offsets + .byte %11101111 // 4 + .byte %11111101 // 7 + .byte %10000000 // 10 + .byte %11110000 // 13 diff --git a/src/test/kc/complex/new_30_years_low_resolution/demo.h b/src/test/kc/complex/new_30_years_low_resolution/demo.h new file mode 100644 index 000000000..b452a9d05 --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/demo.h @@ -0,0 +1,13 @@ +// Demo Flow Engine + +// Start the demo IRQ. Can be called multiple times! +// Setting IRQ to the "demo" IRQ running outside the parts and +// Setting memory to IO + RAM (no kernal/basic) +void demo_start(); + +// Work to be performed every frame while the demo runs +// Assumes that I/O is enabled +void demo_work(); + +// Counts total demo frames +extern volatile unsigned int demo_frame_count; diff --git a/src/test/kc/complex/new_30_years_low_resolution/demo.ld b/src/test/kc/complex/new_30_years_low_resolution/demo.ld new file mode 100644 index 000000000..fd55e6b82 --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/demo.ld @@ -0,0 +1,19 @@ +// Commodore 64 PRG executable file +.plugin "se.triad.kickass.CruncherPlugins" +.file [name="%O", type="prg", segments="Program"] +.segmentdef Program [segments="Basic, Common, Part2, Part1, InitDemo"] +.segmentdef Basic [start=$0801] +.segmentdef Common [segments="Code, Data"] +.segmentdef Code [start=%P] +.segmentdef Data [startAfter="Code"] +.segmentdef Part2 [segments="CodePart2, DataPart2, InitPart2"] +.segmentdef CodePart2 [startAfter="Data"] +.segmentdef DataPart2 [startAfter="CodePart2"] +.segmentdef InitPart2 [startAfter="DataPart2"] +.segmentdef Part1 [segments="CodePart1, DataPart1, InitPart1"] +.segmentdef CodePart1 [startAfter="InitPart2"] +.segmentdef DataPart1 [startAfter="CodePart1"] +.segmentdef InitPart1 [startAfter="DataPart1"] +.segmentdef InitDemo [startAfter="InitPart1"] +.segment Basic +:BasicUpstart(%E) \ No newline at end of file diff --git a/src/test/kc/complex/new_30_years_low_resolution/do-it-again-$AC00-$FA-8580.sid b/src/test/kc/complex/new_30_years_low_resolution/do-it-again-$AC00-$FA-8580.sid new file mode 100644 index 000000000..6e27dff63 Binary files /dev/null and b/src/test/kc/complex/new_30_years_low_resolution/do-it-again-$AC00-$FA-8580.sid differ diff --git a/src/test/kc/complex/new_30_years_low_resolution/happy-newyear.png b/src/test/kc/complex/new_30_years_low_resolution/happy-newyear.png new file mode 100644 index 000000000..920f4a4a9 Binary files /dev/null and b/src/test/kc/complex/new_30_years_low_resolution/happy-newyear.png differ diff --git a/src/test/kc/complex/new_30_years_low_resolution/logo-bitmap-640.png b/src/test/kc/complex/new_30_years_low_resolution/logo-bitmap-640.png new file mode 100644 index 000000000..1640df601 Binary files /dev/null and b/src/test/kc/complex/new_30_years_low_resolution/logo-bitmap-640.png differ diff --git a/src/test/kc/complex/new_30_years_low_resolution/mcbitmap.asm b/src/test/kc/complex/new_30_years_low_resolution/mcbitmap.asm new file mode 100644 index 000000000..ebc83d3c2 --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/mcbitmap.asm @@ -0,0 +1,168 @@ +// KickAssembler functions for converting a Picture to a multi-color bitmap +// Based on Code by Cruzer/CML, April 2011 (https://codebase64.org/doku.php?id=base:double_screen_horizontal_scroller&s[]=koala) +// Based on a routine from Codebase64 by Martin Piper +//--------------------------------------------------------------------------------------------------------------------- +// Picture - a result from LoadPicture() +// - The upper line is used for defining the c64 palette, with multicolor sized pixels +// - The pixel to the right of the palette defines the background color + +// Usage: +// #import "mcbitmap.asm" +// .var mcBmmData = getMcBitmapData(LoadPicture("bitmap.png")) +// *=$0c00 "SCREEN"; +// .for (var y=0; y<25; y++) +// .for (var x=0; x<40; x++) +// .byte getMcScreenData(x, y, mcBmmData) +// *=$1c00 "COLORS" +// colorRam: +// .for (var y=0; y<25; y++) +// .for (var x=0; x<40; x++) +// .byte getMcColorData(x, y, mcBmmData) +// *=$2000 "BITMAP"; +// .for (var y=0; y<25; y++) +// .for (var x=0; x<40; x++) +// .fill 8, getMcPixelData(x, y, i, mcBmmData) + +#importonce + +.struct McBitmapPalette { + // Hashtable mapping RGB to C64 color + // rgb2c64.get(rgb) returns a C64 color (0-15) + rgb2c64, + // The (C64) background color of the image + bgColor +} + +// Bitmap data for a single (4x8 MC pixel) block +.struct McBitmapBlock { + // List() with 8 pixel bytes + pixels, + // C64 color for %01 pixels + color1, + // C64 color for %10 pixels + color2, + // C64 color for %11 pixels + color3, + // Byte for screen (color1*$10 + color2) + screendata, + // Byte for color (color3) + colordata +} + +// Bitmap data for an entire picture +.struct McBitmapData { + // The McBitmapPalette + palette, + // The number of X-blocks (40 for a normal full screen bitmap) + width, + // The number of Y-blocks (25 for a normal full screen bitmap) + height, + // List with width*height McBitmapBlock ordered row by row. + // blocks.get(y*width+x) is the block at (x,y) + blocks +} + +// Get multicolor-bitmap data for a picture. The first row of the picture contains the palette and is discarded. (pixel #0-#15 as colors representing C64 color black, white, ... pixel #16 representing the background color) +.function getMcBitmapData(pic) { + .var palette = findMcPalette(pic) + .var width = floor(pic.width/8) + .var height = floor((pic.height-1)/8) // -1 to account for the first line containing the palette + .var blocks = List() + .for (var y=0; yplex_id=(sprite->plex_id-1)&(FRAME_COUNT-1) + +#include "multiplex-bucket.h" +#include + +#ifdef DEBUG_PLEX +#include +#endif + +#ifndef PLEX_SPRITE_PTRS +#define PLEX_SPRITE_PTRS DEFAULT_SCREEN+OFFSET_SPRITE_PTRS +#endif + +// The screen sprite pointers to update +char * const SCREEN_SPRITE_PTRS = PLEX_SPRITE_PTRS; + +// The Y-position (IRQ raster line) starting each bucket +char BUCKET_YPOS[BUCKET_COUNT] = { 0x10, 0x48, 0x58, 0x72, 0x8e, 0xaa, 0xc0, 0xd0, 0xde }; + +// The y-positions of the multiplexer sprites. (These are converted to multiplexer buckets) +char PLEX_YPOS[PLEX_COUNT]; + +// The low byte of the x-positions of the multiplexer sprites +char PLEX_XPOS[PLEX_COUNT]; + +// The MSB of the x-positions of the multiplexer sprites (0/1) +char PLEX_XPOS_MSB[PLEX_COUNT]; + +// The sprite pointers for the multiplexed sprites +char PLEX_PTR[PLEX_COUNT]; + +// The sprite color for the multiplexed sprites +//char PLEX_COL[PLEX_COUNT]; + +// Indexes of the plex-sprites sorted by sprite y-position. Each call to plexSort() will fix the sorting if changes to the Y-positions have ruined it. +char PLEX_SORTED_IDX[PLEX_COUNT]; + +// Initialize data structures for the multiplexer +void plexPrepareInit() { + // Initial sorting is trivial + for(char i=0; i bucket_ypos) { + // The real sprite is not free in the current bucket - move to the next bucket! + #ifdef DEBUG_PLEX + if(bucket_id>=BUCKET_COUNT-1) printf("plex#%hhx ypos:%hhx not free in last bucket#%hhx ypos:%hhx. real sprite#%hhx free at ypos %hhx.\n", plex_id, ypos, bucket_id, bucket_ypos, real_sprite_id, real_sprite_free_ypos[real_sprite_id]); + #endif + // Move to the next bucket + bucket_id++; + bucket_ypos = BUCKET_YPOS[bucket_id]; + bucket += BUCKET_SIZE; + // Zero-fill the next sprite in the bucket (if not full) + if(sprite!=bucket) sprite->ypos=0; + // Set current sprite to start of next bucket + sprite = bucket; + } + // Identify problems filling buckets + #ifdef DEBUG_PLEX + if(ypos<=bucket_ypos) printf("plex#%hhx ypos:%hhx <= bucket#%hhx ypos:%hhx. lower bucket ypos or introduce new with lower ypos.\n", plex_id, ypos, bucket_id, bucket_ypos); + #endif + // Put the sprite into the bucket + sprite->ypos = ypos; + sprite->plex_id = plex_id; + // Increase bucket ypos to account for time spent placing the sprite + bucket_ypos += 1; + // Update next free ypos for the real sprite + real_sprite_free_ypos[real_sprite_id] = ypos+22; + // Move to the next real sprite + real_sprite_id = (real_sprite_id+1)&7; + // Move to the next sprite in the bucket + sprite++; + } + // Zero-fill the next sprite in the bucket (if not full) + bucket += BUCKET_SIZE; + if(sprite!=bucket) sprite->ypos=0; +} + +// The next "real" sprite being used by the multiplexer +volatile char plex_real_sprite_idx = 0; + +// Start a new frame (initializing data structures) +void plexFrameStart() { + plex_real_sprite_idx = 0; +} + +// Show the sprites in a specific bucket +// - bucket: The bucket to show +void plexBucketShow(struct BucketSprite* bucket) { + // Masks used for MSB + char MSB_SET_MASK_BY_ID[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; + char MSB_CLEAR_MASK_BY_ID[8] = { 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f }; + // Use a char* to optimize the code! + char* bucket_ptr = (char*)bucket; + char real_idx = plex_real_sprite_idx*2; + char i=0; + while(bucket_ptr[i]) { + SPRITES_YPOS[real_idx] = bucket_ptr[i++]; + char plex_id = bucket_ptr[i]; + SPRITES_XPOS[real_idx] = PLEX_XPOS[plex_id]; + real_idx /= 2; + if(PLEX_XPOS_MSB[plex_id]) { + *SPRITES_XMSB |= MSB_SET_MASK_BY_ID[real_idx]; + } else { + *SPRITES_XMSB &= MSB_CLEAR_MASK_BY_ID[real_idx]; + } + SCREEN_SPRITE_PTRS[real_idx] = PLEX_PTR[plex_id]; + //SPRITES_COLOR[real_idx] = PLEX_COL[plex_id]; + real_idx = (real_idx+1)&7; + real_idx *= 2; + i++; + if(i==BUCKET_SIZE*sizeof(struct BucketSprite)) break; + } + plex_real_sprite_idx = real_idx/2; +} + +// Updates the PLEX_ID's preparing for the next cycle of the multiplexer. +// Performs: sprite->plex_id=(sprite->plex_id-1)&(FRAME_COUNT-1) +void plexFrameEnd(struct BucketSprite* frame) { + char* sprite_plex_ids = &frame->plex_id; + for(char i=0;iplex_id=(sprite->plex_id-1)&(FRAME_COUNT-1) + +// The number of sprites in the multiplexer +#define PLEX_COUNT 32 +// The number of sprites per multiplexer bucket +#define BUCKET_SIZE 8 +// The number of multiplexer buckets per frame +#define BUCKET_COUNT 9 +// The number of multiplexer frames +#define FRAME_COUNT 8 + +// The Y-position (IRQ raster line) starting each bucket +extern char BUCKET_YPOS[BUCKET_COUNT]; + +// The y-positions of the multiplexer sprites. (These are converted to multiplexer buckets) +extern char PLEX_YPOS[PLEX_COUNT]; + +// The low byte of the x-positions of the multiplexer sprites +extern char PLEX_XPOS[PLEX_COUNT]; + +// The MSB of the x-positions of the multiplexer sprites (0/1) +extern char PLEX_XPOS_MSB[PLEX_COUNT]; + +// The sprite pointers for the multiplexed sprites +extern char PLEX_PTR[PLEX_COUNT]; + +// The sprite color for the multiplexed sprites +//extern char PLEX_COL[PLEX_COUNT]; + +// A single sprite in a multiplexer bucket +struct BucketSprite { + char ypos; + char plex_id; +}; + +// Initialize data structures for the multiplexer +void plexPrepareInit(); + +// Performs run-time bucket sort of the sprites in the PLEX_ arrays into struct BucketSprite[] +// Starts by performing a true sorting of the sprites based on Y-position (using insertion sort) +void plexPrepareFrame(struct BucketSprite* frame); + +// Start a new frame (initializing data structures) +void plexFrameStart(); + +// Show the sprites in a specific bucket +// - bucketSprites: The bucket to show +void plexBucketShow(struct BucketSprite* bucket); + +// Updates the PLEX_ID's preparing for the next cycle of the multiplexer. +// Performs: sprite->plex_id=(sprite->plex_id-1)&(FRAME_COUNT-1) +void plexFrameEnd(struct BucketSprite* frame); diff --git a/src/test/kc/complex/new_30_years_low_resolution/new_30_years_low_resolution.c b/src/test/kc/complex/new_30_years_low_resolution/new_30_years_low_resolution.c new file mode 100644 index 000000000..3e56859e9 --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/new_30_years_low_resolution.c @@ -0,0 +1,142 @@ +// The Demo collects the parts and handles overall control + +#pragma target(c64) +#pragma link("demo.ld") +#pragma zp_reserve(0xfa..0xfb) // Reserved for music player +#pragma interrupt(hardware_clobber) +#pragma emulator("C64Debugger") + +#include +#include <6502.h> +#include +#include "demo.h" +#include "byteboozer.h" +#include "part1-happynewyear.c" +#include "part2-swingplex.c" + +#pragma code_seg(Code) +#pragma data_seg(Data) + +char* const DEMO_MUSIC = 0xAC00; +// Pointer to the music init routine +void()* const musicInit = (void()*) DEMO_MUSIC; +// Pointer to the music play routine +void()* const musicPlay = (void()*) DEMO_MUSIC+3; + +#pragma data_seg(InitDemo) + +// SID tune +char DEMO_MUSIC_CRUNCHED[] = kickasm(resource "do-it-again-$AC00-$FA-8580.sid", uses DEMO_MUSIC) {{ + .modify B2() { + .pc = DEMO_MUSIC "MUSIC" + .const music = LoadSid("do-it-again-$AC00-$FA-8580.sid") + .fill music.size, music.getData(i) + } +}}; + +#pragma data_seg(Data) + +void main() { + // Initialize the demo - start the IRQ + demo_init(); + // Decrunch music + byteboozer_decrunch(DEMO_MUSIC_CRUNCHED); + // Init music + asm { lda #0 } + (*musicInit)(); + // Initialize the demo - start the IRQ + demo_start(); + // Initialize Part 1 (Revealing "Happy New Year" logo) + part1_init(); + // Start part 1 at 0:04,5 + while(demo_frame_count<5*50) ; + // Run Part 1 (Revealing "Happy New Year" logo) + part1_run(); + // Initialize part 2 + part2_init(); + // Wait for the right place to start part 2 + while(demo_frame_count<16*50) ; + // Disable sparkler + sparkler_active = 0; + // Run part 2 + part2_run(); + + for(;;) ; + +} + +// Initialize demo code. +// Can be called multiple times! +// Setting IRQ to the "demo" IRQ running outside the parts and +// Setting memory to IO + RAM (no kernal/basic) +void demo_init() { + SEI(); + // Disable kernal & basic + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + *PROCPORT = PROCPORT_RAM_IO; + // Disable CIA 1 Timer IRQ + CIA1->INTERRUPT = CIA_INTERRUPT_CLEAR; + // Acknowledge any timer IRQ + asm { lda CIA1_INTERRUPT } + // Acknowledge any VIC IRQ + *IRQ_STATUS = 0x0f; +} + +// Start the demo IRQ. Can be called multiple times! +// Setting IRQ to the "demo" IRQ running outside the parts and +// Setting memory to IO + RAM (no kernal/basic) +void demo_start() { + demo_init(); + // Set raster line to 0x00 + *VICII_CONTROL &= 0x7f; + *RASTER = 0; + // Set the IRQ routine + *HARDWARE_IRQ = &irq_demo; + // Enable Raster Interrupt + *IRQ_ENABLE = IRQ_RASTER; + CLI(); +} + +// Counts total demo frames +volatile unsigned int demo_frame_count = 0; + +// Work to be performed every frame while the demo runs +// Assumes that I/O is enabled +void demo_work() { + // Increase frame count + demo_frame_count++; + // Play music + (*musicPlay)(); + // Animate the sparkler + if(sparkler_active) + sparkler_anim(); +} + +// Is the sparkler active +volatile char sparkler_active = 0; + +// The sparkler sprite idx +volatile char sparkler_idx = 0; + +// Animate the sparkler sprite +void sparkler_anim() { + if(++sparkler_idx==30) sparkler_idx=0; + P1_SCREEN_SPRITE_PTRS[0] = toSpritePtr(P1_SPRITES)+sparkler_idx/2; +} + +// IRQ running during between parts +__interrupt void irq_demo() { + // Remember processor port value + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + char port_value = *PROCPORT; + // Enable IO + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + *PROCPORT = PROCPORT_RAM_IO; + // Perform any demo work + demo_work(); + // Acknowledge the IRQ + *IRQ_STATUS = IRQ_RASTER; + // Restore processor port value + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + *PROCPORT = port_value; +} \ No newline at end of file diff --git a/src/test/kc/complex/new_30_years_low_resolution/part1-happynewyear.c b/src/test/kc/complex/new_30_years_low_resolution/part1-happynewyear.c new file mode 100644 index 000000000..a896c140c --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/part1-happynewyear.c @@ -0,0 +1,355 @@ +// Show the Happy New Year image as a MC bitmap +#include +#include <6502.h> +#include +#include "byteboozer.h" + +#pragma code_seg(CodePart1) +#pragma data_seg(DataPart1) + +char * const P1_COLORS = 0xa800; // A800-AFFF +char * const P1_PIXELS = 0xc000; // C000-DFFF +char * const P1_SCREEN = 0xe000; // E000-E3FF +char * const P1_SPRITES = 0xfc00; // E000-E3FF +char * const PIXELS_EMPTY = 0xe800; // E800-EFFF +// A copy of the load screen and colors +char * const LOAD_SCREEN = 0xe400; // E400-E7FF +char * const LOAD_CHARSET = 0xf000; // F000-F7FF +char * const LOAD_COLORS = 0xf800; // F800-FBFF + +// Flipper cosine easing table +unsigned int * const FLIPPER_EASING = 0xa400; + +// Sprite pointers +char * const P1_SCREEN_SPRITE_PTRS = 0xe3f8; // P1_SCREEN+OFFSET_SPRITE_PTRS; + +#pragma data_seg(InitPart1) + +// MC Bitmap Data +char P1_PIXELS_CRUNCHED[] = kickasm(resource "happy-newyear.png", resource "mcbitmap.asm", uses P1_PIXELS) {{ + .modify B2() { + .pc = P1_PIXELS "HAPPYNEWYEAR PIXELS" + #import "mcbitmap.asm" + .var mcBmmData1 = getMcBitmapData(LoadPicture("happy-newyear.png")) + .for (var y=0; y<25; y++) + .for (var x=0; x<40; x++) + .fill 8, getMcPixelData(x, y, i, mcBmmData1) + } +}}; + +char P1_SCREEN_CRUNCHED[] = kickasm(uses P1_SCREEN) {{ + .modify B2() { + .pc = P1_SCREEN "HAPPYNEWYEAR SCREEN" + .for (var y=0; y<25; y++) + .for (var x=0; x<40; x++) + .byte getMcScreenData(x, y, mcBmmData1) + } +}}; + +char P1_COLORS_CRUNCHED[] = kickasm(uses P1_COLORS) {{ + .modify B2() { + .pc = P1_COLORS "HAPPYNEWYEAR COLORS" + .for (var y=0; y<25; y++) + .for (var x=0; x<40; x++) + .byte getMcColorData(x, y, mcBmmData1) + } +}}; + +// Sparkler sprites +char P1_SPRITES_CRUNCHED[] = kickasm(uses P1_SPRITES, resource "sparklers.png") {{ + .modify B2() { + .pc = P1_SPRITES "P1_SPRITES" + // Pixels 11 01 10 11 + .var p1_sprites = LoadPicture("sparklers.png", List().add($000000, $daccc3, $472a24, $957a71)) + .for(var sx=0;sx<15;sx++) { + .for (var y=0;y<21; y++) { + .for (var c=0; c<3; c++) { + .byte p1_sprites.getMulticolorByte(sx*3+c,y) + } + } + .byte 0 + } + } +}}; + +// An easing curve from 0x000 to 0x130 +export char FLIPPER_EASING_CRUNCHED[] = kickasm {{ + .modify B2() { + .pc = FLIPPER_EASING "FLIPPER_EASING" + .fillword $130, round($98+$98*cos(PI+PI*i/$130)) + } +}}; + +#pragma data_seg(DataPart1) + +void part1_init() { + // Disable IO + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + *PROCPORT = PROCPORT_RAM_ALL; + // Decrunch pixels + byteboozer_decrunch(P1_PIXELS_CRUNCHED); + // Enable IO, Disable kernal & basic + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + *PROCPORT = PROCPORT_RAM_IO; + // Decrunch screen + byteboozer_decrunch(P1_SCREEN_CRUNCHED); + // Decrunch colors + byteboozer_decrunch(P1_COLORS_CRUNCHED); + // Decrunch sprites + byteboozer_decrunch(P1_SPRITES_CRUNCHED); + // Decrunch flipper sine table + byteboozer_decrunch(FLIPPER_EASING_CRUNCHED); + // Initialize the badlines + init_rasters(); + // Fill some empty pixels + memset(PIXELS_EMPTY, 0x00, 0x800); + // Enable CHARGEN, Disable kernal & basic + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + *PROCPORT = PROCPORT_RAM_CHARROM; + memcpy(LOAD_CHARSET, CHARGEN, 0x800); + // Enable IO, Disable kernal & basic + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + *PROCPORT = PROCPORT_RAM_IO; + // Copy loading screen + memcpy(LOAD_SCREEN, DEFAULT_SCREEN, 0x0400); + // Copy loading colors + memcpy(LOAD_COLORS, COLS, 1000); +} + +void part1_run() { + SEI(); + // Disable kernal & basic + *PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK; + *PROCPORT = PROCPORT_RAM_IO; + // Disable CIA 1 Timer IRQ + CIA1->INTERRUPT = CIA_INTERRUPT_CLEAR; + // Acknowledge any timer IRQ + asm { lda CIA1_INTERRUPT } + // Acknowledge any VIC IRQ + *IRQ_STATUS = 0x0f; + // Set raster line to 0x136 + *VICII_CONTROL |= 0x80; + *RASTER = IRQ_PART1_TOP_LINE; + // Set the IRQ routine + *HARDWARE_IRQ = &irq_part1_top; + // Enable Raster Interrupt + *IRQ_ENABLE = IRQ_RASTER; + // Show Sparkler + VICII->SPRITES_MC = 0x01; + VICII->SPRITE0_COLOR = PINK; + VICII->SPRITES_MCOLOR1 = YELLOW; + VICII->SPRITES_MCOLOR2 = PURPLE; + VICII->SPRITES_XMSB = 0x01; // 262 + VICII->SPRITE0_X = 22; // 262 + VICII->SPRITE0_Y = 190; // 144 + P1_SCREEN_SPRITE_PTRS[0] = toSpritePtr(P1_SPRITES); + CLI(); + + part1_loop(); + +} + +// Signals the main() loop to do work when all rasters are complete +volatile char p1_work_ready = 0; + +// Handle some stuff in the main() routine +void part1_loop() { + p1_work_ready = 0; + for(;;) { + while(p1_work_ready==0) ; + // Fix colors + flipper_fix_colors(); + // Show sparkler at 0:09 + if(!sparkler_active && demo_frame_count>9*50-3) { + VICII->SPRITES_ENABLE = 0x01; + sparkler_active = 1; + } + // Perform any demo-wide work + demo_work(); + // Wait for the right place to exit part 1 + if(demo_frame_count>14*50) { + // Re-start the demo base IRQ + demo_start(); + // Leave part 1 + break; + } + // My work is done! + p1_work_ready = 0; + } +} + +// Top of the flipper +volatile unsigned int irq_flipper_top_line = 0x00; +// Bottom of the flipper +volatile unsigned int irq_flipper_bottom_line = 0x08; +// 1 if flipper is done +volatile char flipper_done = 0; + +// 1 if the raster line is a badline +__align(0x100) char RASTER_BADLINES[0x130]; + +// Initialize the BADLINES +void init_rasters() { + for(unsigned int i=0;iBORDER_COLOR = BLACK; + VICII->BG_COLOR = BLACK; + // Set BMM + VICII->CONTROL1 |= VICII_BMM; + // Set MCM + VICII->CONTROL2 |= VICII_MCM; + // Change graphics bank + CIA2->PORT_A = toDd00(P1_SCREEN); + // Show screen + VICII->MEMORY = toD018(P1_SCREEN, P1_PIXELS); + + // Set up the flipper IRQ + if(>irq_flipper_top_line) + *VICII_CONTROL |= 0x80; + else + *VICII_CONTROL &= 0x7f; + *RASTER = (MEMORY = toD018(LOAD_SCREEN, PIXELS_EMPTY); + ldx #LIGHT_GREEN // VICII->BORDER_COLOR = LIGHT_GREEN; + ldy #$1b // VICII->CONTROL1 &= ~VICII_BMM; + sta VICII_MEMORY + stx BORDER_COLOR + sty VICII_CONTROL + stx BG_COLOR + lda #$c8 // VICII->CONTROL2 &= ~VICII_MCM; + sta VICII_CONTROL2 + } + // Set up the flipper IRQ + if(>irq_flipper_bottom_line) + *VICII_CONTROL |= 0x80; + else + *VICII_CONTROL &= 0x7f; + *RASTER = (BORDER_COLOR = LIGHT_BLUE; + VICII->BG_COLOR = BLUE; + // Show default screen + VICII->MEMORY = toD018(LOAD_SCREEN, LOAD_CHARSET); + + if(!flipper_done) { + // Move the flipper down + unsigned int irq_flipper_line = FLIPPER_EASING[irq_flipper_idx++]; + // Check limits + if(irq_flipper_line<8) + irq_flipper_top_line = 0; + else + irq_flipper_top_line = irq_flipper_line-8; + + if(irq_flipper_line>0x128) + irq_flipper_bottom_line = 0x130; + else + irq_flipper_bottom_line = irq_flipper_line+8; + + // Are we done + if(irq_flipper_line==0x130) + flipper_done = 1; + } + + // Set up the IRQ again + *VICII_CONTROL |=0x80; + *RASTER = IRQ_PART1_TOP_LINE; + *HARDWARE_IRQ = &irq_part1_top; + // Acknowledge the IRQ + *IRQ_STATUS = IRQ_RASTER; +} + +// Waits until at the exact start of raster line +// Excepts to start at a line divisible by 8 (0x00, 0x08, x010, ...). +// Waits line_offset (0-7) additional lines. +void raster_fine(char line_offset) { + kickasm(uses line_offset, uses RASTER_BADLINES, clobbers "AXY") {{ + jmp aligned + .align $100 + aligned: + ldy RASTER + ldx line_offset + inx + rst: + nop + nop + nop + nop + dex // 2 + beq done // 2 + lda RASTER_BADLINES,y // 4 + beq notbad // 3 + bad: + nop // 2 + nop + nop + nop + nop + dex + beq done + iny + nop + bit $ea + notbad: + .fill 18, NOP + bit $ea + iny + jmp rst + done: + }} +} + +// The current char line where the flipper switches from bitmap to text +volatile char flipper_charline = 0; + +// Fixes the colors for the flipper +// Updates with bitmap colors when the bitmap is being shown +void flipper_fix_colors() { + if(irq_flipper_top_line>0x2e && irq_flipper_top_line<0xf6) { + char charline = (char)((irq_flipper_top_line-0x2e)/8); + if(charline>=flipper_charline) { + unsigned int offset = (unsigned int)flipper_charline*40; + // We have to update some colors + char* colors = COLS+offset; + char* happy_cols = P1_COLORS+offset; + for(char i=0;i<40;i++) + colors[i] = happy_cols[i]; + flipper_charline++; + } + } +} diff --git a/src/test/kc/complex/new_30_years_low_resolution/part2-swingplex.c b/src/test/kc/complex/new_30_years_low_resolution/part2-swingplex.c new file mode 100644 index 000000000..b7b633c64 --- /dev/null +++ b/src/test/kc/complex/new_30_years_low_resolution/part2-swingplex.c @@ -0,0 +1,631 @@ +// 2-screen bitmap logo and a multiplexer scroller + +#include +#include <6502.h> +#include +#include "demo.h" +#include "byteboozer.h" + +#pragma code_seg(CodePart2) +#pragma data_seg(DataPart2) + +#include "../clib/vsp.h" +#define PLEX_SPRITE_PTRS 0xe3f8 +#include "multiplex-bucket.h" + +// Memory layout of the graphics bank +char * const LOGO_DATA = 0x5400; +char * const PART2_BITMAP = 0xc000; // -0xdfff +char * const PART2_SCREEN = 0xe000; // -0xe400 +char * const PART2_SPRITES = 0xe400; // -0xf400 +// Location PLEX ID updaters are placed when running +char * const PLEX_ID_UPDATERS = 0x3c00; +// Location where the crunched PLEX ID updaters are placed to be decrunched +char * const PLEX_ID_UPDATERS_CRUNCHED2 = 0x7c00; // -0xFF72 +// Size of the crunched PLEX ID updaters +const unsigned int PLEX_ID_UPDATERS_CRUNCHED_SIZE = 0x0b72; +// Location where the crunched LOGO DATA is placed to be decrunched +char * const LOGO_DATA_CRUNCHED2 = 0x8800; // -0xAA2D +// Size of the crunched PLEX ID updaters +const unsigned int LOGO_DATA_CRUNCHED_SIZE = 0x222d; + +// Char-based sizes for the logo +const char LOGO_HEIGHT = 25; +const char LOGO_WIDTH = 80; + +// Address of screen data +char * const LOGO_DATA_SCREEN = LOGO_DATA; +// Address of color data +char * const LOGO_DATA_COLORS = LOGO_DATA_SCREEN+LOGO_HEIGHT*LOGO_WIDTH; +// Address of pixel data +char * const LOGO_DATA_BITMAP = LOGO_DATA_COLORS+LOGO_HEIGHT*LOGO_WIDTH; + +// Sprite pointer for sprite 0 +#define SPRITE_0 toSpritePtr(PART2_SPRITES) + +// The sprite font +#pragma data_seg(InitPart2) +char SPRITES_CRUNCHED[] = kickasm(resource "spritefont.png") {{ + .modify B2() { + .pc = PART2_SPRITES "PART2_SPRITES" + .var p2_sprites = LoadPicture("spritefont.png", List().add($000000, $ffffff)) + .for(var sy=0;sy<8;sy++) { + .for(var sx=0;sx<8;sx++) { + .for (var y=0;y<21; y++) { + .for (var c=0; c<3; c++) { + .byte p2_sprites.getSinglecolorByte(sx*3+c,sy*21+y) + } + } + .byte 0 + } + } + } +}}; +#pragma data_seg(DataPart2) + +#pragma data_seg(InitPart2) +char LOGO_DATA_CRUNCHED[] = kickasm(resource "logo-bitmap-640.png", resource "mcbitmap.asm", uses LOGO_HEIGHT, uses LOGO_WIDTH) {{ + .modify B2() { + .pc = LOGO_DATA "LOGO DATA" + #import "mcbitmap.asm" + .var mcBmmData2 = getMcBitmapData(LoadPicture("logo-bitmap-640.png")) + // Screen data + .for (var y=0; y