all repos — mgba @ 1b0d74f3dcb4725562aa47ef667543c00ce3ce56

mGBA Game Boy Advance Emulator

src/ds/gx.c (view raw)

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