From 25c7c41b07490ff0e058489a24475188f0684d56 Mon Sep 17 00:00:00 2001 From: TomCh Date: Wed, 9 Jan 2019 21:29:36 +0000 Subject: [PATCH] Reinstate support for old AppleWin 1.25 "Color (Standard)" video mode (#357) (PR #614) Renamed this video mode to: "Color (RGB Monitor)" Also renamed "Color (Monitor)" to "Color (NTSC Monitor)". As for the colours: I've changed them from the original 1.25 colours. Instead I runtime-generate the colours from the NTSC code. See NTSC.cpp's GenerateBaseColors(). This shifts the same 4-bit pattern in, combining with NTSC color phase, until the colour stabilises. Then I average the next 4 RGB values to get the final colour. The reason for this is that we now have consistent colours between NTSC and this simplified rendering mode. NB. The 2 greys (in GR,DGR,DHGR) are now the same RGB value. --- AppleWinExpress2008.vcproj | 8 + source/Applewin.cpp | 36 +- source/Applewin.h | 2 +- source/NTSC.cpp | 239 +++++++++- source/Video.cpp | 47 +- source/Video.h | 9 +- source/Video_OriginalColorTVMode.cpp | 651 +++++++++++++++++++++++++++ source/Video_OriginalColorTVMode.h | 8 + 8 files changed, 963 insertions(+), 37 deletions(-) create mode 100644 source/Video_OriginalColorTVMode.cpp create mode 100644 source/Video_OriginalColorTVMode.h diff --git a/AppleWinExpress2008.vcproj b/AppleWinExpress2008.vcproj index ccc80c1f8..852494bc2 100644 --- a/AppleWinExpress2008.vcproj +++ b/AppleWinExpress2008.vcproj @@ -913,6 +913,14 @@ RelativePath=".\source\Video.h" > + + + + Config_Load_Video() + const bool bShowAboutDlg = CheckOldAppleWinVersion(); // Post: g_OldAppleWinVersion + LoadConfiguration(); LogFileOutput("Main: LoadConfiguration()\n"); @@ -1531,10 +1558,7 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int) MemInitialize(); LogFileOutput("Main: MemInitialize()\n"); - char szOldAppleWinVersion[sizeof(VERSIONSTRING)] = {0}; - RegLoadString(TEXT(REG_CONFIG), TEXT(REGVALUE_VERSION), 1, szOldAppleWinVersion, sizeof(szOldAppleWinVersion)); - - const bool bShowAboutDlg = strcmp(szOldAppleWinVersion, VERSIONSTRING) != 0; + // Show About dialog after creating main window (need g_hFrameWindow) if (bShowAboutDlg) { if (!AboutDlg()) diff --git a/source/Applewin.h b/source/Applewin.h index 894cae024..1d3d950f8 100644 --- a/source/Applewin.h +++ b/source/Applewin.h @@ -9,7 +9,7 @@ void LogFileTimeUntilFirstKeyRead(void); void SetCurrentCLK6502(); bool SetCurrentImageDir(const char* pszImageDir); -extern const UINT16* GetAppleWinVersion(void); +extern const UINT16* GetOldAppleWinVersion(void); extern char VERSIONSTRING[]; // Constructed in WinMain() extern const TCHAR *g_pAppTitle; diff --git a/source/NTSC.cpp b/source/NTSC.cpp index 088626dc2..a189bade9 100644 --- a/source/NTSC.cpp +++ b/source/NTSC.cpp @@ -26,6 +26,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Frame.h" #include "Memory.h" // MemGetMainPtr() MemGetAuxPtr() #include "Video.h" // g_pFramebufferbits + #include "Video_OriginalColorTVMode.h" #include "NTSC.h" #include "NTSC_CharSet.h" @@ -745,6 +746,26 @@ inline void updatePixels( uint16_t bits ) //=========================================================================== +inline void updateVideoScannerHorzEOLSimple() +{ + if (VIDEO_SCANNER_MAX_HORZ == ++g_nVideoClockHorz) + { + g_nVideoClockHorz = 0; + + if (++g_nVideoClockVert == VIDEO_SCANNER_MAX_VERT) + { + g_nVideoClockVert = 0; + + updateFlashRate(); + } + + if (g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY) + { + updateVideoScannerAddress(); + } + } +} + #if !EXTEND_14M_VIDEO_BY_1_PIXEL // NOTE: This writes out-of-bounds for a 560x384 framebuffer inline void updateVideoScannerHorzEOL() @@ -881,8 +902,8 @@ inline void updateVideoScannerAddress() g_pVideoAddress = g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY ? g_pScanLines[2*g_nVideoClockVert] : g_pScanLines[0]; // Adjust, as these video styles have 2x 14M pixels of pre-render - // NB. For VT_COLOR_MONITOR, also check color-burst so that TEXT and MIXED(HGR+TEXT) render the TEXT at the same offset (GH#341) - if (g_eVideoType == VT_MONO_TV || g_eVideoType == VT_COLOR_TV || (g_eVideoType == VT_COLOR_MONITOR && GetColorBurst())) + // NB. For VT_COLOR_MONITOR_NTSC, also check color-burst so that TEXT and MIXED(HGR+TEXT) render the TEXT at the same offset (GH#341) + if (g_eVideoType == VT_MONO_TV || g_eVideoType == VT_COLOR_TV || (g_eVideoType == VT_COLOR_MONITOR_NTSC && GetColorBurst())) g_pVideoAddress -= 2; // GH#555: For the 14M video modes (DHGR,DGR,80COL), start rendering 1x 14M pixel early to account for these video modes being shifted right by 1 pixel @@ -890,10 +911,11 @@ inline void updateVideoScannerAddress() // . 7x 14M pixels early + 1x 14M pixel shifted right = 2 complete color phase rotations. // . ie. the 14M colors are correct, but being 1 pixel out is the closest we can get the 7M and 14M video modes to overlap. // . The alternative is to render the 14M correctly 7 pixels early, but have 7-pixel borders left (for 7M modes) or right (for 14M modes). - if ((g_pFuncUpdateGraphicsScreen == updateScreenDoubleHires80) || + if (((g_pFuncUpdateGraphicsScreen == updateScreenDoubleHires80) || (g_pFuncUpdateGraphicsScreen == updateScreenDoubleLores80) || (g_pFuncUpdateGraphicsScreen == updateScreenText80) || (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED && g_pFuncUpdateTextScreen == updateScreenText80)) + && (g_eVideoType != VT_COLOR_MONITOR_RGB)) // Fix for "Ansi Story" (Turn the disk over) - Top row of TEXT80 is shifted by 1 pixel { g_pVideoAddress -= 1; } @@ -1270,8 +1292,8 @@ inline void zeroPixel0_14M(void) // GH#555 if (g_nVideoClockHorz == VIDEO_SCANNER_HORZ_START) { UINT32* p = ((UINT32*)g_pVideoAddress) - 14; // Point back to pixel-0 - // NB. For VT_COLOR_MONITOR, also check color-burst so that TEXT and MIXED(HGR+TEXT) render the TEXT at the same offset (GH#341) - if (g_eVideoType == VT_MONO_TV || g_eVideoType == VT_COLOR_TV || (g_eVideoType == VT_COLOR_MONITOR && GetColorBurst())) + // NB. For VT_COLOR_MONITOR_NTSC, also check color-burst so that TEXT and MIXED(HGR+TEXT) render the TEXT at the same offset (GH#341) + if (g_eVideoType == VT_MONO_TV || g_eVideoType == VT_COLOR_TV || (g_eVideoType == VT_COLOR_MONITOR_NTSC && GetColorBurst())) { p[2] = 0; p[g_kFrameBufferWidth+2] = 0; // Next line (there are 2 lines per Apple II scanline) @@ -1318,6 +1340,36 @@ void updateScreenDoubleHires40 (long cycles6502) // wsUpdateVideoHires0 } //=========================================================================== + +void updateScreenDoubleHires80Simplified (long cycles6502 ) // wsUpdateVideoDblHires +{ + if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED) + { + g_pFuncUpdateTextScreen( cycles6502 ); + return; + } + + for (; cycles6502 > 0; --cycles6502) + { + uint16_t addr = getVideoScannerAddressHGR(); + + if (g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY) + { + if ((g_nVideoClockHorz < VIDEO_SCANNER_HORZ_COLORBURST_END) && (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_COLORBURST_BEG)) + { + g_nColorBurstPixels = 1024; + } + else if (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_START) + { + uint16_t addr = getVideoScannerAddressHGR(); + UpdateDHiResCell(g_nVideoClockHorz-VIDEO_SCANNER_HORZ_START, g_nVideoClockVert, addr, g_pVideoAddress); + g_pVideoAddress += 14; + } + } + updateVideoScannerHorzEOLSimple(); + } +} + void updateScreenDoubleHires80 (long cycles6502 ) // wsUpdateVideoDblHires { if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED) @@ -1396,6 +1448,36 @@ void updateScreenDoubleLores40 (long cycles6502) // wsUpdateVideo7MLores } //=========================================================================== + +static void updateScreenDoubleLores80Simplified (long cycles6502) // wsUpdateVideoDblLores +{ + if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED) + { + g_pFuncUpdateTextScreen( cycles6502 ); + return; + } + + for (; cycles6502 > 0; --cycles6502) + { + uint16_t addr = getVideoScannerAddressTXT(); + + if (g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY) + { + if ((g_nVideoClockHorz < VIDEO_SCANNER_HORZ_COLORBURST_END) && (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_COLORBURST_BEG)) + { + g_nColorBurstPixels = 1024; + } + else if (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_START) + { + uint16_t addr = getVideoScannerAddressTXT(); + UpdateDLoResCell(g_nVideoClockHorz-VIDEO_SCANNER_HORZ_START, g_nVideoClockVert, addr, g_pVideoAddress); + g_pVideoAddress += 14; + } + } + updateVideoScannerHorzEOLSimple(); + } +} + void updateScreenDoubleLores80 (long cycles6502) // wsUpdateVideoDblLores { if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED) @@ -1446,6 +1528,33 @@ void updateScreenDoubleLores80 (long cycles6502) // wsUpdateVideoDblLores } //=========================================================================== +static void updateScreenSingleHires40Simplified (long cycles6502) +{ + if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED) + { + g_pFuncUpdateTextScreen( cycles6502 ); + return; + } + + for (; cycles6502 > 0; --cycles6502) + { + if (g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY) + { + if ((g_nVideoClockHorz < VIDEO_SCANNER_HORZ_COLORBURST_END) && (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_COLORBURST_BEG)) + { + g_nColorBurstPixels = 1024; + } + else if (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_START) + { + uint16_t addr = getVideoScannerAddressHGR(); + UpdateHiResCell(g_nVideoClockHorz-VIDEO_SCANNER_HORZ_START, g_nVideoClockVert, addr, g_pVideoAddress); + g_pVideoAddress += 14; + } + } + updateVideoScannerHorzEOLSimple(); + } +} + void updateScreenSingleHires40 (long cycles6502) { if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED) @@ -1487,6 +1596,35 @@ void updateScreenSingleHires40 (long cycles6502) } //=========================================================================== +static void updateScreenSingleLores40Simplified (long cycles6502) +{ + if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED) + { + g_pFuncUpdateTextScreen( cycles6502 ); + return; + } + + for (; cycles6502 > 0; --cycles6502) + { + uint16_t addr = getVideoScannerAddressTXT(); + + if (g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY) + { + if ((g_nVideoClockHorz < VIDEO_SCANNER_HORZ_COLORBURST_END) && (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_COLORBURST_BEG)) + { + g_nColorBurstPixels = 1024; + } + else if (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_START) + { + uint16_t addr = getVideoScannerAddressTXT(); + UpdateLoResCell(g_nVideoClockHorz-VIDEO_SCANNER_HORZ_START, g_nVideoClockVert, addr, g_pVideoAddress); + g_pVideoAddress += 14; + } + } + updateVideoScannerHorzEOLSimple(); + } +} + void updateScreenSingleLores40 (long cycles6502) { if (g_nVideoMixed && g_nVideoClockVert >= VIDEO_SCANNER_Y_MIXED) @@ -1582,7 +1720,8 @@ void updateScreenText80 (long cycles6502) aux ^= g_nTextFlashMask; uint16_t bits = (main << 7) | (aux & 0x7f); - bits = (bits << 1) | g_nLastColumnPixelNTSC; // GH#555: Align TEXT80 chars with DHGR + if (g_eVideoType != VT_COLOR_MONITOR_RGB) // No extra 14M bit needed for VT_COLOR_MONITOR_RGB + bits = (bits << 1) | g_nLastColumnPixelNTSC; // GH#555: Align TEXT80 chars with DHGR updatePixels( bits ); g_nLastColumnPixelNTSC = (bits >> 14) & 1; @@ -1686,38 +1825,71 @@ void NTSC_SetVideoMode( uint32_t uVideoModeFlags ) g_nTextPage = 1; g_nHiresPage = 1; - if (uVideoModeFlags & VF_PAGE2) { + if (uVideoModeFlags & VF_PAGE2) + { // Apple IIe, Technical Notes, #3: Double High-Resolution Graphics // 80STORE must be OFF to display page 2 - if (0 == (uVideoModeFlags & VF_80STORE)) { + if (0 == (uVideoModeFlags & VF_80STORE)) + { g_nTextPage = 2; g_nHiresPage = 2; } } - if (uVideoModeFlags & VF_TEXT) { + if (uVideoModeFlags & VF_TEXT) + { if (uVideoModeFlags & VF_80COL) g_pFuncUpdateGraphicsScreen = updateScreenText80; else g_pFuncUpdateGraphicsScreen = updateScreenText40; } - else if (uVideoModeFlags & VF_HIRES) { + else if (uVideoModeFlags & VF_HIRES) + { if (uVideoModeFlags & VF_DHIRES) + { if (uVideoModeFlags & VF_80COL) - g_pFuncUpdateGraphicsScreen = updateScreenDoubleHires80; + { + if (g_eVideoType == VT_COLOR_MONITOR_RGB) + g_pFuncUpdateGraphicsScreen = updateScreenDoubleHires80Simplified; + else + g_pFuncUpdateGraphicsScreen = updateScreenDoubleHires80; + } else + { g_pFuncUpdateGraphicsScreen = updateScreenDoubleHires40; + } + } else - g_pFuncUpdateGraphicsScreen = updateScreenSingleHires40; + { + if (g_eVideoType == VT_COLOR_MONITOR_RGB) + g_pFuncUpdateGraphicsScreen = updateScreenSingleHires40Simplified; + else + g_pFuncUpdateGraphicsScreen = updateScreenSingleHires40; + } } - else { + else + { if (uVideoModeFlags & VF_DHIRES) + { if (uVideoModeFlags & VF_80COL) - g_pFuncUpdateGraphicsScreen = updateScreenDoubleLores80; + { + if (g_eVideoType == VT_COLOR_MONITOR_RGB) + g_pFuncUpdateGraphicsScreen = updateScreenDoubleLores80Simplified; + else + g_pFuncUpdateGraphicsScreen = updateScreenDoubleLores80; + } else + { g_pFuncUpdateGraphicsScreen = updateScreenDoubleLores40; + } + } else - g_pFuncUpdateGraphicsScreen = updateScreenSingleLores40; + { + if (g_eVideoType == VT_COLOR_MONITOR_RGB) + g_pFuncUpdateGraphicsScreen = updateScreenSingleLores40Simplified; + else + g_pFuncUpdateGraphicsScreen = updateScreenSingleLores40; + } } } @@ -1745,7 +1917,7 @@ void NTSC_SetVideoStyle() // (int v, int s) } break; - case VT_COLOR_MONITOR: + case VT_COLOR_MONITOR_NTSC: default: r = 0xFF; g = 0xFF; @@ -1776,7 +1948,6 @@ void NTSC_SetVideoStyle() // (int v, int s) } break; -// case VT_MONO_WHITE: //VT_MONO_MONITOR: //3: case VT_MONO_AMBER: r = 0xFF; g = 0x80; @@ -1789,6 +1960,7 @@ void NTSC_SetVideoStyle() // (int v, int s) b = 0x00; goto _mono; + case VT_COLOR_MONITOR_RGB: case VT_MONO_WHITE: r = 0xFF; g = 0xFF; @@ -1820,6 +1992,7 @@ void NTSC_SetVideoStyle() // (int v, int s) //=========================================================================== void GenerateVideoTables( void ); +void GenerateBaseColors(baseColors_t pBaseNtscColors); void NTSC_VideoInit( uint8_t* pFramebuffer ) // wsVideoInit { @@ -1842,6 +2015,10 @@ void NTSC_VideoInit( uint8_t* pFramebuffer ) // wsVideoInit VideoReinitialize(); // Setup g_pFunc_ntsc*Pixel() + bgra_t baseColors[kNumBaseColors]; + GenerateBaseColors(&baseColors); + VideoInitializeOriginal(&baseColors); + #if HGR_TEST_PATTERN // Init HGR to almost all-possible-combinations // CALL-151 @@ -2121,3 +2298,31 @@ static void GenerateVideoTables( void ) VideoResetState(); SetApple2Type(currentApple2Type); } + +void GenerateBaseColors(baseColors_t pBaseNtscColors) +{ + for (UINT i=0; i<16; i++) + { + g_nColorPhaseNTSC = INITIAL_COLOR_PHASE; + g_nSignalBitsNTSC = 0; + + // 12 iterations for colour to "stabilise", then 4 iterations to calc the average + // - after colour "stabilises" then it repeats through 4 phases (with different RGB values for each phase) + uint32_t bits = (i<<12) | (i<<8) | (i<<4) | i; // 16 bits + + uint32_t colors[4]; + for (UINT j=0; j<16; j++) + { + colors[j&3] = getScanlineColor(bits & 1, g_aHueColorTV[g_nColorPhaseNTSC]); + bits >>= 1; + updateColorPhase(); + } + + int r = (((colors[0]>>16)&0xff) + ((colors[1]>>16)&0xff) + ((colors[2]>>16)&0xff) + ((colors[3]>>16)&0xff)) / 4; + int g = (((colors[0]>> 8)&0xff) + ((colors[1]>> 8)&0xff) + ((colors[2]>> 8)&0xff) + ((colors[3]>> 8)&0xff)) / 4; + int b = (((colors[0] )&0xff) + ((colors[1] )&0xff) + ((colors[2] )&0xff) + ((colors[3] )&0xff)) / 4; + uint32_t color = ((r<<16) | (g<<8) | b) | ALPHA32_MASK; + + (*pBaseNtscColors)[i] = * (bgra_t*) &color; + } +} diff --git a/source/Video.cpp b/source/Video.cpp index 989b095bd..c30f01a1e 100644 --- a/source/Video.cpp +++ b/source/Video.cpp @@ -83,14 +83,11 @@ static LPBITMAPINFO g_pFramebufferinfo = NULL; HBITMAP g_hLogoBitmap; -const int MAX_SOURCE_Y = 512; -static LPBYTE g_aSourceStartofLine[ MAX_SOURCE_Y ]; - COLORREF g_nMonochromeRGB = RGB(0xC0,0xC0,0xC0); uint32_t g_uVideoMode = VF_TEXT; // Current Video Mode (this is the last set one as it may change mid-scan line!) -DWORD g_eVideoType = VT_COLOR_TV; +DWORD g_eVideoType = VT_DEFAULT; DWORD g_uHalfScanLines = 1; // drop 50% scan lines for a more authentic look static const bool g_bVideoScannerNTSC = true; // NTSC video scanning (or PAL) @@ -100,9 +97,10 @@ static const bool g_bVideoScannerNTSC = true; // NTSC video scanning (or PAL) // NOTE: KEEP IN SYNC: VideoType_e g_aVideoChoices g_apVideoModeDesc TCHAR g_aVideoChoices[] = TEXT("Monochrome (Custom)\0") - TEXT("Color Monitor\0") - TEXT("B&W TV\0") + TEXT("Color (RGB Monitor)\0") + TEXT("Color (NTSC Monitor)\0") TEXT("Color TV\0") + TEXT("B&W TV\0") TEXT("Monochrome (Amber)\0") TEXT("Monochrome (Green)\0") TEXT("Monochrome (White)\0") @@ -113,9 +111,10 @@ static const bool g_bVideoScannerNTSC = true; // NTSC video scanning (or PAL) char *g_apVideoModeDesc[ NUM_VIDEO_MODES ] = { "Monochrome Monitor (Custom)" - , "Color Monitor" - , "B&W TV" + , "Color (RGB Monitor)" + , "Color (NTSC Monitor)" , "Color TV" + , "B&W TV" , "Amber Monitor" , "Green Monitor" , "White Monitor" @@ -1193,14 +1192,42 @@ bool IsVideoRom4K(void) //=========================================================================== +enum VideoType127_e +{ + VT127_MONO_CUSTOM + , VT127_COLOR_MONITOR_NTSC + , VT127_MONO_TV + , VT127_COLOR_TV + , VT127_MONO_AMBER + , VT127_MONO_GREEN + , VT127_MONO_WHITE + , VT127_NUM_VIDEO_MODES +}; + void Config_Load_Video() { REGLOAD(TEXT(REGVALUE_VIDEO_MODE ),&g_eVideoType); REGLOAD(TEXT(REGVALUE_VIDEO_HALF_SCAN_LINES),&g_uHalfScanLines); REGLOAD(TEXT(REGVALUE_VIDEO_MONO_COLOR ),&g_nMonochromeRGB); + const UINT16* pOldVersion = GetOldAppleWinVersion(); + if (pOldVersion[0] == 1 && pOldVersion[1] <= 27 && pOldVersion[2] <= 13) + { + switch (g_eVideoType) + { + case VT127_MONO_CUSTOM: g_eVideoType = VT_MONO_CUSTOM; break; + case VT127_COLOR_MONITOR_NTSC: g_eVideoType = VT_COLOR_MONITOR_NTSC; break; + case VT127_MONO_TV: g_eVideoType = VT_MONO_TV; break; + case VT127_COLOR_TV: g_eVideoType = VT_COLOR_TV; break; + case VT127_MONO_AMBER: g_eVideoType = VT_MONO_AMBER; break; + case VT127_MONO_GREEN: g_eVideoType = VT_MONO_GREEN; break; + case VT127_MONO_WHITE: g_eVideoType = VT_MONO_WHITE; break; + default: g_eVideoType = VT_DEFAULT; break; + } + } + if (g_eVideoType >= NUM_VIDEO_MODES) - g_eVideoType = VT_COLOR_MONITOR; + g_eVideoType = VT_DEFAULT; } void Config_Save_Video() @@ -1233,9 +1260,9 @@ static void videoCreateDIBSection() ); SelectObject(g_hDeviceDC,g_hDeviceBitmap); - // CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE FRAME BUFFER // DRAW THE SOURCE IMAGE INTO THE SOURCE BIT BUFFER ZeroMemory( g_pFramebufferbits, GetFrameBufferWidth()*GetFrameBufferHeight()*sizeof(bgra_t) ); + // CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE FRAME BUFFER NTSC_VideoInit( g_pFramebufferbits ); } diff --git a/source/Video.h b/source/Video.h index b77713675..ea543811c 100644 --- a/source/Video.h +++ b/source/Video.h @@ -7,13 +7,15 @@ enum VideoType_e { VT_MONO_CUSTOM - , VT_COLOR_MONITOR - , VT_MONO_TV + , VT_COLOR_MONITOR_RGB // Color rendering from AppleWin 1.25 (GH#357) + , VT_COLOR_MONITOR_NTSC , VT_COLOR_TV + , VT_MONO_TV , VT_MONO_AMBER , VT_MONO_GREEN , VT_MONO_WHITE , NUM_VIDEO_MODES + , VT_DEFAULT = VT_COLOR_TV }; extern TCHAR g_aVideoChoices[]; @@ -24,7 +26,7 @@ VF_80COL = 0x00000001, VF_DHIRES = 0x00000002, VF_HIRES = 0x00000004, - VF_80STORE= 0x00000008, // was called VF_MASK2 + VF_80STORE= 0x00000008, VF_MIXED = 0x00000010, VF_PAGE2 = 0x00000020, VF_TEXT = 0x00000040 @@ -61,6 +63,7 @@ #define PACKED // TODO: FIXME: gcc/clang __attribute__ #endif +// TODO: Replace with WinGDI.h / RGBQUAD struct bgra_t { uint8_t b; diff --git a/source/Video_OriginalColorTVMode.cpp b/source/Video_OriginalColorTVMode.cpp new file mode 100644 index 000000000..366b106ee --- /dev/null +++ b/source/Video_OriginalColorTVMode.cpp @@ -0,0 +1,651 @@ +// Sync'd with 1.25.0.4 source + +#include "StdAfx.h" + +#include "Frame.h" +#include "Memory.h" // MemGetMainPtr() MemGetAuxPtr() +#include "Video.h" +#include "Video_OriginalColorTVMode.h" + +const int SRCOFFS_LORES = 0; // 0 +const int SRCOFFS_HIRES = (SRCOFFS_LORES + 16); // 16 +const int SRCOFFS_DHIRES = (SRCOFFS_HIRES + 512); // 528 +const int SRCOFFS_TOTAL = (SRCOFFS_DHIRES + 2560); // 3088 + +const int MAX_SOURCE_Y = 512; +static LPBYTE g_aSourceStartofLine[ MAX_SOURCE_Y ]; +#define SETSOURCEPIXEL(x,y,c) g_aSourceStartofLine[(y)][(x)] = (c) + +// TC: Tried to remove HiresToPalIndex[] translation table, so get purple bars when hires data is: 0x80 0x80... +// . V_CreateLookup_HiResHalfPixel_Authentic() uses both ColorMapping (CM_xxx) indices and Color_Palette_Index_e (HGR_xxx)! +#define DO_OPT_PALETTE 0 + +enum Color_Palette_Index_e +{ +// hires (don't change order) - For tv emulation HGR Video Mode +#if DO_OPT_PALETTE + HGR_VIOLET // HCOLOR=2 VIOLET , 2800: 01 00 55 2A + , HGR_BLUE // HCOLOR=6 BLUE , 3000: 81 00 D5 AA + , HGR_GREEN // HCOLOR=1 GREEN , 2400: 02 00 2A 55 + , HGR_ORANGE // HCOLOR=5 ORANGE , 2C00: 82 00 AA D5 + , HGR_BLACK + , HGR_WHITE +#else + HGR_BLACK + , HGR_WHITE + , HGR_BLUE // HCOLOR=6 BLUE , 3000: 81 00 D5 AA + , HGR_ORANGE // HCOLOR=5 ORANGE , 2C00: 82 00 AA D5 + , HGR_GREEN // HCOLOR=1 GREEN , 2400: 02 00 2A 55 + , HGR_VIOLET // HCOLOR=2 VIOLET , 2800: 01 00 55 2A +#endif +// TV emu + , HGR_GREY1 + , HGR_GREY2 + , HGR_YELLOW + , HGR_AQUA + , HGR_PURPLE + , HGR_PINK +// lores & dhires + , BLACK + , DEEP_RED + , DARK_BLUE + , MAGENTA + , DARK_GREEN + , DARK_GRAY + , BLUE + , LIGHT_BLUE + , BROWN + , ORANGE + , LIGHT_GRAY + , PINK + , GREEN + , YELLOW + , AQUA + , WHITE +}; + +// __ Map HGR color index to Palette index +enum ColorMapping +{ + CM_Violet + , CM_Blue + , CM_Green + , CM_Orange + , CM_Black + , CM_White + , NUM_COLOR_MAPPING +}; + +const BYTE HiresToPalIndex[ NUM_COLOR_MAPPING ] = +{ + HGR_VIOLET + , HGR_BLUE + , HGR_GREEN + , HGR_ORANGE + , HGR_BLACK + , HGR_WHITE +}; + +const BYTE LoresResColors[16] = { + BLACK, DEEP_RED, DARK_BLUE, MAGENTA, + DARK_GREEN,DARK_GRAY,BLUE, LIGHT_BLUE, + BROWN, ORANGE, LIGHT_GRAY,PINK, + GREEN, YELLOW, AQUA, WHITE + }; + +const BYTE DoubleHiresPalIndex[16] = { + BLACK, DARK_BLUE, DARK_GREEN,BLUE, + BROWN, LIGHT_GRAY,GREEN, AQUA, + DEEP_RED,MAGENTA, DARK_GRAY, LIGHT_BLUE, + ORANGE, PINK, YELLOW, WHITE + }; + +#define SETRGBCOLOR(r,g,b) {b,g,r,0} + +static RGBQUAD PalIndex2RGB[] = +{ +// hires +#if DO_OPT_PALETTE + SETRGBCOLOR(/*MAGENTA, */ 0xC7,0x34,0xFF), // FD Linards Tweaked 0xFF,0x00,0xFF -> 0xC7,0x34,0xFF + SETRGBCOLOR(/*BLUE, */ 0x0D,0xA1,0xFF), // FC Linards Tweaked 0x00,0x00,0xFF -> 0x0D,0xA1,0xFF + SETRGBCOLOR(/*GREEN, */ 0x38,0xCB,0x00), // FA Linards Tweaked 0x00,0xFF,0x00 -> 0x38,0xCB,0x00 + SETRGBCOLOR(/*ORANGE, */ 0xF2,0x5E,0x00), // 0xFF,0x80,0x00 -> Linards Tweaked 0xF2,0x5E,0x00 + SETRGBCOLOR(/*HGR_BLACK, */ 0x00,0x00,0x00), // For TV emulation HGR Video Mode + SETRGBCOLOR(/*HGR_WHITE, */ 0xFF,0xFF,0xFF), +#else + SETRGBCOLOR(/*HGR_BLACK, */ 0x00,0x00,0x00), // For TV emulation HGR Video Mode + SETRGBCOLOR(/*HGR_WHITE, */ 0xFF,0xFF,0xFF), + SETRGBCOLOR(/*BLUE, */ 0x0D,0xA1,0xFF), // FC Linards Tweaked 0x00,0x00,0xFF -> 0x0D,0xA1,0xFF + SETRGBCOLOR(/*ORANGE, */ 0xF2,0x5E,0x00), // 0xFF,0x80,0x00 -> Linards Tweaked 0xF2,0x5E,0x00 + SETRGBCOLOR(/*GREEN, */ 0x38,0xCB,0x00), // FA Linards Tweaked 0x00,0xFF,0x00 -> 0x38,0xCB,0x00 + SETRGBCOLOR(/*MAGENTA, */ 0xC7,0x34,0xFF), // FD Linards Tweaked 0xFF,0x00,0xFF -> 0xC7,0x34,0xFF +#endif + +// TV emu + SETRGBCOLOR(/*HGR_GREY1, */ 0x80,0x80,0x80), + SETRGBCOLOR(/*HGR_GREY2, */ 0x80,0x80,0x80), + SETRGBCOLOR(/*HGR_YELLOW,*/ 0x9E,0x9E,0x00), // 0xD0,0xB0,0x10 -> 0x9E,0x9E,0x00 + SETRGBCOLOR(/*HGR_AQUA, */ 0x00,0xCD,0x4A), // 0x20,0xB0,0xB0 -> 0x00,0xCD,0x4A + SETRGBCOLOR(/*HGR_PURPLE,*/ 0x61,0x61,0xFF), // 0x60,0x50,0xE0 -> 0x61,0x61,0xFF + SETRGBCOLOR(/*HGR_PINK, */ 0xFF,0x32,0xB5), // 0xD0,0x40,0xA0 -> 0xFF,0x32,0xB5 + +// lores & dhires + SETRGBCOLOR(/*BLACK,*/ 0x00,0x00,0x00), // 0 + SETRGBCOLOR(/*DEEP_RED,*/ 0x9D,0x09,0x66), // 0xD0,0x00,0x30 -> Linards Tweaked 0x9D,0x09,0x66 + SETRGBCOLOR(/*DARK_BLUE,*/ 0x2A,0x2A,0xE5), // 4 // Linards Tweaked 0x00,0x00,0x80 -> 0x2A,0x2A,0xE5 + SETRGBCOLOR(/*MAGENTA,*/ 0xC7,0x34,0xFF), // FD Linards Tweaked 0xFF,0x00,0xFF -> 0xC7,0x34,0xFF + SETRGBCOLOR(/*DARK_GREEN,*/ 0x00,0x80,0x00), // 2 // not used + SETRGBCOLOR(/*DARK_GRAY,*/ 0x80,0x80,0x80), // F8 + SETRGBCOLOR(/*BLUE,*/ 0x0D,0xA1,0xFF), // FC Linards Tweaked 0x00,0x00,0xFF -> 0x0D,0xA1,0xFF + SETRGBCOLOR(/*LIGHT_BLUE,*/ 0xAA,0xAA,0xFF), // 0x60,0xA0,0xFF -> Linards Tweaked 0xAA,0xAA,0xFF + SETRGBCOLOR(/*BROWN,*/ 0x55,0x55,0x00), // 0x80,0x50,0x00 -> Linards Tweaked 0x55,0x55,0x00 + SETRGBCOLOR(/*ORANGE,*/ 0xF2,0x5E,0x00), // 0xFF,0x80,0x00 -> Linards Tweaked 0xF2,0x5E,0x00 + SETRGBCOLOR(/*LIGHT_GRAY,*/ 0xC0,0xC0,0xC0), // 7 // GR: COLOR=10 + SETRGBCOLOR(/*PINK,*/ 0xFF,0x89,0xE5), // 0xFF,0x90,0x80 -> Linards Tweaked 0xFF,0x89,0xE5 + SETRGBCOLOR(/*GREEN,*/ 0x38,0xCB,0x00), // FA Linards Tweaked 0x00,0xFF,0x00 -> 0x38,0xCB,0x00 + SETRGBCOLOR(/*YELLOW,*/ 0xD5,0xD5,0x1A), // FB Linards Tweaked 0xFF,0xFF,0x00 -> 0xD5,0xD5,0x1A + SETRGBCOLOR(/*AQUA,*/ 0x62,0xF6,0x99), // 0x40,0xFF,0x90 -> Linards Tweaked 0x62,0xF6,0x99 + SETRGBCOLOR(/*WHITE,*/ 0xFF,0xFF,0xFF), +}; + +//=========================================================================== + +static void V_CreateLookup_DoubleHires () +{ +#define OFFSET 3 +#define SIZE 10 + + for (int column = 0; column < 256; column++) { + int coloffs = SIZE * column; + for (unsigned byteval = 0; byteval < 256; byteval++) { + int color[SIZE]; + ZeroMemory(color,sizeof(color)); + unsigned pattern = MAKEWORD(byteval,column); + int pixel; + for (pixel = 1; pixel < 15; pixel++) { + if (pattern & (1 << pixel)) { + int pixelcolor = 1 << ((pixel-OFFSET) & 3); + if ((pixel >= OFFSET+2) && (pixel < SIZE+OFFSET+2) && (pattern & (0x7 << (pixel-4)))) + color[pixel-(OFFSET+2)] |= pixelcolor; + if ((pixel >= OFFSET+1) && (pixel < SIZE+OFFSET+1) && (pattern & (0xF << (pixel-4)))) + color[pixel-(OFFSET+1)] |= pixelcolor; + if ((pixel >= OFFSET+0) && (pixel < SIZE+OFFSET+0)) + color[pixel-(OFFSET+0)] |= pixelcolor; + if ((pixel >= OFFSET-1) && (pixel < SIZE+OFFSET-1) && (pattern & (0xF << (pixel+1)))) + color[pixel-(OFFSET-1)] |= pixelcolor; + if ((pixel >= OFFSET-2) && (pixel < SIZE+OFFSET-2) && (pattern & (0x7 << (pixel+2)))) + color[pixel-(OFFSET-2)] |= pixelcolor; + } + } + +#if 0 + if (g_eVideoType == VT_COLOR_TEXT_OPTIMIZED) + { + // Activate for fringe reduction on white HGR text - drawback: loss of color mix patterns in HGR Video Mode. + for (pixel = 0; pixel < 13; pixel++) + { + if ((pattern & (0xF << pixel)) == (unsigned)(0xF << pixel)) + for (int pos = pixel; pos < pixel + 4; pos++) + if (pos >= OFFSET && pos < SIZE+OFFSET) + color[pos-OFFSET] = 15; + } + } +#endif + + int y = byteval << 1; + for (int x = 0; x < SIZE; x++) { + SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y ,DoubleHiresPalIndex[ color[x] ]); + SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y+1,DoubleHiresPalIndex[ color[x] ]); + } + } + } +#undef SIZE +#undef OFFSET +} + +//=========================================================================== + +#if 0 +static void V_CreateLookup_Hires() +{ +// int iMonochrome = GetMonochromeIndex(); + + // BYTE colorval[6] = {MAGENTA,BLUE,GREEN,ORANGE,BLACK,WHITE}; + // BYTE colorval[6] = {HGR_MAGENTA,HGR_BLUE,HGR_GREEN,HGR_RED,HGR_BLACK,HGR_WHITE}; + for (int iColumn = 0; iColumn < 16; iColumn++) + { + int coloffs = iColumn << 5; + + for (unsigned iByte = 0; iByte < 256; iByte++) + { + int aPixels[11]; + + aPixels[ 0] = iColumn & 4; + aPixels[ 1] = iColumn & 8; + aPixels[ 9] = iColumn & 1; + aPixels[10] = iColumn & 2; + + int nBitMask = 1; + int iPixel; + for (iPixel = 2; iPixel < 9; iPixel++) { + aPixels[iPixel] = ((iByte & nBitMask) != 0); + nBitMask <<= 1; + } + + int hibit = ((iByte & 0x80) != 0); + int x = 0; + int y = iByte << 1; + + while (x < 28) + { + int adj = (x >= 14) << 1; + int odd = (x >= 14); + + for (iPixel = 2; iPixel < 9; iPixel++) + { + int color = CM_Black; + if (aPixels[iPixel]) + { + if (aPixels[iPixel-1] || aPixels[iPixel+1]) + color = CM_White; + else + color = ((odd ^ (iPixel&1)) << 1) | hibit; + } + else if (aPixels[iPixel-1] && aPixels[iPixel+1]) + { + // Activate fringe reduction on white HGR text - drawback: loss of color mix patterns in HGR video mode. + // VT_COLOR_MONITOR_RGB = Fill in colors in between white pixels + // VT_COLOR_TVEMU = Fill in colors in between white pixels (Post Processing will mix/merge colors) + // VT_COLOR_TEXT_OPTIMIZED --> !(aPixels[iPixel-2] && aPixels[iPixel+2]) = Don't fill in colors in between white + if ((g_eVideoType == VT_COLOR_TVEMU) || !(aPixels[iPixel-2] && aPixels[iPixel+2]) ) + color = ((odd ^ !(iPixel&1)) << 1) | hibit; // No white HGR text optimization + } + + //if (g_eVideoType == VT_MONO_AUTHENTIC) { + // int nMonoColor = (color != CM_Black) ? iMonochrome : BLACK; + // SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj ,y , nMonoColor); // buggy + // SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y , nMonoColor); // buggy + // SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj ,y+1,BLACK); // BL + // SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1,BLACK); // BR + //} else + { + // Colors - Top/Bottom Left/Right + // cTL cTR + // cBL cBR + SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj ,y ,HiresToPalIndex[color]); // cTL + SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y ,HiresToPalIndex[color]); // cTR + SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj ,y+1,HiresToPalIndex[color]); // cBL + SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1,HiresToPalIndex[color]); // cBR + } + x += 2; + } + } + } + } +} +#endif + +//=========================================================================== + +void V_CreateLookup_Lores() +{ + for (int color = 0; color < 16; color++) + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + SETSOURCEPIXEL(SRCOFFS_LORES+x,(color << 4)+y,LoresResColors[color]); +} + +//=========================================================================== + +#define HALF_PIXEL_SOLID 1 +#define HALF_PIXEL_BLEED 0 + +void V_CreateLookup_HiResHalfPixel_Authentic(VideoType_e videoType) // Colors are solid (100% coverage) +{ + // 2-bits from previous byte, 2-bits from next byte = 2^4 = 16 total permutations + for (int iColumn = 0; iColumn < 16; iColumn++) + { + int offsetx = iColumn << 5; // every column is 32 bytes wide -- 7 apple pixels = 14 pixels + 2 pad + 14 pixels + 2 pad + + for (unsigned iByte = 0; iByte < 256; iByte++) + { + int aPixels[11]; // c2 c1 b7 b6 b5 b4 b3 b2 b1 b0 c8 c4 + +/* +aPixel[i] + A 9|8 7 6 5 4 3 2|1 0 + Z W|b b b b b b b|X Y +----+-------------+---- +prev| existing |next +bits| hi-res byte |bits + +Legend: + XYZW = iColumn in binary + b = Bytes in binary +*/ + // aPixel[] = 48bbbbbbbb12, where b = iByte in binary, # is bit-n of column + aPixels[ 0] = iColumn & 4; // previous byte, 2nd last pixel + aPixels[ 1] = iColumn & 8; // previous byte, last pixel + aPixels[ 9] = iColumn & 1; // next byte, first pixel + aPixels[10] = iColumn & 2; // next byte, second pixel + + // Convert raw pixel Byte value to binary and stuff into bit array of pixels on off + int nBitMask = 1; + int iPixel; + for (iPixel = 2; iPixel < 9; iPixel++) + { + aPixels[iPixel] = ((iByte & nBitMask) != 0); + nBitMask <<= 1; + } + + int hibit = (iByte >> 7) & 1; // ((iByte & 0x80) != 0); + int x = 0; + int y = iByte << 1; + +/* Test cases + 81 blue + 2000:D5 AA D5 AA + 82 orange + 2800:AA D5 AA D5 + FF white bleed "thru" + 3000:7F 80 7F 80 + 3800:FF 80 FF 80 + 2028:80 7F 80 7F + 2828:80 FF 80 FF + Edge Case for Half Luminance ! + 2000:C4 00 // Green HalfLumBlue + 2400:C4 80 // Green Green + Edge Case for Color Bleed ! + 2000:40 00 + 2400:40 80 +*/ + + // Fixup missing pixels that normally have been scan-line shifted -- Apple "half-pixel" -- but cross 14-pixel boundaries. + if( hibit ) + { + if ( aPixels[1] ) // preceeding pixel on? +#if 0 // Optimization: Doesn't seem to matter if we ignore the 2 pixels of the next byte + for (iPixel = 0; iPixel < 9; iPixel++) // NOTE: You MUST start with the preceding 2 pixels !!! + if (aPixels[iPixel]) // pixel on +#endif + { + if (aPixels[2] || aPixels[0]) // White if pixel from previous byte and first pixel of this byte is on + { + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y , HGR_WHITE ); + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y+1, HGR_WHITE ); + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y , HGR_WHITE ); + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y+1, HGR_WHITE ); + } else { // Optimization: odd = (iPixel & 1); if (!odd) case is same as if(odd) !!! // Reference: Gumball - Gumball Machine + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y , HGR_ORANGE ); // left half of orange pixels + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y+1, HGR_ORANGE ); + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y , HGR_BLUE ); // right half of blue pixels 4, 11, 18, ... + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y+1, HGR_BLUE ); + } + } +#if HALF_PIXEL_SOLID +// Test Patterns +// 81 blue +// 2000:D5 AA D5 AA -> 2001:AA D5 should not have black gap, should be blue +// 82 orange +// 2800:AA D5 AA D5 +// Game: Elite -- Loading Logo +// 2444:BB F7 -> 2000:BB F7 // Should not have orange in-between gap -- Elite "Firebird" Logo +// -> 2400:00 BB F7 // Should not have blue in-between gap ) +// 21D0:C0 00 -> HalfLumBlue +// 25D0:C0 D0 88 -> Blue black orange black orange +// 29D0:C0 90 88 -> Blue black orange +// Game: Ultima 4 -- Ultima 4 Logo - bottom half of screen has a "mini-game" / demo -- far right has tree and blue border +// 2176:2A AB green black_gap white blue_border // Should have black gap between green and white + else if ( aPixels[0] ) // prev prev pixel on + { +// Game: Gumball +// 218E:AA 97 => 2000: A9 87 orange_white // Should have no gap between orange and white +// 229A:AB A9 87 -> 2000: 00 A9 87 white orange black blue_white // Should have no gap between blue and white +// 2001:BB F7 white blue white (Gumball Intermission) +// Torture Half-Pixel HGR Tests: This is a real bitch to solve -- we really need to check: +// if (hibit_prev_byte && !aPixels[iPixel-3] && aPixels[iPixel-2] && !aPixels[iPixel] && hibit_this_byte) then set first half-pixel of this byte to either blue or orange +// 2000:A9 87 halfblack blue black black orange black orange black +// 2400:BB F7 halfblack white white black white white white halfblack +// or +// 2000:A0 83 orange should "bleed" thru +// 2400:B0 83 should have black gap + + if ( aPixels[2] ) +#if HALF_PIXEL_BLEED // No Half-Pixel Bleed + if ( aPixels[3] ) { + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y , DARK_BLUE ); // Gumball: 229A: AB A9 87 + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y+1, DARK_BLUE ); + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y , BROWN ); // half luminance red Elite: 2444: BB F7 + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y+1, BROWN ); // half luminance red Gumball: 218E: AA 97 + } else { + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y , HGR_BLUE ); // 2000:D5 AA D5 + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y+1, HGR_BLUE ); + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y , HGR_ORANGE ); // 2000: AA D5 + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y+1, HGR_ORANGE ); + } +#else + if ((videoType == VT_COLOR_MONITOR_RGB) || ( !aPixels[3] )) + { // "Text optimized" IF this pixel on, and adjacent right pixel off, then colorize first half-pixel of this byte + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y , HGR_BLUE ); // 2000:D5 AA D5 + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y+1, HGR_BLUE ); + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y , HGR_ORANGE ); // 2000: AA D5 + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y+1, HGR_ORANGE ); + } +#endif // HALF_PIXEL_BLEED + } +#endif // HALF_PIXEL_SOLID + } + x += hibit; + + while (x < 28) + { + int adj = (x >= 14) << 1; // Adjust start of 7 last pixels to be 16-byte aligned! + int odd = (x >= 14); + for (iPixel = 2; iPixel < 9; iPixel++) + { + int color = CM_Black; + if (aPixels[iPixel]) // pixel on + { + color = CM_White; + if (aPixels[iPixel-1] || aPixels[iPixel+1]) // adjacent pixels are always white + color = CM_White; + else + color = ((odd ^ (iPixel&1)) << 1) | hibit; // map raw color to our hi-res colors + } +#if HALF_PIXEL_SOLID + else if (aPixels[iPixel-1] && aPixels[iPixel+1]) // IF prev_pixel && next_pixel THEN + { + // Activate fringe reduction on white HGR text - drawback: loss of color mix patterns in HGR video mode. + if ( + (videoType == VT_COLOR_MONITOR_RGB) // Fill in colors in between white pixels +// || (videoType == VT_COLOR_TVEMU) // Fill in colors in between white pixels (Post Processing will mix/merge colors) + || !(aPixels[iPixel-2] && aPixels[iPixel+2]) ) // VT_COLOR_TEXT_OPTIMIZED -> Don't fill in colors in between white + { + // Test Pattern: Ultima 4 Logo - Castle + // 3AC8: 36 5B 6D 36 + color = ((odd ^ !(iPixel&1)) << 1) | hibit; // No white HGR text optimization + } + } +#endif + // Colors - Top/Bottom Left/Right + // cTL cTR + // cBL cBR + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+adj ,y ,HiresToPalIndex[color]); // cTL + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+adj+1,y ,HiresToPalIndex[color]); // cTR + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+adj ,y+1,HiresToPalIndex[color]); // cBL + SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+adj+1,y+1,HiresToPalIndex[color]); // cBR + x += 2; + } + } + } + } +} + +//=========================================================================== + +static void CopySource(int w, int h, int sx, int sy, bgra_t *pVideoAddress) +{ + UINT32* pDst = (UINT32*) pVideoAddress; + LPBYTE pSrc = g_aSourceStartofLine[ sy ] + sx; + int nBytes; + + while (h--) + { + nBytes = w; + while (nBytes) + { + --nBytes; + if (g_uHalfScanLines && !(h & 1)) + { + // 50% Half Scan Line clears every odd scanline (and SHIFT+PrintScreen saves only the even rows) + *(pDst+nBytes) = 0; + } + else + { + _ASSERT( *(pSrc+nBytes) < (sizeof(PalIndex2RGB)/sizeof(PalIndex2RGB[0])) ); + const RGBQUAD& rRGB = PalIndex2RGB[ *(pSrc+nBytes) ]; + const UINT32 rgb = (((UINT32)rRGB.rgbRed)<<16) | (((UINT32)rRGB.rgbGreen)<<8) | ((UINT32)rRGB.rgbBlue); + *(pDst+nBytes) = rgb; + } + } + + pDst -= GetFrameBufferWidth(); + pSrc -= SRCOFFS_TOTAL; + } +} + +//=========================================================================== + +void UpdateHiResCell (int x, int y, uint16_t addr, bgra_t *pVideoAddress) +{ + uint8_t *pMain = MemGetMainPtr(addr); + BYTE byteval1 = (x > 0) ? *(pMain-1) : 0; + BYTE byteval2 = *(pMain); + BYTE byteval3 = (x < 39) ? *(pMain+1) : 0; + +#define COLOFFS (((byteval1 & 0x60) << 2) | ((byteval3 & 0x03) << 5)) +#if 0 + if (g_eVideoType == VT_COLOR_TVEMU) + { + CopyMixedSource( + xpixel >> 1, (ypixel+(yoffset >> 9)) >> 1, + SRCOFFS_HIRES+COLOFFS+((x & 1) << 4), (((int)byteval2) << 1) + ); + } + else +#endif + { + CopySource(14,2, SRCOFFS_HIRES+COLOFFS+((x & 1) << 4), (((int)byteval2) << 1), pVideoAddress); + } +#undef COLOFFS +} + +//=========================================================================== + +void UpdateDHiResCell (int x, int y, uint16_t addr, bgra_t *pVideoAddress) +{ + const int xpixel = x*14; + + uint8_t *pAux = MemGetAuxPtr(addr); + uint8_t *pMain = MemGetMainPtr(addr); + + BYTE byteval1 = (x > 0) ? *(pMain-1) : 0; + BYTE byteval2 = *pAux; + BYTE byteval3 = *pMain; + BYTE byteval4 = (x < 39) ? *(pAux+1) : 0; + + DWORD dwordval = (byteval1 & 0x70) | ((byteval2 & 0x7F) << 7) | + ((byteval3 & 0x7F) << 14) | ((byteval4 & 0x07) << 21); +#define PIXEL 0 +#define COLOR ((xpixel + PIXEL) & 3) +#define VALUE (dwordval >> (4 + PIXEL - COLOR)) + CopySource(7,2, SRCOFFS_DHIRES+10*HIBYTE(VALUE)+COLOR, LOBYTE(VALUE)<<1, pVideoAddress); +#undef PIXEL +#define PIXEL 7 + CopySource(7,2, SRCOFFS_DHIRES+10*HIBYTE(VALUE)+COLOR, LOBYTE(VALUE)<<1, pVideoAddress+7); +#undef PIXEL +#undef COLOR +#undef VALUE +} + +//=========================================================================== + +// Tested with Deater's Cycle-Counting Megademo +void UpdateLoResCell (int x, int y, uint16_t addr, bgra_t *pVideoAddress) +{ + const BYTE val = *MemGetMainPtr(addr); + + if ((y & 4) == 0) + { + CopySource(14,2, SRCOFFS_LORES+((x & 1) << 1), ((val & 0xF) << 4), pVideoAddress); + } + else + { + CopySource(14,2, SRCOFFS_LORES+((x & 1) << 1), (val & 0xF0), pVideoAddress); + } +} + +//=========================================================================== + +#define ROL_NIB(x) ( (((x)<<1)&0xF) | (((x)>>3)&1) ) + +// Tested with FT's Ansi Story +void UpdateDLoResCell (int x, int y, uint16_t addr, bgra_t *pVideoAddress) +{ + BYTE auxval = *MemGetAuxPtr(addr); + const BYTE mainval = *MemGetMainPtr(addr); + + const BYTE auxval_h = auxval >> 4; + const BYTE auxval_l = auxval & 0xF; + auxval = (ROL_NIB(auxval_h)<<4) | ROL_NIB(auxval_l); + + if ((y & 4) == 0) + { + CopySource(7,2, SRCOFFS_LORES+((x & 1) << 1), ((auxval & 0xF) << 4), pVideoAddress); + CopySource(7,2, SRCOFFS_LORES+((x & 1) << 1), ((mainval & 0xF) << 4), pVideoAddress+7); + } + else + { + CopySource(7,2, SRCOFFS_LORES+((x & 1) << 1), (auxval & 0xF0), pVideoAddress); + CopySource(7,2, SRCOFFS_LORES+((x & 1) << 1), (mainval & 0xF0), pVideoAddress+7); + } +} + +//=========================================================================== + +static LPBYTE g_pSourcePixels = NULL; + +static void V_CreateDIBSections(void) +{ + g_pSourcePixels = new BYTE[SRCOFFS_TOTAL * MAX_SOURCE_Y]; + + // CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE SOURCE IMAGE + for (int y = 0; y < MAX_SOURCE_Y; y++) + g_aSourceStartofLine[ y ] = g_pSourcePixels + SRCOFFS_TOTAL*((MAX_SOURCE_Y-1) - y); + + // DRAW THE SOURCE IMAGE INTO THE SOURCE BIT BUFFER + ZeroMemory(g_pSourcePixels, SRCOFFS_TOTAL*MAX_SOURCE_Y); // 32 bytes/pixel * 16 colors = 512 bytes/row + + V_CreateLookup_Lores(); + +// if ( g_eVideoType == VT_COLOR_TVEMU ) +// V_CreateLookup_Hires(); +// else + V_CreateLookup_HiResHalfPixel_Authentic(VT_COLOR_MONITOR_RGB); + + V_CreateLookup_DoubleHires(); +} + +void VideoInitializeOriginal(baseColors_t pBaseNtscColors) +{ + // CREATE THE SOURCE IMAGE AND DRAW INTO THE SOURCE BIT BUFFER + V_CreateDIBSections(); + +#if 1 + memcpy(&PalIndex2RGB[BLACK], *pBaseNtscColors, sizeof(RGBQUAD)*kNumBaseColors); + PalIndex2RGB[HGR_BLUE] = PalIndex2RGB[BLUE]; + PalIndex2RGB[HGR_ORANGE] = PalIndex2RGB[ORANGE]; + PalIndex2RGB[HGR_GREEN] = PalIndex2RGB[GREEN]; + PalIndex2RGB[HGR_VIOLET] = PalIndex2RGB[MAGENTA]; +#endif +} diff --git a/source/Video_OriginalColorTVMode.h b/source/Video_OriginalColorTVMode.h new file mode 100644 index 000000000..ed35ebc4c --- /dev/null +++ b/source/Video_OriginalColorTVMode.h @@ -0,0 +1,8 @@ +void UpdateHiResCell(int x, int y, uint16_t addr, bgra_t *pVideoAddress); +void UpdateDHiResCell(int x, int y, uint16_t addr, bgra_t *pVideoAddress); +void UpdateLoResCell(int x, int y, uint16_t addr, bgra_t *pVideoAddress); +void UpdateDLoResCell(int x, int y, uint16_t addr, bgra_t *pVideoAddress); + +const UINT kNumBaseColors = 16; +typedef bgra_t (*baseColors_t)[kNumBaseColors]; +void VideoInitializeOriginal(baseColors_t pBaseNtscColors);