]> Shamusworld >> Repos - apple2/blobdiff - src/video.cpp
Add support for .hdv hard drive images, new "Rob Color TV" palette.
[apple2] / src / video.cpp
index 11e3736885fa506e9b5c6be6cc9decdc0a732232..cc5805cc4ad0579850c50fe567f5701b79ef3536 100644 (file)
@@ -4,7 +4,7 @@
 // All the video modes that a real Apple 2 supports are handled here
 //
 // by James Hammons
-// (c) 2005-2017 Underground Software
+// (c) 2005-2018 Underground Software
 //
 // JLH = James Hammons <jlhamm@acm.org>
 //
 //   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]
+// - 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 <string.h>                                    // for memset()
@@ -74,18 +73,19 @@ bool hiRes = false;
 bool alternateCharset = false;
 bool col80Mode = false;
 SDL_Renderer * sdlRenderer = NULL;
+SDL_Window * sdlWindow = NULL;
 
 // Local variables
 
-static SDL_Window * sdlWindow = NULL;
 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
+// "Master Color Values" palette (ugly, Apple engineer hand-picked colors)
 
 static uint8_t colors[16 * 4] = {
        0x00, 0x00, 0x00, 0xFF,                         // Black
@@ -126,6 +126,69 @@ static uint8_t altColors[16 * 4] = {
        0x7D, 0xDB, 0xBA, 0xFF,
        0xFB, 0xFB, 0xFB, 0xFF };
 
+// This color palette comes from xapple2 (looks a bit shit to me  :-)
+// I've included it to have yet another point of comparison to the execrable
+// "Master Color Values" palette.
+/*
+ * https://mrob.com/pub/xapple2/colors.html
+ * detailed research into accurate colors
+                 --chroma--
+ Color name      phase ampl luma   -R- -G- -B-
+ black    COLOR=0    0   0    0      0   0   0
+ gray     COLOR=5    0   0   50    156 156 156
+ grey     COLOR=10   0   0   50    156 156 156
+ white    COLOR=15   0   0  100    255 255 255
+ dk blue  COLOR=2    0  60   25     96  78 189
+ lt blue  COLOR=7    0  60   75    208 195 255
+ purple   COLOR=3   45 100   50    255  68 253
+ purple   HCOLOR=2  45 100   50    255  68 253
+ red      COLOR=1   90  60   25    227  30  96
+ pink     COLOR=11  90  60   75    255 160 208
+ orange   COLOR=9  135 100   50    255 106  60
+ orange   HCOLOR=5 135 100   50    255 106  60
+ brown    COLOR=8  180  60   25     96 114   3
+ yellow   COLOR=13 180  60   75    208 221 141
+ lt green COLOR=12 225 100   50     20 245  60
+ green    HCOLOR=1 225 100   50     20 245  60
+ dk green COLOR=4  270  60   25      0 163  96
+ aqua     COLOR=14 270  60   75    114 255 208
+ med blue COLOR=6  315 100   50     20 207 253
+ blue     HCOLOR=6 315 100   50     20 207 253
+ NTSC Hsync          0   0  -40      0   0   0
+ NTSC black          0   0    7.5   41  41  41
+ NTSC Gray75         0   0   77    212 212 212
+ YIQ +Q             33 100   50    255  81 255
+ NTSC magenta       61  82   36    255  40 181
+ NTSC red          104  88   28    255  28  76
+ YIQ +I            123 100   50    255  89  82
+ NTSC yellow       167  62   69    221 198 121
+ Color burst       180  40   0       0   4   0
+ YIQ -Q            213 100   50     51 232  41
+ NTSC green        241  82   48     12 234  97
+ NTSC cyan         284  88   56     10 245 198
+ YIQ -I            303 100   50      0 224 231
+ NTSC blue         347  62   15     38  65 155
+*/
+
+static uint8_t robColors[16 * 4] = {
+       0x00, 0x00, 0x00, 0xFF, // Black
+       0xE3, 0x1E, 0x60, 0xFF, // Deep Red (Magenta) 227  30  96
+       0x60, 0x4E, 0xBD, 0xFF, // Dark Blue 96  78 189
+       0xFF, 0x44, 0xFD, 0xFF, // Purple (Violet) 255  68 253
+       0x00, 0xA3, 0x60, 0xFF, // Dark Green 0 163  96
+       0x9C, 0x9C, 0x9C, 0xFF, // Dark Gray (Gray 1) 156 156 156
+       0x14, 0xCF, 0xFD, 0xFF, // Medium Blue (Blue)  20 207 253
+       0xD0, 0xC3, 0xFF, 0xFF, // Light Blue (Cyan) 208 195 255
+       0x60, 0x72, 0x03, 0xFF, // Brown 96 114   3
+       0xFF, 0x6A, 0x3C, 0xFF, // Orange 255 106  60
+       0xD4, 0xD4, 0xD4, 0xFF, // Light Gray (Gray 2) 212 212 212
+       0xFF, 0xA0, 0xD0, 0xFF, // Pink 255 160 208
+       0x14, 0xF5, 0x3C, 0xFF, // Light Green (Green) 20 245  60
+       0xD0, 0xDD, 0x8D, 0xFF, // Yellow 208 221 141
+       0x72, 0xFF, 0xD0, 0xFF, // Aquamarine (Aqua) 114 255 208
+       0xFF, 0xFF, 0xFF, 0xFF  // White
+};
+
 // Lo-res starting line addresses
 
 static uint16_t lineAddrLoRes[24] = {
@@ -236,7 +299,6 @@ uint16_t appleHiresToMono[0x200] = {
        0x207F, 0x387F, 0x267F, 0x3E7F, 0x21FF, 0x39FF, 0x27FF, 0x3FFF  // $Fx
 };
 
-//static uint8_t blurTable[0x800][8];                          // Color TV blur table
 static uint8_t blurTable[0x80][8];                             // Color TV blur table
 static uint8_t mirrorTable[0x100];
 static uint32_t * palette = (uint32_t *)altColors;
@@ -250,6 +312,7 @@ 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*/);
@@ -262,34 +325,6 @@ void SetupBlurTable(void)
        //       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.
-#if 0
-//     for(uint16_t bitPat=0; bitPat<0x800; bitPat++)
-       for(uint16_t bitPat=0; bitPat<0x80; bitPat++)
-       {
-/*             uint16_t w3 = bitPat & 0x888;
-               uint16_t w2 = bitPat & 0x444;
-               uint16_t w1 = bitPat & 0x222;
-               uint16_t w0 = bitPat & 0x111;*/
-               uint16_t w3 = bitPat & 0x88;
-               uint16_t w2 = bitPat & 0x44;
-               uint16_t w1 = bitPat & 0x22;
-               uint16_t w0 = bitPat & 0x11;
-
-               uint16_t blurred3 = (w3 | (w3 >> 1) | (w3 >> 2) | (w3 >> 3)) & 0x00FF;
-               uint16_t blurred2 = (w2 | (w2 >> 1) | (w2 >> 2) | (w2 >> 3)) & 0x00FF;
-               uint16_t blurred1 = (w1 | (w1 >> 1) | (w1 >> 2) | (w1 >> 3)) & 0x00FF;
-               uint16_t blurred0 = (w0 | (w0 >> 1) | (w0 >> 2) | (w0 >> 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][7 - i] = color;
-               }
-       }
-#else
        for(uint16_t bitPat=0; bitPat<0x800; bitPat+=0x10)
        {
                uint16_t w0 = bitPat & 0x111, w1 = bitPat & 0x222, w2 = bitPat & 0x444, w3 = bitPat & 0x888;
@@ -308,7 +343,6 @@ void SetupBlurTable(void)
                        blurTable[bitPat >> 4][7 - i] = color;
                }
        }
