all repos — mgba @ e6d32707fc02b1aa376596b2ce5ed85ab974ff6e

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