all repos — mgba @ 6f04c89c5fdb86d99a4ffdd90b2d8eda5b7c9fc9

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