all repos — mgba @ 9dfcef3f45efe580e252ec8bc3852257e9de9fd6

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