Last active
November 24, 2020 09:13
-
-
Save nonarkitten/cc8525882505b5c8a3b3b2490072d53a to your computer and use it in GitHub Desktop.
This file contains 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
#include <stdint.h> | |
typedef struct { | |
uint16_t *bitplane[6]; | |
uint16_t *palette; | |
uint16_t modulo; | |
} amiga_t; | |
static const int width = 320; | |
static const int height = 200; | |
static const uint16_t palette[16] = { // Simple RGBV | |
0x000,0x00A,0x0A0,0x0AA,0xA00,0xA0A,0xAA0,0xAAA, | |
0x555,0x55F,0x5F5,0x5FF,0xF55,0xF5F,0xFF5,0xFFF, | |
}; | |
// This is the index from the above table from any RGB 565 colour | |
static uint8_t best_palette[65535]; | |
// This is the dog-leg distance between the above best colour and any RGB 565 colour | |
static uint8_t get_distance[65535]; | |
// Compute the approximate hypotenuse between two RGB values | |
static uint8_t compute_distance(uint16_t rgb565a, uint16_t rgb565b) { | |
int aD = ((rgb565a >> 11) & 0x1F) - ((rgb565b >> 11) & 0x1F); | |
int bD = ((rgb565a >> 6) & 0x1F) - ((rgb565b >> 6) & 0x1F); | |
int cD = ((rgb565a >> 0) & 0x1F) - ((rgb565b >> 0) & 0x1F); | |
// Absolute | |
if (aD < 0) aD = -aD; | |
if (bD < 0) bD = -bD; | |
if (cD < 0) cD = -cD; | |
// Sort so that aD is smallest and cD is largest | |
if (aD > bD) (aD, bD) = (bD, aD); | |
if (aD > cD) (aD, cD) = (cD, aD); | |
if (bD > cD) (bD, cD) = (cD, bD); | |
// Approximate hypot | |
return (uint8_t)(aD / 9 + bD / 3 + cD); | |
} | |
// Convert RGB 444 to RGB 565 with some bit twiddling | |
static uint16_t toRgb565(uint16_r rgb444) { | |
return | |
(((rgb444 & 0xF00) << 4) | (rgb444 & 0x800) << 0) | /* 0xF800 */ | |
(((rgb444 & 0x0F0) << 3) | (rgb444 & 0x0C0) >> 1) | /* 0x03E0 */ | |
(((rgb444 & 0x00F) << 1) | (rgb444 & 0x008) >> 3); /* 0x001F */ | |
} | |
// Generate our two tables; do this before calling encode | |
// otherwise hillarity may ensue | |
static void make_get_distance(void) { | |
for(int rgb=0; rgb<65536; rgb++) { | |
uint8_t max_c, max_d = 255; | |
for(int c=0; c<16; c++) { | |
uint16_t rgb565 = toRgb565(palette[c]); | |
uint8_t d = compute_distance(rgb, rgb565); | |
if(d < max_d) max_d = d, max_c = c; | |
} | |
best_palette[rgb] = max_c; | |
get_distance[rgb] = max_d; | |
} | |
} | |
// Take the pointer to an RGB 565 format and write out HAM6 RGB | |
// This routine uses the above computed tables; make sure you | |
// generate those before calling this. Right now that palette is | |
// static, but we'll rewrite the palette just in case we try and | |
// get a little smarter in the future | |
void encode(uint16_t* rgb565, amiga_t* a) { | |
uint16_t word = 0; | |
uint8_t state = 0; | |
// Copy our palette to the Amiga | |
for(int c=0; c<16; c++) a->palette[c] = palette[c]; | |
// Interate through each scanline | |
for(int y=0; y<height; y++) { | |
// Scanlines will start with the border color 0 | |
uint16_t c_out = palette[0]; | |
// Handle scanline in 16-bit chunks (Amiga planar format) | |
for(int x=0; x<width; x+=16) { | |
// Buffers for our bitplane data | |
uint16_t bitplane[6] = { 0 }; | |
// For each bit | |
for(int b=0; b<16; b++) { | |
// Grab our source colour | |
uint16_t c_in = *rgb565++; | |
// Alternate red, green and blue on each column; we change green twice | |
// because the human eye is more sensitive to green | |
switch(state & 3) { | |
case 0: /* r */ c_out = (c_cout & 0x07FF) | (c_in & 0xF800); break; | |
case 1: /* g */ c_out = (c_cout & 0xF81F) | (c_in & 0x03E0); break; | |
case 2: /* b */ c_out = (c_cout & 0xFFE0) | (c_in & 0x001F); break; | |
case 3: /* g */ c_out = (c_cout & 0xF81F) | (c_in & 0x03E0); break; | |
} | |
state = (state + 1) & 3; | |
// Compute the distance our estimate is from the original source and | |
// compare that to the distance from any of the sixteen base colours. | |
// This is tedious and not needed with AGA hires, but we're doing ECS | |
uint8_t d1 = get_distance[c_out]; | |
uint8_t d2 = compute_distance(c_in, c_out); | |
uint8_t bits; | |
// Assign the bits according to the best match from above | |
if(d1 < d2) { | |
// index color is a better choice than HAM | |
// index color if the 6-5 bit combination is 00 | |
bits = best_palette[c_in]; | |
} else if(p == 0) { | |
// red if the 6-5 bit combination is 10 | |
bits = 0x10 | ((c_out >> 12) & 0xF); | |
} else if(p == 2) { | |
// blue if the 6-5 bit combination is 01 | |
bits = 0x20 | ((c_out >> 1) & 0xF); | |
} else { | |
// green if the 6-5 bit combination is 11 | |
bits = 0x30 | ((c_out >> 7) & 0xF); | |
} | |
// Perform chunky to planar for our one set of bits | |
for(int p=0; p<6; p++) bitplane[i] = (bitplane[i] << 1) | (bits & 1), bits >>= 1; | |
} // end for(b) | |
// Write out the bitplane data to the Amiga | |
for(int i=0; i<6; i++) a->bitplane[i][word] = bitplane[i]; | |
// And next time, we'll write to the next word | |
word++; | |
} // end for(x) | |
// At the end of the scanline, add the modulo to the begining of the next line | |
word+=modulo; | |
} // end for(y) | |
} | |
// This is a gist and not intended to be a full program; I do not know how this ought | |
// to integrate into our project, how it gets the Amiga pointers to chip RAM, but I | |
// will show how to open and monitor the Pi's framebuffer, wait for the vertical sync | |
// and output a new frame to the Amiga. | |
int main(amiga_t *amigaptrs) { | |
// Our screen information; note that we don't CHANGE any of this, just monitor | |
struct fb_fix_screeninfo fbfix; | |
struct fb_var_screeninfo fbvar; | |
// open the frame buffer device; some systems may have fb1... | |
int fbdev = open("/dev/fb0", O_RDWR); | |
// Do forever, or until CTRL+C is pressed | |
while(true) { | |
// wait for the frame buffer to be initialized and set to 320x200 | |
// other modes will require a bit more work in the above code | |
while(true) { | |
ioctl(fbdev, FBIOGET_VSCREENINFO, &fbvar); | |
if(fbvar.xres == 320 && fbvar.yres == 200 && fbvar.bits_per_pixel == 16) break; | |
else usleep(100000); | |
} | |
ioctl(fbdev, FBIOGET_FSCREENINFO, &fbfix); | |
uint32_t screen_size = fbfix.smem_len; | |
uint16_t* fbp = (uint16_t*)mmap(0, screen_size, PROT_READ|PROT_WRITE, MAP_SHARED, fbdev, 0); | |
while(true) { | |
uint32_t dummy; | |
// Wait for vsync; note that this is unreliable if more than one application waits! | |
ioctl(fbdev, FBIO_WAITFORVSYNC, &dummy); | |
encode(fbp, amiga_ptrs); | |
// If the screen mode has changed, then exit | |
ioctl(fbdev, FBIOGET_VSCREENINFO, &fbvar); | |
memcpy( &fbvar_copy, &fbvar, sizeof(struct fb_var_screeninfo)); | |
if(fbvar.xres != 320 || fbvar.yres != 200 || fbvar.bits_per_pixel != 16) break; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment