all repos — mgba @ 64b396aff9fd048bc5b3c9bc158d2c22b67faf13

mGBA Game Boy Advance Emulator

Merge branch 'feature/sio-lockstep'
Jeffrey Pfau jeffrey@endrift.com
Thu, 19 Mar 2015 22:19:49 -0700
commit

64b396aff9fd048bc5b3c9bc158d2c22b67faf13

parent

b8fe87324f8987db99ede4b3332a9ea781726e85

M CMakeLists.txtCMakeLists.txt

@@ -23,10 +23,11 @@ file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c)

file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c) file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c) +file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c) list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c) source_group("ARM core" FILES ${ARM_SRC}) -source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC}) +source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC}) source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC}) source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}}) include_directories(${CMAKE_SOURCE_DIR}/src/arm)

@@ -296,6 +297,7 @@ ${GBA_RR_SRC}

${GBA_SV_SRC} ${DEBUGGER_SRC} ${RENDERER_SRC} + ${SIO_SRC} ${UTIL_SRC} ${VFS_SRC} ${OS_SRC}
M src/gba/sio.hsrc/gba/sio.h

@@ -30,11 +30,11 @@

struct GBASIODriver { struct GBASIO* p; - int (*init)(struct GBASIODriver* driver); + bool (*init)(struct GBASIODriver* driver); void (*deinit)(struct GBASIODriver* driver); - int (*load)(struct GBASIODriver* driver); - int (*unload)(struct GBASIODriver* driver); - int (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); + bool (*load)(struct GBASIODriver* driver); + bool (*unload)(struct GBASIODriver* driver); + uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles); };
A src/gba/sio/lockstep.c

@@ -0,0 +1,193 @@

