Skip to content

Instantly share code, notes, and snippets.

@nonarkitten
Last active November 24, 2020 09:13
Show Gist options
  • Save nonarkitten/cc8525882505b5c8a3b3b2490072d53a to your computer and use it in GitHub Desktop.
Save nonarkitten/cc8525882505b5c8a3b3b2490072d53a to your computer and use it in GitHub Desktop.
#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