all repos — mgba @ 0d0a37c858ea7c91c4fc4a5db239c5a7bae7948d

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