AppleWin/source/Video.cpp

3270 lines
102 KiB
C++

/*
AppleWin : An Apple //e emulator for Windows
Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2010, Tom Charlesworth, Michael Pohoreski, Nick Westgate
AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Description: Emulation of video modes
*
* Author: Various
*/
#include "StdAfx.h"
#include "AppleWin.h"
#include "CPU.h"
#include "Frame.h"
#include "Keyboard.h"
#include "Memory.h"
#include "Registry.h"
#include "Video.h"
#include "..\resource\resource.h"
#include "Configuration\PropertySheet.h"
#include "Debugger\Debugger_Color.h" // For NUM_DEBUG_COLORS
#define HALF_PIXEL_SOLID 1
#define HALF_PIXEL_BLEED 0
#define COLORS_TWEAKED 1
/* reference: technote tn-iigs-063 "Master Color Values"
Color Color Register LR HR DHR Master Color R,G,B
Name Value # # # Value
----------------------------------------------------
Black 0 0 0,4 0 $0000 (0,0,0) -> (00,00,00) Windows
(Magenta) Deep Red 1 1 1 $0D03 (D,0,3) -> (D0,00,30) Custom
Dark Blue 2 2 8 $0009 (0,0,9) -> (00,00,80) Windows
(Violet) Purple 3 3 2 9 $0D2D (D,2,D) -> (FF,00,FF) Windows
Dark Green 4 4 4 $0072 (0,7,2) -> (00,80,00) Windows
(Gray 1) Dark Gray 5 5 5 $0555 (5,5,5) -> (80,80,80) Windows
(Blue) Medium Blue 6 6 6 C $022F (2,2,F) -> (00,00,FF) Windows
(Cyan) Light Blue 7 7 D $06AF (6,A,F) -> (60,A0,FF) Custom
Brown 8 8 2 $0850 (8,5,0) -> (80,50,00) Custom
Orange 9 9 5 3 $0F60 (F,6,0) -> (FF,80,00) Custom (modified to match better with the other Hi-Res Colors)
(Gray 2) Light Gray A A A $0AAA (A,A,A) -> (C0,C0,C0) Windows
Pink B B B $0F98 (F,9,8) -> (FF,90,80) Custom
(Green) Light Green C C 1 6 $01D0 (1,D,0) -> (00,FF,00) Windows
Yellow D D 7 $0FF0 (F,F,0) -> (FF,FF,00) Windows
(Aqua) Aquamarine E E E $04F9 (4,F,9) -> (40,FF,90) Custom
White F F 3,7 F $0FFF (F,F,F) -> (FF,FF,FF) Windows
LR: Lo-Res HR: Hi-Res DHR: Double Hi-Res */
#define FLASH_80_COL 1 // Bug #7238
#define HALF_SHIFT_DITHER 0
enum Color_Palette_Index_e
{
// The first 10 are the DEFAULT Windows colors (as it reserves 20 colors)
BLACK = 0x00 // 0x00,0x00,0x00
, DARK_RED = 0x01 // 0x80,0x00,0x00
, DARK_GREEN = 0x02 // 0x00,0x80,0x00 (Half Green)
, DARK_YELLOW = 0x03 // 0x80,0x80,0x00
, DARK_BLUE = 0x04 // 0x00,0x00,0x80 (Half Blue)
, DARK_MAGENTA = 0x05 // 0x80,0x00,0x80
, DARK_CYAN = 0x06 // 0x00,0x80,0x80
, LIGHT_GRAY = 0x07 // 0xC0,0xC0,0xC0
, MONEY_GREEN = 0x08 // 0xC0,0xDC,0xC0 // not used
, SKY_BLUE = 0x09 // 0xA6,0xCA,0xF0 // not used
// Really need to have Quarter Green and Quarter Blue for Hi-Res
// OUR CUSTOM COLORS -- the extra colors HGR mode can display
// , DEEP_BLUE // Breaks TV Emulation Reference Test !?!? // Breaks the dam palette -- black monochrome TEXT output bug *sigh*
, DEEP_RED
, LIGHT_BLUE
, BROWN
, ORANGE
, PINK
, AQUA
// CUSTOM HGR COLORS (don't change order) - For tv emulation HGR Video Mode
, HGR_BLACK
, HGR_WHITE
, HGR_BLUE // HCOLOR=6 BLUE , $81
, HGR_RED // HCOLOR=5 ORANGE , $82
, HGR_GREEN // HCOLOR=1 GREEN , $01
, HGR_MAGENTA // HCOLOR=2 MAGENTA, $02
, HGR_GREY1
, HGR_GREY2
, HGR_YELLOW
, HGR_AQUA
, HGR_PURPLE
, HGR_PINK
// MONOCHROME
// NOTE: 50% is assumed to come after 100% luminance !!! See: V_CreateLookup_MonoHiRes()
// User customizable
, MONOCHROME_CUSTOM // 100% luminance
, MONOCHROME_CUSTOM_50 // 50% luminance
// Pre-set "Monochromes"
, MONOCHROME_AMBER
// , MONOCHROME_AMBER_50 // BUG - something trashing our palette entry !!!
, MONOCHROME_GREEN
// , MONOCHROME_GREEN_50 // BUG - something trashing our palette entry !!!
, DEBUG_COLORS_START
, DEBUG_COLORS_END = DEBUG_COLORS_START + NUM_DEBUG_COLORS
// DD Full Screen Palette ?!?!
// , LOGO_COLORS_START
// , LOGO_COLORS_END = LOGO_COLORS_START + 128
, NUM_COLOR_PALETTE
// The last 10 are the DEFAULT Windows colors (as it reserves 20 colors)
, CREAM = 0xF6
, MEDIUM_GRAY = 0xF7
, DARK_GRAY = 0xF8
, RED = 0xF9
, GREEN = 0xFA
, YELLOW = 0xFB
, BLUE = 0xFC
, MAGENTA = 0xFD
, CYAN = 0xFE
, WHITE = 0xFF
};
// __ Map HGR color index to Palette index
enum ColorMapping
{
CM_Magenta
, CM_Blue
, CM_Green
, CM_Orange
, CM_Black
, CM_White
, NUM_COLOR_MAPPING
};
const BYTE HiresToPalIndex[ NUM_COLOR_MAPPING ] =
{
HGR_MAGENTA
, HGR_BLUE
, HGR_GREEN
, HGR_RED
, 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
BLACK, DEEP_RED, DARK_BLUE, MAGENTA,
DARK_GREEN,DARK_GRAY,BLUE, LIGHT_BLUE,
BROWN, ORANGE, LIGHT_GRAY,PINK,
GREEN, YELLOW, AQUA, HGR_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
BLACK, DARK_BLUE, DARK_GREEN,BLUE,
BROWN, LIGHT_GRAY, GREEN, AQUA,
DEEP_RED,MAGENTA, DARK_GRAY, LIGHT_BLUE,
ORANGE, PINK, YELLOW, HGR_WHITE
};
const int SRCOFFS_40COL = 0; // 0
const int SRCOFFS_IIPLUS = (SRCOFFS_40COL + 256); // 256
const int SRCOFFS_80COL = (SRCOFFS_IIPLUS + 256); // 512
const int SRCOFFS_LORES = (SRCOFFS_80COL + 128); // 640
const int SRCOFFS_HIRES = (SRCOFFS_LORES + 16); // 656
const int SRCOFFS_DHIRES = (SRCOFFS_HIRES + 512); // 1168
const int SRCOFFS_TOTAL = (SRCOFFS_DHIRES + 2560); // 3278
enum VideoFlag_e
{
VF_80COL = 0x00000001,
VF_DHIRES = 0x00000002,
VF_HIRES = 0x00000004,
VF_80STORE= 0x00000008,
VF_MIXED = 0x00000010,
VF_PAGE2 = 0x00000020,
VF_TEXT = 0x00000040
};
#define SW_80COL (g_uVideoMode & VF_80COL)
#define SW_DHIRES (g_uVideoMode & VF_DHIRES)
#define SW_HIRES (g_uVideoMode & VF_HIRES)
#define SW_80STORE (g_uVideoMode & VF_80STORE)
#define SW_MIXED (g_uVideoMode & VF_MIXED)
#define SW_PAGE2 (g_uVideoMode & VF_PAGE2)
#define SW_TEXT (g_uVideoMode & VF_TEXT)
#define SETSOURCEPIXEL(x,y,c) g_aSourceStartofLine[(y)][(x)] = (c)
#define SETFRAMECOLOR(i,r,g,b) g_pFramebufferinfo->bmiColors[i].rgbRed = r; \
g_pFramebufferinfo->bmiColors[i].rgbGreen = g; \
g_pFramebufferinfo->bmiColors[i].rgbBlue = b; \
g_pFramebufferinfo->bmiColors[i].rgbReserved = PC_NOCOLLAPSE;
#define HGR_MATRIX_YOFFSET 2 // For tv emulation HGR Video Mode
// video scanner constants
int const kHBurstClock = 53; // clock when Color Burst starts
int const kHBurstClocks = 4; // clocks per Color Burst duration
int const kHClock0State = 0x18; // H[543210] = 011000
int const kHClocks = 65; // clocks per horizontal scan (including HBL)
int const kHPEClock = 40; // clock when HPE (horizontal preset enable) goes low
int const kHPresetClock = 41; // clock when H state presets
int const kHSyncClock = 49; // clock when HSync starts
int const kHSyncClocks = 4; // clocks per HSync duration
int const kNTSCScanLines = 262; // total scan lines including VBL (NTSC)
int const kNTSCVSyncLine = 224; // line when VSync starts (NTSC)
int const kPALScanLines = 312; // total scan lines including VBL (PAL)
int const kPALVSyncLine = 264; // line when VSync starts (PAL)
int const kVLine0State = 0x100; // V[543210CBA] = 100000000
int const kVPresetLine = 256; // line when V state presets
int const kVSyncLines = 4; // lines per VSync duration
static BYTE celldirty[40][32]; // [TC: 27/06/2014] NB. No longer used!
// NUM_COLOR_PALETTE
static COLORREF customcolors[256]; // MONOCHROME is last custom color
static HBITMAP g_hDeviceBitmap;
static HDC g_hDeviceDC;
LPBYTE g_pFramebufferbits = NULL; // last drawn frame
static LPBITMAPINFO g_pFramebufferinfo = NULL;
static LPBYTE g_aFrameBufferOffset[FRAMEBUFFER_H]; // array of pointers to start of each scanline
static LPBYTE g_pHiresBank1;
static LPBYTE g_pHiresBank0;
HBITMAP g_hLogoBitmap;
static HPALETTE g_hPalette;
static HBITMAP g_hSourceBitmap;
static LPBYTE g_pSourcePixels;
static LPBITMAPINFO g_pSourceHeader;
const int MAX_SOURCE_Y = 512;
static LPBYTE g_aSourceStartofLine[ MAX_SOURCE_Y ];
static LPBYTE g_pTextBank1; // Aux
static LPBYTE g_pTextBank0; // Main
// For tv emulation HGR Video Mode
// 2 extra scan lines on bottom?
static BYTE hgrpixelmatrix[FRAMEBUFFER_W/2][FRAMEBUFFER_H/2 + 2 * HGR_MATRIX_YOFFSET];
static BYTE colormixbuffer[6];
static WORD colormixmap[6][6][6];
//
static int g_nAltCharSetOffset = 0; // alternate character set
static /*bool*/ UINT g_VideoForceFullRedraw = 1;
static bool g_bVideoUpdatedThisFrame = false;
static LPBYTE framebufferaddr = (LPBYTE)0;
static LONG g_nFrameBufferPitch = 0;
COLORREF monochrome = RGB(0xC0,0xC0,0xC0);
static BOOL rebuiltsource = 0;
static LPBYTE vidlastmem = NULL;
static UINT g_uVideoMode = VF_TEXT;
DWORD g_eVideoType = VT_COLOR_TVEMU;
DWORD g_uHalfScanLines = 1; // drop 50% scan lines for a more authentic look
static bool g_bTextFlashState = false;
static bool g_bTextFlashFlag = false;
static bool bVideoScannerNTSC = true; // NTSC video scanning (or PAL)
//-------------------------------------
// Video consts:
const UINT nVBlStop_NTSC = 21;
const UINT nVBlStop_PAL = 29;
// NOTE: KEEP IN SYNC: VideoType_e g_aVideoChoices g_apVideoModeDesc
TCHAR g_aVideoChoices[] =
TEXT("Monochrome (Custom Luminance)\0")
TEXT("Color (Standard)\0")
TEXT("Color (Text Optimized)\0")
TEXT("Color (TV emulation)\0")
TEXT("Monochrome (Amber)\0")
TEXT("Monochrome (Green)\0")
TEXT("Monochrome (White)\0")
;
// AppleWin 1.19.4 VT_COLOR_AUTHENTIC -> VT_COLOR_HALFPIXEL -> VT_COLOR_STANDARD "Color Half-Pixel Authentic
// NOTE: KEEP IN SYNC: VideoType_e g_aVideoChoices g_apVideoModeDesc
// The window title will be set to this.
char *g_apVideoModeDesc[ NUM_VIDEO_MODES ] =
{
"Monochrome (Custom)"
, "Standard"
, "Text Optimized"
, "TV"
, "Amber"
, "Green"
, "White"
};
// Prototypes (Private) _____________________________________________
void V_CreateLookup_DoubleHires ();
void V_CreateLookup_Hires (); // Old "Full-Pixel" support only: STANDARD, TEXT_OPTIMIZED, TVEMU
void V_CreateLookup_HiResHalfPixel_Authentic (); // New "Half_Pixel" support: STANDARD, TEXT_OPTIMIZED
void V_CreateLookup_HiresHalfShiftFull ();
void V_CreateLookup_Lores ();
void V_CreateLookup_Text (HDC dc);
// Monochrome Full-Pixel Support
void V_CreateLookup_MonoDoubleHiRes ();
void V_CreateLookup_MonoHiRes ();
void V_CreateLookup_MonoLoRes ();
void V_CreateLookup_MonoText (HDC dc);
// Monochrome Half-Pixel Support
void V_CreateLookup_MonoHiResHalfPixel_Real ();
bool g_bDisplayPrintScreenFileName = false;
bool g_bShowPrintScreenWarningDialog = true;
void Util_MakeScreenShotFileName( char *pFinalFileName_ );
bool Util_TestScreenShotFileName( const char *pFileName );
// true = 280x192
// false = 560x384
void Video_SaveScreenShot( const char *pScreenShotFileName );
void Video_MakeScreenShot( FILE *pFile );
int GetMonochromeIndex();
void V_CreateIdentityPalette ();
void V_CreateDIBSections ();
HBRUSH V_CreateCustomBrush (COLORREF nColor);
/** Our BitBlit() / VRAM_Copy()
@param dx Dst X
@param dy Dst Y
@param w Width (same for src & dst)
@param h Height (same for src & dst)
@param sx Src X
@param sy Src Y
// =========================================================================== */
static inline void CopySource8(int dx, int dy, int w, int h, int sx, int sy)
{
LPBYTE pDst = g_aFrameBufferOffset[ dy ] + dx;
LPBYTE pSrc = g_aSourceStartofLine[ sy ] + sx;
int nBytes;
while (h--)
{
nBytes = w;
// If not multiple of 3 bytes, copy first 3 bytes, so the next copy is 4-byte aligned.
while (nBytes & 3)
{
--nBytes;
if (g_uHalfScanLines && !(h & 1))
*(pDst+nBytes) = 0; // 50% Half Scan Line clears every odd scanline (and SHIFT+PrintScreen saves only the even rows)
else
*(pDst+nBytes) = *(pSrc+nBytes);
}
// Copy 4 bytes at a time
while (nBytes)
{
nBytes -= 4;
if (g_uHalfScanLines && !(h & 1))
*(LPDWORD)(pDst+nBytes) = 0; // 50% Half Scan Line clears every odd scanline (and SHIFT+PrintScreen saves only the even rows)
else
*(LPDWORD)(pDst+nBytes) = *(LPDWORD)(pSrc+nBytes);
}
pDst -= g_nFrameBufferPitch;
pSrc -= SRCOFFS_TOTAL;
}
}
static void CopySource(int dx, int dy, int w, int h, int sx, int sy)
{
if (!g_bIsFullScreen || !GetFullScreen32Bit())
{
CopySource8(dx,dy,w,h,sx,sy);
return;
}
UINT32* pDst = (UINT32*) (g_aFrameBufferOffset[ dy ] + dx*sizeof(UINT32));
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
{
const RGBQUAD& rRGB = g_pFramebufferinfo->bmiColors[ *(pSrc+nBytes) ];
const UINT32 rgb = (((UINT32)rRGB.rgbRed)<<16) | (((UINT32)rRGB.rgbGreen)<<8) | ((UINT32)rRGB.rgbBlue);
*(pDst+nBytes) = rgb;
}
}
pDst -= g_nFrameBufferPitch / sizeof(UINT32);
pSrc -= SRCOFFS_TOTAL;
}
}
//===========================================================================
void CreateFrameOffsetTable (LPBYTE addr, LONG pitch)
{
if ((framebufferaddr == addr) && (g_nFrameBufferPitch == pitch))
return;
framebufferaddr = addr;
g_nFrameBufferPitch = pitch;
// CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE FRAME BUFFER
for (int y = 0; y < FRAMEBUFFER_H; y++)
g_aFrameBufferOffset[y] = framebufferaddr + g_nFrameBufferPitch*((FRAMEBUFFER_H-1)-y);
}
//===========================================================================
void VideoInitialize ()
{
// CREATE A BUFFER FOR AN IMAGE OF THE LAST DRAWN MEMORY
vidlastmem = (LPBYTE)VirtualAlloc(NULL,0x10000,MEM_COMMIT,PAGE_READWRITE);
ZeroMemory(vidlastmem,0x10000);
// LOAD THE LOGO
g_hLogoBitmap = (HBITMAP)LoadImage(g_hInstance, MAKEINTRESOURCE(IDB_APPLEWIN), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
// CREATE A BITMAPINFO STRUCTURE FOR THE FRAME BUFFER
g_pFramebufferinfo = (LPBITMAPINFO)VirtualAlloc(
NULL,
sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD),
MEM_COMMIT,
PAGE_READWRITE);
ZeroMemory(g_pFramebufferinfo,sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD));
g_pFramebufferinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
g_pFramebufferinfo->bmiHeader.biWidth = FRAMEBUFFER_W;
g_pFramebufferinfo->bmiHeader.biHeight = FRAMEBUFFER_H;
g_pFramebufferinfo->bmiHeader.biPlanes = 1;
g_pFramebufferinfo->bmiHeader.biBitCount = 8;
g_pFramebufferinfo->bmiHeader.biClrUsed = 256;
// CREATE A BITMAPINFO STRUCTURE FOR THE SOURCE IMAGE
g_pSourceHeader = (LPBITMAPINFO)VirtualAlloc(
NULL,
sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD),
MEM_COMMIT,
PAGE_READWRITE);
ZeroMemory(g_pSourceHeader,sizeof(BITMAPINFOHEADER));
g_pSourceHeader->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
g_pSourceHeader->bmiHeader.biWidth = SRCOFFS_TOTAL;
g_pSourceHeader->bmiHeader.biHeight = 512;
g_pSourceHeader->bmiHeader.biPlanes = 1;
g_pSourceHeader->bmiHeader.biBitCount = 8;
g_pSourceHeader->bmiHeader.biClrUsed = 256;
// VideoReinitialize() ... except we set the frame buffer palette....
V_CreateIdentityPalette();
//RGB() -> none
//PALETTERGB() -> PC_EXPLICIT
//??? RGB() -> PC_NOCOLLAPSE
for( int iColor = 0; iColor < NUM_COLOR_PALETTE; iColor++ )
customcolors[ iColor ] = ((DWORD)PC_EXPLICIT << 24) | RGB(
g_pFramebufferinfo->bmiColors[iColor].rgbRed,
g_pFramebufferinfo->bmiColors[iColor].rgbGreen,
g_pFramebufferinfo->bmiColors[iColor].rgbBlue
);
// CREATE THE FRAME BUFFER DIB SECTION AND DEVICE CONTEXT,
// CREATE THE SOURCE IMAGE DIB SECTION AND DRAW INTO THE SOURCE BIT BUFFER
V_CreateDIBSections();
// RESET THE VIDEO MODE SWITCHES AND THE CHARACTER SET OFFSET
VideoResetState();
}
//===========================================================================
int GetMonochromeIndex()
{
int iMonochrome;
switch (g_eVideoType)
{
case VT_MONO_AMBER : iMonochrome = MONOCHROME_AMBER ; break;
case VT_MONO_GREEN : iMonochrome = MONOCHROME_GREEN ; break;
case VT_MONO_WHITE : iMonochrome = HGR_WHITE ; break;
default : iMonochrome = MONOCHROME_CUSTOM; break; // caller will use MONOCHROME_CUSTOM MONOCHROME_CUSTOM_50 !
}
return iMonochrome;
}
//===========================================================================
void V_CreateIdentityPalette ()
{
if (g_hPalette)
{
DeleteObject(g_hPalette);
}
g_hPalette = (HPALETTE)0;
SETFRAMECOLOR(BLACK, 0x00,0x00,0x00); // 0
SETFRAMECOLOR(DARK_RED, 0x80,0x00,0x00); // 1 // used by TV
SETFRAMECOLOR(DARK_GREEN, 0x00,0x80,0x00); // 2
SETFRAMECOLOR(DARK_YELLOW, 0x80,0x80,0x00); // 3
SETFRAMECOLOR(DARK_BLUE, 0x00,0x00,0x80); // 4
SETFRAMECOLOR(DARK_MAGENTA,0x80,0x00,0x80); // 5
SETFRAMECOLOR(DARK_CYAN, 0x00,0x80,0x80); // 6
SETFRAMECOLOR(LIGHT_GRAY, 0xC0,0xC0,0xC0); // 7 // GR: COLOR=10
SETFRAMECOLOR(MONEY_GREEN, 0xC0,0xDC,0xC0); // 8 // not used
SETFRAMECOLOR(SKY_BLUE, 0xA6,0xCA,0xF0); // 9 // not used
// SET FRAME BUFFER TABLE ENTRIES TO CUSTOM COLORS
#if COLORS_TWEAKED
SETFRAMECOLOR(DARK_RED, 0x9D,0x09,0x66); // 1 // Linards Tweaked
SETFRAMECOLOR(DARK_GREEN, 0x00,0x76,0x1A); // 2 // Linards Tweaked
SETFRAMECOLOR(DARK_BLUE, 0x2A,0x2A,0xE5); // 4 // Linards Tweaked
SETFRAMECOLOR(DEEP_RED, 0x9D,0x09,0x66); // 0xD0,0x00,0x30 -> Linards Tweaked 0x9D,0x09,0x66
SETFRAMECOLOR(LIGHT_BLUE,0xAA,0xAA,0xFF); // 0x60,0xA0,0xFF -> Linards Tweaked 0xAA,0xAA,0xFF
SETFRAMECOLOR(BROWN, 0x55,0x55,0x00); // 0x80,0x50,0x00 -> Linards Tweaked 0x55,0x55,0x00
SETFRAMECOLOR(ORANGE, 0xF2,0x5E,0x00); // 0xFF,0x80,0x00 -> Linards Tweaked 0xF2,0x5E,0x00
SETFRAMECOLOR(PINK, 0xFF,0x89,0xE5); // 0xFF,0x90,0x80 -> Linards Tweaked 0xFF,0x89,0xE5
SETFRAMECOLOR(AQUA, 0x62,0xF6,0x99); // 0x40,0xFF,0x90 -> Linards Tweaked 0x62,0xF6,0x99
SETFRAMECOLOR(HGR_BLACK, 0x00,0x00,0x00); // For TV emulation HGR Video Mode
SETFRAMECOLOR(HGR_WHITE, 0xFF,0xFF,0xFE); // BUG: PALETTE COLLAPSE! NOT white!? Win32 collapses the palette if you have duplicate colors!
SETFRAMECOLOR(HGR_BLUE, 0x0D,0xA1,0xFF); // 0x00,0x80,0xFF -> Linards Tweaked 0x0D,0xA1,0xFF
SETFRAMECOLOR(HGR_RED, 0xF2,0x5E,0x00); // 0xF0,0x50,0x00 -> Linards Tweaked 0xF2,0x5E,0x00
SETFRAMECOLOR(HGR_GREEN, 0x38,0xCB,0x00); // 0x20,0xC0,0x00 -> Linards Tweaked 0x38,0xCB,0x00
SETFRAMECOLOR(HGR_MAGENTA,0xC7,0x34,0xFF); // 0xA0,0x00,0xFF -> Linards Tweaked 0xC7,0x34,0xFF
SETFRAMECOLOR(HGR_GREY1, 0x80,0x80,0x80);
SETFRAMECOLOR(HGR_GREY2, 0x80,0x80,0x80);
SETFRAMECOLOR(HGR_YELLOW, 0x9E,0x9E,0x00); // 0xD0,0xB0,0x10 -> 0x9E,0x9E,0x00
SETFRAMECOLOR(HGR_AQUA, 0x00,0xCD,0x4A); // 0x20,0xB0,0xB0 -> 0x00,0xCD,0x4A
SETFRAMECOLOR(HGR_PURPLE, 0x61,0x61,0xFF); // 0x60,0x50,0xE0 -> 0x61,0x61,0xFF
SETFRAMECOLOR(HGR_PINK, 0xFF,0x32,0xB5); // 0xD0,0x40,0xA0 -> 0xFF,0x32,0xB5
#else
SETFRAMECOLOR(DEEP_RED, 0xD0,0x00,0x30); // 0xD0,0x00,0x30
SETFRAMECOLOR(LIGHT_BLUE,0x60,0xA0,0xFF); // 0x60,0xA0,0xFF
SETFRAMECOLOR(BROWN, 0x80,0x50,0x00); // 0x80,0x50,0x00
SETFRAMECOLOR(ORANGE, 0xFF,0x80,0x00); // 0xFF,0x80,0x00
SETFRAMECOLOR(PINK, 0xFF,0x90,0x80); // 0xFF,0x90,0x80
SETFRAMECOLOR(AQUA, 0x40,0xFF,0x90); // 0x40,0xFF,0x90
SETFRAMECOLOR(HGR_BLACK, 0x00,0x00,0x00); // For TV emulation HGR Video Mode
SETFRAMECOLOR(HGR_WHITE, 0xFF,0xFF,0xFE); // BUG: PALETTE COLLAPSE! NOT white!? Win32 collapses the palette if you have duplicate colors!
SETFRAMECOLOR(HGR_BLUE, 0x00,0x80,0xFF); // 0x00,0x80,0xFF
SETFRAMECOLOR(HGR_RED, 0xF0,0x50,0x00); // 0xF0,0x50,0x00
SETFRAMECOLOR(HGR_GREEN, 0x20,0xC0,0x00); // 0x20,0xC0,0x00
SETFRAMECOLOR(HGR_MAGENTA,0xA0,0x00,0xFF); // 0xA0,0x00,0xFF
SETFRAMECOLOR(HGR_GREY1, 0x80,0x80,0x80);
SETFRAMECOLOR(HGR_GREY2, 0x80,0x80,0x80);
SETFRAMECOLOR(HGR_YELLOW, 0xD0,0xB0,0x10); // 0xD0,0xB0,0x10
SETFRAMECOLOR(HGR_AQUA, 0x20,0xB0,0xB0); // 0x20,0xB0,0xB0
SETFRAMECOLOR(HGR_PURPLE, 0x60,0x50,0xE0); // 0x60,0x50,0xE0
SETFRAMECOLOR(HGR_PINK, 0xD0,0x40,0xA0); // 0xD0,0x40,0xA0
#endif
SETFRAMECOLOR( MONOCHROME_CUSTOM
, GetRValue(monochrome)
, GetGValue(monochrome)
, GetBValue(monochrome)
);
SETFRAMECOLOR( MONOCHROME_CUSTOM_50
, ((GetRValue(monochrome)/2) & 0xFF)
, ((GetGValue(monochrome)/2) & 0xFF)
, ((GetBValue(monochrome)/2) & 0xFF)
);
// SEE: V_CreateLookup_MonoText
SETFRAMECOLOR( MONOCHROME_AMBER , 0xFF,0x80,0x01); // Used for Monochrome Hi-Res graphics not text!
SETFRAMECOLOR( MONOCHROME_GREEN , 0x00,0xC0,0x01); // Used for Monochrome Hi-Res graphics not text!
// BUG PALETTE COLLAPSE: WTF?? Soon as we set 0xFF,0xFF,0xFF we lose text colors?!?!
// Windows is collapsing the palette!!!
//SETFRAMECOLOR( MONOCHROME_WHITE , 0xFE,0xFE,0xFE); // Used for Monochrome Hi-Res graphics not text!
#if COLORS_TWEAKED
SETFRAMECOLOR(CREAM, 0xFF,0xFB,0xF0); // F6
SETFRAMECOLOR(MEDIUM_GRAY, 0xA0,0xA0,0xA4); // F7
SETFRAMECOLOR(DARK_GRAY, 0x80,0x80,0x80); // F8
SETFRAMECOLOR(RED, 0xFF,0x00,0x00); // F9
SETFRAMECOLOR(GREEN, 0x38,0xCB,0x00); // FA Linards Tweaked
SETFRAMECOLOR(YELLOW, 0xD5,0xD5,0x1A); // FB Linards Tweaked
SETFRAMECOLOR(BLUE, 0x0D,0xA1,0xFF); // FC Linards Tweaked
SETFRAMECOLOR(MAGENTA, 0xC7,0x34,0xFF); // FD Linards Tweaked
SETFRAMECOLOR(CYAN, 0x00,0xFF,0xFF); // FE
SETFRAMECOLOR(WHITE, 0xFF,0xFF,0xFF); // FF
#else
SETFRAMECOLOR(CREAM, 0xFF,0xFB,0xF0); // F6
SETFRAMECOLOR(MEDIUM_GRAY, 0xA0,0xA0,0xA4); // F7
SETFRAMECOLOR(DARK_GRAY, 0x80,0x80,0x80); // F8
SETFRAMECOLOR(RED, 0xFF,0x00,0x00); // F9
SETFRAMECOLOR(GREEN, 0x00,0xFF,0x00); // FA
SETFRAMECOLOR(YELLOW, 0xFF,0xFF,0x00); // FB
SETFRAMECOLOR(BLUE, 0x00,0x00,0xFF); // FC
SETFRAMECOLOR(MAGENTA, 0xFF,0x00,0xFF); // FD
SETFRAMECOLOR(CYAN, 0x00,0xFF,0xFF); // FE
SETFRAMECOLOR(WHITE, 0xFF,0xFF,0xFF); // FF
#endif
// IF WE ARE IN A PALETTIZED VIDEO MODE, CREATE AN IDENTITY PALETTE
HWND window = GetDesktopWindow();
HDC dc = GetDC(window);
// int GetDeviceCaps( HDC, nIndex );
int colors = GetDeviceCaps(dc,SIZEPALETTE); // 16/24/32bpp = 0
int system = GetDeviceCaps(dc,NUMCOLORS); // 16/24/32bpp = -1
#if 0
// DD Full Screen Palette
// Full Screen Debug Colors
BYTE *pTmp;
pTmp = ((BYTE*)g_pFramebufferinfo) + sizeof(BITMAPINFOHEADER);
pTmp += (DEBUG_COLORS_START * 4);
Debug_UpdatePalette( pTmp );
// GET THE PALETTE ENTRIES OF THE LOGO
RGBQUAD aLogoPalette[256];
ZeroMemory(aLogoPalette,sizeof(aLogoPalette));
if (g_hLogoBitmap)
{
BYTE *pSrc = NULL;
BITMAP bmp;
PBITMAPINFO pbmi;
// WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
if (GetObject(g_hLogoBitmap, sizeof(BITMAP), (LPSTR)&bmp))
{
pSrc = (BYTE*) pbmi->bmiColors;
// Logo uses 128 colors
pTmp = ((BYTE*)g_pFramebufferinfo) + sizeof(BITMAPINFOHEADER);
pTmp += (LOGO_COLORS_START * 4);
int iPal = 0;
for( iPal = 0; iPal < 128; iPal++ )
{
*pTmp++ = *pSrc++;
*pTmp++ = *pSrc++;
*pTmp++ = *pSrc++;
*pTmp++ = *pSrc++;
}
}
}
#endif
int bRasterPalette = (GetDeviceCaps(dc,RASTERCAPS) & RC_PALETTE);
if (bRasterPalette && (colors <= 256))
{
// GET THE PALETTE ENTRIES OF THE LOGO
RGBQUAD aLogoPalette[256];
ZeroMemory(aLogoPalette,sizeof(aLogoPalette));
if (g_hLogoBitmap) {
HDC memdc = CreateCompatibleDC(dc);
SelectObject(memdc,g_hLogoBitmap);
GetDIBColorTable(memdc,0,colors,aLogoPalette);
DeleteDC(memdc);
}
// CREATE A PALETTE ENTRY ARRAY
LOGPALETTE *paldata = (LOGPALETTE *)VirtualAlloc(
NULL,
sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY),
MEM_COMMIT,
PAGE_READWRITE
);
paldata->palVersion = 0x300;
paldata->palNumEntries = colors;
GetSystemPaletteEntries(dc,0,colors,paldata->palPalEntry);
// FILL IN THE PALETTE ENTRIES
int paletteindex = 0;
int logoindex = 0;
int halftoneindex = 0;
// COPY THE SYSTEM PALETTE ENTRIES AT THE BEGINNING OF THE PALETTE
for (; paletteindex < system/2; paletteindex++)
paldata->palPalEntry[paletteindex].peFlags = 0;
// FILL IN THE MIDDLE PORTION OF THE PALETTE WITH OUR OWN COLORS
for (int ourindex = DEEP_RED; ourindex <= NUM_COLOR_PALETTE; ourindex++) {
paldata->palPalEntry[paletteindex].peRed = g_pFramebufferinfo->bmiColors[ourindex].rgbRed;
paldata->palPalEntry[paletteindex].peGreen = g_pFramebufferinfo->bmiColors[ourindex].rgbGreen;
paldata->palPalEntry[paletteindex].peBlue = g_pFramebufferinfo->bmiColors[ourindex].rgbBlue;
paldata->palPalEntry[paletteindex].peFlags = PC_NOCOLLAPSE;
paletteindex++;
}
for (; paletteindex < colors-system/2; paletteindex++) {
// IF THIS PALETTE ENTRY IS NEEDED FOR THE LOGO, COPY IN THE LOGO COLOR
if (aLogoPalette[logoindex].rgbRed &&
aLogoPalette[logoindex].rgbGreen &&
aLogoPalette[logoindex].rgbBlue)
{
paldata->palPalEntry[paletteindex].peRed = aLogoPalette[logoindex].rgbRed;
paldata->palPalEntry[paletteindex].peGreen = aLogoPalette[logoindex].rgbGreen;
paldata->palPalEntry[paletteindex].peBlue = aLogoPalette[logoindex].rgbBlue;
}
// OTHERWISE, ADD A HALFTONING COLOR, SO THAT OTHER APPLICATIONS
// RUNNING IN THE BACKGROUND WILL HAVE SOME REASONABLE COLORS TO USE
else
{
static BYTE halftonetable[6] = {32,64,96,160,192,224};
paldata->palPalEntry[paletteindex].peRed = halftonetable[halftoneindex % 6];
paldata->palPalEntry[paletteindex].peGreen = halftonetable[halftoneindex/6 % 6];
paldata->palPalEntry[paletteindex].peBlue = halftonetable[halftoneindex/36 % 6];
++halftoneindex;
}
++logoindex;
paldata->palPalEntry[paletteindex].peFlags = PC_NOCOLLAPSE;
}
// COPY THE SYSTEM PALETTE ENTRIES AT THE END OF THE PALETTE
for (; paletteindex < colors; paletteindex++)
paldata->palPalEntry[paletteindex].peFlags = 0;
// FILL THE FRAME BUFFER TABLE WITH COLORS FROM OUR PALETTE
for (int iPal = 0; iPal < colors; iPal++) {
g_pFramebufferinfo->bmiColors[ iPal ].rgbRed = paldata->palPalEntry[ iPal ].peRed;
g_pFramebufferinfo->bmiColors[ iPal ].rgbGreen = paldata->palPalEntry[ iPal ].peGreen;
g_pFramebufferinfo->bmiColors[ iPal ].rgbBlue = paldata->palPalEntry[ iPal ].peBlue;
}
// CREATE THE PALETTE
g_hPalette = CreatePalette(paldata);
VirtualFree(paldata,0,MEM_RELEASE);
}
ReleaseDC(window,dc);
}
//===========================================================================
void V_CreateDIBSections ()
{
CopyMemory(g_pSourceHeader->bmiColors,g_pFramebufferinfo->bmiColors,256*sizeof(RGBQUAD));
// CREATE THE DEVICE CONTEXT
HWND window = GetDesktopWindow();
HDC dc = GetDC(window);
if (g_hDeviceDC)
{
DeleteDC(g_hDeviceDC);
}
g_hDeviceDC = CreateCompatibleDC(dc);
// CREATE THE FRAME BUFFER DIB SECTION
if (g_hDeviceBitmap)
DeleteObject(g_hDeviceBitmap);
g_hDeviceBitmap = CreateDIBSection(
dc,
g_pFramebufferinfo,
DIB_RGB_COLORS,
(LPVOID *)&g_pFramebufferbits,0,0
);
SelectObject(g_hDeviceDC,g_hDeviceBitmap);
// CREATE THE SOURCE IMAGE DIB SECTION
HDC sourcedc = CreateCompatibleDC(dc);
ReleaseDC(window,dc);
if (g_hSourceBitmap)
DeleteObject(g_hSourceBitmap);
g_hSourceBitmap = CreateDIBSection(
sourcedc,
g_pSourceHeader,
DIB_RGB_COLORS,
(LPVOID *)&g_pSourcePixels,0,0
);
SelectObject(sourcedc,g_hSourceBitmap);
// 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*512); // 32 bytes/pixel * 16 colors = 512 bytes/row
// First monochrome mode is seperate from others
if ((g_eVideoType >= VT_COLOR_STANDARD) && (g_eVideoType < VT_MONO_AMBER))
{
V_CreateLookup_Text(sourcedc);
V_CreateLookup_Lores();
if ( g_eVideoType == VT_COLOR_TVEMU )
V_CreateLookup_Hires();
else
V_CreateLookup_HiResHalfPixel_Authentic();
V_CreateLookup_DoubleHires();
}
else
{
V_CreateLookup_MonoText(sourcedc);
V_CreateLookup_MonoLoRes();
switch (g_eVideoType)
{
case VT_MONO_AMBER : /* intentional fall-thru */
case VT_MONO_GREEN : /* intentional fall-thru */
case VT_MONO_WHITE : /* intentional fall-thru */
case VT_MONO_HALFPIXEL_REAL : V_CreateLookup_MonoHiResHalfPixel_Real() ; break;
default: V_CreateLookup_MonoHiRes(); break;
}
V_CreateLookup_MonoDoubleHiRes();
}
DeleteDC(sourcedc);
}
//===========================================================================
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 (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;
}
}
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
}
//===========================================================================
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_STANDARD = 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;
}
}
}
}
}
//===========================================================================
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]);
}
//===========================================================================
void V_CreateLookup_MonoDoubleHiRes ()
{
int iMonochrome = GetMonochromeIndex();
for (int column = 0; column < 256; column++)
{
int coloffs = 10 * column;
for (unsigned byteval = 0; byteval < 256; byteval++)
{
unsigned pattern = MAKEWORD(byteval,column);
int y = byteval << 1;
for (int x = 0; x < 10; x++)
{
BYTE colorval = pattern & (1 << (x+3)) ? iMonochrome : BLACK;
#if 0
if (g_eVideoType == VT_MONO_AUTHENTIC)
{
SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y ,colorval);
SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y+1,BLACK);
}
else
#endif
{
SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y ,colorval);
SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y+1,colorval);
}
}
}
}
}
//===========================================================================
void V_CreateLookup_MonoHiRes ()
{
const int iMonochrome = GetMonochromeIndex();
for (int column = 0; column < 512; column += 16)
{
for (int y = 0; y < 512; y += 2) // optimization: Byte=0..FF, Row=Byte*2
{
unsigned val = (y >> 1); // iByte = (y / 2)
for (int x = 0; x < 16; x += 2) // 8 pixels
{
BYTE colorval = (val & 1) ? iMonochrome : BLACK;
val >>= 1;
SETSOURCEPIXEL(SRCOFFS_HIRES+column+x ,y ,colorval);
SETSOURCEPIXEL(SRCOFFS_HIRES+column+x+1,y ,colorval);
{
SETSOURCEPIXEL(SRCOFFS_HIRES+column+x ,y+1,colorval);
SETSOURCEPIXEL(SRCOFFS_HIRES+column+x+1,y+1,colorval);
}
}
}
}
}
//===========================================================================
void V_CreateLookup_HiResHalfPixel_Authentic () // 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_RED ); // left half of orange pixels
SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+0 ,y+1, HGR_RED );
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_RED ); // 2000: AA D5
SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y+1, HGR_RED );
}
#else
if ((g_eVideoType == VT_COLOR_STANDARD) || ( !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_RED ); // 2000: AA D5
SETSOURCEPIXEL(SRCOFFS_HIRES+offsetx+x+16,y+1, HGR_RED );
}
#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 (
(g_eVideoType == VT_COLOR_STANDARD) // Fill in colors in between white pixels
|| (g_eVideoType == 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;
}
}
}
}
}
//===========================================================================
void V_CreateLookup_MonoHiResHalfPixel_Real ()
{
int iMono = GetMonochromeIndex();
for (int iColumn = 0; iColumn < 16; iColumn++)
{
int offset = iColumn << 5; // every column is 32 bytes wide
for (unsigned iByte = 0; iByte < 256; iByte++)
{
int aPixels[11]; // c2 c1 b7 b6 b5 b4 b3 b2 b1 b0 c8 c4
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 >> 7) & 1; // ((iByte & 0x80) != 0);
int x = 0;
int y = iByte << 1;
// Fixup missing pixels that normally have been scan-line shifted -- Apple "half-pixel" -- but cross 14-pixel boundaries.
if( hibit )
{
/* Test Cases
// Games
Archon Logo
Gumball (at Machine)
// Applesoft
HGR:HCOLOR=5:HPLOT 0,0 TO 279,0
// Blue
// Orange
CALL-151
C050 C052 C057
2000:D0 80 00
2800:80 D0 00
*/
if ( aPixels[1] ) // preceeding pixel on?
{
SETSOURCEPIXEL(SRCOFFS_HIRES+offset+x ,y ,iMono); // first 7 HGR_BLUE
SETSOURCEPIXEL(SRCOFFS_HIRES+offset+x ,y+1,iMono); // first 7
SETSOURCEPIXEL(SRCOFFS_HIRES+offset+x+16,y ,iMono); // second 7 HGR_RED
SETSOURCEPIXEL(SRCOFFS_HIRES+offset+x+16,y+1,iMono); // second 7
}
}
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 = aPixels[iPixel] ? iMono : HGR_BLACK;
// Colors - Top/Bottom Left/Right
SETSOURCEPIXEL(SRCOFFS_HIRES+offset+x+adj +hibit,y ,color); // TL
SETSOURCEPIXEL(SRCOFFS_HIRES+offset+x+adj+1+hibit,y ,color); // BL
SETSOURCEPIXEL(SRCOFFS_HIRES+offset+x+adj +hibit,y+1,color); // BL
SETSOURCEPIXEL(SRCOFFS_HIRES+offset+x+adj+1+hibit,y+1,color); // BR
x += 2;
}
}
}
}
}
//===========================================================================
void V_CreateLookup_MonoLoRes () {
int iMonochrome = GetMonochromeIndex();
for (int color = 0; color < 16; color++)
{
for (int x = 0; x < 16; x++)
{
for (int y = 0; y < 16; y++)
{
BYTE colorval = (color >> (x & 3) & 1) ? iMonochrome : BLACK;
#if 0
if (g_eVideoType == VT_MONO_AUTHENTIC)
{
if (y & 1)
SETSOURCEPIXEL(SRCOFFS_LORES+x,(color << 4)+y,BLACK);
else
SETSOURCEPIXEL(SRCOFFS_LORES+x,(color << 4)+y,colorval);
}
else
#endif
{
SETSOURCEPIXEL(SRCOFFS_LORES+x,(color << 4)+y,colorval);
}
}
}
}
}
// google: CreateDIBPatternBrushPt
// http://209.85.141.104/search?q=cache:mB3htrQGW8kJ:bookfire.net/wince/wince-programming-ms-press2/source/prowice/ch02e.htm
struct BRUSHBMP
{
BITMAPINFOHEADER bmi;
COLORREF dwPal[2];
BYTE bBits[64];
};
HBRUSH V_CreateCustomBrush(COLORREF nColor)
{
BRUSHBMP brbmp;
BYTE *pBytes;
int i;
//DWORD dwBits[6][2] =
//{
// {0x000000ff,0x00000000}, // HS_HORIZONTAL 0 /* ----- */
// {0x10101010,0x10101010}, // HS_VERTICAL 1 /* ||||| */
// {0x01020408,0x10204080}, // HS_FDIAGONAL 2 /* \\\\\ */
// {0x80402010,0x08040201}, // HS_BDIAGONAL 3 /* ///// */
// {0x101010ff,0x10101010}, // HS_CROSS 4 /* +++++ */
// {0x81422418,0x18244281}, // HS_DIAGCROSS 5 /* xxxxx */
//};
// if ((HatchStyle < 0) || (HatchStyle > 6))
// return 0;
int HatchStyle = 0;
DWORD dwBits[1][2] =
{
// {0xff00ff00,0xff00ff00} // every other scan line
{0xFFFFFFFF,0xFFFFFFFF}
};
memset (&brbmp, 0, sizeof (brbmp));
brbmp.bmi.biSize = sizeof (BITMAPINFOHEADER);
brbmp.bmi.biWidth = 8;
brbmp.bmi.biHeight = 8;
brbmp.bmi.biPlanes = 1;
brbmp.bmi.biBitCount = 1;
brbmp.bmi.biClrUsed = 2;
brbmp.bmi.biClrImportant = 2;
// Initialize the palette of the bitmap.
brbmp.dwPal[0] = PALETTERGB(0x00,0x00,0x00);
brbmp.dwPal[1] = PALETTERGB(
(BYTE)((nColor >> 16) & 0xff),
(BYTE)((nColor >> 8) & 0xff),
(BYTE)((nColor >> 0) & 0xff));
// Write the hatch data to the bitmap.
pBytes = (BYTE *)&dwBits[HatchStyle];
for (i = 0; i < 8; i++)
brbmp.bBits[i*4] = *pBytes++;
// Return the handle of the brush created.
return CreateDIBPatternBrushPt (&brbmp, DIB_RGB_COLORS);
}
//===========================================================================
static void CreateLookup_TextCommon(HDC hDstDC, DWORD rop)
{
HBITMAP hCharBitmap[4];
HDC hSrcDC = CreateCompatibleDC(hDstDC);
hCharBitmap[0] = LoadBitmap(g_hInstance,TEXT("CHARSET40"));
hCharBitmap[1] = LoadBitmap(g_hInstance,TEXT("CHARSET82"));
hCharBitmap[2] = LoadBitmap(g_hInstance,TEXT("CHARSET8C")); // FIXME: Pravets 8M probably has the same charset as Pravets 8C
hCharBitmap[3] = LoadBitmap(g_hInstance,TEXT("CHARSET8C"));
SelectObject(hSrcDC, hCharBitmap[g_nCharsetType]);
// TODO: Update with APPLE_FONT_Y_ values
BitBlt( hDstDC, SRCOFFS_40COL ,0,256,512,hSrcDC,0, 0,rop);
BitBlt( hDstDC, SRCOFFS_IIPLUS,0,256,256,hSrcDC,0,512,rop); // Chars for Apple ][
StretchBlt(hDstDC, SRCOFFS_80COL ,0,128,512,hSrcDC,0, 0,256,512,rop); // Chars for 80 col mode
DeleteDC(hSrcDC);
for (UINT i=0; i<4; i++)
DeleteObject(hCharBitmap[i]);
}
static void V_CreateLookup_Text(HDC hDstDC)
{
CreateLookup_TextCommon(hDstDC, SRCCOPY);
}
static void V_CreateLookup_MonoText(HDC hDstDC)
{
HBRUSH hBrush;
switch (g_eVideoType)
{
case VT_MONO_AMBER: hBrush = CreateSolidBrush(RGB(0xFF,0x80,0x00)); break;
case VT_MONO_GREEN: hBrush = CreateSolidBrush(RGB(0x00,0xC0,0x00)); break;
case VT_MONO_WHITE: hBrush = CreateSolidBrush(RGB(0xFF,0xFF,0xFF)); break;
default : hBrush = CreateSolidBrush(monochrome); break;
}
SelectObject(hDstDC, hBrush);
// NB. MERGECOPY (not SRCCOPY) to merge the src with the colour of the dst's selected brush
CreateLookup_TextCommon(hDstDC, MERGECOPY);
SelectObject(hDstDC,GetStockObject(NULL_BRUSH));
DeleteObject(hBrush);
}
//===========================================================================
void SetLastDrawnImage ()
{
memcpy(vidlastmem+0x400,g_pTextBank0,0x400);
if (SW_HIRES)
memcpy(vidlastmem+0x2000,g_pHiresBank0,0x2000);
if (SW_DHIRES && SW_HIRES)
memcpy(vidlastmem,g_pHiresBank1,0x2000);
else if (SW_80COL) // Don't test for !SW_HIRES, as some 80-col text routines have SW_HIRES set (Bug #8300)
memcpy(vidlastmem,g_pTextBank1,0x400);
int loop;
for (loop = 0; loop < 256; loop++)
{
*(memdirty+loop) &= ~2;
}
}
//===========================================================================
static inline int GetOriginal2EOffset(BYTE ch)
{
// No mousetext for original IIe
return !IsOriginal2E() || !g_nAltCharSetOffset || (ch<0x40) || (ch>0x5F) ? 0 : -g_nAltCharSetOffset;
}
bool Update40ColCell (int x, int y, int xpixel, int ypixel, int offset)
{
BYTE ch = *(g_pTextBank0+offset);
bool bCharChanged = (ch != *(vidlastmem+offset+0x400) || g_VideoForceFullRedraw);
// FLASHing chars:
// - FLASHing if:Alt Char Set is OFF && 0x40<=char<=0x7F
// - The inverse of this char is located at: char+0x40
bool bCharFlashing = (g_nAltCharSetOffset == 0) && (ch >= 0x40) && (ch <= 0x7F);
if(bCharChanged || (bCharFlashing && g_bTextFlashFlag))
{
bool bInvert = bCharFlashing ? g_bTextFlashState : false;
CopySource(xpixel,ypixel,
APPLE_FONT_WIDTH, APPLE_FONT_HEIGHT,
(IS_APPLE2 ? SRCOFFS_IIPLUS : SRCOFFS_40COL) + ((ch & 0x0F) << 4),
(ch & 0xF0) + g_nAltCharSetOffset + GetOriginal2EOffset(ch) + (bInvert ? 0x40 : 0x00));
return true;
}
return false;
}
inline bool _Update80ColumnCell( BYTE c, const int xPixel, const int yPixel, bool bCharFlashing )
{
bool bInvert = bCharFlashing ? g_bTextFlashState : false;
CopySource(
xPixel, yPixel,
(APPLE_FONT_WIDTH / 2), APPLE_FONT_HEIGHT,
SRCOFFS_80COL + ((c & 0x0F) << 3),
(c & 0xF0) + g_nAltCharSetOffset + GetOriginal2EOffset(c) + (bInvert ? 0x40 : 0x00));
return true;
}
//===========================================================================
bool Update80ColCell (int x, int y, int xpixel, int ypixel, int offset)
{
bool bDirty = false;
#if FLASH_80_COL
BYTE c1 = *(g_pTextBank1 + offset); // aux
BYTE c0 = *(g_pTextBank0 + offset); // main
bool bC1Changed = (c1 != *(vidlastmem + offset + 0) || g_VideoForceFullRedraw);
bool bC0Changed = (c0 != *(vidlastmem + offset + 0x400) || g_VideoForceFullRedraw);
bool bC1Flashing = (g_nAltCharSetOffset == 0) && (c1 >= 0x40) && (c1 <= 0x7F);
bool bC0Flashing = (g_nAltCharSetOffset == 0) && (c0 >= 0x40) && (c0 <= 0x7F);
if (bC1Changed || (bC1Flashing && g_bTextFlashFlag))
bDirty = _Update80ColumnCell( c1, xpixel, ypixel, bC1Flashing );
if (bC0Changed || (bC0Flashing && g_bTextFlashFlag))
bDirty |= _Update80ColumnCell( c0, xpixel + 7, ypixel, bC0Flashing );
#else
BYTE auxval = *(g_pTextBank1 + offset); // aux
BYTE mainval = *(g_pTextBank0 + offset); // main
if ((auxval != *(vidlastmem+offset)) ||
(mainval != *(vidlastmem+offset+0x400)) ||
g_VideoForceFullRedraw)
{
CopySource(xpixel,ypixel,
(APPLE_FONT_WIDTH / 2), APPLE_FONT_HEIGHT,
SRCOFFS_80COL + ((auxval & 15)<<3),
((auxval>>4)<<4) + g_nAltCharSetOffset);
CopySource(xpixel+7,ypixel,
(APPLE_FONT_WIDTH / 2), APPLE_FONT_HEIGHT,
SRCOFFS_80COL + ((mainval & 15)<<3),
((mainval>>4)<<4) + g_nAltCharSetOffset );
bDirty = true;
}
#endif
return bDirty;
}
//===========================================================================
bool UpdateDHiResCell (int x, int y, int xpixel, int ypixel, int offset)
{
bool bDirty = false;
int yoffset = 0;
while (yoffset < 0x2000) {
BYTE byteval1 = (x > 0) ? *(g_pHiresBank0+offset+yoffset-1) : 0;
BYTE byteval2 = *(g_pHiresBank1 +offset+yoffset);
BYTE byteval3 = *(g_pHiresBank0+offset+yoffset);
BYTE byteval4 = (x < 39) ? *(g_pHiresBank1 +offset+yoffset+1) : 0;
if ((byteval2 != *(vidlastmem+offset+yoffset)) ||
(byteval3 != *(vidlastmem+offset+yoffset+0x2000)) ||
((x > 0) && ((byteval1 & 0x70) != (*(vidlastmem+offset+yoffset+0x1FFF) & 0x70))) ||
((x < 39) && ((byteval4 & 0x07) != (*(vidlastmem+offset+yoffset+ 1) & 0x07))) ||
g_VideoForceFullRedraw) {
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(xpixel+PIXEL,ypixel+(yoffset >> 9),7,2,
SRCOFFS_DHIRES+10*HIBYTE(VALUE)+COLOR,LOBYTE(VALUE)<<1);
#undef PIXEL
#define PIXEL 7
CopySource(xpixel+PIXEL,ypixel+(yoffset >> 9),7,2,
SRCOFFS_DHIRES+10*HIBYTE(VALUE)+COLOR,LOBYTE(VALUE)<<1);
#undef PIXEL
#undef COLOR
#undef VALUE
bDirty = true;
}
yoffset += 0x400;
}
return bDirty;
}
/*
Color Reference Tests:
2000:D5 AA D5 AA D5 AA // blue blue blue
2400:AA D5 2A 55 55 2A //+ red green magenta
// //= grey aqua purple
2C00:AA D5 AA D5 2A 55 // red red green
3000:2A 55 55 2A 55 2A //+ green magenta magenta
// //= yellow pink grey
*/
//===========================================================================
BYTE MixColors(BYTE c1, BYTE c2)
{
// For tv emulation HGR Video Mode
#define COMBINATION(c1,c2,ref1,ref2) (((c1)==(ref1)&&(c2)==(ref2)) || ((c1)==(ref2)&&(c2)==(ref1)))
if (c1 == c2)
return c1;
if (COMBINATION(c1,c2,HGR_BLUE,HGR_RED))
return HGR_GREY1;
else if (COMBINATION(c1,c2,HGR_GREEN,HGR_MAGENTA))
return HGR_GREY2;
else if (COMBINATION(c1,c2,HGR_RED,HGR_GREEN))
return HGR_YELLOW;
else if (COMBINATION(c1,c2,HGR_BLUE,HGR_GREEN))
return HGR_AQUA;
else if (COMBINATION(c1,c2,HGR_BLUE,HGR_MAGENTA))
return HGR_PURPLE;
else if (COMBINATION(c1,c2,HGR_RED,HGR_MAGENTA))
return HGR_PINK;
else
return MONOCHROME_CUSTOM; // visible failure indicator
#undef COMBINATION
}
//===========================================================================
void CreateColorMixMap()
{
// For tv emulation HGR Video Mode
#define FROM_NEIGHBOUR 0x00
int t,m,b;
BYTE cTop, cMid, cBot;
WORD mixTop, mixBot;
#define MIX_THRESHOLD 0x12 // bottom 2 HGR colors
for (t=0; t<6; t++)
for (m=0; m<6; m++)
for (b=0; b<6; b++) {
cTop = t | 0x10;
cMid = m | 0x10;
cBot = b | 0x10;
if (cMid < MIX_THRESHOLD) {
mixTop = mixBot = cMid;
} else {
if (cTop < MIX_THRESHOLD) {
mixTop = FROM_NEIGHBOUR;
} else {
mixTop = MixColors(cMid,cTop);
}
if (cBot < MIX_THRESHOLD) {
mixBot = FROM_NEIGHBOUR;
} else {
mixBot = MixColors(cMid,cBot);
}
if (mixTop == FROM_NEIGHBOUR && mixBot != FROM_NEIGHBOUR) {
mixTop = mixBot;
} else if (mixBot == FROM_NEIGHBOUR && mixTop != FROM_NEIGHBOUR) {
mixBot = mixTop;
} else if (mixBot == FROM_NEIGHBOUR && mixTop == FROM_NEIGHBOUR) {
mixBot = mixTop = cMid;
}
}
colormixmap[t][m][b] = (mixTop << 8) | mixBot;
}
#undef FROM_NEIGHBOUR
}
//===========================================================================
void __stdcall MixColorsVertical(int matx, int maty)
{
// For tv emulation HGR Video Mode
WORD twoHalfPixel;
int bot1idx, bot2idx;
if (SW_MIXED && maty > 159) {
if (maty < 161) {
bot1idx = hgrpixelmatrix[matx][maty+1] & 0x0F;
bot2idx = 0;
} else {
bot1idx = bot2idx = 0;
}
} else {
bot1idx = hgrpixelmatrix[matx][maty+1] & 0x0F;
bot2idx = hgrpixelmatrix[matx][maty+2] & 0x0F;
}
twoHalfPixel = colormixmap[hgrpixelmatrix[matx][maty-2] & 0x0F]
[hgrpixelmatrix[matx][maty-1] & 0x0F]
[hgrpixelmatrix[matx][maty ] & 0x0F];
colormixbuffer[0] = (twoHalfPixel & 0xFF00) >> 8;
colormixbuffer[1] = twoHalfPixel & 0x00FF;
twoHalfPixel = colormixmap[hgrpixelmatrix[matx][maty-1] & 0x0F]
[hgrpixelmatrix[matx][maty ] & 0x0F]
[bot1idx];
colormixbuffer[2] = (twoHalfPixel & 0xFF00) >> 8;
colormixbuffer[3] = twoHalfPixel & 0x00FF;
twoHalfPixel = colormixmap[hgrpixelmatrix[matx][maty ] & 0x0F]
[bot1idx]
[bot2idx];
colormixbuffer[4] = (twoHalfPixel & 0xFF00) >> 8;
colormixbuffer[5] = twoHalfPixel & 0x00FF;
}
//===========================================================================
static inline void __stdcall CopyMixedSource8(int x, int y, int sourcex, int sourcey)
{
// For tv emulation HGR Video Mode
const BYTE* const currsourceptr = g_aSourceStartofLine[sourcey]+sourcex;
BYTE* const currdestptr = g_aFrameBufferOffset[y*2] + (x*2);
const int matx = x;
const int maty = HGR_MATRIX_YOFFSET + y;
const int hgrlinesabove = (y > 0) ? 1 : 0;
const int hgrlinesbelow = SW_MIXED ? ((y < 159)? 1:0) : ((y < 191)? 1:0);
const int istart = 2 - (hgrlinesabove*2);
const int iend = 3 + (hgrlinesbelow*2);
// transfer 7 pixels (i.e. the visible part of an apple hgr-byte) from row to pixelmatrix
for (int count = 0, bufxoffset = 0; count < 7; count++, bufxoffset += 2)
{
hgrpixelmatrix[matx+count][maty] = *(currsourceptr+bufxoffset);
// color mixing between adjacent scanlines at current x position
MixColorsVertical(matx+count, maty);
// transfer up to 6 mixed (half-)pixels of current column to framebuffer
BYTE* currptr = currdestptr+bufxoffset;
if (hgrlinesabove)
currptr += g_nFrameBufferPitch * 2;
for (int i = istart; i <= iend; currptr -= g_nFrameBufferPitch, i++)
{
if (g_uHalfScanLines && (i & 1))
*currptr = *(currptr+1) = 0; // 50% Half Scan Line clears every odd scanline (and SHIFT+PrintScreen saves only the even rows)
else
*currptr = *(currptr+1) = colormixbuffer[i];
}
}
}
// For tv emulation HGR Video Mode
static void __stdcall CopyMixedSource(int x, int y, int sourcex, int sourcey)
{
if (!g_bIsFullScreen || !GetFullScreen32Bit())
{
CopyMixedSource8(x,y,sourcex,sourcey);
return;
}
const BYTE* const currsourceptr = g_aSourceStartofLine[sourcey]+sourcex;
UINT32* const currdestptr = (UINT32*) (g_aFrameBufferOffset[ y*2 ] + (x*2)*sizeof(UINT32));
const int matx = x;
const int maty = HGR_MATRIX_YOFFSET + y;
const int hgrlinesabove = (y > 0) ? 1 : 0;
const int hgrlinesbelow = SW_MIXED ? ((y < 159)? 1:0) : ((y < 191)? 1:0);
const int istart = 2 - (hgrlinesabove*2);
const int iend = 3 + (hgrlinesbelow*2);
// transfer 7 pixels (i.e. the visible part of an apple hgr-byte) from row to pixelmatrix
for (int count = 0, bufxoffset = 0; count < 7; count++, bufxoffset += 2)
{
hgrpixelmatrix[matx+count][maty] = *(currsourceptr+bufxoffset);
// color mixing between adjacent scanlines at current x position
MixColorsVertical(matx+count, maty);
// transfer up to 6 mixed (half-)pixels of current column to framebuffer
UINT32* currptr = currdestptr+bufxoffset;
if (hgrlinesabove)
currptr += (g_nFrameBufferPitch / sizeof(UINT32)) * 2;
for (int i = istart; i <= iend; currptr -= g_nFrameBufferPitch/sizeof(UINT32), i++)
{
if (g_uHalfScanLines && (i & 1))
{
// 50% Half Scan Line clears every odd scanline (and SHIFT+PrintScreen saves only the even rows)
*currptr = *(currptr+1) = 0;
}
else
{
const RGBQUAD& rRGB = g_pFramebufferinfo->bmiColors[ colormixbuffer[i] ];
const UINT32 rgb = (((UINT32)rRGB.rgbRed)<<16) | (((UINT32)rRGB.rgbGreen)<<8) | ((UINT32)rRGB.rgbBlue);
*currptr = *(currptr+1) = rgb;
}
}
}
}
//===========================================================================
bool UpdateHiResCell (int x, int y, int xpixel, int ypixel, int offset)
{
bool bDirty = false;
int yoffset = 0;
while (yoffset < 0x2000)
{
#if 0 // TRACE_VIDEO
static char sText[ 256 ];
sprintf(sText, "x: %3d y: %3d xpix: %3d ypix: %3d offset: %04X \n"
, x, y, xpixel, ypixel, offset, yoffset
);
OutputDebugString("sText");
#endif
BYTE byteval1 = (x > 0) ? *(g_pHiresBank0+offset+yoffset-1) : 0;
BYTE byteval2 = *(g_pHiresBank0+offset+yoffset );
BYTE byteval3 = (x < 39) ? *(g_pHiresBank0+offset+yoffset+1) : 0;
if ((byteval2 != *(vidlastmem+offset+yoffset+0x2000)) ||
((x > 0) && ((byteval1 & 0x60) != (*(vidlastmem+offset+yoffset+0x1FFF) & 0x60))) ||
((x < 39) && ((byteval3 & 0x03) != (*(vidlastmem+offset+yoffset+0x2001) & 0x03))) ||
g_VideoForceFullRedraw)
{
#define COLOFFS (((byteval1 & 0x60) << 2) | \
((byteval3 & 0x03) << 5))
if (g_eVideoType == VT_COLOR_TVEMU)
{
CopyMixedSource(
xpixel >> 1, (ypixel+(yoffset >> 9)) >> 1,
SRCOFFS_HIRES+COLOFFS+((x & 1) << 4), (((int)byteval2) << 1)
);
}
else
{
CopySource(
xpixel,ypixel+(yoffset >> 9),
14,2, // 2x upscale: 280x192 -> 560x384
SRCOFFS_HIRES+COLOFFS+((x & 1) << 4), (((int)byteval2) << 1)
);
}
#undef COLOFFS
bDirty = true;
}
yoffset += 0x400;
}
return bDirty;
}
//===========================================================================
bool UpdateLoResCell (int x, int y, int xpixel, int ypixel, int offset)
{
BYTE val = *(g_pTextBank0+offset);
if ((val != *(vidlastmem+offset+0x400)) || g_VideoForceFullRedraw)
{
CopySource(xpixel,ypixel,
14,8,
SRCOFFS_LORES+((x & 1) << 1),((val & 0xF) << 4));
CopySource(xpixel,ypixel+8,
14,8,
SRCOFFS_LORES+((x & 1) << 1),(val & 0xF0));
return true;
}
return false;
}
//===========================================================================
#define ROL_NIB(x) ( (((x)<<1)&0xF) | (((x)>>3)&1) )
bool UpdateDLoResCell (int x, int y, int xpixel, int ypixel, int offset)
{
BYTE auxval = *(g_pTextBank1 + offset);
BYTE mainval = *(g_pTextBank0 + offset);
if ( (auxval != *(vidlastmem+offset)) ||
(mainval != *(vidlastmem+offset+0x400)) ||
g_VideoForceFullRedraw
)
{
const BYTE auxval_h = auxval >> 4;
const BYTE auxval_l = auxval & 0xF;
auxval = (ROL_NIB(auxval_h)<<4) | ROL_NIB(auxval_l); // Fix Bug #14879
CopySource( xpixel,ypixel , 7,8,SRCOFFS_LORES+((x & 1) << 1),((auxval & 0xF) << 4));
CopySource( xpixel,ypixel+8, 7,8,SRCOFFS_LORES+((x & 1) << 1),(auxval & 0xF0));
//
CopySource( xpixel+7,ypixel , 7,8, SRCOFFS_LORES+((x & 1) << 1),((mainval & 0xF) << 4));
CopySource( xpixel+7,ypixel+8, 7,8, SRCOFFS_LORES+((x & 1) << 1),(mainval & 0xF0));
return true;
}
return false;
}
//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//
//===========================================================================
BOOL VideoApparentlyDirty ()
{
if (SW_MIXED || g_VideoForceFullRedraw)
return 1;
DWORD address = (SW_HIRES && !SW_TEXT)
? (0x20 << (SW_PAGE2 ? 1 : 0))
: (0x04 << (SW_PAGE2 ? 1 : 0));
DWORD length = (SW_HIRES && !SW_TEXT) ? 0x20 : 0x4;
while (length--)
if (*(memdirty+(address++)) & 2)
return 1;
//
// Scan visible text page for any flashing chars
if((SW_TEXT || SW_MIXED) && (g_nAltCharSetOffset == 0))
{
BYTE* pTextBank0 = MemGetMainPtr(0x400 << (SW_PAGE2 ? 1 : 0));
BYTE* pTextBank1 = MemGetAuxPtr (0x400 << (SW_PAGE2 ? 1 : 0));
const bool b80Col = SW_80COL;
// Scan 8 long-lines of 120 chars (at 128 char offsets):
// . Skip 8-char holes in TEXT
for(UINT y=0; y<8; y++)
{
for(UINT x=0; x<40*3; x++)
{
BYTE ch = pTextBank0[y*128+x];
if((ch >= 0x40) && (ch <= 0x7F))
return 1;
if (b80Col)
{
ch = pTextBank1[y*128+x];
if((ch >= 0x40) && (ch <= 0x7F))
return 1;
}
}
}
}
return 0;
}
//===========================================================================
void VideoBenchmark () {
Sleep(500);
// PREPARE TWO DIFFERENT FRAME BUFFERS, EACH OF WHICH HAVE HALF OF THE
// BYTES SET TO 0x14 AND THE OTHER HALF SET TO 0xAA
int loop;
LPDWORD mem32 = (LPDWORD)mem;
for (loop = 4096; loop < 6144; loop++)
*(mem32+loop) = ((loop & 1) ^ ((loop & 0x40) >> 6)) ? 0x14141414
: 0xAAAAAAAA;
for (loop = 6144; loop < 8192; loop++)
*(mem32+loop) = ((loop & 1) ^ ((loop & 0x40) >> 6)) ? 0xAAAAAAAA
: 0x14141414;
// SEE HOW MANY TEXT FRAMES PER SECOND WE CAN PRODUCE WITH NOTHING ELSE
// GOING ON, CHANGING HALF OF THE BYTES IN THE VIDEO BUFFER EACH FRAME TO
// SIMULATE THE ACTIVITY OF AN AVERAGE GAME
DWORD totaltextfps = 0;
g_uVideoMode = VF_TEXT;
FillMemory(mem+0x400,0x400,0x14);
VideoRedrawScreen();
DWORD milliseconds = GetTickCount();
while (GetTickCount() == milliseconds) ;
milliseconds = GetTickCount();
DWORD cycle = 0;
do {
if (cycle & 1)
FillMemory(mem+0x400,0x400,0x14);
else
CopyMemory(mem+0x400,mem+((cycle & 2) ? 0x4000 : 0x6000),0x400);
VideoRefreshScreen();
if (cycle++ >= 3)
cycle = 0;
totaltextfps++;
} while (GetTickCount() - milliseconds < 1000);
// SEE HOW MANY HIRES FRAMES PER SECOND WE CAN PRODUCE WITH NOTHING ELSE
// GOING ON, CHANGING HALF OF THE BYTES IN THE VIDEO BUFFER EACH FRAME TO
// SIMULATE THE ACTIVITY OF AN AVERAGE GAME
DWORD totalhiresfps = 0;
g_uVideoMode = VF_HIRES;
FillMemory(mem+0x2000,0x2000,0x14);
VideoRedrawScreen();
milliseconds = GetTickCount();
while (GetTickCount() == milliseconds) ;
milliseconds = GetTickCount();
cycle = 0;
do {
if (cycle & 1)
FillMemory(mem+0x2000,0x2000,0x14);
else
CopyMemory(mem+0x2000,mem+((cycle & 2) ? 0x4000 : 0x6000),0x2000);
VideoRefreshScreen();
if (cycle++ >= 3)
cycle = 0;
totalhiresfps++;
} while (GetTickCount() - milliseconds < 1000);
// DETERMINE HOW MANY 65C02 CLOCK CYCLES WE CAN EMULATE PER SECOND WITH
// NOTHING ELSE GOING ON
CpuSetupBenchmark();
DWORD totalmhz10 = 0;
milliseconds = GetTickCount();
while (GetTickCount() == milliseconds) ;
milliseconds = GetTickCount();
cycle = 0;
do {
CpuExecute(100000);
totalmhz10++;
} while (GetTickCount() - milliseconds < 1000);
// IF THE PROGRAM COUNTER IS NOT IN THE EXPECTED RANGE AT THE END OF THE
// CPU BENCHMARK, REPORT AN ERROR AND OPTIONALLY TRACK IT DOWN
if ((regs.pc < 0x300) || (regs.pc > 0x400))
if (MessageBox(g_hFrameWindow,
TEXT("The emulator has detected a problem while running ")
TEXT("the CPU benchmark. Would you like to gather more ")
TEXT("information?"),
TEXT("Benchmarks"),
MB_ICONQUESTION | MB_YESNO | MB_SETFOREGROUND) == IDYES) {
BOOL error = 0;
WORD lastpc = 0x300;
int loop = 0;
while ((loop < 10000) && !error) {
CpuSetupBenchmark();
CpuExecute(loop);
if ((regs.pc < 0x300) || (regs.pc > 0x400))
error = 1;
else {
lastpc = regs.pc;
++loop;
}
}
if (error) {
TCHAR outstr[256];
wsprintf(outstr,
TEXT("The emulator experienced an error %u clock cycles ")
TEXT("into the CPU benchmark. Prior to the error, the ")
TEXT("program counter was at $%04X. After the error, it ")
TEXT("had jumped to $%04X."),
(unsigned)loop,
(unsigned)lastpc,
(unsigned)regs.pc);
MessageBox(g_hFrameWindow,
outstr,
TEXT("Benchmarks"),
MB_ICONINFORMATION | MB_SETFOREGROUND);
}
else
MessageBox(g_hFrameWindow,
TEXT("The emulator was unable to locate the exact ")
TEXT("point of the error. This probably means that ")
TEXT("the problem is external to the emulator, ")
TEXT("happening asynchronously, such as a problem in ")
TEXT("a timer interrupt handler."),
TEXT("Benchmarks"),
MB_ICONINFORMATION | MB_SETFOREGROUND);
}
// DO A REALISTIC TEST OF HOW MANY FRAMES PER SECOND WE CAN PRODUCE
// WITH FULL EMULATION OF THE CPU, JOYSTICK, AND DISK HAPPENING AT
// THE SAME TIME
DWORD realisticfps = 0;
FillMemory(mem+0x2000,0x2000,0xAA);
VideoRedrawScreen();
milliseconds = GetTickCount();
while (GetTickCount() == milliseconds) ;
milliseconds = GetTickCount();
cycle = 0;
do {
if (realisticfps < 10) {
int cycles = 100000;
while (cycles > 0) {
DWORD executedcycles = CpuExecute(103);
cycles -= executedcycles;
DiskUpdatePosition(executedcycles);
JoyUpdateButtonLatch(executedcycles);
}
}
if (cycle & 1)
FillMemory(mem+0x2000,0x2000,0xAA);
else
CopyMemory(mem+0x2000,mem+((cycle & 2) ? 0x4000 : 0x6000),0x2000);
VideoRefreshScreen();
if (cycle++ >= 3)
cycle = 0;
realisticfps++;
} while (GetTickCount() - milliseconds < 1000);
// DISPLAY THE RESULTS
VideoDisplayLogo();
TCHAR outstr[256];
wsprintf(outstr,
TEXT("Pure Video FPS:\t%u hires, %u text\n")
TEXT("Pure CPU MHz:\t%u.%u%s\n\n")
TEXT("EXPECTED AVERAGE VIDEO GAME\n")
TEXT("PERFORMANCE: %u FPS"),
(unsigned)totalhiresfps,
(unsigned)totaltextfps,
(unsigned)(totalmhz10/10),
(unsigned)(totalmhz10 % 10),
(LPCTSTR)(IS_APPLE2 ? TEXT(" (6502)") : TEXT("")),
(unsigned)realisticfps);
MessageBox(g_hFrameWindow,
outstr,
TEXT("Benchmarks"),
MB_ICONINFORMATION | MB_SETFOREGROUND);
}
//===========================================================================
BYTE VideoCheckMode (WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles)
{
address &= 0xFF;
if (address == 0x7F)
return MemReadFloatingBus(SW_DHIRES != 0, uExecutedCycles);
else {
BOOL result = 0;
switch (address) {
case 0x1A: result = SW_TEXT; break;
case 0x1B: result = SW_MIXED; break;
case 0x1D: result = SW_HIRES; break;
case 0x1E: result = g_nAltCharSetOffset; break;
case 0x1F: result = SW_80COL; break;
case 0x7F: result = SW_DHIRES; break;
}
return KeybGetKeycode() | (result ? 0x80 : 0);
}
}
//===========================================================================
/*
// Drol expects = 80
68DE A5 02 LDX #02
68E0 AD 50 C0 LDA TXTCLR
68E3 C9 80 CMP #80
68E5 D0 F7 BNE $68DE
6957 A5 02 LDX #02
6959 AD 50 C0 LDA TXTCLR
695C C9 80 CMP #80
695E D0 F7 BNE $68DE
69D3 A5 02 LDX #02
69D5 AD 50 C0 LDA TXTCLR
69D8 C9 80 CMP #80
69DA D0 F7 BNE $68DE
// Karateka expects < 80
07DE AD 19 C0 LDA RDVBLBAR
07E1 30 FB BMI $7DE
77A1 AD 19 C0 LDA RDVBLBAR
77A4 30 FB BMI $7DE
// Gumball expects non-zero low-nibble on VBL
BBB5 A5 60 LDA $60
BBB7 4D 50 C0 EOR TXTCLR
BBBA 85 60 STA $60
BBBC 29 0F AND #$0F
BBBE F0 F5 BEQ $BBB5
BBC0 C9 0F CMP #$0F
BBC2 F0 F1 BEQ $BBB5
// Diversi-Dial (DD4.DSK or DIAL.DSK)
F822 LDA RDVBLBAR
F825 EOR #$3C
BMI $F82A
RTS
F82A LDA $F825+1
EOR #$80
STA $F825+1
BMI $F86A
...
F86A RTS
*/
BYTE VideoCheckVbl (WORD, WORD, BYTE, BYTE, ULONG uExecutedCycles)
{
bool bVblBar = false;
VideoGetScannerAddress(&bVblBar, uExecutedCycles);
BYTE r = KeybGetKeycode();
return (r & ~0x80) | ((bVblBar) ? 0x80 : 0);
}
//===========================================================================
void VideoChooseColor ()
{
CHOOSECOLOR cc;
ZeroMemory(&cc,sizeof(CHOOSECOLOR));
cc.lStructSize = sizeof(CHOOSECOLOR);
cc.hwndOwner = g_hFrameWindow;
cc.rgbResult = monochrome;
cc.lpCustColors = customcolors + 1;
cc.Flags = CC_RGBINIT | CC_SOLIDCOLOR;
if (ChooseColor(&cc))
{
monochrome = cc.rgbResult;
VideoReinitialize();
if ((g_nAppMode != MODE_LOGO) && (g_nAppMode != MODE_DEBUG))
{
VideoRedrawScreen();
}
Config_Save_Video();
}
}
//===========================================================================
void VideoDestroy () {
// DESTROY BUFFERS
VirtualFree(g_pFramebufferinfo,0,MEM_RELEASE);
VirtualFree(g_pSourceHeader ,0,MEM_RELEASE);
VirtualFree(vidlastmem ,0,MEM_RELEASE);
g_pFramebufferinfo = NULL;
g_pSourceHeader = NULL;
vidlastmem = NULL;
// DESTROY FRAME BUFFER
DeleteDC(g_hDeviceDC);
DeleteObject(g_hDeviceBitmap);
g_hDeviceDC = (HDC)0;
g_hDeviceBitmap = (HBITMAP)0;
// DESTROY SOURCE IMAGE
DeleteObject(g_hSourceBitmap);
g_hSourceBitmap = (HBITMAP)0;
// DESTROY LOGO
if (g_hLogoBitmap) {
DeleteObject(g_hLogoBitmap);
g_hLogoBitmap = (HBITMAP)0;
}
// DESTROY PALETTE
if (g_hPalette) {
DeleteObject(g_hPalette);
g_hPalette = (HPALETTE)0;
}
}
//===========================================================================
void VideoDrawLogoBitmap(HDC hDstDC, int xoff, int yoff, int srcw, int srch, int scale)
{
HDC hSrcDC = CreateCompatibleDC( hDstDC );
SelectObject( hSrcDC, g_hLogoBitmap );
StretchBlt(
hDstDC, // hdcDest
xoff, yoff, // nXDest, nYDest
scale * srcw, scale * srch, // nWidth, nHeight
hSrcDC, // hdcSrc
0, 0, // nXSrc, nYSrc
srcw, srch,
SRCCOPY // dwRop
);
DeleteObject( hSrcDC );
}
//===========================================================================
void VideoDisplayLogo ()
{
int xoff = 0, yoff = 0, scale = 0;
HDC hFrameDC = FrameGetDC();
// DRAW THE LOGO
HBRUSH brush = CreateSolidBrush(PALETTERGB(0x70,0x30,0xE0));
SelectObject(hFrameDC, brush);
SelectObject(hFrameDC, GetStockObject(NULL_PEN));
int nViewportCX, nViewportCY;
GetViewportCXCY(nViewportCX, nViewportCY);
Rectangle(hFrameDC, 0, 0, nViewportCX+1, nViewportCY+1);
if (g_hLogoBitmap)
{
BITMAP bm;
if (GetObject(g_hLogoBitmap, sizeof(bm), &bm))
{
scale = nViewportCX / bm.bmWidth;
if (nViewportCY / bm.bmHeight < scale)
scale = nViewportCY / bm.bmHeight;
if (scale > 0)
{
if (nViewportCX > bm.bmWidth)
xoff = (nViewportCX - (scale * bm.bmWidth)) / 2;
if (nViewportCY > bm.bmHeight)
yoff = (nViewportCY - (scale * bm.bmHeight)) / 2;
VideoDrawLogoBitmap( hFrameDC, xoff, yoff, bm.bmWidth, bm.bmHeight, scale );
}
}
}
// DRAW THE VERSION NUMBER
HFONT font = CreateFont(-20,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,
VARIABLE_PITCH | 4 | FF_SWISS,
TEXT("Arial"));
SelectObject(hFrameDC,font);
SetTextAlign(hFrameDC,TA_RIGHT | TA_TOP);
SetBkMode(hFrameDC,TRANSPARENT);
char szVersion[ 64 ] = "";
sprintf( szVersion, "Version %s", VERSIONSTRING );
#define DRAWVERSION(x,y,c) \
SetTextColor(hFrameDC,c); \
TextOut(hFrameDC, \
scale*540+x+xoff,scale*358+y+yoff, \
szVersion, \
strlen(szVersion));
if (GetDeviceCaps(hFrameDC,PLANES) * GetDeviceCaps(hFrameDC,BITSPIXEL) <= 4) {
DRAWVERSION( 2, 2,RGB(0x00,0x00,0x00));
DRAWVERSION( 1, 1,RGB(0x00,0x00,0x00));
DRAWVERSION( 0, 0,RGB(0xFF,0x00,0xFF));
} else {
DRAWVERSION( 1, 1,PALETTERGB(0x30,0x30,0x70));
DRAWVERSION(-1,-1,PALETTERGB(0xC0,0x70,0xE0));
DRAWVERSION( 0, 0,PALETTERGB(0x70,0x30,0xE0));
}
#if _DEBUG
sprintf( szVersion, "DEBUG" );
DRAWVERSION( 2, -358*scale,RGB(0x00,0x00,0x00));
DRAWVERSION( 1, -357*scale,RGB(0x00,0x00,0x00));
DRAWVERSION( 0, -356*scale,RGB(0xFF,0x00,0xFF));
#endif
#undef DRAWVERSION
FrameReleaseDC();
DeleteObject(brush);
DeleteObject(font);
}
//===========================================================================
void VideoRealizePalette(HDC dc)
{
#if 0
if( g_bIsFullScreen )
{
if( !g_pDDPal )
{
PALETTEENTRY aPal[256];
BYTE *pSrc = ((BYTE*)g_pFramebufferinfo) + sizeof(BITMAPINFOHEADER);
BYTE *pDst = ((BYTE*)aPal);
int iPal;
for(iPal = 0; iPal < 256; iPal++ )
{
*(pDst + 0) = *(pSrc + 2); // BGR -> RGB
*(pDst + 1) = *(pSrc + 1);
*(pDst + 2) = *(pSrc + 0);
*(pDst + 3) = 0;
pDst += 4;
pSrc += 4;
}
if (g_pDD->CreatePalette(DDPCAPS_8BIT, aPal, &g_pDDPal, NULL) != DD_OK)
{
g_pDDPal = NULL;
}
}
if (g_pDDPal)
{
g_pDDPrimarySurface->SetPalette(g_pDDPal); // this sets the palette for the primary surface
}
}
else
{
if (g_hPalette)
{
SelectPalette(dc,g_hPalette,0);
RealizePalette(dc);
}
}
#endif
if (g_hPalette)
{
SelectPalette(dc,g_hPalette,0);
RealizePalette(dc);
}
}
//===========================================================================
// Called by DrawFrameWindow() when in fullscreen mode (eg. after WM_PAINT msg)
VideoUpdateFuncPtr_t VideoRedrawScreen (UINT n)
{
g_VideoForceFullRedraw = n;
return VideoRefreshScreen();
}
VideoUpdateFuncPtr_t VideoRedrawScreen ()
{
g_VideoForceFullRedraw = 1;
return VideoRefreshScreen();
}
//===========================================================================
void _Video_Dirty()
{
ZeroMemory(celldirty,40*32);
}
//===========================================================================
void _Video_SetupBanks( bool bBank2 )
{
g_pHiresBank1 = MemGetAuxPtr (0x2000 << (int)bBank2);
g_pHiresBank0 = MemGetMainPtr(0x2000 << (int)bBank2);
g_pTextBank1 = MemGetAuxPtr (0x400 << (int)bBank2);
g_pTextBank0 = MemGetMainPtr(0x400 << (int)bBank2);
}
//===========================================================================
// NB. Can get "big" 1000+ms times: these occur during disk loading when the emulator is at full-speed.
//#define DEBUG_REFRESH_TIMINGS
#if defined(_DEBUG) && defined(DEBUG_REFRESH_TIMINGS)
static void DebugRefresh(char uDebugFlag)
{
static DWORD uLastRefreshTime = 0;
const DWORD dwEmuTime_ms = CpuGetEmulationTime_ms();
const DWORD uTimeBetweenRefreshes = uLastRefreshTime ? dwEmuTime_ms - uLastRefreshTime : 0;
uLastRefreshTime = dwEmuTime_ms;
if (!uTimeBetweenRefreshes)
return; // 1st time in func
char szStr[100];
sprintf(szStr, "Time between refreshes = %d ms %c\n", uTimeBetweenRefreshes, (uDebugFlag==0)?' ':uDebugFlag);
OutputDebugString(szStr);
}
#endif
VideoUpdateFuncPtr_t VideoRefreshScreen ()
{
#if defined(_DEBUG) && defined(DEBUG_REFRESH_TIMINGS)
DebugRefresh(0);
#endif
// CHECK EACH CELL FOR CHANGED BYTES. REDRAW PIXELS FOR THE CHANGED BYTES
// IN THE FRAME BUFFER. MARK CELLS IN WHICH REDRAWING HAS TAKEN PLACE AS
// DIRTY.
_Video_Dirty();
_Video_SetupBanks( SW_PAGE2 != 0 );
VideoUpdateFuncPtr_t pfUpdate = SW_TEXT
? SW_80COL
? Update80ColCell
: Update40ColCell
: SW_HIRES
? (SW_DHIRES && SW_80COL)
? UpdateDHiResCell
: UpdateHiResCell
: (SW_DHIRES && SW_80COL)
? UpdateDLoResCell
: UpdateLoResCell;
bool bMixed = (SW_MIXED) ? true : false;
_Video_RedrawScreen( pfUpdate, bMixed );
//g_VideoForceFullRedraw = 0;
if (g_VideoForceFullRedraw) --g_VideoForceFullRedraw;
return pfUpdate;
}
//===========================================================================
void _Video_RedrawScreen( VideoUpdateFuncPtr_t pfUpdate, bool bMixed )
{
LPBYTE pDstFrameBufferBits = 0;
LONG pitch = 0;
HDC hFrameDC = FrameGetVideoDC(&pDstFrameBufferBits,&pitch);
CreateFrameOffsetTable(pDstFrameBufferBits,pitch); // ptr to start of each scanline
BOOL anydirty = 0;
int y = 0;
int ypixel = 0;
while (y < 20) {
int offset = ((y & 7) << 7) + ((y >> 3) * 40);
int x = 0;
int xpixel = 0;
while (x < 40) {
anydirty |= celldirty[x][y] = pfUpdate(x,y,xpixel,ypixel,offset+x);
++x;
xpixel += 14;
}
++y;
ypixel += 16;
}
if( bMixed ) {
pfUpdate = SW_80COL
? Update80ColCell
: Update40ColCell;
}
while (y < 24) {
int offset = ((y & 7) << 7) + ((y >> 3) * 40);
int x = 0;
int xpixel = 0;
while (x < 40) {
anydirty |= celldirty[x][y] = pfUpdate(x,y,xpixel,ypixel,offset+x);
++x;
xpixel += 14;
}
++y;
ypixel += 16;
}
// Clear this flag after TEXT screen has been updated
g_bTextFlashFlag = false;
#if 1
// New simpified code:
// . Oliver Schmidt gets a flickering mouse cursor with this code
if (hFrameDC && anydirty)
{
int nViewportCX, nViewportCY;
GetViewportCXCY(nViewportCX, nViewportCY);
StretchBlt(hFrameDC, 0 ,0, nViewportCX, nViewportCY, g_hDeviceDC, 0, 0, FRAMEBUFFER_W, FRAMEBUFFER_H, SRCCOPY);
GdiFlush();
}
#else
// Original code:
if (!hFrameDC || !anydirty)
{
FrameReleaseVideoDC();
SetLastDrawnImage();
g_VideoForceFullRedraw = 0;
return;
}
// COPY DIRTY CELLS FROM THE DEVICE DEPENDENT BITMAP ONTO THE SCREEN
// IN LONG HORIZONTAL RECTANGLES
BOOL remainingdirty = 0;
y = 0;
ypixel = 0;
while (y < 24) {
int start = -1;
int startx = 0;
int x = 0;
int xpixel = 0;
while (x < 40) {
if ((x == 39) && celldirty[x][y])
if (start >= 0) {
xpixel += 14;
celldirty[x][y] = 0;
}
else
remainingdirty = 1;
if ((start >= 0) && !celldirty[x][y]) {
if ((x - startx > 1) || ((x == 39) && (xpixel == FRAMEBUFFER_W))) {
int height = 1;
while ((y+height < 24)
&& celldirty[startx][y+height]
&& celldirty[x-1][y+height]
&& celldirty[(startx+x-1) >> 1][y+height])
height++;
BitBlt(hFrameDC,start,ypixel,xpixel-start,height << 4,
g_hDeviceDC,start,ypixel,SRCCOPY);
while (height--) {
int loop = startx;
while (loop < x+(xpixel == FRAMEBUFFER_W))
celldirty[loop++][y+height] = 0;
}
start = -1;
}
else
remainingdirty = 1;
start = -1;
}
else if ((start == -1) && celldirty[x][y] && (x < 39)) {
start = xpixel;
startx = x;
}
x++;
xpixel += 14;
}
y++;
ypixel += 16;
}
// COPY ANY REMAINING DIRTY CELLS FROM THE DEVICE DEPENDENT BITMAP
// ONTO THE SCREEN IN VERTICAL RECTANGLES
if (remainingdirty) {
int x = 0;
int xpixel = 0;
while (x < 40) {
int start = -1;
int y = 0;
int ypixel = 0;
while (y < 24) {
if ((y == 23) && celldirty[x][y]) {
if (start == -1)
start = ypixel;
ypixel += 16;
celldirty[x][y] = 0;
}
if ((start >= 0) && !celldirty[x][y]) {
BitBlt(hFrameDC,xpixel,start,14,ypixel-start,
g_hDeviceDC,xpixel,start,SRCCOPY);
start = -1;
}
else if ((start == -1) && celldirty[x][y])
start = ypixel;
y++;
ypixel += 16;
}
x++;
xpixel += 14;
}
}
GdiFlush();
#endif
FrameReleaseVideoDC();
SetLastDrawnImage();
}
//===========================================================================
void VideoReinitialize ()
{
V_CreateIdentityPalette();
V_CreateDIBSections();
}
//===========================================================================
void VideoResetState ()
{
g_nAltCharSetOffset = 0;
g_uVideoMode = VF_TEXT;
g_VideoForceFullRedraw = 1;
g_bVideoUpdatedThisFrame = false;
}
//===========================================================================
BYTE VideoSetMode (WORD, WORD address, BYTE write, BYTE, ULONG uExecutedCycles)
{
address &= 0xFF;
DWORD oldpage2 = SW_PAGE2;
int oldvalue = g_nAltCharSetOffset+(int)(g_uVideoMode & ~(VF_80STORE | VF_PAGE2));
switch (address)
{
case 0x00: g_uVideoMode &= ~VF_80STORE; break;
case 0x01: g_uVideoMode |= VF_80STORE; break;
case 0x0C: if (!IS_APPLE2) g_uVideoMode &= ~VF_80COL; break;
case 0x0D: if (!IS_APPLE2) g_uVideoMode |= VF_80COL; break;
case 0x0E: if (!IS_APPLE2) g_nAltCharSetOffset = 0; break; // Alternate char set off
case 0x0F: if (!IS_APPLE2) g_nAltCharSetOffset = 256; break; // Alternate char set on
case 0x50: g_uVideoMode &= ~VF_TEXT; break;
case 0x51: g_uVideoMode |= VF_TEXT; break;
case 0x52: g_uVideoMode &= ~VF_MIXED; break;
case 0x53: g_uVideoMode |= VF_MIXED; break;
case 0x54: g_uVideoMode &= ~VF_PAGE2; break;
case 0x55: g_uVideoMode |= VF_PAGE2; break;
case 0x56: g_uVideoMode &= ~VF_HIRES; break;
case 0x57: g_uVideoMode |= VF_HIRES; break;
case 0x5E: if (!IS_APPLE2) g_uVideoMode |= VF_DHIRES; break;
case 0x5F: if (!IS_APPLE2) g_uVideoMode &= ~VF_DHIRES; break;
}
if (SW_80STORE)
g_uVideoMode &= ~VF_PAGE2;
if (oldvalue != g_nAltCharSetOffset+(int)(g_uVideoMode & ~(VF_80STORE | VF_PAGE2)))
g_VideoForceFullRedraw = 1; // Defer video redraw until VideoEndOfVideoFrame()
if (oldpage2 != SW_PAGE2)
{
// /g_bVideoUpdatedThisFrame/ is used to limit the video update to once per 60Hz frame (CPU clk=1MHz):
// . this easily supports the common double-buffered "flip-immediate" case (eg. Airheart flips at a max of ~15Hz, Skyfox/Boulderdash at a max of ~11Hz)
// . crucially this prevents tight-loop page flipping (GH#129,GH#204) from max'ing out an x86 CPU core (and not providing realtime emulation)
// NB. Deferring the update by just setting /g_VideoForceFullRedraw/ is not an option, since this doesn't provide "flip-immediate"
//
// Ultimately this isn't the correct solution, and proper cycle-accurate video rendering should be done, but this is a much bigger job!
//
if (!g_bVideoUpdatedThisFrame)
{
VideoRefreshScreen();
g_bVideoUpdatedThisFrame = true;
}
}
return MemReadFloatingBus(uExecutedCycles);
}
//===========================================================================
// Called at 60Hz (every 16.666ms)
static void VideoUpdateFlash()
{
static UINT nTextFlashCnt = 0;
// Flash rate:
// . NTSC : 60/16 ~= 4Hz
// . PAL : 50/16 ~= 3Hz
nTextFlashCnt = (nTextFlashCnt+1) & 0xf;
// BUG: In unthrottled CPU mode, flash rate should not be affected
if(nTextFlashCnt == 0)
{
g_bTextFlashState = !g_bTextFlashState;
// Redraw any FLASHing chars if any text showing. NB. No FLASH g_nAppMode for 80 cols
if ((SW_TEXT || SW_MIXED) ) // && !SW_80COL) // FIX: FLASH 80-Column
g_bTextFlashFlag = true;
}
}
//===========================================================================
// Called from main-loop every 17030 cycles (ie. 60Hz when CPU = 1MHz)
void VideoEndOfVideoFrame(void)
{
g_bVideoUpdatedThisFrame = false; // Allow page1/2 toggle to result in an immediate video redraw
VideoUpdateFlash(); // TODO: Flash rate should be constant (regardless of CPU speed)
if (!VideoApparentlyDirty())
return;
// Apple II is not page flipping...
static DWORD dwLastTime = 0;
DWORD dwCurrTime = GetTickCount();
if (!g_bFullSpeed ||
(dwCurrTime-dwLastTime >= 100)) // FullSpeed: update every 100ms
{
VideoRefreshScreen();
dwLastTime = dwCurrTime;
}
}
//===========================================================================
bool VideoGetSW80COL(void)
{
return SW_80COL ? true : false;
}
bool VideoGetSWDHIRES(void)
{
return SW_DHIRES ? true : false;
}
bool VideoGetSWHIRES(void)
{
return SW_HIRES ? true : false;
}
bool VideoGetSW80STORE(void)
{
return SW_80STORE ? true : false;
}
bool VideoGetSWMIXED(void)
{
return SW_MIXED ? true : false;
}
bool VideoGetSWPAGE2(void)
{
return SW_PAGE2 ? true : false;
}
bool VideoGetSWTEXT(void)
{
return SW_TEXT ? true : false;
}
bool VideoGetSWAltCharSet(void)
{
return g_nAltCharSetOffset == 0;
}
//===========================================================================
void VideoSetForceFullRedraw(void)
{
g_VideoForceFullRedraw = 1;
}
//===========================================================================
DWORD VideoGetSnapshot(SS_IO_Video* pSS)
{
pSS->bAltCharSet = !(g_nAltCharSetOffset == 0);
pSS->dwVidMode = g_uVideoMode;
return 0;
}
//===========================================================================
DWORD VideoSetSnapshot(SS_IO_Video* pSS)
{
g_nAltCharSetOffset = !pSS->bAltCharSet ? 0 : 256;
g_uVideoMode = pSS->dwVidMode;
return 0;
}
//===========================================================================
//
// References to Jim Sather's books are given as eg:
// UTAIIe:5-7,P3 (Understanding the Apple IIe, chapter 5, page 7, Paragraph 3)
//
WORD VideoGetScannerAddress(bool* pbVblBar_OUT, const DWORD uExecutedCycles)
{
// get video scanner position
//
int nCycles = CpuGetCyclesThisVideoFrame(uExecutedCycles);
// machine state switches
//
int nHires = (SW_HIRES && !SW_TEXT) ? 1 : 0;
int nPage2 = SW_PAGE2 ? 1 : 0;
int n80Store = SW_80STORE ? 1 : 0;
// calculate video parameters according to display standard
//
int nScanLines = bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines;
int nVSyncLine = bVideoScannerNTSC ? kNTSCVSyncLine : kPALVSyncLine;
int nScanCycles = nScanLines * kHClocks;
// calculate horizontal scanning state
//
int nHClock = (nCycles + kHPEClock) % kHClocks; // which horizontal scanning clock
int nHState = kHClock0State + nHClock; // H state bits
if (nHClock >= kHPresetClock) // check for horizontal preset
{
nHState -= 1; // correct for state preset (two 0 states)
}
int h_0 = (nHState >> 0) & 1; // get horizontal state bits
int h_1 = (nHState >> 1) & 1;
int h_2 = (nHState >> 2) & 1;
int h_3 = (nHState >> 3) & 1;
int h_4 = (nHState >> 4) & 1;
int h_5 = (nHState >> 5) & 1;
// calculate vertical scanning state
//
int nVLine = nCycles / kHClocks; // which vertical scanning line
int nVState = kVLine0State + nVLine; // V state bits
if ((nVLine >= kVPresetLine)) // check for previous vertical state preset
{
nVState -= nScanLines; // compensate for preset
}
int v_A = (nVState >> 0) & 1; // get vertical state bits
int v_B = (nVState >> 1) & 1;
int v_C = (nVState >> 2) & 1;
int v_0 = (nVState >> 3) & 1;
int v_1 = (nVState >> 4) & 1;
int v_2 = (nVState >> 5) & 1;
int v_3 = (nVState >> 6) & 1;
int v_4 = (nVState >> 7) & 1;
int v_5 = (nVState >> 8) & 1;
// calculate scanning memory address
//
if (nHires && SW_MIXED && v_4 && v_2) // HIRES TIME signal (UTAIIe:5-7,P3)
{
nHires = 0; // address is in text memory for mixed hires
}
int nAddend0 = 0x0D; // 1 1 0 1
int nAddend1 = (h_5 << 2) | (h_4 << 1) | (h_3 << 0);
int nAddend2 = (v_4 << 3) | (v_3 << 2) | (v_4 << 1) | (v_3 << 0);
int nSum = (nAddend0 + nAddend1 + nAddend2) & 0x0F; // SUM (UTAIIe:5-9)
int nAddress = 0; // build address from video scanner equations (UTAIIe:5-8,T5.1)
nAddress |= h_0 << 0; // a0
nAddress |= h_1 << 1; // a1
nAddress |= h_2 << 2; // a2
nAddress |= nSum << 3; // a3 - a6
nAddress |= v_0 << 7; // a7
nAddress |= v_1 << 8; // a8
nAddress |= v_2 << 9; // a9
int p2a = !(nPage2 && !n80Store);
int p2b = nPage2 && !n80Store;
if (nHires) // hires?
{
// Y: insert hires-only address bits
//
nAddress |= v_A << 10; // a10
nAddress |= v_B << 11; // a11
nAddress |= v_C << 12; // a12
nAddress |= p2a << 13; // a13
nAddress |= p2b << 14; // a14
}
else
{
// N: insert text-only address bits
//
nAddress |= p2a << 10; // a10
nAddress |= p2b << 11; // a11
// Apple ][ (not //e) and HBL?
//
if (IS_APPLE2 && // Apple II only (UTAIIe:I-4,#5)
!h_5 && (!h_4 || !h_3)) // HBL (UTAIIe:8-10,F8.5)
{
nAddress |= 1 << 12; // Y: a12 (add $1000 to address!)
}
}
// update VBL' state
//
if (pbVblBar_OUT != NULL)
{
*pbVblBar_OUT = !v_4 || !v_3; // VBL' = (v_4 & v_3)' (UTAIIe:5-10,#3)
}
return static_cast<WORD>(nAddress);
}
//===========================================================================
// Derived from VideoGetScannerAddress()
bool VideoGetVbl(const DWORD uExecutedCycles)
{
// get video scanner position
//
int nCycles = CpuGetCyclesThisVideoFrame(uExecutedCycles);
// calculate video parameters according to display standard
//
int nScanLines = bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines;
// calculate vertical scanning state
//
int nVLine = nCycles / kHClocks; // which vertical scanning line
int nVState = kVLine0State + nVLine; // V state bits
if ((nVLine >= kVPresetLine)) // check for previous vertical state preset
{
nVState -= nScanLines; // compensate for preset
}
int v_3 = (nVState >> 6) & 1;
int v_4 = (nVState >> 7) & 1;
// update VBL' state
//
if (v_4 & v_3) // VBL?
{
return false; // Y: VBL' is false
}
else
{
return true; // N: VBL' is true
}
}
//===========================================================================
#define SCREENSHOT_BMP 1
#define SCREENSHOT_TGA 0
// alias for nSuffixScreenShotFileName
static int g_nLastScreenShot = 0;
const int nMaxScreenShot = 999999999;
static int g_iScreenshotType;
static char *g_pLastDiskImageName = NULL;
//const int nMaxScreenShot = 2;
//===========================================================================
void Video_ResetScreenshotCounter( char *pImageName )
{
g_nLastScreenShot = 0;
g_pLastDiskImageName = pImageName;
}
//===========================================================================
void Util_MakeScreenShotFileName( char *pFinalFileName_ )
{
char sPrefixScreenShotFileName[ 256 ] = "AppleWin_ScreenShot";
// TODO: g_sScreenshotDir
char *pPrefixFileName = g_pLastDiskImageName ? g_pLastDiskImageName : sPrefixScreenShotFileName;
#if SCREENSHOT_BMP
sprintf( pFinalFileName_, "%s_%09d.bmp", pPrefixFileName, g_nLastScreenShot );
#endif
#if SCREENSHOT_TGA
sprintf( pFinalFileName_, "%s%09d.tga", pPrefixFileName, g_nLastScreenShot );
#endif
}
// Returns TRUE if file exists, else FALSE
//===========================================================================
bool Util_TestScreenShotFileName( const char *pFileName )
{
bool bFileExists = false;
FILE *pFile = fopen( pFileName, "rt" );
if (pFile)
{
fclose( pFile );
bFileExists = true;
}
return bFileExists;
}
//===========================================================================
void Video_TakeScreenShot( int iScreenShotType )
{
char sScreenShotFileName[ MAX_PATH ];
g_iScreenshotType = iScreenShotType;
// find last screenshot filename so we don't overwrite the existing user ones
bool bExists = true;
while( bExists )
{
if (g_nLastScreenShot > nMaxScreenShot) // Holy Crap! User has maxed the number of screenshots!?
{
sprintf( sScreenShotFileName, "You have more then %d screenshot filenames! They will no longer be saved.\n\nEither move some of your screenshots or increase the maximum in video.cpp\n", nMaxScreenShot );
MessageBox( g_hFrameWindow, sScreenShotFileName, "Warning", MB_OK );
g_nLastScreenShot = 0;
return;
}
Util_MakeScreenShotFileName( sScreenShotFileName );
bExists = Util_TestScreenShotFileName( sScreenShotFileName );
if( !bExists )
{
break;
}
g_nLastScreenShot++;
}
Video_SaveScreenShot( sScreenShotFileName );
g_nLastScreenShot++;
}
typedef char int8;
typedef short int16;
typedef int int32;
typedef unsigned char u8;
typedef signed short s16;
/// turn of MSVC struct member padding
#pragma pack(push,1)
struct bgra_t
{
u8 b;
u8 g;
u8 r;
u8 a; // reserved on Win32
};
struct WinBmpHeader_t
{
// BITMAPFILEHEADER // Addr Size
char nCookie[2] ; // 0x00 0x02 BM
int32 nSizeFile ; // 0x02 0x04 0 = ignore
int16 nReserved1 ; // 0x06 0x02
int16 nReserved2 ; // 0x08 0x02
int32 nOffsetData ; // 0x0A 0x04
// == 0x0D (14)
// BITMAPINFOHEADER
int32 nStructSize ; // 0x0E 0x04 biSize
int32 nWidthPixels ; // 0x12 0x04 biWidth
int32 nHeightPixels ; // 0x16 0x04 biHeight
int16 nPlanes ; // 0x1A 0x02 biPlanes
int16 nBitsPerPixel ; // 0x1C 0x02 biBitCount
int32 nCompression ; // 0x1E 0x04 biCompression 0 = BI_RGB
int32 nSizeImage ; // 0x22 0x04 0 = ignore
int32 nXPelsPerMeter ; // 0x26 0x04
int32 nYPelsPerMeter ; // 0x2A 0x04
int32 nPaletteColors ; // 0x2E 0x04
int32 nImportantColors; // 0x32 0x04
// == 0x28 (40)
// RGBQUAD
// pixelmap
};
#pragma pack(pop)
WinBmpHeader_t g_tBmpHeader;
#if SCREENSHOT_TGA
enum TargaImageType_e
{
TARGA_RGB = 2
};
struct TargaHeader_t
{ // Addr Bytes
u8 nIdBytes ; // 00 01 size of ID field that follows 18 byte header (0 usually)
u8 bHasPalette ; // 01 01
u8 iImageType ; // 02 01 type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed
s16 iPaletteFirstColor ; // 03 02
s16 nPaletteColors ; // 05 02
u8 nPaletteBitsPerEntry ; // 07 01 number of bits per palette entry 15,16,24,32
s16 nOriginX ; // 08 02 image x origin
s16 nOriginY ; // 0A 02 image y origin
s16 nWidthPixels ; // 0C 02
s16 nHeightPixels ; // 0E 02
u8 nBitsPerPixel ; // 10 01 image bits per pixel 8,16,24,32
u8 iDescriptor ; // 11 01 image descriptor bits (vh flip bits)
// pixel data...
u8 aPixelData[1] ; // rgb
};
TargaHeader_t g_tTargaHeader;
#endif // SCREENSHOT_TGA
//===========================================================================
void Video_MakeScreenShot(FILE *pFile)
{
#if SCREENSHOT_BMP
g_tBmpHeader.nCookie[ 0 ] = 'B'; // 0x42
g_tBmpHeader.nCookie[ 1 ] = 'M'; // 0x4d
g_tBmpHeader.nSizeFile = 0;
g_tBmpHeader.nReserved1 = 0;
g_tBmpHeader.nReserved2 = 0;
g_tBmpHeader.nOffsetData = sizeof(WinBmpHeader_t) + (256 * sizeof(bgra_t));
g_tBmpHeader.nStructSize = 0x28; // sizeof( WinBmpHeader_t );
g_tBmpHeader.nWidthPixels = g_iScreenshotType ? FRAMEBUFFER_W/2 :FRAMEBUFFER_W;
g_tBmpHeader.nHeightPixels = g_iScreenshotType ? FRAMEBUFFER_H/2 : FRAMEBUFFER_H;
g_tBmpHeader.nPlanes = 1;
g_tBmpHeader.nBitsPerPixel = 8;
g_tBmpHeader.nCompression = BI_RGB;
g_tBmpHeader.nSizeImage = 0;
g_tBmpHeader.nXPelsPerMeter = 0;
g_tBmpHeader.nYPelsPerMeter = 0;
g_tBmpHeader.nPaletteColors = 256;
g_tBmpHeader.nImportantColors = 0;
// char sText[256];
// sprintf( sText, "sizeof: BITMAPFILEHEADER = %d\n", sizeof(BITMAPFILEHEADER) ); // = 14
// MessageBox( g_hFrameWindow, sText, "Info 1", MB_OK );
// sprintf( sText, "sizeof: BITMAPINFOHEADER = %d\n", sizeof(BITMAPINFOHEADER) ); // = 40
// MessageBox( g_hFrameWindow, sText, "Info 2", MB_OK );
char sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize[ sizeof( WinBmpHeader_t ) == (14 + 40) ];
sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize;
// Write Header
int nLen;
fwrite( &g_tBmpHeader, sizeof( g_tBmpHeader ), 1, pFile );
// Write Palette Data
u8 *pSrc = ((u8*)g_pFramebufferinfo) + sizeof(BITMAPINFOHEADER);
nLen = g_tBmpHeader.nPaletteColors * sizeof(bgra_t); // RGBQUAD
fwrite( pSrc, nLen, 1, pFile );
pSrc += nLen;
// Write Pixel Data
// No need to use GetDibBits() since we already have http://msdn.microsoft.com/en-us/library/ms532334.aspx
// @reference: "Storing an Image" http://msdn.microsoft.com/en-us/library/ms532340(VS.85).aspx
pSrc = ((u8*)g_pFramebufferbits);
nLen = g_tBmpHeader.nWidthPixels * g_tBmpHeader.nHeightPixels * g_tBmpHeader.nBitsPerPixel / 8;
if( g_iScreenshotType == SCREENSHOT_280x192 )
{
pSrc += FRAMEBUFFER_W; // Start on odd scanline (otherwise for 50% scanline mode get an all black image!)
u8 aScanLine[ 280 ];
u8 *pDst;
// 50% Half Scan Line clears every odd scanline.
// SHIFT+PrintScreen saves only the even rows.
// NOTE: Keep in sync with _Video_RedrawScreen() & Video_MakeScreenShot()
for( int y = 0; y < FRAMEBUFFER_H/2; y++ )
{
pDst = aScanLine;
for( int x = 0; x < FRAMEBUFFER_W/2; x++ )
{
*pDst++ = pSrc[1]; // correction for left edge loss of scaled scanline [Bill Buckel, B#18928]
pSrc += 2; // skip odd pixels
}
fwrite( aScanLine, FRAMEBUFFER_W/2, 1, pFile );
pSrc += FRAMEBUFFER_W; // scan lines doubled - skip odd ones
}
}
else
{
fwrite( pSrc, nLen, 1, pFile );
}
#endif // SCREENSHOT_BMP
#if SCREENSHOT_TGA
TargaHeader_t *pHeader = &g_tTargaHeader;
memset( (void*)pHeader, 0, sizeof( TargaHeader_t ) );
pHeader->iImageType = TARGA_RGB;
pHeader->nWidthPixels = FRAMEBUFFER_W;
pHeader->nHeightPixels = FRAMEBUFFER_H;
pHeader->nBitsPerPixel = 24;
#endif // SCREENSHOT_TGA
}
//===========================================================================
void Video_SaveScreenShot( const char *pScreenShotFileName )
{
FILE *pFile = fopen( pScreenShotFileName, "wb" );
if( pFile )
{
Video_MakeScreenShot( pFile );
fclose( pFile );
}
if( g_bDisplayPrintScreenFileName )
{
MessageBox( g_hFrameWindow, pScreenShotFileName, "Screen Captured", MB_OK );
}
}
//===========================================================================
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 ),&monochrome);
if (g_eVideoType >= NUM_VIDEO_MODES)
g_eVideoType = VT_COLOR_STANDARD; // Old default: VT_COLOR_TVEMU
}
void Config_Save_Video()
{
REGSAVE(TEXT(REGVALUE_VIDEO_MODE ),g_eVideoType);
REGSAVE(TEXT(REGVALUE_VIDEO_HALF_SCAN_LINES),g_uHalfScanLines);
REGSAVE(TEXT(REGVALUE_VIDEO_MONO_COLOR ),monochrome);
}