all repos — mgba @ 1882b155677ce11c4fd6e02bb5285fe78a2cc3f4

mGBA Game Boy Advance Emulator

src/ds/renderers/software.c (view raw)

  1/* Copyright (c) 2013-2017 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 <mgba/internal/ds/renderers/software.h>
  7#include "gba/renderers/software-private.h"
  8
  9#include <mgba/internal/arm/macros.h>
 10#include <mgba/internal/ds/gx.h>
 11#include <mgba/internal/ds/io.h>
 12
 13static void DSVideoSoftwareRendererInit(struct DSVideoRenderer* renderer);
 14static void DSVideoSoftwareRendererDeinit(struct DSVideoRenderer* renderer);
 15static void DSVideoSoftwareRendererReset(struct DSVideoRenderer* renderer);
 16static uint16_t DSVideoSoftwareRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value);
 17static void DSVideoSoftwareRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value);
 18static void DSVideoSoftwareRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam);
 19static void DSVideoSoftwareRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot);
 20static void DSVideoSoftwareRendererDrawScanline(struct DSVideoRenderer* renderer, int y);
 21static void DSVideoSoftwareRendererDrawScanlineDirectly(struct DSVideoRenderer* renderer, int y, color_t* scanline);
 22static void DSVideoSoftwareRendererFinishFrame(struct DSVideoRenderer* renderer);
 23static void DSVideoSoftwareRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels);
 24static void DSVideoSoftwareRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels);
 25
 26static void DSVideoSoftwareRendererDrawBackgroundExt0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY);
 27static void DSVideoSoftwareRendererDrawBackgroundExt1(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY);
 28static void DSVideoSoftwareRendererDrawBackgroundExt2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY);
 29
 30static bool _regenerateExtPalette(struct DSVideoSoftwareRenderer* renderer, bool obj, bool engB, int slot) {
 31	color_t* palette;
 32	color_t* variantPalette;
 33	struct GBAVideoSoftwareRenderer* softwareRenderer;
 34	uint16_t* vram;
 35	if (!obj) {
 36		if (!engB) {
 37			palette = &renderer->extPaletteA[slot * 4096];
 38			variantPalette = &renderer->variantPaletteA[slot * 4096];
 39			softwareRenderer = &renderer->engA;
 40			vram = renderer->d.vramABGExtPal[slot];
 41		} else {
 42			palette = &renderer->extPaletteB[slot * 4096];
 43			variantPalette = &renderer->variantPaletteB[slot * 4096];
 44			softwareRenderer = &renderer->engB;
 45			vram = renderer->d.vramBBGExtPal[slot];
 46		}
 47	} else {
 48		if (!engB) {
 49			palette = renderer->objExtPaletteA;
 50			variantPalette = renderer->variantPaletteA;
 51			softwareRenderer = &renderer->engA;
 52			vram = renderer->d.vramAOBJExtPal;
 53		} else {
 54			palette = renderer->objExtPaletteB;
 55			variantPalette = renderer->variantPaletteB;
 56			softwareRenderer = &renderer->engB;
 57			vram = renderer->d.vramBOBJExtPal;
 58		}
 59	}
 60	if (!vram) {
 61		return false;
 62	}
 63	int i;
 64	for (i = 0; i < 4096; ++i) {
 65		uint16_t value = vram[i];
 66#ifdef COLOR_16_BIT
 67#ifdef COLOR_5_6_5
 68		unsigned color = 0;
 69		color |= (value & 0x001F) << 11;
 70		color |= (value & 0x03E0) << 1;
 71		color |= (value & 0x7C00) >> 10;
 72#else
 73		unsigned color = value;
 74#endif
 75#else
 76		unsigned color = 0;
 77		color |= (value << 3) & 0xF8;
 78		color |= (value << 6) & 0xF800;
 79		color |= (value << 9) & 0xF80000;
 80		color |= (color >> 5) & 0x070707;
 81#endif
 82		palette[i] = color;
 83		if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 84			variantPalette[i] = _brighten(color, softwareRenderer->bldy);
 85		} else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 86			variantPalette[i] = _darken(color, softwareRenderer->bldy);
 87		}
 88	}
 89	if (obj) {
 90		softwareRenderer->objExtPalette = palette;
 91		softwareRenderer->objExtVariantPalette = variantPalette;
 92	} else {
 93		if (slot >= 2) {
 94			if (GBARegisterBGCNTIsExtPaletteSlot(softwareRenderer->bg[slot - 2].control)) {
 95				softwareRenderer->bg[slot - 2].extPalette = palette;
 96				softwareRenderer->bg[slot - 2].variantPalette = variantPalette;
 97			}
 98		} else if (slot < 2 && !GBARegisterBGCNTIsExtPaletteSlot(softwareRenderer->bg[slot].control) ) {
 99			softwareRenderer->bg[slot].extPalette = palette;
100			softwareRenderer->bg[slot].variantPalette = variantPalette;
101		}
102		softwareRenderer->bg[slot].extPalette = palette;
103		softwareRenderer->bg[slot].variantPalette = variantPalette;
104	}
105	return true;
106}
107
108static void _updateCharBase(struct DSVideoSoftwareRenderer* softwareRenderer, bool engB) {
109	struct GBAVideoSoftwareRenderer* eng;
110	if (!engB) {
111		eng = &softwareRenderer->engA;
112	} else {
113		eng = &softwareRenderer->engB;
114	}
115	int i;
116	uint32_t charBase = DSRegisterDISPCNTGetCharBase(softwareRenderer->dispcntA) << 16;
117	uint32_t screenBase = DSRegisterDISPCNTGetScreenBase(softwareRenderer->dispcntA) << 16;
118	for (i = 0; i < 4; ++i) {
119		if (!engB) {
120			uint32_t control = eng->bg[i].control;
121			eng->d.writeVideoRegister(&eng->d, DS9_REG_A_BG0CNT + i * 2, control);
122			eng->bg[i].control = control;
123		}
124
125		eng->bg[i].charBase = GBARegisterBGCNTGetCharBase(eng->bg[i].control) << 14;
126
127		if (!engB) {
128			softwareRenderer->engA.bg[i].charBase += charBase;
129			softwareRenderer->engA.bg[i].screenBase &= ~0x70000;
130			softwareRenderer->engA.bg[i].screenBase |= screenBase;
131		}
132	}
133}
134
135void DSVideoSoftwareRendererCreate(struct DSVideoSoftwareRenderer* renderer) {
136	renderer->d.init = DSVideoSoftwareRendererInit;
137	renderer->d.reset = DSVideoSoftwareRendererReset;
138	renderer->d.deinit = DSVideoSoftwareRendererDeinit;
139	renderer->d.writeVideoRegister = DSVideoSoftwareRendererWriteVideoRegister;
140	renderer->d.writePalette = DSVideoSoftwareRendererWritePalette;
141	renderer->d.writeOAM = DSVideoSoftwareRendererWriteOAM;
142	renderer->d.invalidateExtPal = DSVideoSoftwareRendererInvalidateExtPal;
143	renderer->d.drawScanline = DSVideoSoftwareRendererDrawScanline;
144	renderer->d.drawScanlineDirectly = DSVideoSoftwareRendererDrawScanlineDirectly;
145	renderer->d.finishFrame = DSVideoSoftwareRendererFinishFrame;
146	renderer->d.getPixels = DSVideoSoftwareRendererGetPixels;
147	renderer->d.putPixels = DSVideoSoftwareRendererPutPixels;
148
149	renderer->engA.d.cache = NULL;
150	GBAVideoSoftwareRendererCreate(&renderer->engA);
151	renderer->engA.combinedObjSort = true;
152
153	renderer->engB.d.cache = NULL;
154	GBAVideoSoftwareRendererCreate(&renderer->engB);
155	renderer->engB.combinedObjSort = true;
156}
157
158static void DSVideoSoftwareRendererInit(struct DSVideoRenderer* renderer) {
159	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
160	softwareRenderer->engA.d.palette = &renderer->palette[0];
161	softwareRenderer->engA.d.oam = &renderer->oam->oam[0];
162	softwareRenderer->engA.masterEnd = DS_VIDEO_HORIZONTAL_PIXELS;
163	softwareRenderer->engA.masterHeight = DS_VIDEO_VERTICAL_PIXELS;
164	softwareRenderer->engA.masterScanlines = DS_VIDEO_VERTICAL_TOTAL_PIXELS;
165	softwareRenderer->engA.outputBufferStride = softwareRenderer->outputBufferStride;
166	softwareRenderer->engB.d.palette = &renderer->palette[512];
167	softwareRenderer->engB.d.oam = &renderer->oam->oam[1];
168	softwareRenderer->engB.masterEnd = DS_VIDEO_HORIZONTAL_PIXELS;
169	softwareRenderer->engB.masterHeight = DS_VIDEO_VERTICAL_PIXELS;
170	softwareRenderer->engB.masterScanlines = DS_VIDEO_VERTICAL_TOTAL_PIXELS;
171	softwareRenderer->engB.outputBufferStride = softwareRenderer->outputBufferStride;
172
173	DSVideoSoftwareRendererReset(renderer);
174}
175
176static void DSVideoSoftwareRendererReset(struct DSVideoRenderer* renderer) {
177	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
178	softwareRenderer->engA.d.reset(&softwareRenderer->engA.d);
179	softwareRenderer->engB.d.reset(&softwareRenderer->engB.d);
180	softwareRenderer->powcnt = 0;
181	softwareRenderer->dispcntA = 0;
182	softwareRenderer->dispcntB = 0;
183}
184
185static void DSVideoSoftwareRendererDeinit(struct DSVideoRenderer* renderer) {
186	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
187	softwareRenderer->engA.d.deinit(&softwareRenderer->engA.d);
188	softwareRenderer->engB.d.deinit(&softwareRenderer->engB.d);
189}
190
191static void DSVideoSoftwareRendererUpdateDISPCNT(struct DSVideoSoftwareRenderer* softwareRenderer, bool engB) {
192	uint32_t dispcnt;
193	struct GBAVideoSoftwareRenderer* eng;
194	if (!engB) {
195		dispcnt = softwareRenderer->dispcntA;
196		eng = &softwareRenderer->engA;
197	} else {
198		dispcnt = softwareRenderer->dispcntB;
199		eng = &softwareRenderer->engB;
200	}
201	uint16_t fakeDispcnt = dispcnt & 0xFF87;
202	if (!DSRegisterDISPCNTIsTileObjMapping(dispcnt)) {
203		eng->tileStride = 0x20;
204	} else {
205		eng->tileStride = 0x20 << DSRegisterDISPCNTGetTileBoundary(dispcnt);
206		fakeDispcnt = GBARegisterDISPCNTFillObjCharacterMapping(fakeDispcnt);
207	}
208	eng->d.writeVideoRegister(&eng->d, DS9_REG_A_DISPCNT_LO, fakeDispcnt);
209	eng->dispcnt |= dispcnt & 0xFFFF0000;
210	if (DSRegisterDISPCNTIsBgExtPalette(dispcnt)) {
211		color_t* extPalette;
212		if (!engB) {
213			extPalette = softwareRenderer->extPaletteA;
214		} else {
215			extPalette = softwareRenderer->extPaletteB;
216		}
217		int i;
218		for (i = 0; i < 4; ++i) {
219			int slot = i;
220			if (i < 2 && GBARegisterBGCNTIsExtPaletteSlot(eng->bg[i].control)) {
221				slot += 2;
222			}
223			if (eng->bg[i].extPalette != &extPalette[slot * 4096]) {
224				_regenerateExtPalette(softwareRenderer, false, engB, slot);
225			}
226		}
227	} else {
228		eng->bg[0].extPalette = NULL;
229		eng->bg[1].extPalette = NULL;
230		eng->bg[2].extPalette = NULL;
231		eng->bg[3].extPalette = NULL;
232	}
233	if (DSRegisterDISPCNTIsObjExtPalette(dispcnt)) {
234		if (!engB) {
235			if (softwareRenderer->engA.objExtPalette != softwareRenderer->objExtPaletteA) {
236				_regenerateExtPalette(softwareRenderer, true, engB, 0);
237			}
238		} else {
239			if (softwareRenderer->engB.objExtPalette != softwareRenderer->objExtPaletteB) {
240				_regenerateExtPalette(softwareRenderer, true, engB, 0);
241			}
242		}
243	} else {
244		if (!engB) {
245			softwareRenderer->engA.objExtPalette = NULL;
246		} else {
247			softwareRenderer->engB.objExtPalette = NULL;
248		}
249	}
250	if (!engB) {
251		eng->dispcnt = DSRegisterDISPCNTClear3D(eng->dispcnt);
252		eng->dispcnt |= DSRegisterDISPCNTIs3D(dispcnt);
253		_updateCharBase(softwareRenderer, engB);
254	}
255}
256
257static uint16_t DSVideoSoftwareRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
258	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
259	if (address >= DS9_REG_A_BG0CNT && address <= DS9_REG_A_BLDY) {
260		softwareRenderer->engA.d.writeVideoRegister(&softwareRenderer->engA.d, address, value);
261	} else if (address >= DS9_REG_B_BG0CNT && address <= DS9_REG_B_BLDY) {
262		softwareRenderer->engB.d.writeVideoRegister(&softwareRenderer->engB.d, address & 0xFF, value);
263	} else {
264		mLOG(DS_VIDEO, STUB, "Stub video register write: %04X:%04X", address, value);
265	}
266	switch (address) {
267	case DS9_REG_A_BG0CNT:
268	case DS9_REG_A_BG1CNT:
269		softwareRenderer->engA.bg[(address - DS9_REG_A_BG0CNT) >> 1].control = value;
270		// Fall through
271	case DS9_REG_A_BG2CNT:
272	case DS9_REG_A_BG3CNT:
273		_updateCharBase(softwareRenderer, false);
274		break;
275	case DS9_REG_B_BG0CNT:
276	case DS9_REG_B_BG1CNT:
277		softwareRenderer->engB.bg[(address - DS9_REG_B_BG0CNT) >> 1].control = value;
278		// Fall through
279	case DS9_REG_B_BG2CNT:
280	case DS9_REG_B_BG3CNT:
281		_updateCharBase(softwareRenderer, true);
282		break;
283	case DS9_REG_A_MASTER_BRIGHT:
284		softwareRenderer->engA.masterBright = DSRegisterMASTER_BRIGHTGetMode(value);
285		softwareRenderer->engA.masterBrightY = DSRegisterMASTER_BRIGHTGetY(value);
286		if (softwareRenderer->engA.masterBrightY > 0x10) {
287			softwareRenderer->engA.masterBrightY = 0x10;
288		}
289		break;
290	case DS9_REG_B_MASTER_BRIGHT:
291		softwareRenderer->engB.masterBright = DSRegisterMASTER_BRIGHTGetMode(value);
292		softwareRenderer->engB.masterBrightY = DSRegisterMASTER_BRIGHTGetY(value);
293		if (softwareRenderer->engB.masterBrightY > 0x10) {
294			softwareRenderer->engB.masterBrightY = 0x10;
295		}
296		break;
297	case DS9_REG_A_BLDCNT:
298	case DS9_REG_A_BLDY:
299		// TODO: Optimize
300		_regenerateExtPalette(softwareRenderer, false, false, 0);
301		_regenerateExtPalette(softwareRenderer, false, false, 1);
302		_regenerateExtPalette(softwareRenderer, false, false, 2);
303		_regenerateExtPalette(softwareRenderer, false, false, 3);
304		_regenerateExtPalette(softwareRenderer, true, false, 0);
305		break;
306	case DS9_REG_B_BLDCNT:
307	case DS9_REG_B_BLDY:
308		// TODO: Optimize
309		_regenerateExtPalette(softwareRenderer, false, true, 0);
310		_regenerateExtPalette(softwareRenderer, false, true, 1);
311		_regenerateExtPalette(softwareRenderer, false, true, 2);
312		_regenerateExtPalette(softwareRenderer, false, true, 3);
313		_regenerateExtPalette(softwareRenderer, true, true, 0);
314		break;
315	case DS9_REG_A_DISPCNT_LO:
316		softwareRenderer->dispcntA &= 0xFFFF0000;
317		softwareRenderer->dispcntA |= value;
318		DSVideoSoftwareRendererUpdateDISPCNT(softwareRenderer, false);
319		break;
320	case DS9_REG_A_DISPCNT_HI:
321		softwareRenderer->dispcntA &= 0x0000FFFF;
322		softwareRenderer->dispcntA |= value << 16;
323		DSVideoSoftwareRendererUpdateDISPCNT(softwareRenderer, false);
324		break;
325	case DS9_REG_B_DISPCNT_LO:
326		softwareRenderer->dispcntB &= 0xFFFF0000;
327		softwareRenderer->dispcntB |= value;
328		DSVideoSoftwareRendererUpdateDISPCNT(softwareRenderer, true);
329		break;
330	case DS9_REG_B_DISPCNT_HI:
331		softwareRenderer->dispcntB &= 0x0000FFFF;
332		softwareRenderer->dispcntB |= value << 16;
333		DSVideoSoftwareRendererUpdateDISPCNT(softwareRenderer, true);
334		break;
335	case DS9_REG_POWCNT1:
336		value &= 0x810F;
337		softwareRenderer->powcnt = value;
338	}
339	return value;
340}
341
342static void DSVideoSoftwareRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
343	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
344	if (address < 0x400) {
345		softwareRenderer->engA.d.writePalette(&softwareRenderer->engA.d, address & 0x3FF, value);
346	} else {
347		softwareRenderer->engB.d.writePalette(&softwareRenderer->engB.d, address & 0x3FF, value);
348	}
349}
350
351static void DSVideoSoftwareRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam) {
352	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
353	if (oam < 0x200) {
354		softwareRenderer->engA.d.writeOAM(&softwareRenderer->engA.d, oam & 0x1FF);
355	} else {
356		softwareRenderer->engB.d.writeOAM(&softwareRenderer->engB.d, oam & 0x1FF);
357	}
358}
359
360static void DSVideoSoftwareRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot) {
361	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
362	_regenerateExtPalette(softwareRenderer, obj, engB, slot);
363}
364
365static void DSVideoSoftwareRendererDrawGBAScanline(struct GBAVideoRenderer* renderer, struct DSGX* gx, int y) {
366	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
367
368	int x;
369	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
370	if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) {
371		for (x = 0; x < softwareRenderer->masterEnd; ++x) {
372			row[x] = GBA_COLOR_WHITE;
373		}
374		return;
375	}
376
377	GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y);
378	int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y);
379	memset(softwareRenderer->alphaA, softwareRenderer->blda, sizeof(softwareRenderer->alphaA));
380	memset(softwareRenderer->alphaB, softwareRenderer->bldb, sizeof(softwareRenderer->alphaB));
381
382	softwareRenderer->bg[2].sx = softwareRenderer->bg[2].dmx * y;
383	softwareRenderer->bg[2].sy = softwareRenderer->bg[2].dmy * y;
384	softwareRenderer->bg[3].sx = softwareRenderer->bg[3].dmx * y;
385	softwareRenderer->bg[3].sy = softwareRenderer->bg[3].dmy * y;
386
387	int w;
388	unsigned priority;
389	for (priority = 0; priority < 4; ++priority) {
390		softwareRenderer->end = 0;
391		for (w = 0; w < softwareRenderer->nWindows; ++w) {
392			softwareRenderer->start = softwareRenderer->end;
393			softwareRenderer->end = softwareRenderer->windows[w].endX;
394			softwareRenderer->currentWindow = softwareRenderer->windows[w].control;
395			if (spriteLayers & (1 << priority)) {
396				GBAVideoSoftwareRendererPostprocessSprite(softwareRenderer, priority);
397			}
398			if (TEST_LAYER_ENABLED(0)) {
399				if (DSRegisterDISPCNTIs3D(softwareRenderer->dispcnt) && gx) {
400					const color_t* scanline;
401					gx->renderer->getScanline(gx->renderer, y, &scanline);
402					uint32_t flags = (softwareRenderer->bg[0].priority << OFFSET_PRIORITY) | FLAG_IS_BACKGROUND;
403					flags |= FLAG_TARGET_2 * softwareRenderer->bg[0].target2;
404					flags |= FLAG_TARGET_1 * (softwareRenderer->bg[0].target1 && softwareRenderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(softwareRenderer->currentWindow.packed));
405					int x;
406					for (x = softwareRenderer->start; x < softwareRenderer->end; ++x) {
407						color_t color = scanline[x];
408						if (color & 0xFC000000) {
409							if (softwareRenderer->bg[0].target1 && GBAWindowControlIsBlendEnable(softwareRenderer->currentWindow.packed)) {
410								if (softwareRenderer->blendEffect == BLEND_DARKEN) {
411									color = _darken(color, softwareRenderer->bldy) | (color & 0xFF000000);
412								} else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
413									color = _brighten(color, softwareRenderer->bldy) | (color & 0xFF000000);
414								}
415							}
416							if ((scanline[x] >> 28) != 0xF) {
417								// TODO: More precise values
418								softwareRenderer->alphaA[x] = (color >> 28) + 1;
419								softwareRenderer->alphaB[x] = 0xF - (color >> 28);
420								_compositeBlendNoObjwin(softwareRenderer, x, (color & 0x00FFFFFF) | flags | FLAG_TARGET_1, softwareRenderer->row[x]);
421							} else {
422								if (!(flags & FLAG_TARGET_2) || !(softwareRenderer->row[x] & FLAG_TARGET_1)) {
423									_compositeNoBlendNoObjwin(softwareRenderer, x, (color & 0x00FFFFFF) | flags, softwareRenderer->row[x]);
424									softwareRenderer->alphaA[x] = 0x10;
425									softwareRenderer->alphaB[x] = 0;
426								} else if (softwareRenderer->row[x] & FLAG_TARGET_1) {
427									_compositeBlendNoObjwin(softwareRenderer, x, (color & 0x00FFFFFF) | flags, softwareRenderer->row[x]);
428								}
429							}
430						}
431					}
432				} else {
433					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[0], y);
434				}
435			}
436			if (TEST_LAYER_ENABLED(1)) {
437				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[1], y);
438			}
439			if (TEST_LAYER_ENABLED(2)) {
440				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
441				case 0:
442				case 1:
443				case 3:
444					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[2], y);
445					break;
446				case 2:
447				case 4:
448					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[2], y);
449					break;
450				case 5:
451					if (!GBARegisterBGCNTIsExtendedMode1(softwareRenderer->bg[2].control)) {
452						DSVideoSoftwareRendererDrawBackgroundExt0(softwareRenderer, &softwareRenderer->bg[2], y);
453					} else if (!GBARegisterBGCNTIsExtendedMode0(softwareRenderer->bg[2].control)) {
454						DSVideoSoftwareRendererDrawBackgroundExt1(softwareRenderer, &softwareRenderer->bg[2], y);
455					} else {
456						DSVideoSoftwareRendererDrawBackgroundExt2(softwareRenderer, &softwareRenderer->bg[2], y);
457					}
458					break;
459				}
460			}
461			if (TEST_LAYER_ENABLED(3)) {
462				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
463				case 0:
464					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[3], y);
465					break;
466				case 1:
467				case 2:
468					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[3], y);
469					break;
470				case 3:
471				case 4:
472				case 5:
473					if (!GBARegisterBGCNTIsExtendedMode1(softwareRenderer->bg[3].control)) {
474						DSVideoSoftwareRendererDrawBackgroundExt0(softwareRenderer, &softwareRenderer->bg[3], y);
475					} else if (!GBARegisterBGCNTIsExtendedMode0(softwareRenderer->bg[3].control)) {
476						DSVideoSoftwareRendererDrawBackgroundExt1(softwareRenderer, &softwareRenderer->bg[3], y);
477					} else {
478						DSVideoSoftwareRendererDrawBackgroundExt2(softwareRenderer, &softwareRenderer->bg[3], y);
479					}
480					break;
481				}
482			}
483		}
484	}
485
486	GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer);
487}
488
489static void _drawScanlineA(struct DSVideoSoftwareRenderer* softwareRenderer, int y) {
490	memcpy(softwareRenderer->engA.d.vramBG, softwareRenderer->d.vramABG, sizeof(softwareRenderer->engA.d.vramBG));
491	memcpy(softwareRenderer->engA.d.vramOBJ, softwareRenderer->d.vramAOBJ, sizeof(softwareRenderer->engA.d.vramOBJ));
492	color_t* row = &softwareRenderer->engA.outputBuffer[softwareRenderer->outputBufferStride * y];
493
494	int x;
495	switch (DSRegisterDISPCNTGetDispMode(softwareRenderer->dispcntA)) {
496	case 0:
497		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
498			row[x] = GBA_COLOR_WHITE;
499		}
500		return;
501	case 1:
502		DSVideoSoftwareRendererDrawGBAScanline(&softwareRenderer->engA.d, softwareRenderer->d.gx, y);
503		break;
504	case 2: {
505		uint16_t* vram = &softwareRenderer->d.vram[0x10000 * DSRegisterDISPCNTGetVRAMBlock(softwareRenderer->dispcntA)];
506		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
507			color_t color;
508			LOAD_16(color, (x + y * DS_VIDEO_HORIZONTAL_PIXELS) * 2, vram);
509#ifndef COLOR_16_BIT
510			unsigned color32 = 0;
511			color32 |= (color << 9) & 0xF80000;
512			color32 |= (color << 3) & 0xF8;
513			color32 |= (color << 6) & 0xF800;
514			color32 |= (color32 >> 5) & 0x070707;
515			color = color32;
516#elif COLOR_5_6_5
517			uint16_t color16 = 0;
518			color16 |= (color & 0x001F) << 11;
519			color16 |= (color & 0x03E0) << 1;
520			color16 |= (color & 0x7C00) >> 10;
521			color = color16;
522#endif
523			softwareRenderer->engA.row[x] = color;
524		}
525		break;
526	}
527	case 3:
528		break;
529	}
530
531#ifdef COLOR_16_BIT
532#if defined(__ARM_NEON) && !defined(__APPLE__)
533	_to16Bit(row, softwareRenderer->engA.row, DS_VIDEO_HORIZONTAL_PIXELS);
534#else
535	for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
536		row[x] = softwareRenderer->engA.row[x];
537	}
538#endif
539#else
540	switch (softwareRenderer->engA.masterBright) {
541	case 0:
542	default:
543		memcpy(row, softwareRenderer->engA.row, softwareRenderer->engA.masterEnd * sizeof(*row));
544		break;
545	case 1:
546		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
547			row[x] = _brighten(softwareRenderer->engA.row[x], softwareRenderer->engA.masterBrightY);
548		}
549		break;
550	case 2:
551		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
552			row[x] = _darken(softwareRenderer->engA.row[x], softwareRenderer->engA.masterBrightY);
553		}
554		break;
555	}
556#endif
557}
558
559static void _drawScanlineB(struct DSVideoSoftwareRenderer* softwareRenderer, int y) {
560	memcpy(softwareRenderer->engB.d.vramBG, softwareRenderer->d.vramBBG, sizeof(softwareRenderer->engB.d.vramBG));
561	memcpy(softwareRenderer->engB.d.vramOBJ, softwareRenderer->d.vramBOBJ, sizeof(softwareRenderer->engB.d.vramOBJ));
562	color_t* row = &softwareRenderer->engB.outputBuffer[softwareRenderer->outputBufferStride * y];
563
564	int x;
565	switch (DSRegisterDISPCNTGetDispMode(softwareRenderer->dispcntB)) {
566	case 0:
567		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
568			row[x] = GBA_COLOR_WHITE;
569		}
570		return;
571	case 1:
572		DSVideoSoftwareRendererDrawGBAScanline(&softwareRenderer->engB.d, NULL, y);
573		break;
574	}
575
576#ifdef COLOR_16_BIT
577#if defined(__ARM_NEON) && !defined(__APPLE__)
578	_to16Bit(row, softwareRenderer->engB.row, DS_VIDEO_HORIZONTAL_PIXELS);
579#else
580	for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
581		row[x] = softwareRenderer->engB.row[x];
582	}
583#endif
584#else
585	switch (softwareRenderer->engB.masterBright) {
586	case 0:
587	default:
588		memcpy(row, softwareRenderer->engB.row, softwareRenderer->engB.masterEnd * sizeof(*row));
589		break;
590	case 1:
591		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
592			row[x] = _brighten(softwareRenderer->engB.row[x], softwareRenderer->engB.masterBrightY);
593		}
594		break;
595	case 2:
596		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
597			row[x] = _darken(softwareRenderer->engB.row[x], softwareRenderer->engB.masterBrightY);
598		}
599		break;
600	}
601#endif
602}
603
604static void DSVideoSoftwareRendererDrawScanline(struct DSVideoRenderer* renderer, int y) {
605	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
606	if (!DSRegisterPOWCNT1IsSwap(softwareRenderer->powcnt)) {
607		softwareRenderer->engA.outputBuffer = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * DS_VIDEO_VERTICAL_PIXELS];
608		softwareRenderer->engB.outputBuffer = softwareRenderer->outputBuffer;
609	} else {
610		softwareRenderer->engA.outputBuffer = softwareRenderer->outputBuffer;
611		softwareRenderer->engB.outputBuffer = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * DS_VIDEO_VERTICAL_PIXELS];
612	}
613
614	_drawScanlineA(softwareRenderer, y);
615	_drawScanlineB(softwareRenderer, y);
616}
617
618static void DSVideoSoftwareRendererDrawScanlineDirectly(struct DSVideoRenderer* renderer, int y, color_t* scanline) {
619	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
620	DSVideoSoftwareRendererDrawGBAScanline(&softwareRenderer->engA.d, softwareRenderer->d.gx, y);
621	memcpy(scanline, softwareRenderer->engA.row, softwareRenderer->engA.masterEnd * sizeof(*scanline));
622}
623
624static void DSVideoSoftwareRendererFinishFrame(struct DSVideoRenderer* renderer) {
625	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
626	softwareRenderer->engA.d.finishFrame(&softwareRenderer->engA.d);
627	softwareRenderer->engB.d.finishFrame(&softwareRenderer->engB.d);
628}
629
630static void DSVideoSoftwareRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels) {
631	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
632#ifdef COLOR_16_BIT
633#error Not yet supported
634#else
635	*stride = softwareRenderer->outputBufferStride;
636	*pixels = softwareRenderer->outputBuffer;
637#endif
638}
639
640static void DSVideoSoftwareRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels) {
641}
642
643#define EXT_0_COORD_OVERFLOW \
644	localX = x & (sizeAdjusted - 1); \
645	localY = y & (sizeAdjusted - 1); \
646
647#define EXT_0_COORD_NO_OVERFLOW \
648	if ((x | y) & ~(sizeAdjusted - 1)) { \
649		continue; \
650	} \
651	localX = x; \
652	localY = y;
653
654#define EXT_0_EXT_PAL \
655	paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \
656	palette = &mainPalette[paletteData];
657
658#define EXT_0_PAL palette = mainPalette;
659
660#define EXT_0_NO_MOSAIC(COORD, PAL) \
661	COORD \
662	uint32_t screenBase = background->screenBase + (localX >> 10) + (((localY >> 6) & 0xFE0) << background->size); \
663	uint16_t* screenBlock = renderer->d.vramBG[screenBase >> VRAM_BLOCK_OFFSET]; \
664	if (UNLIKELY(!screenBlock)) { \
665		continue; \
666	} \
667	LOAD_16(mapData, screenBase & (VRAM_BLOCK_MASK - 1), screenBlock); \
668	PAL; \
669	if (GBA_TEXT_MAP_VFLIP(mapData)) { \
670		localY = 0x7FF - localY; \
671	} \
672	if (GBA_TEXT_MAP_HFLIP(mapData)) { \
673		localX = 0x7FF - localX; \
674	} \
675	uint32_t charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8); \
676	uint16_t* vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \
677	if (UNLIKELY(!vram)) { \
678		continue; \
679	} \
680	pixelData = ((uint8_t*) vram)[charBase & VRAM_BLOCK_MASK];
681
682#define EXT_0_MOSAIC(COORD, PAL) \
683		if (!mosaicWait) { \
684			EXT_0_NO_MOSAIC(COORD, PAL) \
685			mosaicWait = mosaicH; \
686		} else { \
687			--mosaicWait; \
688		}
689
690#define EXT_0_LOOP(MOSAIC, COORD, PAL, BLEND, OBJWIN) \
691	for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { \
692		x += background->dx; \
693		y += background->dy; \
694		\
695		uint32_t current = *pixel; \
696		MOSAIC(COORD, PAL) \
697		if (pixelData) { \
698			COMPOSITE_256_ ## OBJWIN (BLEND, 0); \
699		} \
700	}
701
702#define DRAW_BACKGROUND_EXT_0(BLEND, OBJWIN) \
703	if (background->extPalette) { \
704		if (background->overflow) { \
705			if (mosaicH > 1) { \
706				EXT_0_LOOP(EXT_0_MOSAIC, EXT_0_COORD_OVERFLOW, EXT_0_EXT_PAL, BLEND, OBJWIN); \
707			} else { \
708				EXT_0_LOOP(EXT_0_NO_MOSAIC, EXT_0_COORD_OVERFLOW, EXT_0_EXT_PAL, BLEND, OBJWIN); \
709			} \
710		} else { \
711			if (mosaicH > 1) { \
712				EXT_0_LOOP(EXT_0_MOSAIC, EXT_0_COORD_NO_OVERFLOW, EXT_0_EXT_PAL, BLEND, OBJWIN); \
713			} else { \
714				EXT_0_LOOP(EXT_0_NO_MOSAIC, EXT_0_COORD_NO_OVERFLOW, EXT_0_EXT_PAL, BLEND, OBJWIN); \
715			} \
716		} \
717	} else { \
718		if (background->overflow) { \
719			if (mosaicH > 1) { \
720				EXT_0_LOOP(EXT_0_MOSAIC, EXT_0_COORD_OVERFLOW, EXT_0_PAL, BLEND, OBJWIN); \
721			} else { \
722				EXT_0_LOOP(EXT_0_NO_MOSAIC, EXT_0_COORD_OVERFLOW, EXT_0_PAL, BLEND, OBJWIN); \
723			} \
724		} else { \
725			if (mosaicH > 1) { \
726				EXT_0_LOOP(EXT_0_MOSAIC, EXT_0_COORD_NO_OVERFLOW, EXT_0_PAL, BLEND, OBJWIN); \
727			} else { \
728				EXT_0_LOOP(EXT_0_NO_MOSAIC, EXT_0_COORD_NO_OVERFLOW, EXT_0_PAL, BLEND, OBJWIN); \
729			} \
730		} \
731	}
732
733void DSVideoSoftwareRendererDrawBackgroundExt0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
734	int sizeAdjusted = 0x8000 << background->size;
735
736	BACKGROUND_BITMAP_INIT;
737
738	color_t* mainPalette = background->extPalette;
739	if (variant) {
740		mainPalette = background->variantPalette;
741	}
742	if (!mainPalette) {
743		mainPalette = renderer->normalPalette;
744		if (variant) {
745			mainPalette = renderer->variantPalette;
746		}
747	}
748	int paletteData;
749
750	uint16_t mapData;
751	uint8_t pixelData = 0;
752
753	int outX;
754	uint32_t* pixel;
755
756	if (!objwinSlowPath) {
757		if (!(flags & FLAG_TARGET_2)) {
758			DRAW_BACKGROUND_EXT_0(NoBlend, NO_OBJWIN);
759		} else {
760			DRAW_BACKGROUND_EXT_0(Blend, NO_OBJWIN);
761		}
762	} else {
763		if (!(flags & FLAG_TARGET_2)) {
764			DRAW_BACKGROUND_EXT_0(NoBlend, OBJWIN);
765		} else {
766			DRAW_BACKGROUND_EXT_0(Blend, OBJWIN);
767		}
768	}
769}
770
771#define DS_BACKGROUND_BITMAP_ITERATE(W, H)                         \
772	x += background->dx;                                           \
773	y += background->dy;                                           \
774                                                                   \
775	if (background->overflow) {                                    \
776		localX = x & ((W << 8) - 1);                               \
777		localY = y & ((H << 8) - 1);                               \
778	} else if (x < 0 || y < 0 || (x >> 8) >= W || (y >> 8) >= H) { \
779		continue;                                                  \
780	} else {                                                       \
781		localX = x;                                                \
782		localY = y;                                                \
783	}
784
785void DSVideoSoftwareRendererDrawBackgroundExt1(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
786	BACKGROUND_BITMAP_INIT;
787
788	uint32_t screenBase = (background->screenBase & 0xFF00) * 8;
789	uint8_t color;
790	int width, height;
791	switch (background->size) {
792	case 0:
793		width = 128;
794		height = 128;
795		break;
796	case 1:
797		width = 256;
798		height = 256;
799		break;
800	case 2:
801		width = 512;
802		height = 256;
803		break;
804	case 3:
805		width = 512;
806		height = 512;
807		break;
808	}
809
810	int outX;
811	for (outX = renderer->start; outX < renderer->end; ++outX) {
812		DS_BACKGROUND_BITMAP_ITERATE(width, height);
813
814		if (!mosaicWait) {
815			uint32_t address = (localX >> 8) + (localY >> 8) * width + screenBase;
816			uint8_t* vram = (uint8_t*) renderer->d.vramBG[address >> VRAM_BLOCK_OFFSET];
817			if (UNLIKELY(!vram)) {
818				continue;
819			}
820			color = vram[address & VRAM_BLOCK_MASK];
821			mosaicWait = mosaicH;
822		} else {
823			--mosaicWait;
824		}
825
826		uint32_t current = renderer->row[outX];
827		if (color && IS_WRITABLE(current)) {
828			if (!objwinSlowPath) {
829				_compositeBlendNoObjwin(renderer, outX, palette[color] | flags, current);
830			} else if (objwinForceEnable || (!(current & FLAG_OBJWIN)) == objwinOnly) {
831				color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette;
832				unsigned mergedFlags = flags;
833				if (current & FLAG_OBJWIN) {
834					mergedFlags = objwinFlags;
835				}
836				_compositeBlendObjwin(renderer, outX, currentPalette[color] | mergedFlags, current);
837			}
838		}
839	}
840}
841
842void DSVideoSoftwareRendererDrawBackgroundExt2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
843	BACKGROUND_BITMAP_INIT;
844
845	uint32_t screenBase = (background->screenBase & 0xFF00) * 4;
846	uint32_t color;
847	int width, height;
848	switch (background->size) {
849	case 0:
850		width = 128;
851		height = 128;
852		break;
853	case 1:
854		width = 256;
855		height = 256;
856		break;
857	case 2:
858		width = 512;
859		height = 256;
860		break;
861	case 3:
862		width = 512;
863		height = 512;
864		break;
865	}
866
867	int outX;
868	for (outX = renderer->start; outX < renderer->end; ++outX) {
869		DS_BACKGROUND_BITMAP_ITERATE(width, height);
870
871		if (!mosaicWait) {
872			uint32_t address = ((localX >> 8) + (localY >> 8) * width + screenBase) << 1;
873			uint16_t* vram = renderer->d.vramBG[address >> VRAM_BLOCK_OFFSET];
874			if (UNLIKELY(!vram)) {
875				continue;
876			}
877			LOAD_16(color, address & VRAM_BLOCK_MASK, vram);
878#ifndef COLOR_16_BIT
879			unsigned color32;
880			color32 = 0;
881			color32 |= (color << 3) & 0xF8;
882			color32 |= (color << 6) & 0xF800;
883			color32 |= (color << 9) & 0xF80000;
884			color32 |= (color32 >> 5) & 0x070707;
885			color = color32;
886#elif COLOR_5_6_5
887			uint16_t color16 = 0;
888			color16 |= (color & 0x001F) << 11;
889			color16 |= (color & 0x03E0) << 1;
890			color16 |= (color & 0x7C00) >> 10;
891			color = color16;
892#endif
893			mosaicWait = mosaicH;
894		} else {
895			--mosaicWait;
896		}
897
898		uint32_t current = renderer->row[outX];
899		if (!objwinSlowPath || (!(current & FLAG_OBJWIN)) != objwinOnly) {
900			unsigned mergedFlags = flags;
901			if (current & FLAG_OBJWIN) {
902				mergedFlags = objwinFlags;
903			}
904			if (!variant) {
905				_compositeBlendObjwin(renderer, outX, color | mergedFlags, current);
906			} else if (renderer->blendEffect == BLEND_BRIGHTEN) {
907				_compositeBlendObjwin(renderer, outX, _brighten(color, renderer->bldy) | mergedFlags, current);
908			} else if (renderer->blendEffect == BLEND_DARKEN) {
909				_compositeBlendObjwin(renderer, outX, _darken(color, renderer->bldy) | mergedFlags, current);
910			}
911		}
912	}
913}