all repos — mgba @ 40e9a7ef266042c036ab9b689038ceffda193aed

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