all repos — mgba @ 6b84383d1ad29d9dd39a62a80c1c8c5878979f45

mGBA Game Boy Advance Emulator

Merge branch 'master' (early part) into medusa
Vicki Pfau vi@endrift.com
Sat, 28 Sep 2019 15:58:26 -0700
commit

6b84383d1ad29d9dd39a62a80c1c8c5878979f45

parent

bb78133b21b868905453207d061cf9cdf79bc474

M CHANGESCHANGES

@@ -60,6 +60,8 @@ - GB: Fix savedata initialization (fixes mgba.io/i/1473, mgba.io/i/1478)

- GB Memory: Better emulate 0xFEA0 region on DMG, MGB and AGB - GB Printer: Reset printer buffer index after printing - GB Video: Fix mode 0 window edge case (fixes mgba.io/i/1519) + - GBA Audio: Fix channel 4 aliasing (fixes mgba.io/i/1265) + - GB Audio: Improve channel 4 supersampling Other fixes: - Qt: Fix some Qt display driver race conditions - Core: Improved lockstep driver reliability (Le Hoang Quyen)

@@ -78,6 +80,7 @@ - GBA Cheats: Fix value incrementing in CB slide codes (fixes mgba.io/i/1501)

- Qt: Only show emulator restart warning once per settings saving - Qt: Improve cheat view UX - GB: Fix SGB controller incrementing (fixes mgba.io/i/1104) + - FFmpeg: Drain recording buffers Misc: - GBA Savedata: EEPROM performance fixes - GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash

@@ -105,6 +108,8 @@ - Qt: Add option to pause on minimizing window (closes mgba.io/i/1379)

- Switch: Support file associations - Qt: Show error message if file failed to load - Qt: Scale pixel color values to full range (fixes mgba.io/i/1511) + - Qt, OpenGL: Disable integer scaling for dimensions that don't fit + - Feature: Switch from ImageMagick to FFmpeg for GIF generation 0.7.2: (2019-05-25) Emulation fixes:
M CMakeLists.txtCMakeLists.txt

@@ -36,7 +36,6 @@ set(USE_ZLIB ON CACHE BOOL "Whether or not to enable zlib support")

set(USE_MINIZIP ON CACHE BOOL "Whether or not to enable external minizip support") set(USE_PNG ON CACHE BOOL "Whether or not to enable PNG support") set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable LIBZIP support") - set(USE_MAGICK ON CACHE BOOL "Whether or not to enable ImageMagick support") set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support") set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support") set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core")

@@ -467,12 +466,11 @@ set(WANT_PNG ${USE_PNG})

set(WANT_SQLITE3 ${USE_SQLITE3}) set(USE_CMOCKA ${BUILD_SUITE}) -find_feature(USE_FFMPEG "libavcodec;libavformat;libavutil;libswscale") +find_feature(USE_FFMPEG "libavcodec;libavfilter;libavformat;libavutil;libswscale") find_feature(USE_ZLIB "ZLIB") find_feature(USE_MINIZIP "minizip") find_feature(USE_PNG "PNG") find_feature(USE_LIBZIP "libzip") -find_feature(USE_MAGICK "MagickWand") find_feature(USE_EPOXY "epoxy") find_feature(USE_CMOCKA "cmocka") find_feature(USE_SQLITE3 "sqlite3")

@@ -531,15 +529,20 @@ else()

list(APPEND FEATURES LIBAVRESAMPLE) list(APPEND FEATURES LIBAV) endif() - include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWRESAMPLE_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS}) - link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWRESAMPLE_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS}) + include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFILTER_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWRESAMPLE_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS}) + link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFILTER_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]+" LIBAVFILTER_VERSION_MAJOR ${libavfilter_VERSION}) string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION}) string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION}) string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION}) - list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES}) + list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFILTER_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES}) + if(WIN32) + list(APPEND DEPENDENCY_LIB bcrypt) + endif() 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},libavfilter${LIBAVFILTER_VERSION_MAJOR}|libavfilter-ffmpeg${LIBAVFILTER_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})

@@ -559,29 +562,6 @@ endif()

list(APPEND THIRD_PARTY_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c") -if(USE_MAGICK) - list(APPEND FEATURES MAGICK) - include_directories(AFTER ${MAGICKWAND_INCLUDE_DIRS}) - link_directories(${MAGICKWAND_LIBRARY_DIRS}) - list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/imagemagick/imagemagick-gif-encoder.c") - list(APPEND DEPENDENCY_LIB ${MAGICKWAND_LIBRARIES}) - string(REGEX MATCH "^[0-9]+\\.[0-9]+" MAGICKWAND_VERSION_PARTIAL ${MagickWand_VERSION}) - string(REGEX MATCH "^[0-9]+" MAGICKWAND_VERSION_MAJOR ${MagickWand_VERSION}) - if(${MAGICKWAND_VERSION_PARTIAL} STREQUAL "6.7") - set(MAGICKWAND_DEB_VERSION "5") - elseif(${MagickWand_VERSION} STREQUAL "6.9.10") - set(MAGICKWAND_DEB_VERSION "-6.q16-6") - elseif(${MagickWand_VERSION} STREQUAL "6.9.7") - set(MAGICKWAND_DEB_VERSION "-6.q16-3") - else() - set(MAGICKWAND_DEB_VERSION "-6.q16-2") - endif() - list(APPEND FEATURE_DEFINES MAGICKWAND_VERSION_MAJOR=${MAGICKWAND_VERSION_MAJOR}) - list(APPEND FEATURE_FLAGS ${MAGICKWAND_CFLAGS_OTHER}) - - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libmagickwand${MAGICKWAND_DEB_VERSION}") -endif() - if(WANT_ZLIB AND NOT USE_ZLIB) set(SKIP_INSTALL_ALL ON) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib zlib EXCLUDE_FROM_ALL)

@@ -1142,9 +1122,10 @@ 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) + add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}>" "$<TARGET_FILE:${BINARY_NAME}>.debug") + add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:${BINARY_NAME}>") + add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$<TARGET_FILE:${BINARY_NAME}>.debug" "$<TARGET_FILE:${BINARY_NAME}>") + install(FILES "$<TARGET_FILE:${BINARY_NAME}>.debug" DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}-dbg) endif() endif() if(APPLE)

@@ -1265,8 +1246,7 @@ if(NOT WIN32)

message(STATUS " CLI debugger: ${USE_EDITLINE}") endif() message(STATUS " GDB stub: ${USE_GDB_STUB}") - message(STATUS " Video recording: ${USE_FFMPEG}") - message(STATUS " GIF recording: ${USE_MAGICK}") + message(STATUS " GIF/Video recording: ${USE_FFMPEG}") message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}") message(STATUS " ZIP support: ${SUMMARY_ZIP}") message(STATUS " 7-Zip support: ${USE_LZMA}")
M README.mdREADME.md

@@ -163,7 +163,7 @@ This will build and install medusa into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them.

If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are: - brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit pkg-config + brew install cmake ffmpeg libzip qt5 sdl2 libedit pkg-config mkdir build cd build cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..

@@ -177,11 +177,11 @@ To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("MSYS2 MinGW 32-bit") (or the 64-bit version "MSYS2 MinGW 64-bit" if you want to build for x86_64) and run this additional command (including the braces) to install the needed dependencies (please note that this involves downloading over 1100MiB of packages, so it will take a long time):

For x86 (32 bit) builds: - pacman -Sy --needed base-devel git mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git} + pacman -Sy --needed base-devel git mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git} For x86_64 (64 bit) builds: - pacman -Sy --needed base-devel git mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git} + pacman -Sy --needed base-devel git mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git} Check out the source code by running this command:
M src/core/flags.h.insrc/core/flags.h.in

@@ -99,10 +99,6 @@ #ifndef USE_LZMA

#cmakedefine USE_LZMA #endif -#ifndef USE_MAGICK -#cmakedefine USE_MAGICK -#endif - #ifndef USE_MINIZIP #cmakedefine USE_MINIZIP #endif
M src/feature/ffmpeg/ffmpeg-encoder.csrc/feature/ffmpeg/ffmpeg-encoder.c

@@ -11,6 +11,9 @@

#include <libavcodec/version.h> #include <libavcodec/avcodec.h> +#include <libavfilter/buffersink.h> +#include <libavfilter/buffersrc.h> + #include <libavutil/version.h> #if LIBAVUTIL_VERSION_MAJOR >= 53 #include <libavutil/buffer.h>

@@ -31,12 +34,17 @@ static void _ffmpegPostAudioFrame(struct mAVStream*, int16_t left, int16_t right);

static void _ffmpegSetVideoDimensions(struct mAVStream*, unsigned width, unsigned height); static void _ffmpegSetVideoFrameRate(struct mAVStream*, unsigned numerator, unsigned denominator); +static bool _ffmpegWriteAudioFrame(struct FFmpegEncoder* encoder, struct AVFrame* audioFrame); +static bool _ffmpegWriteVideoFrame(struct FFmpegEncoder* encoder, struct AVFrame* videoFrame); + enum { PREFERRED_SAMPLE_RATE = 0x8000 }; void FFmpegEncoderInit(struct FFmpegEncoder* encoder) { +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) av_register_all(); +#endif encoder->d.videoDimensionsChanged = _ffmpegSetVideoDimensions; encoder->d.postVideoFrame = _ffmpegPostVideoFrame;

@@ -48,13 +56,29 @@ encoder->audioCodec = NULL;

encoder->videoCodec = NULL; encoder->containerFormat = NULL; FFmpegEncoderSetAudio(encoder, "flac", 0); - FFmpegEncoderSetVideo(encoder, "png", 0); + FFmpegEncoderSetVideo(encoder, "libx264", 0, 0); FFmpegEncoderSetContainer(encoder, "matroska"); FFmpegEncoderSetDimensions(encoder, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS; encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS; encoder->frameCycles = VIDEO_TOTAL_LENGTH; encoder->cycles = GBA_ARM7TDMI_FREQUENCY; + encoder->frameskip = 1; + encoder->skipResidue = 0; + encoder->ipixFormat = +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + AV_PIX_FMT_RGB565; +#else + AV_PIX_FMT_BGR555; +#endif +#else +#ifndef USE_LIBAV + AV_PIX_FMT_0BGR32; +#else + AV_PIX_FMT_BGR32; +#endif +#endif encoder->resampleContext = NULL; encoder->absf = NULL; encoder->context = NULL;

@@ -67,6 +91,15 @@ encoder->postaudioBuffer = NULL;

encoder->video = NULL; encoder->videoStream = NULL; encoder->videoFrame = NULL; + encoder->graph = NULL; + encoder->source = NULL; + encoder->sink = NULL; + encoder->sinkFrame = NULL; + + int i; + for (i = 0; i < FFMPEG_FILTERS_MAX; ++i) { + encoder->filters[i] = NULL; + } } bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, unsigned abr) {

@@ -131,7 +164,7 @@ encoder->audioBitrate = abr;

return true; } -bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, unsigned vbr) { +bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, unsigned vbr, int frameskip) { static const struct { enum AVPixelFormat format; int priority;

@@ -150,7 +183,8 @@ { AV_PIX_FMT_0RGB, 3 },

#endif { AV_PIX_FMT_YUV422P, 4 }, { AV_PIX_FMT_YUV444P, 5 }, - { AV_PIX_FMT_YUV420P, 6 } + { AV_PIX_FMT_YUV420P, 6 }, + { AV_PIX_FMT_PAL8, 7 }, }; if (!vcodec) {

@@ -180,6 +214,7 @@ return false;

} encoder->videoCodec = vcodec; encoder->videoBitrate = vbr; + encoder->frameskip = frameskip + 1; return true; }

@@ -227,6 +262,7 @@

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

