all repos — mgba @ 0454a30b2e599728c1955022736cba841be9577c

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