diff --git a/CHANGES b/CHANGES index 7f00b78e8..9f4fdcde0 100644 --- a/CHANGES +++ b/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) diff --git a/cinema/gba/blend/disabled-bg-semitrans-blend/baseline_0000.png b/cinema/gba/blend/disabled-bg-semitrans-blend/baseline_0000.png new file mode 100644 index 000000000..f90f448c4 Binary files /dev/null and b/cinema/gba/blend/disabled-bg-semitrans-blend/baseline_0000.png differ diff --git a/cinema/gba/blend/disabled-bg-semitrans-blend/config.ini b/cinema/gba/blend/disabled-bg-semitrans-blend/config.ini new file mode 100644 index 000000000..f26f6d8b1 --- /dev/null +++ b/cinema/gba/blend/disabled-bg-semitrans-blend/config.ini @@ -0,0 +1,3 @@ +[testinfo] +skip=10 +frames=1 diff --git a/cinema/gba/blend/disabled-bg-semitrans-blend/test.gba b/cinema/gba/blend/disabled-bg-semitrans-blend/test.gba new file mode 100644 index 000000000..91cb67e5f Binary files /dev/null and b/cinema/gba/blend/disabled-bg-semitrans-blend/test.gba differ diff --git a/cinema/gba/blend/gbg-blend/baseline_0000.png b/cinema/gba/blend/gbg-blend/baseline_0000.png new file mode 100644 index 000000000..2fbf58114 Binary files /dev/null and b/cinema/gba/blend/gbg-blend/baseline_0000.png differ diff --git a/cinema/gba/blend/gbg-blend/baseline_0001.png b/cinema/gba/blend/gbg-blend/baseline_0001.png new file mode 100644 index 000000000..5bc3c37a9 Binary files /dev/null and b/cinema/gba/blend/gbg-blend/baseline_0001.png differ diff --git a/cinema/gba/blend/gbg-blend/baseline_0002.png b/cinema/gba/blend/gbg-blend/baseline_0002.png new file mode 100644 index 000000000..1d38b9113 Binary files /dev/null and b/cinema/gba/blend/gbg-blend/baseline_0002.png differ diff --git a/cinema/gba/blend/gbg-blend/baseline_0003.png b/cinema/gba/blend/gbg-blend/baseline_0003.png new file mode 100644 index 000000000..368572a31 Binary files /dev/null and b/cinema/gba/blend/gbg-blend/baseline_0003.png differ diff --git a/cinema/gba/blend/gbg-blend/baseline_0004.png b/cinema/gba/blend/gbg-blend/baseline_0004.png new file mode 100644 index 000000000..25ac9a790 Binary files /dev/null and b/cinema/gba/blend/gbg-blend/baseline_0004.png differ diff --git a/cinema/gba/blend/gbg-blend/baseline_0005.png b/cinema/gba/blend/gbg-blend/baseline_0005.png new file mode 100644 index 000000000..c6d340652 Binary files /dev/null and b/cinema/gba/blend/gbg-blend/baseline_0005.png differ diff --git a/cinema/gba/blend/gbg-blend/test.mvl b/cinema/gba/blend/gbg-blend/test.mvl new file mode 100644 index 000000000..02be0a86a Binary files /dev/null and b/cinema/gba/blend/gbg-blend/test.mvl differ diff --git a/src/gb/sio/lockstep.c b/src/gb/sio/lockstep.c index 4d1f1f549..b8dccf891 100644 --- a/src/gb/sio/lockstep.c +++ b/src/gb/sio/lockstep.c @@ -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]); diff --git a/src/gba/gba.c b/src/gba/gba.c index 65b848e80..d3f0d9ee3 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -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; diff --git a/src/gba/renderers/software-mode0.c b/src/gba/renderers/software-mode0.c index c42a4a71c..d1feeb22b 100644 --- a/src/gba/renderers/software-mode0.c +++ b/src/gba/renderers/software-mode0.c @@ -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; diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 3fc49f226..8881cf84b 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -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; diff --git a/src/gba/renderers/software-private.h b/src/gba/renderers/software-private.h index c0162374b..7dd5474f2 100644 --- a/src/gba/renderers/software-private.h +++ b/src/gba/renderers/software-private.h @@ -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; \ diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index b76edff7e..a0d2f8c3b 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -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(); + 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 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 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(); gl2Backend = static_cast(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(painter->m_backend); if (painter->m_widget && painter->supportsShaders()) { QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions(); fn->glFinish(); - mGLES2Context* gl2Backend = reinterpret_cast(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(); 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::max()) { + m_freeTex.enqueue(m_bridgeTexOut); + } + m_bridgeTexOut = m_queueTex.dequeue(); +#if defined(BUILD_GLES2) || defined(BUILD_GLES3) + if (supportsShaders()) { + mGLES2Context* gl2Backend = reinterpret_cast(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::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(); + 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 diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 77ea6d195..3ed31a9d8 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -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 m_painter; QThread m_drawThread; + QThread m_proxyThread; std::shared_ptr m_context; mGLWidget* m_gl; + QOffscreenSurface m_proxySurface; + std::unique_ptr m_proxyContext; }; class PainterGL : public QObject { @@ -137,15 +143,21 @@ public: void setContext(std::shared_ptr); 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); 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 m_free; QQueue m_queue; uint32_t* m_buffer = nullptr; + + std::array m_bridgeTexes; + QQueue m_freeTex; + QQueue m_queueTex; + + GLuint m_bridgeTexIn = std::numeric_limits::max(); + GLuint m_bridgeTexOut = std::numeric_limits::max(); + QPainter m_painter; QMutex m_mutex; QWindow* m_window; diff --git a/src/platform/qt/ForwarderController.cpp b/src/platform/qt/ForwarderController.cpp index 6083a1d5e..fe0530a73 100644 --- a/src/platform/qt/ForwarderController.cpp +++ b/src/platform/qt/ForwarderController.cpp @@ -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) diff --git a/src/platform/qt/ForwarderController.h b/src/platform/qt/ForwarderController.h index 7129b63d0..e53006bc2 100644 --- a/src/platform/qt/ForwarderController.h +++ b/src/platform/qt/ForwarderController.h @@ -30,6 +30,10 @@ public: void setGenerator(std::unique_ptr&& 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 m_generator; QFile m_sourceFile; + QString m_baseFilename; bool m_inProgress = false; QByteArray m_originalPath; }; diff --git a/src/platform/qt/ForwarderView.cpp b/src/platform/qt/ForwarderView.cpp index 2e8929f1d..0546e7180 100644 --- a/src/platform/qt/ForwarderView.cpp +++ b/src/platform/qt/ForwarderView.cpp @@ -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; } diff --git a/src/platform/qt/ForwarderView.ui b/src/platform/qt/ForwarderView.ui index d5a7de842..1c72e3caa 100644 --- a/src/platform/qt/ForwarderView.ui +++ b/src/platform/qt/ForwarderView.ui @@ -87,7 +87,7 @@ - + false diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index 7f29100bd..f44323603 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -14,24 +14,46 @@ #include #endif +#include + 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(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(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(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(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; +} diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index 03aad86d8..5ad6124db 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once -#include #include +#include #include #include @@ -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