all repos — mgba @ a3cd5f8ca4a39b069622bb603d3d6437ff7c8584

mGBA Game Boy Advance Emulator

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