Merge branch 'feature/sio-lockstep'

This commit is contained in:
Jeffrey Pfau 2015-03-19 22:19:49 -07:00
commit 64b396aff9
18 changed files with 468 additions and 35 deletions

View File

@ -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 @@ set(CORE_SRC
${GBA_SV_SRC}
${DEBUGGER_SRC}
${RENDERER_SRC}
${SIO_SRC}
${UTIL_SRC}
${VFS_SRC}
${OS_SRC}

View File

@ -30,11 +30,11 @@ struct GBASIO;
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);
};

193
src/gba/sio/lockstep.c Normal file
View File

@ -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;
}

52
src/gba/sio/lockstep.h Normal file
View File

@ -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

View File

@ -51,6 +51,7 @@ set(SOURCE_FILES
KeyEditor.cpp
LoadSaveState.cpp
LogView.cpp
MultiplayerController.cpp
OverrideView.cpp
SavestateButton.cpp
SensorView.cpp

View File

@ -23,8 +23,11 @@ ConfigOption::ConfigOption(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 @@ QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMen
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;
@ -48,6 +54,9 @@ QAction* ConfigOption::addBoolean(const QString& text, QMenu* parent) {
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));
return action;
@ -76,7 +85,10 @@ void ConfigOption::setValue(const QVariant& value) {
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)

View File

@ -32,11 +32,11 @@ Q_OBJECT
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;
};

View File

@ -7,6 +7,7 @@
#include "AudioProcessor.h"
#include "InputController.h"
#include "MultiplayerController.h"
#include <QDateTime>
#include <QThread>
@ -41,6 +42,7 @@ GameController::GameController(QObject* parent)
, m_turbo(false)
, m_turboForced(false)
, m_inputController(nullptr)
, m_multiplayer(nullptr)
{
m_renderer = new GBAVideoSoftwareRenderer;
GBAVideoSoftwareRendererCreate(m_renderer);
@ -140,12 +142,30 @@ 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_threadContext.override = override;
m_threadContext.hasOverride = true;

View File

@ -12,6 +12,8 @@
#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
@ -59,6 +62,10 @@ public:
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 @@ private:
bool m_turboForced;
InputController* m_inputController;
std::shared_ptr<MultiplayerController> m_multiplayer;
struct GameControllerLux : GBALuminanceSource {
GameController* p;

View File

@ -19,8 +19,9 @@ extern "C" {
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 @@ InputController::InputController(QObject* parent)
#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()));

View File

@ -31,7 +31,7 @@ Q_OBJECT
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 @@ private:
GBAInputMap m_inputMap;
ConfigController* m_config;
int m_playerId;
#ifdef BUILD_SDL
GBASDLEvents m_sdlEvents;

View File

@ -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;
}

View File

@ -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

View File

@ -23,6 +23,7 @@
#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 @@ extern "C" {
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 @@ Window::Window(ConfigController* config, QWidget* parent)
#endif
, m_mruMenu(nullptr)
, m_shortcutController(new ShortcutController(this))
, m_playerId(playerId)
{
setWindowTitle(PROJECT_NAME);
setFocusPolicy(Qt::StrongFocus);
@ -525,8 +528,23 @@ void Window::setupMenu(QMenuBar* menubar) {
quickSaveMenu->addAction(quickSave);
}
#ifndef Q_OS_MAC
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
addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit");
#endif
@ -585,12 +603,16 @@ 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()); });
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
#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);

View File

@ -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 @@ private:
QList<QString> m_mruFiles;
QMenu* m_mruMenu;
ShortcutController* m_shortcutController;
int m_playerId;
#ifdef USE_FFMPEG
VideoView* m_videoView;

View File

@ -105,7 +105,7 @@ int main(int argc, char** argv) {
renderer.events.bindings = &inputMap;
GBASDLInitBindings(&inputMap);
GBASDLInitEvents(&renderer.events);
GBASDLInitEvents(&renderer.events, 0);
GBASDLEventsLoadConfig(&renderer.events, GBAConfigGetInput(&config));
context.overrides = GBAConfigGetOverrides(&config);

View File

@ -21,13 +21,13 @@
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

View File

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