src/ds/gx/software.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/software.h>
7
8#include <mgba-util/memory.h>
9#include "gba/renderers/software-private.h"
10
11#define SCREEN_SIZE (DS_VIDEO_VERTICAL_PIXELS << 12)
12
13DEFINE_VECTOR(DSGXSoftwarePolygonList, struct DSGXSoftwarePolygon);
14DEFINE_VECTOR(DSGXSoftwareEdgeList, struct DSGXSoftwareEdge);
15DEFINE_VECTOR(DSGXSoftwareSpanList, struct DSGXSoftwareSpan);
16
17static void DSGXSoftwareRendererInit(struct DSGXRenderer* renderer);
18static void DSGXSoftwareRendererReset(struct DSGXRenderer* renderer);
19static void DSGXSoftwareRendererDeinit(struct DSGXRenderer* renderer);
20static void DSGXSoftwareRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount);
21static void DSGXSoftwareRendererDrawScanline(struct DSGXRenderer* renderer, int y);
22static void DSGXSoftwareRendererGetScanline(struct DSGXRenderer* renderer, int y, color_t** output);
23
24static void _expandColor(uint16_t c15, int8_t* r, int8_t* g, int8_t* b) {
25 *r = ((c15 << 1) & 0x3E) | 1;
26 *g = ((c15 >> 4) & 0x3E) | 1;
27 *b = ((c15 >> 9) & 0x3E) | 1;
28}
29
30static int _edgeSort(const void* a, const void* b) {
31 const struct DSGXSoftwareEdge* ea = a;
32 const struct DSGXSoftwareEdge* eb = b;
33
34 // Sort upside down
35 if (ea->y0 < eb->y0) {
36 return 1;
37 }
38 if (ea->y0 > eb->y0) {
39 return -1;
40 }
41 if (ea->y1 < eb->y1) {
42 return 1;
43 }
44 if (ea->y1 > eb->y1) {
45 return -1;
46 }
47 return 0;
48}
49
50static bool _edgeToSpan(struct DSGXSoftwareSpan* span, const struct DSGXSoftwareEdge* edge, int index, int32_t y) {
51 // TODO: Perspective correction
52 int32_t height = edge->y1 - edge->y0;
53 int64_t yw = (y << 12) - edge->y0;
54 if (!height) {
55 return false;
56 }
57 // Clamp to bounds
58 if (yw < 0) {
59 yw = 0;
60 } else if (yw > height) {
61 yw = height;
62 }
63 span->ep[index].x = ((int64_t) (edge->x1 - edge->x0) * yw) / height + edge->x0;
64 if (index && span->ep[0].x > span->ep[index].x) {
65 int32_t temp = span->ep[index].x;
66 span->ep[index] = span->ep[0];
67 span->ep[0].x = temp;
68 index = 0;
69 }
70 span->ep[index].w = ((int64_t) (edge->w1 - edge->w0) * yw) / height + edge->w0;
71 span->ep[index].cr = ((int32_t) (edge->cr1 - edge->cr0) * yw) / height + edge->cr0;
72 span->ep[index].cg = ((int32_t) (edge->cg1 - edge->cg0) * yw) / height + edge->cg0;
73 span->ep[index].cb = ((int32_t) (edge->cb1 - edge->cb0) * yw) / height + edge->cb0;
74 span->ep[index].s = ((int32_t) (edge->s1 - edge->s0) * yw) / height + edge->s0;
75 span->ep[index].t = ((int32_t) (edge->t1 - edge->t0) * yw) / height + edge->t0;
76
77 return true;
78}
79
80static int _spanSort(const void* a, const void* b) {
81 const struct DSGXSoftwareSpan* sa = a;
82 const struct DSGXSoftwareSpan* sb = b;
83
84 // Sort backwards
85 if (sa->ep[0].x < sb->ep[0].x) {
86 return 1;
87 }
88 if (sa->ep[0].x > sb->ep[0].x) {
89 return -1;
90 }
91 if (sa->ep[1].x < sb->ep[1].x) {
92 return 1;
93 }
94 if (sa->ep[1].x > sb->ep[1].x) {
95 return -1;
96 }
97 return 0;
98}
99
100static color_t _lerpColor(const struct DSGXSoftwareSpan* span, unsigned x) {
101 int64_t width = span->ep[1].x - span->ep[0].x;
102 int64_t xw = ((uint64_t) x << 12) - span->ep[0].x;
103 if (!width) {
104 return 0; // TODO?
105 }
106 // Clamp to bounds
107 if (xw < 0) {
108 xw = 0;
109 } else if (xw > width) {
110 xw = width;
111 }
112 uint32_t w = ((int64_t) (span->ep[0].w - span->ep[1].w) * xw) / width + span->ep[1].w;
113
114 uint64_t r = ((span->ep[1].cr * (int64_t) span->ep[0].w - span->ep[0].cr * (int64_t) span->ep[1].w) * xw) / width + span->ep[0].cr * (int64_t) span->ep[1].w;
115 uint64_t g = ((span->ep[1].cg * (int64_t) span->ep[0].w - span->ep[0].cg * (int64_t) span->ep[1].w) * xw) / width + span->ep[0].cg * (int64_t) span->ep[1].w;
116 uint64_t b = ((span->ep[1].cb * (int64_t) span->ep[0].w - span->ep[0].cb * (int64_t) span->ep[1].w) * xw) / width + span->ep[0].cb * (int64_t) span->ep[1].w;
117 r /= w;
118 g /= w;
119 b /= w;
120#ifndef COLOR_16_BIT
121 color_t rgb = (r << 2) & 0xF8;
122 rgb |= (g << 10) & 0xF800;
123 rgb |= (b << 18) & 0xF80000;
124 return rgb;
125#else
126#error Unsupported color depth
127#endif
128}
129
130void DSGXSoftwareRendererCreate(struct DSGXSoftwareRenderer* renderer) {
131 renderer->d.init = DSGXSoftwareRendererInit;
132 renderer->d.reset = DSGXSoftwareRendererReset;
133 renderer->d.deinit = DSGXSoftwareRendererDeinit;
134 renderer->d.setRAM = DSGXSoftwareRendererSetRAM;
135 renderer->d.drawScanline = DSGXSoftwareRendererDrawScanline;
136 renderer->d.getScanline = DSGXSoftwareRendererGetScanline;
137}
138
139static void DSGXSoftwareRendererInit(struct DSGXRenderer* renderer) {
140 struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
141 DSGXSoftwarePolygonListInit(&softwareRenderer->activePolys, DS_GX_POLYGON_BUFFER_SIZE / 4);
142 DSGXSoftwareEdgeListInit(&softwareRenderer->activeEdges, DS_GX_POLYGON_BUFFER_SIZE);
143 DSGXSoftwareSpanListInit(&softwareRenderer->activeSpans, DS_GX_POLYGON_BUFFER_SIZE / 2);
144 softwareRenderer->bucket = anonymousMemoryMap(sizeof(*softwareRenderer->bucket) * DS_GX_POLYGON_BUFFER_SIZE);
145 softwareRenderer->scanlineCache = anonymousMemoryMap(sizeof(color_t) * DS_VIDEO_HORIZONTAL_PIXELS * 48);
146}
147
148static void DSGXSoftwareRendererReset(struct DSGXRenderer* renderer) {
149 struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
150 // TODO
151}
152
153static void DSGXSoftwareRendererDeinit(struct DSGXRenderer* renderer) {
154 struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
155 DSGXSoftwarePolygonListDeinit(&softwareRenderer->activePolys);
156 DSGXSoftwareEdgeListDeinit(&softwareRenderer->activeEdges);
157 DSGXSoftwareSpanListDeinit(&softwareRenderer->activeSpans);
158 mappedMemoryFree(softwareRenderer->bucket, sizeof(*softwareRenderer->bucket) * DS_GX_POLYGON_BUFFER_SIZE);
159 mappedMemoryFree(softwareRenderer->scanlineCache, sizeof(color_t) * DS_VIDEO_HORIZONTAL_PIXELS * 48);
160}
161
162static void DSGXSoftwareRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount) {
163 struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
164
165 softwareRenderer->verts = verts;
166 DSGXSoftwarePolygonListClear(&softwareRenderer->activePolys);
167 DSGXSoftwareEdgeListClear(&softwareRenderer->activeEdges);
168 unsigned i;
169 for (i = 0; i < polyCount; ++i) {
170 struct DSGXSoftwarePolygon* poly = DSGXSoftwarePolygonListAppend(&softwareRenderer->activePolys);
171 struct DSGXSoftwareEdge* edge = DSGXSoftwareEdgeListAppend(&softwareRenderer->activeEdges);
172 poly->poly = &polys[i];
173 edge->polyId = i;
174
175 struct DSGXVertex* v0 = &verts[poly->poly->vertIds[0]];
176 struct DSGXVertex* v1;
177
178 int v;
179 for (v = 1; v < poly->poly->verts; ++v) {
180 v1 = &verts[poly->poly->vertIds[v]];
181 if (v0->vy >= v1->vy) {
182 edge->y0 = SCREEN_SIZE - v0->vy;
183 edge->x0 = v0->vx;
184 edge->w0 = v0->vw;
185 _expandColor(v0->color, &edge->cr0, &edge->cg0, &edge->cb0);
186 edge->s0 = v0->s;
187 edge->t0 = v0->t;
188
189 edge->y1 = SCREEN_SIZE - v1->vy;
190 edge->x1 = v1->vx;
191 edge->w1 = v1->vw;
192 _expandColor(v1->color, &edge->cr1, &edge->cg1, &edge->cb1);
193 edge->s1 = v1->s;
194 edge->t1 = v1->t;
195 } else {
196 edge->y0 = SCREEN_SIZE - v1->vy;
197 edge->x0 = v1->vx;
198 edge->w0 = v1->vw;
199 _expandColor(v1->color, &edge->cr0, &edge->cg0, &edge->cb0);
200 edge->s0 = v1->s;
201 edge->t0 = v1->t;
202
203 edge->y1 = SCREEN_SIZE - v0->vy;
204 edge->x1 = v0->vx;
205 edge->w1 = v0->vw;
206 _expandColor(v0->color, &edge->cr1, &edge->cg1, &edge->cb1);
207 edge->s1 = v0->s;
208 edge->t1 = v0->t;
209 }
210
211 edge = DSGXSoftwareEdgeListAppend(&softwareRenderer->activeEdges);
212 edge->polyId = i;
213 v0 = v1;
214 }
215
216 v1 = &verts[poly->poly->vertIds[0]];
217 if (v0->vy >= v1->vy) {
218 edge->y0 = SCREEN_SIZE - v0->vy;
219 edge->x0 = v0->vx;
220 edge->w0 = v0->vw;
221 _expandColor(v0->color, &edge->cr0, &edge->cg0, &edge->cb0);
222 edge->s0 = v0->s;
223 edge->t0 = v0->t;
224
225 edge->y1 = SCREEN_SIZE - v1->vy;
226 edge->x1 = v1->vx;
227 edge->w1 = v1->vw;
228 _expandColor(v1->color, &edge->cr1, &edge->cg1, &edge->cb1);
229 edge->s1 = v1->s;
230 edge->t1 = v1->t;
231 } else {
232 edge->y0 = SCREEN_SIZE - v1->vy;
233 edge->x0 = v1->vx;
234 edge->w0 = v1->vw;
235 _expandColor(v1->color, &edge->cr0, &edge->cg0, &edge->cb0);
236 edge->s0 = v1->s;
237 edge->t0 = v1->t;
238
239 edge->y1 = SCREEN_SIZE - v0->vy;
240 edge->x1 = v0->vx;
241 edge->w1 = v0->vw;
242 _expandColor(v0->color, &edge->cr1, &edge->cg1, &edge->cb1);
243 edge->s1 = v0->s;
244 edge->t1 = v0->t;
245 }
246 }
247 qsort(DSGXSoftwareEdgeListGetPointer(&softwareRenderer->activeEdges, 0), DSGXSoftwareEdgeListSize(&softwareRenderer->activeEdges), sizeof(struct DSGXSoftwareEdge), _edgeSort);
248}
249
250static void DSGXSoftwareRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
251 struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
252 DSGXSoftwareSpanListClear(&softwareRenderer->activeSpans);
253 memset(softwareRenderer->bucket, 0, sizeof(*softwareRenderer->bucket) * DS_GX_POLYGON_BUFFER_SIZE);
254 size_t i;
255 for (i = DSGXSoftwareEdgeListSize(&softwareRenderer->activeEdges); i; --i) {
256 size_t idx = i - 1;
257 struct DSGXSoftwareEdge* edge = DSGXSoftwareEdgeListGetPointer(&softwareRenderer->activeEdges, idx);
258 if (edge->y1 >> 12 < y) {
259 DSGXSoftwareEdgeListShift(&softwareRenderer->activeEdges, idx, 1);
260 continue;
261 } else if (edge->y0 >> 12 > y) {
262 continue;
263 }
264
265 unsigned poly = edge->polyId;
266 struct DSGXSoftwareSpan* span = softwareRenderer->bucket[poly];
267 if (span && !span->ep[1].w) {
268 _edgeToSpan(span, edge, 1, y);
269 softwareRenderer->bucket[poly] = NULL;
270 } else if (!span) {
271 span = DSGXSoftwareSpanListAppend(&softwareRenderer->activeSpans);
272 memset(span, 0, sizeof(*span));
273 if (!_edgeToSpan(span, edge, 0, y)) {
274 // Horizontal line
275 DSGXSoftwareSpanListShift(&softwareRenderer->activeSpans, DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans) - 1, 1);
276 } else {
277 softwareRenderer->bucket[poly] = span;
278 }
279 }
280 }
281 qsort(DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, 0), DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans), sizeof(struct DSGXSoftwareSpan), _spanSort);
282
283 y %= 48;
284 color_t* scanline = &softwareRenderer->scanlineCache[DS_VIDEO_HORIZONTAL_PIXELS * y];
285
286 size_t nextSpanX = DS_VIDEO_HORIZONTAL_PIXELS;
287 if (DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans)) {
288 nextSpanX = DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans) - 1)->ep[0].x;
289 nextSpanX >>= 12;
290 }
291 for (i = 0; i < DS_VIDEO_HORIZONTAL_PIXELS; ++i) {
292 struct DSGXSoftwareSpan* span = NULL;
293 if (i >= nextSpanX) {
294 size_t nextSpanId = DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans);
295 span = DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, nextSpanId - 1);
296 while (i > (uint32_t) (span->ep[1].x >> 12)) {
297 DSGXSoftwareSpanListShift(&softwareRenderer->activeSpans, nextSpanId - 1, 1);
298 --nextSpanId;
299 if (!nextSpanId) {
300 nextSpanX = DS_VIDEO_HORIZONTAL_PIXELS;
301 span = NULL;
302 break;
303 }
304 span = DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, nextSpanId - 1);
305 nextSpanX = span->ep[0].x >> 12;
306 }
307 if (i < nextSpanX) {
308 span = NULL;
309 }
310 }
311 if (span) {
312 scanline[i] = _lerpColor(span, i);
313 } else {
314 scanline[i] = FLAG_UNWRITTEN; // TODO
315 }
316 }
317}
318
319static void DSGXSoftwareRendererGetScanline(struct DSGXRenderer* renderer, int y, color_t** output) {
320 struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
321 y %= 48;
322 *output = &softwareRenderer->scanlineCache[DS_VIDEO_HORIZONTAL_PIXELS * y];
323}