all repos — mgba @ 9b0393d50ffcd27c487740098c3c0bb72b16529f

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