all repos — mgba @ f41f3a847893450843f33e6c45c7025fdd8e1fc9

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