all repos — mgba @ 498aa541fc660c8755cda99cf8889171b3bd5381

mGBA Game Boy Advance Emulator

src/ds/gx.c (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 <mgba/internal/ds/gx.h>
   7
   8#include <mgba/internal/ds/ds.h>
   9#include <mgba/internal/ds/io.h>
  10
  11mLOG_DEFINE_CATEGORY(DS_GX, "DS GX", "ds.gx");
  12
  13#define DS_GX_FIFO_SIZE 256
  14#define DS_GX_PIPE_SIZE 4
  15
  16static void DSGXDummyRendererInit(struct DSGXRenderer* renderer);
  17static void DSGXDummyRendererReset(struct DSGXRenderer* renderer);
  18static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer);
  19static void DSGXDummyRendererInvalidateTex(struct DSGXRenderer* renderer, int slot);
  20static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort);
  21static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y);
  22static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, const color_t** output);
  23
  24static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry);
  25
  26static bool _boxTestVertex(struct DSGX* gx, struct DSGXVertex* vertex);
  27
  28static const int32_t _gxCommandCycleBase[DS_GX_CMD_MAX] = {
  29	[DS_GX_CMD_NOP] = 0,
  30	[DS_GX_CMD_MTX_MODE] = 2,
  31	[DS_GX_CMD_MTX_PUSH] = 34,
  32	[DS_GX_CMD_MTX_POP] = 72,
  33	[DS_GX_CMD_MTX_STORE] = 34,
  34	[DS_GX_CMD_MTX_RESTORE] = 72,
  35	[DS_GX_CMD_MTX_IDENTITY] = 38,
  36	[DS_GX_CMD_MTX_LOAD_4x4] = 68,
  37	[DS_GX_CMD_MTX_LOAD_4x3] = 60,
  38	[DS_GX_CMD_MTX_MULT_4x4] = 70,
  39	[DS_GX_CMD_MTX_MULT_4x3] = 62,
  40	[DS_GX_CMD_MTX_MULT_3x3] = 56,
  41	[DS_GX_CMD_MTX_SCALE] = 44,
  42	[DS_GX_CMD_MTX_TRANS] = 44,
  43	[DS_GX_CMD_COLOR] = 2,
  44	[DS_GX_CMD_NORMAL] = 18,
  45	[DS_GX_CMD_TEXCOORD] = 2,
  46	[DS_GX_CMD_VTX_16] = 18,
  47	[DS_GX_CMD_VTX_10] = 16,
  48	[DS_GX_CMD_VTX_XY] = 16,
  49	[DS_GX_CMD_VTX_XZ] = 16,
  50	[DS_GX_CMD_VTX_YZ] = 16,
  51	[DS_GX_CMD_VTX_DIFF] = 16,
  52	[DS_GX_CMD_POLYGON_ATTR] = 2,
  53	[DS_GX_CMD_TEXIMAGE_PARAM] = 2,
  54	[DS_GX_CMD_PLTT_BASE] = 2,
  55	[DS_GX_CMD_DIF_AMB] = 8,
  56	[DS_GX_CMD_SPE_EMI] = 8,
  57	[DS_GX_CMD_LIGHT_VECTOR] = 12,
  58	[DS_GX_CMD_LIGHT_COLOR] = 2,
  59	[DS_GX_CMD_SHININESS] = 64,
  60	[DS_GX_CMD_BEGIN_VTXS] = 2,
  61	[DS_GX_CMD_END_VTXS] = 2,
  62	[DS_GX_CMD_SWAP_BUFFERS] = 784,
  63	[DS_GX_CMD_VIEWPORT] = 2,
  64	[DS_GX_CMD_BOX_TEST] = 206,
  65	[DS_GX_CMD_POS_TEST] = 18,
  66	[DS_GX_CMD_VEC_TEST] = 10,
  67};
  68
  69static const int32_t _gxCommandParams[DS_GX_CMD_MAX] = {
  70	[DS_GX_CMD_MTX_MODE] = 1,
  71	[DS_GX_CMD_MTX_POP] = 1,
  72	[DS_GX_CMD_MTX_STORE] = 1,
  73	[DS_GX_CMD_MTX_RESTORE] = 1,
  74	[DS_GX_CMD_MTX_LOAD_4x4] = 16,
  75	[DS_GX_CMD_MTX_LOAD_4x3] = 12,
  76	[DS_GX_CMD_MTX_MULT_4x4] = 16,
  77	[DS_GX_CMD_MTX_MULT_4x3] = 12,
  78	[DS_GX_CMD_MTX_MULT_3x3] = 9,
  79	[DS_GX_CMD_MTX_SCALE] = 3,
  80	[DS_GX_CMD_MTX_TRANS] = 3,
  81	[DS_GX_CMD_COLOR] = 1,
  82	[DS_GX_CMD_NORMAL] = 1,
  83	[DS_GX_CMD_TEXCOORD] = 1,
  84	[DS_GX_CMD_VTX_16] = 2,
  85	[DS_GX_CMD_VTX_10] = 1,
  86	[DS_GX_CMD_VTX_XY] = 1,
  87	[DS_GX_CMD_VTX_XZ] = 1,
  88	[DS_GX_CMD_VTX_YZ] = 1,
  89	[DS_GX_CMD_VTX_DIFF] = 1,
  90	[DS_GX_CMD_POLYGON_ATTR] = 1,
  91	[DS_GX_CMD_TEXIMAGE_PARAM] = 1,
  92	[DS_GX_CMD_PLTT_BASE] = 1,
  93	[DS_GX_CMD_DIF_AMB] = 1,
  94	[DS_GX_CMD_SPE_EMI] = 1,
  95	[DS_GX_CMD_LIGHT_VECTOR] = 1,
  96	[DS_GX_CMD_LIGHT_COLOR] = 1,
  97	[DS_GX_CMD_SHININESS] = 32,
  98	[DS_GX_CMD_BEGIN_VTXS] = 1,
  99	[DS_GX_CMD_SWAP_BUFFERS] = 1,
 100	[DS_GX_CMD_VIEWPORT] = 1,
 101	[DS_GX_CMD_BOX_TEST] = 3,
 102	[DS_GX_CMD_POS_TEST] = 2,
 103	[DS_GX_CMD_VEC_TEST] = 1,
 104};
 105
 106static struct DSGXRenderer dummyRenderer = {
 107	.init = DSGXDummyRendererInit,
 108	.reset = DSGXDummyRendererReset,
 109	.deinit = DSGXDummyRendererDeinit,
 110	.invalidateTex = DSGXDummyRendererInvalidateTex,
 111	.setRAM = DSGXDummyRendererSetRAM,
 112	.drawScanline = DSGXDummyRendererDrawScanline,
 113	.getScanline = DSGXDummyRendererGetScanline,
 114};
 115
 116static void _pullPipe(struct DSGX* gx) {
 117	if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
 118		struct DSGXEntry entry = { 0 };
 119		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
 120		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
 121		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
 122		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
 123		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
 124		CircleBufferWrite8(&gx->pipe, entry.command);
 125		CircleBufferWrite8(&gx->pipe, entry.params[0]);
 126		CircleBufferWrite8(&gx->pipe, entry.params[1]);
 127		CircleBufferWrite8(&gx->pipe, entry.params[2]);
 128		CircleBufferWrite8(&gx->pipe, entry.params[3]);
 129	}
 130	if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
 131		struct DSGXEntry entry = { 0 };
 132		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
 133		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
 134		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
 135		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
 136		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
 137		CircleBufferWrite8(&gx->pipe, entry.command);
 138		CircleBufferWrite8(&gx->pipe, entry.params[0]);
 139		CircleBufferWrite8(&gx->pipe, entry.params[1]);
 140		CircleBufferWrite8(&gx->pipe, entry.params[2]);
 141		CircleBufferWrite8(&gx->pipe, entry.params[3]);
 142	}
 143}
 144
 145static void _updateClipMatrix(struct DSGX* gx) {
 146	DSGXMtxMultiply(&gx->clipMatrix, &gx->posMatrix, &gx->projMatrix);
 147	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_00 >> 1] = gx->clipMatrix.m[0];
 148	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_01 >> 1] = gx->clipMatrix.m[0] >> 16;
 149	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_02 >> 1] = gx->clipMatrix.m[1];
 150	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_03 >> 1] = gx->clipMatrix.m[1] >> 16;
 151	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_04 >> 1] = gx->clipMatrix.m[2];
 152	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_05 >> 1] = gx->clipMatrix.m[2] >> 16;
 153	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_06 >> 1] = gx->clipMatrix.m[3];
 154	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_07 >> 1] = gx->clipMatrix.m[3] >> 16;
 155	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_08 >> 1] = gx->clipMatrix.m[4];
 156	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_09 >> 1] = gx->clipMatrix.m[4] >> 16;
 157	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0A >> 1] = gx->clipMatrix.m[5];
 158	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0B >> 1] = gx->clipMatrix.m[5] >> 16;
 159	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0C >> 1] = gx->clipMatrix.m[6];
 160	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0D >> 1] = gx->clipMatrix.m[6] >> 16;
 161	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0E >> 1] = gx->clipMatrix.m[7];
 162	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0F >> 1] = gx->clipMatrix.m[7] >> 16;
 163	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_10 >> 1] = gx->clipMatrix.m[8];
 164	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_11 >> 1] = gx->clipMatrix.m[8] >> 16;
 165	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_12 >> 1] = gx->clipMatrix.m[9];
 166	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_13 >> 1] = gx->clipMatrix.m[9] >> 16;
 167	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_14 >> 1] = gx->clipMatrix.m[10];
 168	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_15 >> 1] = gx->clipMatrix.m[10] >> 16;
 169	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_16 >> 1] = gx->clipMatrix.m[11];
 170	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_17 >> 1] = gx->clipMatrix.m[11] >> 16;
 171	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_18 >> 1] = gx->clipMatrix.m[12];
 172	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_19 >> 1] = gx->clipMatrix.m[12] >> 16;
 173	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1A >> 1] = gx->clipMatrix.m[13];
 174	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1B >> 1] = gx->clipMatrix.m[13] >> 16;
 175	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1C >> 1] = gx->clipMatrix.m[14];
 176	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1D >> 1] = gx->clipMatrix.m[14] >> 16;
 177	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1E >> 1] = gx->clipMatrix.m[15];
 178	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1F >> 1] = gx->clipMatrix.m[15] >> 16;
 179}
 180
 181static bool _clipPolygon(struct DSGX* gx, struct DSGXPolygon* poly) {
 182	struct DSGXVertex* vbuf = gx->vertexBuffer[gx->bufferIndex];
 183	int nOffscreen = 0;
 184	unsigned offscreenVerts[4] = {};
 185	int v;
 186	// Collect offscreen vertices
 187	for (v = 0; v < poly->verts; ++v) {
 188		if (!_boxTestVertex(gx, &vbuf[gx->currentPoly.vertIds[v]])) {
 189			offscreenVerts[nOffscreen] = gx->currentPoly.vertIds[v];
 190			++nOffscreen;
 191		}
 192	}
 193	if (!nOffscreen) {
 194		return true;
 195	}
 196
 197	// All of the vertices are offscreen
 198	// TODO: clip vertices instead of discarding them
 199	if (nOffscreen == poly->verts) {
 200		// Remove all vertices before remaking them
 201		gx->vertexIndex -= poly->verts;
 202		// Put back the ones that are on the strip
 203		for (v = 0; v < gx->currentPoly.verts; ++v) {
 204			unsigned vid = gx->currentPoly.vertIds[v];
 205			int ov;
 206			for (ov = 0; ov < nOffscreen; ++ov) {
 207				if (vid != offscreenVerts[ov]) {
 208					continue;
 209				}
 210				vbuf[gx->vertexIndex] = vbuf[vid];
 211				gx->currentPoly.vertIds[v] = gx->vertexIndex;
 212				++gx->vertexIndex;
 213				break;
 214			}
 215		}
 216		return false;
 217	}
 218	return true;
 219}
 220
 221static int32_t _dotViewport(struct DSGXVertex* vertex, int32_t* col) {
 222	int64_t a;
 223	int64_t b;
 224	int64_t sum;
 225	a = col[0];
 226	b = vertex->x;
 227	sum = a * b;
 228	a = col[4];
 229	b = vertex->y;
 230	sum += a * b;
 231	a = col[8];
 232	b = vertex->z;
 233	sum += a * b;
 234	a = col[12];
 235	b = MTX_ONE;
 236	sum += a * b;
 237	return sum >> 8LL;
 238}
 239
 240static int16_t _dotTexture(struct DSGXVertex* vertex, int mode, int32_t* col) {
 241	int64_t a;
 242	int64_t b;
 243	int64_t sum;
 244	switch (mode) {
 245	case 1:
 246		a = col[0];
 247		b = vertex->s << 8;
 248		sum = a * b;
 249		a = col[4];
 250		b = vertex->t << 8;
 251		sum += a * b;
 252		a = col[8];
 253		b = MTX_ONE >> 4;
 254		sum += a * b;
 255		a = col[12];
 256		b = MTX_ONE >> 4;
 257		sum += a * b;
 258		break;
 259	case 2:
 260		return 0;
 261	case 3:
 262		a = col[0];
 263		b = vertex->vx << 8;
 264		sum = a * b;
 265		a = col[4];
 266		b = vertex->vy << 8;
 267		sum += a * b;
 268		a = col[8];
 269		b = vertex->vz << 8;
 270		sum += a * b;
 271		a = col[12];
 272		b = MTX_ONE;
 273		sum += a * b;
 274	}
 275	return sum >> 20;
 276}
 277
 278static void _emitVertex(struct DSGX* gx, uint16_t x, uint16_t y, uint16_t z) {
 279	if (gx->vertexMode < 0 || gx->vertexIndex == DS_GX_VERTEX_BUFFER_SIZE || gx->polygonIndex == DS_GX_POLYGON_BUFFER_SIZE) {
 280		return;
 281	}
 282	gx->currentVertex.x = x;
 283	gx->currentVertex.y = y;
 284	gx->currentVertex.z = z;
 285	gx->currentVertex.vx = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[0]);
 286	gx->currentVertex.vy = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[1]);
 287	gx->currentVertex.vz = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[2]);
 288	gx->currentVertex.vw = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[3]);
 289
 290	// TODO: What to do if w is 0?
 291
 292	gx->currentVertex.vx = (gx->currentVertex.vx + gx->currentVertex.vw) * (int64_t) (gx->viewportWidth << 12) / (gx->currentVertex.vw * 2) + (gx->viewportX1 << 12);
 293	gx->currentVertex.vy = (gx->currentVertex.vy + gx->currentVertex.vw) * (int64_t) (gx->viewportHeight << 12) / (gx->currentVertex.vw * 2) + (gx->viewportY1 << 12);
 294	//gx->currentVertex.vw = 0x40000000 / gx->currentVertex.vw;
 295
 296	if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) > 0) {
 297		int32_t m12 = gx->texMatrix.m[12];
 298		int32_t m13 = gx->texMatrix.m[13];
 299		if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) > 1) {
 300			gx->texMatrix.m[12] = gx->currentVertex.vs;
 301			gx->texMatrix.m[13] = gx->currentVertex.vt;
 302		}
 303		gx->currentVertex.vs = _dotTexture(&gx->currentVertex, DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams), &gx->texMatrix.m[0]);
 304		gx->currentVertex.vt = _dotTexture(&gx->currentVertex, DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams), &gx->texMatrix.m[1]);
 305		gx->texMatrix.m[12] = m12;
 306		gx->texMatrix.m[13] = m13;
 307	} else {
 308		gx->currentVertex.vs = gx->currentVertex.s;
 309		gx->currentVertex.vt = gx->currentVertex.t;
 310	}
 311
 312	struct DSGXVertex* vbuf = gx->vertexBuffer[gx->bufferIndex];
 313	vbuf[gx->vertexIndex] = gx->currentVertex;
 314
 315	gx->currentPoly.vertIds[gx->currentPoly.verts] = gx->vertexIndex;
 316
 317	++gx->vertexIndex;
 318	++gx->currentPoly.verts;
 319	int totalVertices;
 320	switch (gx->vertexMode) {
 321	case 0:
 322	case 2:
 323		totalVertices = 3;
 324		break;
 325	case 1:
 326	case 3:
 327		totalVertices = 4;
 328		break;
 329	}
 330	if (gx->currentPoly.verts == totalVertices) {
 331		struct DSGXPolygon* pbuf = gx->polygonBuffer[gx->bufferIndex];
 332		pbuf[gx->polygonIndex] = gx->currentPoly;
 333
 334		switch (gx->vertexMode) {
 335		case 0:
 336		case 1:
 337			gx->currentPoly.verts = 0;
 338			break;
 339		case 2:
 340			gx->currentPoly.vertIds[0] = gx->currentPoly.vertIds[1];
 341			gx->currentPoly.vertIds[1] = gx->currentPoly.vertIds[2];
 342			gx->currentPoly.verts = 2;
 343			break;
 344		case 3:
 345			gx->currentPoly.vertIds[0] = gx->currentPoly.vertIds[2];
 346			gx->currentPoly.vertIds[1] = gx->currentPoly.vertIds[3];
 347			// Ensure quads don't cross over
 348			pbuf[gx->polygonIndex].vertIds[2] = gx->currentPoly.vertIds[3];
 349			pbuf[gx->polygonIndex].vertIds[3] = gx->currentPoly.vertIds[2];
 350			gx->currentPoly.verts = 2;
 351			break;
 352		}
 353
 354		if (_clipPolygon(gx, &pbuf[gx->polygonIndex])) {
 355			++gx->polygonIndex;
 356		}
 357	}
 358}
 359
 360static void _flushOutstanding(struct DSGX* gx) {
 361	if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(struct DSGXEntry))) {
 362		return;
 363	}
 364	if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
 365		gx->p->cpuBlocked &= ~DS_CPU_BLOCK_GX;
 366		DSGXWriteFIFO(gx, gx->outstandingEntry);
 367		gx->outstandingEntry.command = 0;
 368	}
 369	while (gx->outstandingCommand[0] && !gx->outstandingParams[0]) {
 370		if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(struct DSGXEntry))) {
 371			return;
 372		}
 373		DSGXWriteFIFO(gx, (struct DSGXEntry) { 0 });
 374	}
 375}
 376
 377static bool _boxTestVertex(struct DSGX* gx, struct DSGXVertex* vertex) {
 378	int32_t vx = _dotViewport(vertex, &gx->clipMatrix.m[0]);
 379	int32_t vy = _dotViewport(vertex, &gx->clipMatrix.m[1]);
 380	int32_t vz = _dotViewport(vertex, &gx->clipMatrix.m[2]);
 381	int32_t vw = _dotViewport(vertex, &gx->clipMatrix.m[3]);
 382
 383	vx = (vx + vw) * (int64_t) (gx->viewportWidth << 12) / (vw * 2) + (gx->viewportX1 << 12);
 384	vy = (vy + vw) * (int64_t) (gx->viewportHeight << 12) / (vw * 2) + (gx->viewportY1 << 12);
 385	vx >>= 12;
 386	vy >>= 12;
 387
 388	if (vx < gx->viewportX1) {
 389		return false;
 390	}
 391	if (vx >= gx->viewportX2) {
 392		return false;
 393	}
 394	if (vy < gx->viewportY1) {
 395		return false;
 396	}
 397	if (vy >= gx->viewportY2) {
 398		return false;
 399	}
 400	// TODO: depth clipping
 401	return true;
 402}
 403
 404static bool _boxTest(struct DSGX* gx) {
 405	int16_t x = gx->activeEntries[0].params[0];
 406	x |= gx->activeEntries[0].params[1] << 8;
 407	int16_t y = gx->activeEntries[0].params[2];
 408	y |= gx->activeEntries[0].params[3] << 8;
 409	int16_t z = gx->activeEntries[1].params[0];
 410	z |= gx->activeEntries[1].params[1] << 8;
 411	int16_t w = gx->activeEntries[1].params[2];
 412	w |= gx->activeEntries[1].params[3] << 8;
 413	int16_t h = gx->activeEntries[2].params[0];
 414	h |= gx->activeEntries[2].params[1] << 8;
 415	int16_t d = gx->activeEntries[2].params[2];
 416	d |= gx->activeEntries[2].params[3] << 8;
 417
 418	struct DSGXVertex vertex = {
 419		.x = x,
 420		.y = y,
 421		.z = z
 422	};
 423	if (_boxTestVertex(gx, &vertex)) {
 424		return true;
 425	}
 426
 427	vertex.x += w;
 428	if (_boxTestVertex(gx, &vertex)) {
 429		return true;
 430	}
 431
 432	vertex.x = x;
 433	vertex.y += h;
 434	if (_boxTestVertex(gx, &vertex)) {
 435		return true;
 436	}
 437
 438	vertex.x += w;
 439	if (_boxTestVertex(gx, &vertex)) {
 440		return true;
 441	}
 442
 443	vertex.x = x;
 444	vertex.y = y;
 445	vertex.z += d;
 446	if (_boxTestVertex(gx, &vertex)) {
 447		return true;
 448	}
 449
 450	vertex.x += w;
 451	if (_boxTestVertex(gx, &vertex)) {
 452		return true;
 453	}
 454
 455	vertex.x = x;
 456	vertex.y += h;
 457	if (_boxTestVertex(gx, &vertex)) {
 458		return true;
 459	}
 460
 461	vertex.x += w;
 462	if (_boxTestVertex(gx, &vertex)) {
 463		return true;
 464	}
 465
 466	return false;
 467}
 468
 469static void _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 470	struct DSGX* gx = context;
 471	uint32_t cycles;
 472	bool first = true;
 473	while (!gx->swapBuffers) {
 474		if (CircleBufferSize(&gx->pipe) <= 2 * sizeof(struct DSGXEntry)) {
 475			_pullPipe(gx);
 476		}
 477
 478		if (!CircleBufferSize(&gx->pipe)) {
 479			cycles = 0;
 480			break;
 481		}
 482
 483		DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
 484		int projMatrixPointer = DSRegGXSTATGetProjMatrixStackLevel(gxstat);
 485
 486		struct DSGXEntry entry = { 0 };
 487		CircleBufferDump(&gx->pipe, (int8_t*) &entry.command, 1);
 488		cycles = _gxCommandCycleBase[entry.command];
 489
 490		if (first) {
 491			first = false;
 492		} else if (!gx->activeParams && cycles > cyclesLate) {
 493			break;
 494		}
 495		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.command);
 496		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[0]);
 497		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[1]);
 498		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[2]);
 499		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[3]);
 500
 501		if (gx->activeParams) {
 502			int index = _gxCommandParams[entry.command] - gx->activeParams;
 503			gx->activeEntries[index] = entry;
 504			--gx->activeParams;
 505		} else {
 506			gx->activeParams = _gxCommandParams[entry.command];
 507			if (gx->activeParams) {
 508				--gx->activeParams;
 509			}
 510			if (gx->activeParams) {
 511				gx->activeEntries[0] = entry;
 512			}
 513		}
 514
 515		if (gx->activeParams) {
 516			continue;
 517		}
 518
 519		switch (entry.command) {
 520		case DS_GX_CMD_MTX_MODE:
 521			if (entry.params[0] < 4) {
 522				gx->mtxMode = entry.params[0];
 523			} else {
 524				mLOG(DS_GX, GAME_ERROR, "Invalid GX MTX_MODE %02X", entry.params[0]);
 525			}
 526			break;
 527		case DS_GX_CMD_MTX_PUSH:
 528			switch (gx->mtxMode) {
 529			case 0:
 530				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrix));
 531				++projMatrixPointer;
 532				break;
 533			case 2:
 534				memcpy(&gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->vecMatrix, sizeof(gx->vecMatrix));
 535				// Fall through
 536			case 1:
 537				memcpy(&gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->posMatrix, sizeof(gx->posMatrix));
 538				++gx->pvMatrixPointer;
 539				break;
 540			case 3:
 541				mLOG(DS_GX, STUB, "Unimplemented GX MTX_PUSH mode");
 542				break;
 543			}
 544			break;
 545		case DS_GX_CMD_MTX_POP: {
 546			int8_t offset = entry.params[0];
 547			offset <<= 2;
 548			offset >>= 2;
 549			switch (gx->mtxMode) {
 550			case 0:
 551				projMatrixPointer -= offset;
 552				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
 553				break;
 554			case 1:
 555				gx->pvMatrixPointer -= offset;
 556				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
 557				break;
 558			case 2:
 559				gx->pvMatrixPointer -= offset;
 560				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->vecMatrix));
 561				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
 562				break;
 563			case 3:
 564				mLOG(DS_GX, STUB, "Unimplemented GX MTX_POP mode");
 565				break;
 566			}
 567			_updateClipMatrix(gx);
 568			break;
 569		}
 570		case DS_GX_CMD_MTX_STORE: {
 571			int8_t offset = entry.params[0] & 0x1F;
 572			// TODO: overflow
 573			switch (gx->mtxMode) {
 574			case 0:
 575				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrixStack));
 576				break;
 577			case 2:
 578				memcpy(&gx->vecMatrixStack[offset], &gx->vecMatrix, sizeof(gx->vecMatrix));
 579				// Fall through
 580			case 1:
 581				memcpy(&gx->posMatrixStack[offset], &gx->posMatrix, sizeof(gx->posMatrix));
 582				break;
 583			case 3:
 584				mLOG(DS_GX, STUB, "Unimplemented GX MTX_STORE mode");
 585				break;
 586			}
 587			break;
 588		}
 589		case DS_GX_CMD_MTX_RESTORE: {
 590			int8_t offset = entry.params[0] & 0x1F;
 591			// TODO: overflow
 592			switch (gx->mtxMode) {
 593			case 0:
 594				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
 595				break;
 596			case 2:
 597				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[offset], sizeof(gx->vecMatrix));
 598				// Fall through
 599			case 1:
 600				memcpy(&gx->posMatrix, &gx->posMatrixStack[offset], sizeof(gx->posMatrix));
 601				break;
 602			case 3:
 603				mLOG(DS_GX, STUB, "Unimplemented GX MTX_RESTORE mode");
 604				break;
 605			}
 606			_updateClipMatrix(gx);
 607			break;
 608		}
 609		case DS_GX_CMD_MTX_IDENTITY:
 610			switch (gx->mtxMode) {
 611			case 0:
 612				DSGXMtxIdentity(&gx->projMatrix);
 613				break;
 614			case 2:
 615				DSGXMtxIdentity(&gx->vecMatrix);
 616				// Fall through
 617			case 1:
 618				DSGXMtxIdentity(&gx->posMatrix);
 619				break;
 620			case 3:
 621				DSGXMtxIdentity(&gx->texMatrix);
 622				break;
 623			}
 624			_updateClipMatrix(gx);
 625			break;
 626		case DS_GX_CMD_MTX_LOAD_4x4: {
 627			struct DSGXMatrix m;
 628			int i;
 629			for (i = 0; i < 16; ++i) {
 630				m.m[i] = gx->activeEntries[i].params[0];
 631				m.m[i] |= gx->activeEntries[i].params[1] << 8;
 632				m.m[i] |= gx->activeEntries[i].params[2] << 16;
 633				m.m[i] |= gx->activeEntries[i].params[3] << 24;
 634			}
 635			switch (gx->mtxMode) {
 636			case 0:
 637				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
 638				break;
 639			case 2:
 640				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
 641				// Fall through
 642			case 1:
 643				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
 644				break;
 645			case 3:
 646				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
 647				break;
 648			}
 649			_updateClipMatrix(gx);
 650			break;
 651		}
 652		case DS_GX_CMD_MTX_LOAD_4x3: {
 653			struct DSGXMatrix m;
 654			int i, j;
 655			for (j = 0; j < 4; ++j) {
 656				for (i = 0; i < 3; ++i) {
 657					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 658					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 659					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 660					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 661				}
 662				m.m[j * 4 + 3] = 0;
 663			}
 664			m.m[15] = MTX_ONE;
 665			switch (gx->mtxMode) {
 666			case 0:
 667				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
 668				break;
 669			case 2:
 670				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
 671				// Fall through
 672			case 1:
 673				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
 674				break;
 675			case 3:
 676				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
 677				break;
 678			}
 679			_updateClipMatrix(gx);
 680			break;
 681		}
 682		case DS_GX_CMD_MTX_MULT_4x4: {
 683			struct DSGXMatrix m;
 684			int i;
 685			for (i = 0; i < 16; ++i) {
 686				m.m[i] = gx->activeEntries[i].params[0];
 687				m.m[i] |= gx->activeEntries[i].params[1] << 8;
 688				m.m[i] |= gx->activeEntries[i].params[2] << 16;
 689				m.m[i] |= gx->activeEntries[i].params[3] << 24;
 690			}
 691			switch (gx->mtxMode) {
 692			case 0:
 693				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 694				break;
 695			case 2:
 696				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 697				// Fall through
 698			case 1:
 699				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 700				break;
 701			case 3:
 702				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 703				break;
 704			}
 705			_updateClipMatrix(gx);
 706			break;
 707		}
 708		case DS_GX_CMD_MTX_MULT_4x3: {
 709			struct DSGXMatrix m;
 710			int i, j;
 711			for (j = 0; j < 4; ++j) {
 712				for (i = 0; i < 3; ++i) {
 713					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 714					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 715					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 716					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 717				}
 718				m.m[j * 4 + 3] = 0;
 719			}
 720			m.m[15] = MTX_ONE;
 721			switch (gx->mtxMode) {
 722			case 0:
 723				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 724				break;
 725			case 2:
 726				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 727				// Fall through
 728			case 1:
 729				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 730				break;
 731			case 3:
 732				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 733				break;
 734			}
 735			_updateClipMatrix(gx);
 736			break;
 737		}
 738		case DS_GX_CMD_MTX_MULT_3x3: {
 739			struct DSGXMatrix m;
 740			int i, j;
 741			for (j = 0; j < 3; ++j) {
 742				for (i = 0; i < 3; ++i) {
 743					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 744					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 745					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 746					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 747				}
 748				m.m[j * 4 + 3] = 0;
 749			}
 750			m.m[12] = 0;
 751			m.m[13] = 0;
 752			m.m[14] = 0;
 753			m.m[15] = MTX_ONE;
 754			switch (gx->mtxMode) {
 755			case 0:
 756				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 757				break;
 758			case 2:
 759				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 760				// Fall through
 761			case 1:
 762				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 763				break;
 764			case 3:
 765				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 766				break;
 767			}
 768			_updateClipMatrix(gx);
 769			break;
 770		}
 771		case DS_GX_CMD_MTX_TRANS: {
 772			int32_t m[3];
 773			m[0] = gx->activeEntries[0].params[0];
 774			m[0] |= gx->activeEntries[0].params[1] << 8;
 775			m[0] |= gx->activeEntries[0].params[2] << 16;
 776			m[0] |= gx->activeEntries[0].params[3] << 24;
 777			m[1] = gx->activeEntries[1].params[0];
 778			m[1] |= gx->activeEntries[1].params[1] << 8;
 779			m[1] |= gx->activeEntries[1].params[2] << 16;
 780			m[1] |= gx->activeEntries[1].params[3] << 24;
 781			m[2] = gx->activeEntries[2].params[0];
 782			m[2] |= gx->activeEntries[2].params[1] << 8;
 783			m[2] |= gx->activeEntries[2].params[2] << 16;
 784			m[2] |= gx->activeEntries[2].params[3] << 24;
 785			switch (gx->mtxMode) {
 786			case 0:
 787				DSGXMtxTranslate(&gx->projMatrix, m);
 788				break;
 789			case 2:
 790				DSGXMtxTranslate(&gx->vecMatrix, m);
 791				// Fall through
 792			case 1:
 793				DSGXMtxTranslate(&gx->posMatrix, m);
 794				break;
 795			case 3:
 796				DSGXMtxTranslate(&gx->texMatrix, m);
 797				break;
 798			}
 799			_updateClipMatrix(gx);
 800			break;
 801		}
 802		case DS_GX_CMD_MTX_SCALE: {
 803			int32_t m[3];
 804			m[0] = gx->activeEntries[0].params[0];
 805			m[0] |= gx->activeEntries[0].params[1] << 8;
 806			m[0] |= gx->activeEntries[0].params[2] << 16;
 807			m[0] |= gx->activeEntries[0].params[3] << 24;
 808			m[1] = gx->activeEntries[1].params[0];
 809			m[1] |= gx->activeEntries[1].params[1] << 8;
 810			m[1] |= gx->activeEntries[1].params[2] << 16;
 811			m[1] |= gx->activeEntries[1].params[3] << 24;
 812			m[2] = gx->activeEntries[2].params[0];
 813			m[2] |= gx->activeEntries[2].params[1] << 8;
 814			m[2] |= gx->activeEntries[2].params[2] << 16;
 815			m[2] |= gx->activeEntries[2].params[3] << 24;
 816			switch (gx->mtxMode) {
 817			case 0:
 818				DSGXMtxScale(&gx->projMatrix, m);
 819				break;
 820			case 2:
 821				DSGXMtxScale(&gx->vecMatrix, m);
 822				// Fall through
 823			case 1:
 824				DSGXMtxScale(&gx->posMatrix, m);
 825				break;
 826			case 3:
 827				DSGXMtxScale(&gx->texMatrix, m);
 828				break;
 829			}
 830			_updateClipMatrix(gx);
 831			break;
 832		}
 833		case DS_GX_CMD_COLOR:
 834			gx->currentVertex.color = entry.params[0];
 835			gx->currentVertex.color |= entry.params[1] << 8;
 836			break;
 837		case DS_GX_CMD_TEXCOORD:
 838			gx->currentVertex.s = entry.params[0];
 839			gx->currentVertex.s |= entry.params[1] << 8;
 840			gx->currentVertex.t = entry.params[2];
 841			gx->currentVertex.t |= entry.params[3] << 8;
 842			break;
 843		case DS_GX_CMD_VTX_16: {
 844			int16_t x = gx->activeEntries[0].params[0];
 845			x |= gx->activeEntries[0].params[1] << 8;
 846			int16_t y = gx->activeEntries[0].params[2];
 847			y |= gx->activeEntries[0].params[3] << 8;
 848			int16_t z = gx->activeEntries[1].params[0];
 849			z |= gx->activeEntries[1].params[1] << 8;
 850			_emitVertex(gx, x, y, z);
 851			break;
 852		}
 853		case DS_GX_CMD_VTX_10: {
 854			int32_t xyz = entry.params[0];
 855			xyz |= entry.params[1] << 8;
 856			xyz |= entry.params[2] << 16;
 857			xyz |= entry.params[3] << 24;
 858			int16_t x = (xyz << 6) & 0xFFC0;
 859			int16_t y = (xyz >> 4) & 0xFFC0;
 860			int16_t z = (xyz >> 14) & 0xFFC0;
 861			_emitVertex(gx, x, y, z);
 862			break;
 863		}
 864		case DS_GX_CMD_VTX_XY: {
 865			int16_t x = entry.params[0];
 866			x |= entry.params[1] << 8;
 867			int16_t y = entry.params[2];
 868			y |= entry.params[3] << 8;
 869			_emitVertex(gx, x, y, gx->currentVertex.z);
 870			break;
 871		}
 872		case DS_GX_CMD_VTX_XZ: {
 873			int16_t x = entry.params[0];
 874			x |= entry.params[1] << 8;
 875			int16_t z = entry.params[2];
 876			z |= entry.params[3] << 8;
 877			_emitVertex(gx, x, gx->currentVertex.y, z);
 878			break;
 879		}
 880		case DS_GX_CMD_VTX_YZ: {
 881			int16_t y = entry.params[0];
 882			y |= entry.params[1] << 8;
 883			int16_t z = entry.params[2];
 884			z |= entry.params[3] << 8;
 885			_emitVertex(gx, gx->currentVertex.x, y, z);
 886			break;
 887		}
 888		case DS_GX_CMD_VTX_DIFF: {
 889			int32_t xyz = entry.params[0];
 890			xyz |= entry.params[1] << 8;
 891			xyz |= entry.params[2] << 16;
 892			xyz |= entry.params[3] << 24;
 893			int16_t x = (xyz << 6) & 0xFFC0;
 894			int16_t y = (xyz >> 4) & 0xFFC0;
 895			int16_t z = (xyz >> 14) & 0xFFC0;
 896			_emitVertex(gx, gx->currentVertex.x + (x >> 6), gx->currentVertex.y + (y >> 6), gx->currentVertex.z + (z >> 6));
 897			break;
 898		}
 899		case DS_GX_CMD_POLYGON_ATTR:
 900			gx->nextPoly.polyParams = entry.params[0];
 901			gx->nextPoly.polyParams |= entry.params[1] << 8;
 902			gx->nextPoly.polyParams |= entry.params[2] << 16;
 903			gx->nextPoly.polyParams |= entry.params[3] << 24;
 904			break;
 905		case DS_GX_CMD_TEXIMAGE_PARAM:
 906			gx->nextPoly.texParams = entry.params[0];
 907			gx->nextPoly.texParams |= entry.params[1] << 8;
 908			gx->nextPoly.texParams |= entry.params[2] << 16;
 909			gx->nextPoly.texParams |= entry.params[3] << 24;
 910			break;
 911		case DS_GX_CMD_PLTT_BASE:
 912			gx->nextPoly.palBase = entry.params[0];
 913			gx->nextPoly.palBase |= entry.params[1] << 8;
 914			gx->nextPoly.palBase |= entry.params[2] << 16;
 915			gx->nextPoly.palBase |= entry.params[3] << 24;
 916			break;
 917		case DS_GX_CMD_BEGIN_VTXS:
 918			gx->vertexMode = entry.params[0] & 3;
 919			gx->currentPoly = gx->nextPoly;
 920			break;
 921		case DS_GX_CMD_END_VTXS:
 922			gx->vertexMode = -1;
 923			break;
 924		case DS_GX_CMD_SWAP_BUFFERS:
 925			gx->swapBuffers = true;
 926			gx->wSort = entry.params[0] & 2;
 927			memset(&gx->currentVertex, 0, sizeof(gx->currentVertex));
 928			memset(&gx->nextPoly, 0, sizeof(gx-> nextPoly));
 929			gx->currentVertex.color = 0x7FFF;
 930			break;
 931		case DS_GX_CMD_VIEWPORT:
 932			gx->viewportX1 = (uint8_t) entry.params[0];
 933			gx->viewportY1 = (uint8_t) entry.params[1];
 934			gx->viewportX2 = (uint8_t) entry.params[2];
 935			gx->viewportY2 = (uint8_t) entry.params[3];
 936			gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
 937			gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
 938			break;
 939		case DS_GX_CMD_BOX_TEST:
 940			gxstat = DSRegGXSTATClearTestBusy(gxstat);
 941			gxstat = DSRegGXSTATTestFillBoxTestResult(gxstat, _boxTest(gx));
 942			break;
 943		default:
 944			mLOG(DS_GX, STUB, "Unimplemented GX command %02X:%02X %02X %02X %02X", entry.command, entry.params[0], entry.params[1], entry.params[2], entry.params[3]);
 945			break;
 946		}
 947
 948		gxstat = DSRegGXSTATSetPVMatrixStackLevel(gxstat, gx->pvMatrixPointer);
 949		gxstat = DSRegGXSTATSetProjMatrixStackLevel(gxstat, projMatrixPointer);
 950		gxstat = DSRegGXSTATTestFillMatrixStackError(gxstat, projMatrixPointer || gx->pvMatrixPointer >= 0x1F);
 951		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
 952
 953		if (cyclesLate >= cycles) {
 954			cyclesLate -= cycles;
 955		} else {
 956			break;
 957		}
 958	}
 959	if (cycles && !gx->swapBuffers) {
 960		mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
 961	}
 962	_flushOutstanding(gx);
 963	DSGXUpdateGXSTAT(gx);
 964}
 965
 966void DSGXInit(struct DSGX* gx) {
 967	gx->renderer = &dummyRenderer;
 968	CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
 969	CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
 970	gx->vertexBuffer[0] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
 971	gx->vertexBuffer[1] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
 972	gx->polygonBuffer[0] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
 973	gx->polygonBuffer[1] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
 974	gx->fifoEvent.name = "DS GX FIFO";
 975	gx->fifoEvent.priority = 0xC;
 976	gx->fifoEvent.context = gx;
 977	gx->fifoEvent.callback = _fifoRun;
 978}
 979
 980void DSGXDeinit(struct DSGX* gx) {
 981	DSGXAssociateRenderer(gx, &dummyRenderer);
 982	CircleBufferDeinit(&gx->fifo);
 983	CircleBufferDeinit(&gx->pipe);
 984	free(gx->vertexBuffer[0]);
 985	free(gx->vertexBuffer[1]);
 986	free(gx->polygonBuffer[0]);
 987	free(gx->polygonBuffer[1]);
 988}
 989
 990void DSGXReset(struct DSGX* gx) {
 991	CircleBufferClear(&gx->fifo);
 992	CircleBufferClear(&gx->pipe);
 993	DSGXMtxIdentity(&gx->projMatrix);
 994	DSGXMtxIdentity(&gx->texMatrix);
 995	DSGXMtxIdentity(&gx->posMatrix);
 996	DSGXMtxIdentity(&gx->vecMatrix);
 997
 998	DSGXMtxIdentity(&gx->clipMatrix);
 999	DSGXMtxIdentity(&gx->projMatrixStack);
1000	DSGXMtxIdentity(&gx->texMatrixStack);
1001	int i;
1002	for (i = 0; i < 32; ++i) {
1003		DSGXMtxIdentity(&gx->posMatrixStack[i]);
1004		DSGXMtxIdentity(&gx->vecMatrixStack[i]);
1005	}
1006	gx->swapBuffers = false;
1007	gx->bufferIndex = 0;
1008	gx->vertexIndex = 0;
1009	gx->polygonIndex = 0;
1010	gx->mtxMode = 0;
1011	gx->pvMatrixPointer = 0;
1012	gx->vertexMode = -1;
1013
1014	gx->viewportX1 = 0;
1015	gx->viewportY1 = 0;
1016	gx->viewportX2 = DS_VIDEO_HORIZONTAL_PIXELS - 1;
1017	gx->viewportY2 = DS_VIDEO_VERTICAL_PIXELS - 1;
1018	gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
1019	gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
1020
1021	memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
1022	memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
1023	memset(&gx->outstandingEntry, 0, sizeof(gx->outstandingEntry));
1024	gx->activeParams = 0;
1025	memset(&gx->currentVertex, 0, sizeof(gx->currentVertex));
1026	memset(&gx->nextPoly, 0, sizeof(gx-> nextPoly));
1027	gx->currentVertex.color = 0x7FFF;
1028	gx->dmaSource = -1;
1029}
1030
1031void DSGXAssociateRenderer(struct DSGX* gx, struct DSGXRenderer* renderer) {
1032	gx->renderer->deinit(gx->renderer);
1033	gx->renderer = renderer;
1034	memcpy(gx->renderer->tex, gx->tex, sizeof(gx->renderer->tex));
1035	memcpy(gx->renderer->texPal, gx->texPal, sizeof(gx->renderer->texPal));
1036	gx->renderer->init(gx->renderer);
1037}
1038
1039void DSGXUpdateGXSTAT(struct DSGX* gx) {
1040	uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
1041	value = DSRegGXSTATIsDoIRQ(value);
1042
1043	size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
1044	// XXX
1045	if (gx->swapBuffers) {
1046		entries++;
1047	}
1048	value = DSRegGXSTATSetFIFOEntries(value, entries);
1049	value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
1050	value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
1051
1052	if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
1053		(DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
1054		DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
1055	}
1056
1057	value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
1058
1059	gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
1060
1061	struct GBADMA* dma = NULL;
1062	if (gx->dmaSource >= 0) {
1063		dma = &gx->p->ds9.memory.dma[gx->dmaSource];
1064		if (GBADMARegisterGetTiming9(dma->reg) != DS_DMA_TIMING_GEOM_FIFO) {
1065			gx->dmaSource = -1;
1066		} else if (GBADMARegisterIsEnable(dma->reg) && entries < (DS_GX_FIFO_SIZE / 2) && !dma->nextCount) {
1067			dma->nextCount = dma->count;
1068			if (dma->count > 112) {
1069				dma->nextCount = 112;
1070			}
1071			dma->when = mTimingCurrentTime(&gx->p->ds9.timing);
1072			DSDMAUpdate(&gx->p->ds9);
1073		}
1074	}
1075}
1076
1077static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
1078	gx->outstandingCommand[0] = command;
1079	gx->outstandingCommand[1] = command >> 8;
1080	gx->outstandingCommand[2] = command >> 16;
1081	gx->outstandingCommand[3] = command >> 24;
1082	if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
1083		gx->outstandingCommand[0] = 0;
1084	}
1085	if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
1086		gx->outstandingCommand[1] = 0;
1087	}
1088	if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
1089		gx->outstandingCommand[2] = 0;
1090	}
1091	if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
1092		gx->outstandingCommand[3] = 0;
1093	}
1094	gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
1095	gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
1096	gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
1097	gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
1098	_flushOutstanding(gx);
1099	DSGXUpdateGXSTAT(gx);
1100}
1101
1102static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
1103	if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(entry))) {
1104		mLOG(DS_GX, INFO, "FIFO full");
1105		if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
1106			abort();
1107		}
1108		gx->p->cpuBlocked |= DS_CPU_BLOCK_GX;
1109		gx->outstandingEntry = entry;
1110		gx->p->ds9.cpu->nextEvent = 0;
1111		return;
1112	}
1113	if (gx->outstandingCommand[0]) {
1114		entry.command = gx->outstandingCommand[0];
1115		if (gx->outstandingParams[0]) {
1116			--gx->outstandingParams[0];
1117		}
1118		if (!gx->outstandingParams[0]) {
1119			// TODO: improve this
1120			memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
1121			memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
1122			gx->outstandingParams[3] = 0;
1123			gx->outstandingCommand[3] = 0;
1124		}
1125	} else {
1126		gx->outstandingParams[0] = _gxCommandParams[entry.command];
1127		if (gx->outstandingParams[0]) {
1128			--gx->outstandingParams[0];
1129		}
1130		if (gx->outstandingParams[0]) {
1131			gx->outstandingCommand[0] = entry.command;
1132		}
1133	}
1134	uint32_t cycles = _gxCommandCycleBase[entry.command];
1135	if (!cycles) {
1136		return;
1137	}
1138	if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
1139		CircleBufferWrite8(&gx->pipe, entry.command);
1140		CircleBufferWrite8(&gx->pipe, entry.params[0]);
1141		CircleBufferWrite8(&gx->pipe, entry.params[1]);
1142		CircleBufferWrite8(&gx->pipe, entry.params[2]);
1143		CircleBufferWrite8(&gx->pipe, entry.params[3]);
1144	} else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
1145		CircleBufferWrite8(&gx->fifo, entry.command);
1146		CircleBufferWrite8(&gx->fifo, entry.params[0]);
1147		CircleBufferWrite8(&gx->fifo, entry.params[1]);
1148		CircleBufferWrite8(&gx->fifo, entry.params[2]);
1149		CircleBufferWrite8(&gx->fifo, entry.params[3]);
1150	}
1151	if (entry.command == DS_GX_CMD_BOX_TEST) {
1152		DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
1153		gxstat = DSRegGXSTATFillTestBusy(gxstat);
1154		gxstat = DSRegGXSTATClearBoxTestResult(gxstat);
1155		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
1156	}
1157	if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
1158		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
1159	}
1160
1161	_flushOutstanding(gx);
1162}
1163
1164uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
1165	uint16_t oldValue = gx->p->memory.io9[address >> 1];
1166	switch (address) {
1167	case DS9_REG_DISP3DCNT:
1168		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1169		break;
1170	case DS9_REG_GXSTAT_LO:
1171		value = DSRegGXSTATIsMatrixStackError(value);
1172		if (value) {
1173			oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
1174			oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
1175		}
1176		value = oldValue;
1177		break;
1178	case DS9_REG_GXSTAT_HI:
1179		value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
1180		gx->p->memory.io9[address >> 1] = value;
1181		DSGXUpdateGXSTAT(gx);
1182		value = gx->p->memory.io9[address >> 1];
1183		break;
1184	default:
1185		if (address < DS9_REG_GXFIFO_00) {
1186			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1187		} else if (address <= DS9_REG_GXFIFO_1F) {
1188			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1189		} else if (address < DS9_REG_GXSTAT_LO) {
1190			struct DSGXEntry entry = {
1191				.command = (address & 0x1FC) >> 2,
1192				.params = {
1193					value,
1194					value >> 8,
1195				}
1196			};
1197			if (entry.command < DS_GX_CMD_MAX) {
1198				DSGXWriteFIFO(gx, entry);
1199			}
1200		} else {
1201			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1202		}
1203		break;
1204	}
1205	return value;
1206}
1207
1208uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
1209	switch (address) {
1210	case DS9_REG_DISP3DCNT:
1211		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1212		break;
1213	case DS9_REG_GXSTAT_LO:
1214		value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
1215		value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
1216		break;
1217	default:
1218		if (address < DS9_REG_GXFIFO_00) {
1219			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1220		} else if (address <= DS9_REG_GXFIFO_1F) {
1221			if (gx->outstandingParams[0]) {
1222				struct DSGXEntry entry = {
1223					.command = gx->outstandingCommand[0],
1224					.params = {
1225						value,
1226						value >> 8,
1227						value >> 16,
1228						value >> 24
1229					}
1230				};
1231				DSGXWriteFIFO(gx, entry);
1232			} else {
1233				DSGXUnpackCommand(gx, value);
1234			}
1235		} else if (address < DS9_REG_GXSTAT_LO) {
1236			struct DSGXEntry entry = {
1237				.command = (address & 0x1FC) >> 2,
1238				.params = {
1239					value,
1240					value >> 8,
1241					value >> 16,
1242					value >> 24
1243				}
1244			};
1245			DSGXWriteFIFO(gx, entry);
1246		} else {
1247			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1248		}
1249		break;
1250	}
1251	return value;
1252}
1253
1254void DSGXFlush(struct DSGX* gx) {
1255	if (gx->swapBuffers) {
1256		gx->renderer->setRAM(gx->renderer, gx->vertexBuffer[gx->bufferIndex], gx->polygonBuffer[gx->bufferIndex], gx->polygonIndex, gx->wSort);
1257		gx->swapBuffers = false;
1258		gx->bufferIndex ^= 1;
1259		gx->vertexIndex = 0;
1260		gx->polygonIndex = 0;
1261		if (CircleBufferSize(&gx->fifo)) {
1262			mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
1263		}
1264	}
1265
1266	DSGXUpdateGXSTAT(gx);
1267}
1268
1269void DSGXScheduleDMA(struct DSCommon* dscore, int number, struct GBADMA* info) {
1270	UNUSED(info);
1271	dscore->p->gx.dmaSource = number;
1272}
1273
1274static void DSGXDummyRendererInit(struct DSGXRenderer* renderer) {
1275	UNUSED(renderer);
1276	// Nothing to do
1277}
1278
1279static void DSGXDummyRendererReset(struct DSGXRenderer* renderer) {
1280	UNUSED(renderer);
1281	// Nothing to do
1282}
1283
1284static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer) {
1285	UNUSED(renderer);
1286	// Nothing to do
1287}
1288
1289static void DSGXDummyRendererInvalidateTex(struct DSGXRenderer* renderer, int slot) {
1290	UNUSED(renderer);
1291	UNUSED(slot);
1292	// Nothing to do
1293}
1294
1295static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort) {
1296	UNUSED(renderer);
1297	UNUSED(verts);
1298	UNUSED(polys);
1299	UNUSED(polyCount);
1300	UNUSED(wSort);
1301	// Nothing to do
1302}
1303
1304static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
1305	UNUSED(renderer);
1306	UNUSED(y);
1307	// Nothing to do
1308}
1309
1310static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, const color_t** output) {
1311	UNUSED(renderer);
1312	UNUSED(y);
1313	*output = NULL;
1314	// Nothing to do
1315}