Added support for VS DualSystem (WIP - dual video/audio, input, save states, movies & netplay working)

This commit is contained in:
Sour 2018-07-02 21:32:59 -04:00
parent 1489868a3f
commit 47fbe93b62
23 changed files with 409 additions and 85 deletions

View file

@ -643,7 +643,7 @@ void BaseMapper::ApplyCheats()
void BaseMapper::GetMemoryRanges(MemoryRanges &ranges) void BaseMapper::GetMemoryRanges(MemoryRanges &ranges)
{ {
if(_gameSystem == GameSystem::VsUniSystem) { if(_gameSystem == GameSystem::VsUniSystem || _gameSystem == GameSystem::VsDualSystem) {
ranges.AddHandler(MemoryOperation::Read, 0x6000, 0xFFFF); ranges.AddHandler(MemoryOperation::Read, 0x6000, 0xFFFF);
ranges.AddHandler(MemoryOperation::Write, 0x6000, 0xFFFF); ranges.AddHandler(MemoryOperation::Write, 0x6000, 0xFFFF);
} else { } else {

View file

@ -9,6 +9,11 @@
BaseRenderer::BaseRenderer(shared_ptr<Console> console) BaseRenderer::BaseRenderer(shared_ptr<Console> console)
{ {
_console = console; _console = console;
if(console->IsMaster()) {
//Only display messages on the master CPU's screen
MessageManager::RegisterMessageManager(this);
}
} }
void BaseRenderer::DisplayMessage(string title, string message) void BaseRenderer::DisplayMessage(string title, string message)

View file

@ -45,36 +45,55 @@
#include "DebugHud.h" #include "DebugHud.h"
#include "NotificationManager.h" #include "NotificationManager.h"
Console::Console() Console::Console(shared_ptr<Console> master)
{ {
_master = master;
_model = NesModel::NTSC; _model = NesModel::NTSC;
if(_master) {
_master->_notificationManager->SendNotification(ConsoleNotificationType::VsDualSystemStarted);
}
} }
Console::~Console() Console::~Console()
{ {
MovieManager::Stop(); MovieManager::Stop();
GetSoundMixer()->StopRecording();
} }
void Console::Init() void Console::Init()
{ {
_notificationManager.reset(new NotificationManager()); _notificationManager.reset(new NotificationManager());
_saveStateManager.reset(new SaveStateManager(shared_from_this()));
_videoRenderer.reset(new VideoRenderer(shared_from_this())); _videoRenderer.reset(new VideoRenderer(shared_from_this()));
_videoDecoder.reset(new VideoDecoder(shared_from_this())); _videoDecoder.reset(new VideoDecoder(shared_from_this()));
_saveStateManager.reset(new SaveStateManager(shared_from_this()));
_cheatManager.reset(new CheatManager(shared_from_this())); _cheatManager.reset(new CheatManager(shared_from_this()));
_debugHud.reset(new DebugHud()); _debugHud.reset(new DebugHud());
_soundMixer.reset(new SoundMixer(shared_from_this())); _soundMixer.reset(new SoundMixer(shared_from_this()));
_soundMixer->SetNesModel(_model);
} }
void Console::Release(bool forShutdown) void Console::Release(bool forShutdown)
{ {
if(forShutdown) { if(forShutdown) {
_saveStateManager.reset(); _videoDecoder->StopThread();
_videoRenderer.reset(); _videoRenderer->StopThread();
_videoDecoder.reset(); _videoDecoder.reset();
_videoRenderer.reset();
_debugHud.reset(); _debugHud.reset();
_saveStateManager.reset();
_cheatManager.reset(); _cheatManager.reset();
_soundMixer.reset();
_notificationManager.reset();
}
if(_master) {
_master->_notificationManager->SendNotification(ConsoleNotificationType::VsDualSystemStopped);
} }
_rewindManager.reset(); _rewindManager.reset();
@ -85,7 +104,13 @@ void Console::Release(bool forShutdown)
_hdAudioDevice.reset(); _hdAudioDevice.reset();
_systemActionManager.reset(); _systemActionManager.reset();
if(_slave) {
_slave->Release(true);
_slave.reset();
}
_master.reset();
_cpu.reset(); _cpu.reset();
_ppu.reset(); _ppu.reset();
_apu.reset(); _apu.reset();
@ -246,20 +271,26 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
//Changed game, stop all recordings //Changed game, stop all recordings
MovieManager::Stop(); MovieManager::Stop();
GetSoundMixer()->StopRecording(); _soundMixer->StopRecording();
StopRecordingHdPack(); StopRecordingHdPack();
} }
#ifndef LIBRETRO
//Don't use auto-save manager for libretro
_autoSaveManager.reset(new AutoSaveManager(shared_from_this()));
#endif
_mapper = mapper; _mapper = mapper;
_memoryManager.reset(new MemoryManager(shared_from_this())); _memoryManager.reset(new MemoryManager(shared_from_this()));
_cpu.reset(new CPU(shared_from_this())); _cpu.reset(new CPU(shared_from_this()));
_apu.reset(new APU(shared_from_this())); _apu.reset(new APU(shared_from_this()));
if(_slave) {
_slave->Release(false);
_slave.reset();
}
if(!_master && _mapper->GetMapperInfo().System == GameSystem::VsDualSystem) {
_slave.reset(new Console(shared_from_this()));
_slave->Init();
_slave->Initialize(romFile, patchFile);
}
GameSystem system = _mapper->GetMapperInfo().System; GameSystem system = _mapper->GetMapperInfo().System;
switch(system) { switch(system) {
@ -269,6 +300,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
break; break;
case GameSystem::VsUniSystem: case GameSystem::VsUniSystem:
case GameSystem::VsDualSystem:
_systemActionManager.reset(new VsSystemActionManager(shared_from_this())); _systemActionManager.reset(new VsSystemActionManager(shared_from_this()));
break; break;
@ -285,7 +317,8 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
//When power cycling, poll counter must be preserved to allow movies to playback properly //When power cycling, poll counter must be preserved to allow movies to playback properly
pollCounter = _controlManager->GetPollCounter(); pollCounter = _controlManager->GetPollCounter();
} }
if(system == GameSystem::VsUniSystem) {
if(system == GameSystem::VsUniSystem || system == GameSystem::VsDualSystem) {
_controlManager.reset(new VsControlManager(shared_from_this(), _systemActionManager, _mapper->GetMapperControlDevice())); _controlManager.reset(new VsControlManager(shared_from_this(), _systemActionManager, _mapper->GetMapperControlDevice()));
} else { } else {
_controlManager.reset(new ControlManager(shared_from_this(), _systemActionManager, _mapper->GetMapperControlDevice())); _controlManager.reset(new ControlManager(shared_from_this(), _systemActionManager, _mapper->GetMapperControlDevice()));
@ -330,6 +363,13 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
ResetComponents(false); ResetComponents(false);
#ifndef LIBRETRO
//Don't use auto-save manager for libretro
//Only enable auto-save for the master console (VS Dualsystem)
if(IsMaster()) {
_autoSaveManager.reset(new AutoSaveManager(shared_from_this()));
}
#endif
_rewindManager.reset(new RewindManager(shared_from_this())); _rewindManager.reset(new RewindManager(shared_from_this()));
_notificationManager->RegisterNotificationListener(_rewindManager); _notificationManager->RegisterNotificationListener(_rewindManager);
@ -337,13 +377,15 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
FolderUtilities::AddKnownGameFolder(romFile.GetFolderPath()); FolderUtilities::AddKnownGameFolder(romFile.GetFolderPath());
string modelName = _model == NesModel::PAL ? "PAL" : (_model == NesModel::Dendy ? "Dendy" : "NTSC"); if(IsMaster()) {
string messageTitle = MessageManager::Localize("GameLoaded") + " (" + modelName + ")"; string modelName = _model == NesModel::PAL ? "PAL" : (_model == NesModel::Dendy ? "Dendy" : "NTSC");
MessageManager::DisplayMessage(messageTitle, FolderUtilities::GetFilename(GetMapperInfo().RomName, false)); string messageTitle = MessageManager::Localize("GameLoaded") + " (" + modelName + ")";
if(EmulationSettings::GetOverclockRate() != 100) { MessageManager::DisplayMessage(messageTitle, FolderUtilities::GetFilename(GetMapperInfo().RomName, false));
MessageManager::DisplayMessage("ClockRate", std::to_string(EmulationSettings::GetOverclockRate()) + "%"); if(EmulationSettings::GetOverclockRate() != 100) {
MessageManager::DisplayMessage("ClockRate", std::to_string(EmulationSettings::GetOverclockRate()) + "%");
}
EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed);
} }
EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed);
Resume(); Resume();
return true; return true;
} }
@ -392,6 +434,24 @@ shared_ptr<NotificationManager> Console::GetNotificationManager()
return _notificationManager; return _notificationManager;
} }
bool Console::IsDualSystem()
{
return _slave != nullptr || _master != nullptr;
}
shared_ptr<Console> Console::GetDualConsole()
{
//When called from the master, returns the slave.
//When called from the slave, returns the master.
//Returns a null pointer when not running a dualsystem game
return _slave ? _slave : _master;
}
bool Console::IsMaster()
{
return !_master;
}
BaseMapper* Console::GetMapper() BaseMapper* Console::GetMapper()
{ {
return _mapper.get(); return _mapper.get();
@ -476,6 +536,11 @@ void Console::Reset(bool softReset)
void Console::ResetComponents(bool softReset) void Console::ResetComponents(bool softReset)
{ {
if(_slave) {
//Always reset/power cycle the slave alongside the master CPU
_slave->ResetComponents(softReset);
}
_soundMixer->StopAudio(true); _soundMixer->StopAudio(true);
_memoryManager->Reset(softReset); _memoryManager->Reset(softReset);
@ -523,15 +588,26 @@ void Console::Pause()
//Make sure debugger resumes if we try to pause the emu, otherwise we will get deadlocked. //Make sure debugger resumes if we try to pause the emu, otherwise we will get deadlocked.
debugger->Suspend(); debugger->Suspend();
} }
_pauseLock.Acquire();
//Spin wait until emu pauses if(_master) {
_runLock.Acquire(); //When trying to pause/resume the slave, we need to pause/resume the master instead
_master->Pause();
} else {
_pauseLock.Acquire();
//Spin wait until emu pauses
_runLock.Acquire();
}
} }
void Console::Resume() void Console::Resume()
{ {
_runLock.Release(); if(_master) {
_pauseLock.Release(); //When trying to pause/resume the slave, we need to pause/resume the master instead
_master->Resume();
} else {
_runLock.Release();
_pauseLock.Release();
}
shared_ptr<Debugger> debugger = _debugger; shared_ptr<Debugger> debugger = _debugger;
if(debugger) { if(debugger) {
@ -567,6 +643,8 @@ void Console::Run()
int timeLagDataIndex = 0; int timeLagDataIndex = 0;
double lastFrameMin = 9999; double lastFrameMin = 9999;
double lastFrameMax = 0; double lastFrameMax = 0;
int32_t cycleGap = 0;
uint32_t currentFrameNumber = 0;
uint32_t lastFrameNumber = -1; uint32_t lastFrameNumber = -1;
@ -590,13 +668,32 @@ void Console::Run()
while(true) { while(true) {
_cpu->Exec(); _cpu->Exec();
uint32_t currentFrameNumber = _ppu->GetFrameCount(); currentFrameNumber = _ppu->GetFrameCount();
if(_slave) {
while(true) {
//Run the slave until it catches up to the master CPU (and take into account the CPU count overflow that occurs every ~20mins)
cycleGap = _cpu->GetCycleCount() - _slave->_cpu->GetCycleCount();
if(cycleGap > 5 || cycleGap < -10000 || currentFrameNumber > _slave->_ppu->GetFrameCount()) {
_slave->_cpu->Exec();
} else {
break;
}
}
}
if(currentFrameNumber != lastFrameNumber) { if(currentFrameNumber != lastFrameNumber) {
_soundMixer->ProcessEndOfFrame(); _soundMixer->ProcessEndOfFrame();
if(_slave) {
_slave->_soundMixer->ProcessEndOfFrame();
}
bool displayDebugInfo = EmulationSettings::CheckFlag(EmulationFlags::DisplayDebugInfo); bool displayDebugInfo = EmulationSettings::CheckFlag(EmulationFlags::DisplayDebugInfo);
if(displayDebugInfo) { if(displayDebugInfo) {
DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData); DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData);
if(_slave) {
_slave->DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData);
}
} }
lastFrameTimer.Reset(); lastFrameTimer.Reset();
@ -623,6 +720,9 @@ void Console::Run()
//Prevent audio from looping endlessly while game is paused //Prevent audio from looping endlessly while game is paused
_soundMixer->StopAudio(); _soundMixer->StopAudio();
if(_slave) {
_slave->_soundMixer->StopAudio();
}
_runLock.Release(); _runLock.Release();
@ -695,7 +795,7 @@ void Console::Run()
_soundMixer->StopAudio(); _soundMixer->StopAudio();
MovieManager::Stop(); MovieManager::Stop();
GetSoundMixer()->StopRecording(); _soundMixer->StopRecording();
PlatformUtilities::EnableScreensaver(); PlatformUtilities::EnableScreensaver();
PlatformUtilities::RestoreTimerResolution(); PlatformUtilities::RestoreTimerResolution();
@ -725,12 +825,22 @@ void Console::Run()
bool Console::IsRunning() bool Console::IsRunning()
{ {
return !_stopLock.IsFree() && _running; if(_master) {
//For slave CPU, return the master's state
return _master->IsRunning();
} else {
return !_stopLock.IsFree() && _running;
}
} }
bool Console::IsPaused() bool Console::IsPaused()
{ {
return _runLock.IsFree() || !_pauseLock.IsFree() || !_running; if(_master) {
//For slave CPU, return the master's state
return _master->IsPaused();
} else {
return _runLock.IsFree() || !_pauseLock.IsFree() || !_running;
}
} }
void Console::UpdateNesModel(bool sendNotification) void Console::UpdateNesModel(bool sendNotification)
@ -801,6 +911,11 @@ void Console::SaveState(ostream &saveStream)
} else { } else {
Snapshotable::WriteEmptyBlock(&saveStream); Snapshotable::WriteEmptyBlock(&saveStream);
} }
if(_slave) {
//For VS Dualsystem, append the 2nd console's savestate
_slave->SaveState(saveStream);
}
} }
} }
@ -823,6 +938,11 @@ void Console::LoadState(istream &loadStream, uint32_t stateVersion)
} else { } else {
Snapshotable::SkipBlock(&loadStream); Snapshotable::SkipBlock(&loadStream);
} }
if(_slave) {
//For VS Dualsystem, the slave console's savestate is appended to the end of the file
_slave->LoadState(loadStream, stateVersion);
}
shared_ptr<Debugger> debugger = _debugger; shared_ptr<Debugger> debugger = _debugger;
if(debugger) { if(debugger) {

View file

@ -50,6 +50,10 @@ private:
shared_ptr<BaseMapper> _mapper; shared_ptr<BaseMapper> _mapper;
shared_ptr<ControlManager> _controlManager; shared_ptr<ControlManager> _controlManager;
shared_ptr<MemoryManager> _memoryManager; shared_ptr<MemoryManager> _memoryManager;
//Used by VS-DualSystem
shared_ptr<Console> _master;
shared_ptr<Console> _slave;
shared_ptr<SystemActionManager> _systemActionManager; shared_ptr<SystemActionManager> _systemActionManager;
@ -87,7 +91,7 @@ private:
void DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, double &lastFrameMin, double &lastFrameMax, double *timeLagData); void DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, double &lastFrameMin, double &lastFrameMax, double *timeLagData);
public: public:
Console(); Console(shared_ptr<Console> master = nullptr);
~Console(); ~Console();
void Init(); void Init();
@ -100,6 +104,10 @@ public:
shared_ptr<SoundMixer> GetSoundMixer(); shared_ptr<SoundMixer> GetSoundMixer();
shared_ptr<NotificationManager> GetNotificationManager(); shared_ptr<NotificationManager> GetNotificationManager();
bool IsDualSystem();
shared_ptr<Console> GetDualConsole();
bool IsMaster();
void ProcessCpuClock(); void ProcessCpuClock();
CPU* GetCpu(); CPU* GetCpu();
PPU* GetPpu(); PPU* GetPpu();

