From 1fd5cc001c0083fc19a3720c09f4c188a5547522 Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Mon, 4 Jan 2021 01:02:00 +0100 Subject: [PATCH] Added new_30_years_low_resolution. --- .../new_30_years_low_resolution/byteboozer.c | 18 + .../new_30_years_low_resolution/byteboozer.h | 6 + .../byteboozer_decrunch.asm | 182 +++++ .../new_30_years_low_resolution/demo.h | 13 + .../new_30_years_low_resolution/demo.ld | 19 + .../do-it-again-$AC00-$FA-8580.sid | Bin 0 -> 4563 bytes .../happy-newyear.png | Bin 0 -> 14307 bytes .../logo-bitmap-640.png | Bin 0 -> 17623 bytes .../new_30_years_low_resolution/mcbitmap.asm | 168 +++++ .../multiplex-bucket.c | 195 ++++++ .../multiplex-bucket.h | 81 +++ .../new_30_years_low_resolution.c | 142 ++++ .../part1-happynewyear.c | 355 ++++++++++ .../part2-swingplex.c | 631 ++++++++++++++++++ .../new_30_years_low_resolution/sparklers.png | Bin 0 -> 1613 bytes .../spritefont.png | Bin 0 -> 4272 bytes 16 files changed, 1810 insertions(+) create mode 100644 src/test/kc/complex/new_30_years_low_resolution/byteboozer.c create mode 100644 src/test/kc/complex/new_30_years_low_resolution/byteboozer.h create mode 100644 src/test/kc/complex/new_30_years_low_resolution/byteboozer_decrunch.asm create mode 100644 src/test/kc/complex/new_30_years_low_resolution/demo.h create mode 100644 src/test/kc/complex/new_30_years_low_resolution/demo.ld create mode 100644 src/test/kc/complex/new_30_years_low_resolution/do-it-again-$AC00-$FA-8580.sid create mode 100644 src/test/kc/complex/new_30_years_low_resolution/happy-newyear.png create mode 100644 src/test/kc/complex/new_30_years_low_resolution/logo-bitmap-640.png create mode 100644 src/test/kc/complex/new_30_years_low_resolution/mcbitmap.asm create mode 100644 src/test/kc/complex/new_30_years_low_resolution/multiplex-bucket.c create mode 100644 src/test/kc/complex/new_30_years_low_resolution/multiplex-bucket.h create mode 100644 src/test/kc/complex/new_30_years_low_resolution/new_30_years_low_resolution.c create mode 100644 src/test/kc/complex/new_30_years_low_resolution/part1-happynewyear.c create mode 100644 src/test/kc/complex/new_30_years_low_resolution/part2-swingplex.c create mode 100644 src/test/kc/complex/new_30_years_low_resolution/sparklers.png create mode 100644 src/test/kc/complex/new_30_years_low_resolution/spritefont.png 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 0000000000000000000000000000000000000000..6e27dff63052f096d76e802599abbcd94beee30f GIT binary patch literal 4563 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 + +#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