-#endif
 
        for(int i=0; i<256; i++)
        {
@@ -329,7 +363,12 @@ void TogglePalette(void)
        if (palette == (uint32_t *)colors)
        {
                palette = (uint32_t *)altColors;
-               SpawnMessage("Color TV palette");
+               SpawnMessage("ApplePC Color TV palette");
+       }
+       else if (palette == (uint32_t *)altColors)
+       {
+               palette = (uint32_t *)robColors;
+               SpawnMessage("Rob's Color TV palette");
        }
        else
        {
@@ -352,6 +391,12 @@ void CycleScreenTypes(void)
 }
 
 
+void ToggleTickDisplay(void)
+{
+       showFrameTicks = !showFrameTicks;
+}
+
+
 static uint32_t msgTicks = 0;
 static char message[4096];
 
@@ -364,34 +409,43 @@ void SpawnMessage(const char * text, ...)
        va_end(arg);
 
        msgTicks = 120;
+//WriteLog("\n%s\n", message);
 }
 
 
-static void DrawString2(uint32_t x, uint32_t y, uint32_t color);
+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);
+                       DrawString2(x, y, 0x00000000, message);
 
-       DrawString2(8, 8, 0x0020FF20);
+       DrawString2(8, 8, 0x0020FF20, message);
 }
 
 
