all repos — mgba @ e6aa23f19cdfe97161f3696fbb3dbc4e3794edd5

mGBA Game Boy Advance Emulator

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

e6aa23f19cdfe97161f3696fbb3dbc4e3794edd5

parent

8b04eca8f024debbb10ec942af3e47a172ac09b0

M CHANGESCHANGES

@@ -75,6 +75,14 @@ - PSP2: Fix more issues causing poor audio

- GBA Memory: Fix Vast Fame support (taizou) (fixes mgba.io/i/1170) - GB, GBA Savedata: Fix unmasking savedata crash - GBA DMA: Fix temporal sorting of DMAs of different priorities + - FFmpeg: Fix encoding audio/video queue issues + - GB Serialize: Fix IRQ pending/EI pending confusion + - GB MBC: Improve multicart detection heuristic (fixes mgba.io/i/1177) + - GB Audio: Fix channel 3 reset value + - GB Audio: Fix channel 4 initial LFSR + - GB, GBA Video: Don't call finishFrame twice in thread proxy + - GB Audio: Fix channel 1, 2 and 4 reset timing + - Util: Fix wrapping edge cases in RingFIFO Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)

@@ -108,14 +116,28 @@ - Qt: Allow pausing game at load (fixes mgba.io/i/1129)

- Wii: Move audio handling to callbacks (fixes mgba.io/i/803) - Qt: Clean up FPS target UI (fixes mgba.io/i/436) - Core: Remove broken option for whether rewinding restores save games + - FFmpeg: Support lossless VP9 encoding + - mGUI: Add fast forward toggle +Changes from beta 1: +Features: + - Libretro: Add Game Boy cheat support +Bugfixes: + - PSP2: Fix audio crackling after fast forward + - PSP2: Fix audio crackling when buffer is full + - 3DS: Fix unused screens not clearing (fixes mgba.io/i/1184) +Misc: + - mGUI: Add SGB border configuration option + +0.6 beta 1: (2018-09-24) +- Initial beta for 0.6 -0.6.3: (2017-04-14) +0.6.3: (2018-04-14) Bugfixes: - GB Audio: Revert unsigned audio changes - GB Video: Fix bad merge (fixes mgba.io/i/1040) - GBA Video: Fix OBJ blending regression (fixes mgba.io/i/1037) -0.6.2: (2017-04-03) +0.6.2: (2018-04-03) Bugfixes: - Core: Fix ROM patches not being unloaded when disabled (fixes mgba.io/i/962) - 3DS: Fix opening files in directory names with trailing slashes
M CMakeLists.txtCMakeLists.txt

@@ -49,6 +49,7 @@ set(BUILD_SHARED ON CACHE BOOL "Build a shared library")

set(SKIP_LIBRARY OFF CACHE BOOL "Skip building the library (useful for only building libretro or OpenEmu cores)") set(BUILD_GL ON CACHE BOOL "Build with OpenGL") set(BUILD_GLES2 OFF CACHE BOOL "Build with OpenGL|ES 2") +set(BUILD_GLES3 OFF CACHE BOOL "Build with OpenGL|ES 3") set(USE_EPOXY ON CACHE STRING "Build with libepoxy") set(DISABLE_DEPS OFF CACHE BOOL "Build without dependencies") set(DISTBUILD OFF CACHE BOOL "Build distribution packages")

@@ -93,7 +94,7 @@ source_group("Utilities" FILES ${UTIL_SRC})

include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src) if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release, RelWithDebInfo, or Debug)" FORCE) endif() if(UNIX OR WIN32_UNIX_PATHS)

@@ -268,6 +269,14 @@ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")

set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto") endif() +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang") + find_program(OBJCOPY ${cross_prefix}objcopy) + find_program(STRIP ${cross_prefix}strip) + + set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE} -gdwarf") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf") +endif() + if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA) if(NOT BUILD_EGL) add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)

@@ -301,7 +310,7 @@ add_definitions(-DFIXED_ROM_BUFFER)

endif() if(DEFINED SWITCH) - set(BUILD_GLES2 ON CACHE BOOL "Build with OpenGL|ES 2" FORCE) + set(BUILD_GLES3 ON CACHE BOOL "Build with OpenGL|ES 3" FORCE) endif() if(NOT M_CORE_GBA)

@@ -443,6 +452,13 @@ endif()

if(NOT BUILD_GLES2) set(OPENGLES2_LIBRARY "" CACHE PATH "" FORCE) endif() +if(BUILD_GLES3) + find_path(OPENGLES3_INCLUDE_DIR NAMES GLES3/gl3.h) + find_library(OPENGLES3_LIBRARY NAMES GLESv3 GLESv2) + if(NOT OPENGLES3_INCLUDE_DIR OR NOT OPENGLES3_LIBRARY) + set(BUILD_GLES3 OFF CACHE BOOL "OpenGL|ES 3 not found" FORCE) + endif() +endif() set(WANT_ZLIB ${USE_ZLIB}) set(WANT_PNG ${USE_PNG}) set(WANT_LIBZIP ${USE_LIBZIP})

@@ -909,6 +925,10 @@ if(BUILD_GLES2)

add_definitions(-DBUILD_GLES2) endif() +if(BUILD_GLES3) + add_definitions(-DBUILD_GLES3) +endif() + if(DISABLE_FRONTENDS) set(BUILD_SDL OFF) set(BUILD_QT OFF)

@@ -1062,9 +1082,18 @@ set(CPACK_STRIP_FILES ON)

if(DISTBUILD) set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) + if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" AND BUILD_SHARED) + if(NOT APPLE) + add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}>" "$<TARGET_FILE:${BINARY_NAME}>.dSYM") + add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${STRIP}" -S "$<TARGET_FILE:${BINARY_NAME}>") + install(FILES "$<TARGET_FILE:${BINARY_NAME}>.dSYM" DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}-dbg) + endif() + endif() if(WIN32 OR APPLE) - set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-qt ${BINARY_NAME}-sdl ${BINARY_NAME}-perf) - set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) + set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-qt ${BINARY_NAME}-sdl ${BINARY_NAME}-qt-dbg ${BINARY_NAME}-sdl-dbg ${BINARY_NAME}-perf) + if(APPLE) + set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) + endif() elseif(3DS) set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-3ds ${BINARY_NAME}-perf) elseif(WII)

@@ -1111,6 +1140,19 @@ cpack_add_component_group(sdl PARENT_GROUP base)

cpack_add_component(${BINARY_NAME}-sdl GROUP sdl) endif() +if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + cpack_add_component_group(debug PARENT_GROUP dev) + if(BUILD_SHARED AND NOT IS_EMBEDDED) + cpack_add_component(lib${BINARY_NAME}-dbg GROUP debug) + endif() + if(BUILD_QT) + cpack_add_component(${BINARY_NAME}-qt-dbg GROUP debug) + endif() + if(SDL_FOUND) + cpack_add_component(${BINARY_NAME}-sdl-dbg GROUP debug) + endif() +endif() + cpack_add_component_group(test PARENT_GROUP dev) cpack_add_component(${BINARY_NAME}-perf GROUP test) cpack_add_component(${BINARY_NAME}-fuzz GROUP test)

@@ -1126,6 +1168,9 @@ list(APPEND SUMMARY_GL_LIST "OpenGL")

endif() if(BUILD_GLES2) list(APPEND SUMMARY_GL_LIST "OpenGL|ES 2") + endif() + if(BUILD_GLES3) + list(APPEND SUMMARY_GL_LIST "OpenGL|ES 3") endif() endif() if(NOT SUMMARY_GL_LIST)
M doc/medusa-emu-qt.6doc/medusa-emu-qt.6

@@ -17,6 +17,7 @@ .Op Fl C Ar option Ns = Ns Ar value

.Op Fl l Ar loglevel .Op Fl p Ar patchfile .Op Fl s Ar n +.Op Fl t Ar statefile .Ar file .Sh DESCRIPTION .Nm

@@ -71,13 +72,7 @@ 16 \(en debugging messages

.It 32 \(en stub messages for unimplemented features .It -256 \(en in\(hygame errors -.It -512 \(en software interrupts -.It -1024 \(en emulator status messages -.It -2048 \(en serial I/O messages +64 \(en in\(hygame errors .El The default is to log warnings, errors, fatal errors, and status messages. .It Fl p Ar patchfile , Fl -patch Ar patchfile

@@ -86,6 +81,9 @@ .It Fl s Ar n , Fl -frameskip Ar n

Skip every .Ar n frames. +.It Fl t Ar statefile , Fl -savestate Ar statefile +Load initial game state from +.Ar statefile . .El .Sh CONTROLS The default controls are as follows:
M doc/medusa-emu.6doc/medusa-emu.6

@@ -18,7 +18,7 @@ .Op Fl c Ar cheatfile

.Op Fl l Ar loglevel .Op Fl p Ar patchfile .Op Fl s Ar n -.Op Fl v Ar moviefile +.Op Fl t Ar statefile .Ar file .Sh DESCRIPTION .Nm

@@ -79,13 +79,7 @@ 16 \(en debugging messages

.It 32 \(en stub messages for unimplemented features .It -256 \(en in\(hygame errors -.It -512 \(en software interrupts -.It -1024 \(en emulator status messages -.It -2048 \(en serial I/O messages +64 \(en in\(hygame errors .El The default is to log warnings, errors, fatal errors, and status messages. .It Fl p Ar patchfile , Fl -patch Ar patchfile

@@ -94,9 +88,9 @@ .It Fl s Ar n , Fl -frameskip Ar n

Skip every .Ar n frames. -.It Fl v Ar moviefile , Fl -movie Ar moviefile -Play back a movie of recording input from -.Ar moviefile . +.It Fl t Ar statefile , Fl -savestate Ar statefile +Load initial game state from +.Ar statefile . .El .Sh CONTROLS The default controls are as follows:
M include/mgba-util/gui.hinclude/mgba-util/gui.h

@@ -47,7 +47,8 @@ BATTERY_HALF = 2,

BATTERY_HIGH = 3, BATTERY_FULL = 4, - BATTERY_CHARGING = 8 + BATTERY_CHARGING = 8, + BATTERY_NOT_PRESENT = 16 }; struct GUIBackground {
M include/mgba-util/ring-fifo.hinclude/mgba-util/ring-fifo.h

@@ -20,6 +20,7 @@

void RingFIFOInit(struct RingFIFO* buffer, size_t capacity); void RingFIFODeinit(struct RingFIFO* buffer); size_t RingFIFOCapacity(const struct RingFIFO* buffer); +size_t RingFIFOSize(const struct RingFIFO* buffer); void RingFIFOClear(struct RingFIFO* buffer); size_t RingFIFOWrite(struct RingFIFO* buffer, const void* value, size_t length); size_t RingFIFORead(struct RingFIFO* buffer, void* output, size_t length);
M include/mgba/core/config.hinclude/mgba/core/config.h

@@ -33,7 +33,6 @@ int logLevel;

int frameskip; bool rewindEnable; int rewindBufferCapacity; - bool rewindSave; float fpsTarget; size_t audioBuffers; unsigned sampleRate;
M include/mgba/internal/gb/audio.hinclude/mgba/internal/gb/audio.h

@@ -137,6 +137,9 @@ bool stop;

int length; uint32_t lfsr; + int nSamples; + int samples; + int8_t sample; };
M include/mgba/internal/gb/gb.hinclude/mgba/internal/gb/gb.h

@@ -177,6 +177,7 @@ void GBGetGameCode(const struct GB* gba, char* out);

void GBTestKeypadIRQ(struct GB* gb); +void GBFrameStarted(struct GB* gb); void GBFrameEnded(struct GB* gb); CXX_GUARD_END
M include/mgba/internal/gb/io.hinclude/mgba/internal/gb/io.h

@@ -102,9 +102,9 @@ REG_SVBK = 0x70,

REG_UNK72 = 0x72, REG_UNK73 = 0x73, REG_UNK74 = 0x74, - REG_PCM12 = 0x75, - REG_PCM34 = 0x76, - REG_UNK77 = 0x77, + REG_UNK75 = 0x75, + REG_PCM12 = 0x76, + REG_PCM34 = 0x77, REG_MAX = 0x100 };
M include/mgba/internal/gb/serialize.hinclude/mgba/internal/gb/serialize.h

@@ -47,7 +47,7 @@ * | 0x0003C - 0x0003F: EI pending cycles

* | 0x00040 - 0x00043: Reserved (DI pending cycles) * | 0x00044 - 0x00047: Flags * | bit 0: Is condition met? - * | bit 1: Is condition IRQ pending? + * | bit 1: Is IRQ pending? * | bit 2: Double speed * | bit 3: Is EI pending? * | bits 4 - 31: Reserved

@@ -232,7 +232,7 @@ DECL_BITFIELD(GBSerializedCpuFlags, uint32_t);

DECL_BIT(GBSerializedCpuFlags, Condition, 0); DECL_BIT(GBSerializedCpuFlags, IrqPending, 1); DECL_BIT(GBSerializedCpuFlags, DoubleSpeed, 2); -DECL_BIT(GBSerializedCpuFlags, EiPending, 1); +DECL_BIT(GBSerializedCpuFlags, EiPending, 3); DECL_BITFIELD(GBSerializedTimerFlags, uint8_t); DECL_BIT(GBSerializedTimerFlags, IrqPending, 0);
M res/patrons.txtres/patrons.txt

@@ -1,7 +1,8 @@

+Elijah Chondropoulos Jaime J. Denizard Fog Philip Horton +Oskenso Kashi Rohit Nirmal Rhys Powell -rootfather Yuri Kunde Schlesner
M src/core/config.csrc/core/config.c

@@ -331,7 +331,6 @@ _lookupIntValue(config, "logLevel", &opts->logLevel);

_lookupIntValue(config, "frameskip", &opts->frameskip); _lookupIntValue(config, "volume", &opts->volume); _lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity); - _lookupIntValue(config, "rewindSave", &opts->rewindSave); _lookupFloatValue(config, "fpsTarget", &opts->fpsTarget); unsigned audioBuffers; if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) {

@@ -391,7 +390,6 @@ ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel);

ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity); - ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindSave", opts->rewindSave); ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget); ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers); ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate);
M src/core/serialize.csrc/core/serialize.c

@@ -359,7 +359,6 @@ mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item);

} } if (flags & SAVESTATE_RTC) { - mLOG(SAVESTATE, INFO, "Loading RTC"); struct mStateExtdataItem item; if (core->rtc.d.serialize) { core->rtc.d.serialize(&core->rtc.d, &item);
M src/feature/ffmpeg/ffmpeg-encoder.csrc/feature/ffmpeg/ffmpeg-encoder.c

@@ -211,7 +211,6 @@

encoder->currentAudioSample = 0; encoder->currentAudioFrame = 0; encoder->currentVideoFrame = 0; - encoder->nextAudioPts = 0; AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); #ifndef USE_LIBAV

@@ -306,8 +305,8 @@ #endif

encoder->video->bit_rate = encoder->videoBitrate; encoder->video->width = encoder->width; encoder->video->height = encoder->height; - encoder->videoStream->time_base = (AVRational) { encoder->frameCycles, encoder->cycles }; - encoder->video->time_base = encoder->videoStream->time_base; + encoder->video->time_base = (AVRational) { encoder->frameCycles, encoder->cycles }; + encoder->video->framerate = (AVRational) { encoder->cycles, encoder->frameCycles }; encoder->video->pix_fmt = encoder->pixFormat; encoder->video->gop_size = 60; encoder->video->max_b_frames = 3;

@@ -340,6 +339,10 @@ if (encoder->videoBitrate == 0) {

av_opt_set(encoder->video->priv_data, "crf", "0", 0); encoder->video->pix_fmt = AV_PIX_FMT_YUV444P; } + } + if (strcmp(vcodec->name, "libvpx-vp9") == 0 && encoder->videoBitrate == 0) { + av_opt_set(encoder->video->priv_data, "lossless", "1", 0); + encoder->video->pix_fmt = AV_PIX_FMT_YUV444P; } avcodec_open2(encoder->video, vcodec, 0);

@@ -466,14 +469,13 @@ int samples = swr_convert(encoder->resampleContext, encoder->audioFrame->data, encoder->postaudioBufferSize / channelSize,

(const uint8_t**) &encoder->audioBuffer, encoder->audioBufferSize / 4); #endif - encoder->audioFrame->pts = av_rescale_q(encoder->currentAudioFrame, encoder->audio->time_base, encoder->audioStream->time_base); + encoder->audioFrame->pts = encoder->currentAudioFrame; encoder->currentAudioFrame += samples; AVPacket packet; av_init_packet(&packet); packet.data = 0; packet.size = 0; - packet.pts = encoder->audioFrame->pts; int gotData; #ifdef FFMPEG_USE_PACKETS

@@ -483,6 +485,9 @@ gotData = (gotData == 0) && packet.size;

#else avcodec_encode_audio2(encoder->audio, &packet, encoder->audioFrame, &gotData); #endif + packet.pts = av_rescale_q(encoder->audioFrame->pts, encoder->audio->time_base, encoder->audioStream->time_base); + packet.dts = packet.pts; + if (gotData) { if (encoder->absf) { AVPacket tempPacket;

@@ -541,7 +546,6 @@ #if LIBAVCODEC_VERSION_MAJOR >= 55

av_frame_make_writable(encoder->videoFrame); #endif encoder->videoFrame->pts = av_rescale_q(encoder->currentVideoFrame, encoder->video->time_base, encoder->videoStream->time_base); - packet.pts = encoder->videoFrame->pts; ++encoder->currentVideoFrame; sws_scale(encoder->scaleContext, (const uint8_t* const*) &pixels, (const int*) &stride, 0, encoder->iheight, encoder->videoFrame->data, encoder->videoFrame->linesize);

@@ -553,6 +557,7 @@ gotData = avcodec_receive_packet(encoder->video, &packet) == 0;

#else avcodec_encode_video2(encoder->video, &packet, encoder->videoFrame, &gotData); #endif + packet.pts = encoder->videoFrame->pts; if (gotData) { #ifndef FFMPEG_USE_PACKET_UNREF if (encoder->video->coded_frame->key_frame) {
M src/feature/ffmpeg/ffmpeg-encoder.hsrc/feature/ffmpeg/ffmpeg-encoder.h

@@ -56,7 +56,6 @@ size_t postaudioBufferSize;

AVFrame* audioFrame; size_t currentAudioSample; int64_t currentAudioFrame; - int64_t nextAudioPts; // TODO (0.6): Remove #ifdef USE_LIBAVRESAMPLE struct AVAudioResampleContext* resampleContext; #else
M src/feature/gui/gui-config.csrc/feature/gui/gui-config.c

@@ -108,6 +108,16 @@ *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {

.title = "Select SGB BIOS path", .data = "sgb.bios", }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Enable SGB borders", + .data = "sgb.borders", + .submenu = 0, + .state = true, + .validStates = (const char*[]) { + "Off", "On" + }, + .nStates = 2 + }; #endif size_t i; const char* mapNames[GUI_MAX_INPUTS + 1];
M src/feature/gui/gui-runner.csrc/feature/gui/gui-runner.c

@@ -54,7 +54,8 @@ [mGUI_INPUT_INCREASE_BRIGHTNESS] = "Increase solar brightness",

[mGUI_INPUT_DECREASE_BRIGHTNESS] = "Decrease solar brightness", [mGUI_INPUT_SCREEN_MODE] = "Screen mode", [mGUI_INPUT_SCREENSHOT] = "Take screenshot", - [mGUI_INPUT_FAST_FORWARD] = "Fast forward", + [mGUI_INPUT_FAST_FORWARD_HELD] = "Fast forward (held)", + [mGUI_INPUT_FAST_FORWARD_TOGGLE] = "Fast forward (toggle)", }, .nKeys = GUI_INPUT_MAX };

@@ -412,6 +413,7 @@ gettimeofday(&tv, 0);

runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec; int frame = 0; + bool fastForward = false; while (running) { if (runner->running) { running = runner->running(runner);

@@ -442,7 +444,10 @@ if (guiKeys & (1 << mGUI_INPUT_SCREENSHOT)) {

mCoreTakeScreenshot(runner->core); } if (runner->setFrameLimiter) { - if (heldKeys & (1 << mGUI_INPUT_FAST_FORWARD)) { + if (guiKeys & (1 << mGUI_INPUT_FAST_FORWARD_TOGGLE)) { + fastForward = !fastForward; + } + if (fastForward || (heldKeys & (1 << mGUI_INPUT_FAST_FORWARD_HELD))) { runner->setFrameLimiter(runner, false); } else { runner->setFrameLimiter(runner, true);
M src/feature/gui/gui-runner.hsrc/feature/gui/gui-runner.h

@@ -22,7 +22,8 @@ mGUI_INPUT_INCREASE_BRIGHTNESS = GUI_INPUT_USER_START,

mGUI_INPUT_DECREASE_BRIGHTNESS, mGUI_INPUT_SCREEN_MODE, mGUI_INPUT_SCREENSHOT, - mGUI_INPUT_FAST_FORWARD, + mGUI_INPUT_FAST_FORWARD_HELD, + mGUI_INPUT_FAST_FORWARD_TOGGLE }; struct mGUIBackground {
M src/feature/gui/remap.csrc/feature/gui/remap.c

@@ -69,13 +69,13 @@ }

if (item->data == (void*) (GUI_INPUT_MAX + map->info->nKeys + 2)) { for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { item = GUIMenuItemListGetPointer(&menu.items, i); - if ((uint32_t) item->data < 1) { + if ((uintptr_t) item->data < 1) { continue; } - if ((uint32_t) item->data < GUI_INPUT_MAX + 1) { - mInputBindKey(&params->keyMap, keys->id, item->state - 1, (uint32_t) item->data - 1); - } else if ((uint32_t) item->data < GUI_INPUT_MAX + map->info->nKeys + 1) { - mInputBindKey(map, keys->id, item->state - 1, (uint32_t) item->data - GUI_INPUT_MAX - 1); + if ((uintptr_t) item->data < GUI_INPUT_MAX + 1) { + mInputBindKey(&params->keyMap, keys->id, item->state - 1, (uintptr_t) item->data - 1); + } else if ((uintptr_t) item->data < GUI_INPUT_MAX + map->info->nKeys + 1) { + mInputBindKey(map, keys->id, item->state - 1, (uintptr_t) item->data - GUI_INPUT_MAX - 1); } } break;
M src/feature/imagemagick/imagemagick-gif-encoder.hsrc/feature/imagemagick/imagemagick-gif-encoder.h

@@ -12,9 +12,6 @@ CXX_GUARD_START

#include <mgba/core/interface.h> -#define MAGICKCORE_HDRI_ENABLE 0 -#define MAGICKCORE_QUANTUM_DEPTH 8 - #if MAGICKWAND_VERSION_MAJOR >= 7 #include <MagickWand/MagickWand.h> #else
M src/feature/thread-proxy.csrc/feature/thread-proxy.c

@@ -121,7 +121,7 @@ read = RingFIFORead(&proxyRenderer->dirtyQueue, data, length);

if (!block || read) { break; } - mLOG(GBA_VIDEO, DEBUG, "Proxy thread can't read VRAM. CPU thread asleep?"); + mLOG(GBA_VIDEO, DEBUG, "Can't read %"PRIz"u bytes. CPU thread asleep?", length); MutexLock(&proxyRenderer->mutex); ConditionWake(&proxyRenderer->fromThreadCond); ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);

@@ -142,7 +142,7 @@ mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");

_proxyThreadRecover(proxyRenderer); return; } - while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { + while (RingFIFOSize(&proxyRenderer->dirtyQueue)) { ConditionWake(&proxyRenderer->toThreadCond); ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); }
M src/feature/video-logger.csrc/feature/video-logger.c

@@ -258,6 +258,9 @@ 0,

0xDEADBEEF, }; logger->writeData(logger, &dirty, sizeof(dirty)); + if (logger->wait) { + logger->wait(logger); + } } void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger) {
M src/gb/audio.csrc/gb/audio.c

@@ -37,6 +37,8 @@

static void _updateSquareSample(struct GBAudioSquareChannel* ch); static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch); +static int8_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch); + static void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate); static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesLate); static void _updateChannel2(struct mTiming* timing, void* user, uint32_t cyclesLate);

@@ -93,7 +95,7 @@ audio->ch4Event.priority = 0x15;

audio->sampleEvent.context = audio; audio->sampleEvent.name = "GB Audio Sample"; audio->sampleEvent.callback = _sample; - audio->ch1Event.priority = 0x18; + audio->sampleEvent.priority = 0x18; } void GBAudioDeinit(struct GBAudio* audio) {

@@ -118,6 +120,7 @@ }

audio->ch1 = (struct GBAudioSquareChannel) { .envelope = { .dead = 2 } }; audio->ch2 = (struct GBAudioSquareChannel) { .envelope = { .dead = 2 } }; audio->ch3 = (struct GBAudioWaveChannel) { .bank = 0 }; + audio->ch4 = (struct GBAudioNoiseChannel) { .nSamples = 0 }; // TODO: DMG randomness audio->ch3.wavedata8[0] = 0x00; audio->ch3.wavedata8[1] = 0xFF;

@@ -203,11 +206,6 @@ }

} if (GBAudioRegisterControlIsRestart(value << 8)) { audio->playingCh1 = _resetEnvelope(&audio->ch1.envelope); - - if (audio->playingCh1) { - _updateSquareSample(&audio->ch1); - } - audio->ch1.sweep.realFrequency = audio->ch1.control.frequency; _resetSweep(&audio->ch1.sweep); if (audio->playingCh1 && audio->ch1.sweep.shift) {

@@ -219,7 +217,9 @@ if (audio->ch1.control.stop && !(audio->frame & 1)) {

--audio->ch1.control.length; } } - if (audio->playingCh1 && audio->ch1.envelope.dead != 2 && !mTimingIsScheduled(audio->timing, &audio->ch1Event)) { + if (audio->playingCh1 && audio->ch1.envelope.dead != 2) { + _updateSquareChannel(&audio->ch1); + mTimingDeschedule(audio->timing, &audio->ch1Event); mTimingSchedule(audio->timing, &audio->ch1Event, 0); } }

@@ -260,17 +260,15 @@ }

if (GBAudioRegisterControlIsRestart(value << 8)) { audio->playingCh2 = _resetEnvelope(&audio->ch2.envelope); - if (audio->playingCh2) { - _updateSquareSample(&audio->ch2); - } - if (!audio->ch2.control.length) { audio->ch2.control.length = 64; if (audio->ch2.control.stop && !(audio->frame & 1)) { --audio->ch2.control.length; } } - if (audio->playingCh2 && audio->ch2.envelope.dead != 2 && !mTimingIsScheduled(audio->timing, &audio->ch2Event)) { + if (audio->playingCh2 && audio->ch2.envelope.dead != 2) { + _updateSquareChannel(&audio->ch2); + mTimingDeschedule(audio->timing, &audio->ch2Event); mTimingSchedule(audio->timing, &audio->ch2Event, 0); } }

@@ -331,6 +329,7 @@ audio->ch3.wavedata8[3] = audio->ch3.wavedata8[((audio->ch3.window >> 1) & ~3) + 3];

} } audio->ch3.window = 0; + audio->ch3.sample = 0; } mTimingDeschedule(audio->timing, &audio->ch3Fade); mTimingDeschedule(audio->timing, &audio->ch3Event);

@@ -376,9 +375,9 @@ if (GBAudioRegisterNoiseControlIsRestart(value)) {

audio->playingCh4 = _resetEnvelope(&audio->ch4.envelope); if (audio->ch4.power) { - audio->ch4.lfsr = 0x40; + audio->ch4.lfsr = 0x7F; } else { - audio->ch4.lfsr = 0x4000; + audio->ch4.lfsr = 0x7FFF; } if (!audio->ch4.length) { audio->ch4.length = 64;

@@ -386,7 +385,8 @@ if (audio->ch4.stop && !(audio->frame & 1)) {

--audio->ch4.length; } } - if (audio->playingCh4 && audio->ch4.envelope.dead != 2 && !mTimingIsScheduled(audio->timing, &audio->ch4Event)) { + if (audio->playingCh4 && audio->ch4.envelope.dead != 2) { + mTimingDeschedule(audio->timing, &audio->ch4Event); mTimingSchedule(audio->timing, &audio->ch4Event, 0); } }

@@ -563,11 +563,13 @@ if (audio->playingCh4 && !audio->ch4.envelope.dead) {

--audio->ch4.envelope.nextStep; if (audio->ch4.envelope.nextStep == 0) { int8_t sample = audio->ch4.sample > 0; + audio->ch4.samples -= audio->ch4.sample; _updateEnvelope(&audio->ch4.envelope); if (audio->ch4.envelope.dead == 2) { mTimingDeschedule(timing, &audio->ch4Event); } audio->ch4.sample = sample * audio->ch4.envelope.currentVolume; + audio->ch4.samples += audio->ch4.sample; } } break;

@@ -575,47 +577,48 @@ }

} void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) { - int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : 0x8; + int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : -0x8; int sampleLeft = dcOffset; int sampleRight = dcOffset; if (audio->playingCh1 && !audio->forceDisableCh[0]) { if (audio->ch1Left) { - sampleLeft -= audio->ch1.sample; + sampleLeft += audio->ch1.sample; } if (audio->ch1Right) { - sampleRight -= audio->ch1.sample; + sampleRight += audio->ch1.sample; } } if (audio->playingCh2 && !audio->forceDisableCh[1]) { if (audio->ch2Left) { - sampleLeft -= audio->ch2.sample; + sampleLeft += audio->ch2.sample; } if (audio->ch2Right) { - sampleRight -= audio->ch2.sample; + sampleRight += audio->ch2.sample; } } if (audio->playingCh3 && !audio->forceDisableCh[2]) { if (audio->ch3Left) { - sampleLeft -= audio->ch3.sample; + sampleLeft += audio->ch3.sample; } if (audio->ch3Right) { - sampleRight -= audio->ch3.sample; + sampleRight += audio->ch3.sample; } } if (audio->playingCh4 && !audio->forceDisableCh[3]) { + int8_t sample = _coalesceNoiseChannel(&audio->ch4); if (audio->ch4Left) { - sampleLeft -= audio->ch4.sample; + sampleLeft += sample; } if (audio->ch4Right) { - sampleRight -= audio->ch4.sample; + sampleRight += sample; } }

@@ -631,8 +634,8 @@ struct GBAudio* audio = user;

int16_t sampleLeft = 0; int16_t sampleRight = 0; GBAudioSamplePSG(audio, &sampleLeft, &sampleRight); - sampleLeft = (sampleLeft * audio->masterVolume * 9) >> 7; - sampleRight = (sampleRight * audio->masterVolume * 9) >> 7; + sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7; + sampleRight = (sampleRight * audio->masterVolume * 6) >> 7; mCoreSyncLockAudio(audio->p->sync); unsigned produced;

@@ -742,6 +745,17 @@ default:

// This should never be hit return period * 4; } +} + +static int8_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) { + if (!ch->nSamples) { + return ch->sample; + } + // TODO keep track of timing + int8_t sample = ch->samples / ch->nSamples; + ch->nSamples = 0; + ch->samples = 0; + return sample; } static void _updateEnvelope(struct GBAudioEnvelope* envelope) {

@@ -894,18 +908,17 @@ static void _updateChannel4(struct mTiming* timing, void* user, uint32_t cyclesLate) {

struct GBAudio* audio = user; struct GBAudioNoiseChannel* ch = &audio->ch4; - int32_t baseCycles = ch->ratio ? 2 * ch->ratio : 1; - baseCycles <<= ch->frequency; - baseCycles *= 8 * audio->timingFactor; - int32_t cycles = 0; + int32_t cycles = ch->ratio ? 2 * ch->ratio : 1; + cycles <<= ch->frequency; + cycles *= 8 * audio->timingFactor; + + int lsb = ch->lfsr & 1; + ch->sample = lsb * ch->envelope.currentVolume; + ++ch->nSamples; + ch->samples += ch->sample; + ch->lfsr >>= 1; + ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8); - do { - int lsb = ch->lfsr & 1; - ch->sample = lsb * ch->envelope.currentVolume; - ch->lfsr >>= 1; - ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8); - cycles += baseCycles; - } while (cycles + baseCycles < audio->sampleInterval); mTimingSchedule(timing, &audio->ch4Event, cycles - cyclesLate); }
M src/gb/extra/proxy.csrc/gb/extra/proxy.c

