all repos — mgba @ 2b64d45906d5b687d4cf240a140dee7a48cf1aad

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