all repos — mgba @ dfd360bfbb9355f13f2cc896f5da9a26f5094033

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