all repos — mgba @ 67aaac84b61329433d12f657db023d8cda338f54

mGBA Game Boy Advance Emulator

Merge branch 'master' (early part) into medusa
Vicki Pfau vi@endrift.com
Fri, 28 Jun 2019 16:19:16 -0700
commit

67aaac84b61329433d12f657db023d8cda338f54

parent

a6dcfcc338ce99f8839860d5ecb069b927370062

M CHANGESCHANGES

@@ -61,6 +61,7 @@ - GBA SIO: Fix unconnected SIOCNT for multi mode (fixes mgba.io/i/1105)

- GBA BIOS: Fix BitUnPack final byte - GB I/O: DMA register is R/W - GB Video: Fix SCX timing + - GBA Video: Improve sprite cycle counting (fixes mgba.io/i/1126) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)

@@ -88,6 +89,9 @@ - GB, GBA Audio: Increase max audio volume

- GB: Fix VRAM/palette locking (fixes mgba.io/i/1109) - GB Video: Darken colors in GBA mode - FFmpeg: Support libswresample (fixes mgba.io/i/1120, mgba.io/b/123) + - FFmpeg: Support lossless h.264 encoding + - Feature: Added loading savestates from command line + - Qt: Allow pausing game at load (fixes mgba.io/i/1129) 0.6.3: (2017-04-14) Bugfixes:
M CMakeLists.txtCMakeLists.txt

@@ -521,17 +521,17 @@ link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWRESAMPLE_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})

list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c") string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION}) string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION}) - string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION}) - string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION}) string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION}) - math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1") list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}") if(USE_LIBSWRESAMPLE) + string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION}) + math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswresample${LIBSWRESAMPLE_VERSION_DEBIAN}|libswresample-ffmpeg${LIBSWRESAMPLE_VERSION_DEBIAN}") else() + string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavresample${LIBAVRESAMPLE_VERSION_MAJOR}|libavresample-ffmpeg${LIBAVRESAMPLE_VERSION_MAJOR}") endif() set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavutil${LIBAVUTIL_VERSION_MAJOR}|libavutil-ffmpeg${LIBAVUTIL_VERSION_MAJOR}")
M include/mgba/feature/commandline.hinclude/mgba/feature/commandline.h

