all repos — mgba @ d5d7349259c1076bc000d70ac30d1938ed161292

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