Last active
October 16, 2023 22:22
-
-
Save juj/84fc977c7f928e9343bb6f5f74c45a57 to your computer and use it in GitHub Desktop.
llvm-mos small C64 sprite example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C64 sprite example with llvm-mos. | |
// Jukka Jylänki. Released to public domain. | |
// Build with -Oz -DNDEBUG for smallest code size (640 bytes). | |
#include <stdint.h> | |
#include <stdio.h> | |
static void TestAssert(bool condition, const char *str, const char *file, int line, const char *func) | |
{ | |
#ifndef NDEBUG // Build with -DNDEBUG to remove this bloating code size | |
if (!condition) { | |
printf("%s:%d:%s: ASSERT \"%s\" FAILED!\n", file, line, func, str); | |
for(;;) { // Halt, and blink border red-black | |
*(volatile uint8_t*)0xD020 = (*(volatile uint8_t*)0xD020) & 0xF ? 0 : 0x0A; // D020h - screen border color | |
for(unsigned int i = 0; i < 65535; ++i) asm("NOP":::"memory"); // pause for a moment | |
} | |
} | |
#endif | |
} | |
#define Assert(x) TestAssert((x), #x, __FILE__, __LINE__, __func__); | |
// In the functions below, the sprite index i must be in range [0,7]. | |
void SET_SPRITE_PTR(uint8_t i, void *ptr) | |
{ | |
Assert((uintptr_t)ptr < 16384 && (uintptr_t)ptr % 64 == 0); // C64 hardware places constraints on possible sprite mem locations | |
// Sprite ptr address depends on the value of D018h. | |
// In production code however we don't want to dynamically compute the sprite ptr address based on | |
// D018h as that would be costly. So instead we hardcode the sprite_ptr_addr value here, and only | |
// assert that we got it right. | |
uint16_t actual_sprite_ptr_addr = (((*(uint16_t*)0xD018) & 0xF0) << 6) + 1016; | |
const uint16_t hardcoded_sprite_ptr_addr = 2040; // If you change value in D018h, update this address accordingly. | |
Assert(hardcoded_sprite_ptr_addr == actual_sprite_ptr_addr); | |
((uint8_t*)hardcoded_sprite_ptr_addr)[i] = (uint8_t)((uintptr_t)ptr >> 6); | |
} | |
void ENABLE_SPRITE(uint8_t i) { *(uint8_t*)0xD015 |= (1<<i); } // Show i'th sprite | |
void DISABLE_SPRITE(uint8_t i) { *(uint8_t*)0xD015 &= ~(1<<i); } // Hide i'th sprite | |
bool IS_SPRITE_ENABLED(uint8_t i) { return !!(*(uint8_t*)0xD015 & (1<<i)); } | |
void SET_SPRITE_COLOR(uint8_t i, uint8_t color) { ((uint8_t*)0xD027)[i] = color; } // Color [0,15] | |
void SET_SPRITE_POS(uint8_t i, uint16_t x, uint8_t y) // X=[0, 511], Y=[0,255] | |
{ | |
((uint8_t*)0xD000)[i<<1] = x; // Set bits 0-7 of x coordinate | |
((uint8_t*)0xD000)[(i<<1)+1] = y; // Set y coordinate | |
if (x>>8) *((uint8_t*)0xD010) |= 1<<i; // Set bit 8 of x coordinate in overflow memory | |
else *((uint8_t*)0xD010) &= ~(1<<i); // ...or clear bit 8 of x coordinate in overflow memory | |
} | |
uint8_t sprite[63] __attribute__((aligned(64))) = // Each sprite is 24x21 bits and must be located in memory aligned to 64 bytes. | |
{ | |
0b00000000, 0b01010101, 0b00000000, | |
0b00000010, 0b10101010, 0b10000000, | |
0b00000101, 0b01010101, 0b01010000, | |
0b00101010, 0b10101010, 0b10101000, | |
0b01010101, 0b01010101, 0b01010101, | |
0b10101010, 0b10101010, 0b10101010, | |
0b01010101, 0b01010101, 0b01010101, | |
0b10101010, 0b10101010, 0b10101010, | |
0b01010101, 0b01010101, 0b01010101, | |
0b10101010, 0b10101010, 0b10101010, | |
0b01010101, 0b01010101, 0b01010101, | |
0b10101010, 0b10101010, 0b10101010, | |
0b01010101, 0b01010101, 0b01010101, | |
0b10101010, 0b10101010, 0b10101010, | |
0b01010101, 0b01010101, 0b01010101, | |
0b10101010, 0b10101010, 0b10101010, | |
0b01010101, 0b01010101, 0b01010101, | |
0b00101010, 0b10101010, 0b10101000, | |
0b00000101, 0b01010101, 0b01010000, | |
0b00000010, 0b10101010, 0b10000000, | |
0b00000000, 0b01010101, 0b00000000, | |
}; | |
static const int8_t sincos[256] = { 0,3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54,57,59,62,65,67,70,73,75,78,80,82,85,87,89,91,94,96,98,100,102,103,105,107,108,110,112,113,114,116,117,118,119,120,121,122,123,123,124,125,125,126,126,126,126,126,127,126,126,126,126,126,125,125,124,123,123,122,121,120,119,118,117,116,114,113,112,110,108,107,105,103,102,100,98,96,94,91,89,87,85,82,80,78,75,73,70,67,65,62,59,57,54,51,48,45,42,39,36,33,30,27,24,21,18,15,12,9,6,3,0,-3,-6,-9,-12,-15,-18,-21,-24,-27,-30,-33,-36,-39,-42,-45,-48,-51,-54,-57,-59,-62,-65,-67,-70,-73,-75,-78,-80,-82,-85,-87,-89,-91,-94,-96,-98,-100,-102,-103,-105,-107,-108,-110,-112,-113,-114,-116,-117,-118,-119,-120,-121,-122,-123,-123,-124,-125,-125,-126,-126,-126,-126,-126,-127,-126,-126,-126,-126,-126,-125,-125,-124,-123,-123,-122,-121,-120,-119,-118,-117,-116,-114,-113,-112,-110,-108,-107,-105,-103,-102,-100,-98,-96,-94,-91,-89,-87,-85,-82,-80,-78,-75,-73,-70,-67,-65,-62,-59,-57,-54,-51,-48,-45,-42,-39,-36,-33,-30,-27,-24,-21,-18,-15,-12,-9,-6,-3, }; | |
uint16_t RASTER_LINE() { return ((uint16_t)(*((volatile uint8_t*)0xD011) & 0x80) << 1) | *(volatile uint8_t*)0xD012; } // ghetto | |
int main() | |
{ | |
for(uint8_t i = 0; i < 8; ++i) | |
{ | |
SET_SPRITE_PTR(i, sprite); // Set all 8 sprites to show the same sprite image above | |
ENABLE_SPRITE(i); | |
SET_SPRITE_COLOR(i, i + (i>=6)); // Assign colors, but skip color 6 since it is the default background color | |
} | |
for(uint8_t i = 0;; ++i) | |
{ | |
while(RASTER_LINE() < 256); // Wait until raster line passes visible sprites so changing positions won't flicker | |
for(int j = 0; j < 8; ++j) | |
SET_SPRITE_POS(j, 170+(sincos[(uint8_t)(i+(j<<5))]>>1), 136+(sincos[(uint8_t)(i+64+(j<<5))]>>1)); // Animate around in a circle | |
while(RASTER_LINE() >= 256); // Wait for one frame to pass to limit animation speed | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment