Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2019-09-28 15:25:08 -07:00
commit 8ca80d4a94
94 changed files with 2542 additions and 1028 deletions

12
CHANGES
View File

@ -28,6 +28,8 @@ Features:
- Support Discord Rich Presence
- Debugger: Add tracing to file
- Map viewer supports bitmapped GBA modes
- OpenGL renderer with high-resolution upscaling support
- Experimental high level "XQ" audio for most GBA games
Emulation fixes:
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
- GBA: Reset now reloads multiboot ROMs
@ -43,6 +45,9 @@ Emulation fixes:
- GBA Memory: Fix writing to OBJ memory in modes 3 and 5
- GBA: Fix RTC on non-standard sized ROMs (fixes mgba.io/i/1400)
- GBA Memory: Prevent writing to mirrored BG VRAM (fixes mgba.io/i/743)
- GBA Video: Fix sprite mosaic clamping (fixes mgba.io/i/1008)
- GB: Fix HALT when IE and IF unused bits are set (fixes mgba.io/i/1349)
- GBA Video: Implement mosaic on transformed sprites (fixes mgba.io/b/9)
Other fixes:
- Qt: More app metadata fixes
- Qt: Fix load recent from archive (fixes mgba.io/i/1325)
@ -60,6 +65,10 @@ Other fixes:
- Wii: Fix aspect ratio (fixes mgba.io/i/500)
- Qt: Fix some Qt display driver race conditions
- FFmpeg: Fix audio conversion producing gaps
- Core: Improved lockstep driver reliability (Le Hoang Quyen)
- GBA: Fix skipping BIOS on irregularly sized ROMs
- Qt: Fix bounded fast forward with Qt Multimedia
- Qt: Fix saving settings with native FPS target
Misc:
- GBA Savedata: EEPROM performance fixes
- GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash
@ -79,6 +88,9 @@ Misc:
- Qt: Open a message box for Qt frontend errors
- GBA Video: Clean up dead code in sprite rendering loop
- FFmpeg: Support audio-only recording
- Qt: Increase maximum magnifications and scaling
- Qt: Add native FPS button to settings view
- Qt: Improve sync code
0.7.1: (2019-02-24)
Bugfixes:

View File

