all repos — mgba @ ef3cc7bd9f28d73fdff912587fc42260948d5b51

mGBA Game Boy Advance Emulator

Qt: Redo frame inspector using video logs
Vicki Pfau vi@endrift.com
Sat, 01 Jun 2019 14:31:51 -0700
commit

ef3cc7bd9f28d73fdff912587fc42260948d5b51

parent

59d2e58bbbd42746d2bc781fe8d8adfe1ff3588f

3 files changed, 178 insertions(+), 83 deletions(-)

jump to
M src/platform/qt/FrameView.cppsrc/platform/qt/FrameView.cpp

@@ -14,6 +14,8 @@ #include <cmath>

#include "CoreController.h" +#include <mgba/core/core.h> +#include <mgba/feature/video-logger.h> #ifdef M_CORE_GBA #include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/io.h>

@@ -37,7 +39,6 @@ connect(&m_glowTimer, &QTimer::timeout, this, [this]() {

++m_glowFrame; invalidateQueue(); }); - m_glowTimer.start(); m_ui.renderedView->installEventFilter(this); m_ui.compositedView->installEventFilter(this);

@@ -66,6 +67,15 @@

QPixmap rendered = m_rendered.scaledToHeight(m_rendered.height() * m_ui.magnification->value()); m_ui.renderedView->setPixmap(rendered); }); + m_controller->addFrameAction(std::bind(&FrameView::frameCallback, this, m_callbackLocker)); +} + +FrameView::~FrameView() { + QMutexLocker locker(&m_mutex); + *m_callbackLocker = false; + if (m_vl) { + m_vl->deinit(m_vl); + } } bool FrameView::lookupLayer(const QPointF& coord, Layer*& out) {

@@ -122,24 +132,26 @@ layer->enabled = false;

m_disabled.insert(layer->id); } +#ifdef M_CORE_GBA void FrameView::updateTilesGBA(bool force) { if (m_ui.freeze->checkState() == Qt::Checked) { return; } + QMutexLocker locker(&m_mutex); m_queue.clear(); { CoreController::Interrupter interrupter(m_controller); - updateRendered(); uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io; QRgb backdrop = M_RGB5_TO_RGB8(static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[0]); - int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]); + m_gbaDispcnt = io[REG_DISPCNT >> 1]; + int mode = GBARegisterDISPCNTGetMode(m_gbaDispcnt); std::array<bool, 4> enabled{ - bool(GBARegisterDISPCNTIsBg0Enable(io[REG_DISPCNT >> 1])), - bool(GBARegisterDISPCNTIsBg1Enable(io[REG_DISPCNT >> 1])), - bool(GBARegisterDISPCNTIsBg2Enable(io[REG_DISPCNT >> 1])), - bool(GBARegisterDISPCNTIsBg3Enable(io[REG_DISPCNT >> 1])), + bool(GBARegisterDISPCNTIsBg0Enable(m_gbaDispcnt)), + bool(GBARegisterDISPCNTIsBg1Enable(m_gbaDispcnt)), + bool(GBARegisterDISPCNTIsBg2Enable(m_gbaDispcnt)), + bool(GBARegisterDISPCNTIsBg3Enable(m_gbaDispcnt)), }; for (int priority = 0; priority < 4; ++priority) {

@@ -204,10 +216,53 @@ !m_disabled.contains({ LayerId::BACKDROP }),

QPixmap::fromImage(backdropImage), {}, {0, 0}, false }); + updateRendered(); } invalidateQueue(QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS)); } +void FrameView::injectGBA() { + mVideoLogger* logger = m_vl->videoLogger; + mVideoLoggerInjectionPoint(logger, LOGGER_INJECTION_FIRST_SCANLINE); + GBA* gba = static_cast<GBA*>(m_vl->board); + gba->video.renderer->highlightBG[0] = false; + gba->video.renderer->highlightBG[1] = false; + gba->video.renderer->highlightBG[2] = false; + gba->video.renderer->highlightBG[3] = false; + for (int i = 0; i < 128; ++i) { + gba->video.renderer->highlightOBJ[i] = false; + } + QPalette palette; + gba->video.renderer->highlightColor = palette.color(QPalette::HighlightedText).rgb(); + gba->video.renderer->highlightAmount = sin(m_glowFrame * M_PI / 30) * 64 + 64; + + for (const Layer& layer : m_queue) { + switch (layer.id.type) { + case LayerId::SPRITE: + if (!layer.enabled) { + mVideoLoggerInjectOAM(logger, layer.id.index << 2, 0x200); + } + if (layer.id == m_active) { + gba->video.renderer->highlightOBJ[layer.id.index] = true; + } + break; + case LayerId::BACKGROUND: + m_vl->enableVideoLayer(m_vl, layer.id.index, layer.enabled); + if (layer.id == m_active) { + gba->video.renderer->highlightBG[layer.id.index] = true; + } + break; + } + } + if (m_ui.disableScanline->checkState() == Qt::Checked) { + mVideoLoggerIgnoreAfterInjection(logger, (1 << DIRTY_PALETTE) | (1 << DIRTY_OAM) | (1 << DIRTY_REGISTER)); + } else { + mVideoLoggerIgnoreAfterInjection(logger, 0); + } +} +#endif + +#ifdef M_CORE_GB void FrameView::updateTilesGB(bool force) { if (m_ui.freeze->checkState() == Qt::Checked) { return;

