all repos — mgba @ 5f572ffb6238881b6fda7e7531e5fd7c68c0ef6b

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) {
533					softwareRenderer->row[x] = _mix(softwareRenderer->bldb, backdrop, softwareRenderer->blda, color);
534				}
535			}
536		}
537	}
538
539#ifdef COLOR_16_BIT
540#if defined(__ARM_NEON) && !defined(__APPLE__)
541	_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
542#else
543	for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
544		row[x] = softwareRenderer->row[x];
545	}
546#endif
547#else
548	memcpy(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS * sizeof(*row));
549#endif
550}
551
552static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
553	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
554
555	softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;
556	softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy;
557	softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;
558	softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
559}
560
561static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels) {
562	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
563
564	*stride = softwareRenderer->outputBufferStride;
565	*pixels = softwareRenderer->outputBuffer;
566}
567
568static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
569	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
570
571	uint32_t* colorPixels = pixels;
572	unsigned i;
573	for (i = 0; i < VIDEO_VERTICAL_PIXELS; ++i) {
574		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
575	}
576}
577
578static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
579	renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt) && !renderer->d.disableBG[0];
580	renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt) && !renderer->d.disableBG[1];
581	renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt) && !renderer->d.disableBG[2];
582	renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt) && !renderer->d.disableBG[3];
583}
584
585static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
586	UNUSED(renderer);
587	bg->priority = GBARegisterBGCNTGetPriority(value);
588	bg->charBase = GBARegisterBGCNTGetCharBase(value) << 14;
589	bg->mosaic = GBARegisterBGCNTGetMosaic(value);
590	bg->multipalette = GBARegisterBGCNTGet256Color(value);
591	bg->screenBase = GBARegisterBGCNTGetScreenBase(value) << 11;
592	bg->overflow = GBARegisterBGCNTGetOverflow(value);
593	bg->size = GBARegisterBGCNTGetSize(value);
594}
595
596static void GBAVideoSoftwareRendererWriteBGPA(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
597	bg->dx = value;
598}
599
600static void GBAVideoSoftwareRendererWriteBGPB(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
601	bg->dmx = value;
602}
603
604static void GBAVideoSoftwareRendererWriteBGPC(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
605	bg->dy = value;
606}
607
608static void GBAVideoSoftwareRendererWriteBGPD(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
609	bg->dmy = value;
610}
611
612static void GBAVideoSoftwareRendererWriteBGX_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
613	bg->refx = (bg->refx & 0xFFFF0000) | value;
614	bg->sx = bg->refx;
615}
616
617static void GBAVideoSoftwareRendererWriteBGX_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
618	bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
619	bg->refx <<= 4;
620	bg->refx >>= 4;
621	bg->sx = bg->refx;
622}
623
624static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
625	bg->refy = (bg->refy & 0xFFFF0000) | value;
626	bg->sy = bg->refy;
627}
628
629static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value) {
630	bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
631	bg->refy <<= 4;
632	bg->refy >>= 4;
633	bg->sy = bg->refy;
634}
635
636static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
637	enum BlendEffect oldEffect = renderer->blendEffect;
638
639	renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
640	renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
641	renderer->bg[2].target1 = GBARegisterBLDCNTGetTarget1Bg2(value);
642	renderer->bg[3].target1 = GBARegisterBLDCNTGetTarget1Bg3(value);
643	renderer->bg[0].target2 = GBARegisterBLDCNTGetTarget2Bg0(value);
644	renderer->bg[1].target2 = GBARegisterBLDCNTGetTarget2Bg1(value);
645	renderer->bg[2].target2 = GBARegisterBLDCNTGetTarget2Bg2(value);
646	renderer->bg[3].target2 = GBARegisterBLDCNTGetTarget2Bg3(value);
647
648	renderer->blendEffect = GBARegisterBLDCNTGetEffect(value);
649	renderer->target1Obj = GBARegisterBLDCNTGetTarget1Obj(value);
650	renderer->target1Bd = GBARegisterBLDCNTGetTarget1Bd(value);
651	renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
652	renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
653
654	renderer->anyTarget2 = value & 0x3F00;
655
656	if (oldEffect != renderer->blendEffect) {
657		_updatePalettes(renderer);
658	}
659}
660
661#define TEST_LAYER_ENABLED(X) \
662	(renderer->bg[X].enabled && \
663	(GBAWindowControlIsBg ## X ## Enable(renderer->currentWindow.packed) || \
664	(GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlIsBg ## X ## Enable (renderer->objwin.packed))) && \
665	renderer->bg[X].priority == priority)
666
667static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
668	int w;
669	renderer->end = 0;
670	int spriteLayers = 0;
671	if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
672		if (renderer->oamDirty) {
673			_cleanOAM(renderer);
674		}
675		int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
676		int mosaicY = y - (y % mosaicV);
677		for (w = 0; w < renderer->nWindows; ++w) {
678			renderer->start = renderer->end;
679			renderer->end = renderer->windows[w].endX;
680			renderer->currentWindow = renderer->windows[w].control;
681			if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed) && !GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt)) {
682				continue;
683			}
684			int i;
685			int drawn;
686			for (i = 0; i < renderer->oamMax; ++i) {
687				int localY = y;
688				struct GBAVideoSoftwareSprite* sprite = &renderer->sprites[i];
689				if (GBAObjAttributesAIsMosaic(sprite->obj.a)) {
690					localY = mosaicY;
691				}
692				if ((localY < sprite->y && (sprite->endY - 256 < 0 || localY >= sprite->endY - 256)) || localY >= sprite->endY) {
693					continue;
694				}
695				drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY);
696				spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c);
697			}
698		}
699	}
700
701	int priority;
702	for (priority = 0; priority < 4; ++priority) {
703		renderer->end = 0;
704		for (w = 0; w < renderer->nWindows; ++w) {
705			renderer->start = renderer->end;
706			renderer->end = renderer->windows[w].endX;
707			renderer->currentWindow = renderer->windows[w].control;
708			if (spriteLayers & (1 << priority)) {
709				GBAVideoSoftwareRendererPostprocessSprite(renderer, priority);
710			}
711			if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) {
712				GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[0], y);
713			}
714			if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) {
715				GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[1], y);
716			}
717			if (TEST_LAYER_ENABLED(2)) {
718				switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) {
719				case 0:
720					GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[2], y);
721					break;
722				case 1:
723				case 2:
724					GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[2], y);
725					break;
726				case 3:
727					GBAVideoSoftwareRendererDrawBackgroundMode3(renderer, &renderer->bg[2], y);
728					break;
729				case 4:
730					GBAVideoSoftwareRendererDrawBackgroundMode4(renderer, &renderer->bg[2], y);
731					break;
732				case 5:
733					GBAVideoSoftwareRendererDrawBackgroundMode5(renderer, &renderer->bg[2], y);
734					break;
735				}
736			}
737			if (TEST_LAYER_ENABLED(3)) {
738				switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) {
739				case 0:
740					GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[3], y);
741					break;
742				case 2:
743					GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[3], y);
744					break;
745				}
746			}
747		}
748	}
749	renderer->bg[2].sx += renderer->bg[2].dmx;
750	renderer->bg[2].sy += renderer->bg[2].dmy;
751	renderer->bg[3].sx += renderer->bg[3].dmx;
752	renderer->bg[3].sy += renderer->bg[3].dmy;
753}
754
755static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
756	int i;
757	if (renderer->blendEffect == BLEND_BRIGHTEN) {
758		for (i = 0; i < 512; ++i) {
759			renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
760		}
761	} else if (renderer->blendEffect == BLEND_DARKEN) {
762		for (i = 0; i < 512; ++i) {
763			renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
764		}
765	} else {
766		for (i = 0; i < 512; ++i) {
767			renderer->variantPalette[i] = renderer->normalPalette[i];
768		}
769	}
770}