mirror of https://github.com/mgba-emu/mgba.git
Qt: Add frame inspector for GBA games
This commit is contained in:
parent
db2b56f418
commit
86efc6cc9f
1
CHANGES
1
CHANGES
|
@ -11,6 +11,7 @@ Features:
|
|||
- OpenGL renderer with high-resolution upscaling support
|
||||
- Experimental high level "XQ" audio for most GBA games
|
||||
- Interframe blending for games that use flicker effects
|
||||
- Frame inspector for dissecting and debugging rendering
|
||||
Emulation fixes:
|
||||
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
|
||||
- GBA: Reset now reloads multiboot ROMs
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2019 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
|
||||
|
@ -9,6 +9,16 @@
|
|||
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/io.h>
|
||||
#endif
|
||||
|
||||
#include <mgba/core/map-cache.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||
|
@ -98,3 +108,166 @@ void AssetView::compositeTile(const void* tBuffer, void* buffer, size_t stride,
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QImage AssetView::compositeMap(int map, mMapCacheEntry* mapStatus) {
|
||||
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, map);
|
||||
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
|
||||
int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
|
||||
QImage rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
|
||||
uchar* bgBits = rawMap.bits();
|
||||
for (int j = 0; j < tilesH; ++j) {
|
||||
for (int i = 0; i < tilesW; ++i) {
|
||||
mMapCacheCleanTile(mapCache, mapStatus, i, j);
|
||||
}
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32);
|
||||
}
|
||||
}
|
||||
return rawMap.rgbSwapped();
|
||||
}
|
||||
|
||||
QImage AssetView::compositeObj(const ObjInfo& objInfo) {
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, objInfo.paletteSet);
|
||||
const color_t* rawPalette = mTileCacheGetPalette(tileCache, objInfo.paletteId);
|
||||
unsigned colors = 1 << objInfo.bits;
|
||||
QVector<QRgb> palette;
|
||||
|
||||
palette.append(rawPalette[0] & 0xFFFFFF);
|
||||
for (unsigned c = 1; c < colors && c < 256; ++c) {
|
||||
palette.append(rawPalette[c] | 0xFF000000);
|
||||
}
|
||||
|
||||
QImage image = QImage(QSize(objInfo.width * 8, objInfo.height * 8), QImage::Format_Indexed8);
|
||||
image.setColorTable(palette);
|
||||
uchar* bits = image.bits();
|
||||
unsigned t = objInfo.tile;
|
||||
for (int y = 0; y < objInfo.height; ++y) {
|
||||
for (int x = 0; x < objInfo.width; ++x, ++t) {
|
||||
compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), bits, objInfo.width * 8, x * 8, y * 8, objInfo.bits);
|
||||
}
|
||||
t += objInfo.stride - objInfo.width;
|
||||
}
|
||||
return image.rgbSwapped();
|
||||
}
|
||||
|
||||
bool AssetView::lookupObj(int id, struct ObjInfo* info) {
|
||||
switch (m_controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case PLATFORM_GBA:
|
||||
return lookupObjGBA(id, info);
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB:
|
||||
return lookupObjGB(id, info);
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
bool AssetView::lookupObjGBA(int id, struct ObjInfo* info) {
|
||||
if (id > 127) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
|
||||
const GBAObj* obj = &gba->video.oam.obj[id];
|
||||
|
||||
unsigned shape = GBAObjAttributesAGetShape(obj->a);
|
||||
unsigned size = GBAObjAttributesBGetSize(obj->b);
|
||||
unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
|
||||
unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
|
||||
unsigned tile = GBAObjAttributesCGetTile(obj->c);
|
||||
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
|
||||
unsigned tileBase = tile;
|
||||
unsigned paletteSet;
|
||||
unsigned bits;
|
||||
if (GBAObjAttributesAIs256Color(obj->a)) {
|
||||
paletteSet = 3;
|
||||
palette = 0;
|
||||
tile /= 2;
|
||||
bits = 8;
|
||||
} else {
|
||||
paletteSet = 2;
|
||||
bits = 4;
|
||||
}
|
||||
ObjInfo newInfo{
|
||||
tile,
|
||||
width / 8,
|
||||
height / 8,
|
||||
width / 8,
|
||||
palette,
|
||||
paletteSet,
|
||||
bits,
|
||||
!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a),
|
||||
GBAObjAttributesCGetPriority(obj->c),
|
||||
GBAObjAttributesBGetX(obj->b),
|
||||
GBAObjAttributesAGetY(obj->a),
|
||||
GBAObjAttributesBIsHFlip(obj->b),
|
||||
GBAObjAttributesBIsVFlip(obj->b),
|
||||
};
|
||||
GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
|
||||
if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
|
||||
newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
|
||||
};
|
||||
*info = newInfo;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
bool AssetView::lookupObjGB(int id, struct ObjInfo* info) {
|
||||
if (id > 39) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
|
||||
const GBObj* obj = &gb->video.oam.obj[id];
|
||||
|
||||
unsigned width = 8;
|
||||
unsigned height = 8;
|
||||
GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
|
||||
if (GBRegisterLCDCIsObjSize(lcdc)) {
|
||||
height = 16;
|
||||
}
|
||||
unsigned tile = obj->tile;
|
||||
unsigned palette = 0;
|
||||
if (gb->model >= GB_MODEL_CGB) {
|
||||
if (GBObjAttributesIsBank(obj->attr)) {
|
||||
tile += 512;
|
||||
}
|
||||
palette = GBObjAttributesGetCGBPalette(obj->attr);
|
||||
} else {
|
||||
palette = GBObjAttributesGetPalette(obj->attr);
|
||||
}
|
||||
palette += 8;
|
||||
|
||||
ObjInfo newInfo{
|
||||
tile,
|
||||
1,
|
||||
height / 8,
|
||||
1,
|
||||
palette,
|
||||
0,
|
||||
2,
|
||||
obj->y != 0 && obj->y < 160,
|
||||
GBObjAttributesGetPriority(obj->attr),
|
||||
obj->x,
|
||||
obj->y,
|
||||
GBObjAttributesIsXFlip(obj->attr),
|
||||
GBObjAttributesIsYFlip(obj->attr),
|
||||
};
|
||||
*info = newInfo;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AssetView::ObjInfo::operator!=(const ObjInfo& other) const {
|
||||
return other.tile != tile ||
|
||||
other.width != width ||
|
||||
other.height != height ||
|
||||
other.stride != stride ||
|
||||
other.paletteId != paletteId ||
|
||||
other.paletteSet != paletteSet;
|
||||
}
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
struct mMapCacheEntry;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class CoreController;
|
||||
|
@ -22,8 +24,6 @@ Q_OBJECT
|
|||
public:
|
||||
AssetView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
|
||||
static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8);
|
||||
|
||||
protected slots:
|
||||
void updateTiles();
|
||||
void updateTiles(bool force);
|
||||
|
@ -40,9 +40,42 @@ protected:
|
|||
void showEvent(QShowEvent*) override;
|
||||
|
||||
mCacheSet* const m_cacheSet;
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
|
||||
protected:
|
||||
struct ObjInfo {
|
||||
unsigned tile;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned stride;
|
||||
unsigned paletteId;
|
||||
unsigned paletteSet;
|
||||
unsigned bits;
|
||||
|
||||
bool enabled : 1;
|
||||
unsigned priority : 2;
|
||||
unsigned x : 9;
|
||||
unsigned y : 9;
|
||||
bool hflip : 1;
|
||||
bool vflip : 1;
|
||||
|
||||
bool operator!=(const ObjInfo&) const;
|
||||
};
|
||||
|
||||
static void compositeTile(const void* tile, void* image, size_t stride, size_t x, size_t y, int depth = 8);
|
||||
QImage compositeMap(int map, mMapCacheEntry*);
|
||||
QImage compositeObj(const ObjInfo&);
|
||||
|
||||
bool lookupObj(int id, struct ObjInfo*);
|
||||
|
||||
private:
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
#ifdef M_CORE_GBA
|
||||
bool lookupObjGBA(int id, struct ObjInfo*);
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
bool lookupObjGB(int id, struct ObjInfo*);
|
||||
#endif
|
||||
|
||||
QTimer m_updateTimer;
|
||||
};
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ set(SOURCE_FILES
|
|||
Display.cpp
|
||||
DisplayGL.cpp
|
||||
DisplayQt.cpp
|
||||
FrameView.cpp
|
||||
GBAApp.cpp
|
||||
GBAKeyEditor.cpp
|
||||
GIFView.cpp
|
||||
|
@ -122,6 +123,7 @@ set(UI_FILES
|
|||
BattleChipView.ui
|
||||
CheatsView.ui
|
||||
DebuggerConsole.ui
|
||||
FrameView.ui
|
||||
GIFView.ui
|
||||
IOViewer.ui
|
||||
LoadSaveState.ui
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
/* Copyright (c) 2013-2019 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 "FrameView.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "CoreController.h"
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/io.h>
|
||||
#include <mgba/internal/gba/memory.h>
|
||||
#include <mgba/internal/gba/video.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/memory.h>
|
||||
#endif
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
FrameView::FrameView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||
: AssetView(controller, parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_glowTimer.setInterval(33);
|
||||
connect(&m_glowTimer, &QTimer::timeout, this, [this]() {
|
||||
++m_glowFrame;
|
||||
invalidateQueue();
|
||||
});
|
||||
m_glowTimer.start();
|
||||
|
||||
m_ui.compositedView->installEventFilter(this);
|
||||
|
||||
connect(m_ui.queue, &QListWidget::itemChanged, this, [this](QListWidgetItem* item) {
|
||||
Layer& layer = m_queue[item->data(Qt::UserRole).toInt()];
|
||||
layer.enabled = item->checkState() == Qt::Checked;
|
||||
if (layer.enabled) {
|
||||
m_disabled.remove(layer.id);
|
||||
} else {
|
||||
m_disabled.insert(layer.id);
|
||||
}
|
||||
invalidateQueue();
|
||||
});
|
||||
connect(m_ui.queue, &QListWidget::currentItemChanged, this, [this](QListWidgetItem* item) {
|
||||
if (item) {
|
||||
m_active = m_queue[item->data(Qt::UserRole).toInt()].id;
|
||||
} else {
|
||||
m_active = {};
|
||||
}
|
||||
invalidateQueue();
|
||||
});
|
||||
}
|
||||
|
||||
void FrameView::selectLayer(const QPointF& coord) {
|
||||
for (const Layer& layer : m_queue) {
|
||||
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()));
|
||||
}
|
||||
|
||||
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 {
|
||||
region = layer.mask.translated(location.x(), location.y());
|
||||
}
|
||||
|
||||
if (region.contains(QPoint(coord.x(), coord.y()))) {
|
||||
m_active = layer.id;
|
||||
m_glowFrame = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameView::updateTilesGBA(bool force) {
|
||||
if (m_ui.freeze->checkState() == Qt::Checked) {
|
||||
return;
|
||||
}
|
||||
m_queue.clear();
|
||||
{
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
updateRendered();
|
||||
|
||||
uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io;
|
||||
int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]);
|
||||
|
||||
std::array<bool, 4> enabled{
|
||||
GBARegisterDISPCNTIsBg0Enable(io[REG_DISPCNT >> 1]),
|
||||
GBARegisterDISPCNTIsBg1Enable(io[REG_DISPCNT >> 1]),
|
||||
GBARegisterDISPCNTIsBg2Enable(io[REG_DISPCNT >> 1]),
|
||||
GBARegisterDISPCNTIsBg3Enable(io[REG_DISPCNT >> 1]),
|
||||
};
|
||||
|
||||
for (int priority = 0; priority < 4; ++priority) {
|
||||
for (int sprite = 0; sprite < 128; ++sprite) {
|
||||
ObjInfo info;
|
||||
lookupObj(sprite, &info);
|
||||
|
||||
if (!info.enabled || info.priority != priority) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QPointF offset(info.x, info.y);
|
||||
QImage obj(compositeObj(info));
|
||||
if (info.hflip || info.vflip) {
|
||||
obj = obj.mirrored(info.hflip, info.vflip);
|
||||
}
|
||||
m_queue.append({
|
||||
{ LayerId::SPRITE, sprite },
|
||||
!m_disabled.contains({ LayerId::SPRITE, sprite}),
|
||||
QPixmap::fromImage(obj),
|
||||
{}, offset, false
|
||||
});
|
||||
if (m_queue.back().image.hasAlpha()) {
|
||||
m_queue.back().mask = QRegion(m_queue.back().image.mask());
|
||||
} else {
|
||||
m_queue.back().mask = QRegion(0, 0, m_queue.back().image.width(), m_queue.back().image.height());
|
||||
}
|
||||
}
|
||||
|
||||
for (int bg = 0; bg < 4; ++bg) {
|
||||
if (!enabled[bg]) {
|
||||
continue;
|
||||
}
|
||||
if (GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + bg]) != priority) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QPointF offset;
|
||||
if (mode == 0) {
|
||||
offset.setX(-(io[(REG_BG0HOFS >> 1) + (bg << 1)] & 0x1FF));
|
||||
offset.setY(-(io[(REG_BG0VOFS >> 1) + (bg << 1)] & 0x1FF));
|
||||
};
|
||||
m_queue.append({
|
||||
{ LayerId::BACKGROUND, bg },
|
||||
!m_disabled.contains({ LayerId::BACKGROUND, bg}),
|
||||
QPixmap::fromImage(compositeMap(bg, m_mapStatus[bg])),
|
||||
{}, offset, true
|
||||
});
|
||||
if (m_queue.back().image.hasAlpha()) {
|
||||
m_queue.back().mask = QRegion(m_queue.back().image.mask());
|
||||
} else {
|
||||
m_queue.back().mask = QRegion(0, 0, m_queue.back().image.width(), m_queue.back().image.height());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidateQueue(QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS));
|
||||
}
|
||||
|
||||
void FrameView::updateTilesGB(bool force) {
|
||||
if (m_ui.freeze->checkState() == Qt::Checked) {
|
||||
return;
|
||||
}
|
||||
m_queue.clear();
|
||||
{
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
updateRendered();
|
||||
}
|
||||
invalidateQueue(m_controller->screenDimensions());
|
||||
}
|
||||
|
||||
void FrameView::invalidateQueue(const QSize& dims) {
|
||||
QSize realDims = dims;
|
||||
if (!dims.isValid()) {
|
||||
realDims = m_composited.size() / m_ui.magnification->value();
|
||||
}
|
||||
bool blockSignals = m_ui.queue->blockSignals(true);
|
||||
QPixmap composited(realDims);
|
||||
|
||||
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, realDims.width(), realDims.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;
|
||||
if (i >= m_ui.queue->count()) {
|
||||
item = new QListWidgetItem;
|
||||
m_ui.queue->addItem(item);
|
||||
} else {
|
||||
item = m_ui.queue->item(i);
|
||||
}
|
||||
item->setText(layer.id.readable());
|
||||
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);
|
||||
|
||||
m_composited = composited.scaled(realDims * m_ui.magnification->value());
|
||||
m_ui.compositedView->setPixmap(m_composited);
|
||||
}
|
||||
|
||||
void FrameView::updateRendered() {
|
||||
if (m_ui.freeze->checkState() == Qt::Checked) {
|
||||
return;
|
||||
}
|
||||
m_rendered.convertFromImage(m_controller->getPixels());
|
||||
m_rendered = m_rendered.scaledToHeight(m_rendered.height() * m_ui.magnification->value());
|
||||
m_ui.renderedView->setPixmap(m_rendered);
|
||||
}
|
||||
|
||||
bool FrameView::eventFilter(QObject* obj, QEvent* event) {
|
||||
if (event->type() != QEvent::MouseButtonPress) {
|
||||
return false;
|
||||
}
|
||||
QPointF pos = static_cast<QMouseEvent*>(event)->localPos();
|
||||
pos /= m_ui.magnification->value();
|
||||
selectLayer(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString FrameView::LayerId::readable() const {
|
||||
QString typeStr;
|
||||
switch (type) {
|
||||
case NONE:
|
||||
return tr("None");
|
||||
case BACKGROUND:
|
||||
typeStr = tr("Background");
|
||||
break;
|
||||
case WINDOW:
|
||||
typeStr = tr("Window");
|
||||
break;
|
||||
case SPRITE:
|
||||
typeStr = tr("Sprite");
|
||||
break;
|
||||
}
|
||||
if (index < 0) {
|
||||
return typeStr;
|
||||
}
|
||||
return tr("%1 %2").arg(typeStr).arg(index);
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/* Copyright (c) 2013-2019 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/. */
|
||||
#pragma once
|
||||
|
||||
#include "ui_FrameView.h"
|
||||
|
||||
#include <QBitmap>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QPixmap>
|
||||
#include <QSet>
|
||||
#include <QTimer>
|
||||
|
||||
#include "AssetView.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class CoreController;
|
||||
|
||||
class FrameView : public AssetView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FrameView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void selectLayer(const QPointF& coord);
|
||||
|
||||
protected:
|
||||
#ifdef M_CORE_GBA
|
||||
void updateTilesGBA(bool force) override;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
void updateTilesGB(bool force) override;
|
||||
#endif
|
||||
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
private:
|
||||
struct LayerId {
|
||||
enum {
|
||||
NONE = 0,
|
||||
BACKGROUND,
|
||||
WINDOW,
|
||||
SPRITE
|
||||
} type = NONE;
|
||||
int index = -1;
|
||||
|
||||
bool operator==(const LayerId& other) const { return other.type == type && other.index == index; }
|
||||
operator uint() const { return (type << 8) | index; }
|
||||
QString readable() const;
|
||||
};
|
||||
|
||||
struct Layer {
|
||||
LayerId id;
|
||||
bool enabled;
|
||||
QPixmap image;
|
||||
QRegion mask;
|
||||
QPointF location;
|
||||
bool repeats;
|
||||
};
|
||||
|
||||
void invalidateQueue(const QSize& dims = QSize());
|
||||
void updateRendered();
|
||||
|
||||
Ui::FrameView m_ui;
|
||||
|
||||
LayerId m_active{};
|
||||
|
||||
int m_glowFrame;
|
||||
QTimer m_glowTimer;
|
||||
|
||||
QList<Layer> m_queue;
|
||||
QSet<LayerId> m_disabled;
|
||||
QPixmap m_composited;
|
||||
QPixmap m_rendered;
|
||||
mMapCacheEntry m_mapStatus[4][128 * 128] = {}; // TODO: Correct size
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FrameView</class>
|
||||
<widget class="QWidget" name="FrameView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>869</width>
|
||||
<height>875</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Inspect frame</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,1,0,1,0">
|
||||
<item row="0" 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>8</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Magnification</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="freeze">
|
||||
<property name="text">
|
||||
<string>Freeze frame</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" rowspan="2">
|
||||
<widget class="QScrollArea" name="compositedArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>591</width>
|
||||
<height>403</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="compositedView">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="4">
|
||||
<widget class="QScrollArea" name="renderedArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>591</width>
|
||||
<height>446</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="renderedView">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<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>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QPushButton" name="exportButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<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>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -211,6 +211,7 @@ void MapView::updateTilesGBA(bool force) {
|
|||
mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
|
||||
memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
|
||||
}
|
||||
m_rawMap = m_rawMap.rgbSwapped();
|
||||
} else {
|
||||
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
|
||||
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
|
||||
|
@ -225,19 +226,9 @@ void MapView::updateTilesGBA(bool force) {
|
|||
m_ui.bgInfo->setCustomProperty("priority", priority);
|
||||
m_ui.bgInfo->setCustomProperty("offset", offset);
|
||||
m_ui.bgInfo->setCustomProperty("transform", transform);
|
||||
m_rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
|
||||
uchar* bgBits = m_rawMap.bits();
|
||||
for (int j = 0; j < tilesH; ++j) {
|
||||
for (int i = 0; i < tilesW; ++i) {
|
||||
mMapCacheCleanTile(mapCache, m_mapStatus, i, j);
|
||||
}
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32);
|
||||
}
|
||||
}
|
||||
m_rawMap = compositeMap(m_map, m_mapStatus);
|
||||
}
|
||||
}
|
||||
m_rawMap = m_rawMap.rgbSwapped();
|
||||
QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
|
||||
if (m_ui.magnification->value() > 1) {
|
||||
map = map.scaled(map.size() * m_ui.magnification->value());
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1273</width>
|
||||
<height>736</height>
|
||||
<width>941</width>
|
||||
<height>617</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -92,8 +92,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>835</width>
|
||||
<height>720</height>
|
||||
<width>613</width>
|
||||
<height>601</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
|
|
@ -19,9 +19,7 @@
|
|||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/io.h>
|
||||
#endif
|
||||
#include <mgba-util/png-io.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -53,11 +51,7 @@ ObjView::ObjView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
|
||||
updateTiles(true);
|
||||
});
|
||||
#ifdef USE_PNG
|
||||
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
|
||||
#else
|
||||
m_ui.exportButton->setVisible(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ObjView::selectObj(int obj) {
|
||||
|
@ -77,79 +71,56 @@ 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];
|
||||
unsigned tile = GBAObjAttributesCGetTile(obj->c);
|
||||
m_ui.tiles->setTileCount(width * height / 64);
|
||||
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
|
||||
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
|
||||
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
|
||||
unsigned tileBase = tile;
|
||||
unsigned paletteSet;
|
||||
unsigned bits;
|
||||
ObjInfo newInfo;
|
||||
lookupObj(m_objId, &newInfo);
|
||||
|
||||
m_ui.tiles->setTileCount(newInfo.width * newInfo.height);
|
||||
m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
|
||||
m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
|
||||
unsigned tileBase = newInfo.tile;
|
||||
unsigned tile = newInfo.tile;
|
||||
if (GBAObjAttributesAIs256Color(obj->a)) {
|
||||
m_ui.palette->setText("256-color");
|
||||
paletteSet = 3;
|
||||
m_ui.tile->setBoundary(1024, 1, 3);
|
||||
m_ui.tile->setPalette(0);
|
||||
m_boundary = 1024;
|
||||
palette = 0;
|
||||
tile /= 2;
|
||||
bits = 8;
|
||||
tileBase *= 2;
|
||||
} else {
|
||||
m_ui.palette->setText(QString::number(palette));
|
||||
paletteSet = 2;
|
||||
m_ui.palette->setText(QString::number(newInfo.paletteId));
|
||||
m_ui.tile->setBoundary(2048, 0, 2);
|
||||
m_ui.tile->setPalette(palette);
|
||||
m_boundary = 2048;
|
||||
bits = 4;
|
||||
m_ui.tile->setPalette(newInfo.paletteId);
|
||||
}
|
||||
ObjInfo newInfo{
|
||||
tile,
|
||||
width / 8,
|
||||
height / 8,
|
||||
width / 8,
|
||||
palette,
|
||||
paletteSet,
|
||||
bits
|
||||
};
|
||||
if (newInfo != m_objInfo) {
|
||||
force = true;
|
||||
}
|
||||
GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
|
||||
if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
|
||||
newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
|
||||
};
|
||||
m_objInfo = newInfo;
|
||||
m_tileOffset = tile;
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, paletteSet);
|
||||
m_tileOffset = newInfo.tile;
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
|
||||
|
||||
int i = 0;
|
||||
for (int y = 0; y < height / 8; ++y) {
|
||||
for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) {
|
||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, palette);
|
||||
for (int y = 0; y < newInfo.height; ++y) {
|
||||
for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
|
||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
|
||||
if (data) {
|
||||
m_ui.tiles->setTile(i, data);
|
||||
} else if (force) {
|
||||
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, palette));
|
||||
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId));
|
||||
}
|
||||
}
|
||||
tile += newInfo.stride - width / 8;
|
||||
tileBase += newInfo.stride - width / 8;
|
||||
tile += newInfo.stride - newInfo.width;
|
||||
tileBase += newInfo.stride - newInfo.width;
|
||||
}
|
||||
|
||||
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));
|
||||
m_ui.x->setText(QString::number(newInfo.x));
|
||||
m_ui.y->setText(QString::number(newInfo.y));
|
||||
m_ui.w->setText(QString::number(newInfo.width * 8));
|
||||
m_ui.h->setText(QString::number(newInfo.height * 8));
|
||||
|
||||
m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
|
||||
m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c)));
|
||||
m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b));
|
||||
m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b));
|
||||
m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a));
|
||||
m_ui.priority->setText(QString::number(newInfo.priority));
|
||||
m_ui.flippedH->setChecked(newInfo.hflip);
|
||||
m_ui.flippedV->setChecked(newInfo.vflip);
|
||||
m_ui.enabled->setChecked(newInfo.enabled);
|
||||
m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
|
||||
m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
|
||||
|
||||
|
@ -182,39 +153,17 @@ void ObjView::updateTilesGB(bool force) {
|
|||
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
|
||||
const GBObj* obj = &gb->video.oam.obj[m_objId];
|
||||
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
||||
unsigned width = 8;
|
||||
unsigned height = 8;
|
||||
GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
|
||||
if (GBRegisterLCDCIsObjSize(lcdc)) {
|
||||
height = 16;
|
||||
}
|
||||
unsigned tile = obj->tile;
|
||||
m_ui.tiles->setTileCount(width * height / 64);
|
||||
m_ui.tile->setBoundary(1024, 0, 0);
|
||||
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
|
||||
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
|
||||
unsigned palette = 0;
|
||||
if (gb->model >= GB_MODEL_CGB) {
|
||||
if (GBObjAttributesIsBank(obj->attr)) {
|
||||
tile += 512;
|
||||
}
|
||||
palette = GBObjAttributesGetCGBPalette(obj->attr);
|
||||
} else {
|
||||
palette = GBObjAttributesGetPalette(obj->attr);
|
||||
}
|
||||
m_ui.palette->setText(QString::number(palette));
|
||||
palette += 8;
|
||||
ObjInfo newInfo;
|
||||
lookupObj(m_objId, &newInfo);
|
||||
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
||||
unsigned tile = newInfo.tile;
|
||||
m_ui.tiles->setTileCount(newInfo.height);
|
||||
m_ui.tile->setBoundary(1024, 0, 0);
|
||||
m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
|
||||
m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
|
||||
m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
|
||||
|
||||
ObjInfo newInfo{
|
||||
tile,
|
||||
1,
|
||||
height / 8,
|
||||
1,
|
||||
palette,
|
||||
0,
|
||||
2
|
||||
};
|
||||
if (newInfo != m_objInfo) {
|
||||
force = true;
|
||||
}
|
||||
|
@ -223,27 +172,27 @@ void ObjView::updateTilesGB(bool force) {
|
|||
m_boundary = 1024;
|
||||
|
||||
int i = 0;
|
||||
m_ui.tile->setPalette(palette);
|
||||
for (int y = 0; y < height / 8; ++y, ++i) {
|
||||
m_ui.tile->setPalette(newInfo.paletteId);
|
||||
for (int y = 0; y < newInfo.height; ++y, ++i) {
|
||||
unsigned t = tile + i;
|
||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, palette);
|
||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId);
|
||||
if (data) {
|
||||
m_ui.tiles->setTile(i, data);
|
||||
} else if (force) {
|
||||
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, palette));
|
||||
m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId));
|
||||
}
|
||||
}
|
||||
|
||||
m_ui.x->setText(QString::number(obj->x));
|
||||
m_ui.y->setText(QString::number(obj->y));
|
||||
m_ui.w->setText(QString::number(width));
|
||||
m_ui.h->setText(QString::number(height));
|
||||
m_ui.x->setText(QString::number(newInfo.x));
|
||||
m_ui.y->setText(QString::number(newInfo.y));
|
||||
m_ui.w->setText(QString::number(8));
|
||||
m_ui.h->setText(QString::number(newInfo.height * 8));
|
||||
|
||||
m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
|
||||
m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr)));
|
||||
m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr));
|
||||
m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr));
|
||||
m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160);
|
||||
m_ui.priority->setText(QString::number(newInfo.priority));
|
||||
m_ui.flippedH->setChecked(newInfo.hflip);
|
||||
m_ui.flippedV->setChecked(newInfo.vflip);
|
||||
m_ui.enabled->setChecked(newInfo.enabled);
|
||||
m_ui.doubleSize->setChecked(false);
|
||||
m_ui.mosaic->setChecked(false);
|
||||
m_ui.transform->setText(tr("N/A"));
|
||||
|
@ -251,51 +200,10 @@ void ObjView::updateTilesGB(bool force) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_PNG
|
||||
void ObjView::exportObj() {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
|
||||
tr("Portable Network Graphics (*.png)"));
|
||||
VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
if (!vf) {
|
||||
LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8);
|
||||
|
||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, m_objInfo.paletteSet);
|
||||
const color_t* rawPalette = mTileCacheGetPalette(tileCache, m_objInfo.paletteId);
|
||||
unsigned colors = 1 << m_objInfo.bits;
|
||||
uint32_t palette[256];
|
||||
|
||||
palette[0] = rawPalette[0];
|
||||
for (unsigned c = 1; c < colors && c < 256; ++c) {
|
||||
palette[c] = rawPalette[c] | 0xFF000000;
|
||||
}
|
||||
PNGWritePalette(png, info, palette, colors);
|
||||
|
||||
uint8_t* buffer = new uint8_t[m_objInfo.width * m_objInfo.height * 8 * 8];
|
||||
unsigned t = m_objInfo.tile;
|
||||
for (int y = 0; y < m_objInfo.height; ++y) {
|
||||
for (int x = 0; x < m_objInfo.width; ++x, ++t) {
|
||||
compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), reinterpret_cast<color_t*>(buffer), m_objInfo.width * 8, x * 8, y * 8, m_objInfo.bits);
|
||||
}
|
||||
t += m_objInfo.stride - m_objInfo.width;
|
||||
}
|
||||
PNGWritePixels8(png, m_objInfo.width * 8, m_objInfo.height * 8, m_objInfo.width * 8, static_cast<void*>(buffer));
|
||||
PNGWriteClose(png, info);
|
||||
delete[] buffer;
|
||||
vf->close(vf);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ObjView::ObjInfo::operator!=(const ObjInfo& other) {
|
||||
return other.tile != tile ||
|
||||
other.width != width ||
|
||||
other.height != height ||
|
||||
other.stride != stride ||
|
||||
other.paletteId != paletteId ||
|
||||
other.paletteSet != paletteSet;
|
||||
QImage obj = compositeObj(m_objInfo);
|
||||
obj.save(filename, "PNG");
|
||||
}
|
||||
|
|
|
@ -21,10 +21,8 @@ Q_OBJECT
|
|||
public:
|
||||
ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
|
||||
#ifdef USE_PNG
|
||||
public slots:
|
||||
void exportObj();
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
void selectObj(int);
|
||||
|
@ -43,17 +41,7 @@ private:
|
|||
std::shared_ptr<CoreController> m_controller;
|
||||
mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size
|
||||
int m_objId = 0;
|
||||
struct ObjInfo {
|
||||
unsigned tile;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned stride;
|
||||
unsigned paletteId;
|
||||
unsigned paletteSet;
|
||||
unsigned bits;
|
||||
|
||||
bool operator!=(const ObjInfo&);
|
||||
} m_objInfo = {};
|
||||
ObjInfo m_objInfo = {};
|
||||
|
||||
int m_tileOffset;
|
||||
int m_boundary;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "DebuggerConsoleController.h"
|
||||
#include "Display.h"
|
||||
#include "CoreController.h"
|
||||
#include "FrameView.h"
|
||||
#include "GBAApp.h"
|
||||
#include "GDBController.h"
|
||||
#include "GDBWindow.h"
|
||||
|
@ -1437,7 +1438,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_overrideView->recheck();
|
||||
}, "tools");
|
||||
|
||||
m_actions.addAction(tr("Game &Pak sensors..."), "sensorWindow", [this]() {
|
||||
m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() {
|
||||
if (!m_sensorView) {
|
||||
m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController));
|
||||
if (m_controller) {
|
||||
|
@ -1467,6 +1468,12 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools");
|
||||
addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools");
|
||||
addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "tools");
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
Action* frameWindow = addGameAction(tr("&Frame inspector..."), "frameWindow", openControllerTView<FrameView>(), "tools");
|
||||
m_platformActions.insert(PLATFORM_GBA, frameWindow);
|
||||
#endif
|
||||
|
||||
addGameAction(tr("View memory..."), "memoryView", openControllerTView<MemoryView>(), "tools");
|
||||
addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools");
|
||||
|
||||
|
|
Loading…
Reference in New Issue