@@ -326,8 +362,8 @@ #endif

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

@@ -366,6 +402,54 @@ av_opt_set(encoder->video->priv_data, "lossless", "1", 0);

encoder->video->pix_fmt = AV_PIX_FMT_YUV444P; } + if (encoder->pixFormat == AV_PIX_FMT_PAL8) { + encoder->graph = avfilter_graph_alloc(); + + const struct AVFilter* source = avfilter_get_by_name("buffer"); + const struct AVFilter* sink = avfilter_get_by_name("buffersink"); + const struct AVFilter* split = avfilter_get_by_name("split"); + const struct AVFilter* palettegen = avfilter_get_by_name("palettegen"); + const struct AVFilter* paletteuse = avfilter_get_by_name("paletteuse"); + + if (!source || !sink || !split || !palettegen || !paletteuse || !encoder->graph) { + FFmpegEncoderClose(encoder); + return false; + } + + char args[256]; + snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d", + encoder->video->width, encoder->video->height, encoder->ipixFormat, + encoder->video->time_base.num, encoder->video->time_base.den); + + int res = 0; + res |= avfilter_graph_create_filter(&encoder->source, source, NULL, args, NULL, encoder->graph); + res |= avfilter_graph_create_filter(&encoder->sink, sink, NULL, NULL, NULL, encoder->graph); + res |= avfilter_graph_create_filter(&encoder->filters[0], split, NULL, NULL, NULL, encoder->graph); + res |= avfilter_graph_create_filter(&encoder->filters[1], palettegen, NULL, "reserve_transparent=off", NULL, encoder->graph); + res |= avfilter_graph_create_filter(&encoder->filters[2], paletteuse, NULL, "dither=none", NULL, encoder->graph); + if (res < 0) { + FFmpegEncoderClose(encoder); + return false; + } + + res = 0; + res |= avfilter_link(encoder->source, 0, encoder->filters[0], 0); + res |= avfilter_link(encoder->filters[0], 0, encoder->filters[1], 0); + res |= avfilter_link(encoder->filters[0], 1, encoder->filters[2], 0); + res |= avfilter_link(encoder->filters[1], 0, encoder->filters[2], 1); + res |= avfilter_link(encoder->filters[2], 0, encoder->sink, 0); + if (res < 0 || avfilter_graph_config(encoder->graph, NULL) < 0) { + FFmpegEncoderClose(encoder); + return false; + } + +#if LIBAVCODEC_VERSION_MAJOR >= 55 + encoder->sinkFrame = av_frame_alloc(); +#else + encoder->sinkFrame = avcodec_alloc_frame(); +#endif + } + if (avcodec_open2(encoder->video, vcodec, 0) < 0) { FFmpegEncoderClose(encoder); return false;

@@ -375,12 +459,12 @@ encoder->videoFrame = av_frame_alloc();

#else encoder->videoFrame = avcodec_alloc_frame(); #endif - encoder->videoFrame->format = encoder->video->pix_fmt; + encoder->videoFrame->format = encoder->video->pix_fmt != AV_PIX_FMT_PAL8 ? encoder->video->pix_fmt : encoder->ipixFormat; encoder->videoFrame->width = encoder->video->width; encoder->videoFrame->height = encoder->video->height; encoder->videoFrame->pts = 0; _ffmpegSetVideoDimensions(&encoder->d, encoder->iwidth, encoder->iheight); - av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->video->width, encoder->video->height, encoder->video->pix_fmt, 32); + av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->videoFrame->width, encoder->videoFrame->height, encoder->videoFrame->format, 32); #ifdef FFMPEG_USE_CODECPAR avcodec_parameters_from_context(encoder->videoStream->codecpar, encoder->video); #endif

@@ -394,6 +478,33 @@ return true;

} void FFmpegEncoderClose(struct FFmpegEncoder* encoder) { + if (encoder->audio) { + while (true) { + if (!_ffmpegWriteAudioFrame(encoder, NULL)) { + break; + } + } + } + if (encoder->video) { + if (encoder->graph) { + if (av_buffersrc_add_frame(encoder->source, NULL) >= 0) { + while (true) { + int res = av_buffersink_get_frame(encoder->sink, encoder->sinkFrame); + if (res < 0) { + break; + } + _ffmpegWriteVideoFrame(encoder, encoder->sinkFrame); + av_frame_unref(encoder->sinkFrame); + } + } + } + while (true) { + if (!_ffmpegWriteVideoFrame(encoder, NULL)) { + break; + } + } + } + if (encoder->context && encoder->context->pb) { av_write_trailer(encoder->context); avio_close(encoder->context->pb);

@@ -444,6 +555,15 @@ av_frame_free(&encoder->videoFrame);

#else avcodec_free_frame(&encoder->videoFrame); #endif + } + + if (encoder->sinkFrame) { +#if LIBAVCODEC_VERSION_MAJOR >= 55 + av_frame_free(&encoder->sinkFrame); +#else + avcodec_free_frame(&encoder->sinkFrame); +#endif + encoder->sinkFrame = NULL; } if (encoder->video) {

@@ -456,6 +576,18 @@ sws_freeContext(encoder->scaleContext);

encoder->scaleContext = NULL; } + if (encoder->graph) { + avfilter_graph_free(&encoder->graph); + encoder->graph = NULL; + encoder->source = NULL; + encoder->sink = NULL; + + int i; + for (i = 0; i < FFMPEG_FILTERS_MAX; ++i) { + encoder->filters[i] = NULL; + } + } + if (encoder->context) { avformat_free_context(encoder->context); encoder->context = NULL;

@@ -514,6 +646,10 @@

encoder->audioFrame->pts = encoder->currentAudioFrame; encoder->currentAudioFrame += samples; + _ffmpegWriteAudioFrame(encoder, encoder->audioFrame); +} + +bool _ffmpegWriteAudioFrame(struct FFmpegEncoder* encoder, struct AVFrame* audioFrame) { AVPacket packet; av_init_packet(&packet); packet.data = 0;

@@ -521,13 +657,13 @@ packet.size = 0;

int gotData; #ifdef FFMPEG_USE_PACKETS - avcodec_send_frame(encoder->audio, encoder->audioFrame); + avcodec_send_frame(encoder->audio, audioFrame); gotData = avcodec_receive_packet(encoder->audio, &packet); gotData = (gotData == 0) && packet.size; #else - avcodec_encode_audio2(encoder->audio, &packet, encoder->audioFrame, &gotData); + avcodec_encode_audio2(encoder->audio, &packet, audioFrame, &gotData); #endif - packet.pts = av_rescale_q(encoder->audioFrame->pts, encoder->audio->time_base, encoder->audioStream->time_base); + packet.pts = av_rescale_q(packet.pts, encoder->audio->time_base, encoder->audioStream->time_base); packet.dts = packet.pts; if (gotData) {

@@ -570,20 +706,20 @@ av_packet_unref(&packet);

#else av_free_packet(&packet); #endif + return gotData; } void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size_t stride) { struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream; if (!encoder->context || !encoder->videoCodec) { + return; + } + encoder->skipResidue = (encoder->skipResidue + 1) % encoder->frameskip; + if (encoder->skipResidue) { return; } stride *= BYTES_PER_PIXEL; - AVPacket packet; - - av_init_packet(&packet); - packet.data = 0; - packet.size = 0; #if LIBAVCODEC_VERSION_MAJOR >= 55 av_frame_make_writable(encoder->videoFrame); #endif

@@ -592,14 +728,37 @@ ++encoder->currentVideoFrame;

sws_scale(encoder->scaleContext, (const uint8_t* const*) &pixels, (const int*) &stride, 0, encoder->iheight, encoder->videoFrame->data, encoder->videoFrame->linesize); + if (encoder->graph) { + if (av_buffersrc_add_frame(encoder->source, encoder->videoFrame) < 0) { + return; + } + while (true) { + int res = av_buffersink_get_frame(encoder->sink, encoder->sinkFrame); + if (res < 0) { + break; + } + _ffmpegWriteVideoFrame(encoder, encoder->sinkFrame); + av_frame_unref(encoder->sinkFrame); + } + } else { + _ffmpegWriteVideoFrame(encoder, encoder->videoFrame); + } +} + +bool _ffmpegWriteVideoFrame(struct FFmpegEncoder* encoder, struct AVFrame* videoFrame) { + AVPacket packet; + + av_init_packet(&packet); + packet.data = 0; + packet.size = 0; + int gotData; #ifdef FFMPEG_USE_PACKETS - avcodec_send_frame(encoder->video, encoder->videoFrame); + avcodec_send_frame(encoder->video, videoFrame); gotData = avcodec_receive_packet(encoder->video, &packet) == 0; #else - avcodec_encode_video2(encoder->video, &packet, encoder->videoFrame, &gotData); + avcodec_encode_video2(encoder->video, &packet, videoFrame, &gotData); #endif - packet.pts = encoder->videoFrame->pts; if (gotData) { #ifndef FFMPEG_USE_PACKET_UNREF if (encoder->video->coded_frame->key_frame) {

@@ -614,6 +773,8 @@ av_packet_unref(&packet);

#else av_free_packet(&packet); #endif + + return gotData; } static void _ffmpegSetVideoDimensions(struct mAVStream* stream, unsigned width, unsigned height) {

@@ -626,21 +787,8 @@ encoder->iheight = height;

if (encoder->scaleContext) { sws_freeContext(encoder->scaleContext); } - encoder->scaleContext = sws_getContext(encoder->iwidth, encoder->iheight, -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - AV_PIX_FMT_RGB565, -#else - AV_PIX_FMT_BGR555, -#endif -#else -#ifndef USE_LIBAV - AV_PIX_FMT_0BGR32, -#else - AV_PIX_FMT_BGR32, -#endif -#endif - encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt, + encoder->scaleContext = sws_getContext(encoder->iwidth, encoder->iheight, encoder->ipixFormat, + encoder->videoFrame->width, encoder->videoFrame->height, encoder->videoFrame->format, SWS_POINT, 0, 0, 0); }
M src/feature/ffmpeg/ffmpeg-encoder.hsrc/feature/ffmpeg/ffmpeg-encoder.h

@@ -34,6 +34,8 @@ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 8, 0)

#define FFMPEG_USE_PACKET_UNREF #endif +#define FFMPEG_FILTERS_MAX 4 + struct FFmpegEncoder { struct mAVStream d; struct AVFormatContext* context;

@@ -70,6 +72,7 @@ struct AVStream* audioStream;

struct AVCodecContext* video; enum AVPixelFormat pixFormat; + enum AVPixelFormat ipixFormat; struct AVFrame* videoFrame; int width; int height;

@@ -77,14 +80,22 @@ int iwidth;

int iheight; unsigned frameCycles; unsigned cycles; + int frameskip; + int skipResidue; int64_t currentVideoFrame; struct SwsContext* scaleContext; struct AVStream* videoStream; + + struct AVFilterGraph* graph; + struct AVFilterContext* source; + struct AVFilterContext* sink; + struct AVFilterContext* filters[FFMPEG_FILTERS_MAX]; + struct AVFrame* sinkFrame; }; void FFmpegEncoderInit(struct FFmpegEncoder*); bool FFmpegEncoderSetAudio(struct FFmpegEncoder*, const char* acodec, unsigned abr); -bool FFmpegEncoderSetVideo(struct FFmpegEncoder*, const char* vcodec, unsigned vbr); +bool FFmpegEncoderSetVideo(struct FFmpegEncoder*, const char* vcodec, unsigned vbr, int frameskip); bool FFmpegEncoderSetContainer(struct FFmpegEncoder*, const char* container); void FFmpegEncoderSetDimensions(struct FFmpegEncoder*, int width, int height); bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder*);
D src/feature/imagemagick/imagemagick-gif-encoder.c

