all repos — mgba @ e4795c5d86c934cb038c076b1bc456109e510fca

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/io.h>
 11
 12static void DSVideoSoftwareRendererInit(struct DSVideoRenderer* renderer);
 13static void DSVideoSoftwareRendererDeinit(struct DSVideoRenderer* renderer);
 14static void DSVideoSoftwareRendererReset(struct DSVideoRenderer* renderer);
 15static uint16_t DSVideoSoftwareRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value);
 16static void DSVideoSoftwareRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value);
 17static void DSVideoSoftwareRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam);
 18static void DSVideoSoftwareRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot);
 19static void DSVideoSoftwareRendererDrawScanline(struct DSVideoRenderer* renderer, int y);
 20static void DSVideoSoftwareRendererFinishFrame(struct DSVideoRenderer* renderer);
 21static void DSVideoSoftwareRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels);
 22static void DSVideoSoftwareRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels);
 23
 24static bool _regenerateExtPalette(struct DSVideoSoftwareRenderer* renderer, bool obj, bool engB, int slot) {
 25	color_t* palette;
 26	color_t* variantPalette;
 27	struct GBAVideoSoftwareRenderer* softwareRenderer;
 28	uint16_t* vram;
 29	if (!obj) {
 30		if (!engB) {
 31			palette = &renderer->extPaletteA[slot * 4096];
 32			variantPalette = &renderer->variantPaletteA[slot * 4096];
 33			softwareRenderer = &renderer->engA;
 34			vram = renderer->d.vramABGExtPal[slot];
 35		} else {
 36			palette = &renderer->extPaletteB[slot * 4096];
 37			variantPalette = &renderer->variantPaletteB[slot * 4096];
 38			softwareRenderer = &renderer->engB;
 39			vram = renderer->d.vramBBGExtPal[slot];
 40		}
 41	} else {
 42		if (!engB) {
 43			palette = renderer->objExtPaletteA;
 44			variantPalette = renderer->variantPaletteA;
 45			softwareRenderer = &renderer->engA;
 46			vram = renderer->d.vramAOBJExtPal;
 47		} else {
 48			palette = renderer->objExtPaletteB;
 49			variantPalette = renderer->variantPaletteB;
 50			softwareRenderer = &renderer->engB;
 51			vram = renderer->d.vramBOBJExtPal;
 52		}
 53	}
 54	if (!vram) {
 55		return false;
 56	}
 57	int i;
 58	for (i = 0; i < 4096; ++i) {
 59		uint16_t value = vram[i];
 60#ifdef COLOR_16_BIT
 61#ifdef COLOR_5_6_5
 62		unsigned color = 0;
 63		color |= (value & 0x001F) << 11;
 64		color |= (value & 0x03E0) << 1;
 65		color |= (value & 0x7C00) >> 10;
 66#else
 67		unsigned color = value;
 68#endif
 69#else
 70		unsigned color = 0;
 71		color |= (value << 3) & 0xF8;
 72		color |= (value << 6) & 0xF800;
 73		color |= (value << 9) & 0xF80000;
 74		color |= (color >> 5) & 0x070707;
 75#endif
 76		palette[i] = color;
 77		if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
 78			variantPalette[i] = _brighten(color, softwareRenderer->bldy);
 79		} else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
 80			variantPalette[i] = _darken(color, softwareRenderer->bldy);
 81		}
 82	}
 83	return true;
 84}
 85
 86
 87void DSVideoSoftwareRendererCreate(struct DSVideoSoftwareRenderer* renderer) {
 88	renderer->d.init = DSVideoSoftwareRendererInit;
 89	renderer->d.reset = DSVideoSoftwareRendererReset;
 90	renderer->d.deinit = DSVideoSoftwareRendererDeinit;
 91	renderer->d.writeVideoRegister = DSVideoSoftwareRendererWriteVideoRegister;
 92	renderer->d.writePalette = DSVideoSoftwareRendererWritePalette;
 93	renderer->d.writeOAM = DSVideoSoftwareRendererWriteOAM;
 94	renderer->d.invalidateExtPal = DSVideoSoftwareRendererInvalidateExtPal;
 95	renderer->d.drawScanline = DSVideoSoftwareRendererDrawScanline;
 96	renderer->d.finishFrame = DSVideoSoftwareRendererFinishFrame;
 97	renderer->d.getPixels = DSVideoSoftwareRendererGetPixels;
 98	renderer->d.putPixels = DSVideoSoftwareRendererPutPixels;
 99
100	renderer->engA.d.cache = NULL;
101	GBAVideoSoftwareRendererCreate(&renderer->engA);
102	renderer->engB.d.cache = NULL;
103	GBAVideoSoftwareRendererCreate(&renderer->engB);
104}
105
106static void DSVideoSoftwareRendererInit(struct DSVideoRenderer* renderer) {
107	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
108	softwareRenderer->engA.d.palette = &renderer->palette[0];
109	softwareRenderer->engA.d.oam = &renderer->oam->oam[0];
110	softwareRenderer->engA.masterEnd = DS_VIDEO_HORIZONTAL_PIXELS;
111	softwareRenderer->engA.masterHeight = DS_VIDEO_VERTICAL_PIXELS;
112	softwareRenderer->engA.masterScanlines = DS_VIDEO_VERTICAL_TOTAL_PIXELS;
113	softwareRenderer->engA.outputBufferStride = softwareRenderer->outputBufferStride;
114	softwareRenderer->engB.d.palette = &renderer->palette[512];
115	softwareRenderer->engB.d.oam = &renderer->oam->oam[1];
116	softwareRenderer->engB.masterEnd = DS_VIDEO_HORIZONTAL_PIXELS;
117	softwareRenderer->engB.masterHeight = DS_VIDEO_VERTICAL_PIXELS;
118	softwareRenderer->engB.masterScanlines = DS_VIDEO_VERTICAL_TOTAL_PIXELS;
119	softwareRenderer->engB.outputBufferStride = softwareRenderer->outputBufferStride;
120
121	DSVideoSoftwareRendererReset(renderer);
122}
123
124static void DSVideoSoftwareRendererReset(struct DSVideoRenderer* renderer) {
125	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
126	softwareRenderer->engA.d.reset(&softwareRenderer->engA.d);
127	softwareRenderer->engB.d.reset(&softwareRenderer->engB.d);
128	softwareRenderer->powcnt = 0;
129	softwareRenderer->dispcntA = 0;
130	softwareRenderer->dispcntB = 0;
131}
132
133static void DSVideoSoftwareRendererDeinit(struct DSVideoRenderer* renderer) {
134	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
135	softwareRenderer->engA.d.deinit(&softwareRenderer->engA.d);
136	softwareRenderer->engB.d.deinit(&softwareRenderer->engB.d);
137}
138
139static void DSVideoSoftwareRendererUpdateDISPCNT(struct DSVideoSoftwareRenderer* softwareRenderer, bool engB) {
140	uint32_t dispcnt;
141	struct GBAVideoSoftwareRenderer* eng;
142	if (!engB) {
143		dispcnt = softwareRenderer->dispcntA;
144		eng = &softwareRenderer->engA;
145	} else {
146		dispcnt = softwareRenderer->dispcntB;
147		eng = &softwareRenderer->engB;
148	}
149	uint16_t fakeDispcnt = dispcnt & 0xFF87;
150	if (!DSRegisterDISPCNTIsTileObjMapping(dispcnt)) {
151		eng->tileStride = 0x20;
152	} else {
153		eng->tileStride = 0x20 << DSRegisterDISPCNTGetTileBoundary(dispcnt);
154		fakeDispcnt = GBARegisterDISPCNTFillObjCharacterMapping(fakeDispcnt);
155	}
156	eng->d.writeVideoRegister(&eng->d, DS9_REG_A_DISPCNT_LO, fakeDispcnt);
157	eng->dispcnt |= dispcnt & 0xFFFF0000;
158	if (DSRegisterDISPCNTIsBgExtPalette(dispcnt)) {
159		color_t* extPalette;
160		if (!engB) {
161			extPalette = softwareRenderer->extPaletteA;
162		} else {
163			extPalette = softwareRenderer->extPaletteB;
164		}
165		int i;
166		for (i = 0; i < 4; ++i) {
167			int slot = i;
168			if (i < 2 && GBARegisterBGCNTIsExtPaletteSlot(eng->bg[i].control)) {
169				slot += 2;
170			}
171			if (eng->bg[i].extPalette != &extPalette[slot * 4096] && _regenerateExtPalette(softwareRenderer, false, engB, slot)) {
172				eng->bg[i].extPalette = &extPalette[slot * 4096];
173			}
174		}
175	} else {
176		eng->bg[0].extPalette = NULL;
177		eng->bg[1].extPalette = NULL;
178		eng->bg[2].extPalette = NULL;
179		eng->bg[3].extPalette = NULL;
180	}
181	if (DSRegisterDISPCNTIsObjExtPalette(dispcnt)) {
182		if (!engB) {
183			softwareRenderer->engA.objExtPalette = softwareRenderer->objExtPaletteA;
184		} else {
185			softwareRenderer->engB.objExtPalette = softwareRenderer->objExtPaletteB;
186		}
187	} else {
188		if (!engB) {
189			softwareRenderer->engA.objExtPalette = NULL;
190		} else {
191			softwareRenderer->engB.objExtPalette = NULL;
192		}
193	}
194	if (!engB) {
195		uint32_t charBase = DSRegisterDISPCNTGetCharBase(softwareRenderer->dispcntA) << 16;
196		uint32_t screenBase = DSRegisterDISPCNTGetScreenBase(softwareRenderer->dispcntA) << 16;
197		softwareRenderer->engA.d.writeVideoRegister(&softwareRenderer->engA.d, DS9_REG_A_BG0CNT, softwareRenderer->engA.bg[0].control);
198		softwareRenderer->engA.bg[0].charBase += charBase;
199		softwareRenderer->engA.bg[0].screenBase &= ~0x70000;
200		softwareRenderer->engA.bg[0].screenBase |= screenBase;
201		softwareRenderer->engA.d.writeVideoRegister(&softwareRenderer->engA.d, DS9_REG_A_BG1CNT, softwareRenderer->engA.bg[1].control);
202		softwareRenderer->engA.bg[1].charBase += charBase;
203		softwareRenderer->engA.bg[1].screenBase &= ~0x70000;
204		softwareRenderer->engA.bg[1].screenBase |= screenBase;
205		softwareRenderer->engA.d.writeVideoRegister(&softwareRenderer->engA.d, DS9_REG_A_BG2CNT, softwareRenderer->engA.bg[2].control);
206		softwareRenderer->engA.bg[2].charBase += charBase;
207		softwareRenderer->engA.bg[2].screenBase &= ~0x70000;
208		softwareRenderer->engA.bg[2].screenBase |= screenBase;
209		softwareRenderer->engA.d.writeVideoRegister(&softwareRenderer->engA.d, DS9_REG_A_BG3CNT, softwareRenderer->engA.bg[3].control);
210		softwareRenderer->engA.bg[3].charBase += charBase;
211		softwareRenderer->engA.bg[3].screenBase &= ~0x70000;
212		softwareRenderer->engA.bg[3].screenBase |= screenBase;
213	}
214}
215
216static uint16_t DSVideoSoftwareRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
217	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
218	if (address >= DS9_REG_A_BG0CNT && address <= DS9_REG_A_BLDY) {
219		softwareRenderer->engA.d.writeVideoRegister(&softwareRenderer->engA.d, address, value);
220	} else if (address >= DS9_REG_B_BG0CNT && address <= DS9_REG_B_BLDY) {
221		softwareRenderer->engB.d.writeVideoRegister(&softwareRenderer->engB.d, address & 0xFF, value);
222	} else {
223		mLOG(DS_VIDEO, STUB, "Stub video register write: %04X:%04X", address, value);
224	}
225	switch (address) {
226	case DS9_REG_A_BG0CNT:
227	case DS9_REG_A_BG1CNT:
228		softwareRenderer->engA.bg[(address - DS9_REG_A_BG0CNT) >> 1].control = value;
229		break;
230	case DS9_REG_B_BG0CNT:
231	case DS9_REG_B_BG1CNT:
232		softwareRenderer->engB.bg[(address - DS9_REG_A_BG0CNT) >> 1].control = value;
233		break;
234	case DS9_REG_A_DISPCNT_LO:
235		softwareRenderer->dispcntA &= 0xFFFF0000;
236		softwareRenderer->dispcntA |= value;
237		DSVideoSoftwareRendererUpdateDISPCNT(softwareRenderer, false);
238		break;
239	case DS9_REG_A_DISPCNT_HI:
240		softwareRenderer->dispcntA &= 0x0000FFFF;
241		softwareRenderer->dispcntA |= value << 16;
242		DSVideoSoftwareRendererUpdateDISPCNT(softwareRenderer, false);
243		break;
244	case DS9_REG_B_DISPCNT_LO:
245		softwareRenderer->dispcntB &= 0xFFFF0000;
246		softwareRenderer->dispcntB |= value;
247		DSVideoSoftwareRendererUpdateDISPCNT(softwareRenderer, true);
248		break;
249	case DS9_REG_B_DISPCNT_HI:
250		softwareRenderer->dispcntB &= 0x0000FFFF;
251		softwareRenderer->dispcntB |= value << 16;
252		DSVideoSoftwareRendererUpdateDISPCNT(softwareRenderer, true);
253		break;
254	case DS9_REG_POWCNT1:
255		value &= 0x810F;
256		softwareRenderer->powcnt = value;
257	}
258	return value;
259}
260
261static void DSVideoSoftwareRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) {
262	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
263	if (address < 0x400) {
264		softwareRenderer->engA.d.writePalette(&softwareRenderer->engA.d, address & 0x3FF, value);
265	} else {
266		softwareRenderer->engB.d.writePalette(&softwareRenderer->engB.d, address & 0x3FF, value);
267	}
268}
269
270static void DSVideoSoftwareRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam) {
271	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
272	if (oam < 0x200) {
273		softwareRenderer->engA.d.writeOAM(&softwareRenderer->engA.d, oam & 0x1FF);
274	} else {
275		softwareRenderer->engB.d.writeOAM(&softwareRenderer->engB.d, oam & 0x1FF);
276	}
277}
278
279static void DSVideoSoftwareRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot) {
280	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
281	_regenerateExtPalette(softwareRenderer, obj, engB, slot);
282}
283
284static void DSVideoSoftwareRendererDrawGBAScanline(struct GBAVideoRenderer* renderer, int y) {
285	struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
286
287	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
288	if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) {
289		int x;
290		for (x = 0; x < softwareRenderer->masterEnd; ++x) {
291			row[x] = GBA_COLOR_WHITE;
292		}
293		return;
294	}
295
296	GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y);
297	int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y);
298
299	int w;
300	unsigned priority;
301	for (priority = 0; priority < 4; ++priority) {
302		softwareRenderer->end = 0;
303		for (w = 0; w < softwareRenderer->nWindows; ++w) {
304			softwareRenderer->start = softwareRenderer->end;
305			softwareRenderer->end = softwareRenderer->windows[w].endX;
306			softwareRenderer->currentWindow = softwareRenderer->windows[w].control;
307			if (spriteLayers & (1 << priority)) {
308				GBAVideoSoftwareRendererPostprocessSprite(softwareRenderer, priority);
309			}
310			if (TEST_LAYER_ENABLED(0)) {
311				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[0], y);
312			}
313			if (TEST_LAYER_ENABLED(1)) {
314				GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[1], y);
315			}
316			if (TEST_LAYER_ENABLED(2)) {
317				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
318				case 0:
319				case 1:
320				case 3:
321					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[2], y);
322					break;
323				case 2:
324				case 4:
325					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[2], y);
326					break;
327				}
328			}
329			if (TEST_LAYER_ENABLED(3)) {
330				switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) {
331				case 0:
332					GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[3], y);
333					break;
334				case 1:
335				case 2:
336					GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[3], y);
337					break;
338				}
339			}
340		}
341	}
342	softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx;
343	softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy;
344	softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx;
345	softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy;
346
347	GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer);
348
349#ifdef COLOR_16_BIT
350#if defined(__ARM_NEON) && !defined(__APPLE__)
351	_to16Bit(row, softwareRenderer->row, softwareRenderer->masterEnd);
352#else
353	for (x = 0; x < softwareRenderer->masterEnd; ++x) {
354		row[x] = softwareRenderer->row[x];
355	}
356#endif
357#else
358	memcpy(row, softwareRenderer->row, softwareRenderer->masterEnd * sizeof(*row));
359#endif
360}
361
362static void _drawScanlineA(struct DSVideoSoftwareRenderer* softwareRenderer, int y) {
363	memcpy(softwareRenderer->engA.d.vramBG, softwareRenderer->d.vramABG, sizeof(softwareRenderer->engA.d.vramBG));
364	memcpy(softwareRenderer->engA.d.vramOBJ, softwareRenderer->d.vramAOBJ, sizeof(softwareRenderer->engA.d.vramOBJ));
365	color_t* row = &softwareRenderer->engA.outputBuffer[softwareRenderer->outputBufferStride * y];
366
367	int x;
368	switch (DSRegisterDISPCNTGetDispMode(softwareRenderer->dispcntA)) {
369	case 0:
370		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
371			row[x] = GBA_COLOR_WHITE;
372		}
373		return;
374	case 1:
375		DSVideoSoftwareRendererDrawGBAScanline(&softwareRenderer->engA.d, y);
376		return;
377	case 2: {
378		uint16_t* vram = &softwareRenderer->d.vram[0x10000 * DSRegisterDISPCNTGetVRAMBlock(softwareRenderer->dispcntA)];
379		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
380			color_t color;
381			LOAD_16(color, (x + y * DS_VIDEO_HORIZONTAL_PIXELS) * 2, vram);
382#ifndef COLOR_16_BIT
383			unsigned color32 = 0;
384			color32 |= (color << 9) & 0xF80000;
385			color32 |= (color << 3) & 0xF8;
386			color32 |= (color << 6) & 0xF800;
387			color32 |= (color32 >> 5) & 0x070707;
388			color = color32;
389#elif COLOR_5_6_5
390			uint16_t color16 = 0;
391			color16 |= (color & 0x001F) << 11;
392			color16 |= (color & 0x03E0) << 1;
393			color16 |= (color & 0x7C00) >> 10;
394			color = color16;
395#endif
396			softwareRenderer->row[x] = color;
397		}
398		break;
399	}
400	case 3:
401		break;
402	}
403
404#ifdef COLOR_16_BIT
405#if defined(__ARM_NEON) && !defined(__APPLE__)
406	_to16Bit(row, softwareRenderer->row, DS_VIDEO_HORIZONTAL_PIXELS);
407#else
408	for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
409		row[x] = softwareRenderer->row[x];
410	}
411#endif
412#else
413	memcpy(row, softwareRenderer->row, DS_VIDEO_HORIZONTAL_PIXELS * sizeof(*row));
414#endif
415}
416
417static void _drawScanlineB(struct DSVideoSoftwareRenderer* softwareRenderer, int y) {
418	memcpy(softwareRenderer->engB.d.vramBG, softwareRenderer->d.vramBBG, sizeof(softwareRenderer->engB.d.vramBG));
419	memcpy(softwareRenderer->engB.d.vramOBJ, softwareRenderer->d.vramBOBJ, sizeof(softwareRenderer->engB.d.vramOBJ));
420	color_t* row = &softwareRenderer->engB.outputBuffer[softwareRenderer->outputBufferStride * y];
421
422	int x;
423	switch (DSRegisterDISPCNTGetDispMode(softwareRenderer->dispcntB)) {
424	case 0:
425		for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
426			row[x] = GBA_COLOR_WHITE;
427		}
428		return;
429	case 1:
430		DSVideoSoftwareRendererDrawGBAScanline(&softwareRenderer->engB.d, y);
431		return;
432	}
433
434#ifdef COLOR_16_BIT
435#if defined(__ARM_NEON) && !defined(__APPLE__)
436	_to16Bit(row, softwareRenderer->row, DS_VIDEO_HORIZONTAL_PIXELS);
437#else
438	for (x = 0; x < DS_VIDEO_HORIZONTAL_PIXELS; ++x) {
439		row[x] = softwareRenderer->row[x];
440	}
441#endif
442#else
443	memcpy(row, softwareRenderer->row, DS_VIDEO_HORIZONTAL_PIXELS * sizeof(*row));
444#endif
445}
446
447static void DSVideoSoftwareRendererDrawScanline(struct DSVideoRenderer* renderer, int y) {
448	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
449	if (!DSRegisterPOWCNT1IsSwap(softwareRenderer->powcnt)) {
450		softwareRenderer->engA.outputBuffer = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * DS_VIDEO_VERTICAL_PIXELS];
451		softwareRenderer->engB.outputBuffer = softwareRenderer->outputBuffer;
452	} else {
453		softwareRenderer->engA.outputBuffer = softwareRenderer->outputBuffer;
454		softwareRenderer->engB.outputBuffer = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * DS_VIDEO_VERTICAL_PIXELS];
455	}
456
457	_drawScanlineA(softwareRenderer, y);
458	_drawScanlineB(softwareRenderer, y);
459}
460
461static void DSVideoSoftwareRendererFinishFrame(struct DSVideoRenderer* renderer) {
462	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
463	softwareRenderer->engA.d.finishFrame(&softwareRenderer->engA.d);
464	softwareRenderer->engB.d.finishFrame(&softwareRenderer->engB.d);
465}
466
467static void DSVideoSoftwareRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels) {
468	struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer;
469#ifdef COLOR_16_BIT
470#error Not yet supported
471#else
472	*stride = softwareRenderer->outputBufferStride;
473	*pixels = softwareRenderer->outputBuffer;
474#endif
475}
476
477static void DSVideoSoftwareRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels) {
478}