mirror of https://github.com/mgba-emu/mgba.git
Compare commits
44 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
456ac2222b | |
![]() |
a413dda335 | |
![]() |
c8d7a7d1a3 | |
![]() |
9d6d2c9c6b | |
![]() |
f7acda6d64 | |
![]() |
0da3498ee8 | |
![]() |
d17b1da1b7 | |
![]() |
0d52995877 | |
![]() |
63a019b749 | |
![]() |
fac26cda3c | |
![]() |
245f4b961a | |
![]() |
3d723d5cf4 | |
![]() |
1a4dc70731 | |
![]() |
411b7dccb4 | |
![]() |
947a1c8f5c | |
![]() |
576f607c81 | |
![]() |
ab790f4c6d | |
![]() |
94277cffbb | |
![]() |
7ba3b40977 | |
![]() |
3dbb90e574 | |
![]() |
c323ab384c | |
![]() |
411741fba3 | |
![]() |
9f057e2719 | |
![]() |
0fc6b02691 | |
![]() |
3866d74707 | |
![]() |
aeaa40e373 | |
![]() |
8e58dcadb8 | |
![]() |
f632b85338 | |
![]() |
3cd63cb71c | |
![]() |
4914954c67 | |
![]() |
20c16bc8e7 | |
![]() |
838e4a182e | |
![]() |
e3605d291c | |
![]() |
12f7168e9f | |
![]() |
455525e3b6 | |
![]() |
3f39bd2536 | |
![]() |
838122c234 | |
![]() |
c6d7c8f601 | |
![]() |
9a3ec792dd | |
![]() |
c4718d6907 | |
![]() |
64255a716b | |
![]() |
277aa36d12 | |
![]() |
81cb4c3bf6 | |
![]() |
df599a1fb6 |
39
CHANGES
39
CHANGES
|
@ -1,3 +1,32 @@
|
||||||
|
0.10.1: (2022-01-10)
|
||||||
|
Emulation fixes:
|
||||||
|
- GB Audio: Fix channels 1/2 not playing when resetting volume (fixes mgba.io/i/2614)
|
||||||
|
- GB Audio: Fix channel 3 volume being changed between samples (fixes mgba.io/i/1896)
|
||||||
|
- GB Audio: Fix up boot sequence
|
||||||
|
- GB Audio: Fix updating channels other than 2 when writing NR5x
|
||||||
|
- GB BIOS: Include timing in degenerate ArcTan2 cases (fixes mgba.io/i/2763)
|
||||||
|
- GB Memory: Actually, HDMAs should start when LCD is off (fixes mgba.io/i/2662)
|
||||||
|
- GB Serialize: Don't write BGP/OBP when loading SCGB state (fixes mgba.io/i/2694)
|
||||||
|
- GB SIO: Further fix bidirectional transfer starting
|
||||||
|
- GBA: Fix resetting key IRQ state (fixes mgba.io/i/2716)
|
||||||
|
- GBA Video: Ignore disabled backgrounds as OBJ blend target (fixes mgba.io/i/2489)
|
||||||
|
Other fixes:
|
||||||
|
- GBA: Fix forceskip BIOS logic for multiboot ROMs (fixes mgba.io/i/2753)
|
||||||
|
- GBA Cheats: Fix issues detecting unencrypted cheats (fixes mgba.io/i/2724)
|
||||||
|
- Qt: Manually split filename to avoid overzealous splitting (fixes mgba.io/i/2681)
|
||||||
|
- Qt: Fix scanning specific e-Reader dotcodes (fixes mgba.io/i/2693)
|
||||||
|
- Qt: Don't re-enable sync if GBA link modes aren't the same (fixes mgba.io/i/2044)
|
||||||
|
- Qt: Improve handling of multiplayer syncing (fixes mgba.io/i/2720)
|
||||||
|
- Qt: Fix initializing update revision info
|
||||||
|
- Qt: Redo stable branch detection heuristic (fixes mgba.io/i/2679)
|
||||||
|
- Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685)
|
||||||
|
- VFS: Fix minizip write returning 0 on success instead of size
|
||||||
|
Misc:
|
||||||
|
- macOS: Add category to plist (closes mgba.io/i/2691)
|
||||||
|
- macOS: Fix modern build with libepoxy (fixes mgba.io/i/2700)
|
||||||
|
- Qt: Keep track of current palette preset name (fixes mgba.io/i/2680)
|
||||||
|
- Qt: Move OpenGL proxy onto its own thread (fixes mgba.io/i/2493)
|
||||||
|
|
||||||
0.10.0: (2022-10-11)
|
0.10.0: (2022-10-11)
|
||||||
Features:
|
Features:
|
||||||
- Preliminary Lua scripting support
|
- Preliminary Lua scripting support
|
||||||
|
@ -6,7 +35,7 @@ Features:
|
||||||
- Tool for converting scanned pictures of e-Reader cards to raw dotcode data
|
- Tool for converting scanned pictures of e-Reader cards to raw dotcode data
|
||||||
- Options for muting when inactive, minimized, or for different players in multiplayer
|
- Options for muting when inactive, minimized, or for different players in multiplayer
|
||||||
- Cheat code support in homebrew ports
|
- Cheat code support in homebrew ports
|
||||||
- Acclerometer and gyro support for controllers on PC
|
- Accelerometer and gyro support for controllers on PC
|
||||||
- Support for combo "Super Game Boy Color" SGB + GBC ROM hacks
|
- Support for combo "Super Game Boy Color" SGB + GBC ROM hacks
|
||||||
- Improved support for HuC-3 mapper, including RTC
|
- Improved support for HuC-3 mapper, including RTC
|
||||||
- Support for 64 kiB SRAM saves used in some bootlegs
|
- Support for 64 kiB SRAM saves used in some bootlegs
|
||||||
|
@ -20,7 +49,7 @@ Emulation fixes:
|
||||||
- ARM7: Fix unsigned multiply timing
|
- ARM7: Fix unsigned multiply timing
|
||||||
- GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378)
|
- GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378)
|
||||||
- GB: Fix HALT breaking M-cycle alignment (fixes mgba.io/i/250)
|
- GB: Fix HALT breaking M-cycle alignment (fixes mgba.io/i/250)
|
||||||
- GB Audio: Fix channel 1/2 reseting edge cases (fixes mgba.io/i/1925)
|
- GB Audio: Fix channel 1/2 resetting edge cases (fixes mgba.io/i/1925)
|
||||||
- GB Audio: Properly apply per-model audio differences
|
- GB Audio: Properly apply per-model audio differences
|
||||||
- GB Audio: Revamp channel rendering
|
- GB Audio: Revamp channel rendering
|
||||||
- GB Audio: Fix APU re-enable timing glitch
|
- GB Audio: Fix APU re-enable timing glitch
|
||||||
|
@ -130,7 +159,7 @@ Emulation fixes:
|
||||||
Other fixes:
|
Other fixes:
|
||||||
- ARM Decoder: Fix decoding of lsl r0 (fixes mgba.io/i/2349)
|
- ARM Decoder: Fix decoding of lsl r0 (fixes mgba.io/i/2349)
|
||||||
- FFmpeg: Don't attempt to use YUV 4:2:0 for lossless videos (fixes mgba.io/i/2084)
|
- FFmpeg: Don't attempt to use YUV 4:2:0 for lossless videos (fixes mgba.io/i/2084)
|
||||||
- GB Video: Fix memory leak when reseting SGB games
|
- GB Video: Fix memory leak when resetting SGB games
|
||||||
- GBA: Fix out of bounds ROM accesses on patched ROMs smaller than 32 MiB
|
- GBA: Fix out of bounds ROM accesses on patched ROMs smaller than 32 MiB
|
||||||
- GBA: Fix maximum tile ID in caching for 256-color modes
|
- GBA: Fix maximum tile ID in caching for 256-color modes
|
||||||
- GBA Video: Fix cache updating with proxy and GL renderers
|
- GBA Video: Fix cache updating with proxy and GL renderers
|
||||||
|
@ -241,7 +270,7 @@ Emulation fixes:
|
||||||
- GBA BIOS: Implement dummy sound driver calls
|
- GBA BIOS: Implement dummy sound driver calls
|
||||||
- GBA BIOS: Improve HLE BIOS timing
|
- GBA BIOS: Improve HLE BIOS timing
|
||||||
- GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808)
|
- GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808)
|
||||||
- GBA BIOS: Make HLE BIOS calls interruptable (fixes mgba.io/i/1711 and mgba.io/i/1823)
|
- GBA BIOS: Make HLE BIOS calls interruptible (fixes mgba.io/i/1711 and mgba.io/i/1823)
|
||||||
- GBA BIOS: Fix invalid decompression bounds checking
|
- GBA BIOS: Fix invalid decompression bounds checking
|
||||||
- GBA DMA: Linger last DMA on bus (fixes mgba.io/i/301 and mgba.io/i/1320)
|
- GBA DMA: Linger last DMA on bus (fixes mgba.io/i/301 and mgba.io/i/1320)
|
||||||
- GBA DMA: Fix ordering and timing of overlapping DMAs
|
- GBA DMA: Fix ordering and timing of overlapping DMAs
|
||||||
|
@ -257,7 +286,7 @@ Emulation fixes:
|
||||||
- GBA Serialize: Fix alignment check when loading states
|
- GBA Serialize: Fix alignment check when loading states
|
||||||
- GBA SIO: Fix copying Normal mode transfer values
|
- GBA SIO: Fix copying Normal mode transfer values
|
||||||
- GBA SIO: Fix Normal mode being totally broken (fixes mgba.io/i/1800)
|
- GBA SIO: Fix Normal mode being totally broken (fixes mgba.io/i/1800)
|
||||||
- GBA SIO: Fix deseralizing SIO registers
|
- GBA SIO: Fix deserializing SIO registers
|
||||||
- GBA SIO: Fix hanging on starting a second multiplayer window (fixes mgba.io/i/854)
|
- GBA SIO: Fix hanging on starting a second multiplayer window (fixes mgba.io/i/854)
|
||||||
- GBA SIO: Fix Normal mode transfer start timing (fixes mgba.io/i/425)
|
- GBA SIO: Fix Normal mode transfer start timing (fixes mgba.io/i/425)
|
||||||
- GBA Timers: Fix toggling timer cascading while timer is active (fixes mgba.io/i/2043)
|
- GBA Timers: Fix toggling timer cascading while timer is active (fixes mgba.io/i/2043)
|
||||||
|
|
|
@ -718,8 +718,12 @@ if (USE_LZMA)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_EPOXY)
|
if(USE_EPOXY)
|
||||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gles2.c)
|
if(NOT APPLE OR NOT MACOSX_SDK VERSION_GREATER 10.14)
|
||||||
list(APPEND FEATURE_DEFINES BUILD_GL BUILD_GLES2 BUILD_GLES3)
|
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c)
|
||||||
|
list(APPEND FEATURE_DEFINES BUILD_GL)
|
||||||
|
endif()
|
||||||
|
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gles2.c)
|
||||||
|
list(APPEND FEATURE_DEFINES BUILD_GLES2 BUILD_GLES3)
|
||||||
list(APPEND FEATURES EPOXY)
|
list(APPEND FEATURES EPOXY)
|
||||||
include_directories(AFTER ${EPOXY_INCLUDE_DIRS})
|
include_directories(AFTER ${EPOXY_INCLUDE_DIRS})
|
||||||
link_directories(${EPOXY_LIBRARY_DIRS})
|
link_directories(${EPOXY_LIBRARY_DIRS})
|
||||||
|
|
|
@ -50,6 +50,7 @@ The following mappers are fully supported:
|
||||||
- MBC2
|
- MBC2
|
||||||
- MBC3
|
- MBC3
|
||||||
- MBC3+RTC
|
- MBC3+RTC
|
||||||
|
- MBC30
|
||||||
- MBC5
|
- MBC5
|
||||||
- MBC5+Rumble
|
- MBC5+Rumble
|
||||||
- MBC7
|
- MBC7
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
[testinfo]
|
||||||
|
skip=10
|
||||||
|
frames=1
|
Binary file not shown.
|
@ -56,5 +56,7 @@
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.games</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -412,6 +412,13 @@ local gameRubyEn = Generation3En:new{
|
||||||
_speciesNameTable=0x1f716c,
|
_speciesNameTable=0x1f716c,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local gameRubyEnR1 = Generation3En:new{
|
||||||
|
name="Ruby (USA)",
|
||||||
|
_party=0x3004360,
|
||||||
|
_partyCount=0x3004350,
|
||||||
|
_speciesNameTable=0x1f7184,
|
||||||
|
}
|
||||||
|
|
||||||
local gameSapphireEn = Generation3En:new{
|
local gameSapphireEn = Generation3En:new{
|
||||||
name="Sapphire (USA)",
|
name="Sapphire (USA)",
|
||||||
_party=0x3004360,
|
_party=0x3004360,
|
||||||
|
@ -419,6 +426,13 @@ local gameSapphireEn = Generation3En:new{
|
||||||
_speciesNameTable=0x1f70fc,
|
_speciesNameTable=0x1f70fc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local gameSapphireEnR1 = Generation3En:new{
|
||||||
|
name="Sapphire (USA)",
|
||||||
|
_party=0x3004360,
|
||||||
|
_partyCount=0x3004350,
|
||||||
|
_speciesNameTable=0x1f7114,
|
||||||
|
}
|
||||||
|
|
||||||
local gameEmeraldEn = Generation3En:new{
|
local gameEmeraldEn = Generation3En:new{
|
||||||
name="Emerald (USA)",
|
name="Emerald (USA)",
|
||||||
_party=0x20244ec,
|
_party=0x20244ec,
|
||||||
|
@ -471,6 +485,10 @@ gameCrc32 = {
|
||||||
[0x7d527d62] = gameYellowEn,
|
[0x7d527d62] = gameYellowEn,
|
||||||
[0x84ee4776] = gameFireRedEnR1,
|
[0x84ee4776] = gameFireRedEnR1,
|
||||||
[0xdaffecec] = gameLeafGreenEnR1,
|
[0xdaffecec] = gameLeafGreenEnR1,
|
||||||
|
[0x61641576] = gameRubyEnR1, -- Rev 1
|
||||||
|
[0xaeac73e6] = gameRubyEnR1, -- Rev 2
|
||||||
|
[0xbafedae5] = gameSapphireEnR1, -- Rev 1
|
||||||
|
[0x9cc4410e] = gameSapphireEnR1, -- Rev 2
|
||||||
}
|
}
|
||||||
|
|
||||||
function printPartyStatus(game, buffer)
|
function printPartyStatus(game, buffer)
|
||||||
|
|
|
@ -78,14 +78,7 @@ static void _updateMatch(const char* key, const char* value, void* user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const char* item = &key[dotLoc + 1];
|
const char* item = &key[dotLoc + 1];
|
||||||
|
_updateUpdate(match->out, item, value);
|
||||||
struct Table* out = user;
|
|
||||||
struct mUpdate* update = HashTableLookup(out, match->channel);
|
|
||||||
if (!update) {
|
|
||||||
update = calloc(1, sizeof(*update));
|
|
||||||
HashTableInsert(out, match->channel, update);
|
|
||||||
}
|
|
||||||
_updateUpdate(update, item, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mUpdaterInit(struct mUpdaterContext* context, const char* manifest) {
|
bool mUpdaterInit(struct mUpdaterContext* context, const char* manifest) {
|
||||||
|
|
|
@ -269,6 +269,29 @@ void GBAudioWriteNR31(struct GBAudio* audio, uint8_t value) {
|
||||||
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) {
|
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) {
|
||||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
||||||
audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value);
|
audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value);
|
||||||
|
|
||||||
|
audio->ch3.sample = audio->ch3.wavedata8[audio->ch3.window >> 1];
|
||||||
|
if (!(audio->ch3.window & 1)) {
|
||||||
|
audio->ch3.sample >>= 4;
|
||||||
|
}
|
||||||
|
audio->ch3.sample &= 0xF;
|
||||||
|
int volume;
|
||||||
|
switch (audio->ch3.volume) {
|
||||||
|
case 0:
|
||||||
|
volume = 4;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
volume = 0;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
volume = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case 3:
|
||||||
|
volume = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
audio->ch3.sample >>= volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) {
|
void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) {
|
||||||
|
@ -377,13 +400,13 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAudioWriteNR50(struct GBAudio* audio, uint8_t value) {
|
void GBAudioWriteNR50(struct GBAudio* audio, uint8_t value) {
|
||||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
|
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF);
|
||||||
audio->volumeRight = GBRegisterNR50GetVolumeRight(value);
|
audio->volumeRight = GBRegisterNR50GetVolumeRight(value);
|
||||||
audio->volumeLeft = GBRegisterNR50GetVolumeLeft(value);
|
audio->volumeLeft = GBRegisterNR50GetVolumeLeft(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t value) {
|
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t value) {
|
||||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
|
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF);
|
||||||
audio->ch1Right = GBRegisterNR51GetCh1Right(value);
|
audio->ch1Right = GBRegisterNR51GetCh1Right(value);
|
||||||
audio->ch2Right = GBRegisterNR51GetCh2Right(value);
|
audio->ch2Right = GBRegisterNR51GetCh2Right(value);
|
||||||
audio->ch3Right = GBRegisterNR51GetCh3Right(value);
|
audio->ch3Right = GBRegisterNR51GetCh3Right(value);
|
||||||
|
@ -476,11 +499,11 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
|
||||||
if (!audio->enable) {
|
if (!audio->enable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (audio->p && channels != 0xF && timestamp - audio->lastSample > (int) (SAMPLE_INTERVAL * audio->timingFactor)) {
|
if (audio->p && channels != 0x1F && timestamp - audio->lastSample > (int) (SAMPLE_INTERVAL * audio->timingFactor)) {
|
||||||
GBAudioSample(audio, timestamp);
|
GBAudioSample(audio, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audio->playingCh1 && (channels & 0x1)) {
|
if (audio->playingCh1 && (channels & 0x1) && audio->ch1.envelope.dead != 2) {
|
||||||
int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor;
|
int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor;
|
||||||
int32_t diff = timestamp - audio->ch1.lastUpdate;
|
int32_t diff = timestamp - audio->ch1.lastUpdate;
|
||||||
if (diff >= period) {
|
if (diff >= period) {
|
||||||
|
@ -490,7 +513,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
|
||||||
_updateSquareSample(&audio->ch1);
|
_updateSquareSample(&audio->ch1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (audio->playingCh2 && (channels & 0x2)) {
|
if (audio->playingCh2 && (channels & 0x2) && audio->ch2.envelope.dead != 2) {
|
||||||
int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor;
|
int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor;
|
||||||
int32_t diff = timestamp - audio->ch2.lastUpdate;
|
int32_t diff = timestamp - audio->ch2.lastUpdate;
|
||||||
if (diff >= period) {
|
if (diff >= period) {
|
||||||
|
@ -756,7 +779,7 @@ void GBAudioSample(struct GBAudio* audio, int32_t timestamp) {
|
||||||
for (sample = audio->sampleIndex; timestamp >= interval && sample < GB_MAX_SAMPLES; ++sample, timestamp -= interval) {
|
for (sample = audio->sampleIndex; timestamp >= interval && sample < GB_MAX_SAMPLES; ++sample, timestamp -= interval) {
|
||||||
int16_t sampleLeft = 0;
|
int16_t sampleLeft = 0;
|
||||||
int16_t sampleRight = 0;
|
int16_t sampleRight = 0;
|
||||||
GBAudioRun(audio, sample * interval + audio->lastSample, 0xF);
|
GBAudioRun(audio, sample * interval + audio->lastSample, 0x1F);
|
||||||
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
|
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
|
||||||
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
|
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
|
||||||
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
|
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
|
||||||
|
@ -863,7 +886,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudi
|
||||||
envelope->currentVolume &= 0xF;
|
envelope->currentVolume &= 0xF;
|
||||||
}
|
}
|
||||||
_updateEnvelopeDead(envelope);
|
_updateEnvelopeDead(envelope);
|
||||||
return (envelope->initialVolume || envelope->direction) && envelope->dead != 2;
|
return envelope->initialVolume || envelope->direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
|
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
|
||||||
|
|
21
src/gb/gb.c
21
src/gb/gb.c
|
@ -596,13 +596,13 @@ void GBReset(struct SM83Core* cpu) {
|
||||||
GBVideoReset(&gb->video);
|
GBVideoReset(&gb->video);
|
||||||
GBTimerReset(&gb->timer);
|
GBTimerReset(&gb->timer);
|
||||||
GBIOReset(gb);
|
GBIOReset(gb);
|
||||||
|
GBAudioReset(&gb->audio);
|
||||||
if (!gb->biosVf && gb->memory.rom) {
|
if (!gb->biosVf && gb->memory.rom) {
|
||||||
GBSkipBIOS(gb);
|
GBSkipBIOS(gb);
|
||||||
} else {
|
} else {
|
||||||
mTimingSchedule(&gb->timing, &gb->timer.event, 0);
|
mTimingSchedule(&gb->timing, &gb->timer.event, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
GBAudioReset(&gb->audio);
|
|
||||||
GBSIOReset(&gb->sio);
|
GBSIOReset(&gb->sio);
|
||||||
|
|
||||||
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
||||||
|
@ -744,6 +744,25 @@ void GBSkipBIOS(struct GB* gb) {
|
||||||
GBUnmapBIOS(gb);
|
GBUnmapBIOS(gb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GBIOWrite(gb, GB_REG_NR52, 0xF1);
|
||||||
|
GBIOWrite(gb, GB_REG_NR14, 0x3F);
|
||||||
|
GBIOWrite(gb, GB_REG_NR10, 0x80);
|
||||||
|
GBIOWrite(gb, GB_REG_NR11, 0xBF);
|
||||||
|
GBIOWrite(gb, GB_REG_NR12, 0xF3);
|
||||||
|
GBIOWrite(gb, GB_REG_NR13, 0xF3);
|
||||||
|
GBIOWrite(gb, GB_REG_NR24, 0x3F);
|
||||||
|
GBIOWrite(gb, GB_REG_NR21, 0x3F);
|
||||||
|
GBIOWrite(gb, GB_REG_NR22, 0x00);
|
||||||
|
GBIOWrite(gb, GB_REG_NR34, 0x3F);
|
||||||
|
GBIOWrite(gb, GB_REG_NR30, 0x7F);
|
||||||
|
GBIOWrite(gb, GB_REG_NR31, 0xFF);
|
||||||
|
GBIOWrite(gb, GB_REG_NR32, 0x9F);
|
||||||
|
GBIOWrite(gb, GB_REG_NR44, 0x3F);
|
||||||
|
GBIOWrite(gb, GB_REG_NR41, 0xFF);
|
||||||
|
GBIOWrite(gb, GB_REG_NR42, 0x00);
|
||||||
|
GBIOWrite(gb, GB_REG_NR43, 0x00);
|
||||||
|
GBIOWrite(gb, GB_REG_NR50, 0x77);
|
||||||
|
GBIOWrite(gb, GB_REG_NR51, 0xF3);
|
||||||
GBIOWrite(gb, GB_REG_LCDC, 0x91);
|
GBIOWrite(gb, GB_REG_LCDC, 0x91);
|
||||||
gb->memory.io[GB_REG_BANK] = 0x1;
|
gb->memory.io[GB_REG_BANK] = 0x1;
|
||||||
GBVideoSkipBIOS(&gb->video);
|
GBVideoSkipBIOS(&gb->video);
|
||||||
|
|
32
src/gb/io.c
32
src/gb/io.c
|
@ -162,36 +162,7 @@ void GBIOReset(struct GB* gb) {
|
||||||
GBIOWrite(gb, GB_REG_TMA, 0);
|
GBIOWrite(gb, GB_REG_TMA, 0);
|
||||||
GBIOWrite(gb, GB_REG_TAC, 0);
|
GBIOWrite(gb, GB_REG_TAC, 0);
|
||||||
GBIOWrite(gb, GB_REG_IF, 1);
|
GBIOWrite(gb, GB_REG_IF, 1);
|
||||||
gb->audio.playingCh1 = false;
|
|
||||||
gb->audio.playingCh2 = false;
|
|
||||||
gb->audio.playingCh3 = false;
|
|
||||||
gb->audio.playingCh4 = false;
|
|
||||||
GBIOWrite(gb, GB_REG_NR52, 0xF1);
|
|
||||||
GBIOWrite(gb, GB_REG_NR14, 0x3F);
|
|
||||||
GBIOWrite(gb, GB_REG_NR10, 0x80);
|
|
||||||
GBIOWrite(gb, GB_REG_NR11, 0xBF);
|
|
||||||
GBIOWrite(gb, GB_REG_NR12, 0xF3);
|
|
||||||
GBIOWrite(gb, GB_REG_NR13, 0xF3);
|
|
||||||
GBIOWrite(gb, GB_REG_NR24, 0x3F);
|
|
||||||
GBIOWrite(gb, GB_REG_NR21, 0x3F);
|
|
||||||
GBIOWrite(gb, GB_REG_NR22, 0x00);
|
|
||||||
GBIOWrite(gb, GB_REG_NR34, 0x3F);
|
|
||||||
GBIOWrite(gb, GB_REG_NR30, 0x7F);
|
|
||||||
GBIOWrite(gb, GB_REG_NR31, 0xFF);
|
|
||||||
GBIOWrite(gb, GB_REG_NR32, 0x9F);
|
|
||||||
GBIOWrite(gb, GB_REG_NR44, 0x3F);
|
|
||||||
GBIOWrite(gb, GB_REG_NR41, 0xFF);
|
|
||||||
GBIOWrite(gb, GB_REG_NR42, 0x00);
|
|
||||||
GBIOWrite(gb, GB_REG_NR43, 0x00);
|
|
||||||
GBIOWrite(gb, GB_REG_NR50, 0x77);
|
|
||||||
GBIOWrite(gb, GB_REG_NR51, 0xF3);
|
|
||||||
if (!gb->biosVf) {
|
|
||||||
GBIOWrite(gb, GB_REG_LCDC, 0x91);
|
|
||||||
gb->memory.io[GB_REG_BANK] = 1;
|
|
||||||
} else {
|
|
||||||
GBIOWrite(gb, GB_REG_LCDC, 0x00);
|
GBIOWrite(gb, GB_REG_LCDC, 0x00);
|
||||||
gb->memory.io[GB_REG_BANK] = 0xFF;
|
|
||||||
}
|
|
||||||
GBIOWrite(gb, GB_REG_SCY, 0x00);
|
GBIOWrite(gb, GB_REG_SCY, 0x00);
|
||||||
GBIOWrite(gb, GB_REG_SCX, 0x00);
|
GBIOWrite(gb, GB_REG_SCX, 0x00);
|
||||||
GBIOWrite(gb, GB_REG_LYC, 0x00);
|
GBIOWrite(gb, GB_REG_LYC, 0x00);
|
||||||
|
@ -203,6 +174,7 @@ void GBIOReset(struct GB* gb) {
|
||||||
}
|
}
|
||||||
GBIOWrite(gb, GB_REG_WY, 0x00);
|
GBIOWrite(gb, GB_REG_WY, 0x00);
|
||||||
GBIOWrite(gb, GB_REG_WX, 0x00);
|
GBIOWrite(gb, GB_REG_WX, 0x00);
|
||||||
|
gb->memory.io[GB_REG_BANK] = 0xFF;
|
||||||
if (gb->model & GB_MODEL_CGB) {
|
if (gb->model & GB_MODEL_CGB) {
|
||||||
GBIOWrite(gb, GB_REG_KEY0, 0);
|
GBIOWrite(gb, GB_REG_KEY0, 0);
|
||||||
GBIOWrite(gb, GB_REG_JOYP, 0xFF);
|
GBIOWrite(gb, GB_REG_JOYP, 0xFF);
|
||||||
|
@ -754,7 +726,7 @@ void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
||||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_SCX, state->io[GB_REG_SCX]);
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_SCX, state->io[GB_REG_SCX]);
|
||||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WY, state->io[GB_REG_WY]);
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WY, state->io[GB_REG_WY]);
|
||||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WX, state->io[GB_REG_WX]);
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WX, state->io[GB_REG_WX]);
|
||||||
if (gb->model & GB_MODEL_SGB) {
|
if (gb->model == GB_MODEL_SGB) {
|
||||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_BGP, state->io[GB_REG_BGP]);
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_BGP, state->io[GB_REG_BGP]);
|
||||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP0, state->io[GB_REG_OBP0]);
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP0, state->io[GB_REG_OBP0]);
|
||||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP1, state->io[GB_REG_OBP1]);
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP1, state->io[GB_REG_OBP1]);
|
||||||
|
|
|
@ -558,7 +558,7 @@ uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) {
|
||||||
gb->memory.hdmaDest |= 0x8000;
|
gb->memory.hdmaDest |= 0x8000;
|
||||||
bool wasHdma = gb->memory.isHdma;
|
bool wasHdma = gb->memory.isHdma;
|
||||||
gb->memory.isHdma = value & 0x80;
|
gb->memory.isHdma = value & 0x80;
|
||||||
if ((!wasHdma && !gb->memory.isHdma) || (GBRegisterLCDCIsEnable(gb->memory.io[GB_REG_LCDC]) && gb->video.mode == 0)) {
|
if ((!wasHdma && !gb->memory.isHdma) || gb->video.mode == 0) {
|
||||||
if (gb->memory.isHdma) {
|
if (gb->memory.isHdma) {
|
||||||
gb->memory.hdmaRemaining = 0x10;
|
gb->memory.hdmaRemaining = 0x10;
|
||||||
} else {
|
} else {
|
||||||
|
@ -566,8 +566,6 @@ uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) {
|
||||||
}
|
}
|
||||||
gb->cpuBlocked = true;
|
gb->cpuBlocked = true;
|
||||||
mTimingSchedule(&gb->timing, &gb->memory.hdmaEvent, 0);
|
mTimingSchedule(&gb->timing, &gb->memory.hdmaEvent, 0);
|
||||||
} else if (gb->memory.isHdma && !GBRegisterLCDCIsEnable(gb->memory.io[GB_REG_LCDC])) {
|
|
||||||
return 0x80 | ((value + 1) & 0x7F);
|
|
||||||
}
|
}
|
||||||
return value & 0x7F;
|
return value & 0x7F;
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tell the other GBs they can continue up to where we were
|
// Tell the other GBs they can continue up to where we were
|
||||||
node->p->d.addCycles(&node->p->d, node->id, node->eventDiff);
|
node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
node->phase = node->p->d.transferActive;
|
node->phase = node->p->d.transferActive;
|
||||||
#endif
|
#endif
|
||||||
|
@ -169,26 +169,28 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
||||||
|
|
||||||
static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
||||||
enum mLockstepPhase transferActive;
|
enum mLockstepPhase transferActive;
|
||||||
|
int id;
|
||||||
|
|
||||||
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
||||||
|
ATOMIC_LOAD(id, node->id);
|
||||||
|
|
||||||
bool signal = false;
|
bool signal = false;
|
||||||
switch (transferActive) {
|
switch (transferActive) {
|
||||||
case TRANSFER_IDLE:
|
case TRANSFER_IDLE:
|
||||||
node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
|
node->p->d.addCycles(&node->p->d, id, LOCKSTEP_INCREMENT);
|
||||||
break;
|
break;
|
||||||
case TRANSFER_STARTING:
|
case TRANSFER_STARTING:
|
||||||
case TRANSFER_FINISHING:
|
case TRANSFER_FINISHING:
|
||||||
break;
|
break;
|
||||||
case TRANSFER_STARTED:
|
case TRANSFER_STARTED:
|
||||||
if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
|
if (node->p->d.unusedCycles(&node->p->d, id) > node->eventDiff) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
node->transferFinished = false;
|
node->transferFinished = false;
|
||||||
signal = true;
|
signal = true;
|
||||||
break;
|
break;
|
||||||
case TRANSFER_FINISHED:
|
case TRANSFER_FINISHED:
|
||||||
if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
|
if (node->p->d.unusedCycles(&node->p->d, id) > node->eventDiff) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_finishTransfer(node);
|
_finishTransfer(node);
|
||||||
|
@ -199,7 +201,7 @@ static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
||||||
node->phase = node->p->d.transferActive;
|
node->phase = node->p->d.transferActive;
|
||||||
#endif
|
#endif
|
||||||
if (signal) {
|
if (signal) {
|
||||||
node->p->d.signal(&node->p->d, 1 << node->id);
|
node->p->d.signal(&node->p->d, 1 << id);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -215,11 +217,13 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
||||||
int32_t cycles = 0;
|
int32_t cycles = 0;
|
||||||
node->nextEvent -= cyclesLate;
|
node->nextEvent -= cyclesLate;
|
||||||
if (node->nextEvent <= 0) {
|
if (node->nextEvent <= 0) {
|
||||||
if (!node->id) {
|
int id;
|
||||||
|
ATOMIC_LOAD(id, node->id);
|
||||||
|
if (!id) {
|
||||||
cycles = _masterUpdate(node);
|
cycles = _masterUpdate(node);
|
||||||
} else {
|
} else {
|
||||||
cycles = _slaveUpdate(node);
|
cycles = _slaveUpdate(node);
|
||||||
cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
|
cycles += node->p->d.useCycles(&node->p->d, id, node->eventDiff);
|
||||||
}
|
}
|
||||||
node->eventDiff = 0;
|
node->eventDiff = 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -240,7 +244,9 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
||||||
|
|
||||||
static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
|
static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
|
||||||
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
|
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
|
||||||
node->p->pendingSB[node->id] = value;
|
int id;
|
||||||
|
ATOMIC_LOAD(id, node->id);
|
||||||
|
node->p->pendingSB[id] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value) {
|
static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value) {
|
||||||
|
@ -252,11 +258,17 @@ static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t valu
|
||||||
mLockstepLock(&node->p->d);
|
mLockstepLock(&node->p->d);
|
||||||
bool claimed = false;
|
bool claimed = false;
|
||||||
if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) {
|
if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) {
|
||||||
if (node->id != 0) {
|
int id;
|
||||||
|
ATOMIC_LOAD(id, node->id);
|
||||||
|
if (id != 0) {
|
||||||
|
unsigned sb;
|
||||||
node->p->players[0]->id = 1;
|
node->p->players[0]->id = 1;
|
||||||
node->p->players[1] = node->p->players[0];
|
|
||||||
node->p->players[0] = node->p->players[1];
|
|
||||||
node->id = 0;
|
node->id = 0;
|
||||||
|
node->p->players[1] = node->p->players[0];
|
||||||
|
node->p->players[0] = node;
|
||||||
|
sb = node->p->pendingSB[0];
|
||||||
|
node->p->pendingSB[0] = node->p->pendingSB[1];
|
||||||
|
node->p->pendingSB[1] = sb;
|
||||||
}
|
}
|
||||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
||||||
ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);
|
ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);
|
||||||
|
|
|
@ -336,12 +336,14 @@ static int16_t _ArcTan(int32_t i, int32_t* r1, int32_t* r3, uint32_t* cycles) {
|
||||||
|
|
||||||
static int16_t _ArcTan2(int32_t x, int32_t y, int32_t* r1, uint32_t* cycles) {
|
static int16_t _ArcTan2(int32_t x, int32_t y, int32_t* r1, uint32_t* cycles) {
|
||||||
if (!y) {
|
if (!y) {
|
||||||
|
*cycles = 11;
|
||||||
if (x >= 0) {
|
if (x >= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0x8000;
|
return 0x8000;
|
||||||
}
|
}
|
||||||
if (!x) {
|
if (!x) {
|
||||||
|
*cycles = 11;
|
||||||
if (y >= 0) {
|
if (y >= 0) {
|
||||||
return 0x4000;
|
return 0x4000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,13 +126,13 @@ static bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_
|
||||||
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3);
|
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3);
|
||||||
}
|
}
|
||||||
|
|
||||||
rgsaP = GBACheatGameSharkProbability(op1, op1);
|
rgsaP = GBACheatGameSharkProbability(op1, op2);
|
||||||
if (rgsaP > maxProbability) {
|
if (rgsaP > maxProbability) {
|
||||||
maxProbability = rgsaP;
|
maxProbability = rgsaP;
|
||||||
GBACheatSetGameSharkVersion(set, GBA_GS_GSAV1_RAW);
|
GBACheatSetGameSharkVersion(set, GBA_GS_GSAV1_RAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
rparP = GBACheatProActionReplayProbability(op1, op1);
|
rparP = GBACheatProActionReplayProbability(op1, op2);
|
||||||
if (rparP > maxProbability) {
|
if (rparP > maxProbability) {
|
||||||
maxProbability = rparP;
|
maxProbability = rparP;
|
||||||
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3_RAW);
|
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3_RAW);
|
||||||
|
|
|
@ -700,8 +700,8 @@ static void _GBACoreReset(struct mCore* core) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ARMReset(core->cpu);
|
ARMReset(core->cpu);
|
||||||
bool forceSkip = gba->romVf && GBAIsMB(gba->romVf);
|
bool forceSkip = gba->mbVf || core->opts.skipBios;
|
||||||
if (!(forceSkip || core->opts.skipBios) && (gba->romVf || gba->memory.rom) && gba->pristineRomSize >= 0xA0 && gba->biosVf) {
|
if (!forceSkip && (gba->romVf || gba->memory.rom) && gba->pristineRomSize >= 0xA0 && gba->biosVf) {
|
||||||
uint32_t crc = doCrc32(&gba->memory.rom[1], 0x9C);
|
uint32_t crc = doCrc32(&gba->memory.rom[1], 0x9C);
|
||||||
if (crc != LOGO_CRC32) {
|
if (crc != LOGO_CRC32) {
|
||||||
mLOG(STATUS, WARN, "Invalid logo, skipping BIOS");
|
mLOG(STATUS, WARN, "Invalid logo, skipping BIOS");
|
||||||
|
@ -709,7 +709,7 @@ static void _GBACoreReset(struct mCore* core) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceSkip || (core->opts.skipBios && (gba->romVf || gba->memory.rom))) {
|
if (forceSkip) {
|
||||||
GBASkipBIOS(core->board);
|
GBASkipBIOS(core->board);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,7 @@ void GBAReset(struct ARMCore* cpu) {
|
||||||
gba->earlyExit = false;
|
gba->earlyExit = false;
|
||||||
gba->dmaPC = 0;
|
gba->dmaPC = 0;
|
||||||
gba->biosStall = 0;
|
gba->biosStall = 0;
|
||||||
|
gba->keysLast = 0x400;
|
||||||
if (gba->yankedRomSize) {
|
if (gba->yankedRomSize) {
|
||||||
gba->memory.romSize = gba->yankedRomSize;
|
gba->memory.romSize = gba->yankedRomSize;
|
||||||
gba->memory.romMask = toPow2(gba->memory.romSize) - 1;
|
gba->memory.romMask = toPow2(gba->memory.romSize) - 1;
|
||||||
|
@ -274,8 +275,10 @@ void GBASkipBIOS(struct GBA* gba) {
|
||||||
if (cpu->gprs[ARM_PC] == BASE_RESET + WORD_SIZE_ARM) {
|
if (cpu->gprs[ARM_PC] == BASE_RESET + WORD_SIZE_ARM) {
|
||||||
if (gba->memory.rom) {
|
if (gba->memory.rom) {
|
||||||
cpu->gprs[ARM_PC] = BASE_CART0;
|
cpu->gprs[ARM_PC] = BASE_CART0;
|
||||||
} else {
|
} else if (gba->memory.wram[0x30]) {
|
||||||
cpu->gprs[ARM_PC] = BASE_WORKING_RAM + 0xC0;
|
cpu->gprs[ARM_PC] = BASE_WORKING_RAM + 0xC0;
|
||||||
|
} else {
|
||||||
|
cpu->gprs[ARM_PC] = BASE_WORKING_RAM;
|
||||||
}
|
}
|
||||||
gba->video.vcount = 0x7E;
|
gba->video.vcount = 0x7E;
|
||||||
gba->memory.io[REG_VCOUNT >> 1] = 0x7E;
|
gba->memory.io[REG_VCOUNT >> 1] = 0x7E;
|
||||||
|
|
|
@ -664,11 +664,13 @@ static const char* const _finalize =
|
||||||
" if (((topFlags.y & 13) == 5 || topFlags.w > 0) && (bottomFlags.y & 2) == 2) {\n"
|
" if (((topFlags.y & 13) == 5 || topFlags.w > 0) && (bottomFlags.y & 2) == 2) {\n"
|
||||||
" topPixel.rgb *= float(topFlags.z) / 16.;\n"
|
" topPixel.rgb *= float(topFlags.z) / 16.;\n"
|
||||||
" topPixel.rgb += bottomPixel.rgb * float(windowFlags.y) / 16.;\n"
|
" topPixel.rgb += bottomPixel.rgb * float(windowFlags.y) / 16.;\n"
|
||||||
" } else if ((topFlags.y & 13) == 9) {\n"
|
" } else if (topFlags.w == 0) { \n"
|
||||||
|
" if ((topFlags.y & 13) == 9) {\n"
|
||||||
" topPixel.rgb += (1. - topPixel.rgb) * float(windowFlags.z) / 16.;\n"
|
" topPixel.rgb += (1. - topPixel.rgb) * float(windowFlags.z) / 16.;\n"
|
||||||
" } else if ((topFlags.y & 13) == 13) {\n"
|
" } else if ((topFlags.y & 13) == 13) {\n"
|
||||||
" topPixel.rgb -= topPixel.rgb * float(windowFlags.z) / 16.;\n"
|
" topPixel.rgb -= topPixel.rgb * float(windowFlags.z) / 16.;\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
|
" }\n"
|
||||||
" color = topPixel;\n"
|
" color = topPixel;\n"
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
|
|
|
@ -169,10 +169,10 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
||||||
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
||||||
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) {
|
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) {
|
||||||
int target2 = renderer->target2Bd;
|
int target2 = renderer->target2Bd;
|
||||||
target2 |= renderer->bg[0].target2;
|
target2 |= renderer->bg[0].target2 && renderer->bg[0].enabled;
|
||||||
target2 |= renderer->bg[1].target2;
|
target2 |= renderer->bg[1].target2 && renderer->bg[1].enabled;
|
||||||
target2 |= renderer->bg[2].target2;
|
target2 |= renderer->bg[2].target2 && renderer->bg[2].enabled;
|
||||||
target2 |= renderer->bg[3].target2;
|
target2 |= renderer->bg[3].target2 && renderer->bg[3].enabled;
|
||||||
if (target2) {
|
if (target2) {
|
||||||
renderer->forceTarget1 = true;
|
renderer->forceTarget1 = true;
|
||||||
flags |= FLAG_REBLEND;
|
flags |= FLAG_REBLEND;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#include "ApplicationUpdatePrompt.h"
|
#include "ApplicationUpdatePrompt.h"
|
||||||
#include "ConfigController.h"
|
#include "ConfigController.h"
|
||||||
|
@ -71,9 +72,10 @@ QStringList ApplicationUpdater::listChannels() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ApplicationUpdater::currentChannel() {
|
QString ApplicationUpdater::currentChannel() {
|
||||||
QLatin1String version(projectVersion);
|
QString version(projectVersion);
|
||||||
QLatin1String branch(gitBranch);
|
QString branch(gitBranch);
|
||||||
if (branch == QLatin1String("heads/") + version) {
|
QRegularExpression stable("^(?:(?:refs/)?(?:tags|heads)/)?[0-9]+\\.[0-9]+\\.[0-9]+$");
|
||||||
|
if (branch.contains(stable) || (branch == "(unknown)" && version.contains(stable))) {
|
||||||
return QLatin1String("stable");
|
return QLatin1String("stable");
|
||||||
} else {
|
} else {
|
||||||
return QLatin1String("dev");
|
return QLatin1String("dev");
|
||||||
|
@ -137,7 +139,15 @@ QUrl ApplicationUpdater::parseManifest(const QByteArray& manifest) {
|
||||||
QString ApplicationUpdater::destination() const {
|
QString ApplicationUpdater::destination() const {
|
||||||
QFileInfo path(updateInfo().url.path());
|
QFileInfo path(updateInfo().url.path());
|
||||||
QDir dir(ConfigController::configDir());
|
QDir dir(ConfigController::configDir());
|
||||||
return dir.filePath(QLatin1String("update.") + path.completeSuffix());
|
// QFileInfo::completeSuffix will eat all .'s in the filename...including
|
||||||
|
// ones in the version string, turning mGBA-1.0.0-win32.7z into
|
||||||
|
// 0.0-win32.7z instead of the intended .7z
|
||||||
|
// As a result, so we have to split out the complete suffix manually.
|
||||||
|
QString suffix(path.suffix());
|
||||||
|
if (path.completeBaseName().endsWith(".tar")) {
|
||||||
|
suffix = "tar." + suffix;
|
||||||
|
}
|
||||||
|
return dir.filePath(QLatin1String("update.") + suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* ApplicationUpdater::platform() {
|
const char* ApplicationUpdater::platform() {
|
||||||
|
@ -166,7 +176,8 @@ const char* ApplicationUpdater::platform() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationUpdater::UpdateInfo::UpdateInfo(const QString& prefix, const mUpdate* update)
|
ApplicationUpdater::UpdateInfo::UpdateInfo(const QString& prefix, const mUpdate* update)
|
||||||
: size(update->size)
|
: rev(-1)
|
||||||
|
, size(update->size)
|
||||||
, url(prefix + update->path)
|
, url(prefix + update->path)
|
||||||
{
|
{
|
||||||
if (update->rev > 0) {
|
if (update->rev > 0) {
|
||||||
|
|
|
@ -118,6 +118,7 @@ set(SOURCE_FILES
|
||||||
MessagePainter.cpp
|
MessagePainter.cpp
|
||||||
MultiplayerController.cpp
|
MultiplayerController.cpp
|
||||||
ObjView.cpp
|
ObjView.cpp
|
||||||
|
OpenGLBug.cpp
|
||||||
OverrideView.cpp
|
OverrideView.cpp
|
||||||
PaletteView.cpp
|
PaletteView.cpp
|
||||||
PlacementControl.cpp
|
PlacementControl.cpp
|
||||||
|
|
|
@ -914,7 +914,10 @@ void CoreController::scanCard(const QString& path) {
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_eReaderData = file.read(2912);
|
QByteArray eReaderData = file.read(2912);
|
||||||
|
if (eReaderData.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
file.seek(0);
|
file.seek(0);
|
||||||
QStringList lines;
|
QStringList lines;
|
||||||
|
@ -936,6 +939,7 @@ void CoreController::scanCard(const QString& path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scanCards(lines);
|
scanCards(lines);
|
||||||
|
m_eReaderData = eReaderData;
|
||||||
} else if (image.size() == QSize(989, 44) || image.size() == QSize(639, 44)) {
|
} else if (image.size() == QSize(989, 44) || image.size() == QSize(639, 44)) {
|
||||||
const uchar* bits = image.constBits();
|
const uchar* bits = image.constBits();
|
||||||
size_t size;
|
size_t size;
|
||||||
|
|
|
@ -44,6 +44,8 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core;
|
||||||
#define OVERHEAD_NSEC 300000
|
#define OVERHEAD_NSEC 300000
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "OpenGLBug.h"
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
|
||||||
QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
|
QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
|
||||||
|
@ -175,6 +177,11 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
||||||
m_drawThread.setObjectName("Painter Thread");
|
m_drawThread.setObjectName("Painter Thread");
|
||||||
m_painter->setThread(&m_drawThread);
|
m_painter->setThread(&m_drawThread);
|
||||||
|
|
||||||
|
m_proxyThread.setObjectName("OpenGL Proxy Thread");
|
||||||
|
m_proxyContext = std::make_unique<QOpenGLContext>();
|
||||||
|
m_proxyContext->setFormat(format);
|
||||||
|
connect(m_painter.get(), &PainterGL::created, this, &DisplayGL::setupProxyThread);
|
||||||
|
|
||||||
connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
|
connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
|
||||||
connect(m_painter.get(), &PainterGL::started, this, [this] {
|
connect(m_painter.get(), &PainterGL::started, this, [this] {
|
||||||
m_hasStarted = true;
|
m_hasStarted = true;
|
||||||
|
@ -189,6 +196,11 @@ DisplayGL::~DisplayGL() {
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
|
QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
|
||||||
m_drawThread.exit();
|
m_drawThread.exit();
|
||||||
m_drawThread.wait();
|
m_drawThread.wait();
|
||||||
|
|
||||||
|
if (m_proxyThread.isRunning()) {
|
||||||
|
m_proxyThread.exit();
|
||||||
|
m_proxyThread.wait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DisplayGL::supportsShaders() const {
|
bool DisplayGL::supportsShaders() const {
|
||||||
|
@ -209,9 +221,6 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
||||||
m_painter->setContext(controller);
|
m_painter->setContext(controller);
|
||||||
m_painter->setMessagePainter(messagePainter());
|
m_painter->setMessagePainter(messagePainter());
|
||||||
m_context = controller;
|
m_context = controller;
|
||||||
if (videoProxy()) {
|
|
||||||
videoProxy()->moveToThread(&m_drawThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
lockAspectRatio(isAspectRatioLocked());
|
lockAspectRatio(isAspectRatioLocked());
|
||||||
lockIntegerScaling(isIntegerScalingLocked());
|
lockIntegerScaling(isIntegerScalingLocked());
|
||||||
|
@ -411,11 +420,34 @@ bool DisplayGL::shouldDisableUpdates() {
|
||||||
void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
|
void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
|
||||||
Display::setVideoProxy(proxy);
|
Display::setVideoProxy(proxy);
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
proxy->moveToThread(&m_drawThread);
|
proxy->moveToThread(&m_proxyThread);
|
||||||
}
|
}
|
||||||
m_painter->setVideoProxy(proxy);
|
m_painter->setVideoProxy(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DisplayGL::setupProxyThread() {
|
||||||
|
m_proxyContext->moveToThread(&m_proxyThread);
|
||||||
|
m_proxySurface.create();
|
||||||
|
connect(&m_proxyThread, &QThread::started, m_proxyContext.get(), [this]() {
|
||||||
|
m_proxyContext->setShareContext(m_painter->shareContext());
|
||||||
|
m_proxyContext->create();
|
||||||
|
m_proxyContext->makeCurrent(&m_proxySurface);
|
||||||
|
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||||
|
epoxy_handle_external_wglMakeCurrent();
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
connect(m_painter.get(), &PainterGL::texSwapped, m_proxyContext.get(), [this]() {
|
||||||
|
if (!m_context->hardwareAccelerated()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (videoProxy()) {
|
||||||
|
videoProxy()->processData();
|
||||||
|
}
|
||||||
|
m_painter->updateFramebufferHandle();
|
||||||
|
}, Qt::BlockingQueuedConnection);
|
||||||
|
m_proxyThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
int DisplayGL::framebufferHandle() {
|
int DisplayGL::framebufferHandle() {
|
||||||
return m_painter->glTex();
|
return m_painter->glTex();
|
||||||
}
|
}
|
||||||
|
@ -481,9 +513,15 @@ void PainterGL::create() {
|
||||||
|
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
if (m_supportsShaders) {
|
if (m_supportsShaders) {
|
||||||
|
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||||
gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
|
gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
|
||||||
mGLES2ContextCreate(gl2Backend);
|
mGLES2ContextCreate(gl2Backend);
|
||||||
m_backend = &gl2Backend->d;
|
m_backend = &gl2Backend->d;
|
||||||
|
fn->glGenTextures(m_bridgeTexes.size(), m_bridgeTexes.data());
|
||||||
|
for (auto tex : m_bridgeTexes) {
|
||||||
|
m_freeTex.enqueue(tex);
|
||||||
|
}
|
||||||
|
m_bridgeTexIn = m_freeTex.dequeue();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -503,10 +541,10 @@ void PainterGL::create() {
|
||||||
painter->makeCurrent();
|
painter->makeCurrent();
|
||||||
|
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
|
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
|
||||||
if (painter->m_widget && painter->supportsShaders()) {
|
if (painter->m_widget && painter->supportsShaders()) {
|
||||||
QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||||
fn->glFinish();
|
fn->glFinish();
|
||||||
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
|
|
||||||
painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]);
|
painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]);
|
||||||
painter->m_finalTexIdx ^= 1;
|
painter->m_finalTexIdx ^= 1;
|
||||||
gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx];
|
gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx];
|
||||||
|
@ -540,6 +578,9 @@ void PainterGL::create() {
|
||||||
m_backend->filter = false;
|
m_backend->filter = false;
|
||||||
m_backend->lockAspectRatio = false;
|
m_backend->lockAspectRatio = false;
|
||||||
m_backend->interframeBlending = false;
|
m_backend->interframeBlending = false;
|
||||||
|
m_gl->doneCurrent();
|
||||||
|
|
||||||
|
emit created();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::destroy() {
|
void PainterGL::destroy() {
|
||||||
|
@ -548,9 +589,11 @@ void PainterGL::destroy() {
|
||||||
}
|
}
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
|
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||||
if (m_shader.passes) {
|
if (m_shader.passes) {
|
||||||
mGLES2ShaderFree(&m_shader);
|
mGLES2ShaderFree(&m_shader);
|
||||||
}
|
}
|
||||||
|
fn->glDeleteTextures(m_bridgeTexes.size(), m_bridgeTexes.data());
|
||||||
#endif
|
#endif
|
||||||
m_backend->deinit(m_backend);
|
m_backend->deinit(m_backend);
|
||||||
m_gl->doneCurrent();
|
m_gl->doneCurrent();
|
||||||
|
@ -627,8 +670,20 @@ void PainterGL::filter(bool filter) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS
|
||||||
|
#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
|
||||||
|
#endif
|
||||||
|
|
||||||
void PainterGL::start() {
|
void PainterGL::start() {
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
|
#if defined(BUILD_GLES3) && !defined(Q_OS_MAC)
|
||||||
|
if (glContextHasBug(OpenGLBug::GLTHREAD_BLOCKS_SWAP)) {
|
||||||
|
// Suggested on Discord as a way to strongly hint that glthread should be disabled
|
||||||
|
// See https://gitlab.freedesktop.org/mesa/mesa/-/issues/8035
|
||||||
|
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||||
|
fn->glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
if (m_supportsShaders && m_shader.passes) {
|
if (m_supportsShaders && m_shader.passes) {
|
||||||
|
@ -636,6 +691,7 @@ void PainterGL::start() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
resizeContext();
|
resizeContext();
|
||||||
|
m_context->addFrameAction(std::bind(&PainterGL::swapTex, this));
|
||||||
|
|
||||||
m_buffer = nullptr;
|
m_buffer = nullptr;
|
||||||
m_active = true;
|
m_active = true;
|
||||||
|
@ -644,7 +700,7 @@ void PainterGL::start() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::draw() {
|
void PainterGL::draw() {
|
||||||
if (!m_started || m_queue.isEmpty()) {
|
if (!m_started || (m_queue.isEmpty() && m_queueTex.isEmpty())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,7 +727,7 @@ void PainterGL::draw() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dequeue();
|
dequeue();
|
||||||
bool forceRedraw = !m_videoProxy;
|
bool forceRedraw = true;
|
||||||
if (!m_delayTimer.isValid()) {
|
if (!m_delayTimer.isValid()) {
|
||||||
m_delayTimer.start();
|
m_delayTimer.start();
|
||||||
} else {
|
} else {
|
||||||
|
@ -725,11 +781,6 @@ void PainterGL::doStop() {
|
||||||
m_videoProxy->processData();
|
m_videoProxy->processData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_videoProxy) {
|
|
||||||
m_videoProxy->reset();
|
|
||||||
m_videoProxy->moveToThread(m_window->thread());
|
|
||||||
m_videoProxy.reset();
|
|
||||||
}
|
|
||||||
m_backend->clear(m_backend);
|
m_backend->clear(m_backend);
|
||||||
m_backend->swap(m_backend);
|
m_backend->swap(m_backend);
|
||||||
}
|
}
|
||||||
|
@ -759,9 +810,11 @@ void PainterGL::performDraw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::enqueue(const uint32_t* backing) {
|
void PainterGL::enqueue(const uint32_t* backing) {
|
||||||
|
if (!backing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
uint32_t* buffer = nullptr;
|
uint32_t* buffer = nullptr;
|
||||||
if (backing) {
|
|
||||||
if (m_free.isEmpty()) {
|
if (m_free.isEmpty()) {
|
||||||
buffer = m_queue.dequeue();
|
buffer = m_queue.dequeue();
|
||||||
} else {
|
} else {
|
||||||
|
@ -771,26 +824,46 @@ void PainterGL::enqueue(const uint32_t* backing) {
|
||||||
QSize size = m_context->screenDimensions();
|
QSize size = m_context->screenDimensions();
|
||||||
memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
|
memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
m_queue.enqueue(buffer);
|
m_queue.enqueue(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PainterGL::enqueue(GLuint tex) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
if (m_freeTex.isEmpty()) {
|
||||||
|
m_bridgeTexIn = m_queueTex.dequeue();
|
||||||
|
} else {
|
||||||
|
m_bridgeTexIn = m_freeTex.takeLast();
|
||||||
|
}
|
||||||
|
m_queueTex.enqueue(tex);
|
||||||
|
}
|
||||||
|
|
||||||
void PainterGL::dequeue() {
|
void PainterGL::dequeue() {
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
if (m_queue.isEmpty()) {
|
if (!m_queue.isEmpty()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint32_t* buffer = m_queue.dequeue();
|
uint32_t* buffer = m_queue.dequeue();
|
||||||
if (m_buffer) {
|
if (m_buffer) {
|
||||||
m_free.append(m_buffer);
|
m_free.append(m_buffer);
|
||||||
m_buffer = nullptr;
|
|
||||||
}
|
}
|
||||||
m_buffer = buffer;
|
m_buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_queueTex.isEmpty()) {
|
||||||
|
if (m_bridgeTexOut != std::numeric_limits<GLuint>::max()) {
|
||||||
|
m_freeTex.enqueue(m_bridgeTexOut);
|
||||||
|
}
|
||||||
|
m_bridgeTexOut = m_queueTex.dequeue();
|
||||||
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
|
if (supportsShaders()) {
|
||||||
|
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
|
||||||
|
gl2Backend->tex = m_bridgeTexOut;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::dequeueAll(bool keep) {
|
void PainterGL::dequeueAll(bool keep) {
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
uint32_t* buffer = 0;
|
uint32_t* buffer = nullptr;
|
||||||
while (!m_queue.isEmpty()) {
|
while (!m_queue.isEmpty()) {
|
||||||
buffer = m_queue.dequeue();
|
buffer = m_queue.dequeue();
|
||||||
if (keep) {
|
if (keep) {
|
||||||
|
@ -806,6 +879,19 @@ void PainterGL::dequeueAll(bool keep) {
|
||||||
m_free.append(m_buffer);
|
m_free.append(m_buffer);
|
||||||
m_buffer = nullptr;
|
m_buffer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_queueTex.clear();
|
||||||
|
m_freeTex.clear();
|
||||||
|
for (auto tex : m_bridgeTexes) {
|
||||||
|
if (keep && tex == m_bridgeTexIn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_freeTex.enqueue(tex);
|
||||||
|
}
|
||||||
|
if (!keep) {
|
||||||
|
m_bridgeTexIn = m_freeTex.dequeue();
|
||||||
|
m_bridgeTexOut = std::numeric_limits<GLuint>::max();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
|
void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
|
||||||
|
@ -821,12 +907,20 @@ void PainterGL::setShaders(struct VDir* dir) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
|
if (!m_started) {
|
||||||
|
makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_shader.passes) {
|
if (m_shader.passes) {
|
||||||
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
||||||
mGLES2ShaderFree(&m_shader);
|
mGLES2ShaderFree(&m_shader);
|
||||||
}
|
}
|
||||||
mGLES2ShaderLoad(&m_shader, dir);
|
mGLES2ShaderLoad(&m_shader, dir);
|
||||||
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
|
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
|
||||||
|
|
||||||
|
if (!m_started) {
|
||||||
|
m_gl->doneCurrent();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -835,10 +929,18 @@ void PainterGL::clearShaders() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
|
if (!m_started) {
|
||||||
|
makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_shader.passes) {
|
if (m_shader.passes) {
|
||||||
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
||||||
mGLES2ShaderFree(&m_shader);
|
mGLES2ShaderFree(&m_shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_started) {
|
||||||
|
m_gl->doneCurrent();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,4 +963,39 @@ int PainterGL::glTex() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QOpenGLContext* PainterGL::shareContext() {
|
||||||
|
if (m_widget) {
|
||||||
|
return m_widget->context();
|
||||||
|
} else {
|
||||||
|
return m_gl.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PainterGL::updateFramebufferHandle() {
|
||||||
|
QOpenGLFunctions_Baseline* fn = m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
||||||
|
// TODO: Figure out why glFlush doesn't work here on Intel/Windows
|
||||||
|
if (glContextHasBug(OpenGLBug::CROSS_THREAD_FLUSH)) {
|
||||||
|
fn->glFinish();
|
||||||
|
} else {
|
||||||
|
fn->glFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreController::Interrupter interrupter(m_context);
|
||||||
|
if (!m_context->hardwareAccelerated()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
enqueue(m_bridgeTexIn);
|
||||||
|
m_context->setFramebufferHandle(m_bridgeTexIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PainterGL::swapTex() {
|
||||||
|
if (!m_started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreController::Interrupter interrupter(m_context);
|
||||||
|
emit texSwapped();
|
||||||
|
m_context->addFrameAction(std::bind(&PainterGL::swapTex, this));
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -112,6 +112,9 @@ protected:
|
||||||
virtual void paintEvent(QPaintEvent*) override { forceDraw(); }
|
virtual void paintEvent(QPaintEvent*) override { forceDraw(); }
|
||||||
virtual void resizeEvent(QResizeEvent*) override;
|
virtual void resizeEvent(QResizeEvent*) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void setupProxyThread();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resizePainter();
|
void resizePainter();
|
||||||
bool shouldDisableUpdates();
|
bool shouldDisableUpdates();
|
||||||
|
@ -122,8 +125,11 @@ private:
|
||||||
bool m_hasStarted = false;
|
bool m_hasStarted = false;
|
||||||
std::unique_ptr<PainterGL> m_painter;
|
std::unique_ptr<PainterGL> m_painter;
|
||||||
QThread m_drawThread;
|
QThread m_drawThread;
|
||||||
|
QThread m_proxyThread;
|
||||||
std::shared_ptr<CoreController> m_context;
|
std::shared_ptr<CoreController> m_context;
|
||||||
mGLWidget* m_gl;
|
mGLWidget* m_gl;
|
||||||
|
QOffscreenSurface m_proxySurface;
|
||||||
|
std::unique_ptr<QOpenGLContext> m_proxyContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PainterGL : public QObject {
|
class PainterGL : public QObject {
|
||||||
|
@ -137,15 +143,21 @@ public:
|
||||||
void setContext(std::shared_ptr<CoreController>);
|
void setContext(std::shared_ptr<CoreController>);
|
||||||
void setMessagePainter(MessagePainter*);
|
void setMessagePainter(MessagePainter*);
|
||||||
void enqueue(const uint32_t* backing);
|
void enqueue(const uint32_t* backing);
|
||||||
|
void enqueue(GLuint tex);
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
bool supportsShaders() const { return m_supportsShaders; }
|
bool supportsShaders() const { return m_supportsShaders; }
|
||||||
int glTex();
|
int glTex();
|
||||||
|
|
||||||
|
QOpenGLContext* shareContext();
|
||||||
|
|
||||||
void setVideoProxy(std::shared_ptr<VideoProxy>);
|
void setVideoProxy(std::shared_ptr<VideoProxy>);
|
||||||
void interrupt();
|
void interrupt();
|
||||||
|
|
||||||
|
// Run on main thread
|
||||||
|
void swapTex();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void create();
|
void create();
|
||||||
void destroy();
|
void destroy();
|
||||||
|
@ -163,13 +175,16 @@ public slots:
|
||||||
void showFrameCounter(bool enable);
|
void showFrameCounter(bool enable);
|
||||||
void filter(bool filter);
|
void filter(bool filter);
|
||||||
void resizeContext();
|
void resizeContext();
|
||||||
|
void updateFramebufferHandle();
|
||||||
|
|
||||||
void setShaders(struct VDir*);
|
void setShaders(struct VDir*);
|
||||||
void clearShaders();
|
void clearShaders();
|
||||||
VideoShader* shaders();
|
VideoShader* shaders();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void created();
|
||||||
void started();
|
void started();
|
||||||
|
void texSwapped();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void doStop();
|
void doStop();
|
||||||
|
@ -184,6 +199,14 @@ private:
|
||||||
QList<uint32_t*> m_free;
|
QList<uint32_t*> m_free;
|
||||||
QQueue<uint32_t*> m_queue;
|
QQueue<uint32_t*> m_queue;
|
||||||
uint32_t* m_buffer = nullptr;
|
uint32_t* m_buffer = nullptr;
|
||||||
|
|
||||||
|
std::array<GLuint, 3> m_bridgeTexes;
|
||||||
|
QQueue<GLuint> m_freeTex;
|
||||||
|
QQueue<GLuint> m_queueTex;
|
||||||
|
|
||||||
|
GLuint m_bridgeTexIn = std::numeric_limits<GLuint>::max();
|
||||||
|
GLuint m_bridgeTexOut = std::numeric_limits<GLuint>::max();
|
||||||
|
|
||||||
QPainter m_painter;
|
QPainter m_painter;
|
||||||
QMutex m_mutex;
|
QMutex m_mutex;
|
||||||
QWindow* m_window;
|
QWindow* m_window;
|
||||||
|
|
|
@ -39,6 +39,7 @@ static const QList<GBMemoryBankControllerType> s_mbcList{
|
||||||
GB_UNL_BBD,
|
GB_UNL_BBD,
|
||||||
GB_UNL_HITEK,
|
GB_UNL_HITEK,
|
||||||
GB_UNL_SACHEN_MMC1,
|
GB_UNL_SACHEN_MMC1,
|
||||||
|
GB_UNL_SACHEN_MMC2,
|
||||||
};
|
};
|
||||||
|
|
||||||
static QMap<GBModel, QString> s_gbModelNames;
|
static QMap<GBModel, QString> s_gbModelNames;
|
||||||
|
|
|
@ -14,24 +14,46 @@
|
||||||
#include <mgba/internal/gb/gb.h>
|
#include <mgba/internal/gb/gb.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* node)
|
MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* gbNode)
|
||||||
: controller(coreController)
|
: controller(coreController)
|
||||||
, gbNode(node)
|
|
||||||
{
|
{
|
||||||
|
node.gb = gbNode;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* node)
|
MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* gbaNode)
|
||||||
: controller(coreController)
|
: controller(coreController)
|
||||||
, gbaNode(node)
|
|
||||||
{
|
{
|
||||||
|
node.gba = gbaNode;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int MultiplayerController::Player::id() const {
|
||||||
|
switch (controller->platform()) {
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
case mPLATFORM_GBA:
|
||||||
|
return node.gba->id;
|
||||||
|
#endif
|
||||||
|
#ifdef M_CORE_GB
|
||||||
|
case mPLATFORM_GB:
|
||||||
|
return node.gb->id;
|
||||||
|
#endif
|
||||||
|
case mPLATFORM_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MultiplayerController::Player::operator<(const MultiplayerController::Player& other) const {
|
||||||
|
return id() < other.id();
|
||||||
|
}
|
||||||
|
|
||||||
MultiplayerController::MultiplayerController() {
|
MultiplayerController::MultiplayerController() {
|
||||||
mLockstepInit(&m_lockstep);
|
mLockstepInit(&m_lockstep);
|
||||||
m_lockstep.context = this;
|
m_lockstep.context = this;
|
||||||
|
@ -65,6 +87,7 @@ MultiplayerController::MultiplayerController() {
|
||||||
player->awake = 0;
|
player->awake = 0;
|
||||||
slept = true;
|
slept = true;
|
||||||
}
|
}
|
||||||
|
player->controller->setSync(true);
|
||||||
return slept;
|
return slept;
|
||||||
};
|
};
|
||||||
m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
||||||
|
@ -72,44 +95,51 @@ MultiplayerController::MultiplayerController() {
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||||
if (!id) {
|
Player* player = controller->player(id);
|
||||||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
|
||||||
Player* player = &controller->m_players[i];
|
|
||||||
#ifdef M_CORE_GBA
|
|
||||||
if (player->controller->platform() == mPLATFORM_GBA && player->gbaNode->d.p->mode != controller->m_players[0].gbaNode->d.p->mode) {
|
|
||||||
player->controller->setSync(true);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
player->controller->setSync(false);
|
|
||||||
player->cyclesPosted += cycles;
|
|
||||||
if (player->awake < 1) {
|
|
||||||
switch (player->controller->platform()) {
|
switch (player->controller->platform()) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
case mPLATFORM_GBA:
|
case mPLATFORM_GBA:
|
||||||
player->gbaNode->nextEvent += player->cyclesPosted;
|
if (!id) {
|
||||||
|
for (int i = 1; i < controller->m_players.count(); ++i) {
|
||||||
|
player = controller->player(i);
|
||||||
|
player->controller->setSync(false);
|
||||||
|
player->cyclesPosted += cycles;
|
||||||
|
if (player->awake < 1) {
|
||||||
|
player->node.gba->nextEvent += player->cyclesPosted;
|
||||||
|
}
|
||||||
|
mCoreThreadStopWaiting(player->controller->thread());
|
||||||
|
player->awake = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player->controller->setSync(true);
|
||||||
|
player->cyclesPosted += cycles;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
case mPLATFORM_GB:
|
case mPLATFORM_GB:
|
||||||
player->gbNode->nextEvent += player->cyclesPosted;
|
if (!id) {
|
||||||
|
player = controller->player(1);
|
||||||
|
player->controller->setSync(false);
|
||||||
|
player->cyclesPosted += cycles;
|
||||||
|
if (player->awake < 1) {
|
||||||
|
player->node.gb->nextEvent += player->cyclesPosted;
|
||||||
|
}
|
||||||
|
mCoreThreadStopWaiting(player->controller->thread());
|
||||||
|
player->awake = 1;
|
||||||
|
} else {
|
||||||
|
player->controller->setSync(true);
|
||||||
|
player->cyclesPosted += cycles;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
mCoreThreadStopWaiting(player->controller->thread());
|
|
||||||
player->awake = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
controller->m_players[id].controller->setSync(true);
|
|
||||||
controller->m_players[id].cyclesPosted += cycles;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
m_lockstep.useCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
m_lockstep.useCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
||||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||||
Player* player = &controller->m_players[id];
|
Player* player = controller->player(id);
|
||||||
player->cyclesPosted -= cycles;
|
player->cyclesPosted -= cycles;
|
||||||
if (player->cyclesPosted <= 0) {
|
if (player->cyclesPosted <= 0) {
|
||||||
mCoreThreadWaitFromThread(player->controller->thread());
|
mCoreThreadWaitFromThread(player->controller->thread());
|
||||||
|
@ -118,21 +148,21 @@ MultiplayerController::MultiplayerController() {
|
||||||
cycles = player->cyclesPosted;
|
cycles = player->cyclesPosted;
|
||||||
return cycles;
|
return cycles;
|
||||||
};
|
};
|
||||||
m_lockstep.unusedCycles= [](mLockstep* lockstep, int id) {
|
m_lockstep.unusedCycles = [](mLockstep* lockstep, int id) {
|
||||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||||
Player* player = &controller->m_players[id];
|
Player* player = controller->player(id);
|
||||||
auto cycles = player->cyclesPosted;
|
auto cycles = player->cyclesPosted;
|
||||||
return cycles;
|
return cycles;
|
||||||
};
|
};
|
||||||
m_lockstep.unload = [](mLockstep* lockstep, int id) {
|
m_lockstep.unload = [](mLockstep* lockstep, int id) {
|
||||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||||
if (id) {
|
if (id) {
|
||||||
Player* player = &controller->m_players[id];
|
Player* player = controller->player(id);
|
||||||
player->controller->setSync(true);
|
player->controller->setSync(true);
|
||||||
player->cyclesPosted = 0;
|
player->cyclesPosted = 0;
|
||||||
|
|
||||||
// release master GBA if it is waiting for this GBA
|
// release master GBA if it is waiting for this GBA
|
||||||
player = &controller->m_players[0];
|
player = controller->player(0);
|
||||||
player->waitMask &= ~(1 << id);
|
player->waitMask &= ~(1 << id);
|
||||||
if (!player->waitMask && player->awake < 1) {
|
if (!player->waitMask && player->awake < 1) {
|
||||||
mCoreThreadStopWaiting(player->controller->thread());
|
mCoreThreadStopWaiting(player->controller->thread());
|
||||||
|
@ -140,7 +170,7 @@ MultiplayerController::MultiplayerController() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
for (int i = 1; i < controller->m_players.count(); ++i) {
|
||||||
Player* player = &controller->m_players[i];
|
Player* player = controller->player(i);
|
||||||
player->controller->setSync(true);
|
player->controller->setSync(true);
|
||||||
switch (player->controller->platform()) {
|
switch (player->controller->platform()) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
|
@ -160,12 +190,12 @@ MultiplayerController::MultiplayerController() {
|
||||||
switch (player->controller->platform()) {
|
switch (player->controller->platform()) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
case mPLATFORM_GBA:
|
case mPLATFORM_GBA:
|
||||||
player->gbaNode->nextEvent += player->cyclesPosted;
|
player->node.gba->nextEvent += player->cyclesPosted;
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
case mPLATFORM_GB:
|
case mPLATFORM_GB:
|
||||||
player->gbNode->nextEvent += player->cyclesPosted;
|
player->node.gb->nextEvent += player->cyclesPosted;
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
|
@ -315,3 +345,29 @@ int MultiplayerController::attached() {
|
||||||
num = m_lockstep.attached;
|
num = m_lockstep.attached;
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MultiplayerController::Player* MultiplayerController::player(int id) {
|
||||||
|
Player* player = &m_players[id];
|
||||||
|
switch (player->controller->platform()) {
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
case mPLATFORM_GBA:
|
||||||
|
if (player->node.gba->id != id) {
|
||||||
|
std::sort(m_players.begin(), m_players.end());
|
||||||
|
player = &m_players[id];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef M_CORE_GB
|
||||||
|
case mPLATFORM_GB:
|
||||||
|
if (player->node.gb->id != id) {
|
||||||
|
std::swap(m_players[0], m_players[1]);
|
||||||
|
player = &m_players[id];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case mPLATFORM_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QMutex>
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QMutex>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include <mgba/core/lockstep.h>
|
#include <mgba/core/lockstep.h>
|
||||||
|
@ -44,6 +44,10 @@ signals:
|
||||||
void gameDetached();
|
void gameDetached();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
union Node {
|
||||||
|
GBSIOLockstepNode* gb;
|
||||||
|
GBASIOLockstepNode* gba;
|
||||||
|
};
|
||||||
struct Player {
|
struct Player {
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
Player(CoreController* controller, GBSIOLockstepNode* node);
|
Player(CoreController* controller, GBSIOLockstepNode* node);
|
||||||
|
@ -52,13 +56,18 @@ private:
|
||||||
Player(CoreController* controller, GBASIOLockstepNode* node);
|
Player(CoreController* controller, GBASIOLockstepNode* node);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int id() const;
|
||||||
|
bool operator<(const Player&) const;
|
||||||
|
|
||||||
CoreController* controller;
|
CoreController* controller;
|
||||||
GBSIOLockstepNode* gbNode = nullptr;
|
Node node = {nullptr};
|
||||||
GBASIOLockstepNode* gbaNode = nullptr;
|
|
||||||
int awake = 1;
|
int awake = 1;
|
||||||
int32_t cyclesPosted = 0;
|
int32_t cyclesPosted = 0;
|
||||||
unsigned waitMask = 0;
|
unsigned waitMask = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Player* player(int id);
|
||||||
|
|
||||||
union {
|
union {
|
||||||
mLockstep m_lockstep;
|
mLockstep m_lockstep;
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/* 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/. */
|
||||||
|
#include "OpenGLBug.h"
|
||||||
|
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
#include <QOpenGLFunctions>
|
||||||
|
|
||||||
|
namespace QGBA {
|
||||||
|
|
||||||
|
bool glContextHasBug(OpenGLBug bug) {
|
||||||
|
QOpenGLContext* context = QOpenGLContext::currentContext();
|
||||||
|
if (!context) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
QOpenGLFunctions* fn = context->functions();
|
||||||
|
QString vendor(reinterpret_cast<const char*>(fn->glGetString(GL_VENDOR)));
|
||||||
|
QString renderer(reinterpret_cast<const char*>(fn->glGetString(GL_RENDERER)));
|
||||||
|
QString version(reinterpret_cast<const char*>(fn->glGetString(GL_VERSION)));
|
||||||
|
|
||||||
|
switch (bug) {
|
||||||
|
case OpenGLBug::CROSS_THREAD_FLUSH:
|
||||||
|
#ifndef Q_OS_WIN
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return vendor == "Intel";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case OpenGLBug::GLTHREAD_BLOCKS_SWAP:
|
||||||
|
return version.contains(" Mesa ");
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* 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/. */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace QGBA {
|
||||||
|
|
||||||
|
enum class OpenGLBug {
|
||||||
|
CROSS_THREAD_FLUSH, // mgba.io/i/2761
|
||||||
|
GLTHREAD_BLOCKS_SWAP, // mgba.io/i/2767
|
||||||
|
};
|
||||||
|
|
||||||
|
bool glContextHasBug(OpenGLBug);
|
||||||
|
|
||||||
|
}
|
|
@ -308,6 +308,7 @@ void ReportView::generateReport() {
|
||||||
deferredBinaries.append(qMakePair(QString("Save %1").arg(winId), save));
|
deferredBinaries.append(qMakePair(QString("Save %1").arg(winId), save));
|
||||||
}
|
}
|
||||||
mStateExtdataDeinit(&extdata);
|
mStateExtdataDeinit(&extdata);
|
||||||
|
vf->close(vf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -296,9 +296,14 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
|
||||||
}
|
}
|
||||||
|
|
||||||
const GBColorPreset* colorPresets;
|
const GBColorPreset* colorPresets;
|
||||||
|
QString usedPreset = m_controller->getQtOption("gb.pal").toString();
|
||||||
size_t nPresets = GBColorPresetList(&colorPresets);
|
size_t nPresets = GBColorPresetList(&colorPresets);
|
||||||
for (size_t i = 0; i < nPresets; ++i) {
|
for (size_t i = 0; i < nPresets; ++i) {
|
||||||
m_ui.colorPreset->addItem(QString(colorPresets[i].name));
|
QString presetName(colorPresets[i].name);
|
||||||
|
m_ui.colorPreset->addItem(presetName);
|
||||||
|
if (usedPreset == presetName) {
|
||||||
|
m_ui.colorPreset->setCurrentIndex(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
connect(m_ui.colorPreset, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this, colorPresets](int n) {
|
connect(m_ui.colorPreset, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this, colorPresets](int n) {
|
||||||
const GBColorPreset* preset = &colorPresets[n];
|
const GBColorPreset* preset = &colorPresets[n];
|
||||||
|
@ -640,6 +645,7 @@ void SettingsView::updateConfig() {
|
||||||
m_controller->setOption(color.toUtf8().constData(), m_gbColors[colorId] & ~0xFF000000);
|
m_controller->setOption(color.toUtf8().constData(), m_gbColors[colorId] & ~0xFF000000);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
m_controller->setQtOption("gb.pal", m_ui.colorPreset->currentText());
|
||||||
|
|
||||||
int gbColors = GB_COLORS_CGB;
|
int gbColors = GB_COLORS_CGB;
|
||||||
if (m_ui.gbColor->isChecked()) {
|
if (m_ui.gbColor->isChecked()) {
|
||||||
|
|
|
@ -1107,6 +1107,8 @@ void Window::changeRenderer() {
|
||||||
if (!m_controller) {
|
if (!m_controller) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoreController::Interrupter interrupter(m_controller);
|
||||||
if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && m_controller->supportsFeature(CoreController::Feature::OPENGL)) {
|
if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && m_controller->supportsFeature(CoreController::Feature::OPENGL)) {
|
||||||
std::shared_ptr<VideoProxy> proxy = m_display->videoProxy();
|
std::shared_ptr<VideoProxy> proxy = m_display->videoProxy();
|
||||||
if (!proxy) {
|
if (!proxy) {
|
||||||
|
|
|
@ -593,7 +593,11 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) {
|
||||||
|
|
||||||
ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
|
ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
|
||||||
struct VFileZip* vfz = (struct VFileZip*) vf;
|
struct VFileZip* vfz = (struct VFileZip*) vf;
|
||||||
return zipWriteInFileInZip(vfz->z, buffer, size);
|
int res = zipWriteInFileInZip(vfz->z, buffer, size);
|
||||||
|
if (res != ZIP_OK) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* _vfzMap(struct VFile* vf, size_t size, int flags) {
|
void* _vfzMap(struct VFile* vf, size_t size, int flags) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ if(NOT PROJECT_NAME)
|
||||||
endif()
|
endif()
|
||||||
set(LIB_VERSION_MAJOR 0)
|
set(LIB_VERSION_MAJOR 0)
|
||||||
set(LIB_VERSION_MINOR 10)
|
set(LIB_VERSION_MINOR 10)
|
||||||
set(LIB_VERSION_PATCH 0)
|
set(LIB_VERSION_PATCH 1)
|
||||||
set(LIB_VERSION_ABI 0.10)
|
set(LIB_VERSION_ABI 0.10)
|
||||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||||
set(SUMMARY "${PROJECT_NAME} Game Boy Advance Emulator")
|
set(SUMMARY "${PROJECT_NAME} Game Boy Advance Emulator")
|
||||||
|
|
Loading…
Reference in New Issue