all repos — mgba @ fe2f67e2aa2246e8ec2c2599a480d6ff7557f3f7

mGBA Game Boy Advance Emulator

src/ds/gx.c (view raw)

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