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
|
- OpenGL renderer with high-resolution upscaling support
|
||||||
- Experimental high level "XQ" audio for most GBA games
|
- Experimental high level "XQ" audio for most GBA games
|
||||||
- Interframe blending for games that use flicker effects
|
- Interframe blending for games that use flicker effects
|
||||||
|
- Frame inspector for dissecting and debugging rendering
|
||||||
Emulation fixes:
|
Emulation fixes:
|
||||||
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
|
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
|
||||||
- GBA: Reset now reloads multiboot ROMs
|
- 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
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
@ -9,6 +9,16 @@
|
||||||
|
|
||||||
#include <QTimer>
|
#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;
|
using namespace QGBA;
|
||||||
|
|
||||||
AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
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;
|
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>
|
#include <memory>
|
||||||
|
|
||||||
|
struct mMapCacheEntry;
|
||||||
|
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
|
||||||
class CoreController;
|
class CoreController;
|
||||||
|
@ -22,8 +24,6 @@ Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AssetView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
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:
|
protected slots:
|
||||||
void updateTiles();
|
void updateTiles();
|
||||||
void updateTiles(bool force);
|
void updateTiles(bool force);
|
||||||
|
@ -40,9 +40,42 @@ protected:
|
||||||
void showEvent(QShowEvent*) override;
|
void showEvent(QShowEvent*) override;
|
||||||
|
|
||||||
mCacheSet* const m_cacheSet;
|
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:
|
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;
|
QTimer m_updateTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@ set(SOURCE_FILES
|
||||||
Display.cpp
|
Display.cpp
|
||||||
DisplayGL.cpp
|
DisplayGL.cpp
|
||||||
DisplayQt.cpp
|
DisplayQt.cpp
|
||||||
|
FrameView.cpp
|
||||||
GBAApp.cpp
|
GBAApp.cpp
|
||||||
GBAKeyEditor.cpp
|
GBAKeyEditor.cpp
|
||||||
GIFView.cpp
|
GIFView.cpp
|
||||||
|
@ -122,6 +123,7 @@ set(UI_FILES
|
||||||
BattleChipView.ui
|
BattleChipView.ui
|
||||||
CheatsView.ui
|
CheatsView.ui
|
||||||
DebuggerConsole.ui
|
DebuggerConsole.ui
|
||||||
|
FrameView.ui
|
||||||
GIFView.ui
|
GIFView.ui
|
||||||
IOViewer.ui
|
IOViewer.ui
|
||||||
LoadSaveState.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);
|
mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
|
||||||
memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
|
memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
|
||||||
}
|
}
|
||||||
|
m_rawMap = m_rawMap.rgbSwapped();
|
||||||
} else {
|
} else {
|
||||||
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
|
mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
|
||||||
int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
|
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("priority", priority);
|
||||||
m_ui.bgInfo->setCustomProperty("offset", offset);
|
m_ui.bgInfo->setCustomProperty("offset", offset);
|
||||||
m_ui.bgInfo->setCustomProperty("transform", transform);
|
m_ui.bgInfo->setCustomProperty("transform", transform);
|
||||||
m_rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
|
m_rawMap = compositeMap(m_map, m_mapStatus);
|
||||||
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 = m_rawMap.rgbSwapped();
|
|
||||||
QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
|
QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
|
||||||
if (m_ui.magnification->value() > 1) {
|
if (m_ui.magnification->value() > 1) {
|
||||||
map = map.scaled(map.size() * m_ui.magnification->value());
|
map = map.scaled(map.size() * m_ui.magnification->value());
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1273</width>
|
<width>941</width>
|
||||||
<height>736</height>
|
<height>617</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -92,8 +92,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>835</width>
|
<width>613</width>
|
||||||
<height>720</height>
|
<height>601</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
|
|
@ -19,9 +19,7 @@
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
#include <mgba/internal/gb/gb.h>
|
#include <mgba/internal/gb/gb.h>
|
||||||
#include <mgba/internal/gb/io.h>
|
|
||||||
#endif
|
#endif
|
||||||
#include <mgba-util/png-io.h>
|
|
||||||
#include <mgba-util/vfs.h>
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
using namespace QGBA;
|
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]() {
|
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
|
||||||
updateTiles(true);
|
updateTiles(true);
|
||||||
});
|
});
|
||||||
#ifdef USE_PNG
|
|
||||||
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
|
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
|
||||||
#else
|
|
||||||
m_ui.exportButton->setVisible(false);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjView::selectObj(int obj) {
|
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 GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
|
||||||
const GBAObj* obj = &gba->video.oam.obj[m_objId];
|
const GBAObj* obj = &gba->video.oam.obj[m_objId];
|
||||||
|
|
||||||
unsigned shape = GBAObjAttributesAGetShape(obj->a);
|
ObjInfo newInfo;
|
||||||
unsigned size = GBAObjAttributesBGetSize(obj->b);
|
lookupObj(m_objId, &newInfo);
|
||||||
unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
|
|
||||||
unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
|
m_ui.tiles->setTileCount(newInfo.width * newInfo.height);
|
||||||
unsigned tile = GBAObjAttributesCGetTile(obj->c);
|
m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
|
||||||
m_ui.tiles->setTileCount(width * height / 64);
|
m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
|
||||||
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
|
unsigned tileBase = newInfo.tile;
|
||||||
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
|
unsigned tile = newInfo.tile;
|
||||||
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
|
|
||||||
unsigned tileBase = tile;
|
|
||||||
unsigned paletteSet;
|
|
||||||
unsigned bits;
|
|
||||||
if (GBAObjAttributesAIs256Color(obj->a)) {
|
if (GBAObjAttributesAIs256Color(obj->a)) {
|
||||||
m_ui.palette->setText("256-color");
|
m_ui.palette->setText("256-color");
|
||||||
paletteSet = 3;
|
|
||||||
m_ui.tile->setBoundary(1024, 1, 3);
|
m_ui.tile->setBoundary(1024, 1, 3);
|
||||||
m_ui.tile->setPalette(0);
|
m_ui.tile->setPalette(0);
|
||||||
m_boundary = 1024;
|
m_boundary = 1024;
|
||||||
palette = 0;
|
tileBase *= 2;
|
||||||
tile /= 2;
|
|
||||||
bits = 8;
|
|
||||||
} else {
|
} else {
|
||||||
m_ui.palette->setText(QString::number(palette));
|
m_ui.palette->setText(QString::number(newInfo.paletteId));
|
||||||
paletteSet = 2;
|
|
||||||
m_ui.tile->setBoundary(2048, 0, 2);
|
m_ui.tile->setBoundary(2048, 0, 2);
|
||||||
m_ui.tile->setPalette(palette);
|
m_ui.tile->setPalette(newInfo.paletteId);
|
||||||
m_boundary = 2048;
|
|
||||||
bits = 4;
|
|
||||||
}
|
}
|
||||||
ObjInfo newInfo{
|
|
||||||
tile,
|
|
||||||
width / 8,
|
|
||||||
height / 8,
|
|
||||||
width / 8,
|
|
||||||
palette,
|
|
||||||
paletteSet,
|
|
||||||
bits
|
|
||||||
};
|
|
||||||
if (newInfo != m_objInfo) {
|
if (newInfo != m_objInfo) {
|
||||||
force = true;
|
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_objInfo = newInfo;
|
||||||
m_tileOffset = tile;
|
m_tileOffset = newInfo.tile;
|
||||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, paletteSet);
|
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (int y = 0; y < height / 8; ++y) {
|
for (int y = 0; y < newInfo.height; ++y) {
|
||||||
for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) {
|
for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
|
||||||
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, palette);
|
const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
|
||||||
if (data) {
|
if (data) {
|
||||||
m_ui.tiles->setTile(i, data);
|
m_ui.tiles->setTile(i, data);
|
||||||
} else if (force) {
|
} 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;
|
tile += newInfo.stride - newInfo.width;
|
||||||
tileBase += newInfo.stride - width / 8;
|
tileBase += newInfo.stride - newInfo.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b)));
|
m_ui.x->setText(QString::number(newInfo.x));
|
||||||
m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a)));
|
m_ui.y->setText(QString::number(newInfo.y));
|
||||||
m_ui.w->setText(QString::number(width));
|
m_ui.w->setText(QString::number(newInfo.width * 8));
|
||||||
m_ui.h->setText(QString::number(height));
|
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.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.priority->setText(QString::number(newInfo.priority));
|
||||||
m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b));
|
m_ui.flippedH->setChecked(newInfo.hflip);
|
||||||
m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b));
|
m_ui.flippedV->setChecked(newInfo.vflip);
|
||||||
m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a));
|
m_ui.enabled->setChecked(newInfo.enabled);
|
||||||
m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
|
m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
|
||||||
m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(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 GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
|
||||||
const GBObj* obj = &gb->video.oam.obj[m_objId];
|
const GBObj* obj = &gb->video.oam.obj[m_objId];
|
||||||
|
|
||||||
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
ObjInfo newInfo;
|
||||||
unsigned width = 8;
|
lookupObj(m_objId, &newInfo);
|
||||||
unsigned height = 8;
|
|
||||||
GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
|
mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
||||||
if (GBRegisterLCDCIsObjSize(lcdc)) {
|
unsigned tile = newInfo.tile;
|
||||||
height = 16;
|
m_ui.tiles->setTileCount(newInfo.height);
|
||||||
}
|
m_ui.tile->setBoundary(1024, 0, 0);
|
||||||
unsigned tile = obj->tile;
|
m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
|
||||||
m_ui.tiles->setTileCount(width * height / 64);
|
m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
|
||||||
m_ui.tile->setBoundary(1024, 0, 0);
|
m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
|
||||||
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{
|
|
||||||
tile,
|
|
||||||
1,
|
|
||||||
height / 8,
|
|
||||||
1,
|
|
||||||
palette,
|
|
||||||
0,
|
|
||||||
2
|
|
||||||
};
|
|
||||||
if (newInfo != m_objInfo) {
|
if (newInfo != m_objInfo) {
|
||||||
force = true;
|
force = true;
|
||||||
}
|
}
|
||||||
|
@ -223,27 +172,27 @@ void ObjView::updateTilesGB(bool force) {
|
||||||
m_boundary = 1024;
|
m_boundary = 1024;
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
m_ui.tile->setPalette(palette);
|
m_ui.tile->setPalette(newInfo.paletteId);
|
||||||
for (int y = 0; y < height / 8; ++y, ++i) {
|
for (int y = 0; y < newInfo.height; ++y, ++i) {
|
||||||
unsigned t = tile + 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) {
|
if (data) {
|
||||||
m_ui.tiles->setTile(i, data);
|
m_ui.tiles->setTile(i, data);
|
||||||
} else if (force) {
|
} 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.x->setText(QString::number(newInfo.x));
|
||||||
m_ui.y->setText(QString::number(obj->y));
|
m_ui.y->setText(QString::number(newInfo.y));
|
||||||
m_ui.w->setText(QString::number(width));
|
m_ui.w->setText(QString::number(8));
|
||||||
m_ui.h->setText(QString::number(height));
|
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.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.priority->setText(QString::number(newInfo.priority));
|
||||||
m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr));
|
m_ui.flippedH->setChecked(newInfo.hflip);
|
||||||
m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr));
|
m_ui.flippedV->setChecked(newInfo.vflip);
|
||||||
m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160);
|
m_ui.enabled->setChecked(newInfo.enabled);
|
||||||
m_ui.doubleSize->setChecked(false);
|
m_ui.doubleSize->setChecked(false);
|
||||||
m_ui.mosaic->setChecked(false);
|
m_ui.mosaic->setChecked(false);
|
||||||
m_ui.transform->setText(tr("N/A"));
|
m_ui.transform->setText(tr("N/A"));
|
||||||
|
@ -251,51 +200,10 @@ void ObjView::updateTilesGB(bool force) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_PNG
|
|
||||||
void ObjView::exportObj() {
|
void ObjView::exportObj() {
|
||||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
|
QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
|
||||||
tr("Portable Network Graphics (*.png)"));
|
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);
|
CoreController::Interrupter interrupter(m_controller);
|
||||||
png_structp png = PNGWriteOpen(vf);
|
QImage obj = compositeObj(m_objInfo);
|
||||||
png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8);
|
obj.save(filename, "PNG");
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,8 @@ Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||||
|
|
||||||
#ifdef USE_PNG
|
|
||||||
public slots:
|
public slots:
|
||||||
void exportObj();
|
void exportObj();
|
||||||
#endif
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void selectObj(int);
|
void selectObj(int);
|
||||||
|
@ -43,17 +41,7 @@ private:
|
||||||
std::shared_ptr<CoreController> m_controller;
|
std::shared_ptr<CoreController> m_controller;
|
||||||
mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size
|
mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size
|
||||||
int m_objId = 0;
|
int m_objId = 0;
|
||||||
struct ObjInfo {
|
ObjInfo m_objInfo = {};
|
||||||
unsigned tile;
|
|
||||||
unsigned width;
|
|
||||||
unsigned height;
|
|
||||||
unsigned stride;
|
|
||||||
unsigned paletteId;
|
|
||||||
unsigned paletteSet;
|
|
||||||
unsigned bits;
|
|
||||||
|
|
||||||
bool operator!=(const ObjInfo&);
|
|
||||||
} m_objInfo = {};
|
|
||||||
|
|
||||||
int m_tileOffset;
|
int m_tileOffset;
|
||||||
int m_boundary;
|
int m_boundary;
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "DebuggerConsoleController.h"
|
#include "DebuggerConsoleController.h"
|
||||||
#include "Display.h"
|
#include "Display.h"
|
||||||
#include "CoreController.h"
|
#include "CoreController.h"
|
||||||
|
#include "FrameView.h"
|
||||||
#include "GBAApp.h"
|
#include "GBAApp.h"
|
||||||
#include "GDBController.h"
|
#include "GDBController.h"
|
||||||
#include "GDBWindow.h"
|
#include "GDBWindow.h"
|
||||||
|
@ -1437,7 +1438,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
m_overrideView->recheck();
|
m_overrideView->recheck();
|
||||||
}, "tools");
|
}, "tools");
|
||||||
|
|
||||||
m_actions.addAction(tr("Game &Pak sensors..."), "sensorWindow", [this]() {
|
m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() {
|
||||||
if (!m_sensorView) {
|
if (!m_sensorView) {
|
||||||
m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController));
|
m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController));
|
||||||
if (m_controller) {
|
if (m_controller) {
|
||||||
|
@ -1467,6 +1468,12 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools");
|
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools");
|
||||||
addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools");
|
addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools");
|
||||||
addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "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("View memory..."), "memoryView", openControllerTView<MemoryView>(), "tools");
|
||||||
addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools");
|
addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue