// // Apple 2 video support // // All the video modes that a real Apple 2 supports are handled here // // by James Hammons // (c) 2005-2018 Underground Software // // JLH = James Hammons // // WHO WHEN WHAT // --- ---------- ----------------------------------------------------------- // JLH 12/01/2005 Added color TV/monochrome emulation to hi-res code // JLH 12/09/2005 Cleaned up color TV emulation code // JLH 12/09/2005 Fixed lo-res color TV/mono emulation modes // // STILL TO DO: // // - Fix LoRes mode green mono to skip every other scanline instead of fill // like white mono does [DONE] // - Double HiRes [DONE] // - 80 column text [DONE] // - Fix OSD text display so that it's visible no matter what background is // there [DONE] // // Display routines seem MUCH slower now... !!! INVESTIGATE !!! [not anymore] #include "video.h" #include // for memset() #include #include // for va_* stuff #include "apple2.h" #include "apple2-icon-64x64.h" #include "charset.h" #include "log.h" #include "settings.h" #include "gui/font14pt.h" #include "gui/gui.h" /* Reference: Technote tn-iigs-063 "Master Color Values" Color Color Register LR HR DHR Master Color R,G,B Name Value # # # Value ---------------------------------------------------- Black 0 0 0,4 0 $0000 (0,0,0) (Magenta) Deep Red 1 1 1 $0D03 (D,0,3) Dark Blue 2 2 8 $0009 (0,0,9) (Violet) Purple 3 3 2 9 $0D2D (D,2,D) Dark Green 4 4 4 $0072 (0,7,2) (Gray 1) Dark Gray 5 5 5 $0555 (5,5,5) (Blue) Medium Blue 6 6 6 C $022F (2,2,F) (Cyan) Light Blue 7 7 D $06AF (6,A,F) Brown 8 8 2 $0850 (8,5,0) Orange 9 9 5 3 $0F60 (F,6,0) (Gray 2) Light Gray A A A $0AAA (A,A,A) Pink B B B $0F98 (F,9,8) (Green) Light Green C C 1 6 $01D0 (1,D,0) Yellow D D 7 $0FF0 (F,F,0) (Aqua) Aquamarine E E E $04F9 (4,F,9) White F F 3,7 F $0FFF (F,F,F) LR: Lo-Res HR: Hi-Res DHR: Double Hi-Res N.B.: These colors look like shit */ // Global variables bool flash = false; bool textMode = true; bool mixedMode = false; bool displayPage2 = false; bool hiRes = false; bool alternateCharset = false; bool col80Mode = false; SDL_Renderer * sdlRenderer = NULL; SDL_Window * sdlWindow = NULL; // Local variables static SDL_Texture * sdlTexture = NULL; static uint32_t * scrBuffer; static int scrPitch; static bool showFrameTicks = false; // We set up the colors this way so that they'll be endian safe // when we cast them to a uint32_t. Note that the format is RGBA. // "Master Color Values" palette static uint8_t colors[16 * 4] = { 0x00, 0x00, 0x00, 0xFF, // Black 0xDD, 0x00, 0x33, 0xFF, // Deep Red (Magenta) 0x00, 0x00, 0x99, 0xFF, // Dark Blue 0xDD, 0x22, 0xDD, 0xFF, // Purple (Violet) 0x00, 0x77, 0x22, 0xFF, // Dark Green 0x55, 0x55, 0x55, 0xFF, // Dark Gray (Gray 1) 0x22, 0x22, 0xFF, 0xFF, // Medium Blue (Blue) 0x66, 0xAA, 0xFF, 0xFF, // Light Blue (Cyan) 0x88, 0x55, 0x00, 0xFF, // Brown 0xFF, 0x66, 0x00, 0xFF, // Orange 0xAA, 0xAA, 0xAA, 0xFF, // Light Gray (Gray 2) 0xFF, 0x99, 0x88, 0xFF, // Pink 0x11, 0xDD, 0x00, 0xFF, // Light Green (Green) 0xFF, 0xFF, 0x00, 0xFF, // Yellow 0x44, 0xFF, 0x99, 0xFF, // Aquamarine (Aqua) 0xFF, 0xFF, 0xFF, 0xFF // White }; // This palette comes from ApplePC's colors (more realistic to my eye ;-) static uint8_t altColors[16 * 4] = { 0x00, 0x00, 0x00, 0xFF, 0x7D, 0x20, 0x41, 0xFF, 0x41, 0x30, 0x7D, 0xFF, 0xBE, 0x51, 0xBE, 0xFF, 0x00, 0x5D, 0x3C, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF, 0x41, 0x8E, 0xBA, 0xFF, 0xBE, 0xAE, 0xFB, 0xFF, 0x3C, 0x4D, 0x00, 0xFF, 0xBA, 0x6D, 0x41, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF, 0xFB, 0x9E, 0xBE, 0xFF, 0x3C, 0xAA, 0x3C, 0xFF, 0xBA, 0xCB, 0x7D, 0xFF, 0x7D, 0xDB, 0xBA, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF }; // Lo-res starting line addresses static uint16_t lineAddrLoRes[24] = { 0x0400, 0x0480, 0x0500, 0x0580, 0x0600, 0x0680, 0x0700, 0x0780, 0x0428, 0x04A8, 0x0528, 0x05A8, 0x0628, 0x06A8, 0x0728, 0x07A8, 0x0450, 0x04D0, 0x0550, 0x05D0, 0x0650, 0x06D0, 0x0750, 0x07D0 }; // Hi-res starting line addresses static uint16_t lineAddrHiRes[192] = { 0x2000, 0x2400, 0x2800, 0x2C00, 0x3000, 0x3400, 0x3800, 0x3C00, 0x2080, 0x2480, 0x2880, 0x2C80, 0x3080, 0x3480, 0x3880, 0x3C80, 0x2100, 0x2500, 0x2900, 0x2D00, 0x3100, 0x3500, 0x3900, 0x3D00, 0x2180, 0x2580, 0x2980, 0x2D80, 0x3180, 0x3580, 0x3980, 0x3D80, 0x2200, 0x2600, 0x2A00, 0x2E00, 0x3200, 0x3600, 0x3A00, 0x3E00, 0x2280, 0x2680, 0x2A80, 0x2E80, 0x3280, 0x3680, 0x3A80, 0x3E80, 0x2300, 0x2700, 0x2B00, 0x2F00, 0x3300, 0x3700, 0x3B00, 0x3F00, 0x2380, 0x2780, 0x2B80, 0x2F80, 0x3380, 0x3780, 0x3B80, 0x3F80, 0x2028, 0x2428, 0x2828, 0x2C28, 0x3028, 0x3428, 0x3828, 0x3C28, 0x20A8, 0x24A8, 0x28A8, 0x2CA8, 0x30A8, 0x34A8, 0x38A8, 0x3CA8, 0x2128, 0x2528, 0x2928, 0x2D28, 0x3128, 0x3528, 0x3928, 0x3D28, 0x21A8, 0x25A8, 0x29A8, 0x2DA8, 0x31A8, 0x35A8, 0x39A8, 0x3DA8, 0x2228, 0x2628, 0x2A28, 0x2E28, 0x3228, 0x3628, 0x3A28, 0x3E28, 0x22A8, 0x26A8, 0x2AA8, 0x2EA8, 0x32A8, 0x36A8, 0x3AA8, 0x3EA8, 0x2328, 0x2728, 0x2B28, 0x2F28, 0x3328, 0x3728, 0x3B28, 0x3F28, 0x23A8, 0x27A8, 0x2BA8, 0x2FA8, 0x33A8, 0x37A8, 0x3BA8, 0x3FA8, 0x2050, 0x2450, 0x2850, 0x2C50, 0x3050, 0x3450, 0x3850, 0x3C50, 0x20D0, 0x24D0, 0x28D0, 0x2CD0, 0x30D0, 0x34D0, 0x38D0, 0x3CD0, 0x2150, 0x2550, 0x2950, 0x2D50, 0x3150, 0x3550, 0x3950, 0x3D50, 0x21D0, 0x25D0, 0x29D0, 0x2DD0, 0x31D0, 0x35D0, 0x39D0, 0x3DD0, 0x2250, 0x2650, 0x2A50, 0x2E50, 0x3250, 0x3650, 0x3A50, 0x3E50, 0x22D0, 0x26D0, 0x2AD0, 0x2ED0, 0x32D0, 0x36D0, 0x3AD0, 0x3ED0, 0x2350, 0x2750, 0x2B50, 0x2F50, 0x3350, 0x3750, 0x3B50, 0x3F50, 0x23D0, 0x27D0, 0x2BD0, 0x2FD0, 0x33D0, 0x37D0, 0x3BD0, 0x3FD0 }; uint16_t appleHiresToMono[0x200] = { 0x0000, 0x3000, 0x0C00, 0x3C00, 0x0300, 0x3300, 0x0F00, 0x3F00, 0x00C0, 0x30C0, 0x0CC0, 0x3CC0, 0x03C0, 0x33C0, 0x0FC0, 0x3FC0, // $0x 0x0030, 0x3030, 0x0C30, 0x3C30, 0x0330, 0x3330, 0x0F30, 0x3F30, 0x00F0, 0x30F0, 0x0CF0, 0x3CF0, 0x03F0, 0x33F0, 0x0FF0, 0x3FF0, // $1x 0x000C, 0x300C, 0x0C0C, 0x3C0C, 0x030C, 0x330C, 0x0F0C, 0x3F0C, 0x00CC, 0x30CC, 0x0CCC, 0x3CCC, 0x03CC, 0x33CC, 0x0FCC, 0x3FCC, // $2x 0x003C, 0x303C, 0x0C3C, 0x3C3C, 0x033C, 0x333C, 0x0F3C, 0x3F3C, 0x00FC, 0x30FC, 0x0CFC, 0x3CFC, 0x03FC, 0x33FC, 0x0FFC, 0x3FFC, // $3x 0x0003, 0x3003, 0x0C03, 0x3C03, 0x0303, 0x3303, 0x0F03, 0x3F03, 0x00C3, 0x30C3, 0x0CC3, 0x3CC3, 0x03C3, 0x33C3, 0x0FC3, 0x3FC3, // $4x 0x0033, 0x3033, 0x0C33, 0x3C33, 0x0333, 0x3333, 0x0F33, 0x3F33, 0x00F3, 0x30F3, 0x0CF3, 0x3CF3, 0x03F3, 0x33F3, 0x0FF3, 0x3FF3, // $5x 0x000F, 0x300F, 0x0C0F, 0x3C0F, 0x030F, 0x330F, 0x0F0F, 0x3F0F, 0x00CF, 0x30CF, 0x0CCF, 0x3CCF, 0x03CF, 0x33CF, 0x0FCF, 0x3FCF, // $6x 0x003F, 0x303F, 0x0C3F, 0x3C3F, 0x033F, 0x333F, 0x0F3F, 0x3F3F, 0x00FF, 0x30FF, 0x0CFF, 0x3CFF, 0x03FF, 0x33FF, 0x0FFF, 0x3FFF, // $7x 0x0000, 0x1800, 0x0600, 0x1E00, 0x0180, 0x1980, 0x0780, 0x1F80, 0x0060, 0x1860, 0x0660, 0x1E60, 0x01E0, 0x19E0, 0x07E0, 0x1FE0, // $8x 0x0018, 0x1818, 0x0618, 0x1E18, 0x0198, 0x1998, 0x0798, 0x1F98, 0x0078, 0x1878, 0x0678, 0x1E78, 0x01F8, 0x19F8, 0x07F8, 0x1FF8, // $9x 0x0006, 0x1806, 0x0606, 0x1E06, 0x0186, 0x1986, 0x0786, 0x1F86, 0x0066, 0x1866, 0x0666, 0x1E66, 0x01E6, 0x19E6, 0x07E6, 0x1FE6, // $Ax 0x001E, 0x181E, 0x061E, 0x1E1E, 0x019E, 0x199E, 0x079E, 0x1F9E, 0x007E, 0x187E, 0x067E, 0x1E7E, 0x01FE, 0x19FE, 0x07FE, 0x1FFE, // $Bx 0x0001, 0x1801, 0x0601, 0x1E01, 0x0181, 0x1981, 0x0781, 0x1F81, 0x0061, 0x1861, 0x0661, 0x1E61, 0x01E1, 0x19E1, 0x07E1, 0x1FE1, // $Cx 0x0019, 0x1819, 0x0619, 0x1E19, 0x0199, 0x1999, 0x0799, 0x1F99, 0x0079, 0x1879, 0x0679, 0x1E79, 0x01F9, 0x19F9, 0x07F9, 0x1FF9, // $Dx 0x0007, 0x1807, 0x0607, 0x1E07, 0x0187, 0x1987, 0x0787, 0x1F87, 0x0067, 0x1867, 0x0667, 0x1E67, 0x01E7, 0x19E7, 0x07E7, 0x1FE7, // $Ex 0x001F, 0x181F, 0x061F, 0x1E1F, 0x019F, 0x199F, 0x079F, 0x1F9F, 0x007F, 0x187F, 0x067F, 0x1E7F, 0x01FF, 0x19FF, 0x07FF, 0x1FFF, // $Fx // Second half adds in the previous byte's lo pixel 0x0000, 0x3000, 0x0C00, 0x3C00, 0x0300, 0x3300, 0x0F00, 0x3F00, 0x00C0, 0x30C0, 0x0CC0, 0x3CC0, 0x03C0, 0x33C0, 0x0FC0, 0x3FC0, // $0x 0x0030, 0x3030, 0x0C30, 0x3C30, 0x0330, 0x3330, 0x0F30, 0x3F30, 0x00F0, 0x30F0, 0x0CF0, 0x3CF0, 0x03F0, 0x33F0, 0x0FF0, 0x3FF0, // $1x 0x000C, 0x300C, 0x0C0C, 0x3C0C, 0x030C, 0x330C, 0x0F0C, 0x3F0C, 0x00CC, 0x30CC, 0x0CCC, 0x3CCC, 0x03CC, 0x33CC, 0x0FCC, 0x3FCC, // $2x 0x003C, 0x303C, 0x0C3C, 0x3C3C, 0x033C, 0x333C, 0x0F3C, 0x3F3C, 0x00FC, 0x30FC, 0x0CFC, 0x3CFC, 0x03FC, 0x33FC, 0x0FFC, 0x3FFC, // $3x 0x0003, 0x3003, 0x0C03, 0x3C03, 0x0303, 0x3303, 0x0F03, 0x3F03, 0x00C3, 0x30C3, 0x0CC3, 0x3CC3, 0x03C3, 0x33C3, 0x0FC3, 0x3FC3, // $4x 0x0033, 0x3033, 0x0C33, 0x3C33, 0x0333, 0x3333, 0x0F33, 0x3F33, 0x00F3, 0x30F3, 0x0CF3, 0x3CF3, 0x03F3, 0x33F3, 0x0FF3, 0x3FF3, // $5x 0x000F, 0x300F, 0x0C0F, 0x3C0F, 0x030F, 0x330F, 0x0F0F, 0x3F0F, 0x00CF, 0x30CF, 0x0CCF, 0x3CCF, 0x03CF, 0x33CF, 0x0FCF, 0x3FCF, // $6x 0x003F, 0x303F, 0x0C3F, 0x3C3F, 0x033F, 0x333F, 0x0F3F, 0x3F3F, 0x00FF, 0x30FF, 0x0CFF, 0x3CFF, 0x03FF, 0x33FF, 0x0FFF, 0x3FFF, // $7x 0x2000, 0x3800, 0x2600, 0x3E00, 0x2180, 0x3980, 0x2780, 0x3F80, 0x2060, 0x3860, 0x2660, 0x3E60, 0x21E0, 0x39E0, 0x27E0, 0x3FE0, // $8x 0x2018, 0x3818, 0x2618, 0x3E18, 0x2198, 0x3998, 0x2798, 0x3F98, 0x2078, 0x3878, 0x2678, 0x3E78, 0x21F8, 0x39F8, 0x27F8, 0x3FF8, // $9x 0x2006, 0x3806, 0x2606, 0x3E06, 0x2186, 0x3986, 0x2786, 0x3F86, 0x2066, 0x3866, 0x2666, 0x3E66, 0x21E6, 0x39E6, 0x27E6, 0x3FE6, // $Ax 0x201E, 0x381E, 0x261E, 0x3E1E, 0x219E, 0x399E, 0x279E, 0x3F9E, 0x207E, 0x387E, 0x267E, 0x3E7E, 0x21FE, 0x39FE, 0x27FE, 0x3FFE, // $Bx 0x2001, 0x3801, 0x2601, 0x3E01, 0x2181, 0x3981, 0x2781, 0x3F81, 0x2061, 0x3861, 0x2661, 0x3E61, 0x21E1, 0x39E1, 0x27E1, 0x3FE1, // $Cx 0x2019, 0x3819, 0x2619, 0x3E19, 0x2199, 0x3999, 0x2799, 0x3F99, 0x2079, 0x3879, 0x2679, 0x3E79, 0x21F9, 0x39F9, 0x27F9, 0x3FF9, // $Dx 0x2007, 0x3807, 0x2607, 0x3E07, 0x2187, 0x3987, 0x2787, 0x3F87, 0x2067, 0x3867, 0x2667, 0x3E67, 0x21E7, 0x39E7, 0x27E7, 0x3FE7, // $Ex 0x201F, 0x381F, 0x261F, 0x3E1F, 0x219F, 0x399F, 0x279F, 0x3F9F, 0x207F, 0x387F, 0x267F, 0x3E7F, 0x21FF, 0x39FF, 0x27FF, 0x3FFF // $Fx }; static uint8_t blurTable[0x80][8]; // Color TV blur table static uint8_t mirrorTable[0x100]; static uint32_t * palette = (uint32_t *)altColors; enum { ST_FIRST_ENTRY = 0, ST_COLOR_TV = 0, ST_WHITE_MONO, ST_GREEN_MONO, ST_LAST_ENTRY }; static uint8_t screenType = ST_COLOR_TV; // Local functions static void Render40ColumnTextLine(uint8_t line); static void Render80ColumnTextLine(uint8_t line); static void Render40ColumnText(void); static void Render80ColumnText(void); static void RenderLoRes(uint16_t toLine = 24); static void RenderDLoRes(uint16_t toLine = 24); static void RenderHiRes(uint16_t toLine = 192); static void RenderDHiRes(uint16_t toLine = 192); static void RenderVideoFrame(/*uint32_t *, int*/); void SetupBlurTable(void) { // NOTE: This table only needs to be 7 bits wide instead of 11, since the // last four bits are copies of the previous four... // Odd. Doing the bit patterns from 0-$7F doesn't work, but going // from 0-$7FF stepping by 16 does. Hm. // Well, it seems that going from 0-$7F doesn't have enough precision to do the job. for(uint16_t bitPat=0; bitPat<0x800; bitPat+=0x10) { uint16_t w0 = bitPat & 0x111, w1 = bitPat & 0x222, w2 = bitPat & 0x444, w3 = bitPat & 0x888; uint16_t blurred0 = (w0 | (w0 >> 1) | (w0 >> 2) | (w0 >> 3)) & 0x00FF; uint16_t blurred1 = (w1 | (w1 >> 1) | (w1 >> 2) | (w1 >> 3)) & 0x00FF; uint16_t blurred2 = (w2 | (w2 >> 1) | (w2 >> 2) | (w2 >> 3)) & 0x00FF; uint16_t blurred3 = (w3 | (w3 >> 1) | (w3 >> 2) | (w3 >> 3)) & 0x00FF; for(int8_t i=7; i>=0; i--) { uint8_t color = (((blurred0 >> i) & 0x01) << 3) | (((blurred1 >> i) & 0x01) << 2) | (((blurred2 >> i) & 0x01) << 1) | ((blurred3 >> i) & 0x01); blurTable[bitPat >> 4][7 - i] = color; } } for(int i=0; i<256; i++) { mirrorTable[i] = ((i & 0x01) << 7) | ((i & 0x02) << 5) | ((i & 0x04) << 3) | ((i & 0x08) << 1) | ((i & 0x10) >> 1) | ((i & 0x20) >> 3) | ((i & 0x40) >> 5) | ((i & 0x80) >> 7); } } void TogglePalette(void) { if (palette == (uint32_t *)colors) { palette = (uint32_t *)altColors; SpawnMessage("Color TV palette"); } else { palette = (uint32_t *)colors; SpawnMessage("\"Master Color Values\" palette"); } } void CycleScreenTypes(void) { char scrTypeStr[3][40] = { "Color TV", "White monochrome", "Green monochrome" }; screenType++; if (screenType == ST_LAST_ENTRY) screenType = ST_FIRST_ENTRY; SpawnMessage("%s", scrTypeStr[screenType]); } void ToggleTickDisplay(void) { showFrameTicks = !showFrameTicks; } static uint32_t msgTicks = 0; static char message[4096]; void SpawnMessage(const char * text, ...) { va_list arg; va_start(arg, text); vsprintf(message, text, arg); va_end(arg); msgTicks = 120; //WriteLog("\n%s\n", message); } static void DrawString2(uint32_t x, uint32_t y, uint32_t color, char * msg); static void DrawString(void) { //This approach works, and seems to be fast enough... Though it probably would //be better to make the oversized font to match this one... for(uint32_t x=7; x<=9; x++) for(uint32_t y=7; y<=9; y++) DrawString2(x, y, 0x00000000, message); DrawString2(8, 8, 0x0020FF20, message); } static void DrawString(uint32_t x, uint32_t y, uint32_t color, char * msg) { //This approach works, and seems to be fast enough... Though it probably would //be better to make the oversized font to match this one... for(uint32_t xx=x-1; xx<=x+1; xx++) for(uint32_t yy=y-1; yy<=y+1; yy++) DrawString2(xx, yy, 0x00000000, msg); DrawString2(x, y, color, msg); } static void DrawString2(uint32_t x, uint32_t y, uint32_t color, char * msg) { uint32_t length = strlen(msg), address = x + (y * VIRTUAL_SCREEN_WIDTH); uint8_t nBlue = (color >> 16) & 0xFF, nGreen = (color >> 8) & 0xFF, nRed = color & 0xFF; for(uint32_t i=0; i> 16) & 0xFF, eGreen = (existingColor >> 8) & 0xFF, eRed = existingColor & 0xFF; //This could be sped up by using a table of 5 + 5 + 5 bits (32 levels transparency -> 32768 entries) //Here we've modified it to have 33 levels of transparency (could have any # we want!) //because dividing by 32 is faster than dividing by 31...! uint8_t invTrans = 255 - trans; uint32_t bRed = (eRed * invTrans + nRed * trans) / 255; uint32_t bGreen = (eGreen * invTrans + nGreen * trans) / 255; uint32_t bBlue = (eBlue * invTrans + nBlue * trans) / 255; //THIS IS NOT ENDIAN SAFE //NB: Setting the alpha channel here does nothing. *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = 0x7F000000 | (bBlue << 16) | (bGreen << 8) | bRed; } } } address += FONT_WIDTH; } } static void DrawFrameTicks(void) { uint32_t color = 0x00FF2020; uint32_t address = 8 + (24 * VIRTUAL_SCREEN_WIDTH); for(uint32_t i=0; i<17; i++) { for(uint32_t yy=0; yy<5; yy++) { for(uint32_t xx=0; xx<9; xx++) { //THIS IS NOT ENDIAN SAFE //NB: Setting the alpha channel here does nothing. *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = 0x7F000000; } } address += (5 * VIRTUAL_SCREEN_WIDTH); } address = 8 + (24 * VIRTUAL_SCREEN_WIDTH); // frameTicks is the amount of time remaining; so to show the amount // consumed, we subtract it from 17. uint32_t bars = 17 - frameTicks; if (bars & 0x80000000) bars = 0; for(uint32_t i=0; i<17; i++) { for(uint32_t yy=1; yy<4; yy++) { for(uint32_t xx=1; xx<8; xx++) { //THIS IS NOT ENDIAN SAFE //NB: Setting the alpha channel here does nothing. *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = (i < bars ? color : 0x003F0000); } } address += (5 * VIRTUAL_SCREEN_WIDTH); } static char msg[32]; if ((frameTimePtr % 15) == 0) { // uint32_t prevClock = (frameTimePtr + 1) % 60; uint64_t prevClock = (frameTimePtr + 1) % 60; // float fps = 59.0f / (((float)frameTime[frameTimePtr] - (float)frameTime[prevClock]) / 1000.0f); double fps = 59.0 / ((double)(frameTime[frameTimePtr] - frameTime[prevClock]) / (double)SDL_GetPerformanceFrequency()); sprintf(msg, "%.1lf FPS", fps); } DrawString(20, 24, color, msg); } static void Render40ColumnTextLine(uint8_t line) { uint32_t pixelOn = (screenType == ST_GREEN_MONO ? 0xFF61FF61 : 0xFFFFFFFF); for(int x=0; x<40; x++) { uint8_t chr = ram[lineAddrLoRes[line] + (displayPage2 ? 0x0400 : 0x0000) + x]; // Render character at (x, y) for(int cy=0; cy<8; cy++) { for(int cx=0; cx<7; cx++) { uint32_t pixel = 0xFF000000; if (alternateCharset) { if (textChar2e[(chr * 56) + cx + (cy * 7)]) pixel = pixelOn; } else { if ((chr & 0xC0) == 0x40) { if (textChar2e[((chr & 0x3F) * 56) + cx + (cy * 7)]) pixel = pixelOn; if (flash) pixel = pixel ^ (screenType == ST_GREEN_MONO ? 0x0061FF61 : 0x00FFFFFF); } else { if (textChar2e[(chr * 56) + cx + (cy * 7)]) pixel = pixelOn; } } scrBuffer[(x * 7 * 2) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + (cx * 2) + 0 + (cy * VIRTUAL_SCREEN_WIDTH * 2)] = pixel; scrBuffer[(x * 7 * 2) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + (cx * 2) + 1 + (cy * VIRTUAL_SCREEN_WIDTH * 2)] = pixel; // QnD method to get blank alternate lines in text mode if (screenType == ST_GREEN_MONO) pixel = 0xFF000000; { scrBuffer[(x * 7 * 2) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + (cx * 2) + 0 + (((cy * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = pixel; scrBuffer[(x * 7 * 2) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + (cx * 2) + 1 + (((cy * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = pixel; } } } } } static void Render80ColumnTextLine(uint8_t line) { uint32_t pixelOn = (screenType == ST_GREEN_MONO ? 0xFF61FF61 : 0xFFFFFFFF); for(int x=0; x<80; x++) { uint8_t chr; if (x & 0x01) chr = ram[lineAddrLoRes[line] + (x >> 1)]; else chr = ram2[lineAddrLoRes[line] + (x >> 1)]; // Render character at (x, y) for(int cy=0; cy<8; cy++) { for(int cx=0; cx<7; cx++) { uint32_t pixel = 0xFF000000; if (alternateCharset) { if (textChar2e[(chr * 56) + cx + (cy * 7)]) pixel = pixelOn; } else { if ((chr & 0xC0) == 0x40) { if (textChar2e[((chr & 0x3F) * 56) + cx + (cy * 7)]) pixel = pixelOn; if (flash) pixel = pixel ^ (screenType == ST_GREEN_MONO ? 0x0061FF61 : 0x00FFFFFF); } else { if (textChar2e[(chr * 56) + cx + (cy * 7)]) pixel = pixelOn; } } scrBuffer[(x * 7) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + cx + (cy * 2 * VIRTUAL_SCREEN_WIDTH)] = pixel; // QnD method to get blank alternate lines in text mode if (screenType == ST_GREEN_MONO) pixel = 0xFF000000; scrBuffer[(x * 7) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + cx + (((cy * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = pixel; } } } } static void Render40ColumnText(void) { for(uint8_t line=0; line<24; line++) Render40ColumnTextLine(line); } static void Render80ColumnText(void) { for(uint8_t line=0; line<24; line++) Render80ColumnTextLine(line); } static void RenderLoRes(uint16_t toLine/*= 24*/) { // NOTE: The green mono rendering doesn't skip every other line... !!! FIX !!! // Also, we could set up three different Render functions depending on which // render type was set and call it with a function pointer. Would be faster // then the nested ifs we have now. /* Note that these colors correspond to the bit patterns generated by the numbers 0-F in order: Color #s correspond to the bit patterns in reverse... Interesting! 00 00 00 -> 0 [0000] -> 0 (lores color #) 3c 4d 00 -> 8 [0001] -> 8? BROWN 00 5d 3c -> 4 [0010] -> 4? DARK GREEN 3c aa 3c -> 12 [0011] -> 12? LIGHT GREEN (GREEN) 41 30 7d -> 2 [0100] -> 2? DARK BLUE 7d 7d 7d -> 10 [0101] -> 10? LIGHT GRAY 41 8e ba -> 6 [0110] -> 6? MEDIUM BLUE (BLUE) 7d db ba -> 14 [0111] -> 14? AQUAMARINE (AQUA) 7d 20 41 -> 1 [1000] -> 1? DEEP RED (MAGENTA) ba 6d 41 -> 9 [1001] -> 9? ORANGE 7d 7d 7d -> 5 [1010] -> 5? DARK GRAY ba cb 7d -> 13 [1011] -> 13? YELLOW be 51 be -> 3 [1100] -> 3 PURPLE (VIOLET) fb 9e be -> 11 [1101] -> 11? PINK be ae fb -> 7 [1110] -> 7? LIGHT BLUE (CYAN) fb fb fb -> 15 [1111] -> 15 WHITE */ uint8_t mirrorNybble[16] = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; //This is the old "perfect monitor" rendering code... /* if (screenType != ST_COLOR_TV) // Not correct, but for now... //if (1) { for(uint16_t y=0; y> 4]; for(int cy=4; cy<8; cy++) for(int cx=0; cx<14; cx++) scrBuffer[(x * 14) + (y * VIRTUAL_SCREEN_WIDTH * 8) + cx + (cy * VIRTUAL_SCREEN_WIDTH)] = pixel; } } } else//*/ uint32_t pixelOn = (screenType == ST_WHITE_MONO ? 0xFFFFFFFF : 0xFF61FF61); for(uint16_t y=0; y> 24; pixels <<= 4; for(uint8_t j=0; j<4; j++) { uint8_t color = blurTable[bitPat][j]; for(uint32_t cy=0; cy<8; cy++) { scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; // scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; } } } previous3Bits = pixels & 0x70000000; } else { for(int j=0; j<28; j++) { for(uint32_t cy=0; cy<8; cy++) { scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); // scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); } pixels <<= 1; } } } // Now do bottom half... previous3Bits = 0; for(uint16_t x=0; x<40; x+=2) { uint8_t scrByte1 = ram[lineAddrLoRes[y] + (displayPage2 ? 0x0400 : 0x0000) + x + 0] >> 4; uint8_t scrByte2 = ram[lineAddrLoRes[y] + (displayPage2 ? 0x0400 : 0x0000) + x + 1] >> 4; scrByte1 = mirrorNybble[scrByte1]; scrByte2 = mirrorNybble[scrByte2]; // This is just a guess, but it'll have to do for now... uint32_t pixels = previous3Bits | (scrByte1 << 24) | (scrByte1 << 20) | (scrByte1 << 16) | ((scrByte1 & 0x0C) << 12) | ((scrByte2 & 0x03) << 12) | (scrByte2 << 8) | (scrByte2 << 4) | scrByte2; // We now have 28 pixels (expanded from 14) in word: mask is $0F FF FF FF // 0ppp 1111 1111 1111 11|11 1111 1111 1111 // 31 27 23 19 15 11 7 3 0 if (screenType == ST_COLOR_TV) { for(uint8_t i=0; i<7; i++) { uint8_t bitPat = (pixels & 0x7F000000) >> 24; pixels <<= 4; for(uint8_t j=0; j<4; j++) { uint8_t color = blurTable[bitPat][j]; for(uint32_t cy=8; cy<16; cy++) { scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; // scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; } } } previous3Bits = pixels & 0x70000000; } else { for(int j=0; j<28; j++) { for(uint32_t cy=8; cy<16; cy++) { scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); // scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); } pixels <<= 1; } } } } } // // Render the Double Lo Res screen (HIRES off, DHIRES on) // static void RenderDLoRes(uint16_t toLine/*= 24*/) { // NOTE: The green mono rendering doesn't skip every other line... !!! FIX !!! // Also, we could set up three different Render functions depending on // which render type was set and call it with a function pointer. Would be // faster then the nested ifs we have now. /* Note that these colors correspond to the bit patterns generated by the numbers 0-F in order: Color #s correspond to the bit patterns in reverse... Interesting! [It's because the video generator reads the bit patters from bit 0--which makes them backwards from the normal POV.] 00 00 00 -> 0 [0000] -> 0 (lores color #) 3C 4D 00 -> 8 [0001] -> 8? BROWN 00 5D 3C -> 4 [0010] -> 4? DARK GREEN 3C AA 3C -> 12 [0011] -> 12? LIGHT GREEN (GREEN) 41 30 7D -> 2 [0100] -> 2? DARK BLUE 7D 7D 7D -> 10 [0101] -> 10? LIGHT GRAY (Grays are identical) 41 8E BA -> 6 [0110] -> 6? MEDIUM BLUE (BLUE) 7D DB BA -> 14 [0111] -> 14? AQUAMARINE (AQUA) 7D 20 41 -> 1 [1000] -> 1? DEEP RED (MAGENTA) BA 6D 41 -> 9 [1001] -> 9? ORANGE 7D 7D 7D -> 5 [1010] -> 5? DARK GRAY (Grays are identical) BA CB 7D -> 13 [1011] -> 13? YELLOW BE 51 BE -> 3 [1100] -> 3 PURPLE (VIOLET) FB 9E BE -> 11 [1101] -> 11? PINK BE AE FB -> 7 [1110] -> 7? LIGHT BLUE (CYAN) FB FB FB -> 15 [1111] -> 15 WHITE */ uint8_t mirrorNybble[16] = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; // Rotated one bit right (in the nybble)--right instead of left because // these are backwards after all :-P uint8_t mirrorNybble2[16] = { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; uint32_t pixelOn = (screenType == ST_WHITE_MONO ? 0xFFFFFFFF : 0xFF61FF61); for(uint16_t y=0; y> 24; pixels <<= 4; for(uint8_t j=0; j<4; j++) { uint8_t color = blurTable[bitPat][j]; for(uint32_t cy=0; cy<8; cy++) { scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; // scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; } } } previous3Bits = pixels & 0x70000000; } else { for(int j=0; j<28; j++) { for(uint32_t cy=0; cy<8; cy++) { scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); } pixels <<= 1; } } } // Now do bottom half... previous3Bits = 0; for(uint16_t x=0; x<40; x+=2) { uint8_t scrByte3 = ram2[lineAddrLoRes[y] + x + 0] >> 4; uint8_t scrByte4 = ram2[lineAddrLoRes[y] + x + 1] >> 4; uint8_t scrByte1 = ram[lineAddrLoRes[y] + x + 0] >> 4; uint8_t scrByte2 = ram[lineAddrLoRes[y] + x + 1] >> 4; scrByte1 = mirrorNybble[scrByte1]; scrByte2 = mirrorNybble[scrByte2]; scrByte3 = mirrorNybble2[scrByte3]; scrByte4 = mirrorNybble2[scrByte4]; // This is just a guess, but it'll have to do for now... // uint32_t pixels = previous3Bits | (scrByte1 << 24) | (scrByte1 << 20) | (scrByte1 << 16) // | ((scrByte1 & 0x0C) << 12) | ((scrByte2 & 0x03) << 12) // | (scrByte2 << 8) | (scrByte2 << 4) | scrByte2; uint32_t pixels = previous3Bits | (scrByte3 << 24) | (scrByte3 << 20) | (scrByte1 << 16) | ((scrByte1 & 0x0C) << 12) | ((scrByte4 & 0x03) << 12) | (scrByte4 << 8) | (scrByte2 << 4) | scrByte2; // We now have 28 pixels (expanded from 14) in word: mask is $0F FF FF FF // 0ppp 1111 1111 1111 11|11 1111 1111 1111 // 31 27 23 19 15 11 7 3 0 if (screenType == ST_COLOR_TV) { for(uint8_t i=0; i<7; i++) { uint8_t bitPat = (pixels & 0x7F000000) >> 24; pixels <<= 4; for(uint8_t j=0; j<4; j++) { uint8_t color = blurTable[bitPat][j]; for(uint32_t cy=8; cy<16; cy++) { scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; } } } previous3Bits = pixels & 0x70000000; } else { for(int j=0; j<28; j++) { for(uint32_t cy=8; cy<16; cy++) { scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); } pixels <<= 1; } } } } } static void RenderHiRes(uint16_t toLine/*= 192*/) { //printf("RenderHiRes to line %u\n", toLine); // NOTE: Not endian safe. !!! FIX !!! [DONE] #if 0 uint32_t pixelOn = (screenType == ST_WHITE_MONO ? 0xFFFFFFFF : 0xFF61FF61); #else // Now it is. Now roll this fix into all the other places... !!! FIX !!! // The colors are set in the 8-bit array as R G B A uint8_t monoColors[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0xFF, 0x61, 0xFF }; uint32_t * colorPtr = (uint32_t *)monoColors; uint32_t pixelOn = (screenType == ST_WHITE_MONO ? colorPtr[0] : colorPtr[1]); #endif for(uint16_t y=0; y> 24; pixels <<= 4; for(uint8_t j=0; j<4; j++) { uint8_t color = blurTable[bitPat][j]; #if 0 //This doesn't seem to make things go any faster... //It's the OpenGL render that's faster... Hmm... scrBuffer[(x * 14) + (i * 4) + j + (y * VIRTUAL_SCREEN_WIDTH)] = palette[color]; #else scrBuffer[(x * 14) + (i * 4) + j + (((y * 2) + 0) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; scrBuffer[(x * 14) + (i * 4) + j + (((y * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = palette[color]; #endif } } previous3bits = pixels & 0x70000000; } else { for(int j=0; j<28; j++) { scrBuffer[(x * 14) + j + (((y * 2) + 0) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); if (screenType == ST_GREEN_MONO) pixels &= 0x07FFFFFF; scrBuffer[(x * 14) + j + (((y * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); pixels <<= 1; } } } } } static void RenderDHiRes(uint16_t toLine/*= 192*/) { // Now it is. Now roll this fix into all the other places... !!! FIX !!! // The colors are set in the 8-bit array as R G B A uint8_t monoColors[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0xFF, 0x61, 0xFF }; uint32_t * colorPtr = (uint32_t *)monoColors; uint32_t pixelOn = (screenType == ST_WHITE_MONO ? colorPtr[0] : colorPtr[1]); for(uint16_t y=0; y> 1); // We now have 28 pixels (expanded from 14) in word: mask is $0F FF FF FF // 0ppp 1111 1111 1111 1111 1111 1111 1111 // 31 27 23 19 15 11 7 3 0 if (screenType == ST_COLOR_TV) { for(uint8_t i=0; i<7; i++) { uint8_t bitPat = (pixels & 0xFE000000) >> 25; pixels <<= 4; for(uint8_t j=0; j<4; j++) { uint32_t color = palette[blurTable[bitPat][j]]; scrBuffer[(x * 14) + (i * 4) + j + (((y * 2) + 0) * VIRTUAL_SCREEN_WIDTH)] = color; scrBuffer[(x * 14) + (i * 4) + j + (((y * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = color; } } previous4bits = pixels & 0xF0000000; } else { for(int j=0; j<28; j++) { scrBuffer[(x * 14) + j + (((y * 2) + 0) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); if (screenType == ST_GREEN_MONO) pixels &= 0x07FFFFFF; scrBuffer[(x * 14) + j + (((y * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000); pixels <<= 1; } } } } } void RenderVideoFrame(void) { if (GUI::powerOnState == true) { if (textMode) { if (!col80Mode) Render40ColumnText(); else Render80ColumnText(); } else { if (mixedMode) { if (dhires) { if (hiRes) RenderDHiRes(160); else RenderDLoRes(20); } else if (hiRes) RenderHiRes(160); else RenderLoRes(20); Render40ColumnTextLine(20); Render40ColumnTextLine(21); Render40ColumnTextLine(22); Render40ColumnTextLine(23); } else { if (dhires) { if (hiRes) RenderDHiRes(); else RenderDLoRes(); } else if (hiRes) RenderHiRes(); else RenderLoRes(); } } } else { memset(scrBuffer, 0, VIRTUAL_SCREEN_WIDTH * VIRTUAL_SCREEN_HEIGHT * sizeof(uint32_t)); } if (msgTicks) { DrawString(); msgTicks--; } if (showFrameTicks) DrawFrameTicks(); } // // Prime SDL and create surfaces // bool InitVideo(void) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE) != 0) { WriteLog("Video: Could not initialize the SDL library: %s\n", SDL_GetError()); return false; } #if 0 int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, 0, &sdlWindow, &sdlRenderer); // int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 1, VIRTUAL_SCREEN_HEIGHT * 1, 0, &sdlWindow, &sdlRenderer); if (retVal != 0) { WriteLog("Video: Could not create window and/or renderer: %s\n", SDL_GetError()); return false; } #else sdlWindow = SDL_CreateWindow("Apple2", settings.winX, settings.winY, VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, 0); if (sdlWindow == NULL) { WriteLog("Video: Could not create window: %s\n", SDL_GetError()); return false; } sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (sdlRenderer == NULL) { WriteLog("Video: Could not create renderer: %s\n", SDL_GetError()); return false; } #endif // Make sure what we put there is what we get: SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); SDL_RenderSetLogicalSize(sdlRenderer, VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT); // Set the application's icon & title... SDL_Surface * iconSurface = SDL_CreateRGBSurfaceFrom(icon, 64, 64, 32, 64*4, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); SDL_SetWindowIcon(sdlWindow, iconSurface); SDL_FreeSurface(iconSurface); SDL_SetWindowTitle(sdlWindow, "Apple2 Emulator"); sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT); // Start in fullscreen, if user requested it via config file int response = SDL_SetWindowFullscreen(sdlWindow, (settings.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0)); if (response != 0) WriteLog("Video::FullScreen: SDL error = %s\n", SDL_GetError()); SetupBlurTable(); WriteLog("Video: Successfully initialized.\n"); return true; } // // Free various SDL components // void VideoDone(void) { WriteLog("Video: Shutting down SDL...\n"); SDL_DestroyTexture(sdlTexture); SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow(sdlWindow); SDL_Quit(); WriteLog("Video: Done.\n"); } // // Render the Apple video screen to the primary texture // void RenderAppleScreen(SDL_Renderer * renderer) { SDL_LockTexture(sdlTexture, NULL, (void **)&scrBuffer, &scrPitch); RenderVideoFrame(); SDL_UnlockTexture(sdlTexture); SDL_RenderCopy(renderer, sdlTexture, NULL, NULL); } // // Fullscreen <-> window switching // void ToggleFullScreen(void) { settings.fullscreen = !settings.fullscreen; int retVal = SDL_SetWindowFullscreen(sdlWindow, (settings.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0)); if (retVal != 0) WriteLog("Video::ToggleFullScreen: SDL error = %s\n", SDL_GetError()); }