View file

@ -20,25 +20,25 @@ class ControlManager : public Snapshotable, public IMemoryHandler
private: private:
vector<IInputRecorder*> _inputRecorders; vector<IInputRecorder*> _inputRecorders;
vector<IInputProvider*> _inputProviders; vector<IInputProvider*> _inputProviders;
SimpleLock _deviceLock;
//Static so that power cycle does not reset its value //Static so that power cycle does not reset its value
//TODOCONSOLE : PollCounter needs to be kept through power cycle //TODOCONSOLE : PollCounter needs to be kept through power cycle
uint32_t _pollCounter; uint32_t _pollCounter;
vector<shared_ptr<BaseControlDevice>> _controlDevices;
shared_ptr<BaseControlDevice> _systemActionManager;
shared_ptr<BaseControlDevice> _mapperControlDevice; shared_ptr<BaseControlDevice> _mapperControlDevice;
uint32_t _lagCounter = 0; uint32_t _lagCounter = 0;
bool _isLagging = false; bool _isLagging = false;
uint8_t GetOpenBusMask(uint8_t port); uint8_t GetOpenBusMask(uint8_t port);
void RegisterControlDevice(shared_ptr<BaseControlDevice> controlDevice);
protected: protected:
shared_ptr<Console> _console; shared_ptr<Console> _console;
SimpleLock _deviceLock;
vector<shared_ptr<BaseControlDevice>> _controlDevices;
shared_ptr<BaseControlDevice> _systemActionManager;
void RegisterControlDevice(shared_ptr<BaseControlDevice> controlDevice);
virtual void StreamState(bool saving) override; virtual void StreamState(bool saving) override;
virtual ControllerType GetControllerType(uint8_t port); virtual ControllerType GetControllerType(uint8_t port);
@ -47,7 +47,7 @@ public:
ControlManager(shared_ptr<Console> console, shared_ptr<BaseControlDevice> systemActionManager, shared_ptr<BaseControlDevice> mapperControlDevice); ControlManager(shared_ptr<Console> console, shared_ptr<BaseControlDevice> systemActionManager, shared_ptr<BaseControlDevice> mapperControlDevice);
virtual ~ControlManager(); virtual ~ControlManager();
void UpdateControlDevices(); virtual void UpdateControlDevices();
void UpdateInputState(); void UpdateInputState();
uint32_t GetLagCounter(); uint32_t GetLagCounter();

