all repos — mgba @ 35fcb725e42bfe55245cf2fafc552ad7e06784b3

mGBA Game Boy Advance Emulator

Qt: Prototype sprite viewer
Jeffrey Pfau jeffrey@endrift.com
Fri, 21 Oct 2016 01:53:51 -0700
commit

35fcb725e42bfe55245cf2fafc552ad7e06784b3

parent

24e51e1c85d7a1cc3cf1b806eeb93328a1ad2711

M CHANGESCHANGES

@@ -3,6 +3,7 @@ Features:

- GBA: Support printing debug strings from inside a game - GBA: Better cheat type autodetection - GB: Tile viewer + - Sprite viewer Bugfixes: - LR35902: Fix core never exiting with certain event patterns - GB Timer: Improve DIV reset behavior
A src/platform/qt/AssetView.cpp

@@ -0,0 +1,58 @@

+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "AssetView.h" + +#include <QTimer> + +extern "C" { +#ifdef M_CORE_GBA +#include "gba/gba.h" +#endif +} + +using namespace QGBA; + +AssetView::AssetView(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) + , m_tileCache(controller->tileCache()) +{ + m_updateTimer.setSingleShot(true); + m_updateTimer.setInterval(1); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles())); + + connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), &m_updateTimer, SLOT(start())); + connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close())); +} + +void AssetView::updateTiles(bool force) { + if (!m_controller->thread() || !m_controller->thread()->core) { + return; + } + + switch (m_controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + updateTilesGBA(force); + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + updateTilesGB(force); + break; +#endif + default: + return; + } +} + +void AssetView::resizeEvent(QResizeEvent*) { + updateTiles(true); +} + +void AssetView::showEvent(QShowEvent*) { + updateTiles(true); +}
A src/platform/qt/AssetView.h

@@ -0,0 +1,44 @@

