Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2022-05-29 22:27:40 -07:00
commit 8337786b64
37 changed files with 924 additions and 229 deletions

View File

@ -56,12 +56,17 @@ Emulation fixes:
- GB Video: Draw SGB border pieces that overlap GB graphics (fixes mgba.io/i/1339)
- GBA: Improve timing when not booting from BIOS
- GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059)
- GBA DMA: Fix DMA source direction bits being cleared (fixes mgba.io/i/2410)
- GBA I/O: Redo internal key input, enabling edge-based key IRQs
- GBA I/O: Disable open bus behavior on invalid register 06A
- GBA Memory: Fix misaligned 32-bit I/O loads (fixes mgba.io/i/2307)
- GBA Video: Fix OpenGL rendering on M1 Macs
Other fixes:
- Core: Don't attempt to restore rewind diffs past start of rewind
- FFmpeg: Fix crash when encoding audio with some containers
- FFmpeg: Fix GIF recording (fixes mgba.io/i/2393)
- GB: Fix temporary saves
- GB, GBA: Save writeback-pending masked saves on unload (fixes mgba.io/i/2396)
Misc:
- Core: Suspend runloop when a core crashes
- GB Video: Add default SGB border
@ -71,6 +76,9 @@ Misc:
- Qt: Only set default controller bindings if loading fails (fixes mgba.io/i/799)
- Qt: Save converter now supports importing GameShark Advance saves
- Qt: Save positions of multiplayer windows (closes mgba.io/i/2128)
- Qt: Add optional frame counter to OSD (closes mgba.io/i/1728)
- Qt: Add optional emulation-related information on reset (closes mgba.io/i/1780)
- Qt: Add QOpenGLWidget cross-thread codepath for macOS (fixes mgba.io/i/1754)
- Windows: Attach to console if present
0.9.3: (2021-12-17)

View File

