mirror of https://github.com/mgba-emu/mgba.git
Qt: Redo frame inspector using video logs
This commit is contained in:
parent
59d2e58bbb
commit
ef3cc7bd9f
|
@ -14,6 +14,8 @@
|
|||
|
||||
#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 @@ FrameView::FrameView(std::shared_ptr<CoreController> controller, QWidget* parent
|
|||
++m_glowFrame;
|
||||
invalidateQueue();
|
||||
});
|
||||
m_glowTimer.start();
|
||||
|
||||
m_ui.renderedView->installEventFilter(this);
|
||||
m_ui.compositedView->installEventFilter(this);
|
||||
|
@ -66,6 +67,15 @@ FrameView::FrameView(std::shared_ptr<CoreController> controller, QWidget* parent
|
|||
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 @@ void FrameView::disableLayer(const QPointF& coord) {
|
|||
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 @@ void FrameView::updateTilesGBA(bool force) {
|
|||
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 @@ void FrameView::updateTilesGB(bool force) {
|
|||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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 @@ void FrameView::invalidateQueue(const QSize& dims) {
|
|||
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);
|
||||
}
|
||||
|
@ -336,6 +360,53 @@ bool FrameView::eventFilter(QObject* obj, QEvent* event) {
|
|||
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;
|
||||
switch (type) {
|
||||
|
|
|
@ -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 @@ public slots:
|
|||
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 @@ private:
|
|||
} 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 @@ private:
|
|||
|
||||
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 @@ private:
|
|||
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)};
|
||||
};
|
||||
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
<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 @@
|
|||
</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 @@
|
|||
<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 @@
|
|||
</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 @@
|
|||
<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 @@
|
|||
</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 @@
|
|||
</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>
|
||||
|
|
Loading…
Reference in New Issue