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)
|
- GB Video: Draw SGB border pieces that overlap GB graphics (fixes mgba.io/i/1339)
|
||||||
- GBA: Improve timing when not booting from BIOS
|
- GBA: Improve timing when not booting from BIOS
|
||||||
- GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059)
|
- 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: Redo internal key input, enabling edge-based key IRQs
|
||||||
- GBA I/O: Disable open bus behavior on invalid register 06A
|
- 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 Memory: Fix misaligned 32-bit I/O loads (fixes mgba.io/i/2307)
|
||||||
|
- GBA Video: Fix OpenGL rendering on M1 Macs
|
||||||
Other fixes:
|
Other fixes:
|
||||||
- Core: Don't attempt to restore rewind diffs past start of rewind
|
- Core: Don't attempt to restore rewind diffs past start of rewind
|
||||||
- FFmpeg: Fix crash when encoding audio with some containers
|
- 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:
|
Misc:
|
||||||
- Core: Suspend runloop when a core crashes
|
- Core: Suspend runloop when a core crashes
|
||||||
- GB Video: Add default SGB border
|
- 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: Only set default controller bindings if loading fails (fixes mgba.io/i/799)
|
||||||
- Qt: Save converter now supports importing GameShark Advance saves
|
- Qt: Save converter now supports importing GameShark Advance saves
|
||||||
- Qt: Save positions of multiplayer windows (closes mgba.io/i/2128)
|
- 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
|
- Windows: Attach to console if present
|
||||||
|
|
||||||
0.9.3: (2021-12-17)
|
0.9.3: (2021-12-17)
|
||||||
|
|
|
@ -29,6 +29,7 @@ struct GDBStub {
|
||||||
|
|
||||||
char line[GDB_STUB_MAX_LINE];
|
char line[GDB_STUB_MAX_LINE];
|
||||||
char outgoing[GDB_STUB_MAX_LINE];
|
char outgoing[GDB_STUB_MAX_LINE];
|
||||||
|
char memoryMapXml[GDB_STUB_MAX_LINE];
|
||||||
enum GDBStubAckState lineAck;
|
enum GDBStubAckState lineAck;
|
||||||
|
|
||||||
Socket socket;
|
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;
|
struct VFile* sramRealVf;
|
||||||
uint32_t sramSize;
|
uint32_t sramSize;
|
||||||
int sramDirty;
|
int sramDirty;
|
||||||
int32_t sramDirtAge;
|
uint32_t sramDirtAge;
|
||||||
bool sramMaskWriteback;
|
bool sramMaskWriteback;
|
||||||
|
|
||||||
int sgbBit;
|
int sgbBit;
|
||||||
|
|
|
@ -65,11 +65,6 @@ enum {
|
||||||
GB_SIZE_MBC6_FLASH = 0x100000,
|
GB_SIZE_MBC6_FLASH = 0x100000,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
|
||||||
GB_SRAM_DIRT_NEW = 1,
|
|
||||||
GB_SRAM_DIRT_SEEN = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBMemory;
|
struct GBMemory;
|
||||||
typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_t value);
|
typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_t value);
|
||||||
typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address);
|
typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address);
|
||||||
|
|
|
@ -60,11 +60,6 @@ enum FlashManufacturer {
|
||||||
FLASH_MFG_SANYO = 0x1362
|
FLASH_MFG_SANYO = 0x1362
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SavedataDirty {
|
|
||||||
SAVEDATA_DIRT_NEW = 1,
|
|
||||||
SAVEDATA_DIRT_SEEN = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
SAVEDATA_FLASH_BASE = 0x0E005555,
|
SAVEDATA_FLASH_BASE = 0x0E005555,
|
||||||
|
|
||||||
|
@ -92,7 +87,7 @@ struct GBASavedata {
|
||||||
unsigned settling;
|
unsigned settling;
|
||||||
struct mTimingEvent dust;
|
struct mTimingEvent dust;
|
||||||
|
|
||||||
enum SavedataDirty dirty;
|
int dirty;
|
||||||
uint32_t dirtAge;
|
uint32_t dirtAge;
|
||||||
|
|
||||||
enum FlashStateMachine flashState;
|
enum FlashStateMachine flashState;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <mgba/internal/arm/debugger/debugger.h>
|
#include <mgba/internal/arm/debugger/debugger.h>
|
||||||
#include <mgba/internal/arm/isa-inlines.h>
|
#include <mgba/internal/arm/isa-inlines.h>
|
||||||
#include <mgba/internal/gba/memory.h>
|
#include <mgba/internal/gba/memory.h>
|
||||||
|
#include <mgba-util/string.h>
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
|
@ -29,6 +30,30 @@ enum {
|
||||||
MACH_O_ARM_V4T = 5
|
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 _sendMessage(struct GDBStub* stub);
|
||||||
|
|
||||||
static void _gdbStubDeinit(struct mDebugger* debugger) {
|
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]);
|
_int2hex32(cpu->gprs[ARM_PC] - (cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM), &stub->outgoing[i]);
|
||||||
i += 8;
|
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
|
// CPU status
|
||||||
_int2hex32(cpu->cpsr.packed, &stub->outgoing[i]);
|
_int2hex32(cpu->cpsr.packed, &stub->outgoing[i]);
|
||||||
i += 8;
|
i += 8;
|
||||||
|
@ -433,7 +448,58 @@ static void _processQSupportedCommand(struct GDBStub* stub, const char* message)
|
||||||
}
|
}
|
||||||
message = end + 1;
|
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) {
|
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);
|
strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4);
|
||||||
} else if (!strncmp("sThreadInfo#", message, 12)) {
|
} else if (!strncmp("sThreadInfo#", message, 12)) {
|
||||||
strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4);
|
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)) {
|
} else if (!strncmp("Supported:", message, 10)) {
|
||||||
_processQSupportedCommand(stub, message + 10);
|
_processQSupportedCommand(stub, message + 10);
|
||||||
}
|
}
|
||||||
|
@ -487,7 +560,6 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) {
|
||||||
unsigned i = 0;
|
unsigned i = 0;
|
||||||
uint32_t address = _readHex(readAddress, &i);
|
uint32_t address = _readHex(readAddress, &i);
|
||||||
readAddress += i + 1;
|
readAddress += i + 1;
|
||||||
uint32_t kind = _readHex(readAddress, &i);
|
|
||||||
|
|
||||||
struct mBreakpoint breakpoint = {
|
struct mBreakpoint breakpoint = {
|
||||||
.address = address,
|
.address = address,
|
||||||
|
@ -499,8 +571,6 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) {
|
||||||
|
|
||||||
switch (message[0]) {
|
switch (message[0]) {
|
||||||
case '0':
|
case '0':
|
||||||
ARMDebuggerSetSoftwareBreakpoint(stub->d.platform, address, kind == 2 ? MODE_THUMB : MODE_ARM);
|
|
||||||
break;
|
|
||||||
case '1':
|
case '1':
|
||||||
stub->d.platform->setBreakpoint(stub->d.platform, &breakpoint);
|
stub->d.platform->setBreakpoint(stub->d.platform, &breakpoint);
|
||||||
break;
|
break;
|
||||||
|
@ -710,6 +780,8 @@ bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAdd
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memset(stub->memoryMapXml, 0, GDB_STUB_MAX_LINE);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
|
|
@ -165,6 +165,7 @@ void applyArguments(const struct mArguments* args, struct mSubParser* subparser,
|
||||||
}
|
}
|
||||||
if (args->bios) {
|
if (args->bios) {
|
||||||
mCoreConfigSetOverrideValue(config, "bios", args->bios);
|
mCoreConfigSetOverrideValue(config, "bios", args->bios);
|
||||||
|
mCoreConfigSetOverrideIntValue(config, "useBios", true);
|
||||||
}
|
}
|
||||||
HashTableEnumerate(&args->configOverrides, _tableApply, config);
|
HashTableEnumerate(&args->configOverrides, _tableApply, config);
|
||||||
if (subparser) {
|
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);
|
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 (encoder->graph) {
|
||||||
if (av_buffersrc_add_frame(encoder->source, encoder->videoFrame) < 0) {
|
if (av_buffersrc_write_frame(encoder->source, encoder->videoFrame) < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
26
src/gb/gb.c
26
src/gb/gb.c
|
@ -5,6 +5,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include <mgba/internal/gb/gb.h>
|
#include <mgba/internal/gb/gb.h>
|
||||||
|
|
||||||
|
#include <mgba/internal/defines.h>
|
||||||
#include <mgba/internal/gb/io.h>
|
#include <mgba/internal/gb/io.h>
|
||||||
#include <mgba/internal/gb/mbc.h>
|
#include <mgba/internal/gb/mbc.h>
|
||||||
#include <mgba/internal/sm83/sm83.h>
|
#include <mgba/internal/sm83/sm83.h>
|
||||||
|
@ -17,8 +18,6 @@
|
||||||
#include <mgba-util/patch.h>
|
#include <mgba-util/patch.h>
|
||||||
#include <mgba-util/vfs.h>
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
#define CLEANUP_THRESHOLD 15
|
|
||||||
|
|
||||||
const uint32_t CGB_SM83_FREQUENCY = 0x800000;
|
const uint32_t CGB_SM83_FREQUENCY = 0x800000;
|
||||||
const uint32_t SGB_SM83_FREQUENCY = 0x418B1E;
|
const uint32_t SGB_SM83_FREQUENCY = 0x418B1E;
|
||||||
|
|
||||||
|
@ -203,6 +202,14 @@ void GBResizeSram(struct GB* gb, size_t size) {
|
||||||
if (gb->memory.sram) {
|
if (gb->memory.sram) {
|
||||||
vf->unmap(vf, gb->memory.sram, gb->sramSize);
|
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);
|
gb->memory.sram = vf->map(vf, size, MAP_READ);
|
||||||
}
|
}
|
||||||
if (gb->memory.sram == (void*) -1) {
|
if (gb->memory.sram == (void*) -1) {
|
||||||
|
@ -233,25 +240,20 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) {
|
||||||
if (!gb->sramVf) {
|
if (!gb->sramVf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (gb->sramDirty & GB_SRAM_DIRT_NEW) {
|
if (mSavedataClean(&gb->sramDirty, &gb->sramDirtAge, frameCount)) {
|
||||||
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 (gb->sramMaskWriteback) {
|
if (gb->sramMaskWriteback) {
|
||||||
GBSavedataUnmask(gb);
|
GBSavedataUnmask(gb);
|
||||||
}
|
}
|
||||||
if (gb->memory.mbcType == GB_MBC3_RTC) {
|
if (gb->memory.mbcType == GB_MBC3_RTC) {
|
||||||
GBMBCRTCWrite(gb);
|
GBMBCRTCWrite(gb);
|
||||||
}
|
}
|
||||||
gb->sramDirty = 0;
|
if (gb->sramVf == gb->sramRealVf) {
|
||||||
if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) {
|
if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) {
|
||||||
mLOG(GB_MEM, INFO, "Savedata synced");
|
mLOG(GB_MEM, INFO, "Savedata synced");
|
||||||
} else {
|
} else {
|
||||||
mLOG(GB_MEM, INFO, "Savedata failed to sync!");
|
mLOG(GB_MEM, INFO, "Savedata failed to sync!");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size_t c;
|
size_t c;
|
||||||
for (c = 0; c < mCoreCallbacksListSize(&gb->coreCallbacks); ++c) {
|
for (c = 0; c < mCoreCallbacksListSize(&gb->coreCallbacks); ++c) {
|
||||||
|
@ -271,7 +273,7 @@ void GBSavedataMask(struct GB* gb, struct VFile* vf, bool writeback) {
|
||||||
}
|
}
|
||||||
gb->sramVf = vf;
|
gb->sramVf = vf;
|
||||||
gb->sramMaskWriteback = writeback;
|
gb->sramMaskWriteback = writeback;
|
||||||
gb->memory.sram = vf->map(vf, gb->sramSize, MAP_READ);
|
GBResizeSram(gb, gb->sramSize);
|
||||||
GBMBCSwitchSramBank(gb, gb->memory.sramCurrentBank);
|
GBMBCSwitchSramBank(gb, gb->memory.sramCurrentBank);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +318,9 @@ void GBUnloadROM(struct GB* gb) {
|
||||||
gb->memory.mbcType = GB_MBC_AUTODETECT;
|
gb->memory.mbcType = GB_MBC_AUTODETECT;
|
||||||
gb->isPristine = false;
|
gb->isPristine = false;
|
||||||
|
|
||||||
|
if (!gb->sramDirty) {
|
||||||
gb->sramMaskWriteback = false;
|
gb->sramMaskWriteback = false;
|
||||||
|
}
|
||||||
GBSavedataUnmask(gb);
|
GBSavedataUnmask(gb);
|
||||||
GBSramDeinit(gb);
|
GBSramDeinit(gb);
|
||||||
if (gb->sramRealVf) {
|
if (gb->sramRealVf) {
|
||||||
|
|
|
@ -6,9 +6,10 @@
|
||||||
#include <mgba/internal/gb/mbc.h>
|
#include <mgba/internal/gb/mbc.h>
|
||||||
|
|
||||||
#include <mgba/core/interface.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/gb.h>
|
||||||
#include <mgba/internal/gb/memory.h>
|
#include <mgba/internal/gb/memory.h>
|
||||||
|
#include <mgba/internal/sm83/sm83.h>
|
||||||
#include <mgba-util/crc32.h>
|
#include <mgba-util/crc32.h>
|
||||||
#include <mgba-util/vfs.h>
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
|
@ -615,6 +616,7 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
address &= 0x1FF;
|
address &= 0x1FF;
|
||||||
memory->sramBank[(address >> 1)] &= 0xF0 >> shift;
|
memory->sramBank[(address >> 1)] &= 0xF0 >> shift;
|
||||||
memory->sramBank[(address >> 1)] |= (value & 0xF) << shift;
|
memory->sramBank[(address >> 1)] |= (value & 0xF) << shift;
|
||||||
|
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -776,6 +778,7 @@ void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
case 0x2B:
|
case 0x2B:
|
||||||
if (memory->sramAccess) {
|
if (memory->sramAccess) {
|
||||||
memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM_HALFBANK - 1)] = value;
|
memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM_HALFBANK - 1)] = value;
|
||||||
|
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x2C:
|
case 0x2C:
|
||||||
|
@ -841,6 +844,7 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
break;
|
break;
|
||||||
case 0x5:
|
case 0x5:
|
||||||
_GBMBC7Write(&gb->memory, address, value);
|
_GBMBC7Write(&gb->memory, address, value);
|
||||||
|
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -1163,6 +1167,7 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
address &= 0x7F;
|
address &= 0x7F;
|
||||||
if (address == 0 && value & 1) {
|
if (address == 0 && value & 1) {
|
||||||
value &= 6; // TODO: Timing
|
value &= 6; // TODO: Timing
|
||||||
|
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
|
||||||
_GBPocketCamCapture(memory);
|
_GBPocketCamCapture(memory);
|
||||||
}
|
}
|
||||||
if (address < sizeof(memory->mbcState.pocketCam.registers)) {
|
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) {
|
switch (tama5->registers[GBTAMA5_CS] >> 1) {
|
||||||
case 0x0: // RAM write
|
case 0x0: // RAM write
|
||||||
memory->sram[address] = out;
|
memory->sram[address] = out;
|
||||||
|
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
|
||||||
break;
|
break;
|
||||||
case 0x1: // RAM read
|
case 0x1: // RAM read
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <mgba/internal/gb/memory.h>
|
#include <mgba/internal/gb/memory.h>
|
||||||
|
|
||||||
#include <mgba/core/interface.h>
|
#include <mgba/core/interface.h>
|
||||||
|
#include <mgba/internal/defines.h>
|
||||||
#include <mgba/internal/gb/gb.h>
|
#include <mgba/internal/gb/gb.h>
|
||||||
#include <mgba/internal/gb/io.h>
|
#include <mgba/internal/gb/io.h>
|
||||||
#include <mgba/internal/gb/mbc.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) {
|
if (memory->rtcAccess) {
|
||||||
memory->rtcRegs[memory->activeRtcReg] = value;
|
memory->rtcRegs[memory->activeRtcReg] = value;
|
||||||
} else if (memory->sramAccess && memory->sram && memory->directSramAccess) {
|
} else if (memory->sramAccess && memory->sram && memory->directSramAccess) {
|
||||||
|
if (memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] != value) {
|
||||||
memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value;
|
memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value;
|
||||||
|
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
memory->mbcWrite(gb, address, value);
|
memory->mbcWrite(gb, address, value);
|
||||||
}
|
}
|
||||||
gb->sramDirty |= GB_SRAM_DIRT_NEW;
|
|
||||||
return;
|
return;
|
||||||
case GB_REGION_WORKING_RAM_BANK0:
|
case GB_REGION_WORKING_RAM_BANK0:
|
||||||
case GB_REGION_WORKING_RAM_BANK0 + 2:
|
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 {
|
} else {
|
||||||
memory->mbcWrite(gb, address, value);
|
memory->mbcWrite(gb, address, value);
|
||||||
}
|
}
|
||||||
gb->sramDirty |= GB_SRAM_DIRT_NEW;
|
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
|
||||||
return;
|
return;
|
||||||
case GB_REGION_WORKING_RAM_BANK0:
|
case GB_REGION_WORKING_RAM_BANK0:
|
||||||
case GB_REGION_WORKING_RAM_BANK0 + 2:
|
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)) {
|
if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) {
|
||||||
currentDma->nextSource = currentDma->source;
|
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;
|
currentDma->nextDest = currentDma->dest;
|
||||||
|
|
||||||
uint32_t width = 2 << GBADMARegisterGetWidth(currentDma->reg);
|
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;
|
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;
|
int destOffset = DMA_OFFSET[GBADMARegisterGetDestControl(info->reg)] * width;
|
||||||
if (source) {
|
if (source) {
|
||||||
source += sourceOffset;
|
source += sourceOffset;
|
||||||
|
|
|
@ -149,7 +149,9 @@ void GBAUnloadROM(struct GBA* gba) {
|
||||||
gba->memory.rom = NULL;
|
gba->memory.rom = NULL;
|
||||||
gba->isPristine = false;
|
gba->isPristine = false;
|
||||||
|
|
||||||
|
if (!gba->memory.savedata.dirty) {
|
||||||
gba->memory.savedata.maskWriteback = false;
|
gba->memory.savedata.maskWriteback = false;
|
||||||
|
}
|
||||||
GBASavedataUnmask(&gba->memory.savedata);
|
GBASavedataUnmask(&gba->memory.savedata);
|
||||||
GBASavedataDeinit(&gba->memory.savedata);
|
GBASavedataDeinit(&gba->memory.savedata);
|
||||||
if (gba->memory.savedata.realVf) {
|
if (gba->memory.savedata.realVf) {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <mgba/internal/arm/decoder.h>
|
#include <mgba/internal/arm/decoder.h>
|
||||||
#include <mgba/internal/arm/macros.h>
|
#include <mgba/internal/arm/macros.h>
|
||||||
|
#include <mgba/internal/defines.h>
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
#include <mgba/internal/gba/dma.h>
|
#include <mgba/internal/gba/dma.h>
|
||||||
#include <mgba/internal/gba/io.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 {
|
} else {
|
||||||
memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value;
|
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) {
|
} else if (memory->hw.devices & HW_TILT) {
|
||||||
GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value);
|
GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value);
|
||||||
} else if (memory->savedata.type == SAVEDATA_SRAM512) {
|
} else if (memory->savedata.type == SAVEDATA_SRAM512) {
|
||||||
memory->savedata.data[address & (SIZE_CART_SRAM512 - 1)] = value;
|
memory->savedata.data[address & (SIZE_CART_SRAM512 - 1)] = value;
|
||||||
memory->savedata.dirty |= SAVEDATA_DIRT_NEW;
|
memory->savedata.dirty |= mSAVEDATA_DIRT_NEW;
|
||||||
} else {
|
} else {
|
||||||
mLOG(GBA_MEM, GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address);
|
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";
|
"precision highp isampler2D;\n";
|
||||||
|
|
||||||
static const GLchar* const _gl3Header =
|
static const GLchar* const _gl3Header =
|
||||||
"#version 150 core\n"
|
"#version 330 core\n"
|
||||||
"#define OUT(n)\n"
|
"#define OUT(n) layout(location = n)\n"
|
||||||
PALETTE_ENTRY
|
PALETTE_ENTRY
|
||||||
"precision highp float;\n";
|
"precision highp float;\n";
|
||||||
|
|
||||||
|
@ -684,7 +684,7 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) {
|
||||||
renderer->scale = 1;
|
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();
|
GLuint program = glCreateProgram();
|
||||||
shader->program = program;
|
shader->program = program;
|
||||||
|
|
||||||
|
@ -697,14 +697,6 @@ static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVide
|
||||||
if (log[0]) {
|
if (log[0]) {
|
||||||
mLOG(GBA_VIDEO, ERROR, "Fragment shader compilation failure: %s", log);
|
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);
|
glLinkProgram(program);
|
||||||
glGetProgramInfoLog(program, 2048, 0, log);
|
glGetProgramInfoLog(program, 2048, 0, log);
|
||||||
if (log[0]) {
|
if (log[0]) {
|
||||||
|
@ -719,6 +711,7 @@ static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVide
|
||||||
glEnableVertexAttribArray(positionLocation);
|
glEnableVertexAttribArray(positionLocation);
|
||||||
glVertexAttribPointer(positionLocation, 2, GL_INT, GL_FALSE, 0, NULL);
|
glVertexAttribPointer(positionLocation, 2, GL_INT, GL_FALSE, 0, NULL);
|
||||||
|
|
||||||
|
size_t i;
|
||||||
for (i = 0; uniforms[i].name; ++i) {
|
for (i = 0; uniforms[i].name; ++i) {
|
||||||
shader->uniforms[uniforms[i].type] = glGetUniformLocation(program, uniforms[i].name);
|
shader->uniforms[uniforms[i].type] = glGetUniformLocation(program, uniforms[i].name);
|
||||||
}
|
}
|
||||||
|
@ -824,7 +817,7 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) {
|
||||||
char log[2048];
|
char log[2048];
|
||||||
const GLchar* shaderBuffer[4];
|
const GLchar* shaderBuffer[4];
|
||||||
const GLubyte* version = glGetString(GL_VERSION);
|
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;
|
shaderBuffer[0] = _gl3Header;
|
||||||
} else {
|
} else {
|
||||||
shaderBuffer[0] = _gles3Header;
|
shaderBuffer[0] = _gles3Header;
|
||||||
|
@ -838,53 +831,47 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) {
|
||||||
if (log[0]) {
|
if (log[0]) {
|
||||||
mLOG(GBA_VIDEO, ERROR, "Vertex shader compilation failure: %s", log);
|
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[1] = _renderMode0;
|
||||||
|
|
||||||
shaderBuffer[2] = _renderTile16;
|
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;
|
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[1] = _renderMode2;
|
||||||
shaderBuffer[2] = _interpolate;
|
shaderBuffer[2] = _interpolate;
|
||||||
|
|
||||||
shaderBuffer[3] = _fetchTileOverflow;
|
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;
|
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[1] = _renderMode4;
|
||||||
shaderBuffer[2] = _interpolate;
|
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[1] = _renderMode35;
|
||||||
shaderBuffer[2] = _interpolate;
|
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[1] = _renderObj;
|
||||||
|
|
||||||
shaderBuffer[2] = _renderTile16;
|
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;
|
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;
|
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;
|
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;
|
shaderBuffer[1] = _finalize;
|
||||||
_compileShader(glRenderer, &glRenderer->finalizeShader, shaderBuffer, 2, vs, _uniformsFinalize, onlyColor, log);
|
_compileShader(glRenderer, &glRenderer->finalizeShader, shaderBuffer, 2, vs, _uniformsFinalize, log);
|
||||||
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
glDeleteShader(vs);
|
glDeleteShader(vs);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <mgba/internal/gba/savedata.h>
|
#include <mgba/internal/gba/savedata.h>
|
||||||
|
|
||||||
#include <mgba/internal/arm/macros.h>
|
#include <mgba/internal/arm/macros.h>
|
||||||
|
#include <mgba/internal/defines.h>
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
#include <mgba/internal/gba/serialize.h>
|
#include <mgba/internal/gba/serialize.h>
|
||||||
|
|
||||||
|
@ -24,7 +25,6 @@
|
||||||
#define FLASH_PROGRAM_CYCLES 650
|
#define FLASH_PROGRAM_CYCLES 650
|
||||||
// This needs real testing, and is only an estimation currently
|
// This needs real testing, and is only an estimation currently
|
||||||
#define EEPROM_SETTLE_CYCLES 115000
|
#define EEPROM_SETTLE_CYCLES 115000
|
||||||
#define CLEANUP_THRESHOLD 15
|
|
||||||
|
|
||||||
mLOG_DEFINE_CATEGORY(GBA_SAVE, "GBA Savedata", "gba.savedata");
|
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:
|
case FLASH_STATE_RAW:
|
||||||
switch (savedata->command) {
|
switch (savedata->command) {
|
||||||
case FLASH_COMMAND_PROGRAM:
|
case FLASH_COMMAND_PROGRAM:
|
||||||
savedata->dirty |= SAVEDATA_DIRT_NEW;
|
savedata->dirty |= mSAVEDATA_DIRT_NEW;
|
||||||
savedata->currentBank[address] = value;
|
savedata->currentBank[address] = value;
|
||||||
savedata->command = FLASH_COMMAND_NONE;
|
savedata->command = FLASH_COMMAND_NONE;
|
||||||
mTimingDeschedule(savedata->timing, &savedata->dust);
|
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];
|
uint8_t current = savedata->data[savedata->writeAddress >> 3];
|
||||||
current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
|
current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
|
||||||
current |= (value & 0x1) << (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;
|
savedata->data[savedata->writeAddress >> 3] = current;
|
||||||
mTimingDeschedule(savedata->timing, &savedata->dust);
|
mTimingDeschedule(savedata->timing, &savedata->dust);
|
||||||
mTimingSchedule(savedata->timing, &savedata->dust, EEPROM_SETTLE_CYCLES);
|
mTimingSchedule(savedata->timing, &savedata->dust, EEPROM_SETTLE_CYCLES);
|
||||||
|
@ -565,15 +565,10 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
|
||||||
if (!savedata->vf) {
|
if (!savedata->vf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (savedata->dirty & SAVEDATA_DIRT_NEW) {
|
if (mSavedataClean(&savedata->dirty, &savedata->dirtAge, frameCount)) {
|
||||||
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 (savedata->maskWriteback) {
|
if (savedata->maskWriteback) {
|
||||||
GBASavedataUnmask(savedata);
|
GBASavedataUnmask(savedata);
|
||||||
}
|
}
|
||||||
savedata->dirty = 0;
|
|
||||||
if (savedata->mapMode & MAP_WRITE) {
|
if (savedata->mapMode & MAP_WRITE) {
|
||||||
size_t size = GBASavedataSize(savedata);
|
size_t size = GBASavedataSize(savedata);
|
||||||
if (savedata->data && savedata->vf->sync(savedata->vf, savedata->data, size)) {
|
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) {
|
void _flashErase(struct GBASavedata* savedata) {
|
||||||
mLOG(GBA_SAVE, DEBUG, "Performing flash chip erase");
|
mLOG(GBA_SAVE, DEBUG, "Performing flash chip erase");
|
||||||
savedata->dirty |= SAVEDATA_DIRT_NEW;
|
savedata->dirty |= mSAVEDATA_DIRT_NEW;
|
||||||
size_t size = SIZE_CART_FLASH512;
|
size_t size = SIZE_CART_FLASH512;
|
||||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||||
size = SIZE_CART_FLASH1M;
|
size = SIZE_CART_FLASH1M;
|
||||||
|
@ -660,7 +655,7 @@ void _flashErase(struct GBASavedata* savedata) {
|
||||||
|
|
||||||
void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
|
void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
|
||||||
mLOG(GBA_SAVE, DEBUG, "Performing flash sector erase at 0x%04x", 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;
|
size_t size = 0x1000;
|
||||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||||
mLOG(GBA_SAVE, DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);
|
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->shaders[n].dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
context->finalShader.dirty = true;
|
||||||
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
|
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, drawW, drawH, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, drawW, drawH, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo);
|
||||||
|
@ -375,7 +376,6 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
|
||||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||||
|
|
||||||
context->finalShader.filter = v->filter;
|
context->finalShader.filter = v->filter;
|
||||||
context->finalShader.dirty = true;
|
|
||||||
_drawShader(context, &context->initialShader);
|
_drawShader(context, &context->initialShader);
|
||||||
if (v->interframeBlending) {
|
if (v->interframeBlending) {
|
||||||
context->interframeShader.blend = true;
|
context->interframeShader.blend = true;
|
||||||
|
@ -437,7 +437,10 @@ void mGLES2ContextCreate(struct mGLES2Context* context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void mGLES2ContextUseFramebuffer(struct mGLES2Context* context) {
|
void mGLES2ContextUseFramebuffer(struct mGLES2Context* context) {
|
||||||
|
if (!context->finalShader.fbo) {
|
||||||
glGenFramebuffers(1, &context->finalShader.fbo);
|
glGenFramebuffers(1, &context->finalShader.fbo);
|
||||||
|
}
|
||||||
|
if (!context->finalShader.tex) {
|
||||||
glGenTextures(1, &context->finalShader.tex);
|
glGenTextures(1, &context->finalShader.tex);
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
|
glBindTexture(GL_TEXTURE_2D, context->finalShader.tex);
|
||||||
|
@ -446,6 +449,7 @@ void mGLES2ContextUseFramebuffer(struct mGLES2Context* context) {
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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, 0);
|
||||||
|
}
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo);
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context->finalShader.tex, 0);
|
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();
|
QString copyright = m_ui.copyright->text();
|
||||||
copyright.replace("{year}", tr("2021"));
|
copyright.replace("{year}", QLatin1String("2022"));
|
||||||
m_ui.copyright->setText(copyright);
|
m_ui.copyright->setText(copyright);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,12 @@ Q_OBJECT
|
||||||
public:
|
public:
|
||||||
typedef std::function<void ()> Function;
|
typedef std::function<void ()> Function;
|
||||||
typedef std::function<void (bool)> BooleanFunction;
|
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(Function, const QString& name, const QString& visibleName, QObject* parent = nullptr);
|
||||||
Action(BooleanFunction, 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 isEnabled() const { return m_enabled; }
|
||||||
bool isActive() const { return m_active; }
|
bool isActive() const { return m_active; }
|
||||||
bool isExclusive() const { return m_exclusive; }
|
bool isExclusive() const { return m_exclusive; }
|
||||||
|
Role role() const { return m_role; }
|
||||||
|
|
||||||
void setExclusive(bool exclusive = true) { m_exclusive = exclusive; }
|
void setExclusive(bool exclusive = true) { m_exclusive = exclusive; }
|
||||||
|
void setRole(Role role) { m_role = role; }
|
||||||
|
|
||||||
Action& operator=(const Action&);
|
Action& operator=(const Action&);
|
||||||
|
|
||||||
|
@ -62,6 +70,7 @@ private:
|
||||||
bool m_enabled = true;
|
bool m_enabled = true;
|
||||||
bool m_active = false;
|
bool m_active = false;
|
||||||
bool m_exclusive = false;
|
bool m_exclusive = false;
|
||||||
|
Role m_role = Role::NO_ROLE;
|
||||||
|
|
||||||
Function m_function;
|
Function m_function;
|
||||||
BooleanFunction m_booleanFunction;
|
BooleanFunction m_booleanFunction;
|
||||||
|
|
|
@ -79,6 +79,20 @@ void ActionMapper::rebuildMenu(const QString& menu, QMenu* qmenu, QWidget* conte
|
||||||
} else if (!m_defaultShortcuts[actionName].isEmpty()) {
|
} else if (!m_defaultShortcuts[actionName].isEmpty()) {
|
||||||
qaction->setShortcut(m_defaultShortcuts[actionName][0]);
|
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) {
|
QObject::connect(qaction, &QAction::triggered, [qaction, action](bool enabled) {
|
||||||
if (qaction->isCheckable()) {
|
if (qaction->isCheckable()) {
|
||||||
action->trigger(enabled);
|
action->trigger(enabled);
|
||||||
|
|
|
@ -73,7 +73,9 @@ private:
|
||||||
|
|
||||||
template<typename T, typename V>
|
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) {
|
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>
|
template<typename T, typename V>
|
||||||
|
|
|
@ -4,6 +4,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
set(PLATFORM_SRC)
|
set(PLATFORM_SRC)
|
||||||
set(QT_STATIC OFF)
|
set(QT_STATIC OFF)
|
||||||
|
set(QT_DEFINES)
|
||||||
|
|
||||||
if(BUILD_SDL)
|
if(BUILD_SDL)
|
||||||
add_definitions(-DBUILD_SDL)
|
add_definitions(-DBUILD_SDL)
|
||||||
|
@ -36,6 +37,11 @@ if(NOT Qt5Widgets_FOUND)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
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]")
|
if(Qt5Widgets_VERSION MATCHES "^5.1[0-9]")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8")
|
||||||
else()
|
else()
|
||||||
|
@ -186,7 +192,6 @@ if(M_CORE_GB)
|
||||||
list(APPEND PLATFORM_SRC ${GB_SRC})
|
list(APPEND PLATFORM_SRC ${GB_SRC})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(QT_DEFINES)
|
|
||||||
if(Qt5Multimedia_FOUND)
|
if(Qt5Multimedia_FOUND)
|
||||||
list(APPEND AUDIO_SRC
|
list(APPEND AUDIO_SRC
|
||||||
AudioProcessorQt.cpp
|
AudioProcessorQt.cpp
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <QMutexLocker>
|
#include <QMutexLocker>
|
||||||
|
|
||||||
#include <mgba/core/serialize.h>
|
#include <mgba/core/serialize.h>
|
||||||
|
#include <mgba/core/version.h>
|
||||||
#include <mgba/feature/video-logger.h>
|
#include <mgba/feature/video-logger.h>
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
|
@ -86,12 +87,17 @@ CoreController::CoreController(mCore* core, QObject* parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
controller->m_resetActions.clear();
|
controller->m_resetActions.clear();
|
||||||
|
controller->m_frameCounter = -1;
|
||||||
|
|
||||||
if (!controller->m_hwaccel) {
|
if (!controller->m_hwaccel) {
|
||||||
context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer.data()), controller->screenDimensions().width());
|
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");
|
QMetaObject::invokeMethod(controller, "didReset");
|
||||||
|
if (controller->m_showResetInfo) {
|
||||||
|
QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
|
||||||
|
}
|
||||||
controller->finishFrame();
|
controller->finishFrame();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -483,6 +489,10 @@ void CoreController::setSync(bool sync) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CoreController::showResetInfo(bool enable) {
|
||||||
|
m_showResetInfo = enable;
|
||||||
|
}
|
||||||
|
|
||||||
void CoreController::setRewinding(bool rewind) {
|
void CoreController::setRewinding(bool rewind) {
|
||||||
if (!m_threadContext.core->opts.rewindEnable) {
|
if (!m_threadContext.core->opts.rewindEnable) {
|
||||||
return;
|
return;
|
||||||
|
@ -1053,6 +1063,7 @@ void CoreController::finishFrame() {
|
||||||
mCoreThreadPauseFromThread(&m_threadContext);
|
mCoreThreadPauseFromThread(&m_threadContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
++m_frameCounter;
|
||||||
}
|
}
|
||||||
updateKeys();
|
updateKeys();
|
||||||
|
|
||||||
|
|
|
@ -126,6 +126,7 @@ public:
|
||||||
bool videoSync() const { return m_videoSync; }
|
bool videoSync() const { return m_videoSync; }
|
||||||
|
|
||||||
void addFrameAction(std::function<void ()> callback);
|
void addFrameAction(std::function<void ()> callback);
|
||||||
|
uint64_t frameCounter() const { return m_frameCounter; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void start();
|
void start();
|
||||||
|
@ -134,6 +135,7 @@ public slots:
|
||||||
void setPaused(bool paused);
|
void setPaused(bool paused);
|
||||||
void frameAdvance();
|
void frameAdvance();
|
||||||
void setSync(bool enable);
|
void setSync(bool enable);
|
||||||
|
void showResetInfo(bool enable);
|
||||||
|
|
||||||
void setRewinding(bool);
|
void setRewinding(bool);
|
||||||
void rewind(int count = 0);
|
void rewind(int count = 0);
|
||||||
|
@ -232,6 +234,7 @@ private:
|
||||||
uint32_t m_crc32;
|
uint32_t m_crc32;
|
||||||
QString m_internalTitle;
|
QString m_internalTitle;
|
||||||
QString m_dbTitle;
|
QString m_dbTitle;
|
||||||
|
bool m_showResetInfo = false;
|
||||||
|
|
||||||
QByteArray m_activeBuffer;
|
QByteArray m_activeBuffer;
|
||||||
QByteArray m_completeBuffer;
|
QByteArray m_completeBuffer;
|
||||||
|
@ -240,6 +243,7 @@ private:
|
||||||
std::unique_ptr<mCacheSet> m_cacheSet;
|
std::unique_ptr<mCacheSet> m_cacheSet;
|
||||||
std::unique_ptr<Override> m_override;
|
std::unique_ptr<Override> m_override;
|
||||||
|
|
||||||
|
uint64_t m_frameCounter;
|
||||||
QList<std::function<void()>> m_resetActions;
|
QList<std::function<void()>> m_resetActions;
|
||||||
QList<std::function<void()>> m_frameActions;
|
QList<std::function<void()>> m_frameActions;
|
||||||
QMutex m_actionMutex{QMutex::Recursive};
|
QMutex m_actionMutex{QMutex::Recursive};
|
||||||
|
|
|
@ -35,10 +35,12 @@ Display* Display::create(QWidget* parent) {
|
||||||
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
|
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
|
||||||
format.setVersion(2, 0);
|
format.setVersion(2, 0);
|
||||||
} else {
|
} else {
|
||||||
format.setVersion(3, 2);
|
format.setVersion(3, 3);
|
||||||
}
|
}
|
||||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||||
if (!DisplayGL::supportsFormat(format)) {
|
if (DisplayGL::supportsFormat(format)) {
|
||||||
|
QSurfaceFormat::setDefaultFormat(format);
|
||||||
|
} else {
|
||||||
#ifdef BUILD_GL
|
#ifdef BUILD_GL
|
||||||
LOG(QT, WARN) << ("Failed to create an OpenGL Core context, trying old-style...");
|
LOG(QT, WARN) << ("Failed to create an OpenGL Core context, trying old-style...");
|
||||||
format.setVersion(1, 4);
|
format.setVersion(1, 4);
|
||||||
|
@ -102,14 +104,20 @@ QSize Display::viewportSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Display::attach(std::shared_ptr<CoreController> controller) {
|
void Display::attach(std::shared_ptr<CoreController> controller) {
|
||||||
connect(controller.get(), &CoreController::stateLoaded, this, &Display::resizeContext);
|
CoreController* controllerP = controller.get();
|
||||||
connect(controller.get(), &CoreController::stateLoaded, this, &Display::forceDraw);
|
connect(controllerP, &CoreController::stateLoaded, this, &Display::resizeContext);
|
||||||
connect(controller.get(), &CoreController::rewound, this, &Display::forceDraw);
|
connect(controllerP, &CoreController::stateLoaded, this, &Display::forceDraw);
|
||||||
connect(controller.get(), &CoreController::paused, this, &Display::pauseDrawing);
|
connect(controllerP, &CoreController::rewound, this, &Display::forceDraw);
|
||||||
connect(controller.get(), &CoreController::unpaused, this, &Display::unpauseDrawing);
|
connect(controllerP, &CoreController::paused, this, &Display::pauseDrawing);
|
||||||
connect(controller.get(), &CoreController::frameAvailable, this, &Display::framePosted);
|
connect(controllerP, &CoreController::unpaused, this, &Display::unpauseDrawing);
|
||||||
connect(controller.get(), &CoreController::statusPosted, this, &Display::showMessage);
|
connect(controllerP, &CoreController::frameAvailable, this, &Display::framePosted);
|
||||||
connect(controller.get(), &CoreController::didReset, this, &Display::resizeContext);
|
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) {
|
void Display::configure(ConfigController* config) {
|
||||||
|
@ -119,6 +127,7 @@ void Display::configure(ConfigController* config) {
|
||||||
interframeBlending(opts->interframeBlending);
|
interframeBlending(opts->interframeBlending);
|
||||||
filter(opts->resampleVideo);
|
filter(opts->resampleVideo);
|
||||||
config->updateOption("showOSD");
|
config->updateOption("showOSD");
|
||||||
|
config->updateOption("showFrameCounter");
|
||||||
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
if (opts->shader) {
|
if (opts->shader) {
|
||||||
struct VDir* shader = VDirOpen(opts->shader);
|
struct VDir* shader = VDirOpen(opts->shader);
|
||||||
|
@ -131,12 +140,15 @@ void Display::configure(ConfigController* config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Display::resizeEvent(QResizeEvent*) {
|
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) {
|
void Display::lockAspectRatio(bool lock) {
|
||||||
m_lockAspectRatio = lock;
|
m_lockAspectRatio = lock;
|
||||||
m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Display::lockIntegerScaling(bool lock) {
|
void Display::lockIntegerScaling(bool lock) {
|
||||||
|
@ -151,6 +163,13 @@ void Display::showOSDMessages(bool enable) {
|
||||||
m_showOSD = enable;
|
m_showOSD = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Display::showFrameCounter(bool enable) {
|
||||||
|
m_showFrameCounter = enable;
|
||||||
|
if (!enable) {
|
||||||
|
m_messagePainter.clearFrameCounter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Display::filter(bool filter) {
|
void Display::filter(bool filter) {
|
||||||
m_filter = filter;
|
m_filter = filter;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ public:
|
||||||
bool hasInterframeBlending() const { return m_interframeBlending; }
|
bool hasInterframeBlending() const { return m_interframeBlending; }
|
||||||
bool isFiltered() const { return m_filter; }
|
bool isFiltered() const { return m_filter; }
|
||||||
bool isShowOSD() const { return m_showOSD; }
|
bool isShowOSD() const { return m_showOSD; }
|
||||||
|
bool isShowFrameCounter() const { return m_showFrameCounter; }
|
||||||
|
|
||||||
virtual void attach(std::shared_ptr<CoreController>);
|
virtual void attach(std::shared_ptr<CoreController>);
|
||||||
virtual void configure(ConfigController*);
|
virtual void configure(ConfigController*);
|
||||||
|
@ -71,6 +72,7 @@ public slots:
|
||||||
virtual void lockIntegerScaling(bool lock);
|
virtual void lockIntegerScaling(bool lock);
|
||||||
virtual void interframeBlending(bool enable);
|
virtual void interframeBlending(bool enable);
|
||||||
virtual void showOSDMessages(bool enable);
|
virtual void showOSDMessages(bool enable);
|
||||||
|
virtual void showFrameCounter(bool enable);
|
||||||
virtual void filter(bool filter);
|
virtual void filter(bool filter);
|
||||||
virtual void framePosted() = 0;
|
virtual void framePosted() = 0;
|
||||||
virtual void setShaders(struct VDir*) = 0;
|
virtual void setShaders(struct VDir*) = 0;
|
||||||
|
@ -93,6 +95,7 @@ private:
|
||||||
|
|
||||||
MessagePainter m_messagePainter;
|
MessagePainter m_messagePainter;
|
||||||
bool m_showOSD = true;
|
bool m_showOSD = true;
|
||||||
|
bool m_showFrameCounter = false;
|
||||||
bool m_lockAspectRatio = false;
|
bool m_lockAspectRatio = false;
|
||||||
bool m_lockIntegerScaling = false;
|
bool m_lockIntegerScaling = false;
|
||||||
bool m_interframeBlending = false;
|
bool m_interframeBlending = false;
|
||||||
|
|
|
@ -9,14 +9,14 @@
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QMutexLocker>
|
#include <QMutexLocker>
|
||||||
#include <QOffscreenSurface>
|
|
||||||
#include <QOpenGLContext>
|
|
||||||
#include <QOpenGLFunctions>
|
#include <QOpenGLFunctions>
|
||||||
|
#include <QOpenGLFunctions_3_2_Core>
|
||||||
#include <QOpenGLPaintDevice>
|
#include <QOpenGLPaintDevice>
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
@ -32,6 +32,12 @@
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define OVERHEAD_NSEC 1000000
|
||||||
|
#else
|
||||||
|
#define OVERHEAD_NSEC 300000
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
|
||||||
QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
|
QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
|
||||||
|
@ -43,13 +49,96 @@ uint qHash(const QSurfaceFormat& format, uint seed) {
|
||||||
return qHash(representation, 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)
|
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
||||||
: Display(parent)
|
: Display(parent)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_NativeWindow);
|
setAttribute(Qt::WA_NativeWindow);
|
||||||
|
window()->windowHandle()->setFormat(format);
|
||||||
windowHandle()->create();
|
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_drawThread.setObjectName("Painter Thread");
|
||||||
m_painter->setThread(&m_drawThread);
|
m_painter->setThread(&m_drawThread);
|
||||||
|
|
||||||
|
@ -98,16 +187,20 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
||||||
lockIntegerScaling(isIntegerScalingLocked());
|
lockIntegerScaling(isIntegerScalingLocked());
|
||||||
interframeBlending(hasInterframeBlending());
|
interframeBlending(hasInterframeBlending());
|
||||||
showOSDMessages(isShowOSD());
|
showOSDMessages(isShowOSD());
|
||||||
|
showFrameCounter(isShowFrameCounter());
|
||||||
filter(isFiltered());
|
filter(isFiltered());
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||||
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
|
messagePainter()->resize(size(), devicePixelRatioF());
|
||||||
#else
|
#else
|
||||||
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
|
messagePainter()->resize(size(), devicePixelRatio());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CoreController::Interrupter interrupter(controller);
|
CoreController::Interrupter interrupter(controller);
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "start");
|
QMetaObject::invokeMethod(m_painter.get(), "start");
|
||||||
|
if (!m_gl) {
|
||||||
setUpdatesEnabled(false);
|
setUpdatesEnabled(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
|
bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
|
||||||
|
@ -189,7 +282,9 @@ void DisplayGL::unpauseDrawing() {
|
||||||
m_isDrawing = true;
|
m_isDrawing = true;
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
|
QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
|
||||||
#ifndef Q_OS_MAC
|
#ifndef Q_OS_MAC
|
||||||
|
if (!m_gl) {
|
||||||
setUpdatesEnabled(false);
|
setUpdatesEnabled(false);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,6 +315,11 @@ void DisplayGL::showOSDMessages(bool enable) {
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(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) {
|
void DisplayGL::filter(bool filter) {
|
||||||
Display::filter(filter);
|
Display::filter(filter);
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
|
QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
|
||||||
|
@ -274,10 +374,21 @@ int DisplayGL::framebufferHandle() {
|
||||||
return m_painter->glTex();
|
return m_painter->glTex();
|
||||||
}
|
}
|
||||||
|
|
||||||
PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
|
PainterGL::PainterGL(QWindow* window, mGLWidget* widget, const QSurfaceFormat& format)
|
||||||
: m_surface(surface)
|
: m_window(window)
|
||||||
, m_format(format)
|
, 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);
|
m_supportsShaders = m_format.version() >= qMakePair(2, 0);
|
||||||
for (auto& buf : m_buffers) {
|
for (auto& buf : m_buffers) {
|
||||||
m_free.append(&buf.front());
|
m_free.append(&buf.front());
|
||||||
|
@ -307,6 +418,9 @@ void PainterGL::makeCurrent() {
|
||||||
void PainterGL::create() {
|
void PainterGL::create() {
|
||||||
m_gl = std::make_unique<QOpenGLContext>();
|
m_gl = std::make_unique<QOpenGLContext>();
|
||||||
m_gl->setFormat(m_format);
|
m_gl->setFormat(m_format);
|
||||||
|
if (m_widget) {
|
||||||
|
m_gl->setShareContext(m_widget->context());
|
||||||
|
}
|
||||||
m_gl->create();
|
m_gl->create();
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
|
|
||||||
|
@ -317,7 +431,7 @@ void PainterGL::create() {
|
||||||
mGLES2Context* gl2Backend;
|
mGLES2Context* gl2Backend;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_window = std::make_unique<QOpenGLPaintDevice>();
|
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
|
||||||
|
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
auto version = m_format.version();
|
auto version = m_format.version();
|
||||||
|
@ -342,11 +456,36 @@ void PainterGL::create() {
|
||||||
}
|
}
|
||||||
painter->m_gl->swapBuffers(painter->m_surface);
|
painter->m_gl->swapBuffers(painter->m_surface);
|
||||||
painter->makeCurrent();
|
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);
|
m_backend->init(m_backend, 0);
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
if (m_supportsShaders) {
|
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);
|
m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -369,7 +508,7 @@ void PainterGL::destroy() {
|
||||||
#endif
|
#endif
|
||||||
m_backend->deinit(m_backend);
|
m_backend->deinit(m_backend);
|
||||||
m_gl->doneCurrent();
|
m_gl->doneCurrent();
|
||||||
m_window.reset();
|
m_paintDev.reset();
|
||||||
m_gl.reset();
|
m_gl.reset();
|
||||||
|
|
||||||
free(m_backend);
|
free(m_backend);
|
||||||
|
@ -404,8 +543,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::resize(const QSize& size) {
|
void PainterGL::resize(const QSize& size) {
|
||||||
|
qreal r = m_window->devicePixelRatio();
|
||||||
m_size = size;
|
m_size = size;
|
||||||
m_window->setSize(m_size);
|
m_paintDev->setSize(m_size * r);
|
||||||
|
m_paintDev->setDevicePixelRatio(r);
|
||||||
if (m_started && !m_active) {
|
if (m_started && !m_active) {
|
||||||
forceDraw();
|
forceDraw();
|
||||||
}
|
}
|
||||||
|
@ -429,6 +570,10 @@ void PainterGL::showOSD(bool enable) {
|
||||||
m_showOSD = enable;
|
m_showOSD = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PainterGL::showFrameCounter(bool enable) {
|
||||||
|
m_showFrameCounter = enable;
|
||||||
|
}
|
||||||
|
|
||||||
void PainterGL::filter(bool filter) {
|
void PainterGL::filter(bool filter) {
|
||||||
m_backend->filter = filter;
|
m_backend->filter = filter;
|
||||||
if (m_started && !m_active) {
|
if (m_started && !m_active) {
|
||||||
|
@ -462,7 +607,7 @@ void PainterGL::draw() {
|
||||||
if (!sync->audioWait && !sync->videoFrameWait) {
|
if (!sync->audioWait && !sync->videoFrameWait) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_delayTimer.elapsed() >= 1000 / m_surface->screen()->refreshRate()) {
|
if (m_delayTimer.elapsed() >= 1000 / m_window->screen()->refreshRate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!m_drawTimer.isActive()) {
|
if (!m_drawTimer.isActive()) {
|
||||||
|
@ -476,13 +621,13 @@ void PainterGL::draw() {
|
||||||
m_delayTimer.start();
|
m_delayTimer.start();
|
||||||
} else {
|
} else {
|
||||||
if (sync->audioWait || sync->videoFrameWait) {
|
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);
|
QThread::usleep(500);
|
||||||
}
|
}
|
||||||
forceRedraw = sync->videoFrameWait;
|
forceRedraw = sync->videoFrameWait;
|
||||||
}
|
}
|
||||||
if (!forceRedraw) {
|
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);
|
mCoreSyncWaitFrameEnd(sync);
|
||||||
|
@ -497,7 +642,7 @@ void PainterGL::draw() {
|
||||||
void PainterGL::forceDraw() {
|
void PainterGL::forceDraw() {
|
||||||
performDraw();
|
performDraw();
|
||||||
if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
m_delayTimer.restart();
|
m_delayTimer.restart();
|
||||||
|
@ -522,7 +667,7 @@ void PainterGL::stop() {
|
||||||
}
|
}
|
||||||
if (m_videoProxy) {
|
if (m_videoProxy) {
|
||||||
m_videoProxy->reset();
|
m_videoProxy->reset();
|
||||||
m_videoProxy->moveToThread(m_surface->thread());
|
m_videoProxy->moveToThread(m_window->thread());
|
||||||
m_videoProxy.reset();
|
m_videoProxy.reset();
|
||||||
}
|
}
|
||||||
m_backend->clear(m_backend);
|
m_backend->clear(m_backend);
|
||||||
|
@ -540,14 +685,14 @@ void PainterGL::unpause() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::performDraw() {
|
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);
|
m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
|
||||||
if (m_buffer) {
|
if (m_buffer) {
|
||||||
m_backend->postFrame(m_backend, m_buffer);
|
m_backend->postFrame(m_backend, m_buffer);
|
||||||
}
|
}
|
||||||
m_backend->drawFrame(m_backend);
|
m_backend->drawFrame(m_backend);
|
||||||
if (m_showOSD && m_messagePainter) {
|
if (m_showOSD && m_messagePainter) {
|
||||||
m_painter.begin(m_window.get());
|
m_painter.begin(m_paintDev.get());
|
||||||
m_messagePainter->paint(&m_painter);
|
m_messagePainter->paint(&m_painter);
|
||||||
m_painter.end();
|
m_painter.end();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,11 @@
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
#include <QOffscreenSurface>
|
||||||
#include <QOpenGLContext>
|
#include <QOpenGLContext>
|
||||||
|
#include <QOpenGLShaderProgram>
|
||||||
|
#include <QOpenGLVertexArrayObject>
|
||||||
|
#include <QOpenGLWidget>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
@ -35,11 +39,37 @@
|
||||||
#include "platform/video-backend.h"
|
#include "platform/video-backend.h"
|
||||||
|
|
||||||
class QOpenGLPaintDevice;
|
class QOpenGLPaintDevice;
|
||||||
|
class QOpenGLWidget;
|
||||||
|
|
||||||
uint qHash(const QSurfaceFormat&, uint seed = 0);
|
uint qHash(const QSurfaceFormat&, uint seed = 0);
|
||||||
|
|
||||||
namespace QGBA {
|
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 PainterGL;
|
||||||
class DisplayGL : public Display {
|
class DisplayGL : public Display {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -66,6 +96,7 @@ public slots:
|
||||||
void lockIntegerScaling(bool lock) override;
|
void lockIntegerScaling(bool lock) override;
|
||||||
void interframeBlending(bool enable) override;
|
void interframeBlending(bool enable) override;
|
||||||
void showOSDMessages(bool enable) override;
|
void showOSDMessages(bool enable) override;
|
||||||
|
void showFrameCounter(bool enable) override;
|
||||||
void filter(bool filter) override;
|
void filter(bool filter) override;
|
||||||
void framePosted() override;
|
void framePosted() override;
|
||||||
void setShaders(struct VDir*) override;
|
void setShaders(struct VDir*) override;
|
||||||
|
@ -87,13 +118,14 @@ private:
|
||||||
std::unique_ptr<PainterGL> m_painter;
|
std::unique_ptr<PainterGL> m_painter;
|
||||||
QThread m_drawThread;
|
QThread m_drawThread;
|
||||||
std::shared_ptr<CoreController> m_context;
|
std::shared_ptr<CoreController> m_context;
|
||||||
|
mGLWidget* m_gl;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PainterGL : public QObject {
|
class PainterGL : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PainterGL(QWindow* surface, const QSurfaceFormat& format);
|
PainterGL(QWindow* surface, mGLWidget* widget, const QSurfaceFormat& format);
|
||||||
~PainterGL();
|
~PainterGL();
|
||||||
|
|
||||||
void setThread(QThread*);
|
void setThread(QThread*);
|
||||||
|
@ -122,6 +154,7 @@ public slots:
|
||||||
void lockIntegerScaling(bool lock);
|
void lockIntegerScaling(bool lock);
|
||||||
void interframeBlending(bool enable);
|
void interframeBlending(bool enable);
|
||||||
void showOSD(bool enable);
|
void showOSD(bool enable);
|
||||||
|
void showFrameCounter(bool enable);
|
||||||
void filter(bool filter);
|
void filter(bool filter);
|
||||||
void resizeContext();
|
void resizeContext();
|
||||||
|
|
||||||
|
@ -144,10 +177,14 @@ private:
|
||||||
uint32_t* m_buffer = nullptr;
|
uint32_t* m_buffer = nullptr;
|
||||||
QPainter m_painter;
|
QPainter m_painter;
|
||||||
QMutex m_mutex;
|
QMutex m_mutex;
|
||||||
QWindow* m_surface;
|
QWindow* m_window;
|
||||||
|
QSurface* m_surface;
|
||||||
QSurfaceFormat m_format;
|
QSurfaceFormat m_format;
|
||||||
std::unique_ptr<QOpenGLPaintDevice> m_window;
|
std::unique_ptr<QOpenGLPaintDevice> m_paintDev;
|
||||||
std::unique_ptr<QOpenGLContext> m_gl;
|
std::unique_ptr<QOpenGLContext> m_gl;
|
||||||
|
int m_finalTexIdx = 0;
|
||||||
|
GLuint m_finalTex[2];
|
||||||
|
mGLWidget* m_widget;
|
||||||
bool m_active = false;
|
bool m_active = false;
|
||||||
bool m_started = false;
|
bool m_started = false;
|
||||||
QTimer m_drawTimer;
|
QTimer m_drawTimer;
|
||||||
|
@ -155,6 +192,7 @@ private:
|
||||||
CoreController::Interrupter m_interrupter;
|
CoreController::Interrupter m_interrupter;
|
||||||
bool m_supportsShaders;
|
bool m_supportsShaders;
|
||||||
bool m_showOSD;
|
bool m_showOSD;
|
||||||
|
bool m_showFrameCounter;
|
||||||
VideoShader m_shader{};
|
VideoShader m_shader{};
|
||||||
VideoBackend* m_backend = nullptr;
|
VideoBackend* m_backend = nullptr;
|
||||||
QSize m_size;
|
QSize m_size;
|
||||||
|
|
|
@ -107,7 +107,7 @@ void DisplayQt::paintEvent(QPaintEvent*) {
|
||||||
}
|
}
|
||||||
painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height));
|
painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height));
|
||||||
painter.setOpacity(1);
|
painter.setOpacity(1);
|
||||||
if (isShowOSD()) {
|
if (isShowOSD() || isShowFrameCounter()) {
|
||||||
messagePainter()->paint(&painter);
|
messagePainter()->paint(&painter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ MessagePainter::MessagePainter(QObject* parent)
|
||||||
{
|
{
|
||||||
m_messageFont = GBAApp::app()->monospaceFont();
|
m_messageFont = GBAApp::app()->monospaceFont();
|
||||||
m_messageFont.setPixelSize(13);
|
m_messageFont.setPixelSize(13);
|
||||||
|
m_frameFont = GBAApp::app()->monospaceFont();
|
||||||
|
m_frameFont.setPixelSize(10);
|
||||||
connect(&m_messageTimer, &QTimer::timeout, this, &MessagePainter::clearMessage);
|
connect(&m_messageTimer, &QTimer::timeout, this, &MessagePainter::clearMessage);
|
||||||
m_messageTimer.setSingleShot(true);
|
m_messageTimer.setSingleShot(true);
|
||||||
m_messageTimer.setInterval(5000);
|
m_messageTimer.setInterval(5000);
|
||||||
|
@ -25,40 +27,35 @@ MessagePainter::MessagePainter(QObject* parent)
|
||||||
clearMessage();
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagePainter::resize(const QSize& size, bool lockAspectRatio, qreal scaleFactor) {
|
void MessagePainter::resize(const QSize& size, qreal scaleFactor) {
|
||||||
int w = size.width();
|
double drawW = size.width();
|
||||||
int h = size.height();
|
double drawH = size.height();
|
||||||
int drawW = w;
|
double area = pow(drawW * drawW * drawW * drawH * drawH, 0.2);
|
||||||
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);
|
|
||||||
m_scaleFactor = scaleFactor;
|
m_scaleFactor = scaleFactor;
|
||||||
m_local = QPoint(1, GBA_VIDEO_VERTICAL_PIXELS - m_messageFont.pixelSize() - 1);
|
m_world.reset();
|
||||||
m_local = m_world.map(m_local);
|
m_world.scale(area / 220., area / 220.);
|
||||||
m_local += QPoint((w - drawW) / 2, (h - drawH) / 2);
|
m_local = QPoint(area / 100., drawH - m_messageFont.pixelSize() * m_world.m22() * 1.3);
|
||||||
m_pixmapBuffer = QPixmap(drawW * m_scaleFactor,
|
|
||||||
(m_messageFont.pixelSize() + 2) * m_world.m22() * m_scaleFactor);
|
QFontMetrics metrics(m_frameFont);
|
||||||
m_pixmapBuffer.setDevicePixelRatio(m_scaleFactor);
|
m_framePoint = QPoint(drawW / m_world.m11() - metrics.height() * 0.1, metrics.height() * 0.75);
|
||||||
|
|
||||||
m_mutex.lock();
|
m_mutex.lock();
|
||||||
m_message.prepare(m_world, m_messageFont);
|
|
||||||
redraw();
|
redraw();
|
||||||
m_mutex.unlock();
|
m_mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagePainter::redraw() {
|
void MessagePainter::redraw() {
|
||||||
m_pixmapBuffer.fill(Qt::transparent);
|
|
||||||
if (m_message.text().isEmpty()) {
|
if (m_message.text().isEmpty()) {
|
||||||
|
m_pixmapBuffer.fill(Qt::transparent);
|
||||||
m_pixmap = m_pixmapBuffer;
|
m_pixmap = m_pixmapBuffer;
|
||||||
m_pixmap.setDevicePixelRatio(m_scaleFactor);
|
|
||||||
return;
|
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);
|
QPainter painter(&m_pixmapBuffer);
|
||||||
painter.setWorldTransform(m_world);
|
painter.setWorldTransform(m_world);
|
||||||
painter.setRenderHint(QPainter::Antialiasing);
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
@ -74,16 +71,32 @@ void MessagePainter::redraw() {
|
||||||
painter.setPen(Qt::white);
|
painter.setPen(Qt::white);
|
||||||
painter.drawStaticText(0, 0, m_message);
|
painter.drawStaticText(0, 0, m_message);
|
||||||
m_pixmap = m_pixmapBuffer;
|
m_pixmap = m_pixmapBuffer;
|
||||||
m_pixmap.setDevicePixelRatio(m_scaleFactor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagePainter::paint(QPainter* painter) {
|
void MessagePainter::paint(QPainter* painter) {
|
||||||
if (!m_message.text().isEmpty()) {
|
if (!m_message.text().isEmpty()) {
|
||||||
painter->drawPixmap(m_local, m_pixmap);
|
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) {
|
void MessagePainter::showMessage(const QString& message) {
|
||||||
m_mutex.lock();
|
m_mutex.lock();
|
||||||
m_message.setText(message);
|
m_message.setText(message);
|
||||||
|
@ -100,3 +113,16 @@ void MessagePainter::clearMessage() {
|
||||||
m_mutex.unlock();
|
m_mutex.unlock();
|
||||||
m_messageTimer.stop();
|
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:
|
public:
|
||||||
MessagePainter(QObject* parent = nullptr);
|
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 paint(QPainter* painter);
|
||||||
void setScaleFactor(qreal factor);
|
void setScaleFactor(qreal factor);
|
||||||
|
|
||||||
|
@ -27,18 +27,28 @@ public slots:
|
||||||
void showMessage(const QString& message);
|
void showMessage(const QString& message);
|
||||||
void clearMessage();
|
void clearMessage();
|
||||||
|
|
||||||
|
void showFrameCounter(uint64_t);
|
||||||
|
void clearFrameCounter();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void redraw();
|
void redraw();
|
||||||
|
|
||||||
QMutex m_mutex;
|
QMutex m_mutex;
|
||||||
QStaticText m_message;
|
QStaticText m_message;
|
||||||
|
qreal m_scaleFactor = 1;
|
||||||
|
uint64_t m_frameCounter;
|
||||||
|
bool m_drawFrameCounter = false;
|
||||||
|
|
||||||
|
QPoint m_local;
|
||||||
QPixmap m_pixmap;
|
QPixmap m_pixmap;
|
||||||
QPixmap m_pixmapBuffer;
|
QPixmap m_pixmapBuffer;
|
||||||
|
|
||||||
|
QPointF m_framePoint = QPointF(0, 0);
|
||||||
|
QFont m_frameFont;
|
||||||
|
|
||||||
QTimer m_messageTimer{this};
|
QTimer m_messageTimer{this};
|
||||||
QPoint m_local;
|
|
||||||
QTransform m_world;
|
QTransform m_world;
|
||||||
QFont m_messageFont;
|
QFont m_messageFont;
|
||||||
qreal m_scaleFactor = 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -452,6 +452,8 @@ void SettingsView::updateConfig() {
|
||||||
saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
|
saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
|
||||||
saveSetting("interframeBlending", m_ui.interframeBlending);
|
saveSetting("interframeBlending", m_ui.interframeBlending);
|
||||||
saveSetting("showOSD", m_ui.showOSD);
|
saveSetting("showOSD", m_ui.showOSD);
|
||||||
|
saveSetting("showFrameCounter", m_ui.showFrameCounter);
|
||||||
|
saveSetting("showResetInfo", m_ui.showResetInfo);
|
||||||
saveSetting("volume", m_ui.volume);
|
saveSetting("volume", m_ui.volume);
|
||||||
saveSetting("mute", m_ui.mute);
|
saveSetting("mute", m_ui.mute);
|
||||||
saveSetting("fastForwardVolume", m_ui.volumeFf);
|
saveSetting("fastForwardVolume", m_ui.volumeFf);
|
||||||
|
@ -675,6 +677,8 @@ void SettingsView::reloadConfig() {
|
||||||
loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
|
loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
|
||||||
loadSetting("interframeBlending", m_ui.interframeBlending);
|
loadSetting("interframeBlending", m_ui.interframeBlending);
|
||||||
loadSetting("showOSD", m_ui.showOSD, true);
|
loadSetting("showOSD", m_ui.showOSD, true);
|
||||||
|
loadSetting("showFrameCounter", m_ui.showFrameCounter);
|
||||||
|
loadSetting("showResetInfo", m_ui.showResetInfo);
|
||||||
loadSetting("volume", m_ui.volume, 0x100);
|
loadSetting("volume", m_ui.volume, 0x100);
|
||||||
loadSetting("mute", m_ui.mute, false);
|
loadSetting("mute", m_ui.mute, false);
|
||||||
loadSetting("fastForwardVolume", m_ui.volumeFf, m_ui.volume->value());
|
loadSetting("fastForwardVolume", m_ui.volumeFf, m_ui.volume->value());
|
||||||
|
|
|
@ -594,6 +594,13 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<item row="8" column="1">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
||||||
<item>
|
<item>
|
||||||
|
@ -612,6 +619,31 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</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">
|
<item row="10" column="0" colspan="2">
|
||||||
<widget class="Line" name="line_17">
|
<widget class="Line" name="line_17">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -667,20 +699,41 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="16" column="1">
|
<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">
|
<widget class="QCheckBox" name="useDiscordPresence">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enable Discord Rich Presence</string>
|
<string>Enable Discord Rich Presence</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="17" column="0" colspan="2">
|
<item row="18" column="0" colspan="2">
|
||||||
<widget class="Line" name="line_13">
|
<widget class="Line" name="line_13">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="18" column="1">
|
<item row="19" column="1">
|
||||||
<widget class="QCheckBox" name="autosave">
|
<widget class="QCheckBox" name="autosave">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Automatically save state</string>
|
<string>Automatically save state</string>
|
||||||
|
@ -690,7 +743,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="19" column="1">
|
<item row="20" column="1">
|
||||||
<widget class="QCheckBox" name="autoload">
|
<widget class="QCheckBox" name="autoload">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Automatically load state</string>
|
<string>Automatically load state</string>
|
||||||
|
@ -700,14 +753,14 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="20" column="0" colspan="2">
|
<item row="21" column="0" colspan="2">
|
||||||
<widget class="Line" name="line_16">
|
<widget class="Line" name="line_16">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="21" column="1">
|
<item row="22" column="1">
|
||||||
<widget class="QCheckBox" name="cheatAutosave">
|
<widget class="QCheckBox" name="cheatAutosave">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Automatically save cheats</string>
|
<string>Automatically save cheats</string>
|
||||||
|
@ -717,7 +770,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="22" column="1">
|
<item row="23" column="1">
|
||||||
<widget class="QCheckBox" name="cheatAutoload">
|
<widget class="QCheckBox" name="cheatAutoload">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Automatically load cheats</string>
|
<string>Automatically load cheats</string>
|
||||||
|
@ -727,38 +780,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="update">
|
<widget class="QWidget" name="update">
|
||||||
|
@ -2394,6 +2415,38 @@
|
||||||
</hint>
|
</hint>
|
||||||
</hints>
|
</hints>
|
||||||
</connection>
|
</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>
|
</connections>
|
||||||
<buttongroups>
|
<buttongroups>
|
||||||
<buttongroup name="gbColors"/>
|
<buttongroup name="gbColors"/>
|
||||||
|
|
|
@ -771,7 +771,9 @@ void Window::focusInEvent(QFocusEvent*) {
|
||||||
updateMultiplayerActive(true);
|
updateMultiplayerActive(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (m_display) {
|
||||||
m_display->forceDraw();
|
m_display->forceDraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::focusOutEvent(QFocusEvent*) {
|
void Window::focusOutEvent(QFocusEvent*) {
|
||||||
|
@ -899,7 +901,6 @@ void Window::gameStarted() {
|
||||||
m_config->updateOption("lockAspectRatio");
|
m_config->updateOption("lockAspectRatio");
|
||||||
m_config->updateOption("interframeBlending");
|
m_config->updateOption("interframeBlending");
|
||||||
m_config->updateOption("resampleVideo");
|
m_config->updateOption("resampleVideo");
|
||||||
m_config->updateOption("showOSD");
|
|
||||||
if (m_savedScale > 0) {
|
if (m_savedScale > 0) {
|
||||||
resizeFrame(size * m_savedScale);
|
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");
|
m_actions.addAction(tr("Add folder to library..."), "addDirToLibrary", this, &Window::addDirToLibrary, "file");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
m_actions.addMenu(tr("Save games"), "saves", "file");
|
||||||
addGameAction(tr("Load alternate save game..."), "loadAlternateSave", [this]() {
|
addGameAction(tr("Load alternate save game..."), "loadAlternateSave", [this]() {
|
||||||
this->selectSave(false);
|
this->selectSave(false);
|
||||||
}, "file");
|
}, "saves");
|
||||||
addGameAction(tr("Load temporary save game..."), "loadTemporarySave", [this]() {
|
addGameAction(tr("Load temporary save game..."), "loadTemporarySave", [this]() {
|
||||||
this->selectSave(true);
|
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");
|
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);
|
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_actions.addSeparator("file");
|
||||||
m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", [this]() {
|
m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", [this]() {
|
||||||
GBAApp::app()->newWindow();
|
GBAApp::app()->newWindow();
|
||||||
|
@ -1396,11 +1399,8 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
m_actions.addSeparator("file");
|
m_actions.addSeparator("file");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_actions.addAction(tr("About..."), "about", openTView<AboutScreen>(), "file");
|
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);
|
||||||
#ifndef Q_OS_MAC
|
|
||||||
m_actions.addAction(tr("E&xit"), "quit", static_cast<QWidget*>(this), &QWidget::close, "file", QKeySequence::Quit);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_actions.addMenu(tr("&Emulation"), "emu");
|
m_actions.addMenu(tr("&Emulation"), "emu");
|
||||||
|
|
||||||
|
@ -1671,7 +1671,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView<CheatsView>(), "tools");
|
addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView<CheatsView>(), "tools");
|
||||||
|
|
||||||
m_actions.addSeparator("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");
|
m_actions.addAction(tr("Make portable"), "makePortable", this, &Window::tryMakePortable, "tools");
|
||||||
|
|
||||||
#ifdef USE_DEBUGGERS
|
#ifdef USE_DEBUGGERS
|
||||||
|
@ -1799,6 +1799,20 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
}
|
}
|
||||||
}, this);
|
}, 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");
|
ConfigOption* videoScale = m_config->addOption("videoScale");
|
||||||
videoScale->connect([this](const QVariant& value) {
|
videoScale->connect([this](const QVariant& value) {
|
||||||
if (m_display) {
|
if (m_display) {
|
||||||
|
@ -2045,6 +2059,9 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
||||||
|
|
||||||
attachDisplay();
|
attachDisplay();
|
||||||
m_controller->loadConfig(m_config);
|
m_controller->loadConfig(m_config);
|
||||||
|
m_config->updateOption("showOSD");
|
||||||
|
m_config->updateOption("showFrameCounter");
|
||||||
|
m_config->updateOption("showResetInfo");
|
||||||
m_controller->start();
|
m_controller->start();
|
||||||
|
|
||||||
if (!m_pendingState.isEmpty()) {
|
if (!m_pendingState.isEmpty()) {
|
||||||
|
|
|
@ -46,3 +46,10 @@ if(BUILD_CINEMA)
|
||||||
add_test(cinema ${BINARY_NAME}-cinema -v)
|
add_test(cinema ${BINARY_NAME}-cinema -v)
|
||||||
install(TARGETS ${BINARY_NAME}-cinema DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-test)
|
install(TARGETS ${BINARY_NAME}-cinema DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-test)
|
||||||
endif()
|
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