all repos — mgba @ 3ccd29245cc01e76790519636a8aa9ab50ad05fd

mGBA Game Boy Advance Emulator

src/ds/gx.c (view raw)

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