all repos — mgba @ 9b5bda237a75d6d82fafe5a8406811bd6b017b9d

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