all repos — mgba @ 778eb8bc3bc89afec7b82328e5e5d4a8075d9d64

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