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