@@ -252,9 +252,10 @@ void GBVideoProxyRendererFinishFrame(struct GBVideoRenderer* renderer) {

struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { proxyRenderer->logger->lock(proxyRenderer->logger); - proxyRenderer->logger->wait(proxyRenderer->logger); + } + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->finishFrame(proxyRenderer->backend); } - proxyRenderer->backend->finishFrame(proxyRenderer->backend); mVideoLoggerRendererFinishFrame(proxyRenderer->logger); mVideoLoggerRendererFlush(proxyRenderer->logger); if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
M src/gb/gb.csrc/gb/gb.c

@@ -816,6 +816,18 @@ memcpy(&out[4], cart->maker, 4);

} } +void GBFrameStarted(struct GB* gb) { + GBTestKeypadIRQ(gb); + + size_t c; + for (c = 0; c < mCoreCallbacksListSize(&gb->coreCallbacks); ++c) { + struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gb->coreCallbacks, c); + if (callbacks->videoFrameStarted) { + callbacks->videoFrameStarted(callbacks->context); + } + } +} + void GBFrameEnded(struct GB* gb) { GBSramClean(gb, gb->video.frameCounter);

@@ -828,7 +840,21 @@ mCheatRefresh(device, cheats);

} } - GBTestKeypadIRQ(gb); + // TODO: Move to common code + if (gb->stream && gb->stream->postVideoFrame) { + const color_t* pixels; + size_t stride; + gb->video.renderer->getPixels(gb->video.renderer, &stride, (const void**) &pixels); + gb->stream->postVideoFrame(gb->stream, pixels, stride); + } + + size_t c; + for (c = 0; c < mCoreCallbacksListSize(&gb->coreCallbacks); ++c) { + struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gb->coreCallbacks, c); + if (callbacks->videoFrameEnded) { + callbacks->videoFrameEnded(callbacks->context); + } + } } enum GBModel GBNameToModel(const char* model) {
M src/gb/mbc.csrc/gb/mbc.c

