Merge branch 'master' (early part) into medusa
19
CHANGES
|
@ -45,10 +45,16 @@ Features:
|
|||
- Debugger: Add range watchpoints
|
||||
Emulation fixes:
|
||||
- GB Serialize: Don't write BGP/OBP when loading SCGB state (fixes mgba.io/i/2694)
|
||||
- GB SIO: Further fix bidirectional transfer starting
|
||||
- GBA: Fix resetting key IRQ state (fixes mgba.io/i/2716)
|
||||
- GBA Video: Ignore disabled backgrounds as OBJ blend target (fixes mgba.io/i/2489)
|
||||
- GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722)
|
||||
Other fixes:
|
||||
- Qt: Manually split filename to avoid overzealous splitting (fixes mgba.io/i/2681)
|
||||
- Qt: Expand criteria for tag branch names (fixes mgba.io/i/2679)
|
||||
- Qt: Fix scanning specific e-Reader dotcodes (fixes mgba.io/i/2693)
|
||||
- Qt: Don't re-enable sync if GBA link modes aren't the same (fixes mgba.io/i/2044)
|
||||
- Qt: Improve handling of multiplayer syncing (fixes mgba.io/i/2720)
|
||||
- Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685)
|
||||
- VFS: Fix minizip write returning 0 on success instead of size
|
||||
Misc:
|
||||
|
@ -56,7 +62,8 @@ Misc:
|
|||
- GBA: Improve detection of valid ELF ROMs
|
||||
- macOS: Add category to plist (closes mgba.io/i/2691)
|
||||
- macOS: Fix modern build with libepoxy (fixes mgba.io/i/2700)
|
||||
- Qt: Keep track of current pslette preset name (fixes mgba.io/i/2680)
|
||||
- Qt: Keep track of current palette preset name (fixes mgba.io/i/2680)
|
||||
- Qt: Move OpenGL proxy onto its own thread (fixes mgba.io/i/2493)
|
||||
|
||||
0.10.0: (2022-10-11)
|
||||
Features:
|
||||
|
@ -66,7 +73,7 @@ Features:
|
|||
- Tool for converting scanned pictures of e-Reader cards to raw dotcode data
|
||||
- Options for muting when inactive, minimized, or for different players in multiplayer
|
||||
- Cheat code support in homebrew ports
|
||||
- Acclerometer and gyro support for controllers on PC
|
||||
- Accelerometer and gyro support for controllers on PC
|
||||
- Support for combo "Super Game Boy Color" SGB + GBC ROM hacks
|
||||
- Improved support for HuC-3 mapper, including RTC
|
||||
- Support for 64 kiB SRAM saves used in some bootlegs
|
||||
|
@ -80,7 +87,7 @@ Emulation fixes:
|
|||
- ARM7: Fix unsigned multiply timing
|
||||
- GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378)
|
||||
- GB: Fix HALT breaking M-cycle alignment (fixes mgba.io/i/250)
|
||||
- GB Audio: Fix channel 1/2 reseting edge cases (fixes mgba.io/i/1925)
|
||||
- GB Audio: Fix channel 1/2 resetting edge cases (fixes mgba.io/i/1925)
|
||||
- GB Audio: Properly apply per-model audio differences
|
||||
- GB Audio: Revamp channel rendering
|
||||
- GB Audio: Fix APU re-enable timing glitch
|
||||
|
@ -190,7 +197,7 @@ Emulation fixes:
|
|||
Other fixes:
|
||||
- ARM Decoder: Fix decoding of lsl r0 (fixes mgba.io/i/2349)
|
||||
- FFmpeg: Don't attempt to use YUV 4:2:0 for lossless videos (fixes mgba.io/i/2084)
|
||||
- GB Video: Fix memory leak when reseting SGB games
|
||||
- GB Video: Fix memory leak when resetting SGB games
|
||||
- GBA: Fix out of bounds ROM accesses on patched ROMs smaller than 32 MiB
|
||||
- GBA: Fix maximum tile ID in caching for 256-color modes
|
||||
- GBA Video: Fix cache updating with proxy and GL renderers
|
||||
|
@ -301,7 +308,7 @@ Emulation fixes:
|
|||
- GBA BIOS: Implement dummy sound driver calls
|
||||
- GBA BIOS: Improve HLE BIOS timing
|
||||
- GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808)
|
||||
- GBA BIOS: Make HLE BIOS calls interruptable (fixes mgba.io/i/1711 and mgba.io/i/1823)
|
||||
- GBA BIOS: Make HLE BIOS calls interruptible (fixes mgba.io/i/1711 and mgba.io/i/1823)
|
||||
- GBA BIOS: Fix invalid decompression bounds checking
|
||||
- GBA DMA: Linger last DMA on bus (fixes mgba.io/i/301 and mgba.io/i/1320)
|
||||
- GBA DMA: Fix ordering and timing of overlapping DMAs
|
||||
|
@ -317,7 +324,7 @@ Emulation fixes:
|
|||
- GBA Serialize: Fix alignment check when loading states
|
||||
- GBA SIO: Fix copying Normal mode transfer values
|
||||
- GBA SIO: Fix Normal mode being totally broken (fixes mgba.io/i/1800)
|
||||
- GBA SIO: Fix deseralizing SIO registers
|
||||
- GBA SIO: Fix deserializing SIO registers
|
||||
- GBA SIO: Fix hanging on starting a second multiplayer window (fixes mgba.io/i/854)
|
||||
- GBA SIO: Fix Normal mode transfer start timing (fixes mgba.io/i/425)
|
||||
- GBA Timers: Fix toggling timer cascading while timer is active (fixes mgba.io/i/2043)
|
||||
|
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,3 @@
|
|||
[testinfo]
|
||||
skip=10
|
||||
frames=1
|
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 6.0 KiB |
|
@ -157,7 +157,7 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
|||
}
|
||||
}
|
||||
// Tell the other GBs they can continue up to where we were
|
||||
node->p->d.addCycles(&node->p->d, node->id, node->eventDiff);
|
||||
node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->d.transferActive;
|
||||
#endif
|
||||
|
@ -169,26 +169,28 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
|||
|
||||
static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
||||
enum mLockstepPhase transferActive;
|
||||
int id;
|
||||
|
||||
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
||||
ATOMIC_LOAD(id, node->id);
|
||||
|
||||
bool signal = false;
|
||||
switch (transferActive) {
|
||||
case TRANSFER_IDLE:
|
||||
node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
|
||||
node->p->d.addCycles(&node->p->d, id, LOCKSTEP_INCREMENT);
|
||||
break;
|
||||
case TRANSFER_STARTING:
|
||||
case TRANSFER_FINISHING:
|
||||
break;
|
||||
case TRANSFER_STARTED:
|
||||
if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
|
||||
if (node->p->d.unusedCycles(&node->p->d, id) > node->eventDiff) {
|
||||
break;
|
||||
}
|
||||
node->transferFinished = false;
|
||||
signal = true;
|
||||
break;
|
||||
case TRANSFER_FINISHED:
|
||||
if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
|
||||
if (node->p->d.unusedCycles(&node->p->d, id) > node->eventDiff) {
|
||||
break;
|
||||
}
|
||||
_finishTransfer(node);
|
||||
|
@ -199,7 +201,7 @@ static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
|||
node->phase = node->p->d.transferActive;
|
||||
#endif
|
||||
if (signal) {
|
||||
node->p->d.signal(&node->p->d, 1 << node->id);
|
||||
node->p->d.signal(&node->p->d, 1 << id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -215,11 +217,13 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
|||
int32_t cycles = 0;
|
||||
node->nextEvent -= cyclesLate;
|
||||
if (node->nextEvent <= 0) {
|
||||
if (!node->id) {
|
||||
int id;
|
||||
ATOMIC_LOAD(id, node->id);
|
||||
if (!id) {
|
||||
cycles = _masterUpdate(node);
|
||||
} else {
|
||||
cycles = _slaveUpdate(node);
|
||||
cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
|
||||
cycles += node->p->d.useCycles(&node->p->d, id, node->eventDiff);
|
||||
}
|
||||
node->eventDiff = 0;
|
||||
} else {
|
||||
|
@ -240,7 +244,9 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
|||
|
||||
static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
|
||||
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
|
||||
node->p->pendingSB[node->id] = value;
|
||||
int id;
|
||||
ATOMIC_LOAD(id, node->id);
|
||||
node->p->pendingSB[id] = value;
|
||||
}
|
||||
|
||||
static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value) {
|
||||
|
@ -252,11 +258,17 @@ static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t valu
|
|||
mLockstepLock(&node->p->d);
|
||||
bool claimed = false;
|
||||
if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) {
|
||||
if (node->id != 0) {
|
||||
int id;
|
||||
ATOMIC_LOAD(id, node->id);
|
||||
if (id != 0) {
|
||||
unsigned sb;
|
||||
node->p->players[0]->id = 1;
|
||||
node->p->players[1] = node->p->players[0];
|
||||
node->p->players[0] = node->p->players[1];
|
||||
node->id = 0;
|
||||
node->p->players[1] = node->p->players[0];
|
||||
node->p->players[0] = node;
|
||||
sb = node->p->pendingSB[0];
|
||||
node->p->pendingSB[0] = node->p->pendingSB[1];
|
||||
node->p->pendingSB[1] = sb;
|
||||
}
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
||||
ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);
|
||||
|
|
|
@ -213,6 +213,7 @@ void GBAReset(struct ARMCore* cpu) {
|
|||
gba->earlyExit = false;
|
||||
gba->dmaPC = 0;
|
||||
gba->biosStall = 0;
|
||||
gba->keysLast = 0x400;
|
||||
if (gba->yankedRomSize) {
|
||||
gba->memory.romSize = gba->yankedRomSize;
|
||||
gba->memory.romMask = toPow2(gba->memory.romSize) - 1;
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
|
||||
vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \
|
||||
if (LIKELY(vram)) { \
|
||||
|
@ -47,7 +46,6 @@
|
|||
} \
|
||||
LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
if (outX < renderer->start) { \
|
||||
tileData >>= 4 * (renderer->start - outX); \
|
||||
|
@ -101,7 +99,6 @@
|
|||
carryData = 0; \
|
||||
} else { \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= 4 * baseX; \
|
||||
|
@ -132,7 +129,6 @@
|
|||
carryData = 0; \
|
||||
} else { \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= x * 4; \
|
||||
|
@ -163,7 +159,6 @@
|
|||
localY = 7 - localY; \
|
||||
} \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
|
||||
vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \
|
||||
if (UNLIKELY(!vram)) { \
|
||||
|
@ -425,7 +420,6 @@
|
|||
if (LIKELY(vram)) { \
|
||||
int end2 = end - 4; \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
int shift = inX & 0x3; \
|
||||
if (end2 > outX) { \
|
||||
|
@ -467,7 +461,6 @@
|
|||
return; \
|
||||
} \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
int end = mod8 - 4; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
if (end > 0) { \
|
||||
|
@ -510,7 +503,6 @@
|
|||
for (; tileX < tileEnd; ++tileX) { \
|
||||
mapData = background->mapCache[(localX >> 3) & 0x3F]; \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
localX += 8; \
|
||||
localY = inY & 0x7; \
|
||||
if (GBA_TEXT_MAP_VFLIP(mapData)) { \
|
||||
|
@ -623,7 +615,6 @@
|
|||
for (; x < 8 && length; ++x, --length) { \
|
||||
if (!mosaicWait) { \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
if (UNLIKELY(!vram)) { \
|
||||
carryData = 0; \
|
||||
} else { \
|
||||
|
@ -747,24 +738,23 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer
|
|||
|
||||
uint32_t screenBase;
|
||||
uint32_t charBase;
|
||||
color_t* mainPalette = renderer->normalPalette;
|
||||
color_t* palette = renderer->normalPalette;
|
||||
if (background->multipalette && background->extPalette) {
|
||||
mainPalette = background->extPalette;
|
||||
palette = background->extPalette;
|
||||
if (variant) {
|
||||
mainPalette = background->variantPalette;
|
||||
palette = background->variantPalette;
|
||||
}
|
||||
} else {
|
||||
if (renderer->d.highlightAmount && background->highlight) {
|
||||
mainPalette = renderer->highlightPalette;
|
||||
palette = renderer->highlightPalette;
|
||||
}
|
||||
if (variant) {
|
||||
mainPalette = renderer->variantPalette;
|
||||
palette = renderer->variantPalette;
|
||||
if (renderer->d.highlightAmount && background->highlight) {
|
||||
mainPalette = renderer->highlightVariantPalette;
|
||||
palette = renderer->highlightVariantPalette;
|
||||
}
|
||||
}
|
||||
}
|
||||
color_t* palette = mainPalette;
|
||||
PREPARE_OBJWIN;
|
||||
|
||||
int outX = renderer->start;
|
||||
|
|
|
@ -258,10 +258,10 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
||||
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) {
|
||||
int target2 = renderer->target2Bd;
|
||||
target2 |= renderer->bg[0].target2;
|
||||
target2 |= renderer->bg[1].target2;
|
||||
target2 |= renderer->bg[2].target2;
|
||||
target2 |= renderer->bg[3].target2;
|
||||
target2 |= renderer->bg[0].target2 && renderer->bg[0].enabled;
|
||||
target2 |= renderer->bg[1].target2 && renderer->bg[1].enabled;
|
||||
target2 |= renderer->bg[2].target2 && renderer->bg[2].enabled;
|
||||
target2 |= renderer->bg[3].target2 && renderer->bg[3].enabled;
|
||||
if (target2) {
|
||||
renderer->forceTarget1 = true;
|
||||
flags |= FLAG_REBLEND;
|
||||
|
|
|
@ -92,28 +92,55 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
|
||||
#define COMPOSITE_16_OBJWIN(BLEND, IDX) \
|
||||
if (objwinForceEnable || (!(current & FLAG_OBJWIN)) == objwinOnly) { \
|
||||
unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \
|
||||
unsigned color; \
|
||||
unsigned mergedFlags = flags; \
|
||||
if (current & FLAG_OBJWIN) { \
|
||||
mergedFlags = objwinFlags; \
|
||||
color = objwinPalette[paletteData | pixelData]; \
|
||||
} else if ((current & (FLAG_IS_BACKGROUND | FLAG_REBLEND)) == FLAG_REBLEND) { \
|
||||
color = renderer->normalPalette[paletteData | pixelData]; \
|
||||
} else { \
|
||||
color = palette[paletteData | pixelData]; \
|
||||
} \
|
||||
_composite ## BLEND ## Objwin(renderer, outX + IDX, color | mergedFlags, current); \
|
||||
}
|
||||
|
||||
#define COMPOSITE_16_NO_OBJWIN(BLEND, IDX) \
|
||||
_composite ## BLEND ## NoObjwin(renderer, outX + IDX, palette[pixelData] | flags, current);
|
||||
{ \
|
||||
unsigned color; \
|
||||
if ((current & (FLAG_IS_BACKGROUND | FLAG_REBLEND)) == FLAG_REBLEND) { \
|
||||
color = renderer->normalPalette[paletteData | pixelData]; \
|
||||
} else { \
|
||||
color = palette[paletteData | pixelData]; \
|
||||
} \
|
||||
_composite ## BLEND ## NoObjwin(renderer, outX + IDX, color | flags, current); \
|
||||
}
|
||||
|
||||
#define COMPOSITE_256_OBJWIN(BLEND, IDX) \
|
||||
if (objwinForceEnable || (!(current & FLAG_OBJWIN)) == objwinOnly) { \
|
||||
unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \
|
||||
unsigned color; \
|
||||
unsigned mergedFlags = flags; \
|
||||
if (current & FLAG_OBJWIN) { \
|
||||
mergedFlags = objwinFlags; \
|
||||
color = objwinPalette[pixelData]; \
|
||||
} else if ((current & (FLAG_IS_BACKGROUND | FLAG_REBLEND)) == FLAG_REBLEND) { \
|
||||
color = renderer->normalPalette[pixelData]; \
|
||||
} else { \
|
||||
color = palette[pixelData]; \
|
||||
} \
|
||||
_composite ## BLEND ## Objwin(renderer, outX + IDX, color | mergedFlags, current); \
|
||||
}
|
||||
|
||||
#define COMPOSITE_256_NO_OBJWIN COMPOSITE_16_NO_OBJWIN
|
||||
#define COMPOSITE_256_NO_OBJWIN(BLEND, IDX) \
|
||||
{ \
|
||||
unsigned color; \
|
||||
if ((current & (FLAG_IS_BACKGROUND | FLAG_REBLEND)) == FLAG_REBLEND) { \
|
||||
color = renderer->normalPalette[pixelData]; \
|
||||
} else { \
|
||||
color = palette[pixelData]; \
|
||||
} \
|
||||
_composite ## BLEND ## NoObjwin(renderer, outX + IDX, color | flags, current); \
|
||||
}
|
||||
|
||||
#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN, IDX) \
|
||||
pixelData = tileData & 0xF; \
|
||||
|
|
|
@ -175,6 +175,11 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
|||
m_drawThread.setObjectName("Painter Thread");
|
||||
m_painter->setThread(&m_drawThread);
|
||||
|
||||
m_proxyThread.setObjectName("OpenGL Proxy Thread");
|
||||
m_proxyContext = std::make_unique<QOpenGLContext>();
|
||||
m_proxyContext->setFormat(format);
|
||||
connect(m_painter.get(), &PainterGL::created, this, &DisplayGL::setupProxyThread);
|
||||
|
||||
connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
|
||||
connect(m_painter.get(), &PainterGL::started, this, [this] {
|
||||
m_hasStarted = true;
|
||||
|
@ -189,6 +194,11 @@ DisplayGL::~DisplayGL() {
|
|||
QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
|
||||
m_drawThread.exit();
|
||||
m_drawThread.wait();
|
||||
|
||||
if (m_proxyThread.isRunning()) {
|
||||
m_proxyThread.exit();
|
||||
m_proxyThread.wait();
|
||||
}
|
||||
}
|
||||
|
||||
bool DisplayGL::supportsShaders() const {
|
||||
|
@ -212,9 +222,6 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
|||
m_painter->setContext(controller);
|
||||
m_painter->setMessagePainter(messagePainter());
|
||||
m_context = controller;
|
||||
if (videoProxy()) {
|
||||
videoProxy()->moveToThread(&m_drawThread);
|
||||
}
|
||||
|
||||
lockAspectRatio(isAspectRatioLocked());
|
||||
lockIntegerScaling(isIntegerScalingLocked());
|
||||
|
@ -414,11 +421,34 @@ bool DisplayGL::shouldDisableUpdates() {
|
|||
void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
|
||||
Display::setVideoProxy(proxy);
|
||||
if (proxy) {
|
||||
proxy->moveToThread(&m_drawThread);
|
||||
proxy->moveToThread(&m_proxyThread);
|
||||
}
|
||||
m_painter->setVideoProxy(proxy);
|
||||
}
|
||||
|
||||
void DisplayGL::setupProxyThread() {
|
||||
m_proxyContext->moveToThread(&m_proxyThread);
|
||||
connect(&m_proxyThread, &QThread::started, m_proxyContext.get(), [this]() {
|
||||
m_proxyContext->setShareContext(m_painter->shareContext());
|
||||
m_proxyContext->create();
|
||||
m_proxySurface.create();
|
||||
m_proxyContext->makeCurrent(&m_proxySurface);
|
||||
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||
epoxy_handle_external_wglMakeCurrent();
|
||||
#endif
|
||||
});
|
||||
connect(m_painter.get(), &PainterGL::texSwapped, m_proxyContext.get(), [this]() {
|
||||
if (!m_context->hardwareAccelerated()) {
|
||||
return;
|
||||
}
|
||||
if (videoProxy()) {
|
||||
videoProxy()->processData();
|
||||
}
|
||||
m_painter->updateFramebufferHandle();
|
||||
}, Qt::BlockingQueuedConnection);
|
||||
m_proxyThread.start();
|
||||
}
|
||||
|
||||
int DisplayGL::framebufferHandle() {
|
||||
return m_painter->glTex();
|
||||
}
|
||||
|
@ -484,9 +514,15 @@ void PainterGL::create() {
|
|||
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (m_supportsShaders) {
|
||||
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||
gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
|
||||
mGLES2ContextCreate(gl2Backend);
|
||||
m_backend = &gl2Backend->d;
|
||||
fn->glGenTextures(m_bridgeTexes.size(), m_bridgeTexes.data());
|
||||
for (auto tex : m_bridgeTexes) {
|
||||
m_freeTex.enqueue(tex);
|
||||
}
|
||||
m_bridgeTexIn = m_freeTex.dequeue();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -506,10 +542,10 @@ void PainterGL::create() {
|
|||
painter->makeCurrent();
|
||||
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
|
||||
if (painter->m_widget && painter->supportsShaders()) {
|
||||
QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||
fn->glFinish();
|
||||
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
|
||||
painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]);
|
||||
painter->m_finalTexIdx ^= 1;
|
||||
gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx];
|
||||
|
@ -543,6 +579,8 @@ void PainterGL::create() {
|
|||
m_backend->filter = false;
|
||||
m_backend->lockAspectRatio = false;
|
||||
m_backend->interframeBlending = false;
|
||||
|
||||
emit created();
|
||||
}
|
||||
|
||||
void PainterGL::destroy() {
|
||||
|
@ -551,9 +589,11 @@ void PainterGL::destroy() {
|
|||
}
|
||||
makeCurrent();
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||
if (m_shader.passes) {
|
||||
mGLES2ShaderFree(&m_shader);
|
||||
}
|
||||
fn->glDeleteTextures(m_bridgeTexes.size(), m_bridgeTexes.data());
|
||||
#endif
|
||||
m_backend->deinit(m_backend);
|
||||
m_gl->doneCurrent();
|
||||
|
@ -639,6 +679,7 @@ void PainterGL::start() {
|
|||
}
|
||||
#endif
|
||||
resizeContext();
|
||||
m_context->addFrameAction(std::bind(&PainterGL::swapTex, this));
|
||||
|
||||
m_buffer = nullptr;
|
||||
m_active = true;
|
||||
|
@ -647,7 +688,7 @@ void PainterGL::start() {
|
|||
}
|
||||
|
||||
void PainterGL::draw() {
|
||||
if (!m_started || m_queue.isEmpty()) {
|
||||
if (!m_started || (m_queue.isEmpty() && m_queueTex.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -674,7 +715,7 @@ void PainterGL::draw() {
|
|||
return;
|
||||
}
|
||||
dequeue();
|
||||
bool forceRedraw = !m_videoProxy;
|
||||
bool forceRedraw = true;
|
||||
if (!m_delayTimer.isValid()) {
|
||||
m_delayTimer.start();
|
||||
} else {
|
||||
|
@ -728,11 +769,6 @@ void PainterGL::doStop() {
|
|||
m_videoProxy->processData();
|
||||
}
|
||||
}
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->reset();
|
||||
m_videoProxy->moveToThread(m_window->thread());
|
||||
m_videoProxy.reset();
|
||||
}
|
||||
m_backend->clear(m_backend);
|
||||
m_backend->swap(m_backend);
|
||||
}
|
||||
|
@ -762,38 +798,60 @@ void PainterGL::performDraw() {
|
|||
}
|
||||
|
||||
void PainterGL::enqueue(const uint32_t* backing) {
|
||||
if (!backing) {
|
||||
return;
|
||||
}
|
||||
QMutexLocker locker(&m_mutex);
|
||||
uint32_t* buffer = nullptr;
|
||||
if (backing) {
|
||||
if (m_free.isEmpty()) {
|
||||
buffer = m_queue.dequeue();
|
||||
} else {
|
||||
buffer = m_free.takeLast();
|
||||
}
|
||||
if (buffer) {
|
||||
QSize size = m_context->screenDimensions();
|
||||
memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
|
||||
}
|
||||
if (m_free.isEmpty()) {
|
||||
buffer = m_queue.dequeue();
|
||||
} else {
|
||||
buffer = m_free.takeLast();
|
||||
}
|
||||
if (buffer) {
|
||||
QSize size = m_context->screenDimensions();
|
||||
memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
|
||||
}
|
||||
m_queue.enqueue(buffer);
|
||||
}
|
||||
|
||||
void PainterGL::enqueue(GLuint tex) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_freeTex.isEmpty()) {
|
||||
m_bridgeTexIn = m_queueTex.dequeue();
|
||||
} else {
|
||||
m_bridgeTexIn = m_freeTex.takeLast();
|
||||
}
|
||||
m_queueTex.enqueue(tex);
|
||||
}
|
||||
|
||||
void PainterGL::dequeue() {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_queue.isEmpty()) {
|
||||
return;
|
||||
if (!m_queue.isEmpty()) {
|
||||
uint32_t* buffer = m_queue.dequeue();
|
||||
if (m_buffer) {
|
||||
m_free.append(m_buffer);
|
||||
}
|
||||
m_buffer = buffer;
|
||||
}
|
||||
uint32_t* buffer = m_queue.dequeue();
|
||||
if (m_buffer) {
|
||||
m_free.append(m_buffer);
|
||||
m_buffer = nullptr;
|
||||
|
||||
if (!m_queueTex.isEmpty()) {
|
||||
if (m_bridgeTexOut != std::numeric_limits<GLuint>::max()) {
|
||||
m_freeTex.enqueue(m_bridgeTexOut);
|
||||
}
|
||||
m_bridgeTexOut = m_queueTex.dequeue();
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (supportsShaders()) {
|
||||
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
|
||||
gl2Backend->tex = m_bridgeTexOut;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
m_buffer = buffer;
|
||||
}
|
||||
|
||||
void PainterGL::dequeueAll(bool keep) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
uint32_t* buffer = 0;
|
||||
uint32_t* buffer = nullptr;
|
||||
while (!m_queue.isEmpty()) {
|
||||
buffer = m_queue.dequeue();
|
||||
if (keep) {
|
||||
|
@ -805,6 +863,13 @@ void PainterGL::dequeueAll(bool keep) {
|
|||
m_free.append(buffer);
|
||||
}
|
||||
}
|
||||
m_queueTex.clear();
|
||||
m_freeTex.clear();
|
||||
for (auto tex : m_bridgeTexes) {
|
||||
m_freeTex.enqueue(tex);
|
||||
}
|
||||
m_bridgeTexIn = m_freeTex.dequeue();
|
||||
m_bridgeTexOut = std::numeric_limits<GLuint>::max();
|
||||
if (m_buffer && !keep) {
|
||||
m_free.append(m_buffer);
|
||||
m_buffer = nullptr;
|
||||
|
@ -864,4 +929,29 @@ int PainterGL::glTex() {
|
|||
#endif
|
||||
}
|
||||
|
||||
QOpenGLContext* PainterGL::shareContext() {
|
||||
if (m_widget) {
|
||||
return m_widget->context();
|
||||
} else {
|
||||
return m_gl.get();
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::updateFramebufferHandle() {
|
||||
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||
fn->glFinish();
|
||||
enqueue(m_bridgeTexIn);
|
||||
m_context->setFramebufferHandle(m_bridgeTexIn);
|
||||
}
|
||||
|
||||
void PainterGL::swapTex() {
|
||||
if (!m_started) {
|
||||
return;
|
||||
}
|
||||
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
emit texSwapped();
|
||||
m_context->addFrameAction(std::bind(&PainterGL::swapTex, this));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -112,6 +112,9 @@ protected:
|
|||
virtual void paintEvent(QPaintEvent*) override { forceDraw(); }
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
|
||||
private slots:
|
||||
void setupProxyThread();
|
||||
|
||||
private:
|
||||
void resizePainter();
|
||||
bool shouldDisableUpdates();
|
||||
|
@ -122,8 +125,11 @@ private:
|
|||
bool m_hasStarted = false;
|
||||
std::unique_ptr<PainterGL> m_painter;
|
||||
QThread m_drawThread;
|
||||
QThread m_proxyThread;
|
||||
std::shared_ptr<CoreController> m_context;
|
||||
mGLWidget* m_gl;
|
||||
QOffscreenSurface m_proxySurface;
|
||||
std::unique_ptr<QOpenGLContext> m_proxyContext;
|
||||
};
|
||||
|
||||
class PainterGL : public QObject {
|
||||
|
@ -137,15 +143,21 @@ public:
|
|||
void setContext(std::shared_ptr<CoreController>);
|
||||
void setMessagePainter(MessagePainter*);
|
||||
void enqueue(const uint32_t* backing);
|
||||
void enqueue(GLuint tex);
|
||||
|
||||
void stop();
|
||||
|
||||
bool supportsShaders() const { return m_supportsShaders; }
|
||||
int glTex();
|
||||
|
||||
QOpenGLContext* shareContext();
|
||||
|
||||
void setVideoProxy(std::shared_ptr<VideoProxy>);
|
||||
void interrupt();
|
||||
|
||||
// Run on main thread
|
||||
void swapTex();
|
||||
|
||||
public slots:
|
||||
void create();
|
||||
void destroy();
|
||||
|
@ -163,13 +175,16 @@ public slots:
|
|||
void showFrameCounter(bool enable);
|
||||
void filter(bool filter);
|
||||
void resizeContext();
|
||||
void updateFramebufferHandle();
|
||||
|
||||
void setShaders(struct VDir*);
|
||||
void clearShaders();
|
||||
VideoShader* shaders();
|
||||
|
||||
signals:
|
||||
void created();
|
||||
void started();
|
||||
void texSwapped();
|
||||
|
||||
private slots:
|
||||
void doStop();
|
||||
|
@ -184,6 +199,14 @@ private:
|
|||
QList<uint32_t*> m_free;
|
||||
QQueue<uint32_t*> m_queue;
|
||||
uint32_t* m_buffer = nullptr;
|
||||
|
||||
std::array<GLuint, 3> m_bridgeTexes;
|
||||
QQueue<GLuint> m_freeTex;
|
||||
QQueue<GLuint> m_queueTex;
|
||||
|
||||
GLuint m_bridgeTexIn = std::numeric_limits<GLuint>::max();
|
||||
GLuint m_bridgeTexOut = std::numeric_limits<GLuint>::max();
|
||||
|
||||
QPainter m_painter;
|
||||
QMutex m_mutex;
|
||||
QWindow* m_window;
|
||||
|
|
|
@ -65,7 +65,11 @@ void ForwarderController::startBuild(const QString& outFilename) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
downloadManifest();
|
||||
if (m_baseFilename.isEmpty()) {
|
||||
downloadManifest();
|
||||
} else {
|
||||
m_generator->rebuild(m_baseFilename, m_outFilename);
|
||||
}
|
||||
}
|
||||
|
||||
void ForwarderController::downloadForwarderKit() {
|
||||
|
@ -201,7 +205,9 @@ void ForwarderController::gotBuild(QNetworkReply* reply) {
|
|||
}
|
||||
|
||||
void ForwarderController::cleanup() {
|
||||
m_sourceFile.remove();
|
||||
if (m_sourceFile.exists()) {
|
||||
m_sourceFile.remove();
|
||||
}
|
||||
m_inProgress = false;
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
|
|
|
@ -30,6 +30,10 @@ public:
|
|||
void setGenerator(std::unique_ptr<ForwarderGenerator>&& generator);
|
||||
ForwarderGenerator* generator() { return m_generator.get(); }
|
||||
|
||||
void setBaseFilename(const QString& path) { m_baseFilename = path; }
|
||||
void clearBaseFilename() { m_baseFilename = QString(); }
|
||||
QString baseFilename() const { return m_baseFilename; }
|
||||
|
||||
QString channel() const { return m_channel; }
|
||||
bool inProgress() const { return m_inProgress; }
|
||||
|
||||
|
@ -62,6 +66,7 @@ private:
|
|||
QString m_outFilename;
|
||||
std::unique_ptr<ForwarderGenerator> m_generator;
|
||||
QFile m_sourceFile;
|
||||
QString m_baseFilename;
|
||||
bool m_inProgress = false;
|
||||
QByteArray m_originalPath;
|
||||
};
|
||||
|
|
|
@ -94,6 +94,11 @@ void ForwarderView::build() {
|
|||
}
|
||||
m_controller.generator()->setTitle(m_ui.title->text());
|
||||
m_controller.generator()->setRom(m_ui.romFilename->text());
|
||||
if (m_ui.baseType->currentIndex() == 2) {
|
||||
m_controller.setBaseFilename(m_ui.baseFilename->text());
|
||||
} else {
|
||||
m_controller.clearBaseFilename();
|
||||
}
|
||||
m_controller.startBuild(m_ui.outputFilename->text());
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
m_ui.progressBar->setEnabled(true);
|
||||
|
@ -120,9 +125,21 @@ void ForwarderView::validate() {
|
|||
if (!m_ui.system->checkedButton()) {
|
||||
valid = false;
|
||||
}
|
||||
if (m_ui.baseType->currentIndex() != 1) {
|
||||
if (m_ui.baseType->currentIndex() < 1) {
|
||||
valid = false;
|
||||
}
|
||||
if (m_ui.baseType->currentIndex() == 2) {
|
||||
m_ui.baseFilename->setEnabled(true);
|
||||
m_ui.baseLabel->setEnabled(true);
|
||||
m_ui.baseBrowse->setEnabled(true);
|
||||
if (m_ui.baseFilename->text().isEmpty()) {
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
m_ui.baseFilename->setEnabled(true);
|
||||
m_ui.baseLabel->setEnabled(true);
|
||||
m_ui.baseBrowse->setEnabled(true);
|
||||
}
|
||||
if (m_controller.inProgress()) {
|
||||
valid = false;
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<widget class="QLabel" name="baseLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
|
|
|
@ -14,24 +14,46 @@
|
|||
#include <mgba/internal/gb/gb.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* node)
|
||||
MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* gbNode)
|
||||
: controller(coreController)
|
||||
, gbNode(node)
|
||||
{
|
||||
node.gb = gbNode;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* node)
|
||||
MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* gbaNode)
|
||||
: controller(coreController)
|
||||
, gbaNode(node)
|
||||
{
|
||||
node.gba = gbaNode;
|
||||
}
|
||||
#endif
|
||||
|
||||
int MultiplayerController::Player::id() const {
|
||||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
return node.gba->id;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
return node.gb->id;
|
||||
#endif
|
||||
case mPLATFORM_NONE:
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool MultiplayerController::Player::operator<(const MultiplayerController::Player& other) const {
|
||||
return id() < other.id();
|
||||
}
|
||||
|
||||
MultiplayerController::MultiplayerController() {
|
||||
mLockstepInit(&m_lockstep);
|
||||
m_lockstep.context = this;
|
||||
|
@ -65,6 +87,7 @@ MultiplayerController::MultiplayerController() {
|
|||
player->awake = 0;
|
||||
slept = true;
|
||||
}
|
||||
player->controller->setSync(true);
|
||||
return slept;
|
||||
};
|
||||
m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
||||
|
@ -72,44 +95,51 @@ MultiplayerController::MultiplayerController() {
|
|||
abort();
|
||||
}
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
if (!id) {
|
||||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
||||
Player* player = &controller->m_players[i];
|
||||
Player* player = controller->player(id);
|
||||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
if (player->controller->platform() == mPLATFORM_GBA && player->gbaNode->d.p->mode != controller->m_players[0].gbaNode->d.p->mode) {
|
||||
player->controller->setSync(true);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
player->controller->setSync(false);
|
||||
player->cyclesPosted += cycles;
|
||||
if (player->awake < 1) {
|
||||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
player->gbaNode->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
player->gbNode->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
case mPLATFORM_GBA:
|
||||
if (!id) {
|
||||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
||||
player = controller->player(i);
|
||||
player->controller->setSync(false);
|
||||
player->cyclesPosted += cycles;
|
||||
if (player->awake < 1) {
|
||||
player->node.gba->nextEvent += player->cyclesPosted;
|
||||
}
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
}
|
||||
} else {
|
||||
player->controller->setSync(true);
|
||||
player->cyclesPosted += cycles;
|
||||
}
|
||||
} else {
|
||||
controller->m_players[id].controller->setSync(true);
|
||||
controller->m_players[id].cyclesPosted += cycles;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
if (!id) {
|
||||
player = controller->player(1);
|
||||
player->controller->setSync(false);
|
||||
player->cyclesPosted += cycles;
|
||||
if (player->awake < 1) {
|
||||
player->node.gb->nextEvent += player->cyclesPosted;
|
||||
}
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
player->awake = 1;
|
||||
} else {
|
||||
player->controller->setSync(true);
|
||||
player->cyclesPosted += cycles;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
m_lockstep.useCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
Player* player = &controller->m_players[id];
|
||||
Player* player = controller->player(id);
|
||||
player->cyclesPosted -= cycles;
|
||||
if (player->cyclesPosted <= 0) {
|
||||
mCoreThreadWaitFromThread(player->controller->thread());
|
||||
|
@ -118,21 +148,21 @@ MultiplayerController::MultiplayerController() {
|
|||
cycles = player->cyclesPosted;
|
||||
return cycles;
|
||||
};
|
||||
m_lockstep.unusedCycles= [](mLockstep* lockstep, int id) {
|
||||
m_lockstep.unusedCycles = [](mLockstep* lockstep, int id) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
Player* player = &controller->m_players[id];
|
||||
Player* player = controller->player(id);
|
||||
auto cycles = player->cyclesPosted;
|
||||
return cycles;
|
||||
};
|
||||
m_lockstep.unload = [](mLockstep* lockstep, int id) {
|
||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||
if (id) {
|
||||
Player* player = &controller->m_players[id];
|
||||
Player* player = controller->player(id);
|
||||
player->controller->setSync(true);
|
||||
player->cyclesPosted = 0;
|
||||
|
||||
// release master GBA if it is waiting for this GBA
|
||||
player = &controller->m_players[0];
|
||||
player = controller->player(0);
|
||||
player->waitMask &= ~(1 << id);
|
||||
if (!player->waitMask && player->awake < 1) {
|
||||
mCoreThreadStopWaiting(player->controller->thread());
|
||||
|
@ -140,7 +170,7 @@ MultiplayerController::MultiplayerController() {
|
|||
}
|
||||
} else {
|
||||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
||||
Player* player = &controller->m_players[i];
|
||||
Player* player = controller->player(i);
|
||||
player->controller->setSync(true);
|
||||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
|
@ -160,12 +190,12 @@ MultiplayerController::MultiplayerController() {
|
|||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
player->gbaNode->nextEvent += player->cyclesPosted;
|
||||
player->node.gba->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
player->gbNode->nextEvent += player->cyclesPosted;
|
||||
player->node.gb->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
|
@ -315,3 +345,29 @@ int MultiplayerController::attached() {
|
|||
num = m_lockstep.attached;
|
||||
return num;
|
||||
}
|
||||
|
||||
MultiplayerController::Player* MultiplayerController::player(int id) {
|
||||
Player* player = &m_players[id];
|
||||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
if (player->node.gba->id != id) {
|
||||
std::sort(m_players.begin(), m_players.end());
|
||||
player = &m_players[id];
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
if (player->node.gb->id != id) {
|
||||
std::swap(m_players[0], m_players[1]);
|
||||
player = &m_players[id];
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case mPLATFORM_NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QMutex>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
#include <mgba/core/lockstep.h>
|
||||
|
@ -44,6 +44,10 @@ signals:
|
|||
void gameDetached();
|
||||
|
||||
private:
|
||||
union Node {
|
||||
GBSIOLockstepNode* gb;
|
||||
GBASIOLockstepNode* gba;
|
||||
};
|
||||
struct Player {
|
||||
#ifdef M_CORE_GB
|
||||
Player(CoreController* controller, GBSIOLockstepNode* node);
|
||||
|
@ -52,13 +56,18 @@ private:
|
|||
Player(CoreController* controller, GBASIOLockstepNode* node);
|
||||
#endif
|
||||
|
||||
int id() const;
|
||||
bool operator<(const Player&) const;
|
||||
|
||||
CoreController* controller;
|
||||
GBSIOLockstepNode* gbNode = nullptr;
|
||||
GBASIOLockstepNode* gbaNode = nullptr;
|
||||
Node node = {nullptr};
|
||||
int awake = 1;
|
||||
int32_t cyclesPosted = 0;
|
||||
unsigned waitMask = 0;
|
||||
};
|
||||
|
||||
Player* player(int id);
|
||||
|
||||
union {
|
||||
mLockstep m_lockstep;
|
||||
#ifdef M_CORE_GB
|
||||
|
|