all repos — mgba @ 1bc3170755794ad4c0338f54a310c8ff46112646

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 _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) {
140	struct DSGX* gx = context;
141	uint32_t cycles;
142	bool first = true;
143	while (!gx->swapBuffers) {
144		if (CircleBufferSize(&gx->pipe) <= 2 * sizeof(struct DSGXEntry)) {
145			_pullPipe(gx);
146		}
147
148		if (!CircleBufferSize(&gx->pipe)) {
149			cycles = 0;
150			break;
151		}
152
153		DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
154		int projMatrixPointer = DSRegGXSTATGetProjMatrixStackLevel(gxstat);
155
156		struct DSGXEntry entry = { 0 };
157		CircleBufferDump(&gx->pipe, (int8_t*) &entry.command, 1);
158		cycles = _gxCommandCycleBase[entry.command];
159
160		if (first) {
161			first = false;
162		} else if (cycles > cyclesLate) {
163			break;
164		}
165		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.command);
166		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[0]);
167		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[1]);
168		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[2]);
169		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[3]);
170
171		if (gx->activeParams) {
172			int index = _gxCommandParams[entry.command] - gx->activeParams;
173			gx->activeEntries[index] = entry;
174			--gx->activeParams;
175		} else {
176			gx->activeParams = _gxCommandParams[entry.command];
177			if (gx->activeParams) {
178				--gx->activeParams;
179			}
180			if (gx->activeParams) {
181				gx->activeEntries[0] = entry;
182			}
183		}
184
185		if (gx->activeParams) {
186			continue;
187		}
188
189		switch (entry.command) {
190		case DS_GX_CMD_MTX_MODE:
191			if (entry.params[0] < 4) {
192				gx->mtxMode = entry.params[0];
193			} else {
194				mLOG(DS_GX, GAME_ERROR, "Invalid GX MTX_MODE %02X", entry.params[0]);
195			}
196			break;
197		case DS_GX_CMD_MTX_PUSH:
198			switch (gx->mtxMode) {
199			case 0:
200				memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrix));
201				++projMatrixPointer;
202				break;
203			case 2:
204				memcpy(&gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->vecMatrix, sizeof(gx->vecMatrix));
205				// Fall through
206			case 1:
207				memcpy(&gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->posMatrix, sizeof(gx->posMatrix));
208				++gx->pvMatrixPointer;
209				break;
210			case 3:
211				mLOG(DS_GX, STUB, "Unimplemented GX MTX_PUSH mode");
212				break;
213			}
214			break;
215		case DS_GX_CMD_MTX_POP: {
216			int8_t offset = entry.params[0];
217			offset <<= 2;
218			offset >>= 2;
219			switch (gx->mtxMode) {
220			case 0:
221				projMatrixPointer -= offset;
222				memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
223				break;
224			case 1:
225				gx->pvMatrixPointer -= offset;
226				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
227				break;
228			case 2:
229				gx->pvMatrixPointer -= offset;
230				memcpy(&gx->vecMatrix, &gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->vecMatrix));
231				memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
232				break;
233			case 3:
234				mLOG(DS_GX, STUB, "Unimplemented GX MTX_POP mode");
235				break;
236			}
237			break;
238		}
239		case DS_GX_CMD_MTX_IDENTITY:
240			switch (gx->mtxMode) {
241			case 0:
242				DSGXMtxIdentity(&gx->projMatrix);
243				break;
244			case 2:
245				DSGXMtxIdentity(&gx->vecMatrix);
246				// Fall through
247			case 1:
248				DSGXMtxIdentity(&gx->posMatrix);
249				break;
250			case 3:
251				DSGXMtxIdentity(&gx->texMatrix);
252				break;
253			}
254			break;
255		case DS_GX_CMD_MTX_LOAD_4x4: {
256			struct DSGXMatrix m;
257			int i;
258			for (i = 0; i < 16; ++i) {
259				m.m[i] = gx->activeEntries[i].params[0];
260				m.m[i] |= gx->activeEntries[i].params[1] << 8;
261				m.m[i] |= gx->activeEntries[i].params[2] << 16;
262				m.m[i] |= gx->activeEntries[i].params[3] << 24;
263			}
264			switch (gx->mtxMode) {
265			case 0:
266				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
267				break;
268			case 2:
269				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
270				// Fall through
271			case 1:
272				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
273				break;
274			case 3:
275				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
276				break;
277			}
278			break;
279		}
280		case DS_GX_CMD_MTX_LOAD_4x3: {
281			struct DSGXMatrix m;
282			int i, j;
283			for (j = 0; j < 4; ++j) {
284				for (i = 0; i < 3; ++i) {
285					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
286					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
287					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
288					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
289				}
290				m.m[j * 4 + 3] = 0;
291			}
292			m.m[15] = MTX_ONE;
293			switch (gx->mtxMode) {
294			case 0:
295				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
296				break;
297			case 2:
298				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
299				// Fall through
300			case 1:
301				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
302				break;
303			case 3:
304				memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
305				break;
306			}
307			break;
308		}
309		case DS_GX_CMD_MTX_MULT_4x4: {
310			struct DSGXMatrix m;
311			int i;
312			for (i = 0; i < 16; ++i) {
313				m.m[i] = gx->activeEntries[i].params[0];
314				m.m[i] |= gx->activeEntries[i].params[1] << 8;
315				m.m[i] |= gx->activeEntries[i].params[2] << 16;
316				m.m[i] |= gx->activeEntries[i].params[3] << 24;
317			}
318			switch (gx->mtxMode) {
319			case 0:
320				DSGXMtxMultiply(&gx->projMatrix, &m);
321				break;
322			case 2:
323				DSGXMtxMultiply(&gx->vecMatrix, &m);
324				// Fall through
325			case 1:
326				DSGXMtxMultiply(&gx->posMatrix, &m);
327				break;
328			case 3:
329				DSGXMtxMultiply(&gx->texMatrix, &m);
330				break;
331			}
332			break;
333		}
334		case DS_GX_CMD_MTX_MULT_4x3: {
335			struct DSGXMatrix m;
336			int i, j;
337			for (j = 0; j < 4; ++j) {
338				for (i = 0; i < 3; ++i) {
339					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
340					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
341					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
342					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
343				}
344				m.m[j * 4 + 3] = 0;
345			}
346			m.m[15] = MTX_ONE;
347			switch (gx->mtxMode) {
348			case 0:
349				DSGXMtxMultiply(&gx->projMatrix, &m);
350				break;
351			case 2:
352				DSGXMtxMultiply(&gx->vecMatrix, &m);
353				// Fall through
354			case 1:
355				DSGXMtxMultiply(&gx->posMatrix, &m);
356				break;
357			case 3:
358				DSGXMtxMultiply(&gx->texMatrix, &m);
359				break;
360			}
361			break;
362		}
363		case DS_GX_CMD_MTX_MULT_3x3: {
364			struct DSGXMatrix m;
365			int i, j;
366			for (j = 0; j < 3; ++j) {
367				for (i = 0; i < 3; ++i) {
368					m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
369					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
370					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
371					m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
372				}
373				m.m[j * 4 + 3] = 0;
374			}
375			m.m[12] = 0;
376			m.m[13] = 0;
377			m.m[14] = 0;
378			m.m[15] = MTX_ONE;
379			switch (gx->mtxMode) {
380			case 0:
381				memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
382				break;
383			case 2:
384				memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
385				// Fall through
386			case 1:
387				memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
388				break;
389			case 3:
390				memcpy(&gx->texMatrix, &m, sizeof(gx->projMatrix));
391				break;
392			}
393			break;
394		}
395		case DS_GX_CMD_MTX_TRANS: {
396			int32_t m[3];
397			m[0] = gx->activeEntries[0].params[0];
398			m[0] |= gx->activeEntries[0].params[1] << 8;
399			m[0] |= gx->activeEntries[0].params[2] << 16;
400			m[0] |= gx->activeEntries[0].params[3] << 24;
401			m[1] = gx->activeEntries[1].params[0];
402			m[1] |= gx->activeEntries[1].params[1] << 8;
403			m[1] |= gx->activeEntries[1].params[2] << 16;
404			m[1] |= gx->activeEntries[1].params[3] << 24;
405			m[2] = gx->activeEntries[2].params[0];
406			m[2] |= gx->activeEntries[2].params[1] << 8;
407			m[2] |= gx->activeEntries[2].params[2] << 16;
408			m[2] |= gx->activeEntries[2].params[3] << 24;
409			switch (gx->mtxMode) {
410			case 0:
411				DSGXMtxTranslate(&gx->projMatrix, m);
412				break;
413			case 2:
414				DSGXMtxTranslate(&gx->vecMatrix, m);
415				// Fall through
416			case 1:
417				DSGXMtxTranslate(&gx->posMatrix, m);
418				break;
419			case 3:
420				DSGXMtxTranslate(&gx->texMatrix, m);
421				break;
422			}
423			break;
424		}
425		case DS_GX_CMD_MTX_SCALE: {
426			int32_t m[3];
427			m[0] = gx->activeEntries[0].params[0];
428			m[0] |= gx->activeEntries[0].params[1] << 8;
429			m[0] |= gx->activeEntries[0].params[2] << 16;
430			m[0] |= gx->activeEntries[0].params[3] << 24;
431			m[1] = gx->activeEntries[1].params[0];
432			m[1] |= gx->activeEntries[1].params[1] << 8;
433			m[1] |= gx->activeEntries[1].params[2] << 16;
434			m[1] |= gx->activeEntries[1].params[3] << 24;
435			m[2] = gx->activeEntries[2].params[0];
436			m[2] |= gx->activeEntries[2].params[1] << 8;
437			m[2] |= gx->activeEntries[2].params[2] << 16;
438			m[2] |= gx->activeEntries[2].params[3] << 24;
439			switch (gx->mtxMode) {
440			case 0:
441				DSGXMtxScale(&gx->projMatrix, m);
442				break;
443			case 2:
444				DSGXMtxScale(&gx->vecMatrix, m);
445				// Fall through
446			case 1:
447				DSGXMtxScale(&gx->posMatrix, m);
448				break;
449			case 3:
450				DSGXMtxScale(&gx->texMatrix, m);
451				break;
452			}
453			break;
454		}
455		case DS_GX_CMD_SWAP_BUFFERS:
456			gx->swapBuffers = true;
457			break;
458		default:
459			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]);
460			break;
461		}
462
463		gxstat = DSRegGXSTATSetPVMatrixStackLevel(gxstat, gx->pvMatrixPointer);
464		gxstat = DSRegGXSTATSetProjMatrixStackLevel(gxstat, projMatrixPointer);
465		gxstat = DSRegGXSTATTestFillMatrixStackError(gxstat, projMatrixPointer || gx->pvMatrixPointer >= 0x1F);
466		gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
467
468		if (cyclesLate >= cycles) {
469			cyclesLate -= cycles;
470		} else {
471			break;
472		}
473	}
474	DSGXUpdateGXSTAT(gx);
475	if (cycles && !gx->swapBuffers) {
476		mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
477	}
478}
479
480void DSGXInit(struct DSGX* gx) {
481	gx->renderer = &dummyRenderer;
482	CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
483	CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
484	gx->fifoEvent.name = "DS GX FIFO";
485	gx->fifoEvent.priority = 0xC;
486	gx->fifoEvent.context = gx;
487	gx->fifoEvent.callback = _fifoRun;
488}
489
490void DSGXDeinit(struct DSGX* gx) {
491	DSGXAssociateRenderer(gx, &dummyRenderer);
492	CircleBufferDeinit(&gx->fifo);
493	CircleBufferDeinit(&gx->pipe);
494}
495
496void DSGXReset(struct DSGX* gx) {
497	CircleBufferClear(&gx->fifo);
498	CircleBufferClear(&gx->pipe);
499	DSGXMtxIdentity(&gx->projMatrix);
500	DSGXMtxIdentity(&gx->texMatrix);
501	DSGXMtxIdentity(&gx->posMatrix);
502	DSGXMtxIdentity(&gx->vecMatrix);
503
504	DSGXMtxIdentity(&gx->projMatrixStack);
505	DSGXMtxIdentity(&gx->texMatrixStack);
506	int i;
507	for (i = 0; i < 32; ++i) {
508		DSGXMtxIdentity(&gx->posMatrixStack[i]);
509		DSGXMtxIdentity(&gx->vecMatrixStack[i]);
510	}
511	gx->swapBuffers = false;
512	gx->bufferIndex = 0;
513	gx->mtxMode = 0;
514	gx->pvMatrixPointer = 0;
515
516	memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
517	memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
518	gx->activeParams = 0;
519}
520
521void DSGXAssociateRenderer(struct DSGX* gx, struct DSGXRenderer* renderer) {
522	gx->renderer->deinit(gx->renderer);
523	gx->renderer = renderer;
524	gx->renderer->init(gx->renderer);
525}
526
527void DSGXUpdateGXSTAT(struct DSGX* gx) {
528	uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
529	value = DSRegGXSTATIsDoIRQ(value);
530
531	size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
532	// XXX
533	if (gx->swapBuffers) {
534		entries++;
535	}
536	value = DSRegGXSTATSetFIFOEntries(value, entries);
537	value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
538	value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
539
540	if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
541		(DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
542		DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
543	}
544
545	value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
546
547	gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
548}
549
550static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
551	gx->outstandingCommand[0] = command;
552	gx->outstandingCommand[1] = command >> 8;
553	gx->outstandingCommand[2] = command >> 16;
554	gx->outstandingCommand[3] = command >> 24;
555	if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
556		gx->outstandingCommand[0] = 0;
557	}
558	if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
559		gx->outstandingCommand[1] = 0;
560	}
561	if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
562		gx->outstandingCommand[2] = 0;
563	}
564	if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
565		gx->outstandingCommand[3] = 0;
566	}
567	gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
568	gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
569	gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
570	gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
571}
572
573static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
574	if (gx->outstandingParams[0]) {
575		entry.command = gx->outstandingCommand[0];
576		--gx->outstandingParams[0];
577		if (!gx->outstandingParams[0]) {
578			// TODO: improve this
579			memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
580			memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
581			gx->outstandingParams[3] = 0;
582		}
583	} else {
584		gx->outstandingCommand[0] = entry.command;
585		gx->outstandingParams[0] = _gxCommandParams[entry.command];
586		if (gx->outstandingParams[0]) {
587			--gx->outstandingParams[0];
588		}
589	}
590	uint32_t cycles = _gxCommandCycleBase[entry.command];
591	if (!cycles) {
592		return;
593	}
594	if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
595		CircleBufferWrite8(&gx->pipe, entry.command);
596		CircleBufferWrite8(&gx->pipe, entry.params[0]);
597		CircleBufferWrite8(&gx->pipe, entry.params[1]);
598		CircleBufferWrite8(&gx->pipe, entry.params[2]);
599		CircleBufferWrite8(&gx->pipe, entry.params[3]);
600	} else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
601		CircleBufferWrite8(&gx->fifo, entry.command);
602		CircleBufferWrite8(&gx->fifo, entry.params[0]);
603		CircleBufferWrite8(&gx->fifo, entry.params[1]);
604		CircleBufferWrite8(&gx->fifo, entry.params[2]);
605		CircleBufferWrite8(&gx->fifo, entry.params[3]);
606	} else {
607		mLOG(DS_GX, STUB, "Unimplemented GX full");
608	}
609	if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
610		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
611	}
612}
613
614uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
615	uint16_t oldValue = gx->p->memory.io9[address >> 1];
616	switch (address) {
617	case DS9_REG_DISP3DCNT:
618		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
619		break;
620	case DS9_REG_GXSTAT_LO:
621		value = DSRegGXSTATIsMatrixStackError(value);
622		if (value) {
623			oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
624			oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
625		}
626		value = oldValue;
627		break;
628	case DS9_REG_GXSTAT_HI:
629		value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
630		gx->p->memory.io9[address >> 1] = value;
631		DSGXUpdateGXSTAT(gx);
632		value = gx->p->memory.io9[address >> 1];
633		break;
634	default:
635		if (address < DS9_REG_GXFIFO_00) {
636			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
637		} else if (address <= DS9_REG_GXFIFO_1F) {
638			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
639		} else if (address < DS9_REG_GXSTAT_LO) {
640			struct DSGXEntry entry = {
641				.command = (address & 0x1FC) >> 2,
642				.params = {
643					value,
644					value >> 8,
645				}
646			};
647			if (entry.command < DS_GX_CMD_MAX) {
648				DSGXWriteFIFO(gx, entry);
649			}
650		} else {
651			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
652		}
653		break;
654	}
655	return value;
656}
657
658uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
659	switch (address) {
660	case DS9_REG_DISP3DCNT:
661		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
662		break;
663	case DS9_REG_GXSTAT_LO:
664		value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
665		value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
666		break;
667	default:
668		if (address < DS9_REG_GXFIFO_00) {
669			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
670		} else if (address <= DS9_REG_GXFIFO_1F) {
671			if (gx->outstandingParams[0]) {
672				struct DSGXEntry entry = {
673					.command = gx->outstandingCommand[0],
674					.params = {
675						value,
676						value >> 8,
677						value >> 16,
678						value >> 24
679					}
680				};
681				DSGXWriteFIFO(gx, entry);
682			} else {
683				DSGXUnpackCommand(gx, value);
684			}
685		} else if (address < DS9_REG_GXSTAT_LO) {
686			struct DSGXEntry entry = {
687				.command = (address & 0x1FC) >> 2,
688				.params = {
689					value,
690					value >> 8,
691					value >> 16,
692					value >> 24
693				}
694			};
695			DSGXWriteFIFO(gx, entry);
696		} else {
697			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
698		}
699		break;
700	}
701	return value;
702}
703
704void DSGXSwapBuffers(struct DSGX* gx) {
705	mLOG(DS_GX, STUB, "Unimplemented GX swap buffers");
706	gx->swapBuffers = false;
707
708	// TODO
709	DSGXUpdateGXSTAT(gx);
710	if (CircleBufferSize(&gx->fifo)) {
711		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
712	}
713}
714
715static void DSGXDummyRendererInit(struct DSGXRenderer* renderer) {
716	UNUSED(renderer);
717	// Nothing to do
718}
719
720static void DSGXDummyRendererReset(struct DSGXRenderer* renderer) {
721	UNUSED(renderer);
722	// Nothing to do
723}
724
725static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer) {
726	UNUSED(renderer);
727	// Nothing to do
728}
729
730static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount) {
731	UNUSED(renderer);
732	UNUSED(verts);
733	UNUSED(polys);
734	UNUSED(polyCount);
735	// Nothing to do
736}
737
738static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
739	UNUSED(renderer);
740	UNUSED(y);
741	// Nothing to do
742}
743
744static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, color_t** output) {
745	UNUSED(renderer);
746	UNUSED(y);
747	*output = NULL;
748	// Nothing to do
749}