@@ -95,17 +95,27 @@ }

} static bool _isMulticart(const uint8_t* mem) { - bool success = true; + bool success; struct VFile* vf; vf = VFileFromConstMemory(&mem[GB_SIZE_CART_BANK0 * 0x10], 1024); - success = success && GBIsROM(vf); + success = GBIsROM(vf); vf->close(vf); + + if (!success) { + return false; + } vf = VFileFromConstMemory(&mem[GB_SIZE_CART_BANK0 * 0x20], 1024); - success = success && GBIsROM(vf); + success = GBIsROM(vf); vf->close(vf); + if (!success) { + vf = VFileFromConstMemory(&mem[GB_SIZE_CART_BANK0 * 0x30], 1024); + success = GBIsROM(vf); + vf->close(vf); + } + return success; }
M src/gb/renderers/software.csrc/gb/renderers/software.c

@@ -193,7 +193,7 @@ softwareRenderer->scy = 0;

softwareRenderer->scx = 0; softwareRenderer->wy = 0; softwareRenderer->currentWy = 0; - softwareRenderer->lastY = 0; + softwareRenderer->lastY = GB_VIDEO_VERTICAL_PIXELS; softwareRenderer->hasWindow = false; softwareRenderer->wx = 0; softwareRenderer->model = model;
M src/gb/video.csrc/gb/video.c

@@ -337,14 +337,6 @@ mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3));

return; } - size_t c; - for (c = 0; c < mCoreCallbacksListSize(&video->p->coreCallbacks); ++c) { - struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&video->p->coreCallbacks, c); - if (callbacks->videoFrameEnded) { - callbacks->videoFrameEnded(callbacks->context); - } - } - GBFrameEnded(video->p); mCoreSyncPostFrame(video->p->sync); --video->frameskipCounter;

@@ -354,24 +346,10 @@ video->frameskipCounter = video->frameskip;

} ++video->frameCounter; - // TODO: Move to common code - if (video->p->stream && video->p->stream->postVideoFrame) { - const color_t* pixels; - size_t stride; - video->renderer->getPixels(video->renderer, &stride, (const void**) &pixels); - video->p->stream->postVideoFrame(video->p->stream, pixels, stride); - } - if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) { mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); } - - for (c = 0; c < mCoreCallbacksListSize(&video->p->coreCallbacks); ++c) { - struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&video->p->coreCallbacks, c); - if (callbacks->videoFrameStarted) { - callbacks->videoFrameStarted(callbacks->context); - } - } + GBFrameStarted(video->p); } static void _cleanOAM(struct GBVideo* video, int y) {
M src/gba/audio.csrc/gba/audio.c

@@ -253,7 +253,7 @@ sample = 0x3FF;

} else if (sample < 0) { sample = 0; } - return ((sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) * audio->masterVolume) >> 2; + return ((sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) * audio->masterVolume * 3) >> 4; } static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
M src/gba/extra/proxy.csrc/gba/extra/proxy.c

@@ -117,6 +117,7 @@ void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {

struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; _init(proxyRenderer); + _reset(proxyRenderer); proxyRenderer->backend->init(proxyRenderer->backend); }

@@ -150,7 +151,7 @@ proxyRenderer->backend->writePalette(proxyRenderer->backend, item->address, item->value);

} break; case DIRTY_OAM: - if (item->address < SIZE_PALETTE_RAM) { + if (item->address < SIZE_OAM) { logger->oam[item->address] = item->value; proxyRenderer->backend->writeOAM(proxyRenderer->backend, item->address); }

@@ -159,6 +160,8 @@ case DIRTY_VRAM:

if (item->address <= SIZE_VRAM - 0x1000) { logger->readData(logger, &logger->vram[item->address >> 1], 0x1000, true); proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item->address); + } else { + logger->readData(logger, NULL, 0x1000, true); } break; case DIRTY_SCANLINE:

