all repos — mgba @ a635f4de4db88f8fddd4464225edb925bde8da95

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	if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
198		softwareRenderer->variantPalette[address >> 1] = _brighten(color32, softwareRenderer->bldy);
199	} else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
200		softwareRenderer->variantPalette[address >> 1] = _darken(color32, softwareRenderer->bldy);
201	}
202}
203
204static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
205	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
206	uint32_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
207	if (softwareRenderer->dispcnt.forcedBlank) {
208		for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
209			row[x] = GBA_COLOR_WHITE;
210		}
211		return;
212	} else {
213		uint32_t backdrop;
214		if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA) {
215			backdrop = softwareRenderer->normalPalette[0];
216		} else {
217			backdrop = softwareRenderer->variantPalette[0];
218		}
219		for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
220			row[x] = backdrop;
221		}
222	}
223
224	memset(softwareRenderer->flags, 0, sizeof(softwareRenderer->flags));
225	memset(softwareRenderer->spriteLayer, 0, sizeof(softwareRenderer->spriteLayer));
226	softwareRenderer->row = row;
227
228	softwareRenderer->start = 0;
229	softwareRenderer->end = VIDEO_HORIZONTAL_PIXELS;
230	_drawScanline(softwareRenderer, y);
231}
232
233static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
234	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
235
236	pthread_mutex_lock(&softwareRenderer->mutex);
237	renderer->framesPending++;
238	pthread_cond_broadcast(&softwareRenderer->upCond);
239	if (!renderer->turbo) {
240		pthread_cond_wait(&softwareRenderer->downCond, &softwareRenderer->mutex);
241	}
242	pthread_mutex_unlock(&softwareRenderer->mutex);
243}
244
245static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
246	renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
247	renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
248	renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
249	renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
250}
251
252static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
253	(void)(renderer);
254	union GBARegisterBGCNT reg = { .packed = value };
255	bg->priority = reg.priority;
256	bg->charBase = reg.charBase << 14;
257	bg->mosaic = reg.mosaic;
258	bg->multipalette = reg.multipalette;
259	bg->screenBase = reg.screenBase << 11;
260	bg->overflow = reg.overflow;
261	bg->size = reg.size;
262}
263
264static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
265	union {
266		struct {
267			unsigned target1Bg0 : 1;
268			unsigned target1Bg1 : 1;
269			unsigned target1Bg2 : 1;
270			unsigned target1Bg3 : 1;
271			unsigned target1Obj : 1;
272			unsigned target1Bd : 1;
273			enum BlendEffect effect : 2;
274			unsigned target2Bg0 : 1;
275			unsigned target2Bg1 : 1;
276			unsigned target2Bg2 : 1;
277			unsigned target2Bg3 : 1;
278			unsigned target2Obj : 1;
279			unsigned target2Bd : 1;
280		};
281		uint16_t packed;
282	} bldcnt = { .packed = value };
283
284	enum BlendEffect oldEffect = renderer->blendEffect;
285
286	renderer->bg[0].target1 = bldcnt.target1Bg0;
287	renderer->bg[1].target1 = bldcnt.target1Bg1;
288	renderer->bg[2].target1 = bldcnt.target1Bg2;
289	renderer->bg[3].target1 = bldcnt.target1Bg3;
290	renderer->bg[0].target2 = bldcnt.target2Bg0;
291	renderer->bg[1].target2 = bldcnt.target2Bg1;
292	renderer->bg[2].target2 = bldcnt.target2Bg2;
293	renderer->bg[3].target2 = bldcnt.target2Bg3;
294
295	renderer->blendEffect = bldcnt.effect;
296	renderer->target1Obj = bldcnt.target1Obj;
297	renderer->target1Bd = bldcnt.target1Bd;
298	renderer->target2Obj = bldcnt.target2Obj;
299	renderer->target2Bd = bldcnt.target2Bd;
300
301	if (oldEffect != renderer->blendEffect) {
302		_updatePalettes(renderer);
303	}
304}
305
306static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
307	uint32_t* row = renderer->row;
308
309	int i;
310	if (renderer->dispcnt.objEnable) {
311		for (i = 0; i < 128; ++i) {
312			struct GBAObj* sprite = &renderer->d.oam->obj[i];
313			if (sprite->transformed) {
314				_drawTransformedSprite(renderer, &renderer->d.oam->tobj[i], y);
315			} else if (!sprite->disable) {
316				_drawSprite(renderer, sprite, y);
317			}
318		}
319	}
320
321	int priority;
322	for (priority = 0; priority < 4; ++priority) {
323		for (i = 0; i < 4; ++i) {
324			if (renderer->bg[i].enabled && renderer->bg[i].priority == priority) {
325				_drawBackgroundMode0(renderer, &renderer->bg[i], y);
326			}
327		}
328	}
329}
330
331static void _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, uint32_t color, struct PixelFlags flags) {
332	struct PixelFlags currentFlags = renderer->flags[offset];
333	if (currentFlags.isSprite && flags.priority >= currentFlags.priority) {
334		if (currentFlags.target1) {
335			if (currentFlags.written && currentFlags.target2) {
336				renderer->row[offset] = _mix(renderer->blda, renderer->row[offset], renderer->bldb, renderer->spriteLayer[offset]);
337			} else if (flags.target2) {
338				renderer->row[offset] = _mix(renderer->bldb, color, renderer->blda, renderer->spriteLayer[offset]);
339			}
340		} else if (!currentFlags.written) {
341			renderer->row[offset] = renderer->spriteLayer[offset];
342		}
343		renderer->flags[offset].finalized = 1;
344		return;
345	}
346	if (renderer->blendEffect != BLEND_ALPHA) {
347		renderer->row[offset] = color;
348		renderer->flags[offset].finalized = 1;
349	} else if (renderer->blendEffect == BLEND_ALPHA) {
350		if (currentFlags.written) {
351			if (currentFlags.target1 && flags.target2) {
352				renderer->row[offset] = _mix(renderer->bldb, color, renderer->blda, renderer->row[offset]);
353			}
354			renderer->flags[offset].finalized = 1;
355		} else {
356			renderer->row[offset] = color;
357			renderer->flags[offset].target1 = flags.target1;
358		}
359	}
360	renderer->flags[offset].written = 1;
361}
362
363#define BACKGROUND_DRAW_PIXEL_16_NORMAL \
364	if (tileData & 0xF && !renderer->flags[outX].finalized) { \
365		_composite(renderer, outX, renderer->normalPalette[tileData & 0xF | (mapData.palette << 4)], flags); \
366	} \
367	tileData >>= 4;
368
369#define BACKGROUND_DRAW_PIXEL_16_VARIANT \
370	if (tileData & 0xF && !renderer->flags[outX].finalized) { \
371		_composite(renderer, outX, renderer->variantPalette[tileData & 0xF | (mapData.palette << 4)], flags); \
372	} \
373	tileData >>= 4;
374
375#define BACKGROUND_DRAW_PIXEL_256_NORMAL \
376	if (tileData & 0xFF && !renderer->flags[outX].finalized) { \
377		_composite(renderer, outX, renderer->normalPalette[tileData & 0xFF], flags); \
378	} \
379	tileData >>= 8;
380
381#define BACKGROUND_DRAW_PIXEL_256_VARIANT \
382	if (tileData & 0xFF && !renderer->flags[outX].finalized) { \
383		_composite(renderer, outX, renderer->variantPalette[tileData & 0xFF], flags); \
384	} \
385	tileData >>= 8;
386
387#define BACKGROUND_TEXT_SELECT_CHARACTER \
388	localX = tileX * 8 + inX; \
389	xBase = localX & 0xF8; \
390	if (background->size & 1) { \
391		xBase += (localX & 0x100) << 5; \
392	} \
393	screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2); \
394	mapData.packed = renderer->d.vram[screenBase]; \
395	if (!mapData.vflip) { \
396		localY = inY & 0x7; \
397	} else { \
398		localY = 7 - (inY & 0x7); \
399	}
400
401#define BACKGROUND_MODE_0_TILE_16_LOOP(TYPE) \
402	for (tileX; tileX < 30; ++tileX) { \
403		BACKGROUND_TEXT_SELECT_CHARACTER; \
404		charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY; \
405		uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
406		if (tileData) { \
407			if (!mapData.hflip) { \
408				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
409				++outX; \
410				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
411				++outX; \
412				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
413				++outX; \
414				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
415				++outX; \
416				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
417				++outX; \
418				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
419				++outX; \
420				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
421				++outX; \
422				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
423				++outX; \
424			} else { \
425				outX += 7; \
426				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
427				--outX; \
428				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
429				--outX; \
430				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
431				--outX; \
432				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
433				--outX; \
434				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
435				--outX; \
436				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
437				--outX; \
438				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
439				--outX; \
440				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
441				outX += 8; \
442			} \
443		} else { \
444			outX += 8; \
445		} \
446	}
447
448#define BACKGROUND_MODE_0_TILE_256_LOOP(TYPE) \
449		for (tileX; tileX < 30; ++tileX) { \
450			BACKGROUND_TEXT_SELECT_CHARACTER; \
451			charBase = ((background->charBase + (mapData.tile << 6)) >> 2) + (localY << 1); \
452			if (!mapData.hflip) { \
453				uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
454				if (tileData) { \
455						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
456						++outX; \
457						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
458						++outX; \
459						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
460						++outX; \
461						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
462						++outX; \
463				} else { \
464					outX += 4; \
465				} \
466				tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
467				if (tileData) { \
468						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
469						++outX; \
470						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
471						++outX; \
472						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
473						++outX; \
474						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
475						++outX; \
476				} else { \
477					outX += 4; \
478				} \
479			} else { \
480				uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
481				if (tileData) { \
482					outX += 3; \
483					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
484					--outX; \
485					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
486					--outX; \
487					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
488					--outX; \
489					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
490					outX += 4; \
491				} else { \
492					outX += 4; \
493				} \
494				tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
495				if (tileData) { \
496					outX += 3; \
497					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
498					--outX; \
499					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
500					--outX; \
501					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
502					--outX; \
503					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
504					outX += 4; \
505				} else { \
506					outX += 4; \
507				} \
508			} \
509		}
510
511static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
512	int start = renderer->start;
513	int end = renderer->end;
514	int inX = start + background->x;
515	int inY = y + background->y;
516	union GBATextMapData mapData;
517
518	unsigned yBase = inY & 0xF8;
519	if (background->size & 2) {
520		yBase += inY & 0x100;
521	} else if (background->size == 3) {
522		yBase += (inY & 0x100) << 1;
523	}
524
525	int localX;
526	int localY;
527
528	unsigned xBase;
529
530	struct PixelFlags flags = {
531		.target1 = background->target1 && renderer->blendEffect == BLEND_ALPHA,
532		.target2 = background->target2,
533		.priority = background->priority
534	};
535
536	uint32_t screenBase;
537	uint32_t charBase;
538	int variant = background->target1 && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
539
540	int outX = 0;
541	int tileX = 0;
542	if (inX & 0x7) {
543		int end = 0x8 - (inX & 0x7);
544		uint32_t tileData;
545		BACKGROUND_TEXT_SELECT_CHARACTER;
546		charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
547		tileData = ((uint32_t*)renderer->d.vram)[charBase];
548		tileData >>= 4 * (inX & 0x7);
549		if (!variant) {
550			for (outX = 0; outX < end; ++outX) {
551				BACKGROUND_DRAW_PIXEL_16_NORMAL;
552			}
553		} else {
554			for (outX = 0; outX < end; ++outX) {
555				BACKGROUND_DRAW_PIXEL_16_VARIANT;
556			}
557		}
558
559		tileX = 30;
560		BACKGROUND_TEXT_SELECT_CHARACTER;
561		charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
562		tileData = ((uint32_t*)renderer->d.vram)[charBase];
563		if (!variant) {
564			for (outX = VIDEO_HORIZONTAL_PIXELS - 8 + end; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
565				BACKGROUND_DRAW_PIXEL_16_NORMAL;
566			}
567		} else {
568			for (outX = VIDEO_HORIZONTAL_PIXELS - 8 + end; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
569				BACKGROUND_DRAW_PIXEL_16_VARIANT;
570			}
571		}
572
573		tileX = 1;
574		outX = end;
575	}
576
577	if (!background->multipalette) {
578		if (!variant) {
579			BACKGROUND_MODE_0_TILE_16_LOOP(NORMAL);
580		 } else {
581			BACKGROUND_MODE_0_TILE_16_LOOP(VARIANT);
582		 }
583	} else {
584		if (!variant) {
585			BACKGROUND_MODE_0_TILE_256_LOOP(NORMAL);
586		 } else {
587			BACKGROUND_MODE_0_TILE_256_LOOP(VARIANT);
588		 }
589	}
590}
591
592static const int _objSizes[32] = {
593	8, 8,
594	16, 16,
595	32, 32,
596	64, 64,
597	16, 8,
598	32, 8,
599	32, 16,
600	64, 32,
601	8, 16,
602	8, 32,
603	16, 32,
604	32, 64,
605	0, 0,
606	0, 0,
607	0, 0,
608	0, 0
609};
610
611#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \
612		SPRITE_YBASE_ ## DEPTH(inY); \
613		for (int outX = x >= start ? x : start; outX < x + width && outX < end; ++outX) { \
614			int inX = outX - x; \
615			if (sprite->hflip) { \
616				inX = width - inX - 1; \
617			} \
618			if (renderer->flags[outX].isSprite) { \
619				continue; \
620			} \
621			SPRITE_XBASE_ ## DEPTH(inX); \
622			SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \
623		}
624
625#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \
626	for (int outX = x >= start ? x : start; outX < x + totalWidth && outX < end; ++outX) { \
627		if (renderer->flags[outX].isSprite) { \
628			continue; \
629		} \
630		int inY = y - sprite->y; \
631		int inX = outX - x; \
632		int localX = ((mat->a * (inX - (totalWidth >> 1)) + mat->b * (inY - (totalHeight >> 1))) >> 8) + (width >> 1); \
633		int localY = ((mat->c * (inX - (totalWidth >> 1)) + mat->d * (inY - (totalHeight >> 1))) >> 8) + (height >> 1); \
634		\
635		if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \
636			continue; \
637		} \
638		\
639		SPRITE_YBASE_ ## DEPTH(localY); \
640		SPRITE_XBASE_ ## DEPTH(localX); \
641		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
642	}
643
644#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
645#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (localY & 0x7) * 4;
646
647#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
648	uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
649	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
650	if (tileData) { \
651		renderer->spriteLayer[outX] = renderer->normalPalette[0x100 | tileData | (sprite->palette << 4)]; \
652		renderer->flags[outX] = flags; \
653	}
654
655#define SPRITE_DRAW_PIXEL_16_VARIANT(localX) \
656	uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
657	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
658	if (tileData) { \
659		renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)]; \
660		renderer->flags[outX] = flags; \
661	}
662
663#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
664#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width : 0x100) + (localY & 0x7) * 8;
665
666#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
667	uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
668	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
669	if (tileData) { \
670		renderer->spriteLayer[outX] = renderer->normalPalette[0x100 | tileData]; \
671		renderer->flags[outX] = flags; \
672	}
673
674#define SPRITE_DRAW_PIXEL_256_VARIANT(localX) \
675	uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
676	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
677	if (tileData) { \
678		renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData]; \
679		renderer->flags[outX] = flags; \
680	}
681
682static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
683	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
684	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
685	int start = renderer->start;
686	int end = renderer->end;
687	if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
688		return;
689	}
690	struct PixelFlags flags = {
691		.priority = sprite->priority,
692		.isSprite = 1,
693		.target1 = (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
694		.target2 = renderer->target2Obj
695	};
696	int x = sprite->x;
697	int inY = y - sprite->y;
698	if (sprite->y + height - 256 >= 0) {
699		inY += 256;
700	}
701	if (sprite->vflip) {
702		inY = height - inY - 1;
703	}
704	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
705	int variant = renderer->target1Obj && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
706	if (!sprite->multipalette) {
707		if (!variant) {
708			SPRITE_NORMAL_LOOP(16, NORMAL);
709		} else {
710			SPRITE_NORMAL_LOOP(16, VARIANT);
711		}
712	} else {
713		if (!variant) {
714			SPRITE_NORMAL_LOOP(256, NORMAL);
715		} else {
716			SPRITE_NORMAL_LOOP(256, VARIANT);
717		}
718	}
719}
720
721static void _drawTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y) {
722	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
723	int totalWidth = width << sprite->doublesize;
724	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
725	int totalHeight = height << sprite->doublesize;
726	int start = renderer->start;
727	int end = renderer->end;
728	if ((y < sprite->y && (sprite->y + totalHeight - 256 < 0 || y >= sprite->y + totalHeight - 256)) || y >= sprite->y + totalHeight) {
729		return;
730	}
731	struct PixelFlags flags = {
732		.priority = sprite->priority,
733		.isSprite = 1,
734		.target1 = (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
735		.target2 = renderer->target2Obj
736	};
737	int x = sprite->x;
738	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
739	struct GBAOAMMatrix* mat = &renderer->d.oam->mat[sprite->matIndex];
740	int variant = renderer->target1Obj && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
741	if (!sprite->multipalette) {
742		if (!variant) {
743			SPRITE_TRANSFORMED_LOOP(16, NORMAL);
744		} else {
745			SPRITE_TRANSFORMED_LOOP(16, VARIANT);
746		}
747	} else {
748		if (!variant) {
749			SPRITE_TRANSFORMED_LOOP(256, NORMAL);
750		} else {
751			SPRITE_TRANSFORMED_LOOP(256, VARIANT);
752		}
753	}
754}
755
756static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
757	if (renderer->blendEffect == BLEND_BRIGHTEN) {
758		for (int i = 0; i < 512; ++i) {
759			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
760		}
761	} else if (renderer->blendEffect == BLEND_DARKEN) {
762		for (int i = 0; i < 512; ++i) {
763			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
764		}
765	} else {
766		for (int i = 0; i < 512; ++i) {
767			renderer->variantPalette[i] = renderer->normalPalette[i];
768		}
769	}
770}
771
772static inline uint32_t _brighten(uint32_t color, int y) {
773	uint32_t c = 0;
774	uint32_t a;
775	a = color & 0xF8;
776	c |= (a + ((0xF8 - a) * y) / 16) & 0xF8;
777
778	a = color & 0xF800;
779	c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
780
781	a = color & 0xF80000;
782	c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000;
783	return c;
784}
785
786static inline uint32_t _darken(uint32_t color, int y) {
787	uint32_t c = 0;
788	uint32_t a;
789	a = color & 0xF8;
790	c |= (a - (a * y) / 16) & 0xF8;
791
792	a = color & 0xF800;
793	c |= (a - (a * y) / 16) & 0xF800;
794
795	a = color & 0xF80000;
796	c |= (a - (a * y) / 16) & 0xF80000;
797	return c;
798}
799
800static uint32_t _mix(int weightA, uint32_t colorA, int weightB, uint32_t colorB) {
801	uint32_t c = 0;
802	uint32_t a, b;
803	a = colorA & 0xF8;
804	b = colorB & 0xF8;
805	c |= ((a * weightA + b * weightB) / 16) & 0x1F8;
806	if (c & 0x00000100) {
807		c = 0x000000F8;
808	}
809
810	a = colorA & 0xF800;
811	b = colorB & 0xF800;
812	c |= ((a * weightA + b * weightB) / 16) & 0x1F800;
813	if (c & 0x00010000) {
814		c = (c & 0x000000F8) | 0x0000F800;
815	}
816
817	a = colorA & 0xF80000;
818	b = colorB & 0xF80000;
819	c |= ((a * weightA + b * weightB) / 16) & 0x1F80000;
820	if (c & 0x01000000) {
821		c = (c & 0x0000F8F8) | 0x00F80000;
822	}
823	return c;
824}