AppleWin/source/Video.cpp

1429 lines
43 KiB
C++
Raw Normal View History

2006-02-25 20:50:29 +00:00
/*
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
2011-01-06 17:47:17 +00:00
Copyright (C) 2006-2010, Tom Charlesworth, Michael Pohoreski, Nick Westgate
2006-02-25 20:50:29 +00:00
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 "Disk.h" // DiskUpdateDriveState()
#include "Frame.h"
#include "Keyboard.h"
#include "Log.h"
#include "Memory.h"
#include "Registry.h"
#include "Video.h"
2014-12-31 22:13:36 +00:00
#include "NTSC.h"
#include "RGBMonitor.h"
#include "../resource/resource.h"
#include "Configuration/PropertySheet.h"
2016-03-21 23:48:02 +00:00
#include "YamlHelper.h"
2006-02-25 20:50:29 +00:00
2014-09-14 15:15:14 +00:00
#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)
2006-02-25 20:50:29 +00:00
2014-12-31 22:13:36 +00:00
// Globals (Public)
uint8_t *g_pFramebufferbits = NULL; // last drawn frame
int g_nAltCharSetOffset = 0; // alternate character set
// Globals (Private)
// 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
int const kVDisplayableScanLines = 192; // max displayable scanlines
static COLORREF customcolors[256]; // MONOCHROME is last custom color
static HBITMAP g_hDeviceBitmap;
static HDC g_hDeviceDC;
static LPBITMAPINFO g_pFramebufferinfo = NULL;
HBITMAP g_hLogoBitmap;
2015-01-01 17:59:58 +00:00
COLORREF g_nMonochromeRGB = RGB(0xC0,0xC0,0xC0);
2017-07-06 20:36:56 +00:00
uint32_t g_uVideoMode = VF_TEXT; // Current Video Mode (this is the last set one as it may change mid-scan line!)
2009-02-14 03:53:28 +00:00
DWORD g_eVideoType = VT_DEFAULT;
static VideoStyle_e g_eVideoStyle = VS_HALF_SCANLINES;
2009-02-14 03:53:28 +00:00
static bool g_bVideoScannerNTSC = true; // NTSC video scanning (or PAL)
static LPDIRECTDRAW g_lpDD = NULL;
2006-02-25 20:50:29 +00:00
//-------------------------------------
// NOTE: KEEP IN SYNC: VideoType_e g_aVideoChoices g_apVideoModeDesc
TCHAR g_aVideoChoices[] =
TEXT("Monochrome (Custom)\0")
TEXT("Color (RGB Monitor)\0")
TEXT("Color (NTSC Monitor)\0")
TEXT("Color TV\0")
TEXT("B&W TV\0")
TEXT("Monochrome (Amber)\0")
TEXT("Monochrome (Green)\0")
TEXT("Monochrome (White)\0")
;
// 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 Monitor (Custom)"
, "Color (RGB Monitor)"
, "Color (NTSC Monitor)"
, "Color TV"
, "B&W TV"
, "Amber Monitor"
, "Green Monitor"
, "White Monitor"
};
2006-02-25 20:50:29 +00:00
// Prototypes (Private) _____________________________________________
2006-02-25 20:50:29 +00:00
bool g_bDisplayPrintScreenFileName = false;
bool g_bShowPrintScreenWarningDialog = true;
void Util_MakeScreenShotFileName( TCHAR *pFinalFileName_, DWORD chars );
bool Util_TestScreenShotFileName( const TCHAR *pFileName );
void Video_SaveScreenShot( const VideoScreenShot_e ScreenShotType, const TCHAR *pScreenShotFileName );
2017-07-06 20:36:56 +00:00
void Video_MakeScreenShot( FILE *pFile, const VideoScreenShot_e ScreenShotType );
void videoCreateDIBSection();
2006-02-25 20:50:29 +00:00
//===========================================================================
void VideoInitialize ()
{
2014-12-31 22:13:36 +00:00
// RESET THE VIDEO MODE SWITCHES AND THE CHARACTER SET OFFSET
VideoResetState();
// LOAD THE LOGO
g_hLogoBitmap = LoadBitmap( g_hInstance, MAKEINTRESOURCE(IDB_APPLEWIN) );
// 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));
2014-12-31 22:13:36 +00:00
g_pFramebufferinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
g_pFramebufferinfo->bmiHeader.biWidth = GetFrameBufferWidth();
g_pFramebufferinfo->bmiHeader.biHeight = GetFrameBufferHeight();
2014-12-31 22:13:36 +00:00
g_pFramebufferinfo->bmiHeader.biPlanes = 1;
g_pFramebufferinfo->bmiHeader.biBitCount = 32;
g_pFramebufferinfo->bmiHeader.biCompression = BI_RGB;
g_pFramebufferinfo->bmiHeader.biClrUsed = 0;
2015-01-03 05:01:01 +00:00
videoCreateDIBSection();
}
2006-02-25 20:50:29 +00:00
//===========================================================================
//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//
//===========================================================================
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;
2014-09-14 15:15:14 +00:00
g_uVideoMode = VF_TEXT;
2006-02-25 20:50:29 +00:00
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();
2006-02-25 20:50:29 +00:00
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;
2014-09-14 15:15:14 +00:00
g_uVideoMode = VF_HIRES;
2006-02-25 20:50:29 +00:00
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();
2006-02-25 20:50:29 +00:00
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
DWORD totalmhz10[2] = {0,0}; // bVideoUpdate & !bVideoUpdate
for (UINT i=0; i<2; i++)
{
CpuSetupBenchmark();
milliseconds = GetTickCount();
while (GetTickCount() == milliseconds) ;
milliseconds = GetTickCount();
do {
CpuExecute(100000, i==0 ? true : false);
totalmhz10[i]++;
} while (GetTickCount() - milliseconds < 1000);
}
2006-02-25 20:50:29 +00:00
// 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))
2006-05-14 00:44:38 +00:00
if (MessageBox(g_hFrameWindow,
2006-02-25 20:50:29 +00:00
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, true);
2006-02-25 20:50:29 +00:00
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);
2006-05-14 00:44:38 +00:00
MessageBox(g_hFrameWindow,
2006-02-25 20:50:29 +00:00
outstr,
TEXT("Benchmarks"),
MB_ICONINFORMATION | MB_SETFOREGROUND);
}
else
2006-05-14 00:44:38 +00:00
MessageBox(g_hFrameWindow,
2006-02-25 20:50:29 +00:00
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, true);
2006-02-25 20:50:29 +00:00
cycles -= executedcycles;
2019-04-14 16:01:49 +00:00
sg_Disk2Card.UpdateDriveState(executedcycles);
JoyUpdateButtonLatch(executedcycles);
}
2006-02-25 20:50:29 +00:00
}
if (cycle & 1)
FillMemory(mem+0x2000,0x2000,0xAA);
else
CopyMemory(mem+0x2000,mem+((cycle & 2) ? 0x4000 : 0x6000),0x2000);
VideoRedrawScreen();
2006-02-25 20:50:29 +00:00
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 (video update)\n")
TEXT("Pure CPU MHz:\t%u.%u%s (full-speed)\n\n")
2006-02-25 20:50:29 +00:00
TEXT("EXPECTED AVERAGE VIDEO GAME\n")
TEXT("PERFORMANCE: %u FPS"),
(unsigned)totalhiresfps,
(unsigned)totaltextfps,
(unsigned)(totalmhz10[0] / 10), (unsigned)(totalmhz10[0] % 10), (LPCTSTR)(IS_APPLE2 ? TEXT(" (6502)") : TEXT("")),
(unsigned)(totalmhz10[1] / 10), (unsigned)(totalmhz10[1] % 10), (LPCTSTR)(IS_APPLE2 ? TEXT(" (6502)") : TEXT("")),
2006-02-25 20:50:29 +00:00
(unsigned)realisticfps);
2006-05-14 00:44:38 +00:00
MessageBox(g_hFrameWindow,
2006-02-25 20:50:29 +00:00
outstr,
TEXT("Benchmarks"),
MB_ICONINFORMATION | MB_SETFOREGROUND);
}
2014-12-31 22:13:36 +00:00
// This is called from PageConfig
2006-02-25 20:50:29 +00:00
//===========================================================================
2014-12-31 22:13:36 +00:00
void VideoChooseMonochromeColor ()
{
CHOOSECOLOR cc;
ZeroMemory(&cc,sizeof(CHOOSECOLOR));
cc.lStructSize = sizeof(CHOOSECOLOR);
cc.hwndOwner = g_hFrameWindow;
2015-01-01 17:59:58 +00:00
cc.rgbResult = g_nMonochromeRGB;
cc.lpCustColors = customcolors + 1;
cc.Flags = CC_RGBINIT | CC_SOLIDCOLOR;
if (ChooseColor(&cc))
{
2015-01-01 17:59:58 +00:00
g_nMonochromeRGB = cc.rgbResult;
VideoReinitialize();
if ((g_nAppMode != MODE_LOGO) && (g_nAppMode != MODE_DEBUG))
{
VideoRedrawScreen();
}
Config_Save_Video();
}
2006-02-25 20:50:29 +00:00
}
//===========================================================================
void VideoDestroy () {
// DESTROY BUFFERS
2008-08-25 00:36:48 +00:00
VirtualFree(g_pFramebufferinfo,0,MEM_RELEASE);
g_pFramebufferinfo = NULL;
2006-02-25 20:50:29 +00:00
// DESTROY FRAME BUFFER
DeleteDC(g_hDeviceDC);
DeleteObject(g_hDeviceBitmap);
g_hDeviceDC = (HDC)0;
g_hDeviceBitmap = (HBITMAP)0;
2006-02-25 20:50:29 +00:00
// DESTROY LOGO
if (g_hLogoBitmap) {
DeleteObject(g_hLogoBitmap);
g_hLogoBitmap = (HBITMAP)0;
2006-02-25 20:50:29 +00:00
}
}
//===========================================================================
static 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 );
}
2006-02-25 20:50:29 +00:00
//===========================================================================
void VideoDisplayLogo ()
{
int nLogoX = 0, nLogoY = 0;
int scale = GetViewportScale();
HDC hFrameDC = FrameGetDC();
2006-02-25 20:50:29 +00:00
// DRAW THE LOGO
SelectObject(hFrameDC, GetStockObject(NULL_PEN));
if (g_hLogoBitmap)
{
BITMAP bm;
if (GetObject(g_hLogoBitmap, sizeof(bm), &bm))
{
nLogoX = (g_nViewportCX - scale*bm.bmWidth )/2;
nLogoY = (g_nViewportCY - scale*bm.bmHeight)/2;
if( IsFullScreen() )
{
2016-07-26 21:33:45 +00:00
nLogoX += GetFullScreenOffsetX();
nLogoY += GetFullScreenOffsetY();
}
VideoDrawLogoBitmap( hFrameDC, nLogoX, nLogoY, bm.bmWidth, bm.bmHeight, scale );
}
}
2006-02-25 20:50:29 +00:00
// DRAW THE VERSION NUMBER
2015-01-03 22:13:55 +00:00
TCHAR sFontName[] = TEXT("Arial");
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,
2015-01-03 22:13:55 +00:00
sFontName );
SelectObject(hFrameDC,font);
SetTextAlign(hFrameDC,TA_RIGHT | TA_TOP);
SetBkMode(hFrameDC,TRANSPARENT);
2006-02-25 20:50:29 +00:00
TCHAR szVersion[ 64 ];
StringCbPrintf(szVersion, 64, "Version %s", VERSIONSTRING);
2016-07-26 21:33:45 +00:00
int xoff = GetFullScreenOffsetX(), yoff = GetFullScreenOffsetY();
#define DRAWVERSION(x,y,c) \
SetTextColor(hFrameDC,c); \
TextOut(hFrameDC, \
scale*540+x+xoff,scale*358+y+yoff, \
szVersion, \
strlen(szVersion));
2006-02-25 20:50:29 +00:00
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));
}
2006-02-25 20:50:29 +00:00
#if _DEBUG
StringCbPrintf(szVersion, 64, "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
2006-02-25 20:50:29 +00:00
#undef DRAWVERSION
DeleteObject(font);
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2014-06-27 21:43:25 +00:00
void VideoRedrawScreenDuringFullSpeed(DWORD dwCyclesThisFrame, bool bInit /*=false*/)
{
static DWORD dwFullSpeedStartTime = 0;
// static bool bValid = false;
if (bInit)
{
// Just entered full-speed mode
// bValid = false;
dwFullSpeedStartTime = GetTickCount();
return;
}
DWORD dwFullSpeedDuration = GetTickCount() - dwFullSpeedStartTime;
if (dwFullSpeedDuration <= 16) // Only update after every realtime ~17ms of *continuous* full-speed
return;
dwFullSpeedStartTime += dwFullSpeedDuration;
//
#if 0
static BYTE text_main[1024*2] = {0}; // page1 & 2
static BYTE text_aux[1024*2] = {0}; // page1 & 2
static BYTE hgr_main[8192*2] = {0}; // page1 & 2
static BYTE hgr_aux[8192*2] = {0}; // page1 & 2
bool bRedraw = true; // Always redraw for bValid==false (ie. just entered full-speed mode)
if (bValid)
{
if ((g_uVideoMode&(VF_DHIRES|VF_HIRES|VF_TEXT|VF_MIXED)) == VF_HIRES)
{
// HIRES (not MIXED) - eg. AZTEC.DSK
if ((g_uVideoMode&VF_PAGE2) == 0)
bRedraw = memcmp(&hgr_main[0x0000], MemGetMainPtr(0x2000), 8192) != 0;
else
bRedraw = memcmp(&hgr_main[0x2000], MemGetMainPtr(0x4000), 8192) != 0;
}
else
{
bRedraw =
(memcmp(text_main, MemGetMainPtr(0x400), sizeof(text_main)) != 0) ||
(memcmp(text_aux, MemGetAuxPtr(0x400), sizeof(text_aux)) != 0) ||
(memcmp(hgr_main, MemGetMainPtr(0x2000), sizeof(hgr_main)) != 0) ||
(memcmp(hgr_aux, MemGetAuxPtr(0x2000), sizeof(hgr_aux)) != 0);
}
}
if (bRedraw)
VideoRedrawScreenAfterFullSpeed(dwCyclesThisFrame);
// Copy all video memory (+ screen holes)
memcpy(text_main, MemGetMainPtr(0x400), sizeof(text_main));
memcpy(text_aux, MemGetAuxPtr(0x400), sizeof(text_aux));
memcpy(hgr_main, MemGetMainPtr(0x2000), sizeof(hgr_main));
memcpy(hgr_aux, MemGetAuxPtr(0x2000), sizeof(hgr_aux));
bValid = true;
#else
VideoRedrawScreenAfterFullSpeed(dwCyclesThisFrame);
#endif
}
//===========================================================================
void VideoRedrawScreenAfterFullSpeed(DWORD dwCyclesThisFrame)
{
NTSC_VideoClockResync(dwCyclesThisFrame);
VideoRedrawScreen(); // Better (no flicker) than using: NTSC_VideoReinitialize() or VideoReinitialize()
}
//===========================================================================
void VideoRedrawScreen (void)
2011-01-07 09:23:16 +00:00
{
// NB. Can't rely on g_uVideoMode being non-zero (ie. so it can double up as a flag) since 'GR,PAGE1,non-mixed' mode == 0x00.
VideoRefreshScreen( g_uVideoMode, true );
2006-02-25 20:50:29 +00:00
}
2008-08-25 05:25:27 +00:00
//===========================================================================
void VideoRefreshScreen ( uint32_t uRedrawWholeScreenVideoMode /* =0*/, bool bRedrawWholeScreen /* =false*/ )
2011-01-07 09:23:16 +00:00
{
if (bRedrawWholeScreen || g_nAppMode == MODE_PAUSED)
{
// uVideoModeForWholeScreen set if:
// . MODE_DEBUG : always
// . MODE_RUNNING : called from VideoRedrawScreen(), eg. during full-speed
if (bRedrawWholeScreen)
NTSC_SetVideoMode( uRedrawWholeScreenVideoMode );
NTSC_VideoRedrawWholeScreen();
// MODE_DEBUG|PAUSED: Need to refresh a 2nd time if changing video-type, otherwise could have residue from prev image!
// . eg. Amber -> B&W TV
if (g_nAppMode == MODE_DEBUG || g_nAppMode == MODE_PAUSED)
NTSC_VideoRedrawWholeScreen();
}
HDC hFrameDC = FrameGetDC();
2015-01-03 22:13:55 +00:00
2014-12-31 22:13:36 +00:00
if (hFrameDC)
{
int xSrc = GetFrameBufferBorderWidth();
int ySrc = GetFrameBufferBorderHeight();
int xdest = IsFullScreen() ? GetFullScreenOffsetX() : 0;
int ydest = IsFullScreen() ? GetFullScreenOffsetY() : 0;
int wdest = g_nViewportCX;
int hdest = g_nViewportCY;
SetStretchBltMode(hFrameDC, COLORONCOLOR);
StretchBlt(
hFrameDC,
xdest, ydest,
wdest, hdest,
g_hDeviceDC,
xSrc, ySrc,
GetFrameBufferBorderlessWidth(), GetFrameBufferBorderlessHeight(),
SRCCOPY);
2014-12-31 22:13:36 +00:00
}
#ifdef NO_DIRECT_X
#else
//if (g_lpDD) g_lpDD->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, NULL);
#endif // NO_DIRECT_X
GdiFlush();
2008-08-25 05:25:27 +00:00
}
2006-02-25 20:50:29 +00:00
//===========================================================================
void VideoReinitialize (bool bInitVideoScannerAddress /*= true*/)
2011-01-08 21:29:27 +00:00
{
NTSC_VideoReinitialize( g_dwCyclesThisFrame, bInitVideoScannerAddress );
NTSC_VideoInitAppleType();
2014-12-31 22:13:36 +00:00
NTSC_SetVideoStyle();
NTSC_SetVideoTextMode( g_uVideoMode & VF_80COL ? 80 : 40 );
NTSC_SetVideoMode( g_uVideoMode ); // Pre-condition: g_nVideoClockHorz (derived from g_dwCyclesThisFrame)
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2011-01-08 21:29:27 +00:00
void VideoResetState ()
{
g_nAltCharSetOffset = 0;
2014-09-14 15:15:14 +00:00
g_uVideoMode = VF_TEXT;
NTSC_SetVideoTextMode( 40 );
NTSC_SetVideoMode( g_uVideoMode );
2006-02-25 20:50:29 +00:00
RGB_ResetState();
}
2011-01-08 21:29:27 +00:00
2006-02-25 20:50:29 +00:00
//===========================================================================
BYTE VideoSetMode(WORD, WORD address, BYTE write, BYTE, ULONG uExecutedCycles)
{
2011-01-08 21:29:27 +00:00
address &= 0xFF;
const uint32_t oldVideoMode = g_uVideoMode;
Simplified and moved main-loop video update logic into Video.cpp. Removed complex case below for: . VideoHasRefreshed(), 'anyupdates' . VideoCheckPage() Detailed notes below. --- Video updates in ContinueExecution() loop: 'anyupdates' gets set if there were any page-flip(s) in last ~17030 cycles: anyupdates |= VideoHasRefreshed(); ie. VideoRefreshScreen() was called outside of this loop. If there's been a call to VideoRefreshScreen() outside of this loop, and then the video framebuffer gets written to, ie. VideoApparentlyDirty() returns TRUE, then don't call VideoRefreshScreen() from this loop for 3 frames. (If a VideoRefreshScreen() is called outside of this loop then restart the 3 frame count.) So.. if the game is flipping, the VideoApparentlyDirty() will return FALSE (since game writes to other framebuffer). if the game is not flipping, then VideoHasRefreshed() will return FALSE (since no flips occur). Therefore this complex case above probably only arises at a boundary eg. when the game is transitioning between these 2 modes, and so if the emulator does the very occasional screen update in this main loop, it is of no consequence. (I guess this extra logic was to throttle video updates on very old slow machines) --- VideoCheckPage(BOOL bForce) was called twice in main-loop: UnexpectedPage if g_bVideoDisplayPage2 != SW_PAGE2 Once each time through the loop (ie. every 1ms), with bForce=0 if UnexpectedPage && >500ms since last flip then VideoRefreshScreen() Once each video frame (ie. ~17030 cycles) when not flipping, with bForce=1 if UnexpectedPage then VideoRefreshScreen() Basically this was all about supporting FullSpeed mode, and limiting the calls to VideoRefreshScreen().
2014-09-13 21:22:27 +00:00
switch (address)
{
2016-03-21 23:48:02 +00:00
case 0x00: g_uVideoMode &= ~VF_80STORE; break;
case 0x01: g_uVideoMode |= VF_80STORE; break;
2015-01-02 06:03:34 +00:00
case 0x0C: if (!IS_APPLE2){g_uVideoMode &= ~VF_80COL; NTSC_SetVideoTextMode(40);}; break;
case 0x0D: if (!IS_APPLE2){g_uVideoMode |= VF_80COL; NTSC_SetVideoTextMode(80);}; break;
2011-01-08 21:29:27 +00:00
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
2014-09-14 15:15:14 +00:00
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;
2011-01-08 21:29:27 +00:00
}
Simplified and moved main-loop video update logic into Video.cpp. Removed complex case below for: . VideoHasRefreshed(), 'anyupdates' . VideoCheckPage() Detailed notes below. --- Video updates in ContinueExecution() loop: 'anyupdates' gets set if there were any page-flip(s) in last ~17030 cycles: anyupdates |= VideoHasRefreshed(); ie. VideoRefreshScreen() was called outside of this loop. If there's been a call to VideoRefreshScreen() outside of this loop, and then the video framebuffer gets written to, ie. VideoApparentlyDirty() returns TRUE, then don't call VideoRefreshScreen() from this loop for 3 frames. (If a VideoRefreshScreen() is called outside of this loop then restart the 3 frame count.) So.. if the game is flipping, the VideoApparentlyDirty() will return FALSE (since game writes to other framebuffer). if the game is not flipping, then VideoHasRefreshed() will return FALSE (since no flips occur). Therefore this complex case above probably only arises at a boundary eg. when the game is transitioning between these 2 modes, and so if the emulator does the very occasional screen update in this main loop, it is of no consequence. (I guess this extra logic was to throttle video updates on very old slow machines) --- VideoCheckPage(BOOL bForce) was called twice in main-loop: UnexpectedPage if g_bVideoDisplayPage2 != SW_PAGE2 Once each time through the loop (ie. every 1ms), with bForce=0 if UnexpectedPage && >500ms since last flip then VideoRefreshScreen() Once each video frame (ie. ~17030 cycles) when not flipping, with bForce=1 if UnexpectedPage then VideoRefreshScreen() Basically this was all about supporting FullSpeed mode, and limiting the calls to VideoRefreshScreen().
2014-09-13 21:22:27 +00:00
if (!IS_APPLE2)
RGB_SetVideoMode(address);
bool delay = true;
if ((oldVideoMode ^ g_uVideoMode) & VF_PAGE2)
delay = false; // PAGE2 flag changed state, so no 1 cycle delay (GH#656)
NTSC_SetVideoMode( g_uVideoMode, delay );
2011-01-08 21:29:27 +00:00
return MemReadFloatingBus(uExecutedCycles);
2006-02-25 20:50:29 +00:00
}
//===========================================================================
Simplified and moved main-loop video update logic into Video.cpp. Removed complex case below for: . VideoHasRefreshed(), 'anyupdates' . VideoCheckPage() Detailed notes below. --- Video updates in ContinueExecution() loop: 'anyupdates' gets set if there were any page-flip(s) in last ~17030 cycles: anyupdates |= VideoHasRefreshed(); ie. VideoRefreshScreen() was called outside of this loop. If there's been a call to VideoRefreshScreen() outside of this loop, and then the video framebuffer gets written to, ie. VideoApparentlyDirty() returns TRUE, then don't call VideoRefreshScreen() from this loop for 3 frames. (If a VideoRefreshScreen() is called outside of this loop then restart the 3 frame count.) So.. if the game is flipping, the VideoApparentlyDirty() will return FALSE (since game writes to other framebuffer). if the game is not flipping, then VideoHasRefreshed() will return FALSE (since no flips occur). Therefore this complex case above probably only arises at a boundary eg. when the game is transitioning between these 2 modes, and so if the emulator does the very occasional screen update in this main loop, it is of no consequence. (I guess this extra logic was to throttle video updates on very old slow machines) --- VideoCheckPage(BOOL bForce) was called twice in main-loop: UnexpectedPage if g_bVideoDisplayPage2 != SW_PAGE2 Once each time through the loop (ie. every 1ms), with bForce=0 if UnexpectedPage && >500ms since last flip then VideoRefreshScreen() Once each video frame (ie. ~17030 cycles) when not flipping, with bForce=1 if UnexpectedPage then VideoRefreshScreen() Basically this was all about supporting FullSpeed mode, and limiting the calls to VideoRefreshScreen().
2014-09-13 21:22:27 +00:00
bool VideoGetSW80COL(void)
2006-02-25 20:50:29 +00:00
{
return SW_80COL ? true : false;
}
Simplified and moved main-loop video update logic into Video.cpp. Removed complex case below for: . VideoHasRefreshed(), 'anyupdates' . VideoCheckPage() Detailed notes below. --- Video updates in ContinueExecution() loop: 'anyupdates' gets set if there were any page-flip(s) in last ~17030 cycles: anyupdates |= VideoHasRefreshed(); ie. VideoRefreshScreen() was called outside of this loop. If there's been a call to VideoRefreshScreen() outside of this loop, and then the video framebuffer gets written to, ie. VideoApparentlyDirty() returns TRUE, then don't call VideoRefreshScreen() from this loop for 3 frames. (If a VideoRefreshScreen() is called outside of this loop then restart the 3 frame count.) So.. if the game is flipping, the VideoApparentlyDirty() will return FALSE (since game writes to other framebuffer). if the game is not flipping, then VideoHasRefreshed() will return FALSE (since no flips occur). Therefore this complex case above probably only arises at a boundary eg. when the game is transitioning between these 2 modes, and so if the emulator does the very occasional screen update in this main loop, it is of no consequence. (I guess this extra logic was to throttle video updates on very old slow machines) --- VideoCheckPage(BOOL bForce) was called twice in main-loop: UnexpectedPage if g_bVideoDisplayPage2 != SW_PAGE2 Once each time through the loop (ie. every 1ms), with bForce=0 if UnexpectedPage && >500ms since last flip then VideoRefreshScreen() Once each video frame (ie. ~17030 cycles) when not flipping, with bForce=1 if UnexpectedPage then VideoRefreshScreen() Basically this was all about supporting FullSpeed mode, and limiting the calls to VideoRefreshScreen().
2014-09-13 21:22:27 +00:00
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)
{
2016-04-04 21:38:01 +00:00
return g_nAltCharSetOffset != 0;
Simplified and moved main-loop video update logic into Video.cpp. Removed complex case below for: . VideoHasRefreshed(), 'anyupdates' . VideoCheckPage() Detailed notes below. --- Video updates in ContinueExecution() loop: 'anyupdates' gets set if there were any page-flip(s) in last ~17030 cycles: anyupdates |= VideoHasRefreshed(); ie. VideoRefreshScreen() was called outside of this loop. If there's been a call to VideoRefreshScreen() outside of this loop, and then the video framebuffer gets written to, ie. VideoApparentlyDirty() returns TRUE, then don't call VideoRefreshScreen() from this loop for 3 frames. (If a VideoRefreshScreen() is called outside of this loop then restart the 3 frame count.) So.. if the game is flipping, the VideoApparentlyDirty() will return FALSE (since game writes to other framebuffer). if the game is not flipping, then VideoHasRefreshed() will return FALSE (since no flips occur). Therefore this complex case above probably only arises at a boundary eg. when the game is transitioning between these 2 modes, and so if the emulator does the very occasional screen update in this main loop, it is of no consequence. (I guess this extra logic was to throttle video updates on very old slow machines) --- VideoCheckPage(BOOL bForce) was called twice in main-loop: UnexpectedPage if g_bVideoDisplayPage2 != SW_PAGE2 Once each time through the loop (ie. every 1ms), with bForce=0 if UnexpectedPage && >500ms since last flip then VideoRefreshScreen() Once each video frame (ie. ~17030 cycles) when not flipping, with bForce=1 if UnexpectedPage then VideoRefreshScreen() Basically this was all about supporting FullSpeed mode, and limiting the calls to VideoRefreshScreen().
2014-09-13 21:22:27 +00:00
}
//===========================================================================
#define SS_YAML_KEY_ALT_CHARSET "Alt Char Set"
#define SS_YAML_KEY_VIDEO_MODE "Video Mode"
#define SS_YAML_KEY_CYCLES_THIS_FRAME "Cycles This Frame"
#define SS_YAML_KEY_VIDEO_REFRESH_RATE "Video Refresh Rate"
2016-03-21 23:48:02 +00:00
static std::string VideoGetSnapshotStructName(void)
2006-02-25 20:50:29 +00:00
{
2016-03-21 23:48:02 +00:00
static const std::string name("Video");
return name;
}
void VideoSaveSnapshot(YamlSaveHelper& yamlSaveHelper)
{
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", VideoGetSnapshotStructName().c_str());
yamlSaveHelper.SaveBool(SS_YAML_KEY_ALT_CHARSET, g_nAltCharSetOffset ? true : false);
yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_VIDEO_MODE, g_uVideoMode);
yamlSaveHelper.SaveUint(SS_YAML_KEY_CYCLES_THIS_FRAME, g_dwCyclesThisFrame);
yamlSaveHelper.SaveUint(SS_YAML_KEY_VIDEO_REFRESH_RATE, (UINT)GetVideoRefreshRate());
2016-03-21 23:48:02 +00:00
}
void VideoLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
2016-03-21 23:48:02 +00:00
{
if (!yamlLoadHelper.GetSubMap(VideoGetSnapshotStructName()))
return;
if (version >= 4)
{
VideoRefreshRate_e rate = (VideoRefreshRate_e)yamlLoadHelper.LoadUint(SS_YAML_KEY_VIDEO_REFRESH_RATE);
SetVideoRefreshRate(rate); // Trashes: g_dwCyclesThisFrame
SetCurrentCLK6502();
}
g_nAltCharSetOffset = yamlLoadHelper.LoadBool(SS_YAML_KEY_ALT_CHARSET) ? 256 : 0;
g_uVideoMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_VIDEO_MODE);
g_dwCyclesThisFrame = yamlLoadHelper.LoadUint(SS_YAML_KEY_CYCLES_THIS_FRAME);
2016-03-21 23:48:02 +00:00
yamlLoadHelper.PopMap();
2006-02-25 20:50:29 +00:00
}
//===========================================================================
//
// 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(DWORD nCycles, VideoScanner_e videoScannerAddr /*= VS_FullAddr*/)
{
// machine state switches
//
bool bHires = VideoGetSWHIRES() && !VideoGetSWTEXT();
bool bPage2 = VideoGetSWPAGE2();
bool b80Store = VideoGetSW80STORE();
// calculate video parameters according to display standard
//
const int kScanLines = g_bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines;
const int kScanCycles = kScanLines * kHClocks;
_ASSERT(nCycles < (UINT)kScanCycles);
nCycles %= kScanCycles;
// 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 (UTAIIe:3-15,T3.2)
//
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 -= kScanLines; // 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 (bHires && SW_MIXED && v_4 && v_2) // HIRES TIME signal (UTAIIe:5-7,P3)
{
bHires = false; // 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)
WORD nAddressH = 0; // build address from video scanner equations (UTAIIe:5-8,T5.1)
nAddressH |= h_0 << 0; // a0
nAddressH |= h_1 << 1; // a1
nAddressH |= h_2 << 2; // a2
nAddressH |= nSum << 3; // a3 - a6
if (!bHires)
{
// 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)
{
nAddressH |= 1 << 12; // Y: a12 (add $1000 to address!)
}
}
WORD nAddressV = 0;
nAddressV |= v_0 << 7; // a7
nAddressV |= v_1 << 8; // a8
nAddressV |= v_2 << 9; // a9
int p2a = !(bPage2 && !b80Store) ? 1 : 0;
int p2b = (bPage2 && !b80Store) ? 1 : 0;
WORD nAddressP = 0; // Page bits
if (bHires) // hires?
{
// Y: insert hires-only address bits
//
nAddressV |= v_A << 10; // a10
nAddressV |= v_B << 11; // a11
nAddressV |= v_C << 12; // a12
nAddressP |= p2a << 13; // a13
nAddressP |= p2b << 14; // a14
}
else
{
// N: insert text-only address bits
//
nAddressP |= p2a << 10; // a10
nAddressP |= p2b << 11; // a11
}
// VBL' = v_4' | v_3' = (v_4 & v_3)' (UTAIIe:5-10,#3), (UTAIIe:3-15,T3.2)
if (videoScannerAddr == VS_PartialAddrH)
return nAddressH;
if (videoScannerAddr == VS_PartialAddrV)
return nAddressV;
return nAddressP | nAddressV | nAddressH;
}
//===========================================================================
2019-06-29 16:05:07 +00:00
// TODO: Consider replacing simply with: return g_nVideoClockVert < kVDisplayableScanLines
bool VideoGetVblBar(const DWORD uExecutedCycles)
{
// get video scanner position
int nCycles = CpuGetCyclesThisVideoFrame(uExecutedCycles);
// calculate video parameters according to display standard
const int kScanLines = g_bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines;
const int kScanCycles = kScanLines * kHClocks;
nCycles %= kScanCycles;
// VBL'
return nCycles < kVDisplayableScanLines * kHClocks;
}
//===========================================================================
#define MAX_DRAW_DEVICES 10
static char *draw_devices[MAX_DRAW_DEVICES];
static GUID draw_device_guid[MAX_DRAW_DEVICES];
static int num_draw_devices = 0;
static BOOL CALLBACK DDEnumProc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext)
{
int i = num_draw_devices;
if (i == MAX_DRAW_DEVICES)
return TRUE;
if (lpGUID != NULL)
memcpy(&draw_device_guid[i], lpGUID, sizeof (GUID));
draw_devices[i] = _strdup(lpszDesc);
if (g_fh) fprintf(g_fh, "%d: %s - %s\n",i,lpszDesc,lpszDrvName);
num_draw_devices++;
return TRUE;
}
bool DDInit(void)
{
#ifdef NO_DIRECT_X
return false;
#else
HRESULT hr = DirectDrawEnumerate((LPDDENUMCALLBACK)DDEnumProc, NULL);
if (FAILED(hr))
{
LogFileOutput("DSEnumerate failed (%08X)\n", hr);
return false;
}
LogFileOutput("Number of draw devices = %d\n", num_draw_devices);
bool bCreatedOK = false;
for (int x=0; x<num_draw_devices; x++)
{
hr = DirectDrawCreate(&draw_device_guid[x], &g_lpDD, NULL);
if (SUCCEEDED(hr))
{
LogFileOutput("DSCreate succeeded for draw device #%d\n", x);
bCreatedOK = true;
break;
}
LogFileOutput("DSCreate failed for draw device #%d (%08X)\n", x, hr);
}
if (!bCreatedOK)
{
LogFileOutput("DSCreate failed for all draw devices\n");
return false;
}
return true;
#endif // NO_DIRECT_X
}
// From SoundCore.h
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
void DDUninit(void)
{
SAFE_RELEASE(g_lpDD);
}
#undef SAFE_RELEASE
//===========================================================================
2008-08-25 00:36:48 +00:00
#define SCREENSHOT_BMP 1
#define SCREENSHOT_TGA 0
2008-08-31 04:31:35 +00:00
static int g_nLastScreenShot = 0;
const int nMaxScreenShot = 999999999;
static std::string g_pLastDiskImageName;
2008-08-31 04:31:35 +00:00
//===========================================================================
void Video_ResetScreenshotCounter( const std::string & pImageName )
2008-08-31 04:31:35 +00:00
{
g_nLastScreenShot = 0;
g_pLastDiskImageName = pImageName;
}
//===========================================================================
void Util_MakeScreenShotFileName( TCHAR *pFinalFileName_, DWORD chars )
{
const std::string sPrefixScreenShotFileName = "AppleWin_ScreenShot";
// TODO: g_sScreenshotDir
const std::string pPrefixFileName = !g_pLastDiskImageName.empty() ? g_pLastDiskImageName : sPrefixScreenShotFileName;
2008-08-25 00:36:48 +00:00
#if SCREENSHOT_BMP
StringCbPrintf( pFinalFileName_, chars, TEXT("%s_%09d.bmp"), pPrefixFileName.c_str(), g_nLastScreenShot );
2008-08-25 00:36:48 +00:00
#endif
#if SCREENSHOT_TGA
StringCbPrintf( pFinalFileName_, chars, TEXT("%s%09d.tga"), pPrefixFileName.c_str(), g_nLastScreenShot );
2008-08-25 00:36:48 +00:00
#endif
}
// Returns TRUE if file exists, else FALSE
//===========================================================================
bool Util_TestScreenShotFileName( const TCHAR *pFileName )
{
bool bFileExists = false;
FILE *pFile = fopen( pFileName, "rt" );
if (pFile)
{
fclose( pFile );
bFileExists = true;
}
return bFileExists;
}
//===========================================================================
2017-07-06 20:36:56 +00:00
void Video_TakeScreenShot( const VideoScreenShot_e ScreenShotType )
{
TCHAR sScreenShotFileName[ MAX_PATH ];
// find last screenshot filename so we don't overwrite the existing user ones
bool bExists = true;
while( bExists )
{
2008-08-31 04:31:35 +00:00
if (g_nLastScreenShot > nMaxScreenShot) // Holy Crap! User has maxed the number of screenshots!?
{
TCHAR msg[512];
StringCbPrintf( msg, 512, "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, msg, "Warning", MB_OK );
2008-08-31 04:31:35 +00:00
g_nLastScreenShot = 0;
return;
}
Util_MakeScreenShotFileName( sScreenShotFileName, MAX_PATH );
bExists = Util_TestScreenShotFileName( sScreenShotFileName );
if( !bExists )
{
break;
}
2008-08-31 04:31:35 +00:00
g_nLastScreenShot++;
}
Video_SaveScreenShot( ScreenShotType, sScreenShotFileName );
2008-08-31 04:31:35 +00:00
g_nLastScreenShot++;
}
void Video_RedrawAndTakeScreenShot( const TCHAR* pScreenshotFilename )
{
_ASSERT(pScreenshotFilename);
if (!pScreenshotFilename)
return;
VideoRedrawScreen();
Video_SaveScreenShot( SCREENSHOT_560x384, pScreenshotFilename );
}
WinBmpHeader_t g_tBmpHeader;
2008-08-25 00:36:48 +00:00
#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_SetBitmapHeader( WinBmpHeader_t *pBmp, int nWidth, int nHeight, int nBitsPerPixel )
{
2008-08-25 00:36:48 +00:00
#if SCREENSHOT_BMP
pBmp->nCookie[ 0 ] = 'B'; // 0x42
pBmp->nCookie[ 1 ] = 'M'; // 0x4d
pBmp->nSizeFile = 0;
pBmp->nReserved1 = 0;
pBmp->nReserved2 = 0;
#if VIDEO_SCREENSHOT_PALETTE
pBmp->nOffsetData = sizeof(WinBmpHeader_t) + (256 * sizeof(bgra_t));
#else
pBmp->nOffsetData = sizeof(WinBmpHeader_t);
#endif
pBmp->nStructSize = 0x28; // sizeof( WinBmpHeader_t );
pBmp->nWidthPixels = nWidth;
pBmp->nHeightPixels = nHeight;
pBmp->nPlanes = 1;
#if VIDEO_SCREENSHOT_PALETTE
pBmp->nBitsPerPixel = 8;
#else
pBmp->nBitsPerPixel = nBitsPerPixel;
#endif
pBmp->nCompression = BI_RGB; // none
pBmp->nSizeImage = 0;
pBmp->nXPelsPerMeter = 0;
pBmp->nYPelsPerMeter = 0;
#if VIDEO_SCREENSHOT_PALETTE
pBmp->nPaletteColors = 256;
#else
pBmp->nPaletteColors = 0;
#endif
pBmp->nImportantColors = 0;
}
//===========================================================================
2017-07-06 20:36:56 +00:00
static void Video_MakeScreenShot(FILE *pFile, const VideoScreenShot_e ScreenShotType)
{
WinBmpHeader_t *pBmp = &g_tBmpHeader;
Video_SetBitmapHeader(
pBmp,
ScreenShotType == SCREENSHOT_280x192 ? GetFrameBufferBorderlessWidth()/2 : GetFrameBufferBorderlessWidth(),
ScreenShotType == SCREENSHOT_280x192 ? GetFrameBufferBorderlessHeight()/2 : GetFrameBufferBorderlessHeight(),
32
);
2008-08-25 00:36:48 +00:00
// char sText[256];
// sprintf( sText, "sizeof: BITMAPFILEHEADER = %d\n", sizeof(BITMAPFILEHEADER) ); // = 14
// MessageBox( g_hFrameWindow, sText, "Info 1", MB_OK );
2008-08-25 00:36:48 +00:00
// sprintf( sText, "sizeof: BITMAPINFOHEADER = %d\n", sizeof(BITMAPINFOHEADER) ); // = 40
// MessageBox( g_hFrameWindow, sText, "Info 2", MB_OK );
2008-08-25 00:36:48 +00:00
char sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54[ sizeof( WinBmpHeader_t ) == (14 + 40) ];
/**/ sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54[0]=0;
2008-08-25 00:36:48 +00:00
// Write Header
fwrite( pBmp, sizeof( WinBmpHeader_t ), 1, pFile );
uint32_t *pSrc;
#if VIDEO_SCREENSHOT_PALETTE
2008-08-25 00:36:48 +00:00
// Write Palette Data
pSrc = ((uint8_t*)g_pFramebufferinfo) + sizeof(BITMAPINFOHEADER);
int nLen = g_tBmpHeader.nPaletteColors * sizeof(bgra_t); // RGBQUAD
2008-08-25 00:36:48 +00:00
fwrite( pSrc, nLen, 1, pFile );
pSrc += nLen;
#endif
2008-08-25 00:36:48 +00:00
// 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 = (uint32_t*) g_pFramebufferbits;
int xSrc = GetFrameBufferBorderWidth();
int ySrc = GetFrameBufferBorderHeight();
pSrc += xSrc; // Skip left border
pSrc += ySrc * GetFrameBufferWidth(); // Skip top border
2008-08-31 04:31:35 +00:00
2017-07-06 20:36:56 +00:00
if( ScreenShotType == SCREENSHOT_280x192 )
2008-08-31 04:31:35 +00:00
{
pSrc += GetFrameBufferWidth(); // Start on odd scanline (otherwise for 50% scanline mode get an all black image!)
uint32_t aScanLine[ 280 ];
uint32_t *pDst;
2008-08-31 04:31:35 +00:00
// 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( UINT y = 0; y < GetFrameBufferBorderlessHeight()/2; y++ )
2008-08-31 04:31:35 +00:00
{
pDst = aScanLine;
for( UINT x = 0; x < GetFrameBufferBorderlessWidth()/2; x++ )
2008-08-31 04:31:35 +00:00
{
*pDst++ = pSrc[1]; // correction for left edge loss of scaled scanline [Bill Buckel, B#18928]
2008-08-31 04:31:35 +00:00
pSrc += 2; // skip odd pixels
}
fwrite( aScanLine, sizeof(uint32_t), GetFrameBufferBorderlessWidth()/2, pFile );
pSrc += GetFrameBufferWidth(); // scan lines doubled - skip odd ones
pSrc += GetFrameBufferBorderWidth()*2; // Skip right border & next line's left border
2008-08-31 04:31:35 +00:00
}
}
else
{
for( UINT y = 0; y < GetFrameBufferBorderlessHeight(); y++ )
{
fwrite( pSrc, sizeof(uint32_t), GetFrameBufferBorderlessWidth(), pFile );
pSrc += GetFrameBufferWidth();
}
2008-08-31 04:31:35 +00:00
}
2008-08-25 00:36:48 +00:00
#endif // SCREENSHOT_BMP
#if SCREENSHOT_TGA
TargaHeader_t *pHeader = &g_tTargaHeader;
memset( (void*)pHeader, 0, sizeof( TargaHeader_t ) );
2014-12-31 22:13:36 +00:00
pHeader->iImageType = TARGA_RGB;
2008-08-31 04:31:35 +00:00
pHeader->nWidthPixels = FRAMEBUFFER_W;
pHeader->nHeightPixels = FRAMEBUFFER_H;
2014-12-31 22:13:36 +00:00
pHeader->nBitsPerPixel = 24;
2008-08-25 00:36:48 +00:00
#endif // SCREENSHOT_TGA
}
//===========================================================================
static void Video_SaveScreenShot( const VideoScreenShot_e ScreenShotType, const TCHAR *pScreenShotFileName )
{
FILE *pFile = fopen( pScreenShotFileName, "wb" );
if( pFile )
{
2017-07-06 20:36:56 +00:00
Video_MakeScreenShot( pFile, ScreenShotType );
fclose( pFile );
}
if( g_bDisplayPrintScreenFileName )
{
MessageBox( g_hFrameWindow, pScreenShotFileName, "Screen Captured", MB_OK );
}
}
//===========================================================================
static const UINT kVideoRomSize8K = kVideoRomSize4K*2;
static const UINT kVideoRomSize16K = kVideoRomSize8K*2;
static const UINT kVideoRomSizeMax = kVideoRomSize16K;
static BYTE g_videoRom[kVideoRomSizeMax];
static UINT g_videoRomSize = 0;
static bool g_videoRomRockerSwitch = false;
bool ReadVideoRomFile(const TCHAR* pRomFile)
{
g_videoRomSize = 0;
HANDLE h = CreateFile(pRomFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (h == INVALID_HANDLE_VALUE)
return false;
const ULONG size = GetFileSize(h, NULL);
if (size == kVideoRomSize2K || size == kVideoRomSize4K || size == kVideoRomSize8K || size == kVideoRomSize16K)
{
DWORD bytesRead;
if (ReadFile(h, g_videoRom, size, &bytesRead, NULL) && bytesRead == size)
g_videoRomSize = size;
}
if (g_videoRomSize == kVideoRomSize16K)
{
// Use top 8K (assume bottom 8K is all 0xFF's)
memcpy(&g_videoRom[0], &g_videoRom[kVideoRomSize8K], kVideoRomSize8K);
g_videoRomSize = kVideoRomSize8K;
}
CloseHandle(h);
return g_videoRomSize != 0;
}
UINT GetVideoRom(const BYTE*& pVideoRom)
{
pVideoRom = &g_videoRom[0];
return g_videoRomSize;
}
bool GetVideoRomRockerSwitch(void)
{
return g_videoRomRockerSwitch;
}
void SetVideoRomRockerSwitch(bool state)
{
g_videoRomRockerSwitch = state;
}
bool IsVideoRom4K(void)
{
return g_videoRomSize <= kVideoRomSize4K;
}
//===========================================================================
enum VideoType127_e
{
VT127_MONO_CUSTOM
, VT127_COLOR_MONITOR_NTSC
, VT127_MONO_TV
, VT127_COLOR_TV
, VT127_MONO_AMBER
, VT127_MONO_GREEN
, VT127_MONO_WHITE
, VT127_NUM_VIDEO_MODES
};
void Config_Load_Video()
{
DWORD dwTmp;
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_MODE), &dwTmp, (DWORD)VT_DEFAULT);
g_eVideoType = dwTmp;
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_STYLE), &dwTmp, (DWORD)VS_HALF_SCANLINES);
g_eVideoStyle = (VideoStyle_e)dwTmp;
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_MONO_COLOR), &dwTmp, (DWORD)RGB(0xC0, 0xC0, 0xC0));
g_nMonochromeRGB = (COLORREF)dwTmp;
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_REFRESH_RATE), &dwTmp, (DWORD)VR_60HZ);
SetVideoRefreshRate((VideoRefreshRate_e)dwTmp);
//
const UINT16* pOldVersion = GetOldAppleWinVersion();
if (pOldVersion[0] == 1 && pOldVersion[1] <= 28 && pOldVersion[2] <= 1)
{
DWORD dwHalfScanLines;
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_HALF_SCAN_LINES), &dwHalfScanLines, 0);
if (dwHalfScanLines)
g_eVideoStyle = (VideoStyle_e) ((DWORD)g_eVideoStyle | VS_HALF_SCANLINES);
else
g_eVideoStyle = (VideoStyle_e) ((DWORD)g_eVideoStyle & ~VS_HALF_SCANLINES);
REGSAVE(TEXT(REGVALUE_VIDEO_STYLE), g_eVideoStyle);
}
//
if (pOldVersion[0] == 1 && pOldVersion[1] <= 27 && pOldVersion[2] <= 13)
{
switch (g_eVideoType)
{
case VT127_MONO_CUSTOM: g_eVideoType = VT_MONO_CUSTOM; break;
case VT127_COLOR_MONITOR_NTSC: g_eVideoType = VT_COLOR_MONITOR_NTSC; break;
case VT127_MONO_TV: g_eVideoType = VT_MONO_TV; break;
case VT127_COLOR_TV: g_eVideoType = VT_COLOR_TV; break;
case VT127_MONO_AMBER: g_eVideoType = VT_MONO_AMBER; break;
case VT127_MONO_GREEN: g_eVideoType = VT_MONO_GREEN; break;
case VT127_MONO_WHITE: g_eVideoType = VT_MONO_WHITE; break;
default: g_eVideoType = VT_DEFAULT; break;
}
REGSAVE(TEXT(REGVALUE_VIDEO_MODE), g_eVideoType);
}
if (g_eVideoType >= NUM_VIDEO_MODES)
g_eVideoType = VT_DEFAULT;
}
void Config_Save_Video()
{
REGSAVE(TEXT(REGVALUE_VIDEO_MODE) ,g_eVideoType);
REGSAVE(TEXT(REGVALUE_VIDEO_STYLE) ,g_eVideoStyle);
REGSAVE(TEXT(REGVALUE_VIDEO_MONO_COLOR),g_nMonochromeRGB);
REGSAVE(TEXT(REGVALUE_VIDEO_REFRESH_RATE), GetVideoRefreshRate());
}
//===========================================================================
VideoType_e GetVideoType(void)
{
return (VideoType_e) g_eVideoType;
}
// TODO: Can only do this at start-up (mid-emulation requires a more heavy-weight video reinit)
void SetVideoType(VideoType_e newVideoType)
{
g_eVideoType = newVideoType;
}
VideoStyle_e GetVideoStyle(void)
{
return g_eVideoStyle;
}
void SetVideoStyle(VideoStyle_e newVideoStyle)
{
g_eVideoStyle = newVideoStyle;
}
bool IsVideoStyle(VideoStyle_e mask)
{
return (g_eVideoStyle & mask) != 0;
}
2014-12-31 22:13:36 +00:00
//===========================================================================
VideoRefreshRate_e GetVideoRefreshRate(void)
{
return (g_bVideoScannerNTSC == false) ? VR_50HZ : VR_60HZ;
}
void SetVideoRefreshRate(VideoRefreshRate_e rate)
{
if (rate != VR_50HZ)
rate = VR_60HZ;
g_bVideoScannerNTSC = (rate == VR_60HZ);
NTSC_SetRefreshRate(rate);
}
2014-12-31 22:13:36 +00:00
//===========================================================================
static void videoCreateDIBSection()
2014-12-31 22:13:36 +00:00
{
// 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);
// DRAW THE SOURCE IMAGE INTO THE SOURCE BIT BUFFER
ZeroMemory( g_pFramebufferbits, GetFrameBufferWidth()*GetFrameBufferHeight()*sizeof(bgra_t) );
2014-12-31 22:13:36 +00:00
// CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE FRAME BUFFER
2014-12-31 22:13:36 +00:00
NTSC_VideoInit( g_pFramebufferbits );
}