all repos — mgba @ b80e06f9bd127e9ca83141766a40b767c2deb516

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