all repos — mgba @ 4778dc41c8d5e78ede1ed7ea9bd27b77f44ac62a

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 const int32_t _gxCommandCycleBase[DS_GX_CMD_MAX] = {
 17	[DS_GX_CMD_NOP] = 0,
 18	[DS_GX_CMD_MTX_MODE] = 2,
 19	[DS_GX_CMD_MTX_PUSH] = 34,
 20	[DS_GX_CMD_MTX_POP] = 72,
 21	[DS_GX_CMD_MTX_STORE] = 34,
 22	[DS_GX_CMD_MTX_RESTORE] = 72,
 23	[DS_GX_CMD_MTX_IDENTITY] = 38,
 24	[DS_GX_CMD_MTX_LOAD_4x4] = 68,
 25	[DS_GX_CMD_MTX_LOAD_4x3] = 60,
 26	[DS_GX_CMD_MTX_MULT_4x4] = 70,
 27	[DS_GX_CMD_MTX_MULT_4x3] = 62,
 28	[DS_GX_CMD_MTX_MULT_3x3] = 56,
 29	[DS_GX_CMD_MTX_SCALE] = 44,
 30	[DS_GX_CMD_MTX_TRANS] = 44,
 31	[DS_GX_CMD_COLOR] = 2,
 32	[DS_GX_CMD_NORMAL] = 18,
 33	[DS_GX_CMD_TEXCOORD] = 2,
 34	[DS_GX_CMD_VTX_16] = 18,
 35	[DS_GX_CMD_VTX_10] = 16,
 36	[DS_GX_CMD_VTX_XY] = 16,
 37	[DS_GX_CMD_VTX_XZ] = 16,
 38	[DS_GX_CMD_VTX_YZ] = 16,
 39	[DS_GX_CMD_VTX_DIFF] = 16,
 40	[DS_GX_CMD_POLYGON_ATTR] = 2,
 41	[DS_GX_CMD_TEXIMAGE_PARAM] = 2,
 42	[DS_GX_CMD_PLTT_BASE] = 2,
 43	[DS_GX_CMD_DIF_AMB] = 8,
 44	[DS_GX_CMD_SPE_EMI] = 8,
 45	[DS_GX_CMD_LIGHT_VECTOR] = 12,
 46	[DS_GX_CMD_LIGHT_COLOR] = 2,
 47	[DS_GX_CMD_SHININESS] = 64,
 48	[DS_GX_CMD_BEGIN_VTXS] = 2,
 49	[DS_GX_CMD_END_VTXS] = 2,
 50	[DS_GX_CMD_SWAP_BUFFERS] = 784,
 51	[DS_GX_CMD_VIEWPORT] = 2,
 52	[DS_GX_CMD_BOX_TEST] = 206,
 53	[DS_GX_CMD_POS_TEST] = 18,
 54	[DS_GX_CMD_VEC_TEST] = 10,
 55};
 56
 57static const int32_t _gxCommandParams[DS_GX_CMD_MAX] = {
 58	[DS_GX_CMD_MTX_MODE] = 1,
 59	[DS_GX_CMD_MTX_POP] = 1,
 60	[DS_GX_CMD_MTX_STORE] = 1,
 61	[DS_GX_CMD_MTX_RESTORE] = 1,
 62	[DS_GX_CMD_MTX_LOAD_4x4] = 16,
 63	[DS_GX_CMD_MTX_LOAD_4x3] = 12,
 64	[DS_GX_CMD_MTX_MULT_4x4] = 16,
 65	[DS_GX_CMD_MTX_MULT_4x3] = 12,
 66	[DS_GX_CMD_MTX_MULT_3x3] = 9,
 67	[DS_GX_CMD_MTX_SCALE] = 3,
 68	[DS_GX_CMD_MTX_TRANS] = 3,
 69	[DS_GX_CMD_COLOR] = 1,
 70	[DS_GX_CMD_NORMAL] = 1,
 71	[DS_GX_CMD_TEXCOORD] = 1,
 72	[DS_GX_CMD_VTX_16] = 2,
 73	[DS_GX_CMD_VTX_10] = 1,
 74	[DS_GX_CMD_VTX_XY] = 1,
 75	[DS_GX_CMD_VTX_XZ] = 1,
 76	[DS_GX_CMD_VTX_YZ] = 1,
 77	[DS_GX_CMD_VTX_DIFF] = 1,
 78	[DS_GX_CMD_POLYGON_ATTR] = 1,
 79	[DS_GX_CMD_TEXIMAGE_PARAM] = 1,
 80	[DS_GX_CMD_PLTT_BASE] = 1,
 81	[DS_GX_CMD_DIF_AMB] = 1,
 82	[DS_GX_CMD_SPE_EMI] = 1,
 83	[DS_GX_CMD_LIGHT_VECTOR] = 1,
 84	[DS_GX_CMD_LIGHT_COLOR] = 1,
 85	[DS_GX_CMD_SHININESS] = 32,
 86	[DS_GX_CMD_BEGIN_VTXS] = 1,
 87	[DS_GX_CMD_SWAP_BUFFERS] = 1,
 88	[DS_GX_CMD_VIEWPORT] = 1,
 89	[DS_GX_CMD_BOX_TEST] = 3,
 90	[DS_GX_CMD_POS_TEST] = 2,
 91	[DS_GX_CMD_VEC_TEST] = 1,
 92};
 93
 94static void _pullPipe(struct DSGX* gx) {
 95	if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
 96		struct DSGXEntry entry = { 0 };
 97		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
 98		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
 99		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
100		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
101		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
102		CircleBufferWrite8(&gx->pipe, entry.command);
103		CircleBufferWrite8(&gx->pipe, entry.params[0]);
104		CircleBufferWrite8(&gx->pipe, entry.params[1]);
105		CircleBufferWrite8(&gx->pipe, entry.params[2]);
106		CircleBufferWrite8(&gx->pipe, entry.params[3]);
107	}
108	if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
109		struct DSGXEntry entry = { 0 };
110		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
111		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
112		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
113		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
114		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
115		CircleBufferWrite8(&gx->pipe, entry.command);
116		CircleBufferWrite8(&gx->pipe, entry.params[0]);
117		CircleBufferWrite8(&gx->pipe, entry.params[1]);
118		CircleBufferWrite8(&gx->pipe, entry.params[2]);
119		CircleBufferWrite8(&gx->pipe, entry.params[3]);
120	}
121}
122
123static void _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) {
124	struct DSGX* gx = context;
125	uint32_t cycles;
126	bool first = true;
127	while (true) {
128		if (gx->swapBuffers) {
129			break;
130		}
131
132		if (CircleBufferSize(&gx->pipe) <= 2 * sizeof(struct DSGXEntry)) {
133			_pullPipe(gx);
134		}
135
136		struct DSGXEntry entry = { 0 };
137		CircleBufferDump(&gx->pipe, (int8_t*) &entry.command, 1);
138		cycles = _gxCommandCycleBase[entry.command];
139
140		if (first) {
141			first = false;
142		} else if (cycles > cyclesLate) {
143			break;
144		}
145		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.command);
146		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[0]);
147		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[1]);
148		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[2]);
149		CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[3]);
150
151		switch (entry.command) {
152		case DS_GX_CMD_SWAP_BUFFERS:
153			gx->swapBuffers = true;
154			break;
155		default:
156			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]);
157			break;
158		}
159		if (cyclesLate >= cycles) {
160			cyclesLate -= cycles;
161		}
162		if (!CircleBufferSize(&gx->pipe)) {
163			cycles = 0;
164			break;
165		}
166	}
167	DSGXUpdateGXSTAT(gx);
168	if (cycles && !gx->swapBuffers) {
169		mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
170	}
171}
172
173void DSGXInit(struct DSGX* gx) {
174	CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
175	CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
176	gx->fifoEvent.name = "DS GX FIFO";
177	gx->fifoEvent.priority = 0xC;
178	gx->fifoEvent.context = gx;
179	gx->fifoEvent.callback = _fifoRun;
180}
181
182void DSGXDeinit(struct DSGX* gx) {
183	CircleBufferDeinit(&gx->fifo);
184	CircleBufferDeinit(&gx->pipe);
185}
186
187void DSGXReset(struct DSGX* gx) {
188	CircleBufferClear(&gx->fifo);
189	CircleBufferClear(&gx->pipe);
190	gx->swapBuffers = false;
191	memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
192	memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
193}
194
195void DSGXUpdateGXSTAT(struct DSGX* gx) {
196	uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
197	value = DSRegGXSTATIsDoIRQ(value);
198
199	size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
200	// XXX
201	if (gx->swapBuffers) {
202		entries++;
203	}
204	value = DSRegGXSTATSetFIFOEntries(value, entries);
205	value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
206	value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
207
208	if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
209		(DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
210		DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
211	}
212
213	value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
214
215	gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
216}
217
218static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
219	gx->outstandingCommand[0] = command;
220	gx->outstandingCommand[1] = command >> 8;
221	gx->outstandingCommand[2] = command >> 16;
222	gx->outstandingCommand[3] = command >> 24;
223	if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
224		gx->outstandingCommand[0] = 0;
225	}
226	if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
227		gx->outstandingCommand[1] = 0;
228	}
229	if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
230		gx->outstandingCommand[2] = 0;
231	}
232	if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
233		gx->outstandingCommand[3] = 0;
234	}
235	gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
236	gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
237	gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
238	gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
239}
240
241static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
242	if (gx->outstandingParams[0]) {
243		entry.command = gx->outstandingCommand[0];
244		--gx->outstandingParams[0];
245		if (!gx->outstandingParams[0]) {
246			// TODO: improve this
247			memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
248			memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
249			gx->outstandingParams[3] = 0;
250		}
251	} else {
252		gx->outstandingCommand[0] = entry.command;
253		gx->outstandingParams[0] = _gxCommandParams[entry.command];
254		if (gx->outstandingParams[0]) {
255			--gx->outstandingParams[0];
256		}
257	}
258	uint32_t cycles = _gxCommandCycleBase[entry.command];
259	if (!cycles) {
260		return;
261	}
262	if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
263		CircleBufferWrite8(&gx->pipe, entry.command);
264		CircleBufferWrite8(&gx->pipe, entry.params[0]);
265		CircleBufferWrite8(&gx->pipe, entry.params[1]);
266		CircleBufferWrite8(&gx->pipe, entry.params[2]);
267		CircleBufferWrite8(&gx->pipe, entry.params[3]);
268	} else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
269		CircleBufferWrite8(&gx->fifo, entry.command);
270		CircleBufferWrite8(&gx->fifo, entry.params[0]);
271		CircleBufferWrite8(&gx->fifo, entry.params[1]);
272		CircleBufferWrite8(&gx->fifo, entry.params[2]);
273		CircleBufferWrite8(&gx->fifo, entry.params[3]);
274	} else {
275		mLOG(DS_GX, STUB, "Unimplemented GX full");
276	}
277	if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
278		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
279	}
280}
281
282uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
283	uint16_t oldValue = gx->p->memory.io9[address >> 1];
284	switch (address) {
285	case DS9_REG_DISP3DCNT:
286		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
287		break;
288	case DS9_REG_GXSTAT_LO:
289		value = DSRegGXSTATIsMatrixStackError(value);
290		if (value) {
291			oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
292			oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
293		}
294		value = oldValue;
295		break;
296	case DS9_REG_GXSTAT_HI:
297		value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
298		gx->p->memory.io9[address >> 1] = value;
299		DSGXUpdateGXSTAT(gx);
300		value = gx->p->memory.io9[address >> 1];
301		break;
302	default:
303		if (address < DS9_REG_GXFIFO_00) {
304			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
305		} else if (address <= DS9_REG_GXFIFO_1F) {
306			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
307		} else if (address < DS9_REG_GXSTAT_LO) {
308			struct DSGXEntry entry = {
309				.command = (address & 0x1FC) >> 2,
310				.params = {
311					value,
312					value >> 8,
313				}
314			};
315			if (entry.command < DS_GX_CMD_MAX) {
316				DSGXWriteFIFO(gx, entry);
317			}
318		} else {
319			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
320		}
321		break;
322	}
323	return value;
324}
325
326uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
327	switch (address) {
328	case DS9_REG_DISP3DCNT:
329		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
330		break;
331	case DS9_REG_GXSTAT_LO:
332		value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
333		value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
334		break;
335	default:
336		if (address < DS9_REG_GXFIFO_00) {
337			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
338		} else if (address <= DS9_REG_GXFIFO_1F) {
339			if (gx->outstandingParams[0]) {
340				struct DSGXEntry entry = {
341					.command = gx->outstandingCommand[0],
342					.params = {
343						value,
344						value >> 8,
345						value >> 16,
346						value >> 24
347					}
348				};
349				DSGXWriteFIFO(gx, entry);
350			} else {
351				DSGXUnpackCommand(gx, value);
352			}
353		} else if (address < DS9_REG_GXSTAT_LO) {
354			struct DSGXEntry entry = {
355				.command = (address & 0x1FC) >> 2,
356				.params = {
357					value,
358					value >> 8,
359					value >> 16,
360					value >> 24
361				}
362			};
363			DSGXWriteFIFO(gx, entry);
364		} else {
365			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
366		}
367		break;
368	}
369	return value;
370}
371
372void DSGXSwapBuffers(struct DSGX* gx) {
373	mLOG(DS_GX, STUB, "Unimplemented GX swap buffers");
374	gx->swapBuffers = false;
375
376	// TODO
377	DSGXUpdateGXSTAT(gx);
378	if (CircleBufferSize(&gx->fifo)) {
379		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
380	}
381}