@@ -18,7 +18,7 @@ struct mArguments {

char* fname; char* patch; char* cheatsFile; - char* movie; + char* savestate; char* bios; int logLevel; int frameskip;
M src/core/flags.h.insrc/core/flags.h.in

@@ -83,6 +83,14 @@ #ifndef USE_LIBAV

#cmakedefine USE_LIBAV #endif +#ifndef USE_LIBAVRESAMPLE +#cmakedefine USE_LIBAVRESAMPLE +#endif + +#ifndef USE_LIBSWRESAMPLE +#cmakedefine USE_LIBSWRESAMPLE +#endif + #ifndef USE_LIBZIP #cmakedefine USE_LIBZIP #endif
M src/core/thread.csrc/core/thread.c

@@ -201,16 +201,19 @@ MutexLock(&impl->stateMutex);

while (impl->state > THREAD_MAX_RUNNING && impl->state < THREAD_EXITING) { deferred = impl->state; - if (impl->state == THREAD_INTERRUPTING) { + switch (deferred) { + case THREAD_INTERRUPTING: impl->state = THREAD_INTERRUPTED; ConditionWake(&impl->stateCond); - } - - if (impl->state == THREAD_PAUSING) { + break; + case THREAD_PAUSING: impl->state = THREAD_PAUSED; - } - if (impl->state == THREAD_RESETING) { + break; + case THREAD_RESETING: impl->state = THREAD_RUNNING; + break; + default: + break; } if (deferred >= THREAD_MIN_DEFERRED && deferred <= THREAD_MAX_DEFERRED) {

@@ -218,6 +221,9 @@ break;

} deferred = impl->state; + if (deferred == THREAD_INTERRUPTED) { + deferred = impl->savedState; + } while (impl->state >= THREAD_WAITING && impl->state <= THREAD_MAX_WAITING) { ConditionWait(&impl->stateCond, &impl->stateMutex);
M src/feature/commandline.csrc/feature/commandline.c

@@ -39,7 +39,7 @@ { "gdb", no_argument, 0, 'g' },

#endif { "help", no_argument, 0, 'h' }, { "log-level", required_argument, 0, 'l' }, - { "movie", required_argument, 0, 'v' }, + { "savestate", required_argument, 0, 't' }, { "patch", required_argument, 0, 'p' }, { "version", no_argument, 0, '\0' }, { 0, 0, 0, 0 }

@@ -68,7 +68,7 @@

bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) { int ch; char options[64] = - "b:c:C:hl:p:s:v:" + "b:c:C:hl:p:s:t:" #ifdef USE_EDITLINE "d" #endif

@@ -132,8 +132,8 @@ break;

case 's': args->frameskip = atoi(optarg); break; - case 'v': - args->movie = strdup(optarg); + case 't': + args->savestate = strdup(optarg); break; default: if (subparser) {

@@ -179,8 +179,8 @@

free(args->patch); args->patch = 0; - free(args->movie); - args->movie = 0; + free(args->savestate); + args->savestate = 0; free(args->cheatsFile); args->cheatsFile = 0;

@@ -244,7 +244,7 @@ #ifdef USE_GDB_STUB

puts(" -g, --gdb Start GDB session (default port 2345)"); #endif puts(" -l, --log-level N Log level mask"); - puts(" -v, --movie FILE Play back a movie of recorded input"); + puts(" -t, --savestate FILE Load savestate when starting"); puts(" -p, --patch FILE Apply a specified patch file when running"); puts(" -s, --frameskip N Skip every N frames"); puts(" --version Print version and exit");
M src/feature/ffmpeg/ffmpeg-encoder.csrc/feature/ffmpeg/ffmpeg-encoder.c

@@ -318,6 +318,15 @@ #else

encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER; #endif } + + if (encoder->video->codec->id == AV_CODEC_ID_H264 && + (strcasecmp(encoder->containerFormat, "mp4") || + strcasecmp(encoder->containerFormat, "m4v") || + strcasecmp(encoder->containerFormat, "mov"))) { + // QuickTime and a few other things require YUV420 + encoder->video->pix_fmt = AV_PIX_FMT_YUV420P; + } + if (strcmp(vcodec->name, "libx264") == 0) { // Try to adaptively figure out when you can use a slower encoder if (encoder->width * encoder->height > 1000000) {

@@ -327,16 +336,12 @@ av_opt_set(encoder->video->priv_data, "preset", "veryfast", 0);

} else { av_opt_set(encoder->video->priv_data, "preset", "faster", 0); } - av_opt_set(encoder->video->priv_data, "tune", "zerolatency", 0); + if (encoder->videoBitrate == 0) { + av_opt_set(encoder->video->priv_data, "crf", "0", 0); + encoder->video->pix_fmt = AV_PIX_FMT_YUV444P; + } } - if (encoder->video->codec->id == AV_CODEC_ID_H264 && - (strcasecmp(encoder->containerFormat, "mp4") || - strcasecmp(encoder->containerFormat, "m4v") || - strcasecmp(encoder->containerFormat, "mov"))) { - // QuickTime and a few other things require YUV420 - encoder->video->pix_fmt = AV_PIX_FMT_YUV420P; - } avcodec_open2(encoder->video, vcodec, 0); #if LIBAVCODEC_VERSION_MAJOR >= 55 encoder->videoFrame = av_frame_alloc();
M src/gb/mbc.csrc/gb/mbc.c

@@ -446,6 +446,7 @@ }

address &= 0x1FF; memory->sramBank[(address >> 1)] &= 0xF0 >> shift; memory->sramBank[(address >> 1)] |= (value & 0xF) << shift; + break; default: // TODO mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value);

@@ -640,6 +641,7 @@ }

break; case 0x5: _GBMBC7Write(&gb->memory, address, value); + break; default: // TODO mLOG(GB_MBC, STUB, "MBC7 unknown address: %04X:%02X", address, value);
M src/gba/renderers/software-obj.csrc/gba/renderers/software-obj.c

@@ -294,7 +294,6 @@ uint32_t current;

if (GBAObjAttributesAIsTransformed(sprite->a)) { int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a); int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a); - renderer->spriteCyclesRemaining -= 10; struct GBAOAMMatrix mat; LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);

@@ -354,6 +353,7 @@

