all repos — mgba @ 2f2e5398719f9d6a78ae84fc241945b46839b01c

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