all repos — mgba @ 3cd5e8d093a84fee62d1832d90a37e0bbc1870d9

mGBA Game Boy Advance Emulator

src/gba/renderers/video-software.c (view raw)

  1#include "video-software.h"
  2
  3#include "gba.h"
  4#include "gba-io.h"
  5
  6#include <stdlib.h>
  7#include <string.h>
  8
  9static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
 10static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
 11static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
 12static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
 13static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
 14
 15static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
 16static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
 17static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
 18
 19static void _compositeBackground(struct GBAVideoSoftwareRenderer* renderer, int offset, int entry, struct PixelFlags flags);
 20static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
 21static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* spritem, int y);
 22
 23static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
 24static inline uint16_t _brighten(uint16_t color, int y);
 25static inline uint16_t _darken(uint16_t color, int y);
 26static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB);
 27
 28static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer);
 29static int _backgroundComparator(const void* a, const void* b);
 30
 31void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
 32	renderer->d.init = GBAVideoSoftwareRendererInit;
 33	renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
 34	renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
 35	renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
 36	renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
 37
 38	renderer->d.turbo = 0;
 39	renderer->d.framesPending = 0;
 40
 41	renderer->sortedBg[0] = &renderer->bg[0];
 42	renderer->sortedBg[1] = &renderer->bg[1];
 43	renderer->sortedBg[2] = &renderer->bg[2];
 44	renderer->sortedBg[3] = &renderer->bg[3];
 45
 46	{
 47		pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 48		renderer->mutex = mutex;
 49		pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 50		renderer->upCond = cond;
 51		renderer->downCond = cond;
 52	}
 53}
 54
 55static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
 56	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 57	int i;
 58
 59	softwareRenderer->dispcnt.packed = 0x0080;
 60
 61	softwareRenderer->target1Obj = 0;
 62	softwareRenderer->target1Bd = 0;
 63	softwareRenderer->target2Obj = 0;
 64	softwareRenderer->target2Bd = 0;
 65	softwareRenderer->blendEffect = BLEND_NONE;
 66	memset(softwareRenderer->variantPalette, 0, sizeof(softwareRenderer->variantPalette));
 67
 68	softwareRenderer->blda = 0;
 69	softwareRenderer->bldb = 0;
 70	softwareRenderer->bldy = 0;
 71
 72	for (i = 0; i < 4; ++i) {
 73		struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
 74		bg->index = i;
 75		bg->enabled = 0;
 76		bg->priority = 0;
 77		bg->charBase = 0;
 78		bg->mosaic = 0;
 79		bg->multipalette = 0;
 80		bg->screenBase = 0;
 81		bg->overflow = 0;
 82		bg->size = 0;
 83		bg->target1 = 0;
 84		bg->target2 = 0;
 85		bg->x = 0;
 86		bg->y = 0;
 87		bg->refx = 0;
 88		bg->refy = 0;
 89		bg->dx = 256;
 90		bg->dmx = 0;
 91		bg->dy = 0;
 92		bg->dmy = 256;
 93		bg->sx = 0;
 94		bg->sy = 0;
 95	}
 96
 97	pthread_mutex_init(&softwareRenderer->mutex, 0);
 98	pthread_cond_init(&softwareRenderer->upCond, 0);
 99	pthread_cond_init(&softwareRenderer->downCond, 0);
