From 1dc405db3829a6f5eaed21a034e5d0b7ed0c7f5a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Sep 2019 13:08:34 -0700 Subject: [PATCH 01/10] GB Audio: Channel 4 fixes (fixes #1265, closes #1289) --- CHANGES | 2 ++ src/gb/audio.c | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 06bae47db..eff4d6570 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,8 @@ Emulation fixes: - 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) diff --git a/src/gb/audio.c b/src/gb/audio.c index 666ab42b8..b36cc3776 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -37,7 +37,7 @@ static bool _updateSweep(struct GBAudioSquareChannel* sweep, bool initial); 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 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) { } } + 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; } @@ -643,9 +646,6 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) { } } - sampleLeft <<= 3; - sampleRight <<= 3; - *left = sampleLeft * (1 + audio->volumeLeft); *right = sampleRight * (1 + audio->volumeRight); } @@ -768,12 +768,12 @@ static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) { } } -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; From 0cc8046121def54e9fbb29ff28c137b4f3909841 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Sep 2019 22:59:00 -0700 Subject: [PATCH 02/10] Qt: Minor GL fixes --- src/platform/qt/DisplayGL.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 06ff72f11..7aec935f7 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -116,6 +116,7 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); #endif resizePainter(); + setUpdatesEnabled(false); } void DisplayGL::stopDrawing() { @@ -124,12 +125,14 @@ void DisplayGL::stopDrawing() { 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(); } @@ -139,6 +142,7 @@ void DisplayGL::pauseDrawing() { m_isDrawing = false; CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection); + setUpdatesEnabled(true); } } @@ -147,6 +151,7 @@ void DisplayGL::unpauseDrawing() { m_isDrawing = true; CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection); + setUpdatesEnabled(false); } } From 44c9be706097ee578331e264d70ba210ca9fdad5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 15 Sep 2019 13:27:43 -0700 Subject: [PATCH 03/10] Qt: Fix getPixels UAF --- src/platform/qt/CoreController.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 1a025ff8f..ed6020ff8 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -221,12 +221,13 @@ QImage CoreController::getPixels() { 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(pixels), stride * size.height()); } - return QImage(reinterpret_cast(buffer.constData()), - size.width(), size.height(), stride, QImage::Format_RGBX8888); + QImage image(reinterpret_cast(buffer.constData()), + size.width(), size.height(), stride, QImage::Format_RGBX8888); + image.bits(); // Cause QImage to detach + return image; } bool CoreController::isPaused() { From 3920c6191f2caa155b1e5ef67f4b5ce26e8a7093 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 16 Sep 2019 19:14:52 -0700 Subject: [PATCH 04/10] Qt: Improve GL sync (really) --- src/platform/qt/DisplayGL.cpp | 77 +++++++++++++---------------------- src/platform/qt/DisplayGL.h | 8 +--- 2 files changed, 31 insertions(+), 54 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 7aec935f7..175ddc042 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -12,7 +12,9 @@ #include #include #include +#include #include +#include #include #include @@ -278,9 +280,14 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion) #endif m_backend->swap = [](VideoBackend* v) { PainterGL* painter = static_cast(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); @@ -289,6 +296,7 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion) m_shader.preprocessShader = static_cast(&reinterpret_cast(m_backend)->initialShader); } #endif + m_gl->doneCurrent(); m_backend->user = this; m_backend->filter = false; @@ -298,10 +306,6 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion) for (int i = 0; i < 2; ++i) { m_free.append(new uint32_t[1024 * 2048]); } - - m_swapTimer.setInterval(16); - m_swapTimer.setSingleShot(true); - connect(&m_swapTimer, &QTimer::timeout, this, &PainterGL::swap); } PainterGL::~PainterGL() { @@ -390,32 +394,35 @@ void PainterGL::start() { } void PainterGL::draw() { - if (m_queue.isEmpty()) { + if (!m_active || m_queue.isEmpty()) { return; } - - if (m_needsUnlock) { - QTimer::singleShot(0, this, &PainterGL::draw); - return; - } - - 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); + 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); } - } else { - mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); + m_delayTimer.restart(); } + mCoreSyncWaitFrameEnd(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); } @@ -425,10 +432,6 @@ void PainterGL::stop() { 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(); } @@ -458,28 +461,6 @@ void PainterGL::performDraw() { if (m_messagePainter) { m_messagePainter->paint(&m_painter); } - m_frameReady = true; -} - -void PainterGL::swap() { - if (!m_gl->isValid()) { - return; - } - if (m_frameReady) { - m_gl->swapBuffers(m_surface); - m_gl->makeCurrent(m_surface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - m_frameReady = false; - } - if (m_needsUnlock) { - mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); - m_needsUnlock = false; - } - if (!m_queue.isEmpty()) { - QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); - } } void PainterGL::enqueue(const uint32_t* backing) { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 88f18a58a..ffe433f90 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -16,6 +16,7 @@ #endif #endif +#include #include #include #include @@ -108,9 +109,6 @@ public slots: int glTex(); -private slots: - void swap(); - private: void performDraw(); void dequeue(); @@ -131,9 +129,7 @@ private: 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 m_videoProxy; }; From 29fc787fc91cd164d419fac429c14ce4708418bb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 16 Sep 2019 22:04:40 -0700 Subject: [PATCH 05/10] Qt, OpenGL: Disable integer scaling for dimensions that don't fit --- CHANGES | 1 + src/platform/opengl/gl.c | 8 ++++++-- src/platform/opengl/gles2.c | 8 ++++++-- src/platform/qt/DisplayQt.cpp | 8 ++++++-- src/platform/qt/Window.cpp | 8 ++++++-- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index eff4d6570..ad9174978 100644 --- a/CHANGES +++ b/CHANGES @@ -87,6 +87,7 @@ Misc: - 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 0.7.2: (2019-05-25) Emulation fixes: diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 8ac36ae91..3554c87cb 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -88,8 +88,12 @@ static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { } } 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(); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index d26bb7a2c..f9843e9af 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -201,8 +201,12 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) } } 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); diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index be6403dd6..30cb3a8e4 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -106,8 +106,12 @@ void DisplayQt::paintEvent(QPaintEvent*) { } } if (isIntegerScalingLocked()) { - ds.setWidth(ds.width() - ds.width() % m_width); - ds.setHeight(ds.height() - ds.height() % m_height); + 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); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index e05b8ce32..eedd43ac0 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1962,8 +1962,12 @@ void WindowBackground::paintEvent(QPaintEvent* event) { } } 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); From e15185f52145a39d153df60a415044a5d79411ae Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 16 Sep 2019 23:34:09 -0700 Subject: [PATCH 06/10] FFmpeg: Drain recording buffers --- CHANGES | 1 + src/feature/ffmpeg/ffmpeg-encoder.c | 51 ++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index ad9174978..d86113270 100644 --- a/CHANGES +++ b/CHANGES @@ -60,6 +60,7 @@ Other fixes: - 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 diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 42ed5e3e2..ec67bcfab 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -30,6 +30,9 @@ static void _ffmpegPostVideoFrame(struct mAVStream*, const color_t* pixels, size static void _ffmpegPostAudioFrame(struct mAVStream*, int16_t left, int16_t right); static void _ffmpegSetVideoDimensions(struct mAVStream*, unsigned width, unsigned height); +static bool _ffmpegWriteAudioFrame(struct FFmpegEncoder* encoder, struct AVFrame* audioFrame); +static bool _ffmpegWriteVideoFrame(struct FFmpegEncoder* encoder, struct AVFrame* videoFrame); + enum { PREFERRED_SAMPLE_RATE = 0x8000 }; @@ -390,6 +393,21 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { } void FFmpegEncoderClose(struct FFmpegEncoder* encoder) { + if (encoder->audio) { + while (true) { + if (!_ffmpegWriteAudioFrame(encoder, NULL)) { + break; + } + } + } + if (encoder->video) { + while (true) { + if (!_ffmpegWriteVideoFrame(encoder, NULL)) { + break; + } + } + } + if (encoder->context && encoder->context->pb) { av_write_trailer(encoder->context); avio_close(encoder->context->pb); @@ -510,6 +528,10 @@ void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right 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; @@ -517,13 +539,13 @@ void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right 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) { @@ -566,6 +588,7 @@ void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right #else av_free_packet(&packet); #endif + return gotData; } void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size_t stride) { @@ -575,11 +598,6 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size } 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 @@ -588,14 +606,23 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size sws_scale(encoder->scaleContext, (const uint8_t* const*) &pixels, (const int*) &stride, 0, encoder->iheight, encoder->videoFrame->data, encoder->videoFrame->linesize); + _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) { @@ -610,6 +637,8 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size #else av_free_packet(&packet); #endif + + return gotData; } static void _ffmpegSetVideoDimensions(struct mAVStream* stream, unsigned width, unsigned height) { From 1d5f8817a5980ba90556d3679fd3dc7aa18a8fe1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 17 Sep 2019 18:31:09 -0700 Subject: [PATCH 07/10] CMake: New FFmpeg depends on bcrypt system lib on Windows --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ee4c2a70..92165b32d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -521,6 +521,9 @@ if(USE_FFMPEG) 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}) + 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},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}") if(USE_LIBSWRESAMPLE) From 8219b70c2eb34cd6eb3dbe6e1ccaea7f4d01f81f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 17 Sep 2019 18:35:26 -0700 Subject: [PATCH 08/10] CMake: Fix debug file generation --- CMakeLists.txt | 7 ++++--- src/platform/qt/CMakeLists.txt | 7 ++++--- src/platform/sdl/CMakeLists.txt | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92165b32d..edd2c4b66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1117,9 +1117,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 "$" "$.dSYM") - add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${STRIP}" -S "$") - install(FILES "$.dSYM" DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}-dbg) + add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$" "$.debug") + add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${STRIP}" "$") + add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$.debug" "$") + install(FILES "$.debug" DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}-dbg) endif() endif() if(APPLE) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 6df091760..32fa95842 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -365,8 +365,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 "$" "$.dSYM") - add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${STRIP}" -S "$") - install(FILES "$.dSYM" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt-dbg) + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$" "$.debug") + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${STRIP}" "$") + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$.debug" "$") + install(FILES "$.debug" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt-dbg) endif() endif() diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index 12b803175..67b4df479 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/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 "$" "$.dSYM") - add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${STRIP}" -S "$") - install(FILES "$.dSYM" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl-dbg) + add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$" "$.debug") + add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${STRIP}" "$") + add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$.debug" "$") + install(FILES "$.debug" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl-dbg) endif() endif() From 8708a0db52de758753a675bc646fb7b6cdafb874 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 17 Sep 2019 19:06:58 -0700 Subject: [PATCH 09/10] Feature: Switch from ImageMagick to FFmpeg for GIF generation --- CHANGES | 1 + CMakeLists.txt | 38 +--- README.md | 6 +- src/core/flags.h.in | 4 - src/feature/ffmpeg/ffmpeg-encoder.c | 165 +++++++++++++++--- src/feature/ffmpeg/ffmpeg-encoder.h | 13 +- .../imagemagick/imagemagick-gif-encoder.c | 107 ------------ .../imagemagick/imagemagick-gif-encoder.h | 43 ----- src/platform/qt/GIFView.cpp | 39 ++--- src/platform/qt/GIFView.h | 7 +- src/platform/qt/GIFView.ui | 144 ++++++--------- src/platform/qt/VideoView.cpp | 4 +- src/platform/qt/Window.cpp | 12 +- src/platform/qt/Window.h | 6 - 14 files changed, 234 insertions(+), 355 deletions(-) delete mode 100644 src/feature/imagemagick/imagemagick-gif-encoder.c delete mode 100644 src/feature/imagemagick/imagemagick-gif-encoder.h diff --git a/CHANGES b/CHANGES index d86113270..37e530d82 100644 --- a/CHANGES +++ b/CHANGES @@ -89,6 +89,7 @@ Misc: - 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: diff --git a/CMakeLists.txt b/CMakeLists.txt index edd2c4b66..14aff08a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,6 @@ if(NOT LIBMGBA_ONLY) 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") @@ -462,12 +461,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") @@ -513,18 +511,20 @@ if(USE_FFMPEG) 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}) @@ -544,29 +544,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) @@ -1240,8 +1217,7 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY) 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}") diff --git a/README.md b/README.md index 111eaa337..f85550c84 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ This will build and install mGBA into `/usr/bin` and `/usr/lib`. Dependencies th 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` .. @@ -159,11 +159,11 @@ To build on Windows for development, using MSYS2 is recommended. Follow the inst 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: diff --git a/src/core/flags.h.in b/src/core/flags.h.in index facd8743a..f214c7a8f 100644 --- a/src/core/flags.h.in +++ b/src/core/flags.h.in @@ -95,10 +95,6 @@ #cmakedefine USE_LZMA #endif -#ifndef USE_MAGICK -#cmakedefine USE_MAGICK -#endif - #ifndef USE_MINIZIP #cmakedefine USE_MINIZIP #endif diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index ec67bcfab..b327925a3 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -11,6 +11,9 @@ #include #include +#include +#include + #include #if LIBAVUTIL_VERSION_MAJOR >= 53 #include @@ -38,7 +41,9 @@ enum { }; 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; @@ -49,11 +54,27 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) { 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->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; @@ -66,6 +87,15 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) { 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) { @@ -130,7 +160,7 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un 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; @@ -149,7 +179,8 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, un #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) { @@ -179,6 +210,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, un } encoder->videoCodec = vcodec; encoder->videoBitrate = vbr; + encoder->frameskip = frameskip + 1; return true; } @@ -226,6 +258,7 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { encoder->currentAudioSample = 0; encoder->currentAudioFrame = 0; encoder->currentVideoFrame = 0; + encoder->skipResidue = 0; AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); #ifndef USE_LIBAV @@ -325,8 +358,8 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { encoder->video->bit_rate = encoder->videoBitrate; encoder->video->width = encoder->width; encoder->video->height = encoder->height; - encoder->video->time_base = (AVRational) { VIDEO_TOTAL_LENGTH, GBA_ARM7TDMI_FREQUENCY }; - encoder->video->framerate = (AVRational) { GBA_ARM7TDMI_FREQUENCY, VIDEO_TOTAL_LENGTH }; + encoder->video->time_base = (AVRational) { VIDEO_TOTAL_LENGTH * encoder->frameskip, GBA_ARM7TDMI_FREQUENCY }; + encoder->video->framerate = (AVRational) { GBA_ARM7TDMI_FREQUENCY, VIDEO_TOTAL_LENGTH * encoder->frameskip }; encoder->video->pix_fmt = encoder->pixFormat; encoder->video->gop_size = 60; encoder->video->max_b_frames = 3; @@ -365,6 +398,54 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { 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; @@ -374,12 +455,12 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { #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 @@ -401,6 +482,18 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) { } } 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; @@ -460,6 +553,15 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) { #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) { avcodec_close(encoder->video); encoder->video = NULL; @@ -470,6 +572,18 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) { 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; @@ -596,6 +710,10 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size if (!encoder->context || !encoder->videoCodec) { return; } + encoder->skipResidue = (encoder->skipResidue + 1) % encoder->frameskip; + if (encoder->skipResidue) { + return; + } stride *= BYTES_PER_PIXEL; #if LIBAVCODEC_VERSION_MAJOR >= 55 @@ -606,7 +724,21 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size sws_scale(encoder->scaleContext, (const uint8_t* const*) &pixels, (const int*) &stride, 0, encoder->iheight, encoder->videoFrame->data, encoder->videoFrame->linesize); - _ffmpegWriteVideoFrame(encoder, encoder->videoFrame); + 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) { @@ -651,20 +783,7 @@ static void _ffmpegSetVideoDimensions(struct mAVStream* stream, unsigned width, 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); } diff --git a/src/feature/ffmpeg/ffmpeg-encoder.h b/src/feature/ffmpeg/ffmpeg-encoder.h index a5215b8a5..49d386cdc 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.h +++ b/src/feature/ffmpeg/ffmpeg-encoder.h @@ -34,6 +34,8 @@ CXX_GUARD_START #define FFMPEG_USE_PACKET_UNREF #endif +#define FFMPEG_FILTERS_MAX 4 + struct FFmpegEncoder { struct mAVStream d; struct AVFormatContext* context; @@ -70,19 +72,28 @@ struct FFmpegEncoder { struct AVCodecContext* video; enum AVPixelFormat pixFormat; + enum AVPixelFormat ipixFormat; struct AVFrame* videoFrame; int width; int height; int iwidth; int iheight; + 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*); diff --git a/src/feature/imagemagick/imagemagick-gif-encoder.c b/src/feature/imagemagick/imagemagick-gif-encoder.c deleted file mode 100644 index 8d8ebcb5c..000000000 --- a/src/feature/imagemagick/imagemagick-gif-encoder.c +++ /dev/null @@ -1,107 +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 -#include -#include - -static void _magickPostVideoFrame(struct mAVStream*, const color_t* pixels, size_t stride); -static void _magickVideoDimensionsChanged(struct mAVStream*, unsigned width, unsigned height); - -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->frameskip = 2; - encoder->delayMs = -1; - - encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS; - encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS; -} - -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 *= VIDEO_TOTAL_LENGTH * 100; - nts *= VIDEO_TOTAL_LENGTH * 100; - ts /= GBA_ARM7TDMI_FREQUENCY; - nts /= GBA_ARM7TDMI_FREQUENCY; - } - MagickSetImageDelay(encoder->wand, nts - ts); - ++encoder->currentFrame; -} - -static void _magickVideoDimensionsChanged(struct mAVStream* stream, unsigned width, unsigned height) { - struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream; - if (width * height > encoder->iwidth * encoder->iheight) { - free(encoder->frame); - encoder->frame = malloc(width * height * 4); - } - encoder->iwidth = width; - encoder->iheight = height; -} diff --git a/src/feature/imagemagick/imagemagick-gif-encoder.h b/src/feature/imagemagick/imagemagick-gif-encoder.h deleted file mode 100644 index 67afdfe90..000000000 --- a/src/feature/imagemagick/imagemagick-gif-encoder.h +++ /dev/null @@ -1,43 +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 - -CXX_GUARD_START - -#include - -#if MAGICKWAND_VERSION_MAJOR >= 7 -#include -#else -#include -#endif - -struct ImageMagickGIFEncoder { - struct mAVStream d; - MagickWand* wand; - char* outfile; - uint32_t* frame; - - unsigned currentFrame; - int frameskip; - int delayMs; - - unsigned iwidth; - unsigned iheight; -}; - -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 diff --git a/src/platform/qt/GIFView.cpp b/src/platform/qt/GIFView.cpp index 901e6c365..8a6d47fc7 100644 --- a/src/platform/qt/GIFView.cpp +++ b/src/platform/qt/GIFView.cpp @@ -5,7 +5,7 @@ * 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" @@ -13,9 +13,6 @@ #include -#include -#include - using namespace QGBA; GIFView::GIFView(QWidget* parent) @@ -29,11 +26,9 @@ GIFView::GIFView(QWidget* parent) connect(m_ui.selectFile, &QAbstractButton::clicked, this, &GIFView::selectFile); connect(m_ui.filename, &QLineEdit::textChanged, this, &GIFView::setFilename); - connect(m_ui.frameskip, static_cast(&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 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); } } @@ -81,15 +77,4 @@ 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 diff --git a/src/platform/qt/GIFView.h b/src/platform/qt/GIFView.h index b138cf071..fc4ebd1a3 100644 --- a/src/platform/qt/GIFView.h +++ b/src/platform/qt/GIFView.h @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once -#ifdef USE_MAGICK +#ifdef USE_FFMPEG #include @@ -13,7 +13,7 @@ #include "ui_GIFView.h" -#include "feature/imagemagick/imagemagick-gif-encoder.h" +#include "feature/ffmpeg/ffmpeg-encoder.h" namespace QGBA { @@ -41,12 +41,11 @@ signals: private slots: void selectFile(); void setFilename(const QString&); - void updateDelay(); private: Ui::GIFView m_ui; - ImageMagickGIFEncoder m_encoder; + FFmpegEncoder m_encoder; QString m_filename; }; diff --git a/src/platform/qt/GIFView.ui b/src/platform/qt/GIFView.ui index daa5de534..57dab1cd7 100644 --- a/src/platform/qt/GIFView.ui +++ b/src/platform/qt/GIFView.ui @@ -6,18 +6,52 @@ 0 0 - 278 - 247 + 392 + 220 Record GIF - + QLayout::SetFixedSize - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Frameskip + + + + + + + 2 + + + + + + + QDialogButtonBox::Close + + + + @@ -51,6 +85,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -74,81 +121,8 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - Frameskip - - - - - - - 2 - - - - - - - Frame delay (ms) - - - - - - - Automatic - - - true - - - - - - - false - - - 5000 - - - 50 - - - - - - - - - - QDialogButtonBox::Close - - - @@ -169,21 +143,5 @@ - - delayAuto - clicked(bool) - delayMs - setDisabled(bool) - - - 202 - 177 - - - 192 - 148 - - - diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index 9ef896423..42d00f587 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -280,7 +280,7 @@ void VideoView::setVideoCodec(const QString& codec, bool manual) { } 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(); @@ -317,7 +317,7 @@ void VideoView::setAudioBitrate(int br, bool manual) { 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(); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index eedd43ac0..05026660b 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -165,9 +165,6 @@ Window::~Window() { #ifdef USE_FFMPEG delete m_videoView; -#endif - -#ifdef USE_MAGICK delete m_gifView; #endif @@ -507,9 +504,7 @@ void Window::openVideoWindow() { } m_videoView->show(); } -#endif -#ifdef USE_MAGICK void Window::openGIFWindow() { if (!m_gifView) { m_gifView = new GIFView(); @@ -1442,9 +1437,6 @@ void Window::setupMenu(QMenuBar* menubar) { #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 @@ -1874,13 +1866,11 @@ void Window::setController(CoreController* controller, const QString& fname) { } #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); } diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 251550c6d..a7a70f48f 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -101,9 +101,6 @@ public slots: #ifdef USE_FFMPEG void openVideoWindow(); -#endif - -#ifdef USE_MAGICK void openGIFWindow(); #endif @@ -224,9 +221,6 @@ private: #ifdef USE_FFMPEG VideoView* m_videoView = nullptr; -#endif - -#ifdef USE_MAGICK GIFView* m_gifView = nullptr; #endif From 7f4ca56af8d543c1dbf7b8ce12c3d7c6f2ce05d6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 18 Sep 2019 18:51:09 -0700 Subject: [PATCH 10/10] Qt: Fix shader loading while thread not running (fixes #1528) --- src/platform/qt/DisplayGL.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 175ddc042..af0dac717 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -296,7 +296,6 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion) m_shader.preprocessShader = static_cast(&reinterpret_cast(m_backend)->initialShader); } #endif - m_gl->doneCurrent(); m_backend->user = this; m_backend->filter = false; @@ -517,6 +516,12 @@ void PainterGL::setShaders(struct VDir* dir) { 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(m_backend)); mGLES2ShaderFree(&m_shader); @@ -524,6 +529,8 @@ void PainterGL::setShaders(struct VDir* dir) { mGLES2ShaderLoad(&m_shader, dir); if (m_started) { mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); + } else { + m_gl->doneCurrent(); } #endif }