mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2024-11-14 21:37:42 -07:00
separate EmuThread to its own file
This commit is contained in:
parent
fd1e4379b9
commit
f905b6fb93
@ -7,6 +7,7 @@ set(SOURCES_QT_SDL
|
|||||||
main_shaders.h
|
main_shaders.h
|
||||||
Screen.cpp
|
Screen.cpp
|
||||||
Window.cpp
|
Window.cpp
|
||||||
|
EmuThread.cpp
|
||||||
CheatsDialog.cpp
|
CheatsDialog.cpp
|
||||||
Config.cpp
|
Config.cpp
|
||||||
DateTimeDialog.cpp
|
DateTimeDialog.cpp
|
||||||
|
758
src/frontend/qt_sdl/EmuThread.cpp
Normal file
758
src/frontend/qt_sdl/EmuThread.cpp
Normal file
@ -0,0 +1,758 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016-2023 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS 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 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
|
#include "Input.h"
|
||||||
|
#include "AudioInOut.h"
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
#include "FrontendUtil.h"
|
||||||
|
#include "OSD.h"
|
||||||
|
|
||||||
|
#include "Args.h"
|
||||||
|
#include "NDS.h"
|
||||||
|
#include "NDSCart.h"
|
||||||
|
#include "GBACart.h"
|
||||||
|
#include "GPU.h"
|
||||||
|
#include "SPU.h"
|
||||||
|
#include "Wifi.h"
|
||||||
|
#include "Platform.h"
|
||||||
|
#include "LocalMP.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "RTC.h"
|
||||||
|
#include "DSi.h"
|
||||||
|
#include "DSi_I2C.h"
|
||||||
|
#include "GPU3D_Soft.h"
|
||||||
|
#include "GPU3D_OpenGL.h"
|
||||||
|
|
||||||
|
#include "Savestate.h"
|
||||||
|
|
||||||
|
#include "ROMManager.h"
|
||||||
|
//#include "ArchiveUtil.h"
|
||||||
|
//#include "CameraManager.h"
|
||||||
|
|
||||||
|
//#include "CLI.h"
|
||||||
|
|
||||||
|
// TODO: uniform variable spelling
|
||||||
|
using namespace melonDS;
|
||||||
|
|
||||||
|
// TEMP
|
||||||
|
extern bool RunningSomething;
|
||||||
|
extern MainWindow* mainWindow;
|
||||||
|
extern int autoScreenSizing;
|
||||||
|
extern int videoRenderer;
|
||||||
|
extern bool videoSettingsDirty;
|
||||||
|
|
||||||
|
|
||||||
|
EmuThread::EmuThread(QObject* parent) : QThread(parent)
|
||||||
|
{
|
||||||
|
EmuStatus = emuStatus_Exit;
|
||||||
|
EmuRunning = emuStatus_Paused;
|
||||||
|
EmuPauseStack = EmuPauseStackRunning;
|
||||||
|
RunningSomething = false;
|
||||||
|
|
||||||
|
connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint()));
|
||||||
|
connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString)));
|
||||||
|
connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart()));
|
||||||
|
connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop()));
|
||||||
|
connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger()));
|
||||||
|
connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger()));
|
||||||
|
connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger()));
|
||||||
|
connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger()));
|
||||||
|
connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged()));
|
||||||
|
connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled()));
|
||||||
|
connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger()));
|
||||||
|
connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<NDS> EmuThread::CreateConsole(
|
||||||
|
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
|
||||||
|
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
|
||||||
|
) noexcept
|
||||||
|
{
|
||||||
|
auto arm7bios = ROMManager::LoadARM7BIOS();
|
||||||
|
if (!arm7bios)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto arm9bios = ROMManager::LoadARM9BIOS();
|
||||||
|
if (!arm9bios)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto firmware = ROMManager::LoadFirmware(Config::ConsoleType);
|
||||||
|
if (!firmware)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
|
JITArgs jitargs {
|
||||||
|
static_cast<unsigned>(Config::JIT_MaxBlockSize),
|
||||||
|
Config::JIT_LiteralOptimisations,
|
||||||
|
Config::JIT_BranchOptimisations,
|
||||||
|
Config::JIT_FastMemory,
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef GDBSTUB_ENABLED
|
||||||
|
GDBArgs gdbargs {
|
||||||
|
static_cast<u16>(Config::GdbPortARM7),
|
||||||
|
static_cast<u16>(Config::GdbPortARM9),
|
||||||
|
Config::GdbARM7BreakOnStartup,
|
||||||
|
Config::GdbARM9BreakOnStartup,
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NDSArgs ndsargs {
|
||||||
|
std::move(ndscart),
|
||||||
|
std::move(gbacart),
|
||||||
|
*arm9bios,
|
||||||
|
*arm7bios,
|
||||||
|
std::move(*firmware),
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
|
Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt,
|
||||||
|
#else
|
||||||
|
std::nullopt,
|
||||||
|
#endif
|
||||||
|
static_cast<AudioBitDepth>(Config::AudioBitDepth),
|
||||||
|
static_cast<AudioInterpolation>(Config::AudioInterp),
|
||||||
|
#ifdef GDBSTUB_ENABLED
|
||||||
|
Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt,
|
||||||
|
#else
|
||||||
|
std::nullopt,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Config::ConsoleType == 1)
|
||||||
|
{
|
||||||
|
auto arm7ibios = ROMManager::LoadDSiARM7BIOS();
|
||||||
|
if (!arm7ibios)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto arm9ibios = ROMManager::LoadDSiARM9BIOS();
|
||||||
|
if (!arm9ibios)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto nand = ROMManager::LoadNAND(*arm7ibios);
|
||||||
|
if (!nand)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto sdcard = ROMManager::LoadDSiSDCard();
|
||||||
|
DSiArgs args {
|
||||||
|
std::move(ndsargs),
|
||||||
|
*arm9ibios,
|
||||||
|
*arm7ibios,
|
||||||
|
std::move(*nand),
|
||||||
|
std::move(sdcard),
|
||||||
|
Config::DSiFullBIOSBoot,
|
||||||
|
};
|
||||||
|
|
||||||
|
args.GBAROM = nullptr;
|
||||||
|
|
||||||
|
return std::make_unique<melonDS::DSi>(std::move(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<melonDS::NDS>(std::move(ndsargs));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept
|
||||||
|
{
|
||||||
|
// Let's get the cart we want to use;
|
||||||
|
// if we wnat to keep the cart, we'll eject it from the existing console first.
|
||||||
|
std::unique_ptr<NDSCart::CartCommon> nextndscart;
|
||||||
|
if (std::holds_alternative<Keep>(ndsargs))
|
||||||
|
{ // If we want to keep the existing cart (if any)...
|
||||||
|
nextndscart = NDS ? NDS->EjectCart() : nullptr;
|
||||||
|
ndsargs = {};
|
||||||
|
}
|
||||||
|
else if (const auto ptr = std::get_if<std::unique_ptr<NDSCart::CartCommon>>(&ndsargs))
|
||||||
|
{
|
||||||
|
nextndscart = std::move(*ptr);
|
||||||
|
ndsargs = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextndscart && nextndscart->Type() == NDSCart::Homebrew)
|
||||||
|
{
|
||||||
|
// Load DLDISDCard will return nullopt if the SD card is disabled;
|
||||||
|
// SetSDCard will accept nullopt, which means no SD card
|
||||||
|
auto& homebrew = static_cast<NDSCart::CartHomebrew&>(*nextndscart);
|
||||||
|
homebrew.SetSDCard(ROMManager::LoadDLDISDCard());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<GBACart::CartCommon> nextgbacart;
|
||||||
|
if (std::holds_alternative<Keep>(gbaargs))
|
||||||
|
{
|
||||||
|
nextgbacart = NDS ? NDS->EjectGBACart() : nullptr;
|
||||||
|
}
|
||||||
|
else if (const auto ptr = std::get_if<std::unique_ptr<GBACart::CartCommon>>(&gbaargs))
|
||||||
|
{
|
||||||
|
nextgbacart = std::move(*ptr);
|
||||||
|
gbaargs = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NDS || NDS->ConsoleType != Config::ConsoleType)
|
||||||
|
{ // If we're switching between DS and DSi mode, or there's no console...
|
||||||
|
// To ensure the destructor is called before a new one is created,
|
||||||
|
// as the presence of global signal handlers still complicates things a bit
|
||||||
|
NDS = nullptr;
|
||||||
|
NDS::Current = nullptr;
|
||||||
|
|
||||||
|
NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart));
|
||||||
|
|
||||||
|
if (NDS == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NDS->Reset();
|
||||||
|
NDS::Current = NDS.get();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto arm9bios = ROMManager::LoadARM9BIOS();
|
||||||
|
if (!arm9bios)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto arm7bios = ROMManager::LoadARM7BIOS();
|
||||||
|
if (!arm7bios)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType);
|
||||||
|
if (!firmware)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (NDS->ConsoleType == 1)
|
||||||
|
{ // If the console we're updating is a DSi...
|
||||||
|
DSi& dsi = static_cast<DSi&>(*NDS);
|
||||||
|
|
||||||
|
auto arm9ibios = ROMManager::LoadDSiARM9BIOS();
|
||||||
|
if (!arm9ibios)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto arm7ibios = ROMManager::LoadDSiARM7BIOS();
|
||||||
|
if (!arm7ibios)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto nandimage = ROMManager::LoadNAND(*arm7ibios);
|
||||||
|
if (!nandimage)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto dsisdcard = ROMManager::LoadDSiSDCard();
|
||||||
|
|
||||||
|
dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot);
|
||||||
|
dsi.ARM7iBIOS = *arm7ibios;
|
||||||
|
dsi.ARM9iBIOS = *arm9ibios;
|
||||||
|
dsi.SetNAND(std::move(*nandimage));
|
||||||
|
dsi.SetSDCard(std::move(dsisdcard));
|
||||||
|
// We're moving the optional, not the card
|
||||||
|
// (inserting std::nullopt here is okay, it means no card)
|
||||||
|
|
||||||
|
dsi.EjectGBACart();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NDS->ConsoleType == 0)
|
||||||
|
{
|
||||||
|
NDS->SetGBACart(std::move(nextgbacart));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
|
JITArgs jitargs {
|
||||||
|
static_cast<unsigned>(Config::JIT_MaxBlockSize),
|
||||||
|
Config::JIT_LiteralOptimisations,
|
||||||
|
Config::JIT_BranchOptimisations,
|
||||||
|
Config::JIT_FastMemory,
|
||||||
|
};
|
||||||
|
NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt);
|
||||||
|
#endif
|
||||||
|
NDS->SetARM7BIOS(*arm7bios);
|
||||||
|
NDS->SetARM9BIOS(*arm9bios);
|
||||||
|
NDS->SetFirmware(std::move(*firmware));
|
||||||
|
NDS->SetNDSCart(std::move(nextndscart));
|
||||||
|
NDS->SPU.SetInterpolation(static_cast<AudioInterpolation>(Config::AudioInterp));
|
||||||
|
NDS->SPU.SetDegrade10Bit(static_cast<AudioBitDepth>(Config::AudioBitDepth));
|
||||||
|
|
||||||
|
NDS::Current = NDS.get();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::run()
|
||||||
|
{
|
||||||
|
u32 mainScreenPos[3];
|
||||||
|
Platform::FileHandle* file;
|
||||||
|
|
||||||
|
UpdateConsole(nullptr, nullptr);
|
||||||
|
// No carts are inserted when melonDS first boots
|
||||||
|
|
||||||
|
mainScreenPos[0] = 0;
|
||||||
|
mainScreenPos[1] = 0;
|
||||||
|
mainScreenPos[2] = 0;
|
||||||
|
autoScreenSizing = 0;
|
||||||
|
|
||||||
|
videoSettingsDirty = false;
|
||||||
|
|
||||||
|
if (mainWindow->hasOGL)
|
||||||
|
{
|
||||||
|
screenGL = static_cast<ScreenPanelGL*>(mainWindow->panel);
|
||||||
|
screenGL->initOpenGL();
|
||||||
|
videoRenderer = Config::_3DRenderer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
screenGL = nullptr;
|
||||||
|
videoRenderer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoRenderer == 0)
|
||||||
|
{ // If we're using the software renderer...
|
||||||
|
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>(Config::Threaded3D != 0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto glrenderer = melonDS::GLRenderer::New();
|
||||||
|
glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
|
||||||
|
NDS->GPU.SetRenderer3D(std::move(glrenderer));
|
||||||
|
}
|
||||||
|
|
||||||
|
Input::Init();
|
||||||
|
|
||||||
|
u32 nframes = 0;
|
||||||
|
double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency();
|
||||||
|
double lastTime = SDL_GetPerformanceCounter() * perfCountsSec;
|
||||||
|
double frameLimitError = 0.0;
|
||||||
|
double lastMeasureTime = lastTime;
|
||||||
|
|
||||||
|
u32 winUpdateCount = 0, winUpdateFreq = 1;
|
||||||
|
u8 dsiVolumeLevel = 0x1F;
|
||||||
|
|
||||||
|
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read);
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
RTC::StateData state;
|
||||||
|
Platform::FileRead(&state, sizeof(state), 1, file);
|
||||||
|
Platform::CloseFile(file);
|
||||||
|
NDS->RTC.SetState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
char melontitle[100];
|
||||||
|
|
||||||
|
while (EmuRunning != emuStatus_Exit)
|
||||||
|
{
|
||||||
|
Input::Process();
|
||||||
|
|
||||||
|
if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
|
||||||
|
|
||||||
|
if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause();
|
||||||
|
if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset();
|
||||||
|
if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep();
|
||||||
|
|
||||||
|
if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle();
|
||||||
|
|
||||||
|
if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle();
|
||||||
|
if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle();
|
||||||
|
|
||||||
|
if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep)
|
||||||
|
{
|
||||||
|
EmuStatus = emuStatus_Running;
|
||||||
|
if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused;
|
||||||
|
|
||||||
|
if (Input::HotkeyPressed(HK_SolarSensorDecrease))
|
||||||
|
{
|
||||||
|
int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true);
|
||||||
|
if (level != -1)
|
||||||
|
{
|
||||||
|
char msg[64];
|
||||||
|
sprintf(msg, "Solar sensor level: %d", level);
|
||||||
|
OSD::AddMessage(0, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Input::HotkeyPressed(HK_SolarSensorIncrease))
|
||||||
|
{
|
||||||
|
int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true);
|
||||||
|
if (level != -1)
|
||||||
|
{
|
||||||
|
char msg[64];
|
||||||
|
sprintf(msg, "Solar sensor level: %d", level);
|
||||||
|
OSD::AddMessage(0, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NDS->ConsoleType == 1)
|
||||||
|
{
|
||||||
|
DSi& dsi = static_cast<DSi&>(*NDS);
|
||||||
|
double currentTime = SDL_GetPerformanceCounter() * perfCountsSec;
|
||||||
|
|
||||||
|
// Handle power button
|
||||||
|
if (Input::HotkeyDown(HK_PowerButton))
|
||||||
|
{
|
||||||
|
dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime);
|
||||||
|
}
|
||||||
|
else if (Input::HotkeyReleased(HK_PowerButton))
|
||||||
|
{
|
||||||
|
dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle volume buttons
|
||||||
|
if (Input::HotkeyDown(HK_VolumeUp))
|
||||||
|
{
|
||||||
|
dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up);
|
||||||
|
}
|
||||||
|
else if (Input::HotkeyReleased(HK_VolumeUp))
|
||||||
|
{
|
||||||
|
dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Input::HotkeyDown(HK_VolumeDown))
|
||||||
|
{
|
||||||
|
dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down);
|
||||||
|
}
|
||||||
|
else if (Input::HotkeyReleased(HK_VolumeDown))
|
||||||
|
{
|
||||||
|
dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down);
|
||||||
|
}
|
||||||
|
|
||||||
|
dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update render settings if needed
|
||||||
|
// HACK:
|
||||||
|
// once the fast forward hotkey is released, we need to update vsync
|
||||||
|
// to the old setting again
|
||||||
|
if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward))
|
||||||
|
{
|
||||||
|
if (screenGL)
|
||||||
|
{
|
||||||
|
screenGL->setSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0);
|
||||||
|
videoRenderer = Config::_3DRenderer;
|
||||||
|
}
|
||||||
|
#ifdef OGLRENDERER_ENABLED
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
videoRenderer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoRenderer = screenGL ? Config::_3DRenderer : 0;
|
||||||
|
|
||||||
|
videoSettingsDirty = false;
|
||||||
|
|
||||||
|
if (videoRenderer == 0)
|
||||||
|
{ // If we're using the software renderer...
|
||||||
|
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>(Config::Threaded3D != 0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto glrenderer = melonDS::GLRenderer::New();
|
||||||
|
glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
|
||||||
|
NDS->GPU.SetRenderer3D(std::move(glrenderer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process input and hotkeys
|
||||||
|
NDS->SetKeyMask(Input::InputMask);
|
||||||
|
|
||||||
|
if (Input::HotkeyPressed(HK_Lid))
|
||||||
|
{
|
||||||
|
bool lid = !NDS->IsLidClosed();
|
||||||
|
NDS->SetLidClosed(lid);
|
||||||
|
OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened");
|
||||||
|
}
|
||||||
|
|
||||||
|
// microphone input
|
||||||
|
AudioInOut::MicProcess(*NDS);
|
||||||
|
|
||||||
|
// auto screen layout
|
||||||
|
if (Config::ScreenSizing == Frontend::screenSizing_Auto)
|
||||||
|
{
|
||||||
|
mainScreenPos[2] = mainScreenPos[1];
|
||||||
|
mainScreenPos[1] = mainScreenPos[0];
|
||||||
|
mainScreenPos[0] = NDS->PowerControl9 >> 15;
|
||||||
|
|
||||||
|
int guess;
|
||||||
|
if (mainScreenPos[0] == mainScreenPos[2] &&
|
||||||
|
mainScreenPos[0] != mainScreenPos[1])
|
||||||
|
{
|
||||||
|
// constant flickering, likely displaying 3D on both screens
|
||||||
|
// TODO: when both screens are used for 2D only...???
|
||||||
|
guess = Frontend::screenSizing_Even;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mainScreenPos[0] == 1)
|
||||||
|
guess = Frontend::screenSizing_EmphTop;
|
||||||
|
else
|
||||||
|
guess = Frontend::screenSizing_EmphBot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guess != autoScreenSizing)
|
||||||
|
{
|
||||||
|
autoScreenSizing = guess;
|
||||||
|
emit screenLayoutChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// emulate
|
||||||
|
u32 nlines = NDS->RunFrame();
|
||||||
|
|
||||||
|
if (ROMManager::NDSSave)
|
||||||
|
ROMManager::NDSSave->CheckFlush();
|
||||||
|
|
||||||
|
if (ROMManager::GBASave)
|
||||||
|
ROMManager::GBASave->CheckFlush();
|
||||||
|
|
||||||
|
if (ROMManager::FirmwareSave)
|
||||||
|
ROMManager::FirmwareSave->CheckFlush();
|
||||||
|
|
||||||
|
if (!screenGL)
|
||||||
|
{
|
||||||
|
FrontBufferLock.lock();
|
||||||
|
FrontBuffer = NDS->GPU.FrontBuffer;
|
||||||
|
FrontBufferLock.unlock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FrontBuffer = NDS->GPU.FrontBuffer;
|
||||||
|
screenGL->drawScreenGL();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MELONCAP
|
||||||
|
MelonCap::Update();
|
||||||
|
#endif // MELONCAP
|
||||||
|
|
||||||
|
if (EmuRunning == emuStatus_Exit) break;
|
||||||
|
|
||||||
|
winUpdateCount++;
|
||||||
|
if (winUpdateCount >= winUpdateFreq && !screenGL)
|
||||||
|
{
|
||||||
|
emit windowUpdate();
|
||||||
|
winUpdateCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fastforward = Input::HotkeyDown(HK_FastForward);
|
||||||
|
|
||||||
|
if (fastforward && screenGL && Config::ScreenVSync)
|
||||||
|
{
|
||||||
|
screenGL->setSwapInterval(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config::DSiVolumeSync && NDS->ConsoleType == 1)
|
||||||
|
{
|
||||||
|
DSi& dsi = static_cast<DSi&>(*NDS);
|
||||||
|
u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel();
|
||||||
|
if (volumeLevel != dsiVolumeLevel)
|
||||||
|
{
|
||||||
|
dsiVolumeLevel = volumeLevel;
|
||||||
|
emit syncVolumeLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
Config::AudioVolume = volumeLevel * (256.0 / 31.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config::AudioSync && !fastforward)
|
||||||
|
AudioInOut::AudioSync(*this->NDS);
|
||||||
|
|
||||||
|
double frametimeStep = nlines / (60.0 * 263.0);
|
||||||
|
|
||||||
|
{
|
||||||
|
bool limitfps = Config::LimitFPS && !fastforward;
|
||||||
|
|
||||||
|
double practicalFramelimit = limitfps ? frametimeStep : 1.0 / 1000.0;
|
||||||
|
|
||||||
|
double curtime = SDL_GetPerformanceCounter() * perfCountsSec;
|
||||||
|
|
||||||
|
frameLimitError += practicalFramelimit - (curtime - lastTime);
|
||||||
|
if (frameLimitError < -practicalFramelimit)
|
||||||
|
frameLimitError = -practicalFramelimit;
|
||||||
|
if (frameLimitError > practicalFramelimit)
|
||||||
|
frameLimitError = practicalFramelimit;
|
||||||
|
|
||||||
|
if (round(frameLimitError * 1000.0) > 0.0)
|
||||||
|
{
|
||||||
|
SDL_Delay(round(frameLimitError * 1000.0));
|
||||||
|
double timeBeforeSleep = curtime;
|
||||||
|
curtime = SDL_GetPerformanceCounter() * perfCountsSec;
|
||||||
|
frameLimitError -= curtime - timeBeforeSleep;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTime = curtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
nframes++;
|
||||||
|
if (nframes >= 30)
|
||||||
|
{
|
||||||
|
double time = SDL_GetPerformanceCounter() * perfCountsSec;
|
||||||
|
double dt = time - lastMeasureTime;
|
||||||
|
lastMeasureTime = time;
|
||||||
|
|
||||||
|
u32 fps = round(nframes / dt);
|
||||||
|
nframes = 0;
|
||||||
|
|
||||||
|
float fpstarget = 1.0/frametimeStep;
|
||||||
|
|
||||||
|
winUpdateFreq = fps / (u32)round(fpstarget);
|
||||||
|
if (winUpdateFreq < 1)
|
||||||
|
winUpdateFreq = 1;
|
||||||
|
|
||||||
|
int inst = Platform::InstanceID();
|
||||||
|
if (inst == 0)
|
||||||
|
sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
|
||||||
|
else
|
||||||
|
sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1);
|
||||||
|
changeWindowTitle(melontitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// paused
|
||||||
|
nframes = 0;
|
||||||
|
lastTime = SDL_GetPerformanceCounter() * perfCountsSec;
|
||||||
|
lastMeasureTime = lastTime;
|
||||||
|
|
||||||
|
emit windowUpdate();
|
||||||
|
|
||||||
|
EmuStatus = EmuRunning;
|
||||||
|
|
||||||
|
int inst = Platform::InstanceID();
|
||||||
|
if (inst == 0)
|
||||||
|
sprintf(melontitle, "melonDS " MELONDS_VERSION);
|
||||||
|
else
|
||||||
|
sprintf(melontitle, "melonDS (%d)", inst+1);
|
||||||
|
changeWindowTitle(melontitle);
|
||||||
|
|
||||||
|
SDL_Delay(75);
|
||||||
|
|
||||||
|
if (screenGL)
|
||||||
|
screenGL->drawScreenGL();
|
||||||
|
|
||||||
|
ContextRequestKind contextRequest = ContextRequest;
|
||||||
|
if (contextRequest == contextRequest_InitGL)
|
||||||
|
{
|
||||||
|
screenGL = static_cast<ScreenPanelGL*>(mainWindow->panel);
|
||||||
|
screenGL->initOpenGL();
|
||||||
|
ContextRequest = contextRequest_None;
|
||||||
|
}
|
||||||
|
else if (contextRequest == contextRequest_DeInitGL)
|
||||||
|
{
|
||||||
|
screenGL->deinitOpenGL();
|
||||||
|
screenGL = nullptr;
|
||||||
|
ContextRequest = contextRequest_None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write);
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
RTC::StateData state;
|
||||||
|
NDS->RTC.GetState(state);
|
||||||
|
Platform::FileWrite(&state, sizeof(state), 1, file);
|
||||||
|
Platform::CloseFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuStatus = emuStatus_Exit;
|
||||||
|
|
||||||
|
NDS::Current = nullptr;
|
||||||
|
// nds is out of scope, so unique_ptr cleans it up for us
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::changeWindowTitle(char* title)
|
||||||
|
{
|
||||||
|
emit windowTitleChange(QString(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::emuRun()
|
||||||
|
{
|
||||||
|
EmuRunning = emuStatus_Running;
|
||||||
|
EmuPauseStack = EmuPauseStackRunning;
|
||||||
|
RunningSomething = true;
|
||||||
|
|
||||||
|
// checkme
|
||||||
|
emit windowEmuStart();
|
||||||
|
AudioInOut::Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::initContext()
|
||||||
|
{
|
||||||
|
ContextRequest = contextRequest_InitGL;
|
||||||
|
while (ContextRequest != contextRequest_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::deinitContext()
|
||||||
|
{
|
||||||
|
ContextRequest = contextRequest_DeInitGL;
|
||||||
|
while (ContextRequest != contextRequest_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::emuPause()
|
||||||
|
{
|
||||||
|
EmuPauseStack++;
|
||||||
|
if (EmuPauseStack > EmuPauseStackPauseThreshold) return;
|
||||||
|
|
||||||
|
PrevEmuStatus = EmuRunning;
|
||||||
|
EmuRunning = emuStatus_Paused;
|
||||||
|
while (EmuStatus != emuStatus_Paused);
|
||||||
|
|
||||||
|
AudioInOut::Disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::emuUnpause()
|
||||||
|
{
|
||||||
|
if (EmuPauseStack < EmuPauseStackPauseThreshold) return;
|
||||||
|
|
||||||
|
EmuPauseStack--;
|
||||||
|
if (EmuPauseStack >= EmuPauseStackPauseThreshold) return;
|
||||||
|
|
||||||
|
EmuRunning = PrevEmuStatus;
|
||||||
|
|
||||||
|
AudioInOut::Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::emuStop()
|
||||||
|
{
|
||||||
|
EmuRunning = emuStatus_Exit;
|
||||||
|
EmuPauseStack = EmuPauseStackRunning;
|
||||||
|
|
||||||
|
AudioInOut::Disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuThread::emuFrameStep()
|
||||||
|
{
|
||||||
|
if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause();
|
||||||
|
EmuRunning = emuStatus_FrameStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuThread::emuIsRunning()
|
||||||
|
{
|
||||||
|
return EmuRunning == emuStatus_Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuThread::emuIsActive()
|
||||||
|
{
|
||||||
|
return (RunningSomething == 1);
|
||||||
|
}
|
134
src/frontend/qt_sdl/EmuThread.h
Normal file
134
src/frontend/qt_sdl/EmuThread.h
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016-2023 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS 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 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef EMUTHREAD_H
|
||||||
|
#define EMUTHREAD_H
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <variant>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "NDSCart.h"
|
||||||
|
#include "GBACart.h"
|
||||||
|
|
||||||
|
using Keep = std::monostate;
|
||||||
|
using UpdateConsoleNDSArgs = std::variant<Keep, std::unique_ptr<melonDS::NDSCart::CartCommon>>;
|
||||||
|
using UpdateConsoleGBAArgs = std::variant<Keep, std::unique_ptr<melonDS::GBACart::CartCommon>>;
|
||||||
|
namespace melonDS
|
||||||
|
{
|
||||||
|
class NDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScreenPanelGL;
|
||||||
|
|
||||||
|
class EmuThread : public QThread
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
void run() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit EmuThread(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
void changeWindowTitle(char* title);
|
||||||
|
|
||||||
|
// to be called from the UI thread
|
||||||
|
void emuRun();
|
||||||
|
void emuPause();
|
||||||
|
void emuUnpause();
|
||||||
|
void emuStop();
|
||||||
|
void emuFrameStep();
|
||||||
|
|
||||||
|
bool emuIsRunning();
|
||||||
|
bool emuIsActive();
|
||||||
|
|
||||||
|
void initContext();
|
||||||
|
void deinitContext();
|
||||||
|
|
||||||
|
int FrontBuffer = 0;
|
||||||
|
QMutex FrontBufferLock;
|
||||||
|
|
||||||
|
/// Applies the config in args.
|
||||||
|
/// Creates a new NDS console if needed,
|
||||||
|
/// modifies the existing one if possible.
|
||||||
|
/// @return \c true if the console was updated.
|
||||||
|
/// If this returns \c false, then the existing NDS console is not modified.
|
||||||
|
bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept;
|
||||||
|
std::unique_ptr<melonDS::NDS> NDS; // TODO: Proper encapsulation and synchronization
|
||||||
|
signals:
|
||||||
|
void windowUpdate();
|
||||||
|
void windowTitleChange(QString title);
|
||||||
|
|
||||||
|
void windowEmuStart();
|
||||||
|
void windowEmuStop();
|
||||||
|
void windowEmuPause();
|
||||||
|
void windowEmuReset();
|
||||||
|
void windowEmuFrameStep();
|
||||||
|
|
||||||
|
void windowLimitFPSChange();
|
||||||
|
|
||||||
|
void screenLayoutChange();
|
||||||
|
|
||||||
|
void windowFullscreenToggle();
|
||||||
|
|
||||||
|
void swapScreensToggle();
|
||||||
|
void screenEmphasisToggle();
|
||||||
|
|
||||||
|
void syncVolumeLevel();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<melonDS::NDS> CreateConsole(
|
||||||
|
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
|
||||||
|
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
|
||||||
|
) noexcept;
|
||||||
|
|
||||||
|
enum EmuStatusKind
|
||||||
|
{
|
||||||
|
emuStatus_Exit,
|
||||||
|
emuStatus_Running,
|
||||||
|
emuStatus_Paused,
|
||||||
|
emuStatus_FrameStep,
|
||||||
|
};
|
||||||
|
std::atomic<EmuStatusKind> EmuStatus;
|
||||||
|
|
||||||
|
EmuStatusKind PrevEmuStatus;
|
||||||
|
EmuStatusKind EmuRunning;
|
||||||
|
|
||||||
|
constexpr static int EmuPauseStackRunning = 0;
|
||||||
|
constexpr static int EmuPauseStackPauseThreshold = 1;
|
||||||
|
int EmuPauseStack;
|
||||||
|
|
||||||
|
enum ContextRequestKind
|
||||||
|
{
|
||||||
|
contextRequest_None = 0,
|
||||||
|
contextRequest_InitGL,
|
||||||
|
contextRequest_DeInitGL
|
||||||
|
};
|
||||||
|
std::atomic<ContextRequestKind> ContextRequest = contextRequest_None;
|
||||||
|
|
||||||
|
ScreenPanelGL* screenGL;
|
||||||
|
|
||||||
|
int autoScreenSizing;
|
||||||
|
|
||||||
|
int videoRenderer;
|
||||||
|
bool videoSettingsDirty;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EMUTHREAD_H
|
@ -89,7 +89,6 @@ using namespace melonDS;
|
|||||||
extern MainWindow* mainWindow;
|
extern MainWindow* mainWindow;
|
||||||
extern EmuThread* emuThread;
|
extern EmuThread* emuThread;
|
||||||
extern bool RunningSomething;
|
extern bool RunningSomething;
|
||||||
extern int autoScreenSizing;
|
|
||||||
extern QString NdsRomMimeType;
|
extern QString NdsRomMimeType;
|
||||||
extern QStringList NdsRomExtensions;
|
extern QStringList NdsRomExtensions;
|
||||||
extern QString GbaRomMimeType;
|
extern QString GbaRomMimeType;
|
||||||
|
@ -175,756 +175,6 @@ bool camStarted[2];
|
|||||||
//extern int AspectRatiosNum;
|
//extern int AspectRatiosNum;
|
||||||
|
|
||||||
|
|
||||||
EmuThread::EmuThread(QObject* parent) : QThread(parent)
|
|
||||||
{
|
|
||||||
EmuStatus = emuStatus_Exit;
|
|
||||||
EmuRunning = emuStatus_Paused;
|
|
||||||
EmuPauseStack = EmuPauseStackRunning;
|
|
||||||
RunningSomething = false;
|
|
||||||
|
|
||||||
connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint()));
|
|
||||||
connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString)));
|
|
||||||
connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart()));
|
|
||||||
connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop()));
|
|
||||||
connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger()));
|
|
||||||
connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger()));
|
|
||||||
connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger()));
|
|
||||||
connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger()));
|
|
||||||
connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged()));
|
|
||||||
connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled()));
|
|
||||||
connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger()));
|
|
||||||
connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled()));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<NDS> EmuThread::CreateConsole(
|
|
||||||
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
|
|
||||||
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
|
|
||||||
) noexcept
|
|
||||||
{
|
|
||||||
auto arm7bios = ROMManager::LoadARM7BIOS();
|
|
||||||
if (!arm7bios)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto arm9bios = ROMManager::LoadARM9BIOS();
|
|
||||||
if (!arm9bios)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto firmware = ROMManager::LoadFirmware(Config::ConsoleType);
|
|
||||||
if (!firmware)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
#ifdef JIT_ENABLED
|
|
||||||
JITArgs jitargs {
|
|
||||||
static_cast<unsigned>(Config::JIT_MaxBlockSize),
|
|
||||||
Config::JIT_LiteralOptimisations,
|
|
||||||
Config::JIT_BranchOptimisations,
|
|
||||||
Config::JIT_FastMemory,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef GDBSTUB_ENABLED
|
|
||||||
GDBArgs gdbargs {
|
|
||||||
static_cast<u16>(Config::GdbPortARM7),
|
|
||||||
static_cast<u16>(Config::GdbPortARM9),
|
|
||||||
Config::GdbARM7BreakOnStartup,
|
|
||||||
Config::GdbARM9BreakOnStartup,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
NDSArgs ndsargs {
|
|
||||||
std::move(ndscart),
|
|
||||||
std::move(gbacart),
|
|
||||||
*arm9bios,
|
|
||||||
*arm7bios,
|
|
||||||
std::move(*firmware),
|
|
||||||
#ifdef JIT_ENABLED
|
|
||||||
Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt,
|
|
||||||
#else
|
|
||||||
std::nullopt,
|
|
||||||
#endif
|
|
||||||
static_cast<AudioBitDepth>(Config::AudioBitDepth),
|
|
||||||
static_cast<AudioInterpolation>(Config::AudioInterp),
|
|
||||||
#ifdef GDBSTUB_ENABLED
|
|
||||||
Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt,
|
|
||||||
#else
|
|
||||||
std::nullopt,
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Config::ConsoleType == 1)
|
|
||||||
{
|
|
||||||
auto arm7ibios = ROMManager::LoadDSiARM7BIOS();
|
|
||||||
if (!arm7ibios)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto arm9ibios = ROMManager::LoadDSiARM9BIOS();
|
|
||||||
if (!arm9ibios)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto nand = ROMManager::LoadNAND(*arm7ibios);
|
|
||||||
if (!nand)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto sdcard = ROMManager::LoadDSiSDCard();
|
|
||||||
DSiArgs args {
|
|
||||||
std::move(ndsargs),
|
|
||||||
*arm9ibios,
|
|
||||||
*arm7ibios,
|
|
||||||
std::move(*nand),
|
|
||||||
std::move(sdcard),
|
|
||||||
Config::DSiFullBIOSBoot,
|
|
||||||
};
|
|
||||||
|
|
||||||
args.GBAROM = nullptr;
|
|
||||||
|
|
||||||
return std::make_unique<melonDS::DSi>(std::move(args));
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_unique<melonDS::NDS>(std::move(ndsargs));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept
|
|
||||||
{
|
|
||||||
// Let's get the cart we want to use;
|
|
||||||
// if we wnat to keep the cart, we'll eject it from the existing console first.
|
|
||||||
std::unique_ptr<NDSCart::CartCommon> nextndscart;
|
|
||||||
if (std::holds_alternative<Keep>(ndsargs))
|
|
||||||
{ // If we want to keep the existing cart (if any)...
|
|
||||||
nextndscart = NDS ? NDS->EjectCart() : nullptr;
|
|
||||||
ndsargs = {};
|
|
||||||
}
|
|
||||||
else if (const auto ptr = std::get_if<std::unique_ptr<NDSCart::CartCommon>>(&ndsargs))
|
|
||||||
{
|
|
||||||
nextndscart = std::move(*ptr);
|
|
||||||
ndsargs = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextndscart && nextndscart->Type() == NDSCart::Homebrew)
|
|
||||||
{
|
|
||||||
// Load DLDISDCard will return nullopt if the SD card is disabled;
|
|
||||||
// SetSDCard will accept nullopt, which means no SD card
|
|
||||||
auto& homebrew = static_cast<NDSCart::CartHomebrew&>(*nextndscart);
|
|
||||||
homebrew.SetSDCard(ROMManager::LoadDLDISDCard());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<GBACart::CartCommon> nextgbacart;
|
|
||||||
if (std::holds_alternative<Keep>(gbaargs))
|
|
||||||
{
|
|
||||||
nextgbacart = NDS ? NDS->EjectGBACart() : nullptr;
|
|
||||||
}
|
|
||||||
else if (const auto ptr = std::get_if<std::unique_ptr<GBACart::CartCommon>>(&gbaargs))
|
|
||||||
{
|
|
||||||
nextgbacart = std::move(*ptr);
|
|
||||||
gbaargs = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!NDS || NDS->ConsoleType != Config::ConsoleType)
|
|
||||||
{ // If we're switching between DS and DSi mode, or there's no console...
|
|
||||||
// To ensure the destructor is called before a new one is created,
|
|
||||||
// as the presence of global signal handlers still complicates things a bit
|
|
||||||
NDS = nullptr;
|
|
||||||
NDS::Current = nullptr;
|
|
||||||
|
|
||||||
NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart));
|
|
||||||
|
|
||||||
if (NDS == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
NDS->Reset();
|
|
||||||
NDS::Current = NDS.get();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto arm9bios = ROMManager::LoadARM9BIOS();
|
|
||||||
if (!arm9bios)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto arm7bios = ROMManager::LoadARM7BIOS();
|
|
||||||
if (!arm7bios)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType);
|
|
||||||
if (!firmware)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (NDS->ConsoleType == 1)
|
|
||||||
{ // If the console we're updating is a DSi...
|
|
||||||
DSi& dsi = static_cast<DSi&>(*NDS);
|
|
||||||
|
|
||||||
auto arm9ibios = ROMManager::LoadDSiARM9BIOS();
|
|
||||||
if (!arm9ibios)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto arm7ibios = ROMManager::LoadDSiARM7BIOS();
|
|
||||||
if (!arm7ibios)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto nandimage = ROMManager::LoadNAND(*arm7ibios);
|
|
||||||
if (!nandimage)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto dsisdcard = ROMManager::LoadDSiSDCard();
|
|
||||||
|
|
||||||
dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot);
|
|
||||||
dsi.ARM7iBIOS = *arm7ibios;
|
|
||||||
dsi.ARM9iBIOS = *arm9ibios;
|
|
||||||
dsi.SetNAND(std::move(*nandimage));
|
|
||||||
dsi.SetSDCard(std::move(dsisdcard));
|
|
||||||
// We're moving the optional, not the card
|
|
||||||
// (inserting std::nullopt here is okay, it means no card)
|
|
||||||
|
|
||||||
dsi.EjectGBACart();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NDS->ConsoleType == 0)
|
|
||||||
{
|
|
||||||
NDS->SetGBACart(std::move(nextgbacart));
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef JIT_ENABLED
|
|
||||||
JITArgs jitargs {
|
|
||||||
static_cast<unsigned>(Config::JIT_MaxBlockSize),
|
|
||||||
Config::JIT_LiteralOptimisations,
|
|
||||||
Config::JIT_BranchOptimisations,
|
|
||||||
Config::JIT_FastMemory,
|
|
||||||
};
|
|
||||||
NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt);
|
|
||||||
#endif
|
|
||||||
NDS->SetARM7BIOS(*arm7bios);
|
|
||||||
NDS->SetARM9BIOS(*arm9bios);
|
|
||||||
NDS->SetFirmware(std::move(*firmware));
|
|
||||||
NDS->SetNDSCart(std::move(nextndscart));
|
|
||||||
NDS->SPU.SetInterpolation(static_cast<AudioInterpolation>(Config::AudioInterp));
|
|
||||||
NDS->SPU.SetDegrade10Bit(static_cast<AudioBitDepth>(Config::AudioBitDepth));
|
|
||||||
|
|
||||||
NDS::Current = NDS.get();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::run()
|
|
||||||
{
|
|
||||||
u32 mainScreenPos[3];
|
|
||||||
Platform::FileHandle* file;
|
|
||||||
|
|
||||||
UpdateConsole(nullptr, nullptr);
|
|
||||||
// No carts are inserted when melonDS first boots
|
|
||||||
|
|
||||||
mainScreenPos[0] = 0;
|
|
||||||
mainScreenPos[1] = 0;
|
|
||||||
mainScreenPos[2] = 0;
|
|
||||||
autoScreenSizing = 0;
|
|
||||||
|
|
||||||
videoSettingsDirty = false;
|
|
||||||
|
|
||||||
if (mainWindow->hasOGL)
|
|
||||||
{
|
|
||||||
screenGL = static_cast<ScreenPanelGL*>(mainWindow->panel);
|
|
||||||
screenGL->initOpenGL();
|
|
||||||
videoRenderer = Config::_3DRenderer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
screenGL = nullptr;
|
|
||||||
videoRenderer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoRenderer == 0)
|
|
||||||
{ // If we're using the software renderer...
|
|
||||||
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>(Config::Threaded3D != 0));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto glrenderer = melonDS::GLRenderer::New();
|
|
||||||
glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
|
|
||||||
NDS->GPU.SetRenderer3D(std::move(glrenderer));
|
|
||||||
}
|
|
||||||
|
|
||||||
Input::Init();
|
|
||||||
|
|
||||||
u32 nframes = 0;
|
|
||||||
double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency();
|
|
||||||
double lastTime = SDL_GetPerformanceCounter() * perfCountsSec;
|
|
||||||
double frameLimitError = 0.0;
|
|
||||||
double lastMeasureTime = lastTime;
|
|
||||||
|
|
||||||
u32 winUpdateCount = 0, winUpdateFreq = 1;
|
|
||||||
u8 dsiVolumeLevel = 0x1F;
|
|
||||||
|
|
||||||
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read);
|
|
||||||
if (file)
|
|
||||||
{
|
|
||||||
RTC::StateData state;
|
|
||||||
Platform::FileRead(&state, sizeof(state), 1, file);
|
|
||||||
Platform::CloseFile(file);
|
|
||||||
NDS->RTC.SetState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
char melontitle[100];
|
|
||||||
|
|
||||||
while (EmuRunning != emuStatus_Exit)
|
|
||||||
{
|
|
||||||
Input::Process();
|
|
||||||
|
|
||||||
if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
|
|
||||||
|
|
||||||
if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause();
|
|
||||||
if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset();
|
|
||||||
if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep();
|
|
||||||
|
|
||||||
if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle();
|
|
||||||
|
|
||||||
if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle();
|
|
||||||
if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle();
|
|
||||||
|
|
||||||
if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep)
|
|
||||||
{
|
|
||||||
EmuStatus = emuStatus_Running;
|
|
||||||
if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused;
|
|
||||||
|
|
||||||
if (Input::HotkeyPressed(HK_SolarSensorDecrease))
|
|
||||||
{
|
|
||||||
int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true);
|
|
||||||
if (level != -1)
|
|
||||||
{
|
|
||||||
char msg[64];
|
|
||||||
sprintf(msg, "Solar sensor level: %d", level);
|
|
||||||
OSD::AddMessage(0, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Input::HotkeyPressed(HK_SolarSensorIncrease))
|
|
||||||
{
|
|
||||||
int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true);
|
|
||||||
if (level != -1)
|
|
||||||
{
|
|
||||||
char msg[64];
|
|
||||||
sprintf(msg, "Solar sensor level: %d", level);
|
|
||||||
OSD::AddMessage(0, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NDS->ConsoleType == 1)
|
|
||||||
{
|
|
||||||
DSi& dsi = static_cast<DSi&>(*NDS);
|
|
||||||
double currentTime = SDL_GetPerformanceCounter() * perfCountsSec;
|
|
||||||
|
|
||||||
// Handle power button
|
|
||||||
if (Input::HotkeyDown(HK_PowerButton))
|
|
||||||
{
|
|
||||||
dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime);
|
|
||||||
}
|
|
||||||
else if (Input::HotkeyReleased(HK_PowerButton))
|
|
||||||
{
|
|
||||||
dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle volume buttons
|
|
||||||
if (Input::HotkeyDown(HK_VolumeUp))
|
|
||||||
{
|
|
||||||
dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up);
|
|
||||||
}
|
|
||||||
else if (Input::HotkeyReleased(HK_VolumeUp))
|
|
||||||
{
|
|
||||||
dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input::HotkeyDown(HK_VolumeDown))
|
|
||||||
{
|
|
||||||
dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down);
|
|
||||||
}
|
|
||||||
else if (Input::HotkeyReleased(HK_VolumeDown))
|
|
||||||
{
|
|
||||||
dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down);
|
|
||||||
}
|
|
||||||
|
|
||||||
dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update render settings if needed
|
|
||||||
// HACK:
|
|
||||||
// once the fast forward hotkey is released, we need to update vsync
|
|
||||||
// to the old setting again
|
|
||||||
if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward))
|
|
||||||
{
|
|
||||||
if (screenGL)
|
|
||||||
{
|
|
||||||
screenGL->setSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0);
|
|
||||||
videoRenderer = Config::_3DRenderer;
|
|
||||||
}
|
|
||||||
#ifdef OGLRENDERER_ENABLED
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
videoRenderer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
videoRenderer = screenGL ? Config::_3DRenderer : 0;
|
|
||||||
|
|
||||||
videoSettingsDirty = false;
|
|
||||||
|
|
||||||
if (videoRenderer == 0)
|
|
||||||
{ // If we're using the software renderer...
|
|
||||||
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>(Config::Threaded3D != 0));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto glrenderer = melonDS::GLRenderer::New();
|
|
||||||
glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
|
|
||||||
NDS->GPU.SetRenderer3D(std::move(glrenderer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// process input and hotkeys
|
|
||||||
NDS->SetKeyMask(Input::InputMask);
|
|
||||||
|
|
||||||
if (Input::HotkeyPressed(HK_Lid))
|
|
||||||
{
|
|
||||||
bool lid = !NDS->IsLidClosed();
|
|
||||||
NDS->SetLidClosed(lid);
|
|
||||||
OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened");
|
|
||||||
}
|
|
||||||
|
|
||||||
// microphone input
|
|
||||||
AudioInOut::MicProcess(*NDS);
|
|
||||||
|
|
||||||
// auto screen layout
|
|
||||||
if (Config::ScreenSizing == Frontend::screenSizing_Auto)
|
|
||||||
{
|
|
||||||
mainScreenPos[2] = mainScreenPos[1];
|
|
||||||
mainScreenPos[1] = mainScreenPos[0];
|
|
||||||
mainScreenPos[0] = NDS->PowerControl9 >> 15;
|
|
||||||
|
|
||||||
int guess;
|
|
||||||
if (mainScreenPos[0] == mainScreenPos[2] &&
|
|
||||||
mainScreenPos[0] != mainScreenPos[1])
|
|
||||||
{
|
|
||||||
// constant flickering, likely displaying 3D on both screens
|
|
||||||
// TODO: when both screens are used for 2D only...???
|
|
||||||
guess = Frontend::screenSizing_Even;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (mainScreenPos[0] == 1)
|
|
||||||
guess = Frontend::screenSizing_EmphTop;
|
|
||||||
else
|
|
||||||
guess = Frontend::screenSizing_EmphBot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (guess != autoScreenSizing)
|
|
||||||
{
|
|
||||||
autoScreenSizing = guess;
|
|
||||||
emit screenLayoutChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// emulate
|
|
||||||
u32 nlines = NDS->RunFrame();
|
|
||||||
|
|
||||||
if (ROMManager::NDSSave)
|
|
||||||
ROMManager::NDSSave->CheckFlush();
|
|
||||||
|
|
||||||
if (ROMManager::GBASave)
|
|
||||||
ROMManager::GBASave->CheckFlush();
|
|
||||||
|
|
||||||
if (ROMManager::FirmwareSave)
|
|
||||||
ROMManager::FirmwareSave->CheckFlush();
|
|
||||||
|
|
||||||
if (!screenGL)
|
|
||||||
{
|
|
||||||
FrontBufferLock.lock();
|
|
||||||
FrontBuffer = NDS->GPU.FrontBuffer;
|
|
||||||
FrontBufferLock.unlock();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FrontBuffer = NDS->GPU.FrontBuffer;
|
|
||||||
screenGL->drawScreenGL();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef MELONCAP
|
|
||||||
MelonCap::Update();
|
|
||||||
#endif // MELONCAP
|
|
||||||
|
|
||||||
if (EmuRunning == emuStatus_Exit) break;
|
|
||||||
|
|
||||||
winUpdateCount++;
|
|
||||||
if (winUpdateCount >= winUpdateFreq && !screenGL)
|
|
||||||
{
|
|
||||||
emit windowUpdate();
|
|
||||||
winUpdateCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fastforward = Input::HotkeyDown(HK_FastForward);
|
|
||||||
|
|
||||||
if (fastforward && screenGL && Config::ScreenVSync)
|
|
||||||
{
|
|
||||||
screenGL->setSwapInterval(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config::DSiVolumeSync && NDS->ConsoleType == 1)
|
|
||||||
{
|
|
||||||
DSi& dsi = static_cast<DSi&>(*NDS);
|
|
||||||
u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel();
|
|
||||||
if (volumeLevel != dsiVolumeLevel)
|
|
||||||
{
|
|
||||||
dsiVolumeLevel = volumeLevel;
|
|
||||||
emit syncVolumeLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
Config::AudioVolume = volumeLevel * (256.0 / 31.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config::AudioSync && !fastforward)
|
|
||||||
AudioInOut::AudioSync(*emuThread->NDS);
|
|
||||||
|
|
||||||
double frametimeStep = nlines / (60.0 * 263.0);
|
|
||||||
|
|
||||||
{
|
|
||||||
bool limitfps = Config::LimitFPS && !fastforward;
|
|
||||||
|
|
||||||
double practicalFramelimit = limitfps ? frametimeStep : 1.0 / 1000.0;
|
|
||||||
|
|
||||||
double curtime = SDL_GetPerformanceCounter() * perfCountsSec;
|
|
||||||
|
|
||||||
frameLimitError += practicalFramelimit - (curtime - lastTime);
|
|
||||||
if (frameLimitError < -practicalFramelimit)
|
|
||||||
frameLimitError = -practicalFramelimit;
|
|
||||||
if (frameLimitError > practicalFramelimit)
|
|
||||||
frameLimitError = practicalFramelimit;
|
|
||||||
|
|
||||||
if (round(frameLimitError * 1000.0) > 0.0)
|
|
||||||
{
|
|
||||||
SDL_Delay(round(frameLimitError * 1000.0));
|
|
||||||
double timeBeforeSleep = curtime;
|
|
||||||
curtime = SDL_GetPerformanceCounter() * perfCountsSec;
|
|
||||||
frameLimitError -= curtime - timeBeforeSleep;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastTime = curtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
nframes++;
|
|
||||||
if (nframes >= 30)
|
|
||||||
{
|
|
||||||
double time = SDL_GetPerformanceCounter() * perfCountsSec;
|
|
||||||
double dt = time - lastMeasureTime;
|
|
||||||
lastMeasureTime = time;
|
|
||||||
|
|
||||||
u32 fps = round(nframes / dt);
|
|
||||||
nframes = 0;
|
|
||||||
|
|
||||||
float fpstarget = 1.0/frametimeStep;
|
|
||||||
|
|
||||||
winUpdateFreq = fps / (u32)round(fpstarget);
|
|
||||||
if (winUpdateFreq < 1)
|
|
||||||
winUpdateFreq = 1;
|
|
||||||
|
|
||||||
int inst = Platform::InstanceID();
|
|
||||||
if (inst == 0)
|
|
||||||
sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
|
|
||||||
else
|
|
||||||
sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1);
|
|
||||||
changeWindowTitle(melontitle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// paused
|
|
||||||
nframes = 0;
|
|
||||||
lastTime = SDL_GetPerformanceCounter() * perfCountsSec;
|
|
||||||
lastMeasureTime = lastTime;
|
|
||||||
|
|
||||||
emit windowUpdate();
|
|
||||||
|
|
||||||
EmuStatus = EmuRunning;
|
|
||||||
|
|
||||||
int inst = Platform::InstanceID();
|
|
||||||
if (inst == 0)
|
|
||||||
sprintf(melontitle, "melonDS " MELONDS_VERSION);
|
|
||||||
else
|
|
||||||
sprintf(melontitle, "melonDS (%d)", inst+1);
|
|
||||||
changeWindowTitle(melontitle);
|
|
||||||
|
|
||||||
SDL_Delay(75);
|
|
||||||
|
|
||||||
if (screenGL)
|
|
||||||
screenGL->drawScreenGL();
|
|
||||||
|
|
||||||
ContextRequestKind contextRequest = ContextRequest;
|
|
||||||
if (contextRequest == contextRequest_InitGL)
|
|
||||||
{
|
|
||||||
screenGL = static_cast<ScreenPanelGL*>(mainWindow->panel);
|
|
||||||
screenGL->initOpenGL();
|
|
||||||
ContextRequest = contextRequest_None;
|
|
||||||
}
|
|
||||||
else if (contextRequest == contextRequest_DeInitGL)
|
|
||||||
{
|
|
||||||
screenGL->deinitOpenGL();
|
|
||||||
screenGL = nullptr;
|
|
||||||
ContextRequest = contextRequest_None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write);
|
|
||||||
if (file)
|
|
||||||
{
|
|
||||||
RTC::StateData state;
|
|
||||||
NDS->RTC.GetState(state);
|
|
||||||
Platform::FileWrite(&state, sizeof(state), 1, file);
|
|
||||||
Platform::CloseFile(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
EmuStatus = emuStatus_Exit;
|
|
||||||
|
|
||||||
NDS::Current = nullptr;
|
|
||||||
// nds is out of scope, so unique_ptr cleans it up for us
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::changeWindowTitle(char* title)
|
|
||||||
{
|
|
||||||
emit windowTitleChange(QString(title));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::emuRun()
|
|
||||||
{
|
|
||||||
EmuRunning = emuStatus_Running;
|
|
||||||
EmuPauseStack = EmuPauseStackRunning;
|
|
||||||
RunningSomething = true;
|
|
||||||
|
|
||||||
// checkme
|
|
||||||
emit windowEmuStart();
|
|
||||||
AudioInOut::Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::initContext()
|
|
||||||
{
|
|
||||||
ContextRequest = contextRequest_InitGL;
|
|
||||||
while (ContextRequest != contextRequest_None);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::deinitContext()
|
|
||||||
{
|
|
||||||
ContextRequest = contextRequest_DeInitGL;
|
|
||||||
while (ContextRequest != contextRequest_None);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::emuPause()
|
|
||||||
{
|
|
||||||
EmuPauseStack++;
|
|
||||||
if (EmuPauseStack > EmuPauseStackPauseThreshold) return;
|
|
||||||
|
|
||||||
PrevEmuStatus = EmuRunning;
|
|
||||||
EmuRunning = emuStatus_Paused;
|
|
||||||
while (EmuStatus != emuStatus_Paused);
|
|
||||||
|
|
||||||
AudioInOut::Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::emuUnpause()
|
|
||||||
{
|
|
||||||
if (EmuPauseStack < EmuPauseStackPauseThreshold) return;
|
|
||||||
|
|
||||||
EmuPauseStack--;
|
|
||||||
if (EmuPauseStack >= EmuPauseStackPauseThreshold) return;
|
|
||||||
|
|
||||||
EmuRunning = PrevEmuStatus;
|
|
||||||
|
|
||||||
AudioInOut::Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::emuStop()
|
|
||||||
{
|
|
||||||
EmuRunning = emuStatus_Exit;
|
|
||||||
EmuPauseStack = EmuPauseStackRunning;
|
|
||||||
|
|
||||||
AudioInOut::Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuThread::emuFrameStep()
|
|
||||||
{
|
|
||||||
if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause();
|
|
||||||
EmuRunning = emuStatus_FrameStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EmuThread::emuIsRunning()
|
|
||||||
{
|
|
||||||
return EmuRunning == emuStatus_Running;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EmuThread::emuIsActive()
|
|
||||||
{
|
|
||||||
return (RunningSomething == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*void EmuThread::drawScreenGL()
|
|
||||||
{
|
|
||||||
if (!NDS) return;
|
|
||||||
int w = windowInfo.surface_width;
|
|
||||||
int h = windowInfo.surface_height;
|
|
||||||
float factor = windowInfo.surface_scale;
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glDepthMask(false);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
glDisable(GL_STENCIL_TEST);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
glViewport(0, 0, w, h);
|
|
||||||
|
|
||||||
glUseProgram(screenShaderProgram[2]);
|
|
||||||
glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor);
|
|
||||||
|
|
||||||
int frontbuf = FrontBuffer;
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
|
|
||||||
#ifdef OGLRENDERER_ENABLED
|
|
||||||
if (NDS->GPU.GetRenderer3D().Accelerated)
|
|
||||||
{
|
|
||||||
// hardware-accelerated render
|
|
||||||
static_cast<GLRenderer&>(NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
// regular render
|
|
||||||
glBindTexture(GL_TEXTURE_2D, screenTexture);
|
|
||||||
|
|
||||||
if (NDS->GPU.Framebuffer[frontbuf][0] && NDS->GPU.Framebuffer[frontbuf][1])
|
|
||||||
{
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA,
|
|
||||||
GL_UNSIGNED_BYTE, NDS->GPU.Framebuffer[frontbuf][0].get());
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA,
|
|
||||||
GL_UNSIGNED_BYTE, NDS->GPU.Framebuffer[frontbuf][1].get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
screenSettingsLock.lock();
|
|
||||||
|
|
||||||
GLint filter = this->filter ? GL_LINEAR : GL_NEAREST;
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
|
|
||||||
glBindVertexArray(screenVertexArray);
|
|
||||||
|
|
||||||
for (int i = 0; i < numScreens; i++)
|
|
||||||
{
|
|
||||||
glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]);
|
|
||||||
glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3);
|
|
||||||
}
|
|
||||||
|
|
||||||
screenSettingsLock.unlock();
|
|
||||||
|
|
||||||
OSD::Update();
|
|
||||||
OSD::DrawGL(w, h);
|
|
||||||
|
|
||||||
oglContext->SwapBuffers();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,141 +22,18 @@
|
|||||||
#include "glad/glad.h"
|
#include "glad/glad.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QThread>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QMutex>
|
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <variant>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
#include "EmuThread.h"
|
||||||
#include "FrontendUtil.h"
|
#include "FrontendUtil.h"
|
||||||
#include "duckstation/gl/context.h"
|
|
||||||
|
|
||||||
#include "NDSCart.h"
|
|
||||||
#include "GBACart.h"
|
|
||||||
|
|
||||||
using Keep = std::monostate;
|
|
||||||
using UpdateConsoleNDSArgs = std::variant<Keep, std::unique_ptr<melonDS::NDSCart::CartCommon>>;
|
|
||||||
using UpdateConsoleGBAArgs = std::variant<Keep, std::unique_ptr<melonDS::GBACart::CartCommon>>;
|
|
||||||
namespace melonDS
|
|
||||||
{
|
|
||||||
class NDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EmuThread : public QThread
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
void run() override;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit EmuThread(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
void changeWindowTitle(char* title);
|
|
||||||
|
|
||||||
// to be called from the UI thread
|
|
||||||
void emuRun();
|
|
||||||
void emuPause();
|
|
||||||
void emuUnpause();
|
|
||||||
void emuStop();
|
|
||||||
void emuFrameStep();
|
|
||||||
|
|
||||||
bool emuIsRunning();
|
|
||||||
bool emuIsActive();
|
|
||||||
|
|
||||||
void initContext();
|
|
||||||
void deinitContext();
|
|
||||||
|
|
||||||
int FrontBuffer = 0;
|
|
||||||
QMutex FrontBufferLock;
|
|
||||||
|
|
||||||
//void updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix);
|
|
||||||
|
|
||||||
/// Applies the config in args.
|
|
||||||
/// Creates a new NDS console if needed,
|
|
||||||
/// modifies the existing one if possible.
|
|
||||||
/// @return \c true if the console was updated.
|
|
||||||
/// If this returns \c false, then the existing NDS console is not modified.
|
|
||||||
bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept;
|
|
||||||
std::unique_ptr<melonDS::NDS> NDS; // TODO: Proper encapsulation and synchronization
|
|
||||||
signals:
|
|
||||||
void windowUpdate();
|
|
||||||
void windowTitleChange(QString title);
|
|
||||||
|
|
||||||
void windowEmuStart();
|
|
||||||
void windowEmuStop();
|
|
||||||
void windowEmuPause();
|
|
||||||
void windowEmuReset();
|
|
||||||
void windowEmuFrameStep();
|
|
||||||
|
|
||||||
void windowLimitFPSChange();
|
|
||||||
|
|
||||||
void screenLayoutChange();
|
|
||||||
|
|
||||||
void windowFullscreenToggle();
|
|
||||||
|
|
||||||
void swapScreensToggle();
|
|
||||||
void screenEmphasisToggle();
|
|
||||||
|
|
||||||
void syncVolumeLevel();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<melonDS::NDS> CreateConsole(
|
|
||||||
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
|
|
||||||
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
|
|
||||||
) noexcept;
|
|
||||||
//void drawScreenGL();
|
|
||||||
//void initOpenGL();
|
|
||||||
//void deinitOpenGL();
|
|
||||||
|
|
||||||
enum EmuStatusKind
|
|
||||||
{
|
|
||||||
emuStatus_Exit,
|
|
||||||
emuStatus_Running,
|
|
||||||
emuStatus_Paused,
|
|
||||||
emuStatus_FrameStep,
|
|
||||||
};
|
|
||||||
std::atomic<EmuStatusKind> EmuStatus;
|
|
||||||
|
|
||||||
EmuStatusKind PrevEmuStatus;
|
|
||||||
EmuStatusKind EmuRunning;
|
|
||||||
|
|
||||||
constexpr static int EmuPauseStackRunning = 0;
|
|
||||||
constexpr static int EmuPauseStackPauseThreshold = 1;
|
|
||||||
int EmuPauseStack;
|
|
||||||
|
|
||||||
enum ContextRequestKind
|
|
||||||
{
|
|
||||||
contextRequest_None = 0,
|
|
||||||
contextRequest_InitGL,
|
|
||||||
contextRequest_DeInitGL
|
|
||||||
};
|
|
||||||
std::atomic<ContextRequestKind> ContextRequest = contextRequest_None;
|
|
||||||
|
|
||||||
/*GL::Context* oglContext = nullptr;
|
|
||||||
GLuint screenVertexBuffer, screenVertexArray;
|
|
||||||
GLuint screenTexture;
|
|
||||||
GLuint screenShaderProgram[3];
|
|
||||||
GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc;
|
|
||||||
|
|
||||||
QMutex screenSettingsLock;
|
|
||||||
WindowInfo windowInfo;
|
|
||||||
float screenMatrix[Frontend::MaxScreenTransforms][6];
|
|
||||||
int screenKind[Frontend::MaxScreenTransforms];
|
|
||||||
int numScreens;
|
|
||||||
bool filter;
|
|
||||||
|
|
||||||
int lastScreenWidth = -1, lastScreenHeight = -1;*/
|
|
||||||
ScreenPanelGL* screenGL;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MelonApplication : public QApplication
|
class MelonApplication : public QApplication
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user