View file

@ -101,6 +101,8 @@ GameSystem GameDatabase::GetGameSystem(string system)
return GameSystem::Famicom; return GameSystem::Famicom;
} else if(system.compare("VsUni") == 0) { } else if(system.compare("VsUni") == 0) {
return GameSystem::VsUniSystem; return GameSystem::VsUniSystem;
} else if(system.compare("VsDual") == 0) {
return GameSystem::VsDualSystem;
} else if(system.compare("Dendy") == 0) { } else if(system.compare("Dendy") == 0) {
return GameSystem::Dendy; return GameSystem::Dendy;
} else if(system.compare("Playchoice") == 0) { } else if(system.compare("Playchoice") == 0) {

View file

@ -23,6 +23,8 @@ enum class ConsoleNotificationType
EmulationStopped = 17, EmulationStopped = 17,
EventViewerDisplayFrame = 18, EventViewerDisplayFrame = 18,
BeforeEmulationStop = 19, BeforeEmulationStop = 19,
VsDualSystemStarted = 20,
VsDualSystemStopped = 21,
}; };
class INotificationListener class INotificationListener

View file

@ -893,7 +893,7 @@ void PPU::ProcessScanline()
if(IsRenderingEnabled()) { if(IsRenderingEnabled()) {
ReadVram(GetNameTableAddr()); ReadVram(GetNameTableAddr());
if(_scanline == -1 && _nesModel == NesModel::NTSC && _cycle == 339 && (_frameCount & 0x01)) { if(_scanline == -1 && _cycle == 339 && (_frameCount & 0x01) && _nesModel == NesModel::NTSC && EmulationSettings::GetPpuModel() == PpuModel::Ppu2C02) {
//This behavior is NTSC-specific - PAL frames are always the same number of cycles //This behavior is NTSC-specific - PAL frames are always the same number of cycles
//"With rendering enabled, each odd PPU frame is one PPU clock shorter than normal" (skip from 339 to 0, going over 340) //"With rendering enabled, each odd PPU frame is one PPU clock shorter than normal" (skip from 339 to 0, going over 340)
_cycle = 340; _cycle = 340;

View file

@ -11,6 +11,7 @@
SoundMixer::SoundMixer(shared_ptr<Console> console) SoundMixer::SoundMixer(shared_ptr<Console> console)
{ {
_audioDevice = nullptr;
_console = console; _console = console;
_eqFrequencyGrid.reset(new orfanidis_eq::freq_grid()); _eqFrequencyGrid.reset(new orfanidis_eq::freq_grid());
_oggMixer.reset(); _oggMixer.reset();
@ -23,6 +24,8 @@ SoundMixer::SoundMixer(shared_ptr<Console> console)
SoundMixer::~SoundMixer() SoundMixer::~SoundMixer()
{ {
StopRecording();
delete[] _outputBuffer; delete[] _outputBuffer;
_outputBuffer = nullptr; _outputBuffer = nullptr;

View file

@ -102,6 +102,11 @@ public:
uint8_t ReadRAM(uint16_t addr) override uint8_t ReadRAM(uint16_t addr) override
{ {
if(_port >= 2 && _console->IsDualSystem()) {
//Ignore P3/P4 controllers for VS DualSystem - those are used by the slave CPU
return 0;
}
uint8_t output = 0; uint8_t output = 0;
if((addr == 0x4016 && (_port & 0x01) == 0) || (addr == 0x4017 && (_port & 0x01) == 1)) { if((addr == 0x4016 && (_port & 0x01) == 0) || (addr == 0x4017 && (_port & 0x01) == 1)) {

View file

@ -316,6 +316,7 @@ enum class GameSystem
Famicom, Famicom,
Dendy, Dendy,
VsUniSystem, VsUniSystem,
VsDualSystem,
Playchoice, Playchoice,
FDS, FDS,
Unknown, Unknown,

View file

@ -1,5 +1,6 @@
#include "stdafx.h" #include "stdafx.h"
#include "VsControlManager.h" #include "VsControlManager.h"
#include "VsSystem.h"
ControllerType VsControlManager::GetControllerType(uint8_t port) ControllerType VsControlManager::GetControllerType(uint8_t port)
{ {
@ -12,7 +13,13 @@ ControllerType VsControlManager::GetControllerType(uint8_t port)
void VsControlManager::Reset(bool softReset) void VsControlManager::Reset(bool softReset)
{ {
ControlManager::Reset(softReset);
_protectionCounter = 0; _protectionCounter = 0;
UpdateSlaveMasterBit(_console->IsMaster() ? 0x00 : 0x02);
if(!softReset && !_console->IsMaster() && _console->GetDualConsole()) {
RegisterInputProvider(this);
}
} }
void VsControlManager::StreamState(bool saving) void VsControlManager::StreamState(bool saving)
@ -89,6 +96,7 @@ uint8_t VsControlManager::ReadRAM(uint16_t addr)
value = ControlManager::ReadRAM(addr); value = ControlManager::ReadRAM(addr);
value |= ((dipSwitches & 0x01) ? 0x08 : 0x00); value |= ((dipSwitches & 0x01) ? 0x08 : 0x00);
value |= ((dipSwitches & 0x02) ? 0x10 : 0x00); value |= ((dipSwitches & 0x02) ? 0x10 : 0x00);
value |= (_console->IsMaster() ? 0x00 : 0x80);
break; break;
} }
@ -143,5 +151,65 @@ void VsControlManager::WriteRAM(uint16_t addr, uint8_t value)
if(addr == 0x4016) { if(addr == 0x4016) {
_prgChrSelectBit = (value >> 2) & 0x01; _prgChrSelectBit = (value >> 2) & 0x01;
//Bit 2: DualSystem-only
uint8_t slaveMasterBit = (value & 0x02);
if(slaveMasterBit != _slaveMasterBit) {
UpdateSlaveMasterBit(slaveMasterBit);
}
} }
} }
void VsControlManager::UpdateSlaveMasterBit(uint8_t slaveMasterBit)
{
shared_ptr<Console> dualConsole = _console->GetDualConsole();
if(dualConsole) {
VsSystem* mapper = dynamic_cast<VsSystem*>(_console->GetMapper());
if(_console->IsMaster()) {
mapper->UpdateMemoryAccess(slaveMasterBit);
}
if(slaveMasterBit) {
dualConsole->GetCpu()->ClearIrqSource(IRQSource::External);
} else {
//When low, asserts /IRQ on the other CPU
dualConsole->GetCpu()->SetIrqSource(IRQSource::External);
}
}
_slaveMasterBit = slaveMasterBit;
}
void VsControlManager::UpdateControlDevices()
{
if(_console->GetDualConsole()) {
auto lock = _deviceLock.AcquireSafe();
_controlDevices.clear();
RegisterControlDevice(_systemActionManager);
//Force 4x standard controllers
//P3 & P4 will be sent to the slave CPU - see SetInput() below.
for(int i = 0; i < 4; i++) {
shared_ptr<BaseControlDevice> device = CreateControllerDevice(ControllerType::StandardController, i, _console);
if(device) {
RegisterControlDevice(device);
}
}
} else {
ControlManager::UpdateControlDevices();
}
}
bool VsControlManager::SetInput(BaseControlDevice* device)
{
uint8_t port = device->GetPort();
ControlManager* masterControlManager = _console->GetDualConsole()->GetControlManager();
if(masterControlManager && port <= 1) {
shared_ptr<BaseControlDevice> controlDevice = masterControlManager->GetControlDevice(port + 2);
if(controlDevice) {
ControlDeviceState state = controlDevice->GetRawState();
device->SetRawState(state);
}
}
return true;
}

View file

@ -7,13 +7,16 @@
#include <assert.h> #include <assert.h>
#include "StandardController.h" #include "StandardController.h"
#include "MovieManager.h" #include "MovieManager.h"
#include "IInputProvider.h"
class BaseControlDevice; class BaseControlDevice;
class VsControlManager : public ControlManager class VsControlManager : public ControlManager, public IInputProvider
{ {
private: private:
uint8_t _prgChrSelectBit; uint8_t _prgChrSelectBit = 0;
uint8_t _slaveMasterBit = 0;
bool _refreshState = false; bool _refreshState = false;
uint32_t _protectionCounter = 0; uint32_t _protectionCounter = 0;
@ -39,6 +42,7 @@ private:
}; };
ControllerType GetControllerType(uint8_t port) override; ControllerType GetControllerType(uint8_t port) override;
void UpdateSlaveMasterBit(uint8_t slaveMasterBit);
public: public:
using ControlManager::ControlManager; using ControlManager::ControlManager;
@ -50,8 +54,13 @@ public:
uint8_t GetPrgChrSelectBit(); uint8_t GetPrgChrSelectBit();
void UpdateControlDevices() override;
void RemapControllerButtons(); void RemapControllerButtons();
uint8_t ReadRAM(uint16_t addr) override; uint8_t ReadRAM(uint16_t addr) override;
void WriteRAM(uint16_t addr, uint8_t value) override; void WriteRAM(uint16_t addr, uint8_t value) override;
// Inherited via IInputProvider
virtual bool SetInput(BaseControlDevice* device) override;
}; };

View file

@ -6,31 +6,44 @@
class VsSystem : public BaseMapper class VsSystem : public BaseMapper
{ {
private: private:
uint8_t _prgChrSelectBit = false; uint8_t _prgChrSelectBit = 0;
protected: protected:
virtual uint16_t GetPRGPageSize() override { return 0x2000; } virtual uint16_t GetPRGPageSize() override { return 0x2000; }
virtual uint16_t GetCHRPageSize() override { return 0x2000; } virtual uint16_t GetCHRPageSize() override { return 0x2000; }
virtual uint32_t GetWorkRamSize() override { return 0x800; }
virtual void InitMapper() override virtual void InitMapper() override
{ {
//Force VS system if mapper 99 (since we assume VsControlManager exists below)
if(_prgSize >= 0x10000) {
//Assume DualSystem if PRG ROM is 64kb or larger
_gameSystem = GameSystem::VsDualSystem;
} else {
_gameSystem = GameSystem::VsUniSystem;
}
//"Note: unlike all other mappers, an undersize mapper 99 image implies open bus instead of mirroring." //"Note: unlike all other mappers, an undersize mapper 99 image implies open bus instead of mirroring."
//However, it doesn't look like any game actually rely on this behavior? So not implemented for now. //However, it doesn't look like any game actually rely on this behavior? So not implemented for now.
SelectPRGPage(0, 0); uint8_t prgOuter = _console->IsMaster() ? 0 : (GetPRGPageCount() / 2);
SelectPRGPage(1, 1); SelectPRGPage(0, 0 | prgOuter);
SelectPRGPage(2, 2); SelectPRGPage(1, 1 | prgOuter);
SelectPRGPage(3, 3); SelectPRGPage(2, 2 | prgOuter);
SelectPRGPage(3, 3 | prgOuter);
SelectCHRPage(0, 0); uint8_t chrOuter = _console->IsMaster() ? 0 : (GetCHRPageCount() / 2);
SelectCHRPage(0, 0 | chrOuter);
}
//Force VS system if mapper 99 (since we assume VsControlManager exists below) void Reset(bool softReset) override
_gameSystem = GameSystem::VsUniSystem; {
BaseMapper::Reset(softReset);
UpdateMemoryAccess(0);
} }
void StreamState(bool saving) override void StreamState(bool saving) override
{ {
BaseMapper::StreamState(saving); BaseMapper::StreamState(saving);
Stream(_prgChrSelectBit); Stream(_prgChrSelectBit);
} }
@ -40,13 +53,29 @@ protected:
if(_prgChrSelectBit != controlManager->GetPrgChrSelectBit()) { if(_prgChrSelectBit != controlManager->GetPrgChrSelectBit()) {
_prgChrSelectBit = controlManager->GetPrgChrSelectBit(); _prgChrSelectBit = controlManager->GetPrgChrSelectBit();
if(_prgSize > 0x8000) { if(_prgSize > 0x8000 && _prgSize < 0x10000) {
//"Note: In case of games with 40KiB PRG - ROM(as found in VS Gumshoe), the above bit additionally changes 8KiB PRG - ROM at $8000 - $9FFF." //"Note: In case of games with 40KiB PRG - ROM(as found in VS Gumshoe), the above bit additionally changes 8KiB PRG - ROM at $8000 - $9FFF."
//"Only Vs. Gumshoe uses the 40KiB PRG variant; in the iNES encapsulation, the 8KiB banks are arranged as 0, 1, 2, 3, 0alternate, empty" //"Only Vs. Gumshoe uses the 40KiB PRG variant; in the iNES encapsulation, the 8KiB banks are arranged as 0, 1, 2, 3, 0alternate, empty"
SelectPRGPage(0, _prgChrSelectBit << 2); SelectPRGPage(0, _prgChrSelectBit << 2);
} }
SelectCHRPage(0, _prgChrSelectBit); uint8_t chrOuter = _console->IsMaster() ? 0 : (GetCHRPageCount() / 2);
SelectCHRPage(0, _prgChrSelectBit | chrOuter);
}
}
public:
void UpdateMemoryAccess(uint8_t slaveMasterBit)
{
shared_ptr<Console> dualConsole = _console->GetDualConsole();
if(_console->IsMaster() && dualConsole) {
VsSystem* otherMapper = dynamic_cast<VsSystem*>(dualConsole->GetMapper());
//Give memory access to master CPU or slave CPU, based on "slaveMasterBit"
for(int i = 0; i < 4; i++) {
SetCpuMemoryMapping(0x6000 + i * 0x800, 0x67FF + i * 0x800, _workRam, slaveMasterBit ? MemoryAccessType::ReadWrite : MemoryAccessType::NoAccess);
otherMapper->SetCpuMemoryMapping(0x6000 + i * 0x800, 0x67FF + i * 0x800, _workRam, slaveMasterBit ? MemoryAccessType::NoAccess : MemoryAccessType::ReadWrite);
}
} }
} }
}; };

View file

@ -208,6 +208,7 @@ namespace Mesen.GUI.Forms
this.mnuReportBug = new System.Windows.Forms.ToolStripMenuItem(); this.mnuReportBug = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator(); this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator();
this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem(); this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem();
this.ctrlRendererDualSystem = new Mesen.GUI.Controls.ctrlRenderer();
this.panelRenderer.SuspendLayout(); this.panelRenderer.SuspendLayout();
this.panelInfo.SuspendLayout(); this.panelInfo.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.picIcon)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.picIcon)).BeginInit();
@ -221,6 +222,7 @@ namespace Mesen.GUI.Forms
// panelRenderer // panelRenderer
// //
this.panelRenderer.BackColor = System.Drawing.Color.Black; this.panelRenderer.BackColor = System.Drawing.Color.Black;
this.panelRenderer.Controls.Add(this.ctrlRendererDualSystem);
this.panelRenderer.Controls.Add(this.ctrlLoading); this.panelRenderer.Controls.Add(this.ctrlLoading);
this.panelRenderer.Controls.Add(this.panelInfo); this.panelRenderer.Controls.Add(this.panelInfo);
this.panelRenderer.Controls.Add(this.ctrlRecentGames); this.panelRenderer.Controls.Add(this.ctrlRecentGames);
@ -1662,6 +1664,16 @@ namespace Mesen.GUI.Forms
this.mnuAbout.Text = "About"; this.mnuAbout.Text = "About";
this.mnuAbout.Click += new System.EventHandler(this.mnuAbout_Click); this.mnuAbout.Click += new System.EventHandler(this.mnuAbout_Click);
// //
// ctrlRendererDualSystem
//
this.ctrlRendererDualSystem.BackColor = System.Drawing.Color.Black;
this.ctrlRendererDualSystem.Location = new System.Drawing.Point(275, 0);
this.ctrlRendererDualSystem.Margin = new System.Windows.Forms.Padding(0);
this.ctrlRendererDualSystem.Name = "ctrlRendererDualSystem";
this.ctrlRendererDualSystem.Size = new System.Drawing.Size(150, 90);
this.ctrlRendererDualSystem.TabIndex = 8;
this.ctrlRendererDualSystem.Visible = false;
//
// frmMain // frmMain
// //
this.AllowDrop = true; this.AllowDrop = true;
@ -1868,6 +1880,7 @@ namespace Mesen.GUI.Forms
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem27; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem27;
private System.Windows.Forms.ToolStripMenuItem mnuInstallHdPack; private System.Windows.Forms.ToolStripMenuItem mnuInstallHdPack;
private System.Windows.Forms.ToolStripMenuItem mnuTextHooker; private System.Windows.Forms.ToolStripMenuItem mnuTextHooker;
private Controls.ctrlRenderer ctrlRendererDualSystem;
} }
} }

