2 // Apple 2 video support
4 // All the video modes that a real Apple 2 supports are handled here
7 // (c) 2005-2017 Underground Software
9 // JLH = James Hammons <jlhamm@acm.org>
12 // --- ---------- -----------------------------------------------------------
13 // JLH 12/01/2005 Added color TV/monochrome emulation to hi-res code
14 // JLH 12/09/2005 Cleaned up color TV emulation code
15 // JLH 12/09/2005 Fixed lo-res color TV/mono emulation modes
19 // - Fix LoRes mode green mono to skip every other scanline instead of fill
20 // like white mono does [DONE]
21 // - Double HiRes [DONE]
22 // - 80 column text [DONE]
23 // - Fix OSD text display so that it's visible no matter what background is there [DONE]
26 // Display routines seem MUCH slower now... !!! INVESTIGATE !!! [not anymore]
30 #include <string.h> // for memset()
32 #include <stdarg.h> // for va_* stuff
34 #include "apple2-icon-64x64.h"
38 #include "gui/font14pt.h"
41 /* Reference: Technote tn-iigs-063 "Master Color Values"
43 Color Color Register LR HR DHR Master Color R,G,B
44 Name Value # # # Value
45 ----------------------------------------------------
46 Black 0 0 0,4 0 $0000 (0,0,0)
47 (Magenta) Deep Red 1 1 1 $0D03 (D,0,3)
48 Dark Blue 2 2 8 $0009 (0,0,9)
49 (Violet) Purple 3 3 2 9 $0D2D (D,2,D)
50 Dark Green 4 4 4 $0072 (0,7,2)
51 (Gray 1) Dark Gray 5 5 5 $0555 (5,5,5)
52 (Blue) Medium Blue 6 6 6 C $022F (2,2,F)
53 (Cyan) Light Blue 7 7 D $06AF (6,A,F)
54 Brown 8 8 2 $0850 (8,5,0)
55 Orange 9 9 5 3 $0F60 (F,6,0)
56 (Gray 2) Light Gray A A A $0AAA (A,A,A)
57 Pink B B B $0F98 (F,9,8)
58 (Green) Light Green C C 1 6 $01D0 (1,D,0)
59 Yellow D D 7 $0FF0 (F,F,0)
60 (Aqua) Aquamarine E E E $04F9 (4,F,9)
61 White F F 3,7 F $0FFF (F,F,F)
63 LR: Lo-Res HR: Hi-Res DHR: Double Hi-Res
65 N.B.: These colors look like shit */
71 bool mixedMode = false;
72 bool displayPage2 = false;
74 bool alternateCharset = false;
75 bool col80Mode = false;
76 SDL_Renderer * sdlRenderer = NULL;
80 static SDL_Window * sdlWindow = NULL;
81 static SDL_Texture * sdlTexture = NULL;
82 static uint32_t * scrBuffer;
85 // We set up the colors this way so that they'll be endian safe
86 // when we cast them to a uint32_t. Note that the format is RGBA.
88 // "Master Color Values" palette
90 static uint8_t colors[16 * 4] = {
91 0x00, 0x00, 0x00, 0xFF, // Black
92 0xDD, 0x00, 0x33, 0xFF, // Deep Red (Magenta)
93 0x00, 0x00, 0x99, 0xFF, // Dark Blue
94 0xDD, 0x22, 0xDD, 0xFF, // Purple (Violet)
95 0x00, 0x77, 0x22, 0xFF, // Dark Green
96 0x55, 0x55, 0x55, 0xFF, // Dark Gray (Gray 1)
97 0x22, 0x22, 0xFF, 0xFF, // Medium Blue (Blue)
98 0x66, 0xAA, 0xFF, 0xFF, // Light Blue (Cyan)
99 0x88, 0x55, 0x00, 0xFF, // Brown
100 0xFF, 0x66, 0x00, 0xFF, // Orange
101 0xAA, 0xAA, 0xAA, 0xFF, // Light Gray (Gray 2)
102 0xFF, 0x99, 0x88, 0xFF, // Pink
103 0x11, 0xDD, 0x00, 0xFF, // Light Green (Green)
104 0xFF, 0xFF, 0x00, 0xFF, // Yellow
105 0x44, 0xFF, 0x99, 0xFF, // Aquamarine (Aqua)
106 0xFF, 0xFF, 0xFF, 0xFF // White
109 // This palette comes from ApplePC's colors (more realistic to my eye ;-)
111 static uint8_t altColors[16 * 4] = {
112 0x00, 0x00, 0x00, 0xFF,
113 0x7D, 0x20, 0x41, 0xFF,
114 0x41, 0x30, 0x7D, 0xFF,
115 0xBE, 0x51, 0xBE, 0xFF,
116 0x00, 0x5D, 0x3C, 0xFF,
117 0x7D, 0x7D, 0x7D, 0xFF,
118 0x41, 0x8E, 0xBA, 0xFF,
119 0xBE, 0xAE, 0xFB, 0xFF,
120 0x3C, 0x4D, 0x00, 0xFF,
121 0xBA, 0x6D, 0x41, 0xFF,
122 0x7D, 0x7D, 0x7D, 0xFF,
123 0xFB, 0x9E, 0xBE, 0xFF,
124 0x3C, 0xAA, 0x3C, 0xFF,
125 0xBA, 0xCB, 0x7D, 0xFF,
126 0x7D, 0xDB, 0xBA, 0xFF,
127 0xFB, 0xFB, 0xFB, 0xFF };
129 // Lo-res starting line addresses
131 static uint16_t lineAddrLoRes[24] = {
132 0x0400, 0x0480, 0x0500, 0x0580, 0x0600, 0x0680, 0x0700, 0x0780,
133 0x0428, 0x04A8, 0x0528, 0x05A8, 0x0628, 0x06A8, 0x0728, 0x07A8,
134 0x0450, 0x04D0, 0x0550, 0x05D0, 0x0650, 0x06D0, 0x0750, 0x07D0 };
136 // Hi-res starting line addresses
138 static uint16_t lineAddrHiRes[192] = {
139 0x2000, 0x2400, 0x2800, 0x2C00, 0x3000, 0x3400, 0x3800, 0x3C00,
140 0x2080, 0x2480, 0x2880, 0x2C80, 0x3080, 0x3480, 0x3880, 0x3C80,
141 0x2100, 0x2500, 0x2900, 0x2D00, 0x3100, 0x3500, 0x3900, 0x3D00,
142 0x2180, 0x2580, 0x2980, 0x2D80, 0x3180, 0x3580, 0x3980, 0x3D80,
144 0x2200, 0x2600, 0x2A00, 0x2E00, 0x3200, 0x3600, 0x3A00, 0x3E00,
145 0x2280, 0x2680, 0x2A80, 0x2E80, 0x3280, 0x3680, 0x3A80, 0x3E80,
146 0x2300, 0x2700, 0x2B00, 0x2F00, 0x3300, 0x3700, 0x3B00, 0x3F00,
147 0x2380, 0x2780, 0x2B80, 0x2F80, 0x3380, 0x3780, 0x3B80, 0x3F80,
149 0x2028, 0x2428, 0x2828, 0x2C28, 0x3028, 0x3428, 0x3828, 0x3C28,
150 0x20A8, 0x24A8, 0x28A8, 0x2CA8, 0x30A8, 0x34A8, 0x38A8, 0x3CA8,
151 0x2128, 0x2528, 0x2928, 0x2D28, 0x3128, 0x3528, 0x3928, 0x3D28,
152 0x21A8, 0x25A8, 0x29A8, 0x2DA8, 0x31A8, 0x35A8, 0x39A8, 0x3DA8,
154 0x2228, 0x2628, 0x2A28, 0x2E28, 0x3228, 0x3628, 0x3A28, 0x3E28,
155 0x22A8, 0x26A8, 0x2AA8, 0x2EA8, 0x32A8, 0x36A8, 0x3AA8, 0x3EA8,
156 0x2328, 0x2728, 0x2B28, 0x2F28, 0x3328, 0x3728, 0x3B28, 0x3F28,
157 0x23A8, 0x27A8, 0x2BA8, 0x2FA8, 0x33A8, 0x37A8, 0x3BA8, 0x3FA8,
159 0x2050, 0x2450, 0x2850, 0x2C50, 0x3050, 0x3450, 0x3850, 0x3C50,
160 0x20D0, 0x24D0, 0x28D0, 0x2CD0, 0x30D0, 0x34D0, 0x38D0, 0x3CD0,
161 0x2150, 0x2550, 0x2950, 0x2D50, 0x3150, 0x3550, 0x3950, 0x3D50,
162 0x21D0, 0x25D0, 0x29D0, 0x2DD0, 0x31D0, 0x35D0, 0x39D0, 0x3DD0,
164 0x2250, 0x2650, 0x2A50, 0x2E50, 0x3250, 0x3650, 0x3A50, 0x3E50,
165 0x22D0, 0x26D0, 0x2AD0, 0x2ED0, 0x32D0, 0x36D0, 0x3AD0, 0x3ED0,
166 0x2350, 0x2750, 0x2B50, 0x2F50, 0x3350, 0x3750, 0x3B50, 0x3F50,
167 0x23D0, 0x27D0, 0x2BD0, 0x2FD0, 0x33D0, 0x37D0, 0x3BD0, 0x3FD0 };
169 uint16_t appleHiresToMono[0x200] = {
170 0x0000, 0x3000, 0x0C00, 0x3C00, 0x0300, 0x3300, 0x0F00, 0x3F00,
171 0x00C0, 0x30C0, 0x0CC0, 0x3CC0, 0x03C0, 0x33C0, 0x0FC0, 0x3FC0, // $0x
172 0x0030, 0x3030, 0x0C30, 0x3C30, 0x0330, 0x3330, 0x0F30, 0x3F30,
173 0x00F0, 0x30F0, 0x0CF0, 0x3CF0, 0x03F0, 0x33F0, 0x0FF0, 0x3FF0, // $1x
174 0x000C, 0x300C, 0x0C0C, 0x3C0C, 0x030C, 0x330C, 0x0F0C, 0x3F0C,
175 0x00CC, 0x30CC, 0x0CCC, 0x3CCC, 0x03CC, 0x33CC, 0x0FCC, 0x3FCC, // $2x
176 0x003C, 0x303C, 0x0C3C, 0x3C3C, 0x033C, 0x333C, 0x0F3C, 0x3F3C,
177 0x00FC, 0x30FC, 0x0CFC, 0x3CFC, 0x03FC, 0x33FC, 0x0FFC, 0x3FFC, // $3x
178 0x0003, 0x3003, 0x0C03, 0x3C03, 0x0303, 0x3303, 0x0F03, 0x3F03,
179 0x00C3, 0x30C3, 0x0CC3, 0x3CC3, 0x03C3, 0x33C3, 0x0FC3, 0x3FC3, // $4x
180 0x0033, 0x3033, 0x0C33, 0x3C33, 0x0333, 0x3333, 0x0F33, 0x3F33,
181 0x00F3, 0x30F3, 0x0CF3, 0x3CF3, 0x03F3, 0x33F3, 0x0FF3, 0x3FF3, // $5x
182 0x000F, 0x300F, 0x0C0F, 0x3C0F, 0x030F, 0x330F, 0x0F0F, 0x3F0F,
183 0x00CF, 0x30CF, 0x0CCF, 0x3CCF, 0x03CF, 0x33CF, 0x0FCF, 0x3FCF, // $6x
184 0x003F, 0x303F, 0x0C3F, 0x3C3F, 0x033F, 0x333F, 0x0F3F, 0x3F3F,
185 0x00FF, 0x30FF, 0x0CFF, 0x3CFF, 0x03FF, 0x33FF, 0x0FFF, 0x3FFF, // $7x
186 0x0000, 0x1800, 0x0600, 0x1E00, 0x0180, 0x1980, 0x0780, 0x1F80,
187 0x0060, 0x1860, 0x0660, 0x1E60, 0x01E0, 0x19E0, 0x07E0, 0x1FE0, // $8x
188 0x0018, 0x1818, 0x0618, 0x1E18, 0x0198, 0x1998, 0x0798, 0x1F98,
189 0x0078, 0x1878, 0x0678, 0x1E78, 0x01F8, 0x19F8, 0x07F8, 0x1FF8, // $9x
190 0x0006, 0x1806, 0x0606, 0x1E06, 0x0186, 0x1986, 0x0786, 0x1F86,
191 0x0066, 0x1866, 0x0666, 0x1E66, 0x01E6, 0x19E6, 0x07E6, 0x1FE6, // $Ax
192 0x001E, 0x181E, 0x061E, 0x1E1E, 0x019E, 0x199E, 0x079E, 0x1F9E,
193 0x007E, 0x187E, 0x067E, 0x1E7E, 0x01FE, 0x19FE, 0x07FE, 0x1FFE, // $Bx
194 0x0001, 0x1801, 0x0601, 0x1E01, 0x0181, 0x1981, 0x0781, 0x1F81,
195 0x0061, 0x1861, 0x0661, 0x1E61, 0x01E1, 0x19E1, 0x07E1, 0x1FE1, // $Cx
196 0x0019, 0x1819, 0x0619, 0x1E19, 0x0199, 0x1999, 0x0799, 0x1F99,
197 0x0079, 0x1879, 0x0679, 0x1E79, 0x01F9, 0x19F9, 0x07F9, 0x1FF9, // $Dx
198 0x0007, 0x1807, 0x0607, 0x1E07, 0x0187, 0x1987, 0x0787, 0x1F87,
199 0x0067, 0x1867, 0x0667, 0x1E67, 0x01E7, 0x19E7, 0x07E7, 0x1FE7, // $Ex
200 0x001F, 0x181F, 0x061F, 0x1E1F, 0x019F, 0x199F, 0x079F, 0x1F9F,
201 0x007F, 0x187F, 0x067F, 0x1E7F, 0x01FF, 0x19FF, 0x07FF, 0x1FFF, // $Fx
203 // Second half adds in the previous byte's lo pixel
205 0x0000, 0x3000, 0x0C00, 0x3C00, 0x0300, 0x3300, 0x0F00, 0x3F00,
206 0x00C0, 0x30C0, 0x0CC0, 0x3CC0, 0x03C0, 0x33C0, 0x0FC0, 0x3FC0, // $0x
207 0x0030, 0x3030, 0x0C30, 0x3C30, 0x0330, 0x3330, 0x0F30, 0x3F30,
208 0x00F0, 0x30F0, 0x0CF0, 0x3CF0, 0x03F0, 0x33F0, 0x0FF0, 0x3FF0, // $1x
209 0x000C, 0x300C, 0x0C0C, 0x3C0C, 0x030C, 0x330C, 0x0F0C, 0x3F0C,
210 0x00CC, 0x30CC, 0x0CCC, 0x3CCC, 0x03CC, 0x33CC, 0x0FCC, 0x3FCC, // $2x
211 0x003C, 0x303C, 0x0C3C, 0x3C3C, 0x033C, 0x333C, 0x0F3C, 0x3F3C,
212 0x00FC, 0x30FC, 0x0CFC, 0x3CFC, 0x03FC, 0x33FC, 0x0FFC, 0x3FFC, // $3x
213 0x0003, 0x3003, 0x0C03, 0x3C03, 0x0303, 0x3303, 0x0F03, 0x3F03,
214 0x00C3, 0x30C3, 0x0CC3, 0x3CC3, 0x03C3, 0x33C3, 0x0FC3, 0x3FC3, // $4x
215 0x0033, 0x3033, 0x0C33, 0x3C33, 0x0333, 0x3333, 0x0F33, 0x3F33,
216 0x00F3, 0x30F3, 0x0CF3, 0x3CF3, 0x03F3, 0x33F3, 0x0FF3, 0x3FF3, // $5x
217 0x000F, 0x300F, 0x0C0F, 0x3C0F, 0x030F, 0x330F, 0x0F0F, 0x3F0F,
218 0x00CF, 0x30CF, 0x0CCF, 0x3CCF, 0x03CF, 0x33CF, 0x0FCF, 0x3FCF, // $6x
219 0x003F, 0x303F, 0x0C3F, 0x3C3F, 0x033F, 0x333F, 0x0F3F, 0x3F3F,
220 0x00FF, 0x30FF, 0x0CFF, 0x3CFF, 0x03FF, 0x33FF, 0x0FFF, 0x3FFF, // $7x
221 0x2000, 0x3800, 0x2600, 0x3E00, 0x2180, 0x3980, 0x2780, 0x3F80,
222 0x2060, 0x3860, 0x2660, 0x3E60, 0x21E0, 0x39E0, 0x27E0, 0x3FE0, // $8x
223 0x2018, 0x3818, 0x2618, 0x3E18, 0x2198, 0x3998, 0x2798, 0x3F98,
224 0x2078, 0x3878, 0x2678, 0x3E78, 0x21F8, 0x39F8, 0x27F8, 0x3FF8, // $9x
225 0x2006, 0x3806, 0x2606, 0x3E06, 0x2186, 0x3986, 0x2786, 0x3F86,
226 0x2066, 0x3866, 0x2666, 0x3E66, 0x21E6, 0x39E6, 0x27E6, 0x3FE6, // $Ax
227 0x201E, 0x381E, 0x261E, 0x3E1E, 0x219E, 0x399E, 0x279E, 0x3F9E,
228 0x207E, 0x387E, 0x267E, 0x3E7E, 0x21FE, 0x39FE, 0x27FE, 0x3FFE, // $Bx
229 0x2001, 0x3801, 0x2601, 0x3E01, 0x2181, 0x3981, 0x2781, 0x3F81,
230 0x2061, 0x3861, 0x2661, 0x3E61, 0x21E1, 0x39E1, 0x27E1, 0x3FE1, // $Cx
231 0x2019, 0x3819, 0x2619, 0x3E19, 0x2199, 0x3999, 0x2799, 0x3F99,
232 0x2079, 0x3879, 0x2679, 0x3E79, 0x21F9, 0x39F9, 0x27F9, 0x3FF9, // $Dx
233 0x2007, 0x3807, 0x2607, 0x3E07, 0x2187, 0x3987, 0x2787, 0x3F87,
234 0x2067, 0x3867, 0x2667, 0x3E67, 0x21E7, 0x39E7, 0x27E7, 0x3FE7, // $Ex
235 0x201F, 0x381F, 0x261F, 0x3E1F, 0x219F, 0x399F, 0x279F, 0x3F9F,
236 0x207F, 0x387F, 0x267F, 0x3E7F, 0x21FF, 0x39FF, 0x27FF, 0x3FFF // $Fx
239 //static uint8_t blurTable[0x800][8]; // Color TV blur table
240 static uint8_t blurTable[0x80][8]; // Color TV blur table
241 static uint8_t mirrorTable[0x100];
242 static uint32_t * palette = (uint32_t *)altColors;
243 enum { ST_FIRST_ENTRY = 0, ST_COLOR_TV = 0, ST_WHITE_MONO, ST_GREEN_MONO, ST_LAST_ENTRY };
244 static uint8_t screenType = ST_COLOR_TV;
248 static void Render40ColumnTextLine(uint8_t line);
249 static void Render80ColumnTextLine(uint8_t line);
250 static void Render40ColumnText(void);
251 static void Render80ColumnText(void);
252 static void RenderLoRes(uint16_t toLine = 24);
253 static void RenderHiRes(uint16_t toLine = 192);
254 static void RenderDHiRes(uint16_t toLine = 192);
255 static void RenderVideoFrame(/*uint32_t *, int*/);
258 void SetupBlurTable(void)
260 // NOTE: This table only needs to be 7 bits wide instead of 11, since the
261 // last four bits are copies of the previous four...
262 // Odd. Doing the bit patterns from 0-$7F doesn't work, but going
263 // from 0-$7FF stepping by 16 does. Hm.
264 // Well, it seems that going from 0-$7F doesn't have enough precision to do the job.
266 // for(uint16_t bitPat=0; bitPat<0x800; bitPat++)
267 for(uint16_t bitPat=0; bitPat<0x80; bitPat++)
269 /* uint16_t w3 = bitPat & 0x888;
270 uint16_t w2 = bitPat & 0x444;
271 uint16_t w1 = bitPat & 0x222;
272 uint16_t w0 = bitPat & 0x111;*/
273 uint16_t w3 = bitPat & 0x88;
274 uint16_t w2 = bitPat & 0x44;
275 uint16_t w1 = bitPat & 0x22;
276 uint16_t w0 = bitPat & 0x11;
278 uint16_t blurred3 = (w3 | (w3 >> 1) | (w3 >> 2) | (w3 >> 3)) & 0x00FF;
279 uint16_t blurred2 = (w2 | (w2 >> 1) | (w2 >> 2) | (w2 >> 3)) & 0x00FF;
280 uint16_t blurred1 = (w1 | (w1 >> 1) | (w1 >> 2) | (w1 >> 3)) & 0x00FF;
281 uint16_t blurred0 = (w0 | (w0 >> 1) | (w0 >> 2) | (w0 >> 3)) & 0x00FF;
283 for(int8_t i=7; i>=0; i--)
285 uint8_t color = (((blurred0 >> i) & 0x01) << 3)
286 | (((blurred1 >> i) & 0x01) << 2)
287 | (((blurred2 >> i) & 0x01) << 1)
288 | ((blurred3 >> i) & 0x01);
289 blurTable[bitPat][7 - i] = color;
293 for(uint16_t bitPat=0; bitPat<0x800; bitPat+=0x10)
295 uint16_t w0 = bitPat & 0x111, w1 = bitPat & 0x222, w2 = bitPat & 0x444, w3 = bitPat & 0x888;
297 uint16_t blurred0 = (w0 | (w0 >> 1) | (w0 >> 2) | (w0 >> 3)) & 0x00FF;
298 uint16_t blurred1 = (w1 | (w1 >> 1) | (w1 >> 2) | (w1 >> 3)) & 0x00FF;
299 uint16_t blurred2 = (w2 | (w2 >> 1) | (w2 >> 2) | (w2 >> 3)) & 0x00FF;
300 uint16_t blurred3 = (w3 | (w3 >> 1) | (w3 >> 2) | (w3 >> 3)) & 0x00FF;
302 for(int8_t i=7; i>=0; i--)
304 uint8_t color = (((blurred0 >> i) & 0x01) << 3)
305 | (((blurred1 >> i) & 0x01) << 2)
306 | (((blurred2 >> i) & 0x01) << 1)
307 | ((blurred3 >> i) & 0x01);
308 blurTable[bitPat >> 4][7 - i] = color;
313 for(int i=0; i<256; i++)
315 mirrorTable[i] = ((i & 0x01) << 7)
327 void TogglePalette(void)
329 if (palette == (uint32_t *)colors)
331 palette = (uint32_t *)altColors;
332 SpawnMessage("Color TV palette");
336 palette = (uint32_t *)colors;
337 SpawnMessage("\"Master Color Values\" palette");
342 void CycleScreenTypes(void)
344 char scrTypeStr[3][40] = { "Color TV", "White monochrome", "Green monochrome" };
348 if (screenType == ST_LAST_ENTRY)
349 screenType = ST_FIRST_ENTRY;
351 SpawnMessage("%s", scrTypeStr[screenType]);
355 static uint32_t msgTicks = 0;
356 static char message[4096];
358 void SpawnMessage(const char * text, ...)
363 vsprintf(message, text, arg);
370 static void DrawString2(uint32_t x, uint32_t y, uint32_t color);
371 static void DrawString(void)
373 //This approach works, and seems to be fast enough... Though it probably would
374 //be better to make the oversized font to match this one...
375 for(uint32_t x=7; x<=9; x++)
376 for(uint32_t y=7; y<=9; y++)
377 DrawString2(x, y, 0x00000000);
379 DrawString2(8, 8, 0x0020FF20);
383 static void DrawString2(uint32_t x, uint32_t y, uint32_t color)
385 //uint32_t x = 8, y = 8;
386 uint32_t length = strlen(message), address = x + (y * VIRTUAL_SCREEN_WIDTH);
387 // uint32_t color = 0x0020FF20;
388 //This could be done ahead of time, instead of on each pixel...
390 uint8_t nBlue = (color >> 16) & 0xFF, nGreen = (color >> 8) & 0xFF, nRed = color & 0xFF;
392 for(uint32_t i=0; i<length; i++)
394 uint8_t c = message[i];
395 c = (c < 32 ? 0 : c - 32);
396 uint32_t fontAddr = (uint32_t)c * FONT_WIDTH * FONT_HEIGHT;
398 for(uint32_t yy=0; yy<FONT_HEIGHT; yy++)
400 for(uint32_t xx=0; xx<FONT_WIDTH; xx++)
402 /* uint8_t fontTrans = font1[fontAddr++];
403 // uint32_t newTrans = (fontTrans * transparency / 255) << 24;
404 uint32_t newTrans = fontTrans << 24;
405 uint32_t pixel = newTrans | color;
407 *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = pixel;//*/
409 // uint8_t trans = font1[fontAddr++];
410 uint8_t trans = font2[fontAddr++];
414 uint32_t existingColor = *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH));
416 uint8_t eBlue = (existingColor >> 16) & 0xFF,
417 eGreen = (existingColor >> 8) & 0xFF,
418 eRed = existingColor & 0xFF;
420 //This could be sped up by using a table of 5 + 5 + 5 bits (32 levels transparency -> 32768 entries)
421 //Here we've modified it to have 33 levels of transparency (could have any # we want!)
422 //because dividing by 32 is faster than dividing by 31...!
423 uint8_t invTrans = 255 - trans;
425 uint32_t bRed = (eRed * invTrans + nRed * trans) / 255;
426 uint32_t bGreen = (eGreen * invTrans + nGreen * trans) / 255;
427 uint32_t bBlue = (eBlue * invTrans + nBlue * trans) / 255;
429 //THIS IS NOT ENDIAN SAFE
430 //NB: Setting the alpha channel here does nothing.
431 *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = 0x7F000000 | (bBlue << 16) | (bGreen << 8) | bRed;
436 address += FONT_WIDTH;
441 static void Render40ColumnTextLine(uint8_t line)
443 uint32_t pixelOn = (screenType == ST_GREEN_MONO ? 0xFF61FF61 : 0xFFFFFFFF);
445 for(int x=0; x<40; x++)
447 uint8_t chr = ram[lineAddrLoRes[line] + (displayPage2 ? 0x0400 : 0x0000) + x];
449 // Render character at (x, y)
451 for(int cy=0; cy<8; cy++)
453 for(int cx=0; cx<7; cx++)
455 uint32_t pixel = 0xFF000000;
457 if (alternateCharset)
459 if (textChar[((chr & 0x3F) * 56) + cx + (cy * 7)])
463 pixel = pixel ^ (screenType == ST_GREEN_MONO ? 0x0061FF61 : 0x00FFFFFF);
465 if ((chr & 0xC0) == 0x40 && flash)
470 if (textChar2e[(chr * 56) + cx + (cy * 7)])
474 scrBuffer[(x * 7 * 2) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + (cx * 2) + 0 + (cy * VIRTUAL_SCREEN_WIDTH * 2)] = pixel;
475 scrBuffer[(x * 7 * 2) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + (cx * 2) + 1 + (cy * VIRTUAL_SCREEN_WIDTH * 2)] = pixel;
477 // QnD method to get blank alternate lines in text mode
478 if (screenType == ST_GREEN_MONO)
482 scrBuffer[(x * 7 * 2) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + (cx * 2) + 0 + (((cy * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = pixel;
483 scrBuffer[(x * 7 * 2) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + (cx * 2) + 1 + (((cy * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = pixel;
491 static void Render80ColumnTextLine(uint8_t line)
493 uint32_t pixelOn = (screenType == ST_GREEN_MONO ? 0xFF61FF61 : 0xFFFFFFFF);
495 for(int x=0; x<80; x++)
498 // This is wrong; it should grab from the alt bank if Page2 is set, not main RAM @ $0
499 uint8_t chr = ram[lineAddrLoRes[line] + (displayPage2 ? 0x0400 : 0x0000) + x];
502 chr = ram2[lineAddrLoRes[line] + (displayPage2 ? 0x0400 : 0x0000) + x - 40];
507 chr = ram[lineAddrLoRes[line] + (x >> 1)];
509 chr = ram2[lineAddrLoRes[line] + (x >> 1)];
512 // Render character at (x, y)
514 for(int cy=0; cy<8; cy++)
516 for(int cx=0; cx<7; cx++)
518 uint32_t pixel = 0xFF000000;
520 if (alternateCharset)
522 if (textChar[((chr & 0x3F) * 56) + cx + (cy * 7)])
526 pixel = pixel ^ (screenType == ST_GREEN_MONO ? 0x0061FF61 : 0x00FFFFFF);
528 if ((chr & 0xC0) == 0x40 && flash)
533 if (textChar2e[(chr * 56) + cx + (cy * 7)])
537 scrBuffer[(x * 7) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + cx + (cy * 2 * VIRTUAL_SCREEN_WIDTH)] = pixel;
539 // QnD method to get blank alternate lines in text mode
540 if (screenType == ST_GREEN_MONO)
543 scrBuffer[(x * 7) + (line * VIRTUAL_SCREEN_WIDTH * 8 * 2) + cx + (((cy * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = pixel;
550 static void Render40ColumnText(void)
552 for(uint8_t line=0; line<24; line++)
553 Render40ColumnTextLine(line);
557 static void Render80ColumnText(void)
559 for(uint8_t line=0; line<24; line++)
560 Render80ColumnTextLine(line);
564 static void RenderLoRes(uint16_t toLine/*= 24*/)
566 // NOTE: The green mono rendering doesn't skip every other line... !!! FIX !!!
567 // Also, we could set up three different Render functions depending on which
568 // render type was set and call it with a function pointer. Would be faster
569 // then the nested ifs we have now.
571 Note that these colors correspond to the bit patterns generated by the numbers 0-F in order:
572 Color #s correspond to the bit patterns in reverse... Interesting!
574 00 00 00 -> 0 [0000] -> 0 (lores color #)
575 3c 4d 00 -> 8 [0001] -> 8? BROWN
576 00 5d 3c -> 4 [0010] -> 4? DARK GREEN
577 3c aa 3c -> 12 [0011] -> 12? LIGHT GREEN (GREEN)
578 41 30 7d -> 2 [0100] -> 2? DARK BLUE
579 7d 7d 7d -> 10 [0101] -> 10? LIGHT GRAY
580 41 8e ba -> 6 [0110] -> 6? MEDIUM BLUE (BLUE)
581 7d db ba -> 14 [0111] -> 14? AQUAMARINE (AQUA)
582 7d 20 41 -> 1 [1000] -> 1? DEEP RED (MAGENTA)
583 ba 6d 41 -> 9 [1001] -> 9? ORANGE
584 7d 7d 7d -> 5 [1010] -> 5? DARK GRAY
585 ba cb 7d -> 13 [1011] -> 13? YELLOW
586 be 51 be -> 3 [1100] -> 3 PURPLE (VIOLET)
587 fb 9e be -> 11 [1101] -> 11? PINK
588 be ae fb -> 7 [1110] -> 7? LIGHT BLUE (CYAN)
589 fb fb fb -> 15 [1111] -> 15 WHITE
591 uint8_t mirrorNybble[16] = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
593 //This is the old "perfect monitor" rendering code...
594 /* if (screenType != ST_COLOR_TV) // Not correct, but for now...
597 for(uint16_t y=0; y<toLine; y++)
599 for(uint16_t x=0; x<40; x++)
601 uint8_t scrByte = ram[lineAddrLoRes[y] + (displayPage2 ? 0x0400 : 0x0000) + x];
602 uint32_t pixel = palette[scrByte & 0x0F];
604 for(int cy=0; cy<4; cy++)
605 for(int cx=0; cx<14; cx++)
606 scrBuffer[((x * 14) + cx) + (((y * 8) + cy) * VIRTUAL_SCREEN_WIDTH)] = pixel;
608 pixel = palette[scrByte >> 4];
610 for(int cy=4; cy<8; cy++)
611 for(int cx=0; cx<14; cx++)
612 scrBuffer[(x * 14) + (y * VIRTUAL_SCREEN_WIDTH * 8) + cx + (cy * VIRTUAL_SCREEN_WIDTH)] = pixel;
618 uint32_t pixelOn = (screenType == ST_WHITE_MONO ? 0xFFFFFFFF : 0xFF61FF61);
620 for(uint16_t y=0; y<toLine; y++)
622 // Do top half of lores screen bytes...
624 uint32_t previous3Bits = 0;
626 for(uint16_t x=0; x<40; x+=2)
628 uint8_t scrByte1 = ram[lineAddrLoRes[y] + (displayPage2 ? 0x0400 : 0x0000) + x + 0] & 0x0F;
629 uint8_t scrByte2 = ram[lineAddrLoRes[y] + (displayPage2 ? 0x0400 : 0x0000) + x + 1] & 0x0F;
630 scrByte1 = mirrorNybble[scrByte1];
631 scrByte2 = mirrorNybble[scrByte2];
632 // This is just a guess, but it'll have to do for now...
633 uint32_t pixels = previous3Bits | (scrByte1 << 24) | (scrByte1 << 20) | (scrByte1 << 16)
634 | ((scrByte1 & 0x0C) << 12) | ((scrByte2 & 0x03) << 12)
635 | (scrByte2 << 8) | (scrByte2 << 4) | scrByte2;
637 // We now have 28 pixels (expanded from 14) in word: mask is $0F FF FF FF
638 // 0ppp 1111 1111 1111 11|11 1111 1111 1111
639 // 31 27 23 19 15 11 7 3 0
641 if (screenType == ST_COLOR_TV)
643 for(uint8_t i=0; i<7; i++)
645 uint8_t bitPat = (pixels & 0x7F000000) >> 24;
648 for(uint8_t j=0; j<4; j++)
650 uint8_t color = blurTable[bitPat][j];
652 for(uint32_t cy=0; cy<8; cy++)
654 scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color];
655 // scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color];
660 previous3Bits = pixels & 0x70000000;
664 for(int j=0; j<28; j++)
666 for(uint32_t cy=0; cy<8; cy++)
668 scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000);
669 // scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000);
677 // Now do bottom half...
681 for(uint16_t x=0; x<40; x+=2)
683 uint8_t scrByte1 = ram[lineAddrLoRes[y] + (displayPage2 ? 0x0400 : 0x0000) + x + 0] >> 4;
684 uint8_t scrByte2 = ram[lineAddrLoRes[y] + (displayPage2 ? 0x0400 : 0x0000) + x + 1] >> 4;
685 scrByte1 = mirrorNybble[scrByte1];
686 scrByte2 = mirrorNybble[scrByte2];
687 // This is just a guess, but it'll have to do for now...
688 uint32_t pixels = previous3Bits | (scrByte1 << 24) | (scrByte1 << 20) | (scrByte1 << 16)
689 | ((scrByte1 & 0x0C) << 12) | ((scrByte2 & 0x03) << 12)
690 | (scrByte2 << 8) | (scrByte2 << 4) | scrByte2;
692 // We now have 28 pixels (expanded from 14) in word: mask is $0F FF FF FF
693 // 0ppp 1111 1111 1111 11|11 1111 1111 1111
694 // 31 27 23 19 15 11 7 3 0
696 if (screenType == ST_COLOR_TV)
698 for(uint8_t i=0; i<7; i++)
700 uint8_t bitPat = (pixels & 0x7F000000) >> 24;
703 for(uint8_t j=0; j<4; j++)
705 uint8_t color = blurTable[bitPat][j];
707 for(uint32_t cy=8; cy<16; cy++)
709 scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color];
710 // scrBuffer[((x * 14) + (i * 4) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = palette[color];
715 previous3Bits = pixels & 0x70000000;
719 for(int j=0; j<28; j++)
721 for(uint32_t cy=8; cy<16; cy++)
723 scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000);
724 // scrBuffer[((x * 14) + j) + (((y * 16) + cy) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000);
735 static void RenderHiRes(uint16_t toLine/*= 192*/)
737 //printf("RenderHiRes to line %u\n", toLine);
738 // NOTE: Not endian safe. !!! FIX !!! [DONE]
740 uint32_t pixelOn = (screenType == ST_WHITE_MONO ? 0xFFFFFFFF : 0xFF61FF61);
742 // Now it is. Now roll this fix into all the other places... !!! FIX !!!
743 // The colors are set in the 8-bit array as R G B A
744 uint8_t monoColors[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0xFF, 0x61, 0xFF };
745 uint32_t * colorPtr = (uint32_t *)monoColors;
746 uint32_t pixelOn = (screenType == ST_WHITE_MONO ? colorPtr[0] : colorPtr[1]);
749 for(uint16_t y=0; y<toLine; y++)
751 uint16_t previousLoPixel = 0;
752 uint32_t previous3bits = 0;
754 for(uint16_t x=0; x<40; x+=2)
756 uint8_t screenByte = ram[lineAddrHiRes[y] + (displayPage2 ? 0x2000 : 0x0000) + x];
757 uint32_t pixels = appleHiresToMono[previousLoPixel | screenByte];
758 previousLoPixel = (screenByte << 2) & 0x0100;
760 screenByte = ram[lineAddrHiRes[y] + (displayPage2 ? 0x2000 : 0x0000) + x + 1];
761 uint32_t pixels2 = appleHiresToMono[previousLoPixel | screenByte];
762 previousLoPixel = (screenByte << 2) & 0x0100;
764 pixels = previous3bits | (pixels << 14) | pixels2;
766 //testing (this shows on the screen, so it's OK)
769 // pixels = 0x7FFFFFFF;
772 // We now have 28 pixels (expanded from 14) in word: mask is $0F FF FF FF
773 // 0ppp 1111 1111 1111 1111 1111 1111 1111
774 // 31 27 23 19 15 11 7 3 0
776 if (screenType == ST_COLOR_TV)
778 for(uint8_t i=0; i<7; i++)
780 uint8_t bitPat = (pixels & 0x7F000000) >> 24;
783 for(uint8_t j=0; j<4; j++)
785 uint8_t color = blurTable[bitPat][j];
787 //This doesn't seem to make things go any faster...
788 //It's the OpenGL render that's faster... Hmm...
789 scrBuffer[(x * 14) + (i * 4) + j + (y * VIRTUAL_SCREEN_WIDTH)] = palette[color];
791 scrBuffer[(x * 14) + (i * 4) + j + (((y * 2) + 0) * VIRTUAL_SCREEN_WIDTH)] = palette[color];
792 scrBuffer[(x * 14) + (i * 4) + j + (((y * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = palette[color];
797 previous3bits = pixels & 0x70000000;
801 for(int j=0; j<28; j++)
803 scrBuffer[(x * 14) + j + (((y * 2) + 0) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000);
805 if (screenType == ST_GREEN_MONO)
806 pixels &= 0x07FFFFFF;
808 scrBuffer[(x * 14) + j + (((y * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000);
817 static void RenderDHiRes(uint16_t toLine/*= 192*/)
819 // Now it is. Now roll this fix into all the other places... !!! FIX !!!
820 // The colors are set in the 8-bit array as R G B A
821 uint8_t monoColors[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0xFF, 0x61, 0xFF };
822 uint32_t * colorPtr = (uint32_t *)monoColors;
823 uint32_t pixelOn = (screenType == ST_WHITE_MONO ? colorPtr[0] : colorPtr[1]);
825 for(uint16_t y=0; y<toLine; y++)
827 uint32_t previous4bits = 0;
829 for(uint16_t x=0; x<40; x+=2)
831 uint8_t screenByte = ram[lineAddrHiRes[y] + (displayPage2 ? 0x2000 : 0x0000) + x];
832 uint32_t pixels = (mirrorTable[screenByte & 0x7F]) << 14;
833 screenByte = ram[lineAddrHiRes[y] + (displayPage2 ? 0x2000 : 0x0000) + x + 1];
834 pixels = pixels | (mirrorTable[screenByte & 0x7F]);
835 screenByte = ram2[lineAddrHiRes[y] + (displayPage2 ? 0x2000 : 0x0000) + x];
836 pixels = pixels | ((mirrorTable[screenByte & 0x7F]) << 21);
837 screenByte = ram2[lineAddrHiRes[y] + (displayPage2 ? 0x2000 : 0x0000) + x + 1];
838 pixels = pixels | ((mirrorTable[screenByte & 0x7F]) << 7);
839 pixels = previous4bits | (pixels >> 1);
841 // We now have 28 pixels (expanded from 14) in word: mask is $0F FF FF FF
842 // 0ppp 1111 1111 1111 1111 1111 1111 1111
843 // 31 27 23 19 15 11 7 3 0
845 if (screenType == ST_COLOR_TV)
847 for(uint8_t i=0; i<7; i++)
849 uint8_t bitPat = (pixels & 0xFE000000) >> 25;
852 for(uint8_t j=0; j<4; j++)
854 uint32_t color = palette[blurTable[bitPat][j]];
855 scrBuffer[(x * 14) + (i * 4) + j + (((y * 2) + 0) * VIRTUAL_SCREEN_WIDTH)] = color;
856 scrBuffer[(x * 14) + (i * 4) + j + (((y * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = color;
860 previous4bits = pixels & 0xF0000000;
864 for(int j=0; j<28; j++)
866 scrBuffer[(x * 14) + j + (((y * 2) + 0) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000);
868 if (screenType == ST_GREEN_MONO)
869 pixels &= 0x07FFFFFF;
871 scrBuffer[(x * 14) + j + (((y * 2) + 1) * VIRTUAL_SCREEN_WIDTH)] = (pixels & 0x08000000 ? pixelOn : 0xFF000000);
880 void RenderVideoFrame(void)
882 if (GUI::powerOnState == true)
887 Render40ColumnText();
889 Render80ColumnText();
898 Render40ColumnTextLine(20);
899 Render40ColumnTextLine(21);
900 Render40ColumnTextLine(22);
901 Render40ColumnTextLine(23);
906 Render40ColumnTextLine(20);
907 Render40ColumnTextLine(21);
908 Render40ColumnTextLine(22);
909 Render40ColumnTextLine(23);
925 memset(scrBuffer, 0, VIRTUAL_SCREEN_WIDTH * VIRTUAL_SCREEN_HEIGHT * sizeof(uint32_t));
937 // Prime SDL and create surfaces
941 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE) != 0)
943 WriteLog("Video: Could not initialize the SDL library: %s\n", SDL_GetError());
947 // int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, SDL_WINDOW_OPENGL, &sdlWindow, &sdlRenderer);
948 int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, 0, &sdlWindow, &sdlRenderer);
952 WriteLog("Video: Could not window and/or renderer: %s\n", SDL_GetError());
956 // Make the scaled rendering look smoother.
957 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
958 // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
959 SDL_RenderSetLogicalSize(sdlRenderer, VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT);
961 // Set the application's icon & title...
962 SDL_Surface * iconSurface = SDL_CreateRGBSurfaceFrom(icon, 64, 64, 32, 64*4, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
963 SDL_SetWindowIcon(sdlWindow, iconSurface);
964 SDL_FreeSurface(iconSurface);
965 SDL_SetWindowTitle(sdlWindow, "Apple2 Emulator");
967 sdlTexture = SDL_CreateTexture(sdlRenderer,
968 SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING,
969 VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT);
973 WriteLog("Video: Successfully initialized.\n");
979 // Free various SDL components
983 WriteLog("Video: Shutting down SDL...\n");
984 SDL_DestroyTexture(sdlTexture);
985 SDL_DestroyRenderer(sdlRenderer);
986 SDL_DestroyWindow(sdlWindow);
988 WriteLog("Video: Done.\n");
993 // Render the Apple video screen to the primary texture
995 void RenderAppleScreen(SDL_Renderer * renderer)
997 SDL_LockTexture(sdlTexture, NULL, (void **)&scrBuffer, &scrPitch);
999 SDL_UnlockTexture(sdlTexture);
1000 SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);
1005 // Fullscreen <-> window switching
1007 void ToggleFullScreen(void)
1009 settings.fullscreen = !settings.fullscreen;
1011 int retVal = SDL_SetWindowFullscreen(sdlWindow, (settings.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0));
1012 SDL_ShowCursor(settings.fullscreen ? 0 : 1);
1015 WriteLog("Video::ToggleFullScreen: SDL error = %s\n", SDL_GetError());