@@ -220,23 +275,33 @@ }

invalidateQueue(m_controller->screenDimensions()); } +void FrameView::injectGB() { + for (const Layer& layer : m_queue) { + } +} +#endif + void FrameView::invalidateQueue(const QSize& dims) { if (dims.isValid()) { m_dims = dims; } bool blockSignals = m_ui.queue->blockSignals(true); - QPixmap composited(m_dims); - - QPainter painter(&composited); - QPalette palette; - QColor activeColor = palette.color(QPalette::HighlightedText); - activeColor.setAlpha(sin(m_glowFrame * M_PI / 60) * 16 + 96); - - QRectF rect(0, 0, m_dims.width(), m_dims.height()); - painter.setCompositionMode(QPainter::CompositionMode_Source); - painter.fillRect(rect, QColor(0, 0, 0, 0)); + QMutexLocker locker(&m_mutex); + if (m_vl) { + m_vl->reset(m_vl); + switch (m_controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + injectGBA(); +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + injectGB(); +#endif + } + m_vl->runFrame(m_vl); + } - painter.setCompositionMode(QPainter::CompositionMode_DestinationOver); for (int i = 0; i < m_queue.count(); ++i) { const Layer& layer = m_queue[i]; QListWidgetItem* item;

@@ -251,61 +316,20 @@ item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);

item->setCheckState(layer.enabled ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole, i); item->setSelected(layer.id == m_active); - - if (!layer.enabled) { - continue; - } - - QPointF location = layer.location; - QSizeF layerDims(layer.image.width(), layer.image.height()); - QRegion region; - if (layer.repeats) { - if (location.x() + layerDims.width() < 0) { - location.setX(std::fmod(location.x(), layerDims.width())); - } - if (location.y() + layerDims.height() < 0) { - location.setY(std::fmod(location.y(), layerDims.height())); - } - - if (layer.id == m_active) { - region = layer.mask.translated(location.x(), location.y()); - region += layer.mask.translated(location.x() + layerDims.width(), location.y()); - region += layer.mask.translated(location.x(), location.y() + layerDims.height()); - region += layer.mask.translated(location.x() + layerDims.width(), location.y() + layerDims.height()); - } - } else { - QRectF layerRect(location, layerDims); - if (!rect.intersects(layerRect)) { - continue; - } - if (layer.id == m_active) { - region = layer.mask.translated(location.x(), location.y()); - } - } - - if (layer.id == m_active) { - painter.setClipping(true); - painter.setClipRegion(region); - painter.fillRect(rect, activeColor); - painter.setClipping(false); - } - - if (layer.repeats) { - painter.drawPixmap(location, layer.image); - painter.drawPixmap(location + QPointF(layerDims.width(), 0), layer.image); - painter.drawPixmap(location + QPointF(0, layerDims.height()), layer.image); - painter.drawPixmap(location + QPointF(layerDims.width(), layerDims.height()), layer.image); - } else { - painter.drawPixmap(location, layer.image); - } } - painter.end(); while (m_ui.queue->count() > m_queue.count()) { delete m_ui.queue->takeItem(m_queue.count()); } m_ui.queue->blockSignals(blockSignals); + QPixmap composited; + if (m_framebuffer.isNull()) { + updateRendered(); + composited = m_rendered; + } else { + composited.convertFromImage(m_framebuffer); + } m_composited = composited.scaled(m_dims * m_ui.magnification->value()); m_ui.compositedView->setPixmap(m_composited); }

