all repos — mgba @ a6ce525da1ed0f7e14e8db927871d2127681fc95

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