all repos — mgba @ 61a4cc5d230bef3da151ef04c824558d8345b710

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