all repos — mgba @ 4bb82d47f6738d6d707d3844408933ce3e07273d

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 "software-private.h"
  7
  8#include "gba/gba.h"
  9#include "gba/io.h"
 10
 11#include "util/arm-algo.h"
 12
 13static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
 14static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
 15static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer);
 16static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
 17static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
 18static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
 19static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
 20static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
 21static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
 22static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
 23static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels);
 24
 25static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
 26static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
 27static void GBAVideoSoftwareRendererWriteBGPA(struct GBAVideoSoftwareBackground* bg, uint16_t value);
 28static void GBAVideoSoftwareRendererWriteBGPB(struct GBAVideoSoftwareBackground* bg, uint16_t value);
 29static void GBAVideoSoftwareRendererWriteBGPC(struct GBAVideoSoftwareBackground* bg, uint16_t value);
 30static void GBAVideoSoftwareRendererWriteBGPD(struct GBAVideoSoftwareBackground* bg, uint16_t value);
 31static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
 32static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
 33static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);
 34static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
 35static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
 36
 37static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer);
 38static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y);
 39
 40static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
 41
 42static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y);
 43static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win);
 44
 45void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
 46	renderer->d.init = GBAVideoSoftwareRendererInit;
 47	renderer->d.reset = GBAVideoSoftwareRendererReset;
 48	renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
 49	renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
 50	renderer->d.writeVRAM = GBAVideoSoftwareRendererWriteVRAM;
 51	renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
 52	renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
 53	renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
 54	renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
 55	renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
 56	renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
 57
 58	renderer->d.disableBG[0] = false;
 59	renderer->d.disableBG[1] = false;
 60	renderer->d.disableBG[2] = false;
 61	renderer->d.disableBG[3] = false;
 62	renderer->d.disableOBJ = false;
 63}
 64
 65static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
 66	GBAVideoSoftwareRendererReset(renderer);
 67
 68	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 69
 70	int y;
 71	for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
 72		color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
 73		int x;
 74		for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
 75			row[x] = GBA_COLOR_WHITE;
 76		}
 77	}
 78}
 79
 80static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) {
 81	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
 82	int i;
 83
 84	softwareRenderer->dispcnt = 0x0080;
 85
 86	softwareRenderer->target1Obj = 0;
 87	softwareRenderer->target1Bd = 0;
 88	softwareRenderer->target2Obj = 0;
 89	softwareRenderer->target2Bd = 0;
 90	softwareRenderer->blendEffect = BLEND_NONE;
 91	memset(softwareRenderer->normalPalette, 0, sizeof(softwareRenderer->normalPalette));
 92	memset(softwareRenderer->variantPalette, 0, sizeof(softwareRenderer->variantPalette));
 93
 94	softwareRenderer->blda = 0;
 95	softwareRenderer->bldb = 0;
 96	softwareRenderer->bldy = 0;
 97
 98	softwareRenderer->winN[0] = (struct WindowN) { .control = { .priority = 0 } };
 99	softwareRenderer->winN[1] = (struct WindowN) { .control = { .priority = 1 } };
100	softwareRenderer->objwin = (struct WindowControl) { .priority = 2 };
101	softwareRenderer->winout = (struct WindowControl) { .priority = 3 };
102	softwareRenderer->oamMax = 0;
103
104	softwareRenderer->mosaic = 0;
105
106	for (i = 0; i < 4; ++i) {
107		struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
108		bg->index = i;
109		bg->enabled = 0;
110		bg->priority = 0;
111		bg->charBase = 0;
112		bg->mosaic = 0;
113		bg->multipalette = 0;
114		bg->screenBase = 0;
115		bg->overflow = 0;
116		bg->size = 0;
117		bg->target1 = 0;
118		bg->target2 = 0;
119		bg->x = 0;
120		bg->y = 0;
121		bg->refx = 0;
122		bg->refy = 0;
123		bg->dx = 256;
124		bg->dmx = 0;
125		bg->dy = 0;
126		bg->dmy = 256;
127		bg->sx = 0;
128		bg->sy = 0;
129	}
130}
131
132static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
133	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
134	UNUSED(softwareRenderer);
135}
136
137static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
138	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
139	switch (address) {
140	case REG_DISPCNT:
141		softwareRenderer->dispcnt = value;
142		GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
143		break;
144	case REG_BG0CNT:
145		value &= 0xFFCF;
146		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
147		break;
148	case REG_BG1CNT:
149		value &= 0xFFCF;
150		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
151		break;
152	case REG_BG2CNT:
153		value &= 0xFFCF;
154		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
155		break;
156	case REG_BG3CNT:
157		value &= 0xFFCF;
158		GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
159		break;
160	case REG_BG0HOFS:
161		value &= 0x01FF;
162		softwareRenderer->bg[0].x = value;
163		break;
164	case REG_BG0VOFS:
165		value &= 0x01FF;
166		softwareRenderer->bg[0].y = value;
167		break;
168	case REG_BG1HOFS:
169		value &= 0x01FF;
170		softwareRenderer->bg[1].x = value;
171		break;
172	case REG_BG1VOFS:
173		value &= 0x01FF;
174		softwareRenderer->bg[1].y = value;
175		break;
176	case REG_BG2HOFS:
177		value &= 0x01FF;
178		softwareRenderer->bg[2].x = value;
179		break;
180	case REG_BG2VOFS:
181		value &= 0x01FF;
182		softwareRenderer->bg[2].y = value;
183		break;
184	case REG_BG3HOFS:
185		value &= 0x01FF;
186		softwareRenderer->bg[3].x = value;
187		break;
188	case REG_BG3VOFS:
189		value &= 0x01FF;
190		softwareRenderer->bg[3].y = value;
191		break;
192	case REG_BG2PA:
193		GBAVideoSoftwareRendererWriteBGPA(&softwareRenderer->bg[2], value);
194		break;
195	case REG_BG2PB:
196		GBAVideoSoftwareRendererWriteBGPB(&softwareRenderer->bg[2], value);
197		break;
198	case REG_BG2PC:
199		GBAVideoSoftwareRendererWriteBGPC(&softwareRenderer->bg[2], value);
200		break;
201	case REG_BG2PD:
202		GBAVideoSoftwareRendererWriteBGPD(&softwareRenderer->bg[2], value);
203		break;
204	case REG_BG2X_LO:
205		GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[2], value);
206		break;
207	case REG_BG2X_HI:
208		GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[2], value);
209		break;
210	case REG_BG2Y_LO:
211		GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[2], value);
212		break;
213	case REG_BG2Y_HI:
214		GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[2], value);
215		break;
216	case REG_BG3PA:
217		GBAVideoSoftwareRendererWriteBGPA(&softwareRenderer->bg[3], value);
218		break;
219	case REG_BG3PB:
220		GBAVideoSoftwareRendererWriteBGPB(&softwareRenderer->bg[3], value);
221		break;
222	case REG_BG3PC:
223		GBAVideoSoftwareRendererWriteBGPC(&softwareRenderer->bg[3], value);
224		break;
225	case REG_BG3PD:
226		GBAVideoSoftwareRendererWriteBGPD(&softwareRenderer->bg[3], value);
227		break;
228	case REG_BG3X_LO:
229		GBAVideoSoftwareRendererWriteBGX_LO(&softwareRenderer->bg[3], value);
230		break;
231	case REG_BG3X_HI:
232		GBAVideoSoftwareRendererWriteBGX_HI(&softwareRenderer->bg[3], value);
233		break;
234	case REG_BG3Y_LO:
235		GBAVideoSoftwareRendererWriteBGY_LO(&softwareRenderer->bg[3], value);
236		break;
237	case REG_BG3Y_HI:
238		GBAVideoSoftwareRendererWriteBGY_HI(&softwareRenderer->bg[3], value);
239		break;
240	case REG_BLDCNT:
241		GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
242		break;
243	case REG_BLDALPHA:
244		softwareRenderer->blda = value & 0x1F;
245		if (softwareRenderer->blda > 0x10) {
246			softwareRenderer->blda = 0x10;
247		}
248		softwareRenderer->bldb = (value >> 8) & 0x1F;
249		if (softwareRenderer->bldb > 0x10) {
250			softwareRenderer->bldb = 0x10;
251		}
252		break;
253	case REG_BLDY:
254		softwareRenderer->bldy = value & 0x1F;
255		if (softwareRenderer->bldy > 0x10) {
256			softwareRenderer->bldy = 0x10;
257		}
258		_updatePalettes(softwareRenderer);
259		break;
260	case REG_WIN0H:
261		softwareRenderer->winN[0].h.end = value;
262		softwareRenderer->winN[0].h.start = value >> 8;
263		if (softwareRenderer->winN[0].h.start > VIDEO_HORIZONTAL_PIXELS && softwareRenderer->winN[0].h.start > softwareRenderer->winN[0].h.end) {
264			softwareRenderer->winN[0].h.start = 0;
265		}
266		if (softwareRenderer->winN[0].h.end > VIDEO_HORIZONTAL_PIXELS) {
267			softwareRenderer->winN[0].h.end = VIDEO_HORIZONTAL_PIXELS;
268			if (softwareRenderer->winN[0].h.start > VIDEO_HORIZONTAL_PIXELS) {
269				softwareRenderer->winN[0].h.start = VIDEO_HORIZONTAL_PIXELS;
270			}
271		}
272		break;
273	case REG_WIN1H:
274		softwareRenderer->winN[1].h.end = value;
275		softwareRenderer->winN[1].h.start = value >> 8;
276		if (softwareRenderer->winN[1].h.start > VIDEO_HORIZONTAL_PIXELS && softwareRenderer->winN[1].h.start > softwareRenderer->winN[1].h.end) {
277			softwareRenderer->winN[1].h.start = 0;
278		}
279		if (softwareRenderer->winN[1].h.end > VIDEO_HORIZONTAL_PIXELS) {
280			softwareRenderer->winN[1].h.end = VIDEO_HORIZONTAL_PIXELS;
281			if (softwareRenderer->winN[1].h.start > VIDEO_HORIZONTAL_PIXELS) {
282				softwareRenderer->winN[1].h.start = VIDEO_HORIZONTAL_PIXELS;
283			}
284		}
285		break;
286	case REG_WIN0V:
287		softwareRenderer->winN[0].v.end = value;
288		softwareRenderer->winN[0].v.start = value >> 8;
289		if (softwareRenderer->winN[0].v.start > VIDEO_VERTICAL_PIXELS && softwareRenderer->winN[0].v.start > softwareRenderer->winN[0].v.end) {
290			softwareRenderer->winN[0].v.start = 0;
291		}
292		if (softwareRenderer->winN[0].v.end > VIDEO_VERTICAL_PIXELS) {
293			softwareRenderer->winN[0].v.end = VIDEO_VERTICAL_PIXELS;
294			if (softwareRenderer->winN[0].v.start > VIDEO_VERTICAL_PIXELS) {
295				softwareRenderer->winN[0].v.start = VIDEO_VERTICAL_PIXELS;
296			}
297		}
298		break;
299	case REG_WIN1V:
300		softwareRenderer->winN[1].v.end = value;
301		softwareRenderer->winN[1].v.start = value >> 8;
302		if (softwareRenderer->winN[1].v.start > VIDEO_VERTICAL_PIXELS && softwareRenderer->winN[1].v.start > softwareRenderer->winN[1].v.end) {
303			softwareRenderer->winN[1].v.start = 0;
304		}
305		if (softwareRenderer->winN[1].v.end > VIDEO_VERTICAL_PIXELS) {
306			softwareRenderer->winN[1].v.end = VIDEO_VERTICAL_PIXELS;
307			if (softwareRenderer->winN[1].v.start > VIDEO_VERTICAL_PIXELS) {
308				softwareRenderer->winN[1].v.start = VIDEO_VERTICAL_PIXELS;
309			}
310		}
311		break;
312	case REG_WININ:
313		softwareRenderer->winN[0].control.packed = value;
314		softwareRenderer->winN[1].control.packed = value >> 8;
315		break;
316	case REG_WINOUT:
317		softwareRenderer->winout.packed = value;
318		softwareRenderer->objwin.packed = value >> 8;
319		break;
320	case REG_MOSAIC:
321		softwareRenderer->mosaic = value;
322		break;
323	case REG_GREENSWP:
324		GBALog(0, GBA_LOG_STUB, "Stub video register write: 0x%03X", address);
325		break;
326	default:
327		GBALog(0, GBA_LOG_GAME_ERROR, "Invalid video register: 0x%03X", address);
328	}
329	return value;
330}
331
332static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
333	UNUSED(renderer);
334	UNUSED(address);
335}
336
337static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
338	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
339	softwareRenderer->oamDirty = 1;
340	UNUSED(oam);
341}
342
343static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
344	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
345#ifdef COLOR_16_BIT
346#ifdef COLOR_5_6_5
347	unsigned color = 0;
348	color |= (value & 0x001F) << 11;
349	color |= (value & 0x03E0) << 1;
350	color |= (value & 0x7C00) >> 10;
351#else
352	unsigned color = value;
353#endif
354#else
355	unsigned color = 0;
356	color |= (value << 3) & 0xF8;
357	color |= (value << 6) & 0xF800;
358	color |= (value << 9) & 0xF80000;
359#endif
360	softwareRenderer->normalPalette[address >> 1] = color;
361	if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
362		softwareRenderer->variantPalette[address >> 1] = _brighten(color, softwareRenderer->bldy);
363	} else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
364		softwareRenderer->variantPalette[address >> 1] = _darken(color, softwareRenderer->bldy);
365	}
366}
367
368static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y) {
369	if (win->v.end >= win->v.start) {
370		if (y >= win->v.end) {
371			return;
372		}
373		if (y < win->v.start) {
374			return;
375		}
376	} else if (y >= win->v.end && y < win->v.start) {
377		return;
378	}
379	if (win->h.end > VIDEO_HORIZONTAL_PIXELS || win->h.end < win->h.start) {
380		struct WindowN splits[2] = { *win, *win };
381		splits[0].h.start = 0;
382		splits[1].h.end = VIDEO_HORIZONTAL_PIXELS;
383		_breakWindowInner(softwareRenderer, &splits[0]);
384		_breakWindowInner(softwareRenderer, &splits[1]);
385	} else {
386		_breakWindowInner(softwareRenderer, win);
387	}
388}
389
390static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win) {
391	int activeWindow;
392	int startX = 0;
393	if (win->h.end > 0) {
394		for (activeWindow = 0; activeWindow < softwareRenderer->nWindows; ++activeWindow) {
395			if (win->h.start < softwareRenderer->windows[activeWindow].endX) {
396				// Insert a window before the end of the active window
397				struct Window oldWindow = softwareRenderer->windows[activeWindow];
398				if (win->h.start > startX) {
399					// And after the start of the active window
400					int nextWindow = softwareRenderer->nWindows;
401					++softwareRenderer->nWindows;
402					for (; nextWindow > activeWindow; --nextWindow) {
403						softwareRenderer->windows[nextWindow] = softwareRenderer->windows[nextWindow - 1];
404					}
405					softwareRenderer->windows[activeWindow].endX = win->h.start;
406					++activeWindow;
407				}
408				softwareRenderer->windows[activeWindow].control = win->control;
409				softwareRenderer->windows[activeWindow].endX = win->h.end;
410				if (win->h.end >= oldWindow.endX) {
411					// Trim off extra windows we've overwritten
412					for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) {
413						if (VIDEO_CHECKS && activeWindow >= MAX_WINDOW) {
414							GBALog(0, GBA_LOG_FATAL, "Out of bounds window write will occur");
415							return;
416						}
417						softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1];
418						--softwareRenderer->nWindows;
419					}
420				} else {
421					++activeWindow;
422					int nextWindow = softwareRenderer->nWindows;
423					++softwareRenderer->nWindows;
424					for (; nextWindow > activeWindow; --nextWindow) {
425						softwareRenderer->windows[nextWindow] = softwareRenderer->windows[nextWindow - 1];
426					}
427					softwareRenderer->windows[activeWindow] = oldWindow;
428				}
429				break;
430			}
431			startX = softwareRenderer->windows[activeWindow].endX;
432		}
433	}
434#ifdef DEBUG
435	if (softwareRenderer->nWindows > MAX_WINDOW) {
436		GBALog(0, GBA_LOG_FATAL, "Out of bounds window write occurred!");
437	}
438#endif
439}
440
441static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer) {
442	int i;
443	int oamMax = 0;
444	for (i = 0; i < 128; ++i) {
445		struct GBAObj obj;
446		LOAD_16(obj.a, 0, &renderer->d.oam->obj[i].a);
447		LOAD_16(obj.b, 0, &renderer->d.oam->obj[i].b);
448		LOAD_16(obj.c, 0, &renderer->d.oam->obj[i].c);
449		if (GBAObjAttributesAIsTransformed(obj.a) || !GBAObjAttributesAIsDisable(obj.a)) {
450			int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(obj.a) * 4 + GBAObjAttributesBGetSize(obj.b)][1];
451			if (GBAObjAttributesAIsTransformed(obj.a)) {
452				height <<= GBAObjAttributesAGetDoubleSize(obj.a);
453			}
454			if (GBAObjAttributesAGetY(obj.a) < VIDEO_VERTICAL_PIXELS || GBAObjAttributesAGetY(obj.a) + height >= VIDEO_VERTICAL_TOTAL_PIXELS) {
455				renderer->sprites[oamMax].y = GBAObjAttributesAGetY(obj.a);
456				renderer->sprites[oamMax].endY = GBAObjAttributesAGetY(obj.a) + height;
457				renderer->sprites[oamMax].obj = obj;
458				++oamMax;
459			}
460		}
461	}
462	renderer->oamMax = oamMax;
463	renderer->oamDirty = 0;
464}
465
466static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
467	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
468
469	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
470	if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) {
471		int x;
472		for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
473			row[x] = GBA_COLOR_WHITE;
474		}
475		return;
476	}
477
478	int x;
479	for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; x += 4) {
480		softwareRenderer->spriteLayer[x] = FLAG_UNWRITTEN;
481		softwareRenderer->spriteLayer[x + 1] = FLAG_UNWRITTEN;
482		softwareRenderer->spriteLayer[x + 2] = FLAG_UNWRITTEN;
483		softwareRenderer->spriteLayer[x + 3] = FLAG_UNWRITTEN;
484	}
485
486	softwareRenderer->windows[0].endX = VIDEO_HORIZONTAL_PIXELS;
487	softwareRenderer->nWindows = 1;
488	if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt) || GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) {
489		softwareRenderer->windows[0].control = softwareRenderer->winout;
490		if (GBARegisterDISPCNTIsWin1Enable(softwareRenderer->dispcnt)) {
491			_breakWindow(softwareRenderer, &softwareRenderer->winN[1], y);
492		}
493		if (GBARegisterDISPCNTIsWin0Enable(softwareRenderer->dispcnt)) {
494			_breakWindow(softwareRenderer, &softwareRenderer->winN[0], y);
495		}
496	} else {
497		softwareRenderer->windows[0].control.packed = 0xFF;
498	}
499
500	GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
501
502	int w;
503	x = 0;
504	for (w = 0; w < softwareRenderer->nWindows; ++w) {
505		// TOOD: handle objwin on backdrop
506		uint32_t backdrop = FLAG_UNWRITTEN | FLAG_PRIORITY | FLAG_IS_BACKGROUND;
507		if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
508			backdrop |= softwareRenderer->normalPalette[0];
509		} else {
510			backdrop |= softwareRenderer->variantPalette[0];
511		}
512		int end = softwareRenderer->windows[w].endX;
513		for (; x < end; ++x) {
514			softwareRenderer->row[x] = backdrop;
515		}
516	}
517
518	_drawScanline(softwareRenderer, y);
519
520	if (softwareRenderer->target2Bd) {
521		x = 0;
522		for (w = 0; w < softwareRenderer->nWindows; ++w) {
523			uint32_t backdrop = FLAG_UNWRITTEN;
524			if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
525				backdrop |= softwareRenderer->normalPalette[0];
526			} else {
527				backdrop |= softwareRenderer->variantPalette[0];
528			}
529			int end = softwareRenderer->windows[w].endX;
530			for (; x < end; ++x) {
531				uint32_t color = softwareRenderer->row[x];
532				if (color & FLAG_TARGET_1 && color & FLAG_UNWRITTEN) {
533					softwareRenderer->row[x] = _mix(softwareRenderer->bldb, backdrop, softwareRenderer->blda, color);
534				}
535			}
536		}
537	}
538	if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) {
539		x = 0;
540		for (w = 0; w < softwareRenderer->nWindows; ++w) {
541			if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) {
542				continue;
543			}
544			int end = softwareRenderer->windows[w].endX;
545			if (softwareRenderer->blendEffect == BLEND_DARKEN) {
546				for (; x < end; ++x) {
547					uint32_t color = softwareRenderer->row[x];
548					if (color & FLAG_TARGET_1 && !(color & FLAG_UNWRITTEN)) {
549						softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy);
550					}
551				}
552			} else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
553				for (; x < end; ++x) {
554					uint32_t color = softwareRenderer->row[x];
555					if (color & FLAG_TARGET_1 && !(color & FLAG_UNWRITTEN)) {
556						softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy);
557					}
558				}
559			}
560		}
561	}
562
563#ifdef COLOR_16_BIT
564#if defined(__ARM_NEON) && !defined(__APPLE__)
565	_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
566#else
567	for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
568		row[x] = softwareRenderer->row[x];
569	}
570#endif
571#else
572	memcpy(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS * sizeof(*row));
573#endif
574}
575
576static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
577	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
578
579	softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;
580	softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy;
581	softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;
582	softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
583}
584
585static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels) {
586	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
587
588	*stride = softwareRenderer->outputBufferStride;
589	*pixels = softwareRenderer->outputBuffer;
590}
591
592static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
593	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
594
595	uint32_t* colorPixels = pixels;
596	unsigned i;
597	for (i = 0; i < VIDEO_VERTICAL_PIXELS; ++i) {
598		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
599	}
600}
601
602static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
603	renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt) && !renderer->d.disableBG[0];
604	renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt) && !renderer->d.disableBG[1];
605	renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt) && !renderer->d.disableBG[2];
606	renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt) && !renderer->d.disableBG[3];
607}
608
609static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
610	UNUSED(renderer);
611	bg->priority = GBARegisterBGCNTGetPriority(value);
612	bg->charBase = GBARegisterBGCNTGetCharBase(value) << 14;
613	bg->mosaic = GBARegisterBGCNTGetMosaic(value);
614	bg->multipalette = GBARegisterBGCNTGet256Color(value);
615	bg->screenBase = GBARegisterBGCNTGetScreenBase(value) << 11;
616	bg->overflow = GBARegisterBGCNTGetOverflow(value);
617	bg->size = GBARegisterBGCNTGetSize(value);
618}
619
620static void GBAVideoSoftwareRendererWriteBGPA(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
621	bg->dx = value;
622}
623
624static void GBAVideoSoftwareRendererWriteBGPB(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
625	bg->dmx = value;
626}
627
628static void GBAVideoSoftwareRendererWriteBGPC(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
629	bg->dy = value;
630}
631
632static void GBAVideoSoftwareRendererWriteBGPD(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
633	bg->dmy = value;
634}
635
636static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
637	bg->refx = (bg->refx & 0xFFFF0000) | value;
638	bg->sx = bg->refx;
639}
640
641static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
642	bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
643	bg->refx <<= 4;
644	bg->refx >>= 4;
645	bg->sx = bg->refx;
646}
647
648static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
649	bg->refy = (bg->refy & 0xFFFF0000) | value;
650	bg->sy = bg->refy;
651}
652
653static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
654	bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
655	bg->refy <<= 4;
656	bg->refy >>= 4;
657	bg->sy = bg->refy;
658}
659
660static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
661	enum BlendEffect oldEffect = renderer->blendEffect;
662
663	renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
664	renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
665	renderer->bg[2].target1 = GBARegisterBLDCNTGetTarget1Bg2(value);
666	renderer->bg[3].target1 = GBARegisterBLDCNTGetTarget1Bg3(value);
667	renderer->bg[0].target2 = GBARegisterBLDCNTGetTarget2Bg0(value);
668	renderer->bg[1].target2 = GBARegisterBLDCNTGetTarget2Bg1(value);
669	renderer->bg[2].target2 = GBARegisterBLDCNTGetTarget2Bg2(value);
670	renderer->bg[3].target2 = GBARegisterBLDCNTGetTarget2Bg3(value);
671
672	renderer->blendEffect = GBARegisterBLDCNTGetEffect(value);
673	renderer->target1Obj = GBARegisterBLDCNTGetTarget1Obj(value);
674	renderer->target1Bd = GBARegisterBLDCNTGetTarget1Bd(value);
675	renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
676	renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
677
678	renderer->anyTarget2 = value & 0x3F00;
679
680	if (oldEffect != renderer->blendEffect) {
681		_updatePalettes(renderer);
682	}
683}
684
685#define TEST_LAYER_ENABLED(X) \
686	(renderer->bg[X].enabled && \
687	(GBAWindowControlIsBg ## X ## Enable(renderer->currentWindow.packed) || \
688	(GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlIsBg ## X ## Enable (renderer->objwin.packed))) && \
689	renderer->bg[X].priority == priority)
690
691static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
692	int w;
693	renderer->end = 0;
694	int spriteLayers = 0;
695	if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
696		if (renderer->oamDirty) {
697			_cleanOAM(renderer);
698		}
699		int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
700		int mosaicY = y - (y % mosaicV);
701		for (w = 0; w < renderer->nWindows; ++w) {
702			renderer->start = renderer->end;
703			renderer->end = renderer->windows[w].endX;
704			renderer->currentWindow = renderer->windows[w].control;
705			if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed) && !GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt)) {
706				continue;
707			}
708			int i;
709			int drawn;
710			for (i = 0; i < renderer->oamMax; ++i) {
711				int localY = y;
712				struct GBAVideoSoftwareSprite* sprite = &renderer->sprites[i];
713				if (GBAObjAttributesAIsMosaic(sprite->obj.a)) {
714					localY = mosaicY;
715				}
716				if ((localY < sprite->y && (sprite->endY - 256 < 0 || localY >= sprite->endY - 256)) || localY >= sprite->endY) {
717					continue;
718				}
719				drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY);
720				spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
721			}
722		}
723	}
724
725	int priority;
726	for (priority = 0; priority < 4; ++priority) {
727		renderer->end = 0;
728		for (w = 0; w < renderer->nWindows; ++w) {
729			renderer->start = renderer->end;
730			renderer->end = renderer->windows[w].endX;
731			renderer->currentWindow = renderer->windows[w].control;
732			if (spriteLayers & (1 << priority)) {
733				GBAVideoSoftwareRendererPostprocessSprite(renderer, priority);
734			}
735			if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) {
736				GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[0], y);
737			}
738			if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) {
739				GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[1], y);
740			}
741			if (TEST_LAYER_ENABLED(2)) {
742				switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) {
743				case 0:
744					GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[2], y);
745					break;
746				case 1:
747				case 2:
748					GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[2], y);
749					break;
750				case 3:
751					GBAVideoSoftwareRendererDrawBackgroundMode3(renderer, &renderer->bg[2], y);
752					break;
753				case 4:
754					GBAVideoSoftwareRendererDrawBackgroundMode4(renderer, &renderer->bg[2], y);
755					break;
756				case 5:
757					GBAVideoSoftwareRendererDrawBackgroundMode5(renderer, &renderer->bg[2], y);
758					break;
759				}
760			}
761			if (TEST_LAYER_ENABLED(3)) {
762				switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) {
763				case 0:
764					GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[3], y);
765					break;
766				case 2:
767					GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[3], y);
768					break;
769				}
770			}
771		}
772	}
773	renderer->bg[2].sx += renderer->bg[2].dmx;
774	renderer->bg[2].sy += renderer->bg[2].dmy;
775	renderer->bg[3].sx += renderer->bg[3].dmx;
776	renderer->bg[3].sy += renderer->bg[3].dmy;
777}
778
779static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
780	int i;
781	if (renderer->blendEffect == BLEND_BRIGHTEN) {
782		for (i = 0; i < 512; ++i) {
783			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
784		}
785	} else if (renderer->blendEffect == BLEND_DARKEN) {
786		for (i = 0; i < 512; ++i) {
787			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
788		}
789	} else {
790		for (i = 0; i < 512; ++i) {
791			renderer->variantPalette[i] = renderer->normalPalette[i];
792		}
793	}
794}