all repos — mgba @ 30cac2e24b271277d66b42041b0c8a5a95a89f6b

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, bool wSort);
  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 bool _boxTestVertex(struct DSGX* gx, struct DSGXVertex* vertex) {
 333	int32_t vx = _dotViewport(vertex, &gx->clipMatrix.m[0]);
 334	int32_t vy = _dotViewport(vertex, &gx->clipMatrix.m[1]);
 335	int32_t vz = _dotViewport(vertex, &gx->clipMatrix.m[2]);
 336	int32_t vw = _dotViewport(vertex, &gx->clipMatrix.m[3]);
 337
 338	vx = (vx + vw) * (int64_t) (gx->viewportWidth << 12) / (vw * 2) + (gx->viewportX1 << 12);
 339	vy = (vy + vw) * (int64_t) (gx->viewportHeight << 12) / (vw * 2) + (gx->viewportY1 << 12);
 340	vx >>= 12;
 341	vy >>= 12;
 342
 343	if (vx < gx->viewportX1) {
 344		return false;
 345	}
 346	if (vx >= gx->viewportX2) {
 347		return false;
 348	}
 349	if (vy < gx->viewportY1) {
 350		return false;
 351	}
 352	if (vy >= gx->viewportY2) {
 353		return false;
 354	}
 355	// TODO: depth clipping
 356	return true;
 357}
 358
 359static bool _boxTest(struct DSGX* gx) {
 360	int16_t x = gx->activeEntries[0].params[0];
 361	x |= gx->activeEntries[0].params[1] << 8;
 362	int16_t y = gx->activeEntries[0].params[2];
 363	y |= gx->activeEntries[0].params[3] << 8;
 364	int16_t z = gx->activeEntries[1].params[0];
 365	z |= gx->activeEntries[1].params[1] << 8;
 366	int16_t w = gx->activeEntries[1].params[2];
 367	w |= gx->activeEntries[1].params[3] << 8;
 368	int16_t h = gx->activeEntries[2].params[0];
 369	h |= gx->activeEntries[2].params[1] << 8;
 370	int16_t d = gx->activeEntries[2].params[2];
 371	d |= gx->activeEntries[2].params[3] << 8;
 372
 373	struct DSGXVertex vertex = {
 374		.x = x,
 375		.y = y,
 376		.z = z
 377	};
 378	if (_boxTestVertex(gx, &vertex)) {
 379		return true;
 380	}
 381
 382	vertex.x += w;
 383	if (_boxTestVertex(gx, &vertex)) {
 384		return true;
 385	}
 386
 387	vertex.x = x;
 388	vertex.y += h;
 389	if (_boxTestVertex(gx, &vertex)) {
 390		return true;
 391	}
 392
 393	vertex.x += w;
 394	if (_boxTestVertex(gx, &vertex)) {
 395		return true;
 396	}
 397
 398	vertex.x = x;
 399	vertex.y = y;
 400	vertex.z += d;
 401	if (_boxTestVertex(gx, &vertex)) {
 402		return true;
 403	}
 404
 405	vertex.x += w;
 406	if (_boxTestVertex(gx, &vertex)) {
 407		return true;
 408	}
 409
 410	vertex.x = x;
 411	vertex.y += h;
 412	if (_boxTestVertex(gx, &vertex)) {
 413		return true;
 414	}
 415
 416	vertex.x += w;
 417	if (_boxTestVertex(gx, &vertex)) {
 418		return true;
 419	}
 420
 421	return false;
 422}
 423
 424static void _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 425	struct DSGX* gx = context;
 426	uint32_t cycles;
 427	bool first = true;
 428	while (!gx->swapBuffers) {
 429		if (CircleBufferSize(&gx->pipe) <= 2 * sizeof(struct DSGXEntry)) {
 430			_pullPipe(gx);
 431		}
 432
 433		if (!CircleBufferSize(&gx->pipe)) {
 434			cycles = 0;
 435			break;
 436		}
 437
 438		DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
 439		int projMatrixPointer = DSRegGXSTATGetProjMatrixStackLevel(gxstat);
 440
 441		struct DSGXEntry entry = { 0 };
 442		CircleBufferDump(&gx->pipe, (int8_t*) &entry.command, 1);
 443		cycles = _gxCommandCycleBase[entry.command];
 444
 445		if (first) {
 446			first = false;
 447		} else if (!gx->activeParams && cycles > cyclesLate) {
 448			break;
 449		}
 450		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.command);
 451		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[0]);
 452		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[1]);
 453		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[2]);
 454		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[3]);
 455
 456		if (gx->activeParams) {
 457			int index = _gxCommandParams[entry.command] - gx->activeParams;
 458			gx->activeEntries[index] = entry;
 459			--gx->activeParams;
 460		} else {
 461			gx->activeParams = _gxCommandParams[entry.command];
 462			if (gx->activeParams) {
 463				--gx->activeParams;
 464			}
 465			if (gx->activeParams) {
 466				gx->activeEntries[0] = entry;
 467			}
 468		}
 469
 470		if (gx->activeParams) {
 471			continue;
 472		}
 473
 474		switch (entry.command) {
 475		case DS_GX_CMD_MTX_MODE:
 476			if (entry.params[0] < 4) {
 477				gx->mtxMode = entry.params[0];
 478			} else {
 479				mLOG(DS_GX, GAME_ERROR, "Invalid GX MTX_MODE %02X", entry.params[0]);
 480			}
 481			break;
 482		case DS_GX_CMD_MTX_PUSH:
 483			switch (gx->mtxMode) {
 484			case 0:
 485				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrix));
 486				++projMatrixPointer;
 487				break;
 488			case 2:
 489				memcpy(&gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->vecMatrix, sizeof(gx->vecMatrix));
 490				// Fall through
 491			case 1:
 492				memcpy(&gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->posMatrix, sizeof(gx->posMatrix));
 493				++gx->pvMatrixPointer;
 494				break;
 495			case 3:
 496				mLOG(DS_GX, STUB, "Unimplemented GX MTX_PUSH mode");
 497				break;
 498			}
 499			break;
 500		case DS_GX_CMD_MTX_POP: {
 501			int8_t offset = entry.params[0];
 502			offset <<= 2;
 503			offset >>= 2;
 504			switch (gx->mtxMode) {
 505			case 0:
 506				projMatrixPointer -= offset;
 507				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
 508				break;
 509			case 1:
 510				gx->pvMatrixPointer -= offset;
 511				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
 512				break;
 513			case 2:
 514				gx->pvMatrixPointer -= offset;
 515				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->vecMatrix));
 516				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
 517				break;
 518			case 3:
 519				mLOG(DS_GX, STUB, "Unimplemented GX MTX_POP mode");
 520				break;
 521			}
 522			_updateClipMatrix(gx);
 523			break;
 524		}
 525		case DS_GX_CMD_MTX_STORE: {
 526			int8_t offset = entry.params[0] & 0x1F;
 527			// TODO: overflow
 528			switch (gx->mtxMode) {
 529			case 0:
 530				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrixStack));
 531				break;
 532			case 2:
 533				memcpy(&gx->vecMatrixStack[offset], &gx->vecMatrix, sizeof(gx->vecMatrix));
 534				// Fall through
 535			case 1:
 536				memcpy(&gx->posMatrixStack[offset], &gx->posMatrix, sizeof(gx->posMatrix));
 537				break;
 538			case 3:
 539				mLOG(DS_GX, STUB, "Unimplemented GX MTX_STORE mode");
 540				break;
 541			}
 542			break;
 543		}
 544		case DS_GX_CMD_MTX_RESTORE: {
 545			int8_t offset = entry.params[0] & 0x1F;
 546			// TODO: overflow
 547			switch (gx->mtxMode) {
 548			case 0:
 549				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
 550				break;
 551			case 2:
 552				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[offset], sizeof(gx->vecMatrix));
 553				// Fall through
 554			case 1:
 555				memcpy(&gx->posMatrix, &gx->posMatrixStack[offset], sizeof(gx->posMatrix));
 556				break;
 557			case 3:
 558				mLOG(DS_GX, STUB, "Unimplemented GX MTX_RESTORE mode");
 559				break;
 560			}
 561			_updateClipMatrix(gx);
 562			break;
 563		}
 564		case DS_GX_CMD_MTX_IDENTITY:
 565			switch (gx->mtxMode) {
 566			case 0:
 567				DSGXMtxIdentity(&gx->projMatrix);
 568				break;
 569			case 2:
 570				DSGXMtxIdentity(&gx->vecMatrix);
 571				// Fall through
 572			case 1:
 573				DSGXMtxIdentity(&gx->posMatrix);
 574				break;
 575			case 3:
 576				DSGXMtxIdentity(&gx->texMatrix);
 577				break;
 578			}
 579			_updateClipMatrix(gx);
 580			break;
 581		case DS_GX_CMD_MTX_LOAD_4x4: {
 582			struct DSGXMatrix m;
 583			int i;
 584			for (i = 0; i < 16; ++i) {
 585				m.m[i] = gx->activeEntries[i].params[0];
 586				m.m[i] |= gx->activeEntries[i].params[1] << 8;
 587				m.m[i] |= gx->activeEntries[i].params[2] << 16;
 588				m.m[i] |= gx->activeEntries[i].params[3] << 24;
 589			}
 590			switch (gx->mtxMode) {
 591			case 0:
 592				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
 593				break;
 594			case 2:
 595				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
 596				// Fall through
 597			case 1:
 598				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
 599				break;
 600			case 3:
 601				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
 602				break;
 603			}
 604			_updateClipMatrix(gx);
 605			break;
 606		}
 607		case DS_GX_CMD_MTX_LOAD_4x3: {
 608			struct DSGXMatrix m;
 609			int i, j;
 610			for (j = 0; j < 4; ++j) {
 611				for (i = 0; i < 3; ++i) {
 612					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 613					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 614					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 615					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 616				}
 617				m.m[j * 4 + 3] = 0;
 618			}
 619			m.m[15] = MTX_ONE;
 620			switch (gx->mtxMode) {
 621			case 0:
 622				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
 623				break;
 624			case 2:
 625				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
 626				// Fall through
 627			case 1:
 628				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
 629				break;
 630			case 3:
 631				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
 632				break;
 633			}
 634			_updateClipMatrix(gx);
 635			break;
 636		}
 637		case DS_GX_CMD_MTX_MULT_4x4: {
 638			struct DSGXMatrix m;
 639			int i;
 640			for (i = 0; i < 16; ++i) {
 641				m.m[i] = gx->activeEntries[i].params[0];
 642				m.m[i] |= gx->activeEntries[i].params[1] << 8;
 643				m.m[i] |= gx->activeEntries[i].params[2] << 16;
 644				m.m[i] |= gx->activeEntries[i].params[3] << 24;
 645			}
 646			switch (gx->mtxMode) {
 647			case 0:
 648				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 649				break;
 650			case 2:
 651				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 652				// Fall through
 653			case 1:
 654				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 655				break;
 656			case 3:
 657				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 658				break;
 659			}
 660			_updateClipMatrix(gx);
 661			break;
 662		}
 663		case DS_GX_CMD_MTX_MULT_4x3: {
 664			struct DSGXMatrix m;
 665			int i, j;
 666			for (j = 0; j < 4; ++j) {
 667				for (i = 0; i < 3; ++i) {
 668					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 669					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 670					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 671					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 672				}
 673				m.m[j * 4 + 3] = 0;
 674			}
 675			m.m[15] = MTX_ONE;
 676			switch (gx->mtxMode) {
 677			case 0:
 678				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 679				break;
 680			case 2:
 681				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 682				// Fall through
 683			case 1:
 684				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 685				break;
 686			case 3:
 687				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 688				break;
 689			}
 690			_updateClipMatrix(gx);
 691			break;
 692		}
 693		case DS_GX_CMD_MTX_MULT_3x3: {
 694			struct DSGXMatrix m;
 695			int i, j;
 696			for (j = 0; j < 3; ++j) {
 697				for (i = 0; i < 3; ++i) {
 698					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
 699					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
 700					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
 701					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
 702				}
 703				m.m[j * 4 + 3] = 0;
 704			}
 705			m.m[12] = 0;
 706			m.m[13] = 0;
 707			m.m[14] = 0;
 708			m.m[15] = MTX_ONE;
 709			switch (gx->mtxMode) {
 710			case 0:
 711				DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
 712				break;
 713			case 2:
 714				DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
 715				// Fall through
 716			case 1:
 717				DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
 718				break;
 719			case 3:
 720				DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
 721				break;
 722			}
 723			_updateClipMatrix(gx);
 724			break;
 725		}
 726		case DS_GX_CMD_MTX_TRANS: {
 727			int32_t m[3];
 728			m[0] = gx->activeEntries[0].params[0];
 729			m[0] |= gx->activeEntries[0].params[1] << 8;
 730			m[0] |= gx->activeEntries[0].params[2] << 16;
 731			m[0] |= gx->activeEntries[0].params[3] << 24;
 732			m[1] = gx->activeEntries[1].params[0];
 733			m[1] |= gx->activeEntries[1].params[1] << 8;
 734			m[1] |= gx->activeEntries[1].params[2] << 16;
 735			m[1] |= gx->activeEntries[1].params[3] << 24;
 736			m[2] = gx->activeEntries[2].params[0];
 737			m[2] |= gx->activeEntries[2].params[1] << 8;
 738			m[2] |= gx->activeEntries[2].params[2] << 16;
 739			m[2] |= gx->activeEntries[2].params[3] << 24;
 740			switch (gx->mtxMode) {
 741			case 0:
 742				DSGXMtxTranslate(&gx->projMatrix, m);
 743				break;
 744			case 2:
 745				DSGXMtxTranslate(&gx->vecMatrix, m);
 746				// Fall through
 747			case 1:
 748				DSGXMtxTranslate(&gx->posMatrix, m);
 749				break;
 750			case 3:
 751				DSGXMtxTranslate(&gx->texMatrix, m);
 752				break;
 753			}
 754			_updateClipMatrix(gx);
 755			break;
 756		}
 757		case DS_GX_CMD_MTX_SCALE: {
 758			int32_t m[3];
 759			m[0] = gx->activeEntries[0].params[0];
 760			m[0] |= gx->activeEntries[0].params[1] << 8;
 761			m[0] |= gx->activeEntries[0].params[2] << 16;
 762			m[0] |= gx->activeEntries[0].params[3] << 24;
 763			m[1] = gx->activeEntries[1].params[0];
 764			m[1] |= gx->activeEntries[1].params[1] << 8;
 765			m[1] |= gx->activeEntries[1].params[2] << 16;
 766			m[1] |= gx->activeEntries[1].params[3] << 24;
 767			m[2] = gx->activeEntries[2].params[0];
 768			m[2] |= gx->activeEntries[2].params[1] << 8;
 769			m[2] |= gx->activeEntries[2].params[2] << 16;
 770			m[2] |= gx->activeEntries[2].params[3] << 24;
 771			switch (gx->mtxMode) {
 772			case 0:
 773				DSGXMtxScale(&gx->projMatrix, m);
 774				break;
 775			case 2:
 776				DSGXMtxScale(&gx->vecMatrix, m);
 777				// Fall through
 778			case 1:
 779				DSGXMtxScale(&gx->posMatrix, m);
 780				break;
 781			case 3:
 782				DSGXMtxScale(&gx->texMatrix, m);
 783				break;
 784			}
 785			_updateClipMatrix(gx);
 786			break;
 787		}
 788		case DS_GX_CMD_COLOR:
 789			gx->currentVertex.color = entry.params[0];
 790			gx->currentVertex.color |= entry.params[1] << 8;
 791			break;
 792		case DS_GX_CMD_TEXCOORD:
 793			gx->currentVertex.s = entry.params[0];
 794			gx->currentVertex.s |= entry.params[1] << 8;
 795			gx->currentVertex.t = entry.params[2];
 796			gx->currentVertex.t |= entry.params[3] << 8;
 797			break;
 798		case DS_GX_CMD_VTX_16: {
 799			int16_t x = gx->activeEntries[0].params[0];
 800			x |= gx->activeEntries[0].params[1] << 8;
 801			int16_t y = gx->activeEntries[0].params[2];
 802			y |= gx->activeEntries[0].params[3] << 8;
 803			int16_t z = gx->activeEntries[1].params[0];
 804			z |= gx->activeEntries[1].params[1] << 8;
 805			_emitVertex(gx, x, y, z);
 806			break;
 807		}
 808		case DS_GX_CMD_VTX_10: {
 809			int32_t xyz = entry.params[0];
 810			xyz |= entry.params[1] << 8;
 811			xyz |= entry.params[2] << 16;
 812			xyz |= entry.params[3] << 24;
 813			int16_t x = (xyz << 6) & 0xFFC0;
 814			int16_t y = (xyz >> 4) & 0xFFC0;
 815			int16_t z = (xyz >> 14) & 0xFFC0;
 816			_emitVertex(gx, x, y, z);
 817			break;
 818		}
 819		case DS_GX_CMD_VTX_XY: {
 820			int16_t x = entry.params[0];
 821			x |= entry.params[1] << 8;
 822			int16_t y = entry.params[2];
 823			y |= entry.params[3] << 8;
 824			_emitVertex(gx, x, y, gx->currentVertex.z);
 825			break;
 826		}
 827		case DS_GX_CMD_VTX_XZ: {
 828			int16_t x = entry.params[0];
 829			x |= entry.params[1] << 8;
 830			int16_t z = entry.params[2];
 831			z |= entry.params[3] << 8;
 832			_emitVertex(gx, x, gx->currentVertex.y, z);
 833			break;
 834		}
 835		case DS_GX_CMD_VTX_YZ: {
 836			int16_t y = entry.params[0];
 837			y |= entry.params[1] << 8;
 838			int16_t z = entry.params[2];
 839			z |= entry.params[3] << 8;
 840			_emitVertex(gx, gx->currentVertex.x, y, z);
 841			break;
 842		}
 843		case DS_GX_CMD_VTX_DIFF: {
 844			int32_t xyz = entry.params[0];
 845			xyz |= entry.params[1] << 8;
 846			xyz |= entry.params[2] << 16;
 847			xyz |= entry.params[3] << 24;
 848			int16_t x = (xyz << 6) & 0xFFC0;
 849			int16_t y = (xyz >> 4) & 0xFFC0;
 850			int16_t z = (xyz >> 14) & 0xFFC0;
 851			_emitVertex(gx, gx->currentVertex.x + (x >> 6), gx->currentVertex.y + (y >> 6), gx->currentVertex.z + (z >> 6));
 852		}
 853		case DS_GX_CMD_POLYGON_ATTR:
 854			gx->nextPoly.polyParams = entry.params[0];
 855			gx->nextPoly.polyParams |= entry.params[1] << 8;
 856			gx->nextPoly.polyParams |= entry.params[2] << 16;
 857			gx->nextPoly.polyParams |= entry.params[3] << 24;
 858			break;
 859		case DS_GX_CMD_TEXIMAGE_PARAM:
 860			gx->nextPoly.texParams = entry.params[0];
 861			gx->nextPoly.texParams |= entry.params[1] << 8;
 862			gx->nextPoly.texParams |= entry.params[2] << 16;
 863			gx->nextPoly.texParams |= entry.params[3] << 24;
 864			break;
 865		case DS_GX_CMD_PLTT_BASE:
 866			gx->nextPoly.palBase = entry.params[0];
 867			gx->nextPoly.palBase |= entry.params[1] << 8;
 868			gx->nextPoly.palBase |= entry.params[2] << 16;
 869			gx->nextPoly.palBase |= entry.params[3] << 24;
 870			break;
 871		case DS_GX_CMD_BEGIN_VTXS:
 872			gx->vertexMode = entry.params[0] & 3;
 873			gx->currentPoly = gx->nextPoly;
 874			break;
 875		case DS_GX_CMD_END_VTXS:
 876			gx->vertexMode = -1;
 877			break;
 878		case DS_GX_CMD_SWAP_BUFFERS:
 879			gx->swapBuffers = true;
 880			gx->wSort = entry.params[0] & 2;
 881			break;
 882		case DS_GX_CMD_VIEWPORT:
 883			gx->viewportX1 = (uint8_t) entry.params[0];
 884			gx->viewportY1 = (uint8_t) entry.params[1];
 885			gx->viewportX2 = (uint8_t) entry.params[2];
 886			gx->viewportY2 = (uint8_t) entry.params[3];
 887			gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
 888			gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
 889			break;
 890		case DS_GX_CMD_BOX_TEST:
 891			gxstat = DSRegGXSTATClearTestBusy(gxstat);
 892			gxstat = DSRegGXSTATTestFillBoxTestResult(gxstat, _boxTest(gx));
 893			break;
 894		default:
 895			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]);
 896			break;
 897		}
 898
 899		gxstat = DSRegGXSTATSetPVMatrixStackLevel(gxstat, gx->pvMatrixPointer);
 900		gxstat = DSRegGXSTATSetProjMatrixStackLevel(gxstat, projMatrixPointer);
 901		gxstat = DSRegGXSTATTestFillMatrixStackError(gxstat, projMatrixPointer || gx->pvMatrixPointer >= 0x1F);
 902		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
 903
 904		if (cyclesLate >= cycles) {
 905			cyclesLate -= cycles;
 906		} else {
 907			break;
 908		}
 909	}
 910	if (cycles && !gx->swapBuffers) {
 911		mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
 912	}
 913	_flushOutstanding(gx);
 914	DSGXUpdateGXSTAT(gx);
 915}
 916
 917void DSGXInit(struct DSGX* gx) {
 918	gx->renderer = &dummyRenderer;
 919	CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
 920	CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
 921	gx->vertexBuffer[0] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
 922	gx->vertexBuffer[1] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
 923	gx->polygonBuffer[0] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
 924	gx->polygonBuffer[1] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
 925	gx->fifoEvent.name = "DS GX FIFO";
 926	gx->fifoEvent.priority = 0xC;
 927	gx->fifoEvent.context = gx;
 928	gx->fifoEvent.callback = _fifoRun;
 929}
 930
 931void DSGXDeinit(struct DSGX* gx) {
 932	DSGXAssociateRenderer(gx, &dummyRenderer);
 933	CircleBufferDeinit(&gx->fifo);
 934	CircleBufferDeinit(&gx->pipe);
 935	free(gx->vertexBuffer[0]);
 936	free(gx->vertexBuffer[1]);
 937	free(gx->polygonBuffer[0]);
 938	free(gx->polygonBuffer[1]);
 939}
 940
 941void DSGXReset(struct DSGX* gx) {
 942	CircleBufferClear(&gx->fifo);
 943	CircleBufferClear(&gx->pipe);
 944	DSGXMtxIdentity(&gx->projMatrix);
 945	DSGXMtxIdentity(&gx->texMatrix);
 946	DSGXMtxIdentity(&gx->posMatrix);
 947	DSGXMtxIdentity(&gx->vecMatrix);
 948
 949	DSGXMtxIdentity(&gx->clipMatrix);
 950	DSGXMtxIdentity(&gx->projMatrixStack);
 951	DSGXMtxIdentity(&gx->texMatrixStack);
 952	int i;
 953	for (i = 0; i < 32; ++i) {
 954		DSGXMtxIdentity(&gx->posMatrixStack[i]);
 955		DSGXMtxIdentity(&gx->vecMatrixStack[i]);
 956	}
 957	gx->swapBuffers = false;
 958	gx->bufferIndex = 0;
 959	gx->vertexIndex = 0;
 960	gx->polygonIndex = 0;
 961	gx->mtxMode = 0;
 962	gx->pvMatrixPointer = 0;
 963	gx->vertexMode = -1;
 964
 965	gx->viewportX1 = 0;
 966	gx->viewportY1 = 0;
 967	gx->viewportX2 = DS_VIDEO_HORIZONTAL_PIXELS - 1;
 968	gx->viewportY2 = DS_VIDEO_VERTICAL_PIXELS - 1;
 969	gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
 970	gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
 971
 972	memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
 973	memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
 974	memset(&gx->outstandingEntry, 0, sizeof(gx->outstandingEntry));
 975	gx->activeParams = 0;
 976	memset(&gx->currentVertex, 0, sizeof(gx->currentVertex));
 977	memset(&gx->nextPoly, 0, sizeof(gx-> nextPoly));
 978}
 979
 980void DSGXAssociateRenderer(struct DSGX* gx, struct DSGXRenderer* renderer) {
 981	gx->renderer->deinit(gx->renderer);
 982	gx->renderer = renderer;
 983	memcpy(gx->renderer->tex, gx->tex, sizeof(gx->renderer->tex));
 984	memcpy(gx->renderer->texPal, gx->texPal, sizeof(gx->renderer->texPal));
 985	gx->renderer->init(gx->renderer);
 986}
 987
 988void DSGXUpdateGXSTAT(struct DSGX* gx) {
 989	uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
 990	value = DSRegGXSTATIsDoIRQ(value);
 991
 992	size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
 993	// XXX
 994	if (gx->swapBuffers) {
 995		entries++;
 996	}
 997	value = DSRegGXSTATSetFIFOEntries(value, entries);
 998	value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
 999	value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
1000
1001	if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
1002		(DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
1003		DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
1004	}
1005
1006	value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
1007
1008	gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
1009}
1010
1011static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
1012	gx->outstandingCommand[0] = command;
1013	gx->outstandingCommand[1] = command >> 8;
1014	gx->outstandingCommand[2] = command >> 16;
1015	gx->outstandingCommand[3] = command >> 24;
1016	if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
1017		gx->outstandingCommand[0] = 0;
1018	}
1019	if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
1020		gx->outstandingCommand[1] = 0;
1021	}
1022	if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
1023		gx->outstandingCommand[2] = 0;
1024	}
1025	if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
1026		gx->outstandingCommand[3] = 0;
1027	}
1028	gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
1029	gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
1030	gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
1031	gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
1032	_flushOutstanding(gx);
1033	DSGXUpdateGXSTAT(gx);
1034}
1035
1036static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
1037	if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(entry))) {
1038		mLOG(DS_GX, INFO, "FIFO full");
1039		if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
1040			abort();
1041		}
1042		gx->p->cpuBlocked |= DS_CPU_BLOCK_GX;
1043		gx->outstandingEntry = entry;
1044		gx->p->ds9.cpu->nextEvent = 0;
1045		return;
1046	}
1047	if (gx->outstandingCommand[0]) {
1048		entry.command = gx->outstandingCommand[0];
1049		if (gx->outstandingParams[0]) {
1050			--gx->outstandingParams[0];
1051		}
1052		if (!gx->outstandingParams[0]) {
1053			// TODO: improve this
1054			memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
1055			memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
1056			gx->outstandingParams[3] = 0;
1057			gx->outstandingCommand[3] = 0;
1058		}
1059	} else {
1060		gx->outstandingParams[0] = _gxCommandParams[entry.command];
1061		if (gx->outstandingParams[0]) {
1062			--gx->outstandingParams[0];
1063		}
1064		if (gx->outstandingParams[0]) {
1065			gx->outstandingCommand[0] = entry.command;
1066		}
1067	}
1068	uint32_t cycles = _gxCommandCycleBase[entry.command];
1069	if (!cycles) {
1070		return;
1071	}
1072	if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
1073		CircleBufferWrite8(&gx->pipe, entry.command);
1074		CircleBufferWrite8(&gx->pipe, entry.params[0]);
1075		CircleBufferWrite8(&gx->pipe, entry.params[1]);
1076		CircleBufferWrite8(&gx->pipe, entry.params[2]);
1077		CircleBufferWrite8(&gx->pipe, entry.params[3]);
1078	} else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
1079		CircleBufferWrite8(&gx->fifo, entry.command);
1080		CircleBufferWrite8(&gx->fifo, entry.params[0]);
1081		CircleBufferWrite8(&gx->fifo, entry.params[1]);
1082		CircleBufferWrite8(&gx->fifo, entry.params[2]);
1083		CircleBufferWrite8(&gx->fifo, entry.params[3]);
1084	}
1085	if (entry.command == DS_GX_CMD_BOX_TEST) {
1086		DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
1087		gxstat = DSRegGXSTATFillTestBusy(gxstat);
1088		gxstat = DSRegGXSTATClearBoxTestResult(gxstat);
1089		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
1090	}
1091	if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
1092		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
1093	}
1094
1095	_flushOutstanding(gx);
1096}
1097
1098uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
1099	uint16_t oldValue = gx->p->memory.io9[address >> 1];
1100	switch (address) {
1101	case DS9_REG_DISP3DCNT:
1102		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1103		break;
1104	case DS9_REG_GXSTAT_LO:
1105		value = DSRegGXSTATIsMatrixStackError(value);
1106		if (value) {
1107			oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
1108			oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
1109		}
1110		value = oldValue;
1111		break;
1112	case DS9_REG_GXSTAT_HI:
1113		value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
1114		gx->p->memory.io9[address >> 1] = value;
1115		DSGXUpdateGXSTAT(gx);
1116		value = gx->p->memory.io9[address >> 1];
1117		break;
1118	default:
1119		if (address < DS9_REG_GXFIFO_00) {
1120			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1121		} else if (address <= DS9_REG_GXFIFO_1F) {
1122			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1123		} else if (address < DS9_REG_GXSTAT_LO) {
1124			struct DSGXEntry entry = {
1125				.command = (address & 0x1FC) >> 2,
1126				.params = {
1127					value,
1128					value >> 8,
1129				}
1130			};
1131			if (entry.command < DS_GX_CMD_MAX) {
1132				DSGXWriteFIFO(gx, entry);
1133			}
1134		} else {
1135			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1136		}
1137		break;
1138	}
1139	return value;
1140}
1141
1142uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
1143	switch (address) {
1144	case DS9_REG_DISP3DCNT:
1145		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1146		break;
1147	case DS9_REG_GXSTAT_LO:
1148		value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
1149		value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
1150		break;
1151	default:
1152		if (address < DS9_REG_GXFIFO_00) {
1153			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1154		} else if (address <= DS9_REG_GXFIFO_1F) {
1155			if (gx->outstandingParams[0]) {
1156				struct DSGXEntry entry = {
1157					.command = gx->outstandingCommand[0],
1158					.params = {
1159						value,
1160						value >> 8,
1161						value >> 16,
1162						value >> 24
1163					}
1164				};
1165				DSGXWriteFIFO(gx, entry);
1166			} else {
1167				DSGXUnpackCommand(gx, value);
1168			}
1169		} else if (address < DS9_REG_GXSTAT_LO) {
1170			struct DSGXEntry entry = {
1171				.command = (address & 0x1FC) >> 2,
1172				.params = {
1173					value,
1174					value >> 8,
1175					value >> 16,
1176					value >> 24
1177				}
1178			};
1179			DSGXWriteFIFO(gx, entry);
1180		} else {
1181			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1182		}
1183		break;
1184	}
1185	return value;
1186}
1187
1188void DSGXFlush(struct DSGX* gx) {
1189	if (gx->swapBuffers) {
1190		gx->renderer->setRAM(gx->renderer, gx->vertexBuffer[gx->bufferIndex], gx->polygonBuffer[gx->bufferIndex], gx->polygonIndex, gx->wSort);
1191		gx->swapBuffers = false;
1192		gx->bufferIndex ^= 1;
1193		gx->vertexIndex = 0;
1194		gx->polygonIndex = 0;
1195		if (CircleBufferSize(&gx->fifo)) {
1196			mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
1197		}
1198	}
1199
1200	DSGXUpdateGXSTAT(gx);
1201}
1202
1203static void DSGXDummyRendererInit(struct DSGXRenderer* renderer) {
1204	UNUSED(renderer);
1205	// Nothing to do
1206}
1207
1208static void DSGXDummyRendererReset(struct DSGXRenderer* renderer) {
1209	UNUSED(renderer);
1210	// Nothing to do
1211}
1212
1213static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer) {
1214	UNUSED(renderer);
1215	// Nothing to do
1216}
1217
1218static void DSGXDummyRendererInvalidateTex(struct DSGXRenderer* renderer, int slot) {
1219	UNUSED(renderer);
1220	UNUSED(slot);
1221	// Nothing to do
1222}
1223
1224static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort) {
1225	UNUSED(renderer);
1226	UNUSED(verts);
1227	UNUSED(polys);
1228	UNUSED(polyCount);
1229	// Nothing to do
1230}
1231
1232static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
1233	UNUSED(renderer);
1234	UNUSED(y);
1235	// Nothing to do
1236}
1237
1238static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, color_t** output) {
1239	UNUSED(renderer);
1240	UNUSED(y);
1241	*output = NULL;
1242	// Nothing to do
1243}