all repos — mgba @ 49de0fb52e8da117efe0cd076d1bd097844b1f54

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 GBAVideoSoftwareRendererWriteBGPA(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  18static void GBAVideoSoftwareRendererWriteBGPB(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  19static void GBAVideoSoftwareRendererWriteBGPC(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  20static void GBAVideoSoftwareRendererWriteBGPD(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  21static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  22static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  23static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  24static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  25static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
  26
  27static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y);
  28static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
  29static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
  30static void _preprocessTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y);
  31static void _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y);
  32static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, int priority);
  33
  34static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
  35static inline uint32_t _brighten(uint32_t color, int y);
  36static inline uint32_t _darken(uint32_t color, int y);
  37static uint32_t _mix(int weightA, uint32_t colorA, int weightB, uint32_t colorB);
  38
  39void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
  40	renderer->d.init = GBAVideoSoftwareRendererInit;
  41	renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
  42	renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
  43	renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
  44	renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
  45	renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
  46
  47	renderer->d.turbo = 0;
  48	renderer->d.framesPending = 0;
  49	renderer->d.frameskip = 0;
  50
  51	{
  52		pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  53		renderer->mutex = mutex;
  54		pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  55		renderer->upCond = cond;
  56		renderer->downCond = cond;
  57	}
  58}
  59
  60static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
  61	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
  62	int i;
  63
  64	softwareRenderer->dispcnt.packed = 0x0080;
  65
  66	softwareRenderer->target1Obj = 0;
  67	softwareRenderer->target1Bd = 0;
  68	softwareRenderer->target2Obj = 0;
  69	softwareRenderer->target2Bd = 0;
  70	softwareRenderer->blendEffect = BLEND_NONE;
  71	memset(softwareRenderer->normalPalette, 0, sizeof(softwareRenderer->normalPalette));
  72	memset(softwareRenderer->variantPalette, 0, sizeof(softwareRenderer->variantPalette));
  73
  74	softwareRenderer->blda = 0;
  75	softwareRenderer->bldb = 0;
  76	softwareRenderer->bldy = 0;
  77
  78	for (i = 0; i < 4; ++i) {
  79		struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
  80		bg->index = i;
  81		bg->enabled = 0;
  82		bg->priority = 0;
  83		bg->charBase = 0;
  84		bg->mosaic = 0;
  85		bg->multipalette = 0;
  86		bg->screenBase = 0;
  87		bg->overflow = 0;
  88		bg->size = 0;
  89		bg->target1 = 0;
  90		bg->target2 = 0;
  91		bg->x = 0;
  92		bg->y = 0;
  93		bg->refx = 0;
  94		bg->refy = 0;
  95		bg->dx = 256;
  96		bg->dmx = 0;
  97		bg->dy = 0;
  98		bg->dmy = 256;
  99		bg->sx = 0;
 100		bg->sy = 0;
 101	}
 102
 103	pthread_mutex_init(&softwareRenderer->mutex, 0);
 104	pthread_cond_init(&softwareRenderer->upCond, 0);
 105	pthread_cond_init(&softwareRenderer->downCond, 0);
 106}
 107
 108static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
 109	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 110
 111	pthread_mutex_lock(&softwareRenderer->mutex);
 112	pthread_cond_broadcast(&softwareRenderer->upCond);
 113	pthread_mutex_unlock(&softwareRenderer->mutex);
 114
 115	pthread_mutex_destroy(&softwareRenderer->mutex);
 116	pthread_cond_destroy(&softwareRenderer->upCond);
 117	pthread_cond_destroy(&softwareRenderer->downCond);
 118}
 119
 120static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
 121	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 122	switch (address) {
 123	case REG_DISPCNT:
 124		value &= 0xFFFB;
 125		softwareRenderer->dispcnt.packed = value;
 126		GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
 127		break;
 128	case REG_BG0CNT:
 129		value &= 0xFFCF;
 130		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
 131		break;
 132	case REG_BG1CNT:
 133		value &= 0xFFCF;
 134		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
 135		break;
 136	case REG_BG2CNT:
 137		value &= 0xFFCF;
 138		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
 139		break;
 140	case REG_BG3CNT:
 141		value &= 0xFFCF;
 142		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
 143		break;
 144	case REG_BG0HOFS:
 145		value &= 0x01FF;
 146		softwareRenderer->bg[0].x = value;
 147		break;
 148	case REG_BG0VOFS:
 149		value &= 0x01FF;
 150		softwareRenderer->bg[0].y = value;
 151		break;
 152	case REG_BG1HOFS:
 153		value &= 0x01FF;
 154		softwareRenderer->bg[1].x = value;
 155		break;
 156	case REG_BG1VOFS:
 157		value &= 0x01FF;
 158		softwareRenderer->bg[1].y = value;
 159		break;
 160	case REG_BG2HOFS:
 161		value &= 0x01FF;
 162		softwareRenderer->bg[2].x = value;
 163		break;
 164	case REG_BG2VOFS:
 165		value &= 0x01FF;
 166		softwareRenderer->bg[2].y = value;
 167		break;
 168	case REG_BG3HOFS:
 169		value &= 0x01FF;
 170		softwareRenderer->bg[3].x = value;
 171		break;
 172	case REG_BG3VOFS:
 173		value &= 0x01FF;
 174		softwareRenderer->bg[3].y = value;
 175		break;
 176	case REG_BG2PA:
 177		GBAVideoSoftwareRendererWriteBGPA(&softwareRenderer->bg[2], value);
 178		break;
 179	case REG_BG2PB:
 180		GBAVideoSoftwareRendererWriteBGPB(&softwareRenderer->bg[2], value);
 181		break;
 182	case REG_BG2PC:
 183		GBAVideoSoftwareRendererWriteBGPC(&softwareRenderer->bg[2], value);
 184		break;
 185	case REG_BG2PD:
 186		GBAVideoSoftwareRendererWriteBGPD(&softwareRenderer->bg[2], value);
 187		break;
 188	case REG_BG2X_LO:
 189		GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[2], value);
 190		break;
 191	case REG_BG2X_HI:
 192		GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[2], value);
 193		break;
 194	case REG_BG2Y_LO:
 195		GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[2], value);
 196		break;
 197	case REG_BG2Y_HI:
 198		GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[2], value);
 199		break;
 200	case REG_BG3PA:
 201		GBAVideoSoftwareRendererWriteBGPA(&softwareRenderer->bg[3], value);
 202		break;
 203	case REG_BG3PB:
 204		GBAVideoSoftwareRendererWriteBGPB(&softwareRenderer->bg[3], value);
 205		break;
 206	case REG_BG3PC:
 207		GBAVideoSoftwareRendererWriteBGPC(&softwareRenderer->bg[3], value);
 208		break;
 209	case REG_BG3PD:
 210		GBAVideoSoftwareRendererWriteBGPD(&softwareRenderer->bg[3], value);
 211		break;
 212	case REG_BG3X_LO:
 213		GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[3], value);
 214		break;
 215	case REG_BG3X_HI:
 216		GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[3], value);
 217		break;
 218	case REG_BG3Y_LO:
 219		GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[3], value);
 220		break;
 221	case REG_BG3Y_HI:
 222		GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[3], value);
 223		break;
 224	case REG_BLDCNT:
 225		GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
 226		break;
 227	case REG_BLDALPHA:
 228		softwareRenderer->blda = value & 0x1F;
 229		if (softwareRenderer->blda > 0x10) {
 230			softwareRenderer->blda = 0x10;
 231		}
 232		softwareRenderer->bldb = (value >> 8) & 0x1F;
 233		if (softwareRenderer->bldb > 0x10) {
 234			softwareRenderer->bldb = 0x10;
 235		}
 236		break;
 237	case REG_BLDY:
 238		softwareRenderer->bldy = value & 0x1F;
 239		if (softwareRenderer->bldy > 0x10) {
 240			softwareRenderer->bldy = 0x10;
 241		}
 242		_updatePalettes(softwareRenderer);
 243		break;
 244	default:
 245		GBALog(GBA_LOG_STUB, "Stub video register write: %03x", address);
 246	}
 247	return value;
 248}
 249
 250static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
 251	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 252	uint32_t color32 = 0;
 253	color32 |= (value << 3) & 0xF8;
 254	color32 |= (value << 6) & 0xF800;
 255	color32 |= (value << 9) & 0xF80000;
 256	softwareRenderer->normalPalette[address >> 1] = color32;
 257	if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 258		softwareRenderer->variantPalette[address >> 1] = _brighten(color32, softwareRenderer->bldy);
 259	} else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 260		softwareRenderer->variantPalette[address >> 1] = _darken(color32, softwareRenderer->bldy);
 261	}
 262}
 263
 264static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
 265	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 266	if (renderer->frameskip > 0) {
 267		return;
 268	}
 269	uint32_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
 270	if (softwareRenderer->dispcnt.forcedBlank) {
 271		for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
 272			row[x] = GBA_COLOR_WHITE;
 273		}
 274		return;
 275	} else {
 276		uint32_t backdrop = FLAG_UNWRITTEN | FLAG_PRIORITY | FLAG_IS_BACKGROUND;
 277		if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA) {
 278			backdrop |= softwareRenderer->normalPalette[0];
 279		} else {
 280			backdrop |= softwareRenderer->variantPalette[0];
 281		}
 282		for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
 283			row[x] = backdrop;
 284		}
 285	}
 286
 287	softwareRenderer->row = row;
 288
 289	softwareRenderer->start = 0;
 290	softwareRenderer->end = VIDEO_HORIZONTAL_PIXELS;
 291	_drawScanline(softwareRenderer, y);
 292}
 293
 294static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
 295	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 296
 297	pthread_mutex_lock(&softwareRenderer->mutex);
 298	if (renderer->frameskip > 0) {
 299		--renderer->frameskip;
 300	} else {
 301		renderer->framesPending++;
 302		pthread_cond_broadcast(&softwareRenderer->upCond);
 303		if (!renderer->turbo) {
 304			pthread_cond_wait(&softwareRenderer->downCond, &softwareRenderer->mutex);
 305		}
 306	}
 307	pthread_mutex_unlock(&softwareRenderer->mutex);
 308
 309	softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;
 310	softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy;
 311	softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;
 312	softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
 313}
 314
 315static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
 316	renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
 317	renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
 318	renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
 319	renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
 320}
 321
 322static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 323	(void)(renderer);
 324	union GBARegisterBGCNT reg = { .packed = value };
 325	bg->priority = reg.priority;
 326	bg->charBase = reg.charBase << 14;
 327	bg->mosaic = reg.mosaic;
 328	bg->multipalette = reg.multipalette;
 329	bg->screenBase = reg.screenBase << 11;
 330	bg->overflow = reg.overflow;
 331	bg->size = reg.size;
 332}
 333
 334static void GBAVideoSoftwareRendererWriteBGPA(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 335	bg->dx = value;
 336}
 337
 338static void GBAVideoSoftwareRendererWriteBGPB(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 339	bg->dmx = value;
 340}
 341
 342static void GBAVideoSoftwareRendererWriteBGPC(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 343	bg->dy = value;
 344}
 345
 346static void GBAVideoSoftwareRendererWriteBGPD(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 347	bg->dmy = value;
 348}
 349
 350static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 351	bg->refx = (bg->refx & 0xFFFF0000) | value;
 352	bg->sx = bg->refx;
 353}
 354
 355static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 356	bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
 357	bg->refx <<= 4;
 358	bg->refx >>= 4;
 359	bg->sx = bg->refx;
 360}
 361
 362static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 363	bg->refy = (bg->refy & 0xFFFF0000) | value;
 364	bg->sy = bg->refy;
 365}
 366
 367static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 368	bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
 369	bg->refy <<= 4;
 370	bg->refy >>= 4;
 371	bg->sy = bg->refy;
 372}
 373
 374static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
 375	union {
 376		struct {
 377			unsigned target1Bg0 : 1;
 378			unsigned target1Bg1 : 1;
 379			unsigned target1Bg2 : 1;
 380			unsigned target1Bg3 : 1;
 381			unsigned target1Obj : 1;
 382			unsigned target1Bd : 1;
 383			enum BlendEffect effect : 2;
 384			unsigned target2Bg0 : 1;
 385			unsigned target2Bg1 : 1;
 386			unsigned target2Bg2 : 1;
 387			unsigned target2Bg3 : 1;
 388			unsigned target2Obj : 1;
 389			unsigned target2Bd : 1;
 390		};
 391		uint16_t packed;
 392	} bldcnt = { .packed = value };
 393
 394	enum BlendEffect oldEffect = renderer->blendEffect;
 395
 396	renderer->bg[0].target1 = bldcnt.target1Bg0;
 397	renderer->bg[1].target1 = bldcnt.target1Bg1;
 398	renderer->bg[2].target1 = bldcnt.target1Bg2;
 399	renderer->bg[3].target1 = bldcnt.target1Bg3;
 400	renderer->bg[0].target2 = bldcnt.target2Bg0;
 401	renderer->bg[1].target2 = bldcnt.target2Bg1;
 402	renderer->bg[2].target2 = bldcnt.target2Bg2;
 403	renderer->bg[3].target2 = bldcnt.target2Bg3;
 404
 405	renderer->blendEffect = bldcnt.effect;
 406	renderer->target1Obj = bldcnt.target1Obj;
 407	renderer->target1Bd = bldcnt.target1Bd;
 408	renderer->target2Obj = bldcnt.target2Obj;
 409	renderer->target2Bd = bldcnt.target2Bd;
 410
 411	if (oldEffect != renderer->blendEffect) {
 412		_updatePalettes(renderer);
 413	}
 414}
 415
 416static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
 417	int i;
 418	memset(renderer->spriteLayer, 0, sizeof(renderer->spriteLayer));
 419	if (renderer->dispcnt.objEnable) {
 420		for (i = 0; i < 128; ++i) {
 421			struct GBAObj* sprite = &renderer->d.oam->obj[i];
 422			if (sprite->transformed) {
 423				_preprocessTransformedSprite(renderer, &renderer->d.oam->tobj[i], y);
 424			} else if (!sprite->disable) {
 425				_preprocessSprite(renderer, sprite, y);
 426			}
 427		}
 428	}
 429
 430	int priority;
 431	for (priority = 0; priority < 4; ++priority) {
 432		_postprocessSprite(renderer, priority);
 433		if (renderer->bg[0].enabled && renderer->bg[0].priority == priority && renderer->dispcnt.mode < 2) {
 434			_drawBackgroundMode0(renderer, &renderer->bg[0], y);
 435		}
 436		if (renderer->bg[1].enabled && renderer->bg[1].priority == priority && renderer->dispcnt.mode < 2) {
 437			_drawBackgroundMode0(renderer, &renderer->bg[1], y);
 438		}
 439		if (renderer->bg[2].enabled && renderer->bg[2].priority == priority) {
 440			switch (renderer->dispcnt.mode) {
 441			case 0:
 442				_drawBackgroundMode0(renderer, &renderer->bg[2], y);
 443				break;
 444			case 1:
 445			case 2:
 446				_drawBackgroundMode2(renderer, &renderer->bg[2], y);
 447				break;
 448			}
 449			renderer->bg[2].sx += renderer->bg[2].dmx;
 450			renderer->bg[2].sy += renderer->bg[2].dmy;
 451		}
 452		if (renderer->bg[3].enabled && renderer->bg[3].priority == priority) {
 453			switch (renderer->dispcnt.mode) {
 454			case 0:
 455				_drawBackgroundMode0(renderer, &renderer->bg[3], y);
 456				break;
 457			case 2:
 458				_drawBackgroundMode2(renderer, &renderer->bg[3], y);
 459				break;
 460			}
 461			renderer->bg[3].sx += renderer->bg[3].dmx;
 462			renderer->bg[3].sy += renderer->bg[3].dmy;
 463		}
 464	}
 465}
 466
 467static void _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, uint32_t color) {
 468	uint32_t current = renderer->row[offset];
 469	// We stash the priority on the top bits so we cn do a one-operator comparison
 470	// The lower the number, the higher the priority, and sprites take precendence over backgrounds
 471	// We want to do special processing if the color pixel is target 1, however
 472	if (color < current) {
 473		if (current & FLAG_UNWRITTEN) {
 474			renderer->row[offset] = color;
 475		} else if (!(color & FLAG_TARGET_1) || !(current & FLAG_TARGET_2)) {
 476			renderer->row[offset] = color | FLAG_FINALIZED;
 477		} else {
 478			renderer->row[offset] = _mix(renderer->bldb, current, renderer->blda, color) | FLAG_FINALIZED;
 479		}
 480	} else {
 481		if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) {
 482			renderer->row[offset] = _mix(renderer->blda, current, renderer->bldb, color) | FLAG_FINALIZED;
 483		} else {
 484			renderer->row[offset] = current | FLAG_FINALIZED;
 485		}
 486	}
 487}
 488
 489#define BACKGROUND_DRAW_PIXEL_16_NORMAL \
 490	if (tileData & 0xF && !(renderer->row[outX] & FLAG_FINALIZED)) { \
 491		_composite(renderer, outX, renderer->normalPalette[(tileData & 0xF) | (mapData.palette << 4)] | flags); \
 492	} \
 493	tileData >>= 4;
 494
 495#define BACKGROUND_DRAW_PIXEL_16_VARIANT \
 496	if (tileData & 0xF && !(renderer->row[outX] & FLAG_FINALIZED)) { \
 497		_composite(renderer, outX, renderer->variantPalette[(tileData & 0xF) | (mapData.palette << 4)] | flags); \
 498	} \
 499	tileData >>= 4;
 500
 501#define BACKGROUND_DRAW_PIXEL_256_NORMAL \
 502	if (tileData & 0xFF && !(renderer->row[outX] & FLAG_FINALIZED)) { \
 503		_composite(renderer, outX, renderer->normalPalette[tileData & 0xFF] | flags); \
 504	} \
 505	tileData >>= 8;
 506
 507#define BACKGROUND_DRAW_PIXEL_256_VARIANT \
 508	if (tileData & 0xFF && !(renderer->row[outX] & FLAG_FINALIZED)) { \
 509		_composite(renderer, outX, renderer->variantPalette[tileData & 0xFF] | flags); \
 510	} \
 511	tileData >>= 8;
 512
 513#define BACKGROUND_TEXT_SELECT_CHARACTER \
 514	localX = tileX * 8 + inX; \
 515	xBase = localX & 0xF8; \
 516	if (background->size & 1) { \
 517		xBase += (localX & 0x100) << 5; \
 518	} \
 519	screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2); \
 520	mapData.packed = renderer->d.vram[screenBase]; \
 521	if (!mapData.vflip) { \
 522		localY = inY & 0x7; \
 523	} else { \
 524		localY = 7 - (inY & 0x7); \
 525	}
 526
 527#define BACKGROUND_MODE_0_TILE_16_LOOP(TYPE) \
 528	for (; tileX < 30; ++tileX) { \
 529		BACKGROUND_TEXT_SELECT_CHARACTER; \
 530		charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY; \
 531		uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
 532		if (tileData) { \
 533			if (!mapData.hflip) { \
 534				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 535				++outX; \
 536				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 537				++outX; \
 538				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 539				++outX; \
 540				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 541				++outX; \
 542				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 543				++outX; \
 544				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 545				++outX; \
 546				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 547				++outX; \
 548				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 549				++outX; \
 550			} else { \
 551				outX += 7; \
 552				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 553				--outX; \
 554				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 555				--outX; \
 556				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 557				--outX; \
 558				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 559				--outX; \
 560				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 561				--outX; \
 562				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 563				--outX; \
 564				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 565				--outX; \
 566				BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
 567				outX += 8; \
 568			} \
 569		} else { \
 570			outX += 8; \
 571		} \
 572	}
 573
 574#define BACKGROUND_MODE_0_TILE_256_LOOP(TYPE) \
 575		for (; tileX < 30; ++tileX) { \
 576			BACKGROUND_TEXT_SELECT_CHARACTER; \
 577			charBase = ((background->charBase + (mapData.tile << 6)) >> 2) + (localY << 1); \
 578			if (!mapData.hflip) { \
 579				uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
 580				if (tileData) { \
 581						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 582						++outX; \
 583						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 584						++outX; \
 585						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 586						++outX; \
 587						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 588						++outX; \
 589				} else { \
 590					outX += 4; \
 591				} \
 592				tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
 593				if (tileData) { \
 594						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 595						++outX; \
 596						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 597						++outX; \
 598						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 599						++outX; \
 600						BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 601						++outX; \
 602				} else { \
 603					outX += 4; \
 604				} \
 605			} else { \
 606				uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
 607				if (tileData) { \
 608					outX += 3; \
 609					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 610					--outX; \
 611					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 612					--outX; \
 613					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 614					--outX; \
 615					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 616					outX += 4; \
 617				} else { \
 618					outX += 4; \
 619				} \
 620				tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
 621				if (tileData) { \
 622					outX += 3; \
 623					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 624					--outX; \
 625					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 626					--outX; \
 627					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 628					--outX; \
 629					BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
 630					outX += 4; \
 631				} else { \
 632					outX += 4; \
 633				} \
 634			} \
 635		}
 636
 637static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
 638	int inX = background->x;
 639	int inY = y + background->y;
 640	union GBATextMapData mapData;
 641
 642	unsigned yBase = inY & 0xF8;
 643	if (background->size == 2) {
 644		yBase += inY & 0x100;
 645	} else if (background->size == 3) {
 646		yBase += (inY & 0x100) << 1;
 647	}
 648
 649	int localX;
 650	int localY;
 651
 652	unsigned xBase;
 653
 654	int flags = (background->priority << OFFSET_PRIORITY) | FLAG_IS_BACKGROUND;
 655	flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA);
 656	flags |= FLAG_TARGET_2 * background->target2;
 657
 658	uint32_t screenBase;
 659	uint32_t charBase;
 660	int variant = background->target1 && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
 661
 662	int outX = 0;
 663	int tileX = 0;
 664	if (inX & 0x7) {
 665		uint32_t tileData;
 666		BACKGROUND_TEXT_SELECT_CHARACTER;
 667
 668		int end = 0x8 - (inX & 0x7);
 669		if (!background->multipalette) {
 670			charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
 671			tileData = ((uint32_t*)renderer->d.vram)[charBase];
 672			if (!mapData.hflip) {
 673				tileData >>= 4 * (inX & 0x7);
 674				if (!variant) {
 675					for (outX = 0; outX < end; ++outX) {
 676						BACKGROUND_DRAW_PIXEL_16_NORMAL;
 677					}
 678				} else {
 679					for (outX = 0; outX < end; ++outX) {
 680						BACKGROUND_DRAW_PIXEL_16_VARIANT;
 681					}
 682				}
 683			} else {
 684				if (!variant) {
 685					for (outX = end; outX--;) {
 686						BACKGROUND_DRAW_PIXEL_16_NORMAL;
 687					}
 688				} else {
 689					for (outX = end; outX--;) {
 690						BACKGROUND_DRAW_PIXEL_16_VARIANT;
 691					}
 692				}
 693			}
 694		} else {
 695			// TODO: hflip
 696			charBase = ((background->charBase + (mapData.tile << 6)) >> 2) + (localY << 1);
 697			outX = 0;
 698			int end2 = end - 4;
 699			int shift = inX & 0x3;
 700			if (end2 > 0) {
 701				tileData = ((uint32_t*)renderer->d.vram)[charBase];
 702				tileData >>= 8 * shift;
 703				shift = 0;
 704				if (!variant) {
 705					for (; outX < end2; ++outX) {
 706						BACKGROUND_DRAW_PIXEL_256_NORMAL;
 707					}
 708				} else {
 709					for (; outX < end2; ++outX) {
 710						BACKGROUND_DRAW_PIXEL_256_VARIANT;
 711					}
 712				}
 713			}
 714
 715			tileData = ((uint32_t*)renderer->d.vram)[charBase + 1];
 716			tileData >>= 8 * shift;
 717			if (!variant) {
 718				for (; outX < end; ++outX) {
 719					BACKGROUND_DRAW_PIXEL_256_NORMAL;
 720				}
 721			} else {
 722				for (; outX < end; ++outX) {
 723					BACKGROUND_DRAW_PIXEL_256_VARIANT;
 724				}
 725			}
 726		}
 727
 728		tileX = 30;
 729		BACKGROUND_TEXT_SELECT_CHARACTER;
 730		if (!background->multipalette) {
 731			charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
 732			tileData = ((uint32_t*)renderer->d.vram)[charBase];
 733			if (!mapData.hflip) {
 734				if (!variant) {
 735					for (outX = VIDEO_HORIZONTAL_PIXELS - 8 + end; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
 736						BACKGROUND_DRAW_PIXEL_16_NORMAL;
 737					}
 738				} else {
 739					for (outX = VIDEO_HORIZONTAL_PIXELS - 8 + end; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
 740						BACKGROUND_DRAW_PIXEL_16_VARIANT;
 741					}
 742				}
 743			} else {
 744				tileData >>= 4 * end;
 745				if (!variant) {
 746					for (outX = VIDEO_HORIZONTAL_PIXELS - 1; outX > VIDEO_HORIZONTAL_PIXELS - 8; --outX) {
 747						BACKGROUND_DRAW_PIXEL_16_NORMAL;
 748					}
 749				} else {
 750					for (outX = VIDEO_HORIZONTAL_PIXELS - 1; outX > VIDEO_HORIZONTAL_PIXELS - 8; --outX) {
 751						BACKGROUND_DRAW_PIXEL_16_VARIANT;
 752					}
 753				}
 754			}
 755		} else {
 756			// TODO: hflip
 757			charBase = ((background->charBase + (mapData.tile << 6)) >> 2) + (localY << 1);
 758			outX = VIDEO_HORIZONTAL_PIXELS - 8 + end;
 759			int end2 = 4 - end;
 760			if (end2 > 0) {
 761				tileData = ((uint32_t*)renderer->d.vram)[charBase];
 762				if (!variant) {
 763					for (; outX < VIDEO_HORIZONTAL_PIXELS - end2; ++outX) {
 764						BACKGROUND_DRAW_PIXEL_256_NORMAL;
 765					}
 766				} else {
 767					for (; outX < VIDEO_HORIZONTAL_PIXELS - end2; ++outX) {
 768						BACKGROUND_DRAW_PIXEL_256_VARIANT;
 769					}
 770				}
 771				++charBase;
 772			}
 773
 774			tileData = ((uint32_t*)renderer->d.vram)[charBase];
 775			if (!variant) {
 776				for (; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
 777					BACKGROUND_DRAW_PIXEL_256_NORMAL;
 778				}
 779			} else {
 780				for (; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
 781					BACKGROUND_DRAW_PIXEL_256_VARIANT;
 782				}
 783			}
 784		}
 785
 786		tileX = 1;
 787		outX = end;
 788	}
 789
 790	if (!background->multipalette) {
 791		if (!variant) {
 792			BACKGROUND_MODE_0_TILE_16_LOOP(NORMAL);
 793		 } else {
 794			BACKGROUND_MODE_0_TILE_16_LOOP(VARIANT);
 795		 }
 796	} else {
 797		if (!variant) {
 798			BACKGROUND_MODE_0_TILE_256_LOOP(NORMAL);
 799		 } else {
 800			BACKGROUND_MODE_0_TILE_256_LOOP(VARIANT);
 801		 }
 802	}
 803}
 804
 805static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int unused) {
 806	(void)(unused);
 807	int sizeAdjusted = 0x8000 << background->size;
 808
 809	int32_t x = background->sx - background->dx;
 810	int32_t y = background->sy - background->dy;
 811	int32_t localX;
 812	int32_t localY;
 813
 814	int flags = (background->priority << OFFSET_PRIORITY) | FLAG_IS_BACKGROUND;
 815	flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA);
 816	flags |= FLAG_TARGET_2 * background->target2;
 817
 818	uint32_t screenBase = background->screenBase;
 819	uint32_t charBase = background->charBase;
 820	uint8_t mapData;
 821	uint8_t tileData;
 822	int variant = background->target1 && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
 823
 824	int outX;
 825	for (outX = 0; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
 826		x += background->dx;
 827		y += background->dy;
 828
 829		if (background->overflow) {
 830			localX = x & (sizeAdjusted - 1);
 831			localY = y & (sizeAdjusted - 1);
 832		} else if (x < 0 || y < 0 || x >= sizeAdjusted || y >= sizeAdjusted) {
 833			continue;
 834		} else {
 835			localX = x;
 836			localY = y;
 837		}
 838		mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)];
 839		tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)];
 840
 841		if (tileData && !(renderer->row[outX] & FLAG_FINALIZED)) {
 842			if (!variant) {
 843				_composite(renderer, outX, renderer->normalPalette[tileData] | flags);
 844			} else {
 845				_composite(renderer, outX, renderer->variantPalette[tileData] | flags);
 846			}
 847		}
 848	}
 849}
 850
 851static const int _objSizes[32] = {
 852	8, 8,
 853	16, 16,
 854	32, 32,
 855	64, 64,
 856	16, 8,
 857	32, 8,
 858	32, 16,
 859	64, 32,
 860	8, 16,
 861	8, 32,
 862	16, 32,
 863	32, 64,
 864	0, 0,
 865	0, 0,
 866	0, 0,
 867	0, 0
 868};
 869
 870#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \
 871		SPRITE_YBASE_ ## DEPTH(inY); \
 872		for (int outX = x >= start ? x : start; outX < x + width && outX < end; ++outX) { \
 873			int inX = outX - x; \
 874			if (sprite->hflip) { \
 875				inX = width - inX - 1; \
 876			} \
 877			if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
 878				continue; \
 879			} \
 880			SPRITE_XBASE_ ## DEPTH(inX); \
 881			SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \
 882		}
 883
 884#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \
 885	for (int outX = x >= start ? x : start; outX < x + totalWidth && outX < end; ++outX) { \
 886		if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
 887			continue; \
 888		} \
 889		int inX = outX - x; \
 890		int localX = ((mat->a * (inX - (totalWidth >> 1)) + mat->b * (inY - (totalHeight >> 1))) >> 8) + (width >> 1); \
 891		int localY = ((mat->c * (inX - (totalWidth >> 1)) + mat->d * (inY - (totalHeight >> 1))) >> 8) + (height >> 1); \
 892		\
 893		if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \
 894			continue; \
 895		} \
 896		\
 897		SPRITE_YBASE_ ## DEPTH(localY); \
 898		SPRITE_XBASE_ ## DEPTH(localX); \
 899		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
 900	}
 901
 902#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
 903#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (localY & 0x7) * 4;
 904
 905#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
 906	uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
 907	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
 908	if (tileData && !(renderer->spriteLayer[outX])) { \
 909		renderer->spriteLayer[outX] = renderer->normalPalette[0x100 | tileData | (sprite->palette << 4)] | flags; \
 910	}
 911
 912#define SPRITE_DRAW_PIXEL_16_VARIANT(localX) \
 913	uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
 914	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
 915	if (tileData && !(renderer->spriteLayer[outX])) { \
 916		renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)] | flags; \
 917	}
 918
 919#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
 920#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width : 0x80) + (localY & 0x7) * 8;
 921
 922#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
 923	uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
 924	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
 925	if (tileData && !(renderer->spriteLayer[outX])) { \
 926		renderer->spriteLayer[outX] = renderer->normalPalette[0x100 | tileData] | flags; \
 927	}
 928
 929#define SPRITE_DRAW_PIXEL_256_VARIANT(localX) \
 930	uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
 931	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
 932	if (tileData && !(renderer->spriteLayer[outX])) { \
 933		renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData] | flags; \
 934	}
 935
 936static void _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
 937	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
 938	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
 939	int start = renderer->start;
 940	int end = renderer->end;
 941	if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
 942		return;
 943	}
 944	int flags = (sprite->priority << OFFSET_PRIORITY) | FLAG_FINALIZED;
 945	flags |= FLAG_TARGET_1 * ((renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT);
 946	flags |= FLAG_TARGET_2 *renderer->target2Obj;
 947	int x = sprite->x;
 948	int inY = y - sprite->y;
 949	if (sprite->y + height - 256 >= 0) {
 950		inY += 256;
 951	}
 952	if (sprite->vflip) {
 953		inY = height - inY - 1;
 954	}
 955	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
 956	int variant = renderer->target1Obj && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
 957	if (!sprite->multipalette) {
 958		if (!variant) {
 959			SPRITE_NORMAL_LOOP(16, NORMAL);
 960		} else {
 961			SPRITE_NORMAL_LOOP(16, VARIANT);
 962		}
 963	} else {
 964		if (!variant) {
 965			SPRITE_NORMAL_LOOP(256, NORMAL);
 966		} else {
 967			SPRITE_NORMAL_LOOP(256, VARIANT);
 968		}
 969	}
 970}
 971
 972static void _preprocessTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y) {
 973	int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
 974	int totalWidth = width << sprite->doublesize;
 975	int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
 976	int totalHeight = height << sprite->doublesize;
 977	int start = renderer->start;
 978	int end = renderer->end;
 979	if ((y < sprite->y && (sprite->y + totalHeight - 256 < 0 || y >= sprite->y + totalHeight - 256)) || y >= sprite->y + totalHeight) {
 980		return;
 981	}
 982	int flags = (sprite->priority << OFFSET_PRIORITY) | FLAG_FINALIZED;
 983	flags |= FLAG_TARGET_1 * ((renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT);
 984	flags |= FLAG_TARGET_2 *renderer->target2Obj;
 985	int x = sprite->x;
 986	unsigned charBase = BASE_TILE + sprite->tile * 0x20;
 987	struct GBAOAMMatrix* mat = &renderer->d.oam->mat[sprite->matIndex];
 988	int variant = renderer->target1Obj && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
 989	int inY = y - sprite->y;
 990	if (inY < 0) {
 991		inY += 256;
 992	}
 993	if (!sprite->multipalette) {
 994		if (!variant) {
 995			SPRITE_TRANSFORMED_LOOP(16, NORMAL);
 996		} else {
 997			SPRITE_TRANSFORMED_LOOP(16, VARIANT);
 998		}
 999	} else {
1000		if (!variant) {
1001			SPRITE_TRANSFORMED_LOOP(256, NORMAL);
1002		} else {
1003			SPRITE_TRANSFORMED_LOOP(256, VARIANT);
1004		}
1005	}
1006}
1007
1008static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, int priority) {
1009	int x;
1010	for (x = 0; x < 240; ++x) {
1011		uint32_t color = renderer->spriteLayer[x];
1012		if ((color & FLAG_FINALIZED) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority && !(renderer->row[x] & FLAG_FINALIZED)) {
1013			_composite(renderer, x, color & ~FLAG_FINALIZED);
1014		}
1015	}
1016}
1017
1018static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
1019	if (renderer->blendEffect == BLEND_BRIGHTEN) {
1020		for (int i = 0; i < 512; ++i) {
1021			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
1022		}
1023	} else if (renderer->blendEffect == BLEND_DARKEN) {
1024		for (int i = 0; i < 512; ++i) {
1025			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
1026		}
1027	} else {
1028		for (int i = 0; i < 512; ++i) {
1029			renderer->variantPalette[i] = renderer->normalPalette[i];
1030		}
1031	}
1032}
1033
1034static inline uint32_t _brighten(uint32_t color, int y) {
1035	uint32_t c = 0;
1036	uint32_t a;
1037	a = color & 0xF8;
1038	c |= (a + ((0xF8 - a) * y) / 16) & 0xF8;
1039
1040	a = color & 0xF800;
1041	c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
1042
1043	a = color & 0xF80000;
1044	c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000;
1045	return c;
1046}
1047
1048static inline uint32_t _darken(uint32_t color, int y) {
1049	uint32_t c = 0;
1050	uint32_t a;
1051	a = color & 0xF8;
1052	c |= (a - (a * y) / 16) & 0xF8;
1053
1054	a = color & 0xF800;
1055	c |= (a - (a * y) / 16) & 0xF800;
1056
1057	a = color & 0xF80000;
1058	c |= (a - (a * y) / 16) & 0xF80000;
1059	return c;
1060}
1061
1062static uint32_t _mix(int weightA, uint32_t colorA, int weightB, uint32_t colorB) {
1063	uint32_t c = 0;
1064	uint32_t a, b;
1065	a = colorA & 0xF8;
1066	b = colorB & 0xF8;
1067	c |= ((a * weightA + b * weightB) / 16) & 0x1F8;
1068	if (c & 0x00000100) {
1069		c = 0x000000F8;
1070	}
1071
1072	a = colorA & 0xF800;
1073	b = colorB & 0xF800;
1074	c |= ((a * weightA + b * weightB) / 16) & 0x1F800;
1075	if (c & 0x00010000) {
1076		c = (c & 0x000000F8) | 0x0000F800;
1077	}
1078
1079	a = colorA & 0xF80000;
1080	b = colorB & 0xF80000;
1081	c |= ((a * weightA + b * weightB) / 16) & 0x1F80000;
1082	if (c & 0x01000000) {
1083		c = (c & 0x0000F8F8) | 0x00F80000;
1084	}
1085	return c;
1086}