all repos — mgba @ 548be8175198ae506af57bb7babb24eae805ab0a

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			_updateClipMatrix(gx);
517			break;
518		}
519		case DS_GX_CMD_MTX_SCALE: {
520			int32_t m[3];
521			m[0] = gx->activeEntries[0].params[0];
522			m[0] |= gx->activeEntries[0].params[1] << 8;
523			m[0] |= gx->activeEntries[0].params[2] << 16;
524			m[0] |= gx->activeEntries[0].params[3] << 24;
525			m[1] = gx->activeEntries[1].params[0];
526			m[1] |= gx->activeEntries[1].params[1] << 8;
527			m[1] |= gx->activeEntries[1].params[2] << 16;
528			m[1] |= gx->activeEntries[1].params[3] << 24;
529			m[2] = gx->activeEntries[2].params[0];
530			m[2] |= gx->activeEntries[2].params[1] << 8;
531			m[2] |= gx->activeEntries[2].params[2] << 16;
532			m[2] |= gx->activeEntries[2].params[3] << 24;
533			switch (gx->mtxMode) {
534			case 0:
535				DSGXMtxScale(&gx->projMatrix, m);
536				break;
537			case 2:
538				DSGXMtxScale(&gx->vecMatrix, m);
539				// Fall through
540			case 1:
541				DSGXMtxScale(&gx->posMatrix, m);
542				break;
543			case 3:
544				DSGXMtxScale(&gx->texMatrix, m);
545				break;
546			}
547			_updateClipMatrix(gx);
548			break;
549		}
550		case DS_GX_CMD_COLOR:
551			gx->currentVertex.color = entry.params[0];
552			gx->currentVertex.color |= entry.params[1] << 8;
553			break;
554		case DS_GX_CMD_TEXCOORD:
555			gx->currentVertex.s = entry.params[0];
556			gx->currentVertex.s |= entry.params[1] << 8;
557			gx->currentVertex.t = entry.params[2];
558			gx->currentVertex.t |= entry.params[3] << 8;
559			break;
560		case DS_GX_CMD_VTX_16: {
561			int16_t x = gx->activeEntries[0].params[0];
562			x |= gx->activeEntries[0].params[1] << 8;
563			int16_t y = gx->activeEntries[0].params[2];
564			y |= gx->activeEntries[0].params[3] << 8;
565			int16_t z = gx->activeEntries[1].params[0];
566			z |= gx->activeEntries[1].params[1] << 8;
567			_emitVertex(gx, x, y, z);
568			break;
569		}
570		case DS_GX_CMD_VTX_10: {
571			int32_t xyz = gx->activeEntries[0].params[0];
572			xyz |= gx->activeEntries[0].params[1] << 8;
573			xyz |= gx->activeEntries[0].params[2] << 16;
574			xyz |= gx->activeEntries[0].params[3] << 24;
575			int16_t x = (xyz << 6) & 0xFFC0;
576			int16_t y = (xyz >> 4) & 0xFFC0;
577			int16_t z = (xyz >> 14) & 0xFFC0;
578			_emitVertex(gx, x, y, z);
579			break;
580		}
581		case DS_GX_CMD_VTX_XY: {
582			int16_t x = gx->activeEntries[0].params[0];
583			x |= gx->activeEntries[0].params[1] << 8;
584			int16_t y = gx->activeEntries[0].params[2];
585			y |= gx->activeEntries[0].params[3] << 8;
586			_emitVertex(gx, x, y, gx->currentVertex.z);
587			break;
588		}
589		case DS_GX_CMD_VTX_XZ: {
590			int16_t x = gx->activeEntries[0].params[0];
591			x |= gx->activeEntries[0].params[1] << 8;
592			int16_t z = gx->activeEntries[0].params[2];
593			z |= gx->activeEntries[0].params[3] << 8;
594			_emitVertex(gx, x, gx->currentVertex.y, z);
595			break;
596		}
597		case DS_GX_CMD_VTX_YZ: {
598			int16_t y = gx->activeEntries[0].params[0];
599			y |= gx->activeEntries[0].params[1] << 8;
600			int16_t z = gx->activeEntries[0].params[2];
601			z |= gx->activeEntries[0].params[3] << 8;
602			_emitVertex(gx, gx->currentVertex.x, y, z);
603			break;
604		}
605		case DS_GX_CMD_POLYGON_ATTR:
606			gx->currentPoly.polyParams = entry.params[0];
607			gx->currentPoly.polyParams |= entry.params[1] << 8;
608			gx->currentPoly.polyParams |= entry.params[2] << 16;
609			gx->currentPoly.polyParams |= entry.params[3] << 24;
610			break;
611		case DS_GX_CMD_BEGIN_VTXS:
612			gx->vertexMode = entry.params[0] & 3;
613			gx->currentPoly.verts = 0;
614			break;
615		case DS_GX_CMD_END_VTXS:
616			gx->vertexMode = -1;
617			break;
618		case DS_GX_CMD_SWAP_BUFFERS:
619			gx->swapBuffers = true;
620			break;
621		case DS_GX_CMD_VIEWPORT:
622			gx->viewportX1 = (uint8_t) entry.params[0];
623			gx->viewportY1 = (uint8_t) entry.params[1];
624			gx->viewportX2 = (uint8_t) entry.params[2];
625			gx->viewportY2 = (uint8_t) entry.params[3];
626			gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
627			gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
628			break;
629		default:
630			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]);
631			break;
632		}
633
634		gxstat = DSRegGXSTATSetPVMatrixStackLevel(gxstat, gx->pvMatrixPointer);
635		gxstat = DSRegGXSTATSetProjMatrixStackLevel(gxstat, projMatrixPointer);
636		gxstat = DSRegGXSTATTestFillMatrixStackError(gxstat, projMatrixPointer || gx->pvMatrixPointer >= 0x1F);
637		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
638
639		if (cyclesLate >= cycles) {
640			cyclesLate -= cycles;
641		} else {
642			break;
643		}
644	}
645	DSGXUpdateGXSTAT(gx);
646	if (cycles && !gx->swapBuffers) {
647		mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
648	}
649}
650
651void DSGXInit(struct DSGX* gx) {
652	gx->renderer = &dummyRenderer;
653	CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
654	CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
655	gx->vertexBuffer[0] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
656	gx->vertexBuffer[1] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
657	gx->polygonBuffer[0] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
658	gx->polygonBuffer[1] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
659	gx->fifoEvent.name = "DS GX FIFO";
660	gx->fifoEvent.priority = 0xC;
661	gx->fifoEvent.context = gx;
662	gx->fifoEvent.callback = _fifoRun;
663}
664
665void DSGXDeinit(struct DSGX* gx) {
666	DSGXAssociateRenderer(gx, &dummyRenderer);
667	CircleBufferDeinit(&gx->fifo);
668	CircleBufferDeinit(&gx->pipe);
669	free(gx->vertexBuffer[0]);
670	free(gx->vertexBuffer[1]);
671	free(gx->polygonBuffer[0]);
672	free(gx->polygonBuffer[1]);
673}
674
675void DSGXReset(struct DSGX* gx) {
676	CircleBufferClear(&gx->fifo);
677	CircleBufferClear(&gx->pipe);
678	DSGXMtxIdentity(&gx->projMatrix);
679	DSGXMtxIdentity(&gx->texMatrix);
680	DSGXMtxIdentity(&gx->posMatrix);
681	DSGXMtxIdentity(&gx->vecMatrix);
682
683	DSGXMtxIdentity(&gx->clipMatrix);
684	DSGXMtxIdentity(&gx->projMatrixStack);
685	DSGXMtxIdentity(&gx->texMatrixStack);
686	int i;
687	for (i = 0; i < 32; ++i) {
688		DSGXMtxIdentity(&gx->posMatrixStack[i]);
689		DSGXMtxIdentity(&gx->vecMatrixStack[i]);
690	}
691	gx->swapBuffers = false;
692	gx->bufferIndex = 0;
693	gx->vertexIndex = 0;
694	gx->polygonIndex = 0;
695	gx->mtxMode = 0;
696	gx->pvMatrixPointer = 0;
697	gx->vertexMode = -1;
698
699	gx->viewportX1 = 0;
700	gx->viewportY1 = 0;
701	gx->viewportX2 = DS_VIDEO_HORIZONTAL_PIXELS - 1;
702	gx->viewportY2 = DS_VIDEO_VERTICAL_PIXELS - 1;
703	gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
704	gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
705
706	memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
707	memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
708	gx->activeParams = 0;
709	memset(&gx->currentVertex, 0, sizeof(gx->currentVertex));
710}
711
712void DSGXAssociateRenderer(struct DSGX* gx, struct DSGXRenderer* renderer) {
713	gx->renderer->deinit(gx->renderer);
714	gx->renderer = renderer;
715	gx->renderer->init(gx->renderer);
716}
717
718void DSGXUpdateGXSTAT(struct DSGX* gx) {
719	uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
720	value = DSRegGXSTATIsDoIRQ(value);
721
722	size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
723	// XXX
724	if (gx->swapBuffers) {
725		entries++;
726	}
727	value = DSRegGXSTATSetFIFOEntries(value, entries);
728	value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
729	value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
730
731	if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
732		(DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
733		DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
734	}
735
736	value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
737
738	gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
739}
740
741static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
742	gx->outstandingCommand[0] = command;
743	gx->outstandingCommand[1] = command >> 8;
744	gx->outstandingCommand[2] = command >> 16;
745	gx->outstandingCommand[3] = command >> 24;
746	if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
747		gx->outstandingCommand[0] = 0;
748	}
749	if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
750		gx->outstandingCommand[1] = 0;
751	}
752	if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
753		gx->outstandingCommand[2] = 0;
754	}
755	if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
756		gx->outstandingCommand[3] = 0;
757	}
758	gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
759	gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
760	gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
761	gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
762}
763
764static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
765	if (gx->outstandingParams[0]) {
766		entry.command = gx->outstandingCommand[0];
767		--gx->outstandingParams[0];
768		if (!gx->outstandingParams[0]) {
769			// TODO: improve this
770			memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
771			memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
772			gx->outstandingParams[3] = 0;
773		}
774	} else {
775		gx->outstandingCommand[0] = entry.command;
776		gx->outstandingParams[0] = _gxCommandParams[entry.command];
777		if (gx->outstandingParams[0]) {
778			--gx->outstandingParams[0];
779		}
780	}
781	uint32_t cycles = _gxCommandCycleBase[entry.command];
782	if (!cycles) {
783		return;
784	}
785	if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
786		CircleBufferWrite8(&gx->pipe, entry.command);
787		CircleBufferWrite8(&gx->pipe, entry.params[0]);
788		CircleBufferWrite8(&gx->pipe, entry.params[1]);
789		CircleBufferWrite8(&gx->pipe, entry.params[2]);
790		CircleBufferWrite8(&gx->pipe, entry.params[3]);
791	} else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
792		CircleBufferWrite8(&gx->fifo, entry.command);
793		CircleBufferWrite8(&gx->fifo, entry.params[0]);
794		CircleBufferWrite8(&gx->fifo, entry.params[1]);
795		CircleBufferWrite8(&gx->fifo, entry.params[2]);
796		CircleBufferWrite8(&gx->fifo, entry.params[3]);
797	} else {
798		mLOG(DS_GX, STUB, "Unimplemented GX full");
799	}
800	if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
801		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
802	}
803}
804
805uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
806	uint16_t oldValue = gx->p->memory.io9[address >> 1];
807	switch (address) {
808	case DS9_REG_DISP3DCNT:
809		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
810		break;
811	case DS9_REG_GXSTAT_LO:
812		value = DSRegGXSTATIsMatrixStackError(value);
813		if (value) {
814			oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
815			oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
816		}
817		value = oldValue;
818		break;
819	case DS9_REG_GXSTAT_HI:
820		value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
821		gx->p->memory.io9[address >> 1] = value;
822		DSGXUpdateGXSTAT(gx);
823		value = gx->p->memory.io9[address >> 1];
824		break;
825	default:
826		if (address < DS9_REG_GXFIFO_00) {
827			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
828		} else if (address <= DS9_REG_GXFIFO_1F) {
829			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
830		} else if (address < DS9_REG_GXSTAT_LO) {
831			struct DSGXEntry entry = {
832				.command = (address & 0x1FC) >> 2,
833				.params = {
834					value,
835					value >> 8,
836				}
837			};
838			if (entry.command < DS_GX_CMD_MAX) {
839				DSGXWriteFIFO(gx, entry);
840			}
841		} else {
842			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
843		}
844		break;
845	}
846	return value;
847}
848
849uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
850	switch (address) {
851	case DS9_REG_DISP3DCNT:
852		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
853		break;
854	case DS9_REG_GXSTAT_LO:
855		value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
856		value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
857		break;
858	default:
859		if (address < DS9_REG_GXFIFO_00) {
860			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
861		} else if (address <= DS9_REG_GXFIFO_1F) {
862			if (gx->outstandingParams[0]) {
863				struct DSGXEntry entry = {
864					.command = gx->outstandingCommand[0],
865					.params = {
866						value,
867						value >> 8,
868						value >> 16,
869						value >> 24
870					}
871				};
872				DSGXWriteFIFO(gx, entry);
873			} else {
874				DSGXUnpackCommand(gx, value);
875			}
876		} else if (address < DS9_REG_GXSTAT_LO) {
877			struct DSGXEntry entry = {
878				.command = (address & 0x1FC) >> 2,
879				.params = {
880					value,
881					value >> 8,
882					value >> 16,
883					value >> 24
884				}
885			};
886			DSGXWriteFIFO(gx, entry);
887		} else {
888			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
889		}
890		break;
891	}
892	return value;
893}
894
895void DSGXSwapBuffers(struct DSGX* gx) {
896	gx->swapBuffers = false;
897
898	gx->renderer->setRAM(gx->renderer, gx->vertexBuffer[gx->bufferIndex], gx->polygonBuffer[gx->bufferIndex], gx->polygonIndex);
899
900	gx->bufferIndex ^= 1;
901	gx->vertexIndex = 0;
902	gx->polygonIndex = 0;
903
904	DSGXUpdateGXSTAT(gx);
905	if (CircleBufferSize(&gx->fifo)) {
906		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
907	}
908}
909
910static void DSGXDummyRendererInit(struct DSGXRenderer* renderer) {
911	UNUSED(renderer);
912	// Nothing to do
913}
914
915static void DSGXDummyRendererReset(struct DSGXRenderer* renderer) {
916	UNUSED(renderer);
917	// Nothing to do
918}
919
920static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer) {
921	UNUSED(renderer);
922	// Nothing to do
923}
924
925static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount) {
926	UNUSED(renderer);
927	UNUSED(verts);
928	UNUSED(polys);
929	UNUSED(polyCount);
930	// Nothing to do
931}
932
933static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
934	UNUSED(renderer);
935	UNUSED(y);
936	// Nothing to do
937}
938
939static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, color_t** output) {
940	UNUSED(renderer);
941	UNUSED(y);
942	*output = NULL;
943	// Nothing to do
944}