all repos — mgba @ f6f3cb5d3d8b91dd603772ea0eebb2513562a0cf

mGBA Game Boy Advance Emulator

src/platform/qt/ObjView.cpp (view raw)

  1/* Copyright (c) 2013-2016 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "ObjView.h"
  7
  8#include "GBAApp.h"
  9
 10#include <QFontDatabase>
 11#include <QTimer>
 12
 13#include "LogController.h"
 14#include "VFileDevice.h"
 15
 16#ifdef M_CORE_GBA
 17#include <mgba/internal/gba/gba.h>
 18#endif
 19#ifdef M_CORE_GB
 20#include <mgba/internal/gb/gb.h>
 21#include <mgba/internal/gb/io.h>
 22#endif
 23#include <mgba-util/png-io.h>
 24
 25using namespace QGBA;
 26
 27ObjView::ObjView(GameController* controller, QWidget* parent)
 28	: AssetView(controller, parent)
 29	, m_controller(controller)
 30{
 31	m_ui.setupUi(this);
 32	m_ui.tile->setController(controller);
 33
 34	const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
 35
 36	m_ui.x->setFont(font);
 37	m_ui.y->setFont(font);
 38	m_ui.w->setFont(font);
 39	m_ui.h->setFont(font);
 40	m_ui.address->setFont(font);
 41	m_ui.priority->setFont(font);
 42	m_ui.palette->setFont(font);
 43	m_ui.transform->setFont(font);
 44	m_ui.mode->setFont(font);
 45
 46	connect(m_ui.tiles, &TilePainter::indexPressed, this, &ObjView::translateIndex);
 47	connect(m_ui.objId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ObjView::selectObj);
 48	connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
 49		updateTiles(true);
 50	});
 51#ifdef USE_PNG
 52	connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
 53#else
 54	m_ui.exportButton->setVisible(false);
 55#endif
 56}
 57
 58void ObjView::selectObj(int obj) {
 59	m_objId = obj;
 60	updateTiles(true);
 61}
 62
 63void ObjView::translateIndex(int index) {
 64	unsigned x = index % m_objInfo.width;
 65	unsigned y = index / m_objInfo.width;
 66	m_ui.tile->selectIndex(x + y * m_objInfo.stride + m_tileOffset);
 67}
 68
 69#ifdef M_CORE_GBA
 70void ObjView::updateTilesGBA(bool force) {
 71	const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
 72	const GBAObj* obj = &gba->video.oam.obj[m_objId];
 73
 74	unsigned shape = GBAObjAttributesAGetShape(obj->a);
 75	unsigned size = GBAObjAttributesBGetSize(obj->b);
 76	unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
 77	unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
 78	unsigned tile = GBAObjAttributesCGetTile(obj->c);
 79	m_ui.tiles->setTileCount(width * height / 64);
 80	m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
 81	m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
 82	unsigned palette = GBAObjAttributesCGetPalette(obj->c);
 83	unsigned tileBase = tile;
 84	unsigned paletteSet;
 85	unsigned bits;
 86	if (GBAObjAttributesAIs256Color(obj->a)) {
 87		m_ui.palette->setText("256-color");
 88		paletteSet = 1;
 89		m_ui.tile->setPalette(0);
 90		m_ui.tile->setPaletteSet(1, 1024, 1536);
 91		palette = 1;
 92		tile = tile / 2 + 1024;
 93		bits = 8;
 94	} else {
 95		m_ui.palette->setText(QString::number(palette));
 96		paletteSet = 0;
 97		m_ui.tile->setPalette(palette);
 98		m_ui.tile->setPaletteSet(0, 2048, 3072);
 99		palette += 16;
100		tile += 2048;
101		bits = 4;
102	}
103	ObjInfo newInfo{
104		tile,
105		width / 8,
106		height / 8,
107		width / 8,
108		palette,
109		paletteSet,
110		bits
111	};
112	if (newInfo != m_objInfo) {
113		force = true;
114	}
115	GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
116	if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
117		newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
118	};
119	m_objInfo = newInfo;
120	m_tileOffset = tile;
121	mTileCacheSetPalette(m_tileCache.get(), paletteSet);
122
123	int i = 0;
124	for (int y = 0; y < height / 8; ++y) {
125		for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) {
126			const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * tileBase], tile, palette);
127			if (data) {
128				m_ui.tiles->setTile(i, data);
129			} else if (force) {
130				m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), tile, palette));
131			}
132		}
133		tile += newInfo.stride - width / 8;
134		tileBase += newInfo.stride - width / 8;
135	}
136
137	m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b)));
138	m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a)));
139	m_ui.w->setText(QString::number(width));
140	m_ui.h->setText(QString::number(height));
141
142	m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
143	m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c)));
144	m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b));
145	m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b));
146	m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a));
147	m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
148	m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
149
150	if (GBAObjAttributesAIsTransformed(obj->a)) {
151		m_ui.transform->setText(QString::number(GBAObjAttributesBGetMatIndex(obj->b)));
152	} else {
153		m_ui.transform->setText(tr("Off"));
154	}
155
156	switch (GBAObjAttributesAGetMode(obj->a)) {
157	case OBJ_MODE_NORMAL:
158		m_ui.mode->setText(tr("Normal"));
159		break;
160	case OBJ_MODE_SEMITRANSPARENT:
161		m_ui.mode->setText(tr("Trans"));
162		break;
163	case OBJ_MODE_OBJWIN:
164		m_ui.mode->setText(tr("OBJWIN"));
165		break;
166	default:
167		m_ui.mode->setText(tr("Invalid"));
168		break;
169	}
170}
171#endif
172
173#ifdef M_CORE_GB
174void ObjView::updateTilesGB(bool force) {
175	const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
176	const GBObj* obj = &gb->video.oam.obj[m_objId];
177
178	unsigned width = 8;
179	unsigned height = 8;
180	GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
181	if (GBRegisterLCDCIsObjSize(lcdc)) {
182		height = 16;
183	}
184	unsigned tile = obj->tile;
185	m_ui.tiles->setTileCount(width * height / 64);
186	m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
187	m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
188	unsigned palette = 0;
189	if (gb->model >= GB_MODEL_CGB) {
190		if (GBObjAttributesIsBank(obj->attr)) {
191			tile += 512;
192		}
193		palette = GBObjAttributesGetCGBPalette(obj->attr);
194	} else {
195		palette = GBObjAttributesGetPalette(obj->attr);
196	}
197	m_ui.palette->setText(QString::number(palette));
198	palette += 8;
199
200	ObjInfo newInfo{
201		tile,
202		1,
203		height / 8,
204		1,
205		palette,
206		0,
207		2
208	};
209	if (newInfo != m_objInfo) {
210		force = true;
211	}
212	m_objInfo = newInfo;
213	m_tileOffset = tile;
214
215	int i = 0;
216	mTileCacheSetPalette(m_tileCache.get(), 0);
217	m_ui.tile->setPalette(palette);
218	m_ui.tile->setPaletteSet(0, 512, 1024);
219	for (int y = 0; y < height / 8; ++y, ++i) {
220		unsigned t = tile + i;
221		const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[16 * t], t, palette);
222		if (data) {
223			m_ui.tiles->setTile(i, data);
224		} else if (force) {
225			m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t, palette));
226		}
227	}
228
229	m_ui.x->setText(QString::number(obj->x));
230	m_ui.y->setText(QString::number(obj->y));
231	m_ui.w->setText(QString::number(width));
232	m_ui.h->setText(QString::number(height));
233
234	m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
235	m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr)));
236	m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr));
237	m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr));
238	m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160);
239	m_ui.doubleSize->setChecked(false);
240	m_ui.mosaic->setChecked(false);
241	m_ui.transform->setText(tr("N/A"));
242	m_ui.mode->setText(tr("N/A"));
243}
244#endif
245
246#ifdef USE_PNG
247void ObjView::exportObj() {
248	GameController::Interrupter interrupter(m_controller);
249	QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
250	                                                  tr("Portable Network Graphics (*.png)"));
251	VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
252	if (!vf) {
253		LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);
254		return;
255	}
256
257	mTileCacheSetPalette(m_tileCache.get(), m_objInfo.paletteSet);
258	png_structp png = PNGWriteOpen(vf);
259	png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8);
260
261	const uint16_t* rawPalette = mTileCacheGetPalette(m_tileCache.get(), m_objInfo.paletteId);
262	unsigned colors = 1 << m_objInfo.bits;
263	uint32_t palette[256];
264	for (unsigned c = 0; c < colors && c < 256; ++c) {
265		uint16_t color = rawPalette[c];
266		palette[c] = M_R8(rawPalette[c]);
267		palette[c] |= M_G8(rawPalette[c]) << 8;
268		palette[c] |= M_B8(rawPalette[c]) << 16;
269		if (c) {
270			palette[c] |= 0xFF000000;
271		}
272	}
273	PNGWritePalette(png, info, palette, colors);
274
275	uint8_t* buffer = new uint8_t[m_objInfo.width * m_objInfo.height * 8 * 8];
276	unsigned t = m_objInfo.tile;
277	for (int y = 0; y < m_objInfo.height; ++y) {
278		for (int x = 0; x < m_objInfo.width; ++x, ++t) {
279			compositeTile(t, static_cast<void*>(buffer), m_objInfo.width * 8, x * 8, y * 8, m_objInfo.bits);
280		}
281		t += m_objInfo.stride - m_objInfo.width;
282	}
283	PNGWritePixels8(png, m_objInfo.width * 8, m_objInfo.height * 8, m_objInfo.width * 8, static_cast<void*>(buffer));
284	PNGWriteClose(png, info);
285	delete[] buffer;
286}
287#endif
288
289bool ObjView::ObjInfo::operator!=(const ObjInfo& other) {
290	return other.tile != tile ||
291		other.width != width ||
292		other.height != height ||
293		other.stride != stride ||
294		other.paletteId != paletteId ||
295		other.paletteSet != paletteSet;
296}