all repos — mgba @ 000b49e45b30ec9c856b7b2e8cf8baee21daa56d

mGBA Game Boy Advance Emulator

src/ds/gx.c (view raw)

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