@ -29,6 +29,7 @@ struct GDBStub {
char line[GDB_STUB_MAX_LINE];
char outgoing[GDB_STUB_MAX_LINE];
char memoryMapXml[GDB_STUB_MAX_LINE];
enum GDBStubAckState lineAck;
Socket socket;

View File

@ -0,0 +1,31 @@
/* Copyright (c) 2013-2022 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef M_INTERNAL_DEFINES_H
#define M_INTERNAL_DEFINES_H
#define mSAVEDATA_CLEANUP_THRESHOLD 15
enum {
mSAVEDATA_DIRT_NONE = 0,
mSAVEDATA_DIRT_NEW = 1,
mSAVEDATA_DIRT_SEEN = 2,
};
static inline bool mSavedataClean(int* dirty, uint32_t* dirtAge, uint32_t frameCount) {
if (*dirty & mSAVEDATA_DIRT_NEW) {
*dirtAge = frameCount;
*dirty &= ~mSAVEDATA_DIRT_NEW;
if (!(*dirty & mSAVEDATA_DIRT_SEEN)) {
*dirty |= mSAVEDATA_DIRT_SEEN;
}
} else if ((*dirty & mSAVEDATA_DIRT_SEEN) && frameCount - *dirtAge > mSAVEDATA_CLEANUP_THRESHOLD) {
*dirty = 0;
return true;
}
return false;
}
#endif

View File

@ -102,7 +102,7 @@ struct GB {
struct VFile* sramRealVf;
uint32_t sramSize;
int sramDirty;
int32_t sramDirtAge;
uint32_t sramDirtAge;
bool sramMaskWriteback;
int sgbBit;

View File

@ -65,11 +65,6 @@ enum {
GB_SIZE_MBC6_FLASH = 0x100000,
};
enum {
GB_SRAM_DIRT_NEW = 1,
GB_SRAM_DIRT_SEEN = 2
};
struct GBMemory;
typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_t value);
typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address);

View File

@ -60,11 +60,6 @@ enum FlashManufacturer {
FLASH_MFG_SANYO = 0x1362
};
enum SavedataDirty {
SAVEDATA_DIRT_NEW = 1,
SAVEDATA_DIRT_SEEN = 2
};
enum {
SAVEDATA_FLASH_BASE = 0x0E005555,
@ -92,7 +87,7 @@ struct GBASavedata {
unsigned settling;
struct mTimingEvent dust;
enum SavedataDirty dirty;
int dirty;
uint32_t dirtAge;
enum FlashStateMachine flashState;

View File

@ -9,6 +9,7 @@
#include <mgba/internal/arm/debugger/debugger.h>
#include <mgba/internal/arm/isa-inlines.h>
#include <mgba/internal/gba/memory.h>
#include <mgba-util/string.h>
#include <signal.h>
@ -29,6 +30,30 @@ enum {
MACH_O_ARM_V4T = 5
};
static const char* TARGET_XML = "<target version=\"1.0\">"
"<architecture>armv4t</architecture>"
"<osabi>none</osabi>"
"<feature name=\"org.gnu.gdb.arm.core\">"
"<reg name=\"r0\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r1\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r2\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r3\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r4\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r5\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r6\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r7\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r8\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r9\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r10\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r11\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"r12\" bitsize=\"32\" type=\"uint32\"/>"
"<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>"
"<reg name=\"lr\" bitsize=\"32\"/>"
"<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>"
"<reg name=\"cpsr\" bitsize=\"32\" regnum=\"25\"/>"
"</feature>"
"</target>";
static void _sendMessage(struct GDBStub* stub);
static void _gdbStubDeinit(struct mDebugger* debugger) {
@ -336,16 +361,6 @@ static void _readGPRs(struct GDBStub* stub, const char* message) {
_int2hex32(cpu->gprs[ARM_PC] - (cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM), &stub->outgoing[i]);
i += 8;
// Floating point registers, unused on the GBA (8 of them, 24 bits each)
for (r = 0; r < 8 * 3; ++r) {
_int2hex32(0, &stub->outgoing[i]);
i += 8;
}
// Floating point status, unused on the GBA (32 bits)
_int2hex32(0, &stub->outgoing[i]);
i += 8;
// CPU status
_int2hex32(cpu->cpsr.packed, &stub->outgoing[i]);
i += 8;
@ -433,7 +448,58 @@ static void _processQSupportedCommand(struct GDBStub* stub, const char* message)
}
message = end + 1;
}
strncpy(stub->outgoing, "swbreak+;hwbreak+", GDB_STUB_MAX_LINE - 4);
strncpy(stub->outgoing, "swbreak+;hwbreak+;qXfer:features:read+;qXfer:memory-map:read+", GDB_STUB_MAX_LINE - 4);
}
static void _processQXferCommand(struct GDBStub* stub, const char* params, const char* data) {
unsigned offset = 0;
unsigned length = 0;
unsigned index = 0;
for (index = 0; params[index] != ','; ++index) {
offset <<= 4;
offset |= _hex2int(&params[index], 1);
}
++index;
unsigned int paramsLength = strlen(params);
for (; index + 3 < paramsLength; ++index) {
length <<= 4;
length |= _hex2int(&params[index], 1);
}
length += 1;
if (length + 4 > GDB_STUB_MAX_LINE) {
length = GDB_STUB_MAX_LINE - 4;
}
if (strlen(data) < length + offset) {
length = strlen(data) - offset + 1;
stub->outgoing[0] = 'l';
} else {
stub->outgoing[0] = 'm';
}
strlcpy(&stub->outgoing[1], &data[offset], length);
}
static void _generateMemoryMapXml(struct GDBStub* stub, char* memoryMap) {
size_t index = 0;
strncpy(memoryMap, "<memory-map version=\"1.0\">", 27);
index += strlen(memoryMap);
const struct mCoreMemoryBlock* blocks;
size_t nBlocks = stub->d.core->listMemoryBlocks(stub->d.core, &blocks);
size_t i;
for (i = 0; i < nBlocks; ++i) {
if (!(blocks[i].flags & mCORE_MEMORY_MAPPED)) {
continue;
}
const char* type = blocks[i].flags & (mCORE_MEMORY_WRITE | mCORE_MEMORY_WORM) ? "ram" : "rom";
index += snprintf(&memoryMap[index], GDB_STUB_MAX_LINE - index, "<memory type=\"%s\" start=\"0x%08x\" length=\"0x%08x\"/>", type, blocks[i].start, blocks[i].size);
}
int amountLeft = GDB_STUB_MAX_LINE - index;
strncpy(&memoryMap[index], "</memory-map>", amountLeft);
}
static void _processQReadCommand(struct GDBStub* stub, const char* message) {
@ -452,6 +518,13 @@ static void _processQReadCommand(struct GDBStub* stub, const char* message) {
strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4);
} else if (!strncmp("sThreadInfo#", message, 12)) {
strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4);
} else if (!strncmp("Xfer:features:read:target.xml:", message, 30)) {
_processQXferCommand(stub, message + 30, TARGET_XML);
} else if (!strncmp("Xfer:memory-map:read::", message, 22)) {
if (strlen(stub->memoryMapXml) == 0) {
_generateMemoryMapXml(stub, stub->memoryMapXml);
}
_processQXferCommand(stub, message + 22, stub->memoryMapXml);
} else if (!strncmp("Supported:", message, 10)) {
_processQSupportedCommand(stub, message + 10);
}
@ -487,7 +560,6 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) {
unsigned i = 0;
uint32_t address = _readHex(readAddress, &i);
readAddress += i + 1;
uint32_t kind = _readHex(readAddress, &i);
struct mBreakpoint breakpoint = {
.address = address,
@ -499,8 +571,6 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) {
switch (message[0]) {
case '0':
ARMDebuggerSetSoftwareBreakpoint(stub->d.platform, address, kind == 2 ? MODE_THUMB : MODE_ARM);
break;
case '1':
stub->d.platform->setBreakpoint(stub->d.platform, &breakpoint);
break;
@ -710,6 +780,8 @@ bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAdd
goto cleanup;
}
memset(stub->memoryMapXml, 0, GDB_STUB_MAX_LINE);
return true;
cleanup:

View File

@ -165,6 +165,7 @@ void applyArguments(const struct mArguments* args, struct mSubParser* subparser,
}
if (args->bios) {
mCoreConfigSetOverrideValue(config, "bios", args->bios);
mCoreConfigSetOverrideIntValue(config, "useBios", true);
}
HashTableEnumerate(&args->configOverrides, _tableApply, config);
if (subparser) {

View File

@ -789,7 +789,7 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size
sws_scale(encoder->scaleContext, (const uint8_t* const*) &pixels, (const int*) &stride, 0, encoder->iheight, encoder->videoFrame->data, encoder->videoFrame->linesize);
if (encoder->graph) {
if (av_buffersrc_add_frame(encoder->source, encoder->videoFrame) < 0) {
if (av_buffersrc_write_frame(encoder->source, encoder->videoFrame) < 0) {
return;
}
while (true) {

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/defines.h>
#include <mgba/internal/gb/io.h>
#include <mgba/internal/gb/mbc.h>
#include <mgba/internal/sm83/sm83.h>
@ -17,8 +18,6 @@
#include <mgba-util/patch.h>
#include <mgba-util/vfs.h>
#define CLEANUP_THRESHOLD 15
const uint32_t CGB_SM83_FREQUENCY = 0x800000;
const uint32_t SGB_SM83_FREQUENCY = 0x418B1E;
@ -203,6 +202,14 @@ void GBResizeSram(struct GB* gb, size_t size) {
if (gb->memory.sram) {
vf->unmap(vf, gb->memory.sram, gb->sramSize);
}
if (vf->size(vf) < gb->sramSize) {
void* sram = vf->map(vf, vf->size(vf), MAP_READ);
struct VFile* newVf = VFileMemChunk(sram, vf->size(vf));
vf->unmap(vf, sram,vf->size(vf));
vf = newVf;
gb->sramVf = newVf;
vf->truncate(vf, size);
}
gb->memory.sram = vf->map(vf, size, MAP_READ);
}
if (gb->memory.sram == (void*) -1) {
@ -233,24 +240,19 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) {
if (!gb->sramVf) {
return;
}
if (gb->sramDirty & GB_SRAM_DIRT_NEW) {
gb->sramDirtAge = frameCount;
gb->sramDirty &= ~GB_SRAM_DIRT_NEW;
if (!(gb->sramDirty & GB_SRAM_DIRT_SEEN)) {
gb->sramDirty |= GB_SRAM_DIRT_SEEN;
}
} else if ((gb->sramDirty & GB_SRAM_DIRT_SEEN) && frameCount - gb->sramDirtAge > CLEANUP_THRESHOLD) {
if (mSavedataClean(&gb->sramDirty, &gb->sramDirtAge, frameCount)) {
if (gb->sramMaskWriteback) {
GBSavedataUnmask(gb);
}
if (gb->memory.mbcType == GB_MBC3_RTC) {
GBMBCRTCWrite(gb);
}
gb->sramDirty = 0;
if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) {
mLOG(GB_MEM, INFO, "Savedata synced");
} else {
mLOG(GB_MEM, INFO, "Savedata failed to sync!");
if (gb->sramVf == gb->sramRealVf) {
if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) {
mLOG(GB_MEM, INFO, "Savedata synced");
} else {
mLOG(GB_MEM, INFO, "Savedata failed to sync!");
}
}
size_t c;
@ -271,7 +273,7 @@ void GBSavedataMask(struct GB* gb, struct VFile* vf, bool writeback) {
}
gb->sramVf = vf;
gb->sramMaskWriteback = writeback;
gb->memory.sram = vf->map(vf, gb->sramSize, MAP_READ);
GBResizeSram(gb, gb->sramSize);
GBMBCSwitchSramBank(gb, gb->memory.sramCurrentBank);
}
@ -316,7 +318,9 @@ void GBUnloadROM(struct GB* gb) {
gb->memory.mbcType = GB_MBC_AUTODETECT;
gb->isPristine = false;
gb->sramMaskWriteback = false;
if (!gb->sramDirty) {
gb->sramMaskWriteback = false;
}
GBSavedataUnmask(gb);
GBSramDeinit(gb);
if (gb->sramRealVf) {

View File

@ -6,9 +6,10 @@
#include <mgba/internal/gb/mbc.h>
#include <mgba/core/interface.h>
#include <mgba/internal/sm83/sm83.h>
#include <mgba/internal/defines.h>
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/memory.h>
#include <mgba/internal/sm83/sm83.h>
#include <mgba-util/crc32.h>
#include <mgba-util/vfs.h>
@ -615,6 +616,7 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) {
address &= 0x1FF;
memory->sramBank[(address >> 1)] &= 0xF0 >> shift;
memory->sramBank[(address >> 1)] |= (value & 0xF) << shift;
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
break;
default:
// TODO
@ -776,6 +778,7 @@ void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) {
case 0x2B:
if (memory->sramAccess) {
memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM_HALFBANK - 1)] = value;
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
}
break;
case 0x2C:
@ -841,6 +844,7 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) {
break;
case 0x5:
_GBMBC7Write(&gb->memory, address, value);
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
break;
default:
// TODO
@ -1163,6 +1167,7 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) {
address &= 0x7F;
if (address == 0 && value & 1) {
value &= 6; // TODO: Timing
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
_GBPocketCamCapture(memory);
}
if (address < sizeof(memory->mbcState.pocketCam.registers)) {
@ -1287,6 +1292,7 @@ void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
switch (tama5->registers[GBTAMA5_CS] >> 1) {
case 0x0: // RAM write
memory->sram[address] = out;
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
break;
case 0x1: // RAM read
break;

View File

@ -6,6 +6,7 @@
#include <mgba/internal/gb/memory.h>
#include <mgba/core/interface.h>
#include <mgba/internal/defines.h>
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/io.h>
#include <mgba/internal/gb/mbc.h>
@ -356,11 +357,13 @@ void GBStore8(struct SM83Core* cpu, uint16_t address, int8_t value) {
if (memory->rtcAccess) {
memory->rtcRegs[memory->activeRtcReg] = value;
} else if (memory->sramAccess && memory->sram && memory->directSramAccess) {
memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value;
if (memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] != value) {
memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value;
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
}
} else {
memory->mbcWrite(gb, address, value);
}
gb->sramDirty |= GB_SRAM_DIRT_NEW;
return;
case GB_REGION_WORKING_RAM_BANK0:
case GB_REGION_WORKING_RAM_BANK0 + 2:
@ -648,7 +651,7 @@ void GBPatch8(struct SM83Core* cpu, uint16_t address, int8_t value, int8_t* old,
} else {
memory->mbcWrite(gb, address, value);
}
gb->sramDirty |= GB_SRAM_DIRT_NEW;
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
return;
case GB_REGION_WORKING_RAM_BANK0:
case GB_REGION_WORKING_RAM_BANK0 + 2:

View File

@ -83,9 +83,6 @@ uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) {
if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) {
currentDma->nextSource = currentDma->source;
if (currentDma->nextSource >= BASE_CART0 && currentDma->nextSource < BASE_CART_SRAM && GBADMARegisterGetSrcControl(currentDma->reg) < 3) {
currentDma->reg = GBADMARegisterClearSrcControl(currentDma->reg);
}
currentDma->nextDest = currentDma->dest;
uint32_t width = 2 << GBADMARegisterGetWidth(currentDma->reg);
@ -291,7 +288,13 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
}
gba->bus = memory->dmaTransferRegister;
}
int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width;
int sourceOffset;
if (info->nextSource >= BASE_CART0 && info->nextSource < BASE_CART_SRAM && GBADMARegisterGetSrcControl(info->reg) < 3) {
sourceOffset = width;
} else {
sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width;
}
int destOffset = DMA_OFFSET[GBADMARegisterGetDestControl(info->reg)] * width;
if (source) {
source += sourceOffset;

View File

@ -149,7 +149,9 @@ void GBAUnloadROM(struct GBA* gba) {
gba->memory.rom = NULL;
gba->isPristine = false;
gba->memory.savedata.maskWriteback = false;
if (!gba->memory.savedata.dirty) {
gba->memory.savedata.maskWriteback = false;
}
GBASavedataUnmask(&gba->memory.savedata);
GBASavedataDeinit(&gba->memory.savedata);
if (gba->memory.savedata.realVf) {

View File

@ -7,6 +7,7 @@
#include <mgba/internal/arm/decoder.h>
#include <mgba/internal/arm/macros.h>
#include <mgba/internal/defines.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/dma.h>
#include <mgba/internal/gba/io.h>
@ -1075,12 +1076,12 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
} else {
memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value;
}
memory->savedata.dirty |= SAVEDATA_DIRT_NEW;
memory->savedata.dirty |= mSAVEDATA_DIRT_NEW;
} else if (memory->hw.devices & HW_TILT) {
GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value);
} else if (memory->savedata.type == SAVEDATA_SRAM512) {
memory->savedata.data[address & (SIZE_CART_SRAM512 - 1)] = value;
memory->savedata.dirty |= SAVEDATA_DIRT_NEW;
memory->savedata.dirty |= mSAVEDATA_DIRT_NEW;
} else {
mLOG(GBA_MEM, GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address);
}

View File

@ -67,8 +67,8 @@ static const GLchar* const _gles3Header =
"precision highp isampler2D;\n";
static const GLchar* const _gl3Header =
"#version 150 core\n"
"#define OUT(n)\n"
"#version 330 core\n"
"#define OUT(n) layout(location = n)\n"
PALETTE_ENTRY
"precision highp float;\n";
@ -684,7 +684,7 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) {
renderer->scale = 1;
}
static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, const char* const* outFrags, char* log) {
static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, char* log) {
GLuint program = glCreateProgram();
shader->program = program;
@ -697,14 +697,6 @@ static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVide
if (log[0]) {
mLOG(GBA_VIDEO, ERROR, "Fragment shader compilation failure: %s", log);
}
size_t i;
#ifndef BUILD_GLES3
for (i = 0; outFrags[i]; ++i) {
glBindFragDataLocation(program, i, outFrags[i]);
}
#else
UNUSED(outFrags);
#endif
glLinkProgram(program);
glGetProgramInfoLog(program, 2048, 0, log);
if (log[0]) {
@ -719,6 +711,7 @@ static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVide
glEnableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 2, GL_INT, GL_FALSE, 0, NULL);
size_t i;
for (i = 0; uniforms[i].name; ++i) {
shader->uniforms[uniforms[i].type] = glGetUniformLocation(program, uniforms[i].name);
}
@ -824,7 +817,7 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) {
char log[2048];
const GLchar* shaderBuffer[4];
const GLubyte* version = glGetString(GL_VERSION);
if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES "))) {
if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES ")) != 0) {
shaderBuffer[0] = _gl3Header;
} else {
shaderBuffer[0] = _gles3Header;
@ -838,53 +831,47 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) {
if (log[0]) {
mLOG(GBA_VIDEO, ERROR, "Vertex shader compilation failure: %s", log);
}
const char* const noWindow[] = {"color", "flags", NULL};
const char* const window[] = {"color", "flags", "window", NULL};
const char* const onlyWindow[] = {"window", NULL};
const char* const onlyColor[] = {"color", NULL};
shaderBuffer[1] = _renderMode0;
shaderBuffer[2] = _renderTile16;
_compileShader(glRenderer, &glRenderer->bgShader[0], shaderBuffer, 3, vs, _uniformsMode0, noWindow, log);
_compileShader(glRenderer, &glRenderer->bgShader[0], shaderBuffer, 3, vs, _uniformsMode0, log);
shaderBuffer[2] = _renderTile256;
_compileShader(glRenderer, &glRenderer->bgShader[1], shaderBuffer, 3, vs, _uniformsMode0, noWindow, log);
_compileShader(glRenderer, &glRenderer->bgShader[1], shaderBuffer, 3, vs, _uniformsMode0, log);
shaderBuffer[1] = _renderMode2;
shaderBuffer[2] = _interpolate;
shaderBuffer[3] = _fetchTileOverflow;
_compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 4, vs, _uniformsMode2, noWindow, log);
_compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 4, vs, _uniformsMode2, log);
shaderBuffer[3] = _fetchTileNoOverflow;
_compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 4, vs, _uniformsMode2, noWindow, log);
_compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 4, vs, _uniformsMode2, log);
shaderBuffer[1] = _renderMode4;
shaderBuffer[2] = _interpolate;
_compileShader(glRenderer, &glRenderer->bgShader[4], shaderBuffer, 3, vs, _uniformsMode4, noWindow, log);
_compileShader(glRenderer, &glRenderer->bgShader[4], shaderBuffer, 3, vs, _uniformsMode4, log);
shaderBuffer[1] = _renderMode35;
shaderBuffer[2] = _interpolate;
_compileShader(glRenderer, &glRenderer->bgShader[5], shaderBuffer, 3, vs, _uniformsMode35, noWindow, log);
_compileShader(glRenderer, &glRenderer->bgShader[5], shaderBuffer, 3, vs, _uniformsMode35, log);
shaderBuffer[1] = _renderObj;
shaderBuffer[2] = _renderTile16;
_compileShader(glRenderer, &glRenderer->objShader[0], shaderBuffer, 3, vs, _uniformsObj, window, log);
_compileShader(glRenderer, &glRenderer->objShader[0], shaderBuffer, 3, vs, _uniformsObj, log);
shaderBuffer[2] = _renderTile256;
_compileShader(glRenderer, &glRenderer->objShader[1], shaderBuffer, 3, vs, _uniformsObj, window, log);
_compileShader(glRenderer, &glRenderer->objShader[1], shaderBuffer, 3, vs, _uniformsObj, log);
shaderBuffer[1] = _renderObjPriority;
_compileShader(glRenderer, &glRenderer->objShader[2], shaderBuffer, 2, vs, _uniformsObjPriority, noWindow, log);
_compileShader(glRenderer, &glRenderer->objShader[2], shaderBuffer, 2, vs, _uniformsObjPriority, log);
shaderBuffer[1] = _renderWindow;
_compileShader(glRenderer, &glRenderer->windowShader, shaderBuffer, 2, vs, _uniformsWindow, onlyWindow, log);
_compileShader(glRenderer, &glRenderer->windowShader, shaderBuffer, 2, vs, _uniformsWindow, log);
shaderBuffer[1] = _finalize;
_compileShader(glRenderer, &glRenderer->finalizeShader, shaderBuffer, 2, vs, _uniformsFinalize, onlyColor, log);
_compileShader(glRenderer, &glRenderer->finalizeShader, shaderBuffer, 2, vs, _uniformsFinalize, log);
glBindVertexArray(0);
glDeleteShader(vs);

View File

@ -6,6 +6,7 @@
#include <mgba/internal/gba/savedata.h>
#include <mgba/internal/arm/macros.h>
#include <mgba/internal/defines.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/serialize.h>
@ -24,7 +25,6 @@
#define FLASH_PROGRAM_CYCLES 650
// This needs real testing, and is only an estimation currently
#define EEPROM_SETTLE_CYCLES 115000
#define CLEANUP_THRESHOLD 15
mLOG_DEFINE_CATEGORY(GBA_SAVE, "GBA Savedata", "gba.savedata");
@ -379,7 +379,7 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8
case FLASH_STATE_RAW:
switch (savedata->command) {
case FLASH_COMMAND_PROGRAM:
savedata->dirty |= SAVEDATA_DIRT_NEW;
savedata->dirty |= mSAVEDATA_DIRT_NEW;
savedata->currentBank[address] = value;
savedata->command = FLASH_COMMAND_NONE;
mTimingDeschedule(savedata->timing, &savedata->dust);
@ -511,7 +511,7 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
uint8_t current = savedata->data[savedata->writeAddress >> 3];
current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7));
savedata->dirty |= SAVEDATA_DIRT_NEW;
savedata->dirty |= mSAVEDATA_DIRT_NEW;
savedata->data[savedata->writeAddress >> 3] = current;
mTimingDeschedule(savedata->timing, &savedata->dust);
mTimingSchedule(savedata->timing, &savedata->dust, EEPROM_SETTLE_CYCLES);
@ -565,15 +565,10 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
if (!savedata->vf) {
return;
}
if (savedata->dirty & SAVEDATA_DIRT_NEW) {
savedata->dirtAge = frameCount;
savedata->dirty &= ~SAVEDATA_DIRT_NEW;
savedata->dirty |= SAVEDATA_DIRT_SEEN;
} else if ((savedata->dirty & SAVEDATA_DIRT_SEEN) && frameCount - savedata->dirtAge > CLEANUP_THRESHOLD) {
if (mSavedataClean(&savedata->dirty, &savedata->dirtAge, frameCount)) {
if (savedata->maskWriteback) {
GBASavedataUnmask(savedata);
}
savedata->dirty = 0;
if (savedata->mapMode & MAP_WRITE) {
size_t size = GBASavedataSize(savedata);
if (savedata->data && savedata->vf->sync(savedata->vf, savedata->data, size)) {
@ -650,7 +645,7 @@ void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
void _flashErase(struct GBASavedata* savedata) {
mLOG(GBA_SAVE, DEBUG, "Performing flash chip erase");
savedata->dirty |= SAVEDATA_DIRT_NEW;
savedata->dirty |= mSAVEDATA_DIRT_NEW;
size_t size = SIZE_CART_FLASH512;
if (savedata->type == SAVEDATA_FLASH1M) {
size = SIZE_CART_FLASH1M;
@ -660,7 +655,7 @@ void _flashErase(struct GBASavedata* savedata) {
void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
mLOG(GBA_SAVE, DEBUG, "Performing flash sector erase at 0x%04x", sectorStart);
savedata->dirty |= SAVEDATA_DIRT_NEW;
savedata->dirty |= mSAVEDATA_DIRT_NEW;
size_t size = 0x1000;
if (savedata->type == SAVEDATA_FLASH1M) {
mLOG(GBA_SAVE, DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);

View File

@ -235,6 +235,7 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h)
context->shaders[n].dirty = true;
}
}
context->finalShader.dirty = true;
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, drawW, drawH, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo);
@ -375,7 +376,6 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
glGetIntegerv(GL_VIEWPORT, viewport);
context->finalShader.filter = v->filter;
context->finalShader.dirty = true;
_drawShader(context, &context->initialShader);
if (v->interframeBlending) {
context->interframeShader.blend = true;
@ -437,15 +437,19 @@ void mGLES2ContextCreate(struct mGLES2Context* context) {
}
void mGLES2ContextUseFramebuffer(struct mGLES2Context* context) {
glGenFramebuffers(1, &context->finalShader.fbo);
glGenTextures(1, &context->finalShader.tex);
if (!context->finalShader.fbo) {
glGenFramebuffers(1, &context->finalShader.fbo);
}
if (!context->finalShader.tex) {
glGenTextures(1, &context->finalShader.tex);
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
}
glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context->finalShader.tex, 0);

View File

@ -74,7 +74,7 @@ AboutScreen::AboutScreen(QWidget* parent)
{
QString copyright = m_ui.copyright->text();
copyright.replace("{year}", tr("2021"));
copyright.replace("{year}", QLatin1String("2022"));
m_ui.copyright->setText(copyright);
}
}

View File

@ -17,6 +17,12 @@ Q_OBJECT
public:
typedef std::function<void ()> Function;
typedef std::function<void (bool)> BooleanFunction;
enum class Role {
NO_ROLE = 0,
ABOUT,
SETTINGS,
QUIT,
};
Action(Function, const QString& name, const QString& visibleName, QObject* parent = nullptr);
Action(BooleanFunction, const QString& name, const QString& visibleName, QObject* parent = nullptr);
@ -44,8 +50,10 @@ public:
bool isEnabled() const { return m_enabled; }
bool isActive() const { return m_active; }
bool isExclusive() const { return m_exclusive; }
Role role() const { return m_role; }
void setExclusive(bool exclusive = true) { m_exclusive = exclusive; }
void setRole(Role role) { m_role = role; }
Action& operator=(const Action&);
@ -62,6 +70,7 @@ private:
bool m_enabled = true;
bool m_active = false;
bool m_exclusive = false;
Role m_role = Role::NO_ROLE;
Function m_function;
BooleanFunction m_booleanFunction;

View File

@ -79,6 +79,20 @@ void ActionMapper::rebuildMenu(const QString& menu, QMenu* qmenu, QWidget* conte
} else if (!m_defaultShortcuts[actionName].isEmpty()) {
qaction->setShortcut(m_defaultShortcuts[actionName][0]);
}
switch (action->role()) {
case Action::Role::NO_ROLE:
qaction->setMenuRole(QAction::NoRole);
break;
case Action::Role::SETTINGS:
qaction->setMenuRole(QAction::PreferencesRole);
break;
case Action::Role::ABOUT:
qaction->setMenuRole(QAction::AboutRole);
break;
case Action::Role::QUIT:
qaction->setMenuRole(QAction::QuitRole);
break;
}
QObject::connect(qaction, &QAction::triggered, [qaction, action](bool enabled) {
if (qaction->isCheckable()) {
action->trigger(enabled);
@ -172,4 +186,4 @@ Action* ActionMapper::getAction(const QString& itemName) {
QKeySequence ActionMapper::defaultShortcut(const QString& itemName) {
return m_defaultShortcuts[itemName];
}
}

View File

@ -73,7 +73,9 @@ private:
template<typename T, typename V>
Action* ActionMapper::addAction(const QString& visibleName, const QString& name, T* obj, V (T::*method)(), const QString& menu, const QKeySequence& shortcut) {
return addAction(visibleName, name, std::bind(method, obj), menu, shortcut);
return addAction(visibleName, name, [method, obj]() -> void {
(obj->*method)();
}, menu, shortcut);
}
template<typename T, typename V>

View File

@ -4,6 +4,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
set(PLATFORM_SRC)
set(QT_STATIC OFF)
set(QT_DEFINES)
if(BUILD_SDL)
add_definitions(-DBUILD_SDL)
@ -36,6 +37,11 @@ if(NOT Qt5Widgets_FOUND)
endif()
if(APPLE)
execute_process(COMMAND xcrun --show-sdk-version OUTPUT_VARIABLE MACOSX_SDK)
if(MACOSX_SDK VERSION_GREATER 10.14)
list(APPEND QT_DEFINES USE_SHARE_WIDGET)
endif()
if(Qt5Widgets_VERSION MATCHES "^5.1[0-9]")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8")
else()
@ -186,7 +192,6 @@ if(M_CORE_GB)
list(APPEND PLATFORM_SRC ${GB_SRC})
endif()
set(QT_DEFINES)
if(Qt5Multimedia_FOUND)
list(APPEND AUDIO_SRC
AudioProcessorQt.cpp

View File

@ -15,6 +15,7 @@
#include <QMutexLocker>
#include <mgba/core/serialize.h>
#include <mgba/core/version.h>
#include <mgba/feature/video-logger.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/gba.h>
@ -86,12 +87,17 @@ CoreController::CoreController(mCore* core, QObject* parent)
}
controller->m_resetActions.clear();
controller->m_frameCounter = -1;
if (!controller->m_hwaccel) {
context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer.data()), controller->screenDimensions().width());
}
QString message(tr("Reset r%1-%2 %3").arg(gitRevision).arg(QLatin1String(gitCommitShort)).arg(controller->m_crc32, 8, 16, QLatin1Char('0')));
QMetaObject::invokeMethod(controller, "didReset");
if (controller->m_showResetInfo) {
QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
}
controller->finishFrame();
};
@ -483,6 +489,10 @@ void CoreController::setSync(bool sync) {
}
}
void CoreController::showResetInfo(bool enable) {
m_showResetInfo = enable;
}
void CoreController::setRewinding(bool rewind) {
if (!m_threadContext.core->opts.rewindEnable) {
return;
@ -1053,6 +1063,7 @@ void CoreController::finishFrame() {
mCoreThreadPauseFromThread(&m_threadContext);
}
}
++m_frameCounter;
}
updateKeys();

View File

@ -126,6 +126,7 @@ public:
bool videoSync() const { return m_videoSync; }
void addFrameAction(std::function<void ()> callback);
uint64_t frameCounter() const { return m_frameCounter; }
public slots:
void start();
@ -134,6 +135,7 @@ public slots:
void setPaused(bool paused);
void frameAdvance();
void setSync(bool enable);
void showResetInfo(bool enable);
void setRewinding(bool);
void rewind(int count = 0);
@ -232,6 +234,7 @@ private:
uint32_t m_crc32;
QString m_internalTitle;
QString m_dbTitle;
bool m_showResetInfo = false;
QByteArray m_activeBuffer;
QByteArray m_completeBuffer;
@ -240,6 +243,7 @@ private:
std::unique_ptr<mCacheSet> m_cacheSet;
std::unique_ptr<Override> m_override;
uint64_t m_frameCounter;
QList<std::function<void()>> m_resetActions;
QList<std::function<void()>> m_frameActions;
QMutex m_actionMutex{QMutex::Recursive};

View File

@ -35,10 +35,12 @@ Display* Display::create(QWidget* parent) {
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
format.setVersion(2, 0);
} else {
format.setVersion(3, 2);
format.setVersion(3, 3);
}
format.setProfile(QSurfaceFormat::CoreProfile);
if (!DisplayGL::supportsFormat(format)) {
if (DisplayGL::supportsFormat(format)) {
QSurfaceFormat::setDefaultFormat(format);
} else {
#ifdef BUILD_GL
LOG(QT, WARN) << ("Failed to create an OpenGL Core context, trying old-style...");
format.setVersion(1, 4);
@ -102,14 +104,20 @@ QSize Display::viewportSize() {
}
void Display::attach(std::shared_ptr<CoreController> controller) {
connect(controller.get(), &CoreController::stateLoaded, this, &Display::resizeContext);
connect(controller.get(), &CoreController::stateLoaded, this, &Display::forceDraw);
connect(controller.get(), &CoreController::rewound, this, &Display::forceDraw);
connect(controller.get(), &CoreController::paused, this, &Display::pauseDrawing);
connect(controller.get(), &CoreController::unpaused, this, &Display::unpauseDrawing);
connect(controller.get(), &CoreController::frameAvailable, this, &Display::framePosted);
connect(controller.get(), &CoreController::statusPosted, this, &Display::showMessage);
connect(controller.get(), &CoreController::didReset, this, &Display::resizeContext);
CoreController* controllerP = controller.get();
connect(controllerP, &CoreController::stateLoaded, this, &Display::resizeContext);
connect(controllerP, &CoreController::stateLoaded, this, &Display::forceDraw);
connect(controllerP, &CoreController::rewound, this, &Display::forceDraw);
connect(controllerP, &CoreController::paused, this, &Display::pauseDrawing);
connect(controllerP, &CoreController::unpaused, this, &Display::unpauseDrawing);
connect(controllerP, &CoreController::frameAvailable, this, &Display::framePosted);
connect(controllerP, &CoreController::frameAvailable, this, [controllerP, this]() {
if (m_showFrameCounter) {
m_messagePainter.showFrameCounter(controllerP->frameCounter());
}
});
connect(controllerP, &CoreController::statusPosted, this, &Display::showMessage);
connect(controllerP, &CoreController::didReset, this, &Display::resizeContext);
}
void Display::configure(ConfigController* config) {
@ -119,6 +127,7 @@ void Display::configure(ConfigController* config) {
interframeBlending(opts->interframeBlending);
filter(opts->resampleVideo);
config->updateOption("showOSD");
config->updateOption("showFrameCounter");
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (opts->shader) {
struct VDir* shader = VDirOpen(opts->shader);
@ -131,12 +140,15 @@ void Display::configure(ConfigController* config) {
}
void Display::resizeEvent(QResizeEvent*) {
m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
m_messagePainter.resize(size(), devicePixelRatioF());
#else
m_messagePainter.resize(size(), devicePixelRatio());
#endif
}
void Display::lockAspectRatio(bool lock) {
m_lockAspectRatio = lock;
m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio());
}
void Display::lockIntegerScaling(bool lock) {
@ -151,6 +163,13 @@ void Display::showOSDMessages(bool enable) {
m_showOSD = enable;
}
void Display::showFrameCounter(bool enable) {
m_showFrameCounter = enable;
if (!enable) {
m_messagePainter.clearFrameCounter();
}
}
void Display::filter(bool filter) {
m_filter = filter;
}

View File

@ -42,6 +42,7 @@ public:
bool hasInterframeBlending() const { return m_interframeBlending; }
bool isFiltered() const { return m_filter; }
bool isShowOSD() const { return m_showOSD; }
bool isShowFrameCounter() const { return m_showFrameCounter; }
virtual void attach(std::shared_ptr<CoreController>);
virtual void configure(ConfigController*);
@ -71,6 +72,7 @@ public slots:
virtual void lockIntegerScaling(bool lock);
virtual void interframeBlending(bool enable);
virtual void showOSDMessages(bool enable);
virtual void showFrameCounter(bool enable);
virtual void filter(bool filter);
virtual void framePosted() = 0;
virtual void setShaders(struct VDir*) = 0;
@ -93,6 +95,7 @@ private:
MessagePainter m_messagePainter;
bool m_showOSD = true;
bool m_showFrameCounter = false;
bool m_lockAspectRatio = false;
bool m_lockIntegerScaling = false;
bool m_interframeBlending = false;

View File

@ -9,14 +9,14 @@
#include <QApplication>
#include <QMutexLocker>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLFunctions_3_2_Core>
#include <QOpenGLPaintDevice>
#include <QResizeEvent>
#include <QScreen>
#include <QTimer>
#include <QWindow>
#include <QVBoxLayout>
#include <cmath>
@ -32,6 +32,12 @@
#endif
#endif
#ifdef _WIN32
#define OVERHEAD_NSEC 1000000
#else
#define OVERHEAD_NSEC 300000
#endif
using namespace QGBA;
QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
@ -43,13 +49,96 @@ uint qHash(const QSurfaceFormat& format, uint seed) {
return qHash(representation, seed);
}
void mGLWidget::initializeGL() {
m_vao.create();
m_program.create();
m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, R"(#version 150 core
in vec4 position;
out vec2 texCoord;
void main() {
gl_Position = position;
texCoord = (position.st + 1.0) * 0.5;
})");
m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, R"(#version 150 core
in vec2 texCoord;
out vec4 color;
uniform sampler2D tex;
void main() {
color = vec4(texture(tex, texCoord).rgb, 1.0);
})");
m_program.link();
m_program.setUniformValue("tex", 0);
m_positionLocation = m_program.attributeLocation("position");
connect(&m_refresh, &QTimer::timeout, this, static_cast<void (QWidget::*)()>(&QWidget::update));
}
void mGLWidget::finalizeVAO() {
QOpenGLFunctions_3_2_Core* fn = context()->versionFunctions<QOpenGLFunctions_3_2_Core>();
fn->glGetError(); // Clear the error
m_vao.bind();
fn->glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
fn->glEnableVertexAttribArray(m_positionLocation);
fn->glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
m_vao.release();
if (fn->glGetError() == GL_NO_ERROR) {
m_vaoDone = true;
}
}
void mGLWidget::paintGL() {
if (!m_vaoDone) {
finalizeVAO();
}
QOpenGLFunctions_3_2_Core* fn = context()->versionFunctions<QOpenGLFunctions_3_2_Core>();
m_program.bind();
m_vao.bind();
fn->glBindTexture(GL_TEXTURE_2D, m_tex);
fn->glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
fn->glBindTexture(GL_TEXTURE_2D, 0);
m_vao.release();
m_program.release();
// TODO: Better timing
++m_refreshResidue;
if (m_refreshResidue == 3) {
m_refresh.start(16);
m_refreshResidue = 0;
} else {
m_refresh.start(17);
}
}
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
: Display(parent)
{
setAttribute(Qt::WA_NativeWindow);
window()->windowHandle()->setFormat(format);
windowHandle()->create();
m_painter = std::make_unique<PainterGL>(windowHandle(), format);
#ifdef USE_SHARE_WIDGET
bool useShareWidget = true;
#else
// TODO: Does using this on Wayland help?
bool useShareWidget = false;
#endif
if (useShareWidget) {
m_gl = new mGLWidget;
m_gl->setAttribute(Qt::WA_NativeWindow);
m_gl->setFormat(format);
QBoxLayout* layout = new QVBoxLayout;
layout->addWidget(m_gl);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
} else {
m_gl = nullptr;
}
m_painter = std::make_unique<PainterGL>(windowHandle(), m_gl, format);
m_drawThread.setObjectName("Painter Thread");
m_painter->setThread(&m_drawThread);
@ -98,16 +187,20 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
lockIntegerScaling(isIntegerScalingLocked());
interframeBlending(hasInterframeBlending());
showOSDMessages(isShowOSD());
showFrameCounter(isShowFrameCounter());
filter(isFiltered());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
messagePainter()->resize(size(), devicePixelRatioF());
#else
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
messagePainter()->resize(size(), devicePixelRatio());
#endif
CoreController::Interrupter interrupter(controller);
QMetaObject::invokeMethod(m_painter.get(), "start");
setUpdatesEnabled(false);
if (!m_gl) {
setUpdatesEnabled(false);
}
}
bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
@ -189,7 +282,9 @@ void DisplayGL::unpauseDrawing() {
m_isDrawing = true;
QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
#ifndef Q_OS_MAC
setUpdatesEnabled(false);
if (!m_gl) {
setUpdatesEnabled(false);
}
#endif
}
}
@ -220,6 +315,11 @@ void DisplayGL::showOSDMessages(bool enable) {
QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
}
void DisplayGL::showFrameCounter(bool enable) {
Display::showFrameCounter(enable);
QMetaObject::invokeMethod(m_painter.get(), "showFrameCounter", Q_ARG(bool, enable));
}
void DisplayGL::filter(bool filter) {
Display::filter(filter);
QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
@ -274,10 +374,21 @@ int DisplayGL::framebufferHandle() {
return m_painter->glTex();
}
PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
: m_surface(surface)
PainterGL::PainterGL(QWindow* window, mGLWidget* widget, const QSurfaceFormat& format)
: m_window(window)
, m_format(format)
, m_widget(widget)
{
if (widget) {
m_format = widget->format();
QOffscreenSurface* surface = new QOffscreenSurface;
surface->setScreen(window->screen());
surface->setFormat(m_format);
surface->create();
m_surface = surface;
} else {
m_surface = m_window;
}
m_supportsShaders = m_format.version() >= qMakePair(2, 0);
for (auto& buf : m_buffers) {
m_free.append(&buf.front());
@ -307,6 +418,9 @@ void PainterGL::makeCurrent() {
void PainterGL::create() {
m_gl = std::make_unique<QOpenGLContext>();
m_gl->setFormat(m_format);
if (m_widget) {
m_gl->setShareContext(m_widget->context());
}
m_gl->create();
makeCurrent();
@ -317,7 +431,7 @@ void PainterGL::create() {
mGLES2Context* gl2Backend;
#endif
m_window = std::make_unique<QOpenGLPaintDevice>();
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
auto version = m_format.version();
@ -342,11 +456,36 @@ void PainterGL::create() {
}
painter->m_gl->swapBuffers(painter->m_surface);
painter->makeCurrent();
if (painter->m_widget && painter->supportsShaders()) {
QOpenGLFunctions_3_2_Core* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_3_2_Core>();
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];
mGLES2ContextUseFramebuffer(gl2Backend);
}
};
m_backend->init(m_backend, 0);
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (m_supportsShaders) {
if (m_widget) {
m_widget->setVBO(gl2Backend->vbo);
gl2Backend->finalShader.tex = 0;
mGLES2ContextUseFramebuffer(gl2Backend);
m_finalTex[0] = gl2Backend->finalShader.tex;
gl2Backend->finalShader.tex = 0;
mGLES2ContextUseFramebuffer(gl2Backend);
m_finalTex[1] = gl2Backend->finalShader.tex;
m_finalTexIdx = 0;
gl2Backend->finalShader.tex = m_finalTex[m_finalTexIdx];
m_widget->setTex(m_finalTex[m_finalTexIdx]);
}
m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
}
#endif
@ -369,7 +508,7 @@ void PainterGL::destroy() {
#endif
m_backend->deinit(m_backend);
m_gl->doneCurrent();
m_window.reset();
m_paintDev.reset();
m_gl.reset();
free(m_backend);
@ -404,8 +543,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
}
void PainterGL::resize(const QSize& size) {
qreal r = m_window->devicePixelRatio();
m_size = size;
m_window->setSize(m_size);
m_paintDev->setSize(m_size * r);
m_paintDev->setDevicePixelRatio(r);
if (m_started && !m_active) {
forceDraw();
}
@ -429,6 +570,10 @@ void PainterGL::showOSD(bool enable) {
m_showOSD = enable;
}
void PainterGL::showFrameCounter(bool enable) {
m_showFrameCounter = enable;
}
void PainterGL::filter(bool filter) {
m_backend->filter = filter;
if (m_started && !m_active) {
@ -462,7 +607,7 @@ void PainterGL::draw() {
if (!sync->audioWait && !sync->videoFrameWait) {
return;
}
if (m_delayTimer.elapsed() >= 1000 / m_surface->screen()->refreshRate()) {
if (m_delayTimer.elapsed() >= 1000 / m_window->screen()->refreshRate()) {
return;
}
if (!m_drawTimer.isActive()) {
@ -476,13 +621,13 @@ void PainterGL::draw() {
m_delayTimer.start();
} else {
if (sync->audioWait || sync->videoFrameWait) {
while (m_delayTimer.nsecsElapsed() + 1000000 < 1000000000 / sync->fpsTarget) {
while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) {
QThread::usleep(500);
}
forceRedraw = sync->videoFrameWait;
}
if (!forceRedraw) {
forceRedraw = m_delayTimer.nsecsElapsed() + 1000000 >= 1000000000 / m_surface->screen()->refreshRate();
forceRedraw = m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC >= 1000000000 / m_window->screen()->refreshRate();
}
}
mCoreSyncWaitFrameEnd(sync);
@ -497,7 +642,7 @@ void PainterGL::draw() {
void PainterGL::forceDraw() {
performDraw();
if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
if (m_delayTimer.elapsed() < 1000 / m_window->screen()->refreshRate()) {
return;
}
m_delayTimer.restart();
@ -522,7 +667,7 @@ void PainterGL::stop() {
}
if (m_videoProxy) {
m_videoProxy->reset();
m_videoProxy->moveToThread(m_surface->thread());
m_videoProxy->moveToThread(m_window->thread());
m_videoProxy.reset();
}
m_backend->clear(m_backend);
@ -540,14 +685,14 @@ void PainterGL::unpause() {
}
void PainterGL::performDraw() {
float r = m_surface->devicePixelRatio();
float r = m_window->devicePixelRatio();
m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
if (m_buffer) {
m_backend->postFrame(m_backend, m_buffer);
}
m_backend->drawFrame(m_backend);
if (m_showOSD && m_messagePainter) {
m_painter.begin(m_window.get());
m_painter.begin(m_paintDev.get());
m_messagePainter->paint(&m_painter);
m_painter.end();
}

View File

@ -21,7 +21,11 @@
#include <QHash>
#include <QList>
#include <QMouseEvent>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLWidget>
#include <QPainter>
#include <QQueue>
#include <QThread>
@ -35,11 +39,37 @@
#include "platform/video-backend.h"
class QOpenGLPaintDevice;
class QOpenGLWidget;
uint qHash(const QSurfaceFormat&, uint seed = 0);
namespace QGBA {
class mGLWidget : public QOpenGLWidget {
Q_OBJECT
public:
void setTex(GLuint tex) { m_tex = tex; }
void setVBO(GLuint vbo) { m_vbo = vbo; }
void finalizeVAO();
protected:
void initializeGL() override;
void paintGL() override;
private:
GLuint m_tex;
GLuint m_vbo;
bool m_vaoDone = false;
QOpenGLVertexArrayObject m_vao;
QOpenGLShaderProgram m_program;
GLuint m_positionLocation;
QTimer m_refresh;
int m_refreshResidue = 0;
};
class PainterGL;
class DisplayGL : public Display {
Q_OBJECT
@ -66,6 +96,7 @@ public slots:
void lockIntegerScaling(bool lock) override;
void interframeBlending(bool enable) override;
void showOSDMessages(bool enable) override;
void showFrameCounter(bool enable) override;
void filter(bool filter) override;
void framePosted() override;
void setShaders(struct VDir*) override;
@ -87,13 +118,14 @@ private:
std::unique_ptr<PainterGL> m_painter;
QThread m_drawThread;
std::shared_ptr<CoreController> m_context;
mGLWidget* m_gl;
};
class PainterGL : public QObject {
Q_OBJECT
public:
PainterGL(QWindow* surface, const QSurfaceFormat& format);
PainterGL(QWindow* surface, mGLWidget* widget, const QSurfaceFormat& format);
~PainterGL();
void setThread(QThread*);
@ -122,6 +154,7 @@ public slots:
void lockIntegerScaling(bool lock);
void interframeBlending(bool enable);
void showOSD(bool enable);
void showFrameCounter(bool enable);
void filter(bool filter);
void resizeContext();
@ -144,10 +177,14 @@ private:
uint32_t* m_buffer = nullptr;
QPainter m_painter;
QMutex m_mutex;
QWindow* m_surface;
QWindow* m_window;
QSurface* m_surface;
QSurfaceFormat m_format;
std::unique_ptr<QOpenGLPaintDevice> m_window;
std::unique_ptr<QOpenGLPaintDevice> m_paintDev;
std::unique_ptr<QOpenGLContext> m_gl;
int m_finalTexIdx = 0;
GLuint m_finalTex[2];
mGLWidget* m_widget;
bool m_active = false;
bool m_started = false;
QTimer m_drawTimer;
@ -155,6 +192,7 @@ private:
CoreController::Interrupter m_interrupter;
bool m_supportsShaders;
bool m_showOSD;
bool m_showFrameCounter;
VideoShader m_shader{};
VideoBackend* m_backend = nullptr;
QSize m_size;

View File

@ -107,7 +107,7 @@ void DisplayQt::paintEvent(QPaintEvent*) {
}
painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height));
painter.setOpacity(1);
if (isShowOSD()) {
if (isShowOSD() || isShowFrameCounter()) {
messagePainter()->paint(&painter);
}
}

View File

@ -18,6 +18,8 @@ MessagePainter::MessagePainter(QObject* parent)
{
m_messageFont = GBAApp::app()->monospaceFont();
m_messageFont.setPixelSize(13);
m_frameFont = GBAApp::app()->monospaceFont();
m_frameFont.setPixelSize(10);
connect(&m_messageTimer, &QTimer::timeout, this, &MessagePainter::clearMessage);
m_messageTimer.setSingleShot(true);
m_messageTimer.setInterval(5000);
@ -25,40 +27,35 @@ MessagePainter::MessagePainter(QObject* parent)
clearMessage();
}
void MessagePainter::resize(const QSize& size, bool lockAspectRatio, qreal scaleFactor) {
int w = size.width();
int h = size.height();
int drawW = w;
int drawH = h;
if (lockAspectRatio) {
if (w * 2 > h * 3) {
drawW = h * 3 / 2;
} else if (w * 2 < h * 3) {
drawH = w * 2 / 3;
}
}
m_world.reset();
m_world.scale(qreal(drawW) / GBA_VIDEO_HORIZONTAL_PIXELS, qreal(drawH) / GBA_VIDEO_VERTICAL_PIXELS);
void MessagePainter::resize(const QSize& size, qreal scaleFactor) {
double drawW = size.width();
double drawH = size.height();
double area = pow(drawW * drawW * drawW * drawH * drawH, 0.2);
m_scaleFactor = scaleFactor;
m_local = QPoint(1, GBA_VIDEO_VERTICAL_PIXELS - m_messageFont.pixelSize() - 1);
m_local = m_world.map(m_local);
m_local += QPoint((w - drawW) / 2, (h - drawH) / 2);
m_pixmapBuffer = QPixmap(drawW * m_scaleFactor,
(m_messageFont.pixelSize() + 2) * m_world.m22() * m_scaleFactor);
m_pixmapBuffer.setDevicePixelRatio(m_scaleFactor);
m_world.reset();
m_world.scale(area / 220., area / 220.);
m_local = QPoint(area / 100., drawH - m_messageFont.pixelSize() * m_world.m22() * 1.3);
QFontMetrics metrics(m_frameFont);
m_framePoint = QPoint(drawW / m_world.m11() - metrics.height() * 0.1, metrics.height() * 0.75);
m_mutex.lock();
m_message.prepare(m_world, m_messageFont);
redraw();
m_mutex.unlock();
}
void MessagePainter::redraw() {
m_pixmapBuffer.fill(Qt::transparent);
if (m_message.text().isEmpty()) {
m_pixmapBuffer.fill(Qt::transparent);
m_pixmap = m_pixmapBuffer;
m_pixmap.setDevicePixelRatio(m_scaleFactor);
return;
}
m_message.prepare(m_world, m_messageFont);
QSizeF sizef = m_message.size() * m_scaleFactor;
m_pixmapBuffer = QPixmap(sizef.width() * m_world.m11(), sizef.height() * m_world.m22());
m_pixmapBuffer.setDevicePixelRatio(m_scaleFactor);
m_pixmapBuffer.fill(Qt::transparent);
QPainter painter(&m_pixmapBuffer);
painter.setWorldTransform(m_world);
painter.setRenderHint(QPainter::Antialiasing);
@ -74,16 +71,32 @@ void MessagePainter::redraw() {
painter.setPen(Qt::white);
painter.drawStaticText(0, 0, m_message);
m_pixmap = m_pixmapBuffer;
m_pixmap.setDevicePixelRatio(m_scaleFactor);
}
void MessagePainter::paint(QPainter* painter) {
if (!m_message.text().isEmpty()) {
painter->drawPixmap(m_local, m_pixmap);
}
if (m_drawFrameCounter) {
QString frame(tr("Frame %1").arg(m_frameCounter));
QFontMetrics metrics(m_frameFont);
painter->setWorldTransform(m_world);
painter->setRenderHint(QPainter::Antialiasing);
painter->setFont(m_frameFont);
painter->setPen(Qt::black);
painter->translate(-metrics.width(frame), 0);
const static int ITERATIONS = 11;
for (int i = 0; i < ITERATIONS; ++i) {
painter->save();
painter->translate(cos(i * 2.0 * M_PI / ITERATIONS) * 0.8, sin(i * 2.0 * M_PI / ITERATIONS) * 0.8);
painter->drawText(m_framePoint, frame);
painter->restore();
}
painter->setPen(Qt::white);
painter->drawText(m_framePoint, frame);
}
}
void MessagePainter::showMessage(const QString& message) {
m_mutex.lock();
m_message.setText(message);
@ -100,3 +113,16 @@ void MessagePainter::clearMessage() {
m_mutex.unlock();
m_messageTimer.stop();
}
void MessagePainter::showFrameCounter(uint64_t frameCounter) {
m_mutex.lock();
m_frameCounter = frameCounter;
m_drawFrameCounter = true;
m_mutex.unlock();
}
void MessagePainter::clearFrameCounter() {
m_mutex.lock();
m_drawFrameCounter = false;
m_mutex.unlock();
}

View File

@ -19,7 +19,7 @@ Q_OBJECT
public:
MessagePainter(QObject* parent = nullptr);
void resize(const QSize& size, bool lockAspectRatio, qreal scaleFactor);
void resize(const QSize& size, qreal scaleFactor);
void paint(QPainter* painter);
void setScaleFactor(qreal factor);
@ -27,18 +27,28 @@ public slots:
void showMessage(const QString& message);
void clearMessage();
void showFrameCounter(uint64_t);
void clearFrameCounter();
private:
void redraw();
QMutex m_mutex;
QStaticText m_message;
qreal m_scaleFactor = 1;
uint64_t m_frameCounter;
bool m_drawFrameCounter = false;
QPoint m_local;
QPixmap m_pixmap;
QPixmap m_pixmapBuffer;
QPointF m_framePoint = QPointF(0, 0);
QFont m_frameFont;
QTimer m_messageTimer{this};
QPoint m_local;
QTransform m_world;
QFont m_messageFont;
qreal m_scaleFactor = 1;
};
}

View File

@ -452,6 +452,8 @@ void SettingsView::updateConfig() {
saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
saveSetting("interframeBlending", m_ui.interframeBlending);
saveSetting("showOSD", m_ui.showOSD);
saveSetting("showFrameCounter", m_ui.showFrameCounter);
saveSetting("showResetInfo", m_ui.showResetInfo);
saveSetting("volume", m_ui.volume);
saveSetting("mute", m_ui.mute);
saveSetting("fastForwardVolume", m_ui.volumeFf);
@ -675,6 +677,8 @@ void SettingsView::reloadConfig() {
loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
loadSetting("interframeBlending", m_ui.interframeBlending);
loadSetting("showOSD", m_ui.showOSD, true);
loadSetting("showFrameCounter", m_ui.showFrameCounter);
loadSetting("showResetInfo", m_ui.showResetInfo);
loadSetting("volume", m_ui.volume, 0x100);
loadSetting("mute", m_ui.mute, false);
loadSetting("fastForwardVolume", m_ui.volumeFf, m_ui.volume->value());

View File

@ -594,6 +594,13 @@
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_41">
<property name="text">
<string>When inactive:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_21">
<item>
@ -612,6 +619,31 @@
</item>
</layout>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_42">
<property name="text">
<string>When minimized:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_24">
<item>
<widget class="QCheckBox" name="pauseOnMinimize">
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="muteOnMinimize">
<property name="text">
<string>Mute</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="10" column="0" colspan="2">
<widget class="Line" name="line_17">
<property name="orientation">
@ -667,20 +699,41 @@
</widget>
</item>
<item row="16" column="1">
<layout class="QVBoxLayout" name="osdDisplay">
<property name="leftMargin">
<number>20</number>
</property>
<item>
<widget class="QCheckBox" name="showFrameCounter">
<property name="text">
<string>Show frame count in OSD</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showResetInfo">
<property name="text">
<string>Show emulation info on reset</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="17" column="1">
<widget class="QCheckBox" name="useDiscordPresence">
<property name="text">
<string>Enable Discord Rich Presence</string>
</property>
</widget>
</item>
<item row="17" column="0" colspan="2">
<item row="18" column="0" colspan="2">
<widget class="Line" name="line_13">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="18" column="1">
<item row="19" column="1">
<widget class="QCheckBox" name="autosave">
<property name="text">
<string>Automatically save state</string>
@ -690,7 +743,7 @@
</property>
</widget>
</item>
<item row="19" column="1">
<item row="20" column="1">
<widget class="QCheckBox" name="autoload">
<property name="text">
<string>Automatically load state</string>
@ -700,14 +753,14 @@
</property>
</widget>
</item>
<item row="20" column="0" colspan="2">
<item row="21" column="0" colspan="2">
<widget class="Line" name="line_16">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="21" column="1">
<item row="22" column="1">
<widget class="QCheckBox" name="cheatAutosave">
<property name="text">
<string>Automatically save cheats</string>
@ -717,7 +770,7 @@
</property>
</widget>
</item>
<item row="22" column="1">
<item row="23" column="1">
<widget class="QCheckBox" name="cheatAutoload">
<property name="text">
<string>Automatically load cheats</string>
@ -727,38 +780,6 @@
</property>
</widget>
</item>
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_24">
<item>
<widget class="QCheckBox" name="pauseOnMinimize">
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="muteOnMinimize">
<property name="text">
<string>Mute</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_41">
<property name="text">
<string>When inactive:</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_42">
<property name="text">
<string>When minimized:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="update">
@ -2394,6 +2415,38 @@
</hint>
</hints>
</connection>
<connection>
<sender>showOSD</sender>
<signal>toggled(bool)</signal>
<receiver>showFrameCounter</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>374</x>
<y>391</y>
</hint>
<hint type="destinationlabel">
<x>418</x>
<y>431</y>
</hint>
</hints>
</connection>
<connection>
<sender>showOSD</sender>
<signal>toggled(bool)</signal>
<receiver>showResetInfo</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>374</x>
<y>391</y>
</hint>
<hint type="destinationlabel">
<x>418</x>
<y>470</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="gbColors"/>

View File

@ -771,7 +771,9 @@ void Window::focusInEvent(QFocusEvent*) {
updateMultiplayerActive(true);
}
}
m_display->forceDraw();
if (m_display) {
m_display->forceDraw();
}
}
void Window::focusOutEvent(QFocusEvent*) {
@ -899,7 +901,6 @@ void Window::gameStarted() {
m_config->updateOption("lockAspectRatio");
m_config->updateOption("interframeBlending");
m_config->updateOption("resampleVideo");
m_config->updateOption("showOSD");
if (m_savedScale > 0) {
resizeFrame(size * m_savedScale);
}
@ -1262,12 +1263,25 @@ void Window::setupMenu(QMenuBar* menubar) {
m_actions.addAction(tr("Add folder to library..."), "addDirToLibrary", this, &Window::addDirToLibrary, "file");
#endif
m_actions.addMenu(tr("Save games"), "saves", "file");
addGameAction(tr("Load alternate save game..."), "loadAlternateSave", [this]() {
this->selectSave(false);
}, "file");
}, "saves");
addGameAction(tr("Load temporary save game..."), "loadTemporarySave", [this]() {
this->selectSave(true);
}, "file");
}, "saves");
m_actions.addSeparator("saves");
m_actions.addAction(tr("Convert save game..."), "convertSave", openControllerTView<SaveConverter>(), "saves");
#ifdef M_CORE_GBA
Action* importShark = addGameAction(tr("Import GameShark Save..."), "importShark", this, &Window::importSharkport, "saves");
m_platformActions.insert(mPLATFORM_GBA, importShark);
Action* exportShark = addGameAction(tr("Export GameShark Save..."), "exportShark", this, &Window::exportSharkport, "saves");
m_platformActions.insert(mPLATFORM_GBA, exportShark);
#endif
m_actions.addAction(tr("Load &patch..."), "loadPatch", this, &Window::selectPatch, "file");
@ -1367,17 +1381,6 @@ void Window::setupMenu(QMenuBar* menubar) {
m_platformActions.insert(mPLATFORM_GB, quickSave);
}
#ifdef M_CORE_GBA
m_actions.addSeparator("file");
m_actions.addAction(tr("Convert save game..."), "convertSave", openControllerTView<SaveConverter>(), "file");
Action* importShark = addGameAction(tr("Import GameShark Save..."), "importShark", this, &Window::importSharkport, "file");
m_platformActions.insert(mPLATFORM_GBA, importShark);
Action* exportShark = addGameAction(tr("Export GameShark Save..."), "exportShark", this, &Window::exportSharkport, "file");
m_platformActions.insert(mPLATFORM_GBA, exportShark);
#endif
m_actions.addSeparator("file");
m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", [this]() {
GBAApp::app()->newWindow();
@ -1396,11 +1399,8 @@ void Window::setupMenu(QMenuBar* menubar) {
m_actions.addSeparator("file");
#endif
m_actions.addAction(tr("About..."), "about", openTView<AboutScreen>(), "file");
#ifndef Q_OS_MAC
m_actions.addAction(tr("E&xit"), "quit", static_cast<QWidget*>(this), &QWidget::close, "file", QKeySequence::Quit);
#endif
m_actions.addAction(tr("About..."), "about", openTView<AboutScreen>(), "file")->setRole(Action::Role::ABOUT);
m_actions.addAction(tr("E&xit"), "quit", static_cast<QWidget*>(this), &QWidget::close, "file", QKeySequence::Quit)->setRole(Action::Role::SETTINGS);
m_actions.addMenu(tr("&Emulation"), "emu");
@ -1671,7 +1671,7 @@ void Window::setupMenu(QMenuBar* menubar) {
addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView<CheatsView>(), "tools");
m_actions.addSeparator("tools");
m_actions.addAction(tr("Settings..."), "settings", this, &Window::openSettingsWindow, "tools");
m_actions.addAction(tr("Settings..."), "settings", this, &Window::openSettingsWindow, "tools")->setRole(Action::Role::SETTINGS);
m_actions.addAction(tr("Make portable"), "makePortable", this, &Window::tryMakePortable, "tools");
#ifdef USE_DEBUGGERS
@ -1799,6 +1799,20 @@ void Window::setupMenu(QMenuBar* menubar) {
}
}, this);
ConfigOption* showFrameCounter = m_config->addOption("showFrameCounter");
showFrameCounter->connect([this](const QVariant& value) {
if (m_display) {
m_display->showFrameCounter(value.toBool());
}
}, this);
ConfigOption* showResetInfo = m_config->addOption("showResetInfo");
showResetInfo->connect([this](const QVariant& value) {
if (m_controller) {
m_controller->showResetInfo(value.toBool());
}
}, this);
ConfigOption* videoScale = m_config->addOption("videoScale");
videoScale->connect([this](const QVariant& value) {
if (m_display) {
@ -2045,6 +2059,9 @@ void Window::setController(CoreController* controller, const QString& fname) {
attachDisplay();
m_controller->loadConfig(m_config);
m_config->updateOption("showOSD");
m_config->updateOption("showFrameCounter");
m_config->updateOption("showResetInfo");
m_controller->start();
if (!m_pendingState.isEmpty()) {

View File

@ -46,3 +46,10 @@ if(BUILD_CINEMA)
add_test(cinema ${BINARY_NAME}-cinema -v)
install(TARGETS ${BINARY_NAME}-cinema DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-test)
endif()
if(BUILD_ROM_TEST)
add_executable(${BINARY_NAME}-rom-test ${CMAKE_CURRENT_SOURCE_DIR}/rom-test-main.c)
target_link_libraries(${BINARY_NAME}-rom-test ${BINARY_NAME})
target_compile_definitions(${BINARY_NAME}-rom-test PRIVATE "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
install(TARGETS ${BINARY_NAME}-rom-test DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-test)
endif()

View File

@ -0,0 +1,220 @@
/* Copyright (c) 2013-2022 Jeffrey Pfau
* Copyright (c) 2022 Felix Jones
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/core/cheats.h>
#include <mgba/core/config.h>
#include <mgba/core/core.h>
#include <mgba/core/serialize.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/feature/commandline.h>
#include <mgba-util/vfs.h>
#include <signal.h>
#define ROM_TEST_OPTIONS "S:R:"
#define ROM_TEST_USAGE \
"\nAdditional options:\n" \
" -S SWI Run until specified SWI call before exiting\n" \
" -R REGISTER General purpose register to return as exit code\n" \
struct RomTestOpts {
int exitSwiImmediate;
unsigned int returnCodeRegister;
};
static void _romTestShutdown(int signal);
static bool _parseRomTestOpts(struct mSubParser* parser, int option, const char* arg);
static bool _parseSwi(const char* regStr, int* oSwi);
static bool _parseNamedRegister(const char* regStr, unsigned int* oRegister);
static bool _dispatchExiting = false;
static int _exitCode = 0;
#ifdef M_CORE_GBA
static void _romTestSwi3Callback(void* context);
static void _romTestSwi16(struct ARMCore* cpu, int immediate);
static void _romTestSwi32(struct ARMCore* cpu, int immediate);
static int _exitSwiImmediate;
static unsigned int _returnCodeRegister;
void (*_armSwi16)(struct ARMCore* cpu, int immediate);
void (*_armSwi32)(struct ARMCore* cpu, int immediate);
#endif
int main(int argc, char * argv[]) {
signal(SIGINT, _romTestShutdown);
struct RomTestOpts romTestOpts = { 3, 0 };
struct mSubParser subparser = {
.usage = ROM_TEST_USAGE,
.parse = _parseRomTestOpts,
.extraOptions = ROM_TEST_OPTIONS,
.opts = &romTestOpts
};
struct mArguments args;
bool parsed = parseArguments(&args, argc, argv, &subparser);
if (!args.fname) {
parsed = false;
}
if (!parsed || args.showHelp) {
usage(argv[0], ROM_TEST_USAGE);
return !parsed;
}
if (args.showVersion) {
version(argv[0]);
return 0;
}
struct mCore* core = mCoreFind(args.fname);
if (!core) {
return 1;
}
core->init(core);
mCoreInitConfig(core, "romTest");
applyArguments(&args, NULL, &core->config);
mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "remove");
#ifdef M_CORE_GBA
if (core->platform(core) == mPLATFORM_GBA) {
((struct GBA*) core->board)->hardCrash = false;
_exitSwiImmediate = romTestOpts.exitSwiImmediate;
_returnCodeRegister = romTestOpts.returnCodeRegister;
if (_exitSwiImmediate == 3) {
// Hook into SWI 3 (shutdown)
struct mCoreCallbacks callbacks = {0};
callbacks.context = core;
callbacks.shutdown = _romTestSwi3Callback;
core->addCoreCallbacks(core, &callbacks);
} else {
// Custom SWI hooks
_armSwi16 = ((struct GBA*) core->board)->cpu->irqh.swi16;
((struct GBA*) core->board)->cpu->irqh.swi16 = _romTestSwi16;
_armSwi32 = ((struct GBA*) core->board)->cpu->irqh.swi32;
((struct GBA*) core->board)->cpu->irqh.swi32 = _romTestSwi32;
}
}
#endif
bool cleanExit = true;
if (!mCoreLoadFile(core, args.fname)) {
cleanExit = false;
goto loadError;
}
if (args.patch) {
core->loadPatch(core, VFileOpen(args.patch, O_RDONLY));
}
struct VFile* savestate = NULL;
if (args.savestate) {
savestate = VFileOpen(args.savestate, O_RDONLY);
}
core->reset(core);
struct mCheatDevice* device;
if (args.cheatsFile && (device = core->cheatDevice(core))) {
struct VFile* vf = VFileOpen(args.cheatsFile, O_RDONLY);
if (vf) {
mCheatDeviceClear(device);
mCheatParseFile(device, vf);
vf->close(vf);
}
}
if (savestate) {
mCoreLoadStateNamed(core, savestate, 0);
savestate->close(savestate);
}
do {
core->runLoop(core);
} while (!_dispatchExiting);
core->unloadROM(core);
loadError:
freeArguments(&args);
mCoreConfigDeinit(&core->config);
core->deinit(core);
return cleanExit ? _exitCode : 1;
}
static void _romTestShutdown(int signal) {
UNUSED(signal);
_dispatchExiting = true;
}
#ifdef M_CORE_GBA
static void _romTestSwi3Callback(void* context) {
struct mCore* core = context;
_exitCode = ((struct GBA*) core->board)->cpu->regs.gprs[_returnCodeRegister];
_dispatchExiting = true;
}
static void _romTestSwi16(struct ARMCore* cpu, int immediate) {
if (immediate == _exitSwiImmediate) {
_exitCode = cpu->regs.gprs[_returnCodeRegister];
_dispatchExiting = true;
return;
}
_armSwi16(cpu, immediate);
}
static void _romTestSwi32(struct ARMCore* cpu, int immediate) {
if (immediate == _exitSwiImmediate) {
_exitCode = cpu->regs.gprs[_returnCodeRegister];
_dispatchExiting = true;
return;
}
_armSwi32(cpu, immediate);
}
#endif
static bool _parseRomTestOpts(struct mSubParser* parser, int option, const char* arg) {
struct RomTestOpts* opts = parser->opts;
errno = 0;
switch (option) {
case 'S':
return _parseSwi(arg, &opts->exitSwiImmediate);
case 'R':
return _parseNamedRegister(arg, &opts->returnCodeRegister);
default:
return false;
}
}
static bool _parseSwi(const char* swiStr, int* oSwi) {
char* parseEnd;
long swi = strtol(swiStr, &parseEnd, 0);
if (errno || swi > UINT8_MAX || *parseEnd) {
return false;
}
*oSwi = swi;
return true;
}
static bool _parseNamedRegister(const char* regStr, unsigned int* oRegister) {
if (regStr[0] == 'r' || regStr[0] == 'R') {
++regStr;
}
char* parseEnd;
unsigned long regId = strtoul(regStr, &parseEnd, 10);
if (errno || regId > 15 || *parseEnd) {
return false;
}
*oRegister = regId;
return true;
}