all repos — mgba @ f48a69e8e5a3aa8965140e15e643841c70eb6c1e

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