all repos — mgba @ 28151ee65c2e1189b6093cb5f36d4ab46f8a4ae0

mGBA Game Boy Advance Emulator

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

  1/* Copyright (c) 2013-2017 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 "MapView.h"
  7
  8#include "CoreController.h"
  9#include "GBAApp.h"
 10#include "LogController.h"
 11
 12#include <mgba-util/png-io.h>
 13#include <mgba-util/vfs.h>
 14#ifdef M_CORE_GBA
 15#include <mgba/internal/gba/gba.h>
 16#include <mgba/internal/gba/io.h>
 17#include <mgba/internal/gba/memory.h>
 18#include <mgba/internal/gba/video.h>
 19#endif
 20#ifdef M_CORE_GB
 21#include <mgba/internal/gb/gb.h>
 22#include <mgba/internal/gb/memory.h>
 23#endif
 24
 25#include <QButtonGroup>
 26#include <QFontDatabase>
 27#include <QMouseEvent>
 28#include <QRadioButton>
 29#include <QTimer>
 30
 31using namespace QGBA;
 32
 33MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
 34	: AssetView(controller, parent)
 35	, m_controller(controller)
 36{
 37	m_ui.setupUi(this);
 38	m_ui.tile->setController(controller);
 39
 40	switch (m_controller->platform()) {
 41#ifdef M_CORE_GBA
 42	case PLATFORM_GBA:
 43		m_boundary = 2048;
 44		m_addressBase = BASE_VRAM;
 45		m_addressWidth = 8;
 46		m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
 47		m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
 48		m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
 49		m_ui.bgInfo->addCustomProperty("size", tr("Size"));
 50		m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
 51		m_ui.bgInfo->addCustomProperty("transform", tr("Xform"));
 52		break;
 53#endif
 54#ifdef M_CORE_GB
 55	case PLATFORM_GB:
 56		m_boundary = 1024;
 57		m_addressBase = GB_BASE_VRAM;
 58		m_addressWidth = 4;
 59		m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
 60		m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
 61		m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
 62		break;
 63#endif
 64	default:
 65		return;
 66	}
 67	m_ui.tile->setBoundary(m_boundary, 0, 0);
 68
 69	connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
 70		updateTiles(true);
 71	});
 72
 73	CoreController::Interrupter interrupter(m_controller);
 74	const mCoreChannelInfo* videoLayers;
 75	size_t nVideo = m_controller->thread()->core->listVideoLayers(m_controller->thread()->core, &videoLayers);
 76	QButtonGroup* group = new QButtonGroup(this);
 77	for (size_t i = 0; i < nVideo; ++i) {
 78		if (strncmp(videoLayers[i].internalName, "bg", 2) != 0) {
 79			continue;
 80		}
 81		QRadioButton* button = new QRadioButton(tr(videoLayers[i].visibleName));
 82		if (!i) {
 83			button->setChecked(true);
 84		}
 85		m_ui.bgLayout->addWidget(button);
 86		connect(button, &QAbstractButton::pressed, button, [this, i]() {
 87			selectMap(i);
 88		});
 89		group->addButton(button);
 90	}
 91#ifdef USE_PNG
 92	connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MapView::exportMap);
 93#else
 94	m_ui.exportButton->setVisible(false);
 95#endif
 96	m_ui.map->installEventFilter(this);
 97	m_ui.tile->addCustomProperty("mapAddr", tr("Map Addr."));
 98	m_ui.tile->addCustomProperty("flip", tr("Mirror"));
 99	selectTile(0, 0);
100}
101
102void MapView::selectMap(int map) {
103	if (map >= mMapCacheSetSize(&m_cacheSet->maps)) {
104		return;
105	}
106	if (map == m_map) {
107		return;
108	}
109	m_map = map;
110	updateTiles(true);
111}
112
113void MapView::selectTile(int x, int y) {
114	CoreController::Interrupter interrupter(m_controller);
115	mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
116	size_t tileCache = mTileCacheSetIndex(&m_cacheSet->tiles, mapCache->tileCache);
117	m_ui.tile->setBoundary(m_boundary, tileCache, tileCache);
118	uint32_t location = mMapCacheTileId(mapCache, x, y);
119	mMapCacheEntry* entry = &m_mapStatus[location];
120	m_ui.tile->selectIndex(entry->tileId + mapCache->tileStart);
121	m_ui.tile->setPalette(mMapCacheEntryFlagsGetPaletteId(entry->flags));
122	m_ui.tile->setFlip(mMapCacheEntryFlagsGetHMirror(entry->flags), mMapCacheEntryFlagsGetVMirror(entry->flags));
123	location <<= (mMapCacheSystemInfoGetMapAlign(mapCache->sysConfig));
124	location += m_addressBase + mapCache->mapStart;
125
126	QString flip(tr("None"));
127	if (mMapCacheEntryFlagsGetHMirror(entry->flags) && mMapCacheEntryFlagsGetVMirror(entry->flags)) {
128		flip = tr("Both");
129	} else if (mMapCacheEntryFlagsGetHMirror(entry->flags)) {
130		flip = tr("Horizontal");
131	} else if (mMapCacheEntryFlagsGetVMirror(entry->flags)) {
132		flip = tr("Vertical");
133	}
134	m_ui.tile->setCustomProperty("flip", flip);
135	m_ui.tile->setCustomProperty("mapAddr", QString("%0%1")
136		.arg(m_addressWidth == 8 ? "0x" : "")
137		.arg(location, m_addressWidth, 16, QChar('0')));
138}
139
140bool MapView::eventFilter(QObject* obj, QEvent* event) {
141	if (event->type() != QEvent::MouseButtonPress) {
142		return false;
143	}
144	int x = static_cast<QMouseEvent*>(event)->x();
145	int y = static_cast<QMouseEvent*>(event)->y();
146	x /= 8 * m_ui.magnification->value();
147	y /= 8 * m_ui.magnification->value();
148	selectTile(x, y);
149	return true;
150}
151
152void MapView::updateTilesGBA(bool force) {
153	{
154		CoreController::Interrupter interrupter(m_controller);
155		int bitmap = -1;
156		int priority = -1;
157		int frame = 0;
158		QString offset(tr("N/A"));
159		QString transform(tr("N/A"));
160		if (m_controller->platform() == PLATFORM_GBA) {
161			uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io;
162			int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]);
163			if (m_map == 2 && mode > 2) {
164				bitmap = mode == 4 ? 1 : 0;
165				if (mode != 3) {
166					frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
167				}
168			}
169			priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
170			if (mode == 0 || (mode == 1 && m_map != 2)) {
171				offset = QString("%1, %2")
172					.arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
173					.arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]);
174			} else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) {
175				int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
176				refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;
177				int32_t refY = io[(REG_BG2Y_LO >> 1) + ((m_map - 2) << 2)];
178				refY |= io[(REG_BG2Y_HI >> 1) + ((m_map - 2) << 2)] << 16;
179				refX <<= 4;
180				refY <<= 4;
181				refX >>= 4;
182				refY >>= 4;
183				offset = QString("%1\n%2").arg(refX / 65536., 0, 'f', 3).arg(refY / 65536., 0, 'f', 3);
184				transform = QString("%1 %2\n%3 %4")
185					.arg(io[(REG_BG2PA >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
186					.arg(io[(REG_BG2PB >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
187					.arg(io[(REG_BG2PC >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
188					.arg(io[(REG_BG2PD >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2);
189
190			}
191		}
192		if (m_controller->platform() == PLATFORM_GB) {
193			uint8_t* io = static_cast<GB*>(m_controller->thread()->core->board)->memory.io;
194			int x = io[m_map == 0 ? 0x42 : 0x4A];
195			int y = io[m_map == 0 ? 0x43 : 0x4B];
196			offset = QString("%1, %2").arg(x).arg(y);
197		}
198		if (bitmap >= 0) {
199			mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap);
200			int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig);
201			int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig);
202			m_ui.bgInfo->setCustomProperty("screenBase", QString("0x%1").arg(m_addressBase + bitmapCache->bitsStart[frame], 8, 16, QChar('0')));
203			m_ui.bgInfo->setCustomProperty("charBase", tr("N/A"));
204			m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(width).arg(height));
205			m_ui.bgInfo->setCustomProperty("priority", priority);
206			m_ui.bgInfo->setCustomProperty("offset", offset);
207			m_ui.bgInfo->setCustomProperty("transform", transform);
208			m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32);
209			uchar* bgBits = m_rawMap.bits();
210			for (int j = 0; j < height; ++j) {
211				mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
212				memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
213			}
214			m_rawMap = m_rawMap.rgbSwapped();
215		} else {
216			mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
217			int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
218			int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
219			m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1")
220					.arg(m_addressWidth == 8 ? "0x" : "")
221					.arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0')));
222			m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1")
223					.arg(m_addressWidth == 8 ? "0x" : "")
224					.arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0')));
225			m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8));
226			m_ui.bgInfo->setCustomProperty("priority", priority);
227			m_ui.bgInfo->setCustomProperty("offset", offset);
228			m_ui.bgInfo->setCustomProperty("transform", transform);
229			m_rawMap = compositeMap(m_map, m_mapStatus);
230		}
231	}
232	QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
233	if (m_ui.magnification->value() > 1) {
234		map = map.scaled(map.size() * m_ui.magnification->value());
235	}
236	m_ui.map->setPixmap(map);
237}
238
239#ifdef M_CORE_GB
240void MapView::updateTilesGB(bool force) {
241	updateTilesGBA(force);
242}
243#endif
244
245#ifdef USE_PNG
246void MapView::exportMap() {
247	QString filename = GBAApp::app()->getSaveFileName(this, tr("Export map"),
248	                                                  tr("Portable Network Graphics (*.png)"));
249	VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
250	if (!vf) {
251		LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);
252		return;
253	}
254
255	CoreController::Interrupter interrupter(m_controller);
256	png_structp png = PNGWriteOpen(vf);
257	png_infop info = PNGWriteHeaderA(png, m_rawMap.width(), m_rawMap.height());
258
259	QImage map = m_rawMap.rgbSwapped();
260	PNGWritePixelsA(png, map.width(), map.height(), map.bytesPerLine() / 4, static_cast<const void*>(map.constBits()));
261	PNGWriteClose(png, info);
262	vf->close(vf);
263}
264#endif