all repos — mgba @ 1f156921732db400cfbb4a0ff3f8efc0faf5dd1e

mGBA Game Boy Advance Emulator

src/gba/renderers/video-software.c (view raw)

   1/* Copyright (c) 2013-2015 Jeffrey Pfau
   2 *
   3 * This Source Code Form is subject to the terms of the Mozilla Public
   4 * License, v. 2.0. If a copy of the MPL was not distributed with this
   5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
   6#include "gba/renderers/software-private.h"
   7
   8#include <mgba/core/cache-set.h>
   9#include <mgba/internal/arm/macros.h>
  10#include <mgba/internal/gba/io.h>
  11#include <mgba/internal/gba/renderers/cache-set.h>
  12
  13#include <mgba-util/arm-algo.h>
  14#include <mgba-util/memory.h>
  15
  16#define DIRTY_SCANLINE(R, Y) R->scanlineDirty[Y >> 5] |= (1 << (Y & 0x1F))
  17#define CLEAN_SCANLINE(R, Y) R->scanlineDirty[Y >> 5] &= ~(1 << (Y & 0x1F))
  18
  19static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
  20static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
  21static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer);
  22static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
  23static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
  24static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
  25static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
  26static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
  27static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
  28static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels);
  29static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);
  30
  31static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
  32static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
  33static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  34static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  35static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  36static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  37static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
  38
  39static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y);
  40
  41static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
  42
  43static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y);
  44static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win);
  45
  46void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
  47	renderer->d.init = GBAVideoSoftwareRendererInit;
  48	renderer->d.reset = GBAVideoSoftwareRendererReset;
  49	renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
  50	renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
  51	renderer->d.writeVRAM = GBAVideoSoftwareRendererWriteVRAM;
  52	renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
  53	renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
  54	renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
  55	renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
  56	renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
  57	renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
  58
  59	renderer->d.disableBG[0] = false;
  60	renderer->d.disableBG[1] = false;
  61	renderer->d.disableBG[2] = false;
  62	renderer->d.disableBG[3] = false;
  63	renderer->d.disableOBJ = false;
  64	renderer->tileStride = 0x20;
  65	renderer->bitmapStride = 0;
  66	renderer->combinedObjSort = false;
  67	renderer->masterEnd = GBA_VIDEO_HORIZONTAL_PIXELS;
  68	renderer->masterHeight = GBA_VIDEO_VERTICAL_PIXELS;
  69	renderer->masterScanlines = VIDEO_VERTICAL_TOTAL_PIXELS;
  70
  71	renderer->d.highlightBG[0] = false;
  72	renderer->d.highlightBG[1] = false;
  73	renderer->d.highlightBG[2] = false;
  74	renderer->d.highlightBG[3] = false;
  75	int i;
  76	for (i = 0; i < 128; ++i) {
  77		renderer->d.highlightOBJ[i] = false;
  78	}
  79	renderer->d.highlightColor = GBA_COLOR_WHITE;
  80	renderer->d.highlightAmount = 0;
  81
  82	renderer->temporaryBuffer = 0;
  83}
  84
  85static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
  86	GBAVideoSoftwareRendererReset(renderer);
  87
  88	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
  89
  90	int y;
  91	for (y = 0; y < softwareRenderer->masterHeight; ++y) {
  92		color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
  93		int x;
  94		for (x = 0; x < softwareRenderer->masterEnd; ++x) {
  95			row[x] = GBA_COLOR_WHITE;
  96		}
  97	}
  98}
  99
 100static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) {
 101	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 102	int i;
 103
 104	softwareRenderer->dispcnt = 0x0080;
 105
 106	softwareRenderer->target1Obj = 0;
 107	softwareRenderer->target1Bd = 0;
 108	softwareRenderer->target2Obj = 0;
 109	softwareRenderer->target2Bd = 0;
 110	softwareRenderer->blendEffect = BLEND_NONE;
 111	softwareRenderer->masterBright = 0;
 112	softwareRenderer->masterBrightY = 0;
 113	for (i = 0; i < 1024; i += 2) {
 114		uint16_t entry;
 115		LOAD_16(entry, i, softwareRenderer->d.palette);
 116		GBAVideoSoftwareRendererWritePalette(renderer, i, entry);
 117	}
 118	softwareRenderer->objExtPalette = NULL;
 119	softwareRenderer->objExtVariantPalette = NULL;
 120	softwareRenderer->blendDirty = false;
 121	_updatePalettes(softwareRenderer);
 122
 123	softwareRenderer->blda = 0;
 124	softwareRenderer->bldb = 0;
 125	softwareRenderer->bldy = 0;
 126
 127	softwareRenderer->winN[0] = (struct WindowN) { .control = { .priority = 0 } };
 128	softwareRenderer->winN[1] = (struct WindowN) { .control = { .priority = 1 } };
 129	softwareRenderer->objwin = (struct WindowControl) { .priority = 2 };
 130	softwareRenderer->winout = (struct WindowControl) { .priority = 3 };
 131	softwareRenderer->oamDirty = 1;
 132	softwareRenderer->oamMax = 0;
 133
 134	softwareRenderer->mosaic = 0;
 135	softwareRenderer->nextY = 0;
 136
 137	softwareRenderer->objOffsetX = 0;
 138	softwareRenderer->objOffsetY = 0;
 139
 140	memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
 141	memset(softwareRenderer->cache, 0, sizeof(softwareRenderer->cache));
 142	memset(softwareRenderer->nextIo, 0, sizeof(softwareRenderer->nextIo));
 143
 144	for (i = 0; i < 4; ++i) {
 145		struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
 146		bg->index = i;
 147		bg->enabled = 0;
 148		bg->priority = 0;
 149		bg->charBase = 0;
 150		bg->mosaic = 0;
 151		bg->multipalette = 0;
 152		bg->screenBase = 0;
 153		bg->overflow = 0;
 154		bg->size = 0;
 155		bg->target1 = 0;
 156		bg->target2 = 0;
 157		bg->x = 0;
 158		bg->y = 0;
 159		bg->refx = 0;
 160		bg->refy = 0;
 161		bg->dx = 256;
 162		bg->dmx = 0;
 163		bg->dy = 0;
 164		bg->dmy = 256;
 165		bg->sx = 0;
 166		bg->sy = 0;
 167		bg->yCache = -1;
 168		bg->extPalette = NULL;
 169		bg->variantPalette = NULL;
 170		bg->offsetX = 0;
 171		bg->offsetY = 0;
 172	}
 173}
 174
 175static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
 176	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 177	UNUSED(softwareRenderer);
 178}
 179
 180static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
 181	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 182	if (renderer->cache) {
 183		GBAVideoCacheWriteVideoRegister(renderer->cache, address, value);
 184	}
 185
 186	switch (address) {
 187	case REG_DISPCNT:
 188		value &= 0xFFF7;
 189		softwareRenderer->dispcnt = value;
 190		GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
 191		break;
 192	case REG_BG0CNT:
 193		value &= 0xDFFF;
 194		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
 195		break;
 196	case REG_BG1CNT:
 197		value &= 0xDFFF;
 198		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
 199		break;
 200	case REG_BG2CNT:
 201		value &= 0xFFFF;
 202		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
 203		break;
 204	case REG_BG3CNT:
 205		value &= 0xFFFF;
 206		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
 207		break;
 208	case REG_BG0HOFS:
 209		value &= 0x01FF;
 210		softwareRenderer->bg[0].x = value;
 211		break;
 212	case REG_BG0VOFS:
 213		value &= 0x01FF;
 214		softwareRenderer->bg[0].y = value;
 215		break;
 216	case REG_BG1HOFS:
 217		value &= 0x01FF;
 218		softwareRenderer->bg[1].x = value;
 219		break;
 220	case REG_BG1VOFS:
 221		value &= 0x01FF;
 222		softwareRenderer->bg[1].y = value;
 223		break;
 224	case REG_BG2HOFS:
 225		value &= 0x01FF;
 226		softwareRenderer->bg[2].x = value;
 227		break;
 228	case REG_BG2VOFS:
 229		value &= 0x01FF;
 230		softwareRenderer->bg[2].y = value;
 231		break;
 232	case REG_BG3HOFS:
 233		value &= 0x01FF;
 234		softwareRenderer->bg[3].x = value;
 235		break;
 236	case REG_BG3VOFS:
 237		value &= 0x01FF;
 238		softwareRenderer->bg[3].y = value;
 239		break;
 240	case REG_BG2PA:
 241		softwareRenderer->bg[2].dx = value;
 242		break;
 243	case REG_BG2PB:
 244		softwareRenderer->bg[2].dmx = value;
 245		break;
 246	case REG_BG2PC:
 247		softwareRenderer->bg[2].dy = value;
 248		break;
 249	case REG_BG2PD:
 250		softwareRenderer->bg[2].dmy = value;
 251		break;
 252	case REG_BG2X_LO:
 253		GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[2], value);
 254		if (softwareRenderer->bg[2].sx != softwareRenderer->cache[softwareRenderer->nextY].scale[0][0]) {
 255			DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 256		}
 257		break;
 258	case REG_BG2X_HI:
 259		GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[2], value);
 260		if (softwareRenderer->bg[2].sx != softwareRenderer->cache[softwareRenderer->nextY].scale[0][0]) {
 261			DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 262		}
 263		break;
 264	case REG_BG2Y_LO:
 265		GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[2], value);
 266		if (softwareRenderer->bg[2].sy != softwareRenderer->cache[softwareRenderer->nextY].scale[0][1]) {
 267			DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 268		}
 269		break;
 270	case REG_BG2Y_HI:
 271		GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[2], value);
 272		if (softwareRenderer->bg[2].sy != softwareRenderer->cache[softwareRenderer->nextY].scale[0][1]) {
 273			DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 274		}
 275		break;
 276	case REG_BG3PA:
 277		softwareRenderer->bg[3].dx = value;
 278		break;
 279	case REG_BG3PB:
 280		softwareRenderer->bg[3].dmx = value;
 281		break;
 282	case REG_BG3PC:
 283		softwareRenderer->bg[3].dy = value;
 284		break;
 285	case REG_BG3PD:
 286		softwareRenderer->bg[3].dmy = value;
 287		break;
 288	case REG_BG3X_LO:
 289		GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[3], value);
 290		if (softwareRenderer->bg[3].sx != softwareRenderer->cache[softwareRenderer->nextY].scale[1][0]) {
 291			DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 292		}
 293		break;
 294	case REG_BG3X_HI:
 295		GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[3], value);
 296		if (softwareRenderer->bg[3].sx != softwareRenderer->cache[softwareRenderer->nextY].scale[1][0]) {
 297			DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 298		}
 299		break;
 300	case REG_BG3Y_LO:
 301		GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[3], value);
 302		if (softwareRenderer->bg[3].sy != softwareRenderer->cache[softwareRenderer->nextY].scale[1][1]) {
 303			DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 304		}
 305		break;
 306	case REG_BG3Y_HI:
 307		GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[3], value);
 308		if (softwareRenderer->bg[3].sy != softwareRenderer->cache[softwareRenderer->nextY].scale[1][1]) {
 309			DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 310		}
 311		break;
 312	case REG_BLDCNT:
 313		GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
 314		value &= 0x3FFF;
 315		break;
 316	case REG_BLDALPHA:
 317		softwareRenderer->blda = value & 0x1F;
 318		if (softwareRenderer->blda > 0x10) {
 319			softwareRenderer->blda = 0x10;
 320		}
 321		softwareRenderer->bldb = (value >> 8) & 0x1F;
 322		if (softwareRenderer->bldb > 0x10) {
 323			softwareRenderer->bldb = 0x10;
 324		}
 325		softwareRenderer->blendDirty = true;
 326		value &= 0x1F1F;
 327		break;
 328	case REG_BLDY:
 329		value &= 0x1F;
 330		if (value > 0x10) {
 331			value = 0x10;
 332		}
 333		if (softwareRenderer->bldy != value) {
 334			softwareRenderer->bldy = value;
 335			softwareRenderer->blendDirty = true;
 336		}
 337		break;
 338	case REG_WIN0H:
 339		softwareRenderer->winN[0].h.end = value & 0xFF;
 340		softwareRenderer->winN[0].h.start = value >> 8;
 341		if (softwareRenderer->winN[0].h.start > softwareRenderer->masterEnd && softwareRenderer->winN[0].h.start > softwareRenderer->winN[0].h.end) {
 342			softwareRenderer->winN[0].h.start = 0;
 343		}
 344		if (softwareRenderer->winN[0].h.end > softwareRenderer->masterEnd) {
 345			softwareRenderer->winN[0].h.end = softwareRenderer->masterEnd;
 346			if (softwareRenderer->winN[0].h.start > softwareRenderer->masterEnd) {
 347				softwareRenderer->winN[0].h.start = softwareRenderer->masterEnd;
 348			}
 349		}
 350		if (softwareRenderer->masterEnd > 0xFF && softwareRenderer->winN[0].h.end == (softwareRenderer->masterEnd & 0xFF) && softwareRenderer->winN[0].h.start != softwareRenderer->winN[0].h.end) {
 351			softwareRenderer->winN[0].h.end = softwareRenderer->masterEnd;
 352		}
 353		break;
 354	case REG_WIN1H:
 355		softwareRenderer->winN[1].h.end = value & 0xFF;
 356		softwareRenderer->winN[1].h.start = value >> 8;
 357		if (softwareRenderer->winN[1].h.start > softwareRenderer->masterEnd && softwareRenderer->winN[1].h.start > softwareRenderer->winN[1].h.end) {
 358			softwareRenderer->winN[1].h.start = 0;
 359		}
 360		if (softwareRenderer->winN[1].h.end > softwareRenderer->masterEnd) {
 361			softwareRenderer->winN[1].h.end = softwareRenderer->masterEnd;
 362			if (softwareRenderer->winN[1].h.start > softwareRenderer->masterEnd) {
 363				softwareRenderer->winN[1].h.start = softwareRenderer->masterEnd;
 364			}
 365		}
 366		if (softwareRenderer->masterEnd > 0xFF && softwareRenderer->winN[1].h.end == (softwareRenderer->masterEnd & 0xFF) && softwareRenderer->winN[1].h.start != softwareRenderer->winN[1].h.end) {
 367			softwareRenderer->winN[1].h.end = softwareRenderer->masterEnd;
 368		}
 369		break;
 370	case REG_WIN0V:
 371		softwareRenderer->winN[0].v.end = value & 0xFF;
 372		softwareRenderer->winN[0].v.start = value >> 8;
 373		if (softwareRenderer->winN[0].v.start > softwareRenderer->masterHeight && softwareRenderer->winN[0].v.start > softwareRenderer->winN[0].v.end) {
 374			softwareRenderer->winN[0].v.start = 0;
 375		}
 376		if (softwareRenderer->winN[0].v.end > softwareRenderer->masterHeight) {
 377			softwareRenderer->winN[0].v.end = softwareRenderer->masterHeight;
 378			if (softwareRenderer->winN[0].v.start > softwareRenderer->masterHeight) {
 379				softwareRenderer->winN[0].v.start = softwareRenderer->masterHeight;
 380			}
 381		}
 382		break;
 383	case REG_WIN1V:
 384		softwareRenderer->winN[1].v.end = value & 0xFF;
 385		softwareRenderer->winN[1].v.start = value >> 8;
 386		if (softwareRenderer->winN[1].v.start > softwareRenderer->masterHeight && softwareRenderer->winN[1].v.start > softwareRenderer->winN[1].v.end) {
 387			softwareRenderer->winN[1].v.start = 0;
 388		}
 389		if (softwareRenderer->winN[1].v.end > softwareRenderer->masterHeight) {
 390			softwareRenderer->winN[1].v.end = softwareRenderer->masterHeight;
 391			if (softwareRenderer->winN[1].v.start > softwareRenderer->masterHeight) {
 392				softwareRenderer->winN[1].v.start = softwareRenderer->masterHeight;
 393			}
 394		}
 395		break;
 396	case REG_WININ:
 397		value &= 0x3F3F;
 398		softwareRenderer->winN[0].control.packed = value;
 399		softwareRenderer->winN[1].control.packed = value >> 8;
 400		break;
 401	case REG_WINOUT:
 402		value &= 0x3F3F;
 403		softwareRenderer->winout.packed = value;
 404		softwareRenderer->objwin.packed = value >> 8;
 405		break;
 406	case REG_MOSAIC:
 407		softwareRenderer->mosaic = value;
 408		break;
 409	case REG_GREENSWP:
 410		mLOG(GBA_VIDEO, STUB, "Stub video register write: 0x%03X", address);
 411		break;
 412	default:
 413		mLOG(GBA_VIDEO, GAME_ERROR, "Invalid video register: 0x%03X", address);
 414	}
 415	softwareRenderer->nextIo[address >> 1] = value;
 416	if (softwareRenderer->cache[softwareRenderer->nextY].io[address >> 1] != value) {
 417		softwareRenderer->cache[softwareRenderer->nextY].io[address >> 1] = value;
 418		DIRTY_SCANLINE(softwareRenderer, softwareRenderer->nextY);
 419	}
 420	return value;
 421}
 422
 423static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
 424	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 425	if (renderer->cache) {
 426		mCacheSetWriteVRAM(renderer->cache, address);
 427	}
 428	memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
 429	softwareRenderer->bg[0].yCache = -1;
 430	softwareRenderer->bg[1].yCache = -1;
 431	softwareRenderer->bg[2].yCache = -1;
 432	softwareRenderer->bg[3].yCache = -1;
 433}
 434
 435static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
 436	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 437	UNUSED(oam);
 438	softwareRenderer->oamDirty = 1;
 439	memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
 440}
 441
 442static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
 443	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 444	color_t color = mColorFrom555(value);
 445	softwareRenderer->normalPalette[address >> 1] = color;
 446	if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 447		softwareRenderer->variantPalette[address >> 1] = _brighten(color, softwareRenderer->bldy);
 448	} else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 449		softwareRenderer->variantPalette[address >> 1] = _darken(color, softwareRenderer->bldy);
 450	}
 451	if (renderer->cache) {
 452		mCacheSetWritePalette(renderer->cache, address >> 1, color);
 453	}
 454	memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
 455}
 456
 457static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y) {
 458	if (win->v.end >= win->v.start) {
 459		if (y >= win->v.end) {
 460			return;
 461		}
 462		if (y < win->v.start) {
 463			return;
 464		}
 465	} else if (y >= win->v.end && y < win->v.start) {
 466		return;
 467	}
 468	if (win->h.end > softwareRenderer->masterEnd || win->h.end < win->h.start) {
 469		struct WindowN splits[2] = { *win, *win };
 470		splits[0].h.start = 0;
 471		splits[1].h.end = softwareRenderer->masterEnd;
 472		_breakWindowInner(softwareRenderer, &splits[0]);
 473		_breakWindowInner(softwareRenderer, &splits[1]);
 474	} else {
 475		_breakWindowInner(softwareRenderer, win);
 476	}
 477}
 478
 479static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win) {
 480	int activeWindow;
 481	int startX = 0;
 482	if (win->h.end > 0) {
 483		for (activeWindow = 0; activeWindow < softwareRenderer->nWindows; ++activeWindow) {
 484			if (win->h.start < softwareRenderer->windows[activeWindow].endX) {
 485				// Insert a window before the end of the active window
 486				struct Window oldWindow = softwareRenderer->windows[activeWindow];
 487				if (win->h.start > startX) {
 488					// And after the start of the active window
 489					int nextWindow = softwareRenderer->nWindows;
 490					++softwareRenderer->nWindows;
 491					for (; nextWindow > activeWindow; --nextWindow) {
 492						softwareRenderer->windows[nextWindow] = softwareRenderer->windows[nextWindow - 1];
 493					}
 494					softwareRenderer->windows[activeWindow].endX = win->h.start;
 495					++activeWindow;
 496				}
 497				softwareRenderer->windows[activeWindow].control = win->control;
 498				softwareRenderer->windows[activeWindow].endX = win->h.end;
 499				if (win->h.end >= oldWindow.endX) {
 500					// Trim off extra windows we've overwritten
 501					for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) {
 502						if (VIDEO_CHECKS && activeWindow >= MAX_WINDOW) {
 503							mLOG(GBA_VIDEO, FATAL, "Out of bounds window write will occur");
 504							return;
 505						}
 506						softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1];
 507						--softwareRenderer->nWindows;
 508					}
 509				} else {
 510					++activeWindow;
 511					int nextWindow = softwareRenderer->nWindows;
 512					++softwareRenderer->nWindows;
 513					for (; nextWindow > activeWindow; --nextWindow) {
 514						softwareRenderer->windows[nextWindow] = softwareRenderer->windows[nextWindow - 1];
 515					}
 516					softwareRenderer->windows[activeWindow] = oldWindow;
 517				}
 518				break;
 519			}
 520			startX = softwareRenderer->windows[activeWindow].endX;
 521		}
 522	}
 523#ifdef DEBUG
 524	if (softwareRenderer->nWindows > MAX_WINDOW) {
 525		mLOG(GBA_VIDEO, FATAL, "Out of bounds window write occurred!");
 526	}
 527#endif
 528}
 529
 530static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
 531	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 532
 533	if (y == GBA_VIDEO_VERTICAL_PIXELS - 1) {
 534		softwareRenderer->nextY = 0;
 535	} else {
 536		softwareRenderer->nextY = y + 1;
 537	}
 538
 539	bool dirty = softwareRenderer->scanlineDirty[y >> 5] & (1 << (y & 0x1F));
 540	if (memcmp(softwareRenderer->nextIo, softwareRenderer->cache[y].io, sizeof(softwareRenderer->nextIo))) {
 541		memcpy(softwareRenderer->cache[y].io, softwareRenderer->nextIo, sizeof(softwareRenderer->nextIo));
 542		dirty = true;
 543	}
 544
 545	if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) {
 546		if (softwareRenderer->cache[y].scale[0][0] != softwareRenderer->bg[2].sx ||
 547		    softwareRenderer->cache[y].scale[0][1] != softwareRenderer->bg[2].sy ||
 548		    softwareRenderer->cache[y].scale[1][0] != softwareRenderer->bg[3].sx ||
 549		    softwareRenderer->cache[y].scale[1][1] != softwareRenderer->bg[3].sy) {
 550			dirty = true;
 551		}
 552	}
 553	softwareRenderer->cache[y].scale[0][0] = softwareRenderer->bg[2].sx;
 554	softwareRenderer->cache[y].scale[0][1] = softwareRenderer->bg[2].sy;
 555	softwareRenderer->cache[y].scale[1][0] = softwareRenderer->bg[3].sx;
 556	softwareRenderer->cache[y].scale[1][1] = softwareRenderer->bg[3].sy;
 557
 558	if (!dirty) {
 559		if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) {
 560			softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx;
 561			softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy;
 562			softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx;
 563			softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy;
 564		}
 565		return;
 566	}
 567
 568	CLEAN_SCANLINE(softwareRenderer, y);
 569
 570	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
 571	if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) {
 572		int x;
 573		for (x = 0; x < softwareRenderer->masterEnd; ++x) {
 574			row[x] = GBA_COLOR_WHITE;
 575		}
 576		return;
 577	}
 578
 579	uint16_t* objVramBase = softwareRenderer->d.vramOBJ[0];
 580	if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) >= 3) {
 581		softwareRenderer->d.vramOBJ[0] = NULL; // OBJ VRAM bottom is blocked in bitmap modes
 582	}
 583
 584	GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y);
 585	int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y);
 586	softwareRenderer->d.vramOBJ[0] = objVramBase;
 587	if (softwareRenderer->lastHighlightAmount != softwareRenderer->d.highlightAmount) {
 588		softwareRenderer->lastHighlightAmount = softwareRenderer->d.highlightAmount;
 589		if (softwareRenderer->lastHighlightAmount) {
 590			softwareRenderer->blendDirty = true;
 591		}
 592	}
 593
 594	if (softwareRenderer->blendDirty) {
 595		_updatePalettes(softwareRenderer);
 596		softwareRenderer->blendDirty = false;
 597	}
 598
 599	softwareRenderer->bg[0].highlight = softwareRenderer->d.highlightBG[0];
 600	softwareRenderer->bg[1].highlight = softwareRenderer->d.highlightBG[1];
 601	softwareRenderer->bg[2].highlight = softwareRenderer->d.highlightBG[2];
 602	softwareRenderer->bg[3].highlight = softwareRenderer->d.highlightBG[3];
 603
 604	int w;
 605	unsigned priority;
 606	for (priority = 0; priority < 4; ++priority) {
 607		softwareRenderer->end = 0;
 608		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 609			softwareRenderer->start = softwareRenderer->end;
 610			softwareRenderer->end = softwareRenderer->windows[w].endX;
 611			softwareRenderer->currentWindow = softwareRenderer->windows[w].control;
 612			if (spriteLayers & (1 << priority)) {
 613				GBAVideoSoftwareRendererPostprocessSprite(softwareRenderer, priority);
 614			}
 615			if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) < 2) {
 616				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[0], y);
 617			}
 618			if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) < 2) {
 619				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[1], y);
 620			}
 621			if (TEST_LAYER_ENABLED(2)) {
 622				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
 623				case 0:
 624					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[2], y);
 625					break;
 626				case 1:
 627				case 2:
 628					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[2], y);
 629					break;
 630				case 3:
 631					GBAVideoSoftwareRendererDrawBackgroundMode3(softwareRenderer, &softwareRenderer->bg[2], y);
 632					break;
 633				case 4:
 634					GBAVideoSoftwareRendererDrawBackgroundMode4(softwareRenderer, &softwareRenderer->bg[2], y);
 635					break;
 636				case 5:
 637					GBAVideoSoftwareRendererDrawBackgroundMode5(softwareRenderer, &softwareRenderer->bg[2], y);
 638					break;
 639				}
 640			}
 641			if (TEST_LAYER_ENABLED(3)) {
 642				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
 643				case 0:
 644					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[3], y);
 645					break;
 646				case 2:
 647					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[3], y);
 648					break;
 649				}
 650			}
 651		}
 652	}
 653	if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) {
 654		int x = 0;
 655		uint32_t mask = FLAG_REBLEND | FLAG_TARGET_1 | FLAG_IS_BACKGROUND;
 656		uint32_t match = FLAG_REBLEND;
 657		if (GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
 658			mask |= FLAG_OBJWIN;
 659			if (GBAWindowControlIsBlendEnable(softwareRenderer->objwin.packed)) {
 660				match |= FLAG_OBJWIN;
 661			}
 662		}
 663		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 664			if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 665				continue;
 666			}
 667			int end = softwareRenderer->windows[w].endX;
 668			if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 669				for (; x < end; ++x) {
 670					uint32_t color = softwareRenderer->row[x];
 671					if ((color & mask) == match) {
 672						softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy);
 673					}
 674				}
 675			} else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 676				for (; x < end; ++x) {
 677					uint32_t color = softwareRenderer->row[x];
 678					if ((color & mask) == match) {
 679						softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy);
 680					}
 681				}
 682			}
 683		}
 684	}
 685	if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) {
 686		softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx;
 687		softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy;
 688		softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx;
 689		softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy;
 690	}
 691
 692	if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 4) {
 693		++softwareRenderer->bg[0].enabled;
 694		DIRTY_SCANLINE(softwareRenderer, y);
 695	}
 696	if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 4) {
 697		++softwareRenderer->bg[1].enabled;
 698		DIRTY_SCANLINE(softwareRenderer, y);
 699	}
 700	if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 4) {
 701		++softwareRenderer->bg[2].enabled;
 702		DIRTY_SCANLINE(softwareRenderer, y);
 703	}
 704	if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 4) {
 705		++softwareRenderer->bg[3].enabled;
 706		DIRTY_SCANLINE(softwareRenderer, y);
 707	}
 708
 709	GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer);
 710
 711#ifdef COLOR_16_BIT
 712	size_t x;
 713	for (x = 0; x < softwareRenderer->masterEnd; ++x) {
 714		row[x] = softwareRenderer->row[x];
 715		row[x + 1] = softwareRenderer->row[x + 1];
 716		row[x + 2] = softwareRenderer->row[x + 2];
 717		row[x + 3] = softwareRenderer->row[x + 3];
 718	}
 719#else
 720	memcpy(row, softwareRenderer->row, softwareRenderer->masterEnd * sizeof(*row));
 721#endif
 722}
 723
 724static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
 725	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 726
 727	softwareRenderer->nextY = 0;
 728	if (softwareRenderer->temporaryBuffer) {
 729		mappedMemoryFree(softwareRenderer->temporaryBuffer, softwareRenderer->masterEnd * softwareRenderer->masterHeight * 4);
 730		softwareRenderer->temporaryBuffer = 0;
 731	}
 732	softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;
 733	softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy;
 734	softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;
 735	softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
 736
 737	if (softwareRenderer->bg[0].enabled > 0) {
 738		softwareRenderer->bg[0].enabled = 4;
 739	}
 740	if (softwareRenderer->bg[1].enabled > 0) {
 741		softwareRenderer->bg[1].enabled = 4;
 742	}
 743	if (softwareRenderer->bg[2].enabled > 0) {
 744		softwareRenderer->bg[2].enabled = 4;
 745	}
 746	if (softwareRenderer->bg[3].enabled > 0) {
 747		softwareRenderer->bg[3].enabled = 4;
 748	}
 749}
 750
 751static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
 752	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 753	*stride = softwareRenderer->outputBufferStride;
 754	*pixels = softwareRenderer->outputBuffer;
 755}
 756
 757static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
 758	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 759
 760	const color_t* colorPixels = pixels;
 761	int i;
 762	for (i = 0; i < softwareRenderer->masterHeight; ++i) {
 763		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], softwareRenderer->masterEnd * BYTES_PER_PIXEL);
 764	}
 765}
 766
 767static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool active) {
 768	int wasActive = renderer->bg[bg].enabled;
 769	if (!active) {
 770		renderer->bg[bg].enabled = 0;
 771	} else if (!wasActive && active) {
 772		if (renderer->nextY == 0 || GBARegisterDISPCNTGetMode(renderer->dispcnt) > 2) {
 773			// TODO: Investigate in more depth how switching background works in different modes
 774			renderer->bg[bg].enabled = 4;
 775		} else {
 776			renderer->bg[bg].enabled = 1;
 777		}
 778	}
 779}
 780
 781static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
 782	_enableBg(renderer, 0, GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt));
 783	_enableBg(renderer, 1, GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt));
 784	_enableBg(renderer, 2, GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt));
 785	_enableBg(renderer, 3, GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt));
 786}
 787
 788static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 789	UNUSED(renderer);
 790	bg->priority = GBARegisterBGCNTGetPriority(value);
 791	bg->charBase = GBARegisterBGCNTGetCharBase(value) << 14;
 792	bg->charBase &= 0xC000;
 793	bg->mosaic = GBARegisterBGCNTGetMosaic(value);
 794	bg->multipalette = GBARegisterBGCNTGet256Color(value);
 795	bg->screenBase &= ~0xF800;
 796	bg->screenBase |= GBARegisterBGCNTGetScreenBase(value) << 11;
 797	bg->overflow = GBARegisterBGCNTGetOverflow(value);
 798	bg->size = GBARegisterBGCNTGetSize(value);
 799	bg->control = value;
 800}
 801
 802static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 803	bg->refx = (bg->refx & 0xFFFF0000) | value;
 804	bg->sx = bg->refx;
 805}
 806
 807static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 808	bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
 809	bg->refx <<= 4;
 810	bg->refx >>= 4;
 811	bg->sx = bg->refx;
 812}
 813
 814static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 815	bg->refy = (bg->refy & 0xFFFF0000) | value;
 816	bg->sy = bg->refy;
 817}
 818
 819static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 820	bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
 821	bg->refy <<= 4;
 822	bg->refy >>= 4;
 823	bg->sy = bg->refy;
 824}
 825
 826static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
 827	enum GBAVideoBlendEffect oldEffect = renderer->blendEffect;
 828
 829	renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
 830	renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
 831	renderer->bg[2].target1 = GBARegisterBLDCNTGetTarget1Bg2(value);
 832	renderer->bg[3].target1 = GBARegisterBLDCNTGetTarget1Bg3(value);
 833	renderer->bg[0].target2 = GBARegisterBLDCNTGetTarget2Bg0(value);
 834	renderer->bg[1].target2 = GBARegisterBLDCNTGetTarget2Bg1(value);
 835	renderer->bg[2].target2 = GBARegisterBLDCNTGetTarget2Bg2(value);
 836	renderer->bg[3].target2 = GBARegisterBLDCNTGetTarget2Bg3(value);
 837
 838	renderer->blendEffect = GBARegisterBLDCNTGetEffect(value);
 839	renderer->target1Obj = GBARegisterBLDCNTGetTarget1Obj(value);
 840	renderer->target1Bd = GBARegisterBLDCNTGetTarget1Bd(value);
 841	renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
 842	renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
 843
 844	if (oldEffect != renderer->blendEffect) {
 845		renderer->blendDirty = true;
 846	}
 847}
 848
 849void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* softwareRenderer, int y) {
 850	int x;
 851	int masterEnd = softwareRenderer->masterEnd;
 852	for (x = 0; x < masterEnd; x += 4) {
 853		softwareRenderer->spriteLayer[x] = FLAG_UNWRITTEN;
 854		softwareRenderer->spriteLayer[x + 1] = FLAG_UNWRITTEN;
 855		softwareRenderer->spriteLayer[x + 2] = FLAG_UNWRITTEN;
 856		softwareRenderer->spriteLayer[x + 3] = FLAG_UNWRITTEN;
 857	}
 858
 859	softwareRenderer->windows[0].endX = softwareRenderer->masterEnd;
 860	softwareRenderer->nWindows = 1;
 861	if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
 862		softwareRenderer->windows[0].control = softwareRenderer->winout;
 863		if (GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt)) {
 864			_breakWindow(softwareRenderer, &softwareRenderer->winN[1], y);
 865		}
 866		if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt)) {
 867			_breakWindow(softwareRenderer, &softwareRenderer->winN[0], y);
 868		}
 869	} else {
 870		softwareRenderer->windows[0].control.packed = 0xFF;
 871	}
 872
 873	GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
 874
 875	int w;
 876	x = 0;
 877	for (w = 0; w < softwareRenderer->nWindows; ++w) {
 878		// TOOD: handle objwin on backdrop
 879		uint32_t backdrop = FLAG_UNWRITTEN | FLAG_PRIORITY | FLAG_IS_BACKGROUND;
 880		if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 881			backdrop |= softwareRenderer->normalPalette[0];
 882		} else {
 883			backdrop |= softwareRenderer->variantPalette[0];
 884		}
 885		int end = softwareRenderer->windows[w].endX;
 886		for (; x & 3; ++x) {
 887			softwareRenderer->row[x] = backdrop;
 888		}
 889		for (; x < end - 3; x += 4) {
 890			softwareRenderer->row[x] = backdrop;
 891			softwareRenderer->row[x + 1] = backdrop;
 892			softwareRenderer->row[x + 2] = backdrop;
 893			softwareRenderer->row[x + 3] = backdrop;
 894		}
 895		for (; x < end; ++x) {
 896			softwareRenderer->row[x] = backdrop;
 897		}
 898	}
 899
 900	if (softwareRenderer->blendDirty) {
 901		_updatePalettes(softwareRenderer);
 902		softwareRenderer->blendDirty = false;
 903	}
 904}
 905
 906void GBAVideoSoftwareRendererPostprocessBuffer(struct GBAVideoSoftwareRenderer* softwareRenderer) {
 907	int x, w;
 908	if (softwareRenderer->target2Bd) {
 909		x = 0;
 910		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 911			uint32_t backdrop = 0;
 912			if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 913				backdrop |= softwareRenderer->normalPalette[0];
 914			} else {
 915				backdrop |= softwareRenderer->variantPalette[0];
 916			}
 917			int end = softwareRenderer->windows[w].endX;
 918			for (; x < end; ++x) {
 919				uint32_t color = softwareRenderer->row[x];
 920				if (color & FLAG_TARGET_1) {
 921					softwareRenderer->row[x] = _mix(softwareRenderer->alphaB[x], backdrop, softwareRenderer->alphaA[x], color);
 922				}
 923			}
 924		}
 925	}
 926	if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) {
 927		x = 0;
 928		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 929			if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 930				continue;
 931			}
 932			int end = softwareRenderer->windows[w].endX;
 933			if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 934				for (; x < end; ++x) {
 935					uint32_t color = softwareRenderer->row[x];
 936					if ((color & 0xFF000000) == FLAG_REBLEND) {
 937						softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy);
 938					}
 939				}
 940			} else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 941				for (; x < end; ++x) {
 942					uint32_t color = softwareRenderer->row[x];
 943					if ((color & 0xFF000000) == FLAG_REBLEND) {
 944						softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy);
 945					}
 946				}
 947			}
 948		}
 949	}
 950}
 951
 952int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRenderer* renderer, int y) {
 953	int w;
 954	int spriteLayers = 0;
 955	if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
 956		if (renderer->oamDirty) {
 957			renderer->oamMax = GBAVideoRendererCleanOAM(renderer->d.oam->obj, renderer->sprites, renderer->objOffsetY, renderer->masterHeight, renderer->combinedObjSort);
 958			renderer->oamDirty = false;
 959		}
 960		renderer->spriteCyclesRemaining = GBARegisterDISPCNTIsHblankIntervalFree(renderer->dispcnt) ? OBJ_HBLANK_FREE_LENGTH : OBJ_LENGTH;
 961		int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
 962		int mosaicY = y - (y % mosaicV);
 963		int i;
 964		for (i = 0; i < renderer->oamMax; ++i) {
 965			struct GBAVideoRendererSprite* sprite = &renderer->sprites[i];
 966			int localY = y;
 967			renderer->end = 0;
 968			if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
 969				continue;
 970			}
 971			if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) {
 972				localY = mosaicY;
 973				if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) {
 974					localY = sprite->y;
 975				}
 976				if (localY >= (sprite->endY & 0xFF)) {
 977					localY = sprite->endY - 1;
 978				}
 979			}
 980			for (w = 0; w < renderer->nWindows; ++w) {
 981				if (renderer->spriteCyclesRemaining <= 0) {
 982					break;
 983				}
 984				renderer->currentWindow = renderer->windows[w].control;
 985				renderer->start = renderer->end;
 986				renderer->end = renderer->windows[w].endX;
 987				if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed) && !GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt)) {
 988					continue;
 989				}
 990
 991				int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, sprite->index, localY);
 992				spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
 993			}
 994			if (renderer->spriteCyclesRemaining <= 0) {
 995				break;
 996			}
 997		}
 998	}
 999	return spriteLayers;
1000}
1001
1002static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
1003	int i;
1004	memset(renderer->alphaA, renderer->blda, sizeof(renderer->alphaA));
1005	memset(renderer->alphaB, renderer->bldb, sizeof(renderer->alphaB));
1006	if (renderer->blendEffect == BLEND_BRIGHTEN) {
1007		for (i = 0; i < 512; ++i) {
1008			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
1009		}
1010	} else if (renderer->blendEffect == BLEND_DARKEN) {
1011		for (i = 0; i < 512; ++i) {
1012			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
1013		}
1014	} else {
1015		for (i = 0; i < 512; ++i) {
1016			renderer->variantPalette[i] = renderer->normalPalette[i];
1017		}
1018	}
1019	unsigned highlightAmount = renderer->d.highlightAmount >> 4;
1020
1021	if (highlightAmount) {
1022		for (i = 0; i < 512; ++i) {
1023			renderer->highlightPalette[i] = _mix(0x10 - highlightAmount, renderer->normalPalette[i], highlightAmount, renderer->d.highlightColor);
1024			renderer->highlightVariantPalette[i] = _mix(0x10 - highlightAmount, renderer->variantPalette[i], highlightAmount, renderer->d.highlightColor);
1025		}
1026	}
1027}