all repos — mgba @ aac95687698b1912ba5ce8604db943c1d16d48e9

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