all repos — mgba @ b6d189d88b48d7058b4cd004f64edb6114d32378

mGBA Game Boy Advance Emulator

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

   1/* Copyright (c) 2013-2014 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 "video-software.h"
   7
   8#include "gba.h"
   9#include "gba-io.h"
  10
  11#ifdef NDEBUG
  12#define VIDEO_CHECKS false
  13#else
  14#define VIDEO_CHECKS true
  15#endif
  16
  17static const int _objSizes[32] = {
  18	8, 8,
  19	16, 16,
  20	32, 32,
  21	64, 64,
  22	16, 8,
  23	32, 8,
  24	32, 16,
  25	64, 32,
  26	8, 16,
  27	8, 32,
  28	16, 32,
  29	32, 64,
  30	0, 0,
  31	0, 0,
  32	0, 0,
  33	0, 0
  34};
  35
  36static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
  37static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
  38static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
  39static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
  40static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
  41static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
  42static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
  43static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
  44static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels);
  45
  46static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
  47static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
  48static void GBAVideoSoftwareRendererWriteBGPA(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  49static void GBAVideoSoftwareRendererWriteBGPB(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  50static void GBAVideoSoftwareRendererWriteBGPC(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  51static void GBAVideoSoftwareRendererWriteBGPD(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  52static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  53static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  54static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  55static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
  56static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
  57
  58static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y);
  59static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
  60static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
  61static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
  62static void _drawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
  63static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
  64static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer);
  65static int _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y);
  66static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority);
  67
  68static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
  69static inline unsigned _brighten(unsigned color, int y);
  70static inline unsigned _darken(unsigned color, int y);
  71static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB);
  72
  73static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y);
  74static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win);
  75
  76void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
  77	renderer->d.init = GBAVideoSoftwareRendererInit;
  78	renderer->d.reset = GBAVideoSoftwareRendererInit;
  79	renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
  80	renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
  81	renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
  82	renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
  83	renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
  84	renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
  85	renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
  86	renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
  87}
  88
  89static void GBAVideoSoftwareRendererInit(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	memset(softwareRenderer->normalPalette, 0, sizeof(softwareRenderer->normalPalette));
 101	memset(softwareRenderer->variantPalette, 0, sizeof(softwareRenderer->variantPalette));
 102
 103	softwareRenderer->blda = 0;
 104	softwareRenderer->bldb = 0;
 105	softwareRenderer->bldy = 0;
 106
 107	softwareRenderer->winN[0] = (struct WindowN) { .control = { .priority = 0 } };
 108	softwareRenderer->winN[1] = (struct WindowN) { .control = { .priority = 1 } };
 109	softwareRenderer->objwin = (struct WindowControl) { .priority = 2 };
 110	softwareRenderer->winout = (struct WindowControl) { .priority = 3 };
 111	softwareRenderer->oamMax = 0;
 112
 113	softwareRenderer->mosaic = 0;
 114
 115	for (i = 0; i < 4; ++i) {
 116		struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
 117		bg->index = i;
 118		bg->enabled = 0;
 119		bg->priority = 0;
 120		bg->charBase = 0;
 121		bg->mosaic = 0;
 122		bg->multipalette = 0;
 123		bg->screenBase = 0;
 124		bg->overflow = 0;
 125		bg->size = 0;
 126		bg->target1 = 0;
 127		bg->target2 = 0;
 128		bg->x = 0;
 129		bg->y = 0;
 130		bg->refx = 0;
 131		bg->refy = 0;
 132		bg->dx = 256;
 133		bg->dmx = 0;
 134		bg->dy = 0;
 135		bg->dmy = 256;
 136		bg->sx = 0;
 137		bg->sy = 0;
 138	}
 139}
 140
 141static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
 142	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 143	UNUSED(softwareRenderer);
 144}
 145
 146static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
 147	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 148	switch (address) {
 149	case REG_DISPCNT:
 150		softwareRenderer->dispcnt = value;
 151		GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
 152		break;
 153	case REG_BG0CNT:
 154		value &= 0xFFCF;
 155		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
 156		break;
 157	case REG_BG1CNT:
 158		value &= 0xFFCF;
 159		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
 160		break;
 161	case REG_BG2CNT:
 162		value &= 0xFFCF;
 163		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
 164		break;
 165	case REG_BG3CNT:
 166		value &= 0xFFCF;
 167		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
 168		break;
 169	case REG_BG0HOFS:
 170		value &= 0x01FF;
 171		softwareRenderer->bg[0].x = value;
 172		break;
 173	case REG_BG0VOFS:
 174		value &= 0x01FF;
 175		softwareRenderer->bg[0].y = value;
 176		break;
 177	case REG_BG1HOFS:
 178		value &= 0x01FF;
 179		softwareRenderer->bg[1].x = value;
 180		break;
 181	case REG_BG1VOFS:
 182		value &= 0x01FF;
 183		softwareRenderer->bg[1].y = value;
 184		break;
 185	case REG_BG2HOFS:
 186		value &= 0x01FF;
 187		softwareRenderer->bg[2].x = value;
 188		break;
 189	case REG_BG2VOFS:
 190		value &= 0x01FF;
 191		softwareRenderer->bg[2].y = value;
 192		break;
 193	case REG_BG3HOFS:
 194		value &= 0x01FF;
 195		softwareRenderer->bg[3].x = value;
 196		break;
 197	case REG_BG3VOFS:
 198		value &= 0x01FF;
 199		softwareRenderer->bg[3].y = value;
 200		break;
 201	case REG_BG2PA:
 202		GBAVideoSoftwareRendererWriteBGPA(&softwareRenderer->bg[2], value);
 203		break;
 204	case REG_BG2PB:
 205		GBAVideoSoftwareRendererWriteBGPB(&softwareRenderer->bg[2], value);
 206		break;
 207	case REG_BG2PC:
 208		GBAVideoSoftwareRendererWriteBGPC(&softwareRenderer->bg[2], value);
 209		break;
 210	case REG_BG2PD:
 211		GBAVideoSoftwareRendererWriteBGPD(&softwareRenderer->bg[2], value);
 212		break;
 213	case REG_BG2X_LO:
 214		GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[2], value);
 215		break;
 216	case REG_BG2X_HI:
 217		GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[2], value);
 218		break;
 219	case REG_BG2Y_LO:
 220		GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[2], value);
 221		break;
 222	case REG_BG2Y_HI:
 223		GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[2], value);
 224		break;
 225	case REG_BG3PA:
 226		GBAVideoSoftwareRendererWriteBGPA(&softwareRenderer->bg[3], value);
 227		break;
 228	case REG_BG3PB:
 229		GBAVideoSoftwareRendererWriteBGPB(&softwareRenderer->bg[3], value);
 230		break;
 231	case REG_BG3PC:
 232		GBAVideoSoftwareRendererWriteBGPC(&softwareRenderer->bg[3], value);
 233		break;
 234	case REG_BG3PD:
 235		GBAVideoSoftwareRendererWriteBGPD(&softwareRenderer->bg[3], value);
 236		break;
 237	case REG_BG3X_LO:
 238		GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[3], value);
 239		break;
 240	case REG_BG3X_HI:
 241		GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[3], value);
 242		break;
 243	case REG_BG3Y_LO:
 244		GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[3], value);
 245		break;
 246	case REG_BG3Y_HI:
 247		GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[3], value);
 248		break;
 249	case REG_BLDCNT:
 250		GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
 251		break;
 252	case REG_BLDALPHA:
 253		softwareRenderer->blda = value & 0x1F;
 254		if (softwareRenderer->blda > 0x10) {
 255			softwareRenderer->blda = 0x10;
 256		}
 257		softwareRenderer->bldb = (value >> 8) & 0x1F;
 258		if (softwareRenderer->bldb > 0x10) {
 259			softwareRenderer->bldb = 0x10;
 260		}
 261		break;
 262	case REG_BLDY:
 263		softwareRenderer->bldy = value & 0x1F;
 264		if (softwareRenderer->bldy > 0x10) {
 265			softwareRenderer->bldy = 0x10;
 266		}
 267		_updatePalettes(softwareRenderer);
 268		break;
 269	case REG_WIN0H:
 270		softwareRenderer->winN[0].h.end = value;
 271		softwareRenderer->winN[0].h.start = value >> 8;
 272		if (softwareRenderer->winN[0].h.start > VIDEO_HORIZONTAL_PIXELS && softwareRenderer->winN[0].h.start > softwareRenderer->winN[0].h.end) {
 273			softwareRenderer->winN[0].h.start = 0;
 274		}
 275		if (softwareRenderer->winN[0].h.end > VIDEO_HORIZONTAL_PIXELS) {
 276			softwareRenderer->winN[0].h.end = VIDEO_HORIZONTAL_PIXELS;
 277			if (softwareRenderer->winN[0].h.start > VIDEO_HORIZONTAL_PIXELS) {
 278				softwareRenderer->winN[0].h.start = VIDEO_HORIZONTAL_PIXELS;
 279			}
 280		}
 281		break;
 282	case REG_WIN1H:
 283		softwareRenderer->winN[1].h.end = value;
 284		softwareRenderer->winN[1].h.start = value >> 8;
 285		if (softwareRenderer->winN[1].h.start > VIDEO_HORIZONTAL_PIXELS && softwareRenderer->winN[1].h.start > softwareRenderer->winN[1].h.end) {
 286			softwareRenderer->winN[1].h.start = 0;
 287		}
 288		if (softwareRenderer->winN[1].h.end > VIDEO_HORIZONTAL_PIXELS) {
 289			softwareRenderer->winN[1].h.end = VIDEO_HORIZONTAL_PIXELS;
 290			if (softwareRenderer->winN[1].h.start > VIDEO_HORIZONTAL_PIXELS) {
 291				softwareRenderer->winN[1].h.start = VIDEO_HORIZONTAL_PIXELS;
 292			}
 293		}
 294		break;
 295	case REG_WIN0V:
 296		softwareRenderer->winN[0].v.end = value;
 297		softwareRenderer->winN[0].v.start = value >> 8;
 298		if (softwareRenderer->winN[0].v.start > VIDEO_VERTICAL_PIXELS && softwareRenderer->winN[0].v.start > softwareRenderer->winN[0].v.end) {
 299			softwareRenderer->winN[0].v.start = 0;
 300		}
 301		if (softwareRenderer->winN[0].v.end > VIDEO_VERTICAL_PIXELS) {
 302			softwareRenderer->winN[0].v.end = VIDEO_VERTICAL_PIXELS;
 303			if (softwareRenderer->winN[0].v.start > VIDEO_VERTICAL_PIXELS) {
 304				softwareRenderer->winN[0].v.start = VIDEO_VERTICAL_PIXELS;
 305			}
 306		}
 307		break;
 308	case REG_WIN1V:
 309		softwareRenderer->winN[1].v.end = value;
 310		softwareRenderer->winN[1].v.start = value >> 8;
 311		if (softwareRenderer->winN[1].v.start > VIDEO_VERTICAL_PIXELS && softwareRenderer->winN[1].v.start > softwareRenderer->winN[1].v.end) {
 312			softwareRenderer->winN[1].v.start = 0;
 313		}
 314		if (softwareRenderer->winN[1].v.end > VIDEO_VERTICAL_PIXELS) {
 315			softwareRenderer->winN[1].v.end = VIDEO_VERTICAL_PIXELS;
 316			if (softwareRenderer->winN[1].v.start > VIDEO_VERTICAL_PIXELS) {
 317				softwareRenderer->winN[1].v.start = VIDEO_VERTICAL_PIXELS;
 318			}
 319		}
 320		break;
 321	case REG_WININ:
 322		softwareRenderer->winN[0].control.packed = value;
 323		softwareRenderer->winN[1].control.packed = value >> 8;
 324		break;
 325	case REG_WINOUT:
 326		softwareRenderer->winout.packed = value;
 327		softwareRenderer->objwin.packed = value >> 8;
 328		break;
 329	case REG_MOSAIC:
 330		softwareRenderer->mosaic = value;
 331		break;
 332	case REG_GREENSWP:
 333		GBALog(0, GBA_LOG_STUB, "Stub video register write: 0x%03X", address);
 334		break;
 335	default:
 336		GBALog(0, GBA_LOG_GAME_ERROR, "Invalid video register: 0x%03X", address);
 337	}
 338	return value;
 339}
 340
 341static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
 342	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 343	softwareRenderer->oamDirty = 1;
 344	UNUSED(oam);
 345}
 346
 347static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
 348	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 349#ifdef COLOR_16_BIT
 350#ifdef COLOR_5_6_5
 351	unsigned color = 0;
 352	color |= (value & 0x001F) << 11;
 353	color |= (value & 0x03E0) << 1;
 354	color |= (value & 0x7C00) >> 10;
 355#else
 356	unsigned color = value;
 357#endif
 358#else
 359	unsigned color = 0;
 360	color |= (value << 3) & 0xF8;
 361	color |= (value << 6) & 0xF800;
 362	color |= (value << 9) & 0xF80000;
 363#endif
 364	softwareRenderer->normalPalette[address >> 1] = color;
 365	if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 366		softwareRenderer->variantPalette[address >> 1] = _brighten(color, softwareRenderer->bldy);
 367	} else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 368		softwareRenderer->variantPalette[address >> 1] = _darken(color, softwareRenderer->bldy);
 369	}
 370}
 371
 372static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y) {
 373	if (win->v.end >= win->v.start) {
 374		if (y >= win->v.end) {
 375			return;
 376		}
 377		if (y < win->v.start) {
 378			return;
 379		}
 380	} else if (y >= win->v.end && y < win->v.start) {
 381		return;
 382	}
 383	if (win->h.end > VIDEO_HORIZONTAL_PIXELS || win->h.end < win->h.start) {
 384		struct WindowN splits[2] = { *win, *win };
 385		splits[0].h.start = 0;
 386		splits[1].h.end = VIDEO_HORIZONTAL_PIXELS;
 387		_breakWindowInner(softwareRenderer, &splits[0]);
 388		_breakWindowInner(softwareRenderer, &splits[1]);
 389	} else {
 390		_breakWindowInner(softwareRenderer, win);
 391	}
 392}
 393
 394static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win) {
 395	int activeWindow;
 396	int startX = 0;
 397	if (win->h.end > 0) {
 398		for (activeWindow = 0; activeWindow < softwareRenderer->nWindows; ++activeWindow) {
 399			if (win->h.start < softwareRenderer->windows[activeWindow].endX) {
 400				// Insert a window before the end of the active window
 401				struct Window oldWindow = softwareRenderer->windows[activeWindow];
 402				if (win->h.start > startX) {
 403					// And after the start of the active window
 404					int nextWindow = softwareRenderer->nWindows;
 405					++softwareRenderer->nWindows;
 406					for (; nextWindow > activeWindow; --nextWindow) {
 407						softwareRenderer->windows[nextWindow] = softwareRenderer->windows[nextWindow - 1];
 408					}
 409					softwareRenderer->windows[activeWindow].endX = win->h.start;
 410					++activeWindow;
 411				}
 412				softwareRenderer->windows[activeWindow].control = win->control;
 413				softwareRenderer->windows[activeWindow].endX = win->h.end;
 414				if (win->h.end >= oldWindow.endX) {
 415					// Trim off extra windows we've overwritten
 416					for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) {
 417#ifdef DEBUG
 418						if (activeWindow >= MAX_WINDOW) {
 419							GBALog(0, GBA_LOG_DANGER, "Out of bounds window write will occur");
 420							return;
 421						}
 422#endif
 423						softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1];
 424						--softwareRenderer->nWindows;
 425					}
 426				} else {
 427					++activeWindow;
 428					int nextWindow = softwareRenderer->nWindows;
 429					++softwareRenderer->nWindows;
 430					for (; nextWindow > activeWindow; --nextWindow) {
 431						softwareRenderer->windows[nextWindow] = softwareRenderer->windows[nextWindow - 1];
 432					}
 433					softwareRenderer->windows[activeWindow] = oldWindow;
 434				}
 435				break;
 436			}
 437			startX = softwareRenderer->windows[activeWindow].endX;
 438		}
 439	}
 440#ifdef DEBUG
 441	if (softwareRenderer->nWindows > MAX_WINDOW) {
 442		GBALog(0, GBA_LOG_ABORT, "Out of bounds window write occurred!");
 443	}
 444#endif
 445}
 446
 447static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer) {
 448	int i;
 449	int oamMax = 0;
 450	for (i = 0; i < 128; ++i) {
 451		struct GBAObj obj;
 452		LOAD_16(obj.a, 0, &renderer->d.oam->obj[i].a);
 453		LOAD_16(obj.b, 0, &renderer->d.oam->obj[i].b);
 454		LOAD_16(obj.c, 0, &renderer->d.oam->obj[i].c);
 455		if (GBAObjAttributesAIsTransformed(obj.a) || !GBAObjAttributesAIsDisable(obj.a)) {
 456			int height = _objSizes[GBAObjAttributesAGetShape(obj.a) * 8 + GBAObjAttributesBGetSize(obj.b) * 2 + 1];
 457			if (GBAObjAttributesAIsTransformed(obj.a)) {
 458				height <<= GBAObjAttributesAGetDoubleSize(obj.a);
 459			}
 460			if (GBAObjAttributesAGetY(obj.a) < VIDEO_VERTICAL_PIXELS || GBAObjAttributesAGetY(obj.a) + height >= VIDEO_VERTICAL_TOTAL_PIXELS) {
 461				renderer->sprites[oamMax].y = GBAObjAttributesAGetY(obj.a);
 462				renderer->sprites[oamMax].endY = GBAObjAttributesAGetY(obj.a) + height;
 463				renderer->sprites[oamMax].obj = obj;
 464				++oamMax;
 465			}
 466		}
 467	}
 468	renderer->oamMax = oamMax;
 469	renderer->oamDirty = 0;
 470}
 471
 472
 473static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
 474	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 475
 476	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
 477	if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) {
 478		int x;
 479		for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
 480			row[x] = GBA_COLOR_WHITE;
 481		}
 482		return;
 483	}
 484
 485	int x;
 486	for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; x += 4) {
 487		softwareRenderer->spriteLayer[x] = FLAG_UNWRITTEN;
 488		softwareRenderer->spriteLayer[x + 1] = FLAG_UNWRITTEN;
 489		softwareRenderer->spriteLayer[x + 2] = FLAG_UNWRITTEN;
 490		softwareRenderer->spriteLayer[x + 3] = FLAG_UNWRITTEN;
 491	}
 492
 493	softwareRenderer->windows[0].endX = VIDEO_HORIZONTAL_PIXELS;
 494	softwareRenderer->nWindows = 1;
 495	if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
 496		softwareRenderer->windows[0].control = softwareRenderer->winout;
 497		if (GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt)) {
 498			_breakWindow(softwareRenderer, &softwareRenderer->winN[1], y);
 499		}
 500		if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt)) {
 501			_breakWindow(softwareRenderer, &softwareRenderer->winN[0], y);
 502		}
 503	} else {
 504		softwareRenderer->windows[0].control.packed = 0xFF;
 505	}
 506
 507	int w;
 508	x = 0;
 509	for (w = 0; w < softwareRenderer->nWindows; ++w) {
 510		// TOOD: handle objwin on backdrop
 511		uint32_t backdrop = FLAG_UNWRITTEN | FLAG_PRIORITY | FLAG_IS_BACKGROUND;
 512		if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 513			backdrop |= softwareRenderer->normalPalette[0];
 514		} else {
 515			backdrop |= softwareRenderer->variantPalette[0];
 516		}
 517		int end = softwareRenderer->windows[w].endX;
 518		for (; x < end; ++x) {
 519			softwareRenderer->row[x] = backdrop;
 520		}
 521	}
 522
 523	_drawScanline(softwareRenderer, y);
 524
 525	if (softwareRenderer->target2Bd) {
 526		x = 0;
 527		for (w = 0; w < softwareRenderer->nWindows; ++w) {
 528		uint32_t backdrop = FLAG_UNWRITTEN;
 529			if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
 530				backdrop |= softwareRenderer->normalPalette[0];
 531			} else {
 532				backdrop |= softwareRenderer->variantPalette[0];
 533			}
 534			int end = softwareRenderer->windows[w].endX;
 535			for (; x < end; ++x) {
 536				uint32_t color = softwareRenderer->row[x];
 537				if (color & FLAG_TARGET_1) {
 538					softwareRenderer->row[x] = _mix(softwareRenderer->bldb, backdrop, softwareRenderer->blda, color);
 539				}
 540			}
 541		}
 542	}
 543
 544#ifdef COLOR_16_BIT
 545#ifdef __arm__
 546	_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
 547#else
 548	for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
 549		row[x] = softwareRenderer->row[x];
 550	}
 551#endif
 552#else
 553	memcpy(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS * sizeof(*row));
 554#endif
 555}
 556
 557static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
 558	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 559
 560	softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;
 561	softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy;
 562	softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;
 563	softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
 564}
 565
 566static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels) {
 567	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 568
 569	*stride = softwareRenderer->outputBufferStride;
 570	*pixels = softwareRenderer->outputBuffer;
 571}
 572
 573static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
 574	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 575
 576	uint32_t* colorPixels = pixels;
 577	unsigned i;
 578	for (i = 0; i < VIDEO_VERTICAL_PIXELS; ++i) {
 579		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
 580	}
 581}
 582
 583static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
 584	renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt);
 585	renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt);
 586	renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt);
 587	renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt);
 588}
 589
 590static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 591	UNUSED(renderer);
 592	bg->priority = GBARegisterBGCNTGetPriority(value);
 593	bg->charBase = GBARegisterBGCNTGetCharBase(value) << 14;
 594	bg->mosaic = GBARegisterBGCNTGetMosaic(value);
 595	bg->multipalette = GBARegisterBGCNTGet256Color(value);
 596	bg->screenBase = GBARegisterBGCNTGetScreenBase(value) << 11;
 597	bg->overflow = GBARegisterBGCNTGetOverflow(value);
 598	bg->size = GBARegisterBGCNTGetSize(value);
 599}
 600
 601static void GBAVideoSoftwareRendererWriteBGPA(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 602	bg->dx = value;
 603}
 604
 605static void GBAVideoSoftwareRendererWriteBGPB(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 606	bg->dmx = value;
 607}
 608
 609static void GBAVideoSoftwareRendererWriteBGPC(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 610	bg->dy = value;
 611}
 612
 613static void GBAVideoSoftwareRendererWriteBGPD(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 614	bg->dmy = value;
 615}
 616
 617static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 618	bg->refx = (bg->refx & 0xFFFF0000) | value;
 619	bg->sx = bg->refx;
 620}
 621
 622static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 623	bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
 624	bg->refx <<= 4;
 625	bg->refx >>= 4;
 626	bg->sx = bg->refx;
 627}
 628
 629static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 630	bg->refy = (bg->refy & 0xFFFF0000) | value;
 631	bg->sy = bg->refy;
 632}
 633
 634static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
 635	bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
 636	bg->refy <<= 4;
 637	bg->refy >>= 4;
 638	bg->sy = bg->refy;
 639}
 640
 641static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
 642	enum BlendEffect oldEffect = renderer->blendEffect;
 643
 644	renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
 645	renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
 646	renderer->bg[2].target1 = GBARegisterBLDCNTGetTarget1Bg2(value);
 647	renderer->bg[3].target1 = GBARegisterBLDCNTGetTarget1Bg3(value);
 648	renderer->bg[0].target2 = GBARegisterBLDCNTGetTarget2Bg0(value);
 649	renderer->bg[1].target2 = GBARegisterBLDCNTGetTarget2Bg1(value);
 650	renderer->bg[2].target2 = GBARegisterBLDCNTGetTarget2Bg2(value);
 651	renderer->bg[3].target2 = GBARegisterBLDCNTGetTarget2Bg3(value);
 652
 653	renderer->blendEffect = GBARegisterBLDCNTGetEffect(value);
 654	renderer->target1Obj = GBARegisterBLDCNTGetTarget1Obj(value);
 655	renderer->target1Bd = GBARegisterBLDCNTGetTarget1Bd(value);
 656	renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
 657	renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
 658
 659	renderer->anyTarget2 = value & 0x3F00;
 660
 661	if (oldEffect != renderer->blendEffect) {
 662		_updatePalettes(renderer);
 663	}
 664}
 665
 666#define TEST_LAYER_ENABLED(X) \
 667	(renderer->bg[X].enabled && \
 668	(GBAWindowControlIsBg ## X ## Enable(renderer->currentWindow.packed) || \
 669	(GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlIsBg ## X ## Enable (renderer->objwin.packed))) && \
 670	renderer->bg[X].priority == priority)
 671
 672static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
 673	int w;
 674	renderer->end = 0;
 675	int spriteLayers = 0;
 676	if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt)) {
 677		if (renderer->oamDirty) {
 678			_cleanOAM(renderer);
 679		}
 680		int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
 681		int mosaicY = y - (y % mosaicV);
 682		for (w = 0; w < renderer->nWindows; ++w) {
 683			renderer->start = renderer->end;
 684			renderer->end = renderer->windows[w].endX;
 685			renderer->currentWindow = renderer->windows[w].control;
 686			if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed) && !GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt)) {
 687				continue;
 688			}
 689			int i;
 690			int drawn;
 691			for (i = 0; i < renderer->oamMax; ++i) {
 692				int localY = y;
 693				struct GBAVideoSoftwareSprite* sprite = &renderer->sprites[i];
 694				if (GBAObjAttributesAIsMosaic(sprite->obj.a)) {
 695					localY = mosaicY;
 696				}
 697				if ((localY < sprite->y && (sprite->endY - 256 < 0 || localY >= sprite->endY - 256)) || localY >= sprite->endY) {
 698					continue;
 699				}
 700				drawn = _preprocessSprite(renderer, &sprite->obj, localY);
 701				spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
 702			}
 703		}
 704	}
 705
 706	int priority;
 707	for (priority = 0; priority < 4; ++priority) {
 708		renderer->end = 0;
 709		for (w = 0; w < renderer->nWindows; ++w) {
 710			renderer->start = renderer->end;
 711			renderer->end = renderer->windows[w].endX;
 712			renderer->currentWindow = renderer->windows[w].control;
 713			if (spriteLayers & (1 << priority)) {
 714				_postprocessSprite(renderer, priority);
 715			}
 716			if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) {
 717				_drawBackgroundMode0(renderer, &renderer->bg[0], y);
 718			}
 719			if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) {
 720				_drawBackgroundMode0(renderer, &renderer->bg[1], y);
 721			}
 722			if (TEST_LAYER_ENABLED(2)) {
 723				switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) {
 724				case 0:
 725					_drawBackgroundMode0(renderer, &renderer->bg[2], y);
 726					break;
 727				case 1:
 728				case 2:
 729					_drawBackgroundMode2(renderer, &renderer->bg[2], y);
 730					break;
 731				case 3:
 732					_drawBackgroundMode3(renderer, &renderer->bg[2], y);
 733					break;
 734				case 4:
 735					_drawBackgroundMode4(renderer, &renderer->bg[2], y);
 736					break;
 737				case 5:
 738					_drawBackgroundMode5(renderer, &renderer->bg[2], y);
 739					break;
 740				}
 741			}
 742			if (TEST_LAYER_ENABLED(3)) {
 743				switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) {
 744				case 0:
 745					_drawBackgroundMode0(renderer, &renderer->bg[3], y);
 746					break;
 747				case 2:
 748					_drawBackgroundMode2(renderer, &renderer->bg[3], y);
 749					break;
 750				}
 751			}
 752		}
 753	}
 754	renderer->bg[2].sx += renderer->bg[2].dmx;
 755	renderer->bg[2].sy += renderer->bg[2].dmy;
 756	renderer->bg[3].sx += renderer->bg[3].dmx;
 757	renderer->bg[3].sy += renderer->bg[3].dmy;
 758}
 759
 760// We stash the priority on the top bits so we can do a one-operator comparison
 761// The lower the number, the higher the priority, and sprites take precendence over backgrounds
 762// We want to do special processing if the color pixel is target 1, however
 763
 764static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) {
 765	if (color >= current) {
 766		if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) {
 767			color = _mix(renderer->blda, current, renderer->bldb, color);
 768		} else {
 769			color = current & 0x00FFFFFF;
 770		}
 771	} else {
 772		color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN);
 773	}
 774	*pixel = color;
 775}
 776
 777static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) {
 778	// We stash the priority on the top bits so we can do a one-operator comparison
 779	// The lower the number, the higher the priority, and sprites take precendence over backgrounds
 780	// We want to do special processing if the color pixel is target 1, however
 781	if (color >= current) {
 782		if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) {
 783			color = _mix(renderer->blda, current, renderer->bldb, color);
 784		} else {
 785			color = current & 0x00FFFFFF;
 786		}
 787	} else {
 788		color = color & ~FLAG_TARGET_2;
 789	}
 790	*pixel = color;
 791}
 792
 793static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) {
 794	UNUSED(renderer);
 795	if (color < current) {
 796		*pixel = color | (current & FLAG_OBJWIN);
 797	}
 798}
 799
 800static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) {
 801	UNUSED(renderer);
 802	if (color < current) {
 803		*pixel = color;
 804	}
 805}
 806
 807#define COMPOSITE_16_OBJWIN(BLEND) \
 808	if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \
 809		unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \
 810		_composite ## BLEND ## Objwin(renderer, pixel, color | flags, current); \
 811	}
 812
 813#define COMPOSITE_16_NO_OBJWIN(BLEND) \
 814	_composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current);
 815
 816#define COMPOSITE_256_OBJWIN(BLEND) \
 817	if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \
 818		unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \
 819		_composite ## BLEND ## Objwin(renderer, pixel, color | flags, current); \
 820	}
 821
 822#define COMPOSITE_256_NO_OBJWIN(BLEND) \
 823	COMPOSITE_16_NO_OBJWIN(BLEND)
 824
 825#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN) \
 826	pixelData = tileData & 0xF; \
 827	current = *pixel; \
 828	if (pixelData && IS_WRITABLE(current)) { \
 829		COMPOSITE_16_ ## OBJWIN (BLEND); \
 830	} \
 831	tileData >>= 4;
 832
 833#define BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN) \
 834	pixelData = tileData & 0xFF; \
 835	current = *pixel; \
 836	if (pixelData && IS_WRITABLE(current)) { \
 837		COMPOSITE_256_ ## OBJWIN (BLEND); \
 838	} \
 839	tileData >>= 8;
 840
 841#define BACKGROUND_TEXT_SELECT_CHARACTER \
 842	localX = tileX * 8 + inX; \
 843	xBase = localX & 0xF8; \
 844	if (background->size & 1) { \
 845		xBase += (localX & 0x100) << 5; \
 846	} \
 847	screenBase = yBase + (xBase >> 3); \
 848	LOAD_16(mapData, screenBase << 1, vram); \
 849	localY = inY & 0x7; \
 850	if (GBA_TEXT_MAP_VFLIP(mapData)) { \
 851		localY = 7 - localY; \
 852	}
 853
 854#define PREPARE_OBJWIN \
 855	int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); \
 856	int objwinOnly = 0; \
 857	int objwinForceEnable = 0; \
 858	color_t* objwinPalette; \
 859	if (objwinSlowPath) { \
 860		if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \
 861			objwinPalette = renderer->variantPalette; \
 862		} else { \
 863			objwinPalette = renderer->normalPalette; \
 864		} \
 865		switch (background->index) { \
 866		case 0: \
 867			objwinForceEnable = GBAWindowControlIsBg0Enable(renderer->objwin.packed) && GBAWindowControlIsBg0Enable(renderer->currentWindow.packed); \
 868			objwinOnly = !GBAWindowControlIsBg0Enable(renderer->objwin.packed); \
 869			break; \
 870		case 1: \
 871			objwinForceEnable = GBAWindowControlIsBg1Enable(renderer->objwin.packed) && GBAWindowControlIsBg1Enable(renderer->currentWindow.packed); \
 872			objwinOnly = !GBAWindowControlIsBg1Enable(renderer->objwin.packed); \
 873			break; \
 874		case 2: \
 875			objwinForceEnable = GBAWindowControlIsBg2Enable(renderer->objwin.packed) && GBAWindowControlIsBg2Enable(renderer->currentWindow.packed); \
 876			objwinOnly = !GBAWindowControlIsBg2Enable(renderer->objwin.packed); \
 877			break; \
 878		case 3: \
 879			objwinForceEnable = GBAWindowControlIsBg3Enable(renderer->objwin.packed) && GBAWindowControlIsBg3Enable(renderer->currentWindow.packed); \
 880			objwinOnly = !GBAWindowControlIsBg3Enable(renderer->objwin.packed); \
 881			break; \
 882		} \
 883	}
 884
 885#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \
 886	paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
 887	palette = &mainPalette[paletteData]; \
 888	charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
 889	LOAD_32(tileData, charBase, vram); \
 890	if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
 891		tileData >>= 4 * mod8; \
 892		for (; outX < end; ++outX, ++pixel) { \
 893			BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 894		} \
 895	} else { \
 896		for (outX = end - 1; outX >= renderer->start; --outX) { \
 897			uint32_t* pixel = &renderer->row[outX]; \
 898			BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 899		} \
 900	}
 901
 902#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_16(BLEND, OBJWIN) \
 903	charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
 904	LOAD_32(tileData, charBase, vram); \
 905	paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
 906	palette = &mainPalette[paletteData]; \
 907	pixel = &renderer->row[outX]; \
 908	if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
 909		if (outX < renderer->start) { \
 910			tileData >>= 4 * (renderer->start - outX); \
 911			outX = renderer->start; \
 912			pixel = &renderer->row[outX]; \
 913		} \
 914		for (; outX < renderer->end; ++outX, ++pixel) { \
 915			BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 916		} \
 917	} else { \
 918		tileData >>= 4 * (0x8 - mod8); \
 919		int end = renderer->end - 8; \
 920		if (end < -1) { \
 921			end = -1; \
 922		} \
 923		outX = renderer->end - 1; \
 924		pixel = &renderer->row[outX]; \
 925		for (; outX > end; --outX, --pixel) { \
 926			BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 927		} \
 928		/* Needed for consistency checks */ \
 929		if (VIDEO_CHECKS) { \
 930			outX = renderer->end; \
 931			pixel = &renderer->row[outX]; \
 932		} \
 933	}
 934
 935#define DRAW_BACKGROUND_MODE_0_MOSAIC_16(BLEND, OBJWIN) \
 936	for (; tileX < tileEnd; ++tileX) { \
 937		BACKGROUND_TEXT_SELECT_CHARACTER; \
 938		charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
 939		tileData = carryData; \
 940		for (x = 0; x < 8; ++x) { \
 941			if (!mosaicWait) { \
 942				paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
 943				palette = &mainPalette[paletteData]; \
 944				LOAD_32(tileData, charBase, vram); \
 945				if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
 946					tileData >>= x * 4; \
 947				} else { \
 948					tileData >>= (7 - x) * 4; \
 949				} \
 950				tileData &= 0xF; \
 951				tileData |= tileData << 4; \
 952				tileData |= tileData << 8; \
 953				tileData |= tileData << 12; \
 954				tileData |= tileData << 16; \
 955				tileData |= tileData << 20; \
 956				tileData |= tileData << 24; \
 957				tileData |= tileData << 28; \
 958				carryData = tileData; \
 959				mosaicWait = mosaicH; \
 960			} \
 961			--mosaicWait; \
 962			BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 963			++pixel; \
 964		} \
 965	}
 966
 967#define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \
 968	for (; tileX < tileEnd; ++tileX) { \
 969		BACKGROUND_TEXT_SELECT_CHARACTER; \
 970		paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
 971		palette = &mainPalette[paletteData]; \
 972		charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
 973		LOAD_32(tileData, charBase, vram); \
 974		if (tileData) { \
 975			if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
 976				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 977				++pixel; \
 978				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 979				++pixel; \
 980				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 981				++pixel; \
 982				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 983				++pixel; \
 984				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 985				++pixel; \
 986				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 987				++pixel; \
 988				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 989				++pixel; \
 990				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 991				++pixel; \
 992			} else { \
 993				pixel += 7; \
 994				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 995				--pixel; \
 996				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 997				--pixel; \
 998				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
 999				--pixel; \
1000				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
1001				--pixel; \
1002				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
1003				--pixel; \
1004				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
1005				--pixel; \
1006				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
1007				--pixel; \
1008				BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
1009				pixel += 8; \
1010			} \
1011		} else { \
1012			pixel += 8; \
1013		} \
1014	}
1015
1016#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_256(BLEND, OBJWIN) \
1017	charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
1018	int end2 = end - 4; \
1019	if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
1020		int shift = inX & 0x3; \
1021		if (end2 > outX) { \
1022			LOAD_32(tileData, charBase, vram); \
1023			tileData >>= 8 * shift; \
1024			shift = 0; \
1025			for (; outX < end2; ++outX, ++pixel) { \
1026				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1027			} \
1028		} \
1029		\
1030		LOAD_32(tileData, charBase + 4, vram); \
1031		tileData >>= 8 * shift; \
1032		for (; outX < end; ++outX, ++pixel) { \
1033			BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1034		} \
1035	} else { \
1036		int start = outX; \
1037		outX = end - 1; \
1038		pixel = &renderer->row[outX]; \
1039		if (end2 > start) { \
1040			LOAD_32(tileData, charBase, vram); \
1041			for (; outX >= end2; --outX, --pixel) { \
1042				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1043			} \
1044			charBase += 4; \
1045		} \
1046		\
1047		LOAD_32(tileData, charBase, vram); \
1048		for (; outX >= renderer->start; --outX, --pixel) { \
1049			BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1050		} \
1051		outX = end; \
1052		pixel = &renderer->row[outX]; \
1053	}
1054
1055#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256(BLEND, OBJWIN) \
1056	charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
1057	int end = mod8 - 4; \
1058	pixel = &renderer->row[outX]; \
1059	if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
1060		if (end > 0) { \
1061			LOAD_32(tileData, charBase, vram); \
1062			for (; outX < renderer->end - end; ++outX, ++pixel) { \
1063				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1064			} \
1065			charBase += 4; \
1066		} \
1067		\
1068		LOAD_32(tileData, charBase, vram); \
1069		for (; outX < renderer->end; ++outX, ++pixel) { \
1070			BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1071		} \
1072	} else { \
1073		int shift = (8 - mod8) & 0x3; \
1074		int start = outX; \
1075		outX = renderer->end - 1; \
1076		pixel = &renderer->row[outX]; \
1077		if (end > 0) { \
1078			LOAD_32(tileData, charBase, vram); \
1079			tileData >>= 8 * shift; \
1080			for (; outX >= start + 4; --outX, --pixel) { \
1081				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1082			} \
1083			shift = 0; \
1084		} \
1085		\
1086		LOAD_32(tileData, charBase + 4, vram); \
1087		tileData >>= 8 * shift; \
1088		for (; outX >= start; --outX, --pixel) { \
1089			BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1090		} \
1091		/* Needed for consistency checks */ \
1092		if (VIDEO_CHECKS) { \
1093			outX = renderer->end; \
1094			pixel = &renderer->row[outX]; \
1095		} \
1096	}
1097
1098#define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \
1099	for (; tileX < tileEnd; ++tileX) { \
1100		BACKGROUND_TEXT_SELECT_CHARACTER; \
1101		charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
1102		if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
1103			LOAD_32(tileData, charBase, vram); \
1104			if (tileData) { \
1105					BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1106					++pixel; \
1107					BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1108					++pixel; \
1109					BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1110					++pixel; \
1111					BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1112					++pixel; \
1113			} else { \
1114				pixel += 4; \
1115			} \
1116			LOAD_32(tileData, charBase + 4, vram); \
1117			if (tileData) { \
1118					BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1119					++pixel; \
1120					BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1121					++pixel; \
1122					BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1123					++pixel; \
1124					BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1125					++pixel; \
1126			} else { \
1127				pixel += 4; \
1128			} \
1129		} else { \
1130			LOAD_32(tileData, charBase + 4, vram); \
1131			if (tileData) { \
1132				pixel += 3; \
1133				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1134				--pixel; \
1135				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1136				--pixel; \
1137				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1138				--pixel; \
1139				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1140			} \
1141			pixel += 4; \
1142			LOAD_32(tileData, charBase, vram); \
1143			if (tileData) { \
1144				pixel += 3; \
1145				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1146				--pixel; \
1147				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1148				--pixel; \
1149				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1150				--pixel; \
1151				BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1152			} \
1153			pixel += 4; \
1154		} \
1155	}
1156
1157#define DRAW_BACKGROUND_MODE_0_MOSAIC_256(BLEND, OBJWIN) \
1158	for (; tileX < tileEnd; ++tileX) { \
1159		BACKGROUND_TEXT_SELECT_CHARACTER; \
1160		charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
1161		tileData = carryData; \
1162		for (x = 0; x < 8; ++x) { \
1163			if (!mosaicWait) { \
1164				if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
1165					if (x >= 4) { \
1166						LOAD_32(tileData, charBase + 4, vram); \
1167						tileData >>= (x - 4) * 8; \
1168					} else { \
1169						LOAD_32(tileData, charBase, vram); \
1170						tileData >>= x * 8; \
1171					} \
1172				} else { \
1173					if (x >= 4) { \
1174						LOAD_32(tileData, charBase, vram); \
1175						tileData >>= (7 - x) * 8; \
1176					} else { \
1177						LOAD_32(tileData, charBase + 4, vram); \
1178						tileData >>= (3 - x) * 8; \
1179					} \
1180				} \
1181				tileData &= 0xFF; \
1182				carryData = tileData; \
1183				mosaicWait = mosaicH; \
1184			} \
1185			tileData |= tileData << 8; \
1186			--mosaicWait; \
1187			BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
1188			++pixel; \
1189		} \
1190	}
1191
1192#define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \
1193	uint32_t* pixel = &renderer->row[outX]; \
1194	if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \
1195		int mosaicH = GBAMosaicControlGetBgH(renderer->mosaic) + 1; \
1196		int x; \
1197		int mosaicWait = outX % mosaicH; \
1198		int carryData = 0; \
1199		paletteData = 0; /* Quiets compiler warning */ \
1200		DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \
1201		return; \
1202	} \
1203	\
1204	if (inX & 0x7) { \
1205		BACKGROUND_TEXT_SELECT_CHARACTER; \
1206		\
1207		int mod8 = inX & 0x7; \
1208		int end = outX + 0x8 - mod8; \
1209		if (end > renderer->end) { \
1210			end = renderer->end; \
1211		} \
1212		if (UNLIKELY(end == outX)) { \
1213			return; \
1214		} \
1215		if (UNLIKELY(end < outX)) { \
1216			GBALog(0, GBA_LOG_DANGER, "Out of bounds background draw!"); \
1217			return; \
1218		} \
1219		DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \
1220		outX = end; \
1221		if (tileX < tileEnd) { \
1222			++tileX; \
1223		} else if (VIDEO_CHECKS && UNLIKELY(tileX > tileEnd)) { \
1224			GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw! tileX (%u) > tileEnd (%u)", tileX, tileEnd); \
1225			return; \
1226		} \
1227		length -= end - renderer->start; \
1228	} \
1229	/*! TODO: Make sure these lines can be removed */ \
1230	/*!*/ pixel = &renderer->row[outX]; \
1231	outX += (tileEnd - tileX) * 8; \
1232	/*!*/ if (VIDEO_CHECKS &&  UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \
1233	/*!*/	GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw would occur!"); \
1234	/*!*/	return; \
1235	/*!*/ } \
1236	DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN) \
1237	if (length & 0x7) { \
1238		BACKGROUND_TEXT_SELECT_CHARACTER; \
1239		\
1240		int mod8 = length & 0x7; \
1241		if (VIDEO_CHECKS && UNLIKELY(outX + mod8 != renderer->end)) { \
1242			GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw!"); \
1243			return; \
1244		} \
1245		DRAW_BACKGROUND_MODE_0_TILE_PREFIX_ ## BPP (BLEND, OBJWIN) \
1246	} \
1247	if (VIDEO_CHECKS && UNLIKELY(&renderer->row[outX] != pixel)) { \
1248		GBALog(0, GBA_LOG_FATAL, "Background draw ended in the wrong place! Diff: %" PRIXPTR, &renderer->row[outX] - pixel); \
1249	} \
1250	if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \
1251		GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw occurred!"); \
1252		return; \
1253	}
1254
1255static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
1256	int inX = renderer->start + background->x;
1257	int length = renderer->end - renderer->start;
1258	if (background->mosaic) {
1259		int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1;
1260		y -= y % mosaicV;
1261	}
1262	int inY = y + background->y;
1263	uint16_t mapData;
1264
1265	unsigned yBase = inY & 0xF8;
1266	if (background->size == 2) {
1267		yBase += inY & 0x100;
1268	} else if (background->size == 3) {
1269		yBase += (inY & 0x100) << 1;
1270	}
1271	yBase = (background->screenBase >> 1) + (yBase << 2);
1272
1273	int localX;
1274	int localY;
1275
1276	unsigned xBase;
1277
1278	int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND;
1279	flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed));
1280	flags |= FLAG_TARGET_2 * background->target2;
1281	if (renderer->blda == 0x10 && renderer->bldb == 0) {
1282		flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2);
1283	}
1284
1285	uint32_t screenBase;
1286	uint32_t charBase;
1287	int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
1288	color_t* mainPalette = renderer->normalPalette;
1289	if (variant) {
1290		mainPalette = renderer->variantPalette;
1291	}
1292	color_t* palette = mainPalette;
1293	PREPARE_OBJWIN;
1294
1295	int outX = renderer->start;
1296
1297	uint32_t tileData;
1298	uint32_t current;
1299	int pixelData;
1300	int paletteData;
1301	int tileX = 0;
1302	int tileEnd = ((length + inX) >> 3) - (inX >> 3);
1303	uint16_t* vram = renderer->d.vram;
1304
1305	if (!objwinSlowPath) {
1306		if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) {
1307			if (!background->multipalette) {
1308				DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN);
1309			} else {
1310				DRAW_BACKGROUND_MODE_0(256, NoBlend, NO_OBJWIN);
1311			}
1312		} else {
1313			if (!background->multipalette) {
1314				DRAW_BACKGROUND_MODE_0(16, Blend, NO_OBJWIN);
1315			} else {
1316				DRAW_BACKGROUND_MODE_0(256, Blend, NO_OBJWIN);
1317			}
1318		}
1319	} else {
1320		if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) {
1321			if (!background->multipalette) {
1322				DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN);
1323			} else {
1324				DRAW_BACKGROUND_MODE_0(256, NoBlend, OBJWIN);
1325			}
1326		} else {
1327			if (!background->multipalette) {
1328				DRAW_BACKGROUND_MODE_0(16, Blend, OBJWIN);
1329			} else {
1330				DRAW_BACKGROUND_MODE_0(256, Blend, OBJWIN);
1331			}
1332		}
1333	}
1334}
1335
1336#define BACKGROUND_BITMAP_INIT \
1337	int32_t x = background->sx + (renderer->start - 1) * background->dx; \
1338	int32_t y = background->sy + (renderer->start - 1) * background->dy; \
1339	int mosaicH = 0; \
1340	int mosaicWait = 0; \
1341	if (background->mosaic) { \
1342		int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; \
1343		y -= (inY % mosaicV) * background->dmy; \
1344		x -= (inY % mosaicV) * background->dmx; \
1345		mosaicH = GBAMosaicControlGetBgH(renderer->mosaic); \
1346		mosaicWait = renderer->start % (mosaicH + 1); \
1347	} \
1348	int32_t localX; \
1349	int32_t localY; \
1350	\
1351	int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \
1352	flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \
1353	flags |= FLAG_TARGET_2 * background->target2; \
1354	if (renderer->blda == 0x10 && renderer->bldb == 0) { \
1355		flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \
1356	} \
1357	int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \
1358	color_t* palette = renderer->normalPalette; \
1359	if (variant) { \
1360		palette = renderer->variantPalette; \
1361	} \
1362	PREPARE_OBJWIN;
1363
1364#define BACKGROUND_BITMAP_ITERATE(W, H) \
1365	x += background->dx; \
1366	y += background->dy; \
1367	\
1368	if (x < 0 || y < 0 || (x >> 8) >= W || (y >> 8) >= H) { \
1369		continue; \
1370	} else { \
1371		localX = x; \
1372		localY = y; \
1373	}
1374
1375static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
1376	int sizeAdjusted = 0x8000 << background->size;
1377
1378	BACKGROUND_BITMAP_INIT;
1379
1380	uint32_t screenBase = background->screenBase;
1381	uint32_t charBase = background->charBase;
1382	uint8_t mapData;
1383	uint8_t tileData = 0;
1384
1385	int outX;
1386	uint32_t* pixel;
1387	for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
1388		x += background->dx;
1389		y += background->dy;
1390
1391		if (!mosaicWait) {
1392			if (background->overflow) {
1393				localX = x & (sizeAdjusted - 1);
1394				localY = y & (sizeAdjusted - 1);
1395			} else if ((x | y) & ~(sizeAdjusted - 1)) {
1396				continue;
1397			} else {
1398				localX = x;
1399				localY = y;
1400			}
1401			mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)];
1402			tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)];
1403
1404			mosaicWait = mosaicH;
1405		} else {
1406			--mosaicWait;
1407		}
1408
1409		uint32_t current = *pixel;
1410		if (tileData && IS_WRITABLE(current)) {
1411			if (!objwinSlowPath) {
1412				_compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current);
1413			} else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) {
1414				color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette;
1415				_compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | flags, current);
1416			}
1417		}
1418	}
1419}
1420
1421static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
1422	BACKGROUND_BITMAP_INIT;
1423
1424	uint32_t color = renderer->normalPalette[0];
1425
1426	int outX;
1427	uint32_t* pixel;
1428	for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
1429		BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
1430
1431		if (!mosaicWait) {
1432			LOAD_16(color, ((localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS) << 1, renderer->d.vram);
1433#ifndef COLOR_16_BIT
1434			unsigned color32;
1435			color32 = 0;
1436			color32 |= (color << 3) & 0xF8;
1437			color32 |= (color << 6) & 0xF800;
1438			color32 |= (color << 9) & 0xF80000;
1439			color = color32;
1440#endif
1441			mosaicWait = mosaicH;
1442		} else {
1443			--mosaicWait;
1444		}
1445
1446		uint32_t current = *pixel;
1447		if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) {
1448			if (!variant) {
1449				_compositeBlendObjwin(renderer, pixel, color | flags, current);
1450			} else if (renderer->blendEffect == BLEND_BRIGHTEN) {
1451				_compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | flags, current);
1452			} else if (renderer->blendEffect == BLEND_DARKEN) {
1453				_compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | flags, current);
1454			}
1455		}
1456	}
1457}
1458
1459static void _drawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
1460	BACKGROUND_BITMAP_INIT;
1461
1462	uint16_t color = renderer->normalPalette[0];
1463	uint32_t offset = 0;
1464	if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) {
1465		offset = 0xA000;
1466	}
1467
1468	int outX;
1469	uint32_t* pixel;
1470	for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
1471		BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
1472
1473		if (!mosaicWait) {
1474			color = ((uint8_t*)renderer->d.vram)[offset + (localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS];
1475
1476			mosaicWait = mosaicH;
1477		} else {
1478			--mosaicWait;
1479		}
1480
1481		uint32_t current = *pixel;
1482		if (color && IS_WRITABLE(current)) {
1483			if (!objwinSlowPath) {
1484				_compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current);
1485			} else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) {
1486				color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette;
1487				_compositeBlendObjwin(renderer, pixel, currentPalette[color] | flags, current);
1488			}
1489		}
1490	}
1491}
1492
1493static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
1494	BACKGROUND_BITMAP_INIT;
1495
1496	uint32_t color = renderer->normalPalette[0];
1497	uint32_t offset = 0;
1498	if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) {
1499		offset = 0xA000;
1500	}
1501
1502	int outX;
1503	uint32_t* pixel;
1504	for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
1505		BACKGROUND_BITMAP_ITERATE(160, 128);
1506
1507		if (!mosaicWait) {
1508			LOAD_16(color, (offset + (localX >> 8) + (localY >> 8) * 160) << 1, renderer->d.vram);
1509#ifndef COLOR_16_BIT
1510			unsigned color32 = 0;
1511			color32 |= (color << 9) & 0xF80000;
1512			color32 |= (color << 3) & 0xF8;
1513			color32 |= (color << 6) & 0xF800;
1514			color = color32;
1515#endif
1516			mosaicWait = mosaicH;
1517		} else {
1518			--mosaicWait;
1519		}
1520
1521		uint32_t current = *pixel;
1522		if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) {
1523			if (!variant) {
1524				_compositeBlendObjwin(renderer, pixel, color | flags, current);
1525			} else if (renderer->blendEffect == BLEND_BRIGHTEN) {
1526				_compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | flags, current);
1527			} else if (renderer->blendEffect == BLEND_DARKEN) {
1528				_compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | flags, current);
1529			}
1530		}
1531	}
1532}
1533
1534#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \
1535	SPRITE_YBASE_ ## DEPTH(inY); \
1536	unsigned tileData; \
1537	for (; outX < condition; ++outX, inX += xOffset) { \
1538		if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
1539			continue; \
1540		} \
1541		SPRITE_XBASE_ ## DEPTH(inX); \
1542		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \
1543	}
1544
1545#define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \
1546	SPRITE_YBASE_ ## DEPTH(inY); \
1547	unsigned tileData; \
1548	if (outX % mosaicH) { \
1549		inX += (mosaicH - (outX % mosaicH)) * xOffset; \
1550		outX += mosaicH - (outX % mosaicH); \
1551	} \
1552	for (; outX < condition; ++outX, inX += xOffset) { \
1553		if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
1554			continue; \
1555		} \
1556		int localX = inX - xOffset * (outX % mosaicH); \
1557		SPRITE_XBASE_ ## DEPTH(localX); \
1558		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
1559	}
1560
1561#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \
1562	unsigned tileData; \
1563	for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \
1564		if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
1565			continue; \
1566		} \
1567		xAccum += mat.a; \
1568		yAccum += mat.c; \
1569		int localX = (xAccum >> 8) + (width >> 1); \
1570		int localY = (yAccum >> 8) + (height >> 1); \
1571		\
1572		if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \
1573			continue; \
1574		} \
1575		\
1576		SPRITE_YBASE_ ## DEPTH(localY); \
1577		SPRITE_XBASE_ ## DEPTH(localX); \
1578		SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
1579	}
1580
1581#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
1582#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width >> 1 : 0x80) + (localY & 0x7) * 4;
1583
1584#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
1585	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
1586	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
1587	if (tileData && (renderer->spriteLayer[outX] & FLAG_ORDER_MASK) > flags) { \
1588		renderer->spriteLayer[outX] = palette[tileData] | flags; \
1589	}
1590
1591#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \
1592	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
1593	tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
1594	if (tileData) { \
1595		renderer->row[outX] |= FLAG_OBJWIN; \
1596	}
1597
1598#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
1599#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8;
1600
1601#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
1602	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
1603	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
1604	if (tileData && (renderer->spriteLayer[outX] & FLAG_ORDER_MASK) > flags) { \
1605		renderer->spriteLayer[outX] = palette[tileData] | flags; \
1606	}
1607
1608#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \
1609	LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \
1610	tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
1611	if (tileData) { \
1612		renderer->row[outX] |= FLAG_OBJWIN; \
1613	}
1614
1615static int _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
1616	int width = _objSizes[GBAObjAttributesAGetShape(sprite->a) * 8 + GBAObjAttributesBGetSize(sprite->b) * 2];
1617	int height = _objSizes[GBAObjAttributesAGetShape(sprite->a) * 8 + GBAObjAttributesBGetSize(sprite->b) * 2 + 1];
1618	int start = renderer->start;
1619	int end = renderer->end;
1620	uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY;
1621	flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT);
1622	flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN);
1623	int32_t x = GBAObjAttributesBGetX(sprite->b) << 23;
1624	x >>= 23;
1625	uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1];
1626	unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20;
1627	int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
1628	if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT && renderer->target2Bd) {
1629		// Hack: if a sprite is blended, then the variant palette is not used, but we don't know if it's blended in advance
1630		variant = 0;
1631	}
1632	color_t* palette = &renderer->normalPalette[0x100];
1633	if (variant) {
1634		palette = &renderer->variantPalette[0x100];
1635	}
1636
1637	int inY = y - (int) GBAObjAttributesAGetY(sprite->a);
1638
1639	if (GBAObjAttributesAIsTransformed(sprite->a)) {
1640		int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
1641		int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
1642		struct GBAOAMMatrix mat;
1643		LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
1644		LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
1645		LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c);
1646		LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d);
1647
1648		if (inY < 0) {
1649			inY += 256;
1650		}
1651		int outX = x >= start ? x : start;
1652		int inX = outX - x;
1653		int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1));
1654		int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1));
1655
1656		if (!GBAObjAttributesAIs256Color(sprite->a)) {
1657			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
1658			if (flags & FLAG_OBJWIN) {
1659				SPRITE_TRANSFORMED_LOOP(16, OBJWIN);
1660			} else {
1661				SPRITE_TRANSFORMED_LOOP(16, NORMAL);
1662			}
1663		} else {
1664			if (flags & FLAG_OBJWIN) {
1665				SPRITE_TRANSFORMED_LOOP(256, OBJWIN);
1666			} else {
1667				SPRITE_TRANSFORMED_LOOP(256, NORMAL);
1668			}
1669		}
1670	} else {
1671		int outX = x >= start ? x : start;
1672		int condition = x + width;
1673		int mosaicH = 1;
1674		if (GBAObjAttributesAIsMosaic(sprite->a)) {
1675			mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
1676			if (condition % mosaicH) {
1677				condition += mosaicH - (condition % mosaicH);
1678			}
1679		}
1680		if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) {
1681			inY += 256;
1682		}
1683		if (GBAObjAttributesBIsVFlip(sprite->b)) {
1684			inY = height - inY - 1;
1685		}
1686		if (end < condition) {
1687			condition = end;
1688		}
1689		int inX = outX - x;
1690		int xOffset = 1;
1691		if (GBAObjAttributesBIsHFlip(sprite->b)) {
1692			inX = width - inX - 1;
1693			xOffset = -1;
1694		}
1695		if (!GBAObjAttributesAIs256Color(sprite->a)) {
1696			palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
1697			if (flags & FLAG_OBJWIN) {
1698				SPRITE_NORMAL_LOOP(16, OBJWIN);
1699			} else if (GBAObjAttributesAIsMosaic(sprite->a)) {
1700				SPRITE_MOSAIC_LOOP(16, NORMAL);
1701			} else {
1702				SPRITE_NORMAL_LOOP(16, NORMAL);
1703			}
1704		} else {
1705			if (flags & FLAG_OBJWIN) {
1706				SPRITE_NORMAL_LOOP(256, OBJWIN);
1707			} else if (GBAObjAttributesAIsMosaic(sprite->a)) {
1708				SPRITE_MOSAIC_LOOP(256, NORMAL);
1709			} else {
1710				SPRITE_NORMAL_LOOP(256, NORMAL);
1711			}
1712		}
1713	}
1714	return 1;
1715}
1716
1717static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) {
1718	int x;
1719	uint32_t* pixel = &renderer->row[renderer->start];
1720	uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj;
1721
1722	int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt);
1723	bool objwinDisable = false;
1724	bool objwinOnly = false;
1725	if (objwinSlowPath) {
1726		objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed);
1727		objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed);
1728
1729		if (objwinDisable) {
1730			for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
1731				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
1732				uint32_t current = *pixel;
1733				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
1734					_compositeBlendObjwin(renderer, pixel, color | flags, current);
1735				}
1736			}
1737			return;
1738		} else if (objwinOnly) {
1739			for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
1740				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
1741				uint32_t current = *pixel;
1742				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
1743					_compositeBlendObjwin(renderer, pixel, color | flags, current);
1744				}
1745			}
1746			return;
1747		} else {
1748			for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
1749				uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
1750				uint32_t current = *pixel;
1751				if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
1752					_compositeBlendObjwin(renderer, pixel, color | flags, current);
1753				}
1754			}
1755			return;
1756		}
1757	}
1758	for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
1759		uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
1760		uint32_t current = *pixel;
1761		if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
1762			_compositeBlendNoObjwin(renderer, pixel, color | flags, current);
1763		}
1764	}
1765}
1766
1767static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
1768	int i;
1769	if (renderer->blendEffect == BLEND_BRIGHTEN) {
1770		for (i = 0; i < 512; ++i) {
1771			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
1772		}
1773	} else if (renderer->blendEffect == BLEND_DARKEN) {
1774		for (i = 0; i < 512; ++i) {
1775			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
1776		}
1777	} else {
1778		for (i = 0; i < 512; ++i) {
1779			renderer->variantPalette[i] = renderer->normalPalette[i];
1780		}
1781	}
1782}
1783
1784static inline unsigned _brighten(unsigned color, int y) {
1785	unsigned c = 0;
1786	unsigned a;
1787#ifdef COLOR_16_BIT
1788	a = color & 0x1F;
1789	c |= (a + ((0x1F - a) * y) / 16) & 0x1F;
1790
1791#ifdef COLOR_5_6_5
1792	a = color & 0x7C0;
1793	c |= (a + ((0x7C0 - a) * y) / 16) & 0x7C0;
1794
1795	a = color & 0xF800;
1796	c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
1797#else
1798	a = color & 0x3E0;
1799	c |= (a + ((0x3E0 - a) * y) / 16) & 0x3E0;
1800
1801	a = color & 0x7C00;
1802	c |= (a + ((0x7C00 - a) * y) / 16) & 0x7C00;
1803#endif
1804#else
1805	a = color & 0xF8;
1806	c |= (a + ((0xF8 - a) * y) / 16) & 0xF8;
1807
1808	a = color & 0xF800;
1809	c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
1810
1811	a = color & 0xF80000;
1812	c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000;
1813#endif
1814	return c;
1815}
1816
1817static inline unsigned _darken(unsigned color, int y) {
1818	unsigned c = 0;
1819	unsigned a;
1820#ifdef COLOR_16_BIT
1821	a = color & 0x1F;
1822	c |= (a - (a * y) / 16) & 0x1F;
1823
1824#ifdef COLOR_5_6_5
1825	a = color & 0x7C0;
1826	c |= (a - (a * y) / 16) & 0x7C0;
1827
1828	a = color & 0xF800;
1829	c |= (a - (a * y) / 16) & 0xF800;
1830#else
1831	a = color & 0x3E0;
1832	c |= (a - (a * y) / 16) & 0x3E0;
1833
1834	a = color & 0x7C00;
1835	c |= (a - (a * y) / 16) & 0x7C00;
1836#endif
1837#else
1838	a = color & 0xF8;
1839	c |= (a - (a * y) / 16) & 0xF8;
1840
1841	a = color & 0xF800;
1842	c |= (a - (a * y) / 16) & 0xF800;
1843
1844	a = color & 0xF80000;
1845	c |= (a - (a * y) / 16) & 0xF80000;
1846#endif
1847	return c;
1848}
1849
1850static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB) {
1851	unsigned c = 0;
1852	unsigned a, b;
1853#ifdef COLOR_16_BIT
1854#ifdef COLOR_5_6_5
1855	a = colorA & 0xF81F;
1856	b = colorB & 0xF81F;
1857	a |= (colorA & 0x7C0) << 16;
1858	b |= (colorB & 0x7C0) << 16;
1859	c = ((a * weightA + b * weightB) / 16);
1860	if (c & 0x08000000) {
1861		c = (c & ~0x0FC00000) | 0x07C00000;
1862	}
1863	if (c & 0x0020) {
1864		c = (c & ~0x003F) | 0x001F;
1865	}
1866	if (c & 0x10000) {
1867		c = (c & ~0x1F800) | 0xF800;
1868	}
1869	c = (c & 0xF81F) | ((c >> 16) & 0x07C0);
1870#else
1871	a = colorA & 0x7C1F;
1872	b = colorB & 0x7C1F;
1873	a |= (colorA & 0x3E0) << 16;
1874	b |= (colorB & 0x3E0) << 16;
1875	c = ((a * weightA + b * weightB) / 16);
1876	if (c & 0x04000000) {
1877		c = (c & ~0x07E00000) | 0x03E00000;
1878	}
1879	if (c & 0x0020) {
1880		c = (c & ~0x003F) | 0x001F;
1881	}
1882	if (c & 0x10000) {
1883		c = (c & ~0x1F800) | 0xF800;
1884	}
1885	c = (c & 0x7C1F) | ((c >> 16) & 0x03E0);
1886#endif
1887#else
1888	a = colorA & 0xF8;
1889	b = colorB & 0xF8;
1890	c |= ((a * weightA + b * weightB) / 16) & 0x1F8;
1891	if (c & 0x00000100) {
1892		c = 0x000000F8;
1893	}
1894
1895	a = colorA & 0xF800;
1896	b = colorB & 0xF800;
1897	c |= ((a * weightA + b * weightB) / 16) & 0x1F800;
1898	if (c & 0x00010000) {
1899		c = (c & 0x000000F8) | 0x0000F800;
1900	}
1901
1902	a = colorA & 0xF80000;
1903	b = colorB & 0xF80000;
1904	c |= ((a * weightA + b * weightB) / 16) & 0x1F80000;
1905	if (c & 0x01000000) {
1906		c = (c & 0x0000F8F8) | 0x00F80000;
1907	}
1908#endif
1909	return c;
1910}