all repos — mgba @ c62d913e233e7ea3bb23a3f52fcb7b481f2faed5

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] |= (1U << (Y & 0x1F))
  17#define CLEAN_SCANLINE(R, Y) R->scanlineDirty[Y >> 5] &= ~(1U << (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 _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
  40
  41static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y);
  42static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win);
  43
  44void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
  45	renderer->d.init = GBAVideoSoftwareRendererInit;
  46	renderer->d.reset = GBAVideoSoftwareRendererReset;
  47	renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
  48	renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
  49	renderer->d.writeVRAM = GBAVideoSoftwareRendererWriteVRAM;
  50	renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
  51	renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
  52	renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
  53	renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
  54	renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
  55	renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
  56
  57	renderer->d.disableBG[0] = false;
  58	renderer->d.disableBG[1] = false;
  59	renderer->d.disableBG[2] = false;
  60	renderer->d.disableBG[3] = false;
  61	renderer->d.disableOBJ = false;
  62	renderer->tileStride = 0x20;
  63	renderer->bitmapStride = 0;
  64	renderer->combinedObjSort = false;
  65	renderer->masterEnd = GBA_VIDEO_HORIZONTAL_PIXELS;
  66	renderer->masterHeight = GBA_VIDEO_VERTICAL_PIXELS;
  67	renderer->masterScanlines = VIDEO_VERTICAL_TOTAL_PIXELS;
  68
  69	renderer->d.highlightBG[0] = false;
  70	renderer->d.highlightBG[1] = false;
  71	renderer->d.highlightBG[2] = false;
  72	renderer->d.highlightBG[3] = false;
  73	int i;
  74	for (i = 0; i < 128; ++i) {
  75		renderer->d.highlightOBJ[i] = false;
  76	}
  77	renderer->d.highlightColor = GBA_COLOR_WHITE;
  78	renderer->d.highlightAmount = 0;
  79
  80	renderer->temporaryBuffer = 0;
  81}
  82
  83static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
  84	GBAVideoSoftwareRendererReset(renderer);
  85
  86	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
  87
  88	int y;
  89	for (y = 0; y < softwareRenderer->masterHeight; ++y) {
  90		color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
  91		int x;
  92		for (x = 0; x < softwareRenderer->masterEnd; ++x) {
  93			row[x] = GBA_COLOR_WHITE;
  94		}
  95	}
  96}
  97
  98static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) {
  99	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 100	int i;
 101
 102	softwareRenderer->dispcnt = 0x0080;
 103
 104	softwareRenderer->target1Obj = 0;
 105	softwareRenderer->target1Bd = 0;
 106	softwareRenderer->target2Obj = 0;
 107	softwareRenderer->target2Bd = 0;
 108	softwareRenderer->blendEffect = BLEND_NONE;
 109	softwareRenderer->masterBright = 0;
 110	softwareRenderer->masterBrightY = 0;
 111	for (i = 0; i < 1024; i += 2) {
 112		uint16_t entry;
 113		LOAD_16(entry, i, softwareRenderer->d.palette);
 114		GBAVideoSoftwareRendererWritePalette(renderer, i, entry);
 115	}
 116	softwareRenderer->objExtPalette = NULL;
 117	softwareRenderer->objExtVariantPalette = NULL;
 118	softwareRenderer->blendDirty = false;
 119	_updatePalettes(softwareRenderer);
 120
 121	softwareRenderer->blda = 0;
 122	softwareRenderer->bldb = 0;
 123	softwareRenderer->bldy = 0;
 124
 125	softwareRenderer->winN[0] = (struct WindowN) { .control = { .priority = 0 } };
 126	softwareRenderer->winN[1] = (struct WindowN) { .control = { .priority = 1 } };
 127	softwareRenderer->objwin = (struct WindowControl) { .priority = 2 };
 128	softwareRenderer->winout = (struct WindowControl) { .priority = 3 };
 129	softwareRenderer->oamDirty = 1;
 130	softwareRenderer->oamMax = 0;
 131
 132	softwareRenderer->mosaic = 0;
 133	softwareRenderer->nextY = 0;
 134
 135	softwareRenderer->objOffsetX = 0;
 136	softwareRenderer->objOffsetY = 0;
 137
 138	memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty));
 139	memset(softwareRenderer->cache, 0, sizeof(softwareRenderer->cache));
 140	memset(softwareRenderer->nextIo, 0, sizeof(softwareRenderer->nextIo));
 141
 142	softwareRenderer->lastHighlightAmount = 0;
 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] & (1U << (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	softwareRenderer->forceTarget1 = false;
 599
 600	softwareRenderer->bg[0].highlight = softwareRenderer->d.highlightBG[0];
 601	softwareRenderer->bg[1].highlight = softwareRenderer->d.highlightBG[1];
 602	softwareRenderer->bg[2].highlight = softwareRenderer->d.highlightBG[2];
 603	softwareRenderer->bg[3].highlight = softwareRenderer->d.highlightBG[3];
 604
 605	int w;
 606	unsigned priority;
 607	for (priority = 0; priority < 4; ++priority) {
 608		softwareRenderer->end = 0;
 609		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 610			softwareRenderer->start = softwareRenderer->end;
 611			softwareRenderer->end = softwareRenderer->windows[w].endX;
 612			softwareRenderer->currentWindow = softwareRenderer->windows[w].control;
 613			if (spriteLayers & (1 << priority)) {
 614				GBAVideoSoftwareRendererPostprocessSprite(softwareRenderer, priority);
 615			}
 616			if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) < 2) {
 617				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[0], y);
 618			}
 619			if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) < 2) {
 620				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[1], y);
 621			}
 622			if (TEST_LAYER_ENABLED(2)) {
 623				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
 624				case 0:
 625					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[2], y);
 626					break;
 627				case 1:
 628				case 2:
 629					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[2], y);
 630					break;
 631				case 3:
 632					GBAVideoSoftwareRendererDrawBackgroundMode3(softwareRenderer, &softwareRenderer->bg[2], y);
 633					break;
 634				case 4:
 635					GBAVideoSoftwareRendererDrawBackgroundMode4(softwareRenderer, &softwareRenderer->bg[2], y);
 636					break;
 637				case 5:
 638					GBAVideoSoftwareRendererDrawBackgroundMode5(softwareRenderer, &softwareRenderer->bg[2], y);
 639					break;
 640				}
 641			}
 642			if (TEST_LAYER_ENABLED(3)) {
 643				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
 644				case 0:
 645					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[3], y);
 646					break;
 647				case 2:
 648					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[3], y);
 649					break;
 650				}
 651			}
 652		}
 653	}
 654	if (softwareRenderer->forceTarget1 && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) {
 655		uint32_t mask = FLAG_REBLEND | 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		int x = 0;
 664		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 665			int end = softwareRenderer->windows[w].endX;
 666			if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 667				x = end;
 668				continue;
 669			}
 670			if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 671				for (; x < end; ++x) {
 672					uint32_t color = softwareRenderer->row[x];
 673					if ((color & mask) == match) {
 674						softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy);
 675					}
 676				}
 677			} else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 678				for (; x < end; ++x) {
 679					uint32_t color = softwareRenderer->row[x];
 680					if ((color & mask) == match) {
 681						softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy);
 682					}
 683				}
 684			}
 685		}
 686	}
 687	if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) {
 688		softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx;
 689		softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy;
 690		softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx;
 691		softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy;
 692	}
 693
 694	if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 3) {
 695		++softwareRenderer->bg[0].enabled;
 696		DIRTY_SCANLINE(softwareRenderer, y);
 697	}
 698	if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 3) {
 699		++softwareRenderer->bg[1].enabled;
 700		DIRTY_SCANLINE(softwareRenderer, y);
 701	}
 702	if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 3) {
 703		++softwareRenderer->bg[2].enabled;
 704		DIRTY_SCANLINE(softwareRenderer, y);
 705	}
 706	if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 3) {
 707		++softwareRenderer->bg[3].enabled;
 708		DIRTY_SCANLINE(softwareRenderer, y);
 709	}
 710
 711	GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer);
 712
 713#ifdef COLOR_16_BIT
 714	size_t x;
 715	for (x = 0; x < softwareRenderer->masterEnd; ++x) {
 716		row[x] = softwareRenderer->row[x];
 717		row[x + 1] = softwareRenderer->row[x + 1];
 718		row[x + 2] = softwareRenderer->row[x + 2];
 719		row[x + 3] = softwareRenderer->row[x + 3];
 720	}
 721#else
 722	memcpy(row, softwareRenderer->row, softwareRenderer->masterEnd * sizeof(*row));
 723#endif
 724}
 725
 726static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
 727	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 728
 729	softwareRenderer->nextY = 0;
 730	if (softwareRenderer->temporaryBuffer) {
 731		mappedMemoryFree(softwareRenderer->temporaryBuffer, softwareRenderer->masterEnd * softwareRenderer->masterHeight * 4);
 732		softwareRenderer->temporaryBuffer = 0;
 733	}
 734	softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;
 735	softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy;
 736	softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;
 737	softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
 738
 739	if (softwareRenderer->bg[0].enabled > 0) {
 740		softwareRenderer->bg[0].enabled = 3;
 741	}
 742	if (softwareRenderer->bg[1].enabled > 0) {
 743		softwareRenderer->bg[1].enabled = 3;
 744	}
 745	if (softwareRenderer->bg[2].enabled > 0) {
 746		softwareRenderer->bg[2].enabled = 3;
 747	}
 748	if (softwareRenderer->bg[3].enabled > 0) {
 749		softwareRenderer->bg[3].enabled = 3;
 750	}
 751}
 752
 753static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
 754	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 755	*stride = softwareRenderer->outputBufferStride;
 756	*pixels = softwareRenderer->outputBuffer;
 757}
 758
 759static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
 760	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 761
 762	const color_t* colorPixels = pixels;
 763	int i;
 764	for (i = 0; i < softwareRenderer->masterHeight; ++i) {
 765		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], softwareRenderer->masterEnd * BYTES_PER_PIXEL);
 766	}
 767}
 768
 769static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool active) {
 770	int wasActive = renderer->bg[bg].enabled;
 771	if (!active) {
 772		renderer->bg[bg].enabled = 0;
 773	} else if (!wasActive && active) {
 774		if (renderer->nextY == 0 || GBARegisterDISPCNTGetMode(renderer->dispcnt) > 2) {
 775			// TODO: Investigate in more depth how switching background works in different modes
 776			renderer->bg[bg].enabled = 3;
 777		} else {
 778			renderer->bg[bg].enabled = 1;
 779		}
 780	}
 781}
 782
 783static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
 784	_enableBg(renderer, 0, GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt));
 785	_enableBg(renderer, 1, GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt));
 786	_enableBg(renderer, 2, GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt));
 787	_enableBg(renderer, 3, GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt));
 788}
 789
 790static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 791	UNUSED(renderer);
 792	bg->priority = GBARegisterBGCNTGetPriority(value);
 793	bg->charBase = GBARegisterBGCNTGetCharBase(value) << 14;
 794	bg->charBase &= 0xC000;
 795	bg->mosaic = GBARegisterBGCNTGetMosaic(value);
 796	bg->multipalette = GBARegisterBGCNTGet256Color(value);
 797	bg->screenBase &= ~0xF800;
 798	bg->screenBase |= GBARegisterBGCNTGetScreenBase(value) << 11;
 799	bg->overflow = GBARegisterBGCNTGetOverflow(value);
 800	bg->size = GBARegisterBGCNTGetSize(value);
 801	bg->control = value;
 802}
 803
 804static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 805	bg->refx = (bg->refx & 0xFFFF0000) | value;
 806	bg->sx = bg->refx;
 807}
 808
 809static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 810	bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
 811	bg->refx <<= 4;
 812	bg->refx >>= 4;
 813	bg->sx = bg->refx;
 814}
 815
 816static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 817	bg->refy = (bg->refy & 0xFFFF0000) | value;
 818	bg->sy = bg->refy;
 819}
 820
 821static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 822	bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
 823	bg->refy <<= 4;
 824	bg->refy >>= 4;
 825	bg->sy = bg->refy;
 826}
 827
 828static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
 829	enum GBAVideoBlendEffect oldEffect = renderer->blendEffect;
 830
 831	renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
 832	renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
 833	renderer->bg[2].target1 = GBARegisterBLDCNTGetTarget1Bg2(value);
 834	renderer->bg[3].target1 = GBARegisterBLDCNTGetTarget1Bg3(value);
 835	renderer->bg[0].target2 = GBARegisterBLDCNTGetTarget2Bg0(value);
 836	renderer->bg[1].target2 = GBARegisterBLDCNTGetTarget2Bg1(value);
 837	renderer->bg[2].target2 = GBARegisterBLDCNTGetTarget2Bg2(value);
 838	renderer->bg[3].target2 = GBARegisterBLDCNTGetTarget2Bg3(value);
 839
 840	renderer->blendEffect = GBARegisterBLDCNTGetEffect(value);
 841	renderer->target1Obj = GBARegisterBLDCNTGetTarget1Obj(value);
 842	renderer->target1Bd = GBARegisterBLDCNTGetTarget1Bd(value);
 843	renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
 844	renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
 845
 846	if (oldEffect != renderer->blendEffect) {
 847		renderer->blendDirty = true;
 848	}
 849}
 850
 851void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* softwareRenderer, int y) {
 852	int x;
 853	int masterEnd = softwareRenderer->masterEnd;
 854	for (x = 0; x < masterEnd; x += 4) {
 855		softwareRenderer->spriteLayer[x] = FLAG_UNWRITTEN;
 856		softwareRenderer->spriteLayer[x + 1] = FLAG_UNWRITTEN;
 857		softwareRenderer->spriteLayer[x + 2] = FLAG_UNWRITTEN;
 858		softwareRenderer->spriteLayer[x + 3] = FLAG_UNWRITTEN;
 859	}
 860
 861	softwareRenderer->windows[0].endX = softwareRenderer->masterEnd;
 862	softwareRenderer->nWindows = 1;
 863	if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
 864		softwareRenderer->windows[0].control = softwareRenderer->winout;
 865		if (GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt)) {
 866			_breakWindow(softwareRenderer, &softwareRenderer->winN[1], y);
 867		}
 868		if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt)) {
 869			_breakWindow(softwareRenderer, &softwareRenderer->winN[0], y);
 870		}
 871	} else {
 872		softwareRenderer->windows[0].control.packed = 0xFF;
 873	}
 874
 875	GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
 876
 877	if (softwareRenderer->blendDirty) {
 878		_updatePalettes(softwareRenderer);
 879		softwareRenderer->blendDirty = false;
 880	}
 881
 882	int w;
 883	x = 0;
 884	for (w = 0; w < softwareRenderer->nWindows; ++w) {
 885		// TOOD: handle objwin on backdrop
 886		uint32_t backdrop = FLAG_UNWRITTEN | FLAG_PRIORITY | FLAG_IS_BACKGROUND;
 887		if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 888			backdrop |= softwareRenderer->normalPalette[0];
 889		} else {
 890			backdrop |= softwareRenderer->variantPalette[0];
 891		}
 892		int end = softwareRenderer->windows[w].endX;
 893		for (; x & 3; ++x) {
 894			softwareRenderer->row[x] = backdrop;
 895		}
 896		for (; x < end - 3; x += 4) {
 897			softwareRenderer->row[x] = backdrop;
 898			softwareRenderer->row[x + 1] = backdrop;
 899			softwareRenderer->row[x + 2] = backdrop;
 900			softwareRenderer->row[x + 3] = backdrop;
 901		}
 902		for (; x < end; ++x) {
 903			softwareRenderer->row[x] = backdrop;
 904		}
 905	}
 906}
 907
 908void GBAVideoSoftwareRendererPostprocessBuffer(struct GBAVideoSoftwareRenderer* softwareRenderer) {
 909	int x, w;
 910	if (softwareRenderer->target2Bd) {
 911		x = 0;
 912		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 913			uint32_t backdrop = 0;
 914			if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 915				backdrop |= softwareRenderer->normalPalette[0];
 916			} else {
 917				backdrop |= softwareRenderer->variantPalette[0];
 918			}
 919			int end = softwareRenderer->windows[w].endX;
 920			for (; x < end; ++x) {
 921				uint32_t color = softwareRenderer->row[x];
 922				if (color & FLAG_TARGET_1) {
 923					softwareRenderer->row[x] = _mix(softwareRenderer->alphaB[x], backdrop, softwareRenderer->alphaA[x], color);
 924				}
 925			}
 926		}
 927	}
 928	if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) {
 929		x = 0;
 930		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 931			if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 932				continue;
 933			}
 934			int end = softwareRenderer->windows[w].endX;
 935			if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 936				for (; x < end; ++x) {
 937					uint32_t color = softwareRenderer->row[x];
 938					if ((color & 0xFF000000) == FLAG_REBLEND) {
 939						softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy);
 940					}
 941				}
 942			} else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 943				for (; x < end; ++x) {
 944					uint32_t color = softwareRenderer->row[x];
 945					if ((color & 0xFF000000) == FLAG_REBLEND) {
 946						softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy);
 947					}
 948				}
 949			}
 950		}
 951	}
 952}
 953
 954int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRenderer* renderer, int y) {
 955	int w;
 956	int spriteLayers = 0;
 957	if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
 958		if (renderer->oamDirty) {
 959			renderer->oamMax = GBAVideoRendererCleanOAM(renderer->d.oam->obj, renderer->sprites, renderer->objOffsetY, renderer->masterHeight, renderer->combinedObjSort);
 960			renderer->oamDirty = false;
 961		}
 962		renderer->spriteCyclesRemaining = GBARegisterDISPCNTIsHblankIntervalFree(renderer->dispcnt) ? OBJ_HBLANK_FREE_LENGTH : OBJ_LENGTH;
 963		int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
 964		int mosaicY = y - (y % mosaicV);
 965		int i;
 966		for (i = 0; i < renderer->oamMax; ++i) {
 967			struct GBAVideoRendererSprite* sprite = &renderer->sprites[i];
 968			int localY = y;
 969			renderer->end = 0;
 970			if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
 971				continue;
 972			}
 973			if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) {
 974				localY = mosaicY;
 975				if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) {
 976					localY = sprite->y;
 977				}
 978				if (localY >= (sprite->endY & 0xFF)) {
 979					localY = sprite->endY - 1;
 980				}
 981			}
 982			for (w = 0; w < renderer->nWindows; ++w) {
 983				renderer->currentWindow = renderer->windows[w].control;
 984				renderer->start = renderer->end;
 985				renderer->end = renderer->windows[w].endX;
 986				// TODO: partial sprite drawing
 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			renderer->spriteCyclesRemaining -= sprite->cycles;
 995			if (renderer->spriteCyclesRemaining <= 0) {
 996				break;
 997			}
 998		}
 999	}