if (outX < start || outX >= condition) { return 0; } + renderer->spriteCyclesRemaining -= 10; if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) { int alpha = GBAObjAttributesCGetPalette(sprite->c);

@@ -393,7 +393,7 @@ } else {

SPRITE_TRANSFORMED_LOOP(256, NORMAL); } } - if (x + totalWidth > renderer->masterEnd) { + if (end == renderer->masterEnd && x + totalWidth > renderer->masterEnd) { renderer->spriteCyclesRemaining -= (x + totalWidth - renderer->masterEnd) * 2; } } else {

@@ -485,7 +485,7 @@ SPRITE_NORMAL_LOOP(256, NORMAL);

} } - if (x + width > renderer->masterEnd) { + if (end = renderer->masterEnd && x + width > renderer->masterEnd) { renderer->spriteCyclesRemaining -= x + width - renderer->masterEnd; } }
M src/platform/qt/CoreController.cppsrc/platform/qt/CoreController.cpp

@@ -470,6 +470,26 @@ }

}); } +void CoreController::loadState(const QString& path) { + m_statePath = path; + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY); + if (!vf) { + return; + } + if (!controller->m_backupLoadState.isOpen()) { + controller->m_backupLoadState = VFileMemChunk(nullptr, 0); + } + mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); + if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) { + emit controller->frameAvailable(); + emit controller->stateLoaded(); + } + vf->close(vf); + }); +} + void CoreController::saveState(int slot) { if (slot > 0) { m_stateSlot = slot;

@@ -483,6 +503,25 @@ vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());

vf->close(vf); } mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags); + }); +} + +void CoreController::saveState(const QString& path) { + m_statePath = path; + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY); + if (vf) { + controller->m_backupSaveState.resize(vf->size(vf)); + vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); + vf->close(vf); + } + vf = VFileDevice::open(controller->m_statePath, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return; + } + mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags); + vf->close(vf); }); }

@@ -559,7 +598,7 @@ }

