all repos — mgba @ 756474ac569989ed2cd6c940e70ce14d1e65a500

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
 15static const int32_t _gxCommandCycleBase[DS_GX_CMD_MAX] = {
 16	[DS_GX_CMD_NOP] = 0,
 17	[DS_GX_CMD_MTX_MODE] = 2,
 18	[DS_GX_CMD_MTX_PUSH] = 34,
 19	[DS_GX_CMD_MTX_POP] = 72,
 20	[DS_GX_CMD_MTX_STORE] = 34,
 21	[DS_GX_CMD_MTX_RESTORE] = 72,
 22	[DS_GX_CMD_MTX_IDENTITY] = 38,
 23	[DS_GX_CMD_MTX_LOAD_4x4] = 68,
 24	[DS_GX_CMD_MTX_LOAD_4x3] = 60,
 25	[DS_GX_CMD_MTX_MULT_4x4] = 70,
 26	[DS_GX_CMD_MTX_MULT_4x3] = 62,
 27	[DS_GX_CMD_MTX_MULT_3x3] = 56,
 28	[DS_GX_CMD_MTX_SCALE] = 44,
 29	[DS_GX_CMD_MTX_TRANS] = 44,
 30	[DS_GX_CMD_COLOR] = 2,
 31	[DS_GX_CMD_NORMAL] = 18,
 32	[DS_GX_CMD_TEXCOORD] = 2,
 33	[DS_GX_CMD_VTX_16] = 18,
 34	[DS_GX_CMD_VTX_10] = 16,
 35	[DS_GX_CMD_VTX_XY] = 16,
 36	[DS_GX_CMD_VTX_XZ] = 16,
 37	[DS_GX_CMD_VTX_YZ] = 16,
 38	[DS_GX_CMD_VTX_DIFF] = 16,
 39	[DS_GX_CMD_POLYGON_ATTR] = 2,
 40	[DS_GX_CMD_TEXIMAGE_PARAM] = 2,
 41	[DS_GX_CMD_PLTT_BASE] = 2,
 42	[DS_GX_CMD_DIF_AMB] = 8,
 43	[DS_GX_CMD_SPE_EMI] = 8,
 44	[DS_GX_CMD_LIGHT_VECTOR] = 12,
 45	[DS_GX_CMD_LIGHT_COLOR] = 2,
 46	[DS_GX_CMD_SHININESS] = 64,
 47	[DS_GX_CMD_BEGIN_VTXS] = 2,
 48	[DS_GX_CMD_END_VTXS] = 2,
 49	[DS_GX_CMD_SWAP_BUFFERS] = 784,
 50	[DS_GX_CMD_VIEWPORT] = 2,
 51	[DS_GX_CMD_BOX_TEST] = 206,
 52	[DS_GX_CMD_POS_TEST] = 18,
 53	[DS_GX_CMD_VEC_TEST] = 10,
 54};
 55
 56static void _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 57	struct DSGX* gx = context;
 58	uint32_t cycles;
 59	while (true) {
 60		struct DSGXEntry entry = { 0 };
 61		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
 62		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
 63		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
 64		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
 65		CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
 66		cycles = _gxCommandCycleBase[entry.command];
 67
 68		switch (entry.command) {
 69		case DS_GX_CMD_SWAP_BUFFERS:
 70			gx->swapBuffers = true;
 71			break;
 72		default:
 73			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]);
 74			break;
 75		}
 76		if (CircleBufferSize(&gx->fifo)) {
 77			if (cycles <= cyclesLate) {
 78				cyclesLate -= cycles;
 79			} else {
 80				break;
 81			}
 82		} else {
 83			cycles = 0;
 84			break;
 85		}
 86	}
 87	DSGXUpdateGXSTAT(gx);
 88	if (cycles) {
 89		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
 90	}
 91}
 92
 93void DSGXInit(struct DSGX* gx) {
 94	CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
 95	gx->fifoEvent.name = "DS GX FIFO";
 96	gx->fifoEvent.priority = 0xC;
 97	gx->fifoEvent.context = gx;
 98	gx->fifoEvent.callback = _fifoRun;
 99}
