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", "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 DSGXDummyRendererInvalidateTex(struct DSGXRenderer* renderer, int slot);
20static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort);
21static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y);
22static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, const color_t** output);
23
24static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry);
25
26static bool _boxTestVertex(struct DSGX* gx, struct DSGXVertex* vertex);
27
28enum CSCode {
29 CS_LEFT = 1,
30 CS_RIGHT = 2,
31 CS_BOTTOM = 4,
32 CS_TOP = 8,
33 CS_NEAR = 16,
34 CS_FAR = 32
35};
36
37static const int32_t _gxCommandCycleBase[DS_GX_CMD_MAX] = {
38 [DS_GX_CMD_NOP] = 0,
39 [DS_GX_CMD_MTX_MODE] = 2,
40 [DS_GX_CMD_MTX_PUSH] = 34,
41 [DS_GX_CMD_MTX_POP] = 72,
42 [DS_GX_CMD_MTX_STORE] = 34,
43 [DS_GX_CMD_MTX_RESTORE] = 72,
44 [DS_GX_CMD_MTX_IDENTITY] = 38,
45 [DS_GX_CMD_MTX_LOAD_4x4] = 68,
46 [DS_GX_CMD_MTX_LOAD_4x3] = 60,
47 [DS_GX_CMD_MTX_MULT_4x4] = 70,
48 [DS_GX_CMD_MTX_MULT_4x3] = 62,
49 [DS_GX_CMD_MTX_MULT_3x3] = 56,
50 [DS_GX_CMD_MTX_SCALE] = 44,
51 [DS_GX_CMD_MTX_TRANS] = 44,
52 [DS_GX_CMD_COLOR] = 2,
53 [DS_GX_CMD_NORMAL] = 18,
54 [DS_GX_CMD_TEXCOORD] = 2,
55 [DS_GX_CMD_VTX_16] = 18,
56 [DS_GX_CMD_VTX_10] = 16,
57 [DS_GX_CMD_VTX_XY] = 16,
58 [DS_GX_CMD_VTX_XZ] = 16,
59 [DS_GX_CMD_VTX_YZ] = 16,
60 [DS_GX_CMD_VTX_DIFF] = 16,
61 [DS_GX_CMD_POLYGON_ATTR] = 2,
62 [DS_GX_CMD_TEXIMAGE_PARAM] = 2,
63 [DS_GX_CMD_PLTT_BASE] = 2,
64 [DS_GX_CMD_DIF_AMB] = 8,
65 [DS_GX_CMD_SPE_EMI] = 8,
66 [DS_GX_CMD_LIGHT_VECTOR] = 12,
67 [DS_GX_CMD_LIGHT_COLOR] = 2,
68 [DS_GX_CMD_SHININESS] = 64,
69 [DS_GX_CMD_BEGIN_VTXS] = 2,
70 [DS_GX_CMD_END_VTXS] = 2,
71 [DS_GX_CMD_SWAP_BUFFERS] = 784,
72 [DS_GX_CMD_VIEWPORT] = 2,
73 [DS_GX_CMD_BOX_TEST] = 206,
74 [DS_GX_CMD_POS_TEST] = 18,
75 [DS_GX_CMD_VEC_TEST] = 10,
76};
77
78static const int32_t _gxCommandParams[DS_GX_CMD_MAX] = {
79 [DS_GX_CMD_MTX_MODE] = 1,
80 [DS_GX_CMD_MTX_POP] = 1,
81 [DS_GX_CMD_MTX_STORE] = 1,
82 [DS_GX_CMD_MTX_RESTORE] = 1,
83 [DS_GX_CMD_MTX_LOAD_4x4] = 16,
84 [DS_GX_CMD_MTX_LOAD_4x3] = 12,
85 [DS_GX_CMD_MTX_MULT_4x4] = 16,
86 [DS_GX_CMD_MTX_MULT_4x3] = 12,
87 [DS_GX_CMD_MTX_MULT_3x3] = 9,
88 [DS_GX_CMD_MTX_SCALE] = 3,
89 [DS_GX_CMD_MTX_TRANS] = 3,
90 [DS_GX_CMD_COLOR] = 1,
91 [DS_GX_CMD_NORMAL] = 1,
92 [DS_GX_CMD_TEXCOORD] = 1,
93 [DS_GX_CMD_VTX_16] = 2,
94 [DS_GX_CMD_VTX_10] = 1,
95 [DS_GX_CMD_VTX_XY] = 1,
96 [DS_GX_CMD_VTX_XZ] = 1,
97 [DS_GX_CMD_VTX_YZ] = 1,
98 [DS_GX_CMD_VTX_DIFF] = 1,
99 [DS_GX_CMD_POLYGON_ATTR] = 1,
100 [DS_GX_CMD_TEXIMAGE_PARAM] = 1,
101 [DS_GX_CMD_PLTT_BASE] = 1,
102 [DS_GX_CMD_DIF_AMB] = 1,
103 [DS_GX_CMD_SPE_EMI] = 1,
104 [DS_GX_CMD_LIGHT_VECTOR] = 1,
105 [DS_GX_CMD_LIGHT_COLOR] = 1,
106 [DS_GX_CMD_SHININESS] = 32,
107 [DS_GX_CMD_BEGIN_VTXS] = 1,
108 [DS_GX_CMD_SWAP_BUFFERS] = 1,
109 [DS_GX_CMD_VIEWPORT] = 1,
110 [DS_GX_CMD_BOX_TEST] = 3,
111 [DS_GX_CMD_POS_TEST] = 2,
112 [DS_GX_CMD_VEC_TEST] = 1,
113};
114
115static struct DSGXRenderer dummyRenderer = {
116 .init = DSGXDummyRendererInit,
117 .reset = DSGXDummyRendererReset,
118 .deinit = DSGXDummyRendererDeinit,
119 .invalidateTex = DSGXDummyRendererInvalidateTex,
120 .setRAM = DSGXDummyRendererSetRAM,
121 .drawScanline = DSGXDummyRendererDrawScanline,
122 .getScanline = DSGXDummyRendererGetScanline,
123};
124
125static void _pullPipe(struct DSGX* gx) {
126 if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
127 struct DSGXEntry entry = { 0 };
128 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
129 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
130 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
131 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
132 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
133 CircleBufferWrite8(&gx->pipe, entry.command);
134 CircleBufferWrite8(&gx->pipe, entry.params[0]);
135 CircleBufferWrite8(&gx->pipe, entry.params[1]);
136 CircleBufferWrite8(&gx->pipe, entry.params[2]);
137 CircleBufferWrite8(&gx->pipe, entry.params[3]);
138 }
139 if (CircleBufferSize(&gx->fifo) >= sizeof(struct DSGXEntry)) {
140 struct DSGXEntry entry = { 0 };
141 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command);
142 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]);
143 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]);
144 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]);
145 CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]);
146 CircleBufferWrite8(&gx->pipe, entry.command);
147 CircleBufferWrite8(&gx->pipe, entry.params[0]);
148 CircleBufferWrite8(&gx->pipe, entry.params[1]);
149 CircleBufferWrite8(&gx->pipe, entry.params[2]);
150 CircleBufferWrite8(&gx->pipe, entry.params[3]);
151 }
152}
153
154static void _updateClipMatrix(struct DSGX* gx) {
155 DSGXMtxMultiply(&gx->clipMatrix, &gx->posMatrix, &gx->projMatrix);
156 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_00 >> 1] = gx->clipMatrix.m[0];
157 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_01 >> 1] = gx->clipMatrix.m[0] >> 16;
158 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_02 >> 1] = gx->clipMatrix.m[1];
159 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_03 >> 1] = gx->clipMatrix.m[1] >> 16;
160 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_04 >> 1] = gx->clipMatrix.m[2];
161 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_05 >> 1] = gx->clipMatrix.m[2] >> 16;
162 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_06 >> 1] = gx->clipMatrix.m[3];
163 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_07 >> 1] = gx->clipMatrix.m[3] >> 16;
164 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_08 >> 1] = gx->clipMatrix.m[4];
165 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_09 >> 1] = gx->clipMatrix.m[4] >> 16;
166 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0A >> 1] = gx->clipMatrix.m[5];
167 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0B >> 1] = gx->clipMatrix.m[5] >> 16;
168 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0C >> 1] = gx->clipMatrix.m[6];
169 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0D >> 1] = gx->clipMatrix.m[6] >> 16;
170 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0E >> 1] = gx->clipMatrix.m[7];
171 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_0F >> 1] = gx->clipMatrix.m[7] >> 16;
172 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_10 >> 1] = gx->clipMatrix.m[8];
173 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_11 >> 1] = gx->clipMatrix.m[8] >> 16;
174 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_12 >> 1] = gx->clipMatrix.m[9];
175 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_13 >> 1] = gx->clipMatrix.m[9] >> 16;
176 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_14 >> 1] = gx->clipMatrix.m[10];
177 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_15 >> 1] = gx->clipMatrix.m[10] >> 16;
178 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_16 >> 1] = gx->clipMatrix.m[11];
179 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_17 >> 1] = gx->clipMatrix.m[11] >> 16;
180 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_18 >> 1] = gx->clipMatrix.m[12];
181 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_19 >> 1] = gx->clipMatrix.m[12] >> 16;
182 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1A >> 1] = gx->clipMatrix.m[13];
183 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1B >> 1] = gx->clipMatrix.m[13] >> 16;
184 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1C >> 1] = gx->clipMatrix.m[14];
185 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1D >> 1] = gx->clipMatrix.m[14] >> 16;
186 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1E >> 1] = gx->clipMatrix.m[15];
187 gx->p->memory.io9[DS9_REG_CLIPMTX_RESULT_1F >> 1] = gx->clipMatrix.m[15] >> 16;
188}
189
190static inline int32_t _lerp(int32_t x0, int32_t x1, int32_t w0, int32_t w1, int32_t q) {
191 int64_t x = (x1 * (int64_t) w1 - x0 * (int64_t) w0) >> 16;
192 x *= q;
193 x >>= 15;
194 x += x0 * (int64_t) w0;
195 return x >> 16;
196}
197
198static int _cohenSutherlandCode(const struct DSGX* gx, const struct DSGXVertex* v) {
199 int code = 0;
200 if (v->vx < gx->viewportX1 << 12) {
201 code |= CS_LEFT;
202 } else if (v->vx > gx->viewportX2 << 12) {
203 code |= CS_RIGHT;
204 }
205 if (v->vy < gx->viewportY1 << 12) {
206 code |= CS_BOTTOM;
207 } else if (v->vy > gx->viewportY2 << 12) {
208 code |= CS_TOP;
209 }
210 if (v->vz < -v->vw) {
211 code |= CS_NEAR;
212 } else if (v->vz > v->vw) {
213 code |= CS_FAR;
214 }
215 return code;
216}
217
218static void _lerpVertex(const struct DSGXVertex* v0, const struct DSGXVertex* v1, struct DSGXVertex* out, int32_t xw) {
219 int32_t w0 = v0->vw;
220 int32_t w1 = v1->vw;
221 int64_t w = (((int64_t) (w1 - w0) * xw) >> 32) + w0;
222 int64_t wRecip;// = 0x1000000000000LL / w;
223 out->vw = w;
224 // XXX: Disable perspective correction until I figure out how to fix it
225 wRecip = 0x100000000;
226 w0 = 0x10000;
227 w1 = 0x10000;
228
229 out->color = v0->color; // TODO
230
231 int32_t vx = _lerp(v0->vx, v1->vx, w0, w1, xw);
232 int32_t vy = _lerp(v0->vy, v1->vy, w0, w1, xw);
233 int32_t vz = _lerp(v0->vz, v1->vz, w0, w1, xw);
234 out->vx = (vx * wRecip) >> 32;
235 out->vy = (vy * wRecip) >> 32;
236 out->vz = (vz * wRecip) >> 32;
237
238 int32_t s = _lerp(v0->vs, v1->vs, w0, w1, xw);
239 int32_t t = _lerp(v0->vt, v1->vt, w0, w1, xw);
240 out->vs = (s * wRecip) >> 32;
241 out->vt = (t * wRecip) >> 32;
242}
243
244static bool _lerpVertexX(const struct DSGXVertex* v0, const struct DSGXVertex* v1, struct DSGXVertex* out, int32_t x) {
245 int64_t width = v1->vx - v0->vx;
246 int64_t xw = x - v0->vx;
247 if (!width) {
248 return false;
249 }
250 if (xw < 0) {
251 return false;
252 } else if (xw > width) {
253 return false;
254 }
255
256 xw *= 0x80000000LL;
257 xw /= width;
258
259 _lerpVertex(v0, v1, out, xw);
260 return true;
261}
262
263static bool _lerpVertexY(const struct DSGXVertex* v0, const struct DSGXVertex* v1, struct DSGXVertex* out, int32_t y) {
264 int64_t height = v1->vy - v0->vy;
265 int64_t yw = y - v0->vy;
266 if (!height) {
267 return false;
268 }
269 if (yw < 0) {
270 return false;
271 } else if (yw > height) {
272 return false;
273 }
274
275 yw *= 0x80000000LL;
276 yw /= height;
277
278 _lerpVertex(v0, v1, out, yw);
279 return true;
280}
281
282static bool _clipPolygon(struct DSGX* gx, struct DSGXPolygon* poly) {
283 int nOffscreen = 0;
284 int offscreenVerts[8] = { 0, 0, 0, 0 };
285 unsigned oldVerts[4];
286 int v;
287 // Collect offscreen vertices
288 for (v = 0; v < poly->verts; ++v) {
289 offscreenVerts[v] = _cohenSutherlandCode(gx, &gx->pendingVertices[poly->vertIds[v]]);
290 oldVerts[v] = poly->vertIds[v];
291 if (offscreenVerts[v]) {
292 ++nOffscreen;
293 }
294 }
295
296 struct DSGXVertex* vbuf = gx->vertexBuffer[gx->bufferIndex];
297
298 if (!nOffscreen) {
299 for (v = 0; v < poly->verts; ++v) {
300 if (gx->vertexIndex == DS_GX_VERTEX_BUFFER_SIZE) {
301 return false;
302 }
303 int vertexId = oldVerts[v];
304 if (gx->pendingVertexIds[vertexId] >= 0) {
305 poly->vertIds[v] = gx->pendingVertexIds[vertexId];
306 } else {
307 vbuf[gx->vertexIndex] = gx->pendingVertices[vertexId];
308 gx->pendingVertexIds[vertexId] = gx->vertexIndex;
309 poly->vertIds[v] = gx->vertexIndex;
310 ++gx->vertexIndex;
311 }
312 }
313 return true;
314 }
315
316 struct DSGXVertex inList[8];
317 struct DSGXVertex outList[8];
318 int outOffscreenVerts[8] = { 0, 0, 0, 0 };
319 for (v = 0; v < poly->verts; ++v) {
320 inList[v] = gx->pendingVertices[oldVerts[v]];
321 }
322
323 int newV;
324
325 // Clip near
326 newV = 0;
327 for (v = 0; v < poly->verts; ++v) {
328 if (!(offscreenVerts[v] & CS_NEAR)) {
329 outList[newV] = inList[v];
330 outOffscreenVerts[newV] = offscreenVerts[v];
331 ++newV;
332 } else {
333 // TODO
334 }
335 }
336 poly->verts = newV;
337 memcpy(inList, outList, newV * sizeof(*inList));
338 memcpy(offscreenVerts, outOffscreenVerts, newV * sizeof(*offscreenVerts));
339
340 // Clip far
341 newV = 0;
342 for (v = 0; v < poly->verts; ++v) {
343 if (!(offscreenVerts[v] & CS_FAR)) {
344 outList[newV] = inList[v];
345 outOffscreenVerts[newV] = offscreenVerts[v];
346 ++newV;
347 } else {
348 // TODO
349 }
350 }
351 poly->verts = newV;
352 memcpy(inList, outList, newV * sizeof(*inList));
353 memcpy(offscreenVerts, outOffscreenVerts, newV * sizeof(*offscreenVerts));
354
355 // Clip left
356 newV = 0;
357 for (v = 0; v < poly->verts; ++v) {
358 if (!(offscreenVerts[v] & CS_LEFT)) {
359 outList[newV] = inList[v];
360 outOffscreenVerts[newV] = offscreenVerts[v];
361 ++newV;
362 } else {
363 struct DSGXVertex* in = &inList[v];
364 struct DSGXVertex* in2;
365 struct DSGXVertex* out;
366
367 out = &outList[newV];
368 if (v > 0) {
369 in2 = &inList[v - 1];
370 } else {
371 in2 = &inList[poly->verts - 1];
372 }
373 if (_lerpVertexX(in, in2, out, gx->viewportX1 << 12)) {
374 outOffscreenVerts[newV] = _cohenSutherlandCode(gx, out);
375 ++newV;
376 }
377
378 out = &outList[newV];
379 if (v < poly->verts - 1) {
380 in2 = &inList[v + 1];
381 } else {
382 in2 = &inList[0];
383 }
384 if (_lerpVertexX(in, in2, out, gx->viewportX1 << 12)) {
385 outOffscreenVerts[newV] = _cohenSutherlandCode(gx, out);
386 ++newV;
387 }
388 }
389 }
390 poly->verts = newV;
391 memcpy(inList, outList, newV * sizeof(*inList));
392 memcpy(offscreenVerts, outOffscreenVerts, newV * sizeof(*offscreenVerts));
393
394 // Clip right
395 newV = 0;
396 for (v = 0; v < poly->verts; ++v) {
397 if (!(offscreenVerts[v] & CS_RIGHT)) {
398 outList[newV] = inList[v];
399 outOffscreenVerts[newV] = offscreenVerts[v];
400 ++newV;
401 } else {
402 struct DSGXVertex* in = &inList[v];
403 struct DSGXVertex* in2;
404 struct DSGXVertex* out;
405
406 out = &outList[newV];
407 if (v > 0) {
408 in2 = &inList[v - 1];
409 } else {
410 in2 = &inList[poly->verts - 1];
411 }
412 if (_lerpVertexX(in2, in, out, gx->viewportX2 << 12)) {
413 outOffscreenVerts[newV] = _cohenSutherlandCode(gx, out);
414 ++newV;
415 }
416
417 out = &outList[newV];
418 if (v < poly->verts - 1) {
419 in2 = &inList[v + 1];
420 } else {
421 in2 = &inList[0];
422 }
423 if (_lerpVertexX(in2, in, out, gx->viewportX2 << 12)) {
424 outOffscreenVerts[newV] = _cohenSutherlandCode(gx, out);
425 ++newV;
426 }
427 }
428 }
429 poly->verts = newV;
430 memcpy(inList, outList, newV * sizeof(*inList));
431 memcpy(offscreenVerts, outOffscreenVerts, newV * sizeof(*offscreenVerts));
432
433 // Clip bottom
434 newV = 0;
435 for (v = 0; v < poly->verts; ++v) {
436 if (!(offscreenVerts[v] & CS_BOTTOM)) {
437 outList[newV] = inList[v];
438 outOffscreenVerts[newV] = offscreenVerts[v];
439 ++newV;
440 } else {
441 struct DSGXVertex* in = &inList[v];
442 struct DSGXVertex* in2;
443 struct DSGXVertex* out;
444
445 out = &outList[newV];
446 if (v > 0) {
447 in2 = &inList[v - 1];
448 } else {
449 in2 = &inList[poly->verts - 1];
450 }
451 if (_lerpVertexY(in, in2, out, gx->viewportY1 << 12)) {
452 outOffscreenVerts[newV] = _cohenSutherlandCode(gx, out);
453 ++newV;
454 }
455
456 out = &outList[newV];
457 if (v < poly->verts - 1) {
458 in2 = &inList[v + 1];
459 } else {
460 in2 = &inList[0];
461 }
462 if (_lerpVertexY(in, in2, out, gx->viewportY1 << 12)) {
463 outOffscreenVerts[newV] = _cohenSutherlandCode(gx, out);
464 ++newV;
465 }
466 }
467 }
468 poly->verts = newV;
469 memcpy(inList, outList, newV * sizeof(*inList));
470 memcpy(offscreenVerts, outOffscreenVerts, newV * sizeof(*offscreenVerts));
471
472 // Clip top
473 newV = 0;
474 for (v = 0; v < poly->verts; ++v) {
475 if (!(offscreenVerts[v] & CS_TOP)) {
476 outList[newV] = inList[v];
477 outOffscreenVerts[newV] = offscreenVerts[v];
478 ++newV;
479 } else {
480 struct DSGXVertex* in = &inList[v];
481 struct DSGXVertex* in2;
482 struct DSGXVertex* out;
483
484 out = &outList[newV];
485 if (v > 0) {
486 in2 = &inList[v - 1];
487 } else {
488 in2 = &inList[poly->verts - 1];
489 }
490 if (_lerpVertexY(in2, in, out, gx->viewportY2 << 12)) {
491 outOffscreenVerts[newV] = _cohenSutherlandCode(gx, out);
492 ++newV;
493 }
494
495 out = &outList[newV];
496 if (v < poly->verts - 1) {
497 in2 = &inList[v + 1];
498 } else {
499 in2 = &inList[0];
500 }
501 if (_lerpVertexY(in2, in, out, gx->viewportY2 << 12)) {
502 outOffscreenVerts[newV] = _cohenSutherlandCode(gx, out);
503 ++newV;
504 }
505 }
506 }
507 poly->verts = newV;
508 memcpy(inList, outList, newV * sizeof(*inList));
509 memcpy(offscreenVerts, outOffscreenVerts, newV * sizeof(*offscreenVerts));
510
511 for (v = 0; v < poly->verts; ++v) {
512 if (gx->vertexIndex == DS_GX_VERTEX_BUFFER_SIZE) {
513 return false;
514 }
515 // TODO: merge strips
516 vbuf[gx->vertexIndex] = inList[v];
517 poly->vertIds[v] = gx->vertexIndex;
518 ++gx->vertexIndex;
519 }
520
521 return newV > 2;
522}
523
524static int32_t _dotViewport(struct DSGXVertex* vertex, int32_t* col) {
525 int64_t a;
526 int64_t b;
527 int64_t sum;
528 a = col[0];
529 b = vertex->x;
530 sum = a * b;
531 a = col[4];
532 b = vertex->y;
533 sum += a * b;
534 a = col[8];
535 b = vertex->z;
536 sum += a * b;
537 a = col[12];
538 b = MTX_ONE;
539 sum += a * b;
540 return sum >> 8LL;
541}
542
543static int16_t _dotTexture(struct DSGXVertex* vertex, int mode, int32_t* col) {
544 int64_t a;
545 int64_t b;
546 int64_t sum;
547 switch (mode) {
548 case 1:
549 a = col[0];
550 b = vertex->s << 8;
551 sum = a * b;
552 a = col[4];
553 b = vertex->t << 8;
554 sum += a * b;
555 a = col[8];
556 b = MTX_ONE >> 4;
557 sum += a * b;
558 a = col[12];
559 b = MTX_ONE >> 4;
560 sum += a * b;
561 break;
562 case 2:
563 return 0;
564 case 3:
565 a = col[0];
566 b = vertex->vx << 8;
567 sum = a * b;
568 a = col[4];
569 b = vertex->vy << 8;
570 sum += a * b;
571 a = col[8];
572 b = vertex->vz << 8;
573 sum += a * b;
574 a = col[12];
575 b = MTX_ONE;
576 sum += a * b;
577 }
578 return sum >> 20;
579}
580
581static void _emitVertex(struct DSGX* gx, uint16_t x, uint16_t y, uint16_t z) {
582 if (gx->vertexMode < 0 || gx->vertexIndex == DS_GX_VERTEX_BUFFER_SIZE || gx->polygonIndex == DS_GX_POLYGON_BUFFER_SIZE) {
583 return;
584 }
585 gx->currentVertex.x = x;
586 gx->currentVertex.y = y;
587 gx->currentVertex.z = z;
588 gx->currentVertex.vx = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[0]);
589 gx->currentVertex.vy = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[1]);
590 gx->currentVertex.vz = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[2]);
591 gx->currentVertex.vw = _dotViewport(&gx->currentVertex, &gx->clipMatrix.m[3]);
592
593 // TODO: What to do if w is 0?
594
595 gx->currentVertex.vx = (gx->currentVertex.vx + gx->currentVertex.vw) * (int64_t) (gx->viewportWidth << 12) / (gx->currentVertex.vw * 2) + (gx->viewportX1 << 12);
596 gx->currentVertex.vy = (gx->currentVertex.vy + gx->currentVertex.vw) * (int64_t) (gx->viewportHeight << 12) / (gx->currentVertex.vw * 2) + (gx->viewportY1 << 12);
597 //gx->currentVertex.vw = 0x40000000 / gx->currentVertex.vw;
598
599 if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) > 0) {
600 int32_t m12 = gx->texMatrix.m[12];
601 int32_t m13 = gx->texMatrix.m[13];
602 if (DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams) > 1) {
603 gx->texMatrix.m[12] = gx->currentVertex.vs;
604 gx->texMatrix.m[13] = gx->currentVertex.vt;
605 }
606 gx->currentVertex.vs = _dotTexture(&gx->currentVertex, DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams), &gx->texMatrix.m[0]);
607 gx->currentVertex.vt = _dotTexture(&gx->currentVertex, DSGXTexParamsGetCoordTfMode(gx->currentPoly.texParams), &gx->texMatrix.m[1]);
608 gx->texMatrix.m[12] = m12;
609 gx->texMatrix.m[13] = m13;
610 } else {
611 gx->currentVertex.vs = gx->currentVertex.s;
612 gx->currentVertex.vt = gx->currentVertex.t;
613 }
614
615 gx->pendingVertices[gx->pendingVertexIndex] = gx->currentVertex;
616 gx->currentPoly.vertIds[gx->currentPoly.verts] = gx->pendingVertexIndex;
617 gx->pendingVertexIndex = (gx->pendingVertexIndex + 1) & 3;
618
619 ++gx->currentPoly.verts;
620 int totalVertices;
621 switch (gx->vertexMode) {
622 case 0:
623 case 2:
624 totalVertices = 3;
625 break;
626 case 1:
627 case 3:
628 totalVertices = 4;
629 break;
630 }
631 if (gx->currentPoly.verts == totalVertices) {
632 struct DSGXPolygon* pbuf = gx->polygonBuffer[gx->bufferIndex];
633
634 pbuf[gx->polygonIndex] = gx->currentPoly;
635 switch (gx->vertexMode) {
636 case 0:
637 case 1:
638 gx->currentPoly.verts = 0;
639 break;
640 case 2:
641 gx->currentPoly.vertIds[0] = gx->currentPoly.vertIds[1];
642 gx->currentPoly.vertIds[1] = gx->currentPoly.vertIds[2];
643 gx->currentPoly.verts = 2;
644 break;
645 case 3:
646 gx->currentPoly.vertIds[0] = gx->currentPoly.vertIds[2];
647 gx->currentPoly.vertIds[1] = gx->currentPoly.vertIds[3];
648 // Ensure quads don't cross over
649 pbuf[gx->polygonIndex].vertIds[2] = gx->currentPoly.vertIds[3];
650 pbuf[gx->polygonIndex].vertIds[3] = gx->currentPoly.vertIds[2];
651 gx->currentPoly.verts = 2;
652 break;
653 }
654
655 if (_clipPolygon(gx, &pbuf[gx->polygonIndex])) {
656 ++gx->polygonIndex;
657 }
658 if (gx->vertexMode < 2) {
659 memset(gx->pendingVertexIds, -1, sizeof(gx->pendingVertexIds));
660 } else {
661 gx->pendingVertexIds[gx->pendingVertexIndex] = -1;
662 gx->pendingVertexIds[(gx->pendingVertexIndex + 1) & 3] = -1;
663 }
664 }
665}
666
667static void _flushOutstanding(struct DSGX* gx) {
668 if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
669 gx->p->cpuBlocked &= ~DS_CPU_BLOCK_GX;
670 DSGXWriteFIFO(gx, gx->outstandingEntry);
671 gx->outstandingEntry.command = 0;
672 }
673 while (gx->outstandingCommand[0] && !gx->outstandingParams[0]) {
674 DSGXWriteFIFO(gx, (struct DSGXEntry) { 0 });
675 if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(struct DSGXEntry))) {
676 return;
677 }
678 }
679}
680
681static bool _boxTestVertex(struct DSGX* gx, struct DSGXVertex* vertex) {
682 int32_t vx = _dotViewport(vertex, &gx->clipMatrix.m[0]);
683 int32_t vy = _dotViewport(vertex, &gx->clipMatrix.m[1]);
684 int32_t vz = _dotViewport(vertex, &gx->clipMatrix.m[2]);
685 int32_t vw = _dotViewport(vertex, &gx->clipMatrix.m[3]);
686
687 vx = (vx + vw) * (int64_t) (gx->viewportWidth << 12) / (vw * 2) + (gx->viewportX1 << 12);
688 vy = (vy + vw) * (int64_t) (gx->viewportHeight << 12) / (vw * 2) + (gx->viewportY1 << 12);
689 vx >>= 12;
690 vy >>= 12;
691
692 if (vx < gx->viewportX1) {
693 return false;
694 }
695 if (vx > gx->viewportX2) {
696 return false;
697 }
698 if (vy < gx->viewportY1) {
699 return false;
700 }
701 if (vy > gx->viewportY2) {
702 return false;
703 }
704 if (vz < -vw) {
705 return false;
706 }
707 if (vz > vw) {
708 return false;
709 }
710 // TODO: depth clipping
711 return true;
712}
713
714static bool _boxTest(struct DSGX* gx) {
715 int16_t x = gx->activeEntries[0].params[0];
716 x |= gx->activeEntries[0].params[1] << 8;
717 int16_t y = gx->activeEntries[0].params[2];
718 y |= gx->activeEntries[0].params[3] << 8;
719 int16_t z = gx->activeEntries[1].params[0];
720 z |= gx->activeEntries[1].params[1] << 8;
721 int16_t w = gx->activeEntries[1].params[2];
722 w |= gx->activeEntries[1].params[3] << 8;
723 int16_t h = gx->activeEntries[2].params[0];
724 h |= gx->activeEntries[2].params[1] << 8;
725 int16_t d = gx->activeEntries[2].params[2];
726 d |= gx->activeEntries[2].params[3] << 8;
727
728 struct DSGXVertex vertex = {
729 .x = x,
730 .y = y,
731 .z = z
732 };
733 if (_boxTestVertex(gx, &vertex)) {
734 return true;
735 }
736
737 vertex.x += w;
738 if (_boxTestVertex(gx, &vertex)) {
739 return true;
740 }
741
742 vertex.x = x;
743 vertex.y += h;
744 if (_boxTestVertex(gx, &vertex)) {
745 return true;
746 }
747
748 vertex.x += w;
749 if (_boxTestVertex(gx, &vertex)) {
750 return true;
751 }
752
753 vertex.x = x;
754 vertex.y = y;
755 vertex.z += d;
756 if (_boxTestVertex(gx, &vertex)) {
757 return true;
758 }
759
760 vertex.x += w;
761 if (_boxTestVertex(gx, &vertex)) {
762 return true;
763 }
764
765 vertex.x = x;
766 vertex.y += h;
767 if (_boxTestVertex(gx, &vertex)) {
768 return true;
769 }
770
771 vertex.x += w;
772 if (_boxTestVertex(gx, &vertex)) {
773 return true;
774 }
775
776 return false;
777}
778
779static void _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) {
780 struct DSGX* gx = context;
781 uint32_t cycles;
782 bool first = true;
783 while (!gx->swapBuffers) {
784 if (CircleBufferSize(&gx->pipe) <= 2 * sizeof(struct DSGXEntry)) {
785 _pullPipe(gx);
786 }
787
788 if (!CircleBufferSize(&gx->pipe)) {
789 cycles = 0;
790 break;
791 }
792
793 DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
794 int projMatrixPointer = DSRegGXSTATGetProjMatrixStackLevel(gxstat);
795
796 struct DSGXEntry entry = { 0 };
797 CircleBufferDump(&gx->pipe, (int8_t*) &entry.command, 1);
798 cycles = _gxCommandCycleBase[entry.command];
799
800 if (first) {
801 first = false;
802 } else if (!gx->activeParams && cycles > cyclesLate) {
803 break;
804 }
805 CircleBufferRead8(&gx->pipe, (int8_t*) &entry.command);
806 CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[0]);
807 CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[1]);
808 CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[2]);
809 CircleBufferRead8(&gx->pipe, (int8_t*) &entry.params[3]);
810
811 if (gx->activeParams) {
812 int index = _gxCommandParams[entry.command] - gx->activeParams;
813 gx->activeEntries[index] = entry;
814 --gx->activeParams;
815 } else {
816 gx->activeParams = _gxCommandParams[entry.command];
817 if (gx->activeParams) {
818 --gx->activeParams;
819 }
820 if (gx->activeParams) {
821 gx->activeEntries[0] = entry;
822 }
823 }
824
825 if (gx->activeParams) {
826 continue;
827 }
828
829 switch (entry.command) {
830 case DS_GX_CMD_MTX_MODE:
831 if (entry.params[0] < 4) {
832 gx->mtxMode = entry.params[0];
833 } else {
834 mLOG(DS_GX, GAME_ERROR, "Invalid GX MTX_MODE %02X", entry.params[0]);
835 }
836 break;
837 case DS_GX_CMD_MTX_PUSH:
838 switch (gx->mtxMode) {
839 case 0:
840 memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrix));
841 ++projMatrixPointer;
842 break;
843 case 2:
844 memcpy(&gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->vecMatrix, sizeof(gx->vecMatrix));
845 // Fall through
846 case 1:
847 memcpy(&gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], &gx->posMatrix, sizeof(gx->posMatrix));
848 ++gx->pvMatrixPointer;
849 break;
850 case 3:
851 mLOG(DS_GX, STUB, "Unimplemented GX MTX_PUSH mode");
852 break;
853 }
854 break;
855 case DS_GX_CMD_MTX_POP: {
856 int8_t offset = entry.params[0];
857 offset <<= 2;
858 offset >>= 2;
859 switch (gx->mtxMode) {
860 case 0:
861 projMatrixPointer -= offset;
862 memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
863 break;
864 case 1:
865 gx->pvMatrixPointer -= offset;
866 memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
867 break;
868 case 2:
869 gx->pvMatrixPointer -= offset;
870 memcpy(&gx->vecMatrix, &gx->vecMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->vecMatrix));
871 memcpy(&gx->posMatrix, &gx->posMatrixStack[gx->pvMatrixPointer & 0x1F], sizeof(gx->posMatrix));
872 break;
873 case 3:
874 mLOG(DS_GX, STUB, "Unimplemented GX MTX_POP mode");
875 break;
876 }
877 _updateClipMatrix(gx);
878 break;
879 }
880 case DS_GX_CMD_MTX_STORE: {
881 int8_t offset = entry.params[0] & 0x1F;
882 // TODO: overflow
883 switch (gx->mtxMode) {
884 case 0:
885 memcpy(&gx->projMatrixStack, &gx->projMatrix, sizeof(gx->projMatrixStack));
886 break;
887 case 2:
888 memcpy(&gx->vecMatrixStack[offset], &gx->vecMatrix, sizeof(gx->vecMatrix));
889 // Fall through
890 case 1:
891 memcpy(&gx->posMatrixStack[offset], &gx->posMatrix, sizeof(gx->posMatrix));
892 break;
893 case 3:
894 mLOG(DS_GX, STUB, "Unimplemented GX MTX_STORE mode");
895 break;
896 }
897 break;
898 }
899 case DS_GX_CMD_MTX_RESTORE: {
900 int8_t offset = entry.params[0] & 0x1F;
901 // TODO: overflow
902 switch (gx->mtxMode) {
903 case 0:
904 memcpy(&gx->projMatrix, &gx->projMatrixStack, sizeof(gx->projMatrix));
905 break;
906 case 2:
907 memcpy(&gx->vecMatrix, &gx->vecMatrixStack[offset], sizeof(gx->vecMatrix));
908 // Fall through
909 case 1:
910 memcpy(&gx->posMatrix, &gx->posMatrixStack[offset], sizeof(gx->posMatrix));
911 break;
912 case 3:
913 mLOG(DS_GX, STUB, "Unimplemented GX MTX_RESTORE mode");
914 break;
915 }
916 _updateClipMatrix(gx);
917 break;
918 }
919 case DS_GX_CMD_MTX_IDENTITY:
920 switch (gx->mtxMode) {
921 case 0:
922 DSGXMtxIdentity(&gx->projMatrix);
923 break;
924 case 2:
925 DSGXMtxIdentity(&gx->vecMatrix);
926 // Fall through
927 case 1:
928 DSGXMtxIdentity(&gx->posMatrix);
929 break;
930 case 3:
931 DSGXMtxIdentity(&gx->texMatrix);
932 break;
933 }
934 _updateClipMatrix(gx);
935 break;
936 case DS_GX_CMD_MTX_LOAD_4x4: {
937 struct DSGXMatrix m;
938 int i;
939 for (i = 0; i < 16; ++i) {
940 m.m[i] = gx->activeEntries[i].params[0];
941 m.m[i] |= gx->activeEntries[i].params[1] << 8;
942 m.m[i] |= gx->activeEntries[i].params[2] << 16;
943 m.m[i] |= gx->activeEntries[i].params[3] << 24;
944 }
945 switch (gx->mtxMode) {
946 case 0:
947 memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
948 break;
949 case 2:
950 memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
951 // Fall through
952 case 1:
953 memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
954 break;
955 case 3:
956 memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
957 break;
958 }
959 _updateClipMatrix(gx);
960 break;
961 }
962 case DS_GX_CMD_MTX_LOAD_4x3: {
963 struct DSGXMatrix m;
964 int i, j;
965 for (j = 0; j < 4; ++j) {
966 for (i = 0; i < 3; ++i) {
967 m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
968 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
969 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
970 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
971 }
972 m.m[j * 4 + 3] = 0;
973 }
974 m.m[15] = MTX_ONE;
975 switch (gx->mtxMode) {
976 case 0:
977 memcpy(&gx->projMatrix, &m, sizeof(gx->projMatrix));
978 break;
979 case 2:
980 memcpy(&gx->vecMatrix, &m, sizeof(gx->vecMatrix));
981 // Fall through
982 case 1:
983 memcpy(&gx->posMatrix, &m, sizeof(gx->posMatrix));
984 break;
985 case 3:
986 memcpy(&gx->texMatrix, &m, sizeof(gx->texMatrix));
987 break;
988 }
989 _updateClipMatrix(gx);
990 break;
991 }
992 case DS_GX_CMD_MTX_MULT_4x4: {
993 struct DSGXMatrix m;
994 int i;
995 for (i = 0; i < 16; ++i) {
996 m.m[i] = gx->activeEntries[i].params[0];
997 m.m[i] |= gx->activeEntries[i].params[1] << 8;
998 m.m[i] |= gx->activeEntries[i].params[2] << 16;
999 m.m[i] |= gx->activeEntries[i].params[3] << 24;
1000 }
1001 switch (gx->mtxMode) {
1002 case 0:
1003 DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
1004 break;
1005 case 2:
1006 DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
1007 // Fall through
1008 case 1:
1009 DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
1010 break;
1011 case 3:
1012 DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
1013 break;
1014 }
1015 _updateClipMatrix(gx);
1016 break;
1017 }
1018 case DS_GX_CMD_MTX_MULT_4x3: {
1019 struct DSGXMatrix m;
1020 int i, j;
1021 for (j = 0; j < 4; ++j) {
1022 for (i = 0; i < 3; ++i) {
1023 m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
1024 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
1025 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
1026 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
1027 }
1028 m.m[j * 4 + 3] = 0;
1029 }
1030 m.m[15] = MTX_ONE;
1031 switch (gx->mtxMode) {
1032 case 0:
1033 DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
1034 break;
1035 case 2:
1036 DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
1037 // Fall through
1038 case 1:
1039 DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
1040 break;
1041 case 3:
1042 DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
1043 break;
1044 }
1045 _updateClipMatrix(gx);
1046 break;
1047 }
1048 case DS_GX_CMD_MTX_MULT_3x3: {
1049 struct DSGXMatrix m;
1050 int i, j;
1051 for (j = 0; j < 3; ++j) {
1052 for (i = 0; i < 3; ++i) {
1053 m.m[i + j * 4] = gx->activeEntries[i + j * 3].params[0];
1054 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[1] << 8;
1055 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[2] << 16;
1056 m.m[i + j * 4] |= gx->activeEntries[i + j * 3].params[3] << 24;
1057 }
1058 m.m[j * 4 + 3] = 0;
1059 }
1060 m.m[12] = 0;
1061 m.m[13] = 0;
1062 m.m[14] = 0;
1063 m.m[15] = MTX_ONE;
1064 switch (gx->mtxMode) {
1065 case 0:
1066 DSGXMtxMultiply(&gx->projMatrix, &m, &gx->projMatrix);
1067 break;
1068 case 2:
1069 DSGXMtxMultiply(&gx->vecMatrix, &m, &gx->vecMatrix);
1070 // Fall through
1071 case 1:
1072 DSGXMtxMultiply(&gx->posMatrix, &m, &gx->posMatrix);
1073 break;
1074 case 3:
1075 DSGXMtxMultiply(&gx->texMatrix, &m, &gx->texMatrix);
1076 break;
1077 }
1078 _updateClipMatrix(gx);
1079 break;
1080 }
1081 case DS_GX_CMD_MTX_TRANS: {
1082 int32_t m[3];
1083 m[0] = gx->activeEntries[0].params[0];
1084 m[0] |= gx->activeEntries[0].params[1] << 8;
1085 m[0] |= gx->activeEntries[0].params[2] << 16;
1086 m[0] |= gx->activeEntries[0].params[3] << 24;
1087 m[1] = gx->activeEntries[1].params[0];
1088 m[1] |= gx->activeEntries[1].params[1] << 8;
1089 m[1] |= gx->activeEntries[1].params[2] << 16;
1090 m[1] |= gx->activeEntries[1].params[3] << 24;
1091 m[2] = gx->activeEntries[2].params[0];
1092 m[2] |= gx->activeEntries[2].params[1] << 8;
1093 m[2] |= gx->activeEntries[2].params[2] << 16;
1094 m[2] |= gx->activeEntries[2].params[3] << 24;
1095 switch (gx->mtxMode) {
1096 case 0:
1097 DSGXMtxTranslate(&gx->projMatrix, m);
1098 break;
1099 case 2:
1100 DSGXMtxTranslate(&gx->vecMatrix, m);
1101 // Fall through
1102 case 1:
1103 DSGXMtxTranslate(&gx->posMatrix, m);
1104 break;
1105 case 3:
1106 DSGXMtxTranslate(&gx->texMatrix, m);
1107 break;
1108 }
1109 _updateClipMatrix(gx);
1110 break;
1111 }
1112 case DS_GX_CMD_MTX_SCALE: {
1113 int32_t m[3];
1114 m[0] = gx->activeEntries[0].params[0];
1115 m[0] |= gx->activeEntries[0].params[1] << 8;
1116 m[0] |= gx->activeEntries[0].params[2] << 16;
1117 m[0] |= gx->activeEntries[0].params[3] << 24;
1118 m[1] = gx->activeEntries[1].params[0];
1119 m[1] |= gx->activeEntries[1].params[1] << 8;
1120 m[1] |= gx->activeEntries[1].params[2] << 16;
1121 m[1] |= gx->activeEntries[1].params[3] << 24;
1122 m[2] = gx->activeEntries[2].params[0];
1123 m[2] |= gx->activeEntries[2].params[1] << 8;
1124 m[2] |= gx->activeEntries[2].params[2] << 16;
1125 m[2] |= gx->activeEntries[2].params[3] << 24;
1126 switch (gx->mtxMode) {
1127 case 0:
1128 DSGXMtxScale(&gx->projMatrix, m);
1129 break;
1130 case 1:
1131 case 2:
1132 DSGXMtxScale(&gx->posMatrix, m);
1133 break;
1134 case 3:
1135 DSGXMtxScale(&gx->texMatrix, m);
1136 break;
1137 }
1138 _updateClipMatrix(gx);
1139 break;
1140 }
1141 case DS_GX_CMD_COLOR:
1142 gx->currentVertex.color = entry.params[0];
1143 gx->currentVertex.color |= entry.params[1] << 8;
1144 break;
1145 case DS_GX_CMD_TEXCOORD:
1146 gx->currentVertex.s = entry.params[0];
1147 gx->currentVertex.s |= entry.params[1] << 8;
1148 gx->currentVertex.t = entry.params[2];
1149 gx->currentVertex.t |= entry.params[3] << 8;
1150 break;
1151 case DS_GX_CMD_VTX_16: {
1152 int16_t x = gx->activeEntries[0].params[0];
1153 x |= gx->activeEntries[0].params[1] << 8;
1154 int16_t y = gx->activeEntries[0].params[2];
1155 y |= gx->activeEntries[0].params[3] << 8;
1156 int16_t z = gx->activeEntries[1].params[0];
1157 z |= gx->activeEntries[1].params[1] << 8;
1158 _emitVertex(gx, x, y, z);
1159 break;
1160 }
1161 case DS_GX_CMD_VTX_10: {
1162 int32_t xyz = entry.params[0];
1163 xyz |= entry.params[1] << 8;
1164 xyz |= entry.params[2] << 16;
1165 xyz |= entry.params[3] << 24;
1166 int16_t x = (xyz << 6) & 0xFFC0;
1167 int16_t y = (xyz >> 4) & 0xFFC0;
1168 int16_t z = (xyz >> 14) & 0xFFC0;
1169 _emitVertex(gx, x, y, z);
1170 break;
1171 }
1172 case DS_GX_CMD_VTX_XY: {
1173 int16_t x = entry.params[0];
1174 x |= entry.params[1] << 8;
1175 int16_t y = entry.params[2];
1176 y |= entry.params[3] << 8;
1177 _emitVertex(gx, x, y, gx->currentVertex.z);
1178 break;
1179 }
1180 case DS_GX_CMD_VTX_XZ: {
1181 int16_t x = entry.params[0];
1182 x |= entry.params[1] << 8;
1183 int16_t z = entry.params[2];
1184 z |= entry.params[3] << 8;
1185 _emitVertex(gx, x, gx->currentVertex.y, z);
1186 break;
1187 }
1188 case DS_GX_CMD_VTX_YZ: {
1189 int16_t y = entry.params[0];
1190 y |= entry.params[1] << 8;
1191 int16_t z = entry.params[2];
1192 z |= entry.params[3] << 8;
1193 _emitVertex(gx, gx->currentVertex.x, y, z);
1194 break;
1195 }
1196 case DS_GX_CMD_VTX_DIFF: {
1197 int32_t xyz = entry.params[0];
1198 xyz |= entry.params[1] << 8;
1199 xyz |= entry.params[2] << 16;
1200 xyz |= entry.params[3] << 24;
1201 int16_t x = (xyz << 6) & 0xFFC0;
1202 int16_t y = (xyz >> 4) & 0xFFC0;
1203 int16_t z = (xyz >> 14) & 0xFFC0;
1204 _emitVertex(gx, gx->currentVertex.x + (x >> 6), gx->currentVertex.y + (y >> 6), gx->currentVertex.z + (z >> 6));
1205 break;
1206 }
1207 case DS_GX_CMD_POLYGON_ATTR:
1208 gx->nextPoly.polyParams = entry.params[0];
1209 gx->nextPoly.polyParams |= entry.params[1] << 8;
1210 gx->nextPoly.polyParams |= entry.params[2] << 16;
1211 gx->nextPoly.polyParams |= entry.params[3] << 24;
1212 break;
1213 case DS_GX_CMD_TEXIMAGE_PARAM:
1214 gx->nextPoly.texParams = entry.params[0];
1215 gx->nextPoly.texParams |= entry.params[1] << 8;
1216 gx->nextPoly.texParams |= entry.params[2] << 16;
1217 gx->nextPoly.texParams |= entry.params[3] << 24;
1218 break;
1219 case DS_GX_CMD_PLTT_BASE:
1220 gx->nextPoly.palBase = entry.params[0];
1221 gx->nextPoly.palBase |= entry.params[1] << 8;
1222 gx->nextPoly.palBase |= entry.params[2] << 16;
1223 gx->nextPoly.palBase |= entry.params[3] << 24;
1224 break;
1225 case DS_GX_CMD_BEGIN_VTXS:
1226 gx->vertexMode = entry.params[0] & 3;
1227 gx->currentPoly = gx->nextPoly;
1228 memset(gx->pendingVertexIds, -1, sizeof(gx->pendingVertexIds));
1229 break;
1230 case DS_GX_CMD_END_VTXS:
1231 gx->vertexMode = -1;
1232 break;
1233 case DS_GX_CMD_SWAP_BUFFERS:
1234 gx->swapBuffers = true;
1235 gx->wSort = entry.params[0] & 2;
1236 memset(&gx->currentVertex, 0, sizeof(gx->currentVertex));
1237 memset(&gx->nextPoly, 0, sizeof(gx-> nextPoly));
1238 gx->currentVertex.color = 0x7FFF;
1239 break;
1240 case DS_GX_CMD_VIEWPORT:
1241 gx->viewportX1 = (uint8_t) entry.params[0];
1242 gx->viewportY1 = (uint8_t) entry.params[1];
1243 gx->viewportX2 = (uint8_t) entry.params[2];
1244 gx->viewportY2 = (uint8_t) entry.params[3];
1245 gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
1246 gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
1247 break;
1248 case DS_GX_CMD_BOX_TEST:
1249 gxstat = DSRegGXSTATClearTestBusy(gxstat);
1250 gxstat = DSRegGXSTATTestFillBoxTestResult(gxstat, _boxTest(gx));
1251 break;
1252 default:
1253 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]);
1254 break;
1255 }
1256
1257 gxstat = DSRegGXSTATSetPVMatrixStackLevel(gxstat, gx->pvMatrixPointer);
1258 gxstat = DSRegGXSTATSetProjMatrixStackLevel(gxstat, projMatrixPointer);
1259 gxstat = DSRegGXSTATTestFillMatrixStackError(gxstat, projMatrixPointer || gx->pvMatrixPointer >= 0x1F);
1260 gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
1261
1262 if (cyclesLate >= cycles) {
1263 cyclesLate -= cycles;
1264 } else {
1265 break;
1266 }
1267 }
1268 if (cycles && !gx->swapBuffers) {
1269 mTimingSchedule(timing, &gx->fifoEvent, cycles - cyclesLate);
1270 }
1271 if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(struct DSGXEntry))) {
1272 _flushOutstanding(gx);
1273 }
1274 DSGXUpdateGXSTAT(gx);
1275}
1276
1277void DSGXInit(struct DSGX* gx) {
1278 gx->renderer = &dummyRenderer;
1279 CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE);
1280 CircleBufferInit(&gx->pipe, sizeof(struct DSGXEntry) * DS_GX_PIPE_SIZE);
1281 gx->vertexBuffer[0] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
1282 gx->vertexBuffer[1] = malloc(sizeof(struct DSGXVertex) * DS_GX_VERTEX_BUFFER_SIZE);
1283 gx->polygonBuffer[0] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
1284 gx->polygonBuffer[1] = malloc(sizeof(struct DSGXPolygon) * DS_GX_POLYGON_BUFFER_SIZE);
1285 gx->fifoEvent.name = "DS GX FIFO";
1286 gx->fifoEvent.priority = 0xC;
1287 gx->fifoEvent.context = gx;
1288 gx->fifoEvent.callback = _fifoRun;
1289}
1290
1291void DSGXDeinit(struct DSGX* gx) {
1292 DSGXAssociateRenderer(gx, &dummyRenderer);
1293 CircleBufferDeinit(&gx->fifo);
1294 CircleBufferDeinit(&gx->pipe);
1295 free(gx->vertexBuffer[0]);
1296 free(gx->vertexBuffer[1]);
1297 free(gx->polygonBuffer[0]);
1298 free(gx->polygonBuffer[1]);
1299}
1300
1301void DSGXReset(struct DSGX* gx) {
1302 CircleBufferClear(&gx->fifo);
1303 CircleBufferClear(&gx->pipe);
1304 DSGXMtxIdentity(&gx->projMatrix);
1305 DSGXMtxIdentity(&gx->texMatrix);
1306 DSGXMtxIdentity(&gx->posMatrix);
1307 DSGXMtxIdentity(&gx->vecMatrix);
1308
1309 DSGXMtxIdentity(&gx->clipMatrix);
1310 DSGXMtxIdentity(&gx->projMatrixStack);
1311 DSGXMtxIdentity(&gx->texMatrixStack);
1312 int i;
1313 for (i = 0; i < 32; ++i) {
1314 DSGXMtxIdentity(&gx->posMatrixStack[i]);
1315 DSGXMtxIdentity(&gx->vecMatrixStack[i]);
1316 }
1317 gx->swapBuffers = false;
1318 gx->bufferIndex = 0;
1319 gx->vertexIndex = 0;
1320 gx->polygonIndex = 0;
1321 gx->mtxMode = 0;
1322 gx->pvMatrixPointer = 0;
1323 gx->vertexMode = -1;
1324
1325 gx->viewportX1 = 0;
1326 gx->viewportY1 = 0;
1327 gx->viewportX2 = DS_VIDEO_HORIZONTAL_PIXELS - 1;
1328 gx->viewportY2 = DS_VIDEO_VERTICAL_PIXELS - 1;
1329 gx->viewportWidth = gx->viewportX2 - gx->viewportX1;
1330 gx->viewportHeight = gx->viewportY2 - gx->viewportY1;
1331
1332 memset(gx->outstandingParams, 0, sizeof(gx->outstandingParams));
1333 memset(gx->outstandingCommand, 0, sizeof(gx->outstandingCommand));
1334 memset(&gx->outstandingEntry, 0, sizeof(gx->outstandingEntry));
1335 gx->activeParams = 0;
1336 memset(&gx->currentVertex, 0, sizeof(gx->currentVertex));
1337 memset(&gx->nextPoly, 0, sizeof(gx-> nextPoly));
1338 gx->currentVertex.color = 0x7FFF;
1339 gx->dmaSource = -1;
1340}
1341
1342void DSGXAssociateRenderer(struct DSGX* gx, struct DSGXRenderer* renderer) {
1343 gx->renderer->deinit(gx->renderer);
1344 gx->renderer = renderer;
1345 memcpy(gx->renderer->tex, gx->tex, sizeof(gx->renderer->tex));
1346 memcpy(gx->renderer->texPal, gx->texPal, sizeof(gx->renderer->texPal));
1347 gx->renderer->init(gx->renderer);
1348}
1349
1350void DSGXUpdateGXSTAT(struct DSGX* gx) {
1351 uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16;
1352 value = DSRegGXSTATIsDoIRQ(value);
1353
1354 size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry);
1355 // XXX
1356 if (gx->swapBuffers) {
1357 entries++;
1358 }
1359 value = DSRegGXSTATSetFIFOEntries(value, entries);
1360 value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2));
1361 value = DSRegGXSTATSetFIFOEmpty(value, entries == 0);
1362
1363 if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) ||
1364 (DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) {
1365 DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO);
1366 }
1367
1368 value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers);
1369
1370 gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16;
1371
1372 struct GBADMA* dma = NULL;
1373 if (gx->dmaSource >= 0) {
1374 dma = &gx->p->ds9.memory.dma[gx->dmaSource];
1375 if (GBADMARegisterGetTiming9(dma->reg) != DS_DMA_TIMING_GEOM_FIFO) {
1376 gx->dmaSource = -1;
1377 } else if (GBADMARegisterIsEnable(dma->reg) && entries < (DS_GX_FIFO_SIZE / 2) && !dma->nextCount) {
1378 dma->nextCount = dma->count;
1379 if (dma->count > 112) {
1380 dma->nextCount = 112;
1381 }
1382 dma->when = mTimingCurrentTime(&gx->p->ds9.timing);
1383 DSDMAUpdate(&gx->p->ds9);
1384 }
1385 }
1386}
1387
1388static void DSGXUnpackCommand(struct DSGX* gx, uint32_t command) {
1389 gx->outstandingCommand[0] = command;
1390 gx->outstandingCommand[1] = command >> 8;
1391 gx->outstandingCommand[2] = command >> 16;
1392 gx->outstandingCommand[3] = command >> 24;
1393 if (gx->outstandingCommand[0] >= DS_GX_CMD_MAX) {
1394 gx->outstandingCommand[0] = 0;
1395 }
1396 if (gx->outstandingCommand[1] >= DS_GX_CMD_MAX) {
1397 gx->outstandingCommand[1] = 0;
1398 }
1399 if (gx->outstandingCommand[2] >= DS_GX_CMD_MAX) {
1400 gx->outstandingCommand[2] = 0;
1401 }
1402 if (gx->outstandingCommand[3] >= DS_GX_CMD_MAX) {
1403 gx->outstandingCommand[3] = 0;
1404 }
1405 gx->outstandingParams[0] = _gxCommandParams[gx->outstandingCommand[0]];
1406 gx->outstandingParams[1] = _gxCommandParams[gx->outstandingCommand[1]];
1407 gx->outstandingParams[2] = _gxCommandParams[gx->outstandingCommand[2]];
1408 gx->outstandingParams[3] = _gxCommandParams[gx->outstandingCommand[3]];
1409 _flushOutstanding(gx);
1410 DSGXUpdateGXSTAT(gx);
1411}
1412
1413static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) {
1414 if (CircleBufferSize(&gx->fifo) == (DS_GX_FIFO_SIZE * sizeof(entry))) {
1415 mLOG(DS_GX, INFO, "FIFO full");
1416 if (gx->p->cpuBlocked & DS_CPU_BLOCK_GX) {
1417 abort();
1418 }
1419 gx->p->cpuBlocked |= DS_CPU_BLOCK_GX;
1420 gx->outstandingEntry = entry;
1421 gx->p->ds9.cpu->nextEvent = 0;
1422 return;
1423 }
1424 if (gx->outstandingCommand[0]) {
1425 entry.command = gx->outstandingCommand[0];
1426 if (gx->outstandingParams[0]) {
1427 --gx->outstandingParams[0];
1428 }
1429 if (!gx->outstandingParams[0]) {
1430 // TODO: improve this
1431 memmove(&gx->outstandingParams[0], &gx->outstandingParams[1], sizeof(gx->outstandingParams[0]) * 3);
1432 memmove(&gx->outstandingCommand[0], &gx->outstandingCommand[1], sizeof(gx->outstandingCommand[0]) * 3);
1433 gx->outstandingParams[3] = 0;
1434 gx->outstandingCommand[3] = 0;
1435 }
1436 } else {
1437 gx->outstandingParams[0] = _gxCommandParams[entry.command];
1438 if (gx->outstandingParams[0]) {
1439 --gx->outstandingParams[0];
1440 }
1441 if (gx->outstandingParams[0]) {
1442 gx->outstandingCommand[0] = entry.command;
1443 }
1444 }
1445 uint32_t cycles = _gxCommandCycleBase[entry.command];
1446 if (!cycles) {
1447 return;
1448 }
1449 if (CircleBufferSize(&gx->fifo) == 0 && CircleBufferSize(&gx->pipe) < (DS_GX_PIPE_SIZE * sizeof(entry))) {
1450 CircleBufferWrite8(&gx->pipe, entry.command);
1451 CircleBufferWrite8(&gx->pipe, entry.params[0]);
1452 CircleBufferWrite8(&gx->pipe, entry.params[1]);
1453 CircleBufferWrite8(&gx->pipe, entry.params[2]);
1454 CircleBufferWrite8(&gx->pipe, entry.params[3]);
1455 } else if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) {
1456 CircleBufferWrite8(&gx->fifo, entry.command);
1457 CircleBufferWrite8(&gx->fifo, entry.params[0]);
1458 CircleBufferWrite8(&gx->fifo, entry.params[1]);
1459 CircleBufferWrite8(&gx->fifo, entry.params[2]);
1460 CircleBufferWrite8(&gx->fifo, entry.params[3]);
1461 }
1462 if (entry.command == DS_GX_CMD_BOX_TEST) {
1463 DSRegGXSTAT gxstat = gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1];
1464 gxstat = DSRegGXSTATFillTestBusy(gxstat);
1465 gxstat = DSRegGXSTATClearBoxTestResult(gxstat);
1466 gx->p->memory.io9[DS9_REG_GXSTAT_LO >> 1] = gxstat;
1467 }
1468 if (!gx->swapBuffers && !mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) {
1469 mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles);
1470 }
1471
1472 _flushOutstanding(gx);
1473}
1474
1475uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) {
1476 uint16_t oldValue = gx->p->memory.io9[address >> 1];
1477 switch (address) {
1478 case DS9_REG_DISP3DCNT:
1479 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1480 break;
1481 case DS9_REG_GXSTAT_LO:
1482 value = DSRegGXSTATIsMatrixStackError(value);
1483 if (value) {
1484 oldValue = DSRegGXSTATClearMatrixStackError(oldValue);
1485 oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue);
1486 }
1487 value = oldValue;
1488 break;
1489 case DS9_REG_GXSTAT_HI:
1490 value = DSRegGXSTATIsDoIRQ(value << 16) >> 16;
1491 gx->p->memory.io9[address >> 1] = value;
1492 DSGXUpdateGXSTAT(gx);
1493 value = gx->p->memory.io9[address >> 1];
1494 break;
1495 default:
1496 if (address < DS9_REG_GXFIFO_00) {
1497 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1498 } else if (address <= DS9_REG_GXFIFO_1F) {
1499 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1500 } else if (address < DS9_REG_GXSTAT_LO) {
1501 struct DSGXEntry entry = {
1502 .command = (address & 0x1FC) >> 2,
1503 .params = {
1504 value,
1505 value >> 8,
1506 }
1507 };
1508 if (entry.command < DS_GX_CMD_MAX) {
1509 DSGXWriteFIFO(gx, entry);
1510 }
1511 } else {
1512 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value);
1513 }
1514 break;
1515 }
1516 return value;
1517}
1518
1519uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) {
1520 switch (address) {
1521 case DS9_REG_DISP3DCNT:
1522 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1523 break;
1524 case DS9_REG_GXSTAT_LO:
1525 value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value);
1526 value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16);
1527 break;
1528 default:
1529 if (address < DS9_REG_GXFIFO_00) {
1530 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1531 } else if (address <= DS9_REG_GXFIFO_1F) {
1532 if (gx->outstandingParams[0]) {
1533 struct DSGXEntry entry = {
1534 .command = gx->outstandingCommand[0],
1535 .params = {
1536 value,
1537 value >> 8,
1538 value >> 16,
1539 value >> 24
1540 }
1541 };
1542 DSGXWriteFIFO(gx, entry);
1543 } else {
1544 DSGXUnpackCommand(gx, value);
1545 }
1546 } else if (address < DS9_REG_GXSTAT_LO) {
1547 struct DSGXEntry entry = {
1548 .command = (address & 0x1FC) >> 2,
1549 .params = {
1550 value,
1551 value >> 8,
1552 value >> 16,
1553 value >> 24
1554 }
1555 };
1556 DSGXWriteFIFO(gx, entry);
1557 } else {
1558 mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%08X", address, value);
1559 }
1560 break;
1561 }
1562 return value;
1563}
1564
1565void DSGXFlush(struct DSGX* gx) {
1566 if (gx->swapBuffers) {
1567 gx->renderer->setRAM(gx->renderer, gx->vertexBuffer[gx->bufferIndex], gx->polygonBuffer[gx->bufferIndex], gx->polygonIndex, gx->wSort);
1568 gx->swapBuffers = false;
1569 gx->bufferIndex ^= 1;
1570 gx->vertexIndex = 0;
1571 gx->pendingVertexIndex = 0;
1572 gx->polygonIndex = 0;
1573 if (CircleBufferSize(&gx->fifo)) {
1574 mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0);
1575 }
1576 }
1577
1578 DSGXUpdateGXSTAT(gx);
1579}
1580
1581void DSGXScheduleDMA(struct DSCommon* dscore, int number, struct GBADMA* info) {
1582 UNUSED(info);
1583 dscore->p->gx.dmaSource = number;
1584}
1585
1586static void DSGXDummyRendererInit(struct DSGXRenderer* renderer) {
1587 UNUSED(renderer);
1588 // Nothing to do
1589}
1590
1591static void DSGXDummyRendererReset(struct DSGXRenderer* renderer) {
1592 UNUSED(renderer);
1593 // Nothing to do
1594}
1595
1596static void DSGXDummyRendererDeinit(struct DSGXRenderer* renderer) {
1597 UNUSED(renderer);
1598 // Nothing to do
1599}
1600
1601static void DSGXDummyRendererInvalidateTex(struct DSGXRenderer* renderer, int slot) {
1602 UNUSED(renderer);
1603 UNUSED(slot);
1604 // Nothing to do
1605}
1606
1607static void DSGXDummyRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort) {
1608 UNUSED(renderer);
1609 UNUSED(verts);
1610 UNUSED(polys);
1611 UNUSED(polyCount);
1612 UNUSED(wSort);
1613 // Nothing to do
1614}
1615
1616static void DSGXDummyRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
1617 UNUSED(renderer);
1618 UNUSED(y);
1619 // Nothing to do
1620}
1621
1622static void DSGXDummyRendererGetScanline(struct DSGXRenderer* renderer, int y, const color_t** output) {
1623 UNUSED(renderer);
1624 UNUSED(y);
1625 *output = NULL;
1626 // Nothing to do
1627}