all repos — mgba @ 87e5973039cbb6d6bdcd8261445c9455a0df461d

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