all repos — mgba @ 3f6b8b68fc08c0ea0933db898704f9048a24a0c8

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