@ -90,6 +90,7 @@ file(GLOB GB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/*.c)
file(GLOB GB_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/test/*.c)
file(GLOB DS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/*.c)
file(GLOB GBA_CHEATS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/cheats/*.c)
file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/audio-mixer.c)
file(GLOB GBA_RR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/rr/*.c)
file(GLOB CORE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/core/*.c)
file(GLOB CORE_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/core/test/*.c)
@ -261,15 +262,10 @@ if(WIN32)
endif()
elseif(UNIX)
set(USE_PTHREADS ON)
add_definitions(-DUSE_PTHREADS)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_definitions(-D_GNU_SOURCE)
endif()
if(NOT APPLE AND NOT HAIKU)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/posix/*.c)
@ -355,6 +351,7 @@ if(3DS OR WII)
add_definitions(-D_GNU_SOURCE)
endif()
include(CheckCCompilerFlag)
include(CheckFunctionExists)
include(CheckIncludeFiles)
check_function_exists(strdup HAVE_STRDUP)
@ -406,6 +403,27 @@ endif()
check_function_exists(chmod HAVE_CHMOD)
check_function_exists(umask HAVE_UMASK)
if(USE_PTHREADS)
check_include_files("pthread.h" HAVE_PTHREAD_H)
if(HAVE_PTHREAD_H)
check_c_compiler_flag(-pthread HAVE_PTHREAD)
if(HAVE_PTHREAD AND NOT APPLE AND NOT HAIKU)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
check_function_exists(pthread_create HAVE_PTHREAD_CREATE)
if(HAVE_PTHREAD_CREATE)
add_definitions(-DUSE_PTHREADS)
check_include_files("pthread_np.h" HAVE_PTHREAD_NP_H)
check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP)
check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP)
endif()
endif()
endif()
set(FUNCTION_DEFINES)
if(HAVE_STRDUP)
@ -447,6 +465,18 @@ if(HAVE_UMASK)
list(APPEND FUNCTION_DEFINES HAVE_UMASK)
endif()
if(HAVE_PTHREAD_NP_H)
list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_NP_H)
endif()
if(HAVE_PTHREAD_SETNAME_NP)
list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_SETNAME_NP)
endif()
if(HAVE_PTHREAD_SET_NAME_NP)
list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_SET_NAME_NP)
endif()
# Feature dependencies
set(FEATURE_DEFINES)
set(FEATURE_FLAGS)

View File

@ -233,7 +233,6 @@ Footnotes
<a name="missing">[1]</a> Currently missing features on GBA are
- OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5))
- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9))
Missing features on DS are

View File

@ -215,7 +215,6 @@ Fußnoten
<a name="missing">[1]</a> Zurzeit fehlende Features sind
- OBJ-Fenster für die Modi 3, 4 und 5 ([Bug #5](http://mgba.io/b/5))
- Mosaik-Effekt für umgewandelte OBJs ([Bug #9](http://mgba.io/b/9))
<a name="flashdetect">[2]</a> In manchen Fällen ist es nicht möglich, die Größe des Flash-Speichers automatisch zu ermitteln. Diese kann dann zur Laufzeit konfiguriert werden, es wird jedoch empfohlen, den Fehler zu melden.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -12,7 +12,7 @@ CXX_GUARD_START
#include <pthread.h>
#include <sys/time.h>
#if defined(__FreeBSD__) || defined(__OpenBSD__)
#ifdef HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#elif defined(__HAIKU__)
#include <OS.h>
@ -85,20 +85,15 @@ static inline int ThreadJoin(Thread thread) {
}
static inline int ThreadSetName(const char* name) {
#ifdef __APPLE__
#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060
#if defined(__APPLE__) && defined(HAVE_PTHREAD_SETNAME_NP)
return pthread_setname_np(name);
#else
UNUSED(name);
return 0;
#endif
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
#elif defined(HAVE_PTHREAD_SET_NAME_NP)
pthread_set_name_np(pthread_self(), name);
return 0;
#elif defined(__HAIKU__)
rename_thread(find_thread(NULL), name);
return 0;
#elif !defined(BUILD_PANDORA) // Pandora's glibc is too old
#elif defined(HAVE_PTHREAD_SETNAME_NP)
return pthread_setname_np(pthread_self(), name);
#else
UNUSED(name);

View File

@ -13,6 +13,10 @@ CXX_GUARD_START
enum mCPUComponentType {
CPU_COMPONENT_DEBUGGER,
CPU_COMPONENT_CHEAT_DEVICE,
CPU_COMPONENT_MISC_1,
CPU_COMPONENT_MISC_2,
CPU_COMPONENT_MISC_3,
CPU_COMPONENT_MISC_4,
CPU_COMPONENT_MAX
};

View File

@ -10,10 +10,14 @@
CXX_GUARD_START
#include <mgba/core/cpu.h>
#include <mgba/core/log.h>
#include <mgba/internal/gb/audio.h>
#include <mgba-util/circle-buffer.h>
#define MP2K_MAGIC 0x68736D53
#define MP2K_MAX_SOUND_CHANNELS 12
mLOG_DECLARE_CATEGORY(GBA_AUDIO);
struct GBADMA;
@ -44,6 +48,7 @@ DECL_BITFIELD(GBARegisterSOUNDBIAS, uint16_t);
DECL_BITS(GBARegisterSOUNDBIAS, Bias, 0, 10);
DECL_BITS(GBARegisterSOUNDBIAS, Resolution, 14, 2);
struct GBAAudioMixer;
struct GBAAudio {
struct GBA* p;
@ -71,6 +76,8 @@ struct GBAAudio {
GBARegisterSOUNDBIAS soundbias;
struct GBAAudioMixer* mixer;
bool externalMixing;
int32_t sampleInterval;
bool forceDisableChA;
@ -85,6 +92,188 @@ struct GBAStereoSample {
int16_t right;
};
struct GBAMP2kADSR {
uint8_t attack;
uint8_t decay;
uint8_t sustain;
uint8_t release;
};
struct GBAMP2kSoundChannel {
uint8_t status;
uint8_t type;
uint8_t rightVolume;
uint8_t leftVolume;
struct GBAMP2kADSR adsr;
uint8_t ky;
uint8_t envelopeV;
uint8_t envelopeRight;
uint8_t envelopeLeft;
uint8_t echoVolume;
uint8_t echoLength;
uint8_t d1;
uint8_t d2;
uint8_t gt;
uint8_t midiKey;
uint8_t ve;
uint8_t pr;
uint8_t rp;
uint8_t d3[3];
uint32_t ct;
uint32_t fw;
uint32_t freq;
uint32_t waveData;
uint32_t cp;
uint32_t track;
uint32_t pp;
uint32_t np;
uint32_t d4;
uint16_t xpi;
uint16_t xpc;
};
struct GBAMP2kContext {
uint32_t magic;
uint8_t pcmDmaCounter;
uint8_t reverb;
uint8_t maxChans;
uint8_t masterVolume;
uint8_t freq;
uint8_t mode;
uint8_t c15;
uint8_t pcmDmaPeriod;
uint8_t maxLines;
uint8_t gap[3];
int32_t pcmSamplesPerVBlank;
int32_t pcmFreq;
int32_t divFreq;
uint32_t cgbChans;
uint32_t func;
uint32_t intp;
uint32_t cgbSound;
uint32_t cgbOscOff;
uint32_t midiKeyToCgbFreq;
uint32_t mPlayJumpTable;
uint32_t plynote;
uint32_t extVolPit;
uint8_t gap2[16];
struct GBAMP2kSoundChannel chans[MP2K_MAX_SOUND_CHANNELS];
};
struct GBAMP2kMusicPlayerInfo {
uint32_t songHeader;
uint32_t status;
uint8_t trackCount;
uint8_t priority;
uint8_t cmd;
uint8_t unk_B;
uint32_t clock;
uint8_t gap[8];
uint32_t memAccArea;
uint16_t tempoD;
uint16_t tempoU;
uint16_t tempoI;
uint16_t tempoC;
uint16_t fadeOI;
uint16_t fadeOC;
uint16_t fadeOV;
uint32_t tracks;
uint32_t tone;
uint32_t magic;
uint32_t func;
uint32_t intp;
};
struct GBAMP2kInstrument {
uint8_t type;
uint8_t key;
uint8_t length;
union {
uint8_t pan;
uint8_t sweep;
} ps;
union {
uint32_t waveData;
uint32_t subTable;
} data;
union {
struct GBAMP2kADSR adsr;
uint32_t map;
} extInfo;
};
struct GBAMP2kMusicPlayerTrack {
uint8_t flags;
uint8_t wait;
uint8_t patternLevel;
uint8_t repN;
uint8_t gateTime;
uint8_t key;
uint8_t velocity;
uint8_t runningStatus;
uint8_t keyM;
uint8_t pitM;
int8_t keyShift;
int8_t keyShiftX;
int8_t tune;
uint8_t pitX;
int8_t bend;
uint8_t bendRange;
uint8_t volMR;
uint8_t volML;
uint8_t vol;
uint8_t volX;
int8_t pan;
int8_t panX;
int8_t modM;
uint8_t mod;
uint8_t modT;
uint8_t lfoSpeed;
uint8_t lfoSpeedC;
uint8_t lfoDelay;
uint8_t lfoDelayC;
uint8_t priority;
uint8_t echoVolume;
uint8_t echoLength;
uint32_t chan;
struct GBAMP2kInstrument instrument;
uint8_t gap[10];
uint16_t unk_3A;
uint32_t unk_3C;
uint32_t cmdPtr;
uint32_t patternStack[3];
};
struct GBAMP2kTrack {
struct GBAMP2kMusicPlayerTrack track;
struct GBAMP2kSoundChannel* channel;
uint8_t lastCommand;
struct CircleBuffer buffer;
uint32_t samplePlaying;
float currentOffset;
bool waiting;
};
struct GBAAudioMixer {
struct mCPUComponent d;
struct GBAAudio* p;
uint32_t contextAddress;
bool (*engage)(struct GBAAudioMixer* mixer, uint32_t address);
void (*vblank)(struct GBAAudioMixer* mixer);
void (*step)(struct GBAAudioMixer* mixer);
struct GBAMP2kContext context;
struct GBAMP2kMusicPlayerInfo player;
struct GBAMP2kTrack activeTracks[MP2K_MAX_SOUND_CHANNELS];
double tempo;
double frame;
struct GBAStereoSample last;
};
void GBAAudioInit(struct GBAAudio* audio, size_t samples);
void GBAAudioReset(struct GBAAudio* audio);
void GBAAudioDeinit(struct GBAAudio* audio);

View File

@ -0,0 +1,19 @@
/* Copyright (c) 2013-2017 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 GBA_AUDIO_MIXER_H
#define GBA_AUDIO_MIXER_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/internal/gba/audio.h>
void GBAAudioMixerCreate(struct GBAAudioMixer* mixer);
CXX_GUARD_END
#endif

View File

@ -62,20 +62,25 @@ struct GBAVideoGLBackground {
int32_t refx;
int32_t refy;
struct GBAVideoGLAffine affine[4];
struct GBAVideoGLAffine affine;
};
enum {
GBA_GL_FBO_OBJ = 0,
GBA_GL_FBO_WINDOW = 1,
GBA_GL_FBO_OUTPUT = 2,
GBA_GL_FBO_BACKDROP,
GBA_GL_FBO_WINDOW,
GBA_GL_FBO_OUTPUT,
GBA_GL_FBO_MAX
};
enum {
GBA_GL_TEX_OBJ_COLOR = 0,
GBA_GL_TEX_OBJ_FLAGS = 1,
GBA_GL_TEX_WINDOW = 6,
GBA_GL_TEX_OBJ_FLAGS,
GBA_GL_TEX_BACKDROP_COLOR,
GBA_GL_TEX_BACKDROP_FLAGS,
GBA_GL_TEX_WINDOW,
GBA_GL_TEX_AFFINE_2,
GBA_GL_TEX_AFFINE_3,
GBA_GL_TEX_MAX
};
@ -91,6 +96,8 @@ enum {
GBA_GL_BG_OFFSET,
GBA_GL_BG_INFLAGS,
GBA_GL_BG_TRANSFORM,
GBA_GL_BG_RANGE,
GBA_GL_BG_MOSAIC,
GBA_GL_OBJ_VRAM = 2,
GBA_GL_OBJ_PALETTE,
@ -101,6 +108,7 @@ enum {
GBA_GL_OBJ_TRANSFORM,
GBA_GL_OBJ_DIMS,
GBA_GL_OBJ_OBJWIN,
GBA_GL_OBJ_MOSAIC,
GBA_GL_FINALIZE_SCALE = 2,
GBA_GL_FINALIZE_LAYERS,
@ -124,6 +132,7 @@ struct GBAVideoGLRenderer {
uint32_t* temporaryBuffer;
struct GBAVideoGLBackground bg[4];
struct GBAVideoGLAffine affine[2][GBA_VIDEO_VERTICAL_PIXELS];
int oamMax;
bool oamDirty;
@ -144,6 +153,9 @@ struct GBAVideoGLRenderer {
GLuint vramTex;
unsigned vramDirty;
uint16_t shadowRegs[0x30];
uint64_t regsDirty;
struct GBAVideoGLShader bgShader[6];
struct GBAVideoGLShader objShader[2];
struct GBAVideoGLShader finalizeShader;
@ -162,7 +174,7 @@ struct GBAVideoGLRenderer {
GBAMosaicControl mosaic;
struct GBAVideoGLWindowN {
struct GBAVideoWindowRegion h;
struct GBAVideoWindowRegion h[2];
struct GBAVideoWindowRegion v;
GBAWindowControl control;
} winN[2];
@ -171,6 +183,7 @@ struct GBAVideoGLRenderer {
GBAWindowControl objwin;
int firstAffine;
int firstY;
int scale;
};

View File

@ -153,10 +153,12 @@ static void _wait(struct mVideoLogger* logger) {
_proxyThreadRecover(proxyRenderer);
return;
}
MutexLock(&proxyRenderer->mutex);
while (RingFIFOSize(&proxyRenderer->dirtyQueue)) {
ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
}
MutexUnlock(&proxyRenderer->mutex);
}
static void _unlock(struct mVideoLogger* logger) {

View File

@ -250,31 +250,21 @@ void GBVideoProxyRendererFinishScanline(struct GBVideoRenderer* renderer, int y)
void GBVideoProxyRendererFinishFrame(struct GBVideoRenderer* renderer) {
struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer;
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->lock(proxyRenderer->logger);
}
if (!proxyRenderer->logger->block) {
proxyRenderer->backend->finishFrame(proxyRenderer->backend);
}
mVideoLoggerRendererFinishFrame(proxyRenderer->logger);
mVideoLoggerRendererFlush(proxyRenderer->logger);
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->unlock(proxyRenderer->logger);
}
}
static void GBVideoProxyRendererEnableSGBBorder(struct GBVideoRenderer* renderer, bool enable) {
struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer;
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->lock(proxyRenderer->logger);
// Insert an extra item into the queue to make sure it gets flushed
mVideoLoggerRendererFlush(proxyRenderer->logger);
proxyRenderer->logger->wait(proxyRenderer->logger);
}
proxyRenderer->backend->enableSGBBorder(proxyRenderer->backend, enable);
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->unlock(proxyRenderer->logger);
}
}
static void GBVideoProxyRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) {
@ -297,9 +287,6 @@ static void GBVideoProxyRendererPutPixels(struct GBVideoRenderer* renderer, size
struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer;
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->lock(proxyRenderer->logger);
// Insert an extra item into the queue to make sure it gets flushed
mVideoLoggerRendererFlush(proxyRenderer->logger);
proxyRenderer->logger->wait(proxyRenderer->logger);
}
proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels);
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {

View File

@ -723,7 +723,7 @@ static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cycle
void GBHalt(struct LR35902Core* cpu) {
struct GB* gb = (struct GB*) cpu->master;
if (!(gb->memory.ie & gb->memory.io[REG_IF])) {
if (!(gb->memory.ie & gb->memory.io[REG_IF] & 0x1F)) {
cpu->cycles = cpu->nextEvent;
cpu->halted = true;
} else if (gb->model < GB_MODEL_CGB) {

View File

@ -14,6 +14,8 @@
#include <mgba/internal/gba/serialize.h>
#include <mgba/internal/gba/video.h>
#define MP2K_LOCK_MAX 8
#ifdef _3DS
#define blip_add_delta blip_add_delta_fast
#endif
@ -24,7 +26,7 @@ const unsigned GBA_AUDIO_SAMPLES = 2048;
const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t);
const int GBA_AUDIO_VOLUME_MAX = 0x100;
static const int CLOCKS_PER_FRAME = 0x400;
static const int CLOCKS_PER_FRAME = 0x800;
static int _applyBias(struct GBAAudio* audio, int sample);
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate);
@ -49,9 +51,11 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) {
CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE);
CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE);
audio->externalMixing = false;
audio->forceDisableChA = false;
audio->forceDisableChB = false;
audio->masterVolume = GBA_AUDIO_VOLUME_MAX;
audio->mixer = NULL;
}
void GBAAudioReset(struct GBAAudio* audio) {
@ -111,6 +115,20 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA*
mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest);
return;
}
uint32_t source = info->source;
uint32_t magic[2] = {
audio->p->cpu->memory.load32(audio->p->cpu, source - 0x350, NULL),
audio->p->cpu->memory.load32(audio->p->cpu, source - 0x980, NULL)
};
if (audio->mixer) {
if (magic[0] - MP2K_MAGIC <= MP2K_LOCK_MAX) {
audio->mixer->engage(audio->mixer, source - 0x350);
} else if (magic[1] - MP2K_MAGIC <= MP2K_LOCK_MAX) {
audio->mixer->engage(audio->mixer, source - 0x980);
} else {
audio->externalMixing = false;
}
}
info->reg = GBADMARegisterSetDestControl(info->reg, GBA_DMA_FIXED);
info->reg = GBADMARegisterSetWidth(info->reg, 1);
}
@ -265,6 +283,10 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
sampleLeft >>= psgShift;
sampleRight >>= psgShift;
if (audio->mixer) {
audio->mixer->step(audio->mixer);
}
if (!audio->externalMixing) {
if (!audio->forceDisableChA) {
if (audio->chALeft) {
sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA;
@ -284,6 +306,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
}
}
}
sampleLeft = _applyBias(audio, sampleLeft);
sampleRight = _applyBias(audio, sampleRight);

View File

@ -12,6 +12,7 @@
#include <mgba/internal/gba/cheats.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/io.h>
#include <mgba/internal/gba/extra/audio-mixer.h>
#include <mgba/internal/gba/extra/cli.h>
#include <mgba/internal/gba/overrides.h>
#ifndef DISABLE_THREADING
@ -127,6 +128,9 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = {
};
struct mVideoLogContext;
#define CPU_COMPONENT_AUDIO_MIXER CPU_COMPONENT_MISC_1
struct GBACore {
struct mCore d;
struct GBAVideoSoftwareRenderer renderer;
@ -144,6 +148,7 @@ struct GBACore {
const struct Configuration* overrides;
struct mDebuggerPlatform* debuggerPlatform;
struct mCheatDevice* cheatDevice;
struct GBAAudioMixer* audioMixer;
};
static bool _GBACoreInit(struct mCore* core) {
@ -166,6 +171,7 @@ static bool _GBACoreInit(struct mCore* core) {
gbacore->debuggerPlatform = NULL;
gbacore->cheatDevice = NULL;
gbacore->logContext = NULL;
gbacore->audioMixer = NULL;
GBACreate(gba);
// TODO: Restore cheats
@ -217,6 +223,7 @@ static void _GBACoreDeinit(struct mCore* core) {
mCheatDeviceDestroy(gbacore->cheatDevice);
}
free(gbacore->cheatDevice);
free(gbacore->audioMixer);
mCoreConfigFreeOpts(&core->opts);
free(core);
}
@ -280,6 +287,7 @@ static void _GBACoreLoadConfig(struct mCore* core, const struct mCoreConfig* con
mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections");
mCoreConfigCopyValue(&core->config, config, "gba.bios");
mCoreConfigCopyValue(&core->config, config, "gba.audioHle");
#ifndef DISABLE_THREADING
mCoreConfigCopyValue(&core->config, config, "threadedVideo");
@ -475,6 +483,16 @@ static void _GBACoreReset(struct mCore* core) {
GBAVideoAssociateRenderer(&gba->video, renderer);
}
#ifndef MINIMAL_CORE
int useAudioMixer;
if (!gbacore->audioMixer && mCoreConfigGetIntValue(&core->config, "gba.audioHle", &useAudioMixer) && useAudioMixer) {
gbacore->audioMixer = malloc(sizeof(*gbacore->audioMixer));
GBAAudioMixerCreate(gbacore->audioMixer);
((struct ARMCore*) core->cpu)->components[CPU_COMPONENT_AUDIO_MIXER] = &gbacore->audioMixer->d;
ARMHotplugAttach(core->cpu, CPU_COMPONENT_AUDIO_MIXER);
}
#endif
GBAOverrideApplyDefaults(gba, gbacore->overrides);
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
@ -521,7 +539,7 @@ static void _GBACoreReset(struct mCore* core) {
#endif
ARMReset(core->cpu);
if (core->opts.skipBios && gba->isPristine) {
if (core->opts.skipBios && (gba->romVf || gba->memory.rom)) {
GBASkipBIOS(core->board);
}
}

307
src/gba/extra/audio-mixer.c Normal file
View File

@ -0,0 +1,307 @@
/* Copyright (c) 2013-2017 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 <mgba/internal/gba/extra/audio-mixer.h>
#include <mgba/core/blip_buf.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/video.h>
#define OVERSAMPLE 2
static void _mp2kInit(void* cpu, struct mCPUComponent* component);
static void _mp2kDeinit(struct mCPUComponent* component);
static bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address);
static void _mp2kVblank(struct GBAAudioMixer* mixer);
static void _mp2kStep(struct GBAAudioMixer* mixer);
void GBAAudioMixerCreate(struct GBAAudioMixer* mixer) {
mixer->d.init = _mp2kInit;
mixer->d.deinit = _mp2kDeinit;
mixer->engage = _mp2kEngage;
mixer->vblank = _mp2kVblank;
mixer->step = _mp2kStep;
}
void _mp2kInit(void* cpu, struct mCPUComponent* component) {
struct ARMCore* arm = cpu;
struct GBA* gba = (struct GBA*) arm->master;
struct GBAAudioMixer* mixer = (struct GBAAudioMixer*) component;
gba->audio.mixer = mixer;
mixer->p = &gba->audio;
mixer->contextAddress = 0;
mixer->tempo = 120.0 / 75.0;
mixer->frame = 0;
mixer->last.left = 0;
mixer->last.right = 0;
memset(&mixer->context, 0, sizeof(mixer->context));
memset(&mixer->activeTracks, 0, sizeof(mixer->activeTracks));
size_t i;
for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) {
mixer->activeTracks[i].channel = &mixer->context.chans[i];
CircleBufferInit(&mixer->activeTracks[i].buffer, 0x10000);
}
}
void _mp2kDeinit(struct mCPUComponent* component) {
struct GBAAudioMixer* mixer = (struct GBAAudioMixer*) component;
size_t i;
for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) {
CircleBufferDeinit(&mixer->activeTracks[i].buffer);
}
}
static void _loadInstrument(struct ARMCore* cpu, struct GBAMP2kInstrument* instrument, uint32_t base) {
struct ARMMemory* memory = &cpu->memory;
instrument->type = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, type), 0);
instrument->key = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, key), 0);
instrument->length = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, length), 0);
instrument->ps.pan = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, ps.pan), 0);
if (instrument->type == 0x40 || instrument->type == 0x80) {
instrument->data.subTable = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, data.subTable), 0);
instrument->extInfo.map = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.map), 0);
} else {
instrument->data.waveData = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, data.waveData), 0);
instrument->extInfo.adsr.attack = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.attack), 0);
instrument->extInfo.adsr.decay = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.decay), 0);
instrument->extInfo.adsr.sustain = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.sustain), 0);
instrument->extInfo.adsr.release = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.release), 0);
}
}
static void _lookupInstrument(struct ARMCore* cpu, struct GBAMP2kInstrument* instrument, uint8_t key) {
struct ARMMemory* memory = &cpu->memory;
if (instrument->type == 0x40) {
uint32_t subInstrumentBase = instrument->data.subTable;
uint32_t keyTable = instrument->extInfo.map;
uint8_t id = memory->load8(cpu, keyTable + key, 0);
subInstrumentBase += 12 * id;
_loadInstrument(cpu, instrument, subInstrumentBase);
}
if (instrument->type == 0x80) {
uint32_t subInstrumentBase = instrument->data.subTable;
subInstrumentBase += 12 * key;
_loadInstrument(cpu, instrument, subInstrumentBase);
}
}
static void _stepSample(struct GBAAudioMixer* mixer, struct GBAMP2kTrack* track) {
struct ARMCore* cpu = mixer->p->p->cpu;
struct ARMMemory* memory = &cpu->memory;
uint32_t headerAddress;
struct GBAMP2kInstrument instrument = track->track.instrument;
uint8_t note = track->track.key;
_lookupInstrument(cpu, &instrument, note);
double freq;
switch (instrument.type) {
case 0x00:
case 0x08:
case 0x40:
case 0x80:
freq = GBA_ARM7TDMI_FREQUENCY / (double) track->channel->freq;
break;
default:
// We don't care about PSG channels
return;
}
headerAddress = instrument.data.waveData;
if (headerAddress < 0x20) {
mLOG(GBA_AUDIO, ERROR, "Audio track has invalid instrument");
return;
}
uint32_t loopOffset = memory->load32(cpu, headerAddress + 0x8, 0);
uint32_t endOffset = memory->load32(cpu, headerAddress + 0xC, 0);
uint32_t sampleBase = headerAddress + 0x10;
uint32_t sampleI = track->samplePlaying;
double sampleOffset = track->currentOffset;
double updates = VIDEO_TOTAL_LENGTH / (mixer->tempo * mixer->p->sampleInterval / OVERSAMPLE);
int nSample;
for (nSample = 0; nSample < updates; ++nSample) {
int8_t sample = memory->load8(cpu, sampleBase + sampleI, 0);
struct GBAStereoSample stereo = {
(sample * track->channel->leftVolume * track->channel->envelopeV) >> 9,
(sample * track->channel->rightVolume * track->channel->envelopeV) >> 9
};
CircleBufferWrite16(&track->buffer, stereo.left);
CircleBufferWrite16(&track->buffer, stereo.right);
sampleOffset += mixer->p->sampleInterval / OVERSAMPLE;
while (sampleOffset > freq) {
sampleOffset -= freq;
++sampleI;
if (sampleI >= endOffset) {
sampleI = loopOffset;
}
}
}
track->samplePlaying = sampleI;
track->currentOffset = sampleOffset;
}
static void _mp2kReload(struct GBAAudioMixer* mixer) {
struct ARMCore* cpu = mixer->p->p->cpu;
struct ARMMemory* memory = &cpu->memory;
mixer->context.magic = memory->load32(cpu, mixer->contextAddress + offsetof(struct GBAMP2kContext, magic), 0);
int i;
for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) {
struct GBAMP2kSoundChannel* ch = &mixer->context.chans[i];
struct GBAMP2kTrack* track = &mixer->activeTracks[i];
track->waiting = false;
uint32_t base = mixer->contextAddress + offsetof(struct GBAMP2kContext, chans[i]);
ch->status = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, status), 0);
ch->type = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, type), 0);
ch->rightVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, rightVolume), 0);
ch->leftVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, leftVolume), 0);
ch->adsr.attack = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.attack), 0);
ch->adsr.decay = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.decay), 0);
ch->adsr.sustain = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.sustain), 0);
ch->adsr.release = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.release), 0);
ch->ky = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, ky), 0);
ch->envelopeV = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeV), 0);
ch->envelopeRight = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeRight), 0);
ch->envelopeLeft = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeLeft), 0);
ch->echoVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, echoVolume), 0);
ch->echoLength = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, echoLength), 0);
ch->d1 = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d1), 0);
ch->d2 = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d2), 0);
ch->gt = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, gt), 0);
ch->midiKey = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, midiKey), 0);
ch->ve = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, ve), 0);
ch->pr = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, pr), 0);
ch->rp = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, rp), 0);
ch->d3[0] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[0]), 0);
ch->d3[1] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[1]), 0);
ch->d3[2] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[2]), 0);
ch->ct = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, ct), 0);
ch->fw = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, fw), 0);
ch->freq = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, freq), 0);
ch->waveData = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, waveData), 0);
ch->cp = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, cp), 0);
ch->track = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, track), 0);
ch->pp = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, pp), 0);
ch->np = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, np), 0);
ch->d4 = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, d4), 0);
ch->xpi = memory->load16(cpu, base + offsetof(struct GBAMP2kSoundChannel, xpi), 0);
ch->xpc = memory->load16(cpu, base + offsetof(struct GBAMP2kSoundChannel, xpc), 0);
base = ch->track;
if (base) {
track->track.flags = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, flags), 0);
track->track.wait = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, wait), 0);
track->track.patternLevel = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternLevel), 0);
track->track.repN = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, repN), 0);
track->track.gateTime = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, gateTime), 0);
track->track.key = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, key), 0);
track->track.velocity = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, velocity), 0);
track->track.runningStatus = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, runningStatus), 0);
track->track.keyM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyM), 0);
track->track.pitM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pitM), 0);
track->track.keyShift = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyShift), 0);
track->track.keyShiftX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyShiftX), 0);
track->track.tune = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, tune), 0);
track->track.pitX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pitX), 0);
track->track.bend = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, bend), 0);
track->track.bendRange = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, bendRange), 0);
track->track.volMR = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volMR), 0);
track->track.volML = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volML), 0);
track->track.vol = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, vol), 0);
track->track.volX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volX), 0);
track->track.pan = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pan), 0);
track->track.panX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, panX), 0);
track->track.modM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, modM), 0);
track->track.mod = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, mod), 0);
track->track.modT = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, modT), 0);
track->track.lfoSpeed = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoSpeed), 0);
track->track.lfoSpeedC = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoSpeedC), 0);
track->track.lfoDelay = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoDelay), 0);
track->track.lfoDelayC = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoDelayC), 0);
track->track.priority = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, priority), 0);
track->track.echoVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, echoVolume), 0);
track->track.echoLength = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, echoLength), 0);
track->track.chan = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, chan), 0);
_loadInstrument(cpu, &track->track.instrument, base + offsetof(struct GBAMP2kMusicPlayerTrack, instrument));
track->track.cmdPtr = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, cmdPtr), 0);
track->track.patternStack[0] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[0]), 0);
track->track.patternStack[1] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[1]), 0);
track->track.patternStack[2] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[2]), 0);
} else {
memset(&track->track, 0, sizeof(track->track));
}
if (track->track.runningStatus == 0xCD) {
// XCMD isn't supported
mixer->p->externalMixing = false;
}
}
}
bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address) {
if (address < BASE_WORKING_RAM) {
return false;
}
if (address != mixer->contextAddress) {
mixer->contextAddress = address;
mixer->p->externalMixing = true;
_mp2kReload(mixer);
}
return true;
}
void _mp2kStep(struct GBAAudioMixer* mixer) {
mixer->frame += mixer->p->sampleInterval;
while (mixer->frame >= VIDEO_TOTAL_LENGTH / mixer->tempo) {
int i;
for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) {
struct GBAMP2kTrack* track = &mixer->activeTracks[i];
if (track->channel->status > 0) {
_stepSample(mixer, track);
} else {
track->currentOffset = 0;
track->samplePlaying = 0;
CircleBufferClear(&track->buffer);
}
}
mixer->frame -= VIDEO_TOTAL_LENGTH / mixer->tempo;
}
uint32_t interval = mixer->p->sampleInterval / OVERSAMPLE;
int i;
for (i = 0; i < OVERSAMPLE; ++i) {
struct GBAStereoSample sample = {0};
size_t track;
for (track = 0; track < MP2K_MAX_SOUND_CHANNELS; ++track) {
if (!mixer->activeTracks[track].channel->status) {
continue;
}
int16_t value;
CircleBufferRead16(&mixer->activeTracks[track].buffer, &value);
sample.left += value;
CircleBufferRead16(&mixer->activeTracks[track].buffer, &value);
sample.right += value;
}
if (mixer->p->externalMixing) {
blip_add_delta(mixer->p->psg.left, mixer->p->clock + i * interval, sample.left - mixer->last.left);
blip_add_delta(mixer->p->psg.right, mixer->p->clock + i * interval, sample.left - mixer->last.left);
}
mixer->last = sample;
}
}
void _mp2kVblank(struct GBAAudioMixer* mixer) {
if (!mixer->contextAddress) {
return;
}
mLOG(GBA_AUDIO, DEBUG, "Frame");
mixer->p->externalMixing = true;
_mp2kReload(mixer);
}

View File

@ -147,6 +147,7 @@ void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
proxyRenderer->backend->deinit(proxyRenderer->backend);
} else {
proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_DEINIT);
mVideoLoggerRendererFlush(proxyRenderer->logger);
}
mVideoLoggerRendererDeinit(proxyRenderer->logger);
@ -307,28 +308,20 @@ void GBAVideoProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y)
void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->lock(proxyRenderer->logger);
}
if (!proxyRenderer->logger->block) {
proxyRenderer->backend->finishFrame(proxyRenderer->backend);
}
mVideoLoggerRendererFinishFrame(proxyRenderer->logger);
mVideoLoggerRendererFlush(proxyRenderer->logger);
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->unlock(proxyRenderer->logger);
}
}
static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->lock(proxyRenderer->logger);
// Insert an extra item into the queue to make sure it gets flushed
mVideoLoggerRendererFlush(proxyRenderer->logger);
proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_GET_PIXELS);
mVideoLoggerRendererFlush(proxyRenderer->logger);
proxyRenderer->logger->unlock(proxyRenderer->logger);
*pixels = proxyRenderer->logger->pixelBuffer;
*stride = proxyRenderer->logger->pixelStride;
} else {
@ -340,8 +333,6 @@ static void GBAVideoProxyRendererPutPixels(struct GBAVideoRenderer* renderer, si
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
proxyRenderer->logger->lock(proxyRenderer->logger);
// Insert an extra item into the queue to make sure it gets flushed
mVideoLoggerRendererFlush(proxyRenderer->logger);
}
proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels);
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {

View File

@ -241,10 +241,6 @@ void GBAReset(struct ARMCore* cpu) {
if (gba->pristineRomSize > SIZE_CART0) {
GBAMatrixReset(gba);
}
if (!gba->romVf && gba->memory.rom) {
GBASkipBIOS(gba);
}
}
void GBASkipBIOS(struct GBA* gba) {
@ -788,6 +784,10 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) {
void GBAFrameStarted(struct GBA* gba) {
GBATestKeypadIRQ(gba);
if (gba->audio.mixer) {
gba->audio.mixer->vblank(gba->audio.mixer);
}
size_t c;
for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) {
struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gba->coreCallbacks, c);

File diff suppressed because it is too large Load Diff

View File

@ -17,19 +17,12 @@
#define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \
SPRITE_YBASE_ ## DEPTH(inY); \
unsigned tileData; \
if (outX % mosaicH) { \
if (!inX && xOffset > 0) { \
inX = mosaicH - (outX % mosaicH); \
outX += mosaicH - (outX % mosaicH); \
} else if (inX == width - xOffset) { \
inX = mosaicH + (outX % mosaicH); \
outX += mosaicH - (outX % mosaicH); \
} \
} \
for (; outX < condition; ++outX, inX += xOffset) { \
int localX = inX - xOffset * (outX % mosaicH); \
if (localX < 0 || localX > width - 1) { \
continue; \
if (localX < 0) { \
localX = 0; \
} else if (localX > width - 1) {\
localX = width - 1; \
} \
SPRITE_XBASE_ ## DEPTH(localX); \
SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
@ -55,6 +48,31 @@
SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
}
#define SPRITE_TRANSFORMED_MOSAIC_LOOP(DEPTH, TYPE) \
unsigned tileData; \
unsigned widthMask = ~(width - 1); \
unsigned heightMask = ~(height - 1); \
int localX = xAccum >> 8; \
int localY = yAccum >> 8; \
for (; outX < condition; ++outX, ++inX) { \
renderer->spriteCyclesRemaining -= 2; \
xAccum += mat.a; \
yAccum += mat.c; \
\
if (outX % mosaicH == 0) { \
localX = xAccum >> 8; \
localY = yAccum >> 8; \
} \
\
if (localX & widthMask || localY & heightMask) { \
continue; \
} \
\
SPRITE_YBASE_ ## DEPTH(localY); \
SPRITE_XBASE_ ## DEPTH(localX); \
SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
}
#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 4;
@ -281,6 +299,13 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
if (end < condition) {
condition = end;
}
int mosaicH = 1;
if (GBAObjAttributesAIsMosaic(sprite->a)) {
mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
if (condition % mosaicH) {
condition += mosaicH - (condition % mosaicH);
}
}
int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)) + (width << 7);
int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)) + (height << 7);
@ -340,6 +365,13 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
if (flags & FLAG_OBJWIN) {
SPRITE_TRANSFORMED_LOOP(16, OBJWIN);
} else if (mosaicH > 1) {
if (objwinSlowPath) {
objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL_OBJWIN);
} else {
SPRITE_TRANSFORMED_MOSAIC_LOOP(16, NORMAL);
}
} else if (objwinSlowPath) {
objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4];
SPRITE_TRANSFORMED_LOOP(16, NORMAL_OBJWIN);
@ -358,6 +390,12 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 8];
if (flags & FLAG_OBJWIN) {
SPRITE_TRANSFORMED_LOOP(256, OBJWIN);
} else if (mosaicH > 1) {
if (objwinSlowPath) {
SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL_OBJWIN);
} else {
SPRITE_TRANSFORMED_MOSAIC_LOOP(256, NORMAL);
}
} else if (objwinSlowPath) {
objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 8];
SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN);

View File

@ -942,11 +942,17 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere
struct GBAVideoRendererSprite* sprite = &renderer->sprites[i];
int localY = y;
renderer->end = 0;
if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
continue;
}
if (GBAObjAttributesAIsMosaic(sprite->obj.a)) {
localY = mosaicY;
if (localY < sprite->y) {
localY = sprite->y;
}
if (localY >= sprite->endY) {
localY = sprite->endY - 1;
}
if ((localY < sprite->y && (sprite->endY - 256 < 0 || localY >= sprite->endY - 256)) || localY >= sprite->endY) {
continue;
}
for (w = 0; w < renderer->nWindows; ++w) {
if (renderer->spriteCyclesRemaining <= 0) {

View File

@ -16,6 +16,7 @@
static int cheatsSetup(void** state) {
struct mCore* core = GBACoreCreate();
core->init(core);
mCoreInitConfig(core, NULL);
core->cheatDevice(core);
*state = core;
return 0;
@ -26,6 +27,7 @@ static int cheatsTeardown(void** state) {
return 0;
}
struct mCore* core = *state;
mCoreConfigDeinit(&core->config);
core->deinit(core);
return 0;
}

View File

@ -27,7 +27,9 @@ M_TEST_DEFINE(reset) {
struct mCore* core = GBACoreCreate();
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
core->reset(core);
mCoreConfigDeinit(&core->config);
core->deinit(core);
}
@ -36,7 +38,9 @@ M_TEST_DEFINE(loadNullROM) {
assert_non_null(core);
assert_true(core->init(core));
assert_false(core->loadROM(core, NULL));
mCoreInitConfig(core, NULL);
core->reset(core);
mCoreConfigDeinit(&core->config);
core->deinit(core);
}

View File

@ -110,8 +110,7 @@ void GBAVideoReset(struct GBAVideo* video) {
memset(video->palette, 0, sizeof(video->palette));
memset(video->oam.raw, 0, sizeof(video->oam.raw));
video->renderer->deinit(video->renderer);
video->renderer->init(video->renderer);
video->renderer->reset(video->renderer);
}
void GBAVideoDeinit(struct GBAVideo* video) {

View File

@ -136,9 +136,11 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
glBindVertexArray(context->initialShader.vao);
glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
glEnableVertexAttribArray(context->initialShader.positionLocation);
glVertexAttribPointer(context->initialShader.positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glBindVertexArray(context->finalShader.vao);
glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
glEnableVertexAttribArray(context->finalShader.positionLocation);
glVertexAttribPointer(context->finalShader.positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glBindVertexArray(0);
@ -246,7 +248,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
glBindTexture(GL_TEXTURE_2D, oldTex);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
glUseProgram(shader->program);
glUniform1i(shader->texLocation, 0);
@ -303,7 +305,6 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
break;
}
}
glEnableVertexAttribArray(shader->positionLocation);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glBindTexture(GL_TEXTURE_2D, shader->tex);
}
@ -327,6 +328,7 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
_drawShader(context, &context->finalShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glUseProgram(0);
glBindVertexArray(0);
}
void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) {
@ -463,8 +465,8 @@ void mGLES2ShaderAttach(struct mGLES2Context* context, struct mGLES2Shader* shad
glBindVertexArray(context->shaders[i].vao);
glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
glVertexAttribPointer(context->shaders[i].positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(context->shaders[i].positionLocation);
glVertexAttribPointer(context->shaders[i].positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
}
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

View File

@ -67,6 +67,10 @@ void Action::trigger(bool active) {
return;
}
if (m_exclusive && !m_booleanFunction) {
active = true;
}
if (m_function && active) {
m_function();
}

View File

@ -291,6 +291,7 @@ if(QT_STATIC)
if(WIN32)
list(APPEND QT_LIBRARIES qwindows dwmapi imm32 uxtheme Qt5EventDispatcherSupport Qt5FontDatabaseSupport Qt5ThemeSupport Qt5WindowsUIAutomationSupport)
set_target_properties(Qt5::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE};version;winmm;ssl;crypto;ws2_32;iphlpapi;crypt32;userenv;netapi32;wtsapi32")
set_target_properties(Qt5::Gui PROPERTIES INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
elseif(APPLE)
find_package(Cups)
find_package(Qt5PrintSupport)

View File

@ -86,6 +86,7 @@ CoreController::CoreController(mCore* core, QObject* parent)
context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer->data()), controller->screenDimensions().width());
}
QMetaObject::invokeMethod(controller, "didReset");
controller->finishFrame();
};
@ -196,9 +197,6 @@ CoreController::~CoreController() {
mCacheSetDeinit(m_cacheSet.get());
m_cacheSet.reset();
}
mCoreConfigDeinit(&m_threadContext.core->config);
m_threadContext.core->deinit(m_threadContext.core);
}
const color_t* CoreController::drawContext() {
@ -368,7 +366,6 @@ void CoreController::stop() {
#endif
setPaused(false);
mCoreThreadEnd(&m_threadContext);
emit stopping();
}
void CoreController::reset() {
@ -445,13 +442,21 @@ void CoreController::rewind(int states) {
}
void CoreController::setFastForward(bool enable) {
if (m_fastForward == enable) {
return;
}
m_fastForward = enable;
updateFastForward();
emit fastForwardChanged(enable);
}
void CoreController::forceFastForward(bool enable) {
if (m_fastForwardForced == enable) {
return;
}
m_fastForwardForced = enable;
updateFastForward();
emit fastForwardChanged(enable || m_fastForward);
}
void CoreController::loadState(int slot) {

View File

@ -99,6 +99,9 @@ public:
void setInputController(InputController*);
void setLogger(LogController*);
bool audioSync() const { return m_audioSync; }
bool videoSync() const { return m_videoSync; }
public slots:
void start();
void stop();
@ -167,6 +170,7 @@ signals:
void crashed(const QString& errorMessage);
void failed();
void frameAvailable();
void didReset();
void stateLoaded();
void rewound();

View File

@ -20,7 +20,7 @@ Display* Display::create(QWidget* parent) {
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY)
QSurfaceFormat format;
format.setSwapInterval(1);
format.setSwapBehavior(QSurfaceFormat::TripleBuffer);
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
#endif
switch (s_driver) {

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2015 Jeffrey Pfau
/* Copyright (c) 2013-2019 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
@ -34,17 +34,32 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
: Display(parent)
, m_gl(nullptr)
{
setAttribute(Qt::WA_NativeWindow);
windowHandle()->create();
// This can spontaneously re-enter into this->resizeEvent before creation is done, so we
// need to make sure it's initialized to nullptr before we assign the new object to it
m_gl = new QOpenGLContext;
m_gl->setFormat(format);
m_gl->create();
setAttribute(Qt::WA_NativeWindow);
m_gl->makeCurrent(windowHandle());
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
int majorVersion = m_gl->format().majorVersion();
QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
m_gl->doneCurrent();
if (majorVersion == 2 && !extensions.contains("GL_ARB_framebuffer_object")) {
QSurfaceFormat newFormat(format);
newFormat.setVersion(1, 4);
m_gl->setFormat(newFormat);
m_gl->create();
}
m_painter = new PainterGL(&m_videoProxy, windowHandle(), m_gl);
setUpdatesEnabled(false); // Prevent paint events, which can cause race conditions
connect(&m_videoProxy, &VideoProxy::dataAvailable, &m_videoProxy, &VideoProxy::processData);
connect(&m_videoProxy, &VideoProxy::eventPosted, &m_videoProxy, &VideoProxy::handleEvent);
}
DisplayGL::~DisplayGL() {
@ -98,6 +113,7 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
#endif
resizePainter();
connect(m_context.get(), &CoreController::didReset, this, &DisplayGL::resizeContext);
}
void DisplayGL::stopDrawing() {
@ -249,10 +265,9 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent
#endif
m_backend->swap = [](VideoBackend* v) {
PainterGL* painter = static_cast<PainterGL*>(v->user);
if (!painter->m_gl->isValid()) {
return;
if (!painter->m_swapTimer.isActive()) {
QMetaObject::invokeMethod(&painter->m_swapTimer, "start");
}
painter->m_gl->swapBuffers(painter->m_gl->surface());
};
m_backend->init(m_backend, 0);
@ -270,6 +285,10 @@ PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent
for (int i = 0; i < 2; ++i) {
m_free.append(new uint32_t[256 * 512]);
}
m_swapTimer.setInterval(16);
m_swapTimer.setSingleShot(true);
connect(&m_swapTimer, &QTimer::timeout, this, &PainterGL::swap);
}
PainterGL::~PainterGL() {
@ -305,18 +324,8 @@ void PainterGL::resizeContext() {
return;
}
if (!m_active) {
m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
}
QSize size = m_context->screenDimensions();
m_backend->setDimensions(m_backend, size.width(), size.height());
if (!m_active) {
m_gl->doneCurrent();
}
}
void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
@ -368,26 +377,21 @@ void PainterGL::draw() {
return;
}
if (m_needsUnlock) {
QTimer::singleShot(0, this, &PainterGL::draw);
return;
}
if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) {
dequeue();
mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
m_painter.begin(m_window);
performDraw();
m_painter.end();
m_backend->swap(m_backend);
if (!m_delayTimer.isValid()) {
m_delayTimer.start();
} else if (m_gl->format().swapInterval()) {
while (m_delayTimer.elapsed() < 15) {
QThread::usleep(100);
}
m_delayTimer.restart();
}
forceDraw();
if (m_context->thread()->impl->sync.videoFrameWait) {
m_needsUnlock = true;
} else {
mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
}
if (!m_queue.isEmpty()) {
QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
} else {
mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
}
}
@ -404,6 +408,9 @@ void PainterGL::stop() {
dequeueAll();
m_backend->clear(m_backend);
m_backend->swap(m_backend);
if (m_videoProxy) {
m_videoProxy->reset();
}
m_gl->doneCurrent();
m_gl->moveToThread(m_surface->thread());
m_context.reset();
@ -428,6 +435,30 @@ void PainterGL::performDraw() {
if (m_messagePainter) {
m_messagePainter->paint(&m_painter);
}
m_frameReady = true;
}
void PainterGL::swap() {
if (!m_gl->isValid()) {
return;
}
if (m_frameReady) {
m_gl->swapBuffers(m_surface);
m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
m_frameReady = false;
}
if (m_needsUnlock) {
mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
m_needsUnlock = false;
}
if (!m_queue.isEmpty()) {
QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
} else {
m_swapTimer.start();
}
}
void PainterGL::enqueue(const uint32_t* backing) {
@ -480,12 +511,6 @@ void PainterGL::setShaders(struct VDir* dir) {
return;
}
#ifdef BUILD_GLES2
if (!m_active) {
m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
}
if (m_shader.passes) {
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
mGLES2ShaderFree(&m_shader);
@ -494,9 +519,6 @@ void PainterGL::setShaders(struct VDir* dir) {
if (m_started) {
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
}
if (!m_active) {
m_gl->doneCurrent();
}
#endif
}
@ -505,19 +527,10 @@ void PainterGL::clearShaders() {
return;
}
#ifdef BUILD_GLES2
if (!m_active) {
m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
}
if (m_shader.passes) {
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
mGLES2ShaderFree(&m_shader);
}
if (!m_active) {
m_gl->doneCurrent();
}
#endif
}

View File

@ -16,13 +16,13 @@
#endif
#endif
#include <QElapsedTimer>
#include <QOpenGLContext>
#include <QList>
#include <QMouseEvent>
#include <QPainter>
#include <QQueue>
#include <QThread>
#include <QTimer>
#include "VideoProxy.h"
@ -105,6 +105,9 @@ public slots:
int glTex();
private slots:
void swap();
private:
void performDraw();
void dequeue();
@ -125,7 +128,9 @@ private:
VideoBackend* m_backend = nullptr;
QSize m_size;
MessagePainter* m_messagePainter = nullptr;
QElapsedTimer m_delayTimer;
QTimer m_swapTimer{this};
bool m_needsUnlock = false;
bool m_frameReady = false;
VideoProxy* m_videoProxy;
};

View File

@ -127,6 +127,9 @@ void LogController::logToStdout(bool log) {
void LogController::setLogFile(const QString& file) {
m_logStream.reset();
if (file.isEmpty()) {
return;
}
m_logFile = std::make_unique<QFile>(file);
m_logFile->open(QIODevice::Append | QIODevice::Text);
m_logStream = std::make_unique<QTextStream>(m_logFile.get());

View File

@ -104,7 +104,7 @@
<number>1</number>
</property>
<property name="maximum">
<number>4</number>
<number>8</number>
</property>
</widget>
</item>

View File

@ -59,7 +59,7 @@
<number>1</number>
</property>
<property name="maximum">
<number>6</number>
<number>8</number>
</property>
</widget>
</item>

View File

@ -57,6 +57,10 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
}
});
connect(m_ui.nativeGB, &QAbstractButton::pressed, [this]() {
m_ui.fpsTarget->setValue(double(GBA_ARM7TDMI_FREQUENCY) / double(VIDEO_TOTAL_LENGTH));
});
if (m_ui.savegamePath->text().isEmpty()) {
m_ui.savegameSameDir->setChecked(true);
}
@ -364,7 +368,6 @@ void SettingsView::updateConfig() {
saveSetting("videoSync", m_ui.videoSync);
saveSetting("audioSync", m_ui.audioSync);
saveSetting("frameskip", m_ui.frameskip);
saveSetting("fpsTarget", m_ui.fpsTarget);
saveSetting("autofireThreshold", m_ui.autofireThreshold);
saveSetting("lockAspectRatio", m_ui.lockAspectRatio);
saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
@ -395,7 +398,7 @@ void SettingsView::updateConfig() {
saveSetting("logToStdout", m_ui.logToStdout);
saveSetting("logFile", m_ui.logFile);
saveSetting("useDiscordPresence", m_ui.useDiscordPresence);
saveSetting("audioHle", m_ui.audioHle);
saveSetting("gba.audioHle", m_ui.audioHle);
if (m_ui.fastForwardUnbounded->isChecked()) {
saveSetting("fastForwardRatio", "-1");
@ -403,6 +406,13 @@ void SettingsView::updateConfig() {
saveSetting("fastForwardRatio", m_ui.fastForwardRatio);
}
double nativeFps = double(GBA_ARM7TDMI_FREQUENCY) / double(VIDEO_TOTAL_LENGTH);
if (nativeFps - m_ui.fpsTarget->value() < 0.0001) {
m_controller->setOption("fpsTarget", QVariant(nativeFps));
} else {
saveSetting("fpsTarget", m_ui.fpsTarget);
}
switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) {
case IDLE_LOOP_IGNORE:
saveSetting("idleOptimization", "ignore");
@ -549,7 +559,7 @@ void SettingsView::reloadConfig() {
loadSetting("logToStdout", m_ui.logToStdout);
loadSetting("logFile", m_ui.logFile);
loadSetting("useDiscordPresence", m_ui.useDiscordPresence);
loadSetting("audioHle", m_ui.audioHle);
loadSetting("gba.audioHle", m_ui.audioHle);
loadSetting("videoScale", m_ui.videoScale, 1);
m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt());

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>790</width>
<height>686</height>
<width>849</width>
<height>753</height>
</rect>
</property>
<property name="sizePolicy">
@ -40,7 +40,7 @@
</property>
<property name="maximumSize">
<size>
<width>200</width>
<width>180</width>
<height>16777215</height>
</size>
</property>
@ -399,21 +399,21 @@
</item>
</layout>
</item>
<item row="9" column="0" colspan="2">
<item row="10" column="0" colspan="2">
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="0">
<item row="11" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Sync:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="11" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QCheckBox" name="videoSync">
@ -431,27 +431,34 @@
</item>
</layout>
</item>
<item row="11" column="1">
<item row="12" column="1">
<widget class="QCheckBox" name="lockAspectRatio">
<property name="text">
<string>Lock aspect ratio</string>
</property>
</widget>
</item>
<item row="12" column="1">
<item row="13" column="1">
<widget class="QCheckBox" name="lockIntegerScaling">
<property name="text">
<string>Force integer scaling</string>
</property>
</widget>
</item>
<item row="13" column="1">
<item row="14" column="1">
<widget class="QCheckBox" name="resampleVideo">
<property name="text">
<string>Bilinear filtering</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QPushButton" name="nativeGB">
<property name="text">
<string>Native (59.7275)</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="interface_2">
@ -894,7 +901,7 @@
<number>1</number>
</property>
<property name="maximum">
<number>13</number>
<number>16</number>
</property>
</widget>
</item>
@ -903,9 +910,6 @@
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="audioHle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>XQ GBA audio (experimental)</string>
</property>

View File

@ -126,7 +126,7 @@
<number>1</number>
</property>
<property name="maximum">
<number>4</number>
<number>8</number>
</property>
</widget>
</item>

View File

@ -24,6 +24,9 @@ VideoProxy::VideoProxy() {
m_logger.d.writeData = &callback<bool, const void*, size_t>::func<&VideoProxy::writeData>;
m_logger.d.readData = &callback<bool, void*, size_t, bool>::func<&VideoProxy::readData>;
m_logger.d.postEvent = &callback<void, enum mVideoLoggerEvent>::func<&VideoProxy::postEvent>;
connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData);
connect(this, &VideoProxy::eventPosted, this, &VideoProxy::handleEvent);
}
void VideoProxy::attach(CoreController* controller) {
@ -41,7 +44,10 @@ void VideoProxy::init() {
}
void VideoProxy::reset() {
m_mutex.lock();
RingFIFOClear(&m_dirtyQueue);
m_toThreadCond.wakeAll();
m_mutex.unlock();
}
void VideoProxy::deinit() {
@ -92,11 +98,13 @@ void VideoProxy::unlock() {
}
void VideoProxy::wait() {
m_mutex.lock();
while (RingFIFOSize(&m_dirtyQueue)) {
emit dataAvailable();
m_toThreadCond.wakeAll();
m_fromThreadCond.wait(&m_mutex, 1);
}
m_mutex.unlock();
}
void VideoProxy::wake(int y) {

View File

@ -30,11 +30,11 @@ signals:
public slots:
void processData();
void reset();
void handleEvent(int);
private:
void init();
void reset();
void deinit();
bool writeData(const void* data, size_t length);

View File

@ -648,8 +648,12 @@ void Window::closeEvent(QCloseEvent* event) {
m_config->setOption("width", GBA_VIDEO_HORIZONTAL_PIXELS * m_savedScale);
}
saveConfig();
if (m_controller) {
event->ignore();
m_pendingClose = true;
} else {
m_display.reset();
QMainWindow::closeEvent(event);
}
}
void Window::focusInEvent(QFocusEvent*) {
@ -784,7 +788,6 @@ void Window::gameStarted() {
}
attachWidget(m_display.get());
setMouseTracking(true);
m_display->setMinimumSize(size);
setFocus();
#ifndef Q_OS_MAC
@ -792,7 +795,6 @@ void Window::gameStarted() {
menuBar()->hide();
}
#endif
m_display->startDrawing(m_controller);
reloadAudioDriver();
multiplayerChanged();
@ -843,6 +845,11 @@ void Window::gameStarted() {
void Window::gameStopped() {
m_controller.reset();
m_display->stopDrawing();
if (m_pendingClose) {
m_display.reset();
close();
}
for (Action* action : m_platformActions) {
action->setEnabled(true);
}
@ -929,7 +936,6 @@ void Window::reloadDisplayDriver() {
m_shaderView = std::make_unique<ShaderSelector>(m_display.get(), m_config);
#endif
connect(this, &Window::shutdown, m_display.get(), &Display::stopDrawing);
connect(m_display.get(), &Display::hideCursor, [this]() {
if (static_cast<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display.get()) {
m_screenWidget->setCursor(Qt::BlankCursor);
@ -954,8 +960,6 @@ void Window::reloadDisplayDriver() {
#endif
if (m_controller) {
m_display->setMinimumSize(m_controller->screenDimensions());
connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing);
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext);
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw);
connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw);
@ -966,14 +970,13 @@ void Window::reloadDisplayDriver() {
attachWidget(m_display.get());
m_display->startDrawing(m_controller);
} else {
}
#ifdef M_CORE_GB
m_display->setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
#elif defined(M_CORE_GBA)
m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
#endif
}
}
void Window::reloadAudioDriver() {
if (!m_controller) {
@ -991,6 +994,7 @@ void Window::reloadAudioDriver() {
m_audioProcessor->requestSampleRate(opts->sampleRate);
m_audioProcessor->start();
connect(m_controller.get(), &CoreController::stopping, m_audioProcessor.get(), &AudioProcessor::stop);
connect(m_controller.get(), &CoreController::fastForwardChanged, m_audioProcessor.get(), &AudioProcessor::inputParametersChanged);
}
void Window::tryMakePortable() {
@ -1372,7 +1376,7 @@ void Window::setupMenu(QMenuBar* menubar) {
m_actions.addMenu(tr("Audio/&Video"), "av");
m_actions.addMenu(tr("Frame size"), "frame", "av");
for (int i = 1; i <= 6; ++i) {
for (int i = 1; i <= 8; ++i) {
Action* setSize = m_actions.addAction(tr("%1×").arg(QString::number(i)), QString("frame.%1x").arg(QString::number(i)), [this, i]() {
Action* setSize = m_frameSizes[i];
showNormal();
@ -1739,6 +1743,9 @@ void Window::setController(CoreController* controller, const QString& fname) {
if (!controller) {
return;
}
if (m_pendingClose) {
return;
}
if (m_controller) {
m_controller->stop();
@ -1771,12 +1778,14 @@ void Window::setController(CoreController* controller, const QString& fname) {
m_inputController.recalibrateAxes();
m_controller->setInputController(&m_inputController);
m_controller->setLogger(&m_log);
m_display->startDrawing(m_controller);
connect(this, &Window::shutdown, [this]() {
if (!m_controller) {
return;
}
m_controller->stop();
disconnect(m_controller.get(), &CoreController::started, this, &Window::gameStarted);
});
connect(m_controller.get(), &CoreController::started, this, &Window::gameStarted);
@ -1804,7 +1813,6 @@ void Window::setController(CoreController* controller, const QString& fname) {
emit paused(false);
});
connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing);
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::resizeContext);
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw);
connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw);

View File

@ -207,6 +207,7 @@ private:
QString m_pendingPatch;
QString m_pendingState;
bool m_pendingPause = false;
bool m_pendingClose = false;
bool m_hitUnimplementedBiosCall;

File diff suppressed because it is too large Load Diff

View File

@ -4243,21 +4243,26 @@ Game Boy Advance 是任天堂有限公司Nintendo Co., Ltd.)的注册商标
</message>
<message>
<location filename="../SettingsView.ui" line="67"/>
<source>Enhancements</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="72"/>
<source>BIOS</source>
<translation>BIOS</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="72"/>
<location filename="../SettingsView.ui" line="77"/>
<source>Paths</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="77"/>
<location filename="../SettingsView.ui" line="82"/>
<source>Logging</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="82"/>
<location filename="../SettingsView.ui" line="87"/>
<source>Game Boy</source>
<translation>Game Boy</translation>
</message>
@ -4272,491 +4277,522 @@ Game Boy Advance 是任天堂有限公司Nintendo Co., Ltd.)的注册商标
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="129"/>
<location filename="../SettingsView.ui" line="151"/>
<location filename="../SettingsView.ui" line="134"/>
<location filename="../SettingsView.ui" line="156"/>
<source>1536</source>
<translation>1536</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="136"/>
<location filename="../SettingsView.ui" line="141"/>
<source>512</source>
<translation>512</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="141"/>
<location filename="../SettingsView.ui" line="146"/>
<source>768</source>
<translation>768</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="146"/>
<location filename="../SettingsView.ui" line="151"/>
<source>1024</source>
<translation>1024</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="156"/>
<location filename="../SettingsView.ui" line="161"/>
<source>2048</source>
<translation>2048</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="161"/>
<location filename="../SettingsView.ui" line="166"/>
<source>3072</source>
<translation>3072</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="166"/>
<location filename="../SettingsView.ui" line="171"/>
<source>4096</source>
<translation>4096</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="174"/>
<location filename="../SettingsView.ui" line="179"/>
<source>samples</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="183"/>
<location filename="../SettingsView.ui" line="188"/>
<source>Sample rate:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="195"/>
<location filename="../SettingsView.ui" line="212"/>
<location filename="../SettingsView.ui" line="200"/>
<location filename="../SettingsView.ui" line="217"/>
<source>44100</source>
<translation>44100</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="202"/>
<location filename="../SettingsView.ui" line="207"/>
<source>22050</source>
<translation>22050</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="207"/>
<location filename="../SettingsView.ui" line="212"/>
<source>32000</source>
<translation>32000</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="217"/>
<location filename="../SettingsView.ui" line="222"/>
<source>48000</source>
<translation>48000</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="225"/>
<location filename="../SettingsView.ui" line="230"/>
<source>Hz</source>
<translation>Hz</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="234"/>
<location filename="../SettingsView.ui" line="239"/>
<source>Volume:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="265"/>
<location filename="../SettingsView.ui" line="305"/>
<location filename="../SettingsView.ui" line="270"/>
<location filename="../SettingsView.ui" line="310"/>
<source>Mute</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="274"/>
<location filename="../SettingsView.ui" line="279"/>
<source>Fast forward volume:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="321"/>
<location filename="../SettingsView.ui" line="326"/>
<source>Display driver:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="338"/>
<location filename="../SettingsView.ui" line="343"/>
<source>Frameskip:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="347"/>
<location filename="../SettingsView.ui" line="352"/>
<source>Skip every</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="357"/>
<location filename="../SettingsView.ui" line="717"/>
<location filename="../SettingsView.ui" line="362"/>
<location filename="../SettingsView.ui" line="722"/>
<source>frames</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="366"/>
<location filename="../SettingsView.ui" line="371"/>
<source>FPS target:</source>
<translation> FPS:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="391"/>
<location filename="../SettingsView.ui" line="396"/>
<source>frames per second</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="407"/>
<location filename="../SettingsView.ui" line="412"/>
<source>Sync:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="416"/>
<location filename="../SettingsView.ui" line="421"/>
<source>Video</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="423"/>
<location filename="../SettingsView.ui" line="428"/>
<source>Audio</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="432"/>
<location filename="../SettingsView.ui" line="437"/>
<source>Lock aspect ratio</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="439"/>
<location filename="../SettingsView.ui" line="444"/>
<source>Force integer scaling</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="446"/>
<location filename="../SettingsView.ui" line="451"/>
<source>Bilinear filtering</source>
<translation>线</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="457"/>
<location filename="../SettingsView.ui" line="462"/>
<source>Language</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="465"/>
<location filename="../SettingsView.ui" line="470"/>
<source>English</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="480"/>
<location filename="../SettingsView.ui" line="485"/>
<source>Library:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="488"/>
<location filename="../SettingsView.ui" line="493"/>
<source>List view</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="493"/>
<location filename="../SettingsView.ui" line="498"/>
<source>Tree view</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="501"/>
<location filename="../SettingsView.ui" line="506"/>
<source>Show when no game open</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="511"/>
<location filename="../SettingsView.ui" line="516"/>
<source>Clear cache</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="525"/>
<location filename="../SettingsView.ui" line="530"/>
<source>Allow opposing input directions</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="532"/>
<location filename="../SettingsView.ui" line="537"/>
<source>Suspend screensaver</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="542"/>
<location filename="../SettingsView.ui" line="547"/>
<source>Pause when inactive</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="549"/>
<location filename="../SettingsView.ui" line="554"/>
<source>Show FPS in title bar</source>
<translation> FPS</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="573"/>
<location filename="../SettingsView.ui" line="578"/>
<source>Automatically save cheats</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="583"/>
<location filename="../SettingsView.ui" line="588"/>
<source>Automatically load cheats</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="593"/>
<location filename="../SettingsView.ui" line="598"/>
<source>Automatically save state</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="603"/>
<location filename="../SettingsView.ui" line="608"/>
<source>Automatically load state</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="613"/>
<location filename="../SettingsView.ui" line="618"/>
<source>Enable Discord Rich Presence</source>
<translation> Enable Discord Rich Presence</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="627"/>
<location filename="../SettingsView.ui" line="632"/>
<source>Fast forward speed:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="639"/>
<location filename="../SettingsView.ui" line="644"/>
<location filename="../SettingsView.ui" line="891"/>
<source>×</source>
<translation>×</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="658"/>
<location filename="../SettingsView.ui" line="663"/>
<source>Unbounded</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="670"/>
<location filename="../SettingsView.ui" line="675"/>
<source>Autofire interval:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="694"/>
<location filename="../SettingsView.ui" line="699"/>
<source>Enable rewind</source>
<translation>退</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="701"/>
<location filename="../SettingsView.ui" line="706"/>
<source>Rewind history:</source>
<translation>退:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="733"/>
<location filename="../SettingsView.ui" line="738"/>
<source>Idle loops:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="741"/>
<location filename="../SettingsView.ui" line="746"/>
<source>Run all</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="746"/>
<location filename="../SettingsView.ui" line="751"/>
<source>Remove known</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="751"/>
<location filename="../SettingsView.ui" line="756"/>
<source>Detect and remove</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="759"/>
<location filename="../SettingsView.ui" line="764"/>
<source>Preload entire ROM into memory</source>
<translation> ROM </translation>
</message>
<message>
<location filename="../SettingsView.ui" line="773"/>
<location filename="../SettingsView.ui" line="778"/>
<source>Savestate extra data:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="780"/>
<location filename="../SettingsView.ui" line="824"/>
<location filename="../SettingsView.ui" line="785"/>
<location filename="../SettingsView.ui" line="829"/>
<source>Screenshot</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="790"/>
<location filename="../SettingsView.ui" line="834"/>
<location filename="../SettingsView.ui" line="795"/>
<location filename="../SettingsView.ui" line="839"/>
<source>Save data</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="800"/>
<location filename="../SettingsView.ui" line="841"/>
<location filename="../SettingsView.ui" line="805"/>
<location filename="../SettingsView.ui" line="846"/>
<source>Cheat codes</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="817"/>
<location filename="../SettingsView.ui" line="822"/>
<source>Load extra data:</source>
<translation>:</translation>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="852"/>
<source>GB BIOS file:</source>
<translation>GB BIOS :</translation>
<location filename="../SettingsView.ui" line="857"/>
<source>Video renderer:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="871"/>
<location filename="../SettingsView.ui" line="909"/>
<location filename="../SettingsView.ui" line="944"/>
<location filename="../SettingsView.ui" line="972"/>
<location filename="../SettingsView.ui" line="1013"/>
<location filename="../SettingsView.ui" line="1061"/>
<location filename="../SettingsView.ui" line="1109"/>
<location filename="../SettingsView.ui" line="1157"/>
<location filename="../SettingsView.ui" line="1205"/>
<location filename="../SettingsView.ui" line="865"/>
<source>Software</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="870"/>
<source>OpenGL</source>
<translation>OpenGL</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="878"/>
<source>OpenGL enhancements</source>
<translation>OpenGL </translation>
</message>
<message>
<location filename="../SettingsView.ui" line="884"/>
<source>High-resolution scale:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="907"/>
<source>XQ GBA audio (experimental)</source>
<translation>XQ GBA ()</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="937"/>
<location filename="../SettingsView.ui" line="975"/>
<location filename="../SettingsView.ui" line="1010"/>
<location filename="../SettingsView.ui" line="1038"/>
<location filename="../SettingsView.ui" line="1079"/>
<location filename="../SettingsView.ui" line="1127"/>
<location filename="../SettingsView.ui" line="1175"/>
<location filename="../SettingsView.ui" line="1223"/>
<location filename="../SettingsView.ui" line="1271"/>
<source>Browse</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="880"/>
<location filename="../SettingsView.ui" line="918"/>
<source>GB BIOS file:</source>
<translation>GB BIOS :</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="946"/>
<source>Use BIOS file if found</source>
<translation>使 BIOS </translation>
</message>
<message>
<location filename="../SettingsView.ui" line="890"/>
<location filename="../SettingsView.ui" line="956"/>
<source>Skip BIOS intro</source>
<translation> BIOS </translation>
</message>
<message>
<location filename="../SettingsView.ui" line="918"/>
<location filename="../SettingsView.ui" line="984"/>
<source>GBA BIOS file:</source>
<translation>GBA BIOS :</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="925"/>
<location filename="../SettingsView.ui" line="991"/>
<source>GBC BIOS file:</source>
<translation>GBC BIOS :</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="953"/>
<location filename="../SettingsView.ui" line="1019"/>
<source>SGB BIOS file:</source>
<translation>SGB BIOS :</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="988"/>
<location filename="../SettingsView.ui" line="1054"/>
<source>Save games</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1022"/>
<location filename="../SettingsView.ui" line="1070"/>
<location filename="../SettingsView.ui" line="1118"/>
<location filename="../SettingsView.ui" line="1166"/>
<location filename="../SettingsView.ui" line="1214"/>
<location filename="../SettingsView.ui" line="1088"/>
<location filename="../SettingsView.ui" line="1136"/>
<location filename="../SettingsView.ui" line="1184"/>
<location filename="../SettingsView.ui" line="1232"/>
<location filename="../SettingsView.ui" line="1280"/>
<source>Same directory as the ROM</source>
<translation> ROM </translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1036"/>
<source>Save states</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1084"/>
<location filename="../SettingsView.ui" line="1150"/>
<source>Screenshots</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1132"/>
<location filename="../SettingsView.ui" line="1198"/>
<source>Patches</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1180"/>
<location filename="../SettingsView.ui" line="1246"/>
<source>Cheats</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1237"/>
<location filename="../SettingsView.ui" line="1303"/>
<source>Log to file</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1244"/>
<location filename="../SettingsView.ui" line="1310"/>
<source>Log to console</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1258"/>
<location filename="../SettingsView.ui" line="1324"/>
<source>Select Log File</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1271"/>
<source>Game Boy model</source>
<translation>Game Boy </translation>
<location filename="../SettingsView.ui" line="1337"/>
<source>Game Boy model:</source>
<translation>Game Boy :</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1279"/>
<location filename="../SettingsView.ui" line="1315"/>
<location filename="../SettingsView.ui" line="1351"/>
<location filename="../SettingsView.ui" line="1345"/>
<location filename="../SettingsView.ui" line="1381"/>
<location filename="../SettingsView.ui" line="1417"/>
<source>Autodetect</source>
<translation></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1284"/>
<location filename="../SettingsView.ui" line="1320"/>
<location filename="../SettingsView.ui" line="1356"/>
<location filename="../SettingsView.ui" line="1350"/>
<location filename="../SettingsView.ui" line="1386"/>
<location filename="../SettingsView.ui" line="1422"/>
<source>Game Boy (DMG)</source>
<translation>Game Boy (DMG)</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1289"/>
<location filename="../SettingsView.ui" line="1325"/>
<location filename="../SettingsView.ui" line="1361"/>
<location filename="../SettingsView.ui" line="1355"/>
<location filename="../SettingsView.ui" line="1391"/>
<location filename="../SettingsView.ui" line="1427"/>
<source>Super Game Boy (SGB)</source>
<translation>Super Game Boy (SGB)</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1294"/>
<location filename="../SettingsView.ui" line="1330"/>
<location filename="../SettingsView.ui" line="1366"/>
<location filename="../SettingsView.ui" line="1360"/>
<location filename="../SettingsView.ui" line="1396"/>
<location filename="../SettingsView.ui" line="1432"/>
<source>Game Boy Color (CGB)</source>
<translation>Game Boy Color (CGB)</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1299"/>
<location filename="../SettingsView.ui" line="1335"/>
<location filename="../SettingsView.ui" line="1371"/>
<location filename="../SettingsView.ui" line="1365"/>
<location filename="../SettingsView.ui" line="1401"/>
<location filename="../SettingsView.ui" line="1437"/>
<source>Game Boy Advance (AGB)</source>
<translation>Game Boy Advance (AGB)</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1307"/>
<location filename="../SettingsView.ui" line="1373"/>
<source>Super Game Boy model:</source>
<translation>Super Game Boy :</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1343"/>
<location filename="../SettingsView.ui" line="1409"/>
<source>Game Boy Color model:</source>
<translation>Game Boy Color :</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1386"/>
<location filename="../SettingsView.ui" line="1452"/>
<source>Default BG colors:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1553"/>
<location filename="../SettingsView.ui" line="1619"/>
<source>Super Game Boy borders</source>
<translation>Super Game Boy </translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1567"/>
<location filename="../SettingsView.ui" line="1633"/>
<source>Camera driver:</source>
<translation>:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1664"/>
<location filename="../SettingsView.ui" line="1730"/>
<source>Default sprite colors 1:</source>
<translation> 1:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1671"/>
<location filename="../SettingsView.ui" line="1737"/>
<source>Default sprite colors 2:</source>
<translation> 2:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1678"/>
<location filename="../SettingsView.ui" line="1744"/>
<source>Use GBC colors in GB games</source>
<translation> GB 使 GBC </translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1685"/>
<location filename="../SettingsView.ui" line="1751"/>
<source>Camera:</source>
<translation></translation>
</message>

View File

@ -51,9 +51,9 @@ void mSDLGLCommonInit(struct mSDLRenderer* renderer) {
#else
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
#ifdef COLOR_16_BIT
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->fullscreen));
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->player.fullscreen));
#else
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->fullscreen));
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL | SDL_RESIZABLE | (SDL_FULLSCREEN * renderer->player.fullscreen));
#endif
SDL_WM_SetCaption(projectName, "");
#endif

View File

@ -13,7 +13,7 @@
#include <mgba/core/core.h>
#include <mgba/core/thread.h>
#ifndef __APPLE__
#ifdef __linux__
#include <malloc.h>
#endif
@ -37,7 +37,7 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) {
size_t size = renderer->width * renderer->height * BYTES_PER_PIXEL;
#ifdef _WIN32
renderer->outputBuffer = _aligned_malloc(size, 16);
#elif !defined(__APPLE__)
#elif defined(__linux__)
renderer->outputBuffer = memalign(16, size);
#else
posix_memalign((void**) &renderer->outputBuffer, 16, size);

View File

@ -131,12 +131,8 @@ int main(int argc, char** argv) {
renderer.viewportWidth = renderer.core->opts.width;
renderer.viewportHeight = renderer.core->opts.height;
#if SDL_VERSION_ATLEAST(2, 0, 0)
renderer.player.fullscreen = renderer.core->opts.fullscreen;
renderer.player.windowUpdated = 0;
#else
renderer.fullscreen = renderer.core->opts.fullscreen;
#endif
renderer.lockAspectRatio = renderer.core->opts.lockAspectRatio;
renderer.lockIntegerScaling = renderer.core->opts.lockIntegerScaling;

View File

@ -54,8 +54,6 @@ struct mSDLRenderer {
SDL_Texture* sdlTex;
SDL_Renderer* sdlRenderer;
SDL_GLContext* glCtx;
#else
bool fullscreen;
#endif
unsigned width;

View File

@ -63,10 +63,10 @@ struct mSDLPlayer {
size_t playerId;
struct mInputMap* bindings;
struct SDL_JoystickCombo* joystick;
int fullscreen;
int windowUpdated;
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_Window* window;
int fullscreen;
struct mSDLRumble {
struct mRumble d;

View File

@ -22,9 +22,9 @@ void mSDLSWCreate(struct mSDLRenderer* renderer) {
bool mSDLSWInit(struct mSDLRenderer* renderer) {
#ifdef COLOR_16_BIT
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->fullscreen));
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->player.fullscreen));
#else
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->fullscreen));
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | (SDL_FULLSCREEN * renderer->player.fullscreen));
#endif
SDL_WM_SetCaption(projectName, "");