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
# include "AppleWin.h"
# include "CPU.h"
# 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"
2014-08-13 20:30:35 +00:00
2006-02-25 20:50:29 +00:00
# include "..\resource\resource.h"
2012-03-27 21:20:36 +00:00
# include "Configuration\PropertySheet.h"
2014-08-13 20:30:35 +00:00
# include "Debugger\Debugger_Color.h" // For NUM_DEBUG_COLORS
2006-02-25 20:50:29 +00:00
2011-02-17 17:29:30 +00:00
# define HALF_PIXEL_SOLID 1
2011-02-19 23:04:44 +00:00
# define HALF_PIXEL_BLEED 0
2011-02-17 17:29:30 +00:00
2014-12-16 02:02:54 +00:00
# define HALF_DIM_SUPPORT 0
2014-12-14 22:59:01 +00:00
/*
Reference : Technote TN - IIGS - 063 " Master Color Values "
Note : The IIGS colors do NOT map correctly to _accurate_ //e colors.
Color LO HI DHR Master Color R , G , B HGR
Name # # # Value Bytes
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Black 0 0 , 4 0 $ 0000 ( 0 , 0 , 0 ) - > ( 00 , 00 , 00 ) Windows
( Magenta ) Deep Red 1 1 $ 0 D03 ( D , 0 , 3 ) - > ( D0 , 00 , 30 ) Custom
Dark Blue 2 8 $ 000 9 ( 0 , 0 , 9 ) - > ( 00 , 00 , 80 ) Windows
( Violet ) Purple 3 2 9 $ 0 D2D ( D , 2 , D ) - > ( FF , 00 , FF ) Windows 55 2 A
Dark Green 4 4 $ 0072 ( 0 , 7 , 2 ) - > ( 00 , 80 , 00 ) Windows
( Gray 1 ) Dark Gray 5 5 $ 0555 ( 5 , 5 , 5 ) - > ( 80 , 80 , 80 ) Windows
( Blue ) Medium Blue 6 6 C $ 022F ( 2 , 2 , F ) - > ( 00 , 00 , FF ) Windows D5 AA
( Cyan ) Light Blue 7 D $ 06 AF ( 6 , A , F ) - > ( 60 , A0 , FF ) Custom
Brown 8 2 $ 0850 ( 8 , 5 , 0 ) - > ( 80 , 50 , 00 ) Custom
Orange 9 5 3 $ 0F 60 ( F , 6 , 0 ) - > ( FF , 80 , 00 ) Custom AA D5 ( modified to match better with the other Hi - Res Colors )
( Gray 2 ) Light Gray A A $ 0 AAA ( A , A , A ) - > ( C0 , C0 , C0 ) Windows
Pink B B $ 0F 98 ( F , 9 , 8 ) - > ( FF , 90 , 80 ) Custom
( Green ) Light Green C 1 6 $ 01 D0 ( 1 , D , 0 ) - > ( 00 , FF , 00 ) Windows 2 A 55
Yellow D 7 $ 0FF 0 ( F , F , 0 ) - > ( FF , FF , 00 ) Windows
( Aqua ) Aquamarine E E $ 04F 9 ( 4 , F , 9 ) - > ( 40 , FF , 90 ) Custom
White F 3 , 7 F $ 0FF F ( F , F , F ) - > ( FF , FF , FF ) Windows
Legend :
LO : Lo - Res
HI : Hi - Res
DHR : Double Hi - Res
*/
2006-02-25 20:50:29 +00:00
2012-09-16 21:53:07 +00:00
# define FLASH_80_COL 1 // Bug #7238
2006-03-01 03:49:14 +00:00
# define HALF_SHIFT_DITHER 0
2006-03-01 00:23:42 +00:00
enum Color_Palette_Index_e
{
2011-02-19 22:14:08 +00:00
// The first 10 are the DEFAULT Windows colors (as it reserves 20 colors)
BLACK = 0x00 // 0x00,0x00,0x00
, DARK_RED = 0x01 // 0x80,0x00,0x00
, DARK_GREEN = 0x02 // 0x00,0x80,0x00 (Half Green)
, DARK_YELLOW = 0x03 // 0x80,0x80,0x00
, DARK_BLUE = 0x04 // 0x00,0x00,0x80 (Half Blue)
, DARK_MAGENTA = 0x05 // 0x80,0x00,0x80
, DARK_CYAN = 0x06 // 0x00,0x80,0x80
, LIGHT_GRAY = 0x07 // 0xC0,0xC0,0xC0
, MONEY_GREEN = 0x08 // 0xC0,0xDC,0xC0 // not used
, SKY_BLUE = 0x09 // 0xA6,0xCA,0xF0 // not used
// Really need to have Quarter Green and Quarter Blue for Hi-Res
2011-02-18 16:30:12 +00:00
// OUR CUSTOM COLORS -- the extra colors HGR mode can display
2011-02-19 22:14:08 +00:00
// , DEEP_BLUE // Breaks TV Emulation Reference Test !?!? // Breaks the dam palette -- black monochrome TEXT output bug *sigh*
2006-03-01 00:23:42 +00:00
, DEEP_RED
, LIGHT_BLUE
, BROWN
, ORANGE
, PINK
, AQUA
2011-02-19 22:14:08 +00:00
// CUSTOM HGR COLORS (don't change order) - For tv emulation HGR Video Mode
2006-03-01 00:23:42 +00:00
, HGR_BLACK
, HGR_WHITE
2014-12-15 03:05:21 +00:00
, HGR_BLUE // HCOLOR=6 BLUE , 3000: 81 00 D5 AA
, HGR_ORANGE // HCOLOR=5 ORANGE , 2C00: 82 00 AA D5
, HGR_GREEN // HCOLOR=1 GREEN , 2400: 02 00 2A 55
, HGR_VIOLET // HCOLOR=2 VIOLET , 2800: 01 00 55 2A
2006-03-01 00:23:42 +00:00
, HGR_GREY1
, HGR_GREY2
, HGR_YELLOW
, HGR_AQUA
, HGR_PURPLE
, HGR_PINK
2011-01-07 09:23:16 +00:00
// MONOCHROME
, MONOCHROME_CUSTOM // 100% luminance
, MONOCHROME_CUSTOM_50 // 50% luminance
2006-03-01 00:23:42 +00:00
, MONOCHROME_AMBER
, MONOCHROME_GREEN
2009-02-16 20:30:38 +00:00
, DEBUG_COLORS_START
, DEBUG_COLORS_END = DEBUG_COLORS_START + NUM_DEBUG_COLORS
2006-03-01 00:23:42 +00:00
, NUM_COLOR_PALETTE
2011-02-19 22:14:08 +00:00
// The last 10 are the DEFAULT Windows colors (as it reserves 20 colors)
, CREAM = 0xF6
, MEDIUM_GRAY = 0xF7
, DARK_GRAY = 0xF8
, RED = 0xF9
, GREEN = 0xFA
, YELLOW = 0xFB
, BLUE = 0xFC
, MAGENTA = 0xFD
, CYAN = 0xFE
, WHITE = 0xFF
2006-03-01 00:23:42 +00:00
} ;
2011-02-19 22:14:08 +00:00
// __ Map HGR color index to Palette index
enum ColorMapping
{
2014-12-15 02:03:45 +00:00
CM_Violet
2011-02-19 22:14:08 +00:00
, CM_Blue
, CM_Green
, CM_Orange
2014-12-15 03:05:21 +00:00
, CM_Black // Used
, CM_White // Used
2011-02-19 22:14:08 +00:00
, NUM_COLOR_MAPPING
} ;
const BYTE HiresToPalIndex [ NUM_COLOR_MAPPING ] =
{
2014-12-15 02:03:45 +00:00
HGR_VIOLET
2011-02-19 22:14:08 +00:00
, HGR_BLUE
, HGR_GREEN
2014-12-14 22:55:19 +00:00
, HGR_ORANGE
2011-02-19 22:14:08 +00:00
, HGR_BLACK
, HGR_WHITE
} ;
const BYTE LoresResColors [ 16 ] = {
BLACK , DEEP_RED , DARK_BLUE , MAGENTA ,
DARK_GREEN , DARK_GRAY , BLUE , LIGHT_BLUE ,
BROWN , ORANGE , LIGHT_GRAY , PINK ,
GREEN , YELLOW , AQUA , HGR_WHITE
} ;
const BYTE DoubleHiresPalIndex [ 16 ] = {
BLACK , DARK_BLUE , DARK_GREEN , BLUE ,
BROWN , LIGHT_GRAY , GREEN , AQUA ,
DEEP_RED , MAGENTA , DARK_GRAY , LIGHT_BLUE ,
ORANGE , PINK , YELLOW , HGR_WHITE
} ;
2011-01-08 02:58:20 +00:00
const int SRCOFFS_40COL = 0 ; // 0
const int SRCOFFS_IIPLUS = ( SRCOFFS_40COL + 256 ) ; // 256
const int SRCOFFS_80COL = ( SRCOFFS_IIPLUS + 256 ) ; // 512
const int SRCOFFS_LORES = ( SRCOFFS_80COL + 128 ) ; // 640
const int SRCOFFS_HIRES = ( SRCOFFS_LORES + 16 ) ; // 656
const int SRCOFFS_DHIRES = ( SRCOFFS_HIRES + 512 ) ; // 1168
const int SRCOFFS_TOTAL = ( SRCOFFS_DHIRES + 2560 ) ; // 3278
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
2006-06-29 03:28:25 +00:00
# define SETSOURCEPIXEL(x,y,c) g_aSourceStartofLine[(y)][(x)] = (c)
2006-02-25 20:50:29 +00:00
2011-01-09 04:42:46 +00:00
# define SETFRAMECOLOR(i,r,g,b) g_pFramebufferinfo->bmiColors[i].rgbRed = r; \
g_pFramebufferinfo - > bmiColors [ i ] . rgbGreen = g ; \
g_pFramebufferinfo - > bmiColors [ i ] . rgbBlue = b ; \
g_pFramebufferinfo - > bmiColors [ i ] . rgbReserved = PC_NOCOLLAPSE ;
2006-02-25 20:50:29 +00:00
2011-02-19 22:14:08 +00:00
# define HGR_MATRIX_YOFFSET 2 // For tv emulation HGR Video Mode
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
2014-06-27 21:43:25 +00:00
static BYTE celldirty [ 40 ] [ 32 ] ; // [TC: 27/06/2014] NB. No longer used!
2011-01-09 04:42:46 +00:00
// NUM_COLOR_PALETTE
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
2011-01-06 17:10:23 +00:00
static LPBYTE g_aFrameBufferOffset [ FRAMEBUFFER_H ] ; // array of pointers to start of each scanline
2006-06-29 03:28:25 +00:00
static LPBYTE g_pHiresBank1 ;
static LPBYTE g_pHiresBank0 ;
2006-07-02 22:58:12 +00:00
HBITMAP g_hLogoBitmap ;
2006-06-29 03:28:25 +00:00
static HPALETTE g_hPalette ;
static HBITMAP g_hSourceBitmap ;
static LPBYTE g_pSourcePixels ;
static LPBITMAPINFO g_pSourceHeader ;
const int MAX_SOURCE_Y = 512 ;
static LPBYTE g_aSourceStartofLine [ MAX_SOURCE_Y ] ;
static LPBYTE g_pTextBank1 ; // Aux
static LPBYTE g_pTextBank0 ; // Main
2006-02-25 20:50:29 +00:00
2011-02-19 22:14:08 +00:00
// For tv emulation HGR Video Mode
2006-06-29 03:28:25 +00:00
// 2 extra scan lines on bottom?
2008-08-31 04:31:35 +00:00
static BYTE hgrpixelmatrix [ FRAMEBUFFER_W / 2 ] [ FRAMEBUFFER_H / 2 + 2 * HGR_MATRIX_YOFFSET ] ;
2006-02-25 20:50:29 +00:00
static BYTE colormixbuffer [ 6 ] ;
static WORD colormixmap [ 6 ] [ 6 ] [ 6 ] ;
//
2008-08-25 05:25: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
static /*bool*/ UINT g_VideoForceFullRedraw = 1 ;
2014-09-21 20:42:19 +00:00
static bool g_bVideoUpdatedThisFrame = false ;
2008-08-25 05:25:27 +00:00
2006-02-25 20:50:29 +00:00
static LPBYTE framebufferaddr = ( LPBYTE ) 0 ;
2011-01-06 17:10:23 +00:00
static LONG g_nFrameBufferPitch = 0 ;
2015-01-01 17:59:58 +00:00
COLORREF g_nMonochromeRGB = RGB ( 0xC0 , 0xC0 , 0xC0 ) ;
static BOOL rebuiltsource = 0 ;
static LPBYTE vidlastmem = NULL ;
2011-01-04 17:08:01 +00:00
2014-09-14 15:15:14 +00:00
static UINT g_uVideoMode = VF_TEXT ;
2009-02-14 03:53:28 +00:00
2011-02-19 22:14:08 +00:00
DWORD g_eVideoType = VT_COLOR_TVEMU ;
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
DWORD g_uHalfScanLines = 1 ; // drop 50% scan lines for a more authentic look
2009-02-14 03:53:28 +00:00
2006-02-25 20:50:29 +00:00
static bool g_bTextFlashState = false ;
static bool g_bTextFlashFlag = false ;
2006-03-12 09:05:39 +00:00
static bool bVideoScannerNTSC = true ; // NTSC video scanning (or PAL)
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 [ ] =
2011-02-19 22:14:08 +00:00
TEXT ( " Monochrome (Custom Luminance) \0 " )
2011-02-18 15:29:24 +00:00
TEXT ( " Color (Standard) \0 " )
2011-02-19 22:14:08 +00:00
TEXT ( " Color (Text Optimized) \0 " )
2011-01-08 02:58:20 +00:00
TEXT ( " Color (TV emulation) \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
;
2011-02-19 22:14:08 +00:00
// AppleWin 1.19.4 VT_COLOR_AUTHENTIC -> VT_COLOR_HALFPIXEL -> VT_COLOR_STANDARD "Color Half-Pixel Authentic
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 ] =
{
2011-02-19 22:14:08 +00:00
" Monochrome (Custom) "
, " Standard "
, " Text Optimized "
, " TV "
, " Amber "
, " Green "
, " White "
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-02-19 22:14:08 +00:00
void V_CreateLookup_Lores ( ) ;
2015-01-01 01:57:57 +00:00
2011-01-08 02:58:20 +00:00
// Monochrome Half-Pixel Support
2011-02-19 22:14:08 +00:00
void V_CreateLookup_MonoHiResHalfPixel_Real ( ) ;
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 ) ;
// true = 280x192
// false = 560x384
void Video_SaveScreenShot ( const char * pScreenShotFileName ) ;
void Video_MakeScreenShot ( FILE * pFile ) ;
int GetMonochromeIndex ( ) ;
2011-01-09 04:42:46 +00:00
void V_CreateIdentityPalette ( ) ;
void V_CreateDIBSections ( ) ;
2011-01-08 02:58:20 +00:00
/** Our BitBlit() / VRAM_Copy()
2011-01-06 17:10:23 +00:00
@ param dx Dst X
@ param dy Dst Y
@ param w Width ( same for src & dst )
@ param h Height ( same for src & dst )
@ param sx Src X
@ param sy Src Y
// =========================================================================== */
2014-06-26 21:44:02 +00:00
2006-02-25 20:50:29 +00:00
//===========================================================================
2011-01-08 02:58:20 +00:00
void CreateFrameOffsetTable ( LPBYTE addr , LONG pitch )
{
if ( ( framebufferaddr = = addr ) & & ( g_nFrameBufferPitch = = pitch ) )
return ;
framebufferaddr = addr ;
g_nFrameBufferPitch = pitch ;
// CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE FRAME BUFFER
for ( int y = 0 ; y < FRAMEBUFFER_H ; y + + )
g_aFrameBufferOffset [ y ] = framebufferaddr + g_nFrameBufferPitch * ( ( FRAMEBUFFER_H - 1 ) - y ) ;
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
// CREATE A BUFFER FOR AN IMAGE OF THE LAST DRAWN MEMORY
vidlastmem = ( LPBYTE ) VirtualAlloc ( NULL , 0x10000 , MEM_COMMIT , PAGE_READWRITE ) ;
ZeroMemory ( vidlastmem , 0x10000 ) ;
// LOAD THE LOGO
g_hLogoBitmap = ( HBITMAP ) LoadImage ( g_hInstance , MAKEINTRESOURCE ( IDB_APPLEWIN ) , IMAGE_BITMAP , 0 , 0 , LR_CREATEDIBSECTION ) ;
// CREATE A BITMAPINFO STRUCTURE FOR THE FRAME BUFFER
g_pFramebufferinfo = ( LPBITMAPINFO ) VirtualAlloc (
NULL ,
sizeof ( BITMAPINFOHEADER ) + 256 * sizeof ( RGBQUAD ) ,
MEM_COMMIT ,
PAGE_READWRITE ) ;
ZeroMemory ( g_pFramebufferinfo , sizeof ( BITMAPINFOHEADER ) + 256 * sizeof ( RGBQUAD ) ) ;
2014-12-31 22:13:36 +00:00
g_pFramebufferinfo - > bmiHeader . biSize = sizeof ( BITMAPINFOHEADER ) ;
g_pFramebufferinfo - > bmiHeader . biWidth = FRAMEBUFFER_W ;
g_pFramebufferinfo - > bmiHeader . biHeight = FRAMEBUFFER_H ;
g_pFramebufferinfo - > bmiHeader . biPlanes = 1 ;
g_pFramebufferinfo - > bmiHeader . biBitCount = 32 ;
g_pFramebufferinfo - > bmiHeader . biCompression = BI_RGB ;
g_pFramebufferinfo - > bmiHeader . biClrUsed = 0 ;
2011-01-09 04:42:46 +00:00
2014-12-31 22:13:36 +00:00
NTSC_VideoCreateDIBSection ( ) ;
2015-01-01 17:59:58 +00:00
// VideoReinitialize(); // Can't call here since Config_Video_Load() hasn't been called yet
2011-01-09 04:42:46 +00:00
}
//===========================================================================
int GetMonochromeIndex ( )
{
int iMonochrome ;
switch ( g_eVideoType )
{
case VT_MONO_AMBER : iMonochrome = MONOCHROME_AMBER ; break ;
case VT_MONO_GREEN : iMonochrome = MONOCHROME_GREEN ; break ;
2011-02-19 22:14:08 +00:00
case VT_MONO_WHITE : iMonochrome = HGR_WHITE ; break ;
2011-01-09 04:42:46 +00:00
default : iMonochrome = MONOCHROME_CUSTOM ; break ; // caller will use MONOCHROME_CUSTOM MONOCHROME_CUSTOM_50 !
}
return iMonochrome ;
}
//===========================================================================
void V_CreateIdentityPalette ( )
2009-02-16 20:30:38 +00:00
{
2011-02-19 22:14:08 +00:00
SETFRAMECOLOR ( BLACK , 0x00 , 0x00 , 0x00 ) ; // 0
2014-12-15 03:05:21 +00:00
SETFRAMECOLOR ( DARK_RED , 0x80 , 0x00 , 0x00 ) ; // 1 // not used
SETFRAMECOLOR ( DARK_GREEN , 0x00 , 0x80 , 0x00 ) ; // 2 // not used
2011-02-19 22:14:08 +00:00
SETFRAMECOLOR ( DARK_YELLOW , 0x80 , 0x80 , 0x00 ) ; // 3
2014-12-15 03:05:21 +00:00
SETFRAMECOLOR ( DARK_BLUE , 0x00 , 0x00 , 0x80 ) ; // 4 // not used
2011-02-19 22:14:08 +00:00
SETFRAMECOLOR ( DARK_MAGENTA , 0x80 , 0x00 , 0x80 ) ; // 5
SETFRAMECOLOR ( DARK_CYAN , 0x00 , 0x80 , 0x80 ) ; // 6
2014-07-10 21:52:26 +00:00
SETFRAMECOLOR ( LIGHT_GRAY , 0xC0 , 0xC0 , 0xC0 ) ; // 7 // GR: COLOR=10
2011-02-19 22:14:08 +00:00
SETFRAMECOLOR ( MONEY_GREEN , 0xC0 , 0xDC , 0xC0 ) ; // 8 // not used
SETFRAMECOLOR ( SKY_BLUE , 0xA6 , 0xCA , 0xF0 ) ; // 9 // not used
2006-02-25 20:50:29 +00:00
2006-03-01 00:23:42 +00:00
// SET FRAME BUFFER TABLE ENTRIES TO CUSTOM COLORS
2014-12-15 03:05:21 +00:00
// SETFRAMECOLOR(DARK_RED, 0x9D,0x09,0x66); // 1 // Linards Tweaked 0x80,0x00,0x00 -> 0x9D,0x09,0x66
// SETFRAMECOLOR(DARK_GREEN, 0x00,0x76,0x1A); // 2 // Linards Tweaked 0x00,0x80,0x00 -> 0x00,0x76,0x1A
// SETFRAMECOLOR(DARK_BLUE, 0x2A,0x2A,0xE5); // 4 // Linards Tweaked 0x00,0x00,0x80 -> 0x2A,0x2A,0xE5
2011-02-19 22:14:08 +00:00
SETFRAMECOLOR ( DEEP_RED , 0x9D , 0x09 , 0x66 ) ; // 0xD0,0x00,0x30 -> Linards Tweaked 0x9D,0x09,0x66
SETFRAMECOLOR ( LIGHT_BLUE , 0xAA , 0xAA , 0xFF ) ; // 0x60,0xA0,0xFF -> Linards Tweaked 0xAA,0xAA,0xFF
SETFRAMECOLOR ( BROWN , 0x55 , 0x55 , 0x00 ) ; // 0x80,0x50,0x00 -> Linards Tweaked 0x55,0x55,0x00
SETFRAMECOLOR ( ORANGE , 0xF2 , 0x5E , 0x00 ) ; // 0xFF,0x80,0x00 -> Linards Tweaked 0xF2,0x5E,0x00
SETFRAMECOLOR ( PINK , 0xFF , 0x89 , 0xE5 ) ; // 0xFF,0x90,0x80 -> Linards Tweaked 0xFF,0x89,0xE5
SETFRAMECOLOR ( AQUA , 0x62 , 0xF6 , 0x99 ) ; // 0x40,0xFF,0x90 -> Linards Tweaked 0x62,0xF6,0x99
SETFRAMECOLOR ( HGR_BLACK , 0x00 , 0x00 , 0x00 ) ; // For TV emulation HGR Video Mode
SETFRAMECOLOR ( HGR_WHITE , 0xFF , 0xFF , 0xFE ) ; // BUG: PALETTE COLLAPSE! NOT white!? Win32 collapses the palette if you have duplicate colors!
2014-12-15 03:05:21 +00:00
// 20 207 253 = 0x14 0xCF 0xFD
SETFRAMECOLOR ( HGR_BLUE , 24 , 115 , 229 ) ; // HCOLOR=6 BLUE 3000: 81 00 D5 AA // 0x00,0x80,0xFF -> Linards Tweaked 0x0D,0xA1,0xFF
SETFRAMECOLOR ( HGR_ORANGE , 247 , 64 , 30 ) ; // HCOLOR=5 ORANGE 2C00: 82 00 AA D5 // 0xF0,0x50,0x00 -> Linards Tweaked 0xF2,0x5E,0x00
SETFRAMECOLOR ( HGR_GREEN , 27 , 211 , 79 ) ; // HCOLOR=1 GREEN 2400: 02 00 2A 55 // 0x20,0xC0,0x00 -> Linards Tweaked 0x38,0xCB,0x00
SETFRAMECOLOR ( HGR_VIOLET , 227 , 20 , 255 ) ; // HCOLOR=2 VIOLET 2800: 01 00 55 2A // 0xA0,0x00,0xFF -> Linards Tweaked 0xC7,0x34,0xFF
2006-03-01 00:23:42 +00:00
SETFRAMECOLOR ( HGR_GREY1 , 0x80 , 0x80 , 0x80 ) ;
SETFRAMECOLOR ( HGR_GREY2 , 0x80 , 0x80 , 0x80 ) ;
2011-02-19 22:14:08 +00:00
SETFRAMECOLOR ( HGR_YELLOW , 0x9E , 0x9E , 0x00 ) ; // 0xD0,0xB0,0x10 -> 0x9E,0x9E,0x00
SETFRAMECOLOR ( HGR_AQUA , 0x00 , 0xCD , 0x4A ) ; // 0x20,0xB0,0xB0 -> 0x00,0xCD,0x4A
SETFRAMECOLOR ( HGR_PURPLE , 0x61 , 0x61 , 0xFF ) ; // 0x60,0x50,0xE0 -> 0x61,0x61,0xFF
SETFRAMECOLOR ( HGR_PINK , 0xFF , 0x32 , 0xB5 ) ; // 0xD0,0x40,0xA0 -> 0xFF,0x32,0xB5
2006-03-01 00:23:42 +00:00
SETFRAMECOLOR ( MONOCHROME_CUSTOM
2015-01-01 17:59:58 +00:00
, GetRValue ( g_nMonochromeRGB )
, GetGValue ( g_nMonochromeRGB )
, GetBValue ( g_nMonochromeRGB )
2011-01-09 04:42:46 +00:00
) ;
2006-03-01 00:23:42 +00:00
2011-01-07 09:23:16 +00:00
SETFRAMECOLOR ( MONOCHROME_CUSTOM_50
2015-01-01 17:59:58 +00:00
, ( ( GetRValue ( g_nMonochromeRGB ) / 2 ) & 0xFF )
, ( ( GetGValue ( g_nMonochromeRGB ) / 2 ) & 0xFF )
, ( ( GetBValue ( g_nMonochromeRGB ) / 2 ) & 0xFF )
2011-01-09 04:42:46 +00:00
) ;
2011-01-07 09:23:16 +00:00
2011-02-15 17:16:36 +00:00
SETFRAMECOLOR ( MONOCHROME_AMBER , 0xFF , 0x80 , 0x01 ) ; // Used for Monochrome Hi-Res graphics not text!
SETFRAMECOLOR ( MONOCHROME_GREEN , 0x00 , 0xC0 , 0x01 ) ; // Used for Monochrome Hi-Res graphics not text!
2011-01-09 04:42:46 +00:00
// BUG PALETTE COLLAPSE: WTF?? Soon as we set 0xFF,0xFF,0xFF we lose text colors?!?!
// Windows is collapsing the palette!!!
2011-02-19 22:14:08 +00:00
//SETFRAMECOLOR( MONOCHROME_WHITE , 0xFE,0xFE,0xFE); // Used for Monochrome Hi-Res graphics not text!
2011-01-09 04:42:46 +00:00
2011-02-19 22:14:08 +00:00
SETFRAMECOLOR ( CREAM , 0xFF , 0xFB , 0xF0 ) ; // F6
SETFRAMECOLOR ( MEDIUM_GRAY , 0xA0 , 0xA0 , 0xA4 ) ; // F7
SETFRAMECOLOR ( DARK_GRAY , 0x80 , 0x80 , 0x80 ) ; // F8
SETFRAMECOLOR ( RED , 0xFF , 0x00 , 0x00 ) ; // F9
2014-12-15 03:05:21 +00:00
SETFRAMECOLOR ( GREEN , 0x38 , 0xCB , 0x00 ) ; // FA Linards Tweaked 0x00,0xFF,0x00 -> 0x38,0xCB,0x00
SETFRAMECOLOR ( YELLOW , 0xD5 , 0xD5 , 0x1A ) ; // FB Linards Tweaked 0xFF,0xFF,0x00 -> 0xD5,0xD5,0x1A
SETFRAMECOLOR ( BLUE , 0x0D , 0xA1 , 0xFF ) ; // FC Linards Tweaked 0x00,0x00,0xFF -> 0x0D,0xA1,0xFF
SETFRAMECOLOR ( MAGENTA , 0xC7 , 0x34 , 0xFF ) ; // FD Linards Tweaked 0xFF,0x00,0xFF -> 0xC7,0x34,0xFF
2011-02-19 22:14:08 +00:00
SETFRAMECOLOR ( CYAN , 0x00 , 0xFF , 0xFF ) ; // FE
SETFRAMECOLOR ( WHITE , 0xFF , 0xFF , 0xFF ) ; // FF
2006-02-25 20:50:29 +00:00
}
2009-02-14 03:53:28 +00:00
#if 0
2014-07-10 21:52:26 +00:00
//===========================================================================
void V_CreateLookup_HiResHalfPixel_Authentic ( ) // Colors are solid (100% coverage)
{
// 2-bits from previous byte, 2-bits from next byte = 2^4 = 16 total permutations
2011-01-07 09:23:16 +00:00
for ( int iColumn = 0 ; iColumn < 16 ; iColumn + + )
{
2014-07-10 21:52:26 +00:00
int offsetx = iColumn < < 5 ; // every column is 32 bytes wide -- 7 apple pixels = 14 pixels + 2 pad + 14 pixels + 2 pad
2011-01-07 09:23:16 +00:00
for ( unsigned iByte = 0 ; iByte < 256 ; iByte + + )
{
2014-07-10 21:52:26 +00:00
int aPixels [ 11 ] ; // c2 c1 b7 b6 b5 b4 b3 b2 b1 b0 c8 c4
2011-01-07 09:23:16 +00:00
2014-07-10 21:52:26 +00:00
/*
aPixel [ i ]
A 9 | 8 7 6 5 4 3 2 | 1 0
Z W | b b b b b b b | X Y
- - - - + - - - - - - - - - - - - - + - - - -
prev | existing | next
bits | hi - res byte | bits
Legend :
XYZW = iColumn in binary
b = Bytes in binary
*/
// aPixel[] = 48bbbbbbbb12, where b = iByte in binary, # is bit-n of column
aPixels [ 0 ] = iColumn & 4 ; // previous byte, 2nd last pixel
aPixels [ 1 ] = iColumn & 8 ; // previous byte, last pixel
aPixels [ 9 ] = iColumn & 1 ; // next byte, first pixel
aPixels [ 10 ] = iColumn & 2 ; // next byte, second pixel
2011-01-07 09:23:16 +00:00
2014-07-10 21:52:26 +00:00
// Convert raw pixel Byte value to binary and stuff into bit array of pixels on off
2011-01-07 09:23:16 +00:00
int nBitMask = 1 ;
int iPixel ;
for ( iPixel = 2 ; iPixel < 9 ; iPixel + + )
{
aPixels [ iPixel ] = ( ( iByte & nBitMask ) ! = 0 ) ;
nBitMask < < = 1 ;
}
2014-07-10 21:52:26 +00:00
int hibit = ( iByte > > 7 ) & 1 ; // ((iByte & 0x80) != 0);
2011-01-07 09:23:16 +00:00
int x = 0 ;
int y = iByte < < 1 ;
2014-07-10 21:52:26 +00:00
/* Test cases
81 blue
2000 : D5 AA D5 AA
82 orange
2800 : AA D5 AA D5
FF white bleed " thru "
3000 : 7F 80 7F 80
3800 : FF 80 FF 80
2028 : 80 7F 80 7F
2828 : 80 FF 80 FF
Edge Case for Half Luminance !
2000 : C4 00 // Green HalfLumBlue
2400 : C4 80 // Green Green
Edge Case for Color Bleed !
2000 : 40 00
2400 : 40 80
*/
// Fixup missing pixels that normally have been scan-line shifted -- Apple "half-pixel" -- but cross 14-pixel boundaries.
if ( hibit )
{
2015-01-01 01:57:57 +00:00
2014-07-10 21:52:26 +00:00
// Test Patterns
// 81 blue
// 2000:D5 AA D5 AA -> 2001:AA D5 should not have black gap, should be blue
// 82 orange
// 2800:AA D5 AA D5
// Game: Elite -- Loading Logo
// 2444:BB F7 -> 2000:BB F7 // Should not have orange in-between gap -- Elite "Firebird" Logo
// -> 2400:00 BB F7 // Should not have blue in-between gap )
// 21D0:C0 00 -> HalfLumBlue
// 25D0:C0 D0 88 -> Blue black orange black orange
// 29D0:C0 90 88 -> Blue black orange
// Game: Ultima 4 -- Ultima 4 Logo - bottom half of screen has a "mini-game" / demo -- far right has tree and blue border
// 2176:2A AB green black_gap white blue_border // Should have black gap between green and white
// Game: Gumball
// 218E:AA 97 => 2000: A9 87 orange_white // Should have no gap between orange and white
// 229A:AB A9 87 -> 2000: 00 A9 87 white orange black blue_white // Should have no gap between blue and white
// 2001:BB F7 white blue white (Gumball Intermission)
// Torture Half-Pixel HGR Tests: This is a real bitch to solve -- we really need to check:
// if (hibit_prev_byte && !aPixels[iPixel-3] && aPixels[iPixel-2] && !aPixels[iPixel] && hibit_this_byte) then set first half-pixel of this byte to either blue or orange
// 2000:A9 87 halfblack blue black black orange black orange black
// 2400:BB F7 halfblack white white black white white white halfblack
// or
// 2000:A0 83 orange should "bleed" thru
// 2400:B0 83 should have black gap
if ( aPixels [ 2 ] )
# if HALF_PIXEL_BLEED // No Half-Pixel Bleed
SETSOURCEPIXEL ( SRCOFFS_HIRES + offsetx + x + 0 , y , DARK_BLUE ) ; // Gumball: 229A: AB A9 87
SETSOURCEPIXEL ( SRCOFFS_HIRES + offsetx + x + 16 , y , BROWN ) ; // half luminance red Elite: 2444: BB F7
SETSOURCEPIXEL ( SRCOFFS_HIRES + offsetx + x + 16 , y + 1 , BROWN ) ; // half luminance red Gumball: 218E: AA 97
SETSOURCEPIXEL ( SRCOFFS_HIRES + offsetx + x + 0 , y , HGR_BLUE ) ; // 2000:D5 AA D5
2014-12-14 22:55:19 +00:00
SETSOURCEPIXEL ( SRCOFFS_HIRES + offsetx + x + 16 , y , HGR_ORANGE ) ; // 2000: AA D5
2014-07-10 21:52:26 +00:00
# else
2015-01-01 01:57:57 +00:00
// "Text optimized" IF this pixel on, and adjacent right pixel off, then colorize first half-pixel of this byte
2014-07-10 21:52:26 +00:00
SETSOURCEPIXEL ( SRCOFFS_HIRES + offsetx + x + 0 , y , HGR_BLUE ) ; // 2000:D5 AA D5
2014-12-14 22:55:19 +00:00
SETSOURCEPIXEL ( SRCOFFS_HIRES + offsetx + x + 16 , y , HGR_ORANGE ) ; // 2000: AA D5
2014-07-10 21:52:26 +00:00
# endif // HALF_PIXEL_BLEED
// Test Pattern: Ultima 4 Logo - Castle
// 3AC8: 36 5B 6D 36
2011-02-17 17:29:30 +00:00
}
2014-12-16 02:02:54 +00:00
/*
Address Binary - > Displayed
2000 : 01 0 - - - 0001 - > 1 0 0 0 column 1
2400 : 81 1 - - - 0001 - > 1 0 0 0 half - pixel shift right
2800 : 02 1 - - - 0010 - > 0 1 0 0 column 2
2000 : 02 column 2
2400 : 82 half - pixel shift right
2800 : 04 column 3
2000 : 03 0 - - - 0011 - > 1 1 0 0 column 1 & 2
2400 : 83 1 - - - 0011 - > 1 1 0 0 half - pixel shift right
2800 : 06 1 - - - 0110 - > 0 1 1 0 column 2 & 3
@ reference : see Beagle Bro ' s Disk : " Silicon Salad " , File : DOUBLE HI - RES
Fortunately double - hires is supported via pixel doubling , so we can do half - pixel shifts ; - )
*/
2011-01-08 21:29:27 +00:00
2011-01-09 00:45:12 +00:00
// Fixup missing pixels that normally have been scan-line shifted -- Apple "half-pixel" -- but cross 14-pixel boundaries.
2011-01-09 00:40:21 +00:00
/* Test Cases
// Games
Archon Logo
Gumball ( at Machine )
// Applesoft
HGR : HCOLOR = 5 : HPLOT 0 , 0 TO 279 , 0
2011-01-09 00:45:12 +00:00
2011-01-09 00:40:21 +00:00
// Blue
// Orange
2011-01-09 00:45:12 +00:00
CALL - 151
C050 C052 C057
2000 : D0 80 00
2011-01-09 00:40:21 +00:00
2800 : 80 D0 00
*/
2009-02-14 03:53:28 +00:00
# endif
2008-08-27 08:47:52 +00:00
// google: CreateDIBPatternBrushPt
// http://209.85.141.104/search?q=cache:mB3htrQGW8kJ:bookfire.net/wince/wince-programming-ms-press2/source/prowice/ch02e.htm
struct BRUSHBMP
{
BITMAPINFOHEADER bmi ;
COLORREF dwPal [ 2 ] ;
BYTE bBits [ 64 ] ;
} ;
2006-02-25 20:50:29 +00:00
//===========================================================================
2014-09-21 15:12:05 +00:00
static void CreateLookup_TextCommon ( HDC hDstDC , DWORD rop )
2006-06-29 03:28:25 +00:00
{
2014-09-21 15:12:05 +00:00
HBITMAP hCharBitmap [ 4 ] ;
HDC hSrcDC = CreateCompatibleDC ( hDstDC ) ;
2008-06-20 23:47:25 +00:00
hCharBitmap [ 0 ] = LoadBitmap ( g_hInstance , TEXT ( " CHARSET40 " ) ) ;
hCharBitmap [ 1 ] = LoadBitmap ( g_hInstance , TEXT ( " CHARSET82 " ) ) ;
2009-02-14 03:53:28 +00:00
hCharBitmap [ 2 ] = LoadBitmap ( g_hInstance , TEXT ( " CHARSET8C " ) ) ; // FIXME: Pravets 8M probably has the same charset as Pravets 8C
2009-01-09 21:59:22 +00:00
hCharBitmap [ 3 ] = LoadBitmap ( g_hInstance , TEXT ( " CHARSET8C " ) ) ;
2014-09-21 15:12:05 +00:00
SelectObject ( hSrcDC , hCharBitmap [ g_nCharsetType ] ) ;
// TODO: Update with APPLE_FONT_Y_ values
BitBlt ( hDstDC , SRCOFFS_40COL , 0 , 256 , 512 , hSrcDC , 0 , 0 , rop ) ;
BitBlt ( hDstDC , SRCOFFS_IIPLUS , 0 , 256 , 256 , hSrcDC , 0 , 512 , rop ) ; // Chars for Apple ][
StretchBlt ( hDstDC , SRCOFFS_80COL , 0 , 128 , 512 , hSrcDC , 0 , 0 , 256 , 512 , rop ) ; // Chars for 80 col mode
DeleteDC ( hSrcDC ) ;
for ( UINT i = 0 ; i < 4 ; i + + )
DeleteObject ( hCharBitmap [ i ] ) ;
}
2006-02-25 20:50:29 +00:00
//===========================================================================
2009-02-18 22:24:50 +00:00
void SetLastDrawnImage ( )
{
memcpy ( vidlastmem + 0x400 , g_pTextBank0 , 0x400 ) ;
if ( SW_HIRES )
memcpy ( vidlastmem + 0x2000 , g_pHiresBank0 , 0x2000 ) ;
if ( SW_DHIRES & & SW_HIRES )
memcpy ( vidlastmem , g_pHiresBank1 , 0x2000 ) ;
else if ( SW_80COL ) // Don't test for !SW_HIRES, as some 80-col text routines have SW_HIRES set (Bug #8300)
memcpy ( vidlastmem , g_pTextBank1 , 0x400 ) ;
int loop ;
for ( loop = 0 ; loop < 256 ; loop + + )
{
* ( memdirty + loop ) & = ~ 2 ;
}
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2012-09-18 19:55:29 +00:00
static inline int GetOriginal2EOffset ( BYTE ch )
{
// No mousetext for original IIe
return ! IsOriginal2E ( ) | | ! g_nAltCharSetOffset | | ( ch < 0x40 ) | | ( ch > 0x5F ) ? 0 : - g_nAltCharSetOffset ;
}
2011-02-19 22:14:08 +00:00
/*
Color Reference Tests :
2000 : D5 AA D5 AA D5 AA // blue blue blue
2014-12-15 03:05:21 +00:00
2400 : AA D5 2 A 55 55 2 A //+ red green violet
// //= grey aqua violet
2011-02-19 22:14:08 +00:00
2 C00 : AA D5 AA D5 2 A 55 // red red green
2014-12-15 03:05:21 +00:00
3000 : 2 A 55 55 2 A 55 2 A //+ green violet violet
2011-02-19 22:14:08 +00:00
// //= yellow pink grey
*/
2006-02-25 20:50:29 +00:00
//===========================================================================
2009-07-23 21:43:08 +00:00
# define ROL_NIB(x) ( (((x)<<1)&0xF) | (((x)>>3)&1) )
2006-02-25 20:50:29 +00:00
//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//
//===========================================================================
BOOL VideoApparentlyDirty ( )
{
2008-08-25 05:25:27 +00:00
if ( SW_MIXED | | g_VideoForceFullRedraw )
2006-02-25 20:50:29 +00:00
return 1 ;
2008-08-31 04:31:35 +00:00
DWORD address = ( SW_HIRES & & ! SW_TEXT )
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
? ( 0x20 < < ( SW_PAGE2 ? 1 : 0 ) )
: ( 0x04 < < ( SW_PAGE2 ? 1 : 0 ) ) ;
2006-02-25 20:50:29 +00:00
DWORD length = ( SW_HIRES & & ! SW_TEXT ) ? 0x20 : 0x4 ;
while ( length - - )
if ( * ( memdirty + ( address + + ) ) & 2 )
return 1 ;
//
// Scan visible text page for any flashing chars
2006-06-29 03:28:25 +00:00
if ( ( SW_TEXT | | SW_MIXED ) & & ( g_nAltCharSetOffset = = 0 ) )
2006-02-25 20:50:29 +00:00
{
2014-09-15 21:47:50 +00:00
BYTE * pTextBank0 = MemGetMainPtr ( 0x400 < < ( SW_PAGE2 ? 1 : 0 ) ) ;
BYTE * pTextBank1 = MemGetAuxPtr ( 0x400 < < ( SW_PAGE2 ? 1 : 0 ) ) ;
const bool b80Col = SW_80COL ;
2006-02-25 20:50:29 +00:00
// Scan 8 long-lines of 120 chars (at 128 char offsets):
// . Skip 8-char holes in TEXT
for ( UINT y = 0 ; y < 8 ; y + + )
{
for ( UINT x = 0 ; x < 40 * 3 ; x + + )
{
2014-09-15 21:47:50 +00:00
BYTE ch = pTextBank0 [ y * 128 + x ] ;
2006-02-25 20:50:29 +00:00
if ( ( ch > = 0x40 ) & & ( ch < = 0x7F ) )
2014-09-15 21:47:50 +00:00
return 1 ;
if ( b80Col )
2006-02-25 20:50:29 +00:00
{
2014-09-15 21:47:50 +00:00
ch = pTextBank1 [ y * 128 + x ] ;
if ( ( ch > = 0x40 ) & & ( ch < = 0x7F ) )
return 1 ;
2006-02-25 20:50:29 +00:00
}
}
}
}
return 0 ;
}
//===========================================================================
void VideoBenchmark ( ) {
Sleep ( 500 ) ;
// PREPARE TWO DIFFERENT FRAME BUFFERS, EACH OF WHICH HAVE HALF OF THE
// BYTES SET TO 0x14 AND THE OTHER HALF SET TO 0xAA
int loop ;
LPDWORD mem32 = ( LPDWORD ) mem ;
for ( loop = 4096 ; loop < 6144 ; loop + + )
* ( mem32 + loop ) = ( ( loop & 1 ) ^ ( ( loop & 0x40 ) > > 6 ) ) ? 0x14141414
: 0xAAAAAAAA ;
for ( loop = 6144 ; loop < 8192 ; loop + + )
* ( mem32 + loop ) = ( ( loop & 1 ) ^ ( ( loop & 0x40 ) > > 6 ) ) ? 0xAAAAAAAA
: 0x14141414 ;
// SEE HOW MANY TEXT FRAMES PER SECOND WE CAN PRODUCE WITH NOTHING ELSE
// GOING ON, CHANGING HALF OF THE BYTES IN THE VIDEO BUFFER EACH FRAME TO
// SIMULATE THE ACTIVITY OF AN AVERAGE GAME
DWORD totaltextfps = 0 ;
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 ) ;
2015-01-01 01:15:36 +00:00
VideoRedrawScreen ( ) ; // 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 ) ;
2015-01-01 01:15:36 +00:00
VideoRedrawScreen ( ) ; // 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
CpuSetupBenchmark ( ) ;
DWORD totalmhz10 = 0 ;
milliseconds = GetTickCount ( ) ;
while ( GetTickCount ( ) = = milliseconds ) ;
milliseconds = GetTickCount ( ) ;
cycle = 0 ;
do {
CpuExecute ( 100000 ) ;
totalmhz10 + + ;
} while ( GetTickCount ( ) - milliseconds < 1000 ) ;
// IF THE PROGRAM COUNTER IS NOT IN THE EXPECTED RANGE AT THE END OF THE
// CPU BENCHMARK, REPORT AN ERROR AND OPTIONALLY TRACK IT DOWN
if ( ( regs . pc < 0x300 ) | | ( regs . pc > 0x400 ) )
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 ) ;
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 ) ;
cycles - = executedcycles ;
DiskUpdatePosition ( 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 ) ;
2015-01-01 01:15:36 +00:00
VideoRedrawScreen ( ) ; // VideoRefreshScreen();
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 \n \n " )
TEXT ( " EXPECTED AVERAGE VIDEO GAME \n " )
TEXT ( " PERFORMANCE: %u FPS " ) ,
( unsigned ) totalhiresfps ,
( unsigned ) totaltextfps ,
( unsigned ) ( totalmhz10 / 10 ) ,
( unsigned ) ( totalmhz10 % 10 ) ,
2007-05-28 11:16:42 +00:00
( 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 ) ;
}
//===========================================================================
2011-01-09 04:42:46 +00:00
BYTE VideoCheckMode ( WORD , WORD address , BYTE , BYTE , ULONG uExecutedCycles )
2007-05-28 11:16:42 +00:00
{
address & = 0xFF ;
2006-02-25 20:50:29 +00:00
if ( address = = 0x7F )
2010-08-17 07:52:23 +00:00
return MemReadFloatingBus ( SW_DHIRES ! = 0 , uExecutedCycles ) ;
2006-02-25 20:50:29 +00:00
else {
BOOL result = 0 ;
switch ( address ) {
case 0x1A : result = SW_TEXT ; break ;
case 0x1B : result = SW_MIXED ; break ;
case 0x1D : result = SW_HIRES ; break ;
2006-06-29 03:28:25 +00:00
case 0x1E : result = g_nAltCharSetOffset ; break ;
2006-02-25 20:50:29 +00:00
case 0x1F : result = SW_80COL ; break ;
case 0x7F : result = SW_DHIRES ; break ;
}
return KeybGetKeycode ( ) | ( result ? 0x80 : 0 ) ;
}
}
//===========================================================================
2014-07-08 19:58:48 +00:00
2010-08-17 07:52:23 +00:00
/*
// Drol expects = 80
68 DE A5 02 LDX # 02
68E0 AD 50 C0 LDA TXTCLR
68E3 C9 80 CMP # 80
68E5 D0 F7 BNE $ 68 DE
6957 A5 02 LDX # 02
6959 AD 50 C0 LDA TXTCLR
695 C C9 80 CMP # 80
695 E D0 F7 BNE $ 68 DE
69 D3 A5 02 LDX # 02
69 D5 AD 50 C0 LDA TXTCLR
69 D8 C9 80 CMP # 80
69 DA D0 F7 BNE $ 68 DE
// Karateka expects < 80
07 DE AD 19 C0 LDA RDVBLBAR
07E1 30 FB BMI $ 7 DE
77 A1 AD 19 C0 LDA RDVBLBAR
77 A4 30 FB BMI $ 7 DE
// Gumball expects non-zero low-nibble on VBL
BBB5 A5 60 LDA $ 60
BBB7 4 D 50 C0 EOR TXTCLR
BBBA 85 60 STA $ 60
BBBC 29 0F AND # $ 0F
BBBE F0 F5 BEQ $ BBB5
BBC0 C9 0F CMP # $ 0F
BBC2 F0 F1 BEQ $ BBB5
// Diversi-Dial (DD4.DSK or DIAL.DSK)
F822 LDA RDVBLBAR
F825 EOR # $ 3 C
BMI $ F82A
RTS
F82A LDA $ F825 + 1
EOR # $ 80
STA $ F825 + 1
BMI $ F86A
. . .
F86A RTS
*/
2011-01-09 04:42:46 +00:00
BYTE VideoCheckVbl ( WORD , WORD , BYTE , BYTE , ULONG uExecutedCycles )
2006-02-25 20:50:29 +00:00
{
2014-12-21 21:43:49 +00:00
bool bVblBar = VideoGetVbl ( uExecutedCycles ) ;
2006-03-12 09:05:39 +00:00
2010-08-17 07:52:23 +00:00
BYTE r = KeybGetKeycode ( ) ;
2014-12-21 21:43:49 +00:00
return ( r & ~ 0x80 ) | ( bVblBar ? 0x80 : 0 ) ;
2006-03-01 00:23:42 +00:00
}
2006-02-25 20:50:29 +00:00
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 ) ;
2006-06-29 03:28:25 +00:00
VirtualFree ( g_pSourceHeader , 0 , MEM_RELEASE ) ;
2006-02-25 20:50:29 +00:00
VirtualFree ( vidlastmem , 0 , MEM_RELEASE ) ;
2008-08-25 00:36:48 +00:00
g_pFramebufferinfo = NULL ;
2006-06-29 03:28:25 +00:00
g_pSourceHeader = NULL ;
2006-02-25 20:50:29 +00:00
vidlastmem = NULL ;
// 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 SOURCE IMAGE
2006-06-29 03:28:25 +00:00
DeleteObject ( g_hSourceBitmap ) ;
g_hSourceBitmap = ( 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
}
// DESTROY PALETTE
2006-06-29 03:28:25 +00:00
if ( g_hPalette ) {
DeleteObject ( g_hPalette ) ;
g_hPalette = ( HPALETTE ) 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
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 ( )
{
2012-12-29 14:53:52 +00:00
int xoff = 0 , yoff = 0 , scale = 0 ;
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
HBRUSH brush = CreateSolidBrush ( PALETTERGB ( 0x70 , 0x30 , 0xE0 ) ) ;
2012-12-29 14:53:52 +00:00
SelectObject ( hFrameDC , brush ) ;
SelectObject ( hFrameDC , GetStockObject ( NULL_PEN ) ) ;
int nViewportCX , nViewportCY ;
GetViewportCXCY ( nViewportCX , nViewportCY ) ;
Rectangle ( hFrameDC , 0 , 0 , nViewportCX + 1 , nViewportCY + 1 ) ;
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 ) )
{
scale = nViewportCX / bm . bmWidth ;
if ( nViewportCY / bm . bmHeight < scale )
scale = nViewportCY / bm . bmHeight ;
if ( scale > 0 )
{
if ( nViewportCX > bm . bmWidth )
xoff = ( nViewportCX - ( scale * bm . bmWidth ) ) / 2 ;
if ( nViewportCY > bm . bmHeight )
yoff = ( nViewportCY - ( scale * bm . bmHeight ) ) / 2 ;
VideoDrawLogoBitmap ( hFrameDC , xoff , yoff , bm . bmWidth , bm . bmHeight , scale ) ;
}
}
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
HFONT font = CreateFont ( - 20 , 0 , 0 , 0 , FW_NORMAL , 0 , 0 , 0 , ANSI_CHARSET ,
OUT_DEFAULT_PRECIS , CLIP_DEFAULT_PRECIS , DEFAULT_QUALITY ,
VARIABLE_PITCH | 4 | FF_SWISS ,
TEXT ( " Arial " ) ) ;
SelectObject ( hFrameDC , font ) ;
SetTextAlign ( hFrameDC , TA_RIGHT | TA_TOP ) ;
SetBkMode ( hFrameDC , TRANSPARENT ) ;
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 ) ;
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
FrameReleaseDC ( ) ;
DeleteObject ( brush ) ;
DeleteObject ( font ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2009-02-16 20:30:38 +00:00
void VideoRealizePalette ( HDC dc )
{
#if 0
if ( g_bIsFullScreen )
{
if ( ! g_pDDPal )
{
PALETTEENTRY aPal [ 256 ] ;
BYTE * pSrc = ( ( BYTE * ) g_pFramebufferinfo ) + sizeof ( BITMAPINFOHEADER ) ;
BYTE * pDst = ( ( BYTE * ) aPal ) ;
int iPal ;
for ( iPal = 0 ; iPal < 256 ; iPal + + )
{
* ( pDst + 0 ) = * ( pSrc + 2 ) ; // BGR -> RGB
* ( pDst + 1 ) = * ( pSrc + 1 ) ;
* ( pDst + 2 ) = * ( pSrc + 0 ) ;
* ( pDst + 3 ) = 0 ;
pDst + = 4 ;
pSrc + = 4 ;
}
if ( g_pDD - > CreatePalette ( DDPCAPS_8BIT , aPal , & g_pDDPal , NULL ) ! = DD_OK )
{
g_pDDPal = NULL ;
}
}
if ( g_pDDPal )
{
g_pDDPrimarySurface - > SetPalette ( g_pDDPal ) ; // this sets the palette for the primary surface
}
}
else
{
if ( g_hPalette )
{
SelectPalette ( dc , g_hPalette , 0 ) ;
RealizePalette ( dc ) ;
}
}
# endif
if ( g_hPalette )
{
SelectPalette ( dc , g_hPalette , 0 ) ;
RealizePalette ( dc ) ;
}
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2014-06-27 21:43:25 +00:00
2015-01-01 01:15:36 +00:00
void VideoRedrawScreen ( )
2011-01-07 09:23:16 +00:00
{
g_VideoForceFullRedraw = 1 ;
2015-01-01 19:48:59 +00:00
VideoRefreshScreen ( g_uVideoMode ) ; //g_uVideoMode );
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2008-08-25 05:25:27 +00:00
void _Video_Dirty ( )
{
ZeroMemory ( celldirty , 40 * 32 ) ;
}
//===========================================================================
2015-01-01 01:15:36 +00:00
int _Video_SetupBanks ( bool bBank2 )
2008-08-25 05:25:27 +00:00
{
2008-08-31 04:31:35 +00:00
g_pHiresBank1 = MemGetAuxPtr ( 0x2000 < < ( int ) bBank2 ) ;
g_pHiresBank0 = MemGetMainPtr ( 0x2000 < < ( int ) bBank2 ) ;
g_pTextBank1 = MemGetAuxPtr ( 0x400 < < ( int ) bBank2 ) ;
g_pTextBank0 = MemGetMainPtr ( 0x400 < < ( int ) bBank2 ) ;
2015-01-01 01:15:36 +00:00
return bBank2 ? VF_PAGE2 : 0 ;
2008-08-25 05:25:27 +00:00
}
2006-02-25 20:50:29 +00:00
2008-08-25 05:25:27 +00:00
//===========================================================================
2014-07-20 09:23:23 +00:00
// NB. Can get "big" 1000+ms times: these occur during disk loading when the emulator is at full-speed.
//#define DEBUG_REFRESH_TIMINGS
# if defined(_DEBUG) && defined(DEBUG_REFRESH_TIMINGS)
static void DebugRefresh ( char uDebugFlag )
{
static DWORD uLastRefreshTime = 0 ;
2014-09-14 15:12:55 +00:00
const DWORD dwEmuTime_ms = CpuGetEmulationTime_ms ( ) ;
const DWORD uTimeBetweenRefreshes = uLastRefreshTime ? dwEmuTime_ms - uLastRefreshTime : 0 ;
uLastRefreshTime = dwEmuTime_ms ;
2014-07-20 09:23:23 +00:00
if ( ! uTimeBetweenRefreshes )
return ; // 1st time in func
char szStr [ 100 ] ;
sprintf ( szStr , " Time between refreshes = %d ms %c \n " , uTimeBetweenRefreshes , ( uDebugFlag = = 0 ) ? ' ' : uDebugFlag ) ;
OutputDebugString ( szStr ) ;
}
# endif
2015-01-01 01:15:36 +00:00
void VideoRefreshScreen ( int bVideoModeFlags )
2011-01-07 09:23:16 +00:00
{
2014-07-20 09:23:23 +00:00
# if defined(_DEBUG) && defined(DEBUG_REFRESH_TIMINGS)
DebugRefresh ( 0 ) ;
# endif
2015-01-01 19:48:59 +00:00
if ( bVideoModeFlags )
{
NTSC_SetVideoMode ( bVideoModeFlags ) ;
g_pNTSC_FuncVideoUpdate ( VIDEO_SCANNER_6502_CYCLES ) ;
}
2015-01-01 01:15:36 +00:00
2014-12-31 22:13:36 +00:00
// NTSC_BEGIN: wsVideoRefresh()
LPBYTE pDstFrameBufferBits = 0 ;
LONG pitch = 0 ;
HDC hFrameDC = FrameGetVideoDC ( & pDstFrameBufferBits , & pitch ) ;
if ( hFrameDC )
{
2015-01-01 19:48:59 +00:00
StretchBlt (
hFrameDC ,
0 , 0 ,
2015-01-02 01:19:49 +00:00
g_nViewportCX , g_nViewportCY , //dst
2015-01-01 19:48:59 +00:00
g_hDeviceDC ,
0 , 0 ,
2015-01-02 01:19:49 +00:00
FRAMEBUFFER_W , FRAMEBUFFER_H , // src
2015-01-01 19:48:59 +00:00
SRCCOPY ) ;
2014-12-31 22:13:36 +00:00
GdiFlush ( ) ;
}
2015-01-01 01:15:36 +00:00
if ( g_VideoForceFullRedraw )
- - g_VideoForceFullRedraw ;
// NTSC_END
2008-08-25 05:25:27 +00:00
}
//===========================================================================
2015-01-01 01:15:36 +00:00
//void _Video_RedrawScreen( VideoUpdateFuncPtr_t pfUpdate, bool bMixed )
// FrameReleaseVideoDC();
// SetLastDrawnImage();
2006-02-25 20:50:29 +00:00
//===========================================================================
2011-01-08 21:29:27 +00:00
void VideoReinitialize ( )
{
2014-12-31 22:13:36 +00:00
NTSC_VideoInitAppleType ( ) ;
NTSC_SetVideoStyle ( ) ;
2006-02-25 20:50:29 +00:00
}
2011-01-08 21:29:27 +00:00
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 ;
2011-01-08 21:29:27 +00:00
g_VideoForceFullRedraw = 1 ;
2014-09-21 20:42:19 +00:00
g_bVideoUpdatedThisFrame = false ;
2006-02-25 20:50:29 +00:00
}
2011-01-08 21:29:27 +00:00
2006-02-25 20:50:29 +00:00
//===========================================================================
2014-12-21 21:43:49 +00:00
2011-01-09 04:42:46 +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 ;
DWORD oldpage2 = SW_PAGE2 ;
2014-09-14 15:15:14 +00:00
int oldvalue = g_nAltCharSetOffset + ( int ) ( g_uVideoMode & ~ ( VF_80STORE | VF_PAGE2 ) ) ;
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 )
{
2014-12-31 22:13:36 +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
2014-12-31 22:13:36 +00:00
// NTSC_BEGIN
NTSC_SetVideoMode ( g_uVideoMode ) ;
// NTSC_END
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 ( SW_80STORE )
2014-09-14 15:15:14 +00:00
g_uVideoMode & = ~ VF_PAGE2 ;
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
2014-09-14 15:15:14 +00:00
if ( oldvalue ! = g_nAltCharSetOffset + ( int ) ( g_uVideoMode & ~ ( VF_80STORE | VF_PAGE2 ) ) )
2014-09-21 20:42:19 +00:00
g_VideoForceFullRedraw = 1 ; // Defer video redraw until VideoEndOfVideoFrame()
2014-07-08 19:58:48 +00:00
if ( oldpage2 ! = SW_PAGE2 )
{
2014-09-21 20:42:19 +00:00
// /g_bVideoUpdatedThisFrame/ is used to limit the video update to once per 60Hz frame (CPU clk=1MHz):
// . this easily supports the common double-buffered "flip-immediate" case (eg. Airheart flips at a max of ~15Hz, Skyfox/Boulderdash at a max of ~11Hz)
// . crucially this prevents tight-loop page flipping (GH#129,GH#204) from max'ing out an x86 CPU core (and not providing realtime emulation)
// NB. Deferring the update by just setting /g_VideoForceFullRedraw/ is not an option, since this doesn't provide "flip-immediate"
//
// Ultimately this isn't the correct solution, and proper cycle-accurate video rendering should be done, but this is a much bigger job!
2015-01-01 19:48:59 +00:00
// TODO: Is MemReadFloatingBus() still accurate now that we have proper per cycle video rendering??
2014-09-21 20:42:19 +00:00
if ( ! g_bVideoUpdatedThisFrame )
2014-07-08 19:58:48 +00:00
{
2015-01-01 01:15:36 +00:00
VideoRedrawScreen ( ) ; // VideoRefreshScreen();
2014-09-21 20:42:19 +00:00
g_bVideoUpdatedThisFrame = true ;
2011-01-08 21:29:27 +00:00
}
}
2014-07-08 19:58:48 +00:00
2011-01-08 21:29:27 +00:00
return MemReadFloatingBus ( uExecutedCycles ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
// Called at 60Hz (every 16.666ms)
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
static void VideoUpdateFlash ( )
2006-02-25 20:50:29 +00:00
{
static UINT nTextFlashCnt = 0 ;
2009-02-22 14:39:50 +00:00
// Flash rate:
// . NTSC : 60/16 ~= 4Hz
// . PAL : 50/16 ~= 3Hz
nTextFlashCnt = ( nTextFlashCnt + 1 ) & 0xf ;
2006-02-25 20:50:29 +00:00
2008-11-25 22:38:54 +00:00
// BUG: In unthrottled CPU mode, flash rate should not be affected
2009-02-22 14:39:50 +00:00
if ( nTextFlashCnt = = 0 )
2006-02-25 20:50:29 +00:00
{
g_bTextFlashState = ! g_bTextFlashState ;
2006-06-12 22:06:50 +00:00
// Redraw any FLASHing chars if any text showing. NB. No FLASH g_nAppMode for 80 cols
2006-06-29 05:37:44 +00:00
if ( ( SW_TEXT | | SW_MIXED ) ) // && !SW_80COL) // FIX: FLASH 80-Column
2006-02-25 20:50:29 +00:00
g_bTextFlashFlag = true ;
}
}
//===========================================================================
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
// Called from main-loop every 17030 cycles (ie. 60Hz when CPU = 1MHz)
void VideoEndOfVideoFrame ( void )
{
2014-09-21 20:42:19 +00:00
g_bVideoUpdatedThisFrame = false ; // Allow page1/2 toggle to result in an immediate video redraw
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
VideoUpdateFlash ( ) ; // TODO: Flash rate should be constant (regardless of CPU speed)
if ( ! VideoApparentlyDirty ( ) )
return ;
// Apple II is not page flipping...
static DWORD dwLastTime = 0 ;
DWORD dwCurrTime = GetTickCount ( ) ;
if ( ! g_bFullSpeed | |
( dwCurrTime - dwLastTime > = 100 ) ) // FullSpeed: update every 100ms
{
2015-01-01 01:15:36 +00:00
// Required or else screen won't update -- Sheldon's NTSC doesn't have VideoEndOfVideoFrame -- simply calls wsVideoRefresh()
VideoRedrawScreen ( ) ; // VideoRefreshScreen();
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
dwLastTime = dwCurrTime ;
}
}
//===========================================================================
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 )
{
return g_nAltCharSetOffset = = 0 ;
}
//===========================================================================
void VideoSetForceFullRedraw ( void )
{
g_VideoForceFullRedraw = 1 ;
}
2006-02-25 20:50:29 +00:00
//===========================================================================
DWORD VideoGetSnapshot ( SS_IO_Video * pSS )
{
2006-06-29 03:28:25 +00:00
pSS - > bAltCharSet = ! ( g_nAltCharSetOffset = = 0 ) ;
2014-09-14 15:15:14 +00:00
pSS - > dwVidMode = g_uVideoMode ;
2006-02-25 20:50:29 +00:00
return 0 ;
}
2006-03-12 09:05:39 +00:00
//===========================================================================
2006-02-25 20:50:29 +00:00
DWORD VideoSetSnapshot ( SS_IO_Video * pSS )
{
2006-06-29 03:28:25 +00:00
g_nAltCharSetOffset = ! pSS - > bAltCharSet ? 0 : 256 ;
2014-09-14 15:15:14 +00:00
g_uVideoMode = pSS - > dwVidMode ;
2006-02-25 20:50:29 +00:00
return 0 ;
}
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)
//
2007-08-06 21:38:35 +00:00
WORD VideoGetScannerAddress ( bool * pbVblBar_OUT , const DWORD uExecutedCycles )
2006-03-12 09:05:39 +00:00
{
// get video scanner position
//
2010-08-17 07:52:23 +00:00
int nCycles = CpuGetCyclesThisVideoFrame ( uExecutedCycles ) ;
2006-03-12 09:05:39 +00:00
// machine state switches
//
2008-12-30 15:45:48 +00:00
int nHires = ( SW_HIRES & & ! SW_TEXT ) ? 1 : 0 ;
2014-09-14 15:12:55 +00:00
int nPage2 = SW_PAGE2 ? 1 : 0 ;
int n80Store = SW_80STORE ? 1 : 0 ;
2006-03-12 09:05:39 +00:00
// calculate video parameters according to display standard
//
int nScanLines = bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines ;
int nVSyncLine = bVideoScannerNTSC ? kNTSCVSyncLine : kPALVSyncLine ;
int nScanCycles = nScanLines * kHClocks ;
// calculate horizontal scanning state
//
int nHClock = ( nCycles + kHPEClock ) % kHClocks ; // which horizontal scanning clock
int nHState = kHClock0State + nHClock ; // H state bits
if ( nHClock > = kHPresetClock ) // check for horizontal preset
{
nHState - = 1 ; // correct for state preset (two 0 states)
}
int h_0 = ( nHState > > 0 ) & 1 ; // get horizontal state bits
int h_1 = ( nHState > > 1 ) & 1 ;
int h_2 = ( nHState > > 2 ) & 1 ;
int h_3 = ( nHState > > 3 ) & 1 ;
int h_4 = ( nHState > > 4 ) & 1 ;
int h_5 = ( nHState > > 5 ) & 1 ;
// calculate vertical scanning state
//
int nVLine = nCycles / kHClocks ; // which vertical scanning line
int nVState = kVLine0State + nVLine ; // V state bits
if ( ( nVLine > = kVPresetLine ) ) // check for previous vertical state preset
{
nVState - = nScanLines ; // compensate for preset
}
int v_A = ( nVState > > 0 ) & 1 ; // get vertical state bits
int v_B = ( nVState > > 1 ) & 1 ;
int v_C = ( nVState > > 2 ) & 1 ;
int v_0 = ( nVState > > 3 ) & 1 ;
int v_1 = ( nVState > > 4 ) & 1 ;
int v_2 = ( nVState > > 5 ) & 1 ;
int v_3 = ( nVState > > 6 ) & 1 ;
int v_4 = ( nVState > > 7 ) & 1 ;
int v_5 = ( nVState > > 8 ) & 1 ;
// calculate scanning memory address
//
2008-12-30 15:45:48 +00:00
if ( nHires & & SW_MIXED & & v_4 & & v_2 ) // HIRES TIME signal (UTAIIe:5-7,P3)
2006-03-12 09:05:39 +00:00
{
2008-12-30 15:45:48 +00:00
nHires = 0 ; // 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)
int nAddress = 0 ; // build address from video scanner equations (UTAIIe:5-8,T5.1)
nAddress | = h_0 < < 0 ; // a0
nAddress | = h_1 < < 1 ; // a1
nAddress | = h_2 < < 2 ; // a2
nAddress | = nSum < < 3 ; // a3 - a6
nAddress | = v_0 < < 7 ; // a7
nAddress | = v_1 < < 8 ; // a8
nAddress | = v_2 < < 9 ; // a9
2008-12-30 16:40:05 +00:00
int p2a = ! ( nPage2 & & ! n80Store ) ;
int p2b = nPage2 & & ! n80Store ;
2008-12-30 15:45:48 +00:00
2006-03-12 09:05:39 +00:00
if ( nHires ) // hires?
{
2008-12-30 15:45:48 +00:00
// Y: insert hires-only address bits
2006-03-12 09:05:39 +00:00
//
2008-12-30 15:45:48 +00:00
nAddress | = v_A < < 10 ; // a10
nAddress | = v_B < < 11 ; // a11
2006-03-12 09:05:39 +00:00
nAddress | = v_C < < 12 ; // a12
2008-12-30 15:45:48 +00:00
nAddress | = p2a < < 13 ; // a13
nAddress | = 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
//
2008-12-30 15:45:48 +00:00
nAddress | = p2a < < 10 ; // a10
nAddress | = p2b < < 11 ; // a11
// Apple ][ (not //e) and HBL?
//
if ( IS_APPLE2 & & // Apple II only (UTAIIe:I-4,#5)
2008-12-30 16:40:05 +00:00
! h_5 & & ( ! h_4 | | ! h_3 ) ) // HBL (UTAIIe:8-10,F8.5)
2006-03-12 09:05:39 +00:00
{
2008-12-30 16:40:05 +00:00
nAddress | = 1 < < 12 ; // Y: a12 (add $1000 to address!)
2006-03-12 09:05:39 +00:00
}
}
// update VBL' state
//
if ( pbVblBar_OUT ! = NULL )
{
2008-12-30 16:40:05 +00:00
* pbVblBar_OUT = ! v_4 | | ! v_3 ; // VBL' = (v_4 & v_3)' (UTAIIe:5-10,#3)
2006-03-12 09:05:39 +00:00
}
return static_cast < WORD > ( nAddress ) ;
}
//===========================================================================
2007-08-06 21:38:35 +00:00
// Derived from VideoGetScannerAddress()
bool VideoGetVbl ( const DWORD uExecutedCycles )
{
// get video scanner position
//
2010-08-17 07:52:23 +00:00
int nCycles = CpuGetCyclesThisVideoFrame ( uExecutedCycles ) ;
2007-08-06 21:38:35 +00:00
// calculate video parameters according to display standard
//
int nScanLines = bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines ;
// calculate vertical scanning state
//
int nVLine = nCycles / kHClocks ; // which vertical scanning line
int nVState = kVLine0State + nVLine ; // V state bits
if ( ( nVLine > = kVPresetLine ) ) // check for previous vertical state preset
{
nVState - = nScanLines ; // compensate for preset
}
int v_3 = ( nVState > > 6 ) & 1 ;
int v_4 = ( nVState > > 7 ) & 1 ;
// update VBL' state
//
if ( v_4 & v_3 ) // VBL?
{
return false ; // Y: VBL' is false
}
else
{
return true ; // N: VBL' is true
}
}
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
// alias for nSuffixScreenShotFileName
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
2011-02-15 17:16:36 +00:00
static int g_iScreenshotType ;
2008-08-31 04:31:35 +00:00
static char * g_pLastDiskImageName = NULL ;
2008-07-14 16:02:44 +00:00
//const int nMaxScreenShot = 2;
2008-08-31 04:31:35 +00:00
//===========================================================================
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 ;
}
//===========================================================================
2008-08-31 04:31:35 +00:00
void Video_TakeScreenShot ( int iScreenShotType )
2008-07-14 16:02:44 +00:00
{
char sScreenShotFileName [ MAX_PATH ] ;
2008-08-31 04:31:35 +00:00
g_iScreenshotType = iScreenShotType ;
2008-07-14 16:02:44 +00:00
// 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
}
Video_SaveScreenShot ( sScreenShotFileName ) ;
2008-08-31 04:31:35 +00:00
g_nLastScreenShot + + ;
2008-07-14 16:02:44 +00:00
}
2008-08-25 00:36:48 +00:00
typedef char int8 ;
2008-07-14 16:02:44 +00:00
typedef short int16 ;
typedef int int32 ;
2008-08-25 00:36:48 +00:00
typedef unsigned char u8 ;
typedef signed short s16 ;
/// turn of MSVC struct member padding
# pragma pack(push,1)
struct bgra_t
{
u8 b ;
u8 g ;
u8 r ;
u8 a ; // reserved on Win32
} ;
2008-07-14 16:02:44 +00:00
struct WinBmpHeader_t
{
2008-08-25 00:36:48 +00:00
// BITMAPFILEHEADER // Addr Size
char nCookie [ 2 ] ; // 0x00 0x02 BM
int32 nSizeFile ; // 0x02 0x04 0 = ignore
int16 nReserved1 ; // 0x06 0x02
int16 nReserved2 ; // 0x08 0x02
int32 nOffsetData ; // 0x0A 0x04
// == 0x0D (14)
// BITMAPINFOHEADER
int32 nStructSize ; // 0x0E 0x04 biSize
int32 nWidthPixels ; // 0x12 0x04 biWidth
int32 nHeightPixels ; // 0x16 0x04 biHeight
int16 nPlanes ; // 0x1A 0x02 biPlanes
int16 nBitsPerPixel ; // 0x1C 0x02 biBitCount
int32 nCompression ; // 0x1E 0x04 biCompression 0 = BI_RGB
int32 nSizeImage ; // 0x22 0x04 0 = ignore
int32 nXPelsPerMeter ; // 0x26 0x04
int32 nYPelsPerMeter ; // 0x2A 0x04
int32 nPaletteColors ; // 0x2E 0x04
int32 nImportantColors ; // 0x32 0x04
// == 0x28 (40)
// RGBQUAD
// pixelmap
2008-07-14 16:02:44 +00:00
} ;
2008-08-25 00:36:48 +00:00
# pragma pack(pop)
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
2008-07-14 16:02:44 +00:00
//===========================================================================
2008-08-25 00:36:48 +00:00
void Video_MakeScreenShot ( FILE * pFile )
2008-07-14 16:02:44 +00:00
{
2008-08-25 00:36:48 +00:00
# if SCREENSHOT_BMP
g_tBmpHeader . nCookie [ 0 ] = ' B ' ; // 0x42
g_tBmpHeader . nCookie [ 1 ] = ' M ' ; // 0x4d
g_tBmpHeader . nSizeFile = 0 ;
2008-07-14 16:02:44 +00:00
g_tBmpHeader . nReserved1 = 0 ;
g_tBmpHeader . nReserved2 = 0 ;
2008-08-25 00:36:48 +00:00
g_tBmpHeader . nOffsetData = sizeof ( WinBmpHeader_t ) + ( 256 * sizeof ( bgra_t ) ) ;
g_tBmpHeader . nStructSize = 0x28 ; // sizeof( WinBmpHeader_t );
2008-08-31 04:31:35 +00:00
g_tBmpHeader . nWidthPixels = g_iScreenshotType ? FRAMEBUFFER_W / 2 : FRAMEBUFFER_W ;
g_tBmpHeader . nHeightPixels = g_iScreenshotType ? FRAMEBUFFER_H / 2 : FRAMEBUFFER_H ;
2008-07-14 16:02:44 +00:00
g_tBmpHeader . nPlanes = 1 ;
2008-08-25 00:36:48 +00:00
g_tBmpHeader . nBitsPerPixel = 8 ;
2008-07-14 16:02:44 +00:00
g_tBmpHeader . nCompression = BI_RGB ;
g_tBmpHeader . nSizeImage = 0 ;
g_tBmpHeader . nXPelsPerMeter = 0 ;
g_tBmpHeader . nYPelsPerMeter = 0 ;
2008-08-25 00:36:48 +00:00
g_tBmpHeader . nPaletteColors = 256 ;
2008-07-14 16:02:44 +00:00
g_tBmpHeader . nImportantColors = 0 ;
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
char sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize [ sizeof ( WinBmpHeader_t ) = = ( 14 + 40 ) ] ;
sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize ;
// Write Header
int nLen ;
fwrite ( & g_tBmpHeader , sizeof ( g_tBmpHeader ) , 1 , pFile ) ;
2009-02-16 20:30:38 +00:00
2008-08-25 00:36:48 +00:00
// Write Palette Data
u8 * pSrc = ( ( u8 * ) g_pFramebufferinfo ) + sizeof ( BITMAPINFOHEADER ) ;
nLen = g_tBmpHeader . nPaletteColors * sizeof ( bgra_t ) ; // RGBQUAD
fwrite ( pSrc , nLen , 1 , pFile ) ;
pSrc + = nLen ;
// Write Pixel Data
// No need to use GetDibBits() since we already have http://msdn.microsoft.com/en-us/library/ms532334.aspx
// @reference: "Storing an Image" http://msdn.microsoft.com/en-us/library/ms532340(VS.85).aspx
2014-12-31 22:13:36 +00:00
pSrc = ( ( uint8_t * ) g_pFramebufferbits ) ;
2008-08-25 00:36:48 +00:00
nLen = g_tBmpHeader . nWidthPixels * g_tBmpHeader . nHeightPixels * g_tBmpHeader . nBitsPerPixel / 8 ;
2008-08-31 04:31:35 +00:00
if ( g_iScreenshotType = = SCREENSHOT_280x192 )
{
2013-03-28 22:28:42 +00:00
pSrc + = FRAMEBUFFER_W ; // Start on odd scanline (otherwise for 50% scanline mode get an all black image!)
2008-08-31 04:31:35 +00:00
u8 aScanLine [ 280 ] ;
u8 * pDst ;
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()
2008-08-31 04:31:35 +00:00
for ( int y = 0 ; y < FRAMEBUFFER_H / 2 ; y + + )
{
pDst = aScanLine ;
for ( int x = 0 ; x < FRAMEBUFFER_W / 2 ; x + + )
{
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
}
fwrite ( aScanLine , FRAMEBUFFER_W / 2 , 1 , pFile ) ;
pSrc + = FRAMEBUFFER_W ; // scan lines doubled - skip odd ones
}
}
else
{
fwrite ( pSrc , nLen , 1 , pFile ) ;
}
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
}
//===========================================================================
void Video_SaveScreenShot ( const char * pScreenShotFileName )
{
FILE * pFile = fopen ( pScreenShotFileName , " wb " ) ;
if ( pFile )
{
2008-08-25 00:36:48 +00:00
Video_MakeScreenShot ( pFile ) ;
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
//===========================================================================
void Config_Load_Video ( )
{
REGLOAD ( TEXT ( REGVALUE_VIDEO_MODE ) , & g_eVideoType ) ;
REGLOAD ( TEXT ( REGVALUE_VIDEO_HALF_SCAN_LINES ) , & g_uHalfScanLines ) ;
2015-01-01 17:59:58 +00:00
REGLOAD ( TEXT ( REGVALUE_VIDEO_MONO_COLOR ) , & g_nMonochromeRGB ) ;
2012-03-27 21:20:36 +00:00
if ( g_eVideoType > = NUM_VIDEO_MODES )
g_eVideoType = VT_COLOR_STANDARD ; // Old default: VT_COLOR_TVEMU
}
void Config_Save_Video ( )
{
REGSAVE ( TEXT ( REGVALUE_VIDEO_MODE ) , g_eVideoType ) ;
REGSAVE ( TEXT ( REGVALUE_VIDEO_HALF_SCAN_LINES ) , g_uHalfScanLines ) ;
2015-01-01 17:59:58 +00:00
REGSAVE ( TEXT ( REGVALUE_VIDEO_MONO_COLOR ) , g_nMonochromeRGB ) ;
2012-03-27 21:20:36 +00:00
}
2014-12-31 22:13:36 +00:00
// ____________________________________________________________________
//===========================================================================
void NTSC_VideoCreateDIBSection ( )
{
// CREATE THE DEVICE CONTEXT
HWND window = GetDesktopWindow ( ) ;
HDC dc = GetDC ( window ) ;
if ( g_hDeviceDC )
{
DeleteDC ( g_hDeviceDC ) ;
}
g_hDeviceDC = CreateCompatibleDC ( dc ) ;
// CREATE THE FRAME BUFFER DIB SECTION
if ( g_hDeviceBitmap )
DeleteObject ( g_hDeviceBitmap ) ;
g_hDeviceBitmap = CreateDIBSection (
dc ,
g_pFramebufferinfo ,
DIB_RGB_COLORS ,
( LPVOID * ) & g_pFramebufferbits , 0 , 0
) ;
SelectObject ( g_hDeviceDC , g_hDeviceBitmap ) ;
// CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE FRAME BUFFER
// DRAW THE SOURCE IMAGE INTO THE SOURCE BIT BUFFER
ZeroMemory ( g_pFramebufferbits , FRAMEBUFFER_W * FRAMEBUFFER_H * 4 ) ;
NTSC_VideoInit ( g_pFramebufferbits ) ;
}