all repos — mgba @ db9978f0c1ffec18117cf0e2cd5e4949a5d925c2

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