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