all repos — mgba @ aeecbdb56fad783b4870bfa1cb8e065f06f9f4a8

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
611static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
612	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
613	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
614	int start = renderer->start;
615	int end = renderer->end;
616	if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
617		return;
618	}
619	struct PixelFlags flags = {
620		.priority = sprite->priority,
621		.isSprite = 1,
622		.target1 = (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
623		.target2 = renderer->target2Obj
624	};
625	int x = sprite->x;
626	int inY = y - sprite->y;
627	if (sprite->y + height - 256 >= 0) {
628		inY += 256;
629	}
630	if (sprite->vflip) {
631		inY = height - inY - 1;
632	}
633	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
634	unsigned yBase = (inY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (inY & 0x7) * 4;
635	for (int outX = x >= start ? x : start; outX < x + width && outX < end; ++outX) {
636		int inX = outX - x;
637		if (sprite->hflip) {
638			inX = width - inX - 1;
639		}
640		if (renderer->flags[outX].isSprite) {
641			continue;
642		}
643		unsigned xBase = (inX & ~0x7) * 4 + ((inX >> 1) & 2);
644		uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
645		tileData = (tileData >> ((inX & 3) << 2)) & 0xF;
646		if (tileData) {
647			if (renderer->blendEffect == BLEND_NONE || renderer->blendEffect == BLEND_ALPHA || !renderer->target1Obj) {
648				renderer->spriteLayer[outX] = renderer->normalPalette[0x100 | tileData | (sprite->palette << 4)];
649			} else {
650				renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
651			}
652			renderer->flags[outX] = flags;
653		}
654	}
655}
656
657static void _drawTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y) {
658	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
659	int totalWidth = width << sprite->doublesize;
660	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
661	int totalHeight = height << sprite->doublesize;
662	int start = renderer->start;
663	int end = renderer->end;
664	if ((y < sprite->y && (sprite->y + totalHeight - 256 < 0 || y >= sprite->y + totalHeight - 256)) || y >= sprite->y + totalHeight) {
665		return;
666	}
667	struct PixelFlags flags = {
668		.priority = sprite->priority,
669		.isSprite = 1,
670		.target1 = (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
671		.target2 = renderer->target2Obj
672	};
673	int x = sprite->x;
674	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
675	struct GBAOAMMatrix* mat = &renderer->d.oam->mat[sprite->matIndex];
676	for (int outX = x >= start ? x : start; outX < x + totalWidth && outX < end; ++outX) {
677		if (renderer->flags[outX].isSprite) {
678			continue;
679		}
680		int inY = y - sprite->y;
681		int inX = outX - x;
682		int localX = ((mat->a * (inX - (totalWidth >> 1)) + mat->b * (inY - (totalHeight >> 1))) >> 8) + (width >> 1);
683		int localY = ((mat->c * (inX - (totalWidth >> 1)) + mat->d * (inY - (totalHeight >> 1))) >> 8) + (height >> 1);
684
685		if (localX < 0 || localX >= width || localY < 0 || localY >= height) {
686			continue;
687		}
688
689		unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (localY & 0x7) * 4;
690		unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
691		uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
692		tileData = (tileData >> ((localX & 3) << 2)) & 0xF;
693		if (tileData) {
694			if (renderer->blendEffect == BLEND_NONE || renderer->blendEffect == BLEND_ALPHA || !renderer->target1Obj) {
695				renderer->spriteLayer[outX] = renderer->normalPalette[0x100 | tileData | (sprite->palette << 4)];
696			} else {
697				renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
698			}
699			renderer->flags[outX] = flags;
700		}
701	}
702}
703
704static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
705	if (renderer->blendEffect == BLEND_BRIGHTEN) {
706		for (int i = 0; i < 512; ++i) {
707			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
708		}
709	} else if (renderer->blendEffect == BLEND_DARKEN) {
710		for (int i = 0; i < 512; ++i) {
711			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
712		}
713	} else {
714		for (int i = 0; i < 512; ++i) {
715			renderer->variantPalette[i] = renderer->normalPalette[i];
716		}
717	}
718}
719
720static inline uint32_t _brighten(uint32_t color, int y) {
721	uint32_t c = 0;
722	uint32_t a;
723	a = color & 0xF8;
724	c |= (a + ((0xF8 - a) * y) / 16) & 0xF8;
725
726	a = color & 0xF800;
727	c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
728
729	a = color & 0xF80000;
730	c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000;
731	return c;
732}
733
734static inline uint32_t _darken(uint32_t color, int y) {
735	uint32_t c = 0;
736	uint32_t a;
737	a = color & 0xF8;
738	c |= (a - (a * y) / 16) & 0xF8;
739
740	a = color & 0xF800;
741	c |= (a - (a * y) / 16) & 0xF800;
742
743	a = color & 0xF80000;
744	c |= (a - (a * y) / 16) & 0xF80000;
745	return c;
746}
747
748static uint32_t _mix(int weightA, uint32_t colorA, int weightB, uint32_t colorB) {
749	uint32_t c = 0;
750	uint32_t a, b;
751	a = colorA & 0xF8;
752	b = colorB & 0xF8;
753	c |= ((a * weightA + b * weightB) / 16) & 0x1F8;
754	if (c & 0x00000100) {
755		c = 0x000000F8;
756	}
757
758	a = colorA & 0xF800;
759	b = colorB & 0xF800;
760	c |= ((a * weightA + b * weightB) / 16) & 0x1F800;
761	if (c & 0x00010000) {
762		c = (c & 0x000000F8) | 0x0000F800;
763	}
764
765	a = colorA & 0xF80000;
766	b = colorB & 0xF80000;
767	c |= ((a * weightA + b * weightB) / 16) & 0x1F80000;
768	if (c & 0x01000000) {
769		c = (c & 0x0000F8F8) | 0x00F80000;
770	}
771	return c;
772}