+/* 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/. */ +#include "lockstep.h" + +#include "gba/gba.h" +#include "gba/io.h" + +#define LOCKSTEP_INCREMENT 2048 + +static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver); +static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver); +static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver); +static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver); +static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); +static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles); + +void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) { + lockstep->players[0] = 0; + lockstep->players[1] = 0; + lockstep->players[2] = 0; + lockstep->players[3] = 0; + lockstep->multiRecv[0] = 0xFFFF; + lockstep->multiRecv[1] = 0xFFFF; + lockstep->multiRecv[2] = 0xFFFF; + lockstep->multiRecv[3] = 0xFFFF; + lockstep->attached = 0; + lockstep->loaded = 0; + lockstep->transferActive = false; + lockstep->waiting = 0; + lockstep->nextEvent = LOCKSTEP_INCREMENT; + ConditionInit(&lockstep->barrier); + MutexInit(&lockstep->mutex); +} + +void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) { + ConditionDeinit(&lockstep->barrier); + MutexDeinit(&lockstep->mutex); +} + +void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) { + node->d.init = GBASIOLockstepNodeInit; + node->d.deinit = GBASIOLockstepNodeDeinit; + node->d.load = GBASIOLockstepNodeLoad; + node->d.unload = GBASIOLockstepNodeUnload; + node->d.writeRegister = GBASIOLockstepNodeWriteRegister; + node->d.processEvents = GBASIOLockstepNodeProcessEvents; +} + +bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { + if (lockstep->attached == MAX_GBAS) { + return false; + } + lockstep->players[lockstep->attached] = node; + node->p = lockstep; + node->id = lockstep->attached; + ++lockstep->attached; + return true; +} + +void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { + if (lockstep->attached == 0) { + return; + } + int i; + for (i = 0; i < lockstep->attached; ++i) { + if (lockstep->players[i] != node) { + continue; + } + for (++i; i < lockstep->attached; ++i) { + lockstep->players[i - 1] = lockstep->players[i]; + lockstep->players[i - 1]->id = i - 1; + } + --lockstep->attached; + break; + } +} + +bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->nextEvent = LOCKSTEP_INCREMENT; + node->d.p->multiplayerControl.slave = node->id > 0; + return true; +} + +void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) { + UNUSED(driver); +} + +bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->state = LOCKSTEP_IDLE; + MutexLock(&node->p->mutex); + ++node->p->loaded; + node->d.p->rcnt |= 3; + if (node->id) { + node->d.p->rcnt |= 4; + node->d.p->multiplayerControl.slave = 1; + } + MutexUnlock(&node->p->mutex); + return true; +} + +bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + MutexLock(&node->p->mutex); + --node->p->loaded; + ConditionWake(&node->p->barrier); + MutexUnlock(&node->p->mutex); + return true; +} + +static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + if (address == REG_SIOCNT) { + if (value & 0x0080) { + value &= ~0x0080; + if (!node->id) { + MutexLock(&node->p->mutex); + node->p->transferActive = true; + node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1]; + MutexUnlock(&node->p->mutex); + } + } + value &= 0xFF03; + value |= driver->p->siocnt & 0x007C; + } + return value; +} + +static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) { + struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; + node->nextEvent -= cycles; + while (node->nextEvent <= 0) { + MutexLock(&node->p->mutex); + ++node->p->waiting; + if (node->p->waiting < node->p->loaded) { + ConditionWait(&node->p->barrier, &node->p->mutex); + } else { + if (node->p->transferActive) { + node->p->transferCycles -= node->p->nextEvent; + if (node->p->transferCycles > 0) { + if (node->p->transferCycles < LOCKSTEP_INCREMENT) { + node->p->nextEvent = node->p->transferCycles; + } + } else { + node->p->nextEvent = LOCKSTEP_INCREMENT; + node->p->transferActive = false; + int i; + for (i = 0; i < node->p->attached; ++i) { + node->p->multiRecv[i] = node->p->players[i]->multiSend; + node->p->players[i]->state = LOCKSTEP_FINISHED; + } + for (; i < MAX_GBAS; ++i) { + node->p->multiRecv[i] = 0xFFFF; + } + } + } + node->p->waiting = 0; + ConditionWake(&node->p->barrier); + } + if (node->state == LOCKSTEP_FINISHED) { + node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0]; + node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1]; + node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2]; + node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3]; + node->d.p->rcnt |= 1; + node->state = LOCKSTEP_IDLE; + if (node->d.p->multiplayerControl.irq) { + GBARaiseIRQ(node->d.p->p, IRQ_SIO); + } + node->d.p->multiplayerControl.id = node->id; + node->d.p->multiplayerControl.busy = 0; + } else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) { + node->state = LOCKSTEP_STARTED; + node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF; + node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF; + node->d.p->rcnt &= ~1; + node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1]; + if (node->id) { + node->d.p->multiplayerControl.busy = 1; + } + } + node->d.p->multiplayerControl.ready = node->p->loaded == node->p->attached; + node->nextEvent += node->p->nextEvent; + MutexUnlock(&node->p->mutex); + } + return node->nextEvent; +}
A src/gba/sio/lockstep.h

@@ -0,0 +1,52 @@

