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