all repos — mgba @ 77f74a831f0616f37f27c4675d259a7471a85d55

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