all repos — mgba @ 3fcdbd0d8983c024a1c88d85a1cc137e9c9482db

mGBA Game Boy Advance Emulator

src/ds/gx.c (view raw)

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