+/* Copyright (c) 2013-2016 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 QGBA_ASSET_VIEW +#define QGBA_ASSET_VIEW + +#include <QWidget> + +#include "GameController.h" + +namespace QGBA { + +class AssetView : public QWidget { +Q_OBJECT + +public: + AssetView(GameController* controller, QWidget* parent = nullptr); + +protected slots: + void updateTiles(bool force = false); + +protected: +#ifdef M_CORE_GBA + virtual void updateTilesGBA(bool force) = 0; +#endif +#ifdef M_CORE_GB + virtual void updateTilesGB(bool force) = 0; +#endif + + void resizeEvent(QResizeEvent*) override; + void showEvent(QShowEvent*) override; + + const std::shared_ptr<mTileCache> m_tileCache; + +private: + GameController* m_controller; + QTimer m_updateTimer; +}; + +} + +#endif
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -70,6 +70,7 @@ set(SOURCE_FILES

AboutScreen.cpp ArchiveInspector.cpp AssetTile.cpp + AssetView.cpp AudioProcessor.cpp CheatsModel.cpp CheatsView.cpp

@@ -95,6 +96,7 @@ MemoryModel.cpp

MemoryView.cpp MessagePainter.cpp MultiplayerController.cpp + ObjView.cpp OverrideView.cpp PaletteView.cpp ROMInfo.cpp

@@ -121,6 +123,7 @@ IOViewer.ui

LoadSaveState.ui LogView.ui MemoryView.ui + ObjView.ui OverrideView.ui PaletteView.ui ROMInfo.ui
A src/platform/qt/ObjView.cpp

@@ -0,0 +1,115 @@

+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ObjView.h" + +#include "GBAApp.h" + +#include <QFontDatabase> +#include <QTimer> + +extern "C" { +#include "gba/gba.h" +} + +using namespace QGBA; + +ObjView::ObjView(GameController* controller, QWidget* parent) + : AssetView(controller, parent) + , m_controller(controller) + , m_tileStatus{} + , m_objId(0) +{ + m_ui.setupUi(this); + m_ui.tile->setController(controller); + + const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + m_ui.x->setFont(font); + m_ui.y->setFont(font); + m_ui.w->setFont(font); + m_ui.h->setFont(font); + m_ui.address->setFont(font); + + connect(m_ui.tiles, SIGNAL(indexPressed(int)), this, SLOT(translateIndex(int))); + connect(m_ui.objId, SIGNAL(valueChanged(int)), this, SLOT(selectObj(int))); + connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() { + updateTiles(true); + }); +} + +void ObjView::selectObj(int obj) { + m_objId = obj; + updateTiles(true); +} + +void ObjView::translateIndex(int index) { + m_ui.tile->selectIndex(index + m_tileOffset); +} + +#ifdef M_CORE_GBA +void ObjView::updateTilesGBA(bool force) { + const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board); + const GBAObj* obj = &gba->video.oam.obj[m_objId]; + + unsigned shape = GBAObjAttributesAGetShape(obj->a); + unsigned size = GBAObjAttributesBGetSize(obj->b); + unsigned width = GBAVideoObjSizes[shape * 4 + size][0]; + unsigned height = GBAVideoObjSizes[shape * 4 + size][1]; + m_ui.tiles->setTileCount(width * height / 64); + m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value()); + unsigned palette = GBAObjAttributesCGetPalette(obj->c); + unsigned tile = GBAObjAttributesCGetTile(obj->c); + int i = 0; + // TODO: Tile stride + if (GBAObjAttributesAIs256Color(obj->a)) { + mTileCacheSetPalette(m_tileCache.get(), 1); + m_ui.tile->setPalette(0); + m_ui.tile->setPaletteSet(1, 1024, 1536); + tile /= 2; + for (int y = 0; y < height / 8; ++y) { + for (int x = 0; x < width / 8; ++x, ++i) { + unsigned t = tile + i; + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * t], t + 1024, 1); + if (data) { + m_ui.tiles->setTile(i, data); + } else if (force) { + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t + 1024, 1)); + } + } + } + tile += 1024; + } else { + mTileCacheSetPalette(m_tileCache.get(), 0); + m_ui.tile->setPalette(palette); + m_ui.tile->setPaletteSet(0, 2048, 3072); + for (int y = 0; y < height / 8; ++y) { + for (int x = 0; x < width / 8; ++x, ++i) { + unsigned t = tile + i; + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * t], t + 2048, palette + 16); + if (data) { + m_ui.tiles->setTile(i, data); + } else if (force) { + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t + 2048, palette + 16)); + } + } + } + tile += 2048; + } + m_tileOffset = tile; + + m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b))); + m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a))); + m_ui.w->setText(QString::number(width)); + m_ui.h->setText(QString::number(height)); + + // TODO: Flags +} +#endif + +#ifdef M_CORE_GB +void ObjView::updateTilesGB(bool force) { +} +#endif
A src/platform/qt/ObjView.h

@@ -0,0 +1,49 @@

+/* Copyright (c) 2013-2016 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 QGBA_OBJ_VIEW +#define QGBA_OBJ_VIEW + +#include "AssetView.h" +#include "GameController.h" + +#include "ui_ObjView.h" + +extern "C" { +#include "core/tile-cache.h" +} + +namespace QGBA { + +class ObjView : public AssetView { +Q_OBJECT + +public: + ObjView(GameController* controller, QWidget* parent = nullptr); + +private slots: + void selectObj(int); + void translateIndex(int); + +private: +#ifdef M_CORE_GBA + void updateTilesGBA(bool force) override; +#endif +#ifdef M_CORE_GB + void updateTilesGB(bool force) override; +#endif + + Ui::ObjView m_ui; + + GameController* m_controller; + mTileCacheEntry m_tileStatus[1024 * 32]; // TODO: Correct size + int m_objId; + + int m_tileOffset; +}; + +} + +#endif
A src/platform/qt/ObjView.ui

@@ -0,0 +1,326 @@

+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ObjView</class> + <widget class="QWidget" name="ObjView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Sprites</string> + </property> + <layout class="QGridLayout" name="gridLayout" columnstretch="0,1"> + <item row="0" column="1" rowspan="5"> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="0" column="0" alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QGBA::TilePainter" name="tiles" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>8</width> + <height>8</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="3" column="0"> + <widget class="QGBA::AssetTile" name="tile"> + <property name="title"> + <string>Tile</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QSpinBox" name="magnification"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="suffix"> + <string>×</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Magnification</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QSpinBox" name="objId"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>127</number> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Object</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Address</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="address"> + <property name="text"> + <string>0x07000000</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Position</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="x"> + <property name="minimumSize"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="tileId_3"> + <property name="text"> + <string>, </string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="y"> + <property name="minimumSize"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Dimensions</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="w"> + <property name="minimumSize"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>8</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="tileId_5"> + <property name="text"> + <string>×</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="h"> + <property name="minimumSize"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>8</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QGBA::TilePainter</class> + <extends>QWidget</extends> + <header>TilePainter.h</header> + <container>1</container> + <slots> + <slot>setTileMagnification(int)</slot> + </slots> + </customwidget> + <customwidget> + <class>QGBA::AssetTile</class> + <extends>QGroupBox</extends> + <header>AssetTile.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>magnification</sender> + <signal>valueChanged(int)</signal> + <receiver>tiles</receiver> + <slot>setTileMagnification(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>36</x> + <y>58</y> + </hint> + <hint type="destinationlabel"> + <x>278</x> + <y>149</y> + </hint> + </hints> + </connection> + </connections> +</ui>
M src/platform/qt/TilePainter.cppsrc/platform/qt/TilePainter.cpp

@@ -28,6 +28,11 @@ }

void TilePainter::resizeEvent(QResizeEvent* event) { int w = width() / m_size; + if (w < 1) { + // FIXME: Uhh...how did we get here? + // Resizing the window when magnification > 1 seems to trigger this + return; + } int calculatedHeight = (m_tileCount + w - 1) * m_size / w; calculatedHeight -= calculatedHeight % m_size; if (width() / m_size != m_backing.width() / m_size || m_backing.height() != calculatedHeight) {

@@ -55,10 +60,13 @@ }

void TilePainter::setTileCount(int tiles) { m_tileCount = tiles; - int w = width() / m_size; - int h = (tiles + w - 1) * m_size / w; - setMinimumSize(16, h - (h % m_size)); - resizeEvent(nullptr); + if (sizePolicy().verticalPolicy() != QSizePolicy::Fixed) { + // Only manage the size ourselves if we don't appear to have something else managing it + int w = width() / m_size; + int h = (tiles + w - 1) * m_size / w; + setMinimumSize(m_size, h - (h % m_size)); + resizeEvent(nullptr); + } } void TilePainter::setTileMagnification(int mag) {
M src/platform/qt/TileView.cppsrc/platform/qt/TileView.cpp

@@ -10,28 +10,17 @@

#include <QFontDatabase> #include <QTimer> -extern "C" { -#include "gba/gba.h" -} - using namespace QGBA; TileView::TileView(GameController* controller, QWidget* parent) - : QWidget(parent) + : AssetView(controller, parent) , m_controller(controller) , m_tileStatus{} - , m_tileCache(controller->tileCache()) , m_paletteId(0) { m_ui.setupUi(this); m_ui.tile->setController(controller); - m_updateTimer.setSingleShot(true); - m_updateTimer.setInterval(1); - connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles())); - - connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), &m_updateTimer, SLOT(start())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close())); connect(m_ui.tiles, SIGNAL(indexPressed(int)), m_ui.tile, SLOT(selectIndex(int))); connect(m_ui.paletteId, SIGNAL(valueChanged(int)), this, SLOT(updatePalette(int)));

@@ -84,27 +73,6 @@ updateTiles(true);

}); } -void TileView::updateTiles(bool force) { - if (!m_controller->thread() || !m_controller->thread()->core) { - return; - } - - switch (m_controller->platform()) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - updateTilesGBA(force); - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - updateTilesGB(force); - break; -#endif - default: - return; - } -} - #ifdef M_CORE_GBA void TileView::updateTilesGBA(bool force) { if (m_ui.palette256->isChecked()) {

@@ -169,11 +137,3 @@ m_paletteId = palette;

m_ui.tile->setPalette(palette); updateTiles(true); } - -void TileView::resizeEvent(QResizeEvent*) { - updateTiles(true); -} - -void TileView::showEvent(QShowEvent*) { - updateTiles(true); -}
M src/platform/qt/TileView.hsrc/platform/qt/TileView.h

@@ -6,8 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_TILE_VIEW #define QGBA_TILE_VIEW -#include <QWidget> - +#include "AssetView.h" #include "GameController.h" #include "ui_TileView.h"

@@ -18,35 +17,28 @@ }

namespace QGBA { -class TileView : public QWidget { +class TileView : public AssetView { Q_OBJECT public: TileView(GameController* controller, QWidget* parent = nullptr); public slots: - void updateTiles(bool force = false); void updatePalette(int); -protected: - void resizeEvent(QResizeEvent*) override; - void showEvent(QShowEvent*) override; - private: #ifdef M_CORE_GBA - void updateTilesGBA(bool force); + void updateTilesGBA(bool force) override; #endif #ifdef M_CORE_GB - void updateTilesGB(bool force); + void updateTilesGB(bool force) override; #endif Ui::TileView m_ui; GameController* m_controller; - std::shared_ptr<mTileCache> m_tileCache; mTileCacheEntry m_tileStatus[3072 * 32]; // TODO: Correct size int m_paletteId; - QTimer m_updateTimer; }; }
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -30,13 +30,14 @@ #include "LogView.h"

#include "MultiplayerController.h" #include "MemoryView.h" #include "OverrideView.h" +#include "ObjView.h" #include "PaletteView.h" -#include "TileView.h" #include "ROMInfo.h" #include "SensorView.h" #include "SettingsView.h" #include "ShaderSelector.h" #include "ShortcutController.h" +#include "TileView.h" #include "VideoView.h" extern "C" {

@@ -1339,6 +1340,11 @@ QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu);

connect(paletteView, &QAction::triggered, openTView<PaletteView>()); m_gameActions.append(paletteView); addControlledAction(toolsMenu, paletteView, "paletteWindow"); + + QAction* objView = new QAction(tr("View &sprites..."), toolsMenu); + connect(objView, &QAction::triggered, openTView<ObjView>()); + m_gameActions.append(objView); + addControlledAction(toolsMenu, objView, "spriteWindow"); QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu); connect(tileView, &QAction::triggered, openTView<TileView>());