all repos — mgba @ 0e9e1a8dc49e81dec4049629cf3e887a691da7c9

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