all repos — mgba @ 6c2f7b3b731e672c0df42b9ffd6fefdd316c729d

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