@@ -267,9 +270,10 @@ void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {

struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { proxyRenderer->logger->lock(proxyRenderer->logger); - proxyRenderer->logger->wait(proxyRenderer->logger); + } + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->finishFrame(proxyRenderer->backend); } - proxyRenderer->backend->finishFrame(proxyRenderer->backend); mVideoLoggerRendererFinishFrame(proxyRenderer->logger); mVideoLoggerRendererFlush(proxyRenderer->logger); if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {

@@ -283,7 +287,6 @@ 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->getPixels(proxyRenderer->backend, stride, pixels); if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {

@@ -297,7 +300,6 @@ 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) {
M src/platform/3ds/main.csrc/platform/3ds/main.c

@@ -194,7 +194,9 @@ }

C3D_FrameBegin(flags); ctrStartFrame(); + C3D_FrameDrawOn(bottomScreen[doubleBuffer]); C3D_RenderTargetClear(bottomScreen[doubleBuffer], C3D_CLEAR_COLOR, 0, 0); + C3D_FrameDrawOn(topScreen[doubleBuffer]); C3D_RenderTargetClear(topScreen[doubleBuffer], C3D_CLEAR_COLOR, 0, 0); }
M src/platform/libretro/libretro.csrc/platform/libretro/libretro.c

@@ -66,6 +66,8 @@ static struct mImageSource imageSource;

static uint32_t* camData = NULL; static unsigned camWidth; static unsigned camHeight; +static unsigned imcapWidth; +static unsigned imcapHeight; static size_t camStride; static void _reloadSettings(void) {

@@ -604,24 +606,49 @@ } else {

cheatSet = device->createSet(device, NULL); mCheatAddSet(device, cheatSet); } - // Convert the super wonky unportable libretro format to something normal - char realCode[] = "XXXXXXXX XXXXXXXX"; - size_t len = strlen(code) + 1; // Include null terminator - size_t i, pos; - for (i = 0, pos = 0; i < len; ++i) { - if (isspace((int) code[i]) || code[i] == '+') { - realCode[pos] = ' '; - } else { - realCode[pos] = code[i]; +// Convert the super wonky unportable libretro format to something normal +#ifdef M_CORE_GBA + if (core->platform(core) == PLATFORM_GBA) { + char realCode[] = "XXXXXXXX XXXXXXXX"; + size_t len = strlen(code) + 1; // Include null terminator + size_t i, pos; + for (i = 0, pos = 0; i < len; ++i) { + if (isspace((int) code[i]) || code[i] == '+') { + realCode[pos] = ' '; + } else { + realCode[pos] = code[i]; + } + if ((pos == 13 && (realCode[pos] == ' ' || !realCode[pos])) || pos == 17) { + realCode[pos] = '\0'; + mCheatAddLine(cheatSet, realCode, 0); + pos = 0; + continue; + } + ++pos; } - if ((pos == 13 && (realCode[pos] == ' ' || !realCode[pos])) || pos == 17) { - realCode[pos] = '\0'; - mCheatAddLine(cheatSet, realCode, 0); - pos = 0; - continue; + } +#endif +#ifdef M_CORE_GB + if (core->platform(core) == PLATFORM_GB) { + char realCode[] = "XXX-XXX-XXX"; + size_t len = strlen(code) + 1; // Include null terminator + size_t i, pos; + for (i = 0, pos = 0; i < len; ++i) { + if (isspace((int) code[i]) || code[i] == '+') { + realCode[pos] = '\0'; + } else { + realCode[pos] = code[i]; + } + if (pos == 11 || !realCode[pos]) { + realCode[pos] = '\0'; + mCheatAddLine(cheatSet, realCode, 0); + pos = 0; + continue; + } + ++pos; } - ++pos; } +#endif } unsigned retro_get_region(void) {

@@ -777,20 +804,40 @@ return 0xFF - value;

} static void _updateCamera(const uint32_t* buffer, unsigned width, unsigned height, size_t pitch) { - if (!camData || width != camWidth || height != camHeight) { - camData = malloc(sizeof(*buffer) * height * pitch); + if (!camData || width > camWidth || height > camHeight) { + if (camData) { + free(camData); + } + unsigned bufPitch = pitch / sizeof(*buffer); + unsigned bufHeight = height; + if (imcapWidth > bufPitch) { + bufPitch = imcapWidth; + } + if (imcapHeight > bufHeight) { + bufHeight = imcapHeight; + } + camData = malloc(sizeof(*buffer) * bufHeight * bufPitch); + memset(camData, 0xFF, sizeof(*buffer) * bufHeight * bufPitch); camWidth = width; - camHeight = height; - camStride = pitch / sizeof(*buffer); + camHeight = bufHeight; + camStride = bufPitch; } - memcpy(camData, buffer, sizeof(*buffer) * height * pitch); + size_t i; + for (i = 0; i < height; ++i) { + memcpy(&camData[camStride * i], &buffer[pitch * i / sizeof(*buffer)], pitch); + } } static void _startImage(struct mImageSource* image, unsigned w, unsigned h, int colorFormats) { UNUSED(image); UNUSED(colorFormats); + if (camData) { + free(camData); + } camData = NULL; + imcapWidth = w; + imcapHeight = h; cam.start(); }

@@ -803,8 +850,18 @@ static void _requestImage(struct mImageSource* image, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {

UNUSED(image); if (!camData) { cam.start(); + *buffer = NULL; + return; } - *buffer = camData; + size_t offset = 0; + if (imcapWidth < camWidth) { + offset += (camWidth - imcapWidth) / 2; + } + if (imcapHeight < camHeight) { + offset += (camHeight - imcapHeight) / 2 * camStride; + } + + *buffer = &camData[offset]; *stride = camStride; *colorFormat = mCOLOR_XRGB8; }
M src/platform/psp2/psp2-context.csrc/platform/psp2/psp2-context.c

@@ -97,18 +97,23 @@

static THREAD_ENTRY _audioThread(void* context) { struct mPSP2AudioContext* audio = (struct mPSP2AudioContext*) context; uint32_t zeroBuffer[PSP2_SAMPLES] = {0}; + void* buffer = zeroBuffer; int audioPort = sceAudioOutOpenPort(SCE_AUDIO_OUT_PORT_TYPE_MAIN, PSP2_SAMPLES, 48000, SCE_AUDIO_OUT_MODE_STEREO); while (audio->running) { MutexLock(&audio->mutex); - void* buffer; + if (buffer != zeroBuffer) { + // Can only happen in successive iterations + audio->samples -= PSP2_SAMPLES; + ConditionWake(&audio->cond); + } if (audio->samples >= PSP2_SAMPLES) { buffer = &audio->buffer[audio->readOffset]; - audio->samples -= PSP2_SAMPLES; audio->readOffset += PSP2_SAMPLES; if (audio->readOffset >= PSP2_AUDIO_BUFFER_SIZE) { audio->readOffset = 0; } - ConditionWake(&audio->cond); + // Don't mark samples as read until the next loop iteration to prevent + // writing to the buffer while being read (see above) } else { buffer = zeroBuffer; }

@@ -280,6 +285,13 @@ }

void mPSP2SetFrameLimiter(struct mGUIRunner* runner, bool limit) { UNUSED(runner); + if (!frameLimiter && limit) { + MutexLock(&audioContext.mutex); + while (audioContext.samples) { + ConditionWait(&audioContext.cond, &audioContext.mutex); + } + MutexUnlock(&audioContext.mutex); + } frameLimiter = limit; }
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -271,7 +271,7 @@

qt5_wrap_ui(UI_SRC ${UI_FILES}) add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/${BINARY_NAME}.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_SRC} ${AUDIO_SRC} ${RESOURCES}) -set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES};${OS_DEFINES};${QT_DEFINES}") +set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES};${OS_DEFINES};${QT_DEFINES}" COMPILE_OPTIONS "${FEATURE_FLAGS}") list(APPEND QT_LIBRARIES Qt5::Widgets) if(BUILD_GL OR BUILD_GLES2)

@@ -350,3 +350,11 @@ find_program(BASH bash)

install(CODE "execute_process(COMMAND \"${BASH}\" \"${CMAKE_SOURCE_DIR}/tools/deploy-win.sh\" \"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.exe\" \"\${CMAKE_INSTALL_PREFIX}\" \"\$ENV{PWD}\" WORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")" COMPONENT ${BINARY_NAME}-qt) endif() endif() + +if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + if(NOT APPLE) + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}-qt>" "$<TARGET_FILE:${BINARY_NAME}-qt>.dSYM") + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${STRIP}" -S "$<TARGET_FILE:${BINARY_NAME}-qt>") + install(FILES "$<TARGET_FILE:${BINARY_NAME}-qt>.dSYM" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt-dbg) + endif() +endif()
M src/platform/qt/CoreController.cppsrc/platform/qt/CoreController.cpp

@@ -46,6 +46,7 @@ m_buffers[1].resize(size.width() * size.height() * sizeof(color_t));

m_buffers[0].fill(0xFF); m_buffers[1].fill(0xFF); m_activeBuffer = &m_buffers[0]; + m_completeBuffer = m_buffers[0]; m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width());

@@ -209,12 +210,9 @@ mCoreConfigDeinit(&m_threadContext.core->config);