@@ -335,6 +359,53 @@ return true;

} return false; } + +void FrameView::refreshVl() { + QMutexLocker locker(&m_mutex); + m_currentFrame = m_nextFrame; + m_nextFrame = VFileMemChunk(nullptr, 0); + if (m_currentFrame) { + m_controller->endVideoLog(false); + VFile* currentFrame = VFileMemChunk(nullptr, m_currentFrame->size(m_currentFrame)); + void* buffer = currentFrame->map(currentFrame, m_currentFrame->size(m_currentFrame), MAP_WRITE); + m_currentFrame->seek(m_currentFrame, 0, SEEK_SET); + m_currentFrame->read(m_currentFrame, buffer, m_currentFrame->size(m_currentFrame)); + currentFrame->unmap(currentFrame, buffer, m_currentFrame->size(m_currentFrame)); + m_currentFrame = currentFrame; + QMetaObject::invokeMethod(this, "newVl"); + } + m_controller->endVideoLog(); + m_controller->startVideoLog(m_nextFrame, false); +} + +void FrameView::newVl() { + if (!m_glowTimer.isActive()) { + m_glowTimer.start(); + } + QMutexLocker locker(&m_mutex); + if (m_vl) { + m_vl->deinit(m_vl); + } + m_vl = mCoreFindVF(m_currentFrame); + m_vl->init(m_vl); + m_vl->loadROM(m_vl, m_currentFrame); + mCoreInitConfig(m_vl, nullptr); + unsigned width, height; + m_vl->desiredVideoDimensions(m_vl, &width, &height); + m_framebuffer = QImage(width, height, QImage::Format_RGBX8888); + m_vl->setVideoBuffer(m_vl, reinterpret_cast<color_t*>(m_framebuffer.bits()), width); + m_vl->reset(m_vl); +} + +void FrameView::frameCallback(FrameView* viewer, std::shared_ptr<bool> lock) { + if (!*lock) { + return; + } + CoreController::Interrupter interrupter(viewer->m_controller, true); + viewer->refreshVl(); + viewer->m_controller->addFrameAction(std::bind(&FrameView::frameCallback, viewer, lock)); +} + QString FrameView::LayerId::readable() const { QString typeStr;
M src/platform/qt/FrameView.hsrc/platform/qt/FrameView.h

@@ -10,14 +10,19 @@

#include <QBitmap> #include <QImage> #include <QList> +#include <QMutex> #include <QPixmap> #include <QSet> #include <QTimer> #include "AssetView.h" +#include <mgba-util/vfs.h> + #include <memory> +struct VFile; + namespace QGBA { class CoreController;

@@ -27,6 +32,7 @@ Q_OBJECT

public: FrameView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); + ~FrameView(); public slots: void selectLayer(const QPointF& coord);

@@ -35,16 +41,20 @@

protected: #ifdef M_CORE_GBA void updateTilesGBA(bool force) override; + void injectGBA(); #endif #ifdef M_CORE_GB void updateTilesGB(bool force) override; + void injectGB(); #endif bool eventFilter(QObject* obj, QEvent* event) override; private slots: - void invalidateQueue(const QSize& dims = QSize()); + void invalidateQueue(const QSize& = {}); void updateRendered(); + void refreshVl(); + void newVl(); private: struct LayerId {

@@ -57,7 +67,7 @@ BACKDROP

} type = NONE; int index = -1; - bool operator==(const LayerId& other) const { return other.type == type && other.index == index; } + bool operator!=(const LayerId& other) const { return other.type != type || other.index != index; } operator uint() const { return (type << 8) | index; } QString readable() const; };

@@ -73,6 +83,8 @@ };

bool lookupLayer(const QPointF& coord, Layer*&); + static void frameCallback(FrameView*, std::shared_ptr<bool>); + Ui::FrameView m_ui; LayerId m_active{};

@@ -80,12 +92,24 @@

int m_glowFrame; QTimer m_glowTimer; + QMutex m_mutex{QMutex::Recursive}; + VFile* m_currentFrame = nullptr; + VFile* m_nextFrame = nullptr; + mCore* m_vl = nullptr; + QImage m_framebuffer; + QSize m_dims; QList<Layer> m_queue; QSet<LayerId> m_disabled; QPixmap m_composited; QPixmap m_rendered; mMapCacheEntry m_mapStatus[4][128 * 128] = {}; // TODO: Correct size + +#ifdef M_CORE_GBA + uint16_t m_gbaDispcnt; +#endif + + std::shared_ptr<bool> m_callbackLocker{std::make_shared<bool>(true)}; }; }
M src/platform/qt/FrameView.uisrc/platform/qt/FrameView.ui

@@ -13,7 +13,7 @@ </property>

<property name="windowTitle"> <string>Inspect frame</string> </property> - <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,1,0,1,0"> + <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,1,1,0" columnstretch="0,1"> <item row="0" column="0"> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item>

@@ -51,7 +51,7 @@ <string>Freeze frame</string>

</property> </widget> </item> - <item row="4" column="1" rowspan="2"> + <item row="5" column="1" rowspan="2"> <widget class="QScrollArea" name="compositedArea"> <property name="widgetResizable"> <bool>true</bool>

@@ -61,8 +61,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>591</width> - <height>403</height> + <width>567</width> + <height>382</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout_2">

@@ -90,7 +90,7 @@ </layout>

</widget> </widget> </item> - <item row="0" column="1" rowspan="4"> + <item row="0" column="1" rowspan="5"> <widget class="QScrollArea" name="renderedArea"> <property name="widgetResizable"> <bool>true</bool>

@@ -100,8 +100,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>591</width> - <height>446</height> + <width>567</width> + <height>467</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout">

@@ -129,7 +129,10 @@ </layout>

</widget> </widget> </item> - <item row="5" column="0"> + <item row="3" column="0" rowspan="3"> + <widget class="QListWidget" name="queue"/> + </item> + <item row="6" column="0"> <widget class="QPushButton" name="exportButton"> <property name="enabled"> <bool>false</bool>

@@ -139,13 +142,10 @@ <string>Export</string>

</property> </widget> </item> - <item row="2" column="0" rowspan="3"> - <widget class="QListWidget" name="queue"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <item row="2" column="0"> + <widget class="QCheckBox" name="disableScanline"> + <property name="text"> + <string>Disable scanline effects</string> </property> </widget> </item>