From a74d02203e22635b0cb8dc10534fb4efda94f450 Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Sun, 5 Jul 2020 18:27:42 +0200 Subject: [PATCH] Attempting to port lazyNES API to KickC. --- src/main/kc/lib/nes.c | 2 +- src/test/kc/complex/lazynes/example.chr | Bin 0 -> 4096 bytes src/test/kc/complex/lazynes/lazyhello.c | 16 +++ src/test/kc/complex/lazynes/lazymain.c | 61 ++++++++++ src/test/kc/complex/lazynes/lazynes.c | 105 ++++++++++++++++++ src/test/kc/complex/lazynes/lazynes.h | 105 ++++++++++++++++++ src/test/kc/complex/lazynes/sprites.chr | Bin 0 -> 4096 bytes src/test/kc/complex/polygon/polygon.c | 2 +- src/test/kc/complex/xy-scroller/xy-scroller.c | 65 +++++++++++ 9 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 src/test/kc/complex/lazynes/example.chr create mode 100644 src/test/kc/complex/lazynes/lazyhello.c create mode 100644 src/test/kc/complex/lazynes/lazymain.c create mode 100644 src/test/kc/complex/lazynes/lazynes.c create mode 100644 src/test/kc/complex/lazynes/lazynes.h create mode 100644 src/test/kc/complex/lazynes/sprites.chr create mode 100644 src/test/kc/complex/xy-scroller/xy-scroller.c diff --git a/src/main/kc/lib/nes.c b/src/main/kc/lib/nes.c index 4c80cba3d..27e572be0 100644 --- a/src/main/kc/lib/nes.c +++ b/src/main/kc/lib/nes.c @@ -55,7 +55,7 @@ inline void initNES() { // which may be accomplished by waiting for 2 (two) vertical blank intervals. clearVBlankFlag(); waitForVBlank(); - // Clear RAM - since it has all variables and the stack it is necesary to do it inline + // Clear RAM - since it has all variables and the stack it is necessary to do it inline char i=0; do { (MEMORY+0x000)[i] = 0; diff --git a/src/test/kc/complex/lazynes/example.chr b/src/test/kc/complex/lazynes/example.chr new file mode 100644 index 0000000000000000000000000000000000000000..06c1e76c18658fda8616043be6e644ba95c4b7f8 GIT binary patch literal 4096 zcmeHHG0WR95EU3aiO^9)=x-2wiwE(2Tj|gr5lYdNAyWy(__y73lgPJ)4kdKz7z%-s z!N`5G-ID^HyHsbB+`D%=>7FDj^*5WA;G`C((KZDd{q%|-ORN7fO_L(48(^~GHYS`5 zvE?t^7FMa(r#Wq98->)DH8xiS9&f?YQeCO!r{rq?RsrLjG0MoK`~7?IljHq_$4kr% z)VJe&Jm%O(uZ{KZgg*uvLijA$m^b1l2iew|`MAt+_!_aC zB!P+nO^fO~l9cWPGk@v?o6f)n!98!>@p}|JXyaPWQ*7NnM4i_^(%6=~+tJ8He&o4h zsn>%nd4QqSN8z#fVmDG4%@_S{(2X^lBENqS;9xX5J_wKcTdT6ZJzvfj%KdbE=EyAh zBXfEMJ5NO33BCvD7w&>1(MXi_S}+O!yrUEQXTcba>GFKo+z0*Nwve;XkHgoW_irZ| zznd-Z7hhvM7xnnZuXs7-`sg2)`$cn%1xvleU&0r`;7{dzIM01sj9=D^Jy|VPFY5zb z`!1h(=5vhKyq?7N1@_+K0_J*gv+WMW`E$4~)@J<)yRX~y1zybI`e>bb^Kg0oWUb$I Zfz_!BR0XO6Re`ENRiG+R75HBi_zgs0<5B +#include +#include "lazynes.c" +#include "lazyhello.c" + +// RESET Called when the NES is reset, including when it is turned on. +void main() { + // Initialize NES after RESET + initNES(); + // Clear the name table + ppuDataFill(PPU_NAME_TABLE_0, 0, 0x3c0); + // Fill the PPU attribute table + ppuDataFill(PPU_ATTRIBUTE_TABLE_0, 0, 0x40); + // Enable screen rendering and vblank + enableVideoOutput(); + // Execute main code + lnMain(); + // Infinite loop + while(1) ; +} + +// NMI Called when the PPU refreshes the screen (also known as the V-Blank period) +interrupt(hardware_stack) void vblank() { + // DMA transfer the entire sprite buffer to the PPU + ppuSpriteBufferDmaTransfer(SPRITE_BUFFER); + // Set scroll + PPU->PPUSCROLL = 0; + PPU->PPUSCROLL = 0; +} + +// Data (in PRG ROM) +#pragma data_seg(Data) + +// Tile Set (in CHR ROM) +#pragma data_seg(Tiles) +export char TILES[] = kickasm(resource "example.chr") {{ + .import binary "example.chr" +}}; + +// Sprite Buffer (in GAME RAM) +// Will be transferred to the PPU via DMA during vblank +#pragma data_seg(GameRam) +struct SpriteData align(0x100) SPRITE_BUFFER[0x40]; + +// Interrupt Vectors (in PRG ROM) +#pragma data_seg(Vectors) +export void()* const VECTORS[] = { + // NMI Called when the PPU refreshes the screen (also known as the V-Blank period) + &vblank, + // RESET Called when the NES is reset, including when it is turned on. + &main, + // IRQ Called when a BRK instruction is executed. + 0 +}; diff --git a/src/test/kc/complex/lazynes/lazynes.c b/src/test/kc/complex/lazynes/lazynes.c new file mode 100644 index 000000000..0569e9d6b --- /dev/null +++ b/src/test/kc/complex/lazynes/lazynes.c @@ -0,0 +1,105 @@ + // lazyNES - As lazy as possible NES hardware support library for vbcc6502 + // (happily cooperates with Shiru's famitone2 replay code) +// V1.0, 'Lazycow 2020 +// Ported to KickC 2020 by Jesper Gravgaard +// Original Source VBCC aplha 2 http://www.ibaug.de/vbcc/vbcc6502_2.zip + +#include "lazynes.h" +#include + + // Wait for next vblank + // flags: 0, lfBlank or lfSplit (see below) + // result: Amount of frames since last sync [0..31], 128 is added on NTSC +// +ubyte lnSync(ubyte flags) { + // Enable video output if lfBlank not set + if(!(flags&lfBlank)) + enableVideoOutput(); + // Wait for V-Blank + waitForVBlank(); + // Disable video output if lfBlank set + if(flags&lfBlank) + // lfBlank = 1, activates blank mode, blanks screen and allows lnPush() calls + disableVideoOutput(); + + // TODO: Handle lfSplit = 2 : activates split mode, NMI waits for SPR0HIT and sets registers + // TODO: Count frames + return 0; +} + + // Write data into nametables, palettes, CHRRAM, etc. + // (System must be in blank mode!) + // o: Destination address offset in vram + // a: Amount of bytes that should be written + // p: Pointer to data +// +void lnPush(uword o, ubyte a, void* s) { + ppuDataTransfer(o, s, a); +} + + // Write data into nametables, palettes, CHRRAM, etc. + // (Screen has to be visible, doesn't work in blank mode!) + // updateList: Pointer to update list +// +// TODO: void lnList(void* updateList); +// TODO: enum { lfHor=64, lfVer=128, lfEnd=255 }; + + // remarks: + // - The format of the update list is an array of unsigned bytes. + // - There can be 3 different commands in the update list: + // a) addressHi, addressLo, value + // b) addressHi|lfHor, addressLo, amountOfBytes, byte1, byte2, byte3, ... + // c) addressHi|lfVer, addressLo, amountOfBytes, byte1, byte2, byte3, ... + // - Multiple commands can be queued in one list, + // but there can only be one activated updatelist at a time. + // - The end of the list is marked by lfEnd! (important!) + // - It's the same format that's used in set_vram_update() of Shiru's neslib + + + // Scroll background + // x: New horizotnal scrolling offset in pixels, allowed range: [0..511] + // y: New vertical scrolling offset in pixels, allowed range: [0..479] +// +// TODO: void lnScroll(uword x, uword y); + // + // remarks: + // - If a SPR0HIT based splitscreen is used, the 1st call of lnScroll() sets + // the scrolling offsets of the area above the split and the 2nd call of + // lnScroll() sets the scrolling offsets of the area below the split. + + + // Add meta-sprite to display list + // p: Pointer to metasprite data + // x,y: Sprite coordinates + // result: New position offset in OAM after the meta sprite has been added +// +// TODO: ubyte lnAddSpr(void* p, sword x, sword y); + // + // remarks: + // - The format for the metasprite data is an array of unsigned bytes. + // - Four bytes per sprite: x-offset, y-offset, tile, attributes + // - The end of the list is marked by the value 128! (important!) + // - It's the same format that's used in oam_meta_spr() from Shiru's neslib + + + // Query joypad state + // port: Joypad port (1 or 2) + // result: Set of joypad flags (see below) +// +// TODO: ubyte lnGetPad(ubyte port); +// TODO: enum { lfU=8, lfD=4, lfL=2, lfR=1, lfA=128, lfB=64, lfStart=16, lfSelect=32 }; + + + // + // advanced usage +// + +__zp volatile ubyte + lnSpr0Wait, // delay until scroll registers will be set after a SPR0HIT + lnPPUCTRL, // current value of PPUCTRL register (will be written in NMI) + lnPPUMASK; // current value of PPUMASK register (will be written in NMI) + // + // remark: The lazyNES NMI will write the PPUCTRL and PPUMASK registers, + // so don't write PPUCTRL and PPUMASK directly - use these two + // variables insead. Their values will be written in the next NMI. + // Also, don't use these variables before the 1st call of lnSync()! diff --git a/src/test/kc/complex/lazynes/lazynes.h b/src/test/kc/complex/lazynes/lazynes.h new file mode 100644 index 000000000..76ac9d504 --- /dev/null +++ b/src/test/kc/complex/lazynes/lazynes.h @@ -0,0 +1,105 @@ + // lazyNES - As lazy as possible NES hardware support library for vbcc6502 + // (happily cooperates with Shiru's famitone2 replay code) +// V1.0, 'Lazycow 2020 + +// Ported to KickC 2020 by Jesper Gravgaard +// Original Source VBCC aplha 2 http://www.ibaug.de/vbcc/vbcc6502_2.zip + +typedef signed char sbyte; +typedef unsigned char ubyte; +typedef signed short sword; +typedef unsigned short uword; + + // Wait for next vblank + // flags: 0, lfBlank or lfSplit (see below) + // result: Amount of frames since last sync [0..31], 128 is added on NTSC +// +ubyte lnSync(ubyte flags); + enum { + lfBlank = 1, // activates blank mode, blanks screen and allows lnPush() calls + lfSplit = 2 // activates split mode, NMI waits for SPR0HIT and sets registers + }; + + + // Write data into nametables, palettes, CHRRAM, etc. + // (System must be in blank mode!) + // o: Destination address offset in vram + // a: Amount of bytes that should be written + // p: Pointer to data +// +void lnPush(uword o, ubyte a, void* s); + + + // Write data into nametables, palettes, CHRRAM, etc. + // (Screen has to be visible, doesn't work in blank mode!) + // updateList: Pointer to update list +// +// TODO: void lnList(void* updateList); +// TODO: enum { lfHor=64, lfVer=128, lfEnd=255 }; + // + // remarks: + // - The format of the update list is an array of unsigned bytes. + // - There can be 3 different commands in the update list: + // a) addressHi, addressLo, value + // b) addressHi|lfHor, addressLo, amountOfBytes, byte1, byte2, byte3, ... + // c) addressHi|lfVer, addressLo, amountOfBytes, byte1, byte2, byte3, ... + // - Multiple commands can be queued in one list, + // but there can only be one activated updatelist at a time. + // - The end of the list is marked by lfEnd! (important!) + // - It's the same format that's used in set_vram_update() of Shiru's neslib + + + // Common offsets for lnPush() and lnList() +enum { + lnNameTab0=0x2000, lnNameTab1=0x2400, lnNameTab2=0x2800, lnNameTab3=0x2C00, + lnAttrTab0=0x23C0, lnAttrTab1=0x27C0, lnAttrTab2=0x2BC0, lnAttrTab3=0x2FC0, + lnBackCol=0x3F00, + lnChrPal0=0x3F01, lnChrPal1=0x3F05, lnChrPal2=0x3F09, lnChrPal3=0x3F0D, + lnSprPal0=0x3F11, lnSprPal1=0x3F15, lnSprPal2=0x3F19, lnSprPal3=0x3F1D +}; + + // Scroll background + // x: New horizotnal scrolling offset in pixels, allowed range: [0..511] + // y: New vertical scrolling offset in pixels, allowed range: [0..479] +// +// TODO: void lnScroll(uword x, uword y); + // + // remarks: + // - If a SPR0HIT based splitscreen is used, the 1st call of lnScroll() sets + // the scrolling offsets of the area above the split and the 2nd call of + // lnScroll() sets the scrolling offsets of the area below the split. + + // Add meta-sprite to display list + // p: Pointer to metasprite data + // x,y: Sprite coordinates + // result: New position offset in OAM after the meta sprite has been added +// +// TODO: ubyte lnAddSpr(void* p, sword x, sword y); + // + // remarks: + // - The format for the metasprite data is an array of unsigned bytes. + // - Four bytes per sprite: x-offset, y-offset, tile, attributes + // - The end of the list is marked by the value 128! (important!) + // - It's the same format that's used in oam_meta_spr() from Shiru's neslib + + + // Query joypad state + // port: Joypad port (1 or 2) + // result: Set of joypad flags (see below) +// +// TODO: ubyte lnGetPad(ubyte port); +// TODO: enum { lfU=8, lfD=4, lfL=2, lfR=1, lfA=128, lfB=64, lfStart=16, lfSelect=32 }; + + // + // advanced usage +// + +extern __zp volatile ubyte + lnSpr0Wait, // delay until scroll registers will be set after a SPR0HIT + lnPPUCTRL, // current value of PPUCTRL register (will be written in NMI) + lnPPUMASK; // current value of PPUMASK register (will be written in NMI) + // + // remark: The lazyNES NMI will write the PPUCTRL and PPUMASK registers, + // so don't write PPUCTRL and PPUMASK directly - use these two + // variables insead. Their values will be written in the next NMI. + // Also, don't use these variables before the 1st call of lnSync()! diff --git a/src/test/kc/complex/lazynes/sprites.chr b/src/test/kc/complex/lazynes/sprites.chr new file mode 100644 index 0000000000000000000000000000000000000000..1a97ed8818aa2734f73d0929dce7b0ef3831b511 GIT binary patch literal 4096 zcmdFx=g+@?|Ng-E_4V=k>cYaDF#exEUtXM;FhK#v=VWJRXNGZr zw8DV{2O5z04D1qiHj%;Z4D9lT%IW#_`T6yBmU8S2_4W1k_VVlu3=e+T{Q36n6_5s7 z{OQrLDLNJp7=UhgbLYnc2Aj0=^Y{KOvayNV|NsB*IvZr8N7p&?0FX660{{R3 literal 0 HcmV?d00001 diff --git a/src/test/kc/complex/polygon/polygon.c b/src/test/kc/complex/polygon/polygon.c index e622f2109..bb09b081c 100644 --- a/src/test/kc/complex/polygon/polygon.c +++ b/src/test/kc/complex/polygon/polygon.c @@ -247,7 +247,7 @@ void eorfill(char* line_buffer, char* canvas) { } } -// Get the absolute value of a u-bit unsigned number treated as a signed number. +// Get the absolute value of a 8-bit unsigned number treated as a signed number. unsigned char abs_u8(unsigned char u) { if(u & 0x80) { return -u; diff --git a/src/test/kc/complex/xy-scroller/xy-scroller.c b/src/test/kc/complex/xy-scroller/xy-scroller.c new file mode 100644 index 000000000..ba0649bc8 --- /dev/null +++ b/src/test/kc/complex/xy-scroller/xy-scroller.c @@ -0,0 +1,65 @@ +// A full-screen x/y-scroller +// The main screen is double-buffered with a shared charset. Most of the screen is expected to be empty (filled with char 0) +// While scrolling through the pixels on one screen the second screen is prepared. When needed a swap is made. +// At any time an object is being scrolled onto the screen. The object is fetched from an 8x8 char buffer. +// +// The scroll director orchestrates the movement of the main screen, rendering on the second screen from the buffer and the rendering of graphics in the buffer. +// In practice the scroll director calculates a number of frames ahead to identify the operations needed on each frame. It calculates ahead until it encounters the next "new buffer needed". + +#include +#include +#include +#include + +// Dispaly screen #1 (double buffered) +char * const MAIN_SCREEN1 = 0x0400; +// Display screen #2 (double buffered) +char * const MAIN_SCREEN2 = 0x3800; +// Display charset +char * const MAIN_CHARSET = 0x1000; + +// The render 8x8 buffer containing chars to be rendered onto the screen +char RENDER_BUFFER[8*8] = + " ** " + " ****** " + " ****** " + "********" + "********" + " ****** " + " ****** " + " ** "z +; + +void main() { + VICII->MEMORY = toD018(MAIN_SCREEN1, MAIN_CHARSET); + memset(MAIN_SCREEN1, ' ', 1000); + + // positions to render to + char xpos=0, ypos=0; + while(xpos < 40 && ypos < 25) { + char *sc = MAIN_SCREEN1 + (unsigned int)ypos*40 + xpos; + char i=0; + for(char y=0; y<8; y++) { + for(char x=0; x<8; x++) { + char c=RENDER_BUFFER[i++]; + if(c!=' ' && xpos+x<40 && ypos+y<25) + sc[x] = c; + } + sc+=40; + } + xpos+=5; + ypos+=3; + } + + // count the number of chars + unsigned int count = 0; + for(char *sc = MAIN_SCREEN1;sc