m_threadContext.core->deinit(m_threadContext.core); } -color_t* CoreController::drawContext() { +const color_t* CoreController::drawContext() { QMutexLocker locker(&m_mutex); - if (!m_completeBuffer) { - return nullptr; - } - return reinterpret_cast<color_t*>(m_completeBuffer->data()); + return reinterpret_cast<const color_t*>(m_completeBuffer.constData()); } bool CoreController::isPaused() {

@@ -762,7 +760,7 @@ }

void CoreController::finishFrame() { QMutexLocker locker(&m_mutex); - m_completeBuffer = m_activeBuffer; + memcpy(m_completeBuffer.data(), m_activeBuffer->constData(), m_activeBuffer->size()); // TODO: Generalize this to triple buffering? m_activeBuffer = &m_buffers[0];

@@ -770,7 +768,7 @@ if (m_activeBuffer == m_completeBuffer) {

m_activeBuffer = &m_buffers[1]; } // Copy contents to avoid issues when doing frameskip - memcpy(m_activeBuffer->data(), m_completeBuffer->data(), m_activeBuffer->size()); + memcpy(m_activeBuffer->data(), m_completeBuffer.constData(), m_activeBuffer->size()); m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), screenDimensions().width()); for (auto& action : m_frameActions) {
M src/platform/qt/CoreController.hsrc/platform/qt/CoreController.h

@@ -58,7 +58,7 @@ ~CoreController();

mCoreThread* thread() { return &m_threadContext; } - color_t* drawContext(); + const color_t* drawContext(); bool isPaused(); bool hasStarted();

@@ -170,7 +170,7 @@ bool m_patched = false;

QByteArray m_buffers[2]; QByteArray* m_activeBuffer; - QByteArray* m_completeBuffer = nullptr; + QByteArray m_completeBuffer; std::unique_ptr<mCacheSet> m_cacheSet; std::unique_ptr<Override> m_override;
M src/platform/qt/DisplayQt.cppsrc/platform/qt/DisplayQt.cpp

@@ -52,7 +52,7 @@ }

void DisplayQt::framePosted() { update(); - color_t* buffer = m_context->drawContext(); + const color_t* buffer = m_context->drawContext(); if (const_cast<const QImage&>(m_backing).bits() == reinterpret_cast<const uchar*>(buffer)) { return; }
M src/platform/qt/VideoView.cppsrc/platform/qt/VideoView.cpp

@@ -51,6 +51,7 @@

if (s_acodecMap.empty()) { s_acodecMap["mp3"] = "libmp3lame"; s_acodecMap["opus"] = "libopus"; + s_acodecMap["vorbis"] = "libvorbis"; s_acodecMap["uncompressed"] = "pcm_s16le"; } if (s_vcodecMap.empty()) {

@@ -101,7 +102,7 @@ setPreset({

.container = "MKV", .vcodec = "h.264", .acodec = "FLAC", - .vbr = 0, + .vbr = -1, .abr = 0, .dims = QSize(), });

@@ -170,8 +171,8 @@ });

addPreset(m_ui.presetWebM, { .container = "WebM", - .vcodec = "VP8", - .acodec = "Vorbis", + .vcodec = "VP9", + .acodec = "Opus", .vbr = 800, .abr = 128 });

@@ -181,7 +182,7 @@ addPreset(m_ui.presetLossless, {

.container = "MKV", .vcodec = "h.264", .acodec = "FLAC", - .vbr = 0, + .vbr = -1, .abr = 0, .dims = QSize(m_nativeWidth, m_nativeHeight) });

@@ -315,7 +316,7 @@ }

} void VideoView::setVideoBitrate(int br, bool manual) { - m_vbr = br * 1000; + m_vbr = br >= 0 ? br * 1000 : 0; FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr); validateSettings(); if (manual) {
M src/platform/qt/VideoView.uisrc/platform/qt/VideoView.ui

@@ -106,7 +106,7 @@ <layout class="QVBoxLayout" name="verticalLayout_2">

<item> <widget class="QRadioButton" name="presetHQ"> <property name="text"> - <string>High Quality</string> + <string>High &amp;Quality</string> </property> <attribute name="buttonGroup"> <string notr="true">presets</string>

@@ -116,7 +116,7 @@ </item>

<item> <widget class="QRadioButton" name="presetYoutube"> <property name="text"> - <string>YouTube</string> + <string>&amp;YouTube</string> </property> <attribute name="buttonGroup"> <string notr="true">presets</string>

@@ -136,7 +136,7 @@ </item>

<item> <widget class="QRadioButton" name="presetLossless"> <property name="text"> - <string>Lossless</string> + <string>&amp;Lossless</string> </property> <property name="checked"> <bool>true</bool>

@@ -153,7 +153,7 @@ <layout class="QVBoxLayout" name="verticalLayout_3">

<item> <widget class="QRadioButton" name="preset1080"> <property name="text"> - <string>1080p</string> + <string>&amp;1080p</string> </property> <attribute name="buttonGroup"> <string notr="true">resolutions</string>

@@ -163,7 +163,7 @@ </item>

<item> <widget class="QRadioButton" name="preset720"> <property name="text"> - <string>720p</string> + <string>&amp;720p</string> </property> <attribute name="buttonGroup"> <string notr="true">resolutions</string>

@@ -173,7 +173,7 @@ </item>

<item> <widget class="QRadioButton" name="preset480"> <property name="text"> - <string>480p</string> + <string>&amp;480p</string> </property> <attribute name="buttonGroup"> <string notr="true">resolutions</string>

@@ -186,7 +186,7 @@ <property name="enabled">

<bool>false</bool> </property> <property name="text"> - <string>Native</string> + <string>&amp;Native</string> </property> <property name="checked"> <bool>true</bool>

@@ -272,6 +272,11 @@ </item>

<item> <property name="text"> <string>VP8</string> + </property> + </item> + <item> + <property name="text"> + <string>VP9</string> </property> </item> <item>
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -105,3 +105,11 @@ install(TARGETS ${BINARY_NAME}-sdl DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl)

if(UNIX) install(FILES ${CMAKE_SOURCE_DIR}/doc/${BINARY_NAME}.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-sdl) endif() + +if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + if(NOT APPLE) + add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}-sdl>" "$<TARGET_FILE:${BINARY_NAME}-sdl>.dSYM") + add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${STRIP}" -S "$<TARGET_FILE:${BINARY_NAME}-sdl>") + install(FILES "$<TARGET_FILE:${BINARY_NAME}-sdl>.dSYM" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl-dbg) + endif() +endif()
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -57,7 +57,6 @@ struct mCoreOptions opts = {

.useBios = true, .rewindEnable = true, .rewindBufferCapacity = 600, - .rewindSave = true, .audioBuffers = 1024, .videoSync = false, .audioSync = true,
M src/platform/switch/CMakeLists.txtsrc/platform/switch/CMakeLists.txt

@@ -8,7 +8,7 @@ set(OS_DEFINES USE_VFS_FILE IOAPI_NO_64)

list(APPEND CORE_VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) -include_directories(AFTER ${OPENGLES2_INCLUDE_DIR} ${OPENGL_EGL_INCLUDE_DIR}) +include_directories(AFTER ${OPENGLES3_INCLUDE_DIR} ${OPENGL_EGL_INCLUDE_DIR}) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/wii/wii-*.c) if(${CMAKE_BUILD_TYPE} STREQUAL Debug OR ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo)

@@ -34,7 +34,7 @@ endif()

add_executable(${BINARY_NAME}.elf ${GUI_SRC} ${PLATFORM_SRC} main.c) set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") -target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${EGL_LIBRARY} ${OPENGLES2_LIBRARY} ${GLAPI_LIBRARY} ${NOUVEAU_LIBRARY} stdc++ ${OS_LIB}) +target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${EGL_LIBRARY} ${OPENGLES3_LIBRARY} ${GLAPI_LIBRARY} ${NOUVEAU_LIBRARY} stdc++ ${OS_LIB}) add_custom_command(OUTPUT control.nacp COMMAND ${NACPTOOL} --create "${PROJECT_NAME}" "endrift" "${VERSION_STRING}" control.nacp)
M src/platform/switch/gui-font.csrc/platform/switch/gui-font.c

@@ -9,7 +9,7 @@ #include <mgba-util/png-io.h>

#include <mgba-util/string.h> #include <mgba-util/vfs.h> -#include <GLES2/gl2.h> +#include <GLES3/gl3.h> #define GLYPH_HEIGHT 24 #define CELL_HEIGHT 32

@@ -58,7 +58,7 @@ struct GUIFont {

GLuint font; GLuint program; GLuint vbo; - GLuint offsetLocation; + GLuint vao; GLuint texLocation; GLuint dimsLocation; GLuint transformLocation;

@@ -166,12 +166,16 @@ font->transformLocation = glGetUniformLocation(font->program, "transform");

font->originLocation = glGetUniformLocation(font->program, "origin"); font->glyphLocation = glGetUniformLocation(font->program, "glyph"); font->cutoffLocation = glGetUniformLocation(font->program, "cutoff"); - font->offsetLocation = glGetAttribLocation(font->program, "offset"); + GLuint offsetLocation = glGetAttribLocation(font->program, "offset"); glGenBuffers(1, &font->vbo); + glGenVertexArrays(1, &font->vao); + glBindVertexArray(font->vao); glBindBuffer(GL_ARRAY_BUFFER, font->vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); + glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); + glEnableVertexAttribArray(offsetLocation); + glBindVertexArray(0); return font; }

@@ -180,6 +184,7 @@ void GUIFontDestroy(struct GUIFont* font) {

glDeleteBuffers(1, &font->vbo); glDeleteProgram(font->program); glDeleteTextures(1, &font->font); + glDeleteVertexArrays(1, &font->vao); free(font); }

@@ -222,9 +227,9 @@ }

struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph]; glUseProgram(font->program); + glBindVertexArray(font->vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, font->font); - glBindBuffer(GL_ARRAY_BUFFER, font->vbo); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

@@ -235,19 +240,15 @@ glUniform2f(font->dimsLocation, CELL_WIDTH - (metric.padding.left + metric.padding.right) * 2, CELL_HEIGHT - (metric.padding.top + metric.padding.bottom) * 2);

glUniform3f(font->originLocation, x, y - GLYPH_HEIGHT + metric.padding.top * 2, 0); glUniformMatrix2fv(font->transformLocation, 1, GL_FALSE, (float[4]) {1.0, 0.0, 0.0, 1.0}); - glVertexAttribPointer(font->offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); - glEnableVertexAttribArray(font->offsetLocation); - glUniform1f(font->cutoffLocation, 0.1f); glUniform4f(font->colorLocation, 0.0, 0.0, 0.0, ((color >> 24) & 0xFF) / 128.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glUniform1f(font->cutoffLocation, 0.7f); - glUniform4f(font->colorLocation, ((color >> 16) & 0xFF) / 255.0f, ((color >> 8) & 0xFF) / 255.0f, (color & 0xFF) / 255.0f, ((color >> 24) & 0xFF) / 255.0f); + glUniform4f(font->colorLocation, (color & 0xFF) / 255.0f, ((color >> 8) & 0xFF) / 255.0f, ((color >> 16) & 0xFF) / 255.0f, ((color >> 24) & 0xFF) / 255.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisableVertexAttribArray(font->offsetLocation); - glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); glUseProgram(0); }

