diff options
author | Arisotura <thetotalworm@gmail.com> | 2023-12-26 19:24:14 +0100 |
---|---|---|
committer | Arisotura <thetotalworm@gmail.com> | 2023-12-26 19:24:14 +0100 |
commit | f905b6fb93e1ee35ad39f78eef88d221dda26537 (patch) | |
tree | a5c95713855487563f45e5c93ddbf3b32e866ea0 /src | |
parent | fd1e4379b99c01be0ebcfa8cde390a51ca09f716 (diff) |
separate EmuThread to its own file
Diffstat (limited to 'src')
-rw-r--r-- | src/frontend/qt_sdl/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/frontend/qt_sdl/EmuThread.cpp | 758 | ||||
-rw-r--r-- | src/frontend/qt_sdl/EmuThread.h | 134 | ||||
-rw-r--r-- | src/frontend/qt_sdl/Window.cpp | 1 | ||||
-rw-r--r-- | src/frontend/qt_sdl/main.cpp | 750 | ||||
-rw-r--r-- | src/frontend/qt_sdl/main.h | 125 |
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 { |