all repos — mgba @ 6103d49697c8015264ab18cf2a8d3c07dd893289

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 DSGXSoftwareRendererInvalidateTex(struct DSGXRenderer* renderer, int slot);
 21static void DSGXSoftwareRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort);
 22static void DSGXSoftwareRendererDrawScanline(struct DSGXRenderer* renderer, int y);
 23static void DSGXSoftwareRendererGetScanline(struct DSGXRenderer* renderer, int y, const color_t** output);
 24
 25static void _expandColor(uint16_t c15, uint8_t* r, uint8_t* g, uint8_t* b) {
 26	*r = ((c15 << 1) & 0x3E) | 1;
 27	*g = ((c15 >> 4) & 0x3E) | 1;
 28	*b = ((c15 >> 9) & 0x3E) | 1;
 29}
 30
 31static color_t _finishColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
 32#ifndef COLOR_16_BIT
 33	color_t rgba = (r << 2) & 0xF8;
 34	rgba |= (g << 10) & 0xF800;
 35	rgba |= (b << 18) & 0xF80000;
 36	rgba |= (a << 27) & 0xF8000000;
 37	return rgba;
 38#else
 39#error Unsupported color depth
 40#endif
 41}
 42
 43static color_t _lookupColor(struct DSGXSoftwareEndpoint* ep, struct DSGXSoftwarePolygon* poly) {
 44	// TODO: Optimize
 45	uint16_t texel;
 46
 47	int16_t s = ep->s >> 4;
 48	int16_t t = ep->t >> 4;
 49	if (!DSGXTexParamsIsSRepeat(poly->poly->texParams)) {
 50		if (s < 0) {
 51			s = 0;
 52		} else if (s >= poly->texW) {
 53			s = poly->texW - 1;
 54		}
 55	} else if (DSGXTexParamsIsSMirror(poly->poly->texParams)) {
 56		if (s & poly->texW) {
 57			s = poly->texW - s;
 58		}
 59		s &= poly->texW - 1;
 60	} else {
 61		s &= poly->texW - 1;
 62	}
 63	if (!DSGXTexParamsIsTRepeat(poly->poly->texParams)) {
 64		if (t < 0) {
 65			t = 0;
 66		} else if (t >= poly->texH) {
 67			t = poly->texW - 1;
 68		}
 69	} else if (DSGXTexParamsIsTMirror(poly->poly->texParams)) {
 70		if (t & poly->texH) {
 71			t = poly->texH - t;
 72		}
 73		t &= poly->texH - 1;
 74	} else {
 75		t &= poly->texH - 1;
 76	}
 77
 78	uint16_t texelCoord = s + t * poly->texW;
 79	uint8_t a = 0x1F;
 80	switch (poly->texFormat) {
 81	case 0:
 82	default:
 83		return _finishColor(ep->cr, ep->cg, ep->cb, 0x1F);
 84	case 1:
 85		texel = ((uint8_t*) poly->texBase)[texelCoord];
 86		a = (texel >> 5) & 0x7;
 87		a = (a << 2) + (a >> 1);
 88		texel &= 0x1F;
 89		break;
 90	case 2:
 91		texel = ((uint8_t*) poly->texBase)[texelCoord >> 2];
 92		if (texelCoord & 0x3) {
 93			texel >>= 2 * texel & 3;
 94		}
 95		texel &= 0x3;
 96		break;
 97	case 3:
 98		texel = ((uint8_t*) poly->texBase)[texelCoord >> 1];
 99		if (texelCoord & 0x1) {
100			texel >>= 4;
101		}
102		texel &= 0xF;
103		break;
104	case 4:
105		texel = ((uint8_t*) poly->texBase)[texelCoord];
106		break;
107	case 5:
108		return _finishColor(0x3F, 0, 0x3F, 0x1F);
109	case 6:
110		texel = ((uint8_t*) poly->texBase)[texelCoord];
111		a = (texel >> 3) & 0x1F;
112		texel &= 0x7;
113		break;
114	case 7:
115		return _finishColor(0x3F, 0x3F, 0x3F, 0x1F);
116	}
117	if (DSGXTexParamsIs0Transparent(poly->poly->texParams) && !texel) {
118		return 0;
119	}
120	uint8_t r, g, b;
121	unsigned wr, wg, wb;
122	texel = poly->palBase[texel];
123	_expandColor(texel, &r, &g, &b);
124	switch (poly->blendFormat) {
125	case 1:
126	default:
127		// TODO: Alpha
128		return _finishColor(r, g, b, a);
129	case 0:
130		wr = ((r + 1) * (ep->cr + 1) - 1) >> 6;
131		wg = ((g + 1) * (ep->cg + 1) - 1) >> 6;
132		wb = ((b + 1) * (ep->cb + 1) - 1) >> 6;
133		return _finishColor(wr, wg, wb, a);
134	}
135}
136
137static int _edgeSort(const void* a, const void* b) {
138	const struct DSGXSoftwareEdge* ea = a;
139	const struct DSGXSoftwareEdge* eb = b;
140
141	// Sort upside down
142	if (ea->y0 < eb->y0) {
143		return 1;
144	}
145	if (ea->y0 > eb->y0) {
146		return -1;
147	}
148	if (ea->y1 < eb->y1) {
149		return 1;
150	}
151	if (ea->y1 > eb->y1) {
152		return -1;
153	}
154	return 0;
155}
156
157static bool _edgeToSpan(struct DSGXSoftwareSpan* span, const struct DSGXSoftwareEdge* edge, int index, int32_t y) {
158	int32_t height = edge->y1 - edge->y0;
159	int64_t yw = (y << 12) - edge->y0;
160	if (!height) {
161		return false;
162	}
163	// Clamp to bounds
164	if (yw < 0) {
165		yw = 0;
166	} else if (yw > height) {
167		yw = height;
168	}
169	yw *= 0x100000000LL / height;
170
171	span->ep[index].x = (((int64_t) (edge->x1 - edge->x0) * yw) >> 32) + edge->x0;
172
173	if (index && span->ep[0].x > span->ep[index].x) {
174		int32_t temp = span->ep[index].x;
175		span->ep[index] = span->ep[0];
176		span->ep[0].x = temp;
177		index = 0;
178	}
179	int32_t w0 = edge->w0;
180	int32_t w1 = edge->w1;
181	int32_t w = (((int64_t) (edge->w1 - edge->w0) * yw) >> 32) + edge->w0;
182	int64_t wRecip;// = 0x1000000000000LL / w;
183	// XXX: Disable perspective correction until I figure out how to fix it
184	wRecip = 0x100000000;
185	w0 = 0x10000;
186	w1 = 0x10000;
187	span->ep[index].w = w;
188	span->ep[index].z = (((edge->z1 - edge->z0) * yw) >> 32) + edge->z0;
189	span->ep[index].cr = (((((edge->cr1 * (int64_t) w1 - edge->cr0 * (int64_t) w0) * yw) >> 32) + edge->cr0 * (int64_t) w0) * wRecip) >> 48;
190	span->ep[index].cg = (((((edge->cg1 * (int64_t) w1 - edge->cg0 * (int64_t) w0) * yw) >> 32) + edge->cg0 * (int64_t) w0) * wRecip) >> 48;
191	span->ep[index].cb = (((((edge->cb1 * (int64_t) w1 - edge->cb0 * (int64_t) w0) * yw) >> 32) + edge->cb0 * (int64_t) w0) * wRecip) >> 48;
192	span->ep[index].s = (((((edge->s1 * (int64_t) w1 - edge->s0 * (int64_t) w0) * yw) >> 32) + edge->s0 * (int64_t) w0) * wRecip) >> 48;
193	span->ep[index].t = (((((edge->t1 * (int64_t) w1 - edge->t0 * (int64_t) w0) * yw) >> 32) + edge->t0 * (int64_t) w0) * wRecip) >> 48;
194
195	return true;
196}
197
198static int _spanSort(const void* a, const void* b) {
199	const struct DSGXSoftwareSpan* sa = a;
200	const struct DSGXSoftwareSpan* sb = b;
201
202	// Sort backwards
203	if (sa->ep[0].x < sb->ep[0].x) {
204		return 1;
205	}
206	if (sa->ep[0].x > sb->ep[0].x) {
207		return -1;
208	}
209	if (sa->ep[0].w < sb->ep[0].w) {
210		return 1;
211	}
212	if (sa->ep[0].w > sb->ep[0].w) {
213		return -1;
214	}
215	return 0;
216}
217
218static void _lerpEndpoint(const struct DSGXSoftwareSpan* span, struct DSGXSoftwareEndpoint* ep, unsigned x) {
219	int64_t width = span->ep[1].x - span->ep[0].x;
220	int64_t xw = ((uint64_t) x << 12) - span->ep[0].x;
221	if (!width) {
222		return; // TODO?
223	}
224	// Clamp to bounds
225	if (xw < 0) {
226		xw = 0;
227	} else if (xw > width) {
228		xw = width;
229	}
230	xw *= 0x100000000LL / width;
231	int32_t w0 = span->ep[0].w;
232	int32_t w1 = span->ep[1].w;
233	int64_t w = (((int64_t) (w1 - w0) * xw) >> 32) + w0;
234	int64_t wRecip;// = 0x1000000000000LL / w;
235	ep->w = w;
236	// XXX: Disable perspective correction until I figure out how to fix it
237	wRecip = 0x100000000;
238	w0 = 0x10000;
239	w1 = 0x10000;
240
241	ep->z = (((span->ep[1].z - span->ep[0].z) * xw) >> 32) + span->ep[0].z;
242
243	uint64_t r = (((span->ep[1].cr * (int64_t) w1 - span->ep[0].cr * (int64_t) w0) * xw) >> 32) + span->ep[0].cr * (int64_t) w0;
244	uint64_t g = (((span->ep[1].cg * (int64_t) w1 - span->ep[0].cg * (int64_t) w0) * xw) >> 32) + span->ep[0].cg * (int64_t) w0;
245	uint64_t b = (((span->ep[1].cb * (int64_t) w1 - span->ep[0].cb * (int64_t) w0) * xw) >> 32) + span->ep[0].cb * (int64_t) w0;
246	ep->cr = (r * wRecip) >> 48;
247	ep->cg = (g * wRecip) >> 48;
248	ep->cb = (b * wRecip) >> 48;
249
250	int32_t s = (((span->ep[1].s * (int64_t) w1 - span->ep[0].s * (int64_t) w0) * xw) >> 32) + span->ep[0].s * (int64_t) w0;
251	int32_t t = (((span->ep[1].t * (int64_t) w1 - span->ep[0].t * (int64_t) w0) * xw) >> 32) + span->ep[0].t * (int64_t) w0;
252	ep->s = (s * wRecip) >> 48;
253	ep->t = (t * wRecip) >> 48;
254}
255
256void DSGXSoftwareRendererCreate(struct DSGXSoftwareRenderer* renderer) {
257	renderer->d.init = DSGXSoftwareRendererInit;
258	renderer->d.reset = DSGXSoftwareRendererReset;
259	renderer->d.deinit = DSGXSoftwareRendererDeinit;
260	renderer->d.invalidateTex = DSGXSoftwareRendererInvalidateTex;
261	renderer->d.setRAM = DSGXSoftwareRendererSetRAM;
262	renderer->d.drawScanline = DSGXSoftwareRendererDrawScanline;
263	renderer->d.getScanline = DSGXSoftwareRendererGetScanline;
264}
265
266static void DSGXSoftwareRendererInit(struct DSGXRenderer* renderer) {
267	struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
268	DSGXSoftwarePolygonListInit(&softwareRenderer->activePolys, DS_GX_POLYGON_BUFFER_SIZE / 4);
269	DSGXSoftwareEdgeListInit(&softwareRenderer->activeEdges, DS_GX_POLYGON_BUFFER_SIZE);
270	DSGXSoftwareSpanListInit(&softwareRenderer->activeSpans, DS_GX_POLYGON_BUFFER_SIZE / 2);
271	softwareRenderer->bucket = anonymousMemoryMap(sizeof(*softwareRenderer->bucket) * DS_GX_POLYGON_BUFFER_SIZE);
272	softwareRenderer->scanlineCache = anonymousMemoryMap(sizeof(color_t) * DS_VIDEO_VERTICAL_PIXELS * DS_VIDEO_HORIZONTAL_PIXELS);
273}
274
275static void DSGXSoftwareRendererReset(struct DSGXRenderer* renderer) {
276	struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
277	softwareRenderer->flushPending = false;
278}
279
280static void DSGXSoftwareRendererDeinit(struct DSGXRenderer* renderer) {
281	struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
282	DSGXSoftwarePolygonListDeinit(&softwareRenderer->activePolys);
283	DSGXSoftwareEdgeListDeinit(&softwareRenderer->activeEdges);	
284	DSGXSoftwareSpanListDeinit(&softwareRenderer->activeSpans);
285	mappedMemoryFree(softwareRenderer->bucket, sizeof(*softwareRenderer->bucket) * DS_GX_POLYGON_BUFFER_SIZE);
286	mappedMemoryFree(softwareRenderer->scanlineCache, sizeof(color_t) * DS_VIDEO_VERTICAL_PIXELS * DS_VIDEO_HORIZONTAL_PIXELS);
287}
288
289static void DSGXSoftwareRendererInvalidateTex(struct DSGXRenderer* renderer, int slot) {
290	struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
291	// TODO
292}
293
294static void DSGXSoftwareRendererSetRAM(struct DSGXRenderer* renderer, struct DSGXVertex* verts, struct DSGXPolygon* polys, unsigned polyCount, bool wSort) {
295	struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
296
297	softwareRenderer->flushPending = true;
298	softwareRenderer->wSort = wSort;
299	softwareRenderer->verts = verts;
300	DSGXSoftwarePolygonListClear(&softwareRenderer->activePolys);
301	DSGXSoftwareEdgeListClear(&softwareRenderer->activeEdges);
302	unsigned i;
303	for (i = 0; i < polyCount; ++i) {
304		struct DSGXSoftwarePolygon* poly = DSGXSoftwarePolygonListAppend(&softwareRenderer->activePolys);
305		struct DSGXSoftwareEdge* edge = DSGXSoftwareEdgeListAppend(&softwareRenderer->activeEdges);
306		poly->poly = &polys[i];
307		poly->texFormat = DSGXTexParamsGetFormat(poly->poly->texParams);
308		poly->blendFormat = DSGXPolygonAttrsGetMode(poly->poly->polyParams);
309		poly->texW = 8 << DSGXTexParamsGetSSize(poly->poly->texParams);
310		poly->texH = 8 << DSGXTexParamsGetTSize(poly->poly->texParams);
311		switch (poly->texFormat) {
312		case 0:
313		case 7:
314			poly->texBase = NULL;
315			poly->palBase = NULL;
316			break;
317		case 2:
318			poly->texBase = &renderer->tex[DSGXTexParamsGetVRAMBase(poly->poly->texParams) >> VRAM_BLOCK_OFFSET][(DSGXTexParamsGetVRAMBase(poly->poly->texParams) << 2) & 0xFFFF];
319			poly->palBase = &renderer->texPal[poly->poly->palBase >> 12][(poly->poly->palBase << 2) & 0x1FFF];
320			break;
321		default:
322			poly->texBase = &renderer->tex[DSGXTexParamsGetVRAMBase(poly->poly->texParams) >> VRAM_BLOCK_OFFSET][(DSGXTexParamsGetVRAMBase(poly->poly->texParams) << 2) & 0xFFFF];
323			poly->palBase = &renderer->texPal[poly->poly->palBase >> 11][(poly->poly->palBase << 3) & 0x1FFF];
324			break;
325		}
326		edge->polyId = i;
327
328		struct DSGXVertex* v0 = &verts[poly->poly->vertIds[0]];
329		struct DSGXVertex* v1;
330
331		int v;
332		for (v = 1; v < poly->poly->verts; ++v) {
333			v1 = &verts[poly->poly->vertIds[v]];
334			if (v0->vy >= v1->vy) {
335				edge->y0 = SCREEN_SIZE - v0->vy;
336				edge->x0 = v0->vx;
337				edge->z0 = v0->vz;
338				edge->w0 = v0->vw;
339				_expandColor(v0->color, &edge->cr0, &edge->cg0, &edge->cb0);
340				edge->s0 = v0->vs;
341				edge->t0 = v0->vt;
342
343				edge->y1 = SCREEN_SIZE - v1->vy;
344				edge->x1 = v1->vx;
345				edge->z1 = v1->vz;
346				edge->w1 = v1->vw;
347				_expandColor(v1->color, &edge->cr1, &edge->cg1, &edge->cb1);
348				edge->s1 = v1->vs;
349				edge->t1 = v1->vt;
350			} else {
351				edge->y0 = SCREEN_SIZE - v1->vy;
352				edge->x0 = v1->vx;
353				edge->z0 = v1->vz;
354				edge->w0 = v1->vw;
355				_expandColor(v1->color, &edge->cr0, &edge->cg0, &edge->cb0);
356				edge->s0 = v1->vs;
357				edge->t0 = v1->vt;
358
359				edge->y1 = SCREEN_SIZE - v0->vy;
360				edge->x1 = v0->vx;
361				edge->z1 = v0->vz;
362				edge->w1 = v0->vw;
363				_expandColor(v0->color, &edge->cr1, &edge->cg1, &edge->cb1);
364				edge->s1 = v0->vs;
365				edge->t1 = v0->vt;
366			}
367
368			edge = DSGXSoftwareEdgeListAppend(&softwareRenderer->activeEdges);
369			edge->polyId = i;
370			v0 = v1;
371		}
372
373		v1 = &verts[poly->poly->vertIds[0]];
374		if (v0->vy >= v1->vy) {
375			edge->y0 = SCREEN_SIZE - v0->vy;
376			edge->x0 = v0->vx;
377			edge->z0 = v0->vz;
378			edge->w0 = v0->vw;
379			_expandColor(v0->color, &edge->cr0, &edge->cg0, &edge->cb0);
380			edge->s0 = v0->vs;
381			edge->t0 = v0->vt;
382
383			edge->y1 = SCREEN_SIZE - v1->vy;
384			edge->x1 = v1->vx;
385			edge->z1 = v1->vz;
386			edge->w1 = v1->vw;
387			_expandColor(v1->color, &edge->cr1, &edge->cg1, &edge->cb1);
388			edge->s1 = v1->vs;
389			edge->t1 = v1->vt;
390		} else {
391			edge->y0 = SCREEN_SIZE - v1->vy;
392			edge->x0 = v1->vx;
393			edge->w0 = v1->vw;
394			edge->z0 = v1->vz;
395			_expandColor(v1->color, &edge->cr0, &edge->cg0, &edge->cb0);
396			edge->s0 = v1->vs;
397			edge->t0 = v1->vt;
398
399			edge->y1 = SCREEN_SIZE - v0->vy;
400			edge->x1 = v0->vx;
401			edge->z1 = v0->vz;
402			edge->w1 = v0->vw;
403			_expandColor(v0->color, &edge->cr1, &edge->cg1, &edge->cb1);
404			edge->s1 = v0->vs;
405			edge->t1 = v0->vt;
406		}
407	}
408	qsort(DSGXSoftwareEdgeListGetPointer(&softwareRenderer->activeEdges, 0), DSGXSoftwareEdgeListSize(&softwareRenderer->activeEdges), sizeof(struct DSGXSoftwareEdge), _edgeSort);
409}
410
411static void DSGXSoftwareRendererDrawScanline(struct DSGXRenderer* renderer, int y) {
412	struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
413	if (!softwareRenderer->flushPending) {
414		return;
415	}
416	DSGXSoftwareSpanListClear(&softwareRenderer->activeSpans);
417	memset(softwareRenderer->bucket, 0, sizeof(*softwareRenderer->bucket) * DS_GX_POLYGON_BUFFER_SIZE);
418	int i;
419	for (i = DSGXSoftwareEdgeListSize(&softwareRenderer->activeEdges); i; --i) {
420		size_t idx = i - 1;
421		struct DSGXSoftwareEdge* edge = DSGXSoftwareEdgeListGetPointer(&softwareRenderer->activeEdges, idx);
422		if (edge->y1 >> 12 < y) {
423			DSGXSoftwareEdgeListShift(&softwareRenderer->activeEdges, idx, 1);
424			continue;
425		} else if (edge->y0 >> 12 > y) {
426			continue;
427		}
428
429		unsigned poly = edge->polyId;
430		struct DSGXSoftwareSpan* span = softwareRenderer->bucket[poly];
431		if (span && !span->ep[1].w) {
432			_edgeToSpan(span, edge, 1, y);
433			softwareRenderer->bucket[poly] = NULL;
434		} else if (!span) {
435			span = DSGXSoftwareSpanListAppend(&softwareRenderer->activeSpans);
436			memset(&span->ep[1], 0, sizeof(span->ep[1]));
437			span->poly = DSGXSoftwarePolygonListGetPointer(&softwareRenderer->activePolys, poly);
438			if (!_edgeToSpan(span, edge, 0, y)) {
439				// Horizontal line
440				DSGXSoftwareSpanListShift(&softwareRenderer->activeSpans, DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans) - 1, 1);
441			} else {
442				softwareRenderer->bucket[poly] = span;
443			}
444		}
445	}
446	qsort(DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, 0), DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans), sizeof(struct DSGXSoftwareSpan), _spanSort);
447
448	color_t* scanline = &softwareRenderer->scanlineCache[DS_VIDEO_HORIZONTAL_PIXELS * y];
449
450	int nextSpanX = DS_VIDEO_HORIZONTAL_PIXELS;
451	if (DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans)) {
452		nextSpanX = DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans) - 1)->ep[0].x;
453		nextSpanX >>= 12;
454	}
455	for (i = 0; i < DS_VIDEO_HORIZONTAL_PIXELS; ++i) {
456		struct DSGXSoftwareSpan* span = NULL;
457		struct DSGXSoftwareEndpoint ep;
458		int32_t depth = INT32_MAX;
459		scanline[i] = 0;
460		if (i >= nextSpanX) {
461			size_t nextSpanId = DSGXSoftwareSpanListSize(&softwareRenderer->activeSpans);
462			span = DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, nextSpanId - 1);
463			while (i > (span->ep[1].x >> 12) || !span->ep[1].x) {
464				DSGXSoftwareSpanListShift(&softwareRenderer->activeSpans, nextSpanId - 1, 1);
465				--nextSpanId;
466				if (!nextSpanId) {
467					nextSpanX = DS_VIDEO_HORIZONTAL_PIXELS;
468					span = NULL;
469					break;
470				}
471				span = DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, nextSpanId - 1);
472				nextSpanX = span->ep[0].x >> 12;
473			}
474			while (span && i > (span->ep[0].x >> 12)) {
475				if (i <= (span->ep[1].x >> 12)) {
476					_lerpEndpoint(span, &ep, i);
477					color_t color = _lookupColor(&ep, span->poly);
478					if (color & 0xF8000000) {
479						// TODO: Alpha
480						if (softwareRenderer->wSort) {
481							if (ep.w < depth) {
482								depth = ep.w;
483								scanline[i] = color;
484							}
485						} else {
486							if (ep.z < depth) {
487								depth = ep.z;
488								scanline[i] = color;
489							}
490						}
491					}
492				}
493				--nextSpanId;
494				if (!nextSpanId) {
495					break;
496				}
497				span = DSGXSoftwareSpanListGetPointer(&softwareRenderer->activeSpans, nextSpanId - 1);
498			}
499		}
500	}
501	if (y == DS_VIDEO_VERTICAL_PIXELS - 1) {
502		softwareRenderer->flushPending = false;
503	}
504}
505
506static void DSGXSoftwareRendererGetScanline(struct DSGXRenderer* renderer, int y, const color_t** output) {
507	struct DSGXSoftwareRenderer* softwareRenderer = (struct DSGXSoftwareRenderer*) renderer;
508	*output = &softwareRenderer->scanlineCache[DS_VIDEO_HORIZONTAL_PIXELS * y];
509}