// 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()
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
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] = {
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;
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*/);
// 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;
blurTable[bitPat >> 4][7 - i] = color;
}
}
-#endif
for(int i=0; i<256; i++)
{
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
{
}
+void ToggleTickDisplay(void)
+{
+ showFrameTicks = !showFrameTicks;
+}
+
+
static uint32_t msgTicks = 0;
static char message[4096];
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, 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(8, 8, 0x0020FF20);
+ DrawString2(x, y, color, msg);
}
-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)
{
-//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!)
+ 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;
{
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)
}
+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);
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;
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;
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!
*/
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++)
//
// Render the Double Lo Res screen (HIRES off, DHIRES on)
//
-static void RenderDLoRes(void)
+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
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; y++)
+ for(uint16_t y=0; y<toLine; y++)
{
// Do top half of double lores screen bytes...
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
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
{
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
{
DrawString();
msgTicks--;
}
+
+ if (showFrameTicks)
+ DrawFrameTicks();
}
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);
-// int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 1, VIRTUAL_SCREEN_HEIGHT * 1, 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...
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");
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);
}