+/* 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 SIO_LOCKSTEP_H +#define SIO_LOCKSTEP_H + +#include "gba/sio.h" + +#include "util/threading.h" + +enum LockstepState { + LOCKSTEP_IDLE = 0, + LOCKSTEP_STARTED = 1, + LOCKSTEP_FINISHED = 2 +}; + +struct GBASIOLockstep { + struct GBASIOLockstepNode* players[MAX_GBAS]; + int attached; + int loaded; + + uint16_t multiRecv[MAX_GBAS]; + bool transferActive; + int32_t transferCycles; + int32_t nextEvent; + + int waiting; + Mutex mutex; + Condition barrier; +}; + +struct GBASIOLockstepNode { + struct GBASIODriver d; + struct GBASIOLockstep* p; + + int32_t nextEvent; + uint16_t multiSend; + enum LockstepState state; + int id; +}; + +void GBASIOLockstepInit(struct GBASIOLockstep*); +void GBASIOLockstepDeinit(struct GBASIOLockstep*); + +void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*); + +bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); +void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); + +#endif
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -51,6 +51,7 @@ InputController.cpp

KeyEditor.cpp LoadSaveState.cpp LogView.cpp + MultiplayerController.cpp OverrideView.cpp SavestateButton.cpp SensorView.cpp
M src/platform/qt/ConfigController.cppsrc/platform/qt/ConfigController.cpp

@@ -23,8 +23,11 @@ : QObject(parent)

{ } -void ConfigOption::connect(std::function<void(const QVariant&)> slot) { - m_slot = slot; +void ConfigOption::connect(std::function<void(const QVariant&)> slot, QObject* parent) { + m_slots[parent] = slot; + QObject::connect(parent, &QAction::destroyed, [this, slot, parent]() { + m_slots.remove(parent); + }); } QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMenu* parent) {

@@ -33,6 +36,9 @@ action->setCheckable(true);

QObject::connect(action, &QAction::triggered, [this, value]() { emit valueChanged(value); }); + QObject::connect(parent, &QAction::destroyed, [this, action, value]() { + m_actions.removeAll(qMakePair(action, value)); + }); parent->addAction(action); m_actions.append(qMakePair(action, value)); return action;

@@ -47,6 +53,9 @@ QAction* action = new QAction(text, parent);

action->setCheckable(true); QObject::connect(action, &QAction::triggered, [this, action]() { emit valueChanged(action->isChecked()); + }); + QObject::connect(parent, &QAction::destroyed, [this, action]() { + m_actions.removeAll(qMakePair(action, 1)); }); parent->addAction(action); m_actions.append(qMakePair(action, 1));

@@ -76,7 +85,10 @@ bool signalsEnabled = action.first->blockSignals(true);

action.first->setChecked(value == action.second); action.first->blockSignals(signalsEnabled); } - m_slot(value); + std::function<void(const QVariant&)> slot; + foreach(slot, m_slots.values()) { + slot(value); + } } ConfigController::ConfigController(QObject* parent)
M src/platform/qt/ConfigController.hsrc/platform/qt/ConfigController.h

@@ -32,11 +32,11 @@

public: ConfigOption(QObject* parent = nullptr); - void connect(std::function<void(const QVariant&)>); + void connect(std::function<void(const QVariant&)>, QObject* parent = nullptr); - QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = 0); - QAction* addValue(const QString& text, const char* value, QMenu* parent = 0); - QAction* addBoolean(const QString& text, QMenu* parent = 0); + QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = nullptr); + QAction* addValue(const QString& text, const char* value, QMenu* parent = nullptr); + QAction* addBoolean(const QString& text, QMenu* parent = nullptr); public slots: void setValue(bool value);

@@ -49,7 +49,7 @@ signals:

void valueChanged(const QVariant& value); private: - std::function<void(const QVariant&)> m_slot; + QMap<QObject*, std::function<void(const QVariant&)>> m_slots; QList<QPair<QAction*, QVariant>> m_actions; };
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -7,6 +7,7 @@ #include "GameController.h"

#include "AudioProcessor.h" #include "InputController.h" +#include "MultiplayerController.h" #include <QDateTime> #include <QThread>

@@ -41,6 +42,7 @@ , m_audioSync(AUDIO_SYNC)

, m_turbo(false) , m_turboForced(false) , m_inputController(nullptr) + , m_multiplayer(nullptr) { m_renderer = new GBAVideoSoftwareRenderer; GBAVideoSoftwareRendererCreate(m_renderer);

@@ -140,10 +142,28 @@ GameController::~GameController() {

m_audioThread->quit(); m_audioThread->wait(); disconnect(); + clearMultiplayerController(); closeGame(); GBACheatDeviceDestroy(&m_cheatDevice); delete m_renderer; delete[] m_drawContext; +} + +void GameController::setMultiplayerController(std::shared_ptr<MultiplayerController> controller) { + if (controller == m_multiplayer) { + return; + } + clearMultiplayerController(); + m_multiplayer = controller; + controller->attachGame(this); +} + +void GameController::clearMultiplayerController() { + if (!m_multiplayer) { + return; + } + m_multiplayer->detachGame(this); + m_multiplayer.reset(); } void GameController::setOverride(const GBACartridgeOverride& override) {
M src/platform/qt/GameController.hsrc/platform/qt/GameController.h

@@ -12,6 +12,8 @@ #include <QObject>

#include <QMutex> #include <QString> +#include <memory> + extern "C" { #include "gba/cheats.h" #include "gba/hardware.h"

@@ -32,6 +34,7 @@ namespace QGBA {

class AudioProcessor; class InputController; +class MultiplayerController; class GameController : public QObject { Q_OBJECT

@@ -58,6 +61,10 @@ bool videoSync() const { return m_videoSync; }

void setInputController(InputController* controller) { m_inputController = controller; } void setOverrides(Configuration* overrides) { m_threadContext.overrides = overrides; } + + void setMultiplayerController(std::shared_ptr<MultiplayerController> controller); + std::shared_ptr<MultiplayerController> multiplayerController() { return m_multiplayer; } + void clearMultiplayerController(); void setOverride(const GBACartridgeOverride& override); void clearOverride() { m_threadContext.hasOverride = false; }

@@ -167,6 +174,7 @@ bool m_turbo;

bool m_turboForced; InputController* m_inputController; + std::shared_ptr<MultiplayerController> m_multiplayer; struct GameControllerLux : GBALuminanceSource { GameController* p;
M src/platform/qt/InputController.cppsrc/platform/qt/InputController.cpp

@@ -19,8 +19,9 @@ }

using namespace QGBA; -InputController::InputController(QObject* parent) +InputController::InputController(int playerId, QObject* parent) : QObject(parent) + , m_playerId(playerId) , m_config(nullptr) , m_gamepadTimer(nullptr) {

@@ -28,9 +29,8 @@ GBAInputMapInit(&m_inputMap);

#ifdef BUILD_SDL m_sdlEvents.bindings = &m_inputMap; - GBASDLInitEvents(&m_sdlEvents); + GBASDLInitEvents(&m_sdlEvents, playerId); GBASDLInitBindings(&m_inputMap); - SDL_JoystickEventState(SDL_QUERY); m_gamepadTimer = new QTimer(this); connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad()));
M src/platform/qt/InputController.hsrc/platform/qt/InputController.h

@@ -31,7 +31,7 @@

public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(QObject* parent = nullptr); + InputController(int playerId = 0, QObject* parent = nullptr); ~InputController(); void setConfiguration(ConfigController* config);

@@ -67,6 +67,7 @@ bool hasPendingEvent(GBAKey) const;

GBAInputMap m_inputMap; ConfigController* m_config; + int m_playerId; #ifdef BUILD_SDL GBASDLEvents m_sdlEvents;
A src/platform/qt/MultiplayerController.cpp

@@ -0,0 +1,71 @@

+/* 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/. */ +#include "MultiplayerController.h" + +#include "GameController.h" + +using namespace QGBA; + +MultiplayerController::MultiplayerController() { + GBASIOLockstepInit(&m_lockstep); +} + +MultiplayerController::~MultiplayerController() { + GBASIOLockstepDeinit(&m_lockstep); +} + +bool MultiplayerController::attachGame(GameController* controller) { + MutexLock(&m_lockstep.mutex); + if (m_lockstep.attached == MAX_GBAS) { + MutexUnlock(&m_lockstep.mutex); + return false; + } + GBASIOLockstepNode* node = new GBASIOLockstepNode; + GBASIOLockstepNodeCreate(node); + GBASIOLockstepAttachNode(&m_lockstep, node); + MutexUnlock(&m_lockstep.mutex); + + controller->threadInterrupt(); + GBAThread* thread = controller->thread(); + if (controller->isLoaded()) { + GBASIOSetDriver(&thread->gba->sio, &node->d, SIO_MULTI); + } + thread->sioDrivers.multiplayer = &node->d; + controller->threadContinue(); + return true; +} + +void MultiplayerController::detachGame(GameController* controller) { + controller->threadInterrupt(); + MutexLock(&m_lockstep.mutex); + GBAThread* thread = nullptr; + for (int i = 0; i < m_lockstep.attached; ++i) { + thread = controller->thread(); + if (thread->sioDrivers.multiplayer == &m_lockstep.players[i]->d) { + break; + } + thread = nullptr; + } + if (thread) { + GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(thread->sioDrivers.multiplayer); + if (controller->isLoaded()) { + GBASIOSetDriver(&thread->gba->sio, nullptr, SIO_MULTI); + } + thread->sioDrivers.multiplayer = nullptr; + GBASIOLockstepDetachNode(&m_lockstep, node); + delete node; + } + MutexUnlock(&m_lockstep.mutex); + controller->threadContinue(); +} + +int MultiplayerController::attached() { + int num; + MutexLock(&m_lockstep.mutex); + num = m_lockstep.attached; + MutexUnlock(&m_lockstep.mutex); + return num; +}
A src/platform/qt/MultiplayerController.h