@@ -291,9 +292,9 @@ break;

} glUseProgram(font->program); + glBindVertexArray(font->vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, font->font); - glBindBuffer(GL_ARRAY_BUFFER, font->vbo); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

@@ -304,19 +305,15 @@ glUniform2f(font->dimsLocation, metric.width * 2, metric.height * 2);

glUniform3f(font->originLocation, x, y, 0); glUniformMatrix2fv(font->transformLocation, 1, GL_FALSE, (float[4]) {hFlip, 0.0, 0.0, vFlip}); - glVertexAttribPointer(font->offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); - glEnableVertexAttribArray(font->offsetLocation); - glUniform1f(font->cutoffLocation, 0.1f); glUniform4f(font->colorLocation, 0.0, 0.0, 0.0, ((color >> 24) & 0xFF) / 128.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glUniform1f(font->cutoffLocation, 0.7f); - glUniform4f(font->colorLocation, ((color >> 16) & 0xFF) / 255.0f, ((color >> 8) & 0xFF) / 255.0f, (color & 0xFF) / 255.0f, ((color >> 24) & 0xFF) / 255.0f); + glUniform4f(font->colorLocation, (color & 0xFF) / 255.0f, ((color >> 8) & 0xFF) / 255.0f, ((color >> 16) & 0xFF) / 255.0f, ((color >> 24) & 0xFF) / 255.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisableVertexAttribArray(font->offsetLocation); - glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); glUseProgram(0); }

@@ -334,9 +331,9 @@ h = metric.height * 2;

} glUseProgram(font->program); + glBindVertexArray(font->vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, font->font); - glBindBuffer(GL_ARRAY_BUFFER, font->vbo); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

@@ -347,9 +344,6 @@ glUniform2f(font->dimsLocation, metric.width * 2, metric.height * 2);

glUniform3f(font->originLocation, x + w / 2 - metric.width, y + h / 2 - metric.height, 0); glUniformMatrix2fv(font->transformLocation, 1, GL_FALSE, (float[4]) {w * 0.5f / metric.width, 0.0, 0.0, h * 0.5f / metric.height}); - glVertexAttribPointer(font->offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); - glEnableVertexAttribArray(font->offsetLocation); - glUniform1f(font->cutoffLocation, 0.1f); glUniform4f(font->colorLocation, 0.0, 0.0, 0.0, ((color >> 24) & 0xFF) / 128.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

@@ -358,7 +352,6 @@ glUniform1f(font->cutoffLocation, 0.7f);

glUniform4f(font->colorLocation, ((color >> 16) & 0xFF) / 255.0f, ((color >> 8) & 0xFF) / 255.0f, (color & 0xFF) / 255.0f, ((color >> 24) & 0xFF) / 255.0f); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisableVertexAttribArray(font->offsetLocation); - glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); glUseProgram(0); }
M src/platform/switch/main.csrc/platform/switch/main.c

@@ -10,15 +10,17 @@ #include <mgba/internal/gba/audio.h>

#include <mgba/internal/gba/input.h> #include <mgba-util/gui.h> #include <mgba-util/gui/font.h> +#include <mgba-util/gui/menu.h> #include <switch.h> #include <EGL/egl.h> -#include <GLES2/gl2.h> +#include <GLES3/gl3.h> #define AUTO_INPUT 0x4E585031 #define SAMPLES 0x400 #define BUFFER_SIZE 0x1000 #define N_BUFFERS 4 +#define ANALOG_DEADZONE 0x4000 TimeType __nx_time_type = TimeType_UserSystemClock;

@@ -63,21 +65,23 @@ "}";

static GLuint program; static GLuint vbo; -static GLuint offsetLocation; +static GLuint vao; +static GLuint pbo; static GLuint texLocation; static GLuint dimsLocation; static GLuint insizeLocation; static GLuint colorLocation; static GLuint tex; -static color_t frameBuffer[256 * 256]; +static color_t* frameBuffer; static struct mAVStream stream; static int audioBufferActive; static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000))); static AudioOutBuffer audoutBuffer[N_BUFFERS]; static int enqueuedBuffers; static bool frameLimiter = true; -static int framecount = 0; +static unsigned framecount = 0; +static unsigned framecap = 10; static bool initEgl() { s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

@@ -105,11 +109,11 @@ if (!s_surface) {

goto _fail1; } - //EGLint contextAttributeList[] = { - // EGL_CONTEXT_CLIENT_VERSION, 2, - // EGL_NONE - //}; - s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, NULL); + EGLint contextAttributeList[] = { + EGL_CONTEXT_CLIENT_VERSION, 3, + EGL_NONE + }; + s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList); if (!s_context) { goto _fail2; }

@@ -144,12 +148,13 @@ mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);

} static void _drawStart(void) { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT); } static void _drawEnd(void) { - if (frameLimiter || (framecount & 3) == 0) { + if (frameLimiter || framecount >= framecap) { eglSwapBuffers(s_display, s_surface); + framecount = 0; } }

@@ -158,6 +163,40 @@ int keys = 0;

hidScanInput(); u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO); keys |= mInputMapKeyBits(map, AUTO_INPUT, padkeys, 0); + + JoystickPosition jspos; + hidJoystickRead(&jspos, CONTROLLER_P1_AUTO, JOYSTICK_LEFT); + + int l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_LEFT)); + int r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_RIGHT)); + int u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_UP)); + int d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_DOWN)); + + if (l == -1) { + l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DLEFT)); + } + if (r == -1) { + r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DRIGHT)); + } + if (u == -1) { + u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DUP)); + } + if (d == -1) { + d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DDOWN)); + } + + if (jspos.dx < -ANALOG_DEADZONE && l != -1) { + keys |= 1 << l; + } + if (jspos.dx > ANALOG_DEADZONE && r != -1) { + keys |= 1 << r; + } + if (jspos.dy < -ANALOG_DEADZONE && d != -1) { + keys |= 1 << d; + } + if (jspos.dy > ANALOG_DEADZONE && u != -1) { + keys |= 1 << u; + } return keys; }

@@ -196,15 +235,16 @@

double ratio = GBAAudioCalculateRatio(1, 60.0, 1); blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio); blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio); + + mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap); } static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) { - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glUseProgram(program); + glBindVertexArray(vao); float aspectX = width / (float) runner->params.width; float aspectY = height / (float) runner->params.height; float max;

@@ -226,26 +266,39 @@ } else {

glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f); } - glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); - glEnableVertexAttribArray(offsetLocation); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisableVertexAttribArray(offsetLocation); - glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); glUseProgram(0); } +static void _prepareForFrame(struct mGUIRunner* runner) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT); + if (frameBuffer) { + runner->core->setVideoBuffer(runner->core, frameBuffer, 256); + } + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + static void _drawFrame(struct mGUIRunner* runner, bool faded) { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frameBuffer); + ++framecount; + if (!frameLimiter && framecount < framecap) { + return; + } unsigned width, height; runner->core->desiredVideoDimensions(runner->core, &width, &height); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + _drawTex(runner, width, height, faded); - - ++framecount; } static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {

@@ -257,11 +310,7 @@ _drawTex(runner, width, height, faded);

} static uint16_t _pollGameInput(struct mGUIRunner* runner) { - int keys = 0; - hidScanInput(); - u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO); - keys |= mInputMapKeyBits(&runner->core->inputMap, AUTO_INPUT, padkeys, 0); - return keys; + return _pollInput(&runner->core->inputMap); } static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {

@@ -310,7 +359,13 @@ static int _batteryState(void) {

u32 charge; int state = 0; if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) { - state = charge / 25; + state = (charge + 12) / 25; + } else { + return BATTERY_NOT_PRESENT; + } + ChargerType type; + if (R_SUCCEEDED(psmGetChargerType(&type)) && type) { + state |= BATTERY_CHARGING; } return state; }

@@ -338,6 +393,13 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + glGenBuffers(1, &pbo); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW); + frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); program = glCreateProgram(); GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);

@@ -382,12 +444,16 @@ texLocation = glGetUniformLocation(program, "tex");

colorLocation = glGetUniformLocation(program, "color"); dimsLocation = glGetUniformLocation(program, "dims"); insizeLocation = glGetUniformLocation(program, "insize"); - offsetLocation = glGetAttribLocation(program, "offset"); + GLuint offsetLocation = glGetAttribLocation(program, "offset"); glGenBuffers(1, &vbo); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); + glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); + glEnableVertexAttribArray(offsetLocation); + glBindVertexArray(0); stream.videoDimensionsChanged = NULL; stream.postVideoFrame = NULL;

@@ -451,12 +517,26 @@ .nKeys = 26

}, { .id = 0 } }, - .nConfigExtra = 0, + .configExtra = (struct GUIMenuItem[]) { + { + .title = "Fast forward cap", + .data = "fastForwardCap", + .submenu = 0, + .state = 7, + .validStates = (const char*[]) { + "3", "4", "5", "6", "7", "8", "9", + "10", "11", "12", "13", "14", "15", + "20", "30" + }, + .nStates = 15 + }, + }, + .nConfigExtra = 1, .setup = _setup, .teardown = NULL, .gameLoaded = _gameLoaded, .gameUnloaded = NULL, - .prepareForFrame = NULL, + .prepareForFrame = _prepareForFrame, .drawFrame = _drawFrame, .drawScreenshot = _drawScreenshot, .paused = NULL,

@@ -477,7 +557,25 @@ _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);

_mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT); audoutStartAudioOut(); - mGUIRunloop(&runner); + + if (argc > 1) { + size_t i; + for (i = 0; runner.keySources[i].id; ++i) { + mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config)); + } + mGUIRun(&runner, argv[1]); + } else { + mGUIRunloop(&runner); + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + glDeleteBuffers(1, &pbo); + + glDeleteTextures(1, &tex); + glDeleteBuffers(1, &vbo); + glDeleteProgram(program); + glDeleteVertexArrays(1, &vao); psmExit(); audoutExit();
M src/platform/test/perf-main.csrc/platform/test/perf-main.c

@@ -29,11 +29,12 @@ #include <signal.h>

#include <inttypes.h> #include <sys/time.h> -#define PERF_OPTIONS "DF:L:NPS:" +#define PERF_OPTIONS "DF:L:NPS:T" #define PERF_USAGE \ "\nBenchmark options:\n" \ " -F FRAMES Run for the specified number of FRAMES before exiting\n" \ " -N Disable video rendering entirely\n" \ + " -T Use threaded video rendering\n" \ " -P CSV output, useful for parsing\n" \ " -S SEC Run for SEC in-game seconds before exiting\n" \ " -L FILE Load a savestate when starting the test\n" \

@@ -41,6 +42,7 @@ " -D Act as a server"

struct PerfOpts { bool noVideo; + bool threadedVideo; bool csv; unsigned duration; unsigned frames;

@@ -89,7 +91,7 @@

struct mLogger logger = { .log = _log }; mLogSetDefaultLogger(&logger); - struct PerfOpts perfOpts = { false, false, 0, 0, 0, false }; + struct PerfOpts perfOpts = { false, false, false, 0, 0, 0, false }; struct mSubParser subparser = { .usage = PERF_USAGE, .parse = _parsePerfOpts,

@@ -162,6 +164,12 @@ mCoreLoadFile(core, fname);

mCoreConfigInit(&core->config, "perf"); mCoreConfigLoad(&core->config); + if (perfOpts->threadedVideo) { + mCoreConfigSetOverrideIntValue(&core->config, "threadedVideo", 1); + } else { + mCoreConfigSetOverrideIntValue(&core->config, "threadedVideo", 0); + } + struct mCoreOptions opts = {}; mCoreConfigMap(&core->config, &opts); opts.audioSync = false;

@@ -200,6 +208,8 @@ char buffer[256];

const char* rendererName; if (perfOpts->noVideo) { rendererName = "none"; + } else if (perfOpts->threadedVideo) { + rendererName = "threaded-software"; } else { rendererName = "software"; }

@@ -313,6 +323,9 @@ return true;

case 'S': opts->duration = strtoul(arg, 0, 10); return !errno; + case 'T': + opts->threadedVideo = true; + return true; case 'L': opts->savestate = strdup(arg); return true;
M src/platform/wii/main.csrc/platform/wii/main.c

@@ -229,15 +229,6 @@ if (runner->core) {

double ratio = GBAAudioCalculateRatio(1, audioSampleRate, 1); blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio); blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio); - - runner->core->desiredVideoDimensions(runner->core, &corew, &coreh); - int hfactor = vmode->fbWidth / (corew * wAdjust); - int vfactor = vmode->efbHeight / (coreh * hAdjust); - if (hfactor > vfactor) { - scaleFactor = vfactor; - } else { - scaleFactor = hfactor; - } } } }

@@ -812,7 +803,7 @@ }

} void _drawFrame(struct mGUIRunner* runner, bool faded) { - UNUSED(runner); + runner->core->desiredVideoDimensions(runner->core, &corew, &coreh); uint32_t color = 0xFFFFFF3F; if (!faded) { color |= 0xC0;

@@ -838,9 +829,9 @@ }

GX_InvalidateTexAll(); GX_LoadTexObj(&tex, GX_TEXMAP0); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0); - s16 vertWidth = TEX_W; - s16 vertHeight = TEX_H; + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); + s16 vertWidth = corew; + s16 vertHeight = coreh; if (filterMode == FM_LINEAR_2x) { Mtx44 proj;

@@ -850,19 +841,19 @@

GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position2s16(0, TEX_H * 2); GX_Color1u32(0xFFFFFFFF); - GX_TexCoord2s16(0, 1); + GX_TexCoord2f32(0, 1); GX_Position2s16(TEX_W * 2, TEX_H * 2); GX_Color1u32(0xFFFFFFFF); - GX_TexCoord2s16(1, 1); + GX_TexCoord2f32(1, 1); GX_Position2s16(TEX_W * 2, 0); GX_Color1u32(0xFFFFFFFF); - GX_TexCoord2s16(1, 0); + GX_TexCoord2f32(1, 0); GX_Position2s16(0, 0); GX_Color1u32(0xFFFFFFFF); - GX_TexCoord2s16(0, 0); + GX_TexCoord2f32(0, 0); GX_End(); GX_SetTexCopySrc(0, 0, TEX_W * 2, TEX_H * 2);

@@ -871,6 +862,14 @@ GX_CopyTex(rescaleTexmem, GX_TRUE);

GX_LoadTexObj(&rescaleTex, GX_TEXMAP0); } + int hfactor = vmode->fbWidth / (corew * wAdjust); + int vfactor = vmode->efbHeight / (coreh * hAdjust); + if (hfactor > vfactor) { + scaleFactor = vfactor; + } else { + scaleFactor = hfactor; + } + if (screenMode == SM_PA) { vertWidth *= scaleFactor; vertHeight *= scaleFactor;

@@ -885,19 +884,19 @@

GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position2s16(0, vertHeight); GX_Color1u32(color); - GX_TexCoord2s16(0, 1); + GX_TexCoord2f32(0, coreh / (float) TEX_H); GX_Position2s16(vertWidth, vertHeight); GX_Color1u32(color); - GX_TexCoord2s16(1, 1); + GX_TexCoord2f32(corew / (float) TEX_W, coreh / (float) TEX_H); GX_Position2s16(vertWidth, 0); GX_Color1u32(color); - GX_TexCoord2s16(1, 0); + GX_TexCoord2f32(corew / (float) TEX_W, 0); GX_Position2s16(0, 0); GX_Color1u32(color); - GX_TexCoord2s16(0, 0); + GX_TexCoord2f32(0, 0); GX_End(); }
M src/util/gui.csrc/util/gui.c

@@ -5,6 +5,9 @@ * 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-util/gui.h> +#define KEY_DELAY 45 +#define KEY_REPEAT 5 + void GUIInit(struct GUIParams* params) { memset(params->inputHistory, 0, sizeof(params->inputHistory)); strncpy(params->currentPath, params->basePath, PATH_MAX);

@@ -19,7 +22,7 @@ ++params->inputHistory[i];

} else { params->inputHistory[i] = -1; } - if (!params->inputHistory[i] || (params->inputHistory[i] >= 30 && !(params->inputHistory[i] % 5))) { + if (!params->inputHistory[i] || (params->inputHistory[i] >= KEY_DELAY && !(params->inputHistory[i] % KEY_REPEAT))) { newInput |= (1 << i); } }
M src/util/gui/menu.csrc/util/gui/menu.c

@@ -260,6 +260,9 @@ if (!params->batteryState) {

return; } int state = params->batteryState(); + if (state == BATTERY_NOT_PRESENT) { + return; + } uint32_t color = 0xFF000000; if (state == (BATTERY_CHARGING | BATTERY_FULL)) { color |= 0xFFC060;
M src/util/ring-fifo.csrc/util/ring-fifo.c

@@ -22,6 +22,18 @@ size_t RingFIFOCapacity(const struct RingFIFO* buffer) {

return buffer->capacity; } +size_t RingFIFOSize(const struct RingFIFO* buffer) { + const void* read; + const void* write; + ATOMIC_LOAD(read, buffer->readPtr); + ATOMIC_LOAD(write, buffer->writePtr); + if (read <= write) { + return (uintptr_t) write - (uintptr_t) read; + } else { + return buffer->capacity - (uintptr_t) read + (uintptr_t) write; + } +} + void RingFIFOClear(struct RingFIFO* buffer) { ATOMIC_STORE(buffer->readPtr, buffer->data); ATOMIC_STORE(buffer->writePtr, buffer->data);

@@ -33,8 +45,8 @@ void* end;

ATOMIC_LOAD(end, buffer->readPtr); // Wrap around if we can't fit enough in here - if ((intptr_t) data - (intptr_t) buffer->data + length >= buffer->capacity) { - if (end == buffer->data) { + if ((uintptr_t) data - (uintptr_t) buffer->data + length >= buffer->capacity) { + if (end == buffer->data || end > data) { // Oops! If we wrap now, it'll appear empty return 0; }

@@ -65,8 +77,8 @@ void* end;

ATOMIC_LOAD(end, buffer->writePtr); // Wrap around if we can't fit enough in here - if ((intptr_t) data - (intptr_t) buffer->data + length >= buffer->capacity) { - if (end == data) { + if ((uintptr_t) data - (uintptr_t) buffer->data + length >= buffer->capacity) { + if (end >= data) { // Oops! If we wrap now, it'll appear full return 0; }

@@ -78,7 +90,7 @@ if (data > end) {

uintptr_t bufferEnd = (uintptr_t) buffer->data + buffer->capacity; remaining = bufferEnd - (uintptr_t) data; } else { - remaining = (intptr_t) end - (intptr_t) data; + remaining = (uintptr_t) end - (uintptr_t) data; } // If the pointers touch, it's empty if (remaining < length) {

@@ -87,6 +99,6 @@ }

if (output) { memcpy(output, data, length); } - ATOMIC_STORE(buffer->readPtr, (void*) ((intptr_t) data + length)); + ATOMIC_STORE(buffer->readPtr, (void*) ((uintptr_t) data + length)); return length; }