QString fname = info.canonicalFilePath(); Interrupter interrupter(this); mDirectorySetDetachBase(&m_threadContext.core->dirs); - mCoreLoadFile(m_threadContext.core, fname.toLocal8Bit().constData()); + mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData()); } void CoreController::yankPak() {
M src/platform/qt/CoreController.hsrc/platform/qt/CoreController.h

@@ -104,7 +104,9 @@ void setFastForward(bool);

void forceFastForward(bool); void loadState(int slot = 0); + void loadState(const QString& path); void saveState(int slot = 0); + void saveState(const QString& path); void loadBackupState(); void saveBackupState();

@@ -185,6 +187,7 @@

VFileDevice m_backupLoadState; QByteArray m_backupSaveState{nullptr}; int m_stateSlot = 1; + QString m_statePath; int m_loadStateFlags; int m_saveStateFlags;
M src/platform/qt/VideoView.cppsrc/platform/qt/VideoView.cpp

@@ -99,7 +99,7 @@ updatePresets();

setPreset({ .container = "MKV", - .vcodec = "PNG", + .vcodec = "h.264", .acodec = "FLAC", .vbr = 0, .abr = 0,

@@ -179,7 +179,7 @@

if (m_nativeWidth && m_nativeHeight) { addPreset(m_ui.presetLossless, { .container = "MKV", - .vcodec = "PNG", + .vcodec = "h.264", .acodec = "FLAC", .vbr = 0, .abr = 0,
M src/platform/qt/VideoView.uisrc/platform/qt/VideoView.ui

@@ -256,11 +256,6 @@ <bool>true</bool>

</property> <item> <property name="text"> - <string>PNG</string> - </property> - </item> - <item> - <property name="text"> <string>h.264</string> </property> </item>

@@ -352,9 +347,6 @@ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>

</property> <property name="suffix"> <string/> - </property> - <property name="minimum"> - <number>200</number> </property> <property name="maximum"> <number>10000</number>

@@ -523,7 +515,7 @@ </tabstops>

<resources/> <connections/> <buttongroups> - <buttongroup name="presets"/> <buttongroup name="resolutions"/> + <buttongroup name="presets"/> </buttongroups> </ui>
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -200,6 +200,14 @@

void Window::argumentsPassed(mArguments* args) { loadConfig(); + if (args->patch) { + m_pendingPatch = args->patch; + } + + if (args->savestate) { + m_pendingState = args->savestate; + } + if (args->fname) { setController(m_manager->loadGame(args->fname), args->fname); }

@@ -1252,12 +1260,15 @@ pause->setChecked(false);

pause->setCheckable(true); pause->setShortcut(tr("Ctrl+P")); connect(pause, &QAction::triggered, [this](bool paused) { - m_controller->setPaused(paused); + if (m_controller) { + m_controller->setPaused(paused); + } else { + m_pendingPause = paused; + } }); connect(this, &Window::paused, [pause](bool paused) { pause->setChecked(paused); }); - m_gameActions.append(pause); addControlledAction(emulationMenu, pause, "pause"); QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu);

@@ -1911,6 +1922,16 @@ }

m_controller->loadConfig(m_config); m_controller->start(); + + if (!m_pendingState.isEmpty()) { + m_controller->loadState(m_pendingState); + m_pendingState = QString(); + } + + if (m_pendingPause) { + m_controller->setPaused(true); + m_pendingPause = false; + } } WindowBackground::WindowBackground(QWidget* parent)
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -202,6 +202,8 @@ QTimer m_focusCheck;

bool m_autoresume = false; bool m_wasOpened = false; QString m_pendingPatch; + QString m_pendingState; + bool m_pendingPause = false; bool m_hitUnimplementedBiosCall;
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -25,6 +25,7 @@ #include <mgba/core/cheats.h>

#include <mgba/core/core.h> #include <mgba/core/config.h> #include <mgba/core/input.h> +#include <mgba/core/serialize.h> #include <mgba/core/thread.h> #include <mgba/internal/gba/input.h>

@@ -42,6 +43,12 @@ static bool mSDLInit(struct mSDLRenderer* renderer);

static void mSDLDeinit(struct mSDLRenderer* renderer); static int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args); + +static struct VFile* _state = NULL; + +static void _loadState(struct mCoreThread* thread) { + mCoreLoadStateNamed(thread->core, _state, SAVESTATE_RTC); +} int main(int argc, char** argv) { struct mSDLRenderer renderer = {0};

@@ -232,6 +239,15 @@ mSDLSetScreensaverSuspendable(&renderer->events, renderer->core->opts.suspendScreensaver);

mSDLSuspendScreensaver(&renderer->events); #endif if (mSDLInitAudio(&renderer->audio, &thread)) { + if (args->savestate) { + struct VFile* state = VFileOpen(args->savestate, O_RDONLY); + if (state) { + _state = state; + mCoreThreadRunFunction(&thread, _loadState); + _state = NULL; + state->close(state); + } + } renderer->runloop(renderer, &thread); mSDLPauseAudio(&renderer->audio); if (mCoreThreadHasCrashed(&thread)) {
M src/platform/sdl/sdl-events.csrc/platform/sdl/sdl-events.c

@@ -407,7 +407,7 @@ int key = -1;

if (!event->keysym.mod) { key = mInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym); } - if (key != -1 && !event->repeat) { + if (key != -1) { mCoreThreadInterrupt(context); if (event->type == SDL_KEYDOWN) { context->core->addKeys(context->core, 1 << key);

@@ -485,7 +485,7 @@ case SDLK_F7:

case SDLK_F8: case SDLK_F9: mCoreThreadInterrupt(context); - mCoreSaveState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SAVEDATA | SAVESTATE_SCREENSHOT); + mCoreSaveState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SAVEDATA | SAVESTATE_SCREENSHOT | SAVESTATE_RTC); mCoreThreadContinue(context); break; default:

@@ -503,7 +503,7 @@ case SDLK_F7:

case SDLK_F8: case SDLK_F9: mCoreThreadInterrupt(context); - mCoreLoadState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT); + mCoreLoadState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); mCoreThreadContinue(context); break; default:
M src/platform/test/fuzz-main.csrc/platform/test/fuzz-main.c

@@ -26,14 +26,12 @@ "\nAdditional options:\n" \

" -F FRAMES Run for the specified number of FRAMES before exiting\n" \ " -N Disable video rendering entirely\n" \ " -O OFFSET Offset to apply savestate overlay\n" \ - " -S FILE Load a savestate when starting the test\n" \ " -V FILE Overlay a second savestate over the loaded savestate\n" \ struct FuzzOpts { bool noVideo; int frames; size_t overlayOffset; - char* savestate; char* ssOverlay; };

@@ -108,9 +106,8 @@ struct VFile* savestate = 0;

struct VFile* savestateOverlay = 0; size_t overlayOffset; - if (fuzzOpts.savestate) { - savestate = VFileOpen(fuzzOpts.savestate, O_RDONLY); - free(fuzzOpts.savestate); + if (args.savestate) { + savestate = VFileOpen(args.savestate, O_RDONLY); } if (fuzzOpts.ssOverlay) { overlayOffset = fuzzOpts.overlayOffset;

@@ -200,9 +197,6 @@ return true;

case 'O': opts->overlayOffset = strtoul(arg, 0, 10); return !errno; - case 'S': - opts->savestate = strdup(arg); - return true; case 'V': opts->ssOverlay = strdup(arg); return true;