@@ -0,0 +1,32 @@

+/* 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_MULTIPLAYER_CONTROLLER +#define QGBA_MULTIPLAYER_CONTROLLER + +extern "C" { +#include "gba/sio/lockstep.h" +} + +namespace QGBA { + +class GameController; + +class MultiplayerController { +public: + MultiplayerController(); + ~MultiplayerController(); + + bool attachGame(GameController*); + void detachGame(GameController*); + + int attached(); + +private: + GBASIOLockstep m_lockstep; +}; + +} +#endif
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -23,6 +23,7 @@ #include "GDBWindow.h"

#include "GIFView.h" #include "LoadSaveState.h" #include "LogView.h" +#include "MultiplayerController.h" #include "OverrideView.h" #include "SensorView.h" #include "SettingsView.h"

@@ -36,13 +37,14 @@ }

using namespace QGBA; -Window::Window(ConfigController* config, QWidget* parent) +Window::Window(ConfigController* config, int playerId, QWidget* parent) : QMainWindow(parent) , m_logView(new LogView()) , m_stateWindow(nullptr) , m_screenWidget(new WindowBackground()) , m_logo(":/res/mgba-1024.png") , m_config(config) + , m_inputController(playerId) #ifdef USE_FFMPEG , m_videoView(nullptr) #endif

@@ -54,6 +56,7 @@ , m_gdbController(nullptr)

#endif , m_mruMenu(nullptr) , m_shortcutController(new ShortcutController(this)) + , m_playerId(playerId) { setWindowTitle(PROJECT_NAME); setFocusPolicy(Qt::StrongFocus);

@@ -525,8 +528,23 @@ addAction(quickSave);

quickSaveMenu->addAction(quickSave); } + fileMenu->addSeparator(); + QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu); + connect(multiWindow, &QAction::triggered, [this]() { + std::shared_ptr<MultiplayerController> multiplayer = m_controller->multiplayerController(); + if (!multiplayer) { + multiplayer = std::make_shared<MultiplayerController>(); + m_controller->setMultiplayerController(multiplayer); + } + Window* w2 = new Window(m_config, multiplayer->attached()); + w2->setAttribute(Qt::WA_DeleteOnClose); + w2->loadConfig(); + w2->controller()->setMultiplayerController(multiplayer); + w2->show(); + }); + addControlledAction(fileMenu, multiWindow, "multiWindow"); + #ifndef Q_OS_MAC - fileMenu->addSeparator(); addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit"); #endif

@@ -585,12 +603,16 @@ addControlledAction(emulationMenu, rewind, "rewind");

ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu); - videoSync->connect([this](const QVariant& value) { m_controller->setVideoSync(value.toBool()); }); + videoSync->connect([this](const QVariant& value) { + m_controller->setVideoSync(value.toBool()); + }, 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()); }); + audioSync->connect([this](const QVariant& value) { + m_controller->setAudioSync(value.toBool()); + }, this); m_config->updateOption("audioSync"); QMenu* avMenu = menubar->addMenu(tr("Audio/&Video"));

@@ -609,17 +631,23 @@ addControlledAction(frameMenu, frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F")), "fullscreen");

ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio"); lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu); - lockAspectRatio->connect([this](const QVariant& value) { m_display->lockAspectRatio(value.toBool()); }); + lockAspectRatio->connect([this](const QVariant& value) { + m_display->lockAspectRatio(value.toBool()); + }, this); m_config->updateOption("lockAspectRatio"); ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); resampleVideo->addBoolean(tr("Resample video"), avMenu); - resampleVideo->connect([this](const QVariant& value) { m_display->filter(value.toBool()); }); + resampleVideo->connect([this](const QVariant& value) { + m_display->filter(value.toBool()); + }, this); m_config->updateOption("resampleVideo"); QMenu* skipMenu = avMenu->addMenu(tr("Frame&skip")); ConfigOption* skip = m_config->addOption("frameskip"); - skip->connect([this](const QVariant& value) { m_controller->setFrameskip(value.toInt()); }); + skip->connect([this](const QVariant& value) { + m_controller->setFrameskip(value.toInt()); + }, this); for (int i = 0; i <= 10; ++i) { skip->addValue(QString::number(i), i, skipMenu); }

@@ -629,7 +657,9 @@ avMenu->addSeparator();

QMenu* buffersMenu = avMenu->addMenu(tr("Audio buffer &size")); ConfigOption* buffers = m_config->addOption("audioBuffers"); - buffers->connect([this](const QVariant& value) { emit audioBufferSamplesChanged(value.toInt()); }); + buffers->connect([this](const QVariant& value) { + emit audioBufferSamplesChanged(value.toInt()); + }, this); buffers->addValue(tr("512"), 512, buffersMenu); buffers->addValue(tr("768"), 768, buffersMenu); buffers->addValue(tr("1024"), 1024, buffersMenu);

@@ -641,7 +671,9 @@ avMenu->addSeparator();

QMenu* target = avMenu->addMenu("FPS target"); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); - fpsTargetOption->connect([this](const QVariant& value) { emit fpsTargetChanged(value.toInt()); }); + fpsTargetOption->connect([this](const QVariant& value) { + emit fpsTargetChanged(value.toInt()); + }, this); fpsTargetOption->addValue(tr("15"), 15, target); fpsTargetOption->addValue(tr("30"), 30, target); fpsTargetOption->addValue(tr("45"), 45, target);

@@ -733,19 +765,27 @@ addControlledAction(toolsMenu, gamepad, "remapGamepad");

#endif ConfigOption* skipBios = m_config->addOption("skipBios"); - skipBios->connect([this](const QVariant& value) { m_controller->setSkipBIOS(value.toBool()); }); + skipBios->connect([this](const QVariant& value) { + m_controller->setSkipBIOS(value.toBool()); + }, this); ConfigOption* useBios = m_config->addOption("useBios"); useBios->connect([this](const QVariant& value) { m_controller->setUseBIOS(value.toBool()); }); 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("rewindBufferInterval").toInt()); }); + rewindEnable->connect([this](const QVariant& value) { + m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt()); + }, 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("rewindBufferInterval").toInt()); }); + rewindBufferCapacity->connect([this](const QVariant& value) { + m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt()); + }, this); ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval"); - rewindBufferInterval->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt()); }); + rewindBufferInterval->connect([this](const QVariant& value) { + m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt()); + }, this); QMenu* other = new QMenu(tr("Other"), this); m_shortcutController->addMenu(other);
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -39,7 +39,7 @@ class Window : public QMainWindow {

Q_OBJECT public: - Window(ConfigController* config, QWidget* parent = nullptr); + Window(ConfigController* config, int playerId = 0, QWidget* parent = nullptr); virtual ~Window(); GameController* controller() { return m_controller; }

@@ -134,6 +134,7 @@ QTimer m_fpsTimer;

QList<QString> m_mruFiles; QMenu* m_mruMenu; ShortcutController* m_shortcutController; + int m_playerId; #ifdef USE_FFMPEG VideoView* m_videoView;
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -105,7 +105,7 @@ GBASDLInitAudio(&renderer.audio, &context);

renderer.events.bindings = &inputMap; GBASDLInitBindings(&inputMap); - GBASDLInitEvents(&renderer.events); + GBASDLInitEvents(&renderer.events, 0); GBASDLEventsLoadConfig(&renderer.events, GBAConfigGetInput(&config)); context.overrides = GBAConfigGetOverrides(&config);
M src/platform/sdl/sdl-events.csrc/platform/sdl/sdl-events.c

@@ -21,13 +21,13 @@ #endif

static int _openContexts = 0; -bool GBASDLInitEvents(struct GBASDLEvents* context) { +bool GBASDLInitEvents(struct GBASDLEvents* context, int playerId) { if (!_openContexts && SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { return false; } ++_openContexts; SDL_JoystickEventState(SDL_ENABLE); - context->joystick = SDL_JoystickOpen(0); + context->joystick = SDL_JoystickOpen(playerId); #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); #endif
M src/platform/sdl/sdl-events.hsrc/platform/sdl/sdl-events.h

@@ -28,7 +28,7 @@ int windowUpdated;

#endif }; -bool GBASDLInitEvents(struct GBASDLEvents*); +bool GBASDLInitEvents(struct GBASDLEvents*, int playerId); void GBASDLDeinitEvents(struct GBASDLEvents*); void GBASDLInitBindings(struct GBAInputMap* inputMap);