all repos — mgba @ 1a2831200c5e28c408c75bbbd4c14a28801c8216

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	softwareRenderer->spriteCyclesRemaining = GBARegisterDISPCNTIsHblankIntervalFree(softwareRenderer->dispcnt) ? OBJ_HBLANK_FREE_LENGTH : OBJ_LENGTH;
 586	int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y);
 587	softwareRenderer->d.vramOBJ[0] = objVramBase;
 588	if (softwareRenderer->lastHighlightAmount != softwareRenderer->d.highlightAmount) {
 589		softwareRenderer->lastHighlightAmount = softwareRenderer->d.highlightAmount;
 590		if (softwareRenderer->lastHighlightAmount) {
 591			softwareRenderer->blendDirty = true;
 592		}
 593	}
 594
 595	if (softwareRenderer->blendDirty) {
 596		_updatePalettes(softwareRenderer);
 597		softwareRenderer->blendDirty = false;
 598	}
 599	softwareRenderer->forceTarget1 = false;
 600
 601	softwareRenderer->bg[0].highlight = softwareRenderer->d.highlightBG[0];
 602	softwareRenderer->bg[1].highlight = softwareRenderer->d.highlightBG[1];
 603	softwareRenderer->bg[2].highlight = softwareRenderer->d.highlightBG[2];
 604	softwareRenderer->bg[3].highlight = softwareRenderer->d.highlightBG[3];
 605
 606	int w;
 607	unsigned priority;
 608	for (priority = 0; priority < 4; ++priority) {
 609		softwareRenderer->end = 0;
 610		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 611			softwareRenderer->start = softwareRenderer->end;
 612			softwareRenderer->end = softwareRenderer->windows[w].endX;
 613			softwareRenderer->currentWindow = softwareRenderer->windows[w].control;
 614			if (spriteLayers & (1 << priority)) {
 615				GBAVideoSoftwareRendererPostprocessSprite(softwareRenderer, priority);
 616			}
 617			if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) < 2) {
 618				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[0], y);
 619			}
 620			if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) < 2) {
 621				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[1], y);
 622			}
 623			if (TEST_LAYER_ENABLED(2)) {
 624				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
 625				case 0:
 626					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[2], y);
 627					break;
 628				case 1:
 629				case 2:
 630					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[2], y);
 631					break;
 632				case 3:
 633					GBAVideoSoftwareRendererDrawBackgroundMode3(softwareRenderer, &softwareRenderer->bg[2], y);
 634					break;
 635				case 4:
 636					GBAVideoSoftwareRendererDrawBackgroundMode4(softwareRenderer, &softwareRenderer->bg[2], y);
 637					break;
 638				case 5:
 639					GBAVideoSoftwareRendererDrawBackgroundMode5(softwareRenderer, &softwareRenderer->bg[2], y);
 640					break;
 641				}
 642			}
 643			if (TEST_LAYER_ENABLED(3)) {
 644				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
 645				case 0:
 646					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[3], y);
 647					break;
 648				case 2:
 649					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[3], y);
 650					break;
 651				}
 652			}
 653		}
 654	}
 655	if (softwareRenderer->forceTarget1 && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) {
 656		uint32_t mask = FLAG_REBLEND | FLAG_IS_BACKGROUND;
 657		uint32_t match = FLAG_REBLEND;
 658		if (GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
 659			mask |= FLAG_OBJWIN;
 660			if (GBAWindowControlIsBlendEnable(softwareRenderer->objwin.packed)) {
 661				match |= FLAG_OBJWIN;
 662			}
 663		}
 664		int x = 0;
 665		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 666			int end = softwareRenderer->windows[w].endX;
 667			if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 668				x = end;
 669				continue;
 670			}
 671			if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 672				for (; x < end; ++x) {
 673					uint32_t color = softwareRenderer->row[x];
 674					if ((color & mask) == match) {
 675						softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy);
 676					}
 677				}
 678			} else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 679				for (; x < end; ++x) {
 680					uint32_t color = softwareRenderer->row[x];
 681					if ((color & mask) == match) {
 682						softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy);
 683					}
 684				}
 685			}
 686		}
 687	}
 688	if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) {
 689		softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx;
 690		softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy;
 691		softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx;
 692		softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy;
 693	}
 694
 695	if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 3) {
 696		++softwareRenderer->bg[0].enabled;
 697		DIRTY_SCANLINE(softwareRenderer, y);
 698	}
 699	if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 3) {
 700		++softwareRenderer->bg[1].enabled;
 701		DIRTY_SCANLINE(softwareRenderer, y);
 702	}
 703	if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 3) {
 704		++softwareRenderer->bg[2].enabled;
 705		DIRTY_SCANLINE(softwareRenderer, y);
 706	}
 707	if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 3) {
 708		++softwareRenderer->bg[3].enabled;
 709		DIRTY_SCANLINE(softwareRenderer, y);
 710	}
 711
 712	GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer);
 713
 714#ifdef COLOR_16_BIT
 715	size_t x;
 716	for (x = 0; x < softwareRenderer->masterEnd; ++x) {
 717		row[x] = softwareRenderer->row[x];
 718		row[x + 1] = softwareRenderer->row[x + 1];
 719		row[x + 2] = softwareRenderer->row[x + 2];
 720		row[x + 3] = softwareRenderer->row[x + 3];
 721	}
 722#else
 723	memcpy(row, softwareRenderer->row, softwareRenderer->masterEnd * sizeof(*row));
 724#endif
 725}
 726
 727static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
 728	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 729
 730	softwareRenderer->nextY = 0;
 731	if (softwareRenderer->temporaryBuffer) {
 732		mappedMemoryFree(softwareRenderer->temporaryBuffer, softwareRenderer->masterEnd * softwareRenderer->masterHeight * 4);
 733		softwareRenderer->temporaryBuffer = 0;
 734	}
 735	softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;
 736	softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy;
 737	softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;
 738	softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
 739
 740	if (softwareRenderer->bg[0].enabled > 0) {
 741		softwareRenderer->bg[0].enabled = 3;
 742	}
 743	if (softwareRenderer->bg[1].enabled > 0) {
 744		softwareRenderer->bg[1].enabled = 3;
 745	}
 746	if (softwareRenderer->bg[2].enabled > 0) {
 747		softwareRenderer->bg[2].enabled = 3;
 748	}
 749	if (softwareRenderer->bg[3].enabled > 0) {
 750		softwareRenderer->bg[3].enabled = 3;
 751	}
 752}
 753
 754static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
 755	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 756	*stride = softwareRenderer->outputBufferStride;
 757	*pixels = softwareRenderer->outputBuffer;
 758}
 759
 760static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
 761	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 762
 763	const color_t* colorPixels = pixels;
 764	int i;
 765	for (i = 0; i < softwareRenderer->masterHeight; ++i) {
 766		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], softwareRenderer->masterEnd * BYTES_PER_PIXEL);
 767	}
 768}
 769
 770static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool active) {
 771	int wasActive = renderer->bg[bg].enabled;
 772	if (!active) {
 773		renderer->bg[bg].enabled = 0;
 774	} else if (!wasActive && active) {
 775		if (renderer->nextY == 0 || GBARegisterDISPCNTGetMode(renderer->dispcnt) > 2) {
 776			// TODO: Investigate in more depth how switching background works in different modes
 777			renderer->bg[bg].enabled = 3;
 778		} else {
 779			renderer->bg[bg].enabled = 1;
 780		}
 781	}
 782}
 783
 784static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
 785	_enableBg(renderer, 0, GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt));
 786	_enableBg(renderer, 1, GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt));
 787	_enableBg(renderer, 2, GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt));
 788	_enableBg(renderer, 3, GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt));
 789}
 790
 791static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 792	UNUSED(renderer);
 793	bg->priority = GBARegisterBGCNTGetPriority(value);
 794	bg->charBase = GBARegisterBGCNTGetCharBase(value) << 14;
 795	bg->charBase &= 0xC000;
 796	bg->mosaic = GBARegisterBGCNTGetMosaic(value);
 797	bg->multipalette = GBARegisterBGCNTGet256Color(value);
 798	bg->screenBase &= ~0xF800;
 799	bg->screenBase |= GBARegisterBGCNTGetScreenBase(value) << 11;
 800	bg->overflow = GBARegisterBGCNTGetOverflow(value);
 801	bg->size = GBARegisterBGCNTGetSize(value);
 802	bg->control = value;
 803	bg->yCache = -1;
 804}
 805
 806static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 807	bg->refx = (bg->refx & 0xFFFF0000) | value;
 808	bg->sx = bg->refx;
 809}
 810
 811static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 812	bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
 813	bg->refx <<= 4;
 814	bg->refx >>= 4;
 815	bg->sx = bg->refx;
 816}
 817
 818static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 819	bg->refy = (bg->refy & 0xFFFF0000) | value;
 820	bg->sy = bg->refy;
 821}
 822
 823static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 824	bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
 825	bg->refy <<= 4;
 826	bg->refy >>= 4;
 827	bg->sy = bg->refy;
 828}
 829
 830static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
 831	enum GBAVideoBlendEffect oldEffect = renderer->blendEffect;
 832
 833	renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
 834	renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
 835	renderer->bg[2].target1 = GBARegisterBLDCNTGetTarget1Bg2(value);
 836	renderer->bg[3].target1 = GBARegisterBLDCNTGetTarget1Bg3(value);
 837	renderer->bg[0].target2 = GBARegisterBLDCNTGetTarget2Bg0(value);
 838	renderer->bg[1].target2 = GBARegisterBLDCNTGetTarget2Bg1(value);
 839	renderer->bg[2].target2 = GBARegisterBLDCNTGetTarget2Bg2(value);
 840	renderer->bg[3].target2 = GBARegisterBLDCNTGetTarget2Bg3(value);
 841
 842	renderer->blendEffect = GBARegisterBLDCNTGetEffect(value);
 843	renderer->target1Obj = GBARegisterBLDCNTGetTarget1Obj(value);
 844	renderer->target1Bd = GBARegisterBLDCNTGetTarget1Bd(value);
 845	renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
 846	renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
 847
 848	if (oldEffect != renderer->blendEffect) {
 849		renderer->blendDirty = true;
 850	}
 851}
 852
 853void GBAVideoSoftwareRendererPreprocessBuffer(struct GBAVideoSoftwareRenderer* softwareRenderer, int y) {
 854	int x;
 855	int masterEnd = softwareRenderer->masterEnd;
 856	for (x = 0; x < masterEnd; x += 4) {
 857		softwareRenderer->spriteLayer[x] = FLAG_UNWRITTEN;
 858		softwareRenderer->spriteLayer[x + 1] = FLAG_UNWRITTEN;
 859		softwareRenderer->spriteLayer[x + 2] = FLAG_UNWRITTEN;
 860		softwareRenderer->spriteLayer[x + 3] = FLAG_UNWRITTEN;
 861	}
 862
 863	softwareRenderer->windows[0].endX = softwareRenderer->masterEnd;
 864	softwareRenderer->nWindows = 1;
 865	if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
 866		softwareRenderer->windows[0].control = softwareRenderer->winout;
 867		if (GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt)) {
 868			_breakWindow(softwareRenderer, &softwareRenderer->winN[1], y);
 869		}
 870		if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt)) {
 871			_breakWindow(softwareRenderer, &softwareRenderer->winN[0], y);
 872		}
 873	} else {
 874		softwareRenderer->windows[0].control.packed = 0xFF;
 875	}
 876
 877	GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
 878
 879	if (softwareRenderer->blendDirty) {
 880		_updatePalettes(softwareRenderer);
 881		softwareRenderer->blendDirty = false;
 882	}
 883
 884	int w;
 885	x = 0;
 886	for (w = 0; w < softwareRenderer->nWindows; ++w) {
 887		// TOOD: handle objwin on backdrop
 888		uint32_t backdrop = FLAG_UNWRITTEN | FLAG_PRIORITY | FLAG_IS_BACKGROUND;
 889		if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 890			backdrop |= softwareRenderer->normalPalette[0];
 891		} else {
 892			backdrop |= softwareRenderer->variantPalette[0];
 893		}
 894		int end = softwareRenderer->windows[w].endX;
 895		for (; x & 3; ++x) {
 896			softwareRenderer->row[x] = backdrop;
 897		}
 898		for (; x < end - 3; x += 4) {
 899			softwareRenderer->row[x] = backdrop;
 900			softwareRenderer->row[x + 1] = backdrop;
 901			softwareRenderer->row[x + 2] = backdrop;
 902			softwareRenderer->row[x + 3] = backdrop;
 903		}
 904		for (; x < end; ++x) {
 905			softwareRenderer->row[x] = backdrop;
 906		}
 907	}
 908}
 909
 910void GBAVideoSoftwareRendererPostprocessBuffer(struct GBAVideoSoftwareRenderer* softwareRenderer) {
 911	int x, w;
 912	if (softwareRenderer->target2Bd) {
 913		x = 0;
 914		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 915			uint32_t backdrop = 0;
 916			if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 917				backdrop |= softwareRenderer->normalPalette[0];
 918			} else {
 919				backdrop |= softwareRenderer->variantPalette[0];
 920			}
 921			int end = softwareRenderer->windows[w].endX;
 922			for (; x < end; ++x) {
 923				uint32_t color = softwareRenderer->row[x];
 924				if (color & FLAG_TARGET_1) {
 925					softwareRenderer->row[x] = _mix(softwareRenderer->alphaB[x], backdrop, softwareRenderer->alphaA[x], color);
 926				}
 927			}
 928		}
 929	}
 930	if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) {
 931		x = 0;
 932		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 933			if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 934				continue;
 935			}
 936			int end = softwareRenderer->windows[w].endX;
 937			if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 938				for (; x < end; ++x) {
 939					uint32_t color = softwareRenderer->row[x];
 940					if ((color & 0xFF000000) == FLAG_REBLEND) {
 941						softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy);
 942					}
 943				}
 944			} else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 945				for (; x < end; ++x) {
 946					uint32_t color = softwareRenderer->row[x];
 947					if ((color & 0xFF000000) == FLAG_REBLEND) {
 948						softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy);
 949					}
 950				}
 951			}
 952		}
 953	}
 954}
 955
 956int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRenderer* renderer, int y) {
 957	int w;
 958	int spriteLayers = 0;
 959	if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
 960		if (renderer->oamDirty) {
 961			renderer->oamMax = GBAVideoRendererCleanOAM(renderer->d.oam->obj, renderer->sprites, renderer->objOffsetY, renderer->masterHeight, renderer->combinedObjSort);
 962			renderer->oamDirty = false;
 963		}
 964		int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
 965		int mosaicY = y - (y % mosaicV);
 966		int i;
 967		for (i = 0; i < renderer->oamMax; ++i) {
 968			struct GBAVideoRendererSprite* sprite = &renderer->sprites[i];
 969			int localY = y;
 970			renderer->end = 0;
 971			if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
 972				continue;
 973			}
 974			if (GBAObjAttributesAIsMosaic(sprite->obj.a) && mosaicV > 1) {
 975				localY = mosaicY;
 976				if (localY < sprite->y && sprite->y < GBA_VIDEO_VERTICAL_PIXELS) {
 977					localY = sprite->y;
 978				}
 979				if (localY >= (sprite->endY & 0xFF)) {
 980					localY = sprite->endY - 1;
 981				}
 982			}
 983			for (w = 0; w < renderer->nWindows; ++w) {
 984				renderer->currentWindow = renderer->windows[w].control;
 985				renderer->start = renderer->end;
 986				renderer->end = renderer->windows[w].endX;
 987				// TODO: partial sprite drawing
 988				if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed) && !GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt)) {
 989					continue;
 990				}
 991
 992				int drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, sprite->index, localY);
 993				spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
 994			}
 995			renderer->spriteCyclesRemaining -= sprite->cycles;
 996			if (renderer->spriteCyclesRemaining <= 0) {
 997				break;
 998			}
 999		}
