diff options
| -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  { |