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
|
|
|
|
2020-11-11 21:15:27 +00:00
|
|
|
#include "Video.h"
|
2021-11-30 21:41:02 +00:00
|
|
|
#include "CardManager.h"
|
2020-11-26 21:50:06 +00:00
|
|
|
#include "Core.h"
|
2014-08-13 20:30:35 +00:00
|
|
|
#include "CPU.h"
|
|
|
|
#include "Memory.h"
|
|
|
|
#include "Registry.h"
|
2014-12-31 22:13:36 +00:00
|
|
|
#include "NTSC.h"
|
2019-02-02 15:51:27 +00:00
|
|
|
#include "RGBMonitor.h"
|
2021-11-30 21:41:02 +00:00
|
|
|
#include "VidHD.h"
|
2016-03-21 23:48:02 +00:00
|
|
|
#include "YamlHelper.h"
|
2006-02-25 20:50:29 +00:00
|
|
|
|
2020-12-28 16:25:29 +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)
|
2021-11-30 21:41:02 +00:00
|
|
|
#define SW_SHR (g_uVideoMode & VF_SHR)
|
2006-03-12 09:05:39 +00:00
|
|
|
|
2006-02-25 20:50:29 +00:00
|
|
|
//-------------------------------------
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
// NOTE: KEEP IN SYNC: VideoType_e g_aVideoChoices g_apVideoModeDesc
|
|
|
|
const char Video::g_aVideoChoices[] =
|
|
|
|
"Monochrome (Custom)\0"
|
|
|
|
"Color (Composite Idealized)\0"
|
|
|
|
"Color (RGB Card/Monitor)\0"
|
|
|
|
"Color (Composite Monitor)\0"
|
|
|
|
"Color TV\0"
|
|
|
|
"B&W TV\0"
|
|
|
|
"Monochrome (Amber)\0"
|
|
|
|
"Monochrome (Green)\0"
|
|
|
|
"Monochrome (White)\0"
|
|
|
|
;
|
|
|
|
|
|
|
|
// NOTE: KEEP IN SYNC: VideoType_e g_aVideoChoices g_apVideoModeDesc
|
|
|
|
// The window title will be set to this.
|
|
|
|
|
|
|
|
const char Video::m_szModeDesc0[] = "Monochrome (Custom)";
|
|
|
|
const char Video::m_szModeDesc1[] = "Color (Composite Idealized)";
|
|
|
|
const char Video::m_szModeDesc2[] = "Color (RGB Card/Monitor)";
|
|
|
|
const char Video::m_szModeDesc3[] = "Color (Composite Monitor)";
|
|
|
|
const char Video::m_szModeDesc4[] = "Color TV";
|
|
|
|
const char Video::m_szModeDesc5[] = "B&W TV";
|
|
|
|
const char Video::m_szModeDesc6[] = "Monochrome (Amber)";
|
|
|
|
const char Video::m_szModeDesc7[] = "Monochrome (Green)";
|
|
|
|
const char Video::m_szModeDesc8[] = "Monochrome (White)";
|
|
|
|
|
|
|
|
const char* const Video::g_apVideoModeDesc[NUM_VIDEO_MODES] =
|
|
|
|
{
|
|
|
|
Video::m_szModeDesc0,
|
|
|
|
Video::m_szModeDesc1,
|
|
|
|
Video::m_szModeDesc2,
|
|
|
|
Video::m_szModeDesc3,
|
|
|
|
Video::m_szModeDesc4,
|
|
|
|
Video::m_szModeDesc5,
|
|
|
|
Video::m_szModeDesc6,
|
|
|
|
Video::m_szModeDesc7,
|
|
|
|
Video::m_szModeDesc8
|
|
|
|
};
|
2011-01-09 04:42:46 +00:00
|
|
|
|
2006-02-25 20:50:29 +00:00
|
|
|
//===========================================================================
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
UINT Video::GetFrameBufferBorderlessWidth(void)
|
2020-12-27 19:01:35 +00:00
|
|
|
{
|
2021-11-30 21:41:02 +00:00
|
|
|
return HasVidHD() ? kVideoWidthIIgs : kVideoWidthII;
|
2020-12-27 19:01:35 +00:00
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
UINT Video::GetFrameBufferBorderlessHeight(void)
|
2020-12-27 19:01:35 +00:00
|
|
|
{
|
2021-11-30 21:41:02 +00:00
|
|
|
return HasVidHD() ? kVideoHeightIIgs : kVideoHeightII;
|
2020-12-27 19:01:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NB. These border areas are not visible (... and these border areas are unrelated to the 3D border below)
|
2020-12-28 16:25:29 +00:00
|
|
|
UINT Video::GetFrameBufferBorderWidth(void)
|
2020-12-27 19:01:35 +00:00
|
|
|
{
|
|
|
|
static const UINT uBorderW = 20;
|
|
|
|
return uBorderW;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
UINT Video::GetFrameBufferBorderHeight(void)
|
2020-12-27 19:01:35 +00:00
|
|
|
{
|
|
|
|
static const UINT uBorderH = 18;
|
|
|
|
return uBorderH;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
UINT Video::GetFrameBufferWidth(void)
|
2020-12-27 19:01:35 +00:00
|
|
|
{
|
|
|
|
return GetFrameBufferBorderlessWidth() + 2 * GetFrameBufferBorderWidth();
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
UINT Video::GetFrameBufferHeight(void)
|
2020-12-27 19:01:35 +00:00
|
|
|
{
|
|
|
|
return GetFrameBufferBorderlessHeight() + 2 * GetFrameBufferBorderHeight();
|
|
|
|
}
|
|
|
|
|
2021-11-30 21:41:02 +00:00
|
|
|
UINT Video::GetFrameBufferCentringOffsetX(void)
|
|
|
|
{
|
|
|
|
return HasVidHD() ? ((kVideoWidthIIgs - kVideoWidthII) / 2) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT Video::GetFrameBufferCentringOffsetY(void)
|
|
|
|
{
|
|
|
|
return HasVidHD() ? ((kVideoHeightIIgs - kVideoHeightII) / 2) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Video::GetFrameBufferCentringValue(void)
|
|
|
|
{
|
|
|
|
int value = 0;
|
|
|
|
|
|
|
|
if (HasVidHD())
|
|
|
|
{
|
|
|
|
value -= GetFrameBufferCentringOffsetY() * GetFrameBufferWidth();
|
|
|
|
value += GetFrameBufferCentringOffsetX();
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2006-02-25 20:50:29 +00:00
|
|
|
//===========================================================================
|
2020-12-27 19:01:35 +00:00
|
|
|
|
2021-01-13 22:02:48 +00:00
|
|
|
void Video::VideoReinitialize(bool bInitVideoScannerAddress)
|
2011-01-08 21:29:27 +00:00
|
|
|
{
|
2019-03-16 14:27:40 +00:00
|
|
|
NTSC_VideoReinitialize( g_dwCyclesThisFrame, bInitVideoScannerAddress );
|
2016-04-05 21:17:29 +00:00
|
|
|
NTSC_VideoInitAppleType();
|
2014-12-31 22:13:36 +00:00
|
|
|
NTSC_SetVideoStyle();
|
2016-10-31 21:05:10 +00:00
|
|
|
NTSC_SetVideoTextMode( g_uVideoMode & VF_80COL ? 80 : 40 );
|
2023-01-14 14:08:42 +00:00
|
|
|
NTSC_SetVideoMode( g_uVideoMode );
|
2020-09-27 13:09:02 +00:00
|
|
|
VideoSwitchVideocardPalette(RGB_GetVideocard(), GetVideoType());
|
2006-02-25 20:50:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
2020-12-28 16:25:29 +00:00
|
|
|
|
|
|
|
void Video::VideoResetState(void)
|
2011-01-08 21:29:27 +00:00
|
|
|
{
|
|
|
|
g_nAltCharSetOffset = 0;
|
2014-09-14 15:15:14 +00:00
|
|
|
g_uVideoMode = VF_TEXT;
|
2017-02-17 14:26:03 +00:00
|
|
|
|
|
|
|
NTSC_SetVideoTextMode( 40 );
|
|
|
|
NTSC_SetVideoMode( g_uVideoMode );
|
2006-02-25 20:50:29 +00:00
|
|
|
|
2019-02-02 15:51:27 +00:00
|
|
|
RGB_ResetState();
|
|
|
|
}
|
2011-01-08 21:29:27 +00:00
|
|
|
|
2006-02-25 20:50:29 +00:00
|
|
|
//===========================================================================
|
2014-12-21 21:43:49 +00:00
|
|
|
|
2021-11-30 21:41:02 +00:00
|
|
|
BYTE Video::VideoSetMode(WORD pc, WORD address, BYTE write, BYTE d, ULONG uExecutedCycles)
|
2007-05-28 11:16:42 +00:00
|
|
|
{
|
2019-06-24 21:05:32 +00:00
|
|
|
const uint32_t oldVideoMode = g_uVideoMode;
|
|
|
|
|
2021-11-30 21:41:02 +00:00
|
|
|
VidHDCard* vidHD = NULL;
|
|
|
|
if (GetCardMgr().QuerySlot(SLOT3) == CT_VidHD)
|
|
|
|
vidHD = dynamic_cast<VidHDCard*>(GetCardMgr().GetObj(SLOT3));
|
|
|
|
|
|
|
|
address &= 0xFF;
|
Simplified and moved main-loop video update logic into Video.cpp.
Removed complex case below for:
. VideoHasRefreshed(), 'anyupdates'
. VideoCheckPage()
Detailed notes below.
---
Video updates in ContinueExecution() loop:
'anyupdates' gets set if there were any page-flip(s) in last ~17030 cycles:
anyupdates |= VideoHasRefreshed();
ie. VideoRefreshScreen() was called outside of this loop.
If there's been a call to VideoRefreshScreen() outside of this loop,
and then the video framebuffer gets written to, ie. VideoApparentlyDirty() returns TRUE,
then don't call VideoRefreshScreen() from this loop for 3 frames.
(If a VideoRefreshScreen() is called outside of this loop then restart the 3 frame count.)
So..
if the game is flipping, the VideoApparentlyDirty() will return FALSE (since game writes to other framebuffer).
if the game is not flipping, then VideoHasRefreshed() will return FALSE (since no flips occur).
Therefore this complex case above probably only arises at a boundary eg. when the game is transitioning between these 2 modes,
and so if the emulator does the very occasional screen update in this main loop, it is of no consequence.
(I guess this extra logic was to throttle video updates on very old slow machines)
---
VideoCheckPage(BOOL bForce) was called twice in main-loop:
UnexpectedPage if g_bVideoDisplayPage2 != SW_PAGE2
Once each time through the loop (ie. every 1ms), with bForce=0
if UnexpectedPage && >500ms since last flip then VideoRefreshScreen()
Once each video frame (ie. ~17030 cycles) when not flipping, with bForce=1
if UnexpectedPage then VideoRefreshScreen()
Basically this was all about supporting FullSpeed mode, and limiting the calls to VideoRefreshScreen().
2014-09-13 21:22:27 +00:00
|
|
|
switch (address)
|
|
|
|
{
|
2016-03-21 23:48:02 +00:00
|
|
|
case 0x00: g_uVideoMode &= ~VF_80STORE; break;
|
|
|
|
case 0x01: g_uVideoMode |= VF_80STORE; break;
|
2015-01-02 06:03:34 +00:00
|
|
|
case 0x0C: if (!IS_APPLE2){g_uVideoMode &= ~VF_80COL; NTSC_SetVideoTextMode(40);}; break;
|
|
|
|
case 0x0D: if (!IS_APPLE2){g_uVideoMode |= VF_80COL; NTSC_SetVideoTextMode(80);}; break;
|
2011-01-08 21:29:27 +00:00
|
|
|
case 0x0E: if (!IS_APPLE2) g_nAltCharSetOffset = 0; break; // Alternate char set off
|
|
|
|
case 0x0F: if (!IS_APPLE2) g_nAltCharSetOffset = 256; break; // Alternate char set on
|
2021-11-30 21:41:02 +00:00
|
|
|
case 0x22: if (vidHD) vidHD->VideoIOWrite(pc, address, write, d, uExecutedCycles); break; // VidHD IIgs video mode register
|
|
|
|
case 0x29: if (vidHD) vidHD->VideoIOWrite(pc, address, write, d, uExecutedCycles); break; // VidHD IIgs video mode register
|
|
|
|
case 0x34: if (vidHD) vidHD->VideoIOWrite(pc, address, write, d, uExecutedCycles); break; // VidHD IIgs video mode register
|
|
|
|
case 0x35: if (vidHD) vidHD->VideoIOWrite(pc, address, write, d, uExecutedCycles); break; // VidHD IIgs video mode register
|
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
|
|
|
|
2021-11-30 21:41:02 +00:00
|
|
|
if (vidHD && vidHD->IsSHR())
|
|
|
|
g_uVideoMode |= VF_SHR;
|
|
|
|
else
|
|
|
|
g_uVideoMode &= ~VF_SHR;
|
|
|
|
|
2019-02-02 15:51:27 +00:00
|
|
|
if (!IS_APPLE2)
|
|
|
|
RGB_SetVideoMode(address);
|
|
|
|
|
2019-11-16 23:49:21 +00:00
|
|
|
// Only 1-cycle delay for VF_TEXT & VF_MIXED mode changes (GH#656)
|
|
|
|
bool delay = false;
|
|
|
|
if ((oldVideoMode ^ g_uVideoMode) & (VF_TEXT|VF_MIXED))
|
|
|
|
delay = true;
|
2019-06-24 21:05:32 +00:00
|
|
|
|
2021-11-30 21:41:02 +00:00
|
|
|
NTSC_SetVideoMode(g_uVideoMode, delay);
|
2015-01-03 19:44:06 +00:00
|
|
|
|
2011-01-08 21:29:27 +00:00
|
|
|
return MemReadFloatingBus(uExecutedCycles);
|
2006-02-25 20:50:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetSW80COL(void)
|
2006-02-25 20:50:29 +00:00
|
|
|
{
|
|
|
|
return SW_80COL ? true : false;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetSWDHIRES(void)
|
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
|
|
|
{
|
|
|
|
return SW_DHIRES ? true : false;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetSWHIRES(void)
|
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
|
|
|
{
|
|
|
|
return SW_HIRES ? true : false;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetSW80STORE(void)
|
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
|
|
|
{
|
|
|
|
return SW_80STORE ? true : false;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetSWMIXED(void)
|
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
|
|
|
{
|
|
|
|
return SW_MIXED ? true : false;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetSWPAGE2(void)
|
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
|
|
|
{
|
|
|
|
return SW_PAGE2 ? true : false;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetSWTEXT(void)
|
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
|
|
|
{
|
|
|
|
return SW_TEXT ? true : false;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetSWAltCharSet(void)
|
Simplified and moved main-loop video update logic into Video.cpp.
Removed complex case below for:
. VideoHasRefreshed(), 'anyupdates'
. VideoCheckPage()
Detailed notes below.
---
Video updates in ContinueExecution() loop:
'anyupdates' gets set if there were any page-flip(s) in last ~17030 cycles:
anyupdates |= VideoHasRefreshed();
ie. VideoRefreshScreen() was called outside of this loop.
If there's been a call to VideoRefreshScreen() outside of this loop,
and then the video framebuffer gets written to, ie. VideoApparentlyDirty() returns TRUE,
then don't call VideoRefreshScreen() from this loop for 3 frames.
(If a VideoRefreshScreen() is called outside of this loop then restart the 3 frame count.)
So..
if the game is flipping, the VideoApparentlyDirty() will return FALSE (since game writes to other framebuffer).
if the game is not flipping, then VideoHasRefreshed() will return FALSE (since no flips occur).
Therefore this complex case above probably only arises at a boundary eg. when the game is transitioning between these 2 modes,
and so if the emulator does the very occasional screen update in this main loop, it is of no consequence.
(I guess this extra logic was to throttle video updates on very old slow machines)
---
VideoCheckPage(BOOL bForce) was called twice in main-loop:
UnexpectedPage if g_bVideoDisplayPage2 != SW_PAGE2
Once each time through the loop (ie. every 1ms), with bForce=0
if UnexpectedPage && >500ms since last flip then VideoRefreshScreen()
Once each video frame (ie. ~17030 cycles) when not flipping, with bForce=1
if UnexpectedPage then VideoRefreshScreen()
Basically this was all about supporting FullSpeed mode, and limiting the calls to VideoRefreshScreen().
2014-09-13 21:22:27 +00:00
|
|
|
{
|
2016-04-04 21:38:01 +00:00
|
|
|
return g_nAltCharSetOffset != 0;
|
Simplified and moved main-loop video update logic into Video.cpp.
Removed complex case below for:
. VideoHasRefreshed(), 'anyupdates'
. VideoCheckPage()
Detailed notes below.
---
Video updates in ContinueExecution() loop:
'anyupdates' gets set if there were any page-flip(s) in last ~17030 cycles:
anyupdates |= VideoHasRefreshed();
ie. VideoRefreshScreen() was called outside of this loop.
If there's been a call to VideoRefreshScreen() outside of this loop,
and then the video framebuffer gets written to, ie. VideoApparentlyDirty() returns TRUE,
then don't call VideoRefreshScreen() from this loop for 3 frames.
(If a VideoRefreshScreen() is called outside of this loop then restart the 3 frame count.)
So..
if the game is flipping, the VideoApparentlyDirty() will return FALSE (since game writes to other framebuffer).
if the game is not flipping, then VideoHasRefreshed() will return FALSE (since no flips occur).
Therefore this complex case above probably only arises at a boundary eg. when the game is transitioning between these 2 modes,
and so if the emulator does the very occasional screen update in this main loop, it is of no consequence.
(I guess this extra logic was to throttle video updates on very old slow machines)
---
VideoCheckPage(BOOL bForce) was called twice in main-loop:
UnexpectedPage if g_bVideoDisplayPage2 != SW_PAGE2
Once each time through the loop (ie. every 1ms), with bForce=0
if UnexpectedPage && >500ms since last flip then VideoRefreshScreen()
Once each video frame (ie. ~17030 cycles) when not flipping, with bForce=1
if UnexpectedPage then VideoRefreshScreen()
Basically this was all about supporting FullSpeed mode, and limiting the calls to VideoRefreshScreen().
2014-09-13 21:22:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
|
2019-06-28 20:34:34 +00:00
|
|
|
#define SS_YAML_KEY_ALT_CHARSET "Alt Char Set"
|
|
|
|
#define SS_YAML_KEY_VIDEO_MODE "Video Mode"
|
|
|
|
#define SS_YAML_KEY_CYCLES_THIS_FRAME "Cycles This Frame"
|
|
|
|
#define SS_YAML_KEY_VIDEO_REFRESH_RATE "Video Refresh Rate"
|
2016-03-21 23:48:02 +00:00
|
|
|
|
2022-02-28 20:52:18 +00:00
|
|
|
const std::string& Video::VideoGetSnapshotStructName(void)
|
2006-02-25 20:50:29 +00:00
|
|
|
{
|
2016-03-21 23:48:02 +00:00
|
|
|
static const std::string name("Video");
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
void Video::VideoSaveSnapshot(YamlSaveHelper& yamlSaveHelper)
|
2016-03-21 23:48:02 +00:00
|
|
|
{
|
|
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", VideoGetSnapshotStructName().c_str());
|
2019-06-28 20:34:34 +00:00
|
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_ALT_CHARSET, g_nAltCharSetOffset ? true : false);
|
|
|
|
yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_VIDEO_MODE, g_uVideoMode);
|
|
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_CYCLES_THIS_FRAME, g_dwCyclesThisFrame);
|
|
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_VIDEO_REFRESH_RATE, (UINT)GetVideoRefreshRate());
|
2016-03-21 23:48:02 +00:00
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
void Video::VideoLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
|
2016-03-21 23:48:02 +00:00
|
|
|
{
|
|
|
|
if (!yamlLoadHelper.GetSubMap(VideoGetSnapshotStructName()))
|
|
|
|
return;
|
|
|
|
|
2019-06-28 20:34:34 +00:00
|
|
|
if (version >= 4)
|
|
|
|
{
|
|
|
|
VideoRefreshRate_e rate = (VideoRefreshRate_e)yamlLoadHelper.LoadUint(SS_YAML_KEY_VIDEO_REFRESH_RATE);
|
|
|
|
SetVideoRefreshRate(rate); // Trashes: g_dwCyclesThisFrame
|
|
|
|
SetCurrentCLK6502();
|
|
|
|
}
|
|
|
|
|
|
|
|
g_nAltCharSetOffset = yamlLoadHelper.LoadBool(SS_YAML_KEY_ALT_CHARSET) ? 256 : 0;
|
|
|
|
g_uVideoMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_VIDEO_MODE);
|
|
|
|
g_dwCyclesThisFrame = yamlLoadHelper.LoadUint(SS_YAML_KEY_CYCLES_THIS_FRAME);
|
2016-03-21 23:48:02 +00:00
|
|
|
|
|
|
|
yamlLoadHelper.PopMap();
|
2006-02-25 20:50:29 +00:00
|
|
|
}
|
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)
|
|
|
|
//
|
2020-12-28 16:25:29 +00:00
|
|
|
WORD Video::VideoGetScannerAddress(DWORD nCycles, VideoScanner_e videoScannerAddr /*= VS_FullAddr*/)
|
|
|
|
{
|
|
|
|
const int kHBurstClock = 53; // clock when Color Burst starts
|
|
|
|
const int kHBurstClocks = 4; // clocks per Color Burst duration
|
|
|
|
const int kHClock0State = 0x18; // H[543210] = 011000
|
|
|
|
const int kHClocks = 65; // clocks per horizontal scan (including HBL)
|
|
|
|
const int kHPEClock = 40; // clock when HPE (horizontal preset enable) goes low
|
|
|
|
const int kHPresetClock = 41; // clock when H state presets
|
|
|
|
const int kHSyncClock = 49; // clock when HSync starts
|
|
|
|
const int kHSyncClocks = 4; // clocks per HSync duration
|
|
|
|
const int kNTSCScanLines = 262; // total scan lines including VBL (NTSC)
|
|
|
|
const int kNTSCVSyncLine = 224; // line when VSync starts (NTSC)
|
|
|
|
const int kPALScanLines = 312; // total scan lines including VBL (PAL)
|
|
|
|
const int kPALVSyncLine = 264; // line when VSync starts (PAL)
|
|
|
|
const int kVLine0State = 0x100; // V[543210CBA] = 100000000
|
|
|
|
const int kVPresetLine = 256; // line when V state presets
|
|
|
|
const int kVSyncLines = 4; // lines per VSync duration
|
|
|
|
|
|
|
|
// machine state switches
|
2006-03-12 09:05:39 +00:00
|
|
|
//
|
2018-08-06 18:06:28 +00:00
|
|
|
bool bHires = VideoGetSWHIRES() && !VideoGetSWTEXT();
|
|
|
|
bool bPage2 = VideoGetSWPAGE2();
|
|
|
|
bool b80Store = VideoGetSW80STORE();
|
2006-03-12 09:05:39 +00:00
|
|
|
|
|
|
|
// calculate video parameters according to display standard
|
|
|
|
//
|
2018-08-06 18:06:28 +00:00
|
|
|
const int kScanLines = g_bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines;
|
|
|
|
const int kScanCycles = kScanLines * kHClocks;
|
2019-06-28 20:34:34 +00:00
|
|
|
_ASSERT(nCycles < (UINT)kScanCycles);
|
2018-08-06 18:06:28 +00:00
|
|
|
nCycles %= kScanCycles;
|
2006-03-12 09:05:39 +00:00
|
|
|
|
|
|
|
// calculate horizontal scanning state
|
|
|
|
//
|
|
|
|
int nHClock = (nCycles + kHPEClock) % kHClocks; // which horizontal scanning clock
|
|
|
|
int nHState = kHClock0State + nHClock; // H state bits
|
|
|
|
if (nHClock >= kHPresetClock) // check for horizontal preset
|
|
|
|
{
|
|
|
|
nHState -= 1; // correct for state preset (two 0 states)
|
|
|
|
}
|
|
|
|
int h_0 = (nHState >> 0) & 1; // get horizontal state bits
|
|
|
|
int h_1 = (nHState >> 1) & 1;
|
|
|
|
int h_2 = (nHState >> 2) & 1;
|
|
|
|
int h_3 = (nHState >> 3) & 1;
|
|
|
|
int h_4 = (nHState >> 4) & 1;
|
|
|
|
int h_5 = (nHState >> 5) & 1;
|
|
|
|
|
2019-06-28 20:34:34 +00:00
|
|
|
// calculate vertical scanning state (UTAIIe:3-15,T3.2)
|
2006-03-12 09:05:39 +00:00
|
|
|
//
|
|
|
|
int nVLine = nCycles / kHClocks; // which vertical scanning line
|
|
|
|
int nVState = kVLine0State + nVLine; // V state bits
|
2018-08-05 21:19:51 +00:00
|
|
|
if (nVLine >= kVPresetLine) // check for previous vertical state preset
|
2006-03-12 09:05:39 +00:00
|
|
|
{
|
2018-08-06 18:06:28 +00:00
|
|
|
nVState -= kScanLines; // compensate for preset
|
2006-03-12 09:05:39 +00:00
|
|
|
}
|
|
|
|
int v_A = (nVState >> 0) & 1; // get vertical state bits
|
|
|
|
int v_B = (nVState >> 1) & 1;
|
|
|
|
int v_C = (nVState >> 2) & 1;
|
|
|
|
int v_0 = (nVState >> 3) & 1;
|
|
|
|
int v_1 = (nVState >> 4) & 1;
|
|
|
|
int v_2 = (nVState >> 5) & 1;
|
|
|
|
int v_3 = (nVState >> 6) & 1;
|
|
|
|
int v_4 = (nVState >> 7) & 1;
|
|
|
|
int v_5 = (nVState >> 8) & 1;
|
|
|
|
|
|
|
|
// calculate scanning memory address
|
|
|
|
//
|
2018-08-06 18:06:28 +00:00
|
|
|
if (bHires && SW_MIXED && v_4 && v_2) // HIRES TIME signal (UTAIIe:5-7,P3)
|
2006-03-12 09:05:39 +00:00
|
|
|
{
|
2018-08-06 18:06:28 +00:00
|
|
|
bHires = false; // address is in text memory for mixed hires
|
2006-03-12 09:05:39 +00:00
|
|
|
}
|
|
|
|
|
2008-12-30 15:45:48 +00:00
|
|
|
int nAddend0 = 0x0D; // 1 1 0 1
|
|
|
|
int nAddend1 = (h_5 << 2) | (h_4 << 1) | (h_3 << 0);
|
|
|
|
int nAddend2 = (v_4 << 3) | (v_3 << 2) | (v_4 << 1) | (v_3 << 0);
|
|
|
|
int nSum = (nAddend0 + nAddend1 + nAddend2) & 0x0F; // SUM (UTAIIe:5-9)
|
|
|
|
|
2018-08-06 18:06:28 +00:00
|
|
|
WORD nAddressH = 0; // build address from video scanner equations (UTAIIe:5-8,T5.1)
|
|
|
|
nAddressH |= h_0 << 0; // a0
|
|
|
|
nAddressH |= h_1 << 1; // a1
|
|
|
|
nAddressH |= h_2 << 2; // a2
|
|
|
|
nAddressH |= nSum << 3; // a3 - a6
|
|
|
|
if (!bHires)
|
|
|
|
{
|
|
|
|
// Apple ][ (not //e) and HBL?
|
|
|
|
//
|
|
|
|
if (IS_APPLE2 && // Apple II only (UTAIIe:I-4,#5)
|
|
|
|
!h_5 && (!h_4 || !h_3)) // HBL (UTAIIe:8-10,F8.5)
|
|
|
|
{
|
|
|
|
nAddressH |= 1 << 12; // Y: a12 (add $1000 to address!)
|
|
|
|
}
|
|
|
|
}
|
2018-08-05 21:19:51 +00:00
|
|
|
|
2018-08-06 18:06:28 +00:00
|
|
|
WORD nAddressV = 0;
|
|
|
|
nAddressV |= v_0 << 7; // a7
|
|
|
|
nAddressV |= v_1 << 8; // a8
|
|
|
|
nAddressV |= v_2 << 9; // a9
|
2008-12-30 15:45:48 +00:00
|
|
|
|
2018-08-06 18:06:28 +00:00
|
|
|
int p2a = !(bPage2 && !b80Store) ? 1 : 0;
|
|
|
|
int p2b = (bPage2 && !b80Store) ? 1 : 0;
|
2008-12-30 15:45:48 +00:00
|
|
|
|
2018-08-06 18:06:28 +00:00
|
|
|
WORD nAddressP = 0; // Page bits
|
|
|
|
if (bHires) // hires?
|
2006-03-12 09:05:39 +00:00
|
|
|
{
|
2008-12-30 15:45:48 +00:00
|
|
|
// Y: insert hires-only address bits
|
2006-03-12 09:05:39 +00:00
|
|
|
//
|
2018-08-06 18:06:28 +00:00
|
|
|
nAddressV |= v_A << 10; // a10
|
|
|
|
nAddressV |= v_B << 11; // a11
|
|
|
|
nAddressV |= v_C << 12; // a12
|
|
|
|
nAddressP |= p2a << 13; // a13
|
|
|
|
nAddressP |= p2b << 14; // a14
|
2006-03-12 09:05:39 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-12-30 15:45:48 +00:00
|
|
|
// N: insert text-only address bits
|
2006-03-12 09:05:39 +00:00
|
|
|
//
|
2018-08-06 18:06:28 +00:00
|
|
|
nAddressP |= p2a << 10; // a10
|
|
|
|
nAddressP |= p2b << 11; // a11
|
2006-03-12 09:05:39 +00:00
|
|
|
}
|
2018-08-05 21:19:51 +00:00
|
|
|
|
2019-06-28 20:34:34 +00:00
|
|
|
// VBL' = v_4' | v_3' = (v_4 & v_3)' (UTAIIe:5-10,#3), (UTAIIe:3-15,T3.2)
|
2018-08-05 21:19:51 +00:00
|
|
|
|
2018-08-06 18:06:28 +00:00
|
|
|
if (videoScannerAddr == VS_PartialAddrH)
|
|
|
|
return nAddressH;
|
|
|
|
|
|
|
|
if (videoScannerAddr == VS_PartialAddrV)
|
|
|
|
return nAddressV;
|
|
|
|
|
|
|
|
return nAddressP | nAddressV | nAddressH;
|
2006-03-12 09:05:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
2007-08-06 21:38:35 +00:00
|
|
|
|
2019-11-11 19:55:46 +00:00
|
|
|
// Called when *outside* of CpuExecute()
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetVblBarEx(const DWORD dwCyclesThisFrame)
|
2019-11-11 19:55:46 +00:00
|
|
|
{
|
|
|
|
if (g_bFullSpeed)
|
|
|
|
{
|
|
|
|
// Ensure that NTSC video-scanner gets updated during full-speed, so video screen can be redrawn during Apple II VBL
|
|
|
|
NTSC_VideoClockResync(dwCyclesThisFrame);
|
|
|
|
}
|
|
|
|
|
2023-01-14 14:30:12 +00:00
|
|
|
return NTSC_GetVblBar();
|
2019-11-11 19:55:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called when *inside* CpuExecute()
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::VideoGetVblBar(const DWORD uExecutedCycles)
|
2007-08-06 21:38:35 +00:00
|
|
|
{
|
2019-11-10 23:52:29 +00:00
|
|
|
if (g_bFullSpeed)
|
|
|
|
{
|
2019-11-11 19:55:46 +00:00
|
|
|
// Ensure that NTSC video-scanner gets updated during full-speed, so video-dependent Apple II code doesn't hang
|
2019-11-10 23:52:29 +00:00
|
|
|
NTSC_VideoClockResync(CpuGetCyclesThisVideoFrame(uExecutedCycles));
|
|
|
|
}
|
2007-08-06 21:38:35 +00:00
|
|
|
|
2023-01-14 14:30:12 +00:00
|
|
|
return NTSC_GetVblBar();
|
2019-11-03 15:05:28 +00:00
|
|
|
}
|
2008-08-31 04:31:35 +00:00
|
|
|
|
|
|
|
//===========================================================================
|
2020-12-28 16:25:29 +00:00
|
|
|
|
|
|
|
void Video::Video_SetBitmapHeader(WinBmpHeader_t *pBmp, int nWidth, int nHeight, int nBitsPerPixel)
|
2008-07-14 16:02:44 +00:00
|
|
|
{
|
2015-01-10 19:42:53 +00:00
|
|
|
pBmp->nCookie[ 0 ] = 'B'; // 0x42
|
|
|
|
pBmp->nCookie[ 1 ] = 'M'; // 0x4d
|
|
|
|
pBmp->nSizeFile = 0;
|
|
|
|
pBmp->nReserved1 = 0;
|
|
|
|
pBmp->nReserved2 = 0;
|
2015-01-10 19:22:41 +00:00
|
|
|
#if VIDEO_SCREENSHOT_PALETTE
|
2015-01-10 19:42:53 +00:00
|
|
|
pBmp->nOffsetData = sizeof(WinBmpHeader_t) + (256 * sizeof(bgra_t));
|
2015-01-10 19:22:41 +00:00
|
|
|
#else
|
2015-01-10 19:42:53 +00:00
|
|
|
pBmp->nOffsetData = sizeof(WinBmpHeader_t);
|
2015-01-10 19:22:41 +00:00
|
|
|
#endif
|
2015-01-10 19:42:53 +00:00
|
|
|
pBmp->nStructSize = 0x28; // sizeof( WinBmpHeader_t );
|
|
|
|
pBmp->nWidthPixels = nWidth;
|
|
|
|
pBmp->nHeightPixels = nHeight;
|
|
|
|
pBmp->nPlanes = 1;
|
2015-01-10 19:22:41 +00:00
|
|
|
#if VIDEO_SCREENSHOT_PALETTE
|
2015-01-10 19:42:53 +00:00
|
|
|
pBmp->nBitsPerPixel = 8;
|
2015-01-10 19:22:41 +00:00
|
|
|
#else
|
2015-01-10 19:42:53 +00:00
|
|
|
pBmp->nBitsPerPixel = nBitsPerPixel;
|
2015-01-10 19:22:41 +00:00
|
|
|
#endif
|
2015-01-10 19:42:53 +00:00
|
|
|
pBmp->nCompression = BI_RGB; // none
|
|
|
|
pBmp->nSizeImage = 0;
|
|
|
|
pBmp->nXPelsPerMeter = 0;
|
|
|
|
pBmp->nYPelsPerMeter = 0;
|
|
|
|
#if VIDEO_SCREENSHOT_PALETTE
|
|
|
|
pBmp->nPaletteColors = 256;
|
|
|
|
#else
|
|
|
|
pBmp->nPaletteColors = 0;
|
|
|
|
#endif
|
|
|
|
pBmp->nImportantColors = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
2020-12-28 16:25:29 +00:00
|
|
|
|
|
|
|
void Video::Video_MakeScreenShot(FILE *pFile, const VideoScreenShot_e ScreenShotType)
|
2015-01-10 19:42:53 +00:00
|
|
|
{
|
2022-01-03 14:41:03 +00:00
|
|
|
WinBmpHeader_t bmp, *pBmp = &bmp;
|
2015-01-10 19:42:53 +00:00
|
|
|
|
|
|
|
Video_SetBitmapHeader(
|
|
|
|
pBmp,
|
2017-10-11 16:38:36 +00:00
|
|
|
ScreenShotType == SCREENSHOT_280x192 ? GetFrameBufferBorderlessWidth()/2 : GetFrameBufferBorderlessWidth(),
|
|
|
|
ScreenShotType == SCREENSHOT_280x192 ? GetFrameBufferBorderlessHeight()/2 : GetFrameBufferBorderlessHeight(),
|
2015-01-10 19:42:53 +00:00
|
|
|
32
|
|
|
|
);
|
2008-08-25 00:36:48 +00:00
|
|
|
|
2022-01-03 14:41:03 +00:00
|
|
|
#define EXPECTED_BMP_HEADER_SIZE (14 + 40)
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
char sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54[ sizeof( WinBmpHeader_t ) == EXPECTED_BMP_HEADER_SIZE];
|
2015-01-10 19:42:53 +00:00
|
|
|
/**/ sIfSizeZeroOrUnknown_BadWinBmpHeaderPackingSize54[0]=0;
|
2022-01-03 14:41:03 +00:00
|
|
|
#else
|
|
|
|
static_assert(sizeof( WinBmpHeader_t ) == EXPECTED_BMP_HEADER_SIZE, "BadWinBmpHeaderPackingSize");
|
|
|
|
#endif
|
2008-08-25 00:36:48 +00:00
|
|
|
|
|
|
|
// Write Header
|
2015-01-10 19:42:53 +00:00
|
|
|
fwrite( pBmp, sizeof( WinBmpHeader_t ), 1, pFile );
|
2009-02-16 20:30:38 +00:00
|
|
|
|
2015-01-10 19:22:41 +00:00
|
|
|
uint32_t *pSrc;
|
|
|
|
#if VIDEO_SCREENSHOT_PALETTE
|
2008-08-25 00:36:48 +00:00
|
|
|
// Write Palette Data
|
2015-01-10 19:22:41 +00:00
|
|
|
pSrc = ((uint8_t*)g_pFramebufferinfo) + sizeof(BITMAPINFOHEADER);
|
2016-04-04 20:05:58 +00:00
|
|
|
int nLen = g_tBmpHeader.nPaletteColors * sizeof(bgra_t); // RGBQUAD
|
2008-08-25 00:36:48 +00:00
|
|
|
fwrite( pSrc, nLen, 1, pFile );
|
|
|
|
pSrc += nLen;
|
2015-01-10 19:22:41 +00:00
|
|
|
#endif
|
2008-08-25 00:36:48 +00:00
|
|
|
|
|
|
|
// Write Pixel Data
|
|
|
|
// No need to use GetDibBits() since we already have http://msdn.microsoft.com/en-us/library/ms532334.aspx
|
|
|
|
// @reference: "Storing an Image" http://msdn.microsoft.com/en-us/library/ms532340(VS.85).aspx
|
2015-01-10 19:22:41 +00:00
|
|
|
pSrc = (uint32_t*) g_pFramebufferbits;
|
2016-10-11 20:52:11 +00:00
|
|
|
|
2017-10-11 16:38:36 +00:00
|
|
|
int xSrc = GetFrameBufferBorderWidth();
|
|
|
|
int ySrc = GetFrameBufferBorderHeight();
|
2016-10-11 20:52:11 +00:00
|
|
|
|
2017-10-11 16:38:36 +00:00
|
|
|
pSrc += xSrc; // Skip left border
|
|
|
|
pSrc += ySrc * GetFrameBufferWidth(); // Skip top border
|
2008-08-31 04:31:35 +00:00
|
|
|
|
2017-07-06 20:36:56 +00:00
|
|
|
if( ScreenShotType == SCREENSHOT_280x192 )
|
2008-08-31 04:31:35 +00:00
|
|
|
{
|
2017-10-11 16:38:36 +00:00
|
|
|
pSrc += GetFrameBufferWidth(); // Start on odd scanline (otherwise for 50% scanline mode get an all black image!)
|
2013-03-28 22:28:42 +00:00
|
|
|
|
2021-12-07 21:59:05 +00:00
|
|
|
uint32_t aScanLine[kVideoWidthIIgs / 2]; // Big enough to contain both a 280 or 320 line
|
2015-01-10 19:22:41 +00:00
|
|
|
uint32_t *pDst;
|
2008-08-31 04:31:35 +00:00
|
|
|
|
2009-02-18 22:24:50 +00:00
|
|
|
// 50% Half Scan Line clears every odd scanline.
|
2014-08-21 21:53:01 +00:00
|
|
|
// SHIFT+PrintScreen saves only the even rows.
|
2009-02-18 22:24:50 +00:00
|
|
|
// NOTE: Keep in sync with _Video_RedrawScreen() & Video_MakeScreenShot()
|
2017-10-11 16:38:36 +00:00
|
|
|
for( UINT y = 0; y < GetFrameBufferBorderlessHeight()/2; y++ )
|
2008-08-31 04:31:35 +00:00
|
|
|
{
|
|
|
|
pDst = aScanLine;
|
2017-10-11 16:38:36 +00:00
|
|
|
for( UINT x = 0; x < GetFrameBufferBorderlessWidth()/2; x++ )
|
2008-08-31 04:31:35 +00:00
|
|
|
{
|
2013-03-28 22:28:42 +00:00
|
|
|
*pDst++ = pSrc[1]; // correction for left edge loss of scaled scanline [Bill Buckel, B#18928]
|
2008-08-31 04:31:35 +00:00
|
|
|
pSrc += 2; // skip odd pixels
|
|
|
|
}
|
2017-10-11 16:38:36 +00:00
|
|
|
fwrite( aScanLine, sizeof(uint32_t), GetFrameBufferBorderlessWidth()/2, pFile );
|
|
|
|
pSrc += GetFrameBufferWidth(); // scan lines doubled - skip odd ones
|
|
|
|
pSrc += GetFrameBufferBorderWidth()*2; // Skip right border & next line's left border
|
2008-08-31 04:31:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-10-11 16:38:36 +00:00
|
|
|
for( UINT y = 0; y < GetFrameBufferBorderlessHeight(); y++ )
|
2016-04-04 20:05:58 +00:00
|
|
|
{
|
2017-10-11 16:38:36 +00:00
|
|
|
fwrite( pSrc, sizeof(uint32_t), GetFrameBufferBorderlessWidth(), pFile );
|
|
|
|
pSrc += GetFrameBufferWidth();
|
2016-04-04 20:05:58 +00:00
|
|
|
}
|
2008-08-31 04:31:35 +00:00
|
|
|
}
|
2022-01-03 14:41:03 +00:00
|
|
|
|
|
|
|
// re-write the Header to include the file size (otherwise "file" does not recognise it)
|
|
|
|
pBmp->nSizeFile = ftell(pFile);
|
|
|
|
rewind(pFile);
|
|
|
|
fwrite( pBmp, sizeof( WinBmpHeader_t ), 1, pFile );
|
2022-01-07 21:06:24 +00:00
|
|
|
fseek(pFile, 0, SEEK_END);
|
2008-07-14 16:02:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
2020-12-28 16:25:29 +00:00
|
|
|
|
2020-12-29 21:30:17 +00:00
|
|
|
bool Video::ReadVideoRomFile(const char* pRomFile)
|
2018-11-17 16:29:17 +00:00
|
|
|
{
|
|
|
|
g_videoRomSize = 0;
|
|
|
|
|
|
|
|
HANDLE h = CreateFile(pRomFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
|
|
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const ULONG size = GetFileSize(h, NULL);
|
2018-11-19 22:15:04 +00:00
|
|
|
if (size == kVideoRomSize2K || size == kVideoRomSize4K || size == kVideoRomSize8K || size == kVideoRomSize16K)
|
2018-11-17 16:29:17 +00:00
|
|
|
{
|
|
|
|
DWORD bytesRead;
|
|
|
|
if (ReadFile(h, g_videoRom, size, &bytesRead, NULL) && bytesRead == size)
|
|
|
|
g_videoRomSize = size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (g_videoRomSize == kVideoRomSize16K)
|
|
|
|
{
|
|
|
|
// Use top 8K (assume bottom 8K is all 0xFF's)
|
|
|
|
memcpy(&g_videoRom[0], &g_videoRom[kVideoRomSize8K], kVideoRomSize8K);
|
|
|
|
g_videoRomSize = kVideoRomSize8K;
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandle(h);
|
|
|
|
|
|
|
|
return g_videoRomSize != 0;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
UINT Video::GetVideoRom(const BYTE*& pVideoRom)
|
2018-11-17 16:29:17 +00:00
|
|
|
{
|
|
|
|
pVideoRom = &g_videoRom[0];
|
|
|
|
return g_videoRomSize;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::GetVideoRomRockerSwitch(void)
|
2018-11-17 16:29:17 +00:00
|
|
|
{
|
|
|
|
return g_videoRomRockerSwitch;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
void Video::SetVideoRomRockerSwitch(bool state)
|
2018-11-17 16:29:17 +00:00
|
|
|
{
|
|
|
|
g_videoRomRockerSwitch = state;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::IsVideoRom4K(void)
|
2018-11-17 16:29:17 +00:00
|
|
|
{
|
2018-11-19 22:15:04 +00:00
|
|
|
return g_videoRomSize <= kVideoRomSize4K;
|
2018-11-17 16:29:17 +00:00
|
|
|
}
|
|
|
|
|
2012-03-27 21:20:36 +00:00
|
|
|
//===========================================================================
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
void Video::Config_Load_Video()
|
2019-01-09 21:29:36 +00:00
|
|
|
{
|
2020-12-28 16:25:29 +00:00
|
|
|
enum VideoType127_e
|
|
|
|
{
|
|
|
|
VT127_MONO_CUSTOM
|
|
|
|
, VT127_COLOR_MONITOR_NTSC
|
|
|
|
, VT127_MONO_TV
|
|
|
|
, VT127_COLOR_TV
|
|
|
|
, VT127_MONO_AMBER
|
|
|
|
, VT127_MONO_GREEN
|
|
|
|
, VT127_MONO_WHITE
|
|
|
|
, VT127_NUM_VIDEO_MODES
|
|
|
|
};
|
2019-01-09 21:29:36 +00:00
|
|
|
|
2019-08-09 03:50:29 +00:00
|
|
|
DWORD dwTmp;
|
2019-02-24 15:59:35 +00:00
|
|
|
|
2019-08-09 03:50:29 +00:00
|
|
|
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_MODE), &dwTmp, (DWORD)VT_DEFAULT);
|
|
|
|
g_eVideoType = dwTmp;
|
|
|
|
|
|
|
|
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_STYLE), &dwTmp, (DWORD)VS_HALF_SCANLINES);
|
|
|
|
g_eVideoStyle = (VideoStyle_e)dwTmp;
|
|
|
|
|
|
|
|
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_MONO_COLOR), &dwTmp, (DWORD)RGB(0xC0, 0xC0, 0xC0));
|
|
|
|
g_nMonochromeRGB = (COLORREF)dwTmp;
|
|
|
|
|
|
|
|
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_REFRESH_RATE), &dwTmp, (DWORD)VR_60HZ);
|
|
|
|
SetVideoRefreshRate((VideoRefreshRate_e)dwTmp);
|
2019-06-28 20:34:34 +00:00
|
|
|
|
2019-02-24 15:59:35 +00:00
|
|
|
//
|
2012-03-27 21:20:36 +00:00
|
|
|
|
2019-01-09 21:29:36 +00:00
|
|
|
const UINT16* pOldVersion = GetOldAppleWinVersion();
|
2019-02-24 15:59:35 +00:00
|
|
|
if (pOldVersion[0] == 1 && pOldVersion[1] <= 28 && pOldVersion[2] <= 1)
|
|
|
|
{
|
2019-08-09 03:50:29 +00:00
|
|
|
DWORD dwHalfScanLines;
|
|
|
|
REGLOAD_DEFAULT(TEXT(REGVALUE_VIDEO_HALF_SCAN_LINES), &dwHalfScanLines, 0);
|
2019-02-24 15:59:35 +00:00
|
|
|
|
2019-08-09 03:50:29 +00:00
|
|
|
if (dwHalfScanLines)
|
2019-02-24 15:59:35 +00:00
|
|
|
g_eVideoStyle = (VideoStyle_e) ((DWORD)g_eVideoStyle | VS_HALF_SCANLINES);
|
|
|
|
else
|
|
|
|
g_eVideoStyle = (VideoStyle_e) ((DWORD)g_eVideoStyle & ~VS_HALF_SCANLINES);
|
|
|
|
|
|
|
|
REGSAVE(TEXT(REGVALUE_VIDEO_STYLE), g_eVideoStyle);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
|
2019-01-09 21:29:36 +00:00
|
|
|
if (pOldVersion[0] == 1 && pOldVersion[1] <= 27 && pOldVersion[2] <= 13)
|
|
|
|
{
|
|
|
|
switch (g_eVideoType)
|
|
|
|
{
|
|
|
|
case VT127_MONO_CUSTOM: g_eVideoType = VT_MONO_CUSTOM; break;
|
|
|
|
case VT127_COLOR_MONITOR_NTSC: g_eVideoType = VT_COLOR_MONITOR_NTSC; break;
|
|
|
|
case VT127_MONO_TV: g_eVideoType = VT_MONO_TV; break;
|
|
|
|
case VT127_COLOR_TV: g_eVideoType = VT_COLOR_TV; break;
|
|
|
|
case VT127_MONO_AMBER: g_eVideoType = VT_MONO_AMBER; break;
|
|
|
|
case VT127_MONO_GREEN: g_eVideoType = VT_MONO_GREEN; break;
|
|
|
|
case VT127_MONO_WHITE: g_eVideoType = VT_MONO_WHITE; break;
|
|
|
|
default: g_eVideoType = VT_DEFAULT; break;
|
|
|
|
}
|
2019-02-24 15:59:35 +00:00
|
|
|
|
|
|
|
REGSAVE(TEXT(REGVALUE_VIDEO_MODE), g_eVideoType);
|
2019-01-09 21:29:36 +00:00
|
|
|
}
|
|
|
|
|
2012-03-27 21:20:36 +00:00
|
|
|
if (g_eVideoType >= NUM_VIDEO_MODES)
|
2019-01-09 21:29:36 +00:00
|
|
|
g_eVideoType = VT_DEFAULT;
|
2012-03-27 21:20:36 +00:00
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
void Video::Config_Save_Video()
|
2012-03-27 21:20:36 +00:00
|
|
|
{
|
2019-02-24 15:59:35 +00:00
|
|
|
REGSAVE(TEXT(REGVALUE_VIDEO_MODE) ,g_eVideoType);
|
|
|
|
REGSAVE(TEXT(REGVALUE_VIDEO_STYLE) ,g_eVideoStyle);
|
|
|
|
REGSAVE(TEXT(REGVALUE_VIDEO_MONO_COLOR),g_nMonochromeRGB);
|
2019-06-28 20:34:34 +00:00
|
|
|
REGSAVE(TEXT(REGVALUE_VIDEO_REFRESH_RATE), GetVideoRefreshRate());
|
2019-02-24 15:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
uint32_t Video::GetVideoMode(void)
|
|
|
|
{
|
|
|
|
return g_uVideoMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Video::SetVideoMode(uint32_t videoMode)
|
|
|
|
{
|
|
|
|
g_uVideoMode = videoMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
VideoType_e Video::GetVideoType(void)
|
2019-02-24 15:59:35 +00:00
|
|
|
{
|
|
|
|
return (VideoType_e) g_eVideoType;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Can only do this at start-up (mid-emulation requires a more heavy-weight video reinit)
|
2020-12-28 16:25:29 +00:00
|
|
|
void Video::SetVideoType(VideoType_e newVideoType)
|
2019-02-24 15:59:35 +00:00
|
|
|
{
|
|
|
|
g_eVideoType = newVideoType;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
VideoStyle_e Video::GetVideoStyle(void)
|
2019-02-24 15:59:35 +00:00
|
|
|
{
|
|
|
|
return g_eVideoStyle;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
void Video::IncVideoType(void)
|
|
|
|
{
|
|
|
|
g_eVideoType++;
|
|
|
|
if (g_eVideoType >= NUM_VIDEO_MODES)
|
|
|
|
g_eVideoType = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Video::DecVideoType(void)
|
|
|
|
{
|
|
|
|
if (g_eVideoType <= 0)
|
|
|
|
g_eVideoType = NUM_VIDEO_MODES;
|
|
|
|
g_eVideoType--;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Video::SetVideoStyle(VideoStyle_e newVideoStyle)
|
2019-02-24 15:59:35 +00:00
|
|
|
{
|
|
|
|
g_eVideoStyle = newVideoStyle;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
bool Video::IsVideoStyle(VideoStyle_e mask)
|
2019-02-24 15:59:35 +00:00
|
|
|
{
|
|
|
|
return (g_eVideoStyle & mask) != 0;
|
2012-03-27 21:20:36 +00:00
|
|
|
}
|
2014-12-31 22:13:36 +00:00
|
|
|
|
2019-06-28 20:34:34 +00:00
|
|
|
//===========================================================================
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
VideoRefreshRate_e Video::GetVideoRefreshRate(void)
|
2019-06-28 20:34:34 +00:00
|
|
|
{
|
|
|
|
return (g_bVideoScannerNTSC == false) ? VR_50HZ : VR_60HZ;
|
|
|
|
}
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
void Video::SetVideoRefreshRate(VideoRefreshRate_e rate)
|
2019-06-28 20:34:34 +00:00
|
|
|
{
|
|
|
|
if (rate != VR_50HZ)
|
|
|
|
rate = VR_60HZ;
|
|
|
|
|
|
|
|
g_bVideoScannerNTSC = (rate == VR_60HZ);
|
|
|
|
NTSC_SetRefreshRate(rate);
|
|
|
|
}
|
|
|
|
|
2020-08-17 18:26:06 +00:00
|
|
|
//===========================================================================
|
|
|
|
|
2020-12-28 16:25:29 +00:00
|
|
|
const char* Video::VideoGetAppWindowTitle(void)
|
2020-08-17 18:26:06 +00:00
|
|
|
{
|
|
|
|
static const char *apVideoMonitorModeDesc[ 2 ] =
|
|
|
|
{
|
|
|
|
"Color (NTSC Monitor)",
|
|
|
|
"Color (PAL Monitor)"
|
|
|
|
};
|
|
|
|
|
|
|
|
const VideoType_e videoType = GetVideoType();
|
|
|
|
if ( videoType != VT_COLOR_MONITOR_NTSC)
|
|
|
|
return g_apVideoModeDesc[ videoType ];
|
|
|
|
else
|
|
|
|
return apVideoMonitorModeDesc[ GetVideoRefreshRate() == VR_60HZ ? 0 : 1 ]; // NTSC or PAL
|
|
|
|
}
|
2020-12-29 21:30:17 +00:00
|
|
|
|
2021-11-30 21:41:02 +00:00
|
|
|
void Video::Initialize(uint8_t* frameBuffer, bool resetState)
|
2020-12-29 21:30:17 +00:00
|
|
|
{
|
2021-01-03 16:21:24 +00:00
|
|
|
SetFrameBuffer(frameBuffer);
|
2020-12-29 21:30:17 +00:00
|
|
|
|
2021-11-30 21:41:02 +00:00
|
|
|
if (resetState)
|
|
|
|
{
|
|
|
|
// RESET THE VIDEO MODE SWITCHES AND THE CHARACTER SET OFFSET
|
|
|
|
VideoResetState();
|
|
|
|
}
|
2020-12-29 21:30:17 +00:00
|
|
|
|
2021-01-03 16:21:24 +00:00
|
|
|
// DRAW THE SOURCE IMAGE INTO THE SOURCE BIT BUFFER
|
2021-11-30 21:41:02 +00:00
|
|
|
ClearFrameBuffer();
|
2020-12-29 21:30:17 +00:00
|
|
|
|
2021-01-03 16:21:24 +00:00
|
|
|
// CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE FRAME BUFFER
|
|
|
|
NTSC_VideoInit(GetFrameBuffer());
|
2020-12-29 21:30:17 +00:00
|
|
|
|
2021-01-03 16:21:24 +00:00
|
|
|
#if 0
|
|
|
|
DDInit(); // For WaitForVerticalBlank()
|
|
|
|
#endif
|
2020-12-29 21:30:17 +00:00
|
|
|
}
|
|
|
|
|
2021-01-03 16:21:24 +00:00
|
|
|
void Video::Destroy(void)
|
2020-12-29 21:30:17 +00:00
|
|
|
{
|
2021-01-03 16:21:24 +00:00
|
|
|
SetFrameBuffer(NULL);
|
|
|
|
NTSC_Destroy();
|
2020-12-29 21:30:17 +00:00
|
|
|
}
|
|
|
|
|
2021-01-03 16:21:24 +00:00
|
|
|
void Video::VideoRefreshBuffer(uint32_t uRedrawWholeScreenVideoMode, bool bRedrawWholeScreen)
|
2020-12-29 21:30:17 +00:00
|
|
|
{
|
|
|
|
if (bRedrawWholeScreen || g_nAppMode == MODE_PAUSED)
|
|
|
|
{
|
|
|
|
// uVideoModeForWholeScreen set if:
|
|
|
|
// . MODE_DEBUG : always
|
|
|
|
// . MODE_RUNNING : called from VideoRedrawScreen(), eg. during full-speed
|
|
|
|
if (bRedrawWholeScreen)
|
|
|
|
NTSC_SetVideoMode(uRedrawWholeScreenVideoMode);
|
|
|
|
NTSC_VideoRedrawWholeScreen();
|
|
|
|
|
|
|
|
// MODE_DEBUG|PAUSED: Need to refresh a 2nd time if changing video-type, otherwise could have residue from prev image!
|
|
|
|
// . eg. Amber -> B&W TV
|
|
|
|
if (g_nAppMode == MODE_DEBUG || g_nAppMode == MODE_PAUSED)
|
|
|
|
NTSC_VideoRedrawWholeScreen();
|
|
|
|
}
|
2021-01-01 12:42:24 +00:00
|
|
|
}
|
2021-11-30 21:41:02 +00:00
|
|
|
|
|
|
|
void Video::ClearFrameBuffer(void)
|
|
|
|
{
|
2021-12-11 18:05:06 +00:00
|
|
|
UINT32* frameBuffer = (UINT32*)GetFrameBuffer();
|
|
|
|
std::fill(frameBuffer, frameBuffer + GetFrameBufferWidth() * GetFrameBufferHeight(), OPAQUE_BLACK);
|
2021-11-30 21:41:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called when entering debugger, and after viewing Apple II video screen from debugger
|
|
|
|
void Video::ClearSHRResidue(void)
|
|
|
|
{
|
|
|
|
ClearFrameBuffer();
|
|
|
|
GetFrame().VideoPresentScreen();
|
|
|
|
}
|