Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2023-05-03 02:41:03 -07:00
commit 56c8b35ef6
24 changed files with 363 additions and 117 deletions

19
CHANGES
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,3 @@
[testinfo]
skip=10
frames=1

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

View File

@ -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]);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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