all repos — mgba @ 1a0e44c014ad34bea30d237d27015d82d02cda4b

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