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"
2014-08-13 20:30:35 +00:00
2018-02-24 15:12:40 +00:00
# include "Applewin.h"
2014-08-13 20:30:35 +00:00
# include "CPU.h"
2018-02-25 15:09:25 +00:00
# include "Disk.h" // DiskUpdateDriveState()
2014-08-13 20:30:35 +00:00
# include "Frame.h"
# include "Keyboard.h"
# include "Memory.h"
# include "Registry.h"
# include "Video.h"
2014-12-31 22:13:36 +00:00
# include "NTSC.h"
2019-02-02 15:51:27 +00:00
# include "RGBMonitor.h"
2014-08-13 20:30:35 +00:00
2018-02-24 15:12:40 +00:00
# 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)
2006-03-12 09:05:39 +00:00
// 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
2016-12-11 15:02:12 +00:00
int const kVDisplayableScanLines = 192 ; // max displayable scanlines
2006-03-12 09:05:39 +00:00
2011-01-09 04:42:46 +00:00
static COLORREF customcolors [ 256 ] ; // MONOCHROME is last custom color
2006-06-29 03:28:25 +00:00
static HBITMAP g_hDeviceBitmap ;
static HDC g_hDeviceDC ;
2009-02-16 20:30:38 +00:00
static LPBITMAPINFO g_pFramebufferinfo = NULL ;
2006-06-29 03:28:25 +00:00
2006-07-02 22:58:12 +00:00
HBITMAP g_hLogoBitmap ;
2006-06-29 03:28:25 +00:00
2015-01-01 17:59:58 +00:00
COLORREF g_nMonochromeRGB = RGB ( 0xC0 , 0xC0 , 0xC0 ) ;
2011-01-04 17:08:01 +00:00
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
2019-01-09 21:29:36 +00:00
DWORD g_eVideoType = VT_DEFAULT ;
2019-02-24 15:59:35 +00:00
static VideoStyle_e g_eVideoStyle = VS_HALF_SCANLINES ;
2009-02-14 03:53:28 +00:00
2018-08-06 18:06:28 +00:00
static const bool g_bVideoScannerNTSC = true ; // NTSC video scanning (or PAL)
2006-03-12 09:05:39 +00:00
2006-02-25 20:50:29 +00:00
//-------------------------------------
2011-01-08 02:58:20 +00:00
// NOTE: KEEP IN SYNC: VideoType_e g_aVideoChoices g_apVideoModeDesc
TCHAR g_aVideoChoices [ ] =
2016-05-17 21:03:45 +00:00
TEXT ( " Monochrome (Custom) \0 " )
2019-01-09 21:29:36 +00:00
TEXT ( " Color (RGB Monitor) \0 " )
TEXT ( " Color (NTSC Monitor) \0 " )
2016-05-17 21:03:45 +00:00
TEXT ( " Color TV \0 " )
2019-01-09 21:29:36 +00:00
TEXT ( " B&W TV \0 " )
2011-02-15 17:16:36 +00:00
TEXT ( " Monochrome (Amber) \0 " )
TEXT ( " Monochrome (Green) \0 " )
TEXT ( " Monochrome (White) \0 " )
2011-01-08 02:58:20 +00:00
;
// NOTE: KEEP IN SYNC: VideoType_e g_aVideoChoices g_apVideoModeDesc
// The window title will be set to this.
char * g_apVideoModeDesc [ NUM_VIDEO_MODES ] =
{
2016-05-17 21:03:45 +00:00
" Monochrome Monitor (Custom) "
2019-01-09 21:29:36 +00:00
, " Color (RGB Monitor) "
, " Color (NTSC Monitor) "
2016-05-17 21:03:45 +00:00
, " Color TV "
2019-01-09 21:29:36 +00:00
, " B&W TV "
2016-05-17 21:03:45 +00:00
, " Amber Monitor "
, " Green Monitor "
, " White Monitor "
2011-01-08 02:58:20 +00:00
} ;
2006-02-25 20:50:29 +00:00
2008-07-14 16:02:44 +00:00
// Prototypes (Private) _____________________________________________
2006-02-25 20:50:29 +00:00
2011-01-08 02:58:20 +00:00
bool g_bDisplayPrintScreenFileName = false ;
2013-12-23 03:20:54 +00:00
bool g_bShowPrintScreenWarningDialog = true ;
2011-01-08 02:58:20 +00:00
void Util_MakeScreenShotFileName ( char * pFinalFileName_ ) ;
bool Util_TestScreenShotFileName ( const char * pFileName ) ;
2019-03-17 15:01:51 +00:00
void Video_SaveScreenShot ( const VideoScreenShot_e ScreenShotType , const char * pScreenShotFileName ) ;
2017-07-06 20:36:56 +00:00
void Video_MakeScreenShot ( FILE * pFile , const VideoScreenShot_e ScreenShotType ) ;
void videoCreateDIBSection ( ) ;
2014-06-26 21:44:02 +00:00
2006-02-25 20:50:29 +00:00
//===========================================================================
2011-01-09 04:42:46 +00:00
void VideoInitialize ( )
{
2014-12-31 22:13:36 +00:00
// RESET THE VIDEO MODE SWITCHES AND THE CHARACTER SET OFFSET
VideoResetState ( ) ;
2011-01-09 04:42:46 +00:00
// LOAD THE LOGO
2015-01-06 09:45:38 +00:00
g_hLogoBitmap = LoadBitmap ( g_hInstance , MAKEINTRESOURCE ( IDB_APPLEWIN ) ) ;
2011-01-09 04:42:46 +00:00
// 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 ) ;
2017-10-11 16:38:36 +00:00
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 ;
2011-01-09 04:42:46 +00:00
2015-01-03 05:01:01 +00:00
videoCreateDIBSection ( ) ;
2011-01-09 04:42:46 +00:00
}
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 ;
2015-01-03 19:44:06 +00:00
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 ) ;
2016-11-06 14:33:14 +00:00
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 ) ;
2016-11-06 14:33:14 +00:00
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
2018-03-03 22:01:03 +00:00
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 ( ) ;
2017-02-25 22:32:46 +00:00
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 ) {
2017-02-25 22:32:46 +00:00
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 ) ;
2014-09-14 20:23:54 +00:00
JoyUpdateButtonLatch ( executedcycles ) ;
2006-03-12 09:05:39 +00:00
}
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 ) ;
2016-11-06 14:33:14 +00:00
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 " )
2018-03-03 22:01:03 +00:00
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 ,
2018-03-03 22:01:03 +00:00
( 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 ( )
2011-01-09 04:42:46 +00:00
{
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 ;
2011-01-09 04:42:46 +00:00
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 ;
2011-01-09 04:42:46 +00:00
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
2006-06-29 03:28:25 +00:00
DeleteDC ( g_hDeviceDC ) ;
DeleteObject ( g_hDeviceBitmap ) ;
g_hDeviceDC = ( HDC ) 0 ;
g_hDeviceBitmap = ( HBITMAP ) 0 ;
2006-02-25 20:50:29 +00:00
// DESTROY LOGO
2006-06-29 03:28:25 +00:00
if ( g_hLogoBitmap ) {
DeleteObject ( g_hLogoBitmap ) ;
g_hLogoBitmap = ( HBITMAP ) 0 ;
2006-02-25 20:50:29 +00:00
}
}
2006-07-02 22:58:12 +00:00
//===========================================================================
2012-12-29 14:53:52 +00:00
2016-07-12 21:43:31 +00:00
static void VideoDrawLogoBitmap ( HDC hDstDC , int xoff , int yoff , int srcw , int srch , int scale )
2006-07-02 22:58:12 +00:00
{
HDC hSrcDC = CreateCompatibleDC ( hDstDC ) ;
SelectObject ( hSrcDC , g_hLogoBitmap ) ;
2012-12-29 14:53:52 +00:00
StretchBlt (
2006-07-02 22:58:12 +00:00
hDstDC , // hdcDest
2012-12-29 14:53:52 +00:00
xoff , yoff , // nXDest, nYDest
scale * srcw , scale * srch , // nWidth, nHeight
2006-07-02 22:58:12 +00:00
hSrcDC , // hdcSrc
0 , 0 , // nXSrc, nYSrc
2012-12-29 14:53:52 +00:00
srcw , srch ,
2006-07-02 22:58:12 +00:00
SRCCOPY // dwRop
) ;
DeleteObject ( hSrcDC ) ;
}
2006-02-25 20:50:29 +00:00
//===========================================================================
2009-02-18 07:54:24 +00:00
void VideoDisplayLogo ( )
{
2016-07-12 21:43:31 +00:00
int nLogoX = 0 , nLogoY = 0 ;
int scale = GetViewportScale ( ) ;
2015-01-07 08:48:18 +00:00
2006-07-02 22:58:12 +00:00
HDC hFrameDC = FrameGetDC ( ) ;
2006-02-25 20:50:29 +00:00
2006-07-02 22:58:12 +00:00
// DRAW THE LOGO
2012-12-29 14:53:52 +00:00
SelectObject ( hFrameDC , GetStockObject ( NULL_PEN ) ) ;
2006-07-02 22:58:12 +00:00
if ( g_hLogoBitmap )
{
2012-12-29 14:53:52 +00:00
BITMAP bm ;
if ( GetObject ( g_hLogoBitmap , sizeof ( bm ) , & bm ) )
{
2016-07-12 21:43:31 +00:00
nLogoX = ( g_nViewportCX - scale * bm . bmWidth ) / 2 ;
nLogoY = ( g_nViewportCY - scale * bm . bmHeight ) / 2 ;
2012-12-29 14:53:52 +00:00
2017-10-02 21:22:26 +00:00
if ( IsFullScreen ( ) )
2012-12-29 14:53:52 +00:00
{
2016-07-26 21:33:45 +00:00
nLogoX + = GetFullScreenOffsetX ( ) ;
nLogoY + = GetFullScreenOffsetY ( ) ;
2012-12-29 14:53:52 +00:00
}
2017-10-02 21:22:26 +00:00
2016-07-12 21:43:31 +00:00
VideoDrawLogoBitmap ( hFrameDC , nLogoX , nLogoY , bm . bmWidth , bm . bmHeight , scale ) ;
2012-12-29 14:53:52 +00:00
}
2006-07-02 22:58:12 +00:00
}
2006-02-25 20:50:29 +00:00
2006-07-02 22:58:12 +00:00
// DRAW THE VERSION NUMBER
2015-01-03 22:13:55 +00:00
TCHAR sFontName [ ] = TEXT ( " Arial " ) ;
2006-07-02 22:58:12 +00:00
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 ) ;
2006-07-02 22:58:12 +00:00
SelectObject ( hFrameDC , font ) ;
SetTextAlign ( hFrameDC , TA_RIGHT | TA_TOP ) ;
SetBkMode ( hFrameDC , TRANSPARENT ) ;
2006-02-25 20:50:29 +00:00
2011-02-20 07:32:09 +00:00
char szVersion [ 64 ] = " " ;
2009-02-18 07:54:24 +00:00
sprintf ( szVersion , " Version %s " , VERSIONSTRING ) ;
2016-07-26 21:33:45 +00:00
int xoff = GetFullScreenOffsetX ( ) , yoff = GetFullScreenOffsetY ( ) ;
2009-02-18 07:54:24 +00:00
2012-12-29 14:53:52 +00:00
# define DRAWVERSION(x,y,c) \
SetTextColor ( hFrameDC , c ) ; \
TextOut ( hFrameDC , \
scale * 540 + x + xoff , scale * 358 + y + yoff , \
szVersion , \
2009-02-18 07:54:24 +00:00
strlen ( szVersion ) ) ;
2006-02-25 20:50:29 +00:00
2006-07-02 22:58:12 +00:00
if ( GetDeviceCaps ( hFrameDC , PLANES ) * GetDeviceCaps ( hFrameDC , BITSPIXEL ) < = 4 ) {
2011-02-20 07:32:09 +00:00
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-07-02 22:58:12 +00:00
}
2006-02-25 20:50:29 +00:00
2011-02-20 07:32:09 +00:00
# if _DEBUG
sprintf ( szVersion , " DEBUG " ) ;
2012-12-29 14:53:52 +00:00
DRAWVERSION ( 2 , - 358 * scale , RGB ( 0x00 , 0x00 , 0x00 ) ) ;
DRAWVERSION ( 1 , - 357 * scale , RGB ( 0x00 , 0x00 , 0x00 ) ) ;
DRAWVERSION ( 0 , - 356 * scale , RGB ( 0xFF , 0x00 , 0xFF ) ) ;
2011-02-20 07:32:09 +00:00
# endif
2006-02-25 20:50:29 +00:00
# undef DRAWVERSION
2006-07-02 22:58:12 +00:00
DeleteObject ( font ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2014-06-27 21:43:25 +00:00
2016-09-22 21:34:16 +00:00
void VideoRedrawScreenDuringFullSpeed ( DWORD dwCyclesThisFrame , bool bInit /*=false*/ )
2016-07-23 21:53:29 +00:00
{
2016-09-22 21:34:16 +00:00
static DWORD dwFullSpeedStartTime = 0 ;
2017-04-23 20:37:01 +00:00
// static bool bValid = false;
2016-07-23 21:53:29 +00:00
2016-09-22 21:34:16 +00:00
if ( bInit )
2016-07-23 21:53:29 +00:00
{
2016-09-22 21:34:16 +00:00
// Just entered full-speed mode
2017-04-23 20:37:01 +00:00
// bValid = false;
2016-09-22 21:34:16 +00:00
dwFullSpeedStartTime = GetTickCount ( ) ;
2016-07-23 21:53:29 +00:00
return ;
}
2016-09-22 21:34:16 +00:00
DWORD dwFullSpeedDuration = GetTickCount ( ) - dwFullSpeedStartTime ;
if ( dwFullSpeedDuration < = 16 ) // Only update after every realtime ~17ms of *continuous* full-speed
return ;
dwFullSpeedStartTime + = dwFullSpeedDuration ;
2016-07-25 20:19:00 +00:00
//
2017-04-23 20:37:01 +00:00
#if 0
2016-07-25 20:19:00 +00:00
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
2016-07-23 21:53:29 +00:00
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 ;
2017-04-23 20:37:01 +00:00
# else
VideoRedrawScreenAfterFullSpeed ( dwCyclesThisFrame ) ;
# endif
2016-07-23 21:53:29 +00:00
}
//===========================================================================
2016-05-17 21:03:45 +00:00
void VideoRedrawScreenAfterFullSpeed ( DWORD dwCyclesThisFrame )
{
2018-08-06 18:06:28 +00:00
if ( g_bVideoScannerNTSC )
2018-02-02 20:19:48 +00:00
{
NTSC_VideoClockResync ( dwCyclesThisFrame ) ;
}
else // PAL
{
_ASSERT ( 0 ) ;
g_nVideoClockVert = ( uint16_t ) ( dwCyclesThisFrame / kHClocks ) % kPALScanLines ;
g_nVideoClockHorz = ( uint16_t ) ( dwCyclesThisFrame % kHClocks ) ;
}
2016-05-17 21:03:45 +00:00
VideoRedrawScreen ( ) ; // Better (no flicker) than using: NTSC_VideoReinitialize() or VideoReinitialize()
}
//===========================================================================
2016-09-25 09:42:14 +00:00
void VideoRedrawScreen ( void )
2011-01-07 09:23:16 +00:00
{
2016-11-06 14:23:23 +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.
2016-09-25 09:42:14 +00:00
VideoRefreshScreen ( g_uVideoMode , true ) ;
2006-02-25 20:50:29 +00:00
}
2008-08-25 05:25:27 +00:00
//===========================================================================
2014-07-20 09:23:23 +00:00
2016-10-11 20:52:11 +00:00
// TC: Hacky-fix for GH#341 - better to draw to the correct position in the framebuffer to start with! (in NTSC.cpp)
2018-09-09 12:56:55 +00:00
// . NB. Now the dx is corrected in NTSC.cpp, updateVideoScannerAddress()
2016-10-11 20:52:11 +00:00
static void VideoFrameBufferAdjust ( int & xSrc , int & ySrc , bool bInvertY = false )
{
int dx = 0 , dy = 0 ;
if ( g_eVideoType = = VT_MONO_TV | | g_eVideoType = = VT_COLOR_TV )
{
// Adjust the src locations for the NTSC video modes
dy = - 1 ;
}
if ( bInvertY )
2017-04-22 21:22:29 +00:00
dy = - dy ;
2016-10-11 20:52:11 +00:00
xSrc + = dx ;
ySrc + = dy ;
}
2016-11-06 21:59:45 +00:00
void VideoRefreshScreen ( uint32_t uRedrawWholeScreenVideoMode /* =0*/ , bool bRedrawWholeScreen /* =false*/ )
2011-01-07 09:23:16 +00:00
{
2016-09-25 09:42:14 +00:00
if ( bRedrawWholeScreen | | g_nAppMode = = MODE_PAUSED )
2015-01-01 19:48:59 +00:00
{
2016-11-06 14:23:23 +00:00
// uVideoModeForWholeScreen set if:
2016-09-17 20:26:45 +00:00
// . MODE_DEBUG : always
// . MODE_RUNNING : called from VideoRedrawScreen(), eg. during full-speed
2016-09-25 09:42:14 +00:00
if ( bRedrawWholeScreen )
2016-11-06 14:23:23 +00:00
NTSC_SetVideoMode ( uRedrawWholeScreenVideoMode ) ;
2016-09-25 09:42:14 +00:00
NTSC_VideoRedrawWholeScreen ( ) ;
2015-01-01 19:48:59 +00:00
}
2015-01-01 01:15:36 +00:00
2017-10-11 18:18:53 +00:00
HDC hFrameDC = FrameGetDC ( ) ;
2015-01-03 22:13:55 +00:00
2014-12-31 22:13:36 +00:00
if ( hFrameDC )
{
2017-10-11 18:18:53 +00:00
int xSrc = GetFrameBufferBorderWidth ( ) ;
int ySrc = GetFrameBufferBorderHeight ( ) ;
VideoFrameBufferAdjust ( xSrc , ySrc ) ; // TC: Hacky-fix for GH#341
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
}
2016-04-04 20:05:58 +00:00
2015-01-07 08:48:18 +00:00
GdiFlush ( ) ;
2008-08-25 05:25:27 +00:00
}
2006-02-25 20:50:29 +00:00
//===========================================================================
2019-03-16 14:27:40 +00:00
void VideoReinitialize ( bool bInitVideoScannerAddress /*= true*/ )
2011-01-08 21:29:27 +00:00
{
2019-03-16 14:27:40 +00:00
NTSC_VideoReinitialize ( g_dwCyclesThisFrame , bInitVideoScannerAddress ) ;
2016-04-05 21:17:29 +00:00
NTSC_VideoInitAppleType ( ) ;
2014-12-31 22:13:36 +00:00
NTSC_SetVideoStyle ( ) ;
2016-10-31 21:05:10 +00:00
NTSC_SetVideoTextMode ( g_uVideoMode & VF_80COL ? 80 : 40 ) ;
2016-04-05 21:17:29 +00:00
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 ;
2017-02-17 14:26:03 +00:00
NTSC_SetVideoTextMode ( 40 ) ;
NTSC_SetVideoMode ( g_uVideoMode ) ;
2006-02-25 20:50:29 +00:00
2019-02-02 15:51:27 +00:00
RGB_ResetState ( ) ;
}
2011-01-08 21:29:27 +00:00
2006-02-25 20:50:29 +00:00
//===========================================================================
2014-12-21 21:43:49 +00:00
2018-11-17 16:29:17 +00:00
BYTE VideoSetMode ( WORD , WORD address , BYTE write , BYTE , ULONG uExecutedCycles )
2007-05-28 11:16:42 +00:00
{
2011-01-08 21:29:27 +00:00
address & = 0xFF ;
2015-01-05 16:39: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
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
2019-02-02 15:51:27 +00:00
if ( ! IS_APPLE2 )
RGB_SetVideoMode ( address ) ;
2014-12-31 22:13:36 +00:00
NTSC_SetVideoMode ( g_uVideoMode ) ;
2015-01-03 19:44:06 +00:00
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
}
//===========================================================================
2016-03-21 23:48:02 +00:00
# define SS_YAML_KEY_ALTCHARSET "Alt Char Set"
# define SS_YAML_KEY_VIDEOMODE "Video Mode"
# define SS_YAML_KEY_CYCLESTHISFRAME "Cycles This Frame"
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_ALTCHARSET , g_nAltCharSetOffset ? true : false ) ;
yamlSaveHelper . SaveHexUint32 ( SS_YAML_KEY_VIDEOMODE , g_uVideoMode ) ;
yamlSaveHelper . SaveUint ( SS_YAML_KEY_CYCLESTHISFRAME , g_dwCyclesThisFrame ) ;
}
void VideoLoadSnapshot ( YamlLoadHelper & yamlLoadHelper )
{
if ( ! yamlLoadHelper . GetSubMap ( VideoGetSnapshotStructName ( ) ) )
return ;
g_nAltCharSetOffset = yamlLoadHelper . LoadBool ( SS_YAML_KEY_ALTCHARSET ) ? 256 : 0 ;
g_uVideoMode = yamlLoadHelper . LoadUint ( SS_YAML_KEY_VIDEOMODE ) ;
g_dwCyclesThisFrame = yamlLoadHelper . LoadUint ( SS_YAML_KEY_CYCLESTHISFRAME ) ;
yamlLoadHelper . PopMap ( ) ;
2006-02-25 20:50:29 +00:00
}
2006-03-12 09:05:39 +00:00
//===========================================================================
2008-12-30 15:45:48 +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)
//
2018-08-06 18:06:28 +00:00
WORD VideoGetScannerAddress ( DWORD nCycles , VideoScanner_e videoScannerAddr /*= VS_FullAddr*/ )
2006-03-12 09:05:39 +00:00
{
// machine state switches
//
2018-08-06 18:06:28 +00:00
bool bHires = VideoGetSWHIRES ( ) & & ! VideoGetSWTEXT ( ) ;
bool bPage2 = VideoGetSWPAGE2 ( ) ;
bool b80Store = VideoGetSW80STORE ( ) ;
2006-03-12 09:05:39 +00:00
// calculate video parameters according to display standard
//
2018-08-06 18:06:28 +00:00
const int kScanLines = g_bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines ;
const int kScanCycles = kScanLines * kHClocks ;
_ASSERT ( nCycles < kScanCycles ) ;
nCycles % = kScanCycles ;
2006-03-12 09:05:39 +00:00
// 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
2018-08-05 21:19:51 +00:00
if ( nVLine > = kVPresetLine ) // check for previous vertical state preset
2006-03-12 09:05:39 +00:00
{
2018-08-06 18:06:28 +00:00
nVState - = kScanLines ; // compensate for preset
2006-03-12 09:05:39 +00:00
}
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
//
2018-08-06 18:06:28 +00:00
if ( bHires & & SW_MIXED & & v_4 & & v_2 ) // HIRES TIME signal (UTAIIe:5-7,P3)
2006-03-12 09:05:39 +00:00
{
2018-08-06 18:06:28 +00:00
bHires = false ; // address is in text memory for mixed hires
2006-03-12 09:05:39 +00:00
}
2008-12-30 15:45:48 +00:00
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)
2018-08-06 18:06:28 +00:00
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!)
}
}
2018-08-05 21:19:51 +00:00
2018-08-06 18:06:28 +00:00
WORD nAddressV = 0 ;
nAddressV | = v_0 < < 7 ; // a7
nAddressV | = v_1 < < 8 ; // a8
nAddressV | = v_2 < < 9 ; // a9
2008-12-30 15:45:48 +00:00
2018-08-06 18:06:28 +00:00
int p2a = ! ( bPage2 & & ! b80Store ) ? 1 : 0 ;
int p2b = ( bPage2 & & ! b80Store ) ? 1 : 0 ;
2008-12-30 15:45:48 +00:00
2018-08-06 18:06:28 +00:00
WORD nAddressP = 0 ; // Page bits
if ( bHires ) // hires?
2006-03-12 09:05:39 +00:00
{
2008-12-30 15:45:48 +00:00
// Y: insert hires-only address bits
2006-03-12 09:05:39 +00:00
//
2018-08-06 18:06:28 +00:00
nAddressV | = v_A < < 10 ; // a10
nAddressV | = v_B < < 11 ; // a11
nAddressV | = v_C < < 12 ; // a12
nAddressP | = p2a < < 13 ; // a13
nAddressP | = p2b < < 14 ; // a14
2006-03-12 09:05:39 +00:00
}
else
{
2008-12-30 15:45:48 +00:00
// N: insert text-only address bits
2006-03-12 09:05:39 +00:00
//
2018-08-06 18:06:28 +00:00
nAddressP | = p2a < < 10 ; // a10
nAddressP | = p2b < < 11 ; // a11
2006-03-12 09:05:39 +00:00
}
2018-08-05 21:19:51 +00:00
// VBL' = v_4' | v_3' = (v_4 & v_3)' (UTAIIe:5-10,#3)
2018-08-06 18:06:28 +00:00
if ( videoScannerAddr = = VS_PartialAddrH )
return nAddressH ;
if ( videoScannerAddr = = VS_PartialAddrV )
return nAddressV ;
return nAddressP | nAddressV | nAddressH ;
2006-03-12 09:05:39 +00:00
}
//===========================================================================
2007-08-06 21:38:35 +00:00
2016-11-16 11:18:41 +00:00
bool VideoGetVblBar ( const DWORD uExecutedCycles )
2007-08-06 21:38:35 +00:00
{
2016-12-11 15:02:12 +00:00
// get video scanner position
int nCycles = CpuGetCyclesThisVideoFrame ( uExecutedCycles ) ;
2007-08-06 21:38:35 +00:00
2016-12-11 15:02:12 +00:00
// calculate video parameters according to display standard
2018-08-06 18:06:28 +00:00
const int kScanLines = g_bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines ;
2016-12-11 15:02:12 +00:00
const int kScanCycles = kScanLines * kHClocks ;
nCycles % = kScanCycles ;
2007-08-06 21:38:35 +00:00
2016-12-11 15:02:12 +00:00
// VBL'
return nCycles < kVDisplayableScanLines * kHClocks ;
2007-08-06 21:38:35 +00:00
}
2010-08-17 07:52:23 +00:00
//===========================================================================
2008-08-25 00:36:48 +00:00
# define SCREENSHOT_BMP 1
# define SCREENSHOT_TGA 0
2008-07-14 16:02:44 +00:00
2008-08-31 04:31:35 +00:00
static int g_nLastScreenShot = 0 ;
2008-07-14 16:02:44 +00:00
const int nMaxScreenShot = 999999999 ;
2008-08-31 04:31:35 +00:00
static char * g_pLastDiskImageName = NULL ;
//===========================================================================
void Video_ResetScreenshotCounter ( char * pImageName )
{
g_nLastScreenShot = 0 ;
g_pLastDiskImageName = pImageName ;
}
2008-07-14 16:02:44 +00:00
//===========================================================================
void Util_MakeScreenShotFileName ( char * pFinalFileName_ )
{
2008-08-31 04:31:35 +00:00
char sPrefixScreenShotFileName [ 256 ] = " AppleWin_ScreenShot " ;
2011-02-15 17:16:36 +00:00
// TODO: g_sScreenshotDir
2008-08-31 04:31:35 +00:00
char * pPrefixFileName = g_pLastDiskImageName ? g_pLastDiskImageName : sPrefixScreenShotFileName ;
2008-08-25 00:36:48 +00:00
# if SCREENSHOT_BMP
2008-08-31 04:31:35 +00:00
sprintf ( pFinalFileName_ , " %s_%09d.bmp " , pPrefixFileName , g_nLastScreenShot ) ;
2008-08-25 00:36:48 +00:00
# endif
# if SCREENSHOT_TGA
2008-08-31 04:31:35 +00:00
sprintf ( pFinalFileName_ , " %s%09d.tga " , pPrefixFileName , g_nLastScreenShot ) ;
2008-08-25 00:36:48 +00:00
# endif
2008-07-14 16:02:44 +00:00
}
2009-02-16 20:30:38 +00:00
// Returns TRUE if file exists, else FALSE
2007-08-06 21:38:35 +00:00
//===========================================================================
2008-07-14 16:02:44 +00:00
bool Util_TestScreenShotFileName ( const char * 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 )
2008-07-14 16:02:44 +00:00
{
char 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!?
2008-07-14 16:02:44 +00:00
{
sprintf ( sScreenShotFileName , " You have more then %d screenshot filenames! They will no longer be saved. \n \n Either move some of your screenshots or increase the maximum in video.cpp \n " , nMaxScreenShot ) ;
2013-12-06 21:10:41 +00:00
MessageBox ( g_hFrameWindow , sScreenShotFileName , " Warning " , MB_OK ) ;
2008-08-31 04:31:35 +00:00
g_nLastScreenShot = 0 ;
2008-07-14 16:02:44 +00:00
return ;
}
Util_MakeScreenShotFileName ( sScreenShotFileName ) ;
bExists = Util_TestScreenShotFileName ( sScreenShotFileName ) ;
if ( ! bExists )
{
break ;
}
2008-08-31 04:31:35 +00:00
g_nLastScreenShot + + ;
2008-07-14 16:02:44 +00:00
}
2019-03-17 15:01:51 +00:00
Video_SaveScreenShot ( ScreenShotType , sScreenShotFileName ) ;
2008-08-31 04:31:35 +00:00
g_nLastScreenShot + + ;
2008-07-14 16:02:44 +00:00
}
2019-03-17 15:01:51 +00:00
void Video_RedrawAndTakeScreenShot ( const char * pScreenshotFilename )
{
_ASSERT ( pScreenshotFilename ) ;
if ( ! pScreenshotFilename )
return ;
VideoRedrawScreen ( ) ;
Video_SaveScreenShot ( SCREENSHOT_560x384 , pScreenshotFilename ) ;
}
2008-07-14 16:02:44 +00:00
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
2015-01-10 19:42:53 +00:00
void Video_SetBitmapHeader ( WinBmpHeader_t * pBmp , int nWidth , int nHeight , int nBitsPerPixel )
2008-07-14 16:02:44 +00:00
{
2008-08-25 00:36:48 +00:00
# if SCREENSHOT_BMP
2015-01-10 19:42:53 +00:00
pBmp - > nCookie [ 0 ] = ' B ' ; // 0x42
pBmp - > nCookie [ 1 ] = ' M ' ; // 0x4d
pBmp - > nSizeFile = 0 ;
pBmp - > nReserved1 = 0 ;
pBmp - > nReserved2 = 0 ;
2015-01-10 19:22:41 +00:00
# if VIDEO_SCREENSHOT_PALETTE
2015-01-10 19:42:53 +00:00
pBmp - > nOffsetData = sizeof ( WinBmpHeader_t ) + ( 256 * sizeof ( bgra_t ) ) ;
2015-01-10 19:22:41 +00:00
# else
2015-01-10 19:42:53 +00:00
pBmp - > nOffsetData = sizeof ( WinBmpHeader_t ) ;
2015-01-10 19:22:41 +00:00
# endif
2015-01-10 19:42:53 +00:00
pBmp - > nStructSize = 0x28 ; // sizeof( WinBmpHeader_t );
pBmp - > nWidthPixels = nWidth ;
pBmp - > nHeightPixels = nHeight ;
pBmp - > nPlanes = 1 ;
2015-01-10 19:22:41 +00:00
# if VIDEO_SCREENSHOT_PALETTE
2015-01-10 19:42:53 +00:00
pBmp - > nBitsPerPixel = 8 ;
2015-01-10 19:22:41 +00:00
# else
2015-01-10 19:42:53 +00:00
pBmp - > nBitsPerPixel = nBitsPerPixel ;
2015-01-10 19:22:41 +00:00
# endif
2015-01-10 19:42:53 +00:00
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 )
2015-01-10 19:42:53 +00:00
{
WinBmpHeader_t * pBmp = & g_tBmpHeader ;
Video_SetBitmapHeader (
pBmp ,
2017-10-11 16:38:36 +00:00
ScreenShotType = = SCREENSHOT_280x192 ? GetFrameBufferBorderlessWidth ( ) / 2 : GetFrameBufferBorderlessWidth ( ) ,
ScreenShotType = = SCREENSHOT_280x192 ? GetFrameBufferBorderlessHeight ( ) / 2 : GetFrameBufferBorderlessHeight ( ) ,
2015-01-10 19:42:53 +00:00
32
) ;
2008-08-25 00:36:48 +00:00
// char sText[256];
// sprintf( sText, "sizeof: BITMAPFILEHEADER = %d\n", sizeof(BITMAPFILEHEADER) ); // = 14
2013-12-06 21:10:41 +00:00
// 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
2013-12-06 21:10:41 +00:00
// MessageBox( g_hFrameWindow, sText, "Info 2", MB_OK );
2008-08-25 00:36:48 +00:00
2015-01-10 19:42:53 +00:00
char sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54 [ sizeof ( WinBmpHeader_t ) = = ( 14 + 40 ) ] ;
/**/ sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54 [ 0 ] = 0 ;
2008-08-25 00:36:48 +00:00
// Write Header
2015-01-10 19:42:53 +00:00
fwrite ( pBmp , sizeof ( WinBmpHeader_t ) , 1 , pFile ) ;
2009-02-16 20:30:38 +00:00
2015-01-10 19:22:41 +00:00
uint32_t * pSrc ;
# if VIDEO_SCREENSHOT_PALETTE
2008-08-25 00:36:48 +00:00
// Write Palette Data
2015-01-10 19:22:41 +00:00
pSrc = ( ( uint8_t * ) g_pFramebufferinfo ) + sizeof ( BITMAPINFOHEADER ) ;
2016-04-04 20:05:58 +00:00
int nLen = g_tBmpHeader . nPaletteColors * sizeof ( bgra_t ) ; // RGBQUAD
2008-08-25 00:36:48 +00:00
fwrite ( pSrc , nLen , 1 , pFile ) ;
pSrc + = nLen ;
2015-01-10 19:22:41 +00:00
# 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
2015-01-10 19:22:41 +00:00
pSrc = ( uint32_t * ) g_pFramebufferbits ;
2016-10-11 20:52:11 +00:00
2017-10-11 16:38:36 +00:00
int xSrc = GetFrameBufferBorderWidth ( ) ;
int ySrc = GetFrameBufferBorderHeight ( ) ;
2016-10-11 20:52:11 +00:00
VideoFrameBufferAdjust ( xSrc , ySrc , true ) ; // TC: Hacky-fix for GH#341 & GH#356
// Lines stored in reverse, so invert the y-adjust value
2017-10-11 16:38:36 +00:00
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
{
2017-10-11 16:38:36 +00:00
pSrc + = GetFrameBufferWidth ( ) ; // Start on odd scanline (otherwise for 50% scanline mode get an all black image!)
2013-03-28 22:28:42 +00:00
2015-01-10 19:22:41 +00:00
uint32_t aScanLine [ 280 ] ;
uint32_t * pDst ;
2008-08-31 04:31:35 +00:00
2009-02-18 22:24:50 +00:00
// 50% Half Scan Line clears every odd scanline.
2014-08-21 21:53:01 +00:00
// SHIFT+PrintScreen saves only the even rows.
2009-02-18 22:24:50 +00:00
// NOTE: Keep in sync with _Video_RedrawScreen() & Video_MakeScreenShot()
2017-10-11 16:38:36 +00:00
for ( UINT y = 0 ; y < GetFrameBufferBorderlessHeight ( ) / 2 ; y + + )
2008-08-31 04:31:35 +00:00
{
pDst = aScanLine ;
2017-10-11 16:38:36 +00:00
for ( UINT x = 0 ; x < GetFrameBufferBorderlessWidth ( ) / 2 ; x + + )
2008-08-31 04:31:35 +00:00
{
2013-03-28 22:28:42 +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
}
2017-10-11 16:38:36 +00:00
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
{
2017-10-11 16:38:36 +00:00
for ( UINT y = 0 ; y < GetFrameBufferBorderlessHeight ( ) ; y + + )
2016-04-04 20:05:58 +00:00
{
2017-10-11 16:38:36 +00:00
fwrite ( pSrc , sizeof ( uint32_t ) , GetFrameBufferBorderlessWidth ( ) , pFile ) ;
pSrc + = GetFrameBufferWidth ( ) ;
2016-04-04 20:05:58 +00:00
}
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
2008-07-14 16:02:44 +00:00
}
//===========================================================================
2019-03-17 15:01:51 +00:00
static void Video_SaveScreenShot ( const VideoScreenShot_e ScreenShotType , const char * pScreenShotFileName )
2008-07-14 16:02:44 +00:00
{
FILE * pFile = fopen ( pScreenShotFileName , " wb " ) ;
if ( pFile )
{
2017-07-06 20:36:56 +00:00
Video_MakeScreenShot ( pFile , ScreenShotType ) ;
2008-07-14 16:02:44 +00:00
fclose ( pFile ) ;
}
2008-08-25 01:10:37 +00:00
if ( g_bDisplayPrintScreenFileName )
{
2013-12-06 21:10:41 +00:00
MessageBox ( g_hFrameWindow , pScreenShotFileName , " Screen Captured " , MB_OK ) ;
2008-08-25 01:10:37 +00:00
}
2008-07-14 16:02:44 +00:00
}
2012-03-27 21:20:36 +00:00
2018-11-17 16:29:17 +00:00
//===========================================================================
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 char * 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 ) ;
2018-11-19 22:15:04 +00:00
if ( size = = kVideoRomSize2K | | size = = kVideoRomSize4K | | size = = kVideoRomSize8K | | size = = kVideoRomSize16K )
2018-11-17 16:29:17 +00:00
{
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 )
{
2018-11-19 22:15:04 +00:00
return g_videoRomSize < = kVideoRomSize4K ;
2018-11-17 16:29:17 +00:00
}
2012-03-27 21:20:36 +00:00
//===========================================================================
2019-01-09 21:29:36 +00:00
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
} ;
2012-03-27 21:20:36 +00:00
void Config_Load_Video ( )
{
2019-02-24 15:59:35 +00:00
REGLOAD ( TEXT ( REGVALUE_VIDEO_MODE ) , & g_eVideoType ) ;
REGLOAD ( TEXT ( REGVALUE_VIDEO_STYLE ) , ( DWORD * ) & g_eVideoStyle ) ;
REGLOAD ( TEXT ( REGVALUE_VIDEO_MONO_COLOR ) , & g_nMonochromeRGB ) ;
//
2012-03-27 21:20:36 +00:00
2019-01-09 21:29:36 +00:00
const UINT16 * pOldVersion = GetOldAppleWinVersion ( ) ;
2019-02-24 15:59:35 +00:00
if ( pOldVersion [ 0 ] = = 1 & & pOldVersion [ 1 ] < = 28 & & pOldVersion [ 2 ] < = 1 )
{
DWORD halfScanLines = 0 ;
REGLOAD ( TEXT ( REGVALUE_VIDEO_HALF_SCAN_LINES ) , & halfScanLines ) ;
if ( halfScanLines )
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 ) ;
}
//
2019-01-09 21:29:36 +00:00
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 ;
}
2019-02-24 15:59:35 +00:00
REGSAVE ( TEXT ( REGVALUE_VIDEO_MODE ) , g_eVideoType ) ;
2019-01-09 21:29:36 +00:00
}
2012-03-27 21:20:36 +00:00
if ( g_eVideoType > = NUM_VIDEO_MODES )
2019-01-09 21:29:36 +00:00
g_eVideoType = VT_DEFAULT ;
2012-03-27 21:20:36 +00:00
}
void Config_Save_Video ( )
{
2019-02-24 15:59:35 +00:00
REGSAVE ( TEXT ( REGVALUE_VIDEO_MODE ) , g_eVideoType ) ;
REGSAVE ( TEXT ( REGVALUE_VIDEO_STYLE ) , g_eVideoStyle ) ;
REGSAVE ( TEXT ( REGVALUE_VIDEO_MONO_COLOR ) , g_nMonochromeRGB ) ;
}
//===========================================================================
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 ;
2012-03-27 21:20:36 +00:00
}
2014-12-31 22:13:36 +00:00
//===========================================================================
2016-04-12 22:21:05 +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
2017-10-11 16:38:36 +00:00
ZeroMemory ( g_pFramebufferbits , GetFrameBufferWidth ( ) * GetFrameBufferHeight ( ) * sizeof ( bgra_t ) ) ;
2014-12-31 22:13:36 +00:00
2019-01-09 21:29: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 ) ;
}