1 /* Copyright (c) 2013-2019 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 "AssetView.h"
7 
8 #include "CoreController.h"
9 
10 #include <QTimer>
11 
12 #ifdef M_CORE_GBA
13 #include <mgba/internal/gba/gba.h>
14 #endif
15 #ifdef M_CORE_GB
16 #include <mgba/internal/gb/gb.h>
17 #include <mgba/internal/gb/io.h>
18 #endif
19 
20 #include <mgba/core/map-cache.h>
21 
22 using namespace QGBA;
23 
AssetView(std::shared_ptr<CoreController> controller,QWidget * parent)24 AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent)
25 	: QWidget(parent)
26 	, m_cacheSet(controller->graphicCaches())
27 	, m_controller(controller)
28 {
29 	m_updateTimer.setSingleShot(true);
30 	m_updateTimer.setInterval(1);
31 	connect(&m_updateTimer, &QTimer::timeout, this, static_cast<void(AssetView::*)()>(&AssetView::updateTiles));
32 
33 	connect(controller.get(), &CoreController::frameAvailable, &m_updateTimer,
34 	        static_cast<void(QTimer::*)()>(&QTimer::start));
35 	connect(controller.get(), &CoreController::stopping, this, &AssetView::close);
36 	connect(controller.get(), &CoreController::stopping, &m_updateTimer, &QTimer::stop);
37 }
38 
updateTiles()39 void AssetView::updateTiles() {
40 	updateTiles(false);
41 }
42 
updateTiles(bool force)43 void AssetView::updateTiles(bool force) {
44 	switch (m_controller->platform()) {
45 #ifdef M_CORE_GBA
46 	case PLATFORM_GBA:
47 		updateTilesGBA(force);
48 		break;
49 #endif
50 #ifdef M_CORE_GB
51 	case PLATFORM_GB:
52 		updateTilesGB(force);
53 		break;
54 #endif
55 	default:
56 		return;
57 	}
58 }
59 
resizeEvent(QResizeEvent *)60 void AssetView::resizeEvent(QResizeEvent*) {
61 	updateTiles(true);
62 }
63 
showEvent(QShowEvent *)64 void AssetView::showEvent(QShowEvent*) {
65 	updateTiles(true);
66 }
67 
compositeTile(const void * tBuffer,void * buffer,size_t stride,size_t x,size_t y,int depth)68 void AssetView::compositeTile(const void* tBuffer, void* buffer, size_t stride, size_t x, size_t y, int depth) {
69 	if (!tBuffer) {
70 		return;
71 	}
72 	const uint8_t* tile = static_cast<const uint8_t*>(tBuffer);
73 	uint8_t* pixels = static_cast<uint8_t*>(buffer);
74 	size_t base = stride * y + x;
75 	switch (depth) {
76 	case 2:
77 		for (size_t i = 0; i < 8; ++i) {
78 			uint8_t tileDataLower = tile[i * 2];
79 			uint8_t tileDataUpper = tile[i * 2 + 1];
80 			uint8_t pixel;
81 			pixel = ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
82 			pixels[base + i * stride] = pixel;
83 			pixel = ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
84 			pixels[base + i * stride + 1] = pixel;
85 			pixel = ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
86 			pixels[base + i * stride + 2] = pixel;
87 			pixel = ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
88 			pixels[base + i * stride + 3] = pixel;
89 			pixel = ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
90 			pixels[base + i * stride + 4] = pixel;
91 			pixel = ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
92 			pixels[base + i * stride + 5] = pixel;
93 			pixel = (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
94 			pixels[base + i * stride + 6] = pixel;
95 			pixel = ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
96 			pixels[base + i * stride + 7] = pixel;
97 		}
98 		break;
99 	case 4:
100 		for (size_t j = 0; j < 8; ++j) {
101 			for (size_t i = 0; i < 4; ++i) {
102 				pixels[base + j * stride + i * 2] =  tile[j * 4 + i] & 0xF;
103 				pixels[base + j * stride + i * 2 + 1] =  tile[j * 4 + i] >> 4;
104 			}
105 		}
106 		break;
107 	case 8:
108 		for (size_t i = 0; i < 8; ++i) {
109 			memcpy(&pixels[base + i * stride], &tile[i * 8], 8);
110 		}
111 		break;
112 	}
113 }
114 
compositeMap(int map,mMapCacheEntry * mapStatus)115 QImage AssetView::compositeMap(int map, mMapCacheEntry* mapStatus) {
116 	mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, map);
117 	int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
118 	int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
119 	QImage rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
120 	uchar* bgBits = rawMap.bits();
121 	for (int j = 0; j < tilesH; ++j) {
122 		for (int i = 0; i < tilesW; ++i) {
123 			mMapCacheCleanTile(mapCache, mapStatus, i, j);
124 		}
125 		for (int i = 0; i < 8; ++i) {
126 			memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32);
127 		}
128 	}
129 	return rawMap.rgbSwapped();
130 }
131 
compositeObj(const ObjInfo & objInfo)132 QImage AssetView::compositeObj(const ObjInfo& objInfo) {
133 	mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, objInfo.paletteSet);
134 	const color_t* rawPalette = mTileCacheGetPalette(tileCache, objInfo.paletteId);
135 	unsigned colors = 1 << objInfo.bits;
136 	QVector<QRgb> palette;
137 
138 	palette.append(rawPalette[0] & 0xFFFFFF);
139 	for (unsigned c = 1; c < colors && c < 256; ++c) {
140 		palette.append(rawPalette[c] | 0xFF000000);
141 	}
142 
143 	QImage image = QImage(QSize(objInfo.width * 8, objInfo.height * 8), QImage::Format_Indexed8);
144 	image.setColorTable(palette);
145 	uchar* bits = image.bits();
146 	unsigned t = objInfo.tile;
147 	for (int y = 0; y < objInfo.height; ++y) {
148 		for (int x = 0; x < objInfo.width; ++x, ++t) {
149 			compositeTile(static_cast<const void*>(mTileCacheGetVRAM(tileCache, t)), bits, objInfo.width * 8, x * 8, y * 8, objInfo.bits);
150 		}
151 		t += objInfo.stride - objInfo.width;
152 	}
153 	return image.rgbSwapped();
154 }
155 
lookupObj(int id,struct ObjInfo * info)156 bool AssetView::lookupObj(int id, struct ObjInfo* info) {
157 	switch (m_controller->platform()) {
158 #ifdef M_CORE_GBA
159 	case PLATFORM_GBA:
160 		return lookupObjGBA(id, info);
161 #endif
162 #ifdef M_CORE_GB
163 	case PLATFORM_GB:
164 		return lookupObjGB(id, info);
165 #endif
166 	default:
167 		return false;
168 	}
169 }
170 
171 #ifdef M_CORE_GBA
lookupObjGBA(int id,struct ObjInfo * info)172 bool AssetView::lookupObjGBA(int id, struct ObjInfo* info) {
173 	if (id > 127) {
174 		return false;
175 	}
176 
177 	const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
178 	const GBAObj* obj = &gba->video.oam.obj[id];
179 
180 	unsigned shape = GBAObjAttributesAGetShape(obj->a);
181 	unsigned size = GBAObjAttributesBGetSize(obj->b);
182 	unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
183 	unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
184 	unsigned tile = GBAObjAttributesCGetTile(obj->c);
185 	unsigned palette = GBAObjAttributesCGetPalette(obj->c);
186 	unsigned tileBase = tile;
187 	unsigned paletteSet;
188 	unsigned bits;
189 	if (GBAObjAttributesAIs256Color(obj->a)) {
190 		paletteSet = 3;
191 		palette = 0;
192 		tile /= 2;
193 		bits = 8;
194 	} else {
195 		paletteSet = 2;
196 		bits = 4;
197 	}
198 	ObjInfo newInfo{
199 		tile,
200 		width / 8,
201 		height / 8,
202 		width / 8,
203 		palette,
204 		paletteSet,
205 		bits,
206 		!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a),
207 		GBAObjAttributesCGetPriority(obj->c),
208 		GBAObjAttributesBGetX(obj->b),
209 		GBAObjAttributesAGetY(obj->a),
210 		false,
211 		false,
212 	};
213 	if (GBAObjAttributesAIsTransformed(obj->a)) {
214 		int matIndex = GBAObjAttributesBGetMatIndex(obj->b);
215 		const GBAOAMMatrix* mat = &gba->video.oam.mat[matIndex];
216 		QTransform invXform(mat->a / 256., mat->c / 256., mat->b / 256., mat->d / 256., 0, 0);
217 		newInfo.xform = invXform.inverted();
218 	} else {
219 		newInfo.hflip = bool(GBAObjAttributesBIsHFlip(obj->b));
220 		newInfo.vflip = bool(GBAObjAttributesBIsVFlip(obj->b));
221 	}
222 	GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
223 	if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
224 		newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
225 	};
226 	*info = newInfo;
227 	return true;
228 }
229 #endif
230 
231 #ifdef M_CORE_GB
lookupObjGB(int id,struct ObjInfo * info)232 bool AssetView::lookupObjGB(int id, struct ObjInfo* info) {
233 	if (id > 39) {
234 		return false;
235 	}
236 
237 	const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
238 	const GBObj* obj = &gb->video.oam.obj[id];
239 
240 	unsigned width = 8;
241 	unsigned height = 8;
242 	GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
243 	if (GBRegisterLCDCIsObjSize(lcdc)) {
244 		height = 16;
245 	}
246 	unsigned tile = obj->tile;
247 	unsigned palette = 0;
248 	if (gb->model >= GB_MODEL_CGB) {
249 		if (GBObjAttributesIsBank(obj->attr)) {
250 			tile += 512;
251 		}
252 		palette = GBObjAttributesGetCGBPalette(obj->attr);
253 	} else {
254 		palette = GBObjAttributesGetPalette(obj->attr);
255 	}
256 	palette += 8;
257 
258 	ObjInfo newInfo{
259 		tile,
260 		1,
261 		height / 8,
262 		1,
263 		palette,
264 		0,
265 		2,
266 		obj->y != 0 && obj->y < 160 && obj->x != 0 && obj->x < 168,
267 		GBObjAttributesGetPriority(obj->attr),
268 		obj->x - 8,
269 		obj->y - 16,
270 		bool(GBObjAttributesIsXFlip(obj->attr)),
271 		bool(GBObjAttributesIsYFlip(obj->attr)),
272 	};
273 	*info = newInfo;
274 	return true;
275 }
276 #endif
277 
operator !=(const ObjInfo & other) const278 bool AssetView::ObjInfo::operator!=(const ObjInfo& other) const {
279 	return other.tile != tile ||
280 		other.width != width ||
281 		other.height != height ||
282 		other.stride != stride ||
283 		other.paletteId != paletteId ||
284 		other.paletteSet != paletteSet;
285 }