Skip to content

Commit 0c72697

Browse files
libretro: splitscreen multiplayer
1 parent c758314 commit 0c72697

8 files changed

Lines changed: 1739 additions & 76 deletions

File tree

libretro-build/Makefile.common

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ SOURCES_C := $(CORE_DIR)/src/arm/arm.c \
112112
$(CORE_DIR)/src/gba/cart/vfame.c \
113113
$(CORE_DIR)/src/gba/video.c \
114114
$(CORE_DIR)/src/platform/libretro/memory.c \
115+
$(CORE_DIR)/src/platform/libretro/libretro_input.c \
116+
$(CORE_DIR)/src/platform/libretro/libretro_lockstep.c \
117+
$(CORE_DIR)/src/platform/libretro/libretro_multiplayer.c \
115118
$(CORE_DIR)/src/platform/libretro/libretro.c \
116119
$(CORE_DIR)/src/sm83/isa-sm83.c \
117120
$(CORE_DIR)/src/sm83/sm83.c \

src/platform/libretro/libretro.c

Lines changed: 56 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ FS_Archive sdmcArchive;
4242
#endif
4343

4444
#include "libretro_core_options.h"
45+
#include "libretro_input.h"
46+
#include "libretro_multiplayer.h"
4547

4648
#define GBA_RESAMPLED_RATE 65536
4749
static unsigned targetSampleRate = GBA_RESAMPLED_RATE;
@@ -135,19 +137,9 @@ static bool audioLowPassEnabled = false;
135137
static int32_t audioLowPassRange = 0;
136138
static int32_t audioLowPassLeftPrev = 0;
137139
static int32_t audioLowPassRightPrev = 0;
138-
139-
static const int keymap[] = {
140-
RETRO_DEVICE_ID_JOYPAD_A,
141-
RETRO_DEVICE_ID_JOYPAD_B,
142-
RETRO_DEVICE_ID_JOYPAD_SELECT,
143-
RETRO_DEVICE_ID_JOYPAD_START,
144-
RETRO_DEVICE_ID_JOYPAD_RIGHT,
145-
RETRO_DEVICE_ID_JOYPAD_LEFT,
146-
RETRO_DEVICE_ID_JOYPAD_UP,
147-
RETRO_DEVICE_ID_JOYPAD_DOWN,
148-
RETRO_DEVICE_ID_JOYPAD_R,
149-
RETRO_DEVICE_ID_JOYPAD_L,
150-
};
140+
static struct mLibretroTurboState turboState;
141+
static struct mLibretroMultiplayer multiplayer;
142+
static char loadedRomPath[PATH_MAX];
151143

