all repos — mgba @ 3c5a9258a72ef9b7f5904783857c42232064119d

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