aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/frontend/qt_sdl/CMakeLists.txt1
-rw-r--r--src/frontend/qt_sdl/EmuThread.cpp758
-rw-r--r--src/frontend/qt_sdl/EmuThread.h134
-rw-r--r--src/frontend/qt_sdl/Window.cpp1
-rw-r--r--src/frontend/qt_sdl/main.cpp750
-rw-r--r--src/frontend/qt_sdl/main.h125
6 files changed, 894 insertions, 875 deletions
diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt
index c2fa5b5..9281021 100644
--- a/src/frontend/qt_sdl/CMakeLists.txt
+++ b/src/frontend/qt_sdl/CMakeLists.txt
@@ -7,6 +7,7 @@ set(SOURCES_QT_SDL
main_shaders.h
Screen.cpp
Window.cpp
+ EmuThread.cpp
CheatsDialog.cpp
Config.cpp
DateTimeDialog.cpp
diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp
new file mode 100644
index 0000000..f86d30b
--- /dev/null
+++ b/src/frontend/qt_sdl/EmuThread.cpp
@@ -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);
+}
diff --git a/src/frontend/qt_sdl/EmuThread.h b/src/frontend/qt_sdl/EmuThread.h
new file mode 100644
index 0000000..4950ebb
--- /dev/null
+++ b/src/frontend/qt_sdl/EmuThread.h
@@ -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
diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp
index 7e86354..7b67ec5 100644
--- a/src/frontend/qt_sdl/Window.cpp
+++ b/src/frontend/qt_sdl/Window.cpp
@@ -89,7 +89,6 @@ using namespace melonDS;
extern MainWindow* mainWindow;
extern EmuThread* emuThread;
extern bool RunningSomething;
-extern int autoScreenSizing;
extern QString NdsRomMimeType;
extern QStringList NdsRomExtensions;
extern QString GbaRomMimeType;
diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp
index e8859ef..2f08c37 100644
--- a/src/frontend/qt_sdl/main.cpp
+++ b/src/frontend/qt_sdl/main.cpp
@@ -175,756 +175,6 @@ bool camStarted[2];
//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();
-}*/
diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h
index 4034ba3..51157c6 100644
--- a/src/frontend/qt_sdl/main.h
+++ b/src/frontend/qt_sdl/main.h
@@ -22,141 +22,18 @@
#include "glad/glad.h"
#include <QApplication>
-#include <QThread>
#include <QWidget>
#include <QWindow>
#include <QMainWindow>
#include <QImage>
#include <QActionGroup>
#include <QTimer>
-#include <QMutex>
#include <QScreen>
#include <QCloseEvent>
-#include <atomic>
-#include <variant>
-#include <optional>
-
#include "Window.h"
+#include "EmuThread.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
{