152144
#ifndef GIT_VERSION
153145
#define GIT_VERSION ""
@@ -1364,6 +1356,12 @@ void retro_get_system_av_info(struct retro_system_av_info* info) {
13641356
info->geometry.max_height = height;
13651357

13661358
info->geometry.aspect_ratio = width / (double) height;
1359+
mLibretroMultiplayerAdjustGeometry(&multiplayer,
1360+
&info->geometry.base_width,
1361+
&info->geometry.base_height,
1362+
&info->geometry.max_width,
1363+
&info->geometry.max_height,
1364+
&info->geometry.aspect_ratio);
13671365
info->timing.fps = core->frequency(core) / (float) core->frameCycles(core);
13681366

13691367
#ifdef M_CORE_GBA
@@ -1473,9 +1471,14 @@ void retro_init(void) {
14731471
retroAudioLatency = 0;
14741472
updateAudioLatency = false;
14751473
updateAudioRate = false;
1474+
mLibretroTurboStateInit(&turboState);
1475+
mLibretroMultiplayerInit(&multiplayer, VIDEO_WIDTH_MAX, VIDEO_HEIGHT_MAX);
1476+
loadedRomPath[0] = '\0';
14761477
}
14771478

14781479
void retro_deinit(void) {
1480+
mLibretroMultiplayerDeinit(&multiplayer, core);
1481+
14791482
if (outputBuffer) {
14801483
#ifdef _3DS
14811484
linearFree(outputBuffer);
@@ -1515,43 +1518,15 @@ void retro_deinit(void) {
15151518
audioLowPassRange = 0;
15161519
audioLowPassLeftPrev = 0;
15171520
audioLowPassRightPrev = 0;
1518-
}
1519-
1520-
static int turboclock = 0;
1521-
static bool indownstate = true;
1522-
1523-
int16_t cycleturbo(bool a, bool b, bool l, bool r) {
1524-
int16_t buttons = 0;
1525-
turboclock++;
1526-
if (turboclock >= 2) {
1527-
turboclock = 0;
1528-
indownstate = !indownstate;
1529-
}
1530-
1531-
if (a) {
1532-
buttons |= indownstate << 0;
1533-
}
1534-
1535-
if (b) {
1536-
buttons |= indownstate << 1;
1537-
}
1538-
1539-
if (l) {
1540-
buttons |= indownstate << 9;
1541-
}
1542-
1543-
if (r) {
1544-
buttons |= indownstate << 8;
1545-
}
1546-
1547-
return buttons;
1521+
core = NULL;
15481522
}
15491523

15501524
void retro_run(void) {
15511525
if (deferredSetup) {
15521526
_doDeferredSetup();
15531527
}
1554-
uint16_t keys;
1528+
uint16_t player1Keys;
1529+
uint16_t player2Keys;
15551530
bool skipFrame = false;
15561531

15571532
inputPollCallback();
@@ -1575,36 +1550,16 @@ void retro_run(void) {
15751550
#if defined(COLOR_16_BIT) && defined(COLOR_5_6_5)
15761551
_loadPostProcessingSettings();
15771552
#endif
1553+
mLibretroMultiplayerUpdateMode(&multiplayer, environCallback);
1554+
mLibretroMultiplayerApplyMode(&multiplayer, core, data, dataSize, loadedRomPath, logCallback);
15781555
#ifdef M_CORE_GB
15791556
_updateGbPal();
15801557
#endif
15811558
}
15821559

1583-
keys = 0;
1584-
int i;
1585-
if (useBitmasks) {
1586-
int16_t joypadMask = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
1587-
for (i = 0; i < sizeof(keymap) / sizeof(*keymap); ++i) {
1588-
keys |= ((joypadMask >> keymap[i]) & 1) << i;
1589-
}
1590-
// XXX: turbo keys, should be moved to frontend
1591-
#define JOYPAD_BIT(BUTTON) (1 << RETRO_DEVICE_ID_JOYPAD_ ## BUTTON)
1592-
keys |= cycleturbo(joypadMask & JOYPAD_BIT(X), joypadMask & JOYPAD_BIT(Y), joypadMask & JOYPAD_BIT(L2), joypadMask & JOYPAD_BIT(R2));
1593-
#undef JOYPAD_BIT
1594-
} else {
1595-
for (i = 0; i < sizeof(keymap) / sizeof(*keymap); ++i) {
1596-
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, keymap[i])) << i;
1597-
}
1598-
// XXX: turbo keys, should be moved to frontend
1599-
keys |= cycleturbo(
1600-
inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X),
1601-
inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y),
1602-
inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2),
1603-
inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2)
1604-
);
1605-
}
1606-
1607-
core->setKeys(core, keys);
1560+
player1Keys = mLibretroInputReadKeys(0, inputCallback, useBitmasks, &turboState);
1561+
player2Keys = mLibretroInputReadKeys(1, inputCallback, useBitmasks, &turboState);
1562+
mLibretroMultiplayerSetKeys(&multiplayer, core, player1Keys, player2Keys);
16081563