1000	}
1001	return spriteLayers;
1002}
1003
1004static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
1005	int i;
1006	memset(renderer->alphaA, renderer->blda, sizeof(renderer->alphaA));
1007	memset(renderer->alphaB, renderer->bldb, sizeof(renderer->alphaB));
1008	if (renderer->blendEffect == BLEND_BRIGHTEN) {
1009		for (i = 0; i < 512; ++i) {
1010			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
1011		}
1012	} else if (renderer->blendEffect == BLEND_DARKEN) {
1013		for (i = 0; i < 512; ++i) {
1014			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
1015		}
1016	} else {
1017		for (i = 0; i < 512; ++i) {
1018			renderer->variantPalette[i] = renderer->normalPalette[i];
1019		}
1020	}
1021	unsigned highlightAmount = renderer->d.highlightAmount >> 4;
1022
1023	if (highlightAmount) {
1024		for (i = 0; i < 512; ++i) {
1025			renderer->highlightPalette[i] = _mix(0x10 - highlightAmount, renderer->normalPalette[i], highlightAmount, renderer->d.highlightColor);
1026			renderer->highlightVariantPalette[i] = _mix(0x10 - highlightAmount, renderer->variantPalette[i], highlightAmount, renderer->d.highlightColor);
1027		}
1028	}
1029}