all repos — mgba @ cf5d6709fed95b84a52cf172367df7b7dd84ed9f

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