-static void DrawString2(uint32_t x, uint32_t y, uint32_t color)
+static void DrawString(uint32_t x, uint32_t y, uint32_t color, char * msg)
 {
-//uint32_t x = 8, y = 8;
-       uint32_t length = strlen(message), address = x + (y * VIRTUAL_SCREEN_WIDTH);
-//     uint32_t color = 0x0020FF20;
-//This could be done ahead of time, instead of on each pixel...
-//(Now it is!)
+//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<length; i++)
        {
-               uint8_t c = message[i];
+               uint8_t c = msg[i];
                c = (c < 32 ? 0 : c - 32);
                uint32_t fontAddr = (uint32_t)c * FONT_WIDTH * FONT_HEIGHT;
 
@@ -399,14 +453,6 @@ static void DrawString2(uint32_t x, uint32_t y, uint32_t color)
                {
                        for(uint32_t xx=0; xx<FONT_WIDTH; xx++)
                        {
-/*                             uint8_t fontTrans = font1[fontAddr++];
-//                             uint32_t newTrans = (fontTrans * transparency / 255) << 24;
-                               uint32_t newTrans = fontTrans << 24;
-                               uint32_t pixel = newTrans | color;
-
-                               *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = pixel;//*/
-
-//                             uint8_t trans = font1[fontAddr++];
                                uint8_t trans = font2[fontAddr++];
 
                                if (trans)
@@ -438,6 +484,65 @@ static void DrawString2(uint32_t x, uint32_t y, uint32_t color)
 }
 
 
+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);
@@ -456,19 +561,24 @@ static void Render40ColumnTextLine(uint8_t line)
 
                                if (alternateCharset)
                                {
-                                       if (textChar[((chr & 0x3F) * 56) + cx + (cy * 7)])
+                                       if (textChar2e[(chr * 56) + cx + (cy * 7)])
                                                pixel = pixelOn;
-
-                                       if (chr < 0x80)
-                                               pixel = pixel ^ (screenType == ST_GREEN_MONO ? 0x0061FF61 : 0x00FFFFFF);
-
-                                       if ((chr & 0xC0) == 0x40 && flash)
-                                               pixel = 0xFF000000;
                                }
                                else
                                {
-                                       if (textChar2e[(chr * 56) + cx + (cy * 7)])
-                                               pixel = pixelOn;
+                                       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;
@@ -494,20 +604,12 @@ static void Render80ColumnTextLine(uint8_t line)
 
        for(int x=0; x<80; x++)
        {
-#if 0
-// This is wrong; it should grab from the alt bank if Page2 is set, not main RAM @ $0
-               uint8_t chr = ram[lineAddrLoRes[line] + (displayPage2 ? 0x0400 : 0x0000) + x];
-
-               if (x > 39)
-                       chr = ram2[lineAddrLoRes[line] + (displayPage2 ? 0x0400 : 0x0000) + x - 40];
-#else
                uint8_t chr;
 
                if (x & 0x01)
                        chr = ram[lineAddrLoRes[line] + (x >> 1)];
                else
                        chr = ram2[lineAddrLoRes[line] + (x >> 1)];
-#endif
 
                // Render character at (x, y)
 
@@ -519,19 +621,24 @@ static void Render80ColumnTextLine(uint8_t line)
 
                                if (alternateCharset)
                                {
-                                       if (textChar[((chr & 0x3F) * 56) + cx + (cy * 7)])
+                                       if (textChar2e[(chr * 56) + cx + (cy * 7)])
                                                pixel = pixelOn;
-
-                                       if (chr < 0x80)
-                                               pixel = pixel ^ (screenType == ST_GREEN_MONO ? 0x0061FF61 : 0x00FFFFFF);
-
-                                       if ((chr & 0xC0) == 0x40 && flash)
-                                               pixel = 0xFF000000;
                                }
                                else
                                {
-                                       if (textChar2e[(chr * 56) + cx + (cy * 7)])
-                                               pixel = pixelOn;
+                                       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;
@@ -564,9 +671,9 @@ static void Render80ColumnText(void)
 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.
+//       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 than 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!
@@ -590,31 +697,6 @@ 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<toLine; y++)
-               {
-                       for(uint16_t x=0; x<40; x++)
-                       {
-                               uint8_t scrByte = ram[lineAddrLoRes[y] + (displayPage2 ? 0x0400 : 0x0000) + x];
-                               uint32_t pixel = palette[scrByte & 0x0F];
-
-                               for(int cy=0; cy<4; cy++)
-                                       for(int cx=0; cx<14; cx++)
-                                               scrBuffer[((x * 14) + cx) + (((y * 8) + cy) * VIRTUAL_SCREEN_WIDTH)] = pixel;
-
-                               pixel = palette[scrByte >> 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<toLine; y++)
@@ -732,10 +814,171 @@ fb fb fb -> 15 [1111] -> 15              WHITE
 }
 
 
+//
+// 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<toLine; y++)
+       {
+               // Do top half of double lores screen bytes...
+
+               uint32_t previous3Bits = 0;
+
+               for(uint16_t x=0; x<40; x+=2)
+               {
+                       uint8_t scrByte3 = ram2[lineAddrLoRes[y] + x + 0] & 0x0F;
+                       uint8_t scrByte4 = ram2[lineAddrLoRes[y] + x + 1] & 0x0F;
+                       uint8_t scrByte1 = ram[lineAddrLoRes[y] + x + 0] & 0x0F;
+                       uint8_t scrByte2 = ram[lineAddrLoRes[y] + x + 1] & 0x0F;
+                       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 | (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=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
@@ -763,12 +1006,6 @@ static void RenderHiRes(uint16_t toLine/*= 192*/)
 
                        pixels = previous3bits | (pixels << 14) | pixels2;
 
-//testing (this shows on the screen, so it's OK)
-//if (x == 0)
-//{
-//     pixels = 0x7FFFFFFF;
-//}
-
                        // 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
@@ -892,27 +1129,32 @@ void RenderVideoFrame(void)
                {
                        if (mixedMode)
                        {
-                               if (hiRes)
+                               if (dhires)
                                {
-                                       RenderHiRes(160);
-                                       Render40ColumnTextLine(20);
-                                       Render40ColumnTextLine(21);
-                                       Render40ColumnTextLine(22);
-                                       Render40ColumnTextLine(23);
+                                       if (hiRes)
+                                               RenderDHiRes(160);
+                                       else
+                                               RenderDLoRes(20);
                                }
+                               else if (hiRes)
+                                       RenderHiRes(160);
                                else
-                               {
                                        RenderLoRes(20);
-                                       Render40ColumnTextLine(20);
-                                       Render40ColumnTextLine(21);
-                                       Render40ColumnTextLine(22);
-                                       Render40ColumnTextLine(23);
-                               }
+
+                               Render40ColumnTextLine(20);
+                               Render40ColumnTextLine(21);
+                               Render40ColumnTextLine(22);
+                               Render40ColumnTextLine(23);
                        }
                        else
                        {
                                if (dhires)
-                                       RenderDHiRes();
+                               {
+                                       if (hiRes)
+                                               RenderDHiRes();
+                                       else
+                                               RenderDLoRes();
+                               }
                                else if (hiRes)
                                        RenderHiRes();
                                else
@@ -930,6 +1172,9 @@ void RenderVideoFrame(void)
                DrawString();
                msgTicks--;
        }
+
+       if (showFrameTicks)
+               DrawFrameTicks();
 }
 
 
@@ -944,18 +1189,24 @@ bool InitVideo(void)
                return false;
        }
 
-//     int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, SDL_WINDOW_OPENGL, &sdlWindow, &sdlRenderer);
-       int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, 0, &sdlWindow, &sdlRenderer);
+       sdlWindow = SDL_CreateWindow("Apple2", settings.winX, settings.winY, VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, 0);
 
-       if (retVal != 0)
+       if (sdlWindow == NULL)
        {
-               WriteLog("Video: Could not window and/or renderer: %s\n", SDL_GetError());
+               WriteLog("Video: Could not create window: %s\n", SDL_GetError());
                return false;
        }
 
-       // Make the scaled rendering look smoother.
+       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;
+       }
+
+       // Make sure what we put there is what we get:
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
-//     SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
        SDL_RenderSetLogicalSize(sdlRenderer, VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT);
 
        // Set the application's icon & title...
@@ -968,6 +1219,12 @@ bool InitVideo(void)
                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");
@@ -997,6 +1254,7 @@ void RenderAppleScreen(SDL_Renderer * renderer)
        SDL_LockTexture(sdlTexture, NULL, (void **)&scrBuffer, &scrPitch);
        RenderVideoFrame();
        SDL_UnlockTexture(sdlTexture);
+       SDL_RenderClear(renderer);              // Without this, full screen has trash on the sides
        SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);
 }
 
@@ -1009,7 +1267,6 @@ void ToggleFullScreen(void)
        settings.fullscreen = !settings.fullscreen;
 
        int retVal = SDL_SetWindowFullscreen(sdlWindow, (settings.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0));
-       SDL_ShowCursor(settings.fullscreen ? 0 : 1);
 
        if (retVal != 0)
                WriteLog("Video::ToggleFullScreen: SDL error = %s\n", SDL_GetError());