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_IDENTITY:
198 switch (gx->mtxMode) {
199 case 0:
200 DSGXMtxIdentity(&gx->projMatrix);
201 break;
202 case 2:
203 DSGXMtxIdentity(&gx->vecMatrix);
204 // Fall through
205 case 1:
206 DSGXMtxIdentity(&gx->posMatrix);
207 break;
208 case 3:
209 DSGXMtxIdentity(&gx->texMatrix);
210 break;
211 }
212 break;
213 case DS_GX_CMD_MTX_PUSH:
214 switch (gx->mtxMode) {
215 case 0:
216 memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrix));
217 ++projMatrixPointer;
218 break;
219 case 2:
220 memcpy(&gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->vecMatrix, sizeof(gx->vecMatrix));
221 // Fall through
222 case 1:
223 memcpy(&gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->posMatrix, sizeof(gx->posMatrix));
224 ++gx->pvMatrixPointer;
225 break;
226 case 3:
227 mLOG(DS_GX, STUB, "Unimplemented GX MTX_PUSH mode");
228 break;
229 }
230 break;
231 case DS_GX_CMD_MTX_POP: {
232 int8_t offset = entry.params[0];
233 offset <<= 2;
234 offset >>= 2;
235 switch (gx->mtxMode) {
236 case 0:
237 projMatrixPointer -= offset;
238 memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
239 break;
240 case 1:
241 gx->pvMatrixPointer -= offset;
242 memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
243 break;
244 case 2:
245 gx->pvMatrixPointer -= offset;
246 memcpy(&gx->vecMatrix, &gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->vecMatrix));
247 memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
248 break;
249 case 3:
250 mLOG(DS_GX, STUB, "Unimplemented GX MTX_POP mode");
251 break;
252 }
253 break;
254 }
255 case DS_GX_CMD_SWAP_BUFFERS:
256 gx->swapBuffers = true;
257 break;
258 default:
259 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]);
260 break;
261 }
262
263 gxstat = DSRegGXSTATSetPVMatrixStackLevel(gxstat, gx->pvMatrixPointer);
264 gxstat = DSRegGXSTATSetProjMatrixStackLevel(gxstat, projMatrixPointer);
265 gxstat = DSRegGXSTATTestFillMatrixStackError(gxstat, projMatrixPointer || gx->pvMatrixPointer >= 0x1F);
266 gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
267
268 if (cyclesLate >= cycles) {
269 cyclesLate -= cycles;
270 } else {
271 break;
272 }
273 }
274 DSGXUpdateGXSTAT(gx);
275 if (cycles && !gx->swapBuffers) {
276 mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
277 }
278}
279
280void DSGXInit(struct DSGX* gx) {
281 gx->renderer = &dummyRenderer;
282 CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
283 CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
284 gx->fifoEvent.name = "DS GX FIFO";
285 gx->fifoEvent.priority = 0xC;
286 gx->fifoEvent.context = gx;
287 gx->fifoEvent.callback = _fifoRun;
288}
289
290void DSGXDeinit(struct DSGX* gx) {
291 DSGXAssociateRenderer(gx, &dummyRenderer);
292 CircleBufferDeinit(&gx->fifo);
293 CircleBufferDeinit(&gx->pipe);
294}
295
296void DSGXReset(struct DSGX* gx) {
297 CircleBufferClear(&gx->fifo);
298 CircleBufferClear(&gx->pipe);
299 DSGXMtxIdentity(&gx->projMatrix);
300 DSGXMtxIdentity(&gx->texMatrix);
301 DSGXMtxIdentity(&gx->posMatrix);
302 DSGXMtxIdentity(&gx->vecMatrix);
303
304 DSGXMtxIdentity(&gx->projMatrixStack);
305 DSGXMtxIdentity(&gx->texMatrixStack);
306 int i;
307 for (i = 0; i < 32; ++i) {
308 DSGXMtxIdentity(&gx->posMatrixStack[i]);
309 DSGXMtxIdentity(&gx->vecMatrixStack[i]);
310 }
311 gx->swapBuffers = false;
312 gx->bufferIndex = 0;
313 gx->mtxMode = 0;
314 gx->pvMatrixPointer = 0;
315
316 memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
317 memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
318 gx->activeParams = 0;
319}
320
321void DSGXAssociateRenderer(struct DSGX* gx, struct DSGXRenderer* renderer) {
322 gx->renderer->deinit(gx->renderer);
323 gx->renderer = renderer;
324 gx->renderer->init(gx->renderer);
325}
326
327void DSGXUpdateGXSTAT(struct DSGX* gx) {
328 uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
329 value = DSRegGXSTATIsDoIRQ(value);
330
331 size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
332 // XXX
333 if (gx->swapBuffers) {
334 entries++;
335 }
336 value = DSRegGXSTATSetFIFOEntries(value, entries);
337 value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
338 value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
339
340 if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
341 (DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
342 DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
343 }
344
345 value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
346
347 gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
348}
349
350static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
351 gx->outstandingCommand[0] = command;
352 gx->outstandingCommand[1] = command >> 8;
353 gx->outstandingCommand[2] = command >> 16;
354 gx->outstandingCommand[3] = command >> 24;
355 if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
356 gx->outstandingCommand[0] = 0;
357 }
358 if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
359 gx->outstandingCommand[1] = 0;
360 }
361 if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
362 gx->outstandingCommand[2] = 0;
363 }
364 if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
365 gx->outstandingCommand[3] = 0;
366 }
367 gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
368 gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
369 gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
370 gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
371}
372
373static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
374 if (gx->outstandingParams[0]) {
375 entry.command = gx->outstandingCommand[0];
376 --gx->outstandingParams[0];
377 if (!gx->outstandingParams[0]) {
378 // TODO: improve this
379 memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
380 memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
381 gx->outstandingParams[3] = 0;
382 }
383 } else {
384 gx->outstandingCommand[0] = entry.command;
385 gx->outstandingParams[0] = _gxCommandParams[entry.command];
386 if (gx->outstandingParams[0]) {
387 --gx->outstandingParams[0];
388 }
389 }
390 uint32_t cycles = _gxCommandCycleBase[entry.command];
391 if (!cycles) {
392 return;
393 }
394 if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
395 CircleBufferWrite8(&gx->pipe, entry.command);
396 CircleBufferWrite8(&gx->pipe, entry.params[0]);
397 CircleBufferWrite8(&gx->pipe, entry.params[1]);
398 CircleBufferWrite8(&gx->pipe, entry.params[2]);
399 CircleBufferWrite8(&gx->pipe, entry.params[3]);
400 } else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
401 CircleBufferWrite8(&gx->fifo, entry.command);
402 CircleBufferWrite8(&gx->fifo, entry.params[0]);
403 CircleBufferWrite8(&gx->fifo, entry.params[1]);
404 CircleBufferWrite8(&gx->fifo, entry.params[2]);
405 CircleBufferWrite8(&gx->fifo, entry.params[3]);
406 } else {
407 mLOG(DS_GX, STUB, "Unimplemented GX full");
408 }
409 if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
410 mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
411 }
412}
413
414uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
415 uint16_t oldValue = gx->p->memory.io9[address >> 1];
416 switch (address) {
417 case DS9_REG_DISP3DCNT:
418 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
419 break;
420 case DS9_REG_GXSTAT_LO:
421 value = DSRegGXSTATIsMatrixStackError(value);
422 if (value) {
423 oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
424 oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
425 }
426 value = oldValue;
427 break;
428 case DS9_REG_GXSTAT_HI:
429 value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
430 gx->p->memory.io9[address >> 1] = value;
431 DSGXUpdateGXSTAT(gx);
432 value = gx->p->memory.io9[address >> 1];
433 break;
434 default:
435 if (address < DS9_REG_GXFIFO_00) {
436 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
437 } else if (address <= DS9_REG_GXFIFO_1F) {
438 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
439 } else if (address < DS9_REG_GXSTAT_LO) {
440 struct DSGXEntry entry = {
441 .command = (address & 0x1FC) >> 2,
442 .params = {
443 value,
444 value >> 8,
445 }
446 };
447 if (entry.command < DS_GX_CMD_MAX) {
448 DSGXWriteFIFO(gx, entry);
449 }
450 } else {
451 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
452 }
453 break;
454 }
455 return value;
456}
457
458uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
459 switch (address) {
460 case DS9_REG_DISP3DCNT:
461 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
462 break;
463 case DS9_REG_GXSTAT_LO:
464 value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
465 value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
466 break;
467 default:
468 if (address < DS9_REG_GXFIFO_00) {
469 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
470 } else if (address <= DS9_REG_GXFIFO_1F) {
471 if (gx->outstandingParams[0]) {
472 struct DSGXEntry entry = {
473 .command = gx->outstandingCommand[0],
474 .params = {
475 value,
476 value >> 8,
477 value >> 16,
478 value >> 24
479 }
480 };
481 DSGXWriteFIFO(gx, entry);
482 } else {
483 DSGXUnpackCommand(gx, value);
484 }
485 } else if (address < DS9_REG_GXSTAT_LO) {
486 struct DSGXEntry entry = {
487 .command = (address & 0x1FC) >> 2,
488 .params = {
489 value,
490 value >> 8,
491 value >> 16,
492 value >> 24
493 }
494 };
495 DSGXWriteFIFO(gx, entry);
496 } else {
497 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
498 }
499 break;
500 }
501 return value;
502}
503
504void DSGXSwapBuffers(struct DSGX* gx) {
505 mLOG(DS_GX, STUB, "Unimplemented GX swap buffers");
506 gx->swapBuffers = false;
507
508 // TODO
509 DSGXUpdateGXSTAT(gx);
510 if (CircleBufferSize(&gx->fifo)) {
511 mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
512 }
513}
514
515static void DSGXDummyRendererInit(struct DSGXRenderer* renderer) {
516 UNUSED(renderer);
517 // Nothing to do
518}
519
520static void DSGXDummyRendererReset(struct DSGXRenderer* renderer) {
521 UNUSED(renderer);
522 // Nothing to do
523}
524
525static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer) {
526 UNUSED(renderer);
527 // Nothing to do
528}
529
530static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount) {
531 UNUSED(renderer);
532 UNUSED(verts);
533 UNUSED(polys);
534 UNUSED(polyCount);
535 // Nothing to do
536}
537
538static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
539 UNUSED(renderer);
540 UNUSED(y);
541 // Nothing to do
542}
543
544static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, color_t** output) {
545 UNUSED(renderer);
546 UNUSED(y);
547 *output = NULL;
548 // Nothing to do
549}