diff --git a/.travis-deps.sh b/.travis-deps.sh index c76d4c6d6..3056dd4cb 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -3,15 +3,23 @@ if [ $TRAVIS_OS_NAME = "osx" ]; then brew update brew install qt5 ffmpeg imagemagick sdl2 libzip libpng if [ "$CC" == "gcc" ]; then - brew install gcc@4.9 - export CC=gcc-4.9 - export CXX=g++-4.9 + brew install gcc@5 + export CC=gcc-5 + export CXX=g++-5 fi else sudo apt-get clean + sudo add-apt-repository -y ppa:george-edison55/cmake-3.x sudo apt-get update sudo apt-get install -y -q cmake libedit-dev libmagickwand-dev \ libpng-dev libsdl2-dev libzip-dev qtbase5-dev \ libqt5opengl5-dev qtmultimedia5-dev libavcodec-dev \ libavutil-dev libavformat-dev libavresample-dev libswscale-dev + if [ "$CC" == "gcc" ]; then + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get install -y -q gcc-5 g++-5 + export CC=gcc-5 + export CXX=g++-5 + fi fi diff --git a/.travis.yml b/.travis.yml index 2c25e739f..a3691414c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,4 +16,4 @@ matrix: before_install: - source ./.travis-deps.sh -script: mkdir build && cd build && cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 .. && make +script: mkdir build && cd build && cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 .. && make -j2 diff --git a/CHANGES b/CHANGES index 1def18bfe..38ce42b1f 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Misc: - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) - All: Make FIXED_ROM_BUFFER an option instead of 3DS-only - Qt: Don't rebuild library view if style hasn't changed + - Qt: Redo GameController into multiple classes 0.6.0: (2017-07-16) Features: diff --git a/CMakeLists.txt b/CMakeLists.txt index e29d732ef..3ba280ecd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.11) +cmake_minimum_required(VERSION 3.1) project(mGBA) set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries") if(NOT MSVC) diff --git a/include/mgba/core/thread.h b/include/mgba/core/thread.h index d6e4566a3..420533762 100644 --- a/include/mgba/core/thread.h +++ b/include/mgba/core/thread.h @@ -103,6 +103,7 @@ void mCoreThreadWaitFromThread(struct mCoreThread* threadContext); void mCoreThreadStopWaiting(struct mCoreThread* threadContext); void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool); +void mCoreThreadRewindParamsChanged(struct mCoreThread* threadContext); struct mCoreThread* mCoreThreadGet(void); struct mLogger* mCoreThreadLogger(void); diff --git a/src/core/rewind.c b/src/core/rewind.c index f75229195..6a4da46c7 100644 --- a/src/core/rewind.c +++ b/src/core/rewind.c @@ -19,6 +19,9 @@ THREAD_ENTRY _rewindThread(void* context); #endif void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries, bool onThread) { + if (context->currentState) { + return; + } mCoreRewindPatchesInit(&context->patchMemory, entries); size_t e; for (e = 0; e < entries; ++e) { @@ -42,6 +45,9 @@ void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries, } void mCoreRewindContextDeinit(struct mCoreRewindContext* context) { + if (!context->currentState) { + return; + } #ifndef DISABLE_THREADING if (context->onThread) { MutexLock(&context->mutex); @@ -55,6 +61,8 @@ void mCoreRewindContextDeinit(struct mCoreRewindContext* context) { #endif context->previousState->close(context->previousState); context->currentState->close(context->currentState); + context->previousState = NULL; + context->currentState = NULL; size_t s; for (s = 0; s < mCoreRewindPatchesSize(&context->patchMemory); ++s) { deinitPatchFast(mCoreRewindPatchesGetPointer(&context->patchMemory, s)); diff --git a/src/core/thread.c b/src/core/thread.c index 16b22d25e..782b573df 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -167,10 +167,7 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { mLogFilterLoad(threadContext->logger.d.filter, &core->config); } - if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) { - mCoreRewindContextInit(&threadContext->impl->rewind, core->opts.rewindBufferCapacity, true); - threadContext->impl->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0; - } + mCoreThreadRewindParamsChanged(threadContext); _changeState(threadContext->impl, THREAD_RUNNING, true); @@ -252,7 +249,7 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { } bool mCoreThreadStart(struct mCoreThread* threadContext) { - threadContext->impl = malloc(sizeof(*threadContext->impl)); + threadContext->impl = calloc(sizeof(*threadContext->impl), 1); threadContext->impl->state = THREAD_INITIALIZED; threadContext->logger.p = threadContext; if (!threadContext->logger.d.log) { @@ -547,6 +544,16 @@ void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) MutexUnlock(&threadContext->impl->stateMutex); } +void mCoreThreadRewindParamsChanged(struct mCoreThread* threadContext) { + struct mCore* core = threadContext->core; + if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) { + mCoreRewindContextInit(&threadContext->impl->rewind, core->opts.rewindBufferCapacity, true); + threadContext->impl->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0; + } else { + mCoreRewindContextDeinit(&threadContext->impl->rewind); + } +} + void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) { MutexLock(&threadContext->impl->stateMutex); if (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_RUNNING) { diff --git a/src/platform/qt/AssetTile.cpp b/src/platform/qt/AssetTile.cpp index a97895c4b..c82ffe424 100644 --- a/src/platform/qt/AssetTile.cpp +++ b/src/platform/qt/AssetTile.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AssetTile.h" +#include "CoreController.h" #include "GBAApp.h" #include @@ -39,7 +40,7 @@ AssetTile::AssetTile(QWidget* parent) m_ui.b->setFont(font); } -void AssetTile::setController(GameController* controller) { +void AssetTile::setController(std::shared_ptr controller) { m_tileCache = controller->tileCache(); switch (controller->platform()) { #ifdef M_CORE_GBA @@ -83,7 +84,7 @@ void AssetTile::selectIndex(int index) { m_index = index; const uint16_t* data; - mTileCacheSetPalette(m_tileCache.get(), m_paletteSet); + mTileCacheSetPalette(m_tileCache, m_paletteSet); unsigned bpp = 8 << m_tileCache->bpp; int dispIndex = index; int paletteId = m_paletteId; @@ -98,7 +99,7 @@ void AssetTile::selectIndex(int index) { #endif dispIndex -= m_boundary; } - data = mTileCacheGetTile(m_tileCache.get(), index, paletteId); + data = mTileCacheGetTile(m_tileCache, index, paletteId); m_ui.tileId->setText(QString::number(dispIndex * (1 + m_paletteSet))); m_ui.address->setText(tr("%0%1%2") .arg(m_addressWidth == 4 ? index >= m_boundary : 0) @@ -112,7 +113,7 @@ void AssetTile::selectIndex(int index) { void AssetTile::selectColor(int index) { const uint16_t* data; - mTileCacheSetPalette(m_tileCache.get(), m_paletteSet); + mTileCacheSetPalette(m_tileCache, m_paletteSet); unsigned bpp = 8 << m_tileCache->bpp; int paletteId = m_paletteId; // XXX: Do this better @@ -121,7 +122,7 @@ void AssetTile::selectColor(int index) { paletteId += m_tileCache->count / 2; } #endif - data = mTileCacheGetTile(m_tileCache.get(), m_index, m_paletteId); + data = mTileCacheGetTile(m_tileCache, m_index, m_paletteId); uint16_t color = data[index]; m_ui.color->setColor(0, color); m_ui.color->update(); diff --git a/src/platform/qt/AssetTile.h b/src/platform/qt/AssetTile.h index beb42a21a..7d5f77be6 100644 --- a/src/platform/qt/AssetTile.h +++ b/src/platform/qt/AssetTile.h @@ -6,20 +6,22 @@ #ifndef QGBA_ASSET_TILE #define QGBA_ASSET_TILE -#include "GameController.h" - #include "ui_AssetTile.h" +#include + #include namespace QGBA { +class CoreController; + class AssetTile : public QGroupBox { Q_OBJECT public: AssetTile(QWidget* parent = nullptr); - void setController(GameController*); + void setController(std::shared_ptr); public slots: void setPalette(int); @@ -30,7 +32,7 @@ public slots: private: Ui::AssetTile m_ui; - std::shared_ptr m_tileCache; + mTileCache* m_tileCache; int m_paletteId = 0; int m_paletteSet = 0; int m_index = 0; diff --git a/src/platform/qt/AssetView.cpp b/src/platform/qt/AssetView.cpp index 0077cae8a..5ad124ad4 100644 --- a/src/platform/qt/AssetView.cpp +++ b/src/platform/qt/AssetView.cpp @@ -5,32 +5,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AssetView.h" -#include +#include "CoreController.h" -#include +#include using namespace QGBA; -AssetView::AssetView(GameController* controller, QWidget* parent) +AssetView::AssetView(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_tileCache(controller->tileCache()) , m_controller(controller) { m_updateTimer.setSingleShot(true); m_updateTimer.setInterval(1); - connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles())); + connect(&m_updateTimer, &QTimer::timeout, this, static_cast(&AssetView::updateTiles)); - connect(m_controller, &GameController::frameAvailable, &m_updateTimer, + connect(controller.get(), &CoreController::frameAvailable, &m_updateTimer, static_cast(&QTimer::start)); - connect(m_controller, &GameController::gameStopped, this, &AssetView::close); - connect(m_controller, &GameController::gameStopped, &m_updateTimer, &QTimer::stop); + connect(controller.get(), &CoreController::stopping, this, &AssetView::close); + connect(controller.get(), &CoreController::stopping, &m_updateTimer, &QTimer::stop); +} + +void AssetView::updateTiles() { + updateTiles(false); } void AssetView::updateTiles(bool force) { - if (!m_controller->isLoaded()) { - return; - } - switch (m_controller->platform()) { #ifdef M_CORE_GBA case PLATFORM_GBA: @@ -56,7 +56,7 @@ void AssetView::showEvent(QShowEvent*) { } void AssetView::compositeTile(unsigned tileId, void* buffer, size_t stride, size_t x, size_t y, int depth) { - const uint8_t* tile = mTileCacheGetRawTile(m_tileCache.get(), tileId); + const uint8_t* tile = mTileCacheGetRawTile(m_tileCache, tileId); uint8_t* pixels = static_cast(buffer); size_t base = stride * y + x; switch (depth) { diff --git a/src/platform/qt/AssetView.h b/src/platform/qt/AssetView.h index 76dae708b..aabc79ab1 100644 --- a/src/platform/qt/AssetView.h +++ b/src/platform/qt/AssetView.h @@ -6,22 +6,28 @@ #ifndef QGBA_ASSET_VIEW #define QGBA_ASSET_VIEW +#include #include -#include "GameController.h" +#include + +#include namespace QGBA { +class CoreController; + class AssetView : public QWidget { Q_OBJECT public: - AssetView(GameController* controller, QWidget* parent = nullptr); + AssetView(std::shared_ptr controller, QWidget* parent = nullptr); void compositeTile(unsigned tileId, void* image, size_t stride, size_t x, size_t y, int depth = 8); protected slots: - void updateTiles(bool force = false); + void updateTiles(); + void updateTiles(bool force); protected: #ifdef M_CORE_GBA @@ -34,10 +40,10 @@ protected: void resizeEvent(QResizeEvent*) override; void showEvent(QShowEvent*) override; - const std::shared_ptr m_tileCache; + mTileCache* const m_tileCache; private: - GameController* m_controller; + std::shared_ptr m_controller; QTimer m_updateTimer; }; diff --git a/src/platform/qt/AudioProcessor.cpp b/src/platform/qt/AudioProcessor.cpp index 6f0b49cbe..96f04d58b 100644 --- a/src/platform/qt/AudioProcessor.cpp +++ b/src/platform/qt/AudioProcessor.cpp @@ -47,10 +47,18 @@ AudioProcessor::AudioProcessor(QObject* parent) { } -void AudioProcessor::setInput(mCoreThread* input) { +AudioProcessor::~AudioProcessor() { + stop(); +} + +void AudioProcessor::setInput(std::shared_ptr input) { m_context = input; } +void AudioProcessor::stop() { + m_context.reset(); +} + void AudioProcessor::setBufferSamples(int samples) { m_samples = samples; } diff --git a/src/platform/qt/AudioProcessor.h b/src/platform/qt/AudioProcessor.h index 9636f0bd4..16a9179f9 100644 --- a/src/platform/qt/AudioProcessor.h +++ b/src/platform/qt/AudioProcessor.h @@ -7,6 +7,10 @@ #define QGBA_AUDIO_PROCESSOR #include +#include + +#include "CoreController.h" + struct mCoreThread; namespace QGBA { @@ -28,12 +32,14 @@ public: static void setDriver(Driver driver) { s_driver = driver; } AudioProcessor(QObject* parent = nullptr); + ~AudioProcessor(); int getBufferSamples() const { return m_samples; } virtual unsigned sampleRate() const = 0; public slots: - virtual void setInput(mCoreThread* input); + virtual void setInput(std::shared_ptr); + virtual void stop(); virtual bool start() = 0; virtual void pause() = 0; @@ -44,10 +50,10 @@ public slots: virtual void requestSampleRate(unsigned) = 0; protected: - mCoreThread* input() { return m_context; } + mCoreThread* input() { return m_context->thread(); } private: - mCoreThread* m_context = nullptr; + std::shared_ptr m_context; int m_samples = 2048; static Driver s_driver; }; diff --git a/src/platform/qt/AudioProcessorQt.cpp b/src/platform/qt/AudioProcessorQt.cpp index b802d825b..8896e2c37 100644 --- a/src/platform/qt/AudioProcessorQt.cpp +++ b/src/platform/qt/AudioProcessorQt.cpp @@ -20,16 +20,24 @@ AudioProcessorQt::AudioProcessorQt(QObject* parent) { } -void AudioProcessorQt::setInput(mCoreThread* input) { - AudioProcessor::setInput(input); +void AudioProcessorQt::setInput(std::shared_ptr controller) { + AudioProcessor::setInput(controller); if (m_device) { - m_device->setInput(input); + m_device->setInput(input()); if (m_audioOutput) { m_device->setFormat(m_audioOutput->format()); } } } +void AudioProcessorQt::stop() { + if (m_device) { + m_device.reset(); + } + pause(); + AudioProcessor::stop(); +} + bool AudioProcessorQt::start() { if (!input()) { LOG(QT, WARN) << tr("Can't start an audio processor without input"); @@ -37,7 +45,7 @@ bool AudioProcessorQt::start() { } if (!m_device) { - m_device = new AudioDevice(this); + m_device = std::make_unique(this); } if (!m_audioOutput) { @@ -56,7 +64,7 @@ bool AudioProcessorQt::start() { m_device->setInput(input()); m_device->setFormat(m_audioOutput->format()); - m_audioOutput->start(m_device); + m_audioOutput->start(m_device.get()); return m_audioOutput->state() == QAudio::ActiveState; } diff --git a/src/platform/qt/AudioProcessorQt.h b/src/platform/qt/AudioProcessorQt.h index ecf517a15..cd2b9bf67 100644 --- a/src/platform/qt/AudioProcessorQt.h +++ b/src/platform/qt/AudioProcessorQt.h @@ -22,7 +22,8 @@ public: virtual unsigned sampleRate() const override; public slots: - virtual void setInput(mCoreThread* input) override; + virtual void setInput(std::shared_ptr input) override; + virtual void stop() override; virtual bool start() override; virtual void pause() override; @@ -33,7 +34,7 @@ public slots: private: QAudioOutput* m_audioOutput = nullptr; - AudioDevice* m_device = nullptr; + std::unique_ptr m_device; unsigned m_sampleRate = 44100; }; diff --git a/src/platform/qt/AudioProcessorSDL.cpp b/src/platform/qt/AudioProcessorSDL.cpp index 3980108e7..8682ded58 100644 --- a/src/platform/qt/AudioProcessorSDL.cpp +++ b/src/platform/qt/AudioProcessorSDL.cpp @@ -16,16 +16,17 @@ AudioProcessorSDL::AudioProcessorSDL(QObject* parent) { } -AudioProcessorSDL::~AudioProcessorSDL() { - mSDLDeinitAudio(&m_audio); +void AudioProcessorSDL::setInput(std::shared_ptr controller) { + AudioProcessor::setInput(controller); + if (m_audio.core && input()->core != m_audio.core) { + mSDLDeinitAudio(&m_audio); + mSDLInitAudio(&m_audio, input()); + } } -void AudioProcessorSDL::setInput(mCoreThread* input) { - AudioProcessor::setInput(input); - if (m_audio.core && input->core != m_audio.core) { - mSDLDeinitAudio(&m_audio); - mSDLInitAudio(&m_audio, input); - } +void AudioProcessorSDL::stop() { + mSDLDeinitAudio(&m_audio); + AudioProcessor::stop(); } bool AudioProcessorSDL::start() { @@ -51,10 +52,12 @@ void AudioProcessorSDL::pause() { void AudioProcessorSDL::setBufferSamples(int samples) { AudioProcessor::setBufferSamples(samples); - m_audio.samples = samples; - if (m_audio.core) { - mSDLDeinitAudio(&m_audio); - mSDLInitAudio(&m_audio, input()); + if (m_audio.samples != samples) { + m_audio.samples = samples; + if (m_audio.core) { + mSDLDeinitAudio(&m_audio); + mSDLInitAudio(&m_audio, input()); + } } } @@ -62,10 +65,12 @@ void AudioProcessorSDL::inputParametersChanged() { } void AudioProcessorSDL::requestSampleRate(unsigned rate) { - m_audio.sampleRate = rate; - if (m_audio.core) { - mSDLDeinitAudio(&m_audio); - mSDLInitAudio(&m_audio, input()); + if (m_audio.sampleRate != rate) { + m_audio.sampleRate = rate; + if (m_audio.core) { + mSDLDeinitAudio(&m_audio); + mSDLInitAudio(&m_audio, input()); + } } } diff --git a/src/platform/qt/AudioProcessorSDL.h b/src/platform/qt/AudioProcessorSDL.h index e0e354729..07de6e90b 100644 --- a/src/platform/qt/AudioProcessorSDL.h +++ b/src/platform/qt/AudioProcessorSDL.h @@ -18,12 +18,12 @@ Q_OBJECT public: AudioProcessorSDL(QObject* parent = nullptr); - ~AudioProcessorSDL(); virtual unsigned sampleRate() const override; public slots: - virtual void setInput(mCoreThread* input) override; + virtual void setInput(std::shared_ptr input) override; + virtual void stop() override; virtual bool start() override; virtual void pause() override; diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 5f0c28e20..dc1468c99 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -1,6 +1,6 @@ -if(NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -endif() +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7") @@ -66,13 +66,14 @@ set(SOURCE_FILES CheatsModel.cpp CheatsView.cpp ConfigController.cpp + CoreManager.cpp + CoreController.cpp Display.cpp DisplayGL.cpp DisplayQt.cpp GBAApp.cpp GBAKeyEditor.cpp GIFView.cpp - GameController.cpp GamepadAxisEvent.cpp GamepadButtonEvent.cpp GamepadHatEvent.cpp @@ -280,7 +281,7 @@ if(APPLE) if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") get_target_property(QTCOCOA Qt5::QCocoaIntegrationPlugin LOCATION) get_target_property(COREAUDIO Qt5::CoreAudioPlugin LOCATION) - get_target_property(BUNDLE_PATH ${BINARY_NAME}-qt LOCATION) + set(BUNDLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.app) target_sources(${BINARY_NAME}-qt PRIVATE "${PLUGINS}") set_source_files_properties("${QTCOCOA}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp index 73ffa09ab..80e398b4b 100644 --- a/src/platform/qt/CheatsView.cpp +++ b/src/platform/qt/CheatsView.cpp @@ -6,7 +6,7 @@ #include "CheatsView.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include #include @@ -21,7 +21,7 @@ using namespace QGBA; -CheatsView::CheatsView(GameController* controller, QWidget* parent) +CheatsView::CheatsView(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) , m_model(controller->cheatDevice()) @@ -35,8 +35,8 @@ CheatsView::CheatsView(GameController* controller, QWidget* parent) connect(m_ui.save, &QPushButton::clicked, this, &CheatsView::save); connect(m_ui.addSet, &QPushButton::clicked, this, &CheatsView::addSet); connect(m_ui.remove, &QPushButton::clicked, this, &CheatsView::removeSet); - connect(controller, &GameController::gameStopped, this, &CheatsView::close); - connect(controller, &GameController::stateLoaded, &m_model, &CheatsModel::invalidated); + connect(controller.get(), &CoreController::stopping, this, &CheatsView::close); + connect(controller.get(), &CoreController::stateLoaded, &m_model, &CheatsModel::invalidated); QPushButton* add; switch (controller->platform()) { @@ -123,7 +123,7 @@ void CheatsView::save() { } void CheatsView::addSet() { - GameController::Interrupter interrupter(m_controller); + CoreController::Interrupter interrupter(m_controller); mCheatSet* set = m_controller->cheatDevice()->createSet(m_controller->cheatDevice(), nullptr); m_model.addSet(set); } @@ -134,7 +134,7 @@ void CheatsView::removeSet() { if (selection.count() < 1) { return; } - GameController::Interrupter interrupter(m_controller); + CoreController::Interrupter interrupter(m_controller); for (const QModelIndex& index : selection) { m_model.removeAt(selection[0]); } @@ -154,7 +154,7 @@ void CheatsView::enterCheat(int codeType) { if (!set) { return; } - m_controller->threadInterrupt(); + CoreController::Interrupter interrupter(m_controller); if (selection.count() == 0) { m_model.addSet(set); index = m_model.index(m_model.rowCount() - 1, 0, QModelIndex()); @@ -167,6 +167,5 @@ void CheatsView::enterCheat(int codeType) { m_model.endAppendRow(); } set->refresh(set, m_controller->cheatDevice()); - m_controller->threadContinue(); m_ui.codeEntry->clear(); } diff --git a/src/platform/qt/CheatsView.h b/src/platform/qt/CheatsView.h index 69c15c83f..63ea7bd3e 100644 --- a/src/platform/qt/CheatsView.h +++ b/src/platform/qt/CheatsView.h @@ -9,6 +9,7 @@ #include #include +#include #include "CheatsModel.h" @@ -18,13 +19,13 @@ struct mCheatDevice; namespace QGBA { -class GameController; +class CoreController; class CheatsView : public QWidget { Q_OBJECT public: - CheatsView(GameController* controller, QWidget* parent = nullptr); + CheatsView(std::shared_ptr controller, QWidget* parent = nullptr); virtual bool eventFilter(QObject*, QEvent*) override; @@ -38,7 +39,7 @@ private: void enterCheat(int codeType); Ui::CheatsView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; CheatsModel m_model; }; diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 7832b052c..9136f1679 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ConfigController.h" -#include "GameController.h" +#include "CoreController.h" #include #include @@ -98,8 +98,8 @@ ConfigController::ConfigController(QObject* parent) mCoreConfigInit(&m_config, PORT); - m_opts.audioSync = GameController::AUDIO_SYNC; - m_opts.videoSync = GameController::VIDEO_SYNC; + m_opts.audioSync = CoreController::AUDIO_SYNC; + m_opts.videoSync = CoreController::VIDEO_SYNC; m_opts.fpsTarget = 60; m_opts.audioBuffers = 1536; m_opts.sampleRate = 44100; diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp new file mode 100644 index 000000000..0da9443f5 --- /dev/null +++ b/src/platform/qt/CoreController.cpp @@ -0,0 +1,725 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "CoreController.h" + +#include "ConfigController.h" +#include "InputController.h" +#include "LogController.h" +#include "MultiplayerController.h" +#include "Override.h" + +#include +#include + +#include +#include +#ifdef M_CORE_GBA +#include +#include +#include +#endif +#ifdef M_CORE_GB +#include +#include +#endif +#include + +using namespace QGBA; + + +CoreController::CoreController(mCore* core, QObject* parent) + : QObject(parent) + , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC) + , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC) +{ + m_threadContext.core = core; + m_threadContext.userData = this; + + QSize size = screenDimensions(); + m_buffers[0].resize(size.width() * size.height() * sizeof(color_t)); + m_buffers[1].resize(size.width() * size.height() * sizeof(color_t)); + m_activeBuffer = &m_buffers[0]; + + m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer->data()), size.width()); + + m_threadContext.startCallback = [](mCoreThread* context) { + CoreController* controller = static_cast(context->userData); + context->core->setPeripheral(context->core, mPERIPH_ROTATION, controller->m_inputController->rotationSource()); + context->core->setPeripheral(context->core, mPERIPH_RUMBLE, controller->m_inputController->rumble()); + + switch (context->core->platform(context->core)) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, controller->m_inputController->luminance()); + break; +#endif + default: + break; + } + + if (controller->m_override) { + controller->m_override->identify(context->core); + controller->m_override->apply(context->core); + } + + if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) { + mCoreDeleteState(context->core, 0); + } + + if (controller->m_multiplayer) { + controller->m_multiplayer->attachGame(controller); + } + + QMetaObject::invokeMethod(controller, "started"); + }; + + m_threadContext.resetCallback = [](mCoreThread* context) { + CoreController* controller = static_cast(context->userData); + for (auto action : controller->m_resetActions) { + action(); + } + controller->m_resetActions.clear(); + + controller->m_activeBuffer->fill(0xFF); + controller->finishFrame(); + }; + + m_threadContext.frameCallback = [](mCoreThread* context) { + CoreController* controller = static_cast(context->userData); + + controller->finishFrame(); + }; + + m_threadContext.cleanCallback = [](mCoreThread* context) { + CoreController* controller = static_cast(context->userData); + + controller->clearMultiplayerController(); + QMetaObject::invokeMethod(controller, "stopping"); + }; + + m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) { + mThreadLogger* logContext = reinterpret_cast(logger); + mCoreThread* context = logContext->p; + + static const char* savestateMessage = "State %i loaded"; + static const char* savestateFailedMessage = "State %i failed to load"; + static int biosCat = -1; + static int statusCat = -1; + if (!context) { + return; + } + CoreController* controller = static_cast(context->userData); + QString message; + if (biosCat < 0) { + biosCat = mLogCategoryById("gba.bios"); + } + if (statusCat < 0) { + statusCat = mLogCategoryById("core.status"); + } +#ifdef M_CORE_GBA + if (level == mLOG_STUB && category == biosCat) { + va_list argc; + va_copy(argc, args); + int immediate = va_arg(argc, int); + va_end(argc); + QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate)); + } else +#endif + if (category == statusCat) { + // Slot 0 is reserved for suspend points + if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { + va_list argc; + va_copy(argc, args); + int slot = va_arg(argc, int); + va_end(argc); + if (slot == 0) { + format = "Loaded suspend state"; + } + } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) { + va_list argc; + va_copy(argc, args); + int slot = va_arg(argc, int); + va_end(argc); + if (slot == 0) { + return; + } + } + message = QString().vsprintf(format, args); + QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message)); + } + if (level == mLOG_FATAL) { + mCoreThreadMarkCrashed(controller->thread()); + QMetaObject::invokeMethod(controller, "crashed", Q_ARG(const QString&, QString().vsprintf(format, args))); + } + message = QString().vsprintf(format, args); + QMetaObject::invokeMethod(controller, "logPosted", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message)); + }; +} + +CoreController::~CoreController() { + endVideoLog(); + stop(); + disconnect(); + + if (m_tileCache) { + mTileCacheDeinit(m_tileCache.get()); + m_tileCache.reset(); + } + + mCoreThreadJoin(&m_threadContext); + + mCoreConfigDeinit(&m_threadContext.core->config); + m_threadContext.core->deinit(m_threadContext.core); +} + +color_t* CoreController::drawContext() { + QMutexLocker locker(&m_mutex); + if (!m_completeBuffer) { + return nullptr; + } + return reinterpret_cast(m_completeBuffer->data()); +} + +bool CoreController::isPaused() { + return mCoreThreadIsPaused(&m_threadContext); +} + +mPlatform CoreController::platform() const { + return m_threadContext.core->platform(m_threadContext.core); +} + +QSize CoreController::screenDimensions() const { + unsigned width, height; + m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); + + return QSize(width, height); +} + +void CoreController::loadConfig(ConfigController* config) { + Interrupter interrupter(this); + m_loadStateFlags = config->getOption("loadStateExtdata").toInt(); + m_saveStateFlags = config->getOption("saveStateExtdata").toInt(); + m_fastForwardRatio = config->getOption("fastForwardRatio").toFloat(); + m_videoSync = config->getOption("videoSync").toInt(); + m_audioSync = config->getOption("audioSync").toInt(); + m_fpsTarget = config->getOption("fpsTarget").toFloat(); + updateFastForward(); + mCoreLoadForeignConfig(m_threadContext.core, config->config()); + mCoreThreadRewindParamsChanged(&m_threadContext); +} + +#ifdef USE_DEBUGGERS +void CoreController::setDebugger(mDebugger* debugger) { + Interrupter interrupter(this); + if (debugger) { + mDebuggerAttach(debugger, m_threadContext.core); + } else { + m_threadContext.core->detachDebugger(m_threadContext.core); + } +} +#endif + +void CoreController::setMultiplayerController(MultiplayerController* controller) { + if (controller == m_multiplayer) { + return; + } + clearMultiplayerController(); + m_multiplayer = controller; + if (!mCoreThreadHasStarted(&m_threadContext)) { + return; + } + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) { + CoreController* controller = static_cast(thread->userData); + controller->m_multiplayer->attachGame(controller); + }); +} + +void CoreController::clearMultiplayerController() { + if (!m_multiplayer) { + return; + } + m_multiplayer->detachGame(this); + m_multiplayer = nullptr; +} + +mTileCache* CoreController::tileCache() { + if (m_tileCache) { + return m_tileCache.get(); + } + Interrupter interrupter(this); + switch (platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: { + GBA* gba = static_cast(m_threadContext.core->board); + m_tileCache = std::make_unique(); + GBAVideoTileCacheInit(m_tileCache.get()); + GBAVideoTileCacheAssociate(m_tileCache.get(), &gba->video); + mTileCacheSetPalette(m_tileCache.get(), 0); + break; + } +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: { + GB* gb = static_cast(m_threadContext.core->board); + m_tileCache = std::make_unique(); + GBVideoTileCacheInit(m_tileCache.get()); + GBVideoTileCacheAssociate(m_tileCache.get(), &gb->video); + mTileCacheSetPalette(m_tileCache.get(), 0); + break; + } +#endif + default: + return nullptr; + } + return m_tileCache.get(); +} + +void CoreController::setOverride(std::unique_ptr override) { + Interrupter interrupter(this); + m_override = std::move(override); + m_override->identify(m_threadContext.core); +} + +void CoreController::setInputController(InputController* inputController) { + m_inputController = inputController; +} + +void CoreController::setLogger(LogController* logger) { + disconnect(m_log); + m_log = logger; + m_threadContext.logger.d.filter = logger->filter(); + connect(this, &CoreController::logPosted, m_log, &LogController::postLog); +} + +void CoreController::start() { + if (!m_patched) { + mCoreAutoloadPatch(m_threadContext.core); + } + if (!mCoreThreadStart(&m_threadContext)) { + emit failed(); + emit stopping(); + } +} + +void CoreController::stop() { +#ifdef USE_DEBUGGERS + setDebugger(nullptr); +#endif + setPaused(false); + mCoreThreadEnd(&m_threadContext); + emit stopping(); +} + +void CoreController::reset() { + bool wasPaused = isPaused(); + setPaused(false); + Interrupter interrupter(this); + mCoreThreadReset(&m_threadContext); + if (wasPaused) { + setPaused(true); + } +} + +void CoreController::setPaused(bool paused) { + if (paused == isPaused()) { + return; + } + if (paused) { + QMutexLocker locker(&m_mutex); + m_frameActions.append([this]() { + mCoreThreadPauseFromThread(&m_threadContext); + QMetaObject::invokeMethod(this, "paused"); + }); + } else { + mCoreThreadUnpause(&m_threadContext); + emit unpaused(); + } +} + +void CoreController::frameAdvance() { + QMutexLocker locker(&m_mutex); + m_frameActions.append([this]() { + mCoreThreadPauseFromThread(&m_threadContext); + }); + setPaused(false); +} + +void CoreController::setSync(bool sync) { + if (sync) { + m_threadContext.impl->sync.audioWait = m_audioSync; + m_threadContext.impl->sync.videoFrameWait = m_videoSync; + } else { + m_threadContext.impl->sync.audioWait = false; + m_threadContext.impl->sync.videoFrameWait = false; + } +} + +void CoreController::setRewinding(bool rewind) { + if (!m_threadContext.core->opts.rewindEnable) { + return; + } + if (rewind && m_multiplayer && m_multiplayer->attached() > 1) { + return; + } + + if (rewind && isPaused()) { + setPaused(false); + // TODO: restore autopausing + } + mCoreThreadSetRewinding(&m_threadContext, rewind); +} + +void CoreController::rewind(int states) { + { + Interrupter interrupter(this); + if (!states) { + states = INT_MAX; + } + for (int i = 0; i < states; ++i) { + if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) { + break; + } + } + } + emit frameAvailable(); + emit rewound(); +} + +void CoreController::setFastForward(bool enable) { + m_fastForward = enable; + updateFastForward(); +} + +void CoreController::forceFastForward(bool enable) { + m_fastForwardForced = enable; + updateFastForward(); +} + +void CoreController::loadState(int slot) { + if (slot > 0 && slot != m_stateSlot) { + m_stateSlot = slot; + m_backupSaveState.clear(); + } + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast(context->userData); + if (!controller->m_backupLoadState.isOpen()) { + controller->m_backupLoadState = VFileMemChunk(nullptr, 0); + } + mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); + if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) { + emit controller->frameAvailable(); + emit controller->stateLoaded(); + } + }); +} + +void CoreController::saveState(int slot) { + if (slot > 0) { + m_stateSlot = slot; + } + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast(context->userData); + VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false); + if (vf) { + controller->m_backupSaveState.resize(vf->size(vf)); + vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); + vf->close(vf); + } + mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags); + }); +} + +void CoreController::loadBackupState() { + if (!m_backupLoadState.isOpen()) { + return; + } + + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast(context->userData); + controller->m_backupLoadState.seek(0); + if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) { + mLOG(STATUS, INFO, "Undid state load"); + controller->frameAvailable(); + controller->stateLoaded(); + } + controller->m_backupLoadState.close(); + }); +} + +void CoreController::saveBackupState() { + if (m_backupSaveState.isEmpty()) { + return; + } + + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast(context->userData); + VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true); + if (vf) { + vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size()); + vf->close(vf); + mLOG(STATUS, INFO, "Undid state save"); + } + controller->m_backupSaveState.clear(); + }); +} + +void CoreController::loadSave(const QString& path, bool temporary) { + m_resetActions.append([this, path, temporary]() { + VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR); + if (!vf) { + LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path); + return; + } + + if (temporary) { + m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); + } else { + m_threadContext.core->loadSave(m_threadContext.core, vf); + } + }); + reset(); +} + +void CoreController::loadPatch(const QString& patchPath) { + Interrupter interrupter(this); + VFile* patch = VFileDevice::open(patchPath, O_RDONLY); + if (patch) { + m_threadContext.core->loadPatch(m_threadContext.core, patch); + m_patched = true; + } + patch->close(patch); + if (mCoreThreadHasStarted(&m_threadContext)) { + reset(); + } +} + +void CoreController::replaceGame(const QString& path) { + QFileInfo info(path); + if (!info.isReadable()) { + LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); + return; + } + QString fname = info.canonicalFilePath(); + Interrupter interrupter(this); + mDirectorySetDetachBase(&m_threadContext.core->dirs); + mCoreLoadFile(m_threadContext.core, fname.toLocal8Bit().constData()); +} + +void CoreController::yankPak() { +#ifdef M_CORE_GBA + if (platform() != PLATFORM_GBA) { + return; + } + Interrupter interrupter(this); + GBAYankROM(static_cast(m_threadContext.core->board)); +#endif +} + +void CoreController::addKey(int key) { + m_activeKeys |= 1 << key; +} + +void CoreController::clearKey(int key) { + m_activeKeys &= ~(1 << key); +} + +void CoreController::setAutofire(int key, bool enable) { + if (key >= 32 || key < 0) { + return; + } + + m_autofire[key] = enable; + m_autofireStatus[key] = 0; +} + +#ifdef USE_PNG +void CoreController::screenshot() { + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + mCoreTakeScreenshot(context->core); + }); +} +#endif + +void CoreController::setRealTime() { + m_threadContext.core->rtc.override = RTC_NO_OVERRIDE; +} + +void CoreController::setFixedTime(const QDateTime& time) { + m_threadContext.core->rtc.override = RTC_FIXED; + m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); +} + +void CoreController::setFakeEpoch(const QDateTime& time) { + m_threadContext.core->rtc.override = RTC_FAKE_EPOCH; + m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); +} + +void CoreController::importSharkport(const QString& path) { +#ifdef M_CORE_GBA + if (platform() != PLATFORM_GBA) { + return; + } + VFile* vf = VFileDevice::open(path, O_RDONLY); + if (!vf) { + LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path); + return; + } + Interrupter interrupter(this); + GBASavedataImportSharkPort(static_cast(m_threadContext.core->board), vf, false); + vf->close(vf); +#endif +} + +void CoreController::exportSharkport(const QString& path) { +#ifdef M_CORE_GBA + if (platform() != PLATFORM_GBA) { + return; + } + VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path); + return; + } + Interrupter interrupter(this); + GBASavedataExportSharkPort(static_cast(m_threadContext.core->board), vf); + vf->close(vf); +#endif +} + +void CoreController::setAVStream(mAVStream* stream) { + Interrupter interrupter(this); + m_threadContext.core->setAVStream(m_threadContext.core, stream); +} + +void CoreController::clearAVStream() { + Interrupter interrupter(this); + m_threadContext.core->setAVStream(m_threadContext.core, nullptr); +} + +void CoreController::clearOverride() { + m_override.reset(); +} + +void CoreController::startVideoLog(const QString& path) { + if (m_vl) { + return; + } + + Interrupter interrupter(this); + m_vl = mVideoLogContextCreate(m_threadContext.core); + m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + mVideoLogContextSetOutput(m_vl, m_vlVf); + mVideoLogContextWriteHeader(m_vl, m_threadContext.core); +} + +void CoreController::endVideoLog() { + if (!m_vl) { + return; + } + + Interrupter interrupter(this); + mVideoLogContextDestroy(m_threadContext.core, m_vl); + if (m_vlVf) { + m_vlVf->close(m_vlVf); + m_vlVf = nullptr; + } + m_vl = nullptr; +} + +void CoreController::updateKeys() { + int activeKeys = m_activeKeys | updateAutofire() | m_inputController->pollEvents(); + m_threadContext.core->setKeys(m_threadContext.core, activeKeys); +} + +int CoreController::updateAutofire() { + int active = 0; + for (int k = 0; k < 32; ++k) { + if (!m_autofire[k]) { + continue; + } + ++m_autofireStatus[k]; + if (m_autofireStatus[k]) { + m_autofireStatus[k] = 0; + active |= 1 << k; + } + } + return active; +} + +void CoreController::finishFrame() { + QMutexLocker locker(&m_mutex); + m_completeBuffer = m_activeBuffer; + + // TODO: Generalize this to triple buffering? + m_activeBuffer = &m_buffers[0]; + if (m_activeBuffer == m_completeBuffer) { + m_activeBuffer = &m_buffers[1]; + } + m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer->data()), screenDimensions().width()); + + for (auto& action : m_frameActions) { + action(); + } + m_frameActions.clear(); + updateKeys(); + + QMetaObject::invokeMethod(this, "frameAvailable"); +} + +void CoreController::updateFastForward() { + if (m_fastForward || m_fastForwardForced) { + if (m_fastForwardRatio > 0) { + m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio; + } else { + setSync(false); + } + } else { + m_threadContext.impl->sync.fpsTarget = m_fpsTarget; + setSync(true); + } +} + +CoreController::Interrupter::Interrupter(CoreController* parent, bool fromThread) + : m_parent(parent) +{ + if (!m_parent->thread()->impl) { + return; + } + if (!fromThread) { + mCoreThreadInterrupt(m_parent->thread()); + } else { + mCoreThreadInterruptFromThread(m_parent->thread()); + } +} + +CoreController::Interrupter::Interrupter(std::shared_ptr parent, bool fromThread) + : m_parent(parent.get()) +{ + if (!m_parent->thread()->impl) { + return; + } + if (!fromThread) { + mCoreThreadInterrupt(m_parent->thread()); + } else { + mCoreThreadInterruptFromThread(m_parent->thread()); + } +} + +CoreController::Interrupter::Interrupter(const Interrupter& other) + : m_parent(other.m_parent) +{ + if (!m_parent->thread()->impl) { + return; + } + mCoreThreadInterrupt(m_parent->thread()); +} + +CoreController::Interrupter::~Interrupter() { + if (!m_parent->thread()->impl) { + return; + } + mCoreThreadContinue(m_parent->thread()); +} diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h new file mode 100644 index 000000000..1cd162160 --- /dev/null +++ b/src/platform/qt/CoreController.h @@ -0,0 +1,202 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_CORE_CONTROLLER +#define QGBA_CORE_CONTROLLER + +#include +#include +#include +#include +#include + +#include "VFileDevice.h" + +#include +#include + +#include +#include +#include +#include + +struct mCore; + +namespace QGBA { + +class ConfigController; +class InputController; +class LogController; +class MultiplayerController; +class Override; + +class CoreController : public QObject { +Q_OBJECT + +public: + static const bool VIDEO_SYNC = false; + static const bool AUDIO_SYNC = true; + + class Interrupter { + public: + Interrupter(CoreController*, bool fromThread = false); + Interrupter(std::shared_ptr, bool fromThread = false); + Interrupter(const Interrupter&); + ~Interrupter(); + + private: + CoreController* m_parent; + }; + + CoreController(mCore* core, QObject* parent = nullptr); + ~CoreController(); + + mCoreThread* thread() { return &m_threadContext; } + + color_t* drawContext(); + + bool isPaused(); + + mPlatform platform() const; + QSize screenDimensions() const; + + void loadConfig(ConfigController*); + + mCheatDevice* cheatDevice() { return m_threadContext.core->cheatDevice(m_threadContext.core); } + +#ifdef USE_DEBUGGERS + mDebugger* debugger() { return m_threadContext.core->debugger; } + void setDebugger(mDebugger*); +#endif + + void setMultiplayerController(MultiplayerController*); + void clearMultiplayerController(); + MultiplayerController* multiplayerController() { return m_multiplayer; } + + mTileCache* tileCache(); + int stateSlot() const { return m_stateSlot; } + + void setOverride(std::unique_ptr override); + Override* override() { return m_override.get(); } + + void setInputController(InputController*); + void setLogger(LogController*); + +public slots: + void start(); + void stop(); + void reset(); + void setPaused(bool paused); + void frameAdvance(); + void setSync(bool enable); + + void setRewinding(bool); + void rewind(int count = 0); + + void setFastForward(bool); + void forceFastForward(bool); + + void loadState(int slot = 0); + void saveState(int slot = 0); + void loadBackupState(); + void saveBackupState(); + + void loadSave(const QString&, bool temporary); + void loadPatch(const QString&); + void replaceGame(const QString&); + void yankPak(); + + void addKey(int key); + void clearKey(int key); + void setAutofire(int key, bool enable); + +#ifdef USE_PNG + void screenshot(); +#endif + + void setRealTime(); + void setFixedTime(const QDateTime& time); + void setFakeEpoch(const QDateTime& time); + + void importSharkport(const QString& path); + void exportSharkport(const QString& path); + + void setAVStream(mAVStream*); + void clearAVStream(); + + void clearOverride(); + + void startVideoLog(const QString& path); + void endVideoLog(); + +signals: + void started(); + void paused(); + void unpaused(); + void stopping(); + void crashed(const QString& errorMessage); + void failed(); + void frameAvailable(); + void stateLoaded(); + void rewound(); + + void rewindChanged(bool); + void fastForwardChanged(bool); + + void unimplementedBiosCall(int); + void statusPosted(const QString& message); + void logPosted(int level, int category, const QString& log); + +private: + void updateKeys(); + int updateAutofire(); + void finishFrame(); + + void updateFastForward(); + + mCoreThread m_threadContext{}; + + bool m_patched = false; + + QByteArray m_buffers[2]; + QByteArray* m_activeBuffer; + QByteArray* m_completeBuffer = nullptr; + + std::unique_ptr m_tileCache; + std::unique_ptr m_override; + + QList> m_resetActions; + QList> m_frameActions; + QMutex m_mutex; + + int m_activeKeys = 0; + bool m_autofire[32] = {}; + int m_autofireStatus[32] = {}; + + VFileDevice m_backupLoadState; + QByteArray m_backupSaveState{nullptr}; + int m_stateSlot = 1; + int m_loadStateFlags; + int m_saveStateFlags; + + bool m_audioSync = AUDIO_SYNC; + bool m_videoSync = VIDEO_SYNC; + + int m_fastForward = false; + int m_fastForwardForced = false; + float m_fastForwardRatio = -1.f; + float m_fpsTarget; + + InputController* m_inputController = nullptr; + LogController* m_log = nullptr; + MultiplayerController* m_multiplayer = nullptr; + + mVideoLogContext* m_vl = nullptr; + VFile* m_vlVf = nullptr; +}; + +} + +#endif diff --git a/src/platform/qt/CoreManager.cpp b/src/platform/qt/CoreManager.cpp new file mode 100644 index 000000000..e69f0ee27 --- /dev/null +++ b/src/platform/qt/CoreManager.cpp @@ -0,0 +1,165 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "CoreManager.h" + +#include "CoreController.h" +#include "LogController.h" + +#include + +#ifdef M_CORE_GBA +#include +#endif + +#include +#include + +using namespace QGBA; + +void CoreManager::setConfig(const mCoreConfig* config) { + m_config = config; +} + +void CoreManager::setMultiplayerController(MultiplayerController* multiplayer) { + m_multiplayer = multiplayer; +} + +CoreController* CoreManager::loadGame(const QString& path) { + QFileInfo info(path); + if (!info.isReadable()) { + QString fname = info.fileName(); + QString base = info.path(); + if (base.endsWith("/") || base.endsWith(QDir::separator())) { + base.chop(1); + } + VDir* dir = VDirOpenArchive(base.toUtf8().constData()); + if (dir) { + VFile* vf = dir->openFile(dir, fname.toUtf8().constData(), O_RDONLY); + if (vf) { + struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf)); + uint8_t buffer[2048]; + ssize_t read; + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + vfclone->write(vfclone, buffer, read); + } + vf->close(vf); + vf = vfclone; + } + dir->close(dir); + loadGame(vf, fname, base); + } else { + LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); + } + return nullptr; + } + VFile* vf = nullptr; + VDir* archive = VDirOpenArchive(path.toUtf8().constData()); + if (archive) { + VFile* vfOriginal = VDirFindFirst(archive, [](VFile* vf) { + return mCoreIsCompatible(vf) != PLATFORM_NONE; + }); + ssize_t size; + if (vfOriginal && (size = vfOriginal->size(vfOriginal)) > 0) { + void* mem = vfOriginal->map(vfOriginal, size, MAP_READ); + vf = VFileMemChunk(mem, size); + vfOriginal->unmap(vfOriginal, mem, (size_t) read); + vfOriginal->close(vfOriginal); + } + } + QDir dir(info.dir()); + if (!vf) { + vf = VFileOpen(info.canonicalFilePath().toUtf8().constData(), O_RDONLY); + } + return loadGame(vf, info.fileName(), dir.canonicalPath()); +} + +CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QString& base) { + if (!vf) { + return nullptr; + } + + mCore* core = mCoreFindVF(vf); + if (!core) { + return nullptr; + } + + core->init(core); + mCoreInitConfig(core, nullptr); + + if (m_config) { + mCoreLoadForeignConfig(core, m_config); + } + + if (m_preload) { + mCorePreloadVF(core, vf); + } else { + core->loadROM(core, vf); + } + + QFileInfo info(base + "/" + path); + QByteArray bytes(info.baseName().toUtf8()); + strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); + + bytes = info.dir().canonicalPath().toUtf8(); + mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); + mCoreAutoloadSave(core); + + CoreController* cc = new CoreController(core); + if (m_multiplayer) { + cc->setMultiplayerController(m_multiplayer); + } + emit coreLoaded(cc); + return cc; +} + +CoreController* CoreManager::loadBIOS(int platform, const QString& path) { + QFileInfo info(path); + VFile* vf = VFileOpen(info.canonicalFilePath().toUtf8().constData(), O_RDONLY); + if (!vf) { + return nullptr; + } + + mCore* core = nullptr; + switch (platform) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + core = GBACoreCreate(); + break; +#endif + default: + vf->close(vf); + return nullptr; + } + if (!core) { + vf->close(vf); + return nullptr; + } + + core->init(core); + mCoreInitConfig(core, nullptr); + + if (m_config) { + mCoreLoadForeignConfig(core, m_config); + } + + core->loadBIOS(core, vf, 0); + + mCoreConfigSetOverrideIntValue(&core->config, "useBios", 1); + mCoreConfigSetOverrideIntValue(&core->config, "skipBios", 0); + + QByteArray bytes(info.baseName().toUtf8()); + strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); + + bytes = info.dir().canonicalPath().toUtf8(); + mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); + + CoreController* cc = new CoreController(core); + if (m_multiplayer) { + cc->setMultiplayerController(m_multiplayer); + } + emit coreLoaded(cc); + return cc; +} diff --git a/src/platform/qt/CoreManager.h b/src/platform/qt/CoreManager.h new file mode 100644 index 000000000..78800ac41 --- /dev/null +++ b/src/platform/qt/CoreManager.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_CORE_MANAGER +#define QGBA_CORE_MANAGER + +#include +#include +#include + +struct mCoreConfig; +struct VFile; + +namespace QGBA { + +class CoreController; +class MultiplayerController; + +class CoreManager : public QObject { +Q_OBJECT + +public: + void setConfig(const mCoreConfig*); + void setMultiplayerController(MultiplayerController*); + void setPreload(bool preload) { m_preload = preload; } + +public slots: + CoreController* loadGame(const QString& path); + CoreController* loadGame(VFile* vf, const QString& path, const QString& base); + CoreController* loadBIOS(int platform, const QString& path); + +signals: + void coreLoaded(CoreController*); + +private: + const mCoreConfig* m_config = nullptr; + MultiplayerController* m_multiplayer = nullptr; + bool m_preload = false; +}; + +} + +#endif diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index d012f9e27..08b113f72 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DebuggerConsoleController.h" -#include "GameController.h" +#include "CoreController.h" #include @@ -13,8 +13,8 @@ using namespace QGBA; -DebuggerConsoleController::DebuggerConsoleController(GameController* controller, QObject* parent) - : DebuggerController(controller, &m_cliDebugger.d, parent) +DebuggerConsoleController::DebuggerConsoleController(QObject* parent) + : DebuggerController(&m_cliDebugger.d, parent) { m_backend.d.printf = printf; m_backend.d.init = init; @@ -79,7 +79,7 @@ void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { const char* DebuggerConsoleController::readLine(struct CLIDebuggerBackend* be, size_t* len) { Backend* consoleBe = reinterpret_cast(be); DebuggerConsoleController* self = consoleBe->self; - GameController::Interrupter interrupter(self->m_gameController, true); + CoreController::Interrupter interrupter(self->m_gameController, true); QMutexLocker lock(&self->m_mutex); while (self->m_lines.isEmpty()) { self->m_cond.wait(&self->m_mutex); @@ -103,7 +103,7 @@ void DebuggerConsoleController::lineAppend(struct CLIDebuggerBackend* be, const const char* DebuggerConsoleController::historyLast(struct CLIDebuggerBackend* be, size_t* len) { Backend* consoleBe = reinterpret_cast(be); DebuggerConsoleController* self = consoleBe->self; - GameController::Interrupter interrupter(self->m_gameController, true); + CoreController::Interrupter interrupter(self->m_gameController, true); QMutexLocker lock(&self->m_mutex); if (self->m_history.isEmpty()) { return "i"; @@ -115,7 +115,7 @@ const char* DebuggerConsoleController::historyLast(struct CLIDebuggerBackend* be void DebuggerConsoleController::historyAppend(struct CLIDebuggerBackend* be, const char* line) { Backend* consoleBe = reinterpret_cast(be); DebuggerConsoleController* self = consoleBe->self; - GameController::Interrupter interrupter(self->m_gameController, true); + CoreController::Interrupter interrupter(self->m_gameController, true); QMutexLocker lock(&self->m_mutex); self->m_history.append(QString::fromUtf8(line)); } diff --git a/src/platform/qt/DebuggerConsoleController.h b/src/platform/qt/DebuggerConsoleController.h index 543dabeae..83e1b53bb 100644 --- a/src/platform/qt/DebuggerConsoleController.h +++ b/src/platform/qt/DebuggerConsoleController.h @@ -16,13 +16,13 @@ namespace QGBA { -class GameController; +class CoreController; class DebuggerConsoleController : public DebuggerController { Q_OBJECT public: - DebuggerConsoleController(GameController* controller, QObject* parent = nullptr); + DebuggerConsoleController(QObject* parent = nullptr); signals: void log(const QString&); @@ -44,7 +44,7 @@ private: static const char* historyLast(struct CLIDebuggerBackend* be, size_t* len); static void historyAppend(struct CLIDebuggerBackend* be, const char* line); - CLIDebugger m_cliDebugger; + CLIDebugger m_cliDebugger{}; QMutex m_mutex; QWaitCondition m_cond; diff --git a/src/platform/qt/DebuggerController.cpp b/src/platform/qt/DebuggerController.cpp index a1a7a96c5..eb27789a7 100644 --- a/src/platform/qt/DebuggerController.cpp +++ b/src/platform/qt/DebuggerController.cpp @@ -5,32 +5,44 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GDBController.h" -#include "GameController.h" +#include "CoreController.h" using namespace QGBA; -DebuggerController::DebuggerController(GameController* controller, mDebugger* debugger, QObject* parent) +DebuggerController::DebuggerController(mDebugger* debugger, QObject* parent) : QObject(parent) , m_debugger(debugger) - , m_gameController(controller) { } bool DebuggerController::isAttached() { + if (!m_gameController) { + return false; + } return m_gameController->debugger() == m_debugger; } +void DebuggerController::setController(std::shared_ptr controller) { + if (m_gameController && controller != m_gameController) { + m_gameController->disconnect(this); + detach(); + } + m_gameController = controller; + if (controller) { + connect(m_gameController.get(), &CoreController::stopping, [this]() { + setController(nullptr); + }); + } +} + void DebuggerController::attach() { if (isAttached()) { return; } - if (m_gameController->isLoaded()) { + if (m_gameController) { attachInternal(); m_gameController->setDebugger(m_debugger); mDebuggerEnter(m_debugger, DEBUGGER_ENTER_ATTACHED, 0); - } else { - QObject::disconnect(m_autoattach); - m_autoattach = connect(m_gameController, &GameController::gameStarted, this, &DebuggerController::attach); } } @@ -39,16 +51,18 @@ void DebuggerController::detach() { if (!isAttached()) { return; } - GameController::Interrupter interrupter(m_gameController); - shutdownInternal(); - m_gameController->setDebugger(nullptr); + if (m_gameController) { + CoreController::Interrupter interrupter(m_gameController); + shutdownInternal(); + m_gameController->setDebugger(nullptr); + } } void DebuggerController::breakInto() { if (!isAttached()) { return; } - GameController::Interrupter interrupter(m_gameController); + CoreController::Interrupter interrupter(m_gameController); mDebuggerEnter(m_debugger, DEBUGGER_ENTER_MANUAL, 0); } @@ -57,7 +71,7 @@ void DebuggerController::shutdown() { if (!isAttached()) { return; } - GameController::Interrupter interrupter(m_gameController); + CoreController::Interrupter interrupter(m_gameController); shutdownInternal(); } diff --git a/src/platform/qt/DebuggerController.h b/src/platform/qt/DebuggerController.h index 01603ae02..8677af1c5 100644 --- a/src/platform/qt/DebuggerController.h +++ b/src/platform/qt/DebuggerController.h @@ -8,20 +8,23 @@ #include +#include + struct mDebugger; namespace QGBA { -class GameController; +class CoreController; class DebuggerController : public QObject { Q_OBJECT public: - DebuggerController(GameController* controller, mDebugger* debugger, QObject* parent = nullptr); + DebuggerController(mDebugger* debugger, QObject* parent = nullptr); public: bool isAttached(); + void setController(std::shared_ptr); public slots: virtual void attach(); @@ -34,7 +37,7 @@ protected: virtual void shutdownInternal(); mDebugger* const m_debugger; - GameController* const m_gameController; + std::shared_ptr m_gameController; private: QMetaObject::Connection m_autoattach; diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index b7e04d263..f413a2b72 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -8,16 +8,19 @@ #include +#include + #include #include "MessagePainter.h" -struct mCoreThread; struct VDir; struct VideoShader; namespace QGBA { +class CoreController; + class Display : public QWidget { Q_OBJECT @@ -41,6 +44,7 @@ public: bool isIntegerScalingLocked() const { return m_lockIntegerScaling; } bool isFiltered() const { return m_filter; } + virtual void startDrawing(std::shared_ptr) = 0; virtual bool isDrawing() const = 0; virtual bool supportsShaders() const = 0; virtual VideoShader* shaders() = 0; @@ -50,7 +54,6 @@ signals: void hideCursor(); public slots: - virtual void startDrawing(mCoreThread* context) = 0; virtual void stopDrawing() = 0; virtual void pauseDrawing() = 0; virtual void unpauseDrawing() = 0; @@ -58,7 +61,7 @@ public slots: virtual void lockAspectRatio(bool lock); virtual void lockIntegerScaling(bool lock); virtual void filter(bool filter); - virtual void framePosted(const uint32_t*) = 0; + virtual void framePosted() = 0; virtual void setShaders(struct VDir*) = 0; virtual void clearShaders() = 0; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index cffce120e..07ee75dc7 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -7,12 +7,13 @@ #if defined(BUILD_GL) || defined(BUILD_GLES) +#include "CoreController.h" + #include #include #include #include -#include #ifdef BUILD_GL #include "platform/opengl/gl.h" #endif @@ -52,14 +53,14 @@ VideoShader* DisplayGL::shaders() { return shaders; } -void DisplayGL::startDrawing(mCoreThread* thread) { +void DisplayGL::startDrawing(std::shared_ptr controller) { if (m_drawThread) { return; } m_isDrawing = true; - m_painter->setContext(thread); + m_painter->setContext(controller); m_painter->setMessagePainter(messagePainter()); - m_context = thread; + m_context = controller; m_painter->resize(size()); m_gl->move(0, 0); m_drawThread = new QThread(this); @@ -69,7 +70,6 @@ void DisplayGL::startDrawing(mCoreThread* thread) { m_painter->moveToThread(m_drawThread); connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start); m_drawThread->start(); - mCoreSyncSetVideoSync(&m_context->impl->sync, false); lockAspectRatio(isAspectRatioLocked()); lockIntegerScaling(isIntegerScalingLocked()); @@ -85,41 +85,27 @@ void DisplayGL::startDrawing(mCoreThread* thread) { void DisplayGL::stopDrawing() { if (m_drawThread) { m_isDrawing = false; - if (mCoreThreadIsActive(m_context)) { - mCoreThreadInterrupt(m_context); - } + CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection); m_drawThread->exit(); m_drawThread = nullptr; - if (mCoreThreadIsActive(m_context)) { - mCoreThreadContinue(m_context); - } } + m_context.reset(); } void DisplayGL::pauseDrawing() { if (m_drawThread) { m_isDrawing = false; - if (mCoreThreadIsActive(m_context)) { - mCoreThreadInterrupt(m_context); - } + CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection); - if (mCoreThreadIsActive(m_context)) { - mCoreThreadContinue(m_context); - } } } void DisplayGL::unpauseDrawing() { if (m_drawThread) { m_isDrawing = true; - if (mCoreThreadIsActive(m_context)) { - mCoreThreadInterrupt(m_context); - } + CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection); - if (mCoreThreadIsActive(m_context)) { - mCoreThreadContinue(m_context); - } } } @@ -150,9 +136,9 @@ void DisplayGL::filter(bool filter) { } } -void DisplayGL::framePosted(const uint32_t* buffer) { - if (m_drawThread && buffer) { - m_painter->enqueue(buffer); +void DisplayGL::framePosted() { + if (m_drawThread) { + m_painter->enqueue(m_context->drawContext()); QMetaObject::invokeMethod(m_painter, "draw"); } } @@ -183,12 +169,6 @@ void DisplayGL::resizePainter() { PainterGL::PainterGL(int majorVersion, QGLWidget* parent) : m_gl(parent) - , m_active(false) - , m_started(false) - , m_context(nullptr) - , m_shader{} - , m_backend(nullptr) - , m_messagePainter(nullptr) { #ifdef BUILD_GL mGLContext* glBackend; @@ -262,7 +242,7 @@ PainterGL::~PainterGL() { m_backend = nullptr; } -void PainterGL::setContext(mCoreThread* context) { +void PainterGL::setContext(std::shared_ptr context) { m_context = context; if (!context) { @@ -273,9 +253,8 @@ void PainterGL::setContext(mCoreThread* context) { #if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent(); #endif - unsigned width, height; - context->core->desiredVideoDimensions(context->core, &width, &height); - m_backend->setDimensions(m_backend, width, height); + QSize size = m_context->screenDimensions(); + m_backend->setDimensions(m_backend, size.width(), size.height()); m_gl->doneCurrent(); } @@ -329,13 +308,13 @@ void PainterGL::start() { } void PainterGL::draw() { - if (m_queue.isEmpty() || !mCoreThreadIsActive(m_context)) { + if (m_queue.isEmpty()) { return; } - if (mCoreSyncWaitFrameStart(&m_context->impl->sync) || !m_queue.isEmpty()) { + if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) { dequeue(); - mCoreSyncWaitFrameEnd(&m_context->impl->sync); + mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); m_painter.begin(m_gl->context()->device()); performDraw(); m_painter.end(); @@ -349,7 +328,7 @@ void PainterGL::draw() { m_delayTimer.restart(); } } else { - mCoreSyncWaitFrameEnd(&m_context->impl->sync); + mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); } if (!m_queue.isEmpty()) { QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); @@ -375,6 +354,7 @@ void PainterGL::stop() { m_backend->swap(m_backend); m_gl->doneCurrent(); m_gl->context()->moveToThread(m_gl->thread()); + m_context.reset(); moveToThread(m_gl->thread()); } @@ -409,9 +389,8 @@ void PainterGL::enqueue(const uint32_t* backing) { } else { buffer = m_free.takeLast(); } - unsigned width, height; - m_context->core->desiredVideoDimensions(m_context->core, &width, &height); - memcpy(buffer, backing, width * height * BYTES_PER_PIXEL); + QSize size = m_context->screenDimensions(); + memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); m_queue.enqueue(buffer); m_mutex.unlock(); } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index f2f6dbc7c..c4d2916bd 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -46,12 +46,12 @@ public: DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); ~DisplayGL(); + void startDrawing(std::shared_ptr) override; bool isDrawing() const override { return m_isDrawing; } bool supportsShaders() const override; VideoShader* shaders() override; public slots: - void startDrawing(mCoreThread* context) override; void stopDrawing() override; void pauseDrawing() override; void unpauseDrawing() override; @@ -59,7 +59,7 @@ public slots: void lockAspectRatio(bool lock) override; void lockIntegerScaling(bool lock) override; void filter(bool filter) override; - void framePosted(const uint32_t*) override; + void framePosted() override; void setShaders(struct VDir*) override; void clearShaders() override; @@ -74,7 +74,7 @@ private: QGLWidget* m_gl; PainterGL* m_painter; QThread* m_drawThread = nullptr; - mCoreThread* m_context = nullptr; + std::shared_ptr m_context; }; class PainterGL : public QObject { @@ -84,7 +84,7 @@ public: PainterGL(int majorVersion, QGLWidget* parent); ~PainterGL(); - void setContext(mCoreThread*); + void setContext(std::shared_ptr); void setMessagePainter(MessagePainter*); void enqueue(const uint32_t* backing); @@ -116,14 +116,14 @@ private: QPainter m_painter; QMutex m_mutex; QGLWidget* m_gl; - bool m_active; - bool m_started; - mCoreThread* m_context; + bool m_active = false; + bool m_started = false; + std::shared_ptr m_context = nullptr; bool m_supportsShaders; - VideoShader m_shader; - VideoBackend* m_backend; + VideoShader m_shader{}; + VideoBackend* m_backend = nullptr; QSize m_size; - MessagePainter* m_messagePainter; + MessagePainter* m_messagePainter = nullptr; QElapsedTimer m_delayTimer; }; diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index dbbd7aad9..0c63e8b78 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DisplayQt.h" +#include "CoreController.h" + #include #include @@ -17,10 +19,18 @@ DisplayQt::DisplayQt(QWidget* parent) { } -void DisplayQt::startDrawing(mCoreThread* context) { - context->core->desiredVideoDimensions(context->core, &m_width, &m_height); +void DisplayQt::startDrawing(std::shared_ptr controller) { + QSize size = controller->screenDimensions(); + m_width = size.width(); + m_height = size.height(); m_backing = std::move(QImage()); m_isDrawing = true; + m_context = controller; +} + +void DisplayQt::stopDrawing() { + m_isDrawing = false; + m_context.reset(); } void DisplayQt::lockAspectRatio(bool lock) { @@ -38,8 +48,9 @@ void DisplayQt::filter(bool filter) { update(); } -void DisplayQt::framePosted(const uint32_t* buffer) { +void DisplayQt::framePosted() { update(); + color_t* buffer = m_context->drawContext(); if (const_cast(m_backing).bits() == reinterpret_cast(buffer)) { return; } diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index 6fcab1b7a..ac7e63a86 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -19,20 +19,20 @@ Q_OBJECT public: DisplayQt(QWidget* parent = nullptr); + void startDrawing(std::shared_ptr) override; bool isDrawing() const override { return m_isDrawing; } bool supportsShaders() const override { return false; } VideoShader* shaders() override { return nullptr; } public slots: - void startDrawing(mCoreThread* context) override; - void stopDrawing() override { m_isDrawing = false; } + void stopDrawing() override; void pauseDrawing() override { m_isDrawing = false; } void unpauseDrawing() override { m_isDrawing = true; } void forceDraw() override { update(); } void lockAspectRatio(bool lock) override; void lockIntegerScaling(bool lock) override; void filter(bool filter) override; - void framePosted(const uint32_t*) override; + void framePosted() override; void setShaders(struct VDir*) override {} void clearShaders() override {} @@ -44,6 +44,7 @@ private: unsigned m_width; unsigned m_height; QImage m_backing{nullptr}; + std::shared_ptr m_context = nullptr; }; } diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index c92cc402f..9b994047a 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -6,9 +6,10 @@ #include "GBAApp.h" #include "AudioProcessor.h" +#include "CoreController.h" +#include "CoreManager.h" #include "ConfigController.h" #include "Display.h" -#include "GameController.h" #include "Window.h" #include "VFileDevice.h" @@ -57,6 +58,9 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config) reloadGameDB(); + m_manager.setConfig(m_configController->config()); + m_manager.setMultiplayerController(&m_multiplayer); + if (!m_configController->getQtOption("audioDriver").isNull()) { AudioProcessor::setDriver(static_cast(m_configController->getQtOption("audioDriver").toInt())); } @@ -71,7 +75,8 @@ GBAApp::~GBAApp() { bool GBAApp::event(QEvent* event) { if (event->type() == QEvent::FileOpen) { - m_windows[0]->controller()->loadGame(static_cast(event)->file()); + CoreController* core = m_manager.loadGame(static_cast(event)->file()); + m_windows[0]->setController(core, static_cast(event)->file()); return true; } return QApplication::event(event); @@ -81,7 +86,7 @@ Window* GBAApp::newWindow() { if (m_windows.count() >= MAX_GBAS) { return nullptr; } - Window* w = new Window(m_configController, m_multiplayer.attached()); + Window* w = new Window(&m_manager, m_configController, m_multiplayer.attached()); int windowId = m_multiplayer.attached(); connect(w, &Window::destroyed, [this, w]() { m_windows.removeAll(w); @@ -93,7 +98,6 @@ Window* GBAApp::newWindow() { w->setAttribute(Qt::WA_DeleteOnClose); w->loadConfig(); w->show(); - w->controller()->setMultiplayerController(&m_multiplayer); w->multiplayerChanged(); for (Window* w : m_windows) { w->updateMultiplayerStatus(m_windows.count() < MAX_GBAS); @@ -107,7 +111,7 @@ GBAApp* GBAApp::app() { void GBAApp::pauseAll(QList* paused) { for (auto& window : m_windows) { - if (!window->controller()->isLoaded() || window->controller()->isPaused()) { + if (!window->controller() || window->controller()->isPaused()) { continue; } window->controller()->setPaused(true); @@ -117,7 +121,9 @@ void GBAApp::pauseAll(QList* paused) { void GBAApp::continueAll(const QList& paused) { for (auto& window : paused) { - window->controller()->setPaused(false); + if (window->controller()) { + window->controller()->setPaused(false); + } } } diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index 2b7c2e5cc..99db127a5 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -8,8 +8,12 @@ #include #include +#include +#include +#include #include +#include "CoreManager.h" #include "MultiplayerController.h" struct NoIntroDB; @@ -70,6 +74,7 @@ private: ConfigController* m_configController; QList m_windows; MultiplayerController m_multiplayer; + CoreManager m_manager; NoIntroDB* m_db = nullptr; #ifdef USE_SQLITE3 diff --git a/src/platform/qt/GDBController.cpp b/src/platform/qt/GDBController.cpp index 5a72420a0..ade67e08c 100644 --- a/src/platform/qt/GDBController.cpp +++ b/src/platform/qt/GDBController.cpp @@ -5,12 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GDBController.h" -#include "GameController.h" +#include "CoreController.h" using namespace QGBA; -GDBController::GDBController(GameController* controller, QObject* parent) - : DebuggerController(controller, &m_gdbStub.d, parent) +GDBController::GDBController(QObject* parent) + : DebuggerController(&m_gdbStub.d, parent) , m_bindAddress({ IPV4, 0 }) { GDBStubCreate(&m_gdbStub); @@ -21,7 +21,7 @@ ushort GDBController::port() { } bool GDBController::isAttached() { - return m_gameController->debugger() == &m_gdbStub.d; + return m_gameController && m_gameController->debugger() == &m_gdbStub.d; } void GDBController::setPort(ushort port) { @@ -34,7 +34,7 @@ void GDBController::setBindAddress(uint32_t bindAddress) { } void GDBController::listen() { - GameController::Interrupter interrupter(m_gameController); + CoreController::Interrupter interrupter(m_gameController); if (!isAttached()) { attach(); } diff --git a/src/platform/qt/GDBController.h b/src/platform/qt/GDBController.h index fc8a6e6a1..25c0a18bf 100644 --- a/src/platform/qt/GDBController.h +++ b/src/platform/qt/GDBController.h @@ -14,13 +14,13 @@ namespace QGBA { -class GameController; +class CoreController; class GDBController : public DebuggerController { Q_OBJECT public: - GDBController(GameController* controller, QObject* parent = nullptr); + GDBController(QObject* parent = nullptr); public: ushort port(); @@ -38,7 +38,7 @@ signals: private: virtual void shutdownInternal() override; - GDBStub m_gdbStub; + GDBStub m_gdbStub{}; ushort m_port = 2345; Address m_bindAddress; diff --git a/src/platform/qt/GIFView.cpp b/src/platform/qt/GIFView.cpp index 1b9e0156c..901e6c365 100644 --- a/src/platform/qt/GIFView.cpp +++ b/src/platform/qt/GIFView.cpp @@ -7,6 +7,7 @@ #ifdef USE_MAGICK +#include "CoreController.h" #include "GBAApp.h" #include "LogController.h" @@ -39,6 +40,12 @@ GIFView::~GIFView() { stopRecording(); } +void GIFView::setController(std::shared_ptr controller) { + connect(controller.get(), &CoreController::stopping, this, &GIFView::stopRecording); + connect(this, &GIFView::recordingStarted, controller.get(), &CoreController::setAVStream); + connect(this, &GIFView::recordingStopped, controller.get(), &CoreController::clearAVStream, Qt::DirectConnection); +} + void GIFView::startRecording() { int delayMs = m_ui.delayAuto->isChecked() ? -1 : m_ui.delayMs->value(); ImageMagickGIFEncoderSetParams(&m_encoder, m_ui.frameskip->value(), delayMs); diff --git a/src/platform/qt/GIFView.h b/src/platform/qt/GIFView.h index 6641d8d94..f5433ae3e 100644 --- a/src/platform/qt/GIFView.h +++ b/src/platform/qt/GIFView.h @@ -10,12 +10,16 @@ #include +#include + #include "ui_GIFView.h" #include "feature/imagemagick/imagemagick-gif-encoder.h" namespace QGBA { +class CoreController; + class GIFView : public QWidget { Q_OBJECT @@ -26,6 +30,8 @@ public: mAVStream* getStream() { return &m_encoder.d; } public slots: + void setController(std::shared_ptr); + void startRecording(); void stopRecording(); diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp deleted file mode 100644 index d4b7f1e3a..000000000 --- a/src/platform/qt/GameController.cpp +++ /dev/null @@ -1,1288 +0,0 @@ -/* Copyright (c) 2013-2014 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "GameController.h" - -#include "AudioProcessor.h" -#include "InputController.h" -#include "LogController.h" -#include "MultiplayerController.h" -#include "Override.h" -#include "VFileDevice.h" - -#include -#include - -#include - -#include -#include -#include -#include -#ifdef M_CORE_GBA -#include -#include -#include -#include -#include -#endif -#ifdef M_CORE_GB -#include -#include -#endif -#include -#include - -using namespace QGBA; -using namespace std; - -GameController::GameController(QObject* parent) - : QObject(parent) - , m_audioProcessor(AudioProcessor::create()) - , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA) - , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC) -{ -#ifdef M_CORE_GBA - m_lux.p = this; - m_lux.sample = [](GBALuminanceSource* context) { - GameControllerLux* lux = static_cast(context); - lux->value = 0xFF - lux->p->m_luxValue; - }; - - m_lux.readLuminance = [](GBALuminanceSource* context) { - GameControllerLux* lux = static_cast(context); - return lux->value; - }; - setLuminanceLevel(0); -#endif - - m_threadContext.startCallback = [](mCoreThread* context) { - GameController* controller = static_cast(context->userData); - context->core->setPeripheral(context->core, mPERIPH_ROTATION, controller->m_inputController->rotationSource()); - context->core->setPeripheral(context->core, mPERIPH_RUMBLE, controller->m_inputController->rumble()); - - for (size_t i = 0; i < controller->m_audioChannels.size(); ++i) { - context->core->enableAudioChannel(context->core, i, controller->m_audioChannels[i]); - } - for (size_t i = 0; i < controller->m_videoLayers.size(); ++i) { - context->core->enableVideoLayer(context->core, i, controller->m_videoLayers[i]); - } - - switch (context->core->platform(context->core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, &controller->m_lux); - break; -#endif - default: - break; - } - controller->m_fpsTarget = context->impl->sync.fpsTarget; - - if (controller->m_override) { - controller->m_override->identify(context->core); - controller->m_override->apply(context->core); - } - - if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) { - mCoreDeleteState(context->core, 0); - } - - controller->m_gameOpen = true; - if (controller->m_multiplayer) { - controller->m_multiplayer->attachGame(controller); - } - - QString path = controller->m_fname; - if (!controller->m_fsub.isEmpty()) { - path += QDir::separator() + controller->m_fsub; - } - QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(mCoreThread*, context), Q_ARG(const QString&, path)); - QMetaObject::invokeMethod(controller, "startAudio"); - }; - - m_threadContext.resetCallback = [](mCoreThread* context) { - GameController* controller = static_cast(context->userData); - for (auto action : controller->m_resetActions) { - action(); - } - controller->m_resetActions.clear(); - - unsigned width, height; - controller->m_threadContext.core->desiredVideoDimensions(controller->m_threadContext.core, &width, &height); - memset(controller->m_frontBuffer, 0xFF, width * height * BYTES_PER_PIXEL); - QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); - if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { - mCoreThreadPauseFromThread(context); - QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(mCoreThread*, context)); - } - }; - - m_threadContext.cleanCallback = [](mCoreThread* context) { - GameController* controller = static_cast(context->userData); - - if (controller->m_multiplayer) { - controller->m_multiplayer->detachGame(controller); - } - controller->clearOverride(); - controller->endVideoLog(); - - QMetaObject::invokeMethod(controller->m_audioProcessor, "pause"); - - QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(mCoreThread*, context)); - QMetaObject::invokeMethod(controller, "cleanGame"); - }; - - m_threadContext.frameCallback = [](mCoreThread* context) { - GameController* controller = static_cast(context->userData); - unsigned width, height; - controller->m_threadContext.core->desiredVideoDimensions(controller->m_threadContext.core, &width, &height); - memcpy(controller->m_frontBuffer, controller->m_drawContext, width * height * BYTES_PER_PIXEL); - QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); - - // If no one is using the tile cache, disable it - if (controller->m_tileCache && controller->m_tileCache.unique()) { - switch (controller->platform()) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: { - GBA* gba = static_cast(context->core->board); - gba->video.renderer->cache = nullptr; - break; - } -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: { - GB* gb = static_cast(context->core->board); - gb->video.renderer->cache = nullptr; - break; - } -#endif - default: - break; - } - mTileCacheDeinit(controller->m_tileCache.get()); - controller->m_tileCache.reset(); - } - - - if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { - mCoreThreadPauseFromThread(context); - QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(mCoreThread*, context)); - } - }; - - m_threadContext.sleepCallback = [](mCoreThread* context) { - if (!context) { - return; - } - GameController* controller = static_cast(context->userData); - if (!mCoreSaveState(context->core, 0, controller->m_saveStateFlags)) { - return; - } - QMetaObject::invokeMethod(controller, "closeGame"); - }; - - m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) { - mThreadLogger* logContext = reinterpret_cast(logger); - mCoreThread* context = logContext->p; - - static const char* savestateMessage = "State %i loaded"; - static const char* savestateFailedMessage = "State %i failed to load"; - static int biosCat = -1; - static int statusCat = -1; - if (!context) { - return; - } - GameController* controller = static_cast(context->userData); - QString message; - if (biosCat < 0) { - biosCat = mLogCategoryById("gba.bios"); - } - if (statusCat < 0) { - statusCat = mLogCategoryById("core.status"); - } -#ifdef M_CORE_GBA - if (level == mLOG_STUB && category == biosCat) { - va_list argc; - va_copy(argc, args); - int immediate = va_arg(argc, int); - va_end(argc); - QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate)); - } else -#endif - if (category == statusCat) { - // Slot 0 is reserved for suspend points - if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { - va_list argc; - va_copy(argc, args); - int slot = va_arg(argc, int); - va_end(argc); - if (slot == 0) { - format = "Loaded suspend state"; - } - } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) { - va_list argc; - va_copy(argc, args); - int slot = va_arg(argc, int); - va_end(argc); - if (slot == 0) { - return; - } - } - message = QString().vsprintf(format, args); - QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message)); - } - if (level == mLOG_FATAL) { - mCoreThreadMarkCrashed(controller->thread()); - QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args))); - } else if (!(controller->m_logLevels & level)) { - return; - } - message = QString().vsprintf(format, args); - QMetaObject::invokeMethod(controller, "postLog", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message)); - }; - - m_threadContext.userData = this; - - connect(this, &GameController::gamePaused, m_audioProcessor, &AudioProcessor::pause); - connect(this, &GameController::gameStarted, m_audioProcessor, &AudioProcessor::setInput); - connect(this, &GameController::frameAvailable, this, &GameController::pollEvents); - connect(this, &GameController::frameAvailable, this, &GameController::updateAutofire); -} - -GameController::~GameController() { - disconnect(); - closeGame(); - clearMultiplayerController(); - delete m_backupLoadState; -} - -void GameController::setMultiplayerController(MultiplayerController* controller) { - if (controller == m_multiplayer) { - return; - } - clearMultiplayerController(); - m_multiplayer = controller; - if (isLoaded()) { - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) { - GameController* controller = static_cast(thread->userData); - controller->m_multiplayer->attachGame(controller); - }); - } -} - -void GameController::clearMultiplayerController() { - if (!m_multiplayer) { - return; - } - m_multiplayer->detachGame(this); - m_multiplayer = nullptr; -} - -void GameController::setOverride(Override* override) { - m_override = override; - if (isLoaded()) { - Interrupter interrupter(this); - m_override->identify(m_threadContext.core); - } -} - -void GameController::clearOverride() { - delete m_override; - m_override = nullptr; -} - -void GameController::setConfig(const mCoreConfig* config) { - m_config = config; - if (isLoaded()) { - Interrupter interrupter(this); - mCoreLoadForeignConfig(m_threadContext.core, config); - m_audioSync = m_threadContext.impl->sync.audioWait; - m_videoSync = m_threadContext.impl->sync.videoFrameWait; - m_audioProcessor->setInput(&m_threadContext); - } -} - -#ifdef USE_DEBUGGERS -mDebugger* GameController::debugger() { - if (!isLoaded()) { - return nullptr; - } - return m_threadContext.core->debugger; -} - -void GameController::setDebugger(mDebugger* debugger) { - Interrupter interrupter(this); - if (debugger) { - mDebuggerAttach(debugger, m_threadContext.core); - } else { - m_threadContext.core->detachDebugger(m_threadContext.core); - } -} -#endif - -void GameController::loadGame(const QString& path) { - closeGame(); - QFileInfo info(path); - if (!info.isReadable()) { - QString fname = info.fileName(); - QString base = info.path(); - if (base.endsWith("/") || base.endsWith(QDir::separator())) { - base.chop(1); - } - VDir* dir = VDirOpenArchive(base.toUtf8().constData()); - if (dir) { - VFile* vf = dir->openFile(dir, fname.toUtf8().constData(), O_RDONLY); - if (vf) { - struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf)); - uint8_t buffer[2048]; - ssize_t read; - while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { - vfclone->write(vfclone, buffer, read); - } - vf->close(vf); - vf = vfclone; - } - dir->close(dir); - loadGame(vf, fname, base); - } else { - LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); - } - return; - } else { - m_fname = info.canonicalFilePath(); - m_fsub = QString(); - } - m_vf = nullptr; - openGame(); -} - -void GameController::loadGame(VFile* vf, const QString& path, const QString& base) { - closeGame(); - QFileInfo info(base); - if (info.isDir()) { - m_fname = QFileInfo(base + '/' + path).canonicalFilePath(); - m_fsub = QString(); - } else { - m_fname = info.canonicalFilePath(); - m_fsub = path; - } - m_vf = vf; - openGame(); -} - -void GameController::bootBIOS() { - closeGame(); - m_fname = QString(); - openGame(true); -} - -void GameController::openGame(bool biosOnly) { - if (m_fname.isEmpty()) { - biosOnly = true; - } - if (isLoaded()) { - // We need to delay if the game is still cleaning up - QTimer::singleShot(10, this, SLOT(openGame())); - return; - } else if(m_gameOpen) { - cleanGame(); - } - - m_threadContext.core = nullptr; - if (!biosOnly) { - if (m_vf) { - m_threadContext.core = mCoreFindVF(m_vf); - } else { - m_threadContext.core = mCoreFind(m_fname.toUtf8().constData()); - } -#ifdef M_CORE_GBA - } else { - m_threadContext.core = GBACoreCreate(); -#endif - } - - if (!m_threadContext.core) { - return; - } - - m_pauseAfterFrame = false; - - m_threadContext.core->init(m_threadContext.core); - mCoreInitConfig(m_threadContext.core, nullptr); - - unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); - m_drawContext = new uint32_t[width * height]; - m_frontBuffer = new uint32_t[width * height]; - - if (m_config) { - mCoreLoadForeignConfig(m_threadContext.core, m_config); - } - - QByteArray bytes; - if (!biosOnly) { - bytes = m_fname.toUtf8(); - if (m_preload) { - if (m_vf) { - mCorePreloadVF(m_threadContext.core, m_vf); - } else { - mCorePreloadFile(m_threadContext.core, bytes.constData()); - mDirectorySetDetachBase(&m_threadContext.core->dirs); - } - } else { - if (m_vf) { - m_threadContext.core->loadROM(m_threadContext.core, m_vf); - } else { - mCoreLoadFile(m_threadContext.core, bytes.constData()); - mDirectorySetDetachBase(&m_threadContext.core->dirs); - } - } - } else { - bytes = m_bios.toUtf8(); - } - if (bytes.isNull()) { - return; - } - - char dirname[PATH_MAX]; - separatePath(bytes.constData(), dirname, m_threadContext.core->dirs.baseName, 0); - mDirectorySetAttachBase(&m_threadContext.core->dirs, VDirOpen(dirname)); - - m_threadContext.core->setVideoBuffer(m_threadContext.core, m_drawContext, width); - - m_inputController->recalibrateAxes(); - memset(m_drawContext, 0xF8, width * height * 4); - - m_threadContext.core->setAVStream(m_threadContext.core, m_stream); - - if (!biosOnly) { - mCoreAutoloadSave(m_threadContext.core); - if (!m_patch.isNull()) { - VFile* patch = VFileDevice::open(m_patch, O_RDONLY); - if (patch) { - m_threadContext.core->loadPatch(m_threadContext.core, patch); - } - patch->close(patch); - m_patch = QString(); - } else { - mCoreAutoloadPatch(m_threadContext.core); - } - } - m_vf = nullptr; - - if (!mCoreThreadStart(&m_threadContext)) { - emit gameFailed(); - } - if (m_turbo) { - m_threadContext.impl->sync.videoFrameWait = false; - m_threadContext.impl->sync.audioWait = false; - } else { - m_threadContext.impl->sync.videoFrameWait = m_videoSync; - m_threadContext.impl->sync.audioWait = m_audioSync; - } -} - -void GameController::loadBIOS(int platform, const QString& path) { - if (m_bios == path) { - return; - } - if (!m_bios.isNull() && m_gameOpen && this->platform() == platform) { - closeGame(); - m_bios = path; - openGame(); - } else if (!m_gameOpen || m_bios.isNull()) { - m_bios = path; - } -} - -void GameController::loadSave(const QString& path, bool temporary) { - if (!isLoaded()) { - return; - } - m_resetActions.append([this, path, temporary]() { - VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR); - if (!vf) { - LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path); - return; - } - - if (temporary) { - m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); - } else { - m_threadContext.core->loadSave(m_threadContext.core, vf); - } - }); - reset(); -} - -void GameController::yankPak() { - if (!m_gameOpen) { - return; - } - Interrupter interrupter(this); - GBAYankROM(static_cast(m_threadContext.core->board)); -} - -void GameController::replaceGame(const QString& path) { - if (!m_gameOpen) { - return; - } - - QFileInfo info(path); - if (!info.isReadable()) { - LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); - return; - } - m_fname = info.canonicalFilePath(); - Interrupter interrupter(this); - mDirectorySetDetachBase(&m_threadContext.core->dirs); - mCoreLoadFile(m_threadContext.core, m_fname.toLocal8Bit().constData()); -} - -void GameController::loadPatch(const QString& path) { - m_patch = path; - if (m_gameOpen) { - closeGame(); - openGame(); - } -} - -void GameController::importSharkport(const QString& path) { - if (!isLoaded()) { - return; - } -#ifdef M_CORE_GBA - if (platform() != PLATFORM_GBA) { - return; - } - VFile* vf = VFileDevice::open(path, O_RDONLY); - if (!vf) { - LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path); - return; - } - threadInterrupt(); - GBASavedataImportSharkPort(static_cast(m_threadContext.core->board), vf, false); - threadContinue(); - vf->close(vf); -#endif -} - -void GameController::exportSharkport(const QString& path) { - if (!isLoaded()) { - return; - } -#ifdef M_CORE_GBA - if (platform() != PLATFORM_GBA) { - return; - } - VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); - if (!vf) { - LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path); - return; - } - threadInterrupt(); - GBASavedataExportSharkPort(static_cast(m_threadContext.core->board), vf); - threadContinue(); - vf->close(vf); -#endif -} - -void GameController::closeGame() { - if (!m_gameOpen) { - return; - } -#ifdef USE_DEBUGGERS - setDebugger(nullptr); -#endif - if (mCoreThreadIsPaused(&m_threadContext)) { - mCoreThreadUnpause(&m_threadContext); - } - mCoreThreadEnd(&m_threadContext); -} - -void GameController::cleanGame() { - if (!m_gameOpen || mCoreThreadIsActive(&m_threadContext)) { - return; - } - - m_audioProcessor->pause(); - mCoreThreadJoin(&m_threadContext); - - if (m_tileCache) { - mTileCacheDeinit(m_tileCache.get()); - m_tileCache.reset(); - } - - delete[] m_drawContext; - delete[] m_frontBuffer; - - mCoreConfigDeinit(&m_threadContext.core->config); - m_threadContext.core->deinit(m_threadContext.core); - m_threadContext.core = nullptr; - m_gameOpen = false; -} - -void GameController::crashGame(const QString& crashMessage) { - closeGame(); - emit gameCrashed(crashMessage); -} - -bool GameController::isPaused() { - if (!m_gameOpen) { - return false; - } - return mCoreThreadIsPaused(&m_threadContext); -} - -mPlatform GameController::platform() const { - if (!m_gameOpen) { - return PLATFORM_NONE; - } - return m_threadContext.core->platform(m_threadContext.core); -} - -QSize GameController::screenDimensions() const { - if (!m_gameOpen) { - return QSize(); - } - unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); - - return QSize(width, height); -} - -void GameController::setPaused(bool paused) { - if (!isLoaded() || paused == mCoreThreadIsPaused(&m_threadContext)) { - return; - } - m_wasPaused = paused; - if (paused) { - m_pauseAfterFrame.testAndSetRelaxed(false, true); - } else { - mCoreThreadUnpause(&m_threadContext); - startAudio(); - emit gameUnpaused(&m_threadContext); - } -} - -void GameController::reset() { - if (!m_gameOpen) { - return; - } - bool wasPaused = isPaused(); - setPaused(false); - Interrupter interrupter(this); - mCoreThreadReset(&m_threadContext); - if (wasPaused) { - setPaused(true); - } -} - -void GameController::threadInterrupt() { - if (m_gameOpen) { - mCoreThreadInterrupt(&m_threadContext); - } -} - -void GameController::threadContinue() { - if (m_gameOpen) { - mCoreThreadContinue(&m_threadContext); - } -} - -void GameController::frameAdvance() { - if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) { - setPaused(false); - m_wasPaused = true; - } -} - -void GameController::setRewind(bool enable, int capacity, bool rewindSave) { - if (m_gameOpen) { - Interrupter interrupter(this); - if (m_threadContext.core->opts.rewindEnable && m_threadContext.core->opts.rewindBufferCapacity > 0) { - mCoreRewindContextDeinit(&m_threadContext.impl->rewind); - } - m_threadContext.core->opts.rewindEnable = enable; - m_threadContext.core->opts.rewindBufferCapacity = capacity; - m_threadContext.core->opts.rewindSave = rewindSave; - if (enable && capacity > 0) { - mCoreRewindContextInit(&m_threadContext.impl->rewind, capacity, true); - m_threadContext.impl->rewind.stateFlags = rewindSave ? SAVESTATE_SAVEDATA : 0; - } - } -} - -void GameController::rewind(int states) { - threadInterrupt(); - if (!states) { - states = INT_MAX; - } - for (int i = 0; i < states; ++i) { - if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) { - break; - } - } - threadContinue(); - emit frameAvailable(m_drawContext); - emit rewound(&m_threadContext); -} - -void GameController::startRewinding() { - if (!isLoaded()) { - return; - } - if (!m_threadContext.core->opts.rewindEnable) { - return; - } - if (m_multiplayer && m_multiplayer->attached() > 1) { - return; - } - if (m_wasPaused) { - setPaused(false); - m_wasPaused = true; - } - mCoreThreadSetRewinding(&m_threadContext, true); -} - -void GameController::stopRewinding() { - if (!isLoaded()) { - return; - } - mCoreThreadSetRewinding(&m_threadContext, false); - bool signalsBlocked = blockSignals(true); - setPaused(m_wasPaused); - blockSignals(signalsBlocked); -} - -void GameController::keyPressed(int key) { - int mappedKey = 1 << key; - m_activeKeys |= mappedKey; - if (!m_inputController->allowOpposing()) { - if ((m_activeKeys & 0x30) == 0x30) { - m_inactiveKeys |= mappedKey ^ 0x30; - m_activeKeys ^= mappedKey ^ 0x30; - } - if ((m_activeKeys & 0xC0) == 0xC0) { - m_inactiveKeys |= mappedKey ^ 0xC0; - m_activeKeys ^= mappedKey ^ 0xC0; - } - } - updateKeys(); -} - -void GameController::keyReleased(int key) { - int mappedKey = 1 << key; - m_activeKeys &= ~mappedKey; - if (!m_inputController->allowOpposing()) { - if (mappedKey & 0x30) { - m_activeKeys |= m_inactiveKeys & (0x30 ^ mappedKey); - m_inactiveKeys &= ~0x30; - } - if (mappedKey & 0xC0) { - m_activeKeys |= m_inactiveKeys & (0xC0 ^ mappedKey); - m_inactiveKeys &= ~0xC0; - } - } - updateKeys(); -} - -void GameController::clearKeys() { - m_activeKeys = 0; - m_inactiveKeys = 0; - updateKeys(); -} - -void GameController::setAutofire(int key, bool enable) { - if (key >= GBA_KEY_MAX || key < 0) { - return; - } - - if (!enable && m_autofireStatus[key]) { - keyReleased(key); - } - - m_autofire[key] = enable; - m_autofireStatus[key] = 0; -} - -void GameController::setAudioBufferSamples(int samples) { - if (m_audioProcessor) { - threadInterrupt(); - redoSamples(samples); - threadContinue(); - m_audioProcessor->setBufferSamples(samples); - } -} - -void GameController::setAudioSampleRate(unsigned rate) { - if (!rate) { - return; - } - if (m_audioProcessor) { - threadInterrupt(); - redoSamples(m_audioProcessor->getBufferSamples()); - threadContinue(); - m_audioProcessor->requestSampleRate(rate); - } -} - -void GameController::setAudioChannelEnabled(int channel, bool enable) { - if (channel > 5 || channel < 0) { - return; - } - m_audioChannels.reserve(channel + 1); - while (m_audioChannels.size() <= channel) { - m_audioChannels.append(true); - } - m_audioChannels[channel] = enable; - if (isLoaded()) { - m_threadContext.core->enableAudioChannel(m_threadContext.core, channel, enable); - } -} - -void GameController::startAudio() { - if (!m_audioProcessor->start()) { - LOG(QT, ERROR) << tr("Failed to start audio processor"); - // Don't freeze! - m_audioSync = false; - m_videoSync = true; - if (isLoaded()) { - m_threadContext.impl->sync.audioWait = false; - m_threadContext.impl->sync.videoFrameWait = true; - } - } -} - -void GameController::setVideoLayerEnabled(int layer, bool enable) { - if (layer > 4 || layer < 0) { - return; - } - m_videoLayers.reserve(layer + 1); - while (m_videoLayers.size() <= layer) { - m_videoLayers.append(true); - } - m_videoLayers[layer] = enable; - if (isLoaded()) { - m_threadContext.core->enableVideoLayer(m_threadContext.core, layer, enable); - } -} - -void GameController::setFPSTarget(float fps) { - Interrupter interrupter(this); - m_fpsTarget = fps; - if (isLoaded()) { - m_threadContext.impl->sync.fpsTarget = fps; - if (m_turbo && m_turboSpeed > 0) { - m_threadContext.impl->sync.fpsTarget *= m_turboSpeed; - } - } - if (m_audioProcessor) { - redoSamples(m_audioProcessor->getBufferSamples()); - } -} - -void GameController::setUseBIOS(bool use) { - if (use == m_useBios) { - return; - } - m_useBios = use; - if (m_gameOpen) { - closeGame(); - openGame(); - } -} - -void GameController::loadState(int slot) { - if (m_fname.isEmpty()) { - // We're in the BIOS - return; - } - if (slot > 0 && slot != m_stateSlot) { - m_stateSlot = slot; - m_backupSaveState.clear(); - } - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - GameController* controller = static_cast(context->userData); - if (!controller->m_backupLoadState) { - controller->m_backupLoadState = VFileMemChunk(nullptr, 0); - } - mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); - if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) { - emit controller->frameAvailable(controller->m_drawContext); - emit controller->stateLoaded(context); - } - }); -} - -void GameController::saveState(int slot) { - if (m_fname.isEmpty()) { - // We're in the BIOS - return; - } - if (slot > 0) { - m_stateSlot = slot; - } - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - GameController* controller = static_cast(context->userData); - VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false); - if (vf) { - controller->m_backupSaveState.resize(vf->size(vf)); - vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); - vf->close(vf); - } - mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags); - }); -} - -void GameController::loadBackupState() { - if (!m_backupLoadState) { - return; - } - - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - GameController* controller = static_cast(context->userData); - controller->m_backupLoadState->seek(controller->m_backupLoadState, 0, SEEK_SET); - if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) { - mLOG(STATUS, INFO, "Undid state load"); - controller->frameAvailable(controller->m_drawContext); - controller->stateLoaded(context); - } - controller->m_backupLoadState->close(controller->m_backupLoadState); - controller->m_backupLoadState = nullptr; - }); -} - -void GameController::saveBackupState() { - if (m_backupSaveState.isEmpty()) { - return; - } - - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - GameController* controller = static_cast(context->userData); - VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true); - if (vf) { - vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size()); - vf->close(vf); - mLOG(STATUS, INFO, "Undid state save"); - } - controller->m_backupSaveState.clear(); - }); -} - -void GameController::setTurbo(bool set, bool forced) { - if (m_turboForced && !forced) { - return; - } - if (m_turbo == set && m_turboForced == (set && forced)) { - // Don't interrupt the thread if we don't need to - return; - } - if (!m_sync) { - return; - } - m_turbo = set; - m_turboForced = set && forced; - enableTurbo(); -} - -void GameController::setTurboSpeed(float ratio) { - m_turboSpeed = ratio; - enableTurbo(); -} - -void GameController::enableTurbo() { - Interrupter interrupter(this); - if (!isLoaded()) { - return; - } - bool shouldRedoSamples = false; - if (!m_turbo) { - shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget; - m_threadContext.impl->sync.fpsTarget = m_fpsTarget; - m_threadContext.impl->sync.audioWait = m_audioSync; - m_threadContext.impl->sync.videoFrameWait = m_videoSync; - } else if (m_turboSpeed <= 0) { - shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget; - m_threadContext.impl->sync.fpsTarget = m_fpsTarget; - m_threadContext.impl->sync.audioWait = false; - m_threadContext.impl->sync.videoFrameWait = false; - } else { - shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget * m_turboSpeed; - m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_turboSpeed; - m_threadContext.impl->sync.audioWait = true; - m_threadContext.impl->sync.videoFrameWait = false; - } - if (m_audioProcessor && shouldRedoSamples) { - redoSamples(m_audioProcessor->getBufferSamples()); - } -} - -void GameController::setSync(bool enable) { - m_turbo = false; - m_turboForced = false; - if (isLoaded()) { - if (!enable) { - m_threadContext.impl->sync.audioWait = false; - m_threadContext.impl->sync.videoFrameWait = false; - } else { - m_threadContext.impl->sync.audioWait = m_audioSync; - m_threadContext.impl->sync.videoFrameWait = m_videoSync; - } - } - m_sync = enable; -} - -void GameController::setAudioSync(bool enable) { - m_audioSync = enable; - if (isLoaded()) { - m_threadContext.impl->sync.audioWait = enable; - } -} - -void GameController::setVideoSync(bool enable) { - m_videoSync = enable; - if (isLoaded()) { - m_threadContext.impl->sync.videoFrameWait = enable; - } -} - -void GameController::setAVStream(mAVStream* stream) { - Interrupter interrupter(this); - m_stream = stream; - if (isLoaded()) { - m_threadContext.core->setAVStream(m_threadContext.core, stream); - } -} - -void GameController::clearAVStream() { - Interrupter interrupter(this); - m_stream = nullptr; - if (isLoaded()) { - m_threadContext.core->setAVStream(m_threadContext.core, nullptr); - } -} - -#ifdef USE_PNG -void GameController::screenshot() { - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - mCoreTakeScreenshot(context->core); - }); -} -#endif - -void GameController::reloadAudioDriver() { - int samples = 0; - unsigned sampleRate = 0; - if (m_audioProcessor) { - m_audioProcessor->pause(); - samples = m_audioProcessor->getBufferSamples(); - sampleRate = m_audioProcessor->sampleRate(); - delete m_audioProcessor; - } - m_audioProcessor = AudioProcessor::create(); - if (samples) { - m_audioProcessor->setBufferSamples(samples); - } - if (sampleRate) { - m_audioProcessor->requestSampleRate(sampleRate); - } - connect(this, &GameController::gamePaused, m_audioProcessor, &AudioProcessor::pause); - connect(this, &GameController::gameStarted, m_audioProcessor, &AudioProcessor::setInput); - if (isLoaded()) { - m_audioProcessor->setInput(&m_threadContext); - startAudio(); - } -} - -void GameController::setSaveStateExtdata(int flags) { - m_saveStateFlags = flags; -} - -void GameController::setLoadStateExtdata(int flags) { - m_loadStateFlags = flags; -} - -void GameController::setPreload(bool preload) { - m_preload = preload; -} - -void GameController::setLuminanceValue(uint8_t value) { - m_luxValue = value; - value = std::max(value - 0x16, 0); - m_luxLevel = 10; - for (int i = 0; i < 10; ++i) { - if (value < GBA_LUX_LEVELS[i]) { - m_luxLevel = i; - break; - } - } - emit luminanceValueChanged(m_luxValue); -} - -void GameController::setLuminanceLevel(int level) { - int value = 0x16; - level = std::max(0, std::min(10, level)); - if (level > 0) { - value += GBA_LUX_LEVELS[level - 1]; - } - setLuminanceValue(value); -} - -void GameController::setRealTime() { - if (!isLoaded()) { - return; - } - m_threadContext.core->rtc.override = RTC_NO_OVERRIDE; -} - -void GameController::setFixedTime(const QDateTime& time) { - if (!isLoaded()) { - return; - } - m_threadContext.core->rtc.override = RTC_FIXED; - m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); -} - -void GameController::setFakeEpoch(const QDateTime& time) { - if (!isLoaded()) { - return; - } - m_threadContext.core->rtc.override = RTC_FAKE_EPOCH; - m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); -} - -void GameController::updateKeys() { - int activeKeys = m_activeKeys; - activeKeys |= m_activeButtons; - activeKeys &= ~m_inactiveKeys; - if (isLoaded()) { - m_threadContext.core->setKeys(m_threadContext.core, activeKeys); - } -} - -void GameController::redoSamples(int samples) { - if (m_gameOpen && m_threadContext.core) { - m_threadContext.core->setAudioBufferSize(m_threadContext.core, samples); - } - m_audioProcessor->inputParametersChanged(); -} - -void GameController::setLogLevel(int levels) { - Interrupter interrupter(this); - m_logLevels = levels; -} - -void GameController::enableLogLevel(int levels) { - Interrupter interrupter(this); - m_logLevels |= levels; -} - -void GameController::disableLogLevel(int levels) { - Interrupter interrupter(this); - m_logLevels &= ~levels; -} - -void GameController::startVideoLog(const QString& path) { - if (!isLoaded() || m_vl) { - return; - } - - Interrupter interrupter(this); - m_vl = mVideoLogContextCreate(m_threadContext.core); - m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); - mVideoLogContextSetOutput(m_vl, m_vlVf); - mVideoLogContextWriteHeader(m_vl, m_threadContext.core); -} - -void GameController::endVideoLog() { - if (!m_vl) { - return; - } - - Interrupter interrupter(this); - mVideoLogContextDestroy(m_threadContext.core, m_vl); - if (m_vlVf) { - m_vlVf->close(m_vlVf); - m_vlVf = nullptr; - } - m_vl = nullptr; -} - -void GameController::pollEvents() { - if (!m_inputController) { - return; - } - - m_activeButtons = m_inputController->pollEvents(); - updateKeys(); -} - -void GameController::updateAutofire() { - // TODO: Move all key events onto the CPU thread...somehow - for (int k = 0; k < GBA_KEY_MAX; ++k) { - if (!m_autofire[k]) { - continue; - } - m_autofireStatus[k] ^= 1; - if (m_autofireStatus[k]) { - keyPressed(k); - } else { - keyReleased(k); - } - } -} - -std::shared_ptr GameController::tileCache() { - if (m_tileCache) { - return m_tileCache; - } - Interrupter interrupter(this); - switch (platform()) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: { - GBA* gba = static_cast(m_threadContext.core->board); - m_tileCache = std::make_shared(); - GBAVideoTileCacheInit(m_tileCache.get()); - GBAVideoTileCacheAssociate(m_tileCache.get(), &gba->video); - mTileCacheSetPalette(m_tileCache.get(), 0); - break; - } -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: { - GB* gb = static_cast(m_threadContext.core->board); - m_tileCache = std::make_shared(); - GBVideoTileCacheInit(m_tileCache.get()); - GBVideoTileCacheAssociate(m_tileCache.get(), &gb->video); - mTileCacheSetPalette(m_tileCache.get(), 0); - break; - } -#endif - default: - return nullptr; - } - return m_tileCache; -} - -GameController::Interrupter::Interrupter(GameController* parent, bool fromThread) - : m_parent(parent) - , m_fromThread(fromThread) -{ - if (!m_fromThread) { - m_parent->threadInterrupt(); - } else { - mCoreThreadInterruptFromThread(m_parent->thread()); - } -} - -GameController::Interrupter::~Interrupter() { - if (!m_fromThread) { - m_parent->threadContinue(); - } else { - mCoreThreadContinue(m_parent->thread()); - } -} diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h deleted file mode 100644 index 111cbdada..000000000 --- a/src/platform/qt/GameController.h +++ /dev/null @@ -1,262 +0,0 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef QGBA_GAME_CONTROLLER -#define QGBA_GAME_CONTROLLER - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#ifdef BUILD_SDL -#include "platform/sdl/sdl-events.h" -#endif - -struct Configuration; -struct GBAAudio; -struct mCoreConfig; -struct mDebugger; -struct mTileCache; -struct mVideoLogContext; - -namespace QGBA { - -class AudioProcessor; -class InputController; -class MultiplayerController; -class Override; - -class GameController : public QObject { -Q_OBJECT - -public: - static const bool VIDEO_SYNC = false; - static const bool AUDIO_SYNC = true; - - class Interrupter { - public: - Interrupter(GameController*, bool fromThread = false); - ~Interrupter(); - - private: - GameController* m_parent; - bool m_fromThread; - }; - - GameController(QObject* parent = nullptr); - ~GameController(); - - const uint32_t* drawContext() const { return m_drawContext; } - mCoreThread* thread() { return &m_threadContext; } - mCheatDevice* cheatDevice() { return m_threadContext.core ? m_threadContext.core->cheatDevice(m_threadContext.core) : nullptr; } - - void threadInterrupt(); - void threadContinue(); - - bool isPaused(); - bool isLoaded() { return m_gameOpen && mCoreThreadIsActive(&m_threadContext); } - mPlatform platform() const; - - bool audioSync() const { return m_audioSync; } - bool videoSync() const { return m_videoSync; } - QSize screenDimensions() const; - - void setInputController(InputController* controller) { m_inputController = controller; } - - void setMultiplayerController(MultiplayerController* controller); - MultiplayerController* multiplayerController() { return m_multiplayer; } - void clearMultiplayerController(); - - void setOverride(Override* override); - Override* override() { return m_override; } - void clearOverride(); - - void setConfig(const mCoreConfig*); - - int stateSlot() const { return m_stateSlot; } - -#ifdef USE_DEBUGGERS - mDebugger* debugger(); - void setDebugger(mDebugger*); -#endif - - std::shared_ptr tileCache(); - -signals: - void frameAvailable(const uint32_t*); - void gameStarted(mCoreThread*, const QString& fname); - void gameStopped(mCoreThread*); - void gamePaused(mCoreThread*); - void gameUnpaused(mCoreThread*); - void gameCrashed(const QString& errorMessage); - void gameFailed(); - void stateLoaded(mCoreThread*); - void rewound(mCoreThread*); - void unimplementedBiosCall(int); - - void luminanceValueChanged(int); - - void statusPosted(const QString& message); - void postLog(int level, int category, const QString& log); - -public slots: - void loadGame(const QString& path); - void loadGame(VFile* vf, const QString& path, const QString& base); - void loadBIOS(int platform, const QString& path); - void loadSave(const QString& path, bool temporary = true); - void yankPak(); - void replaceGame(const QString& path); - void setUseBIOS(bool); - void loadPatch(const QString& path); - void importSharkport(const QString& path); - void exportSharkport(const QString& path); - void bootBIOS(); - void closeGame(); - void setPaused(bool paused); - void reset(); - void frameAdvance(); - void setRewind(bool enable, int capacity, bool rewindSave); - void rewind(int states = 0); - void startRewinding(); - void stopRewinding(); - void keyPressed(int key); - void keyReleased(int key); - void clearKeys(); - void setAutofire(int key, bool enable); - void setAudioBufferSamples(int samples); - void setAudioSampleRate(unsigned rate); - void setAudioChannelEnabled(int channel, bool enable = true); - void startAudio(); - void setVideoLayerEnabled(int layer, bool enable = true); - void setFPSTarget(float fps); - void loadState(int slot = 0); - void saveState(int slot = 0); - void loadBackupState(); - void saveBackupState(); - void setTurbo(bool, bool forced = true); - void setTurboSpeed(float ratio); - void setSync(bool); - void setAudioSync(bool); - void setVideoSync(bool); - void setAVStream(mAVStream*); - void clearAVStream(); - void reloadAudioDriver(); - void setSaveStateExtdata(int flags); - void setLoadStateExtdata(int flags); - void setPreload(bool); - -#ifdef USE_PNG - void screenshot(); -#endif - - void setLuminanceValue(uint8_t value); - uint8_t luminanceValue() const { return m_luxValue; } - void setLuminanceLevel(int level); - void increaseLuminanceLevel() { setLuminanceLevel(m_luxLevel + 1); } - void decreaseLuminanceLevel() { setLuminanceLevel(m_luxLevel - 1); } - - void setRealTime(); - void setFixedTime(const QDateTime& time); - void setFakeEpoch(const QDateTime& time); - - void setLogLevel(int); - void enableLogLevel(int); - void disableLogLevel(int); - - void startVideoLog(const QString& path); - void endVideoLog(); - -private slots: - void openGame(bool bios = false); - void crashGame(const QString& crashMessage); - void cleanGame(); - - void pollEvents(); - void updateAutofire(); - -private: - void updateKeys(); - void redoSamples(int samples); - void enableTurbo(); - - uint32_t* m_drawContext = nullptr; - uint32_t* m_frontBuffer = nullptr; - mCoreThread m_threadContext{}; - const mCoreConfig* m_config; - mCheatDevice* m_cheatDevice; - int m_activeKeys = 0; - int m_activeButtons = 0; - int m_inactiveKeys = 0; - int m_logLevels = 0; - - bool m_gameOpen = false; - - QString m_fname; - QString m_fsub; - VFile* m_vf = nullptr; - QString m_bios; - bool m_useBios = false; - QString m_patch; - Override* m_override = nullptr; - - AudioProcessor* m_audioProcessor; - - QAtomicInt m_pauseAfterFrame{false}; - QList> m_resetActions; - - bool m_sync = true; - bool m_videoSync = VIDEO_SYNC; - bool m_audioSync = AUDIO_SYNC; - float m_fpsTarget = -1; - bool m_turbo = false; - bool m_turboForced = false; - float m_turboSpeed = -1; - bool m_wasPaused = false; - - std::shared_ptr m_tileCache; - - QList m_audioChannels; - QList m_videoLayers; - - bool m_autofire[GBA_KEY_MAX] = {}; - int m_autofireStatus[GBA_KEY_MAX] = {}; - - int m_stateSlot = 1; - struct VFile* m_backupLoadState = nullptr; - QByteArray m_backupSaveState{nullptr}; - int m_saveStateFlags; - int m_loadStateFlags; - - bool m_preload = false; - - InputController* m_inputController = nullptr; - MultiplayerController* m_multiplayer = nullptr; - - mAVStream* m_stream = nullptr; - - mVideoLogContext* m_vl = nullptr; - VFile* m_vlVf = nullptr; - - struct GameControllerLux : GBALuminanceSource { - GameController* p; - uint8_t value; - } m_lux; - uint8_t m_luxValue; - int m_luxLevel; -}; - -} - -#endif diff --git a/src/platform/qt/IOViewer.cpp b/src/platform/qt/IOViewer.cpp index 030ead19f..76be744ac 100644 --- a/src/platform/qt/IOViewer.cpp +++ b/src/platform/qt/IOViewer.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "IOViewer.h" -#include "GameController.h" +#include "CoreController.h" #include #include @@ -1023,7 +1023,7 @@ const QList& IOViewer::registerDescriptions() { return s_registers; } -IOViewer::IOViewer(GameController* controller, QWidget* parent) +IOViewer::IOViewer(std::shared_ptr controller, QWidget* parent) : QDialog(parent) , m_controller(controller) { @@ -1067,16 +1067,17 @@ IOViewer::IOViewer(GameController* controller, QWidget* parent) } selectRegister(0); + + connect(controller.get(), &CoreController::stopping, this, &QWidget::close); } void IOViewer::updateRegister() { m_value = 0; uint16_t value = 0; - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { + { + CoreController::Interrupter interrupter(m_controller); value = GBAView16(static_cast(m_controller->thread()->core->cpu), BASE_IO | m_register); } - m_controller->threadContinue(); for (int i = 0; i < 16; ++i) { m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked); @@ -1095,11 +1096,10 @@ void IOViewer::bitFlipped() { } void IOViewer::writeback() { - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { + { + CoreController::Interrupter interrupter(m_controller); GBAIOWrite(static_cast(m_controller->thread()->core->board), m_register, m_value); } - m_controller->threadContinue(); updateRegister(); } diff --git a/src/platform/qt/IOViewer.h b/src/platform/qt/IOViewer.h index c2451e08d..1e1da3c62 100644 --- a/src/platform/qt/IOViewer.h +++ b/src/platform/qt/IOViewer.h @@ -9,11 +9,13 @@ #include #include +#include + #include "ui_IOViewer.h" namespace QGBA { -class GameController; +class CoreController; class IOViewer : public QDialog { Q_OBJECT @@ -39,7 +41,7 @@ public: }; typedef QList RegisterDescription; - IOViewer(GameController* controller, QWidget* parent = nullptr); + IOViewer(std::shared_ptr controller, QWidget* parent = nullptr); static const QList& registerDescriptions(); @@ -65,7 +67,7 @@ private: QCheckBox* m_b[16]; - GameController* m_controller; + std::shared_ptr m_controller; }; } diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 4731d81eb..2da1d72bb 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -63,6 +63,21 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN); mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT); mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT); + + +#ifdef M_CORE_GBA + m_lux.p = this; + m_lux.sample = [](GBALuminanceSource* context) { + InputControllerLux* lux = static_cast(context); + lux->value = 0xFF - lux->p->m_luxValue; + }; + + m_lux.readLuminance = [](GBALuminanceSource* context) { + InputControllerLux* lux = static_cast(context); + return lux->value; + }; + setLuminanceLevel(0); +#endif } InputController::~InputController() { @@ -82,7 +97,6 @@ InputController::~InputController() { void InputController::setConfiguration(ConfigController* config) { m_config = config; - setAllowOpposing(config->getOption("allowOpposingDirections").toInt()); loadConfiguration(KEYBOARD); #ifdef BUILD_SDL mSDLEventsLoadConfig(&s_sdlEvents, config->input()); @@ -607,3 +621,34 @@ void InputController::releaseFocus(QWidget* focus) { m_focusParent = m_topLevel; } } + +void InputController::increaseLuminanceLevel() { + setLuminanceLevel(m_luxLevel + 1); +} + +void InputController::decreaseLuminanceLevel() { + setLuminanceLevel(m_luxLevel - 1); +} + +void InputController::setLuminanceLevel(int level) { + int value = 0x16; + level = std::max(0, std::min(10, level)); + if (level > 0) { + value += GBA_LUX_LEVELS[level - 1]; + } + setLuminanceValue(value); +} + +void InputController::setLuminanceValue(uint8_t value) { + m_luxValue = value; + value = std::max(value - 0x16, 0); + m_luxLevel = 10; + for (int i = 0; i < 10; ++i) { + if (value < GBA_LUX_LEVELS[i]) { + m_luxLevel = i; + break; + } + } + emit luminanceValueChanged(m_luxValue); +} + diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index 460973426..c97d11818 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -14,6 +14,7 @@ #include #include +#include #include #ifdef BUILD_SDL @@ -44,9 +45,6 @@ public: void saveProfile(uint32_t type, const QString& profile); const char* profileForType(uint32_t type); - bool allowOpposing() const { return m_allowOpposing; } - void setAllowOpposing(bool allowOpposing) { m_allowOpposing = allowOpposing; } - GBAKey mapKeyboard(int key) const; void bindKey(uint32_t type, int key, GBAKey); @@ -84,9 +82,11 @@ public: mRumble* rumble(); mRotationSource* rotationSource(); + GBALuminanceSource* luminance() { return &m_lux; } signals: void profileLoaded(const QString& profile); + void luminanceValueChanged(int value); public slots: void testGamepad(int type); @@ -97,16 +97,27 @@ public slots: void resumeScreensaver(); void setScreensaverSuspendable(bool); + void increaseLuminanceLevel(); + void decreaseLuminanceLevel(); + void setLuminanceLevel(int level); + void setLuminanceValue(uint8_t value); + private: void postPendingEvent(GBAKey); void clearPendingEvent(GBAKey); bool hasPendingEvent(GBAKey) const; void sendGamepadEvent(QEvent*); + struct InputControllerLux : GBALuminanceSource { + InputController* p; + uint8_t value; + } m_lux; + uint8_t m_luxValue; + int m_luxLevel; + mInputMap m_inputMap; ConfigController* m_config = nullptr; int m_playerId; - bool m_allowOpposing = false; QWidget* m_topLevel; QWidget* m_focusParent; diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index a381c3d22..538254ff6 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "LoadSaveState.h" -#include "GameController.h" +#include "CoreController.h" #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" #include "VFileDevice.h" @@ -20,7 +20,7 @@ using namespace QGBA; -LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) +LoadSaveState::LoadSaveState(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) , m_mode(LoadSave::LOAD) @@ -61,6 +61,8 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) escape->setShortcut(QKeySequence("Esc")); escape->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(escape); + + connect(m_controller.get(), &CoreController::stopping, this, &QWidget::close); } void LoadSaveState::setMode(LoadSave mode) { diff --git a/src/platform/qt/LoadSaveState.h b/src/platform/qt/LoadSaveState.h index b5aa09c59..8da0beef3 100644 --- a/src/platform/qt/LoadSaveState.h +++ b/src/platform/qt/LoadSaveState.h @@ -8,11 +8,13 @@ #include +#include + #include "ui_LoadSaveState.h" namespace QGBA { -class GameController; +class CoreController; class InputController; class SavestateButton; @@ -27,7 +29,7 @@ Q_OBJECT public: const static int NUM_SLOTS = 9; - LoadSaveState(GameController* controller, QWidget* parent = nullptr); + LoadSaveState(std::shared_ptr controller, QWidget* parent = nullptr); void setInputController(InputController* controller); void setMode(LoadSave mode); @@ -46,7 +48,7 @@ private: void triggerState(int slot); Ui::LoadSaveState m_ui; - GameController* m_controller; + std::shared_ptr m_controller; SavestateButton* m_slots[NUM_SLOTS]; LoadSave m_mode; diff --git a/src/platform/qt/LogController.cpp b/src/platform/qt/LogController.cpp index 5de4c74e5..26383ba6d 100644 --- a/src/platform/qt/LogController.cpp +++ b/src/platform/qt/LogController.cpp @@ -11,8 +11,11 @@ LogController LogController::s_global(mLOG_ALL); LogController::LogController(int levels, QObject* parent) : QObject(parent) - , m_logLevel(levels) { + mLogFilterInit(&m_filter); + mLogFilterSet(&m_filter, "gba.bios", mLOG_STUB); + m_filter.defaultLevels = levels; + if (this != &s_global) { connect(&s_global, &LogController::logPosted, this, &LogController::postLog); connect(this, &LogController::levelsSet, &s_global, &LogController::setLevels); @@ -26,24 +29,24 @@ LogController::Stream LogController::operator()(int category, int level) { } void LogController::postLog(int level, int category, const QString& string) { - if (!(m_logLevel & level)) { + if (!mLogFilterTest(&m_filter, category, static_cast(level))) { return; } emit logPosted(level, category, string); } void LogController::setLevels(int levels) { - m_logLevel = levels; + m_filter.defaultLevels = levels; emit levelsSet(levels); } void LogController::enableLevels(int levels) { - m_logLevel |= levels; + m_filter.defaultLevels |= levels; emit levelsEnabled(levels); } void LogController::disableLevels(int levels) { - m_logLevel &= ~levels; + m_filter.defaultLevels &= ~levels; emit levelsDisabled(levels); } diff --git a/src/platform/qt/LogController.h b/src/platform/qt/LogController.h index 0a4be0159..88c24f1b1 100644 --- a/src/platform/qt/LogController.h +++ b/src/platform/qt/LogController.h @@ -8,6 +8,8 @@ #include "GBAApp.h" +#include + #include #include @@ -35,7 +37,8 @@ private: public: LogController(int levels, QObject* parent = nullptr); - int levels() const { return m_logLevel; } + int levels() const { return m_filter.defaultLevels; } + mLogFilter* filter() { return &m_filter; } Stream operator()(int category, int level); @@ -55,7 +58,7 @@ public slots: void disableLevels(int levels); private: - int m_logLevel; + mLogFilter m_filter; static LogController s_global; }; diff --git a/src/platform/qt/MemoryModel.cpp b/src/platform/qt/MemoryModel.cpp index 8b7a08dd7..92fb2aa98 100644 --- a/src/platform/qt/MemoryModel.cpp +++ b/src/platform/qt/MemoryModel.cpp @@ -6,7 +6,7 @@ #include "MemoryModel.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include "LogController.h" #include "VFileDevice.h" @@ -91,7 +91,7 @@ MemoryModel::MemoryModel(QWidget* parent) setRegion(0, 0x10000000, tr("All")); } -void MemoryModel::setController(GameController* controller) { +void MemoryModel::setController(std::shared_ptr controller) { m_core = controller->thread()->core; } diff --git a/src/platform/qt/MemoryModel.h b/src/platform/qt/MemoryModel.h index f74078528..f40b9383a 100644 --- a/src/platform/qt/MemoryModel.h +++ b/src/platform/qt/MemoryModel.h @@ -11,6 +11,7 @@ #include #include #include + #include #include @@ -19,7 +20,7 @@ struct mCore; namespace QGBA { -class GameController; +class CoreController; class MemoryModel : public QAbstractScrollArea { Q_OBJECT @@ -27,7 +28,7 @@ Q_OBJECT public: MemoryModel(QWidget* parent = nullptr); - void setController(GameController* controller); + void setController(std::shared_ptr controller); void setRegion(uint32_t base, uint32_t size, const QString& name = QString(), int segment = -1); void setSegment(int segment); diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 60d4b095b..2230f00aa 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -8,12 +8,12 @@ #include -#include "GameController.h" +#include "CoreController.h" #include "MemoryView.h" using namespace QGBA; -MemorySearch::MemorySearch(GameController* controller, QWidget* parent) +MemorySearch::MemorySearch(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) { @@ -26,6 +26,8 @@ MemorySearch::MemorySearch(GameController* controller, QWidget* parent) connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh); connect(m_ui.numDec, &QPushButton::clicked, this, &MemorySearch::refresh); connect(m_ui.viewMem, &QPushButton::clicked, this, &MemorySearch::openMemory); + + connect(controller.get(), &CoreController::stopping, this, &QWidget::close); } MemorySearch::~MemorySearch() { @@ -109,10 +111,7 @@ void MemorySearch::search() { mCoreMemorySearchParams params; - GameController::Interrupter interrupter(m_controller); - if (!m_controller->isLoaded()) { - return; - } + CoreController::Interrupter interrupter(m_controller); mCore* core = m_controller->thread()->core; if (createParams(¶ms)) { @@ -125,10 +124,7 @@ void MemorySearch::search() { void MemorySearch::searchWithin() { mCoreMemorySearchParams params; - GameController::Interrupter interrupter(m_controller); - if (!m_controller->isLoaded()) { - return; - } + CoreController::Interrupter interrupter(m_controller); mCore* core = m_controller->thread()->core; if (createParams(¶ms)) { @@ -139,10 +135,7 @@ void MemorySearch::searchWithin() { } void MemorySearch::refresh() { - GameController::Interrupter interrupter(m_controller); - if (!m_controller->isLoaded()) { - return; - } + CoreController::Interrupter interrupter(m_controller); mCore* core = m_controller->thread()->core; m_ui.results->clearContents(); @@ -220,7 +213,6 @@ void MemorySearch::openMemory() { MemoryView* memView = new MemoryView(m_controller); memView->jumpToAddress(address); - connect(m_controller, &GameController::gameStopped, memView, &QWidget::close); memView->setAttribute(Qt::WA_DeleteOnClose); memView->show(); } diff --git a/src/platform/qt/MemorySearch.h b/src/platform/qt/MemorySearch.h index 65f365f44..8c02aa28d 100644 --- a/src/platform/qt/MemorySearch.h +++ b/src/platform/qt/MemorySearch.h @@ -6,13 +6,15 @@ #ifndef QGBA_MEMORY_SEARCH #define QGBA_MEMORY_SEARCH +#include + #include "ui_MemorySearch.h" #include namespace QGBA { -class GameController; +class CoreController; class MemorySearch : public QWidget { Q_OBJECT @@ -20,7 +22,7 @@ Q_OBJECT public: static constexpr size_t LIMIT = 10000; - MemorySearch(GameController* controller, QWidget* parent = nullptr); + MemorySearch(std::shared_ptr controller, QWidget* parent = nullptr); ~MemorySearch(); public slots: @@ -36,7 +38,7 @@ private: Ui::MemorySearch m_ui; - GameController* m_controller; + std::shared_ptr m_controller; mCoreMemorySearchResults m_results; QByteArray m_string; diff --git a/src/platform/qt/MemoryView.cpp b/src/platform/qt/MemoryView.cpp index 543dbe6bf..9b91d8180 100644 --- a/src/platform/qt/MemoryView.cpp +++ b/src/platform/qt/MemoryView.cpp @@ -6,13 +6,13 @@ #include "MemoryView.h" -#include "GameController.h" +#include "CoreController.h" #include using namespace QGBA; -MemoryView::MemoryView(GameController* controller, QWidget* parent) +MemoryView::MemoryView(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) { @@ -45,12 +45,12 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent) m_ui.hexfield, static_cast(&MemoryModel::jumpToAddress)); connect(m_ui.hexfield, &MemoryModel::selectionChanged, this, &MemoryView::updateSelection); - connect(controller, &GameController::gameStopped, this, &QWidget::close); + connect(controller.get(), &CoreController::stopping, this, &QWidget::close); - connect(controller, &GameController::frameAvailable, this, &MemoryView::update); - connect(controller, &GameController::gamePaused, this, &MemoryView::update); - connect(controller, &GameController::stateLoaded, this, &MemoryView::update); - connect(controller, &GameController::rewound, this, &MemoryView::update); + connect(controller.get(), &CoreController::frameAvailable, this, &MemoryView::update); + connect(controller.get(), &CoreController::paused, this, &MemoryView::update); + connect(controller.get(), &CoreController::stateLoaded, this, &MemoryView::update); + connect(controller.get(), &CoreController::rewound, this, &MemoryView::update); connect(m_ui.copy, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::copy); connect(m_ui.save, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::save); @@ -94,9 +94,6 @@ void MemoryView::updateSelection(uint32_t start, uint32_t end) { void MemoryView::updateStatus() { int align = m_ui.hexfield->alignment(); - if (!m_controller->isLoaded()) { - return; - } mCore* core = m_controller->thread()->core; QByteArray selection(m_ui.hexfield->serialize()); QString text(m_ui.hexfield->decodeText(selection)); diff --git a/src/platform/qt/MemoryView.h b/src/platform/qt/MemoryView.h index 04a492ca9..049c63aed 100644 --- a/src/platform/qt/MemoryView.h +++ b/src/platform/qt/MemoryView.h @@ -12,13 +12,13 @@ namespace QGBA { -class GameController; +class CoreController; class MemoryView : public QWidget { Q_OBJECT public: - MemoryView(GameController* controller, QWidget* parent = nullptr); + MemoryView(std::shared_ptr controller, QWidget* parent = nullptr); public slots: void update(); @@ -33,7 +33,7 @@ private slots: private: Ui::MemoryView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; QPair m_selection; }; diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index dc5e54ebb..203e32330 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MultiplayerController.h" -#include "GameController.h" +#include "CoreController.h" #ifdef M_CORE_GBA #include @@ -153,7 +153,7 @@ MultiplayerController::MultiplayerController() { }; } -bool MultiplayerController::attachGame(GameController* controller) { +bool MultiplayerController::attachGame(CoreController* controller) { if (m_lockstep.attached == MAX_GBAS) { return false; } @@ -232,13 +232,15 @@ bool MultiplayerController::attachGame(GameController* controller) { return false; } -void MultiplayerController::detachGame(GameController* controller) { +void MultiplayerController::detachGame(CoreController* controller) { mCoreThread* thread = controller->thread(); if (!thread) { return; } + QList interrupters; + for (int i = 0; i < m_players.count(); ++i) { - m_players[i].controller->threadInterrupt(); + interrupters.append(m_players[i].controller); } switch (controller->platform()) { #ifdef M_CORE_GBA @@ -269,20 +271,16 @@ void MultiplayerController::detachGame(GameController* controller) { break; } - controller->threadContinue(); for (int i = 0; i < m_players.count(); ++i) { if (m_players[i].controller == controller) { m_players.removeAt(i); break; } } - for (int i = 0; i < m_players.count(); ++i) { - m_players[i].controller->threadContinue(); - } emit gameDetached(); } -int MultiplayerController::playerId(GameController* controller) { +int MultiplayerController::playerId(CoreController* controller) { for (int i = 0; i < m_players.count(); ++i) { if (m_players[i].controller == controller) { return i; diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index 65054fcb2..b7f043140 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -23,7 +23,7 @@ struct GBASIOLockstepNode; namespace QGBA { -class GameController; +class CoreController; class MultiplayerController : public QObject { Q_OBJECT @@ -31,11 +31,11 @@ Q_OBJECT public: MultiplayerController(); - bool attachGame(GameController*); - void detachGame(GameController*); + bool attachGame(CoreController*); + void detachGame(CoreController*); int attached(); - int playerId(GameController*); + int playerId(CoreController*); signals: void gameAttached(); @@ -43,7 +43,7 @@ signals: private: struct Player { - GameController* controller; + CoreController* controller; GBSIOLockstepNode* gbNode; GBASIOLockstepNode* gbaNode; int awake; diff --git a/src/platform/qt/ObjView.cpp b/src/platform/qt/ObjView.cpp index 35fb9be0b..871a95cbc 100644 --- a/src/platform/qt/ObjView.cpp +++ b/src/platform/qt/ObjView.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ObjView.h" +#include "CoreController.h" #include "GBAApp.h" #include @@ -24,7 +25,7 @@ using namespace QGBA; -ObjView::ObjView(GameController* controller, QWidget* parent) +ObjView::ObjView(std::shared_ptr controller, QWidget* parent) : AssetView(controller, parent) , m_controller(controller) { @@ -119,16 +120,16 @@ void ObjView::updateTilesGBA(bool force) { }; m_objInfo = newInfo; m_tileOffset = tile; - mTileCacheSetPalette(m_tileCache.get(), paletteSet); + mTileCacheSetPalette(m_tileCache, paletteSet); int i = 0; for (int y = 0; y < height / 8; ++y) { for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * tileBase], tile, palette); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * tileBase], tile, palette); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), tile, palette)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, tile, palette)); } } tile += newInfo.stride - width / 8; @@ -215,16 +216,16 @@ void ObjView::updateTilesGB(bool force) { m_tileOffset = tile; int i = 0; - mTileCacheSetPalette(m_tileCache.get(), 0); + mTileCacheSetPalette(m_tileCache, 0); m_ui.tile->setPalette(palette); m_ui.tile->setPaletteSet(0, 512, 1024); for (int y = 0; y < height / 8; ++y, ++i) { unsigned t = tile + i; - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[16 * t], t, palette); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[16 * t], t, palette); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t, palette)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, t, palette)); } } @@ -247,7 +248,7 @@ void ObjView::updateTilesGB(bool force) { #ifdef USE_PNG void ObjView::exportObj() { - GameController::Interrupter interrupter(m_controller); + CoreController::Interrupter interrupter(m_controller); QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"), tr("Portable Network Graphics (*.png)")); VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); @@ -256,11 +257,11 @@ void ObjView::exportObj() { return; } - mTileCacheSetPalette(m_tileCache.get(), m_objInfo.paletteSet); + mTileCacheSetPalette(m_tileCache, m_objInfo.paletteSet); png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8); - const uint16_t* rawPalette = mTileCacheGetPalette(m_tileCache.get(), m_objInfo.paletteId); + const uint16_t* rawPalette = mTileCacheGetPalette(m_tileCache, m_objInfo.paletteId); unsigned colors = 1 << m_objInfo.bits; uint32_t palette[256]; for (unsigned c = 0; c < colors && c < 256; ++c) { diff --git a/src/platform/qt/ObjView.h b/src/platform/qt/ObjView.h index 0e03ce29c..1771883a7 100644 --- a/src/platform/qt/ObjView.h +++ b/src/platform/qt/ObjView.h @@ -7,7 +7,6 @@ #define QGBA_OBJ_VIEW #include "AssetView.h" -#include "GameController.h" #include "ui_ObjView.h" @@ -15,11 +14,13 @@ namespace QGBA { +class CoreController; + class ObjView : public AssetView { Q_OBJECT public: - ObjView(GameController* controller, QWidget* parent = nullptr); + ObjView(std::shared_ptr controller, QWidget* parent = nullptr); #ifdef USE_PNG public slots: @@ -40,7 +41,7 @@ private: Ui::ObjView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size int m_objId = 0; struct ObjInfo { diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp index 6b7652f81..640f5bcf4 100644 --- a/src/platform/qt/OverrideView.cpp +++ b/src/platform/qt/OverrideView.cpp @@ -9,7 +9,7 @@ #include #include "ConfigController.h" -#include "GameController.h" +#include "CoreController.h" #ifdef M_CORE_GBA #include "GBAOverride.h" @@ -28,9 +28,8 @@ QList OverrideView::s_gbModelList; QList OverrideView::s_mbcList; #endif -OverrideView::OverrideView(GameController* controller, ConfigController* config, QWidget* parent) +OverrideView::OverrideView(ConfigController* config, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) - , m_controller(controller) , m_config(config) { #ifdef M_CORE_GB @@ -57,9 +56,6 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, #endif m_ui.setupUi(this); - connect(controller, &GameController::gameStarted, this, &OverrideView::gameStarted); - connect(controller, &GameController::gameStopped, this, &OverrideView::gameStopped); - connect(m_ui.hwAutodetect, &QAbstractButton::toggled, [this] (bool enabled) { m_ui.hwRTC->setEnabled(!enabled); m_ui.hwGyro->setEnabled(!enabled); @@ -106,9 +102,16 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &OverrideView::saveOverride); connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); +} - if (controller->isLoaded()) { - gameStarted(controller->thread()); +void OverrideView::setController(std::shared_ptr controller) { + m_controller = controller; + gameStarted(); + connect(controller.get(), &CoreController::stopping, this, &OverrideView::gameStopped); + if (m_override) { + m_controller->setOverride(std::move(m_override)); + } else { + m_controller->clearOverride(); } } @@ -149,7 +152,7 @@ bool OverrideView::eventFilter(QObject* obj, QEvent* event) { } void OverrideView::saveOverride() { - if (!m_config) { + if (!m_config || !m_controller) { return; } m_config->saveOverride(*m_controller->override()); @@ -158,7 +161,7 @@ void OverrideView::saveOverride() { void OverrideView::updateOverrides() { #ifdef M_CORE_GBA if (m_ui.tabWidget->currentWidget() == m_ui.tabGBA) { - GBAOverride* gba = new GBAOverride; + std::unique_ptr gba(new GBAOverride); memset(gba->override.id, 0, 4); gba->override.savetype = static_cast(m_ui.savetype->currentIndex() - 1); gba->override.hardware = HW_NO_OVERRIDE; @@ -193,18 +196,18 @@ void OverrideView::updateOverrides() { gba->override.idleLoop = parsedIdleLoop; } + if (gba->override.savetype != SAVEDATA_AUTODETECT || gba->override.hardware != HW_NO_OVERRIDE || gba->override.idleLoop != IDLE_LOOP_NONE) { - m_controller->setOverride(gba); + m_override = std::move(gba); } else { - m_controller->clearOverride(); - delete gba; + m_override.reset(); } } #endif #ifdef M_CORE_GB if (m_ui.tabWidget->currentWidget() == m_ui.tabGB) { - GBOverride* gb = new GBOverride; + std::unique_ptr gb(new GBOverride); gb->override.mbc = s_mbcList[m_ui.mbc->currentIndex()]; gb->override.model = s_gbModelList[m_ui.gbModel->currentIndex()]; gb->override.gbColors[0] = m_gbColors[0]; @@ -214,20 +217,17 @@ void OverrideView::updateOverrides() { bool hasOverride = gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT; hasOverride = hasOverride || (m_gbColors[0] | m_gbColors[1] | m_gbColors[2] | m_gbColors[3]); if (hasOverride) { - m_controller->setOverride(gb); + m_override = std::move(gb); } else { - m_controller->clearOverride(); - delete gb; + m_override.reset(); } } #endif } -void OverrideView::gameStarted(mCoreThread* thread) { - if (!thread->core) { - gameStopped(); - return; - } +void OverrideView::gameStarted() { + CoreController::Interrupter interrupter(m_controller); + mCoreThread* thread = m_controller->thread(); m_ui.tabWidget->setEnabled(false); m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(true); @@ -278,6 +278,7 @@ void OverrideView::gameStarted(mCoreThread* thread) { } void OverrideView::gameStopped() { + m_controller.reset(); m_ui.tabWidget->setEnabled(true); m_ui.savetype->setCurrentIndex(0); m_ui.idleLoop->clear(); diff --git a/src/platform/qt/OverrideView.h b/src/platform/qt/OverrideView.h index 3d1c8a3aa..2b971aaae 100644 --- a/src/platform/qt/OverrideView.h +++ b/src/platform/qt/OverrideView.h @@ -8,10 +8,14 @@ #include +#include + #ifdef M_CORE_GB #include #endif +#include "Override.h" + #include "ui_OverrideView.h" struct mCoreThread; @@ -19,21 +23,23 @@ struct mCoreThread; namespace QGBA { class ConfigController; -class GameController; +class CoreController; class Override; class OverrideView : public QDialog { Q_OBJECT public: - OverrideView(GameController* controller, ConfigController* config, QWidget* parent = nullptr); + OverrideView(ConfigController* config, QWidget* parent = nullptr); + + void setController(std::shared_ptr controller); public slots: void saveOverride(); private slots: void updateOverrides(); - void gameStarted(mCoreThread*); + void gameStarted(); void gameStopped(); protected: @@ -42,7 +48,8 @@ protected: private: Ui::OverrideView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; + std::unique_ptr m_override; ConfigController* m_config; #ifdef M_CORE_GB diff --git a/src/platform/qt/PaletteView.cpp b/src/platform/qt/PaletteView.cpp index f669168de..394a54e7e 100644 --- a/src/platform/qt/PaletteView.cpp +++ b/src/platform/qt/PaletteView.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PaletteView.h" +#include "CoreController.h" #include "GBAApp.h" #include "LogController.h" #include "VFileDevice.h" @@ -24,13 +25,13 @@ using namespace QGBA; -PaletteView::PaletteView(GameController* controller, QWidget* parent) +PaletteView::PaletteView(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) { m_ui.setupUi(this); - connect(m_controller, &GameController::frameAvailable, this, &PaletteView::updatePalette); + connect(controller.get(), &CoreController::frameAvailable, this, &PaletteView::updatePalette); m_ui.bgGrid->setDimensions(QSize(16, 16)); m_ui.objGrid->setDimensions(QSize(16, 16)); int count = 256; @@ -61,7 +62,7 @@ PaletteView::PaletteView(GameController* controller, QWidget* parent) connect(m_ui.exportBG, &QAbstractButton::clicked, [this, count] () { exportPalette(0, count); }); connect(m_ui.exportOBJ, &QAbstractButton::clicked, [this, count] () { exportPalette(count, count); }); - connect(controller, &GameController::gameStopped, this, &QWidget::close); + connect(controller.get(), &CoreController::stopping, this, &QWidget::close); } void PaletteView::updatePalette() { @@ -133,7 +134,7 @@ void PaletteView::exportPalette(int start, int length) { length = 512 - start; } - GameController::Interrupter interrupter(m_controller); + CoreController::Interrupter interrupter(m_controller); QString filename = GBAApp::app()->getSaveFileName(this, tr("Export palette"), tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); diff --git a/src/platform/qt/PaletteView.h b/src/platform/qt/PaletteView.h index 057d49a5b..d3bbb6023 100644 --- a/src/platform/qt/PaletteView.h +++ b/src/platform/qt/PaletteView.h @@ -8,20 +8,22 @@ #include -#include "GameController.h" +#include + #include "Swatch.h" #include "ui_PaletteView.h" namespace QGBA { +class CoreController; class Swatch; class PaletteView : public QWidget { Q_OBJECT public: - PaletteView(GameController* controller, QWidget* parent = nullptr); + PaletteView(std::shared_ptr controller, QWidget* parent = nullptr); public slots: void updatePalette(); @@ -34,7 +36,7 @@ private: Ui::PaletteView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; }; } diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index d657731c0..dc53347eb 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -6,7 +6,7 @@ #include "ROMInfo.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include #ifdef M_CORE_GB @@ -21,21 +21,17 @@ using namespace QGBA; -ROMInfo::ROMInfo(GameController* controller, QWidget* parent) +ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) { m_ui.setupUi(this); - if (!controller->isLoaded()) { - return; - } - #ifdef USE_SQLITE3 const NoIntroDB* db = GBAApp::app()->gameDB(); #endif uint32_t crc32 = 0; - GameController::Interrupter interrupter(controller); + CoreController::Interrupter interrupter(controller); mCore* core = controller->thread()->core; char title[17] = {}; core->getGameTitle(core, title); diff --git a/src/platform/qt/ROMInfo.h b/src/platform/qt/ROMInfo.h index 05ecd23a1..b5ce734aa 100644 --- a/src/platform/qt/ROMInfo.h +++ b/src/platform/qt/ROMInfo.h @@ -8,17 +8,19 @@ #include +#include + #include "ui_ROMInfo.h" namespace QGBA { -class GameController; +class CoreController; class ROMInfo : public QDialog { Q_OBJECT public: - ROMInfo(GameController* controller, QWidget* parent = nullptr); + ROMInfo(std::shared_ptr controller, QWidget* parent = nullptr); private: Ui::ROMInfo m_ui; diff --git a/src/platform/qt/SensorView.cpp b/src/platform/qt/SensorView.cpp index 8f107df46..75cc5c3ae 100644 --- a/src/platform/qt/SensorView.cpp +++ b/src/platform/qt/SensorView.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SensorView.h" -#include "GameController.h" +#include "CoreController.h" #include "GamepadAxisEvent.h" #include "InputController.h" @@ -14,9 +14,8 @@ using namespace QGBA; -SensorView::SensorView(GameController* controller, InputController* input, QWidget* parent) +SensorView::SensorView(InputController* input, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) - , m_controller(controller) , m_input(input) , m_rotation(input->rotationSource()) { @@ -26,22 +25,13 @@ SensorView::SensorView(GameController* controller, InputController* input, QWidg this, &SensorView::setLuminanceValue); connect(m_ui.lightSlide, &QAbstractSlider::valueChanged, this, &SensorView::setLuminanceValue); - connect(m_ui.timeNoOverride, &QAbstractButton::clicked, controller, &GameController::setRealTime); - connect(m_ui.timeFixed, &QRadioButton::clicked, [controller, this] () { - controller->setFixedTime(m_ui.time->dateTime()); - }); - connect(m_ui.timeFakeEpoch, &QRadioButton::clicked, [controller, this] () { - controller->setFakeEpoch(m_ui.time->dateTime()); - }); - connect(m_ui.time, &QDateTimeEdit::dateTimeChanged, [controller, this] (const QDateTime&) { + connect(m_ui.time, &QDateTimeEdit::dateTimeChanged, [this] (const QDateTime&) { m_ui.timeButtons->checkedButton()->clicked(); }); - connect(m_ui.timeNow, &QPushButton::clicked, [controller, this] () { + connect(m_ui.timeNow, &QPushButton::clicked, [this] () { m_ui.time->setDateTime(QDateTime::currentDateTime()); }); - connect(m_controller, &GameController::luminanceValueChanged, this, &SensorView::luminanceValueChanged); - m_timer.setInterval(2); connect(&m_timer, &QTimer::timeout, this, &SensorView::updateSensors); if (!m_rotation || !m_rotation->readTiltX || !m_rotation->readTiltY) { @@ -66,6 +56,22 @@ SensorView::SensorView(GameController* controller, InputController* input, QWidg m_input->setGyroSensitivity(value * 1e8f); }); m_input->stealFocus(this); + connect(m_input, &InputController::luminanceValueChanged, this, &SensorView::luminanceValueChanged); +} + +void SensorView::setController(std::shared_ptr controller) { + m_controller = controller; + connect(m_ui.timeNoOverride, &QAbstractButton::clicked, controller.get(), &CoreController::setRealTime); + connect(m_ui.timeFixed, &QRadioButton::clicked, [controller, this] () { + controller->setFixedTime(m_ui.time->dateTime()); + }); + connect(m_ui.timeFakeEpoch, &QRadioButton::clicked, [controller, this] () { + controller->setFakeEpoch(m_ui.time->dateTime()); + }); + + connect(controller.get(), &CoreController::stopping, [this]() { + m_controller.reset(); + }); } void SensorView::jiggerer(QAbstractButton* button, void (InputController::*setter)(int)) { @@ -107,16 +113,7 @@ bool SensorView::eventFilter(QObject*, QEvent* event) { } void SensorView::updateSensors() { - GameController::Interrupter interrupter(m_controller); - if (m_rotation->sample && - (!m_controller->isLoaded() || !(static_cast(m_controller->thread()->core->board)->memory.hw.devices & (HW_GYRO | HW_TILT)))) { - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); + if (m_rotation->sample && (!m_controller || m_controller->isPaused())) { m_rotation->sample(m_rotation); } if (m_rotation->readTiltX && m_rotation->readTiltY) { @@ -132,7 +129,9 @@ void SensorView::updateSensors() { void SensorView::setLuminanceValue(int value) { value = std::max(0, std::min(value, 255)); - m_controller->setLuminanceValue(value); + if (m_input) { + m_input->setLuminanceValue(value); + } } void SensorView::luminanceValueChanged(int value) { diff --git a/src/platform/qt/SensorView.h b/src/platform/qt/SensorView.h index 666c37f41..78dc4d82d 100644 --- a/src/platform/qt/SensorView.h +++ b/src/platform/qt/SensorView.h @@ -10,6 +10,7 @@ #include #include +#include #include "ui_SensorView.h" @@ -18,7 +19,7 @@ struct mRotationSource; namespace QGBA { class ConfigController; -class GameController; +class CoreController; class GamepadAxisEvent; class InputController; @@ -26,7 +27,9 @@ class SensorView : public QDialog { Q_OBJECT public: - SensorView(GameController* controller, InputController* input, QWidget* parent = nullptr); + SensorView(InputController* input, QWidget* parent = nullptr); + + void setController(std::shared_ptr); protected: bool eventFilter(QObject*, QEvent* event) override; @@ -41,7 +44,7 @@ private: Ui::SensorView m_ui; std::function m_jiggered; - GameController* m_controller; + std::shared_ptr m_controller; InputController* m_input; mRotationSource* m_rotation; QTimer m_timer; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index bc9b7a0d4..0a361f280 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -188,19 +188,26 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC SettingsView::~SettingsView() { #if defined(BUILD_GL) || defined(BUILD_GLES) - if (m_shader) { - m_ui.stackedWidget->removeWidget(m_shader); - m_shader->setParent(nullptr); - } + setShaderSelector(nullptr); #endif } void SettingsView::setShaderSelector(ShaderSelector* shaderSelector) { #if defined(BUILD_GL) || defined(BUILD_GLES) + if (m_shader) { + auto items = m_ui.tabs->findItems(tr("Shaders"), Qt::MatchFixedString); + for (const auto& item : items) { + m_ui.tabs->removeItemWidget(item); + } + m_ui.stackedWidget->removeWidget(m_shader); + m_shader->setParent(nullptr); + } m_shader = shaderSelector; - m_ui.stackedWidget->addWidget(m_shader); - m_ui.tabs->addItem(tr("Shaders")); - connect(m_ui.buttonBox, &QDialogButtonBox::accepted, m_shader, &ShaderSelector::saved); + if (shaderSelector) { + m_ui.stackedWidget->addWidget(m_shader); + m_ui.tabs->addItem(tr("Shaders")); + connect(m_ui.buttonBox, &QDialogButtonBox::accepted, m_shader, &ShaderSelector::saved); + } #endif } @@ -283,6 +290,7 @@ void SettingsView::updateConfig() { if (displayDriver != m_controller->getQtOption("displayDriver")) { m_controller->setQtOption("displayDriver", displayDriver); Display::setDriver(static_cast(displayDriver.toInt())); + setShaderSelector(nullptr); emit displayDriverChanged(); } diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index eb74a57a6..be8df4b15 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TileView.h" +#include "CoreController.h" #include "GBAApp.h" #include @@ -16,7 +17,7 @@ using namespace QGBA; -TileView::TileView(GameController* controller, QWidget* parent) +TileView::TileView(std::shared_ptr controller, QWidget* parent) : AssetView(controller, parent) , m_controller(controller) { @@ -79,40 +80,40 @@ TileView::TileView(GameController* controller, QWidget* parent) void TileView::updateTilesGBA(bool force) { if (m_ui.palette256->isChecked()) { m_ui.tiles->setTileCount(1536); - mTileCacheSetPalette(m_tileCache.get(), 1); + mTileCacheSetPalette(m_tileCache, 1); for (int i = 0; i < 1024; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * i], i, 0); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * i], i, 0); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, 0)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, 0)); } } for (int i = 1024; i < 1536; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * i], i, 1); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * i], i, 1); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, 1)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, 1)); } } } else { m_ui.tiles->setTileCount(3072); - mTileCacheSetPalette(m_tileCache.get(), 0); + mTileCacheSetPalette(m_tileCache, 0); for (int i = 0; i < 2048; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * i], i, m_paletteId); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * i], i, m_paletteId); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, m_paletteId)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, m_paletteId)); } } for (int i = 2048; i < 3072; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * i], i, m_paletteId + 16); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * i], i, m_paletteId + 16); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, m_paletteId + 16)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, m_paletteId + 16)); } } } @@ -124,13 +125,13 @@ void TileView::updateTilesGB(bool force) { const GB* gb = static_cast(m_controller->thread()->core->board); int count = gb->model >= GB_MODEL_CGB ? 1024 : 512; m_ui.tiles->setTileCount(count); - mTileCacheSetPalette(m_tileCache.get(), 0); + mTileCacheSetPalette(m_tileCache, 0); for (int i = 0; i < count; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[16 * i], i, m_paletteId); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[16 * i], i, m_paletteId); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, m_paletteId)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, m_paletteId)); } } } diff --git a/src/platform/qt/TileView.h b/src/platform/qt/TileView.h index 6e35df355..2cdb276c7 100644 --- a/src/platform/qt/TileView.h +++ b/src/platform/qt/TileView.h @@ -7,7 +7,6 @@ #define QGBA_TILE_VIEW #include "AssetView.h" -#include "GameController.h" #include "ui_TileView.h" @@ -15,11 +14,13 @@ namespace QGBA { +class CoreController; + class TileView : public AssetView { Q_OBJECT public: - TileView(GameController* controller, QWidget* parent = nullptr); + TileView(std::shared_ptr controller, QWidget* parent = nullptr); public slots: void updatePalette(int); @@ -34,7 +35,7 @@ private: Ui::TileView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; mTileCacheEntry m_tileStatus[3072 * 32] = {}; // TODO: Correct size int m_paletteId = 0; }; diff --git a/src/platform/qt/VFileDevice.cpp b/src/platform/qt/VFileDevice.cpp index 04da09353..d3b8c8bed 100644 --- a/src/platform/qt/VFileDevice.cpp +++ b/src/platform/qt/VFileDevice.cpp @@ -13,7 +13,33 @@ VFileDevice::VFileDevice(VFile* vf, QObject* parent) : QIODevice(parent) , m_vf(vf) { - // Nothing to do + // TODO: Correct mode + if (vf) { + setOpenMode(QIODevice::ReadWrite); + } +} + +void VFileDevice::close() { + QIODevice::close(); + m_vf->close(m_vf); + m_vf = nullptr; +} + +bool VFileDevice::resize(qint64 sz) { + m_vf->truncate(m_vf, sz); + return true; +} + +bool VFileDevice::seek(qint64 pos) { + QIODevice::seek(pos); + return m_vf->seek(m_vf, pos, SEEK_SET) == pos; +} + +VFileDevice& VFileDevice::operator=(VFile* vf) { + close(); + m_vf = vf; + setOpenMode(QIODevice::ReadWrite); + return *this; } qint64 VFileDevice::readData(char* data, qint64 maxSize) { diff --git a/src/platform/qt/VFileDevice.h b/src/platform/qt/VFileDevice.h index 71baecd1a..e4e6b17e0 100644 --- a/src/platform/qt/VFileDevice.h +++ b/src/platform/qt/VFileDevice.h @@ -17,7 +17,16 @@ class VFileDevice : public QIODevice { Q_OBJECT public: - VFileDevice(VFile* vf, QObject* parent = nullptr); + VFileDevice(VFile* vf = nullptr, QObject* parent = nullptr); + + virtual void close() override; + virtual bool seek(qint64 pos) override; + virtual qint64 size() const override; + + bool resize(qint64 sz); + + VFileDevice& operator=(VFile*); + operator VFile*() { return m_vf; } static VFile* open(const QString& path, int mode); static VDir* openDir(const QString& path); @@ -26,7 +35,6 @@ public: protected: virtual qint64 readData(char* data, qint64 maxSize) override; virtual qint64 writeData(const char* data, qint64 maxSize) override; - virtual qint64 size() const override; private: VFile* m_vf; diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index e5f28458e..ba769e2c7 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -195,6 +195,14 @@ VideoView::~VideoView() { free(m_containerCstr); } +void VideoView::setController(std::shared_ptr controller) { + connect(controller.get(), &CoreController::stopping, this, &VideoView::stopRecording); + connect(this, &VideoView::recordingStarted, controller.get(), &CoreController::setAVStream); + connect(this, &VideoView::recordingStopped, controller.get(), &CoreController::clearAVStream, Qt::DirectConnection); + + setNativeResolution(controller->screenDimensions()); +} + void VideoView::startRecording() { if (!validateSettings()) { return; diff --git a/src/platform/qt/VideoView.h b/src/platform/qt/VideoView.h index 6f3ef3ecb..e84d63fe8 100644 --- a/src/platform/qt/VideoView.h +++ b/src/platform/qt/VideoView.h @@ -10,12 +10,18 @@ #include +#include + +#include "CoreController.h" + #include "ui_VideoView.h" #include "feature/ffmpeg/ffmpeg-encoder.h" namespace QGBA { +class CoreController; + class VideoView : public QWidget { Q_OBJECT @@ -26,6 +32,8 @@ public: mAVStream* getStream() { return &m_encoder.d; } public slots: + void setController(std::shared_ptr); + void startRecording(); void stopRecording(); void setNativeResolution(const QSize&); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 046983b81..55b9eb8c9 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2017 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -20,12 +20,14 @@ #endif #include "AboutScreen.h" +#include "AudioProcessor.h" #include "CheatsView.h" #include "ConfigController.h" +#include "CoreController.h" #include "DebuggerConsole.h" #include "DebuggerConsoleController.h" #include "Display.h" -#include "GameController.h" +#include "CoreController.h" #include "GBAApp.h" #include "GDBController.h" #include "GDBWindow.h" @@ -62,8 +64,9 @@ using namespace QGBA; -Window::Window(ConfigController* config, int playerId, QWidget* parent) +Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWidget* parent) : QMainWindow(parent) + , m_manager(manager) , m_logView(new LogView(&m_log)) , m_screenWidget(new WindowBackground()) , m_config(config) @@ -73,20 +76,12 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true); setAttribute(Qt::WA_DeleteOnClose); - m_controller = new GameController(this); - m_controller->setInputController(&m_inputController); updateTitle(); - - m_display = Display::create(this); -#if defined(BUILD_GL) || defined(BUILD_GLES) - m_shaderView = new ShaderSelector(m_display, m_config); -#endif + reloadDisplayDriver(); m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); m_logo = m_logo; // Free memory left over in old pixmap - m_screenWidget->setMinimumSize(m_display->minimumSize()); - m_screenWidget->setSizePolicy(m_display->sizePolicy()); #if defined(M_CORE_GBA) float i = 2; #elif defined(M_CORE_GB) @@ -102,7 +97,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) ConfigOption* showLibrary = m_config->addOption("showLibrary"); showLibrary->connect([this](const QVariant& value) { if (value.toBool()) { - if (m_controller->isLoaded()) { + if (m_controller) { m_screenWidget->layout()->addWidget(m_libraryView); } else { attachWidget(m_libraryView); @@ -122,7 +117,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) VFile* output = m_libraryView->selectedVFile(); if (output) { QPair path = m_libraryView->selectedPath(); - m_controller->loadGame(output, path.second, path.first); + setController(m_manager->loadGame(output, path.second, path.first), path.first + "/" + path.second); } }); #endif @@ -137,61 +132,9 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) m_screenWidget->setLockAspectRatio(true); setCentralWidget(m_screenWidget); - connect(m_controller, &GameController::gameStarted, this, &Window::gameStarted); - connect(m_controller, &GameController::gameStarted, &m_inputController, &InputController::suspendScreensaver); - connect(m_controller, &GameController::gameStopped, m_display, &Display::stopDrawing); - connect(m_controller, &GameController::gameStopped, this, &Window::gameStopped); - connect(m_controller, &GameController::gameStopped, &m_inputController, &InputController::resumeScreensaver); - connect(m_controller, &GameController::stateLoaded, m_display, &Display::forceDraw); - connect(m_controller, &GameController::rewound, m_display, &Display::forceDraw); - connect(m_controller, &GameController::gamePaused, [this](mCoreThread* context) { - unsigned width, height; - context->core->desiredVideoDimensions(context->core, &width, &height); - QImage currentImage(reinterpret_cast(m_controller->drawContext()), width, height, - width * BYTES_PER_PIXEL, QImage::Format_RGBX8888); - QPixmap pixmap; - pixmap.convertFromImage(currentImage); - m_screenWidget->setPixmap(pixmap); - }); - connect(m_controller, &GameController::gamePaused, m_display, &Display::pauseDrawing); -#ifndef Q_OS_MAC - connect(m_controller, &GameController::gamePaused, menuBar(), &QWidget::show); - connect(m_controller, &GameController::gameUnpaused, [this]() { - if(isFullScreen()) { - menuBar()->hide(); - } - }); -#endif - connect(m_controller, &GameController::gamePaused, &m_inputController, &InputController::resumeScreensaver); - connect(m_controller, &GameController::gameUnpaused, m_display, &Display::unpauseDrawing); - connect(m_controller, &GameController::gameUnpaused, &m_inputController, &InputController::suspendScreensaver); - connect(m_controller, &GameController::postLog, &m_log, &LogController::postLog); - connect(m_controller, &GameController::frameAvailable, this, &Window::recordFrame); - connect(m_controller, &GameController::frameAvailable, m_display, &Display::framePosted); - connect(m_controller, &GameController::gameCrashed, this, &Window::gameCrashed); - connect(m_controller, &GameController::gameFailed, this, &Window::gameFailed); - connect(m_controller, &GameController::unimplementedBiosCall, this, &Window::unimplementedBiosCall); - connect(m_controller, &GameController::statusPosted, m_display, &Display::showMessage); - connect(&m_log, &LogController::levelsSet, m_controller, &GameController::setLogLevel); - connect(&m_log, &LogController::levelsEnabled, m_controller, &GameController::enableLogLevel); - connect(&m_log, &LogController::levelsDisabled, m_controller, &GameController::disableLogLevel); - connect(this, &Window::startDrawing, m_display, &Display::startDrawing, Qt::QueuedConnection); - connect(this, &Window::shutdown, m_display, &Display::stopDrawing); - connect(this, &Window::shutdown, m_controller, &GameController::closeGame); connect(this, &Window::shutdown, m_logView, &QWidget::hide); - connect(this, &Window::audioBufferSamplesChanged, m_controller, &GameController::setAudioBufferSamples); - connect(this, &Window::sampleRateChanged, m_controller, &GameController::setAudioSampleRate); - connect(this, &Window::fpsTargetChanged, m_controller, &GameController::setFPSTarget); connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS); connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck); - connect(m_display, &Display::hideCursor, [this]() { - if (static_cast(m_screenWidget->layout())->currentWidget() == m_display) { - m_screenWidget->setCursor(Qt::BlankCursor); - } - }); - connect(m_display, &Display::showCursor, [this]() { - m_screenWidget->unsetCursor(); - }); connect(&m_inputController, &InputController::profileLoaded, m_shortcutController, &ShortcutController::loadProfile); m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL); @@ -204,6 +147,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) Window::~Window() { delete m_logView; + delete m_overrideView; #ifdef USE_FFMPEG delete m_videoView; @@ -221,18 +165,14 @@ Window::~Window() { void Window::argumentsPassed(mArguments* args) { loadConfig(); - if (args->patch) { - m_controller->loadPatch(args->patch); - } - if (args->fname) { - m_controller->loadGame(args->fname); + setController(m_manager->loadGame(args->fname), args->fname); } #ifdef USE_GDB_STUB if (args->debuggerType == DEBUGGER_GDB) { if (!m_gdbController) { - m_gdbController = new GDBController(m_controller, this); + m_gdbController = new GDBController(this); m_gdbController->listen(); } } @@ -255,19 +195,6 @@ void Window::loadConfig() { const mCoreOptions* opts = m_config->options(); reloadConfig(); - // TODO: Move these to ConfigController - if (opts->fpsTarget) { - emit fpsTargetChanged(opts->fpsTarget); - } - - if (opts->audioBuffers) { - emit audioBufferSamplesChanged(opts->audioBuffers); - } - - if (opts->sampleRate) { - emit sampleRateChanged(opts->sampleRate); - } - if (opts->width && opts->height) { resizeFrame(QSize(opts->width, opts->height)); } @@ -276,22 +203,10 @@ void Window::loadConfig() { enterFullScreen(); } -#if defined(BUILD_GL) || defined(BUILD_GLES) - if (opts->shader) { - struct VDir* shader = VDirOpen(opts->shader); - if (shader) { - m_display->setShaders(shader); - m_shaderView->refreshShaders(); - shader->close(shader); - } - } -#endif - m_mruFiles = m_config->getMRU(); updateMRU(); m_inputController.setConfiguration(m_config); - m_controller->setUseBIOS(opts->useBios); } void Window::reloadConfig() { @@ -299,7 +214,13 @@ void Window::reloadConfig() { m_log.setLevels(opts->logLevel); - m_controller->setConfig(m_config->config()); + if (m_controller) { + m_controller->loadConfig(m_config); + if (m_audioProcessor) { + m_audioProcessor->setBufferSamples(opts->audioBuffers); + m_audioProcessor->requestSampleRate(opts->sampleRate); + } + } m_display->lockAspectRatio(opts->lockAspectRatio); m_display->filter(opts->resampleVideo); @@ -372,7 +293,7 @@ QString Window::getFiltersArchive() const { void Window::selectROM() { QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters()); if (!filename.isEmpty()) { - m_controller->loadGame(filename); + setController(m_manager->loadGame(filename), filename); } } @@ -387,7 +308,7 @@ void Window::selectROMInArchive() { VFile* output = archiveInspector->selectedVFile(); QPair path = archiveInspector->selectedPath(); if (output) { - m_controller->loadGame(output, path.second, path.first); + setController(m_manager->loadGame(output, path.second, path.first), path.first + "/" + path.second); } archiveInspector->close(); }); @@ -421,28 +342,32 @@ void Window::selectSave(bool temporary) { } void Window::multiplayerChanged() { + if (!m_controller) { + return; + } int attached = 1; MultiplayerController* multiplayer = m_controller->multiplayerController(); if (multiplayer) { attached = multiplayer->attached(); } - if (m_controller->isLoaded()) { - for (QAction* action : m_nonMpActions) { - action->setDisabled(attached > 1); - } + for (QAction* action : m_nonMpActions) { + action->setDisabled(attached > 1); } } void Window::selectPatch() { QString filename = GBAApp::app()->getOpenFileName(this, tr("Select patch"), tr("Patches (*.ips *.ups *.bps)")); if (!filename.isEmpty()) { - m_controller->loadPatch(filename); + if (m_controller) { + m_controller->loadPatch(filename); + } else { + m_pendingPatch = filename; + } } } void Window::openView(QWidget* widget) { connect(this, &Window::shutdown, widget, &QWidget::close); - connect(m_controller, &GameController::gameStopped, widget, &QWidget::close); widget->setAttribute(Qt::WA_DeleteOnClose); widget->show(); } @@ -465,23 +390,17 @@ void Window::openSettingsWindow() { SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController, m_shortcutController); #if defined(BUILD_GL) || defined(BUILD_GLES) if (m_display->supportsShaders()) { - settingsWindow->setShaderSelector(m_shaderView); + settingsWindow->setShaderSelector(m_shaderView.get()); } #endif - connect(settingsWindow, &SettingsView::biosLoaded, m_controller, &GameController::loadBIOS); - connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver); - connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart); + connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::reloadDisplayDriver); + connect(settingsWindow, &SettingsView::audioDriverChanged, this, &Window::reloadAudioDriver); connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart); connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig); connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear); openView(settingsWindow); } -void Window::openAboutScreen() { - AboutScreen* about = new AboutScreen(); - openView(about); -} - void Window::startVideoLog() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Select video log"), tr("Video logs (*.mvl)")); if (!filename.isEmpty()) { @@ -489,18 +408,19 @@ void Window::startVideoLog() { } } -template -std::function Window::openTView(A arg) { +template +std::function Window::openTView(A... arg) { return [=]() { - T* view = new T(m_controller, arg); + T* view = new T(arg...); openView(view); }; } -template -std::function Window::openTView() { + +template +std::function Window::openControllerTView(A... arg) { return [=]() { - T* view = new T(m_controller); + T* view = new T(m_controller, arg...); openView(view); }; } @@ -509,15 +429,8 @@ std::function Window::openTView() { void Window::openVideoWindow() { if (!m_videoView) { m_videoView = new VideoView(); - connect(m_videoView, &VideoView::recordingStarted, m_controller, &GameController::setAVStream); - connect(m_videoView, &VideoView::recordingStopped, m_controller, &GameController::clearAVStream, Qt::DirectConnection); - connect(m_controller, &GameController::gameStopped, m_videoView, &VideoView::stopRecording); - connect(m_controller, &GameController::gameStopped, m_videoView, &QWidget::close); - connect(m_controller, &GameController::gameStarted, [this]() { - m_videoView->setNativeResolution(m_controller->screenDimensions()); - }); - if (m_controller->isLoaded()) { - m_videoView->setNativeResolution(m_controller->screenDimensions()); + if (m_controller) { + m_videoView->setController(m_controller); } connect(this, &Window::shutdown, m_videoView, &QWidget::close); } @@ -529,10 +442,9 @@ void Window::openVideoWindow() { void Window::openGIFWindow() { if (!m_gifView) { m_gifView = new GIFView(); - connect(m_gifView, &GIFView::recordingStarted, m_controller, &GameController::setAVStream); - connect(m_gifView, &GIFView::recordingStopped, m_controller, &GameController::clearAVStream, Qt::DirectConnection); - connect(m_controller, &GameController::gameStopped, m_gifView, &GIFView::stopRecording); - connect(m_controller, &GameController::gameStopped, m_gifView, &QWidget::close); + if (m_controller) { + m_gifView->setController(m_controller); + } connect(this, &Window::shutdown, m_gifView, &QWidget::close); } m_gifView->show(); @@ -542,9 +454,11 @@ void Window::openGIFWindow() { #ifdef USE_GDB_STUB void Window::gdbOpen() { if (!m_gdbController) { - m_gdbController = new GDBController(m_controller, this); + m_gdbController = new GDBController(this); } GDBWindow* window = new GDBWindow(m_gdbController); + m_gdbController->setController(m_controller); + connect(m_controller.get(), &CoreController::stopping, window, &QWidget::close); openView(window); } #endif @@ -552,9 +466,12 @@ void Window::gdbOpen() { #ifdef USE_DEBUGGERS void Window::consoleOpen() { if (!m_console) { - m_console = new DebuggerConsoleController(m_controller, this); + m_console = new DebuggerConsoleController(this); } DebuggerConsole* window = new DebuggerConsole(m_console); + if (m_controller) { + m_console->setController(m_controller); + } openView(window); } #endif @@ -569,7 +486,9 @@ void Window::keyPressEvent(QKeyEvent* event) { QWidget::keyPressEvent(event); return; } - m_controller->keyPressed(key); + if (m_controller) { + m_controller->addKey(key); + } event->accept(); } @@ -583,7 +502,9 @@ void Window::keyReleaseEvent(QKeyEvent* event) { QWidget::keyPressEvent(event); return; } - m_controller->keyReleased(key); + if (m_controller) { + m_controller->clearKey(key); + } event->accept(); } @@ -595,7 +516,7 @@ void Window::resizeEvent(QResizeEvent* event) { int factor = 0; QSize size(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - if (m_controller->isLoaded()) { + if (m_controller) { size = m_controller->screenDimensions(); } if (m_screenWidget->width() % size.width() == 0 && m_screenWidget->height() % size.height() == 0 && @@ -650,9 +571,6 @@ void Window::focusInEvent(QFocusEvent*) { } void Window::focusOutEvent(QFocusEvent*) { - m_controller->setTurbo(false, false); - m_controller->stopRewinding(); - m_controller->clearKeys(); } void Window::dragEnterEvent(QDragEnterEvent* event) { @@ -674,7 +592,7 @@ void Window::dropEvent(QDropEvent* event) { return; } event->accept(); - m_controller->loadGame(url.toLocalFile()); + setController(m_manager->loadGame(url.toLocalFile()), url.toLocalFile()); } void Window::mouseDoubleClickEvent(QMouseEvent* event) { @@ -694,7 +612,7 @@ void Window::enterFullScreen() { } showFullScreen(); #ifndef Q_OS_MAC - if (m_controller->isLoaded() && !m_controller->isPaused()) { + if (m_controller && !m_controller->isPaused()) { menuBar()->hide(); } #endif @@ -717,36 +635,27 @@ void Window::toggleFullScreen() { } } -void Window::gameStarted(mCoreThread* context, const QString& fname) { - if (!mCoreThreadIsActive(context)) { - return; - } - emit startDrawing(context); +void Window::gameStarted() { for (QAction* action : m_gameActions) { action->setDisabled(false); } #ifdef M_CORE_GBA for (QAction* action : m_gbaActions) { - action->setDisabled(context->core->platform(context->core) != PLATFORM_GBA); + action->setDisabled(m_controller->platform() != PLATFORM_GBA); } #endif multiplayerChanged(); - if (!fname.isEmpty()) { - setWindowFilePath(fname); - appendMRU(fname); - } updateTitle(); - unsigned width, height; - context->core->desiredVideoDimensions(context->core, &width, &height); - m_display->setMinimumSize(width, height); + QSize size = m_controller->screenDimensions(); + m_display->setMinimumSize(size); m_screenWidget->setMinimumSize(m_display->minimumSize()); - m_screenWidget->setDimensions(width, height); + m_screenWidget->setDimensions(size.width(), size.height()); m_config->updateOption("lockIntegerScaling"); m_config->updateOption("lockAspectRatio"); if (m_savedScale > 0) { - resizeFrame(QSize(width, height) * m_savedScale); + resizeFrame(size * m_savedScale); } - attachWidget(m_display); + attachWidget(m_display.get()); #ifndef Q_OS_MAC if (isFullScreen()) { @@ -758,38 +667,40 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) { m_fpsTimer.start(); m_focusCheck.start(); - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { - mCore* core = m_controller->thread()->core; - const mCoreChannelInfo* videoLayers; - const mCoreChannelInfo* audioChannels; - size_t nVideo = core->listVideoLayers(core, &videoLayers); - size_t nAudio = core->listAudioChannels(core, &audioChannels); + CoreController::Interrupter interrupter(m_controller, true); + mCore* core = m_controller->thread()->core; + m_videoLayers->clear(); + m_audioChannels->clear(); + const mCoreChannelInfo* videoLayers; + const mCoreChannelInfo* audioChannels; + size_t nVideo = core->listVideoLayers(core, &videoLayers); + size_t nAudio = core->listAudioChannels(core, &audioChannels); - if (nVideo) { - for (size_t i = 0; i < nVideo; ++i) { - QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers); - action->setCheckable(true); - action->setChecked(true); - connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) { - m_controller->setVideoLayerEnabled(videoLayers[i].id, enable); - }); - m_videoLayers->addAction(action); - } - } - if (nAudio) { - for (size_t i = 0; i < nAudio; ++i) { - QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); - action->setCheckable(true); - action->setChecked(true); - connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { - m_controller->setAudioChannelEnabled(audioChannels[i].id, enable); - }); - m_audioChannels->addAction(action); - } + if (nVideo) { + for (size_t i = 0; i < nVideo; ++i) { + QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) { + m_controller->thread()->core->enableVideoLayer(m_controller->thread()->core, videoLayers[i].id, enable); + }); + m_videoLayers->addAction(action); } } - m_controller->threadContinue(); + if (nAudio) { + for (size_t i = 0; i < nAudio; ++i) { + QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { + m_controller->thread()->core->enableAudioChannel(m_controller->thread()->core, audioChannels[i].id, enable); + }); + m_audioChannels->addAction(action); + } + } + m_display->startDrawing(m_controller); + + reloadAudioDriver(); } void Window::gameStopped() { @@ -803,7 +714,7 @@ void Window::gameStopped() { } setWindowFilePath(QString()); updateTitle(); - detachWidget(m_display); + detachWidget(m_display.get()); m_screenWidget->setDimensions(m_logo.width(), m_logo.height()); m_screenWidget->setLockIntegerScaling(false); m_screenWidget->setLockAspectRatio(true); @@ -821,6 +732,8 @@ void Window::gameStopped() { m_fpsTimer.stop(); m_focusCheck.stop(); + + emit paused(false); } void Window::gameCrashed(const QString& errorMessage) { @@ -829,7 +742,7 @@ void Window::gameCrashed(const QString& errorMessage) { QMessageBox::Ok, this, Qt::Sheet); crash->setAttribute(Qt::WA_DeleteOnClose); crash->show(); - connect(m_controller, &GameController::gameStarted, crash, &QWidget::close); + m_controller->stop(); } void Window::gameFailed() { @@ -838,7 +751,6 @@ void Window::gameFailed() { QMessageBox::Ok, this, Qt::Sheet); fail->setAttribute(Qt::WA_DeleteOnClose); fail->show(); - connect(m_controller, &GameController::gameStarted, fail, &QWidget::close); } void Window::unimplementedBiosCall(int call) { @@ -855,6 +767,74 @@ void Window::unimplementedBiosCall(int call) { fail->show(); } +void Window::reloadDisplayDriver() { + if (m_controller) { + m_display->stopDrawing(); + detachWidget(m_display.get()); + } + m_display = std::move(std::unique_ptr(Display::create(this))); +#if defined(BUILD_GL) || defined(BUILD_GLES) + m_shaderView.reset(); + m_shaderView = std::make_unique(m_display.get(), m_config); +#endif + m_screenWidget->setMinimumSize(m_display->minimumSize()); + m_screenWidget->setSizePolicy(m_display->sizePolicy()); + connect(this, &Window::shutdown, m_display.get(), &Display::stopDrawing); + connect(m_display.get(), &Display::hideCursor, [this]() { + if (static_cast(m_screenWidget->layout())->currentWidget() == m_display.get()) { + m_screenWidget->setCursor(Qt::BlankCursor); + } + }); + connect(m_display.get(), &Display::showCursor, [this]() { + m_screenWidget->unsetCursor(); + }); + + const mCoreOptions* opts = m_config->options(); + m_display->lockAspectRatio(opts->lockAspectRatio); + m_display->filter(opts->resampleVideo); +#if defined(BUILD_GL) || defined(BUILD_GLES) + if (opts->shader) { + struct VDir* shader = VDirOpen(opts->shader); + if (shader && m_display->supportsShaders()) { + m_display->setShaders(shader); + m_shaderView->refreshShaders(); + shader->close(shader); + } + } +#endif + + if (m_controller) { + connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); + connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); + connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw); + connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing); + connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); + connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted); + connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); + + attachWidget(m_display.get()); + m_display->startDrawing(m_controller); + } +} + +void Window::reloadAudioDriver() { + if (!m_controller) { + return; + } + if (m_audioProcessor) { + m_audioProcessor->stop(); + m_audioProcessor.reset(); + } + + const mCoreOptions* opts = m_config->options(); + m_audioProcessor = std::move(std::unique_ptr(AudioProcessor::create())); + m_audioProcessor->setInput(m_controller); + m_audioProcessor->setBufferSamples(opts->audioBuffers); + m_audioProcessor->requestSampleRate(opts->sampleRate); + m_audioProcessor->start(); + connect(m_controller.get(), &CoreController::stopping, m_audioProcessor.get(), &AudioProcessor::stop); +} + void Window::tryMakePortable() { QMessageBox* confirm = new QMessageBox(QMessageBox::Question, tr("Really make portable?"), tr("This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?"), @@ -893,8 +873,8 @@ void Window::showFPS() { void Window::updateTitle(float fps) { QString title; - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { + if (m_controller) { + CoreController::Interrupter interrupter(m_controller); const NoIntroDB* db = GBAApp::app()->gameDB(); NoIntroGame game{}; uint32_t crc32 = 0; @@ -910,19 +890,18 @@ void Window::updateTitle(float fps) { title = QLatin1String(game.name); } #endif - } - MultiplayerController* multiplayer = m_controller->multiplayerController(); - if (multiplayer && multiplayer->attached() > 1) { - title += tr(" - Player %1 of %2").arg(multiplayer->playerId(m_controller) + 1).arg(multiplayer->attached()); - for (QAction* action : m_nonMpActions) { - action->setDisabled(true); - } - } else if (m_controller->isLoaded()) { - for (QAction* action : m_nonMpActions) { - action->setDisabled(false); + MultiplayerController* multiplayer = m_controller->multiplayerController(); + if (multiplayer && multiplayer->attached() > 1) { + title += tr(" - Player %1 of %2").arg(multiplayer->playerId(m_controller.get()) + 1).arg(multiplayer->attached()); + for (QAction* action : m_nonMpActions) { + action->setDisabled(true); + } + } else { + for (QAction* action : m_nonMpActions) { + action->setDisabled(false); + } } } - m_controller->threadContinue(); if (title.isNull()) { setWindowTitle(tr("%1 - %2").arg(projectName).arg(projectVersion)); } else if (fps < 0) { @@ -943,7 +922,6 @@ void Window::openStateWindow(LoadSave ls) { bool wasPaused = m_controller->isPaused(); m_stateWindow = new LoadSaveState(m_controller); connect(this, &Window::shutdown, m_stateWindow, &QWidget::close); - connect(m_controller, &GameController::gameStopped, m_stateWindow, &QWidget::close); connect(m_stateWindow, &LoadSaveState::closed, [this]() { detachWidget(m_stateWindow); m_stateWindow = nullptr; @@ -951,7 +929,11 @@ void Window::openStateWindow(LoadSave ls) { }); if (!wasPaused) { m_controller->setPaused(true); - connect(m_stateWindow, &LoadSaveState::closed, [this]() { m_controller->setPaused(false); }); + connect(m_stateWindow, &LoadSaveState::closed, [this]() { + if (m_controller) { + m_controller->setPaused(false); + } + }); } m_stateWindow->setAttribute(Qt::WA_DeleteOnClose); m_stateWindow->setMode(ls); @@ -979,17 +961,18 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); +#ifdef M_CORE_GBA QAction* bootBIOS = new QAction(tr("Boot BIOS"), fileMenu); connect(bootBIOS, &QAction::triggered, [this]() { - m_controller->loadBIOS(PLATFORM_GBA, m_config->getOption("gba.bios")); - m_controller->bootBIOS(); + setController(m_manager->loadBIOS(PLATFORM_GBA, m_config->getOption("gba.bios")), QString()); }); addControlledAction(fileMenu, bootBIOS, "bootBIOS"); +#endif addControlledAction(fileMenu, fileMenu->addAction(tr("Replace ROM..."), this, SLOT(replaceROM())), "replaceROM"); QAction* romInfo = new QAction(tr("ROM &info..."), fileMenu); - connect(romInfo, &QAction::triggered, openTView()); + connect(romInfo, &QAction::triggered, openControllerTView()); m_gameActions.append(romInfo); addControlledAction(fileMenu, romInfo, "romInfo"); @@ -1021,13 +1004,17 @@ void Window::setupMenu(QMenuBar* menubar) { m_shortcutController->addMenu(quickSaveMenu); QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); - connect(quickLoad, &QAction::triggered, m_controller, &GameController::loadState); + connect(quickLoad, &QAction::triggered, [this] { + m_controller->loadState(); + }); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu); - connect(quickSave, &QAction::triggered, m_controller, &GameController::saveState); + connect(quickLoad, &QAction::triggered, [this] { + m_controller->saveState(); + }); m_gameActions.append(quickSave); m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, "quickSave"); @@ -1037,14 +1024,18 @@ void Window::setupMenu(QMenuBar* menubar) { QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu); undoLoadState->setShortcut(tr("F11")); - connect(undoLoadState, &QAction::triggered, m_controller, &GameController::loadBackupState); + connect(undoLoadState, &QAction::triggered, [this]() { + m_controller->loadBackupState(); + }); m_gameActions.append(undoLoadState); m_nonMpActions.append(undoLoadState); addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu); undoSaveState->setShortcut(tr("Shift+F11")); - connect(undoSaveState, &QAction::triggered, m_controller, &GameController::saveBackupState); + connect(undoSaveState, &QAction::triggered, [this]() { + m_controller->saveBackupState(); + }); m_gameActions.append(undoSaveState); m_nonMpActions.append(undoSaveState); addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState"); @@ -1056,14 +1047,18 @@ void Window::setupMenu(QMenuBar* menubar) { for (i = 1; i < 10; ++i) { quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu); quickLoad->setShortcut(tr("F%1").arg(i)); - connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); }); + connect(quickLoad, &QAction::triggered, [this, i]() { + m_controller->loadState(i); + }); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); quickSave->setShortcut(tr("Shift+F%1").arg(i)); - connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); }); + connect(quickSave, &QAction::triggered, [this, i]() { + m_controller->saveState(i); + }); m_gameActions.append(quickSave); m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); @@ -1096,7 +1091,7 @@ void Window::setupMenu(QMenuBar* menubar) { #endif QAction* about = new QAction(tr("About"), fileMenu); - connect(about, &QAction::triggered, this, &Window::openAboutScreen); + connect(about, &QAction::triggered, openTView()); fileMenu->addAction(about); #ifndef Q_OS_MAC @@ -1107,18 +1102,24 @@ void Window::setupMenu(QMenuBar* menubar) { m_shortcutController->addMenu(emulationMenu); QAction* reset = new QAction(tr("&Reset"), emulationMenu); reset->setShortcut(tr("Ctrl+R")); - connect(reset, &QAction::triggered, m_controller, &GameController::reset); + connect(reset, &QAction::triggered, [this]() { + m_controller->reset(); + }); m_gameActions.append(reset); addControlledAction(emulationMenu, reset, "reset"); QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu); - connect(shutdown, &QAction::triggered, m_controller, &GameController::closeGame); + connect(shutdown, &QAction::triggered, [this]() { + m_controller->stop(); + }); m_gameActions.append(shutdown); addControlledAction(emulationMenu, shutdown, "shutdown"); #ifdef M_CORE_GBA QAction* yank = new QAction(tr("Yank game pak"), emulationMenu); - connect(yank, &QAction::triggered, m_controller, &GameController::yankPak); + connect(yank, &QAction::triggered, [this]() { + m_controller->yankPak(); + }); m_gameActions.append(yank); m_gbaActions.append(yank); addControlledAction(emulationMenu, yank, "yank"); @@ -1129,39 +1130,44 @@ void Window::setupMenu(QMenuBar* menubar) { pause->setChecked(false); pause->setCheckable(true); pause->setShortcut(tr("Ctrl+P")); - connect(pause, &QAction::triggered, m_controller, &GameController::setPaused); - connect(m_controller, &GameController::gamePaused, [this, pause]() { - pause->setChecked(true); + connect(pause, &QAction::triggered, [this](bool paused) { + m_controller->setPaused(paused); + }); + connect(this, &Window::paused, [pause](bool paused) { + pause->setChecked(paused); }); - connect(m_controller, &GameController::gameUnpaused, [pause]() { pause->setChecked(false); }); m_gameActions.append(pause); addControlledAction(emulationMenu, pause, "pause"); QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu); frameAdvance->setShortcut(tr("Ctrl+N")); - connect(frameAdvance, &QAction::triggered, m_controller, &GameController::frameAdvance); + connect(frameAdvance, &QAction::triggered, [this]() { + m_controller->frameAdvance(); + }); m_gameActions.append(frameAdvance); addControlledAction(emulationMenu, frameAdvance, "frameAdvance"); emulationMenu->addSeparator(); m_shortcutController->addFunctions(emulationMenu, [this]() { - m_controller->setTurbo(true, false); + m_controller->setFastForward(true); }, [this]() { - m_controller->setTurbo(false, false); + m_controller->setFastForward(false); }, QKeySequence(Qt::Key_Tab), tr("Fast forward (held)"), "holdFastForward"); QAction* turbo = new QAction(tr("&Fast forward"), emulationMenu); turbo->setCheckable(true); turbo->setChecked(false); turbo->setShortcut(tr("Shift+Tab")); - connect(turbo, SIGNAL(triggered(bool)), m_controller, SLOT(setTurbo(bool))); + connect(turbo, &QAction::triggered, [this](bool value) { + m_controller->forceFastForward(value); + }); addControlledAction(emulationMenu, turbo, "fastForward"); QMenu* ffspeedMenu = emulationMenu->addMenu(tr("Fast forward speed")); ConfigOption* ffspeed = m_config->addOption("fastForwardRatio"); ffspeed->connect([this](const QVariant& value) { - m_controller->setTurboSpeed(value.toFloat()); + reloadConfig(); }, this); ffspeed->addValue(tr("Unbounded"), -1.0f, ffspeedMenu); ffspeed->setValue(QVariant(-1.0f)); @@ -1172,14 +1178,16 @@ void Window::setupMenu(QMenuBar* menubar) { m_config->updateOption("fastForwardRatio"); m_shortcutController->addFunctions(emulationMenu, [this]() { - m_controller->startRewinding(); + m_controller->setRewinding(true); }, [this]() { - m_controller->stopRewinding(); + m_controller->setRewinding(false); }, QKeySequence("`"), tr("Rewind (held)"), "holdRewind"); QAction* rewind = new QAction(tr("Re&wind"), emulationMenu); rewind->setShortcut(tr("~")); - connect(rewind, &QAction::triggered, m_controller, &GameController::rewind); + connect(rewind, &QAction::triggered, [this]() { + m_controller->rewind(); + }); m_gameActions.append(rewind); m_nonMpActions.append(rewind); addControlledAction(emulationMenu, rewind, "rewind"); @@ -1196,14 +1204,14 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu); videoSync->connect([this](const QVariant& value) { - m_controller->setVideoSync(value.toBool()); + reloadConfig(); }, this); m_config->updateOption("videoSync"); ConfigOption* audioSync = m_config->addOption("audioSync"); audioSync->addBoolean(tr("Sync to &audio"), emulationMenu); audioSync->connect([this](const QVariant& value) { - m_controller->setAudioSync(value.toBool()); + reloadConfig(); }, this); m_config->updateOption("audioSync"); @@ -1212,26 +1220,26 @@ void Window::setupMenu(QMenuBar* menubar) { QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); m_shortcutController->addMenu(solarMenu); QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); - connect(solarIncrease, &QAction::triggered, m_controller, &GameController::increaseLuminanceLevel); + connect(solarIncrease, &QAction::triggered, &m_inputController, &InputController::increaseLuminanceLevel); addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu); - connect(solarDecrease, &QAction::triggered, m_controller, &GameController::decreaseLuminanceLevel); + connect(solarDecrease, &QAction::triggered, &m_inputController, &InputController::decreaseLuminanceLevel); addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel"); QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu); - connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); }); + connect(maxSolar, &QAction::triggered, [this]() { m_inputController.setLuminanceLevel(10); }); addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel"); QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu); - connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); }); + connect(minSolar, &QAction::triggered, [this]() { m_inputController.setLuminanceLevel(0); }); addControlledAction(solarMenu, minSolar, "minLuminanceLevel"); solarMenu->addSeparator(); for (int i = 0; i <= 10; ++i) { QAction* setSolar = new QAction(tr("Brightness %1").arg(QString::number(i)), solarMenu); connect(setSolar, &QAction::triggered, [this, i]() { - m_controller->setLuminanceLevel(i); + m_inputController.setLuminanceLevel(i); }); addControlledAction(solarMenu, setSolar, QString("luminanceLevel.%1").arg(QString::number(i))); } @@ -1249,7 +1257,7 @@ void Window::setupMenu(QMenuBar* menubar) { connect(setSize, &QAction::triggered, [this, i, setSize]() { showNormal(); QSize size(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - if (m_controller->isLoaded()) { + if (m_controller) { size = m_controller->screenDimensions(); } size *= i; @@ -1275,7 +1283,7 @@ void Window::setupMenu(QMenuBar* menubar) { lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu); lockAspectRatio->connect([this](const QVariant& value) { m_display->lockAspectRatio(value.toBool()); - if (m_controller->isLoaded()) { + if (m_controller) { m_screenWidget->setLockAspectRatio(value.toBool()); } }, this); @@ -1285,7 +1293,7 @@ void Window::setupMenu(QMenuBar* menubar) { lockIntegerScaling->addBoolean(tr("Force integer scaling"), avMenu); lockIntegerScaling->connect([this](const QVariant& value) { m_display->lockIntegerScaling(value.toBool()); - if (m_controller->isLoaded()) { + if (m_controller) { m_screenWidget->setLockIntegerScaling(value.toBool()); } }, this); @@ -1321,7 +1329,7 @@ void Window::setupMenu(QMenuBar* menubar) { QMenu* target = avMenu->addMenu(tr("FPS target")); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); fpsTargetOption->connect([this](const QVariant& value) { - emit fpsTargetChanged(value.toFloat()); + reloadConfig(); }, this); fpsTargetOption->addValue(tr("15"), 15, target); fpsTargetOption->addValue(tr("30"), 30, target); @@ -1340,7 +1348,9 @@ void Window::setupMenu(QMenuBar* menubar) { #ifdef USE_PNG QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu); screenshot->setShortcut(tr("F12")); - connect(screenshot, &QAction::triggered, m_controller, &GameController::screenshot); + connect(screenshot, &QAction::triggered, [this]() { + m_controller->screenshot(); + }); m_gameActions.append(screenshot); addControlledAction(avMenu, screenshot, "screenshot"); #endif @@ -1364,7 +1374,9 @@ void Window::setupMenu(QMenuBar* menubar) { m_gameActions.append(recordVL); QAction* stopVL = new QAction(tr("Stop video log"), avMenu); - connect(stopVL, &QAction::triggered, m_controller, &GameController::endVideoLog); + connect(stopVL, &QAction::triggered, [this]() { + m_controller->endVideoLog(); + }); addControlledAction(avMenu, stopVL, "stopVL"); m_gameActions.append(stopVL); @@ -1382,7 +1394,16 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(toolsMenu, viewLogs, "viewLogs"); QAction* overrides = new QAction(tr("Game &overrides..."), toolsMenu); - connect(overrides, &QAction::triggered, openTView(m_config)); + connect(overrides, &QAction::triggered, [this]() { + if (!m_overrideView) { + m_overrideView = new OverrideView(m_config); + if (m_controller) { + m_overrideView->setController(m_controller); + } + connect(this, &Window::shutdown, m_overrideView, &QWidget::close); + } + m_overrideView->show(); + }); addControlledAction(toolsMenu, overrides, "overrideWindow"); QAction* sensors = new QAction(tr("Game &Pak sensors..."), toolsMenu); @@ -1390,7 +1411,7 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(toolsMenu, sensors, "sensorWindow"); QAction* cheats = new QAction(tr("&Cheats..."), toolsMenu); - connect(cheats, &QAction::triggered, openTView()); + connect(cheats, &QAction::triggered, openControllerTView()); m_gameActions.append(cheats); addControlledAction(toolsMenu, cheats, "cheatsWindow"); @@ -1410,38 +1431,39 @@ void Window::setupMenu(QMenuBar* menubar) { QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu); connect(gdbWindow, &QAction::triggered, this, &Window::gdbOpen); m_gbaActions.append(gdbWindow); + m_gameActions.append(gdbWindow); addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif toolsMenu->addSeparator(); QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu); - connect(paletteView, &QAction::triggered, openTView()); + connect(paletteView, &QAction::triggered, openControllerTView()); m_gameActions.append(paletteView); addControlledAction(toolsMenu, paletteView, "paletteWindow"); QAction* objView = new QAction(tr("View &sprites..."), toolsMenu); - connect(objView, &QAction::triggered, openTView()); + connect(objView, &QAction::triggered, openControllerTView()); m_gameActions.append(objView); addControlledAction(toolsMenu, objView, "spriteWindow"); QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu); - connect(tileView, &QAction::triggered, openTView()); + connect(tileView, &QAction::triggered, openControllerTView()); m_gameActions.append(tileView); addControlledAction(toolsMenu, tileView, "tileWindow"); QAction* memoryView = new QAction(tr("View memory..."), toolsMenu); - connect(memoryView, &QAction::triggered, openTView()); + connect(memoryView, &QAction::triggered, openControllerTView()); m_gameActions.append(memoryView); addControlledAction(toolsMenu, memoryView, "memoryView"); QAction* memorySearch = new QAction(tr("Search memory..."), toolsMenu); - connect(memorySearch, &QAction::triggered, openTView()); + connect(memorySearch, &QAction::triggered, openControllerTView()); m_gameActions.append(memorySearch); addControlledAction(toolsMenu, memorySearch, "memorySearch"); #ifdef M_CORE_GBA QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu); - connect(ioViewer, &QAction::triggered, openTView()); + connect(ioViewer, &QAction::triggered, openControllerTView()); m_gameActions.append(ioViewer); m_gbaActions.append(ioViewer); addControlledAction(toolsMenu, ioViewer, "ioViewer"); @@ -1454,17 +1476,17 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* useBios = m_config->addOption("useBios"); useBios->connect([this](const QVariant& value) { - m_controller->setUseBIOS(value.toBool()); + reloadConfig(); }, this); ConfigOption* buffers = m_config->addOption("audioBuffers"); buffers->connect([this](const QVariant& value) { - emit audioBufferSamplesChanged(value.toInt()); + reloadConfig(); }, this); ConfigOption* sampleRate = m_config->addOption("sampleRate"); sampleRate->connect([this](const QVariant& value) { - emit sampleRateChanged(value.toUInt()); + reloadConfig(); }, this); ConfigOption* volume = m_config->addOption("volume"); @@ -1474,39 +1496,37 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* rewindEnable = m_config->addOption("rewindEnable"); rewindEnable->connect([this](const QVariant& value) { - m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindSave").toInt()); + reloadConfig(); }, this); ConfigOption* rewindBufferCapacity = m_config->addOption("rewindBufferCapacity"); rewindBufferCapacity->connect([this](const QVariant& value) { - m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindSave").toInt()); + reloadConfig(); }, this); ConfigOption* rewindSave = m_config->addOption("rewindSave"); rewindBufferCapacity->connect([this](const QVariant& value) { - m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toBool()); + reloadConfig(); }, this); ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections"); allowOpposingDirections->connect([this](const QVariant& value) { - m_inputController.setAllowOpposing(value.toBool()); + reloadConfig(); }, this); ConfigOption* saveStateExtdata = m_config->addOption("saveStateExtdata"); saveStateExtdata->connect([this](const QVariant& value) { - m_controller->setSaveStateExtdata(value.toInt()); + reloadConfig(); }, this); - m_config->updateOption("saveStateExtdata"); ConfigOption* loadStateExtdata = m_config->addOption("loadStateExtdata"); loadStateExtdata->connect([this](const QVariant& value) { - m_controller->setLoadStateExtdata(value.toInt()); + reloadConfig(); }, this); - m_config->updateOption("loadStateExtdata"); ConfigOption* preload = m_config->addOption("preload"); preload->connect([this](const QVariant& value) { - m_controller->setPreload(value.toBool()); + m_manager->setPreload(value.toBool()); }, this); m_config->updateOption("preload"); @@ -1617,7 +1637,9 @@ void Window::updateMRU() { for (const QString& file : m_mruFiles) { QAction* item = new QAction(QDir::toNativeSeparators(file).replace("&", "&&"), m_mruMenu); item->setShortcut(QString("Ctrl+%1").arg(i)); - connect(item, &QAction::triggered, [this, file]() { m_controller->loadGame(file); }); + connect(item, &QAction::triggered, [this, file]() { + setController(m_manager->loadGame(file), file); + }); m_mruMenu->addAction(item); ++i; } @@ -1640,7 +1662,7 @@ QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& na } void Window::focusCheck() { - if (!m_config->getOption("pauseOnFocusLost").toInt()) { + if (!m_config->getOption("pauseOnFocusLost").toInt() || !m_controller) { return; } if (QGuiApplication::focusWindow() && m_autoresume) { @@ -1652,6 +1674,106 @@ void Window::focusCheck() { } } +void Window::setController(CoreController* controller, const QString& fname) { + if (!controller) { + return; + } + if (!fname.isEmpty()) { + setWindowFilePath(fname); + appendMRU(fname); + } + + if (m_controller) { + m_controller->disconnect(this); + m_controller->stop(); + m_controller.reset(); + } + + m_controller = std::shared_ptr(controller); + m_inputController.recalibrateAxes(); + m_controller->setInputController(&m_inputController); + m_controller->setLogger(&m_log); + + connect(this, &Window::shutdown, [this]() { + if (!m_controller) { + return; + } + m_controller->stop(); + }); + + connect(m_controller.get(), &CoreController::started, this, &Window::gameStarted); + connect(m_controller.get(), &CoreController::started, &m_inputController, &InputController::suspendScreensaver); + connect(m_controller.get(), &CoreController::stopping, this, &Window::gameStopped); + { + std::shared_ptr controller(m_controller); + connect(m_controller.get(), &CoreController::stopping, [this, controller]() { + if (m_controller == controller) { + m_controller.reset(); + } + }); + } + connect(m_controller.get(), &CoreController::stopping, &m_inputController, &InputController::resumeScreensaver); + connect(m_controller.get(), &CoreController::paused, [this]() { + QSize size = m_controller->screenDimensions(); + QImage currentImage(reinterpret_cast(m_controller->drawContext()), size.width(), size.height(), + size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888); + QPixmap pixmap; + pixmap.convertFromImage(currentImage); + m_screenWidget->setPixmap(pixmap); + emit paused(true); + }); +#ifndef Q_OS_MAC + connect(m_controller.get(), &CoreController::paused, menuBar(), &QWidget::show); + connect(m_controller.get(), &CoreController::unpaused, [this]() { + if(isFullScreen()) { + menuBar()->hide(); + } + }); +#endif + + connect(m_controller.get(), &CoreController::paused, &m_inputController, &InputController::resumeScreensaver); + connect(m_controller.get(), &CoreController::unpaused, [this]() { + emit paused(false); + }); + + connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); + connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); + connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw); + connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing); + connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); + connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted); + connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); + + connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver); + connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame); + connect(m_controller.get(), &CoreController::crashed, this, &Window::gameCrashed); + connect(m_controller.get(), &CoreController::failed, this, &Window::gameFailed); + connect(m_controller.get(), &CoreController::unimplementedBiosCall, this, &Window::unimplementedBiosCall); + + if (m_gdbController) { + m_gdbController->setController(m_controller); + } + if (m_console) { + m_console->setController(m_controller); + } + if (m_gifView) { + m_gifView->setController(m_controller); + } + if (m_videoView) { + m_videoView->setController(m_controller); + } + if (m_overrideView) { + m_overrideView->setController(m_controller); + } + + if (!m_pendingPatch.isEmpty()) { + m_controller->loadPatch(m_pendingPatch); + m_pendingPatch = QString(); + } + + m_controller->start(); +} + WindowBackground::WindowBackground(QWidget* parent) : QLabel(parent) { diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index c009c7ffa..0517d7080 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2017 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ #include #include +#include #include @@ -22,14 +23,17 @@ struct mArguments; namespace QGBA { +class AudioProcessor; class ConfigController; +class CoreController; +class CoreManager; class DebuggerConsoleController; class Display; -class GameController; class GDBController; class GIFView; class LibraryController; class LogView; +class OverrideView; class ShaderSelector; class ShortcutController; class VideoView; @@ -39,10 +43,10 @@ class Window : public QMainWindow { Q_OBJECT public: - Window(ConfigController* config, int playerId = 0, QWidget* parent = nullptr); + Window(CoreManager* manager, ConfigController* config, int playerId = 0, QWidget* parent = nullptr); virtual ~Window(); - GameController* controller() { return m_controller; } + std::shared_ptr controller() { return m_controller; } void setConfig(ConfigController*); void argumentsPassed(mArguments*); @@ -52,13 +56,12 @@ public: void updateMultiplayerStatus(bool canOpenAnother) { m_multiWindow->setEnabled(canOpenAnother); } signals: - void startDrawing(mCoreThread*); + void startDrawing(); void shutdown(); - void audioBufferSamplesChanged(int samples); - void sampleRateChanged(unsigned samples); - void fpsTargetChanged(float target); + void paused(bool); public slots: + void setController(CoreController* controller, const QString& fname); void selectROM(); #ifdef USE_SQLITE3 void selectROMInArchive(); @@ -81,7 +84,6 @@ public slots: void exportSharkport(); void openSettingsWindow(); - void openAboutScreen(); void startVideoLog(); @@ -114,12 +116,15 @@ protected: virtual void mouseDoubleClickEvent(QMouseEvent*) override; private slots: - void gameStarted(mCoreThread*, const QString&); + void gameStarted(); void gameStopped(); void gameCrashed(const QString&); void gameFailed(); void unimplementedBiosCall(int); + void reloadAudioDriver(); + void reloadDisplayDriver(); + void tryMakePortable(); void mustRestart(); @@ -142,8 +147,8 @@ private: void openView(QWidget* widget); - template std::function openTView(A arg); - template std::function openTView(); + template std::function openTView(A... arg); + template std::function openControllerTView(A... arg); QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name); QAction* addHiddenAction(QMenu* menu, QAction* action, const QString& name); @@ -153,8 +158,11 @@ private: QString getFilters() const; QString getFiltersArchive() const; - GameController* m_controller; - Display* m_display; + CoreManager* m_manager; + std::shared_ptr m_controller; + std::unique_ptr m_audioProcessor; + + std::unique_ptr m_display; int m_savedScale; // TODO: Move these to a new class QList m_gameActions; @@ -181,14 +189,17 @@ private: QMenu* m_videoLayers; QMenu* m_audioChannels; ShortcutController* m_shortcutController; - ShaderSelector* m_shaderView; + std::unique_ptr m_shaderView; bool m_fullscreenOnStart = false; QTimer m_focusCheck; bool m_autoresume = false; bool m_wasOpened = false; + QString m_pendingPatch; bool m_hitUnimplementedBiosCall; + OverrideView* m_overrideView = nullptr; + #ifdef USE_FFMPEG VideoView* m_videoView = nullptr; #endif