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