100
101void DSGXDeinit(struct DSGX* gx) {
102	CircleBufferDeinit(&gx->fifo);
103}
104
105void DSGXReset(struct DSGX* gx) {
106	CircleBufferClear(&gx->fifo);
107	gx->swapBuffers = false;
108}
109
110void DSGXUpdateGXSTAT(struct DSGX* gx) {
111	uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
112	value = DSRegGXSTATIsDoIRQ(value);
113
114	size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
115	// XXX
116	if (gx->swapBuffers) {
117		entries++;
118	}
119	value = DSRegGXSTATSetFIFOEntries(value, entries);
120	value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
121	value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
122
123	if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
124		(DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
125		DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
126	}
127
128	value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
129
130	gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
131}
132
133static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
134	uint32_t cycles = _gxCommandCycleBase[entry.command];
135	if (!cycles) {
136		return;
137	}
138	// TODO: Outstanding parameters
139	if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
140		CircleBufferWrite8(&gx->fifo, entry.command);
141		CircleBufferWrite8(&gx->fifo, entry.params[0]);
142		CircleBufferWrite8(&gx->fifo, entry.params[1]);
143		CircleBufferWrite8(&gx->fifo, entry.params[2]);
144		CircleBufferWrite8(&gx->fifo, entry.params[3]);
145		if (!mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
146			mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
147		}
148	} else {
149		mLOG(DS_GX, STUB, "Unimplemented GX full");
150	}
151}
152
153uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
154	uint16_t oldValue = gx->p->memory.io9[address >> 1];
155	switch (address) {
156	case DS9_REG_DISP3DCNT:
157		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
158		break;
159	case DS9_REG_GXSTAT_LO:
160		value = DSRegGXSTATIsMatrixStackError(value);
161		if (value) {
162			oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
163			oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
164		}
165		value = oldValue;
166		break;
167	case DS9_REG_GXSTAT_HI:
168		value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
169		gx->p->memory.io9[address >> 1] = value;
170		DSGXUpdateGXSTAT(gx);
171		value = gx->p->memory.io9[address >> 1];
172		break;
173	default:
174		if (address < DS9_REG_GXFIFO_00) {
175			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
176		} else if (address < DS9_REG_GXSTAT_LO) {
177			struct DSGXEntry entry = {
178				.command = (address & 0x1FC) >> 2,
179				.params = {
180					value,
181					value >> 8,
182				}
183			};
184			if (entry.command < 0x80) {
185				DSGXWriteFIFO(gx, entry);
186			}
187		} else {
188			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
189		}
190		break;
191	}
192	return value;
193}
194
195uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
196	switch (address) {
197	case DS9_REG_DISP3DCNT:
198		mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
199		break;
200	case DS9_REG_GXSTAT_LO:
201		value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
202		value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
203		break;
204	default:
205		if (address < DS9_REG_GXFIFO_00) {
206			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
207		} else if (address < DS9_REG_GXSTAT_LO) {
208			struct DSGXEntry entry = {
209				.command = (address & 0x1FC) >> 2l,
210				.params = {
211					value,
212					value >> 8,
213					value >> 16,
214					value >> 24
215				}
216			};
217			DSGXWriteFIFO(gx, entry);
218		} else {
219			mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
220		}
221		break;
222	}
223	return value;
224}
225
226void DSGXSwapBuffers(struct DSGX* gx) {
227	mLOG(DS_GX, STUB, "Unimplemented GX swap buffers");
228	gx->swapBuffers = false;
229
230	// TODO
231	DSGXUpdateGXSTAT(gx);
232	if (CircleBufferSize(&gx->fifo)) {
233		mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
234	}
235}