mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
1077 lines
26 KiB
C++
1077 lines
26 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Nestopia - NES/Famicom emulator written in C++
|
|
//
|
|
// Copyright (C) 2003-2008 Martin Freij
|
|
//
|
|
// This file is part of Nestopia.
|
|
//
|
|
// Nestopia 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.
|
|
//
|
|
// Nestopia 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 Nestopia; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "NstManagerPaths.hpp"
|
|
#include "NstResourceString.hpp"
|
|
#include "NstWindowParam.hpp"
|
|
#include "NstWindowUser.hpp"
|
|
#include "NstDialogVideo.hpp"
|
|
#include "NstManagerVideo.hpp"
|
|
#include "NstIoScreen.hpp"
|
|
|
|
namespace Nestopia
|
|
{
|
|
namespace Managers
|
|
{
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("t", on)
|
|
#endif
|
|
|
|
struct Video::Callbacks
|
|
{
|
|
static bool NST_CALLBACK ScreenLock(Nes::Video::UserData data,Nes::Video::Output& output)
|
|
{
|
|
NST_ASSERT( data );
|
|
|
|
return static_cast<DirectX::Direct2D*>(data)->LockScreen( output.pixels, output.pitch );
|
|
}
|
|
|
|
static void NST_CALLBACK ScreenUnlock(Nes::Video::UserData data,Nes::Video::Output&)
|
|
{
|
|
NST_ASSERT( data );
|
|
|
|
static_cast<DirectX::Direct2D*>(data)->UnlockScreen();
|
|
static_cast<DirectX::Direct2D*>(data)->RenderScreen( DirectX::Direct2D::RENDER_PICTURE|DirectX::Direct2D::RENDER_FPS|DirectX::Direct2D::RENDER_MSG );
|
|
}
|
|
};
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
inline Video::Fps::Fps()
|
|
: frame(0) {}
|
|
|
|
inline Video::Nsf::Nsf()
|
|
: songTextOffset(0) {}
|
|
|
|
void Video::Nsf::Load(const Nes::Nsf emulator)
|
|
{
|
|
struct Info
|
|
{
|
|
static cstring GoodName(cstring text)
|
|
{
|
|
if
|
|
(
|
|
text && *text &&
|
|
std::strcmp( text, "<?>" ) &&
|
|
std::strcmp( text, "< ? >" ) &&
|
|
std::strcmp( text, "?" )
|
|
)
|
|
return text;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static cstring GetMode(Nes::Nsf::TuneMode mode)
|
|
{
|
|
return
|
|
(
|
|
mode == Nes::Nsf::TUNE_MODE_PAL ? " (PAL)" :
|
|
mode == Nes::Nsf::TUNE_MODE_NTSC ? " (NTSC)" :
|
|
" (NTSC/PAL)"
|
|
);
|
|
}
|
|
};
|
|
|
|
text.Clear();
|
|
|
|
if (cstring name = Info::GoodName( emulator.GetName() ))
|
|
text.Import( name );
|
|
else
|
|
text = "noname";
|
|
|
|
text << Info::GetMode( emulator.GetMode() );
|
|
|
|
cstring const artist = Info::GoodName( emulator.GetArtist() );
|
|
cstring const copyright = Info::GoodName( emulator.GetCopyright() );
|
|
|
|
if (artist)
|
|
(text << "\r\n").Import( artist );
|
|
|
|
if (copyright)
|
|
(text << "\r\n").Import( copyright );
|
|
|
|
if (const uint chips = emulator.GetChips())
|
|
{
|
|
text << (copyright || artist ? ", " : "\r\n");
|
|
|
|
if ( chips & Nes::Nsf::CHIP_MMC5 ) text << "MMC5 ";
|
|
if ( chips & Nes::Nsf::CHIP_FDS ) text << "FDS ";
|
|
if ( chips & Nes::Nsf::CHIP_VRC6 ) text << "VRC6 ";
|
|
if ( chips & Nes::Nsf::CHIP_VRC7 ) text << "VRC7 ";
|
|
if ( chips & Nes::Nsf::CHIP_N163 ) text << "N163 ";
|
|
if ( chips & Nes::Nsf::CHIP_S5B ) text << "Sunsoft5B ";
|
|
|
|
text << ((chips & (chips-1)) ? "chips" : "chip");
|
|
}
|
|
|
|
text << "\r\nSong: ";
|
|
songTextOffset = text.Length();
|
|
|
|
Update( emulator );
|
|
}
|
|
|
|
void Video::Nsf::Update(const Nes::Nsf emulator)
|
|
{
|
|
text.ShrinkTo( songTextOffset );
|
|
text << (emulator.GetCurrentSong() + 1) << '/' << emulator.GetNumSongs();
|
|
}
|
|
|
|
Video::Video
|
|
(
|
|
Window::Custom& w,
|
|
Window::Menu& m,
|
|
Emulator& e,
|
|
const Paths& p,
|
|
const Configuration& cfg
|
|
)
|
|
:
|
|
Manager ( e, m, this, &Video::OnEmuEvent, &Video::OnAppEvent ),
|
|
window ( w ),
|
|
statusBar ( w, STATUSBAR_WIDTH ),
|
|
direct2d ( w ),
|
|
dialog ( new Window::Video( e, direct2d.GetAdapters(), p, cfg ) ),
|
|
sizingMoving ( false ),
|
|
paths ( p ),
|
|
childWindowSwitchCount ( Application::Instance::NumChildWindows() + 1 )
|
|
{
|
|
static const Window::MsgHandler::Entry<Video> messages[] =
|
|
{
|
|
{ WM_PAINT, &Video::OnPaint },
|
|
{ WM_NCPAINT, &Video::OnNcPaint },
|
|
{ WM_ERASEBKGND, &Video::OnEraseBkGnd },
|
|
{ WM_DISPLAYCHANGE, &Video::OnDisplayChange }
|
|
};
|
|
|
|
static const Window::MsgHandler::HookEntry<Video> hooks[] =
|
|
{
|
|
{ WM_ENTERSIZEMOVE, &Video::OnEnterSizeMove },
|
|
{ WM_EXITSIZEMOVE, &Video::OnExitSizeMove }
|
|
};
|
|
|
|
window.Messages().Add( this, messages, hooks );
|
|
|
|
static const Window::Menu::CmdHandler::Entry<Video> commands[] =
|
|
{
|
|
{ IDM_OPTIONS_VIDEO, &Video::OnCmdOptionsVideo },
|
|
{ IDM_MACHINE_OPTIONS_UNLIMITEDSPRITES, &Video::OnCmdMachineUnlimitedSprites },
|
|
{ IDM_FILE_SAVE_SCREENSHOT, &Video::OnCmdFileScreenShot },
|
|
{ IDM_VIEW_WINDOWSIZE_1X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_2X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_3X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_4X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_5X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_6X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_7X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_8X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_9X, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_MAX, &Video::OnCmdViewScreenSize },
|
|
{ IDM_VIEW_WINDOWSIZE_TVASPECT, &Video::OnCmdViewTvAspect },
|
|
{ IDM_VIEW_STATUSBAR, &Video::OnCmdViewStatusBar },
|
|
{ IDM_VIEW_FPS, &Video::OnCmdViewFps }
|
|
};
|
|
|
|
menu.Commands().Add( this, commands );
|
|
|
|
static const Window::Menu::PopupHandler::Entry<Video> popups[] =
|
|
{
|
|
{ Window::Menu::PopupHandler::Pos<IDM_POS_VIEW,IDM_POS_VIEW_SCREENSIZE>::ID, &Video::OnMenuScreenSizes },
|
|
{ Window::Menu::PopupHandler::Pos<IDM_POS_MACHINE,IDM_POS_MACHINE_OPTIONS>::ID, &Video::OnMenuUnlimSprites }
|
|
};
|
|
|
|
menu.Popups().Add( this, popups );
|
|
|
|
Io::Screen::SetCallback( this, &Video::OnScreenText );
|
|
|
|
Nes::Video::Output::lockCallback.Set( &Callbacks::ScreenLock, &direct2d );
|
|
Nes::Video::Output::unlockCallback.Set( &Callbacks::ScreenUnlock, &direct2d );
|
|
|
|
direct2d.EnableAutoFrequency( dialog->UseAutoFrequency() );
|
|
direct2d.SelectAdapter( dialog->GetAdapter() );
|
|
|
|
menu[IDM_FILE_SAVE_SCREENSHOT].Disable();
|
|
|
|
if (cfg["machine"]["no-sprite-limit"].Yes())
|
|
Nes::Video(emulator).EnableUnlimSprites( true );
|
|
|
|
{
|
|
Configuration::ConstSection view( cfg["view"] );
|
|
|
|
if (view["show"]["status-bar"].No())
|
|
{
|
|
menu[IDM_VIEW_FPS].Disable();
|
|
}
|
|
else
|
|
{
|
|
menu[IDM_VIEW_STATUSBAR].Check();
|
|
statusBar.Enable();
|
|
}
|
|
|
|
if (view["show"]["fps"].Yes())
|
|
menu[IDM_VIEW_FPS].Check();
|
|
|
|
const uint size = view["size"]["window"].Int();
|
|
ResetScreenRect( size >= 2 && size <= 9 ? size-1 : 0 );
|
|
}
|
|
|
|
menu[IDM_VIEW_WINDOWSIZE_TVASPECT].Check( dialog->TvAspect() );
|
|
|
|
UpdateMenuScreenSizes( GetDisplayMode() );
|
|
}
|
|
|
|
Video::~Video()
|
|
{
|
|
Io::Screen::UnsetCallback();
|
|
|
|
Nes::Video::Output::lockCallback.Unset();
|
|
Nes::Video::Output::unlockCallback.Unset();
|
|
|
|
window.Messages().RemoveAll( this );
|
|
window.StopTimer( this, &Video::OnTimerText );
|
|
}
|
|
|
|
void Video::Save(Configuration& cfg,const Rect& client) const
|
|
{
|
|
cfg[ "machine" ][ "no-sprite-limit" ].YesNo() = Nes::Video(emulator).AreUnlimSpritesEnabled();
|
|
|
|
{
|
|
Configuration::Section view( cfg["view"] );
|
|
|
|
const uint size = Point(dialog->GetNesRect()).ScaleToFit( client, Point::SCALE_NEAREST );
|
|
|
|
view[ "size" ][ "window" ].Int() = (size <= 8 ? size+1 : 1);
|
|
view[ "show" ][ "status-bar" ].YesNo() = menu[ IDM_VIEW_STATUSBAR ].Checked();
|
|
view[ "show" ][ "fps" ].YesNo() = menu[ IDM_VIEW_FPS ].Checked();
|
|
}
|
|
|
|
dialog->Save( cfg );
|
|
}
|
|
|
|
Video::Point Video::GetDisplayMode() const
|
|
{
|
|
return Fullscreen() ? direct2d.GetFullscreenDisplayMode() : Point( ::GetSystemMetrics( SM_CXSCREEN ), ::GetSystemMetrics( SM_CYSCREEN ) );
|
|
}
|
|
|
|
uint Video::GetMaxMessageLength() const
|
|
{
|
|
return Fullscreen() ? direct2d.GetMaxMessageLength() : statusBar.Enabled() ? statusBar.GetMaxMessageLength() : 0;
|
|
}
|
|
|
|
/**
|
|
* Is called when the Options Video dialog has been closed. Updates the window according to the video settings. For example
|
|
* changing video adapter, resolution etc.
|
|
*/
|
|
void Video::OnCmdOptionsVideo(uint)
|
|
{
|
|
const Rect oldRect( dialog->GetNesRect() );
|
|
|
|
dialog->Open();
|
|
|
|
direct2d.EnableAutoFrequency( dialog->UseAutoFrequency() );
|
|
direct2d.SelectAdapter( dialog->GetAdapter() );
|
|
|
|
if (Windowed() || !SwitchFullscreen( dialog->GetMode() ))
|
|
{
|
|
if (oldRect != dialog->GetNesRect())
|
|
{
|
|
UpdateMenuScreenSizes( GetDisplayMode() );
|
|
|
|
if (Windowed() && window.Restored())
|
|
ResetScreenRect( CalculateWindowScale() );
|
|
}
|
|
|
|
window.Redraw();
|
|
}
|
|
}
|
|
|
|
void Video::OnCmdViewScreenSize(uint id)
|
|
{
|
|
ResetScreenRect( id == IDM_VIEW_WINDOWSIZE_MAX ? Window::Video::SCREEN_STRETCHED : id - IDM_VIEW_WINDOWSIZE_1X );
|
|
Resume();
|
|
}
|
|
|
|
void Video::OnCmdViewTvAspect(uint)
|
|
{
|
|
menu[IDM_VIEW_WINDOWSIZE_TVASPECT].Check( dialog->ToggleTvAspect() );
|
|
|
|
if (Windowed())
|
|
window.PostCommand( IDM_VIEW_WINDOWSIZE_1X + CalculateWindowScale() );
|
|
else
|
|
window.Redraw();
|
|
|
|
Resume();
|
|
}
|
|
|
|
void Video::OnCmdViewStatusBar(uint)
|
|
{
|
|
NST_VERIFY( Windowed() );
|
|
|
|
if (Windowed())
|
|
{
|
|
const bool enable = menu[IDM_VIEW_STATUSBAR].ToggleCheck();
|
|
menu[IDM_VIEW_FPS].Enable( enable );
|
|
|
|
Point size;
|
|
|
|
if (enable)
|
|
{
|
|
statusBar.Enable( true, false );
|
|
size.y = statusBar.Height();
|
|
}
|
|
else
|
|
{
|
|
size.y = statusBar.Height();
|
|
statusBar.Disable();
|
|
}
|
|
|
|
if (window.Restored())
|
|
{
|
|
if (enable)
|
|
window.Size() += size;
|
|
else
|
|
window.Size() -= size;
|
|
}
|
|
else if (!enable)
|
|
{
|
|
window.Redraw();
|
|
}
|
|
|
|
if (enable)
|
|
statusBar.Show();
|
|
|
|
if (emulator.Running() && emulator.IsGame() && menu[IDM_VIEW_FPS].Checked())
|
|
ToggleFps( enable );
|
|
}
|
|
}
|
|
|
|
void Video::OnCmdViewFps(uint)
|
|
{
|
|
NST_VERIFY( Fullscreen() || statusBar.Enabled() );
|
|
|
|
if (Fullscreen() || statusBar.Enabled())
|
|
{
|
|
const bool enable = menu[IDM_VIEW_FPS].ToggleCheck();
|
|
|
|
if (emulator.Running() && emulator.IsGame())
|
|
ToggleFps( enable );
|
|
}
|
|
}
|
|
|
|
void Video::OnCmdMachineUnlimitedSprites(uint)
|
|
{
|
|
const bool enable = !Nes::Video(emulator).AreUnlimSpritesEnabled();
|
|
|
|
if (NES_SUCCEEDED(Nes::Video(emulator).EnableUnlimSprites( enable )))
|
|
Io::Screen() << Resource::String(enable ? IDS_SCREEN_NOSPRITELIMIT_ON : IDS_SCREEN_NOSPRITELIMIT_OFF );
|
|
}
|
|
|
|
void Video::OnCmdFileScreenShot(uint)
|
|
{
|
|
if (emulator.IsGameOn())
|
|
{
|
|
const Path path( paths.GetScreenShotPath() );
|
|
const DirectX::Direct2D::ScreenShotResult result = direct2d.SaveScreenShot( path.Ptr(), path.Extension().Id() );
|
|
|
|
if (result == DirectX::Direct2D::SCREENSHOT_OK)
|
|
{
|
|
const uint length = GetMaxMessageLength();
|
|
|
|
if (length > 22)
|
|
Io::Screen() << Resource::String(IDS_SCREEN_SCREENSHOT_SAVED_TO).Invoke( Path::Compact( path, length - 20 ) );
|
|
}
|
|
else
|
|
{
|
|
const uint ids = (DirectX::Direct2D::SCREENSHOT_UNSUPPORTED ? IDS_SCREENSHOT_UNSUPPORTED_FORMAT : IDS_SCREENSHOT_SAVE_FAILED);
|
|
|
|
if (emulator.NetPlayers())
|
|
Io::Screen() << Resource::String( ids );
|
|
else
|
|
Window::User::Fail( ids );
|
|
}
|
|
}
|
|
}
|
|
|
|
uint Video::CalculateWindowScale(const Rect& rect) const
|
|
{
|
|
return Point(rect.Size()).ScaleToFit( window.PictureCoordinates(), Point::SCALE_NEAREST );
|
|
}
|
|
|
|
uint Video::CalculateWindowScale() const
|
|
{
|
|
return CalculateWindowScale( dialog->GetNesRect() );
|
|
}
|
|
|
|
uint Video::CalculateFullscreenScale() const
|
|
{
|
|
const Point screen( GetDisplayMode() );
|
|
return Point(dialog->GetNesRect()).ScaleToFit( screen + (screen / SCALE_TOLERANCE), Point::SCALE_BELOW, dialog->GetFullscreenScale() );
|
|
}
|
|
|
|
void Video::OnMenuScreenSizes(const Window::Menu::PopupHandler::Param& param)
|
|
{
|
|
uint scale;
|
|
bool check;
|
|
|
|
if (param.show)
|
|
{
|
|
if (Fullscreen())
|
|
{
|
|
if (dialog->GetFullscreenScale() == Window::Video::SCREEN_STRETCHED)
|
|
scale = Window::Video::SCREEN_STRETCHED;
|
|
else
|
|
scale = CalculateFullscreenScale();
|
|
}
|
|
else
|
|
{
|
|
if (window.Maximized())
|
|
{
|
|
scale = Window::Video::SCREEN_STRETCHED;
|
|
}
|
|
else if (WindowMatched())
|
|
{
|
|
scale = CalculateWindowScale();
|
|
}
|
|
else
|
|
{
|
|
scale = 0xBEDBABE;
|
|
}
|
|
}
|
|
|
|
if (scale == Window::Video::SCREEN_STRETCHED)
|
|
{
|
|
scale = IDM_VIEW_WINDOWSIZE_MAX;
|
|
check = true;
|
|
}
|
|
else if (scale < IDM_VIEW_WINDOWSIZE_MAX-IDM_VIEW_WINDOWSIZE_1X)
|
|
{
|
|
scale += IDM_VIEW_WINDOWSIZE_1X;
|
|
check = true;
|
|
}
|
|
else
|
|
{
|
|
scale = IDM_VIEW_WINDOWSIZE_1X;
|
|
check = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
scale = IDM_VIEW_WINDOWSIZE_1X;
|
|
check = false;
|
|
}
|
|
|
|
param.menu[scale].Check( IDM_VIEW_WINDOWSIZE_1X, IDM_VIEW_WINDOWSIZE_MAX, check );
|
|
}
|
|
|
|
void Video::OnMenuUnlimSprites(const Window::Menu::PopupHandler::Param& param)
|
|
{
|
|
param.menu[IDM_MACHINE_OPTIONS_UNLIMITEDSPRITES].Check( param.show && Nes::Video(emulator).AreUnlimSpritesEnabled() );
|
|
}
|
|
|
|
bool Video::WindowMatched(const Rect& nesRect) const
|
|
{
|
|
const Point output( window.PictureCoordinates() );
|
|
const Point input( nesRect );
|
|
|
|
return
|
|
(
|
|
output.x && (output.x % input.x) == 0 &&
|
|
output.y && (output.y % input.y) == 0
|
|
);
|
|
}
|
|
|
|
bool Video::WindowMatched() const
|
|
{
|
|
return WindowMatched( dialog->GetNesRect() );
|
|
}
|
|
|
|
void Video::ResetScreenRect(const uint scale)
|
|
{
|
|
if (Fullscreen())
|
|
{
|
|
if (dialog->GetFullscreenScale() != scale)
|
|
{
|
|
dialog->SetFullscreenScale( scale );
|
|
window.Redraw();
|
|
}
|
|
}
|
|
else if (scale == Window::Video::SCREEN_STRETCHED)
|
|
{
|
|
window.Maximize();
|
|
}
|
|
else
|
|
{
|
|
if (!window.Restored())
|
|
window.Restore();
|
|
|
|
Point size( dialog->GetNesRect() );
|
|
size.ScaleToFit( GetDisplayMode(), Point::SCALE_BELOW, scale );
|
|
|
|
window.Size() = size + window.NonClientCoordinates();
|
|
|
|
if (window.PictureCoordinates() != size)
|
|
window.Size() = size + window.NonClientCoordinates();
|
|
}
|
|
}
|
|
|
|
void Video::UpdateMenuScreenSizes(Point screen) const
|
|
{
|
|
for (uint i=IDM_VIEW_WINDOWSIZE_1X; i < IDM_VIEW_WINDOWSIZE_MAX; ++i)
|
|
menu[i].Remove();
|
|
|
|
const Point original( dialog->GetNesRect() );
|
|
Point nes( original );
|
|
|
|
if (Fullscreen())
|
|
screen += screen / SCALE_TOLERANCE;
|
|
|
|
for (uint i=0; i < (IDM_VIEW_WINDOWSIZE_MAX-IDM_VIEW_WINDOWSIZE_1X); ++i)
|
|
{
|
|
menu.Insert( menu[IDM_VIEW_WINDOWSIZE_MAX], IDM_VIEW_WINDOWSIZE_1X + i, Resource::String(IDS_MENU_X).Invoke( wchar_t('1'+i) ) );
|
|
nes = original * (i+2);
|
|
|
|
if (nes.x > screen.x || nes.y > screen.y)
|
|
break;
|
|
}
|
|
}
|
|
|
|
ibool Video::OnDisplayChange(Window::Param& param)
|
|
{
|
|
UpdateMenuScreenSizes( Point(LOWORD(param.lParam),HIWORD(param.lParam)) );
|
|
return true;
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("t", on)
|
|
#endif
|
|
|
|
bool Video::MustClearFrameScreen() const
|
|
{
|
|
return Fullscreen() && (dialog->GetFullscreenScale() != Window::Video::SCREEN_STRETCHED || dialog->TvAspect());
|
|
}
|
|
|
|
ibool Video::OnPaint(Window::Param&)
|
|
{
|
|
if (emulator.IsOn())
|
|
{
|
|
if (!sizingMoving)
|
|
UpdateScreen();
|
|
|
|
if (emulator.IsGame())
|
|
{
|
|
if (MustClearFrameScreen())
|
|
ClearScreen();
|
|
|
|
if (!sizingMoving && emulator.Running())
|
|
{
|
|
window.Redraw( false );
|
|
return true;
|
|
}
|
|
|
|
Nes::Video( emulator ).Blit( nesOutput );
|
|
}
|
|
else
|
|
{
|
|
ClearScreen();
|
|
direct2d.RenderScreen( DirectX::Direct2D::RENDER_MSG|DirectX::Direct2D::RENDER_NFO );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ClearScreen();
|
|
direct2d.RenderScreen( DirectX::Direct2D::RENDER_MSG );
|
|
}
|
|
|
|
window.Redraw( false );
|
|
PresentScreen();
|
|
|
|
return true;
|
|
}
|
|
|
|
void Video::UpdateDialogBoxMode()
|
|
{
|
|
direct2d.EnableDialogBoxMode
|
|
(
|
|
!emulator.IsOn() ||
|
|
emulator.IsNsf() ||
|
|
menu.Visible()
|
|
);
|
|
}
|
|
|
|
ibool Video::OnNcPaint(Window::Param&)
|
|
{
|
|
UpdateDialogBoxMode();
|
|
return false;
|
|
}
|
|
|
|
ibool Video::OnEraseBkGnd(Window::Param& param)
|
|
{
|
|
param.lResult = true;
|
|
return true;
|
|
}
|
|
|
|
void Video::OnEnterSizeMove(Window::Param&)
|
|
{
|
|
sizingMoving = Windowed();
|
|
}
|
|
|
|
void Video::OnExitSizeMove(Window::Param&)
|
|
{
|
|
sizingMoving = false;
|
|
window.Redraw();
|
|
}
|
|
|
|
uint Video::OnTimerText()
|
|
{
|
|
if (Fullscreen())
|
|
{
|
|
direct2d.ClearMsg();
|
|
window.Redraw();
|
|
}
|
|
else if (statusBar.Enabled())
|
|
{
|
|
statusBar.Text(Window::StatusBar::FIRST_FIELD).Clear();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint Video::OnTimerFps()
|
|
{
|
|
if (emulator.IsGameOn())
|
|
{
|
|
class FpsString : HeapString
|
|
{
|
|
const uint offset;
|
|
|
|
public:
|
|
|
|
FpsString()
|
|
:
|
|
HeapString (Resource::String(IDS_TEXT_FPS) << ": xxx.x"),
|
|
offset (Length() - 5)
|
|
{
|
|
}
|
|
|
|
void Update(uint fps)
|
|
{
|
|
const char dec[] = {'.',fps % (Fps::UPDATE_INTERVAL/1000) ? '5' : '0','\0'};
|
|
fps /= (Fps::UPDATE_INTERVAL/1000);
|
|
|
|
(*this)(offset) = NST_MIN(fps,999);
|
|
Append( dec, 2 );
|
|
}
|
|
|
|
GenericString Number() const
|
|
{
|
|
return (*this)(offset);
|
|
}
|
|
|
|
wcstring Full() const
|
|
{
|
|
return Ptr();
|
|
}
|
|
};
|
|
|
|
static FpsString fpsString;
|
|
|
|
const uint prev = fps.frame;
|
|
fps.frame = emulator.Frame();
|
|
|
|
fpsString.Update( fps.frame - prev );
|
|
|
|
if (Fullscreen())
|
|
{
|
|
direct2d.DrawFps( fpsString.Number() );
|
|
return true;
|
|
}
|
|
else if (statusBar.Enabled())
|
|
{
|
|
statusBar.Text(Window::StatusBar::SECOND_FIELD) << fpsString.Full();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Video::OnScreenText(const GenericString& text,const uint time)
|
|
{
|
|
if (Fullscreen())
|
|
{
|
|
direct2d.DrawMsg( text );
|
|
window.Redraw();
|
|
}
|
|
else if (statusBar.Enabled())
|
|
{
|
|
statusBar.Text(Window::StatusBar::FIRST_FIELD) << text.Ptr();
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
window.StartTimer( this, &Video::OnTimerText, time ? time : SCREEN_TEXT_DURATION );
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
void Video::RepairScreen()
|
|
{
|
|
if (direct2d.Repair())
|
|
{
|
|
if (emulator.IsOn())
|
|
UpdateScreen();
|
|
|
|
direct2d.ClearScreen();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Switches to a fullscreen mode.
|
|
*
|
|
* @param mode The video mode to set to fullscreen.
|
|
* @return True if switched to fullscreen. False if the mode is already set in fullscreen.
|
|
*/
|
|
bool Video::SwitchFullscreen(Mode mode)
|
|
{
|
|
const bool prevFullscreen = Fullscreen();
|
|
const bool toggleMenu = menu.Visible() && direct2d.CanSwitchFullscreen( mode );
|
|
|
|
if (toggleMenu)
|
|
menu.Hide();
|
|
|
|
const bool switched = direct2d.SwitchFullscreen( mode );
|
|
|
|
if (switched && prevFullscreen && dialog->GetFullscreenScale() != Window::Video::SCREEN_STRETCHED)
|
|
dialog->SetFullscreenScale( Window::Video::SCREEN_MATCHED );
|
|
|
|
if (toggleMenu)
|
|
menu.Show();
|
|
|
|
window.Redraw();
|
|
|
|
return switched;
|
|
}
|
|
|
|
/**
|
|
* Switches between window and full screen mode.
|
|
*/
|
|
void Video::SwitchScreen()
|
|
{
|
|
menu[IDM_VIEW_STATUSBAR].Enable( Fullscreen() );
|
|
|
|
if (Windowed())
|
|
{
|
|
if (statusBar.Enabled())
|
|
statusBar.Disable();
|
|
else
|
|
menu[IDM_VIEW_FPS].Enable();
|
|
|
|
SwitchFullscreen( dialog->GetMode() );
|
|
}
|
|
else
|
|
{
|
|
direct2d.SwitchWindowed();
|
|
|
|
if (menu[IDM_VIEW_STATUSBAR].Checked())
|
|
statusBar.Enable();
|
|
else
|
|
menu[IDM_VIEW_FPS].Disable();
|
|
}
|
|
}
|
|
|
|
void Video::OnAppEvent(Instance::Event event,const void* param)
|
|
{
|
|
if (Fullscreen())
|
|
{
|
|
switch (event)
|
|
{
|
|
case Instance::EVENT_WINDOW_CREATE:
|
|
|
|
if (Instance::NumChildWindows() == childWindowSwitchCount)
|
|
{
|
|
const Instance::Events::WindowCreateParam& info =
|
|
(
|
|
*static_cast<const Instance::Events::WindowCreateParam*>(param)
|
|
);
|
|
|
|
const Point mode( GetDisplayMode() );
|
|
|
|
if
|
|
(
|
|
(mode.x < Window::Video::MIN_DIALOG_WIDTH || mode.y < Window::Video::MIN_DIALOG_HEIGHT) &&
|
|
(mode.x < int(info.x) || mode.y < int(info.y))
|
|
)
|
|
SwitchFullscreen( dialog->GetDialogMode() );
|
|
}
|
|
|
|
menu.Show();
|
|
break;
|
|
|
|
case Instance::EVENT_WINDOW_DESTROY:
|
|
|
|
if (Instance::NumChildWindows() == childWindowSwitchCount)
|
|
{
|
|
SwitchFullscreen( dialog->GetMode() ); //switches to fullscreen if necessary
|
|
UpdateDialogBoxMode();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("t", on)
|
|
#endif
|
|
|
|
const Video::Rect& Video::GetInputRect() const
|
|
{
|
|
return dialog->GetInputRect();
|
|
}
|
|
|
|
void Video::UpdateScreen()
|
|
{
|
|
NST_ASSERT( emulator.IsOn() );
|
|
|
|
if (emulator.IsGame())
|
|
{
|
|
Rect picture;
|
|
|
|
if (Windowed())
|
|
{
|
|
picture = window.PictureCoordinates();
|
|
}
|
|
else
|
|
{
|
|
const Point screen( GetDisplayMode() );
|
|
|
|
if (dialog->GetFullscreenScale() == Window::Video::SCREEN_STRETCHED && !dialog->TvAspect())
|
|
{
|
|
picture = screen;
|
|
}
|
|
else
|
|
{
|
|
Point nesPoint( dialog->GetNesRect() );
|
|
nesPoint.ScaleToFit( screen + (screen / SCALE_TOLERANCE), Point::SCALE_BELOW, dialog->GetFullscreenScale() );
|
|
picture = nesPoint;
|
|
|
|
if (dialog->GetFullscreenScale() == Window::Video::SCREEN_STRETCHED)
|
|
{
|
|
picture.right += screen.y - picture.bottom;
|
|
picture.top = 0;
|
|
picture.bottom = screen.y;
|
|
}
|
|
|
|
picture.Center() = screen.Center();
|
|
|
|
if (nesPoint.x > screen.x + (screen.x / SCALE_TOLERANCE))
|
|
{
|
|
picture.left = 0;
|
|
picture.right = screen.x;
|
|
}
|
|
|
|
if (nesPoint.y > screen.y + (screen.y / SCALE_TOLERANCE))
|
|
{
|
|
picture.top = 0;
|
|
picture.bottom = screen.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
Nes::Video::RenderState renderState;
|
|
|
|
renderState.bits.count = direct2d.GetBitsPerPixel();
|
|
NST_ASSERT( renderState.bits.count == 16 || renderState.bits.count == 32 );
|
|
|
|
direct2d.GetBitMask( renderState.bits.mask.r, renderState.bits.mask.g, renderState.bits.mask.b );
|
|
NST_ASSERT( renderState.bits.mask.r && renderState.bits.mask.g && renderState.bits.mask.b );
|
|
|
|
const Rect nesScreen( dialog->GetRenderState( renderState, picture.Size() ) );
|
|
NST_ASSERT( direct2d.GetAdapter().maxScreenSize.x >= renderState.width && direct2d.GetAdapter().maxScreenSize.y >= renderState.height );
|
|
|
|
Nes::Video(emulator).SetRenderState( renderState );
|
|
|
|
if (Windowed())
|
|
{
|
|
direct2d.UpdateWindowView
|
|
(
|
|
Point(renderState.width,renderState.height),
|
|
nesScreen,
|
|
dialog->GetScanlines(),
|
|
dialog->GetScreenCurvature(),
|
|
dialog->GetTextureFilter(),
|
|
dialog->PutTextureInVideoMemory()
|
|
);
|
|
}
|
|
else
|
|
{
|
|
direct2d.UpdateFullscreenView
|
|
(
|
|
picture,
|
|
Point(renderState.width,renderState.height),
|
|
nesScreen,
|
|
dialog->GetScanlines(),
|
|
dialog->GetScreenCurvature(),
|
|
dialog->GetTextureFilter(),
|
|
dialog->PutTextureInVideoMemory()
|
|
);
|
|
}
|
|
|
|
UpdateFieldMergingState();
|
|
}
|
|
else if (Windowed())
|
|
{
|
|
direct2d.UpdateWindowView();
|
|
}
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
void Video::UpdateFieldMergingState() const
|
|
{
|
|
Nes::Video(emulator).EnableFieldMerging
|
|
(
|
|
dialog->EnableFieldMerging() ||
|
|
(dialog->UseAutoFieldMerging() && !direct2d.SmoothFrameRate())
|
|
);
|
|
}
|
|
|
|
void Video::ToggleFps(const bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
NST_ASSERT( Fullscreen() || statusBar.Enabled() );
|
|
|
|
fps.frame = emulator.Frame();
|
|
|
|
if (statusBar.Enabled())
|
|
statusBar.Text(Window::StatusBar::SECOND_FIELD) << (Resource::String(IDS_TEXT_FPS) << ": ").Ptr();
|
|
else
|
|
direct2d.DrawFps( L"0.0" );
|
|
|
|
window.StartTimer( this, &Video::OnTimerFps, Fps::UPDATE_INTERVAL );
|
|
}
|
|
else
|
|
{
|
|
fps.frame = 0;
|
|
|
|
if (window.StopTimer( this, &Video::OnTimerFps ))
|
|
{
|
|
if (statusBar.Enabled())
|
|
statusBar.Text(Window::StatusBar::SECOND_FIELD).Clear();
|
|
else
|
|
direct2d.ClearFps();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Video::StartEmulation()
|
|
{
|
|
if (emulator.IsGame() && menu[IDM_VIEW_FPS].Checked() && (Fullscreen() || statusBar.Enabled()))
|
|
ToggleFps( true );
|
|
}
|
|
|
|
void Video::StopEmulation()
|
|
{
|
|
ToggleFps( false );
|
|
}
|
|
|
|
void Video::OnEmuEvent(const Emulator::Event event,const Emulator::Data data)
|
|
{
|
|
switch (event)
|
|
{
|
|
case Emulator::EVENT_BASE_SPEED:
|
|
|
|
direct2d.UpdateFrameRate
|
|
(
|
|
emulator.GetBaseSpeed(),
|
|
emulator.SyncFrameRate(),
|
|
emulator.UseTripleBuffering()
|
|
);
|
|
|
|
UpdateFieldMergingState();
|
|
break;
|
|
|
|
case Emulator::EVENT_NSF_SELECT:
|
|
|
|
nsf.Update( Nes::Nsf(emulator) );
|
|
direct2d.DrawNfo( nsf.text );
|
|
window.Redraw();
|
|
break;
|
|
|
|
case Emulator::EVENT_POWER_ON:
|
|
|
|
UpdateScreen();
|
|
|
|
case Emulator::EVENT_POWER_OFF:
|
|
|
|
menu[IDM_FILE_SAVE_SCREENSHOT].Enable( event == Emulator::EVENT_POWER_ON && emulator.IsGame() );
|
|
window.Redraw();
|
|
break;
|
|
|
|
case Emulator::EVENT_MODE_NTSC:
|
|
case Emulator::EVENT_MODE_PAL:
|
|
{
|
|
const Rect& currentRect = dialog->GetNesRect
|
|
(
|
|
event == Emulator::EVENT_MODE_NTSC ? Nes::Machine::PAL :
|
|
Nes::Machine::NTSC
|
|
);
|
|
|
|
if (Windowed() && WindowMatched( currentRect ))
|
|
ResetScreenRect( CalculateWindowScale( currentRect ) );
|
|
else
|
|
window.Redraw();
|
|
|
|
break;
|
|
}
|
|
|
|
case Emulator::EVENT_LOAD:
|
|
|
|
if (emulator.IsGame())
|
|
{
|
|
dialog->UpdateAutoModes();
|
|
}
|
|
else if (emulator.IsNsf())
|
|
{
|
|
nsf.Load( Nes::Nsf(emulator) );
|
|
direct2d.DrawNfo( nsf.text );
|
|
}
|
|
break;
|
|
|
|
case Emulator::EVENT_NETPLAY_MODE:
|
|
|
|
menu[IDM_OPTIONS_VIDEO].Enable( !data );
|
|
menu[IDM_MACHINE_OPTIONS_UNLIMITEDSPRITES].Enable( !data );
|
|
menu[IDM_POS_MACHINE][IDM_POS_MACHINE_OPTIONS].Enable( !data );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|