all repos — mgba @ 1de5d4e330563d1cfd889534ba3c81979af607b7

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