all repos — mgba @ 4fd170ac38a9c312f3dd1d0dd912d2c761f60fdb

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#include <mgba-util/math.h>
  11
  12mLOG_DEFINE_CATEGORY(DS_GX, "DS GX", "ds.gx");
  13
  14#define DS_GX_FIFO_SIZE 256
  15#define DS_GX_PIPE_SIZE 4
  16
  17static void DSGXDummyRendererInit(struct DSGXRenderer* renderer);
  18static void DSGXDummyRendererReset(struct DSGXRenderer* renderer);
  19static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer);
  20static void DSGXDummyRendererInvalidateTex(struct DSGXRenderer* renderer, int slot);
  21static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort);
  22static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y);
  23static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, const color_t** output);
  24static void DSGXDummyRendererWriteRegister(struct DSGXRenderer* renderer, uint32_t address, uint16_t value);
  25
  26static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry);
  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	.writeRegister = DSGXDummyRendererWriteRegister,
 115};
 116
 117static void _pullPipe(struct DSGX* gx) {
 118	if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
 119		struct DSGXEntry entry = { 0 };
 120		CircleBufferRead(&gx->fifo, &entry, sizeof(entry));
 121		CircleBufferWrite(&gx->pipe, &entry, sizeof(entry));
 122	}
 123	if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
 124		struct DSGXEntry entry = { 0 };
 125		CircleBufferRead(&gx->fifo, &entry, sizeof(entry));
 126		CircleBufferWrite(&gx->pipe, &entry, sizeof(entry));
 127	}
 128}
 129
 130static void _updateClipMatrix(struct DSGX* gx) {
 131	DSGXMtxMultiply(&gx->clipMatrix, &gx->posMatrix, &gx->projMatrix);
 132	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_00 >> 1] = gx->clipMatrix.m[0];
 133	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_01 >> 1] = gx->clipMatrix.m[0] >> 16;
 134	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_02 >> 1] = gx->clipMatrix.m[1];
 135	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_03 >> 1] = gx->clipMatrix.m[1] >> 16;
 136	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_04 >> 1] = gx->clipMatrix.m[2];
 137	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_05 >> 1] = gx->clipMatrix.m[2] >> 16;
 138	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_06 >> 1] = gx->clipMatrix.m[3];
 139	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_07 >> 1] = gx->clipMatrix.m[3] >> 16;
 140	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_08 >> 1] = gx->clipMatrix.m[4];
 141	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_09 >> 1] = gx->clipMatrix.m[4] >> 16;
 142	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0A >> 1] = gx->clipMatrix.m[5];
 143	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0B >> 1] = gx->clipMatrix.m[5] >> 16;
 144	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0C >> 1] = gx->clipMatrix.m[6];
 145	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0D >> 1] = gx->clipMatrix.m[6] >> 16;
 146	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0E >> 1] = gx->clipMatrix.m[7];
 147	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0F >> 1] = gx->clipMatrix.m[7] >> 16;
 148	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_10 >> 1] = gx->clipMatrix.m[8];
 149	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_11 >> 1] = gx->clipMatrix.m[8] >> 16;
 150	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_12 >> 1] = gx->clipMatrix.m[9];
 151	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_13 >> 1] = gx->clipMatrix.m[9] >> 16;
 152	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_14 >> 1] = gx->clipMatrix.m[10];
 153	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_15 >> 1] = gx->clipMatrix.m[10] >> 16;
 154	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_16 >> 1] = gx->clipMatrix.m[11];
 155	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_17 >> 1] = gx->clipMatrix.m[11] >> 16;
 156	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_18 >> 1] = gx->clipMatrix.m[12];
 157	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_19 >> 1] = gx->clipMatrix.m[12] >> 16;
 158	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1A >> 1] = gx->clipMatrix.m[13];
 159	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1B >> 1] = gx->clipMatrix.m[13] >> 16;
 160	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1C >> 1] = gx->clipMatrix.m[14];
 161	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1D >> 1] = gx->clipMatrix.m[14] >> 16;
 162	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1E >> 1] = gx->clipMatrix.m[15];
 163	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1F >> 1] = gx->clipMatrix.m[15] >> 16;
 164}
 165
 166static inline int32_t _lerp(int32_t x0, int32_t x1, int32_t q, int64_t r) {
 167	int64_t x = x1 - x0;
 168	x *= q;
 169	x /= r;
 170	x += x0;
 171	return x;
 172}
 173
 174static int _cohenSutherlandCode(const struct DSGXVertex* v) {
 175	int code = 0;
 176	if (v->viewCoord[0] < -v->viewCoord[3]) {
 177		code |= 1 << 0;
 178	} else if (v->viewCoord[0] > v->viewCoord[3]) {
 179		code |= 2 << 0;
 180	}
 181	if (v->viewCoord[1] < -v->viewCoord[3]) {
 182		code |= 1 << 2;
 183	} else if (v->viewCoord[1] > v->viewCoord[3]) {
 184		code |= 2 << 2;
 185	}
 186	if (v->viewCoord[2] < -v->viewCoord[3]) {
 187		code |= 1 << 4;
 188	} else if (v->viewCoord[2] > v->viewCoord[3]) {
 189		code |= 2 << 4;
 190	}
 191	return code;
 192}
 193
 194static bool _lerpVertex(const struct DSGXVertex* v0, const struct DSGXVertex* v1, struct DSGXVertex* out, int32_t q, int64_t r) {
 195	if (!r) {
 196		return false;
 197	}
 198	int cr0 = (v0->color) & 0x1F;
 199	int cg0 = (v0->color >> 5) & 0x1F;
 200	int cb0 = (v0->color >> 10) & 0x1F;
 201	int cr1 = (v1->color) & 0x1F;
 202	int cg1 = (v1->color >> 5) & 0x1F;
 203	int cb1 = (v1->color >> 10) & 0x1F;
 204	cr0 = _lerp(cr0, cr1, q, r) & 0x1F;
 205	cg0 = _lerp(cg0, cg1, q, r) & 0x1F;
 206	cb0 = _lerp(cb0, cb1, q, r) & 0x1F;
 207	out->color = cr0 | (cg0 << 5) | (cb0 << 10);
 208
 209	out->viewCoord[0] = _lerp(v0->viewCoord[0], v1->viewCoord[0], q, r);
 210	out->viewCoord[1] = _lerp(v0->viewCoord[1], v1->viewCoord[1], q, r);
 211	out->viewCoord[2] = _lerp(v0->viewCoord[2], v1->viewCoord[2], q, r);
 212	out->viewCoord[3] = _lerp(v0->viewCoord[3], v1->viewCoord[3], q, r);
 213	out->viewportX = v0->viewportX;
 214	out->viewportY = v0->viewportY;
 215	out->viewportWidth = v0->viewportWidth;
 216	out->viewportHeight = v0->viewportHeight;
 217
 218	out->vs = _lerp(v0->vs, v1->vs, q, r);
 219	out->vt = _lerp(v0->vt, v1->vt, q, r);
 220	return true;
 221}
 222
 223static bool _lerpVertexP(const struct DSGXVertex* v0, const struct DSGXVertex* v1, struct DSGXVertex* out, int plane, int sign) {
 224	int32_t q = v0->viewCoord[3] - sign * v0->viewCoord[plane];
 225	int64_t r = q - (v1->viewCoord[3] - sign * v1->viewCoord[plane]);
 226	return _lerpVertex(v0, v1, out, q, r);
 227}
 228
 229static bool _clipPolygon(struct DSGX* gx, struct DSGXPolygon* poly) {
 230	int nOffscreen = 0;
 231	int offscreenVerts[10] = { 0, 0, 0, 0 };
 232	unsigned oldVerts[4];
 233	int v;
 234
 235	if (!DSGXPolygonAttrsIsBackFace(poly->polyParams) || !DSGXPolygonAttrsIsFrontFace(poly->polyParams)) {
 236		// Calculate normal direction and camera dot product average
 237		int64_t nx = 0;
 238		int64_t ny = 0;
 239		int64_t nz = 0;
 240		int64_t dot = 0;
 241		int rank = 29;
 242		for (v = 0; v < poly->verts; ++v) {
 243			struct DSGXVertex v0 = gx->pendingVertices[poly->vertIds[v]];
 244			struct DSGXVertex* v1;
 245			struct DSGXVertex v2;
 246			if (v < poly->verts - 2) {
 247				v1 = &gx->pendingVertices[poly->vertIds[v + 1]];
 248				v2 = gx->pendingVertices[poly->vertIds[v + 2]];
 249			} else if (v < poly->verts - 1) {
 250				v1 = &gx->pendingVertices[poly->vertIds[v + 1]];
 251				v2 = gx->pendingVertices[poly->vertIds[v + 2 - poly->verts]];
 252			} else {
 253				v1 = &gx->pendingVertices[poly->vertIds[v + 1 - poly->verts]];
 254				v2 = gx->pendingVertices[poly->vertIds[v + 2 - poly->verts]];
 255			}
 256			// Center around v1
 257			v0.viewCoord[0] -= v1->viewCoord[0];
 258			v0.viewCoord[1] -= v1->viewCoord[1];
 259			v0.viewCoord[3] -= v1->viewCoord[3];
 260			v2.viewCoord[0] -= v1->viewCoord[0];
 261			v2.viewCoord[1] -= v1->viewCoord[1];
 262			v2.viewCoord[3] -= v1->viewCoord[3];
 263			// Calculate cross product
 264			nx = ((int64_t) v0.viewCoord[1] * v2.viewCoord[3] - (int64_t) v0.viewCoord[3] * v2.viewCoord[1]);
 265			ny = ((int64_t) v0.viewCoord[3] * v2.viewCoord[0] - (int64_t) v0.viewCoord[0] * v2.viewCoord[3]);
 266			nz = ((int64_t) v0.viewCoord[0] * v2.viewCoord[1] - (int64_t) v0.viewCoord[1] * v2.viewCoord[0]);
 267			int rx = 64 - clz64(nx >= 0 ? nx : -nx);
 268			int ry = 64 - clz64(ny >= 0 ? ny : -ny);
 269			int rz = 64 - clz64(nz >= 0 ? nz : -nz);
 270			if (ry > rx) {
 271				rx = ry;
 272			}
 273			if (rz > rx) {
 274				rx = rz;
 275			}
 276			if (rx > rank) {
 277				dot >>= rx - rank;
 278				rank = rx;
 279			}
 280			if (rank > 29) {
 281				nx >>= rank - 29;
 282				ny >>= rank - 29;
 283				nz >>= rank - 29;
 284			}
 285
 286			dot += nx * v1->viewCoord[0] + ny * v1->viewCoord[1] + nz * v1->viewCoord[3];
 287		}
 288		if (!DSGXPolygonAttrsIsFrontFace(poly->polyParams) && dot < 0) {
 289			return false;
 290		}
 291		if (!DSGXPolygonAttrsIsBackFace(poly->polyParams) && dot > 0) {
 292			return false;
 293		}
 294	}
 295
 296	// Collect offscreen vertices
 297	for (v = 0; v < poly->verts; ++v) {
 298		offscreenVerts[v] = _cohenSutherlandCode(&gx->pendingVertices[poly->vertIds[v]]);
 299		oldVerts[v] = poly->vertIds[v];
 300		if (offscreenVerts[v]) {
 301			++nOffscreen;
 302		}
 303	}
 304
 305	struct DSGXVertex* vbuf = gx->vertexBuffer[gx->bufferIndex];
 306
 307	if (!nOffscreen) {
 308		for (v = 0; v < poly->verts; ++v) {
 309			if (gx->vertexIndex == DS_GX_VERTEX_BUFFER_SIZE) {
 310				return false;
 311			}
 312			int vertexId = oldVerts[v];
 313			if (gx->pendingVertexIds[vertexId] >= 0) {
 314				poly->vertIds[v] = gx->pendingVertexIds[vertexId];
 315			} else {
 316				vbuf[gx->vertexIndex] = gx->pendingVertices[vertexId];
 317				gx->pendingVertexIds[vertexId] = gx->vertexIndex;
 318				poly->vertIds[v] = gx->vertexIndex;
 319				++gx->vertexIndex;
 320			}
 321		}
 322		return true;
 323	}
 324
 325	struct DSGXVertex inList[10];
 326	struct DSGXVertex outList[10];
 327	int outOffscreenVerts[10] = { 0, 0, 0, 0 };
 328	for (v = 0; v < poly->verts; ++v) {
 329		inList[v] = gx->pendingVertices[oldVerts[v]];
 330	}
 331
 332	int newV;
 333
 334	int plane;
 335	for (plane = 5; plane >= 0; --plane) {
 336		newV = 0;
 337		for (v = 0; v < poly->verts; ++v) {
 338			if (newV >= 10) {
 339				mLOG(DS_GX, ERROR, "Polygon clipping invariant failed");
 340				return false;
 341			}
 342			if (!(offscreenVerts[v] & (1 << plane))) {
 343				outList[newV] = inList[v];
 344				outOffscreenVerts[newV] = offscreenVerts[v];
 345				++newV;
 346			} else {
 347				struct DSGXVertex* in = &inList[v];
 348				struct DSGXVertex* in2;
 349				struct DSGXVertex* out;
 350				int iv;
 351
 352				if (v > 0) {
 353					iv = v - 1;
 354				} else {
 355					iv = poly->verts - 1;
 356				}
 357				if (!(offscreenVerts[iv] & (1 << plane))) {
 358					in2 = &inList[iv];
 359					out = &outList[newV];
 360					if (_lerpVertexP(in, in2, out, plane >> 1, -1 + (plane & 1) * 2)) {
 361						outOffscreenVerts[newV] = _cohenSutherlandCode(out);
 362						++newV;
 363					}
 364				}
 365
 366				if (v < poly->verts - 1) {
 367					iv = v + 1;
 368				} else {
 369					iv = 0;
 370				}
 371				if (!(offscreenVerts[iv] & (1 << plane))) {
 372					in2 = &inList[iv];
 373					out = &outList[newV];
 374					if (_lerpVertexP(in, in2, out, plane >> 1, -1 + (plane & 1) * 2)) {
 375						outOffscreenVerts[newV] = _cohenSutherlandCode(out);
 376						++newV;
 377					}
 378				}
 379			}
 380		}
 381		poly->verts = newV;
 382		memcpy(inList, outList, newV * sizeof(*inList));
 383		memcpy(offscreenVerts, outOffscreenVerts, newV * sizeof(*offscreenVerts));
 384	}
 385
 386	for (v = 0; v < poly->verts; ++v) {
 387		if (gx->vertexIndex == DS_GX_VERTEX_BUFFER_SIZE) {
 388			return false;
 389		}
 390		// TODO: merge strips
 391		vbuf[gx->vertexIndex] = inList[v];
 392		poly->vertIds[v] = gx->vertexIndex;
 393		++gx->vertexIndex;
 394	}
 395
 396	return newV > 2;
 397}
 398
 399static int32_t _dotViewport(struct DSGXVertex* vertex, int32_t* col) {
 400	int64_t a;
 401	int64_t b;
 402	int64_t sum;
 403	a = col[0];
 404	b = vertex->coord[0];
 405	sum = a * b;
 406	a = col[4];
 407	b = vertex->coord[1];
 408	sum += a * b;
 409	a = col[8];
 410	b = vertex->coord[2];
 411	sum += a * b;
 412	a = col[12];
 413	b = MTX_ONE;
 414	sum += a * b;
 415	return sum >> 8LL;
 416}
 417
 418static int16_t _dotTexture(struct DSGX* gx, int16_t* input, int mode, int c) {
 419	int64_t a;
 420	int64_t b;
 421	int64_t sum = 0;
 422	struct DSGXVertex* vertex = &gx->currentVertex;
 423	const int32_t* col = &gx->texMatrix.m[c];
 424	switch (mode) {
 425	case 1:
 426		a = col[0];
 427		b = vertex->st[0] << 8;
 428		sum = a * b;
 429		a = col[4];
 430		b = vertex->st[1] << 8;
 431		sum += a * b;
 432		a = col[8];
 433		b = MTX_ONE >> 4;
 434		sum += a * b;
 435		a = col[12];
 436		b = MTX_ONE >> 4;
 437		sum += a * b;
 438		return sum >> 20;
 439	case 2:
 440	case 3:
 441		a = col[0];
 442		b = input[0];
 443		sum = a * b;
 444		a = col[4];
 445		b = input[1];
 446		sum += a * b;
 447		a = col[8];
 448		b = input[2];
 449		sum += a * b;
 450		a = vertex->st[c] << 12;
 451		b = MTX_ONE;
 452		sum += a * b;
 453		return sum >> 24;
 454	}
 455	return input[c];
 456}
 457
 458static int32_t _dotFrac(int16_t x, int16_t y, int16_t z, int32_t* col) {
 459	int64_t a;
 460	int64_t b;
 461	int64_t sum;
 462	a = col[0];
 463	b = x;
 464	sum = a * b;
 465	a = col[4];
 466	b = y;
 467	sum += a * b;
 468	a = col[8];
 469	b = z;
 470	sum += a * b;
 471	return sum >> 12;
 472}
 473
 474static int16_t _dot3(int32_t x0, int32_t y0, int32_t z0, int32_t x1, int32_t y1, int32_t z1) {
 475	int32_t a = x0 * x1;
 476	a += y0 * y1;
 477	a += z0 * z1;
 478	a >>= 12;
 479	return a;
 480}
 481
 482static void _emitVertex(struct DSGX* gx, uint16_t x, uint16_t y, uint16_t z) {
 483	if (gx->vertexMode < 0 || gx->vertexIndex == DS_GX_VERTEX_BUFFER_SIZE || gx->polygonIndex == DS_GX_POLYGON_BUFFER_SIZE) {
 484		return;
 485	}
 486	gx->currentVertex.coord[0] = x;
 487	gx->currentVertex.coord[1] = y;
 488	gx->currentVertex.coord[2] = z;
 489	gx->currentVertex.viewCoord[0] = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[0]);
 490	gx->currentVertex.viewCoord[1] = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[1]);
 491	gx->currentVertex.viewCoord[2] = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[2]);
 492	gx->currentVertex.viewCoord[3] = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[3]);
 493
 494	if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) == 0) {
 495		gx->currentVertex.vs = gx->currentVertex.st[0];
 496		gx->currentVertex.vt = gx->currentVertex.st[1];
 497	} else if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) == 3) {
 498		gx->currentVertex.vs = _dotTexture(gx, gx->currentVertex.coord, 3, 0);
 499		gx->currentVertex.vt = _dotTexture(gx, gx->currentVertex.coord, 3, 1);
 500	}
 501
 502	gx->pendingVertices[gx->pendingVertexIndex] = gx->currentVertex;
 503	gx->currentPoly.vertIds[gx->currentPoly.verts] = gx->pendingVertexIndex;
 504	gx->pendingVertexIndex = (gx->pendingVertexIndex + 1) & 3;
 505
 506	++gx->currentPoly.verts;
 507	int totalVertices;
 508	switch (gx->vertexMode) {
 509	case 0:
 510	case 2:
 511		totalVertices = 3;
 512		break;
 513	case 1:
 514	case 3:
 515		totalVertices = 4;
 516		break;
 517	}
 518	if (gx->currentPoly.verts == totalVertices) {
 519		struct DSGXPolygon* pbuf = gx->polygonBuffer[gx->bufferIndex];
 520
 521		pbuf[gx->polygonIndex] = gx->currentPoly;
 522		switch (gx->vertexMode) {
 523		case 0:
 524		case 1:
 525			gx->currentPoly.verts = 0;
 526			break;
 527		case 2:
 528			// Reverse winding if needed
 529			if (gx->reverseWinding) {
 530				pbuf[gx->polygonIndex].vertIds[1] = gx->currentPoly.vertIds[2];
 531				pbuf[gx->polygonIndex].vertIds[2] = gx->currentPoly.vertIds[1];
 532			}
 533			gx->reverseWinding = !gx->reverseWinding;
 534			gx->currentPoly.vertIds[0] = gx->currentPoly.vertIds[1];
 535			gx->currentPoly.vertIds[1] = gx->currentPoly.vertIds[2];
 536			gx->currentPoly.verts = 2;
 537			break;
 538		case 3:
 539			gx->currentPoly.vertIds[0] = gx->currentPoly.vertIds[2];
 540			gx->currentPoly.vertIds[1] = gx->currentPoly.vertIds[3];
 541			// Ensure quads don't cross over
 542			pbuf[gx->polygonIndex].vertIds[2] = gx->currentPoly.vertIds[3];
 543			pbuf[gx->polygonIndex].vertIds[3] = gx->currentPoly.vertIds[2];
 544			gx->currentPoly.verts = 2;
 545			break;
 546		}
 547
 548		if (_clipPolygon(gx, &pbuf[gx->polygonIndex])) {
 549			++gx->polygonIndex;
 550		}
 551		if (gx->vertexMode < 2) {
 552			memset(gx->pendingVertexIds, -1, sizeof(gx->pendingVertexIds));
 553		} else {
 554			gx->pendingVertexIds[gx->pendingVertexIndex] = -1;
 555			gx->pendingVertexIds[(gx->pendingVertexIndex + 1) & 3] = -1;
 556		}
 557	}
 558}
 559
 560static void _flushOutstanding(struct DSGX* gx) {
 561	if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
 562		gx->p->cpuBlocked &= ~DS_CPU_BLOCK_GX;
 563		struct DSGXEntry entry = gx->outstandingEntry;
 564		gx->outstandingEntry.command = 0;
 565		DSGXWriteFIFO(gx, entry);
 566	}
 567	while (gx->outstandingCommand[0] && !gx->outstandingParams[0]) {
 568		if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
 569			return;
 570		}
 571		DSGXWriteFIFO(gx, (struct DSGXEntry) { gx->outstandingCommand[0] });
 572	}
 573}
 574
 575static bool _boxTest(struct DSGX* gx) {
 576	int16_t x = gx->activeEntries[0].params[0];
 577	x |= gx->activeEntries[0].params[1] << 8;
 578	int16_t y = gx->activeEntries[0].params[2];
 579	y |= gx->activeEntries[0].params[3] << 8;
 580	int16_t z = gx->activeEntries[1].params[0];
 581	z |= gx->activeEntries[1].params[1] << 8;
 582	int16_t w = gx->activeEntries[1].params[2];
 583	w |= gx->activeEntries[1].params[3] << 8;
 584	int16_t h = gx->activeEntries[2].params[0];
 585	h |= gx->activeEntries[2].params[1] << 8;
 586	int16_t d = gx->activeEntries[2].params[2];
 587	d |= gx->activeEntries[2].params[3] << 8;
 588
 589	struct DSGXVertex vertex[8] = {
 590		{ .coord = { x    , y    , z     } },
 591		{ .coord = { x    , y    , z + d } },
 592		{ .coord = { x    , y + h, z     } },
 593		{ .coord = { x    , y + h, z + d } },
 594		{ .coord = { x + w, y    , z     } },
 595		{ .coord = { x + w, y    , z + d } },
 596		{ .coord = { x + w, y + h, z     } },
 597		{ .coord = { x + w, y + h, z + d } },
 598	};
 599	int code[8];
 600	int i;
 601	for (i = 0; i < 8; ++i) {
 602		vertex[i].viewCoord[0] = _dotViewport(&vertex[i], &gx->clipMatrix.m[0]);
 603		vertex[i].viewCoord[1] = _dotViewport(&vertex[i], &gx->clipMatrix.m[1]);
 604		vertex[i].viewCoord[2] = _dotViewport(&vertex[i], &gx->clipMatrix.m[2]);
 605		vertex[i].viewCoord[3] = _dotViewport(&vertex[i], &gx->clipMatrix.m[3]);
 606		code[i] = _cohenSutherlandCode(&vertex[i]);
 607	}
 608
 609	if (!(code[0] & code[2] & code[4] & code[6])) {
 610		return true;
 611	}
 612
 613	if (!(code[1] & code[3] & code[5] & code[7])) {
 614		return true;
 615	}
 616
 617	if (!(code[0] & code[1] & code[4] & code[5])) {
 618		return true;
 619	}
 620
 621	if (!(code[2] & code[3] & code[6] & code[7])) {
 622		return true;
 623	}
 624
 625	if (!(code[0] & code[1] & code[2] & code[3])) {
 626		return true;
 627	}
 628
 629	if (!(code[4] & code[5] & code[6] & code[7])) {
 630		return true;
 631	}
 632
 633	return false;
 634}
 635
 636static void _updateDMA(struct DSGX* gx) {
 637	unsigned entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
 638	struct GBADMA* dma = NULL;
 639	if (gx->dmaSource < 0 || entries >= DS_GX_FIFO_SIZE / 2) {
 640		return;
 641	}
 642	dma = &gx->p->ds9.memory.dma[gx->dmaSource];
 643	if (GBADMARegisterGetTiming9(dma->reg) != DS_DMA_TIMING_GEOM_FIFO) {
 644		gx->dmaSource = -1;
 645	} else if (GBADMARegisterIsEnable(dma->reg) && !dma->nextCount) {
 646		dma->nextCount = dma->count;
 647		dma->when = mTimingCurrentTime(&gx->p->ds9.timing);
 648		DSDMAUpdate(&gx->p->ds9);
 649	}
 650}
 651
 652static void _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 653	struct DSGX* gx = context;
 654	uint32_t cycles;
 655	bool first = true;
 656	while (!gx->swapBuffers) {
 657		if (CircleBufferSize(&gx->pipe) <= 2 * sizeof(struct DSGXEntry)) {
 658			_pullPipe(gx);
 659		}
 660
 661		if (!CircleBufferSize(&gx->pipe)) {
 662			cycles = 0;
 663			break;
 664		}
 665
 666		DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
 667		int projMatrixPointer = DSRegGXSTATGetProjMatrixStackLevel(gxstat);
 668
 669		struct DSGXEntry entry = { 0 };
 670		CircleBufferDump(&gx->pipe, (int8_t*) &entry.command, 1);
 671		cycles = _gxCommandCycleBase[entry.command];
 672
 673		if (first) {
 674			first = false;
 675		} else if (!gx->activeParams && cycles > cyclesLate) {
 676			break;
 677		}
 678		CircleBufferRead(&gx->pipe, &entry, sizeof(entry));
 679
 680		if (gx->activeParams) {
 681			int index = _gxCommandParams[entry.command] - gx->activeParams;
 682			gx->activeEntries[index] = entry;
 683			--gx->activeParams;
 684		} else {
 685			gx->activeParams = _gxCommandParams[entry.command];
 686			if (gx->activeParams) {
 687				--gx->activeParams;
 688			}
 689			if (gx->activeParams) {
 690				gx->activeEntries[0] = entry;
 691			}
 692		}
 693
 694		if (gx->activeParams) {
 695			continue;
 696		}
 697
 698		switch (entry.command) {
 699		case DS_GX_CMD_MTX_MODE:
 700			if (entry.params[0] < 4) {
 701				gx->mtxMode = entry.params[0];
 702			} else {
 703				mLOG(DS_GX, GAME_ERROR, "Invalid GX MTX_MODE %02X", entry.params[0]);
 704			}
 705			break;
 706		case DS_GX_CMD_MTX_PUSH:
 707			switch (gx->mtxMode) {
 708			case 0:
 709				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrix));
 710				++projMatrixPointer;
 711				break;
 712			case 1:
 713			case 2:
 714				memcpy(&gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->vecMatrix, sizeof(gx->vecMatrix));
 715				memcpy(&gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->posMatrix, sizeof(gx->posMatrix));
 716				++gx->pvMatrixPointer;
 717				break;
 718			case 3:
 719				mLOG(DS_GX, STUB, "Unimplemented GX MTX_PUSH mode");
 720				break;
 721			}
 722			break;
 723		case DS_GX_CMD_MTX_POP: {
 724			int8_t offset = entry.params[0];
 725			offset <<= 2;
 726			offset >>= 2;
 727			switch (gx->mtxMode) {
 728			case 0:
 729				projMatrixPointer -= offset;
 730				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
 731				break;
 732			case 1:
 733			case 2:
 734				gx->pvMatrixPointer -= offset;
 735				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->vecMatrix));
 736				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
 737				break;
 738			case 3:
 739				mLOG(DS_GX, STUB, "Unimplemented GX MTX_POP mode");
 740				break;
 741			}
 742			_updateClipMatrix(gx);
 743			break;
 744		}
 745		case DS_GX_CMD_MTX_STORE: {
 746			int8_t offset = entry.params[0] & 0x1F;
 747			// TODO: overflow
 748			switch (gx->mtxMode) {
 749			case 0:
 750				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrixStack));
 751				break;
 752			case 1:
 753			case 2:
 754				memcpy(&gx->vecMatrixStack[offset], &gx->vecMatrix, sizeof(gx->vecMatrix));
 755				memcpy(&gx->posMatrixStack[offset], &gx->posMatrix, sizeof(gx->posMatrix));
 756				break;
 757			case 3:
 758				mLOG(DS_GX, STUB, "Unimplemented GX MTX_STORE mode");
 759				break;
 760			}
 761			break;
 762		}
 763		case DS_GX_CMD_MTX_RESTORE: {
 764			int8_t offset = entry.params[0] & 0x1F;
 765			// TODO: overflow
 766			switch (gx->mtxMode) {
 767			case 0:
 768				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
 769				break;
 770			case 1:
 771			case 2:
 772				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[offset], sizeof(gx->vecMatrix));
 773				memcpy(&gx->posMatrix, &gx->posMatrixStack[offset], sizeof(gx->posMatrix));
 774				break;
 775			case 3:
 776				mLOG(DS_GX, STUB, "Unimplemented GX MTX_RESTORE mode");
 777				break;
 778			}
 779			_updateClipMatrix(gx);
 780			break;
 781		}
 782		case DS_GX_CMD_MTX_IDENTITY:
 783			switch (gx->mtxMode) {
 784			case 0:
 785				DSGXMtxIdentity(&gx->projMatrix);
 786				break;
 787			case 2:
 788				DSGXMtxIdentity(&gx->vecMatrix);
 789				// Fall through
 790			case 1:
 791				DSGXMtxIdentity(&gx->posMatrix);
 792				break;
 793			case 3:
 794				DSGXMtxIdentity(&gx->texMatrix);
 795				break;
 796			}
 797			_updateClipMatrix(gx);
 798			break;
 799		case DS_GX_CMD_MTX_LOAD_4x4: {
 800			struct DSGXMatrix m;
 801			int i;
 802			for (i = 0; i < 16; ++i) {
 803				m.m[i] = gx->activeEntries[i].params[0];
 804				m.m[i] |= gx->activeEntries[i].params[1] << 8;
 805				m.m[i] |= gx->activeEntries[i].params[2] << 16;
 806				m.m[i] |= gx->activeEntries[i].params[3] << 24;
 807			}
 808			switch (gx->mtxMode) {
 809			case 0:
 810				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
 811				break;
 812			case 2:
 813				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
 814				// Fall through
 815			case 1:
 816				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
 817				break;
 818			case 3:
 819				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
 820				break;
 821			}
 822			_updateClipMatrix(gx);
 823			break;
 824		}
 825		case DS_GX_CMD_MTX_LOAD_4x3: {
 826			struct DSGXMatrix m;
 827			int i, j;
 828			for (j = 0; j < 4; ++j) {
 829				for (i = 0; i < 3; ++i) {
 830					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 831					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 832					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 833					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 834				}
 835				m.m[j * 4 + 3] = 0;
 836			}
 837			m.m[15] = MTX_ONE;
 838			switch (gx->mtxMode) {
 839			case 0:
 840				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
 841				break;
 842			case 2:
 843				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
 844				// Fall through
 845			case 1:
 846				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
 847				break;
 848			case 3:
 849				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
 850				break;
 851			}
 852			_updateClipMatrix(gx);
 853			break;
 854		}
 855		case DS_GX_CMD_MTX_MULT_4x4: {
 856			struct DSGXMatrix m;
 857			int i;
 858			for (i = 0; i < 16; ++i) {
 859				m.m[i] = gx->activeEntries[i].params[0];
 860				m.m[i] |= gx->activeEntries[i].params[1] << 8;
 861				m.m[i] |= gx->activeEntries[i].params[2] << 16;
 862				m.m[i] |= gx->activeEntries[i].params[3] << 24;
 863			}
 864			switch (gx->mtxMode) {
 865			case 0:
 866				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 867				break;
 868			case 2:
 869				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 870				// Fall through
 871			case 1:
 872				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 873				break;
 874			case 3:
 875				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 876				break;
 877			}
 878			_updateClipMatrix(gx);
 879			break;
 880		}
 881		case DS_GX_CMD_MTX_MULT_4x3: {
 882			struct DSGXMatrix m;
 883			int i, j;
 884			for (j = 0; j < 4; ++j) {
 885				for (i = 0; i < 3; ++i) {
 886					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 887					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 888					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 889					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 890				}
 891				m.m[j * 4 + 3] = 0;
 892			}
 893			m.m[15] = MTX_ONE;
 894			switch (gx->mtxMode) {
 895			case 0:
 896				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 897				break;
 898			case 2:
 899				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 900				// Fall through
 901			case 1:
 902				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 903				break;
 904			case 3:
 905				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 906				break;
 907			}
 908			_updateClipMatrix(gx);
 909			break;
 910		}
 911		case DS_GX_CMD_MTX_MULT_3x3: {
 912			struct DSGXMatrix m;
 913			int i, j;
 914			for (j = 0; j < 3; ++j) {
 915				for (i = 0; i < 3; ++i) {
 916					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 917					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 918					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 919					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 920				}
 921				m.m[j * 4 + 3] = 0;
 922			}
 923			m.m[12] = 0;
 924			m.m[13] = 0;
 925			m.m[14] = 0;
 926			m.m[15] = MTX_ONE;
 927			switch (gx->mtxMode) {
 928			case 0:
 929				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 930				break;
 931			case 2:
 932				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 933				// Fall through
 934			case 1:
 935				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 936				break;
 937			case 3:
 938				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 939				break;
 940			}
 941			_updateClipMatrix(gx);
 942			break;
 943		}
 944		case DS_GX_CMD_MTX_TRANS: {
 945			int32_t m[3];
 946			m[0] = gx->activeEntries[0].params[0];
 947			m[0] |= gx->activeEntries[0].params[1] << 8;
 948			m[0] |= gx->activeEntries[0].params[2] << 16;
 949			m[0] |= gx->activeEntries[0].params[3] << 24;
 950			m[1] = gx->activeEntries[1].params[0];
 951			m[1] |= gx->activeEntries[1].params[1] << 8;
 952			m[1] |= gx->activeEntries[1].params[2] << 16;
 953			m[1] |= gx->activeEntries[1].params[3] << 24;
 954			m[2] = gx->activeEntries[2].params[0];
 955			m[2] |= gx->activeEntries[2].params[1] << 8;
 956			m[2] |= gx->activeEntries[2].params[2] << 16;
 957			m[2] |= gx->activeEntries[2].params[3] << 24;
 958			switch (gx->mtxMode) {
 959			case 0:
 960				DSGXMtxTranslate(&gx->projMatrix, m);
 961				break;
 962			case 2:
 963				DSGXMtxTranslate(&gx->vecMatrix, m);
 964				// Fall through
 965			case 1:
 966				DSGXMtxTranslate(&gx->posMatrix, m);
 967				break;
 968			case 3:
 969				DSGXMtxTranslate(&gx->texMatrix, m);
 970				break;
 971			}
 972			_updateClipMatrix(gx);
 973			break;
 974		}
 975		case DS_GX_CMD_MTX_SCALE: {
 976			int32_t m[3];
 977			m[0] = gx->activeEntries[0].params[0];
 978			m[0] |= gx->activeEntries[0].params[1] << 8;
 979			m[0] |= gx->activeEntries[0].params[2] << 16;
 980			m[0] |= gx->activeEntries[0].params[3] << 24;
 981			m[1] = gx->activeEntries[1].params[0];
 982			m[1] |= gx->activeEntries[1].params[1] << 8;
 983			m[1] |= gx->activeEntries[1].params[2] << 16;
 984			m[1] |= gx->activeEntries[1].params[3] << 24;
 985			m[2] = gx->activeEntries[2].params[0];
 986			m[2] |= gx->activeEntries[2].params[1] << 8;
 987			m[2] |= gx->activeEntries[2].params[2] << 16;
 988			m[2] |= gx->activeEntries[2].params[3] << 24;
 989			switch (gx->mtxMode) {
 990			case 0:
 991				DSGXMtxScale(&gx->projMatrix, m);
 992				break;
 993			case 1:
 994			case 2:
 995				DSGXMtxScale(&gx->posMatrix, m);
 996				break;
 997			case 3:
 998				DSGXMtxScale(&gx->texMatrix, m);
 999				break;
1000			}
1001			_updateClipMatrix(gx);
1002			break;
1003		}
1004		case DS_GX_CMD_COLOR:
1005			gx->currentVertex.color = entry.params[0];
1006			gx->currentVertex.color |= entry.params[1] << 8;
1007			break;
1008		case DS_GX_CMD_NORMAL: {
1009			int32_t xyz = entry.params[0];
1010			xyz |= entry.params[1] << 8;
1011			xyz |= entry.params[2] << 16;
1012			xyz |= entry.params[3] << 24;
1013			int16_t x = (xyz << 6) & 0xFFC0;
1014			int16_t y = (xyz >> 4) & 0xFFC0;
1015			int16_t z = (xyz >> 14) & 0xFFC0;
1016			x >>= 3;
1017			y >>= 3;
1018			z >>= 3;
1019			if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) == 2) {
1020				int16_t input[] = { x, y, z };
1021				gx->currentVertex.vs = _dotTexture(gx, input, 2, 0);
1022				gx->currentVertex.vt = _dotTexture(gx, input, 2, 1);
1023			}
1024			int16_t nx = _dotFrac(x, y, z, &gx->vecMatrix.m[0]);
1025			int16_t ny = _dotFrac(x, y, z, &gx->vecMatrix.m[1]);
1026			int16_t nz = _dotFrac(x, y, z, &gx->vecMatrix.m[2]);
1027			int r = gx->emit & 0x1F;
1028			int g = (gx->emit >> 5) & 0x1F;
1029			int b = (gx->emit >> 10) & 0x1F;
1030			int i;
1031			for (i = 0; i < 4; ++i) {
1032				if (!(DSGXPolygonAttrsGetLights(gx->currentPoly.polyParams) & (1 << i))) {
1033					continue;
1034				}
1035				struct DSGXLight* light = &gx->lights[i];
1036				int diffuse = -_dot3(light->x, light->y, light->z, nx, ny, nz);
1037				if (diffuse < 0) {
1038					diffuse = 0;
1039				}
1040				int specular = -_dot3(-light->x >> 1, -light->y >> 1, (0x1000 - light->z) >> 1, nx, ny, nz);
1041				if (specular < 0) {
1042					specular = 0;
1043				} else {
1044					specular = 2 * specular * specular - (1 << 10);
1045				}
1046				unsigned lr = (light->color) & 0x1F;
1047				unsigned lg = (light->color >> 5) & 0x1F;
1048				unsigned lb = (light->color >> 10) & 0x1F;
1049				unsigned xr, xg, xb;
1050				xr = gx->specular & 0x1F;
1051				xg = (gx->specular >> 5) & 0x1F;
1052				xb = (gx->specular >> 10) & 0x1F;
1053				r += (specular * xr * lr) >> 17;
1054				g += (specular * xg * lg) >> 17;
1055				b += (specular * xb * lb) >> 17;
1056				xr = gx->diffuse & 0x1F;
1057				xg = (gx->diffuse >> 5) & 0x1F;
1058				xb = (gx->diffuse >> 10) & 0x1F;
1059				r += (diffuse * xr * lr) >> 17;
1060				g += (diffuse * xg * lg) >> 17;
1061				b += (diffuse * xb * lb) >> 17;
1062				xr = gx->ambient & 0x1F;
1063				xg = (gx->ambient >> 5) & 0x1F;
1064				xb = (gx->ambient >> 10) & 0x1F;
1065				r += (xr * lr) >> 5;
1066				g += (xg * lg) >> 5;
1067				b += (xb * lb) >> 5;
1068			}
1069			if (r < 0) {
1070				r = 0;
1071			} else if (r > 0x1F) {
1072				r = 0x1F;
1073			}
1074			if (g < 0) {
1075				g = 0;
1076			} else if (g > 0x1F) {
1077				g = 0x1F;
1078			}
1079			if (b < 0) {
1080				b = 0;
1081			} else if (b > 0x1F) {
1082				b = 0x1F;
1083			}
1084			gx->currentVertex.color = r | (g << 5) | (b << 10);
1085			break;
1086		}
1087		case DS_GX_CMD_TEXCOORD:
1088			gx->currentVertex.st[0] = entry.params[0];
1089			gx->currentVertex.st[0] |= entry.params[1] << 8;
1090			gx->currentVertex.st[1] = entry.params[2];
1091			gx->currentVertex.st[1] |= entry.params[3] << 8;
1092			if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) == 1) {
1093				gx->currentVertex.vs = _dotTexture(gx, NULL, 1, 0);
1094				gx->currentVertex.vt = _dotTexture(gx, NULL, 1, 1);
1095			}
1096			break;
1097		case DS_GX_CMD_VTX_16: {
1098			int16_t x = gx->activeEntries[0].params[0];
1099			x |= gx->activeEntries[0].params[1] << 8;
1100			int16_t y = gx->activeEntries[0].params[2];
1101			y |= gx->activeEntries[0].params[3] << 8;
1102			int16_t z = gx->activeEntries[1].params[0];
1103			z |= gx->activeEntries[1].params[1] << 8;
1104			_emitVertex(gx, x, y, z);
1105			break;
1106		}
1107		case DS_GX_CMD_VTX_10: {
1108			int32_t xyz = entry.params[0];
1109			xyz |= entry.params[1] << 8;
1110			xyz |= entry.params[2] << 16;
1111			xyz |= entry.params[3] << 24;
1112			int16_t x = (xyz << 6) & 0xFFC0;
1113			int16_t y = (xyz >> 4) & 0xFFC0;
1114			int16_t z = (xyz >> 14) & 0xFFC0;
1115			_emitVertex(gx, x, y, z);
1116			break;
1117		}
1118		case DS_GX_CMD_VTX_XY: {
1119			int16_t x = entry.params[0];
1120			x |= entry.params[1] << 8;
1121			int16_t y = entry.params[2];
1122			y |= entry.params[3] << 8;
1123			_emitVertex(gx, x, y, gx->currentVertex.coord[2]);
1124			break;
1125		}
1126		case DS_GX_CMD_VTX_XZ: {
1127			int16_t x = entry.params[0];
1128			x |= entry.params[1] << 8;
1129			int16_t z = entry.params[2];
1130			z |= entry.params[3] << 8;
1131			_emitVertex(gx, x, gx->currentVertex.coord[1], z);
1132			break;
1133		}
1134		case DS_GX_CMD_VTX_YZ: {
1135			int16_t y = entry.params[0];
1136			y |= entry.params[1] << 8;
1137			int16_t z = entry.params[2];
1138			z |= entry.params[3] << 8;
1139			_emitVertex(gx, gx->currentVertex.coord[0], y, z);
1140			break;
1141		}
1142		case DS_GX_CMD_VTX_DIFF: {
1143			int32_t xyz = entry.params[0];
1144			xyz |= entry.params[1] << 8;
1145			xyz |= entry.params[2] << 16;
1146			xyz |= entry.params[3] << 24;
1147			int16_t x = (xyz << 6) & 0xFFC0;
1148			int16_t y = (xyz >> 4) & 0xFFC0;
1149			int16_t z = (xyz >> 14) & 0xFFC0;
1150			_emitVertex(gx, gx->currentVertex.coord[0] + (x >> 6),
1151			                gx->currentVertex.coord[1] + (y >> 6),
1152			                gx->currentVertex.coord[2] + (z >> 6));
1153			break;
1154		}
1155		case DS_GX_CMD_DIF_AMB:
1156			gx->diffuse = entry.params[0];
1157			gx->diffuse |= entry.params[1] << 8;
1158			if (gx->diffuse & 0x8000) {
1159				gx->currentVertex.color = gx->diffuse;
1160			}
1161			gx->ambient = entry.params[2];
1162			gx->ambient |= entry.params[3] << 8;
1163			break;
1164		case DS_GX_CMD_SPE_EMI:
1165			gx->specular = entry.params[0];
1166			gx->specular |= entry.params[1] << 8;
1167			gx->emit = entry.params[2];
1168			gx->emit |= entry.params[3] << 8;
1169			break;
1170		case DS_GX_CMD_LIGHT_VECTOR: {
1171			uint32_t xyz = entry.params[0];
1172			xyz |= entry.params[1] << 8;
1173			xyz |= entry.params[2] << 16;
1174			xyz |= entry.params[3] << 24;
1175			struct DSGXLight* light = &gx->lights[xyz >> 30];
1176			int16_t x = (xyz << 6) & 0xFFC0;
1177			int16_t y = (xyz >> 4) & 0xFFC0;
1178			int16_t z = (xyz >> 14) & 0xFFC0;
1179			x >>= 3;
1180			y >>= 3;
1181			z >>= 3;
1182			light->x = _dotFrac(x, y, z, &gx->vecMatrix.m[0]);
1183			light->y = _dotFrac(x, y, z, &gx->vecMatrix.m[1]);
1184			light->z = _dotFrac(x, y, z, &gx->vecMatrix.m[2]);
1185			break;
1186		}
1187		case DS_GX_CMD_LIGHT_COLOR: {
1188			struct DSGXLight* light = &gx->lights[entry.params[3] >> 6];
1189			light->color = entry.params[0];
1190			light->color |= entry.params[1] << 8;
1191			break;
1192		}
1193		case DS_GX_CMD_POLYGON_ATTR:
1194			gx->nextPoly.polyParams = entry.params[0];
1195			gx->nextPoly.polyParams |= entry.params[1] << 8;
1196			gx->nextPoly.polyParams |= entry.params[2] << 16;
1197			gx->nextPoly.polyParams |= entry.params[3] << 24;
1198			break;
1199		case DS_GX_CMD_TEXIMAGE_PARAM:
1200			gx->nextPoly.texParams = entry.params[0];
1201			gx->nextPoly.texParams |= entry.params[1] << 8;
1202			gx->nextPoly.texParams |= entry.params[2] << 16;
1203			gx->nextPoly.texParams |= entry.params[3] << 24;
1204			gx->currentPoly.texParams = gx->nextPoly.texParams;
1205			break;
1206		case DS_GX_CMD_PLTT_BASE:
1207			gx->nextPoly.palBase = entry.params[0];
1208			gx->nextPoly.palBase |= entry.params[1] << 8;
1209			gx->nextPoly.palBase &= 0x00001FFF;
1210			gx->currentPoly.palBase = gx->nextPoly.palBase;
1211			break;
1212		case DS_GX_CMD_BEGIN_VTXS:
1213			gx->vertexMode = entry.params[0] & 3;
1214			gx->currentPoly = gx->nextPoly;
1215			gx->reverseWinding = false;
1216			memset(gx->pendingVertexIds, -1, sizeof(gx->pendingVertexIds));
1217			break;
1218		case DS_GX_CMD_END_VTXS:
1219			gx->vertexMode = -1;
1220			break;
1221		case DS_GX_CMD_SWAP_BUFFERS:
1222			gx->swapBuffers = true;
1223			gx->wSort = entry.params[0] & 2;
1224			break;
1225		case DS_GX_CMD_VIEWPORT:
1226			gx->viewportX1 = (uint8_t) entry.params[0];
1227			gx->viewportY1 = (uint8_t) entry.params[1];
1228			gx->viewportX2 = (uint8_t) entry.params[2];
1229			gx->viewportY2 = (uint8_t) entry.params[3];
1230			gx->viewportWidth = gx->viewportX2 - gx->viewportX1 + 1;
1231			gx->viewportHeight = gx->viewportY2 - gx->viewportY1 + 1;
1232			gx->currentVertex.viewportX = gx->viewportX1;
1233			gx->currentVertex.viewportY = gx->viewportY1;
1234			gx->currentVertex.viewportWidth = gx->viewportWidth;
1235			gx->currentVertex.viewportHeight = gx->viewportHeight;
1236			break;
1237		case DS_GX_CMD_BOX_TEST:
1238			gxstat = DSRegGXSTATClearTestBusy(gxstat);
1239			gxstat = DSRegGXSTATTestFillBoxTestResult(gxstat, _boxTest(gx));
1240			break;
1241		default:
1242			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]);
1243			break;
1244		}
1245
1246		gxstat = DSRegGXSTATSetPVMatrixStackLevel(gxstat, gx->pvMatrixPointer);
1247		gxstat = DSRegGXSTATSetProjMatrixStackLevel(gxstat, projMatrixPointer);
1248		gxstat = DSRegGXSTATTestFillMatrixStackError(gxstat, projMatrixPointer || gx->pvMatrixPointer >= 0x1F);
1249		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
1250
1251		if (cyclesLate >= cycles) {
1252			cyclesLate -= cycles;
1253		} else {
1254			break;
1255		}
1256	}
1257	if (cycles && !gx->swapBuffers) {
1258		mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
1259	}
1260	if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(struct DSGXEntry))) {
1261		_flushOutstanding(gx);
1262	}
1263	DSGXUpdateGXSTAT(gx);
1264	_updateDMA(gx);
1265}
1266
1267void DSGXInit(struct DSGX* gx) {
1268	gx->renderer = &dummyRenderer;
1269	CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
1270	CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
1271	gx->vertexBuffer[0] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
1272	gx->vertexBuffer[1] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
1273	gx->polygonBuffer[0] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
1274	gx->polygonBuffer[1] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
1275	gx->fifoEvent.name = "DS GX FIFO";
1276	gx->fifoEvent.priority = 0xC;
1277	gx->fifoEvent.context = gx;
1278	gx->fifoEvent.callback = _fifoRun;
1279}
1280
1281void DSGXDeinit(struct DSGX* gx) {
1282	DSGXAssociateRenderer(gx, &dummyRenderer);
1283	CircleBufferDeinit(&gx->fifo);
1284	CircleBufferDeinit(&gx->pipe);
1285	free(gx->vertexBuffer[0]);
1286	free(gx->vertexBuffer[1]);
1287	free(gx->polygonBuffer[0]);
1288	free(gx->polygonBuffer[1]);
1289}
1290
1291void DSGXReset(struct DSGX* gx) {
1292	CircleBufferClear(&gx->fifo);
1293	CircleBufferClear(&gx->pipe);
1294	DSGXMtxIdentity(&gx->projMatrix);
1295	DSGXMtxIdentity(&gx->texMatrix);
1296	DSGXMtxIdentity(&gx->posMatrix);
1297	DSGXMtxIdentity(&gx->vecMatrix);
1298
1299	DSGXMtxIdentity(&gx->clipMatrix);
1300	DSGXMtxIdentity(&gx->projMatrixStack);
1301	DSGXMtxIdentity(&gx->texMatrixStack);
1302	int i;
1303	for (i = 0; i < 32; ++i) {
1304		DSGXMtxIdentity(&gx->posMatrixStack[i]);
1305		DSGXMtxIdentity(&gx->vecMatrixStack[i]);
1306	}
1307	gx->swapBuffers = false;
1308	gx->bufferIndex = 0;
1309	gx->vertexIndex = 0;
1310	gx->polygonIndex = 0;
1311	gx->mtxMode = 0;
1312	gx->pvMatrixPointer = 0;
1313	gx->vertexMode = -1;
1314
1315	gx->viewportX1 = 0;
1316	gx->viewportY1 = 0;
1317	gx->viewportX2 = DS_VIDEO_HORIZONTAL_PIXELS - 1;
1318	gx->viewportY2 = DS_VIDEO_VERTICAL_PIXELS - 1;
1319	gx->viewportWidth = gx->viewportX2 - gx->viewportX1 + 1;
1320	gx->viewportHeight = gx->viewportY2 - gx->viewportY1 + 1;
1321
1322	memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
1323	memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
1324	memset(&gx->outstandingEntry, 0, sizeof(gx->outstandingEntry));
1325	gx->activeParams = 0;
1326	memset(&gx->currentVertex, 0, sizeof(gx->currentVertex));
1327	memset(&gx->nextPoly, 0, sizeof(gx-> nextPoly));
1328	gx->currentVertex.color = 0x7FFF;
1329	gx->currentPoly.polyParams = 0x001F00C0;
1330	gx->nextPoly.polyParams = 0x001F00C0;
1331	gx->dmaSource = -1;
1332	gx->renderer->reset(gx->renderer);
1333}
1334
1335void DSGXAssociateRenderer(struct DSGX* gx, struct DSGXRenderer* renderer) {
1336	gx->renderer->deinit(gx->renderer);
1337	gx->renderer = renderer;
1338	memcpy(gx->renderer->tex, gx->tex, sizeof(gx->renderer->tex));
1339	memcpy(gx->renderer->texPal, gx->texPal, sizeof(gx->renderer->texPal));
1340	gx->renderer->toonTable = &gx->p->memory.io9[DS9_REG_TOON_TABLE_00 >> 1];
1341	gx->renderer->init(gx->renderer);
1342}
1343
1344void DSGXUpdateGXSTAT(struct DSGX* gx) {
1345	uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
1346	value = DSRegGXSTATIsDoIRQ(value);
1347
1348	size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
1349	// XXX
1350	if (gx->swapBuffers) {
1351		entries++;
1352	}
1353	value = DSRegGXSTATSetFIFOEntries(value, entries);
1354	value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
1355	value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
1356
1357	if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
1358		(DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
1359		DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
1360	}
1361
1362	value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
1363
1364	gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
1365	_updateDMA(gx);
1366}
1367
1368static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
1369	gx->outstandingCommand[0] = command;
1370	gx->outstandingCommand[1] = command >> 8;
1371	gx->outstandingCommand[2] = command >> 16;
1372	gx->outstandingCommand[3] = command >> 24;
1373	if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
1374		gx->outstandingCommand[0] = 0;
1375	}
1376	if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
1377		gx->outstandingCommand[1] = 0;
1378	}
1379	if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
1380		gx->outstandingCommand[2] = 0;
1381	}
1382	if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
1383		gx->outstandingCommand[3] = 0;
1384	}
1385	if (!_gxCommandCycleBase[gx->outstandingCommand[0]]) {
1386		gx->outstandingCommand[0] = gx->outstandingCommand[1];
1387		gx->outstandingCommand[1] = gx->outstandingCommand[2];
1388		gx->outstandingCommand[2] = gx->outstandingCommand[3];
1389		gx->outstandingCommand[3] = 0;
1390	}
1391	if (!_gxCommandCycleBase[gx->outstandingCommand[1]]) {
1392		gx->outstandingCommand[1] = gx->outstandingCommand[2];
1393		gx->outstandingCommand[2] = gx->outstandingCommand[3];
1394		gx->outstandingCommand[3] = 0;
1395	}
1396	if (!_gxCommandCycleBase[gx->outstandingCommand[2]]) {
1397		gx->outstandingCommand[2] = gx->outstandingCommand[3];
1398		gx->outstandingCommand[3] = 0;
1399	}
1400	gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
1401	gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
1402	gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
1403	gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
1404	_flushOutstanding(gx);
1405	DSGXUpdateGXSTAT(gx);
1406}
1407
1408static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
1409	if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(entry))) {
1410		mLOG(DS_GX, INFO, "FIFO full");
1411		while (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
1412			// Can happen from STM
1413			if (gx->swapBuffers) {
1414				// XXX: Let's hope those GX entries aren't important, since STM isn't preemptable
1415				mLOG(DS_GX, ERROR, "FIFO full with swap pending");
1416				return;
1417			}
1418			mTimingDeschedule(&gx->p->ds9.timing, &gx->fifoEvent);
1419			_fifoRun(&gx->p->ds9.timing, gx, 0);
1420		}
1421		gx->p->cpuBlocked |= DS_CPU_BLOCK_GX;
1422		gx->outstandingEntry = entry;
1423		gx->p->ds9.cpu->nextEvent = 0;
1424		return;
1425	}
1426	if (gx->outstandingCommand[0]) {
1427		entry.command = gx->outstandingCommand[0];
1428		if (gx->outstandingParams[0]) {
1429			--gx->outstandingParams[0];
1430		}
1431		if (!gx->outstandingParams[0]) {
1432			// TODO: improve this
1433			memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
1434			memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
1435			gx->outstandingParams[3] = 0;
1436			gx->outstandingCommand[3] = 0;
1437		}
1438	} else {
1439		if (entry.command >= DS_GX_CMD_MAX) {
1440			mLOG(DS_GX, GAME_ERROR, "Wrote invalid command %02x to GX FIFO", entry.command);
1441			return;
1442		}
1443		gx->outstandingParams[0] = _gxCommandParams[entry.command];
1444		if (gx->outstandingParams[0]) {
1445			--gx->outstandingParams[0];
1446		}
1447		if (gx->outstandingParams[0]) {
1448			gx->outstandingCommand[0] = entry.command;
1449		}
1450	}
1451	uint32_t cycles = _gxCommandCycleBase[entry.command];
1452	if (!cycles) {
1453		mLOG(DS_GX, GAME_ERROR, "Wrote invalid command %02x to GX FIFO", entry.command);
1454		return;
1455	}
1456	if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
1457		CircleBufferWrite(&gx->pipe, &entry, sizeof(entry));
1458	} else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
1459		CircleBufferWrite(&gx->fifo, &entry, sizeof(entry));
1460	}
1461	if (entry.command == DS_GX_CMD_BOX_TEST) {
1462		DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
1463		gxstat = DSRegGXSTATFillTestBusy(gxstat);
1464		gxstat = DSRegGXSTATClearBoxTestResult(gxstat);
1465		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
1466	}
1467	if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
1468		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
1469	}
1470
1471	_flushOutstanding(gx);
1472}
1473
1474uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
1475	uint16_t oldValue = gx->p->memory.io9[address >> 1];
1476	switch (address) {
1477	case DS9_REG_DISP3DCNT:
1478		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1479		break;
1480	case DS9_REG_CLEAR_COLOR_LO:
1481	case DS9_REG_CLEAR_COLOR_HI:
1482		gx->renderer->writeRegister(gx->renderer, address, value);
1483		break;
1484	case DS9_REG_GXSTAT_LO:
1485		value = DSRegGXSTATIsMatrixStackError(value);
1486		if (value) {
1487			oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
1488			oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
1489		}
1490		value = oldValue;
1491		break;
1492	case DS9_REG_GXSTAT_HI:
1493		value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
1494		gx->p->memory.io9[address >> 1] = value;
1495		DSGXUpdateGXSTAT(gx);
1496		value = gx->p->memory.io9[address >> 1];
1497		break;
1498	default:
1499		if (address < DS9_REG_GXFIFO_00) {
1500			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1501		} else if (address <= DS9_REG_GXFIFO_1F) {
1502			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1503		} else if (address < DS9_REG_GXSTAT_LO) {
1504			struct DSGXEntry entry = {
1505				.command = (address & 0x1FC) >> 2,
1506				.params = {
1507					value,
1508					value >> 8,
1509				}
1510			};
1511			if (entry.command < DS_GX_CMD_MAX) {
1512				DSGXWriteFIFO(gx, entry);
1513			}
1514		} else {
1515			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1516		}
1517		break;
1518	}
1519	return value;
1520}
1521
1522uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
1523	switch (address) {
1524	case DS9_REG_DISP3DCNT:
1525		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1526		break;
1527	case DS9_REG_GXSTAT_LO:
1528		value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
1529		value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
1530		break;
1531	case DS9_REG_CLEAR_COLOR_LO:
1532		gx->renderer->writeRegister(gx->renderer, address, value);
1533		gx->renderer->writeRegister(gx->renderer, address + 2, value >> 16);
1534		break;
1535	default:
1536		if (address < DS9_REG_GXFIFO_00) {
1537			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1538		} else if (address <= DS9_REG_GXFIFO_1F) {
1539			if (gx->outstandingParams[0]) {
1540				struct DSGXEntry entry = {
1541					.command = gx->outstandingCommand[0],
1542					.params = {
1543						value,
1544						value >> 8,
1545						value >> 16,
1546						value >> 24
1547					}
1548				};
1549				DSGXWriteFIFO(gx, entry);
1550			} else {
1551				DSGXUnpackCommand(gx, value);
1552			}
1553		} else if (address < DS9_REG_GXSTAT_LO) {
1554			struct DSGXEntry entry = {
1555				.command = (address & 0x1FC) >> 2,
1556				.params = {
1557					value,
1558					value >> 8,
1559					value >> 16,
1560					value >> 24
1561				}
1562			};
1563			DSGXWriteFIFO(gx, entry);
1564		} else {
1565			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1566		}
1567		break;
1568	}
1569	return value;
1570}
1571
1572void DSGXFlush(struct DSGX* gx) {
1573	if (gx->swapBuffers) {
1574		gx->renderer->setRAM(gx->renderer, gx->vertexBuffer[gx->bufferIndex], gx->polygonBuffer[gx->bufferIndex], gx->polygonIndex, gx->wSort);
1575		gx->swapBuffers = false;
1576		gx->bufferIndex ^= 1;
1577		gx->vertexIndex = 0;
1578		gx->pendingVertexIndex = 0;
1579		gx->polygonIndex = 0;
1580		if (CircleBufferSize(&gx->fifo)) {
1581			mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
1582		}
1583	}
1584
1585	DSGXUpdateGXSTAT(gx);
1586}
1587
1588void DSGXScheduleDMA(struct DSCommon* dscore, int number, struct GBADMA* info) {
1589	UNUSED(info);
1590	dscore->p->gx.dmaSource = number;
1591	_updateDMA(&dscore->p->gx);
1592}
1593
1594static void DSGXDummyRendererInit(struct DSGXRenderer* renderer) {
1595	UNUSED(renderer);
1596	// Nothing to do
1597}
1598
1599static void DSGXDummyRendererReset(struct DSGXRenderer* renderer) {
1600	UNUSED(renderer);
1601	// Nothing to do
1602}
1603
1604static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer) {
1605	UNUSED(renderer);
1606	// Nothing to do
1607}
1608
1609static void DSGXDummyRendererInvalidateTex(struct DSGXRenderer* renderer, int slot) {
1610	UNUSED(renderer);
1611	UNUSED(slot);
1612	// Nothing to do
1613}
1614
1615static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort) {
1616	UNUSED(renderer);
1617	UNUSED(verts);
1618	UNUSED(polys);
1619	UNUSED(polyCount);
1620	UNUSED(wSort);
1621	// Nothing to do
1622}
1623
1624static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
1625	UNUSED(renderer);
1626	UNUSED(y);
1627	// Nothing to do
1628}
1629
1630static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, const color_t** output) {
1631	UNUSED(renderer);
1632	UNUSED(y);
1633	*output = NULL;
1634	// Nothing to do
1635}
1636
1637static void DSGXDummyRendererWriteRegister(struct DSGXRenderer* renderer, uint32_t address, uint16_t value) {
1638	UNUSED(renderer);
1639	UNUSED(address);
1640	UNUSED(value);
1641	// Nothing to do
1642}