@@ -1,121 +0,0 @@

-/* Copyright (c) 2013-2015 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 "imagemagick-gif-encoder.h" - -#include <mgba/internal/gba/gba.h> -#include <mgba/gba/interface.h> -#include <mgba-util/string.h> - -static void _magickPostVideoFrame(struct mAVStream*, const color_t* pixels, size_t stride); -static void _magickVideoDimensionsChanged(struct mAVStream*, unsigned width, unsigned height); -static void _magickVideoFrameRateChanged(struct mAVStream*, unsigned numerator, unsigned denominator); - -void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) { - encoder->wand = 0; - - encoder->d.videoDimensionsChanged = _magickVideoDimensionsChanged; - encoder->d.postVideoFrame = _magickPostVideoFrame; - encoder->d.postAudioFrame = 0; - encoder->d.postAudioBuffer = 0; - encoder->d.videoFrameRateChanged = _magickVideoFrameRateChanged; - - encoder->frameskip = 2; - encoder->delayMs = -1; - - encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS; - encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS; - encoder->numerator = VIDEO_TOTAL_LENGTH; - encoder->denominator = GBA_ARM7TDMI_FREQUENCY; -} - -void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs) { - if (ImageMagickGIFEncoderIsOpen(encoder)) { - return; - } - encoder->frameskip = frameskip; - encoder->delayMs = delayMs; -} - -bool ImageMagickGIFEncoderOpen(struct ImageMagickGIFEncoder* encoder, const char* outfile) { - MagickWandGenesis(); - encoder->wand = NewMagickWand(); - MagickSetImageFormat(encoder->wand, "GIF"); - MagickSetImageDispose(encoder->wand, PreviousDispose); - encoder->outfile = strdup(outfile); - encoder->frame = malloc(encoder->iwidth * encoder->iheight * 4); - encoder->currentFrame = 0; - return true; -} - -bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder* encoder) { - if (!encoder->wand) { - return false; - } - - MagickBooleanType success = MagickWriteImages(encoder->wand, encoder->outfile, MagickTrue); - DestroyMagickWand(encoder->wand); - encoder->wand = 0; - free(encoder->outfile); - free(encoder->frame); - MagickWandTerminus(); - return success == MagickTrue; -} - -bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder* encoder) { - return !!encoder->wand; -} - -static void _magickPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size_t stride) { - struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream; - - if (encoder->currentFrame % (encoder->frameskip + 1)) { - ++encoder->currentFrame; - return; - } - - const uint8_t* p8 = (const uint8_t*) pixels; - size_t row; - for (row = 0; row < encoder->iheight; ++row) { - memcpy(&encoder->frame[row * encoder->iwidth], &p8[row * 4 * stride], encoder->iwidth * 4); - } - - MagickConstituteImage(encoder->wand, encoder->iwidth, encoder->iheight, "RGBP", CharPixel, encoder->frame); - uint64_t ts = encoder->currentFrame; - uint64_t nts = encoder->currentFrame + encoder->frameskip + 1; - if (encoder->delayMs >= 0) { - ts *= encoder->delayMs; - nts *= encoder->delayMs; - ts /= 10; - nts /= 10; - } else { - ts *= encoder->numerator * 100; - nts *= encoder->numerator * 100; - ts /= encoder->denominator; - nts /= encoder->denominator; - } - MagickSetImageDelay(encoder->wand, nts - ts); - ++encoder->currentFrame; -} - -static void _magickVideoDimensionsChanged(struct mAVStream* stream, unsigned width, unsigned height) { - struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream; - if (encoder->iwidth == width && encoder->iheight == height) { - return; - } - if (width * height > encoder->iwidth * encoder->iheight) { - free(encoder->frame); - encoder->frame = malloc(width * height * 4); - } - encoder->iwidth = width; - encoder->iheight = height; - encoder->frame = malloc(encoder->iwidth * encoder->iheight * 4); -} - -static void _magickVideoFrameRateChanged(struct mAVStream* stream, unsigned numerator, unsigned denominator) { - struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream; - encoder->numerator = numerator; - encoder->denominator = denominator; -}
D src/feature/imagemagick/imagemagick-gif-encoder.h

@@ -1,46 +0,0 @@

-/* Copyright (c) 2013-2015 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 IMAGEMAGICK_GIF_ENCODER -#define IMAGEMAGICK_GIF_ENCODER - -#include <mgba-util/common.h> - -CXX_GUARD_START - -#include <mgba/core/interface.h> - -#if MAGICKWAND_VERSION_MAJOR >= 7 -#include <MagickWand/MagickWand.h> -#else -#include <wand/MagickWand.h> -#endif - -struct ImageMagickGIFEncoder { - struct mAVStream d; - MagickWand* wand; - char* outfile; - uint32_t* frame; - - unsigned currentFrame; - int frameskip; - int delayMs; - - unsigned iwidth; - unsigned iheight; - - unsigned numerator; - unsigned denominator; -}; - -void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder*); -void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs); -bool ImageMagickGIFEncoderOpen(struct ImageMagickGIFEncoder*, const char* outfile); -bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder*); -bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder*); - -CXX_GUARD_END - -#endif
M src/gb/audio.csrc/gb/audio.c

@@ -37,7 +37,7 @@

static void _updateSquareSample(struct GBAudioSquareChannel* ch); static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch); -static int8_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch); +static int16_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);

@@ -632,8 +632,11 @@ sampleRight += audio->ch3.sample;

} } + sampleLeft <<= 3; + sampleRight <<= 3; + if (!audio->forceDisableCh[3]) { - int8_t sample = _coalesceNoiseChannel(&audio->ch4); + int16_t sample = audio->style == GB_AUDIO_GBA ? (audio->ch4.sample << 3) : _coalesceNoiseChannel(&audio->ch4); if (audio->ch4Left) { sampleLeft += sample; }

@@ -642,9 +645,6 @@ if (audio->ch4Right) {

sampleRight += sample; } } - - sampleLeft <<= 3; - sampleRight <<= 3; *left = sampleLeft * (1 + audio->volumeLeft); *right = sampleRight * (1 + audio->volumeRight);

@@ -768,12 +768,12 @@ return period * 4;

} } -static int8_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) { +static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) { if (!ch->nSamples) { return ch->sample; } // TODO keep track of timing - int8_t sample = ch->samples / ch->nSamples; + int16_t sample = (ch->samples << 3) / ch->nSamples; ch->nSamples = 0; ch->samples = 0; return sample;
M src/platform/opengl/gl.csrc/platform/opengl/gl.c

@@ -90,8 +90,12 @@ drawH = w * v->height / v->width;

} } if (v->lockIntegerScaling) { - drawW -= drawW % v->width; - drawH -= drawH % v->height; + if (drawW >= v->width) { + drawW -= drawW % v->width; + } + if (drawH >= v->height) { + drawH -= drawH % v->height; + } } glMatrixMode(GL_MODELVIEW); glLoadIdentity();
M src/platform/opengl/gles2.csrc/platform/opengl/gles2.c

@@ -201,8 +201,12 @@ drawH = w * v->height / v->width;

} } if (v->lockIntegerScaling) { - drawW -= drawW % v->width; - drawH -= drawH % v->height; + if (drawW >= v->width) { + drawW -= drawW % v->width; + } + if (drawH >= v->height) { + drawH -= drawH % v->height; + } } glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -368,8 +368,9 @@ 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) + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}-qt>" "$<TARGET_FILE:${BINARY_NAME}-qt>.debug") + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:${BINARY_NAME}-qt>") + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$<TARGET_FILE:${BINARY_NAME}-qt>.debug" "$<TARGET_FILE:${BINARY_NAME}-qt>") + install(FILES "$<TARGET_FILE:${BINARY_NAME}-qt>.debug" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt-dbg) endif() endif()
M src/platform/qt/CoreController.cppsrc/platform/qt/CoreController.cpp

@@ -221,12 +221,13 @@ Interrupter interrupter(this);

const void* pixels; m_threadContext.core->getPixels(m_threadContext.core, &pixels, &stride); stride *= BYTES_PER_PIXEL; - buffer.resize(stride * size.height()); - memcpy(buffer.data(), pixels, buffer.size()); + buffer = QByteArray::fromRawData(static_cast<const char*>(pixels), stride * size.height()); } - return QImage(reinterpret_cast<const uchar*>(buffer.constData()), - size.width(), size.height(), stride, QImage::Format_RGBX8888); + QImage image(reinterpret_cast<const uchar*>(buffer.constData()), + size.width(), size.height(), stride, QImage::Format_RGBX8888); + image.bits(); // Cause QImage to detach + return image; } bool CoreController::isPaused() {
M src/platform/qt/DisplayGL.cppsrc/platform/qt/DisplayGL.cpp

@@ -12,7 +12,9 @@

#include <QApplication> #include <QOpenGLContext> #include <QOpenGLPaintDevice> +#include <QMutexLocker> #include <QResizeEvent> +#include <QScreen> #include <QTimer> #include <QWindow>

@@ -119,6 +121,7 @@ #else

messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); #endif resizePainter(); + setUpdatesEnabled(false); } void DisplayGL::stopDrawing() {

@@ -127,12 +130,14 @@ m_isDrawing = false;

CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection); m_drawThread->exit(); + m_drawThread->wait(); m_drawThread = nullptr; m_gl->makeCurrent(windowHandle()); #if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent(); #endif + setUpdatesEnabled(true); } m_context.reset(); }

@@ -142,6 +147,7 @@ if (m_drawThread) {

m_isDrawing = false; CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection); + setUpdatesEnabled(true); } }

@@ -150,6 +156,7 @@ if (m_drawThread) {

m_isDrawing = true; CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection); + setUpdatesEnabled(false); } }

@@ -276,9 +283,14 @@ }

#endif m_backend->swap = [](VideoBackend* v) { PainterGL* painter = static_cast<PainterGL*>(v->user); - if (!painter->m_swapTimer.isActive()) { - QMetaObject::invokeMethod(&painter->m_swapTimer, "start"); + if (!painter->m_gl->isValid()) { + return; } + painter->m_gl->swapBuffers(painter->m_surface); + painter->m_gl->makeCurrent(painter->m_surface); +#if defined(_WIN32) && defined(USE_EPOXY) + epoxy_handle_external_wglMakeCurrent(); +#endif }; m_backend->init(m_backend, 0);

@@ -296,10 +308,6 @@

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() {

@@ -388,32 +396,35 @@ m_started = true;

} void PainterGL::draw() { - if (m_queue.isEmpty()) { + if (!m_active || m_queue.isEmpty()) { return; } - - if (m_needsUnlock) { - QTimer::singleShot(0, this, &PainterGL::draw); - return; + mCoreSync* sync = &m_context->thread()->impl->sync; + mCoreSyncWaitFrameStart(sync); + dequeue(); + if (!m_delayTimer.isValid()) { + m_delayTimer.start(); + } else if (sync->audioWait || sync->videoFrameWait) { + while (m_delayTimer.nsecsElapsed() + 2000000 < 1000000000 / sync->fpsTarget) { + QThread::usleep(500); + } + m_delayTimer.restart(); } + mCoreSyncWaitFrameEnd(sync); - if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) { - dequeue(); - forceDraw(); - if (m_context->thread()->impl->sync.videoFrameWait) { - m_needsUnlock = true; - } else { - mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); - } - } else { - mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); - } + forceDraw(); } void PainterGL::forceDraw() { m_painter.begin(m_window); performDraw(); m_painter.end(); + if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) { + if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) { + return; + } + m_delayTimer.restart(); + } m_backend->swap(m_backend); }

@@ -423,10 +434,6 @@ m_started = false;

dequeueAll(); m_backend->clear(m_backend); m_backend->swap(m_backend); - if (m_swapTimer.isActive()) { - swap(); - m_swapTimer.stop(); - } if (m_videoProxy) { m_videoProxy->reset(); }

@@ -456,28 +463,6 @@ m_painter.endNativePainting();

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); - } } void PainterGL::enqueue(const uint32_t* backing) {

@@ -534,6 +519,12 @@ if (!supportsShaders()) {

return; } #ifdef BUILD_GLES2 + if (!m_started) { + 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);

@@ -541,6 +532,8 @@ }

mGLES2ShaderLoad(&m_shader, dir); if (m_started) { mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses); + } else { + m_gl->doneCurrent(); } #endif }
M src/platform/qt/DisplayGL.hsrc/platform/qt/DisplayGL.h

@@ -16,6 +16,7 @@ #define GLdouble GLdouble

#endif #endif +#include <QElapsedTimer> #include <QOpenGLContext> #include <QList> #include <QMouseEvent>

@@ -108,9 +109,6 @@ VideoShader* shaders();

int glTex(); -private slots: - void swap(); - private: void performDraw(); void dequeue();

@@ -131,9 +129,7 @@ VideoShader m_shader{};

VideoBackend* m_backend = nullptr; QSize m_size; MessagePainter* m_messagePainter = nullptr; - QTimer m_swapTimer{this}; - bool m_needsUnlock = false; - bool m_frameReady = false; + QElapsedTimer m_delayTimer; std::shared_ptr<VideoProxy> m_videoProxy; };
M src/platform/qt/DisplayQt.cppsrc/platform/qt/DisplayQt.cpp

@@ -99,6 +99,21 @@ painter.setRenderHint(QPainter::SmoothPixmapTransform);

} QSize s = size(); QSize ds = viewportSize(); + if (isAspectRatioLocked()) { + if (s.width() * m_height > s.height() * m_width) { + ds.setWidth(s.height() * m_width / m_height); + } else if (s.width() * m_height < s.height() * m_width) { + ds.setHeight(s.width() * m_height / m_width); + } + } + if (isIntegerScalingLocked()) { + if (ds.width() >= m_width) { + ds.setWidth(ds.width() - ds.width() % m_width); + } + if (ds.height() >= m_height) { + ds.setHeight(ds.height() - ds.height() % m_height); + } + } QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); QRect full(origin, ds);
M src/platform/qt/GIFView.cppsrc/platform/qt/GIFView.cpp

@@ -5,16 +5,13 @@ * 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 "GIFView.h" -#ifdef USE_MAGICK +#ifdef USE_FFMPEG #include "CoreController.h" #include "GBAApp.h" #include "LogController.h" #include <QMap> - -#include <mgba/internal/gba/gba.h> -#include <mgba/internal/gba/video.h> using namespace QGBA;

@@ -29,11 +26,9 @@

connect(m_ui.selectFile, &QAbstractButton::clicked, this, &GIFView::selectFile); connect(m_ui.filename, &QLineEdit::textChanged, this, &GIFView::setFilename); - connect(m_ui.frameskip, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), - this, &GIFView::updateDelay); - connect(m_ui.delayAuto, &QAbstractButton::clicked, this, &GIFView::updateDelay); - - ImageMagickGIFEncoderInit(&m_encoder); + FFmpegEncoderInit(&m_encoder); + FFmpegEncoderSetAudio(&m_encoder, nullptr, 0); + FFmpegEncoderSetContainer(&m_encoder, "gif"); } GIFView::~GIFView() {

@@ -44,34 +39,35 @@ void GIFView::setController(std::shared_ptr<CoreController> controller) {

connect(controller.get(), &CoreController::stopping, this, &GIFView::stopRecording); connect(this, &GIFView::recordingStarted, controller.get(), &CoreController::setAVStream); connect(this, &GIFView::recordingStopped, controller.get(), &CoreController::clearAVStream, Qt::DirectConnection); + QSize size(controller->screenDimensions()); + FFmpegEncoderSetDimensions(&m_encoder, size.width(), size.height()); } void GIFView::startRecording() { - int delayMs = m_ui.delayAuto->isChecked() ? -1 : m_ui.delayMs->value(); - ImageMagickGIFEncoderSetParams(&m_encoder, m_ui.frameskip->value(), delayMs); - if (!ImageMagickGIFEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) { + FFmpegEncoderSetVideo(&m_encoder, "gif", 0, m_ui.frameskip->value()); + if (!FFmpegEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) { LOG(QT, ERROR) << tr("Failed to open output GIF file: %1").arg(m_filename); return; } m_ui.start->setEnabled(false); m_ui.stop->setEnabled(true); - m_ui.groupBox->setEnabled(false); + m_ui.frameskip->setEnabled(false); emit recordingStarted(&m_encoder.d); } void GIFView::stopRecording() { emit recordingStopped(); - ImageMagickGIFEncoderClose(&m_encoder); + FFmpegEncoderClose(&m_encoder); m_ui.stop->setEnabled(false); m_ui.start->setEnabled(true); - m_ui.groupBox->setEnabled(true); + m_ui.frameskip->setEnabled(true); } void GIFView::selectFile() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file"), tr("Graphics Interchange Format (*.gif)")); if (!filename.isEmpty()) { m_ui.filename->setText(filename); - if (!ImageMagickGIFEncoderIsOpen(&m_encoder)) { + if (!FFmpegEncoderIsOpen(&m_encoder)) { m_ui.start->setEnabled(true); } }

@@ -79,17 +75,6 @@ }

void GIFView::setFilename(const QString& fname) { m_filename = fname; -} - -void GIFView::updateDelay() { - if (!m_ui.delayAuto->isChecked()) { - return; - } - - uint64_t s = (m_ui.frameskip->value() + 1); - s *= VIDEO_TOTAL_LENGTH * 1000; - s /= GBA_ARM7TDMI_FREQUENCY; - m_ui.delayMs->setValue(s); } #endif
M src/platform/qt/GIFView.hsrc/platform/qt/GIFView.h

@@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once -#ifdef USE_MAGICK +#ifdef USE_FFMPEG #include <QWidget>

@@ -13,7 +13,7 @@ #include <memory>

#include "ui_GIFView.h" -#include "feature/imagemagick/imagemagick-gif-encoder.h" +#include "feature/ffmpeg/ffmpeg-encoder.h" namespace QGBA {

@@ -41,12 +41,11 @@

private slots: void selectFile(); void setFilename(const QString&); - void updateDelay(); private: Ui::GIFView m_ui; - ImageMagickGIFEncoder m_encoder; + FFmpegEncoder m_encoder; QString m_filename; };
M src/platform/qt/GIFView.uisrc/platform/qt/GIFView.ui

@@ -6,18 +6,52 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>278</width> - <height>247</height> + <width>392</width> + <height>220</height> </rect> </property> <property name="windowTitle"> <string>Record GIF</string> </property> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QGridLayout" name="gridLayout_3"> <property name="sizeConstraint"> <enum>QLayout::SetFixedSize</enum> </property> - <item> + <item row="1" column="0"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Frameskip</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QSpinBox" name="frameskip"> + <property name="value"> + <number>2</number> + </property> + </widget> + </item> + <item row="2" column="0" colspan="3"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + <item row="0" column="0" colspan="3"> <layout class="QGridLayout" name="gridLayout"> <item row="1" column="0"> <widget class="QPushButton" name="start">

@@ -51,6 +85,19 @@ <string>Stop</string>

</property> </widget> </item> + <item row="1" column="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> <item row="1" column="3"> <widget class="QPushButton" name="selectFile"> <property name="sizePolicy">

@@ -74,81 +121,8 @@ </sizepolicy>

</property> </widget> </item> - <item row="1" column="2"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> </layout> </item> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string/> - </property> - <layout class="QFormLayout" name="formLayout_2"> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Frameskip</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QSpinBox" name="frameskip"> - <property name="value"> - <number>2</number> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Frame delay (ms)</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QCheckBox" name="delayAuto"> - <property name="text"> - <string>Automatic</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QSpinBox" name="delayMs"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="maximum"> - <number>5000</number> - </property> - <property name="value"> - <number>50</number> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Close</set> - </property> - </widget> - </item> </layout> </widget> <resources/>

@@ -166,22 +140,6 @@ </hint>

<hint type="destinationlabel"> <x>138</x> <y>123</y> - </hint> - </hints> - </connection> - <connection> - <sender>delayAuto</sender> - <signal>clicked(bool)</signal> - <receiver>delayMs</receiver> - <slot>setDisabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>202</x> - <y>177</y> - </hint> - <hint type="destinationlabel"> - <x>192</x> - <y>148</y> </hint> </hints> </connection>
M src/platform/qt/VideoView.cppsrc/platform/qt/VideoView.cpp

@@ -284,7 +284,7 @@ m_videoCodecCstr = nullptr;

} else { m_videoCodecCstr = strdup(m_videoCodec.toUtf8().constData()); } - if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) { + if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr, 0)) { free(m_videoCodecCstr); m_videoCodecCstr = nullptr; m_videoCodec = QString();

@@ -321,7 +321,7 @@ }

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

@@ -198,9 +198,6 @@ delete m_logView;

#ifdef USE_FFMPEG delete m_videoView; -#endif - -#ifdef USE_MAGICK delete m_gifView; #endif

@@ -556,9 +553,7 @@ connect(this, &Window::shutdown, m_videoView, &QWidget::close);

} m_videoView->show(); } -#endif -#ifdef USE_MAGICK void Window::openGIFWindow() { if (!m_gifView) { m_gifView = new GIFView();

@@ -1534,9 +1529,6 @@ #endif

#ifdef USE_FFMPEG addGameAction(tr("Record A/V..."), "recordOutput", this, &Window::openVideoWindow, "av"); -#endif - -#ifdef USE_MAGICK addGameAction(tr("Record GIF..."), "recordGIF", this, &Window::openGIFWindow, "av"); #endif

@@ -1914,13 +1906,11 @@ m_console->setController(m_controller);

} #endif -#ifdef USE_MAGICK +#ifdef USE_FFMPEG if (m_gifView) { m_gifView->setController(m_controller); } -#endif -#ifdef USE_FFMPEG if (m_videoView) { m_videoView->setController(m_controller); }

@@ -2015,8 +2005,12 @@ ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth);

} } if (m_lockIntegerScaling) { - ds.setWidth(ds.width() - ds.width() % m_aspectWidth); - ds.setHeight(ds.height() - ds.height() % m_aspectHeight); + if (ds.width() >= m_aspectWidth) { + ds.setWidth(ds.width() - ds.width() % m_aspectWidth); + } + if (ds.height() >= m_aspectHeight) { + ds.setHeight(ds.height() - ds.height() % m_aspectHeight); + } } QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); QRect full(origin, ds);
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -101,9 +101,6 @@ #endif

#ifdef USE_FFMPEG void openVideoWindow(); -#endif - -#ifdef USE_MAGICK void openGIFWindow(); #endif

@@ -222,9 +219,6 @@ FrameView* m_frameView = nullptr;

#ifdef USE_FFMPEG VideoView* m_videoView = nullptr; -#endif - -#ifdef USE_MAGICK GIFView* m_gifView = nullptr; #endif
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -125,8 +125,9 @@ 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) + add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}-sdl>" "$<TARGET_FILE:${BINARY_NAME}-sdl>.debug") + add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:${BINARY_NAME}-sdl>") + add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$<TARGET_FILE:${BINARY_NAME}-sdl>.debug" "$<TARGET_FILE:${BINARY_NAME}-sdl>") + install(FILES "$<TARGET_FILE:${BINARY_NAME}-sdl>.debug" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl-dbg) endif() endif()