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)
{
if(_gameSystem == GameSystem::VsUniSystem) {
if(_gameSystem == GameSystem::VsUniSystem || _gameSystem == GameSystem::VsDualSystem) {
ranges.AddHandler(MemoryOperation::Read, 0x6000, 0xFFFF);
ranges.AddHandler(MemoryOperation::Write, 0x6000, 0xFFFF);
} else {

View file

@ -9,6 +9,11 @@
BaseRenderer::BaseRenderer(shared_ptr<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)

View file

@ -45,36 +45,55 @@
#include "DebugHud.h"
#include "NotificationManager.h"
Console::Console()
Console::Console(shared_ptr<Console> master)
{
_master = master;
_model = NesModel::NTSC;
if(_master) {
_master->_notificationManager->SendNotification(ConsoleNotificationType::VsDualSystemStarted);
}
}
Console::~Console()
{
MovieManager::Stop();
GetSoundMixer()->StopRecording();
}
void Console::Init()
{
_notificationManager.reset(new NotificationManager());
_saveStateManager.reset(new SaveStateManager(shared_from_this()));
_videoRenderer.reset(new VideoRenderer(shared_from_this()));
_videoDecoder.reset(new VideoDecoder(shared_from_this()));
_saveStateManager.reset(new SaveStateManager(shared_from_this()));
_cheatManager.reset(new CheatManager(shared_from_this()));
_debugHud.reset(new DebugHud());
_soundMixer.reset(new SoundMixer(shared_from_this()));
_soundMixer->SetNesModel(_model);
}
void Console::Release(bool forShutdown)
{
if(forShutdown) {
_saveStateManager.reset();
_videoRenderer.reset();
_videoDecoder->StopThread();
_videoRenderer->StopThread();
_videoDecoder.reset();
_videoRenderer.reset();
_debugHud.reset();
_saveStateManager.reset();
_cheatManager.reset();
_soundMixer.reset();
_notificationManager.reset();
}
if(_master) {
_master->_notificationManager->SendNotification(ConsoleNotificationType::VsDualSystemStopped);
}
_rewindManager.reset();
@ -85,7 +104,13 @@ void Console::Release(bool forShutdown)
_hdAudioDevice.reset();
_systemActionManager.reset();
if(_slave) {
_slave->Release(true);
_slave.reset();
}
_master.reset();
_cpu.reset();
_ppu.reset();
_apu.reset();
@ -246,20 +271,26 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
//Changed game, stop all recordings
MovieManager::Stop();
GetSoundMixer()->StopRecording();
_soundMixer->StopRecording();
StopRecordingHdPack();
}
#ifndef LIBRETRO
//Don't use auto-save manager for libretro
_autoSaveManager.reset(new AutoSaveManager(shared_from_this()));
#endif
_mapper = mapper;
_memoryManager.reset(new MemoryManager(shared_from_this()));
_cpu.reset(new CPU(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;
switch(system) {
@ -269,6 +300,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
break;
case GameSystem::VsUniSystem:
case GameSystem::VsDualSystem:
_systemActionManager.reset(new VsSystemActionManager(shared_from_this()));
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
pollCounter = _controlManager->GetPollCounter();
}
if(system == GameSystem::VsUniSystem) {
if(system == GameSystem::VsUniSystem || system == GameSystem::VsDualSystem) {
_controlManager.reset(new VsControlManager(shared_from_this(), _systemActionManager, _mapper->GetMapperControlDevice()));
} else {
_controlManager.reset(new ControlManager(shared_from_this(), _systemActionManager, _mapper->GetMapperControlDevice()));
@ -330,6 +363,13 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
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()));
_notificationManager->RegisterNotificationListener(_rewindManager);
@ -337,13 +377,15 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
FolderUtilities::AddKnownGameFolder(romFile.GetFolderPath());
string modelName = _model == NesModel::PAL ? "PAL" : (_model == NesModel::Dendy ? "Dendy" : "NTSC");
string messageTitle = MessageManager::Localize("GameLoaded") + " (" + modelName + ")";
MessageManager::DisplayMessage(messageTitle, FolderUtilities::GetFilename(GetMapperInfo().RomName, false));
if(EmulationSettings::GetOverclockRate() != 100) {
MessageManager::DisplayMessage("ClockRate", std::to_string(EmulationSettings::GetOverclockRate()) + "%");
if(IsMaster()) {
string modelName = _model == NesModel::PAL ? "PAL" : (_model == NesModel::Dendy ? "Dendy" : "NTSC");
string messageTitle = MessageManager::Localize("GameLoaded") + " (" + modelName + ")";
MessageManager::DisplayMessage(messageTitle, FolderUtilities::GetFilename(GetMapperInfo().RomName, false));
if(EmulationSettings::GetOverclockRate() != 100) {
MessageManager::DisplayMessage("ClockRate", std::to_string(EmulationSettings::GetOverclockRate()) + "%");
}
EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed);
}
EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed);
Resume();
return true;
}
@ -392,6 +434,24 @@ shared_ptr<NotificationManager> Console::GetNotificationManager()
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()
{
return _mapper.get();
@ -476,6 +536,11 @@ void Console::Reset(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);
_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.
debugger->Suspend();
}
_pauseLock.Acquire();
//Spin wait until emu pauses
_runLock.Acquire();
if(_master) {
//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()
{
_runLock.Release();
_pauseLock.Release();
if(_master) {
//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;
if(debugger) {
@ -567,6 +643,8 @@ void Console::Run()
int timeLagDataIndex = 0;
double lastFrameMin = 9999;
double lastFrameMax = 0;
int32_t cycleGap = 0;
uint32_t currentFrameNumber = 0;
uint32_t lastFrameNumber = -1;
@ -590,13 +668,32 @@ void Console::Run()
while(true) {
_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) {
_soundMixer->ProcessEndOfFrame();
if(_slave) {
_slave->_soundMixer->ProcessEndOfFrame();
}
bool displayDebugInfo = EmulationSettings::CheckFlag(EmulationFlags::DisplayDebugInfo);
if(displayDebugInfo) {
DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData);
if(_slave) {
_slave->DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData);
}
}
lastFrameTimer.Reset();
@ -623,6 +720,9 @@ void Console::Run()
//Prevent audio from looping endlessly while game is paused
_soundMixer->StopAudio();
if(_slave) {
_slave->_soundMixer->StopAudio();
}
_runLock.Release();
@ -695,7 +795,7 @@ void Console::Run()
_soundMixer->StopAudio();
MovieManager::Stop();
GetSoundMixer()->StopRecording();
_soundMixer->StopRecording();
PlatformUtilities::EnableScreensaver();
PlatformUtilities::RestoreTimerResolution();
@ -725,12 +825,22 @@ void Console::Run()
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()
{
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)
@ -801,6 +911,11 @@ void Console::SaveState(ostream &saveStream)
} else {
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 {
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;
if(debugger) {

View file

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

View file

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

View file

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

View file

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

View file

@ -893,7 +893,7 @@ void PPU::ProcessScanline()
if(IsRenderingEnabled()) {
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
//"With rendering enabled, each odd PPU frame is one PPU clock shorter than normal" (skip from 339 to 0, going over 340)
_cycle = 340;

View file

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

View file

@ -102,6 +102,11 @@ public:
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;
if((addr == 0x4016 && (_port & 0x01) == 0) || (addr == 0x4017 && (_port & 0x01) == 1)) {

View file

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

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "VsControlManager.h"
#include "VsSystem.h"
ControllerType VsControlManager::GetControllerType(uint8_t port)
{
@ -12,7 +13,13 @@ ControllerType VsControlManager::GetControllerType(uint8_t port)
void VsControlManager::Reset(bool softReset)
{
ControlManager::Reset(softReset);
_protectionCounter = 0;
UpdateSlaveMasterBit(_console->IsMaster() ? 0x00 : 0x02);
if(!softReset && !_console->IsMaster() && _console->GetDualConsole()) {
RegisterInputProvider(this);
}
}
void VsControlManager::StreamState(bool saving)
@ -89,6 +96,7 @@ uint8_t VsControlManager::ReadRAM(uint16_t addr)
value = ControlManager::ReadRAM(addr);
value |= ((dipSwitches & 0x01) ? 0x08 : 0x00);
value |= ((dipSwitches & 0x02) ? 0x10 : 0x00);
value |= (_console->IsMaster() ? 0x00 : 0x80);
break;
}
@ -143,5 +151,65 @@ void VsControlManager::WriteRAM(uint16_t addr, uint8_t value)
if(addr == 0x4016) {
_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 "StandardController.h"
#include "MovieManager.h"
#include "IInputProvider.h"
class BaseControlDevice;
class VsControlManager : public ControlManager
class VsControlManager : public ControlManager, public IInputProvider
{
private:
uint8_t _prgChrSelectBit;
uint8_t _prgChrSelectBit = 0;
uint8_t _slaveMasterBit = 0;
bool _refreshState = false;
uint32_t _protectionCounter = 0;
@ -39,6 +42,7 @@ private:
};
ControllerType GetControllerType(uint8_t port) override;
void UpdateSlaveMasterBit(uint8_t slaveMasterBit);
public:
using ControlManager::ControlManager;
@ -50,8 +54,13 @@ public:
uint8_t GetPrgChrSelectBit();
void UpdateControlDevices() override;
void RemapControllerButtons();
uint8_t ReadRAM(uint16_t addr) 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
{
private:
uint8_t _prgChrSelectBit = false;
uint8_t _prgChrSelectBit = 0;
protected:
virtual uint16_t GetPRGPageSize() override { return 0x2000; }
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
virtual uint32_t GetWorkRamSize() override { return 0x800; }
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."
//However, it doesn't look like any game actually rely on this behavior? So not implemented for now.
SelectPRGPage(0, 0);
SelectPRGPage(1, 1);
SelectPRGPage(2, 2);
SelectPRGPage(3, 3);
uint8_t prgOuter = _console->IsMaster() ? 0 : (GetPRGPageCount() / 2);
SelectPRGPage(0, 0 | prgOuter);
SelectPRGPage(1, 1 | prgOuter);
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)
_gameSystem = GameSystem::VsUniSystem;
void Reset(bool softReset) override
{
BaseMapper::Reset(softReset);
UpdateMemoryAccess(0);
}
void StreamState(bool saving) override
{
BaseMapper::StreamState(saving);
Stream(_prgChrSelectBit);
}
@ -40,13 +53,29 @@ protected:
if(_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."
//"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);
}
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.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator();
this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem();
this.ctrlRendererDualSystem = new Mesen.GUI.Controls.ctrlRenderer();
this.panelRenderer.SuspendLayout();
this.panelInfo.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.picIcon)).BeginInit();
@ -221,6 +222,7 @@ namespace Mesen.GUI.Forms
// panelRenderer
//
this.panelRenderer.BackColor = System.Drawing.Color.Black;
this.panelRenderer.Controls.Add(this.ctrlRendererDualSystem);
this.panelRenderer.Controls.Add(this.ctrlLoading);
this.panelRenderer.Controls.Add(this.panelInfo);
this.panelRenderer.Controls.Add(this.ctrlRecentGames);
@ -1662,6 +1664,16 @@ namespace Mesen.GUI.Forms
this.mnuAbout.Text = "About";
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
//
this.AllowDrop = true;
@ -1868,6 +1880,7 @@ namespace Mesen.GUI.Forms
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem27;
private System.Windows.Forms.ToolStripMenuItem mnuInstallHdPack;
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 _overrideWindowSize = false;
private bool _shuttingDown = false;
private bool _isDualSystem = false;
private frmFullscreenRenderer _frmFullscreenRenderer = null;
@ -333,23 +334,32 @@ namespace Mesen.GUI.Forms
UpdateEmulationFlags();
}
private void UpdateViewerSize()
private void UpdateViewerSize(bool forceUpdate = false)
{
this.Resize -= frmMain_Resize;
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;
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.Left = (panelRenderer.Width - ctrlRenderer.Width) / 2;
ctrlRenderer.Top = (panelRenderer.Height - ctrlRenderer.Height) / 2;
if(_isDualSystem) {
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) {
this.menuStrip.Visible = false;
}
@ -630,6 +640,24 @@ namespace Mesen.GUI.Forms
}
}));
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) {

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 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 SetFullscreenMode([MarshalAs(UnmanagedType.I1)]bool fullscreen, IntPtr windowHandle, UInt32 monitorWidth, UInt32 monitorHeight);
@ -883,6 +886,8 @@ namespace Mesen.GUI
EmulationStopped = 17,
EventViewerDisplayFrame = 18,
BeforeEmulationStop = 19,
VsDualSystemStarted = 20,
VsDualSystemStopped = 21,
}
public enum ControllerType

View file

@ -41,11 +41,14 @@
#include "../Linux/LinuxKeyManager.h"
#endif
IRenderingDevice *_renderer = nullptr;
IAudioDevice *_soundManager = nullptr;
IKeyManager *_keyManager = nullptr;
unique_ptr<IRenderingDevice> _renderer;
unique_ptr<IAudioDevice> _soundManager;
unique_ptr<IKeyManager> _keyManager;
unique_ptr<ShortcutKeyHandler> _shortcutKeyHandler;
unique_ptr<IRenderingDevice> _dualRenderer;
unique_ptr<IAudioDevice> _dualSoundManager;
void* _windowHandle = nullptr;
void* _viewerHandle = nullptr;
string _returnString;
@ -109,31 +112,55 @@ namespace InteropEmu {
if(!noVideo) {
#ifdef _WIN32
_renderer = new Renderer(_console, (HWND)_viewerHandle);
_renderer.reset(new Renderer(_console, (HWND)_viewerHandle));
#else
_renderer = new SdlRenderer(_console, _viewerHandle);
_renderer.reset(new SdlRenderer(_console, _viewerHandle));
#endif
}
if(!noAudio) {
#ifdef _WIN32
_soundManager = new SoundManager(_console, (HWND)_windowHandle);
_soundManager.reset(new SoundManager(_console, (HWND)_windowHandle));
#else
_soundManager = new SdlSoundManager(_console);
_soundManager.reset(new SdlSoundManager(_console));
#endif
}
if(!noInput) {
#ifdef _WIN32
_keyManager = new WindowsKeyManager(_console, (HWND)_windowHandle);
_keyManager.reset(new WindowsKeyManager(_console, (HWND)_windowHandle));
#else
_keyManager = new LinuxKeyManager(_console);
_keyManager.reset(new LinuxKeyManager(_console));
#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)
{
@ -330,6 +357,7 @@ namespace InteropEmu {
DllExport void __stdcall Release()
{
ReleaseDualSystemAudioVideo();
_shortcutKeyHandler.reset();
GameServer::StopServer();
@ -337,18 +365,9 @@ namespace InteropEmu {
_console->Stop();
if(_renderer) {
delete _renderer;
_renderer = nullptr;
}
if(_soundManager) {
delete _soundManager;
_soundManager = nullptr;
}
if(_keyManager) {
delete _keyManager;
_keyManager = nullptr;
}
_renderer.reset();
_soundManager.reset();
_keyManager.reset();
_console->Release(true);
_console.reset();

View file

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

View file

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

View file

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

View file

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

View file

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