all repos — mgba @ 2e9a64a26ee99cc1105bc8967b67f66e896756c8

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_DRAW_PIXEL_256 \
356	if (tileData & 0xFF && !renderer->flags[outX].finalized) { \
357		_composite(renderer, outX, renderer->normalPalette[tileData & 0xFF], flags); \
358	} \
359	tileData >>= 8;
360
361#define BACKGROUND_TEXT_SELECT_CHARACTER \
362	localX = tileX * 8 + inX; \
363	xBase = localX & 0xF8; \
364	if (background->size & 1) { \
365		xBase += (localX & 0x100) << 5; \
366	} \
367	screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2); \
368	mapData.packed = renderer->d.vram[screenBase]; \
369	if (!mapData.vflip) { \
370		localY = inY & 0x7; \
371	} else { \
372		localY = 7 - (inY & 0x7); \
373	} \
374
375static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
376	int start = renderer->start;
377	int end = renderer->end;
378	int inX = start + background->x;
379	int inY = y + background->y;
380	union GBATextMapData mapData;
381
382	unsigned yBase = inY & 0xF8;
383	if (background->size & 2) {
384		yBase += inY & 0x100;
385	} else if (background->size == 3) {
386		yBase += (inY & 0x100) << 1;
387	}
388
389	int localX;
390	int localY;
391
392	unsigned xBase;
393
394	struct PixelFlags flags = {
395		.target1 = background->target1 && renderer->blendEffect == BLEND_ALPHA,
396		.target2 = background->target2,
397		.priority = background->priority
398	};
399
400	uint32_t screenBase;
401	uint32_t charBase;
402
403	int outX = 0;
404	int tileX = 0;
405	if (inX & 0x7) {
406		int end = 0x8 - (inX & 0x7);
407		uint32_t tileData;
408		BACKGROUND_TEXT_SELECT_CHARACTER;
409		charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
410		tileData = ((uint32_t*)renderer->d.vram)[charBase];
411		tileData >>= 4 * (inX & 0x7);
412		for (outX = 0; outX < end; ++outX) {
413			BACKGROUND_DRAW_PIXEL_16;
414		}
415
416		tileX = 30;
417		BACKGROUND_TEXT_SELECT_CHARACTER;
418		charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
419		tileData = ((uint32_t*)renderer->d.vram)[charBase];
420		for (outX = VIDEO_HORIZONTAL_PIXELS - 8 + end; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
421			BACKGROUND_DRAW_PIXEL_16;
422		}
423
424		tileX = 1;
425		outX = end;
426	}
427
428	if (!background->multipalette) {
429		for (tileX; tileX < 30; ++tileX) {
430			BACKGROUND_TEXT_SELECT_CHARACTER;
431			charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
432			uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase];
433			if (tileData) {
434				if (!mapData.hflip) {
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					BACKGROUND_DRAW_PIXEL_16;
444					++outX;
445					BACKGROUND_DRAW_PIXEL_16;
446					++outX;
447					BACKGROUND_DRAW_PIXEL_16;
448					++outX;
449					BACKGROUND_DRAW_PIXEL_16;
450					++outX;
451				} else {
452					outX += 7;
453					BACKGROUND_DRAW_PIXEL_16;
454					--outX;
455					BACKGROUND_DRAW_PIXEL_16;
456					--outX;
457					BACKGROUND_DRAW_PIXEL_16;
458					--outX;
459					BACKGROUND_DRAW_PIXEL_16;
460					--outX;
461					BACKGROUND_DRAW_PIXEL_16;
462					--outX;
463					BACKGROUND_DRAW_PIXEL_16;
464					--outX;
465					BACKGROUND_DRAW_PIXEL_16;
466					--outX;
467					BACKGROUND_DRAW_PIXEL_16;
468					outX += 8;
469				}
470			} else {
471				outX += 8;
472			}
473		}
474	} else {
475		for (tileX; tileX < 30; ++tileX) {
476			BACKGROUND_TEXT_SELECT_CHARACTER;
477			charBase = ((background->charBase + (mapData.tile << 6)) >> 2) + (localY << 1);
478			if (!mapData.hflip) {
479				uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase];
480				if (tileData) {
481						BACKGROUND_DRAW_PIXEL_256;
482						++outX;
483						BACKGROUND_DRAW_PIXEL_256;
484						++outX;
485						BACKGROUND_DRAW_PIXEL_256;
486						++outX;
487						BACKGROUND_DRAW_PIXEL_256;
488						++outX;
489				} else {
490					outX += 4;
491				}
492				tileData = ((uint32_t*)renderer->d.vram)[charBase + 1];
493				if (tileData) {
494						BACKGROUND_DRAW_PIXEL_256;
495						++outX;
496						BACKGROUND_DRAW_PIXEL_256;
497						++outX;
498						BACKGROUND_DRAW_PIXEL_256;
499						++outX;
500						BACKGROUND_DRAW_PIXEL_256;
501						++outX;
502				} else {
503					outX += 4;
504				}
505			} else {
506				uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase + 1];
507				if (tileData) {
508					outX += 3;
509					BACKGROUND_DRAW_PIXEL_256;
510					--outX;
511					BACKGROUND_DRAW_PIXEL_256;
512					--outX;
513					BACKGROUND_DRAW_PIXEL_256;
514					--outX;
515					BACKGROUND_DRAW_PIXEL_256;
516					outX += 4;
517				} else {
518					outX += 4;
519				}
520				tileData = ((uint32_t*)renderer->d.vram)[charBase];
521				if (tileData) {
522					outX += 3;
523					BACKGROUND_DRAW_PIXEL_256;
524					--outX;
525					BACKGROUND_DRAW_PIXEL_256;
526					--outX;
527					BACKGROUND_DRAW_PIXEL_256;
528					--outX;
529					BACKGROUND_DRAW_PIXEL_256;
530					outX += 4;
531				} else {
532					outX += 4;
533				}
534			}
535		}
536	}
537}
538
539static const int _objSizes[32] = {
540	8, 8,
541	16, 16,
542	32, 32,
543	64, 64,
544	16, 8,
545	32, 8,
546	32, 16,
547	64, 32,
548	8, 16,
549	8, 32,
550	16, 32,
551	32, 64,
552	0, 0,
553	0, 0,
554	0, 0,
555	0, 0
556};
557
558static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
559	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
560	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
561	int start = renderer->start;
562	int end = renderer->end;
563	if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
564		return;
565	}
566	struct PixelFlags flags = {
567		.priority = sprite->priority,
568		.isSprite = 1,
569		.target1 = (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
570		.target2 = renderer->target2Obj
571	};
572	int x = sprite->x;
573	int inY = y - sprite->y;
574	if (sprite->y + height - 256 >= 0) {
575		inY += 256;
576	}
577	if (sprite->vflip) {
578		inY = height - inY - 1;
579	}
580	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
581	unsigned yBase = (inY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (inY & 0x7) * 4;
582	for (int outX = x >= start ? x : start; outX < x + width && outX < end; ++outX) {
583		int inX = outX - x;
584		if (sprite->hflip) {
585			inX = width - inX - 1;
586		}
587		if (renderer->flags[outX].isSprite) {
588			continue;
589		}
590		unsigned xBase = (inX & ~0x7) * 4 + ((inX >> 1) & 2);
591		uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
592		tileData = (tileData >> ((inX & 3) << 2)) & 0xF;
593		if (tileData) {
594			if (renderer->blendEffect == BLEND_NONE || renderer->blendEffect == BLEND_ALPHA || !renderer->target1Obj) {
595				renderer->spriteLayer[outX] = renderer->normalPalette[0x100 | tileData | (sprite->palette << 4)];
596			} else {
597				renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
598			}
599			renderer->flags[outX] = flags;
600		}
601	}
602}
603
604static void _drawTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y) {
605	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
606	int totalWidth = width << sprite->doublesize;
607	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
608	int totalHeight = height << sprite->doublesize;
609	int start = renderer->start;
610	int end = renderer->end;
611	if ((y < sprite->y && (sprite->y + totalHeight - 256 < 0 || y >= sprite->y + totalHeight - 256)) || y >= sprite->y + totalHeight) {
612		return;
613	}
614	struct PixelFlags flags = {
615		.priority = sprite->priority,
616		.isSprite = 1,
617		.target1 = (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
618		.target2 = renderer->target2Obj
619	};
620	int x = sprite->x;
621	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
622	struct GBAOAMMatrix* mat = &renderer->d.oam->mat[sprite->matIndex];
623	for (int outX = x >= start ? x : start; outX < x + totalWidth && outX < end; ++outX) {
624		if (renderer->flags[outX].isSprite) {
625			continue;
626		}
627		int inY = y - sprite->y;
628		int inX = outX - x;
629		int localX = ((mat->a * (inX - (totalWidth >> 1)) + mat->b * (inY - (totalHeight >> 1))) >> 8) + (width >> 1);
630		int localY = ((mat->c * (inX - (totalWidth >> 1)) + mat->d * (inY - (totalHeight >> 1))) >> 8) + (height >> 1);
631
632		if (localX < 0 || localX >= width || localY < 0 || localY >= height) {
633			continue;
634		}
635
636		unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (localY & 0x7) * 4;
637		unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
638		uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
639		tileData = (tileData >> ((localX & 3) << 2)) & 0xF;
640		if (tileData) {
641			if (renderer->blendEffect == BLEND_NONE || renderer->blendEffect == BLEND_ALPHA || !renderer->target1Obj) {
642				renderer->spriteLayer[outX] = renderer->normalPalette[0x100 | tileData | (sprite->palette << 4)];
643			} else {
644				renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
645			}
646			renderer->flags[outX] = flags;
647		}
648	}
649}
650
651static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
652	if (renderer->blendEffect == BLEND_BRIGHTEN) {
653		for (int i = 0; i < 512; ++i) {
654			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
655		}
656	} else if (renderer->blendEffect == BLEND_DARKEN) {
657		for (int i = 0; i < 512; ++i) {
658			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
659		}
660	} else {
661		for (int i = 0; i < 512; ++i) {
662			renderer->variantPalette[i] = renderer->normalPalette[i];
663		}
664	}
665}
666
667static inline uint32_t _brighten(uint32_t color, int y) {
668	uint32_t c = 0;
669	uint32_t a;
670	a = color & 0xF8;
671	c |= (a + ((0xF8 - a) * y) / 16) & 0xF8;
672
673	a = color & 0xF800;
674	c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
675
676	a = color & 0xF80000;
677	c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000;
678	return c;
679}
680
681static inline uint32_t _darken(uint32_t color, int y) {
682	uint32_t c = 0;
683	uint32_t a;
684	a = color & 0xF8;
685	c |= (a - (a * y) / 16) & 0xF8;
686
687	a = color & 0xF800;
688	c |= (a - (a * y) / 16) & 0xF800;
689
690	a = color & 0xF80000;
691	c |= (a - (a * y) / 16) & 0xF80000;
692	return c;
693}
694
695static uint32_t _mix(int weightA, uint32_t colorA, int weightB, uint32_t colorB) {
696	uint32_t c = 0;
697	uint32_t a, b;
698	a = colorA & 0xF8;
699	b = colorB & 0xF8;
700	c |= ((a * weightA + b * weightB) / 16) & 0x1F8;
701	if (c & 0x00000100) {
702		c = 0x000000F8;
703	}
704
705	a = colorA & 0xF800;
706	b = colorB & 0xF800;
707	c |= ((a * weightA + b * weightB) / 16) & 0x1F800;
708	if (c & 0x00010000) {
709		c = (c & 0x000000F8) | 0x0000F800;
710	}
711
712	a = colorA & 0xF80000;
713	b = colorB & 0xF80000;
714	c |= ((a * weightA + b * weightB) / 16) & 0x1F80000;
715	if (c & 0x01000000) {
716		c = (c & 0x0000F8F8) | 0x00F80000;
717	}
718	return c;
719}