View file

@ -52,6 +52,7 @@ namespace Mesen.GUI.Forms
private bool _enableResize = false; private bool _enableResize = false;
private bool _overrideWindowSize = false; private bool _overrideWindowSize = false;
private bool _shuttingDown = false; private bool _shuttingDown = false;
private bool _isDualSystem = false;
private frmFullscreenRenderer _frmFullscreenRenderer = null; private frmFullscreenRenderer _frmFullscreenRenderer = null;
@ -333,23 +334,32 @@ namespace Mesen.GUI.Forms
UpdateEmulationFlags(); UpdateEmulationFlags();
} }
private void UpdateViewerSize() private void UpdateViewerSize(bool forceUpdate = false)
{ {
this.Resize -= frmMain_Resize; this.Resize -= frmMain_Resize;
InteropEmu.ScreenSize size = InteropEmu.GetScreenSize(false); InteropEmu.ScreenSize size = InteropEmu.GetScreenSize(false);
if(!_customSize && this.WindowState != FormWindowState.Maximized) { int width = _isDualSystem ? (size.Width * 2) : size.Width;
if(forceUpdate || (!_customSize && this.WindowState != FormWindowState.Maximized)) {
Size sizeGap = this.Size - this.ClientSize; Size sizeGap = this.Size - this.ClientSize;
UpdateScaleMenu(size.Scale); UpdateScaleMenu(size.Scale);
this.ClientSize = new Size(Math.Max(this.MinimumSize.Width - sizeGap.Width, size.Width), Math.Max(this.MinimumSize.Height - sizeGap.Height, size.Height + (this.HideMenuStrip ? 0 : menuStrip.Height))); this.ClientSize = new Size(Math.Max(this.MinimumSize.Width - sizeGap.Width, width), Math.Max(this.MinimumSize.Height - sizeGap.Height, size.Height + (this.HideMenuStrip ? 0 : menuStrip.Height)));
} }
ctrlRenderer.Size = new Size(size.Width, size.Height); ctrlRenderer.Size = new Size(size.Width, size.Height);
ctrlRenderer.Left = (panelRenderer.Width - ctrlRenderer.Width) / 2; if(_isDualSystem) {
ctrlRenderer.Top = (panelRenderer.Height - ctrlRenderer.Height) / 2; ctrlRendererDualSystem.Size = new Size(size.Width, size.Height);
ctrlRendererDualSystem.Top = (panelRenderer.Height - ctrlRenderer.Height) / 2;
ctrlRenderer.Left = (panelRenderer.Width / 2 - ctrlRenderer.Width) / 2;
ctrlRendererDualSystem.Left = ctrlRenderer.Left + ctrlRenderer.Width;
} else {
ctrlRenderer.Left = (panelRenderer.Width - ctrlRenderer.Width) / 2;
}
ctrlRenderer.Top = (panelRenderer.Height - ctrlRenderer.Height) / 2;
if(this.HideMenuStrip) { if(this.HideMenuStrip) {
this.menuStrip.Visible = false; this.menuStrip.Visible = false;
} }
@ -630,6 +640,24 @@ namespace Mesen.GUI.Forms
} }
})); }));
break; break;
case InteropEmu.ConsoleNotificationType.VsDualSystemStarted:
_isDualSystem = true;
this.BeginInvoke((MethodInvoker)(() => {
ctrlRendererDualSystem.Visible = _isDualSystem;
UpdateViewerSize(true);
InteropEmu.InitializeDualSystem(this.Handle, ctrlRendererDualSystem.Handle);
}));
break;
case InteropEmu.ConsoleNotificationType.VsDualSystemStopped:
_isDualSystem = false;
InteropEmu.ReleaseDualSystemAudioVideo();
this.BeginInvoke((MethodInvoker)(() => {
ctrlRendererDualSystem.Visible = _isDualSystem;
UpdateViewerSize(true);
}));
break;
} }
if(e.NotificationType != InteropEmu.ConsoleNotificationType.PpuFrameDone) { if(e.NotificationType != InteropEmu.ConsoleNotificationType.PpuFrameDone) {

View file

@ -22,6 +22,9 @@ namespace Mesen.GUI
[DllImport(DLLPath)] public static extern void InitializeEmu([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string homeFolder, IntPtr windowHandle, IntPtr dxViewerHandle, [MarshalAs(UnmanagedType.I1)]bool noAudio, [MarshalAs(UnmanagedType.I1)]bool noVideo, [MarshalAs(UnmanagedType.I1)]bool noInput); [DllImport(DLLPath)] public static extern void InitializeEmu([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string homeFolder, IntPtr windowHandle, IntPtr dxViewerHandle, [MarshalAs(UnmanagedType.I1)]bool noAudio, [MarshalAs(UnmanagedType.I1)]bool noVideo, [MarshalAs(UnmanagedType.I1)]bool noInput);
[DllImport(DLLPath)] public static extern void Release(); [DllImport(DLLPath)] public static extern void Release();
[DllImport(DLLPath)] public static extern void InitializeDualSystem(IntPtr windowHandle, IntPtr viewerHandle);
[DllImport(DLLPath)] public static extern void ReleaseDualSystemAudioVideo();
[DllImport(DLLPath)] public static extern void SetDisplayLanguage(Language lang); [DllImport(DLLPath)] public static extern void SetDisplayLanguage(Language lang);
[DllImport(DLLPath)] public static extern void SetFullscreenMode([MarshalAs(UnmanagedType.I1)]bool fullscreen, IntPtr windowHandle, UInt32 monitorWidth, UInt32 monitorHeight); [DllImport(DLLPath)] public static extern void SetFullscreenMode([MarshalAs(UnmanagedType.I1)]bool fullscreen, IntPtr windowHandle, UInt32 monitorWidth, UInt32 monitorHeight);
@ -883,6 +886,8 @@ namespace Mesen.GUI
EmulationStopped = 17, EmulationStopped = 17,
EventViewerDisplayFrame = 18, EventViewerDisplayFrame = 18,
BeforeEmulationStop = 19, BeforeEmulationStop = 19,
VsDualSystemStarted = 20,
VsDualSystemStopped = 21,
} }
public enum ControllerType public enum ControllerType

View file

@ -41,11 +41,14 @@
#include "../Linux/LinuxKeyManager.h" #include "../Linux/LinuxKeyManager.h"
#endif #endif
IRenderingDevice *_renderer = nullptr; unique_ptr<IRenderingDevice> _renderer;
IAudioDevice *_soundManager = nullptr; unique_ptr<IAudioDevice> _soundManager;
IKeyManager *_keyManager = nullptr; unique_ptr<IKeyManager> _keyManager;
unique_ptr<ShortcutKeyHandler> _shortcutKeyHandler; unique_ptr<ShortcutKeyHandler> _shortcutKeyHandler;
unique_ptr<IRenderingDevice> _dualRenderer;
unique_ptr<IAudioDevice> _dualSoundManager;
void* _windowHandle = nullptr; void* _windowHandle = nullptr;
void* _viewerHandle = nullptr; void* _viewerHandle = nullptr;
string _returnString; string _returnString;
@ -109,31 +112,55 @@ namespace InteropEmu {
if(!noVideo) { if(!noVideo) {
#ifdef _WIN32 #ifdef _WIN32
_renderer = new Renderer(_console, (HWND)_viewerHandle); _renderer.reset(new Renderer(_console, (HWND)_viewerHandle));
#else #else
_renderer = new SdlRenderer(_console, _viewerHandle); _renderer.reset(new SdlRenderer(_console, _viewerHandle));
#endif #endif
} }
if(!noAudio) { if(!noAudio) {
#ifdef _WIN32 #ifdef _WIN32
_soundManager = new SoundManager(_console, (HWND)_windowHandle); _soundManager.reset(new SoundManager(_console, (HWND)_windowHandle));
#else #else
_soundManager = new SdlSoundManager(_console); _soundManager.reset(new SdlSoundManager(_console));
#endif #endif
} }
if(!noInput) { if(!noInput) {
#ifdef _WIN32 #ifdef _WIN32
_keyManager = new WindowsKeyManager(_console, (HWND)_windowHandle); _keyManager.reset(new WindowsKeyManager(_console, (HWND)_windowHandle));
#else #else
_keyManager = new LinuxKeyManager(_console); _keyManager.reset(new LinuxKeyManager(_console));
#endif #endif
KeyManager::RegisterKeyManager(_keyManager); KeyManager::RegisterKeyManager(_keyManager.get());
} }
} }
} }
DllExport void __stdcall InitializeDualSystem(void *windowHandle, void *viewerHandle)
{
shared_ptr<Console> slaveConsole = _console->GetDualConsole();
if(slaveConsole){
_console->Pause();
#ifdef _WIN32
_dualRenderer.reset(new Renderer(slaveConsole, (HWND)viewerHandle));
_dualSoundManager.reset(new SoundManager(slaveConsole, (HWND)windowHandle));
#else
_dualRenderer.reset(new SdlRenderer(slaveConsole, viewerHandle));
_dualSoundManager.reset(new SdlSoundManager(slaveConsole));
#endif
_console->Resume();
}
}
DllExport void __stdcall ReleaseDualSystemAudioVideo()
{
_console->Pause();
_dualRenderer.reset();
_dualSoundManager.reset();
_console->Resume();
}
DllExport void __stdcall SetFullscreenMode(bool fullscreen, void *windowHandle, uint32_t monitorWidth, uint32_t monitorHeight) DllExport void __stdcall SetFullscreenMode(bool fullscreen, void *windowHandle, uint32_t monitorWidth, uint32_t monitorHeight)
{ {
@ -330,6 +357,7 @@ namespace InteropEmu {
DllExport void __stdcall Release() DllExport void __stdcall Release()
{ {
ReleaseDualSystemAudioVideo();
_shortcutKeyHandler.reset(); _shortcutKeyHandler.reset();
GameServer::StopServer(); GameServer::StopServer();
@ -337,18 +365,9 @@ namespace InteropEmu {
_console->Stop(); _console->Stop();
if(_renderer) { _renderer.reset();
delete _renderer; _soundManager.reset();
_renderer = nullptr; _keyManager.reset();
}
if(_soundManager) {
delete _soundManager;
_soundManager = nullptr;
}
if(_keyManager) {
delete _keyManager;
_keyManager = nullptr;
}
_console->Release(true); _console->Release(true);
_console.reset(); _console.reset();

View file

@ -21,7 +21,7 @@ extern "C"
//Debugger wrapper //Debugger wrapper
DllExport void __stdcall DebugInitialize() DllExport void __stdcall DebugInitialize()
{ {
_console->GetDebugger(); GetDebugger();
} }
DllExport void __stdcall DebugRelease() DllExport void __stdcall DebugRelease()

View file

@ -5,16 +5,21 @@
#include "../Core/VideoDecoder.h" #include "../Core/VideoDecoder.h"
#include "../Core/EmulationSettings.h" #include "../Core/EmulationSettings.h"
SimpleLock SdlRenderer::_reinitLock;
SimpleLock SdlRenderer::_frameLock;
SdlRenderer::SdlRenderer(shared_ptr<Console> console, void* windowHandle) : BaseRenderer(console), _windowHandle(windowHandle) SdlRenderer::SdlRenderer(shared_ptr<Console> console, void* windowHandle) : BaseRenderer(console), _windowHandle(windowHandle)
{ {
_frameBuffer = nullptr; _frameBuffer = nullptr;
SetScreenSize(256,240); SetScreenSize(256,240);
MessageManager::RegisterMessageManager(this);
} }
SdlRenderer::~SdlRenderer() SdlRenderer::~SdlRenderer()
{ {
_console->GetVideoRenderer()->UnregisterRenderingDevice(this); shared_ptr<VideoRenderer> videoRenderer = _console->GetVideoRenderer();
if(videoRenderer) {
videoRenderer->UnregisterRenderingDevice(this);
}
Cleanup(); Cleanup();
} }

View file

@ -35,8 +35,8 @@ private:
VideoResizeFilter _resizeFilter = VideoResizeFilter::NearestNeighbor; VideoResizeFilter _resizeFilter = VideoResizeFilter::NearestNeighbor;
SimpleLock _frameLock; static SimpleLock _frameLock;
SimpleLock _reinitLock; static SimpleLock _reinitLock;
uint32_t* _frameBuffer; uint32_t* _frameBuffer;
const uint32_t _bytesPerPixel = 4; const uint32_t _bytesPerPixel = 4;

View file

@ -42,6 +42,7 @@ void SdlSoundManager::Release()
bool SdlSoundManager::InitializeAudio(uint32_t sampleRate, bool isStereo) bool SdlSoundManager::InitializeAudio(uint32_t sampleRate, bool isStereo)
{ {
if(SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { if(SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
MessageManager::Log("[Audio] Failed to initialize audio subsystem");
return false; return false;
} }

View file

@ -18,13 +18,14 @@ Renderer::Renderer(shared_ptr<Console> console, HWND hWnd) : BaseRenderer(consol
_hWnd = hWnd; _hWnd = hWnd;
SetScreenSize(256, 240); SetScreenSize(256, 240);
MessageManager::RegisterMessageManager(this);
} }
Renderer::~Renderer() Renderer::~Renderer()
{ {
_console->GetVideoRenderer()->UnregisterRenderingDevice(this); shared_ptr<VideoRenderer> videoRenderer = _console->GetVideoRenderer();
if(videoRenderer) {
videoRenderer->UnregisterRenderingDevice(this);
}
CleanupDevice(); CleanupDevice();
} }