From 9b6b7c73929b8be963d0d453f3a41a60eb87bef0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 15 Jan 2022 20:32:50 -0800 Subject: [PATCH 01/21] GBA DMA: Fix DMA source direction bits being cleared (fixes #2410) --- CHANGES | 1 + src/gba/dma.c | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 74f434bb2..8a20eda57 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Emulation fixes: - GB Video: Draw SGB border pieces that overlap GB graphics (fixes mgba.io/i/1339) - GBA: Improve timing when not booting from BIOS - GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059) + - GBA DMA: Fix DMA source direction bits being cleared (fixes mgba.io/i/2410) - GBA I/O: Redo internal key input, enabling edge-based key IRQs - GBA I/O: Disable open bus behavior on invalid register 06A - GBA Memory: Fix misaligned 32-bit I/O loads (fixes mgba.io/i/2307) diff --git a/src/gba/dma.c b/src/gba/dma.c index d30f0291a..5807e506f 100644 --- a/src/gba/dma.c +++ b/src/gba/dma.c @@ -83,9 +83,6 @@ uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) { if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) { currentDma->nextSource = currentDma->source; - if (currentDma->nextSource >= BASE_CART0 && currentDma->nextSource < BASE_CART_SRAM && GBADMARegisterGetSrcControl(currentDma->reg) < 3) { - currentDma->reg = GBADMARegisterClearSrcControl(currentDma->reg); - } currentDma->nextDest = currentDma->dest; uint32_t width = 2 << GBADMARegisterGetWidth(currentDma->reg); @@ -291,7 +288,13 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) { } gba->bus = memory->dmaTransferRegister; } - int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width; + + int sourceOffset; + if (info->nextSource >= BASE_CART0 && info->nextSource < BASE_CART_SRAM && GBADMARegisterGetSrcControl(info->reg) < 3) { + sourceOffset = width; + } else { + sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width; + } int destOffset = DMA_OFFSET[GBADMARegisterGetDestControl(info->reg)] * width; if (source) { source += sourceOffset; From b512d6d45597a572cd6d7726a186dbb6a5549556 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 15 Jan 2022 17:48:21 -0800 Subject: [PATCH 02/21] Qt: Redo message painter sizing --- src/platform/qt/Display.cpp | 7 ++++-- src/platform/qt/DisplayGL.cpp | 9 ++++--- src/platform/qt/MessagePainter.cpp | 40 +++++++++++------------------- src/platform/qt/MessagePainter.h | 4 +-- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 5c427eb69..0eff451f9 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -114,12 +114,15 @@ void Display::configure(ConfigController* config) { } void Display::resizeEvent(QResizeEvent*) { - m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio()); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + m_messagePainter.resize(size(), devicePixelRatioF()); +#else + m_messagePainter.resize(size(), devicePixelRatio()); +#endif } void Display::lockAspectRatio(bool lock) { m_lockAspectRatio = lock; - m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio()); } void Display::lockIntegerScaling(bool lock) { diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 7d30765d5..8b201c0b0 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -98,10 +98,11 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { filter(isFiltered()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF()); + messagePainter()->resize(size(), devicePixelRatioF()); #else - messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); + messagePainter()->resize(size(), devicePixelRatio()); #endif + CoreController::Interrupter interrupter(controller); QMetaObject::invokeMethod(m_painter.get(), "start"); setUpdatesEnabled(false); @@ -401,8 +402,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) { } void PainterGL::resize(const QSize& size) { + qreal r = m_surface->devicePixelRatio(); m_size = size; - m_window->setSize(m_size); + m_window->setSize(m_size * r); + m_window->setDevicePixelRatio(r); if (m_started && !m_active) { forceDraw(); } diff --git a/src/platform/qt/MessagePainter.cpp b/src/platform/qt/MessagePainter.cpp index 15ab748aa..33aac62f5 100644 --- a/src/platform/qt/MessagePainter.cpp +++ b/src/platform/qt/MessagePainter.cpp @@ -25,40 +25,32 @@ MessagePainter::MessagePainter(QObject* parent) clearMessage(); } -void MessagePainter::resize(const QSize& size, bool lockAspectRatio, qreal scaleFactor) { - int w = size.width(); - int h = size.height(); - int drawW = w; - int drawH = h; - if (lockAspectRatio) { - if (w * 2 > h * 3) { - drawW = h * 3 / 2; - } else if (w * 2 < h * 3) { - drawH = w * 2 / 3; - } - } - m_world.reset(); - m_world.scale(qreal(drawW) / GBA_VIDEO_HORIZONTAL_PIXELS, qreal(drawH) / GBA_VIDEO_VERTICAL_PIXELS); +void MessagePainter::resize(const QSize& size, qreal scaleFactor) { + double drawW = size.width(); + double drawH = size.height(); + double area = pow(drawW * drawW * drawW * drawH * drawH, 0.2); m_scaleFactor = scaleFactor; - m_local = QPoint(1, GBA_VIDEO_VERTICAL_PIXELS - m_messageFont.pixelSize() - 1); - m_local = m_world.map(m_local); - m_local += QPoint((w - drawW) / 2, (h - drawH) / 2); - m_pixmapBuffer = QPixmap(drawW * m_scaleFactor, - (m_messageFont.pixelSize() + 2) * m_world.m22() * m_scaleFactor); - m_pixmapBuffer.setDevicePixelRatio(m_scaleFactor); + m_world.reset(); + m_world.scale(area / 220., area / 220.); + m_local = QPoint(area / 100., drawH - m_messageFont.pixelSize() * m_world.m22() * 1.3); + m_mutex.lock(); - m_message.prepare(m_world, m_messageFont); redraw(); m_mutex.unlock(); } void MessagePainter::redraw() { - m_pixmapBuffer.fill(Qt::transparent); if (m_message.text().isEmpty()) { + m_pixmapBuffer.fill(Qt::transparent); m_pixmap = m_pixmapBuffer; - m_pixmap.setDevicePixelRatio(m_scaleFactor); return; } + m_message.prepare(m_world, m_messageFont); + QSizeF sizef = m_message.size() * m_scaleFactor; + m_pixmapBuffer = QPixmap(sizef.width() * m_world.m11(), sizef.height() * m_world.m22()); + m_pixmapBuffer.setDevicePixelRatio(m_scaleFactor); + m_pixmapBuffer.fill(Qt::transparent); + QPainter painter(&m_pixmapBuffer); painter.setWorldTransform(m_world); painter.setRenderHint(QPainter::Antialiasing); @@ -74,7 +66,6 @@ void MessagePainter::redraw() { painter.setPen(Qt::white); painter.drawStaticText(0, 0, m_message); m_pixmap = m_pixmapBuffer; - m_pixmap.setDevicePixelRatio(m_scaleFactor); } void MessagePainter::paint(QPainter* painter) { @@ -83,7 +74,6 @@ void MessagePainter::paint(QPainter* painter) { } } - void MessagePainter::showMessage(const QString& message) { m_mutex.lock(); m_message.setText(message); diff --git a/src/platform/qt/MessagePainter.h b/src/platform/qt/MessagePainter.h index 37f24c766..1a6d82de5 100644 --- a/src/platform/qt/MessagePainter.h +++ b/src/platform/qt/MessagePainter.h @@ -19,7 +19,7 @@ Q_OBJECT public: MessagePainter(QObject* parent = nullptr); - void resize(const QSize& size, bool lockAspectRatio, qreal scaleFactor); + void resize(const QSize& size, qreal scaleFactor); void paint(QPainter* painter); void setScaleFactor(qreal factor); @@ -32,13 +32,13 @@ private: QMutex m_mutex; QStaticText m_message; + qreal m_scaleFactor = 1; QPixmap m_pixmap; QPixmap m_pixmapBuffer; QTimer m_messageTimer{this}; QPoint m_local; QTransform m_world; QFont m_messageFont; - qreal m_scaleFactor = 1; }; } From 006dba7d696f24f0f3129b2df00802290fb55450 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 15 Jan 2022 20:24:34 -0800 Subject: [PATCH 03/21] Qt: Add optional frame counter to OSD (closes #1728) --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 2 ++ src/platform/qt/CoreController.h | 2 ++ src/platform/qt/Display.cpp | 30 ++++++++++++++++++------- src/platform/qt/Display.h | 3 +++ src/platform/qt/DisplayGL.cpp | 10 +++++++++ src/platform/qt/DisplayGL.h | 3 +++ src/platform/qt/DisplayQt.cpp | 2 +- src/platform/qt/MessagePainter.cpp | 36 ++++++++++++++++++++++++++++++ src/platform/qt/MessagePainter.h | 12 +++++++++- src/platform/qt/SettingsView.cpp | 2 ++ src/platform/qt/SettingsView.ui | 19 +++++++++++----- src/platform/qt/Window.cpp | 10 ++++++++- 13 files changed, 115 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 8a20eda57..0c31a3fd4 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,7 @@ Misc: - Qt: Only set default controller bindings if loading fails (fixes mgba.io/i/799) - Qt: Save converter now supports importing GameShark Advance saves - Qt: Save positions of multiplayer windows (closes mgba.io/i/2128) + - Qt: Add optional frame counter to OSD (closes mgba.io/i/1728) - Windows: Attach to console if present 0.9.3: (2021-12-17) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index d4715620d..8106195e7 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -86,6 +86,7 @@ CoreController::CoreController(mCore* core, QObject* parent) } controller->m_resetActions.clear(); + controller->m_frameCounter = -1; if (!controller->m_hwaccel) { context->core->setVideoBuffer(context->core, reinterpret_cast(controller->m_activeBuffer.data()), controller->screenDimensions().width()); @@ -1081,6 +1082,7 @@ void CoreController::finishFrame() { mCoreThreadPauseFromThread(&m_threadContext); } } + ++m_frameCounter; } updateKeys(); diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index e6c22eb44..be68407ac 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -125,6 +125,7 @@ public: bool videoSync() const { return m_videoSync; } void addFrameAction(std::function callback); + uint64_t frameCounter() const { return m_frameCounter; } public slots: void start(); @@ -244,6 +245,7 @@ private: std::unique_ptr m_cacheSet; std::unique_ptr m_override; + uint64_t m_frameCounter; QList> m_resetActions; QList> m_frameActions; QMutex m_actionMutex{QMutex::Recursive}; diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 0eff451f9..5373fd822 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -85,14 +85,20 @@ Display::Display(QWidget* parent) } void Display::attach(std::shared_ptr controller) { - connect(controller.get(), &CoreController::stateLoaded, this, &Display::resizeContext); - connect(controller.get(), &CoreController::stateLoaded, this, &Display::forceDraw); - connect(controller.get(), &CoreController::rewound, this, &Display::forceDraw); - connect(controller.get(), &CoreController::paused, this, &Display::pauseDrawing); - connect(controller.get(), &CoreController::unpaused, this, &Display::unpauseDrawing); - connect(controller.get(), &CoreController::frameAvailable, this, &Display::framePosted); - connect(controller.get(), &CoreController::statusPosted, this, &Display::showMessage); - connect(controller.get(), &CoreController::didReset, this, &Display::resizeContext); + CoreController* controllerP = controller.get(); + connect(controllerP, &CoreController::stateLoaded, this, &Display::resizeContext); + connect(controllerP, &CoreController::stateLoaded, this, &Display::forceDraw); + connect(controllerP, &CoreController::rewound, this, &Display::forceDraw); + connect(controllerP, &CoreController::paused, this, &Display::pauseDrawing); + connect(controllerP, &CoreController::unpaused, this, &Display::unpauseDrawing); + connect(controllerP, &CoreController::frameAvailable, this, &Display::framePosted); + connect(controllerP, &CoreController::frameAvailable, this, [controllerP, this]() { + if (m_showFrameCounter) { + m_messagePainter.showFrameCounter(controllerP->frameCounter()); + } + }); + connect(controllerP, &CoreController::statusPosted, this, &Display::showMessage); + connect(controllerP, &CoreController::didReset, this, &Display::resizeContext); } void Display::configure(ConfigController* config) { @@ -102,6 +108,7 @@ void Display::configure(ConfigController* config) { interframeBlending(opts->interframeBlending); filter(opts->resampleVideo); config->updateOption("showOSD"); + config->updateOption("showFrameCounter"); #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3) if (opts->shader) { struct VDir* shader = VDirOpen(opts->shader); @@ -137,6 +144,13 @@ void Display::showOSDMessages(bool enable) { m_showOSD = enable; } +void Display::showFrameCounter(bool enable) { + m_showFrameCounter = enable; + if (!enable) { + m_messagePainter.clearFrameCounter(); + } +} + void Display::filter(bool filter) { m_filter = filter; } diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 9edbfa6b8..af3cf7f00 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -42,6 +42,7 @@ public: bool hasInterframeBlending() const { return m_interframeBlending; } bool isFiltered() const { return m_filter; } bool isShowOSD() const { return m_showOSD; } + bool isShowFrameCounter() const { return m_showFrameCounter; } virtual void attach(std::shared_ptr); virtual void configure(ConfigController*); @@ -69,6 +70,7 @@ public slots: virtual void lockIntegerScaling(bool lock); virtual void interframeBlending(bool enable); virtual void showOSDMessages(bool enable); + virtual void showFrameCounter(bool enable); virtual void filter(bool filter); virtual void framePosted() = 0; virtual void setShaders(struct VDir*) = 0; @@ -89,6 +91,7 @@ private: MessagePainter m_messagePainter; bool m_showOSD = true; + bool m_showFrameCounter = false; bool m_lockAspectRatio = false; bool m_lockIntegerScaling = false; bool m_interframeBlending = false; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 8b201c0b0..db57a4777 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -95,6 +95,7 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { lockIntegerScaling(isIntegerScalingLocked()); interframeBlending(hasInterframeBlending()); showOSDMessages(isShowOSD()); + showFrameCounter(isShowFrameCounter()); filter(isFiltered()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) @@ -218,6 +219,11 @@ void DisplayGL::showOSDMessages(bool enable) { QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable)); } +void DisplayGL::showFrameCounter(bool enable) { + Display::showFrameCounter(enable); + QMetaObject::invokeMethod(m_painter.get(), "showFrameCounter", Q_ARG(bool, enable)); +} + void DisplayGL::filter(bool filter) { Display::filter(filter); QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter)); @@ -429,6 +435,10 @@ void PainterGL::showOSD(bool enable) { m_showOSD = enable; } +void PainterGL::showFrameCounter(bool enable) { + m_showFrameCounter = enable; +} + void PainterGL::filter(bool filter) { m_backend->filter = filter; if (m_started && !m_active) { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index d31921151..50583ea32 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -66,6 +66,7 @@ public slots: void lockIntegerScaling(bool lock) override; void interframeBlending(bool enable) override; void showOSDMessages(bool enable) override; + void showFrameCounter(bool enable) override; void filter(bool filter) override; void framePosted() override; void setShaders(struct VDir*) override; @@ -122,6 +123,7 @@ public slots: void lockIntegerScaling(bool lock); void interframeBlending(bool enable); void showOSD(bool enable); + void showFrameCounter(bool enable); void filter(bool filter); void resizeContext(); @@ -155,6 +157,7 @@ private: CoreController::Interrupter m_interrupter; bool m_supportsShaders; bool m_showOSD; + bool m_showFrameCounter; VideoShader m_shader{}; VideoBackend* m_backend = nullptr; QSize m_size; diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 20d919c44..1eefb71f0 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -106,7 +106,7 @@ void DisplayQt::paintEvent(QPaintEvent*) { } painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height)); painter.setOpacity(1); - if (isShowOSD()) { + if (isShowOSD() || isShowFrameCounter()) { messagePainter()->paint(&painter); } } diff --git a/src/platform/qt/MessagePainter.cpp b/src/platform/qt/MessagePainter.cpp index 33aac62f5..cf72051ff 100644 --- a/src/platform/qt/MessagePainter.cpp +++ b/src/platform/qt/MessagePainter.cpp @@ -18,6 +18,8 @@ MessagePainter::MessagePainter(QObject* parent) { m_messageFont = GBAApp::app()->monospaceFont(); m_messageFont.setPixelSize(13); + m_frameFont = GBAApp::app()->monospaceFont(); + m_frameFont.setPixelSize(10); connect(&m_messageTimer, &QTimer::timeout, this, &MessagePainter::clearMessage); m_messageTimer.setSingleShot(true); m_messageTimer.setInterval(5000); @@ -34,6 +36,9 @@ void MessagePainter::resize(const QSize& size, qreal scaleFactor) { m_world.scale(area / 220., area / 220.); m_local = QPoint(area / 100., drawH - m_messageFont.pixelSize() * m_world.m22() * 1.3); + QFontMetrics metrics(m_frameFont); + m_framePoint = QPoint(drawW / m_world.m11() - metrics.height() * 0.1, metrics.height() * 0.75); + m_mutex.lock(); redraw(); m_mutex.unlock(); @@ -72,6 +77,24 @@ void MessagePainter::paint(QPainter* painter) { if (!m_message.text().isEmpty()) { painter->drawPixmap(m_local, m_pixmap); } + if (m_drawFrameCounter) { + QString frame(tr("Frame %1").arg(m_frameCounter)); + QFontMetrics metrics(m_frameFont); + painter->setWorldTransform(m_world); + painter->setRenderHint(QPainter::Antialiasing); + painter->setFont(m_frameFont); + painter->setPen(Qt::black); + painter->translate(-metrics.width(frame), 0); + const static int ITERATIONS = 11; + for (int i = 0; i < ITERATIONS; ++i) { + painter->save(); + painter->translate(cos(i * 2.0 * M_PI / ITERATIONS) * 0.8, sin(i * 2.0 * M_PI / ITERATIONS) * 0.8); + painter->drawText(m_framePoint, frame); + painter->restore(); + } + painter->setPen(Qt::white); + painter->drawText(m_framePoint, frame); + } } void MessagePainter::showMessage(const QString& message) { @@ -90,3 +113,16 @@ void MessagePainter::clearMessage() { m_mutex.unlock(); m_messageTimer.stop(); } + +void MessagePainter::showFrameCounter(uint64_t frameCounter) { + m_mutex.lock(); + m_frameCounter = frameCounter; + m_drawFrameCounter = true; + m_mutex.unlock(); +} + +void MessagePainter::clearFrameCounter() { + m_mutex.lock(); + m_drawFrameCounter = false; + m_mutex.unlock(); +} diff --git a/src/platform/qt/MessagePainter.h b/src/platform/qt/MessagePainter.h index 1a6d82de5..8b40afdc4 100644 --- a/src/platform/qt/MessagePainter.h +++ b/src/platform/qt/MessagePainter.h @@ -27,16 +27,26 @@ public slots: void showMessage(const QString& message); void clearMessage(); + void showFrameCounter(uint64_t); + void clearFrameCounter(); + private: void redraw(); QMutex m_mutex; QStaticText m_message; qreal m_scaleFactor = 1; + uint64_t m_frameCounter; + bool m_drawFrameCounter = false; + + QPoint m_local; QPixmap m_pixmap; QPixmap m_pixmapBuffer; + + QPointF m_framePoint = QPointF(0, 0); + QFont m_frameFont; + QTimer m_messageTimer{this}; - QPoint m_local; QTransform m_world; QFont m_messageFont; }; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 611c6d2de..38350d671 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -450,6 +450,7 @@ void SettingsView::updateConfig() { saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling); saveSetting("interframeBlending", m_ui.interframeBlending); saveSetting("showOSD", m_ui.showOSD); + saveSetting("showFrameCounter", m_ui.showFrameCounter); saveSetting("volume", m_ui.volume); saveSetting("mute", m_ui.mute); saveSetting("fastForwardVolume", m_ui.volumeFf); @@ -668,6 +669,7 @@ void SettingsView::reloadConfig() { loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling); loadSetting("interframeBlending", m_ui.interframeBlending); loadSetting("showOSD", m_ui.showOSD, true); + loadSetting("showFrameCounter", m_ui.showFrameCounter); loadSetting("volume", m_ui.volume, 0x100); loadSetting("mute", m_ui.mute, false); loadSetting("fastForwardVolume", m_ui.volumeFf, m_ui.volume->value()); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 254a7f8ec..ea8a3b8e6 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -667,20 +667,27 @@ + + + Show frame count in OSD + + + + Enable Discord Rich Presence - + Qt::Horizontal - + Automatically save state @@ -690,7 +697,7 @@ - + Automatically load state @@ -700,14 +707,14 @@ - + Qt::Horizontal - + Automatically save cheats @@ -717,7 +724,7 @@ - + Automatically load cheats diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index ef47b4945..72919db56 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -835,7 +835,6 @@ void Window::gameStarted() { m_config->updateOption("lockAspectRatio"); m_config->updateOption("interframeBlending"); m_config->updateOption("resampleVideo"); - m_config->updateOption("showOSD"); if (m_savedScale > 0) { resizeFrame(size * m_savedScale); } @@ -1706,6 +1705,13 @@ void Window::setupMenu(QMenuBar* menubar) { } }, this); + ConfigOption* showFrameCounter = m_config->addOption("showFrameCounter"); + showFrameCounter->connect([this](const QVariant& value) { + if (m_display) { + m_display->showFrameCounter(value.toBool()); + } + }, this); + ConfigOption* videoScale = m_config->addOption("videoScale"); videoScale->connect([this](const QVariant& value) { if (m_display) { @@ -2004,6 +2010,8 @@ void Window::setController(CoreController* controller, const QString& fname) { attachDisplay(); m_controller->loadConfig(m_config); + m_config->updateOption("showOSD"); + m_config->updateOption("showFrameCounter"); m_controller->start(); if (!m_pendingState.isEmpty()) { From 851b01be1526b7c5829d0aec48cc15043fb22b6e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Jan 2022 01:53:22 -0800 Subject: [PATCH 04/21] Qt: Add optional emulation-related information on reset (closes #1780) --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 9 +++ src/platform/qt/CoreController.h | 2 + src/platform/qt/SettingsView.cpp | 2 + src/platform/qt/SettingsView.ui | 118 ++++++++++++++++++++--------- src/platform/qt/Window.cpp | 8 ++ 6 files changed, 104 insertions(+), 36 deletions(-) diff --git a/CHANGES b/CHANGES index 0c31a3fd4..227e201bb 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,7 @@ Misc: - Qt: Save converter now supports importing GameShark Advance saves - Qt: Save positions of multiplayer windows (closes mgba.io/i/2128) - Qt: Add optional frame counter to OSD (closes mgba.io/i/1728) + - Qt: Add optional emulation-related information on reset (closes mgba.io/i/1780) - Windows: Attach to console if present 0.9.3: (2021-12-17) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 8106195e7..86736e911 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #ifdef M_CORE_GBA #include @@ -92,7 +93,11 @@ CoreController::CoreController(mCore* core, QObject* parent) context->core->setVideoBuffer(context->core, reinterpret_cast(controller->m_activeBuffer.data()), controller->screenDimensions().width()); } + QString message(tr("Reset r%1-%2 %3").arg(gitRevision).arg(QLatin1String(gitCommitShort)).arg(controller->m_crc32, 8, 16, QLatin1Char('0'))); QMetaObject::invokeMethod(controller, "didReset"); + if (controller->m_showResetInfo) { + QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message)); + } controller->finishFrame(); }; @@ -479,6 +484,10 @@ void CoreController::setSync(bool sync) { } } +void CoreController::showResetInfo(bool enable) { + m_showResetInfo = enable; +} + void CoreController::setRewinding(bool rewind) { if (!m_threadContext.core->opts.rewindEnable) { return; diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index be68407ac..ae7c37269 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -134,6 +134,7 @@ public slots: void setPaused(bool paused); void frameAdvance(); void setSync(bool enable); + void showResetInfo(bool enable); void setRewinding(bool); void rewind(int count = 0); @@ -237,6 +238,7 @@ private: uint32_t m_crc32; QString m_internalTitle; QString m_dbTitle; + bool m_showResetInfo = false; QByteArray m_activeBuffer; QByteArray m_completeBuffer; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 38350d671..4af948d4b 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -451,6 +451,7 @@ void SettingsView::updateConfig() { saveSetting("interframeBlending", m_ui.interframeBlending); saveSetting("showOSD", m_ui.showOSD); saveSetting("showFrameCounter", m_ui.showFrameCounter); + saveSetting("showResetInfo", m_ui.showResetInfo); saveSetting("volume", m_ui.volume); saveSetting("mute", m_ui.mute); saveSetting("fastForwardVolume", m_ui.volumeFf); @@ -670,6 +671,7 @@ void SettingsView::reloadConfig() { loadSetting("interframeBlending", m_ui.interframeBlending); loadSetting("showOSD", m_ui.showOSD, true); loadSetting("showFrameCounter", m_ui.showFrameCounter); + loadSetting("showResetInfo", m_ui.showResetInfo); loadSetting("volume", m_ui.volume, 0x100); loadSetting("mute", m_ui.mute, false); loadSetting("fastForwardVolume", m_ui.volumeFf, m_ui.volume->value()); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index ea8a3b8e6..47e92ed29 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -594,6 +594,13 @@ + + + + When inactive: + + + @@ -612,6 +619,31 @@ + + + + When minimized: + + + + + + + + + Pause + + + + + + + Mute + + + + + @@ -667,11 +699,25 @@ - - - Show frame count in OSD + + + 20 - + + + + Show frame count in OSD + + + + + + + Show emulation info on reset + + + + @@ -734,38 +780,6 @@ - - - - - - Pause - - - - - - - Mute - - - - - - - - - When inactive: - - - - - - - When minimized: - - - @@ -2321,6 +2335,38 @@ + + showOSD + toggled(bool) + showFrameCounter + setEnabled(bool) + + + 374 + 391 + + + 418 + 431 + + + + + showOSD + toggled(bool) + showResetInfo + setEnabled(bool) + + + 374 + 391 + + + 418 + 470 + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 72919db56..bc1bc14c3 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1712,6 +1712,13 @@ void Window::setupMenu(QMenuBar* menubar) { } }, this); + ConfigOption* showResetInfo = m_config->addOption("showResetInfo"); + showResetInfo->connect([this](const QVariant& value) { + if (m_controller) { + m_controller->showResetInfo(value.toBool()); + } + }, this); + ConfigOption* videoScale = m_config->addOption("videoScale"); videoScale->connect([this](const QVariant& value) { if (m_display) { @@ -2012,6 +2019,7 @@ void Window::setController(CoreController* controller, const QString& fname) { m_controller->loadConfig(m_config); m_config->updateOption("showOSD"); m_config->updateOption("showFrameCounter"); + m_config->updateOption("showResetInfo"); m_controller->start(); if (!m_pendingState.isEmpty()) { From c4e481c110ebac60a2370e43434bb7abce4f0750 Mon Sep 17 00:00:00 2001 From: Felix Jones Date: Tue, 18 Jan 2022 22:36:51 +0100 Subject: [PATCH 05/21] GBA code unit testing front-end (#2411) --- src/platform/test/CMakeLists.txt | 7 + src/platform/test/rom-test-main.c | 220 ++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 src/platform/test/rom-test-main.c diff --git a/src/platform/test/CMakeLists.txt b/src/platform/test/CMakeLists.txt index 2517e83bf..5e20cb41e 100644 --- a/src/platform/test/CMakeLists.txt +++ b/src/platform/test/CMakeLists.txt @@ -46,3 +46,10 @@ if(BUILD_CINEMA) add_test(cinema ${BINARY_NAME}-cinema -v) install(TARGETS ${BINARY_NAME}-cinema DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-test) endif() + +if(BUILD_ROM_TEST) + add_executable(${BINARY_NAME}-rom-test ${CMAKE_CURRENT_SOURCE_DIR}/rom-test-main.c) + target_link_libraries(${BINARY_NAME}-rom-test ${BINARY_NAME}) + target_compile_definitions(${BINARY_NAME}-rom-test PRIVATE "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") + install(TARGETS ${BINARY_NAME}-rom-test DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-test) +endif() diff --git a/src/platform/test/rom-test-main.c b/src/platform/test/rom-test-main.c new file mode 100644 index 000000000..d015f40c0 --- /dev/null +++ b/src/platform/test/rom-test-main.c @@ -0,0 +1,220 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau +* Copyright (c) 2022 Felix Jones +* +* 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 +#include +#include +#include +#include + +#include +#include + +#include + +#define ROM_TEST_OPTIONS "S:R:" +#define ROM_TEST_USAGE \ + "\nAdditional options:\n" \ + " -S SWI Run until specified SWI call before exiting\n" \ + " -R REGISTER General purpose register to return as exit code\n" \ + +struct RomTestOpts { + int exitSwiImmediate; + unsigned int returnCodeRegister; +}; + +static void _romTestShutdown(int signal); +static bool _parseRomTestOpts(struct mSubParser* parser, int option, const char* arg); +static bool _parseSwi(const char* regStr, int* oSwi); +static bool _parseNamedRegister(const char* regStr, unsigned int* oRegister); + +static bool _dispatchExiting = false; +static int _exitCode = 0; + +#ifdef M_CORE_GBA +static void _romTestSwi3Callback(void* context); + +static void _romTestSwi16(struct ARMCore* cpu, int immediate); +static void _romTestSwi32(struct ARMCore* cpu, int immediate); + +static int _exitSwiImmediate; +static unsigned int _returnCodeRegister; + +void (*_armSwi16)(struct ARMCore* cpu, int immediate); +void (*_armSwi32)(struct ARMCore* cpu, int immediate); +#endif + +int main(int argc, char * argv[]) { + signal(SIGINT, _romTestShutdown); + + struct RomTestOpts romTestOpts = { 3, 0 }; + struct mSubParser subparser = { + .usage = ROM_TEST_USAGE, + .parse = _parseRomTestOpts, + .extraOptions = ROM_TEST_OPTIONS, + .opts = &romTestOpts + }; + + struct mArguments args; + bool parsed = parseArguments(&args, argc, argv, &subparser); + if (!args.fname) { + parsed = false; + } + if (!parsed || args.showHelp) { + usage(argv[0], ROM_TEST_USAGE); + return !parsed; + } + if (args.showVersion) { + version(argv[0]); + return 0; + } + struct mCore* core = mCoreFind(args.fname); + if (!core) { + return 1; + } + core->init(core); + mCoreInitConfig(core, "romTest"); + applyArguments(&args, NULL, &core->config); + + mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "remove"); + +#ifdef M_CORE_GBA + if (core->platform(core) == mPLATFORM_GBA) { + ((struct GBA*) core->board)->hardCrash = false; + + _exitSwiImmediate = romTestOpts.exitSwiImmediate; + _returnCodeRegister = romTestOpts.returnCodeRegister; + + if (_exitSwiImmediate == 3) { + // Hook into SWI 3 (shutdown) + struct mCoreCallbacks callbacks = {0}; + callbacks.context = core; + callbacks.shutdown = _romTestSwi3Callback; + core->addCoreCallbacks(core, &callbacks); + } else { + // Custom SWI hooks + _armSwi16 = ((struct GBA*) core->board)->cpu->irqh.swi16; + ((struct GBA*) core->board)->cpu->irqh.swi16 = _romTestSwi16; + _armSwi32 = ((struct GBA*) core->board)->cpu->irqh.swi32; + ((struct GBA*) core->board)->cpu->irqh.swi32 = _romTestSwi32; + } + } +#endif + + bool cleanExit = true; + if (!mCoreLoadFile(core, args.fname)) { + cleanExit = false; + goto loadError; + } + if (args.patch) { + core->loadPatch(core, VFileOpen(args.patch, O_RDONLY)); + } + + struct VFile* savestate = NULL; + + if (args.savestate) { + savestate = VFileOpen(args.savestate, O_RDONLY); + } + + core->reset(core); + + struct mCheatDevice* device; + if (args.cheatsFile && (device = core->cheatDevice(core))) { + struct VFile* vf = VFileOpen(args.cheatsFile, O_RDONLY); + if (vf) { + mCheatDeviceClear(device); + mCheatParseFile(device, vf); + vf->close(vf); + } + } + + if (savestate) { + mCoreLoadStateNamed(core, savestate, 0); + savestate->close(savestate); + } + + do { + core->runLoop(core); + } while (!_dispatchExiting); + + core->unloadROM(core); + +loadError: + freeArguments(&args); + mCoreConfigDeinit(&core->config); + core->deinit(core); + + return cleanExit ? _exitCode : 1; +} + +static void _romTestShutdown(int signal) { + UNUSED(signal); + _dispatchExiting = true; +} + +#ifdef M_CORE_GBA +static void _romTestSwi3Callback(void* context) { + struct mCore* core = context; + _exitCode = ((struct GBA*) core->board)->cpu->regs.gprs[_returnCodeRegister]; + _dispatchExiting = true; +} + +static void _romTestSwi16(struct ARMCore* cpu, int immediate) { + if (immediate == _exitSwiImmediate) { + _exitCode = cpu->regs.gprs[_returnCodeRegister]; + _dispatchExiting = true; + return; + } + _armSwi16(cpu, immediate); +} + +static void _romTestSwi32(struct ARMCore* cpu, int immediate) { + if (immediate == _exitSwiImmediate) { + _exitCode = cpu->regs.gprs[_returnCodeRegister]; + _dispatchExiting = true; + return; + } + _armSwi32(cpu, immediate); +} +#endif + +static bool _parseRomTestOpts(struct mSubParser* parser, int option, const char* arg) { + struct RomTestOpts* opts = parser->opts; + errno = 0; + switch (option) { + case 'S': + return _parseSwi(arg, &opts->exitSwiImmediate); + case 'R': + return _parseNamedRegister(arg, &opts->returnCodeRegister); + default: + return false; + } +} + +static bool _parseSwi(const char* swiStr, int* oSwi) { + char* parseEnd; + long swi = strtol(swiStr, &parseEnd, 0); + if (errno || swi > UINT8_MAX || *parseEnd) { + return false; + } + *oSwi = swi; + return true; +} + +static bool _parseNamedRegister(const char* regStr, unsigned int* oRegister) { + if (regStr[0] == 'r' || regStr[0] == 'R') { + ++regStr; + } + + char* parseEnd; + unsigned long regId = strtoul(regStr, &parseEnd, 10); + if (errno || regId > 15 || *parseEnd) { + return false; + } + *oRegister = regId; + return true; +} From b6e2faaba9106ffd3f7dbf378f145451188f47f9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 11 Jan 2022 22:54:46 -0800 Subject: [PATCH 06/21] Qt: Add QOpenGLWidget cross-thread codepath for macOS (fixes #1754) --- CHANGES | 1 + src/platform/opengl/gles2.c | 22 +++-- src/platform/qt/CMakeLists.txt | 7 +- src/platform/qt/Display.cpp | 4 +- src/platform/qt/DisplayGL.cpp | 162 +++++++++++++++++++++++++++++---- src/platform/qt/DisplayGL.h | 41 ++++++++- src/platform/qt/Window.cpp | 4 +- 7 files changed, 208 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index 227e201bb..c878191e3 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,7 @@ Misc: - Qt: Save positions of multiplayer windows (closes mgba.io/i/2128) - Qt: Add optional frame counter to OSD (closes mgba.io/i/1728) - Qt: Add optional emulation-related information on reset (closes mgba.io/i/1780) + - Qt: Add QOpenGLWidget cross-thread codepath for macOS (fixes mgba.io/i/1754) - Windows: Attach to console if present 0.9.3: (2021-12-17) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 4c01fd3f0..4277b7b76 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -235,6 +235,7 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) context->shaders[n].dirty = true; } } + context->finalShader.dirty = true; glBindTexture(GL_TEXTURE_2D, context->finalShader.tex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, drawW, drawH, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo); @@ -375,7 +376,6 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { glGetIntegerv(GL_VIEWPORT, viewport); context->finalShader.filter = v->filter; - context->finalShader.dirty = true; _drawShader(context, &context->initialShader); if (v->interframeBlending) { context->interframeShader.blend = true; @@ -437,15 +437,19 @@ void mGLES2ContextCreate(struct mGLES2Context* context) { } void mGLES2ContextUseFramebuffer(struct mGLES2Context* context) { - glGenFramebuffers(1, &context->finalShader.fbo); - glGenTextures(1, &context->finalShader.tex); + if (!context->finalShader.fbo) { + glGenFramebuffers(1, &context->finalShader.fbo); + } + if (!context->finalShader.tex) { + glGenTextures(1, &context->finalShader.tex); - glBindTexture(GL_TEXTURE_2D, context->finalShader.tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D, context->finalShader.tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + } glBindFramebuffer(GL_FRAMEBUFFER, context->finalShader.fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context->finalShader.tex, 0); diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 062bfd5a1..fdbe4ab9c 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -4,6 +4,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(PLATFORM_SRC) set(QT_STATIC OFF) +set(QT_DEFINES) if(BUILD_SDL) add_definitions(-DBUILD_SDL) @@ -35,6 +36,11 @@ if(NOT Qt5Widgets_FOUND) endif() if(APPLE) + execute_process(COMMAND xcrun --show-sdk-version OUTPUT_VARIABLE MACOSX_SDK) + if(MACOSX_SDK VERSION_GREATER 10.14) + list(APPEND QT_DEFINES USE_SHARE_WIDGET) + endif() + if(Qt5Widgets_VERSION MATCHES "^5.1[0-9]") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8") else() @@ -183,7 +189,6 @@ if(M_CORE_GB) list(APPEND PLATFORM_SRC ${GB_SRC}) endif() -set(QT_DEFINES) if(Qt5Multimedia_FOUND) list(APPEND AUDIO_SRC AudioProcessorQt.cpp diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 5373fd822..458cfd3a6 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -38,7 +38,9 @@ Display* Display::create(QWidget* parent) { format.setVersion(3, 2); } format.setProfile(QSurfaceFormat::CoreProfile); - if (!DisplayGL::supportsFormat(format)) { + if (DisplayGL::supportsFormat(format)) { + QSurfaceFormat::setDefaultFormat(format); + } else { #ifdef BUILD_GL LOG(QT, WARN) << ("Failed to create an OpenGL Core context, trying old-style..."); format.setVersion(1, 4); diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index db57a4777..867b0e540 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -9,14 +9,14 @@ #include #include -#include -#include #include +#include #include #include #include #include #include +#include #include @@ -43,13 +43,96 @@ uint qHash(const QSurfaceFormat& format, uint seed) { return qHash(representation, seed); } +void mGLWidget::initializeGL() { + m_vao.create(); + m_program.create(); + + m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, R"(#version 150 core + in vec4 position; + out vec2 texCoord; + void main() { + gl_Position = position; + texCoord = (position.st + 1.0) * 0.5; + })"); + + m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, R"(#version 150 core + in vec2 texCoord; + out vec4 color; + uniform sampler2D tex; + void main() { + color = vec4(texture(tex, texCoord).rgb, 1.0); + })"); + + m_program.link(); + m_program.setUniformValue("tex", 0); + m_positionLocation = m_program.attributeLocation("position"); + + connect(&m_refresh, &QTimer::timeout, this, static_cast(&QWidget::update)); +} + +void mGLWidget::finalizeVAO() { + QOpenGLFunctions_3_2_Core* fn = context()->versionFunctions(); + fn->glGetError(); // Clear the error + m_vao.bind(); + fn->glBindBuffer(GL_ARRAY_BUFFER, m_vbo); + fn->glEnableVertexAttribArray(m_positionLocation); + fn->glVertexAttribPointer(m_positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); + m_vao.release(); + if (fn->glGetError() == GL_NO_ERROR) { + m_vaoDone = true; + } +} + +void mGLWidget::paintGL() { + if (!m_vaoDone) { + finalizeVAO(); + } + QOpenGLFunctions_3_2_Core* fn = context()->versionFunctions(); + m_program.bind(); + m_vao.bind(); + fn->glBindTexture(GL_TEXTURE_2D, m_tex); + fn->glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + fn->glBindTexture(GL_TEXTURE_2D, 0); + m_vao.release(); + m_program.release(); + + // TODO: Better timing + ++m_refreshResidue; + if (m_refreshResidue == 3) { + m_refresh.start(16); + m_refreshResidue = 0; + } else { + m_refresh.start(17); + } +} + DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) : Display(parent) { setAttribute(Qt::WA_NativeWindow); + window()->windowHandle()->setFormat(format); windowHandle()->create(); - m_painter = std::make_unique(windowHandle(), format); +#ifdef USE_SHARE_WIDGET + bool useShareWidget = true; +#else + // TODO: Does using this on Wayland help? + bool useShareWidget = false; +#endif + + if (useShareWidget) { + m_gl = new mGLWidget; + m_gl->setAttribute(Qt::WA_NativeWindow); + m_gl->setFormat(format); + QBoxLayout* layout = new QVBoxLayout; + layout->addWidget(m_gl); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + } else { + m_gl = nullptr; + } + + m_painter = std::make_unique(windowHandle(), m_gl, format); m_drawThread.setObjectName("Painter Thread"); m_painter->setThread(&m_drawThread); @@ -106,7 +189,9 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { CoreController::Interrupter interrupter(controller); QMetaObject::invokeMethod(m_painter.get(), "start"); - setUpdatesEnabled(false); + if (!m_gl) { + setUpdatesEnabled(false); + } } bool DisplayGL::supportsFormat(const QSurfaceFormat& format) { @@ -188,7 +273,9 @@ void DisplayGL::unpauseDrawing() { m_isDrawing = true; QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection); #ifndef Q_OS_MAC - setUpdatesEnabled(false); + if (!m_gl) { + setUpdatesEnabled(false); + } #endif } } @@ -278,10 +365,21 @@ int DisplayGL::framebufferHandle() { return m_painter->glTex(); } -PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format) - : m_surface(surface) +PainterGL::PainterGL(QWindow* window, mGLWidget* widget, const QSurfaceFormat& format) + : m_window(window) , m_format(format) + , m_widget(widget) { + if (widget) { + m_format = widget->format(); + QOffscreenSurface* surface = new QOffscreenSurface; + surface->setScreen(window->screen()); + surface->setFormat(m_format); + surface->create(); + m_surface = surface; + } else { + m_surface = m_window; + } m_supportsShaders = m_format.version() >= qMakePair(2, 0); for (auto& buf : m_buffers) { m_free.append(&buf.front()); @@ -311,6 +409,9 @@ void PainterGL::makeCurrent() { void PainterGL::create() { m_gl = std::make_unique(); m_gl->setFormat(m_format); + if (m_widget) { + m_gl->setShareContext(m_widget->context()); + } m_gl->create(); makeCurrent(); @@ -321,7 +422,7 @@ void PainterGL::create() { mGLES2Context* gl2Backend; #endif - m_window = std::make_unique(); + m_paintDev = std::make_unique(); #if defined(BUILD_GLES2) || defined(BUILD_GLES3) auto version = m_format.version(); @@ -346,11 +447,36 @@ void PainterGL::create() { } painter->m_gl->swapBuffers(painter->m_surface); painter->makeCurrent(); + + if (painter->m_widget && painter->supportsShaders()) { + QOpenGLFunctions_3_2_Core* fn = painter->m_gl->versionFunctions(); + fn->glFinish(); + mGLES2Context* gl2Backend = reinterpret_cast(painter->m_backend); + painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]); + painter->m_finalTexIdx ^= 1; + gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx]; + mGLES2ContextUseFramebuffer(gl2Backend); + } }; m_backend->init(m_backend, 0); #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (m_supportsShaders) { + if (m_widget) { + m_widget->setVBO(gl2Backend->vbo); + + gl2Backend->finalShader.tex = 0; + mGLES2ContextUseFramebuffer(gl2Backend); + m_finalTex[0] = gl2Backend->finalShader.tex; + + gl2Backend->finalShader.tex = 0; + mGLES2ContextUseFramebuffer(gl2Backend); + m_finalTex[1] = gl2Backend->finalShader.tex; + + m_finalTexIdx = 0; + gl2Backend->finalShader.tex = m_finalTex[m_finalTexIdx]; + m_widget->setTex(m_finalTex[m_finalTexIdx]); + } m_shader.preprocessShader = static_cast(&reinterpret_cast(m_backend)->initialShader); } #endif @@ -373,7 +499,7 @@ void PainterGL::destroy() { #endif m_backend->deinit(m_backend); m_gl->doneCurrent(); - m_window.reset(); + m_paintDev.reset(); m_gl.reset(); free(m_backend); @@ -408,10 +534,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) { } void PainterGL::resize(const QSize& size) { - qreal r = m_surface->devicePixelRatio(); + qreal r = m_window->devicePixelRatio(); m_size = size; - m_window->setSize(m_size * r); - m_window->setDevicePixelRatio(r); + m_paintDev->setSize(m_size * r); + m_paintDev->setDevicePixelRatio(r); if (m_started && !m_active) { forceDraw(); } @@ -472,7 +598,7 @@ void PainterGL::draw() { if (!sync->audioWait && !sync->videoFrameWait) { return; } - if (m_delayTimer.elapsed() >= 1000 / m_surface->screen()->refreshRate()) { + if (m_delayTimer.elapsed() >= 1000 / m_window->screen()->refreshRate()) { return; } if (!m_drawTimer.isActive()) { @@ -492,7 +618,7 @@ void PainterGL::draw() { forceRedraw = sync->videoFrameWait; } if (!forceRedraw) { - forceRedraw = m_delayTimer.nsecsElapsed() + 1000000 >= 1000000000 / m_surface->screen()->refreshRate(); + forceRedraw = m_delayTimer.nsecsElapsed() + 1000000 >= 1000000000 / m_window->screen()->refreshRate(); } } mCoreSyncWaitFrameEnd(sync); @@ -507,7 +633,7 @@ void PainterGL::draw() { void PainterGL::forceDraw() { performDraw(); if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) { - if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) { + if (m_delayTimer.elapsed() < 1000 / m_window->screen()->refreshRate()) { return; } m_delayTimer.restart(); @@ -532,7 +658,7 @@ void PainterGL::stop() { } if (m_videoProxy) { m_videoProxy->reset(); - m_videoProxy->moveToThread(m_surface->thread()); + m_videoProxy->moveToThread(m_window->thread()); m_videoProxy.reset(); } m_backend->clear(m_backend); @@ -550,14 +676,14 @@ void PainterGL::unpause() { } void PainterGL::performDraw() { - float r = m_surface->devicePixelRatio(); + float r = m_window->devicePixelRatio(); m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r); if (m_buffer) { m_backend->postFrame(m_backend, m_buffer); } m_backend->drawFrame(m_backend); if (m_showOSD && m_messagePainter) { - m_painter.begin(m_window.get()); + m_painter.begin(m_paintDev.get()); m_messagePainter->paint(&m_painter); m_painter.end(); } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 50583ea32..54256ed30 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -21,7 +21,11 @@ #include #include #include +#include #include +#include +#include +#include #include #include #include @@ -35,11 +39,37 @@ #include "platform/video-backend.h" class QOpenGLPaintDevice; +class QOpenGLWidget; uint qHash(const QSurfaceFormat&, uint seed = 0); namespace QGBA { +class mGLWidget : public QOpenGLWidget { +Q_OBJECT + +public: + void setTex(GLuint tex) { m_tex = tex; } + void setVBO(GLuint vbo) { m_vbo = vbo; } + void finalizeVAO(); + +protected: + void initializeGL() override; + void paintGL() override; + +private: + GLuint m_tex; + GLuint m_vbo; + + bool m_vaoDone = false; + QOpenGLVertexArrayObject m_vao; + QOpenGLShaderProgram m_program; + GLuint m_positionLocation; + + QTimer m_refresh; + int m_refreshResidue = 0; +}; + class PainterGL; class DisplayGL : public Display { Q_OBJECT @@ -88,13 +118,14 @@ private: std::unique_ptr m_painter; QThread m_drawThread; std::shared_ptr m_context; + mGLWidget* m_gl; }; class PainterGL : public QObject { Q_OBJECT public: - PainterGL(QWindow* surface, const QSurfaceFormat& format); + PainterGL(QWindow* surface, mGLWidget* widget, const QSurfaceFormat& format); ~PainterGL(); void setThread(QThread*); @@ -146,10 +177,14 @@ private: uint32_t* m_buffer = nullptr; QPainter m_painter; QMutex m_mutex; - QWindow* m_surface; + QWindow* m_window; + QSurface* m_surface; QSurfaceFormat m_format; - std::unique_ptr m_window; + std::unique_ptr m_paintDev; std::unique_ptr m_gl; + int m_finalTexIdx = 0; + GLuint m_finalTex[2]; + mGLWidget* m_widget; bool m_active = false; bool m_started = false; QTimer m_drawTimer; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index bc1bc14c3..be78a8fb9 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -754,7 +754,9 @@ void Window::focusInEvent(QFocusEvent*) { updateMultiplayerActive(true); } } - m_display->forceDraw(); + if (m_display) { + m_display->forceDraw(); + } } void Window::focusOutEvent(QFocusEvent*) { From 506424286f3739d368097d286bb4764d9a6794b4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Jan 2022 18:43:25 -0800 Subject: [PATCH 07/21] Qt: Tighten OpenGL timing variance --- src/platform/qt/DisplayGL.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 867b0e540..a7a12a18d 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -612,13 +612,13 @@ void PainterGL::draw() { m_delayTimer.start(); } else { if (sync->audioWait || sync->videoFrameWait) { - while (m_delayTimer.nsecsElapsed() + 1000000 < 1000000000 / sync->fpsTarget) { + while (m_delayTimer.nsecsElapsed() + 300000 < 1000000000 / sync->fpsTarget) { QThread::usleep(500); } forceRedraw = sync->videoFrameWait; } if (!forceRedraw) { - forceRedraw = m_delayTimer.nsecsElapsed() + 1000000 >= 1000000000 / m_window->screen()->refreshRate(); + forceRedraw = m_delayTimer.nsecsElapsed() + 300000 >= 1000000000 / m_window->screen()->refreshRate(); } } mCoreSyncWaitFrameEnd(sync); From ddca55347ef1618a9f32baf6ebcc3b3375a0a51d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 19 Jan 2022 23:06:19 -0800 Subject: [PATCH 08/21] GBA Video: Fix OpenGL rendering on M1 Macs --- CHANGES | 1 + include/mgba/internal/gba/renderers/gl.h | 2 ++ src/gba/renderers/gl.c | 28 ++++++++++++++++-------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index c878191e3..d45fafc7e 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Emulation fixes: - GBA I/O: Redo internal key input, enabling edge-based key IRQs - GBA I/O: Disable open bus behavior on invalid register 06A - GBA Memory: Fix misaligned 32-bit I/O loads (fixes mgba.io/i/2307) + - GBA Video: Fix OpenGL rendering on M1 Macs Other fixes: - Core: Don't attempt to restore rewind diffs past start of rewind - FFmpeg: Fix crash when encoding audio with some containers diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 773d5dee0..983e82ec8 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -166,6 +166,8 @@ struct GBAVideoGLRenderer { struct GBAVideoGLShader windowShader; struct GBAVideoGLShader finalizeShader; + bool useBindFragData; + GBARegisterDISPCNT dispcnt; unsigned target1Obj; diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 2c16943d5..fdf2ffabc 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -67,11 +67,17 @@ static const GLchar* const _gles3Header = "precision highp isampler2D;\n"; static const GLchar* const _gl3Header = - "#version 150 core\n" + "#version 140 core\n" "#define OUT(n)\n" PALETTE_ENTRY "precision highp float;\n"; +static const GLchar* const _gl33Header = + "#version 330 core\n" + "#define OUT(n) layout(location = n)\n" + PALETTE_ENTRY + "precision highp float;\n"; + static const char* const _vertexShader = "in vec2 position;\n" "uniform ivec2 loc;\n" @@ -698,13 +704,11 @@ static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVide mLOG(GBA_VIDEO, ERROR, "Fragment shader compilation failure: %s", log); } size_t i; -#ifndef BUILD_GLES3 - for (i = 0; outFrags[i]; ++i) { - glBindFragDataLocation(program, i, outFrags[i]); + if (glRenderer->useBindFragData) { + for (i = 0; outFrags[i]; ++i) { + glBindFragDataLocation(program, i, outFrags[i]); + } } -#else - UNUSED(outFrags); -#endif glLinkProgram(program); glGetProgramInfoLog(program, 2048, 0, log); if (log[0]) { @@ -823,9 +827,15 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { char log[2048]; const GLchar* shaderBuffer[4]; + glRenderer->useBindFragData = false; const GLubyte* version = glGetString(GL_VERSION); - if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES "))) { - shaderBuffer[0] = _gl3Header; + if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES ")) != 0) { + if (strncmp((const char*) version, "3.3 ", strlen("3.3 ")) == 0 || strncmp((const char*) version, "4.", strlen("4.")) == 0) { + shaderBuffer[0] = _gl33Header; + } else { + shaderBuffer[0] = _gl3Header; + glRenderer->useBindFragData = true; + } } else { shaderBuffer[0] = _gles3Header; } From 1f531742705543e33752f33ff428b62506c5b2b3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 18:10:53 -0800 Subject: [PATCH 09/21] GBA Video: Bump OpenGL requirement to 3.3 (closes #2425) --- include/mgba/internal/gba/renderers/gl.h | 2 - src/gba/renderers/gl.c | 51 +++++++----------------- src/platform/qt/Display.cpp | 2 +- 3 files changed, 15 insertions(+), 40 deletions(-) diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h index 983e82ec8..773d5dee0 100644 --- a/include/mgba/internal/gba/renderers/gl.h +++ b/include/mgba/internal/gba/renderers/gl.h @@ -166,8 +166,6 @@ struct GBAVideoGLRenderer { struct GBAVideoGLShader windowShader; struct GBAVideoGLShader finalizeShader; - bool useBindFragData; - GBARegisterDISPCNT dispcnt; unsigned target1Obj; diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index fdf2ffabc..4feddefbe 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -67,12 +67,6 @@ static const GLchar* const _gles3Header = "precision highp isampler2D;\n"; static const GLchar* const _gl3Header = - "#version 140 core\n" - "#define OUT(n)\n" - PALETTE_ENTRY - "precision highp float;\n"; - -static const GLchar* const _gl33Header = "#version 330 core\n" "#define OUT(n) layout(location = n)\n" PALETTE_ENTRY @@ -690,7 +684,7 @@ void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) { renderer->scale = 1; } -static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, const char* const* outFrags, char* log) { +static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, char* log) { GLuint program = glCreateProgram(); shader->program = program; @@ -703,12 +697,6 @@ static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVide if (log[0]) { mLOG(GBA_VIDEO, ERROR, "Fragment shader compilation failure: %s", log); } - size_t i; - if (glRenderer->useBindFragData) { - for (i = 0; outFrags[i]; ++i) { - glBindFragDataLocation(program, i, outFrags[i]); - } - } glLinkProgram(program); glGetProgramInfoLog(program, 2048, 0, log); if (log[0]) { @@ -723,6 +711,7 @@ static void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVide glEnableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation, 2, GL_INT, GL_FALSE, 0, NULL); + size_t i; for (i = 0; uniforms[i].name; ++i) { shader->uniforms[uniforms[i].type] = glGetUniformLocation(program, uniforms[i].name); } @@ -827,15 +816,9 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { char log[2048]; const GLchar* shaderBuffer[4]; - glRenderer->useBindFragData = false; const GLubyte* version = glGetString(GL_VERSION); if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES ")) != 0) { - if (strncmp((const char*) version, "3.3 ", strlen("3.3 ")) == 0 || strncmp((const char*) version, "4.", strlen("4.")) == 0) { - shaderBuffer[0] = _gl33Header; - } else { - shaderBuffer[0] = _gl3Header; - glRenderer->useBindFragData = true; - } + shaderBuffer[0] = _gl3Header; } else { shaderBuffer[0] = _gles3Header; } @@ -848,53 +831,47 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) { if (log[0]) { mLOG(GBA_VIDEO, ERROR, "Vertex shader compilation failure: %s", log); } - - const char* const noWindow[] = {"color", "flags", NULL}; - const char* const window[] = {"color", "flags", "window", NULL}; - const char* const onlyWindow[] = {"window", NULL}; - const char* const onlyColor[] = {"color", NULL}; - shaderBuffer[1] = _renderMode0; shaderBuffer[2] = _renderTile16; - _compileShader(glRenderer, &glRenderer->bgShader[0], shaderBuffer, 3, vs, _uniformsMode0, noWindow, log); + _compileShader(glRenderer, &glRenderer->bgShader[0], shaderBuffer, 3, vs, _uniformsMode0, log); shaderBuffer[2] = _renderTile256; - _compileShader(glRenderer, &glRenderer->bgShader[1], shaderBuffer, 3, vs, _uniformsMode0, noWindow, log); + _compileShader(glRenderer, &glRenderer->bgShader[1], shaderBuffer, 3, vs, _uniformsMode0, log); shaderBuffer[1] = _renderMode2; shaderBuffer[2] = _interpolate; shaderBuffer[3] = _fetchTileOverflow; - _compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 4, vs, _uniformsMode2, noWindow, log); + _compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 4, vs, _uniformsMode2, log); shaderBuffer[3] = _fetchTileNoOverflow; - _compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 4, vs, _uniformsMode2, noWindow, log); + _compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 4, vs, _uniformsMode2, log); shaderBuffer[1] = _renderMode4; shaderBuffer[2] = _interpolate; - _compileShader(glRenderer, &glRenderer->bgShader[4], shaderBuffer, 3, vs, _uniformsMode4, noWindow, log); + _compileShader(glRenderer, &glRenderer->bgShader[4], shaderBuffer, 3, vs, _uniformsMode4, log); shaderBuffer[1] = _renderMode35; shaderBuffer[2] = _interpolate; - _compileShader(glRenderer, &glRenderer->bgShader[5], shaderBuffer, 3, vs, _uniformsMode35, noWindow, log); + _compileShader(glRenderer, &glRenderer->bgShader[5], shaderBuffer, 3, vs, _uniformsMode35, log); shaderBuffer[1] = _renderObj; shaderBuffer[2] = _renderTile16; - _compileShader(glRenderer, &glRenderer->objShader[0], shaderBuffer, 3, vs, _uniformsObj, window, log); + _compileShader(glRenderer, &glRenderer->objShader[0], shaderBuffer, 3, vs, _uniformsObj, log); shaderBuffer[2] = _renderTile256; - _compileShader(glRenderer, &glRenderer->objShader[1], shaderBuffer, 3, vs, _uniformsObj, window, log); + _compileShader(glRenderer, &glRenderer->objShader[1], shaderBuffer, 3, vs, _uniformsObj, log); shaderBuffer[1] = _renderObjPriority; - _compileShader(glRenderer, &glRenderer->objShader[2], shaderBuffer, 2, vs, _uniformsObjPriority, noWindow, log); + _compileShader(glRenderer, &glRenderer->objShader[2], shaderBuffer, 2, vs, _uniformsObjPriority, log); shaderBuffer[1] = _renderWindow; - _compileShader(glRenderer, &glRenderer->windowShader, shaderBuffer, 2, vs, _uniformsWindow, onlyWindow, log); + _compileShader(glRenderer, &glRenderer->windowShader, shaderBuffer, 2, vs, _uniformsWindow, log); shaderBuffer[1] = _finalize; - _compileShader(glRenderer, &glRenderer->finalizeShader, shaderBuffer, 2, vs, _uniformsFinalize, onlyColor, log); + _compileShader(glRenderer, &glRenderer->finalizeShader, shaderBuffer, 2, vs, _uniformsFinalize, log); glBindVertexArray(0); glDeleteShader(vs); diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 458cfd3a6..0914a65f4 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -35,7 +35,7 @@ Display* Display::create(QWidget* parent) { if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { format.setVersion(2, 0); } else { - format.setVersion(3, 2); + format.setVersion(3, 3); } format.setProfile(QSurfaceFormat::CoreProfile); if (DisplayGL::supportsFormat(format)) { From 24a41d8453e597f2725ebbfe93cf096fcd44061d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 18:13:34 -0800 Subject: [PATCH 10/21] Qt: Different frame time overheads per OS --- src/platform/qt/DisplayGL.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index a7a12a18d..2c4182abb 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -32,6 +32,12 @@ #endif #endif +#ifdef _WIN32 +#define OVERHEAD_NSEC 1000000 +#else +#define OVERHEAD_NSEC 300000 +#endif + using namespace QGBA; QHash DisplayGL::s_supports; @@ -612,13 +618,13 @@ void PainterGL::draw() { m_delayTimer.start(); } else { if (sync->audioWait || sync->videoFrameWait) { - while (m_delayTimer.nsecsElapsed() + 300000 < 1000000000 / sync->fpsTarget) { + while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) { QThread::usleep(500); } forceRedraw = sync->videoFrameWait; } if (!forceRedraw) { - forceRedraw = m_delayTimer.nsecsElapsed() + 300000 >= 1000000000 / m_window->screen()->refreshRate(); + forceRedraw = m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC >= 1000000000 / m_window->screen()->refreshRate(); } } mCoreSyncWaitFrameEnd(sync); From e3ea64ad65dbdc786861156b26e9a7b205411f13 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 18:34:50 -0800 Subject: [PATCH 11/21] Qt: Use MenuRole with QActions --- src/platform/qt/Action.h | 9 +++++++++ src/platform/qt/ActionMapper.cpp | 16 +++++++++++++++- src/platform/qt/Window.cpp | 9 +++------ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/platform/qt/Action.h b/src/platform/qt/Action.h index 3d1e24c5f..9fe2f6aa2 100644 --- a/src/platform/qt/Action.h +++ b/src/platform/qt/Action.h @@ -17,6 +17,12 @@ Q_OBJECT public: typedef std::function Function; typedef std::function BooleanFunction; + enum class Role { + NO_ROLE = 0, + ABOUT, + SETTINGS, + QUIT, + }; Action(Function, const QString& name, const QString& visibleName, QObject* parent = nullptr); Action(BooleanFunction, const QString& name, const QString& visibleName, QObject* parent = nullptr); @@ -44,8 +50,10 @@ public: bool isEnabled() const { return m_enabled; } bool isActive() const { return m_active; } bool isExclusive() const { return m_exclusive; } + Role role() const { return m_role; } void setExclusive(bool exclusive = true) { m_exclusive = exclusive; } + void setRole(Role role) { m_role = role; } Action& operator=(const Action&); @@ -62,6 +70,7 @@ private: bool m_enabled = true; bool m_active = false; bool m_exclusive = false; + Role m_role = Role::NO_ROLE; Function m_function; BooleanFunction m_booleanFunction; diff --git a/src/platform/qt/ActionMapper.cpp b/src/platform/qt/ActionMapper.cpp index a0d80a0d1..e0166d5aa 100644 --- a/src/platform/qt/ActionMapper.cpp +++ b/src/platform/qt/ActionMapper.cpp @@ -79,6 +79,20 @@ void ActionMapper::rebuildMenu(const QString& menu, QMenu* qmenu, QWidget* conte } else if (!m_defaultShortcuts[actionName].isEmpty()) { qaction->setShortcut(m_defaultShortcuts[actionName][0]); } + switch (action->role()) { + case Action::Role::NO_ROLE: + qaction->setMenuRole(QAction::NoRole); + break; + case Action::Role::SETTINGS: + qaction->setMenuRole(QAction::PreferencesRole); + break; + case Action::Role::ABOUT: + qaction->setMenuRole(QAction::AboutRole); + break; + case Action::Role::QUIT: + qaction->setMenuRole(QAction::QuitRole); + break; + } QObject::connect(qaction, &QAction::triggered, [qaction, action](bool enabled) { if (qaction->isCheckable()) { action->trigger(enabled); @@ -172,4 +186,4 @@ Action* ActionMapper::getAction(const QString& itemName) { QKeySequence ActionMapper::defaultShortcut(const QString& itemName) { return m_defaultShortcuts[itemName]; -} \ No newline at end of file +} diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index be78a8fb9..ece85c851 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1311,11 +1311,8 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addSeparator("file"); #endif - m_actions.addAction(tr("About..."), "about", openTView(), "file"); - -#ifndef Q_OS_MAC - m_actions.addAction(tr("E&xit"), "quit", static_cast(this), &QWidget::close, "file", QKeySequence::Quit); -#endif + m_actions.addAction(tr("About..."), "about", openTView(), "file")->setRole(Action::Role::ABOUT); + m_actions.addAction(tr("E&xit"), "quit", static_cast(this), &QWidget::close, "file", QKeySequence::Quit)->setRole(Action::Role::SETTINGS); m_actions.addMenu(tr("&Emulation"), "emu"); addGameAction(tr("&Reset"), "reset", &CoreController::reset, "emu", QKeySequence("Ctrl+R")); @@ -1579,7 +1576,7 @@ void Window::setupMenu(QMenuBar* menubar) { addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView(), "tools"); m_actions.addSeparator("tools"); - m_actions.addAction(tr("Settings..."), "settings", this, &Window::openSettingsWindow, "tools"); + m_actions.addAction(tr("Settings..."), "settings", this, &Window::openSettingsWindow, "tools")->setRole(Action::Role::SETTINGS); m_actions.addAction(tr("Make portable"), "makePortable", this, &Window::tryMakePortable, "tools"); #ifdef USE_DEBUGGERS From a3ccb49e5d628fb6636702f296d571368ecc201a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 18:39:48 -0800 Subject: [PATCH 12/21] Qt: Update year --- src/platform/qt/AboutScreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/AboutScreen.cpp b/src/platform/qt/AboutScreen.cpp index e3ca55d95..1bf672f98 100644 --- a/src/platform/qt/AboutScreen.cpp +++ b/src/platform/qt/AboutScreen.cpp @@ -74,7 +74,7 @@ AboutScreen::AboutScreen(QWidget* parent) { QString copyright = m_ui.copyright->text(); - copyright.replace("{year}", tr("2021")); + copyright.replace("{year}", QLatin1String("2022")); m_ui.copyright->setText(copyright); } } From d4f1838a4afe5e54427b701657a5c4521ce2840e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 19:19:37 -0800 Subject: [PATCH 13/21] Qt: Don't use bind with templated return type when void needed --- src/platform/qt/ActionMapper.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/ActionMapper.h b/src/platform/qt/ActionMapper.h index f99290bd2..b71f1fbda 100644 --- a/src/platform/qt/ActionMapper.h +++ b/src/platform/qt/ActionMapper.h @@ -73,7 +73,9 @@ private: template Action* ActionMapper::addAction(const QString& visibleName, const QString& name, T* obj, V (T::*method)(), const QString& menu, const QKeySequence& shortcut) { - return addAction(visibleName, name, std::bind(method, obj), menu, shortcut); + return addAction(visibleName, name, [method, obj]() -> void { + (obj->*method)(); + }, menu, shortcut); } template From 27809bd1a390f7a8ef4edcb7579db68485c1047c Mon Sep 17 00:00:00 2001 From: aldelaro5 Date: Sat, 25 Dec 2021 01:05:07 -0500 Subject: [PATCH 14/21] GDB Stub: use only hardware breakpoints regardless of GDB's request This will ignore GDB's request for a software breakpoint as they can be very unreliable in determining whether it should be thumb or ARM. --- src/debugger/gdb-stub.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 23cc52396..e2435b20f 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -487,7 +487,6 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) { unsigned i = 0; uint32_t address = _readHex(readAddress, &i); readAddress += i + 1; - uint32_t kind = _readHex(readAddress, &i); struct mBreakpoint breakpoint = { .address = address, @@ -499,8 +498,6 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) { switch (message[0]) { case '0': - ARMDebuggerSetSoftwareBreakpoint(stub->d.platform, address, kind == 2 ? MODE_THUMB : MODE_ARM); - break; case '1': stub->d.platform->setBreakpoint(stub->d.platform, &breakpoint); break; From 8564f5fef4beb7135d497396cd20a3c8d28f3057 Mon Sep 17 00:00:00 2001 From: aldelaro5 Date: Thu, 30 Dec 2021 16:48:42 -0500 Subject: [PATCH 15/21] GDB Stub: Add target descriptior and memory map xml This allows gdb to know the memory mapping as well as the architecture once connected. It also allows us to not send dummy data for the fpr because we explicitly do not mention them. --- include/mgba/internal/debugger/gdb-stub.h | 1 + src/debugger/gdb-stub.c | 97 ++++++++++++++++++++--- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/include/mgba/internal/debugger/gdb-stub.h b/include/mgba/internal/debugger/gdb-stub.h index 5743834a2..a617bc558 100644 --- a/include/mgba/internal/debugger/gdb-stub.h +++ b/include/mgba/internal/debugger/gdb-stub.h @@ -29,6 +29,7 @@ struct GDBStub { char line[GDB_STUB_MAX_LINE]; char outgoing[GDB_STUB_MAX_LINE]; + char memoryMapXml[GDB_STUB_MAX_LINE]; enum GDBStubAckState lineAck; Socket socket; diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index e2435b20f..7069c7f75 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -29,6 +30,30 @@ enum { MACH_O_ARM_V4T = 5 }; +static const char* TARGET_XML = "" + "armv4t" + "none" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + static void _sendMessage(struct GDBStub* stub); static void _gdbStubDeinit(struct mDebugger* debugger) { @@ -336,16 +361,6 @@ static void _readGPRs(struct GDBStub* stub, const char* message) { _int2hex32(cpu->gprs[ARM_PC] - (cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM), &stub->outgoing[i]); i += 8; - // Floating point registers, unused on the GBA (8 of them, 24 bits each) - for (r = 0; r < 8 * 3; ++r) { - _int2hex32(0, &stub->outgoing[i]); - i += 8; - } - - // Floating point status, unused on the GBA (32 bits) - _int2hex32(0, &stub->outgoing[i]); - i += 8; - // CPU status _int2hex32(cpu->cpsr.packed, &stub->outgoing[i]); i += 8; @@ -433,7 +448,58 @@ static void _processQSupportedCommand(struct GDBStub* stub, const char* message) } message = end + 1; } - strncpy(stub->outgoing, "swbreak+;hwbreak+", GDB_STUB_MAX_LINE - 4); + strncpy(stub->outgoing, "swbreak+;hwbreak+;qXfer:features:read+;qXfer:memory-map:read+", GDB_STUB_MAX_LINE - 4); +} + +static void _processQXferCommand(struct GDBStub* stub, const char* params, const char* data) { + unsigned offset = 0; + unsigned length = 0; + + unsigned index = 0; + for (index = 0; params[index] != ','; ++index) { + offset <<= 4; + offset |= _hex2int(¶ms[index], 1); + } + + ++index; + unsigned int paramsLength = strlen(params); + for (; index + 3 < paramsLength; ++index) { + length <<= 4; + length |= _hex2int(¶ms[index], 1); + } + + length += 1; + + if (length + 4 > GDB_STUB_MAX_LINE) { + length = GDB_STUB_MAX_LINE - 4; + } + + if (strlen(data) < length + offset) { + length = strlen(data) - offset + 1; + stub->outgoing[0] = 'l'; + } else { + stub->outgoing[0] = 'm'; + } + strlcpy(&stub->outgoing[1], &data[offset], length); +} + +static void _generateMemoryMapXml(struct GDBStub* stub, char* memoryMap) { + size_t index = 0; + strncpy(memoryMap, "", 27); + index += strlen(memoryMap); + const struct mCoreMemoryBlock* blocks; + size_t nBlocks = stub->d.core->listMemoryBlocks(stub->d.core, &blocks); + size_t i; + for (i = 0; i < nBlocks; ++i) { + if (!(blocks[i].flags & mCORE_MEMORY_MAPPED)) { + continue; + } + + const char* type = blocks[i].flags & (mCORE_MEMORY_WRITE | mCORE_MEMORY_WORM) ? "ram" : "rom"; + index += snprintf(&memoryMap[index], GDB_STUB_MAX_LINE - index, "", type, blocks[i].start, blocks[i].size); + } + int amountLeft = GDB_STUB_MAX_LINE - index; + strncpy(&memoryMap[index], "", amountLeft); } static void _processQReadCommand(struct GDBStub* stub, const char* message) { @@ -452,6 +518,13 @@ static void _processQReadCommand(struct GDBStub* stub, const char* message) { strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4); } else if (!strncmp("sThreadInfo#", message, 12)) { strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4); + } else if (!strncmp("Xfer:features:read:target.xml:", message, 30)) { + _processQXferCommand(stub, message + 30, TARGET_XML); + } else if (!strncmp("Xfer:memory-map:read::", message, 22)) { + if (strlen(stub->memoryMapXml) == 0) { + _generateMemoryMapXml(stub, stub->memoryMapXml); + } + _processQXferCommand(stub, message + 22, stub->memoryMapXml); } else if (!strncmp("Supported:", message, 10)) { _processQSupportedCommand(stub, message + 10); } @@ -707,6 +780,8 @@ bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAdd goto cleanup; } + memset(stub->memoryMapXml, 0, GDB_STUB_MAX_LINE); + return true; cleanup: From b12717837753557189ad05e1a56afd222d0cccea Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 21:34:23 -0800 Subject: [PATCH 16/21] GB: More selective savegame dirt, unify logic --- include/mgba/internal/defines.h | 31 ++++++++++++++++++++++++++++ include/mgba/internal/gb/gb.h | 2 +- include/mgba/internal/gb/memory.h | 5 ----- include/mgba/internal/gba/savedata.h | 7 +------ src/gb/gb.c | 12 ++--------- src/gb/mbc.c | 8 ++++++- src/gb/memory.c | 9 +++++--- src/gba/memory.c | 5 +++-- src/gba/savedata.c | 17 ++++++--------- 9 files changed, 57 insertions(+), 39 deletions(-) create mode 100644 include/mgba/internal/defines.h diff --git a/include/mgba/internal/defines.h b/include/mgba/internal/defines.h new file mode 100644 index 000000000..8af603736 --- /dev/null +++ b/include/mgba/internal/defines.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2013-2022 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 M_INTERNAL_DEFINES_H +#define M_INTERNAL_DEFINES_H + +#define mSAVEDATA_CLEANUP_THRESHOLD 15 + +enum { + mSAVEDATA_DIRT_NONE = 0, + mSAVEDATA_DIRT_NEW = 1, + mSAVEDATA_DIRT_SEEN = 2, +}; + +static inline bool mSavedataClean(int* dirty, uint32_t* dirtAge, uint32_t frameCount) { + if (*dirty & mSAVEDATA_DIRT_NEW) { + *dirtAge = frameCount; + *dirty &= ~mSAVEDATA_DIRT_NEW; + if (!(*dirty & mSAVEDATA_DIRT_SEEN)) { + *dirty |= mSAVEDATA_DIRT_SEEN; + } + } else if ((*dirty & mSAVEDATA_DIRT_SEEN) && frameCount - *dirtAge > mSAVEDATA_CLEANUP_THRESHOLD) { + *dirty = 0; + return true; + } + return false; +} + +#endif diff --git a/include/mgba/internal/gb/gb.h b/include/mgba/internal/gb/gb.h index d0e59e07e..a0196c162 100644 --- a/include/mgba/internal/gb/gb.h +++ b/include/mgba/internal/gb/gb.h @@ -102,7 +102,7 @@ struct GB { struct VFile* sramRealVf; uint32_t sramSize; int sramDirty; - int32_t sramDirtAge; + uint32_t sramDirtAge; bool sramMaskWriteback; int sgbBit; diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index a9dc8b7e9..29ffb6960 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -65,11 +65,6 @@ enum { GB_SIZE_MBC6_FLASH = 0x100000, }; -enum { - GB_SRAM_DIRT_NEW = 1, - GB_SRAM_DIRT_SEEN = 2 -}; - struct GBMemory; typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_t value); typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address); diff --git a/include/mgba/internal/gba/savedata.h b/include/mgba/internal/gba/savedata.h index 395e06b7a..7f0b84c83 100644 --- a/include/mgba/internal/gba/savedata.h +++ b/include/mgba/internal/gba/savedata.h @@ -60,11 +60,6 @@ enum FlashManufacturer { FLASH_MFG_SANYO = 0x1362 }; -enum SavedataDirty { - SAVEDATA_DIRT_NEW = 1, - SAVEDATA_DIRT_SEEN = 2 -}; - enum { SAVEDATA_FLASH_BASE = 0x0E005555, @@ -92,7 +87,7 @@ struct GBASavedata { unsigned settling; struct mTimingEvent dust; - enum SavedataDirty dirty; + int dirty; uint32_t dirtAge; enum FlashStateMachine flashState; diff --git a/src/gb/gb.c b/src/gb/gb.c index b94d7f482..79a5d60b0 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include #include #include @@ -17,8 +18,6 @@ #include #include -#define CLEANUP_THRESHOLD 15 - const uint32_t CGB_SM83_FREQUENCY = 0x800000; const uint32_t SGB_SM83_FREQUENCY = 0x418B1E; @@ -233,20 +232,13 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) { if (!gb->sramVf) { return; } - if (gb->sramDirty & GB_SRAM_DIRT_NEW) { - gb->sramDirtAge = frameCount; - gb->sramDirty &= ~GB_SRAM_DIRT_NEW; - if (!(gb->sramDirty & GB_SRAM_DIRT_SEEN)) { - gb->sramDirty |= GB_SRAM_DIRT_SEEN; - } - } else if ((gb->sramDirty & GB_SRAM_DIRT_SEEN) && frameCount - gb->sramDirtAge > CLEANUP_THRESHOLD) { + if (mSavedataClean(&gb->sramDirty, &gb->sramDirtAge, frameCount)) { if (gb->sramMaskWriteback) { GBSavedataUnmask(gb); } if (gb->memory.mbcType == GB_MBC3_RTC) { GBMBCRTCWrite(gb); } - gb->sramDirty = 0; if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) { mLOG(GB_MEM, INFO, "Savedata synced"); } else { diff --git a/src/gb/mbc.c b/src/gb/mbc.c index bd8ad46f0..de6f7802d 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -6,9 +6,10 @@ #include #include -#include +#include #include #include +#include #include #include @@ -615,6 +616,7 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) { address &= 0x1FF; memory->sramBank[(address >> 1)] &= 0xF0 >> shift; memory->sramBank[(address >> 1)] |= (value & 0xF) << shift; + gb->sramDirty |= mSAVEDATA_DIRT_NEW; break; default: // TODO @@ -776,6 +778,7 @@ void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) { case 0x2B: if (memory->sramAccess) { memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM_HALFBANK - 1)] = value; + gb->sramDirty |= mSAVEDATA_DIRT_NEW; } break; case 0x2C: @@ -841,6 +844,7 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) { break; case 0x5: _GBMBC7Write(&gb->memory, address, value); + gb->sramDirty |= mSAVEDATA_DIRT_NEW; break; default: // TODO @@ -1163,6 +1167,7 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) { address &= 0x7F; if (address == 0 && value & 1) { value &= 6; // TODO: Timing + gb->sramDirty |= mSAVEDATA_DIRT_NEW; _GBPocketCamCapture(memory); } if (address < sizeof(memory->mbcState.pocketCam.registers)) { @@ -1287,6 +1292,7 @@ void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) { switch (tama5->registers[GBTAMA5_CS] >> 1) { case 0x0: // RAM write memory->sram[address] = out; + gb->sramDirty |= mSAVEDATA_DIRT_NEW; break; case 0x1: // RAM read break; diff --git a/src/gb/memory.c b/src/gb/memory.c index 0ec280aa1..175950e82 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -356,11 +357,13 @@ void GBStore8(struct SM83Core* cpu, uint16_t address, int8_t value) { if (memory->rtcAccess) { memory->rtcRegs[memory->activeRtcReg] = value; } else if (memory->sramAccess && memory->sram && memory->directSramAccess) { - memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value; + if (memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] != value) { + memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value; + gb->sramDirty |= mSAVEDATA_DIRT_NEW; + } } else { memory->mbcWrite(gb, address, value); } - gb->sramDirty |= GB_SRAM_DIRT_NEW; return; case GB_REGION_WORKING_RAM_BANK0: case GB_REGION_WORKING_RAM_BANK0 + 2: @@ -648,7 +651,7 @@ void GBPatch8(struct SM83Core* cpu, uint16_t address, int8_t value, int8_t* old, } else { memory->mbcWrite(gb, address, value); } - gb->sramDirty |= GB_SRAM_DIRT_NEW; + gb->sramDirty |= mSAVEDATA_DIRT_NEW; return; case GB_REGION_WORKING_RAM_BANK0: case GB_REGION_WORKING_RAM_BANK0 + 2: diff --git a/src/gba/memory.c b/src/gba/memory.c index 039e1f8f8..fddef9223 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -1075,12 +1076,12 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo } else { memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value; } - memory->savedata.dirty |= SAVEDATA_DIRT_NEW; + memory->savedata.dirty |= mSAVEDATA_DIRT_NEW; } else if (memory->hw.devices & HW_TILT) { GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value); } else if (memory->savedata.type == SAVEDATA_SRAM512) { memory->savedata.data[address & (SIZE_CART_SRAM512 - 1)] = value; - memory->savedata.dirty |= SAVEDATA_DIRT_NEW; + memory->savedata.dirty |= mSAVEDATA_DIRT_NEW; } else { mLOG(GBA_MEM, GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address); } diff --git a/src/gba/savedata.c b/src/gba/savedata.c index fbae0a554..182a6cc30 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -24,7 +25,6 @@ #define FLASH_PROGRAM_CYCLES 650 // This needs real testing, and is only an estimation currently #define EEPROM_SETTLE_CYCLES 115000 -#define CLEANUP_THRESHOLD 15 mLOG_DEFINE_CATEGORY(GBA_SAVE, "GBA Savedata", "gba.savedata"); @@ -379,7 +379,7 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8 case FLASH_STATE_RAW: switch (savedata->command) { case FLASH_COMMAND_PROGRAM: - savedata->dirty |= SAVEDATA_DIRT_NEW; + savedata->dirty |= mSAVEDATA_DIRT_NEW; savedata->currentBank[address] = value; savedata->command = FLASH_COMMAND_NONE; mTimingDeschedule(savedata->timing, &savedata->dust); @@ -511,7 +511,7 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32 uint8_t current = savedata->data[savedata->writeAddress >> 3]; current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7))); current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7)); - savedata->dirty |= SAVEDATA_DIRT_NEW; + savedata->dirty |= mSAVEDATA_DIRT_NEW; savedata->data[savedata->writeAddress >> 3] = current; mTimingDeschedule(savedata->timing, &savedata->dust); mTimingSchedule(savedata->timing, &savedata->dust, EEPROM_SETTLE_CYCLES); @@ -565,15 +565,10 @@ void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) { if (!savedata->vf) { return; } - if (savedata->dirty & SAVEDATA_DIRT_NEW) { - savedata->dirtAge = frameCount; - savedata->dirty &= ~SAVEDATA_DIRT_NEW; - savedata->dirty |= SAVEDATA_DIRT_SEEN; - } else if ((savedata->dirty & SAVEDATA_DIRT_SEEN) && frameCount - savedata->dirtAge > CLEANUP_THRESHOLD) { + if (mSavedataClean(&savedata->dirty, &savedata->dirtAge, frameCount)) { if (savedata->maskWriteback) { GBASavedataUnmask(savedata); } - savedata->dirty = 0; if (savedata->mapMode & MAP_WRITE) { size_t size = GBASavedataSize(savedata); if (savedata->data && savedata->vf->sync(savedata->vf, savedata->data, size)) { @@ -650,7 +645,7 @@ void _flashSwitchBank(struct GBASavedata* savedata, int bank) { void _flashErase(struct GBASavedata* savedata) { mLOG(GBA_SAVE, DEBUG, "Performing flash chip erase"); - savedata->dirty |= SAVEDATA_DIRT_NEW; + savedata->dirty |= mSAVEDATA_DIRT_NEW; size_t size = SIZE_CART_FLASH512; if (savedata->type == SAVEDATA_FLASH1M) { size = SIZE_CART_FLASH1M; @@ -660,7 +655,7 @@ void _flashErase(struct GBASavedata* savedata) { void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) { mLOG(GBA_SAVE, DEBUG, "Performing flash sector erase at 0x%04x", sectorStart); - savedata->dirty |= SAVEDATA_DIRT_NEW; + savedata->dirty |= mSAVEDATA_DIRT_NEW; size_t size = 0x1000; if (savedata->type == SAVEDATA_FLASH1M) { mLOG(GBA_SAVE, DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart); From ee68e9742de3acab9dd0f221cf5a52d3df2defca Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 22:15:03 -0800 Subject: [PATCH 17/21] GB: Fix temporary saves --- CHANGES | 1 + src/gb/gb.c | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index d45fafc7e..2a601ccb8 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Emulation fixes: Other fixes: - Core: Don't attempt to restore rewind diffs past start of rewind - FFmpeg: Fix crash when encoding audio with some containers + - GB: Fix temporary saves Misc: - Core: Suspend runloop when a core crashes - GB Video: Add default SGB border diff --git a/src/gb/gb.c b/src/gb/gb.c index 79a5d60b0..a350f4067 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -202,6 +202,14 @@ void GBResizeSram(struct GB* gb, size_t size) { if (gb->memory.sram) { vf->unmap(vf, gb->memory.sram, gb->sramSize); } + if (vf->size(vf) < gb->sramSize) { + void* sram = vf->map(vf, vf->size(vf), MAP_READ); + struct VFile* newVf = VFileMemChunk(sram, vf->size(vf)); + vf->unmap(vf, sram,vf->size(vf)); + vf = newVf; + gb->sramVf = newVf; + vf->truncate(vf, size); + } gb->memory.sram = vf->map(vf, size, MAP_READ); } if (gb->memory.sram == (void*) -1) { @@ -239,10 +247,12 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) { if (gb->memory.mbcType == GB_MBC3_RTC) { GBMBCRTCWrite(gb); } - if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) { - mLOG(GB_MEM, INFO, "Savedata synced"); - } else { - mLOG(GB_MEM, INFO, "Savedata failed to sync!"); + if (gb->sramVf == gb->sramRealVf) { + if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) { + mLOG(GB_MEM, INFO, "Savedata synced"); + } else { + mLOG(GB_MEM, INFO, "Savedata failed to sync!"); + } } size_t c; @@ -263,7 +273,7 @@ void GBSavedataMask(struct GB* gb, struct VFile* vf, bool writeback) { } gb->sramVf = vf; gb->sramMaskWriteback = writeback; - gb->memory.sram = vf->map(vf, gb->sramSize, MAP_READ); + GBResizeSram(gb, gb->sramSize); GBMBCSwitchSramBank(gb, gb->memory.sramCurrentBank); } From fff87985fe30b5e4007986018588a72b589c74f2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 22:27:15 -0800 Subject: [PATCH 18/21] GB, GBA: Save writeback-pending masked saves on unload (fixes #2396) --- CHANGES | 1 + src/gb/gb.c | 4 +++- src/gba/gba.c | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 2a601ccb8..8c6dd7b28 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Other fixes: - Core: Don't attempt to restore rewind diffs past start of rewind - FFmpeg: Fix crash when encoding audio with some containers - GB: Fix temporary saves + - GB, GBA: Save writeback-pending masked saves on unload (fixes mgba.io/i/2396) Misc: - Core: Suspend runloop when a core crashes - GB Video: Add default SGB border diff --git a/src/gb/gb.c b/src/gb/gb.c index a350f4067..43f5d93a0 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -318,7 +318,9 @@ void GBUnloadROM(struct GB* gb) { gb->memory.mbcType = GB_MBC_AUTODETECT; gb->isPristine = false; - gb->sramMaskWriteback = false; + if (!gb->sramDirty) { + gb->sramMaskWriteback = false; + } GBSavedataUnmask(gb); GBSramDeinit(gb); if (gb->sramRealVf) { diff --git a/src/gba/gba.c b/src/gba/gba.c index 16d02fd2c..86b597dc2 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -149,7 +149,9 @@ void GBAUnloadROM(struct GBA* gba) { gba->memory.rom = NULL; gba->isPristine = false; - gba->memory.savedata.maskWriteback = false; + if (!gba->memory.savedata.dirty) { + gba->memory.savedata.maskWriteback = false; + } GBASavedataUnmask(&gba->memory.savedata); GBASavedataDeinit(&gba->memory.savedata); if (gba->memory.savedata.realVf) { From 8a310dcfed11b644b75614d74f8bb819e65283d2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Jan 2022 23:49:56 -0800 Subject: [PATCH 19/21] FFmpeg: Fix GIF recording (fixes #2393) --- CHANGES | 1 + src/feature/ffmpeg/ffmpeg-encoder.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8c6dd7b28..287bc4d9d 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Emulation fixes: Other fixes: - Core: Don't attempt to restore rewind diffs past start of rewind - FFmpeg: Fix crash when encoding audio with some containers + - FFmpeg: Fix GIF recording (fixes mgba.io/i/2393) - GB: Fix temporary saves - GB, GBA: Save writeback-pending masked saves on unload (fixes mgba.io/i/2396) Misc: diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 55c874692..ad76ca57b 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -785,7 +785,7 @@ 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); if (encoder->graph) { - if (av_buffersrc_add_frame(encoder->source, encoder->videoFrame) < 0) { + if (av_buffersrc_write_frame(encoder->source, encoder->videoFrame) < 0) { return; } while (true) { From b2c349a4a359b25d9fcc798bcd6e331c6dc606b0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 21 Jan 2022 20:56:43 -0800 Subject: [PATCH 20/21] Core: Passing -b should force-enable BIOS --- src/feature/commandline.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/feature/commandline.c b/src/feature/commandline.c index 2d3f3e357..6cab35094 100644 --- a/src/feature/commandline.c +++ b/src/feature/commandline.c @@ -165,6 +165,7 @@ void applyArguments(const struct mArguments* args, struct mSubParser* subparser, } if (args->bios) { mCoreConfigSetOverrideValue(config, "bios", args->bios); + mCoreConfigSetOverrideIntValue(config, "useBios", true); } HashTableEnumerate(&args->configOverrides, _tableApply, config); if (subparser) { From bcb3e60f6738237d531e1b4354f7abee831d0df6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 21 Jan 2022 21:40:32 -0800 Subject: [PATCH 21/21] Qt: Make separate save games submenu --- src/platform/qt/Window.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index ece85c851..d9a25bbe5 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1197,12 +1197,25 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addAction(tr("Add folder to library..."), "addDirToLibrary", this, &Window::addDirToLibrary, "file"); #endif + m_actions.addMenu(tr("Save games"), "saves", "file"); addGameAction(tr("Load alternate save game..."), "loadAlternateSave", [this]() { this->selectSave(false); - }, "file"); + }, "saves"); addGameAction(tr("Load temporary save game..."), "loadTemporarySave", [this]() { this->selectSave(true); - }, "file"); + }, "saves"); + + m_actions.addSeparator("saves"); + + m_actions.addAction(tr("Convert save game..."), "convertSave", openControllerTView(), "saves"); + +#ifdef M_CORE_GBA + Action* importShark = addGameAction(tr("Import GameShark Save..."), "importShark", this, &Window::importSharkport, "saves"); + m_platformActions.insert(mPLATFORM_GBA, importShark); + + Action* exportShark = addGameAction(tr("Export GameShark Save..."), "exportShark", this, &Window::exportSharkport, "saves"); + m_platformActions.insert(mPLATFORM_GBA, exportShark); +#endif m_actions.addAction(tr("Load &patch..."), "loadPatch", this, &Window::selectPatch, "file"); @@ -1282,17 +1295,6 @@ void Window::setupMenu(QMenuBar* menubar) { m_nonMpActions.append(quickSave); } -#ifdef M_CORE_GBA - m_actions.addSeparator("file"); - m_actions.addAction(tr("Convert save game..."), "convertSave", openControllerTView(), "file"); - - Action* importShark = addGameAction(tr("Import GameShark Save..."), "importShark", this, &Window::importSharkport, "file"); - m_platformActions.insert(mPLATFORM_GBA, importShark); - - Action* exportShark = addGameAction(tr("Export GameShark Save..."), "exportShark", this, &Window::exportSharkport, "file"); - m_platformActions.insert(mPLATFORM_GBA, exportShark); -#endif - m_actions.addSeparator("file"); m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", [this]() { GBAApp::app()->newWindow();