1000	return spriteLayers;
1001}
1002
1003static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
1004	int i;
1005	memset(renderer->alphaA, renderer->blda, sizeof(renderer->alphaA));
1006	memset(renderer->alphaB, renderer->bldb, sizeof(renderer->alphaB));
1007	if (renderer->blendEffect == BLEND_BRIGHTEN) {
1008		for (i = 0; i < 512; ++i) {
1009			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
1010		}
1011	} else if (renderer->blendEffect == BLEND_DARKEN) {
1012		for (i = 0; i < 512; ++i) {
1013			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
1014		}
1015	} else {
1016		for (i = 0; i < 512; ++i) {
1017			renderer->variantPalette[i] = renderer->normalPalette[i];
1018		}
1019	}
1020	unsigned highlightAmount = renderer->d.highlightAmount >> 4;
1021
1022	if (highlightAmount) {
1023		for (i = 0; i < 512; ++i) {
1024			renderer->highlightPalette[i] = _mix(0x10 - highlightAmount, renderer->normalPalette[i], highlightAmount, renderer->d.highlightColor);
1025			renderer->highlightVariantPalette[i] = _mix(0x10 - highlightAmount, renderer->variantPalette[i], highlightAmount, renderer->d.highlightColor);
1026		}
1027	}
1028}