all repos — mgba @ c6ff504e66f44f1480cc9a30531139ea96f37b0d

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