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}