mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
8337786b64
8
CHANGES
8
CHANGES
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -102,7 +102,7 @@ struct GB {
|
|||
struct VFile* sramRealVf;
|
||||
uint32_t sramSize;
|
||||
int sramDirty;
|
||||
int32_t sramDirtAge;
|
||||
uint32_t sramDirtAge;
|
||||
bool sramMaskWriteback;
|
||||
|
||||
int sgbBit;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(¶ms[index], 1);
|
||||
}
|
||||
|
||||
++index;
|
||||
unsigned int paramsLength = strlen(params);
|
||||
for (; index + 3 < paramsLength; ++index) {
|
||||
length <<= 4;
|
||||
length |= _hex2int(¶ms[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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
36
src/gb/gb.c
36
src/gb/gb.c
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue