all repos — mgba @ 5fd2f0c43adc426efd366e61de3486518fb33eec

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