16091564
if (!luxSensorUsed) {
16101565
static bool wasAdjustingLux = false;
@@ -1683,7 +1638,7 @@ void retro_run(void) {
16831638
updateAudioLatency = false;
16841639
}
16851640

1686-
core->runFrame(core);
1641+
mLibretroMultiplayerRunFrame(&multiplayer, core);
16871642
unsigned width, height;
16881643
core->currentVideoSize(core, &width, &height);
16891644

@@ -1707,15 +1662,29 @@ void retro_run(void) {
17071662
}
17081663

17091664
if (!skipFrame) {
1665+
if (multiplayer.active) {
1666+
size_t outPitch;
1667+
unsigned outWidth;
1668+
unsigned outHeight;
1669+
const mColor* frame = mLibretroMultiplayerComposeFrame(&multiplayer, outputBuffer, width, height, &outPitch, &outWidth, &outHeight);
1670+
videoCallback(frame, outWidth, outHeight, outPitch);
1671+
} else {
17101672
#if defined(COLOR_16_BIT) && defined(COLOR_5_6_5)
1711-
if (videoPostProcess) {
1712-
videoPostProcess(width, height);
1713-
videoCallback(ppOutputBuffer, width, height, VIDEO_WIDTH_MAX * sizeof(mColor));
1714-
} else
1673+
if (videoPostProcess) {
1674+
videoPostProcess(width, height);
1675+
videoCallback(ppOutputBuffer, width, height, VIDEO_WIDTH_MAX * sizeof(mColor));
1676+
} else
17151677
#endif
1716-
videoCallback(outputBuffer, width, height, VIDEO_WIDTH_MAX * sizeof(mColor));
1678+
videoCallback(outputBuffer, width, height, VIDEO_WIDTH_MAX * sizeof(mColor));
1679+
}
17171680
} else {
1718-
videoCallback(NULL, width, height, VIDEO_WIDTH_MAX * sizeof(mColor));
1681+
size_t outPitch = VIDEO_WIDTH_MAX * sizeof(mColor);
1682+
unsigned outWidth = width;
1683+
unsigned outHeight = height;
1684+
if (multiplayer.active) {
1685+
mLibretroMultiplayerComposeFrame(&multiplayer, outputBuffer, width, height, &outPitch, &outWidth, &outHeight);
1686+
}
1687+
videoCallback(NULL, outWidth, outHeight, outPitch);
17191688
}
17201689

17211690
/* Check whether audio sample rate has changed */
@@ -1995,6 +1964,12 @@ bool retro_load_game(const struct retro_game_info* game) {
19951964
return false;
19961965
}
19971966

1967+
if (game->path) {
1968+
snprintf(loadedRomPath, sizeof(loadedRomPath), "%s", game->path);
1969+
} else {
1970+
loadedRomPath[0] = '\0';
1971+
}
1972+
19981973
if (game->data) {
19991974
data = anonymousMemoryMap(game->size);
20001975
dataSize = game->size;
@@ -2087,6 +2062,8 @@ bool retro_load_game(const struct retro_game_info* game) {
20872062
_reloadSettings();
20882063
core->loadROM(core, rom);
20892064
deferredSetup = true;
2065+
mLibretroMultiplayerUpdateMode(&multiplayer, environCallback);
2066+
mLibretroMultiplayerApplyMode(&multiplayer, core, data, dataSize, loadedRomPath, logCallback);
20902067

20912068
const char* sysDir = 0;
20922069
const char* biosName = 0;
@@ -2154,12 +2131,15 @@ void retro_unload_game(void) {
21542131
if (!core) {
21552132
return;
21562133
}
2134+
mLibretroMultiplayerDeinit(&multiplayer, core);
21572135
mCoreConfigDeinit(&core->config);
21582136
core->deinit(core);
2137+
core = NULL;
21592138
mappedMemoryFree(data, dataSize);
21602139
data = 0;
21612140
mappedMemoryFree(savedata, GBA_SIZE_FLASH1M);
21622141
savedata = 0;
2142+
loadedRomPath[0] = '\0';
21632143
}
21642144

21652145
size_t retro_serialize_size(void) {

src/platform/libretro/libretro_core_options.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,21 @@ struct retro_core_option_v2_definition option_defs_us[] = {
101101
},
102102
"Autodetect"
103103
},
104+
{
105+
"mgba_multiplayer_splitscreen",
106+
"Multiplayer Splitscreen (Restart)",
107+
NULL,
108+
"Runs a linked second GBA instance and combines both player views into a single output frame.",
109+
NULL,
110+
"system",
111+
{
112+
{ "OFF", "disabled" },
113+
{ "Side by Side", "2-Player Side by Side" },
114+
{ "Top/Bottom", "2-Player Top/Bottom" },
115+
{ NULL, NULL },
116+
},
117+
"OFF"
118+
},
104119
{
105120
"mgba_use_bios",
106121
"Use BIOS File if Found (Restart)",
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#include "libretro_input.h"
2+
3+
#include <string.h>
4+
5+
static const int keymap[] = {
6+
RETRO_DEVICE_ID_JOYPAD_A,
7+
RETRO_DEVICE_ID_JOYPAD_B,
8+
RETRO_DEVICE_ID_JOYPAD_SELECT,
9+
RETRO_DEVICE_ID_JOYPAD_START,
10+
RETRO_DEVICE_ID_JOYPAD_RIGHT,
11+
RETRO_DEVICE_ID_JOYPAD_LEFT,
12+
RETRO_DEVICE_ID_JOYPAD_UP,
13+
RETRO_DEVICE_ID_JOYPAD_DOWN,
14+
RETRO_DEVICE_ID_JOYPAD_R,
15+
RETRO_DEVICE_ID_JOYPAD_L,
16+
};
17+
18+
void mLibretroTurboStateInit(struct mLibretroTurboState* state) {
19+
memset(state, 0, sizeof(*state));
20+
state->downState[0] = true;
21+
state->downState[1] = true;
22+
}
23+
24+
static int16_t _cycleTurbo(unsigned port, bool a, bool b, bool l, bool r, struct mLibretroTurboState* turboState) {
25+
int16_t buttons = 0;
26+
if (port > 1) {
27+
port = 0;
28+
}
29+
turboState->clock[port]++;
30+
if (turboState->clock[port] >= 2) {
31+
turboState->clock[port] = 0;
32+
turboState->downState[port] = !turboState->downState[port];
33+
}
34+
35+
if (a) {
36+
buttons |= turboState->downState[port] << 0;
37+
}
38+
39+
if (b) {
40+
buttons |= turboState->downState[port] << 1;
41+
}
42+
43+
if (l) {
44+
buttons |= turboState->downState[port] << 9;
45+
}
46+
47+
if (r) {
48+
buttons |= turboState->downState[port] << 8;
49+
}
50+
51+
return buttons;
52+
}
53+
54+
uint16_t mLibretroInputReadKeys(unsigned port, retro_input_state_t inputCallback, bool useBitmasks, struct mLibretroTurboState* turboState) {
55+
uint16_t keys = 0;
56+
size_t i;
57+
58+
if (useBitmasks) {
59+
int16_t joypadMask = inputCallback(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
60+
for (i = 0; i < sizeof(keymap) / sizeof(*keymap); ++i) {
61+
keys |= ((joypadMask >> keymap[i]) & 1) << i;
62+
}
63+
#define JOYPAD_BIT(BUTTON) (1 << RETRO_DEVICE_ID_JOYPAD_ ## BUTTON)
64+
keys |= _cycleTurbo(port, joypadMask & JOYPAD_BIT(X), joypadMask & JOYPAD_BIT(Y), joypadMask & JOYPAD_BIT(L2), joypadMask & JOYPAD_BIT(R2), turboState);
65+
#undef JOYPAD_BIT
66+
} else {
67+
for (i = 0; i < sizeof(keymap) / sizeof(*keymap); ++i) {
68+
keys |= (!!inputCallback(port, RETRO_DEVICE_JOYPAD, 0, keymap[i])) << i;
69+
}
70+
keys |= _cycleTurbo(
71+
port,
72+
inputCallback(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X),
73+
inputCallback(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y),
74+
inputCallback(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2),
75+
inputCallback(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2),
76+
turboState
77+
);
78+
}
79+
80+
return keys;
81+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef MGBA_LIBRETRO_INPUT_H
2+
#define MGBA_LIBRETRO_INPUT_H
3+
4+
#include <mgba-util/common.h>
5+
6+
#include "libretro.h"
7+
8+
CXX_GUARD_START
9+
10+
struct mLibretroTurboState {
11+
int clock[2];
12+
bool downState[2];
13+
};
14+
15+
void mLibretroTurboStateInit(struct mLibretroTurboState* state);
16+
uint16_t mLibretroInputReadKeys(unsigned port, retro_input_state_t inputCallback, bool useBitmasks, struct mLibretroTurboState* turboState);
17+
18+
CXX_GUARD_END
19+
20+
#endif

0 commit comments

Comments
 (0)