all repos — mgba @ e9debb197e0b3c35e717bf9fce0642e8fbd78543

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");
  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);
  21static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y);
  22static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, color_t** output);
  23
  24static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry);
  25
  26static const int32_t _gxCommandCycleBase[DS_GX_CMD_MAX] = {
  27	[DS_GX_CMD_NOP] = 0,
  28	[DS_GX_CMD_MTX_MODE] = 2,
  29	[DS_GX_CMD_MTX_PUSH] = 34,
  30	[DS_GX_CMD_MTX_POP] = 72,
  31	[DS_GX_CMD_MTX_STORE] = 34,
  32	[DS_GX_CMD_MTX_RESTORE] = 72,
  33	[DS_GX_CMD_MTX_IDENTITY] = 38,
  34	[DS_GX_CMD_MTX_LOAD_4x4] = 68,
  35	[DS_GX_CMD_MTX_LOAD_4x3] = 60,
  36	[DS_GX_CMD_MTX_MULT_4x4] = 70,
  37	[DS_GX_CMD_MTX_MULT_4x3] = 62,
  38	[DS_GX_CMD_MTX_MULT_3x3] = 56,
  39	[DS_GX_CMD_MTX_SCALE] = 44,
  40	[DS_GX_CMD_MTX_TRANS] = 44,
  41	[DS_GX_CMD_COLOR] = 2,
  42	[DS_GX_CMD_NORMAL] = 18,
  43	[DS_GX_CMD_TEXCOORD] = 2,
  44	[DS_GX_CMD_VTX_16] = 18,
  45	[DS_GX_CMD_VTX_10] = 16,
  46	[DS_GX_CMD_VTX_XY] = 16,
  47	[DS_GX_CMD_VTX_XZ] = 16,
  48	[DS_GX_CMD_VTX_YZ] = 16,
  49	[DS_GX_CMD_VTX_DIFF] = 16,
  50	[DS_GX_CMD_POLYGON_ATTR] = 2,
  51	[DS_GX_CMD_TEXIMAGE_PARAM] = 2,
  52	[DS_GX_CMD_PLTT_BASE] = 2,
  53	[DS_GX_CMD_DIF_AMB] = 8,
  54	[DS_GX_CMD_SPE_EMI] = 8,
  55	[DS_GX_CMD_LIGHT_VECTOR] = 12,
  56	[DS_GX_CMD_LIGHT_COLOR] = 2,
  57	[DS_GX_CMD_SHININESS] = 64,
  58	[DS_GX_CMD_BEGIN_VTXS] = 2,
  59	[DS_GX_CMD_END_VTXS] = 2,
  60	[DS_GX_CMD_SWAP_BUFFERS] = 784,
  61	[DS_GX_CMD_VIEWPORT] = 2,
  62	[DS_GX_CMD_BOX_TEST] = 206,
  63	[DS_GX_CMD_POS_TEST] = 18,
  64	[DS_GX_CMD_VEC_TEST] = 10,
  65};
  66
  67static const int32_t _gxCommandParams[DS_GX_CMD_MAX] = {
  68	[DS_GX_CMD_MTX_MODE] = 1,
  69	[DS_GX_CMD_MTX_POP] = 1,
  70	[DS_GX_CMD_MTX_STORE] = 1,
  71	[DS_GX_CMD_MTX_RESTORE] = 1,
  72	[DS_GX_CMD_MTX_LOAD_4x4] = 16,
  73	[DS_GX_CMD_MTX_LOAD_4x3] = 12,
  74	[DS_GX_CMD_MTX_MULT_4x4] = 16,
  75	[DS_GX_CMD_MTX_MULT_4x3] = 12,
  76	[DS_GX_CMD_MTX_MULT_3x3] = 9,
  77	[DS_GX_CMD_MTX_SCALE] = 3,
  78	[DS_GX_CMD_MTX_TRANS] = 3,
  79	[DS_GX_CMD_COLOR] = 1,
  80	[DS_GX_CMD_NORMAL] = 1,
  81	[DS_GX_CMD_TEXCOORD] = 1,
  82	[DS_GX_CMD_VTX_16] = 2,
  83	[DS_GX_CMD_VTX_10] = 1,
  84	[DS_GX_CMD_VTX_XY] = 1,
  85	[DS_GX_CMD_VTX_XZ] = 1,
  86	[DS_GX_CMD_VTX_YZ] = 1,
  87	[DS_GX_CMD_VTX_DIFF] = 1,
  88	[DS_GX_CMD_POLYGON_ATTR] = 1,
  89	[DS_GX_CMD_TEXIMAGE_PARAM] = 1,
  90	[DS_GX_CMD_PLTT_BASE] = 1,
  91	[DS_GX_CMD_DIF_AMB] = 1,
  92	[DS_GX_CMD_SPE_EMI] = 1,
  93	[DS_GX_CMD_LIGHT_VECTOR] = 1,
  94	[DS_GX_CMD_LIGHT_COLOR] = 1,
  95	[DS_GX_CMD_SHININESS] = 32,
  96	[DS_GX_CMD_BEGIN_VTXS] = 1,
  97	[DS_GX_CMD_SWAP_BUFFERS] = 1,
  98	[DS_GX_CMD_VIEWPORT] = 1,
  99	[DS_GX_CMD_BOX_TEST] = 3,
 100	[DS_GX_CMD_POS_TEST] = 2,
 101	[DS_GX_CMD_VEC_TEST] = 1,
 102};
 103
 104static struct DSGXRenderer dummyRenderer = {
 105	.init = DSGXDummyRendererInit,
 106	.reset = DSGXDummyRendererReset,
 107	.deinit = DSGXDummyRendererDeinit,
 108	.invalidateTex = DSGXDummyRendererInvalidateTex,
 109	.setRAM = DSGXDummyRendererSetRAM,
 110	.drawScanline = DSGXDummyRendererDrawScanline,
 111	.getScanline = DSGXDummyRendererGetScanline,
 112};
 113
 114static void _pullPipe(struct DSGX* gx) {
 115	if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
 116		struct DSGXEntry entry = { 0 };
 117		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
 118		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
 119		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
 120		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
 121		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
 122		CircleBufferWrite8(&gx->pipe, entry.command);
 123		CircleBufferWrite8(&gx->pipe, entry.params[0]);
 124		CircleBufferWrite8(&gx->pipe, entry.params[1]);
 125		CircleBufferWrite8(&gx->pipe, entry.params[2]);
 126		CircleBufferWrite8(&gx->pipe, entry.params[3]);
 127	}
 128	if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
 129		struct DSGXEntry entry = { 0 };
 130		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
 131		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
 132		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
 133		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
 134		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
 135		CircleBufferWrite8(&gx->pipe, entry.command);
 136		CircleBufferWrite8(&gx->pipe, entry.params[0]);
 137		CircleBufferWrite8(&gx->pipe, entry.params[1]);
 138		CircleBufferWrite8(&gx->pipe, entry.params[2]);
 139		CircleBufferWrite8(&gx->pipe, entry.params[3]);
 140	}
 141}
 142
 143static void _updateClipMatrix(struct DSGX* gx) {
 144	DSGXMtxMultiply(&gx->clipMatrix, &gx->posMatrix, &gx->projMatrix);
 145	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_00 >> 1] = gx->clipMatrix.m[0];
 146	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_01 >> 1] = gx->clipMatrix.m[0] >> 16;
 147	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_02 >> 1] = gx->clipMatrix.m[1];
 148	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_03 >> 1] = gx->clipMatrix.m[1] >> 16;
 149	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_04 >> 1] = gx->clipMatrix.m[2];
 150	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_05 >> 1] = gx->clipMatrix.m[2] >> 16;
 151	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_06 >> 1] = gx->clipMatrix.m[3];
 152	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_07 >> 1] = gx->clipMatrix.m[3] >> 16;
 153	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_08 >> 1] = gx->clipMatrix.m[4];
 154	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_09 >> 1] = gx->clipMatrix.m[4] >> 16;
 155	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0A >> 1] = gx->clipMatrix.m[5];
 156	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0B >> 1] = gx->clipMatrix.m[5] >> 16;
 157	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0C >> 1] = gx->clipMatrix.m[6];
 158	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0D >> 1] = gx->clipMatrix.m[6] >> 16;
 159	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0E >> 1] = gx->clipMatrix.m[7];
 160	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0F >> 1] = gx->clipMatrix.m[7] >> 16;
 161	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_10 >> 1] = gx->clipMatrix.m[8];
 162	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_11 >> 1] = gx->clipMatrix.m[8] >> 16;
 163	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_12 >> 1] = gx->clipMatrix.m[9];
 164	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_13 >> 1] = gx->clipMatrix.m[9] >> 16;
 165	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_14 >> 1] = gx->clipMatrix.m[10];
 166	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_15 >> 1] = gx->clipMatrix.m[10] >> 16;
 167	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_16 >> 1] = gx->clipMatrix.m[11];
 168	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_17 >> 1] = gx->clipMatrix.m[11] >> 16;
 169	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_18 >> 1] = gx->clipMatrix.m[12];
 170	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_19 >> 1] = gx->clipMatrix.m[12] >> 16;
 171	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1A >> 1] = gx->clipMatrix.m[13];
 172	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1B >> 1] = gx->clipMatrix.m[13] >> 16;
 173	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1C >> 1] = gx->clipMatrix.m[14];
 174	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1D >> 1] = gx->clipMatrix.m[14] >> 16;
 175	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1E >> 1] = gx->clipMatrix.m[15];
 176	gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1F >> 1] = gx->clipMatrix.m[15] >> 16;
 177}
 178
 179static int32_t _dotViewport(struct DSGXVertex* vertex, int32_t* col) {
 180	int64_t a;
 181	int64_t b;
 182	int64_t sum;
 183	a = col[0];
 184	b = vertex->x;
 185	sum = a * b;
 186	a = col[4];
 187	b = vertex->y;
 188	sum += a * b;
 189	a = col[8];
 190	b = vertex->z;
 191	sum += a * b;
 192	a = col[12];
 193	b = MTX_ONE;
 194	sum += a * b;
 195	return sum >> 8LL;
 196}
 197
 198static int16_t _dotTexture(struct DSGXVertex* vertex, int mode, int32_t* col) {
 199	int64_t a;
 200	int64_t b;
 201	int64_t sum;
 202	switch (mode) {
 203	case 1:
 204		a = col[0];
 205		b = vertex->s;
 206		sum = a * b;
 207		a = col[4];
 208		b = vertex->t;
 209		sum += a * b;
 210		a = col[8];
 211		b = MTX_ONE >> 4;
 212		sum += a * b;
 213		a = col[12];
 214		b = MTX_ONE >> 4;
 215		sum += a * b;
 216		break;
 217	case 2:
 218		return 0;
 219	case 3:
 220		a = col[0];
 221		b = vertex->vx;
 222		sum = a * b;
 223		a = col[4];
 224		b = vertex->vy;
 225		sum += a * b;
 226		a = col[8];
 227		b = vertex->vz;
 228		sum += a * b;
 229		a = col[12];
 230		b = MTX_ONE;
 231		sum += a * b;
 232	}
 233	return sum >> 12;
 234}
 235
 236static void _emitVertex(struct DSGX* gx, uint16_t x, uint16_t y, uint16_t z) {
 237	if (gx->vertexMode < 0 || gx->vertexIndex == DS_GX_VERTEX_BUFFER_SIZE || gx->polygonIndex == DS_GX_POLYGON_BUFFER_SIZE) {
 238		return;
 239	}
 240	gx->currentVertex.x = x;
 241	gx->currentVertex.y = y;
 242	gx->currentVertex.z = z;
 243	gx->currentVertex.vx = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[0]);
 244	gx->currentVertex.vy = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[1]);
 245	gx->currentVertex.vz = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[2]);
 246	gx->currentVertex.vw = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[3]);
 247
 248	// TODO: What to do if w is 0?
 249
 250	gx->currentVertex.vx = (gx->currentVertex.vx + gx->currentVertex.vw) * (int64_t) (gx->viewportWidth << 12) / (gx->currentVertex.vw * 2) + (gx->viewportX1 << 12);
 251	gx->currentVertex.vy = (gx->currentVertex.vy + gx->currentVertex.vw) * (int64_t) (gx->viewportHeight << 12) / (gx->currentVertex.vw * 2) + (gx->viewportY1 << 12);
 252	gx->currentVertex.vw = 0x40000000 / gx->currentVertex.vw;
 253
 254	if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) > 0) {
 255		int32_t m12 = gx->texMatrix.m[12];
 256		int32_t m13 = gx->texMatrix.m[13];
 257		if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) > 1) {
 258			gx->texMatrix.m[12] = gx->currentVertex.vs;
 259			gx->texMatrix.m[13] = gx->currentVertex.vt;
 260		}
 261		gx->currentVertex.vs = _dotTexture(&gx->currentVertex, DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams), &gx->texMatrix.m[0]);
 262		gx->currentVertex.vt = _dotTexture(&gx->currentVertex, DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams), &gx->texMatrix.m[1]);
 263		gx->texMatrix.m[12] = m12;
 264		gx->texMatrix.m[13] = m13;
 265	} else {
 266		gx->currentVertex.vs = gx->currentVertex.s;
 267		gx->currentVertex.vt = gx->currentVertex.t;
 268	}
 269
 270	struct DSGXVertex* vbuf = gx->vertexBuffer[gx->bufferIndex];
 271	vbuf[gx->vertexIndex] = gx->currentVertex;
 272
 273	gx->currentPoly.vertIds[gx->currentPoly.verts] = gx->vertexIndex;
 274
 275	++gx->vertexIndex;
 276	++gx->currentPoly.verts;
 277	int totalVertices;
 278	switch (gx->vertexMode) {
 279	case 0:
 280	case 2:
 281		totalVertices = 3;
 282		break;
 283	case 1:
 284	case 3:
 285		totalVertices = 4;
 286		break;
 287	}
 288	if (gx->currentPoly.verts == totalVertices) {
 289		struct DSGXPolygon* pbuf = gx->polygonBuffer[gx->bufferIndex];
 290		pbuf[gx->polygonIndex] = gx->currentPoly;
 291
 292		switch (gx->vertexMode) {
 293		case 0:
 294		case 1:
 295			gx->currentPoly.verts = 0;
 296			break;
 297		case 2:
 298			gx->currentPoly.vertIds[0] = gx->currentPoly.vertIds[1];
 299			gx->currentPoly.vertIds[1] = gx->currentPoly.vertIds[2];
 300			gx->currentPoly.verts = 2;
 301			break;
 302		case 3:
 303			gx->currentPoly.vertIds[0] = gx->currentPoly.vertIds[2];
 304			gx->currentPoly.vertIds[1] = gx->currentPoly.vertIds[3];
 305			// Ensure quads don't cross over
 306			pbuf[gx->polygonIndex].vertIds[2] = gx->currentPoly.vertIds[3];
 307			pbuf[gx->polygonIndex].vertIds[3] = gx->currentPoly.vertIds[2];
 308			gx->currentPoly.verts = 2;
 309			break;
 310		}
 311		++gx->polygonIndex;
 312	}
 313}
 314
 315static void _flushOutstanding(struct DSGX* gx) {
 316	if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(struct DSGXEntry))) {
 317		return;
 318	}
 319	if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
 320		gx->p->cpuBlocked &= ~DS_CPU_BLOCK_GX;
 321		DSGXWriteFIFO(gx, gx->outstandingEntry);
 322		gx->outstandingEntry.command = 0;
 323	}
 324	while (gx->outstandingCommand[0] && !gx->outstandingParams[0]) {
 325		if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(struct DSGXEntry))) {
 326			return;
 327		}
 328		DSGXWriteFIFO(gx, (struct DSGXEntry) { 0 });
 329	}
 330}
 331
 332static void _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 333	struct DSGX* gx = context;
 334	uint32_t cycles;
 335	bool first = true;
 336	while (!gx->swapBuffers) {
 337		if (CircleBufferSize(&gx->pipe) <= 2 * sizeof(struct DSGXEntry)) {
 338			_pullPipe(gx);
 339		}
 340
 341		if (!CircleBufferSize(&gx->pipe)) {
 342			cycles = 0;
 343			break;
 344		}
 345
 346		DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
 347		int projMatrixPointer = DSRegGXSTATGetProjMatrixStackLevel(gxstat);
 348
 349		struct DSGXEntry entry = { 0 };
 350		CircleBufferDump(&gx->pipe, (int8_t*) &entry.command, 1);
 351		cycles = _gxCommandCycleBase[entry.command];
 352
 353		if (first) {
 354			first = false;
 355		} else if (!gx->activeParams && cycles > cyclesLate) {
 356			break;
 357		}
 358		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.command);
 359		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[0]);
 360		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[1]);
 361		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[2]);
 362		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[3]);
 363
 364		if (gx->activeParams) {
 365			int index = _gxCommandParams[entry.command] - gx->activeParams;
 366			gx->activeEntries[index] = entry;
 367			--gx->activeParams;
 368		} else {
 369			gx->activeParams = _gxCommandParams[entry.command];
 370			if (gx->activeParams) {
 371				--gx->activeParams;
 372			}
 373			if (gx->activeParams) {
 374				gx->activeEntries[0] = entry;
 375			}
 376		}
 377
 378		if (gx->activeParams) {
 379			continue;
 380		}
 381
 382		switch (entry.command) {
 383		case DS_GX_CMD_MTX_MODE:
 384			if (entry.params[0] < 4) {
 385				gx->mtxMode = entry.params[0];
 386			} else {
 387				mLOG(DS_GX, GAME_ERROR, "Invalid GX MTX_MODE %02X", entry.params[0]);
 388			}
 389			break;
 390		case DS_GX_CMD_MTX_PUSH:
 391			switch (gx->mtxMode) {
 392			case 0:
 393				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrix));
 394				++projMatrixPointer;
 395				break;
 396			case 2:
 397				memcpy(&gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->vecMatrix, sizeof(gx->vecMatrix));
 398				// Fall through
 399			case 1:
 400				memcpy(&gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->posMatrix, sizeof(gx->posMatrix));
 401				++gx->pvMatrixPointer;
 402				break;
 403			case 3:
 404				mLOG(DS_GX, STUB, "Unimplemented GX MTX_PUSH mode");
 405				break;
 406			}
 407			break;
 408		case DS_GX_CMD_MTX_POP: {
 409			int8_t offset = entry.params[0];
 410			offset <<= 2;
 411			offset >>= 2;
 412			switch (gx->mtxMode) {
 413			case 0:
 414				projMatrixPointer -= offset;
 415				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
 416				break;
 417			case 1:
 418				gx->pvMatrixPointer -= offset;
 419				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
 420				break;
 421			case 2:
 422				gx->pvMatrixPointer -= offset;
 423				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->vecMatrix));
 424				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
 425				break;
 426			case 3:
 427				mLOG(DS_GX, STUB, "Unimplemented GX MTX_POP mode");
 428				break;
 429			}
 430			_updateClipMatrix(gx);
 431			break;
 432		}
 433		case DS_GX_CMD_MTX_STORE: {
 434			int8_t offset = entry.params[0] & 0x1F;
 435			// TODO: overflow
 436			switch (gx->mtxMode) {
 437			case 0:
 438				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrixStack));
 439				break;
 440			case 2:
 441				memcpy(&gx->vecMatrixStack[offset], &gx->vecMatrix, sizeof(gx->vecMatrix));
 442				// Fall through
 443			case 1:
 444				memcpy(&gx->posMatrixStack[offset], &gx->posMatrix, sizeof(gx->posMatrix));
 445				break;
 446			case 3:
 447				mLOG(DS_GX, STUB, "Unimplemented GX MTX_STORE mode");
 448				break;
 449			}
 450			break;
 451		}
 452		case DS_GX_CMD_MTX_RESTORE: {
 453			int8_t offset = entry.params[0] & 0x1F;
 454			// TODO: overflow
 455			switch (gx->mtxMode) {
 456			case 0:
 457				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
 458				break;
 459			case 2:
 460				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[offset], sizeof(gx->vecMatrix));
 461				// Fall through
 462			case 1:
 463				memcpy(&gx->posMatrix, &gx->posMatrixStack[offset], sizeof(gx->posMatrix));
 464				break;
 465			case 3:
 466				mLOG(DS_GX, STUB, "Unimplemented GX MTX_RESTORE mode");
 467				break;
 468			}
 469			_updateClipMatrix(gx);
 470			break;
 471		}
 472		case DS_GX_CMD_MTX_IDENTITY:
 473			switch (gx->mtxMode) {
 474			case 0:
 475				DSGXMtxIdentity(&gx->projMatrix);
 476				break;
 477			case 2:
 478				DSGXMtxIdentity(&gx->vecMatrix);
 479				// Fall through
 480			case 1:
 481				DSGXMtxIdentity(&gx->posMatrix);
 482				break;
 483			case 3:
 484				DSGXMtxIdentity(&gx->texMatrix);
 485				break;
 486			}
 487			_updateClipMatrix(gx);
 488			break;
 489		case DS_GX_CMD_MTX_LOAD_4x4: {
 490			struct DSGXMatrix m;
 491			int i;
 492			for (i = 0; i < 16; ++i) {
 493				m.m[i] = gx->activeEntries[i].params[0];
 494				m.m[i] |= gx->activeEntries[i].params[1] << 8;
 495				m.m[i] |= gx->activeEntries[i].params[2] << 16;
 496				m.m[i] |= gx->activeEntries[i].params[3] << 24;
 497			}
 498			switch (gx->mtxMode) {
 499			case 0:
 500				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
 501				break;
 502			case 2:
 503				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
 504				// Fall through
 505			case 1:
 506				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
 507				break;
 508			case 3:
 509				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
 510				break;
 511			}
 512			_updateClipMatrix(gx);
 513			break;
 514		}
 515		case DS_GX_CMD_MTX_LOAD_4x3: {
 516			struct DSGXMatrix m;
 517			int i, j;
 518			for (j = 0; j < 4; ++j) {
 519				for (i = 0; i < 3; ++i) {
 520					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 521					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 522					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 523					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 524				}
 525				m.m[j * 4 + 3] = 0;
 526			}
 527			m.m[15] = MTX_ONE;
 528			switch (gx->mtxMode) {
 529			case 0:
 530				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
 531				break;
 532			case 2:
 533				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
 534				// Fall through
 535			case 1:
 536				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
 537				break;
 538			case 3:
 539				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
 540				break;
 541			}
 542			_updateClipMatrix(gx);
 543			break;
 544		}
 545		case DS_GX_CMD_MTX_MULT_4x4: {
 546			struct DSGXMatrix m;
 547			int i;
 548			for (i = 0; i < 16; ++i) {
 549				m.m[i] = gx->activeEntries[i].params[0];
 550				m.m[i] |= gx->activeEntries[i].params[1] << 8;
 551				m.m[i] |= gx->activeEntries[i].params[2] << 16;
 552				m.m[i] |= gx->activeEntries[i].params[3] << 24;
 553			}
 554			switch (gx->mtxMode) {
 555			case 0:
 556				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 557				break;
 558			case 2:
 559				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 560				// Fall through
 561			case 1:
 562				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 563				break;
 564			case 3:
 565				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 566				break;
 567			}
 568			_updateClipMatrix(gx);
 569			break;
 570		}
 571		case DS_GX_CMD_MTX_MULT_4x3: {
 572			struct DSGXMatrix m;
 573			int i, j;
 574			for (j = 0; j < 4; ++j) {
 575				for (i = 0; i < 3; ++i) {
 576					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 577					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 578					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 579					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 580				}
 581				m.m[j * 4 + 3] = 0;
 582			}
 583			m.m[15] = MTX_ONE;
 584			switch (gx->mtxMode) {
 585			case 0:
 586				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 587				break;
 588			case 2:
 589				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 590				// Fall through
 591			case 1:
 592				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 593				break;
 594			case 3:
 595				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 596				break;
 597			}
 598			_updateClipMatrix(gx);
 599			break;
 600		}
 601		case DS_GX_CMD_MTX_MULT_3x3: {
 602			struct DSGXMatrix m;
 603			int i, j;
 604			for (j = 0; j < 3; ++j) {
 605				for (i = 0; i < 3; ++i) {
 606					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 607					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 608					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 609					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 610				}
 611				m.m[j * 4 + 3] = 0;
 612			}
 613			m.m[12] = 0;
 614			m.m[13] = 0;
 615			m.m[14] = 0;
 616			m.m[15] = MTX_ONE;
 617			switch (gx->mtxMode) {
 618			case 0:
 619				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 620				break;
 621			case 2:
 622				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 623				// Fall through
 624			case 1:
 625				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 626				break;
 627			case 3:
 628				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 629				break;
 630			}
 631			_updateClipMatrix(gx);
 632			break;
 633		}
 634		case DS_GX_CMD_MTX_TRANS: {
 635			int32_t m[3];
 636			m[0] = gx->activeEntries[0].params[0];
 637			m[0] |= gx->activeEntries[0].params[1] << 8;
 638			m[0] |= gx->activeEntries[0].params[2] << 16;
 639			m[0] |= gx->activeEntries[0].params[3] << 24;
 640			m[1] = gx->activeEntries[1].params[0];
 641			m[1] |= gx->activeEntries[1].params[1] << 8;
 642			m[1] |= gx->activeEntries[1].params[2] << 16;
 643			m[1] |= gx->activeEntries[1].params[3] << 24;
 644			m[2] = gx->activeEntries[2].params[0];
 645			m[2] |= gx->activeEntries[2].params[1] << 8;
 646			m[2] |= gx->activeEntries[2].params[2] << 16;
 647			m[2] |= gx->activeEntries[2].params[3] << 24;
 648			switch (gx->mtxMode) {
 649			case 0:
 650				DSGXMtxTranslate(&gx->projMatrix, m);
 651				break;
 652			case 2:
 653				DSGXMtxTranslate(&gx->vecMatrix, m);
 654				// Fall through
 655			case 1:
 656				DSGXMtxTranslate(&gx->posMatrix, m);
 657				break;
 658			case 3:
 659				DSGXMtxTranslate(&gx->texMatrix, m);
 660				break;
 661			}
 662			_updateClipMatrix(gx);
 663			break;
 664		}
 665		case DS_GX_CMD_MTX_SCALE: {
 666			int32_t m[3];
 667			m[0] = gx->activeEntries[0].params[0];
 668			m[0] |= gx->activeEntries[0].params[1] << 8;
 669			m[0] |= gx->activeEntries[0].params[2] << 16;
 670			m[0] |= gx->activeEntries[0].params[3] << 24;
 671			m[1] = gx->activeEntries[1].params[0];
 672			m[1] |= gx->activeEntries[1].params[1] << 8;
 673			m[1] |= gx->activeEntries[1].params[2] << 16;
 674			m[1] |= gx->activeEntries[1].params[3] << 24;
 675			m[2] = gx->activeEntries[2].params[0];
 676			m[2] |= gx->activeEntries[2].params[1] << 8;
 677			m[2] |= gx->activeEntries[2].params[2] << 16;
 678			m[2] |= gx->activeEntries[2].params[3] << 24;
 679			switch (gx->mtxMode) {
 680			case 0:
 681				DSGXMtxScale(&gx->projMatrix, m);
 682				break;
 683			case 2:
 684				DSGXMtxScale(&gx->vecMatrix, m);
 685				// Fall through
 686			case 1:
 687				DSGXMtxScale(&gx->posMatrix, m);
 688				break;
 689			case 3:
 690				DSGXMtxScale(&gx->texMatrix, m);
 691				break;
 692			}
 693			_updateClipMatrix(gx);
 694			break;
 695		}
 696		case DS_GX_CMD_COLOR:
 697			gx->currentVertex.color = entry.params[0];
 698			gx->currentVertex.color |= entry.params[1] << 8;
 699			break;
 700		case DS_GX_CMD_TEXCOORD:
 701			gx->currentVertex.s = entry.params[0];
 702			gx->currentVertex.s |= entry.params[1] << 8;
 703			gx->currentVertex.t = entry.params[2];
 704			gx->currentVertex.t |= entry.params[3] << 8;
 705			break;
 706		case DS_GX_CMD_VTX_16: {
 707			int16_t x = gx->activeEntries[0].params[0];
 708			x |= gx->activeEntries[0].params[1] << 8;
 709			int16_t y = gx->activeEntries[0].params[2];
 710			y |= gx->activeEntries[0].params[3] << 8;
 711			int16_t z = gx->activeEntries[1].params[0];
 712			z |= gx->activeEntries[1].params[1] << 8;
 713			_emitVertex(gx, x, y, z);
 714			break;
 715		}
 716		case DS_GX_CMD_VTX_10: {
 717			int32_t xyz = entry.params[0];
 718			xyz |= entry.params[1] << 8;
 719			xyz |= entry.params[2] << 16;
 720			xyz |= entry.params[3] << 24;
 721			int16_t x = (xyz << 6) & 0xFFC0;
 722			int16_t y = (xyz >> 4) & 0xFFC0;
 723			int16_t z = (xyz >> 14) & 0xFFC0;
 724			_emitVertex(gx, x, y, z);
 725			break;
 726		}
 727		case DS_GX_CMD_VTX_XY: {
 728			int16_t x = entry.params[0];
 729			x |= entry.params[1] << 8;
 730			int16_t y = entry.params[2];
 731			y |= entry.params[3] << 8;
 732			_emitVertex(gx, x, y, gx->currentVertex.z);
 733			break;
 734		}
 735		case DS_GX_CMD_VTX_XZ: {
 736			int16_t x = entry.params[0];
 737			x |= entry.params[1] << 8;
 738			int16_t z = entry.params[2];
 739			z |= entry.params[3] << 8;
 740			_emitVertex(gx, x, gx->currentVertex.y, z);
 741			break;
 742		}
 743		case DS_GX_CMD_VTX_YZ: {
 744			int16_t y = entry.params[0];
 745			y |= entry.params[1] << 8;
 746			int16_t z = entry.params[2];
 747			z |= entry.params[3] << 8;
 748			_emitVertex(gx, gx->currentVertex.x, y, z);
 749			break;
 750		}
 751		case DS_GX_CMD_VTX_DIFF: {
 752			int32_t xyz = entry.params[0];
 753			xyz |= entry.params[1] << 8;
 754			xyz |= entry.params[2] << 16;
 755			xyz |= entry.params[3] << 24;
 756			int16_t x = (xyz << 6) & 0xFFC0;
 757			int16_t y = (xyz >> 4) & 0xFFC0;
 758			int16_t z = (xyz >> 14) & 0xFFC0;
 759			_emitVertex(gx, gx->currentVertex.x + (x >> 6), gx->currentVertex.y + (y >> 6), gx->currentVertex.z + (z >> 6));
 760		}
 761		case DS_GX_CMD_POLYGON_ATTR:
 762			gx->nextPoly.polyParams = entry.params[0];
 763			gx->nextPoly.polyParams |= entry.params[1] << 8;
 764			gx->nextPoly.polyParams |= entry.params[2] << 16;
 765			gx->nextPoly.polyParams |= entry.params[3] << 24;
 766			break;
 767		case DS_GX_CMD_TEXIMAGE_PARAM:
 768			gx->nextPoly.texParams = entry.params[0];
 769			gx->nextPoly.texParams |= entry.params[1] << 8;
 770			gx->nextPoly.texParams |= entry.params[2] << 16;
 771			gx->nextPoly.texParams |= entry.params[3] << 24;
 772			break;
 773		case DS_GX_CMD_PLTT_BASE:
 774			gx->nextPoly.palBase = entry.params[0];
 775			gx->nextPoly.palBase |= entry.params[1] << 8;
 776			gx->nextPoly.palBase |= entry.params[2] << 16;
 777			gx->nextPoly.palBase |= entry.params[3] << 24;
 778			break;
 779		case DS_GX_CMD_BEGIN_VTXS:
 780			gx->vertexMode = entry.params[0] & 3;
 781			gx->currentPoly = gx->nextPoly;
 782			break;
 783		case DS_GX_CMD_END_VTXS:
 784			gx->vertexMode = -1;
 785			break;
 786		case DS_GX_CMD_SWAP_BUFFERS:
 787			gx->swapBuffers = true;
 788			break;
 789		case DS_GX_CMD_VIEWPORT:
 790			gx->viewportX1 = (uint8_t) entry.params[0];
 791			gx->viewportY1 = (uint8_t) entry.params[1];
 792			gx->viewportX2 = (uint8_t) entry.params[2];
 793			gx->viewportY2 = (uint8_t) entry.params[3];
 794			gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
 795			gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
 796			break;
 797		default:
 798			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]);
 799			break;
 800		}
 801
 802		gxstat = DSRegGXSTATSetPVMatrixStackLevel(gxstat, gx->pvMatrixPointer);
 803		gxstat = DSRegGXSTATSetProjMatrixStackLevel(gxstat, projMatrixPointer);
 804		gxstat = DSRegGXSTATTestFillMatrixStackError(gxstat, projMatrixPointer || gx->pvMatrixPointer >= 0x1F);
 805		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
 806
 807		if (cyclesLate >= cycles) {
 808			cyclesLate -= cycles;
 809		} else {
 810			break;
 811		}
 812	}
 813	if (cycles && !gx->swapBuffers) {
 814		mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
 815	}
 816	_flushOutstanding(gx);
 817	DSGXUpdateGXSTAT(gx);
 818}
 819
 820void DSGXInit(struct DSGX* gx) {
 821	gx->renderer = &dummyRenderer;
 822	CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
 823	CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
 824	gx->vertexBuffer[0] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
 825	gx->vertexBuffer[1] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
 826	gx->polygonBuffer[0] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
 827	gx->polygonBuffer[1] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
 828	gx->fifoEvent.name = "DS GX FIFO";
 829	gx->fifoEvent.priority = 0xC;
 830	gx->fifoEvent.context = gx;
 831	gx->fifoEvent.callback = _fifoRun;
 832}
 833
 834void DSGXDeinit(struct DSGX* gx) {
 835	DSGXAssociateRenderer(gx, &dummyRenderer);
 836	CircleBufferDeinit(&gx->fifo);
 837	CircleBufferDeinit(&gx->pipe);
 838	free(gx->vertexBuffer[0]);
 839	free(gx->vertexBuffer[1]);
 840	free(gx->polygonBuffer[0]);
 841	free(gx->polygonBuffer[1]);
 842}
 843
 844void DSGXReset(struct DSGX* gx) {
 845	CircleBufferClear(&gx->fifo);
 846	CircleBufferClear(&gx->pipe);
 847	DSGXMtxIdentity(&gx->projMatrix);
 848	DSGXMtxIdentity(&gx->texMatrix);
 849	DSGXMtxIdentity(&gx->posMatrix);
 850	DSGXMtxIdentity(&gx->vecMatrix);
 851
 852	DSGXMtxIdentity(&gx->clipMatrix);
 853	DSGXMtxIdentity(&gx->projMatrixStack);
 854	DSGXMtxIdentity(&gx->texMatrixStack);
 855	int i;
 856	for (i = 0; i < 32; ++i) {
 857		DSGXMtxIdentity(&gx->posMatrixStack[i]);
 858		DSGXMtxIdentity(&gx->vecMatrixStack[i]);
 859	}
 860	gx->swapBuffers = false;
 861	gx->bufferIndex = 0;
 862	gx->vertexIndex = 0;
 863	gx->polygonIndex = 0;
 864	gx->mtxMode = 0;
 865	gx->pvMatrixPointer = 0;
 866	gx->vertexMode = -1;
 867
 868	gx->viewportX1 = 0;
 869	gx->viewportY1 = 0;
 870	gx->viewportX2 = DS_VIDEO_HORIZONTAL_PIXELS - 1;
 871	gx->viewportY2 = DS_VIDEO_VERTICAL_PIXELS - 1;
 872	gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
 873	gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
 874
 875	memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
 876	memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
 877	memset(&gx->outstandingEntry, 0, sizeof(gx->outstandingEntry));
 878	gx->activeParams = 0;
 879	memset(&gx->currentVertex, 0, sizeof(gx->currentVertex));
 880	memset(&gx->nextPoly, 0, sizeof(gx-> nextPoly));
 881}
 882
 883void DSGXAssociateRenderer(struct DSGX* gx, struct DSGXRenderer* renderer) {
 884	gx->renderer->deinit(gx->renderer);
 885	gx->renderer = renderer;
 886	memcpy(gx->renderer->tex, gx->tex, sizeof(gx->renderer->tex));
 887	memcpy(gx->renderer->texPal, gx->texPal, sizeof(gx->renderer->texPal));
 888	gx->renderer->init(gx->renderer);
 889}
 890
 891void DSGXUpdateGXSTAT(struct DSGX* gx) {
 892	uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
 893	value = DSRegGXSTATIsDoIRQ(value);
 894
 895	size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
 896	// XXX
 897	if (gx->swapBuffers) {
 898		entries++;
 899	}
 900	value = DSRegGXSTATSetFIFOEntries(value, entries);
 901	value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
 902	value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
 903
 904	if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
 905		(DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
 906		DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
 907	}
 908
 909	value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
 910
 911	gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
 912}
 913
 914static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
 915	gx->outstandingCommand[0] = command;
 916	gx->outstandingCommand[1] = command >> 8;
 917	gx->outstandingCommand[2] = command >> 16;
 918	gx->outstandingCommand[3] = command >> 24;
 919	if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
 920		gx->outstandingCommand[0] = 0;
 921	}
 922	if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
 923		gx->outstandingCommand[1] = 0;
 924	}
 925	if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
 926		gx->outstandingCommand[2] = 0;
 927	}
 928	if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
 929		gx->outstandingCommand[3] = 0;
 930	}
 931	gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
 932	gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
 933	gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
 934	gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
 935	_flushOutstanding(gx);
 936	DSGXUpdateGXSTAT(gx);
 937}
 938
 939static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
 940	if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(entry))) {
 941		mLOG(DS_GX, INFO, "FIFO full");
 942		if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
 943			abort();
 944		}
 945		gx->p->cpuBlocked |= DS_CPU_BLOCK_GX;
 946		gx->outstandingEntry = entry;
 947		gx->p->ds9.cpu->nextEvent = 0;
 948		return;
 949	}
 950	if (gx->outstandingCommand[0]) {
 951		entry.command = gx->outstandingCommand[0];
 952		if (gx->outstandingParams[0]) {
 953			--gx->outstandingParams[0];
 954		}
 955		if (!gx->outstandingParams[0]) {
 956			// TODO: improve this
 957			memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
 958			memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
 959			gx->outstandingParams[3] = 0;
 960			gx->outstandingCommand[3] = 0;
 961		}
 962	} else {
 963		gx->outstandingParams[0] = _gxCommandParams[entry.command];
 964		if (gx->outstandingParams[0]) {
 965			--gx->outstandingParams[0];
 966		}
 967		if (gx->outstandingParams[0]) {
 968			gx->outstandingCommand[0] = entry.command;
 969		}
 970	}
 971	uint32_t cycles = _gxCommandCycleBase[entry.command];
 972	if (!cycles) {
 973		return;
 974	}
 975	if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
 976		CircleBufferWrite8(&gx->pipe, entry.command);
 977		CircleBufferWrite8(&gx->pipe, entry.params[0]);
 978		CircleBufferWrite8(&gx->pipe, entry.params[1]);
 979		CircleBufferWrite8(&gx->pipe, entry.params[2]);
 980		CircleBufferWrite8(&gx->pipe, entry.params[3]);
 981	} else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
 982		CircleBufferWrite8(&gx->fifo, entry.command);
 983		CircleBufferWrite8(&gx->fifo, entry.params[0]);
 984		CircleBufferWrite8(&gx->fifo, entry.params[1]);
 985		CircleBufferWrite8(&gx->fifo, entry.params[2]);
 986		CircleBufferWrite8(&gx->fifo, entry.params[3]);
 987	}
 988	if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
 989		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
 990	}
 991
 992	_flushOutstanding(gx);
 993}
 994
 995uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
 996	uint16_t oldValue = gx->p->memory.io9[address >> 1];
 997	switch (address) {
 998	case DS9_REG_DISP3DCNT:
 999		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1000		break;
1001	case DS9_REG_GXSTAT_LO:
1002		value = DSRegGXSTATIsMatrixStackError(value);
1003		if (value) {
1004			oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
1005			oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
1006		}
1007		value = oldValue;
1008		break;
1009	case DS9_REG_GXSTAT_HI:
1010		value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
1011		gx->p->memory.io9[address >> 1] = value;
1012		DSGXUpdateGXSTAT(gx);
1013		value = gx->p->memory.io9[address >> 1];
1014		break;
1015	default:
1016		if (address < DS9_REG_GXFIFO_00) {
1017			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1018		} else if (address <= DS9_REG_GXFIFO_1F) {
1019			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1020		} else if (address < DS9_REG_GXSTAT_LO) {
1021			struct DSGXEntry entry = {
1022				.command = (address & 0x1FC) >> 2,
1023				.params = {
1024					value,
1025					value >> 8,
1026				}
1027			};
1028			if (entry.command < DS_GX_CMD_MAX) {
1029				DSGXWriteFIFO(gx, entry);
1030			}
1031		} else {
1032			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1033		}
1034		break;
1035	}
1036	return value;
1037}
1038
1039uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
1040	switch (address) {
1041	case DS9_REG_DISP3DCNT:
1042		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1043		break;
1044	case DS9_REG_GXSTAT_LO:
1045		value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
1046		value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
1047		break;
1048	default:
1049		if (address < DS9_REG_GXFIFO_00) {
1050			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1051		} else if (address <= DS9_REG_GXFIFO_1F) {
1052			if (gx->outstandingParams[0]) {
1053				struct DSGXEntry entry = {
1054					.command = gx->outstandingCommand[0],
1055					.params = {
1056						value,
1057						value >> 8,
1058						value >> 16,
1059						value >> 24
1060					}
1061				};
1062				DSGXWriteFIFO(gx, entry);
1063			} else {
1064				DSGXUnpackCommand(gx, value);
1065			}
1066		} else if (address < DS9_REG_GXSTAT_LO) {
1067			struct DSGXEntry entry = {
1068				.command = (address & 0x1FC) >> 2,
1069				.params = {
1070					value,
1071					value >> 8,
1072					value >> 16,
1073					value >> 24
1074				}
1075			};
1076			DSGXWriteFIFO(gx, entry);
1077		} else {
1078			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1079		}
1080		break;
1081	}
1082	return value;
1083}
1084
1085void DSGXFlush(struct DSGX* gx) {
1086	if (gx->swapBuffers) {
1087		gx->renderer->setRAM(gx->renderer, gx->vertexBuffer[gx->bufferIndex], gx->polygonBuffer[gx->bufferIndex], gx->polygonIndex);
1088		gx->swapBuffers = false;
1089		gx->bufferIndex ^= 1;
1090		gx->vertexIndex = 0;
1091		gx->polygonIndex = 0;
1092		if (CircleBufferSize(&gx->fifo)) {
1093			mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
1094		}
1095	}
1096
1097	DSGXUpdateGXSTAT(gx);
1098}
1099
1100static void DSGXDummyRendererInit(struct DSGXRenderer* renderer) {
1101	UNUSED(renderer);
1102	// Nothing to do
1103}
1104
1105static void DSGXDummyRendererReset(struct DSGXRenderer* renderer) {
1106	UNUSED(renderer);
1107	// Nothing to do
1108}
1109
1110static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer) {
1111	UNUSED(renderer);
1112	// Nothing to do
1113}
1114
1115static void DSGXDummyRendererInvalidateTex(struct DSGXRenderer* renderer, int slot) {
1116	UNUSED(renderer);
1117	UNUSED(slot);
1118	// Nothing to do
1119}
1120
1121static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount) {
1122	UNUSED(renderer);
1123	UNUSED(verts);
1124	UNUSED(polys);
1125	UNUSED(polyCount);
1126	// Nothing to do
1127}
1128
1129static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
1130	UNUSED(renderer);
1131	UNUSED(y);
1132	// Nothing to do
1133}
1134
1135static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, color_t** output) {
1136	UNUSED(renderer);
1137	UNUSED(y);
1138	*output = NULL;
1139	// Nothing to do
1140}