100}
101
102static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
103	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
104
105	pthread_mutex_destroy(&softwareRenderer->mutex);
106	pthread_cond_destroy(&softwareRenderer->upCond);
107	pthread_cond_destroy(&softwareRenderer->downCond);
108}
109
110static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
111	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
112	switch (address) {
113	case REG_DISPCNT:
114		value &= 0xFFFB;
115		softwareRenderer->dispcnt.packed = value;
116		GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
117		break;
118	case REG_BG0CNT:
119		value &= 0xFFCF;
120		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
121		break;
122	case REG_BG1CNT:
123		value &= 0xFFCF;
124		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
125		break;
126	case REG_BG2CNT:
127		value &= 0xFFCF;
128		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
129		break;
130	case REG_BG3CNT:
131		value &= 0xFFCF;
132		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
133		break;
134	case REG_BG0HOFS:
135		value &= 0x01FF;
136		softwareRenderer->bg[0].x = value;
137		break;
138	case REG_BG0VOFS:
139		value &= 0x01FF;
140		softwareRenderer->bg[0].y = value;
141		break;
142	case REG_BG1HOFS:
143		value &= 0x01FF;
144		softwareRenderer->bg[1].x = value;
145		break;
146	case REG_BG1VOFS:
147		value &= 0x01FF;
148		softwareRenderer->bg[1].y = value;
149		break;
150	case REG_BG2HOFS:
151		value &= 0x01FF;
152		softwareRenderer->bg[2].x = value;
153		break;
154	case REG_BG2VOFS:
155		value &= 0x01FF;
156		softwareRenderer->bg[2].y = value;
157		break;
158	case REG_BG3HOFS:
159		value &= 0x01FF;
160		softwareRenderer->bg[3].x = value;
161		break;
162	case REG_BG3VOFS:
163		value &= 0x01FF;
164		softwareRenderer->bg[3].y = value;
165		break;
166	case REG_BLDCNT:
167		GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
168		break;
169	case REG_BLDALPHA:
170		softwareRenderer->blda = value & 0x1F;
171		if (softwareRenderer->blda > 0x10) {
172			softwareRenderer->blda = 0x10;
173		}
174		softwareRenderer->bldb = (value >> 8) & 0x1F;
175		if (softwareRenderer->bldb > 0x10) {
176			softwareRenderer->bldb = 0x10;
177		}
178		break;
179	case REG_BLDY:
180		softwareRenderer->bldy = value & 0x1F;
181		if (softwareRenderer->bldy > 0x10) {
182			softwareRenderer->bldy = 0x10;
183		}
184		_updatePalettes(softwareRenderer);
185		break;
186	default:
187		GBALog(GBA_LOG_STUB, "Stub video register write: %03x", address);
188	}
189	return value;
190}
191
192static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
193	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
194	uint16_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
195	if (softwareRenderer->dispcnt.forcedBlank) {
196		for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
197			row[x] = 0x7FFF;
198		}
199		return;
200	}
201
202	memset(softwareRenderer->flags, 0, sizeof(softwareRenderer->flags));
203	softwareRenderer->row = row;
204
205	if (softwareRenderer->dispcnt.objEnable) {
206		for (int i = 0; i < 128; ++i) {
207			struct GBAObj* sprite = &renderer->oam->obj[i];
208			if (sprite->transformed) {
209				_drawTransformedSprite(softwareRenderer, &renderer->oam->tobj[i], y);
210			} else if (!sprite->disable) {
211				_drawSprite(softwareRenderer, sprite, y);
212			}
213		}
214	}
215
216	for (int i = 0; i < 4; ++i) {
217		if (softwareRenderer->sortedBg[i]->enabled) {
218			_drawBackgroundMode0(softwareRenderer, softwareRenderer->sortedBg[i], y);
219		}
220	}
221}
222
223static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
224	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
225
226	pthread_mutex_lock(&softwareRenderer->mutex);
227	renderer->framesPending++;
228	pthread_cond_broadcast(&softwareRenderer->upCond);
229	if (!renderer->turbo) {
230		pthread_cond_wait(&softwareRenderer->downCond, &softwareRenderer->mutex);
231	}
232	pthread_mutex_unlock(&softwareRenderer->mutex);
233}
234
235static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
236	renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
237	renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
238	renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
239	renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
240}
241
242static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
243	union GBARegisterBGCNT reg = { .packed = value };
244	bg->priority = reg.priority;
245	bg->charBase = reg.charBase << 14;
246	bg->mosaic = reg.mosaic;
247	bg->multipalette = reg.multipalette;
248	bg->screenBase = reg.screenBase << 11;
249	bg->overflow = reg.overflow;
250	bg->size = reg.size;
251
252	_sortBackgrounds(renderer);
253}
254
255static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
256	union {
257		struct {
258			unsigned target1Bg0 : 1;
259			unsigned target1Bg1 : 1;
260			unsigned target1Bg2 : 1;
261			unsigned target1Bg3 : 1;
262			unsigned target1Obj : 1;
263			unsigned target1Bd : 1;
264			enum BlendEffect effect : 2;
265			unsigned target2Bg0 : 1;
266			unsigned target2Bg1 : 1;
267			unsigned target2Bg2 : 1;
268			unsigned target2Bg3 : 1;
269			unsigned target2Obj : 1;
270			unsigned target2Bd : 1;
271		};
272		uint16_t packed;
273	} bldcnt = { .packed = value };
274
275	enum BlendEffect oldEffect = renderer->blendEffect;
276
277	renderer->bg[0].target1 = bldcnt.target1Bg0;
278	renderer->bg[1].target1 = bldcnt.target1Bg1;
279	renderer->bg[2].target1 = bldcnt.target1Bg2;
280	renderer->bg[3].target1 = bldcnt.target1Bg3;
281	renderer->bg[0].target2 = bldcnt.target2Bg0;
282	renderer->bg[1].target2 = bldcnt.target2Bg1;
283	renderer->bg[2].target2 = bldcnt.target2Bg2;
284	renderer->bg[3].target2 = bldcnt.target2Bg3;
285
286	renderer->blendEffect = bldcnt.effect;
287	renderer->target1Obj = bldcnt.target1Obj;
288	renderer->target1Bd = bldcnt.target1Bd;
289	renderer->target2Obj = bldcnt.target2Obj;
290	renderer->target2Bd = bldcnt.target2Bd;
291
292	if (oldEffect != renderer->blendEffect) {
293		_updatePalettes(renderer);
294	}
295}
296
297static void _compositeBackground(struct GBAVideoSoftwareRenderer* renderer, int offset, int entry, struct PixelFlags flags) {
298	if (renderer->flags[offset].isSprite && flags.priority >= renderer->flags[offset].priority) {
299		if (renderer->flags[offset].target1) {
300			if (flags.target2) {
301				renderer->row[offset] = _mix(renderer->bldb, renderer->d.palette[entry], renderer->blda, renderer->row[offset]);
302			}
303		}
304		renderer->flags[offset].finalized = 1;
305		renderer->flags[offset].written = 1;
306		return;
307	}
308	if (renderer->blendEffect == BLEND_NONE || (!flags.target1 && !flags.target2)) {
309		renderer->row[offset] = renderer->d.palette[entry];
310		renderer->flags[offset].finalized = 1;
311	} else if (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN) {
312		renderer->row[offset] = renderer->variantPalette[entry];
313		renderer->flags[offset].finalized = 1;
314	} else if (renderer->blendEffect == BLEND_ALPHA) {
315		if (renderer->flags[offset].written) {
316			if (renderer->flags[offset].target1 && flags.target2) {
317				renderer->row[offset] = _mix(renderer->bldb, renderer->d.palette[entry], renderer->blda, renderer->row[offset]);
318			}
319			renderer->flags[offset].finalized = 1;
320		} else if (renderer->flags[offset].isSprite && renderer->flags[offset].target2 && flags.target1) {
321			renderer->row[offset] = _mix(renderer->blda, renderer->d.palette[entry], renderer->bldb, renderer->row[offset]);
322			renderer->flags[offset].finalized = 1;
323		} else {
324			renderer->row[offset] = renderer->d.palette[entry];
325			renderer->flags[offset].target1 = flags.target1;
326		}
327	}
328	renderer->flags[offset].written = 1;
329}
330
331static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
332	int start = 0;
333	int end = VIDEO_HORIZONTAL_PIXELS;
334	int inX = start + background->x;
335	int inY = y + background->y;
336	union GBATextMapData mapData;
337
338	unsigned yBase = inY & 0xF8;
339	if (background->size & 2) {
340		yBase += inY & 0x100;
341	} else if (background->size == 3) {
342		yBase += (inY & 0x100) << 1;
343	}
344
345	int localX;
346	int localY;
347
348	unsigned xBase;
349
350	uint32_t screenBase;
351	uint32_t charBase;
352
353	for (int outX = start; outX < end; ++outX) {
354		if (renderer->flags[outX].finalized) {
355			continue;
356		}
357		localX = outX + inX;
358		xBase = localX & 0xF8;
359		if (background->size & 1) {
360			xBase += (localX & 0x100) << 5;
361		}
362		screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2);
363		mapData.packed = renderer->d.vram[screenBase];
364		if (!mapData.vflip) {
365			localY = inY & 0x7;
366		} else {
367			localY = 7 - (inY & 0x7);
368		}
369		if (!mapData.hflip) {
370			localX = localX & 0x7;
371		} else {
372			localX = 7 - (localX & 0x7);
373		}
374		charBase = ((background->charBase + (mapData.tile << 5)) >> 1) + (localY << 1) + ((localX >> 2) & 1);
375		uint16_t tileData = renderer->d.vram[charBase];
376		tileData >>= (localX & 0x3) << 2;
377		if (tileData & 0xF) {
378			struct PixelFlags flags = {
379				.target1 = background->target1,
380				.target2 = background->target2,
381				.priority = background->priority
382			};
383			_compositeBackground(renderer, outX, (tileData & 0xF) | (mapData.palette << 4), flags);
384		}
385	}
386}
387
388static const int _objSizes[32] = {
389	8, 8,
390	16, 16,
391	32, 32,
392	64, 64,
393	16, 8,
394	32, 8,
395	32, 16,
396	64, 32,
397	8, 16,
398	8, 32,
399	16, 32,
400	32, 64,
401	0, 0,
402	0, 0,
403	0, 0,
404	0, 0
405};
406
407static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
408	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
409	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
410	if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
411		return;
412	}
413	(void)(renderer);
414	struct PixelFlags flags = {
415		.priority = sprite->priority,
416		.isSprite = 1,
417		.target1 = renderer->target1Obj || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
418		.target2 = renderer->target2Obj
419	};
420	int x = sprite->x;
421	int inY = y - sprite->y;
422	if (sprite->y + height - 256 >= 0) {
423		inY += 256;
424	}
425	if (sprite->vflip) {
426		inY = height - inY - 1;
427	}
428	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
429	unsigned yBase = (inY & ~0x7) * 0x80 + (inY & 0x7) * 4;
430	for (int outX = x >= 0 ? x : 0; outX < x + width && outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
431		int inX = outX - x;
432		if (sprite->hflip) {
433			inX = width - inX - 1;
434		}
435		if (renderer->flags[outX].isSprite) {
436			continue;
437		}
438		unsigned xBase = (inX & ~0x7) * 4 + ((inX >> 1) & 2);
439		uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
440		tileData = (tileData >> ((inX & 3) << 2)) & 0xF;
441		if (tileData) {
442			if (!renderer->target1Obj) {
443				renderer->row[outX] = renderer->d.palette[0x100 | tileData | (sprite->palette << 4)];
444			} else {
445				renderer->row[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
446			}
447			renderer->flags[outX] = flags;
448		}
449	}
450}
451
452static void _drawTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y) {
453	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
454	int totalWidth = width << sprite->doublesize;
455	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
456	int totalHeight = height << sprite->doublesize;
457	if ((y < sprite->y && (sprite->y + totalHeight - 256 < 0 || y >= sprite->y + totalHeight - 256)) || y >= sprite->y + totalHeight) {
458		return;
459	}
460	(void)(renderer);
461	struct PixelFlags flags = {
462		.priority = sprite->priority,
463		.isSprite = 1,
464		.target1 = renderer->target1Obj || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
465		.target2 = renderer->target2Obj
466	};
467	int x = sprite->x;
468	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
469	struct GBAOAMMatrix* mat = &renderer->d.oam->mat[sprite->matIndex];
470	for (int outX = x >= 0 ? x : 0; outX < x + totalWidth && outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
471		if (renderer->flags[outX].isSprite) {
472			continue;
473		}
474		int inY = y - sprite->y;
475		int inX = outX - x;
476		int localX = ((mat->a * (inX - (totalWidth >> 1)) + mat->b * (inY - (totalHeight >> 1))) >> 8) + (width >> 1);
477		int localY = ((mat->c * (inX - (totalWidth >> 1)) + mat->d * (inY - (totalHeight >> 1))) >> 8) + (height >> 1);
478
479		if (localX < 0 || localX >= width || localY < 0 || localY >= height) {
480			continue;
481		}
482
483		unsigned yBase = (localY & ~0x7) * 0x80 + (localY & 0x7) * 4;
484		unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
485		uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
486		tileData = (tileData >> ((localX & 3) << 2)) & 0xF;
487		if (tileData) {
488			if (!renderer->target1Obj) {
489				renderer->row[outX] = renderer->d.palette[0x100 | tileData | (sprite->palette << 4)];
490			} else {
491				renderer->row[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
492			}
493			renderer->flags[outX] = flags;
494		}
495	}
496}
497
498static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
499	if (renderer->blendEffect == BLEND_BRIGHTEN) {
500		for (int i = 0; i < 512; ++i) {
501			renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
502		}
503	} else if (renderer->blendEffect == BLEND_DARKEN) {
504		for (int i = 0; i < 512; ++i) {
505			renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
506		}
507	} else {
508		for (int i = 0; i < 512; ++i) {
509			renderer->variantPalette[i] = renderer->d.palette[i];
510		}
511	}
512}
513
514static inline uint16_t _brighten(uint16_t c, int y) {
515	union GBAColor color = { .packed = c };
516	color.r = color.r + ((31 - color.r) * y) / 16;
517	color.g = color.g + ((31 - color.g) * y) / 16;
518	color.b = color.b + ((31 - color.b) * y) / 16;
519	return color.packed;
520}
521
522static inline uint16_t _darken(uint16_t c, int y) {
523	union GBAColor color = { .packed = c };
524	color.r = color.r - (color.r * y) / 16;
525	color.g = color.g - (color.g * y) / 16;
526	color.b = color.b - (color.b * y) / 16;
527	return color.packed;
528}
529
530static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
531	union GBAColor ca = { .packed = colorA };
532	union GBAColor cb = { .packed = colorB };
533
534	int r = (ca.r * weightA + cb.r * weightB) / 16;
535	ca.r = r > 0x1F ? 0x1F : r;
536
537	int g = (ca.g * weightA + cb.g * weightB) / 16;
538	ca.g = g > 0x1F ? 0x1F : g;
539
540	int b = (ca.b * weightA + cb.b * weightB) / 16;
541	ca.b = b > 0x1F ? 0x1F : b;
542
543	return ca.packed;
544}
545
546static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
547	qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
548}
549
550static int _backgroundComparator(const void* a, const void* b) {
551	const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
552	const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
553	if (bgA->priority != bgB->priority) {
554		return bgA->priority - bgB->priority;
555	} else {
556		return bgA->index - bgB->index;
557	}
558}