all repos — mgba @ 2a7f642d4cba0e962520135a1dff47095dd78c75

mGBA Game Boy Advance Emulator

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

  1/* Copyright (c) 2013-2016 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/gb/renderers/software.h>
  7
  8#include <mgba/core/tile-cache.h>
  9#include <mgba/internal/gb/io.h>
 10#include <mgba-util/memory.h>
 11
 12static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model);
 13static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer);
 14static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value);
 15static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value);
 16static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address);
 17static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax);
 18static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y);
 19static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer);
 20static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels);
 21static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels);
 22
 23static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy);
 24static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y);
 25
 26static void _clearScreen(struct GBVideoSoftwareRenderer* renderer) {
 27	int y;
 28	for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) {
 29		color_t* row = &renderer->outputBuffer[renderer->outputBufferStride * y];
 30		int x;
 31		for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
 32			row[x + 0] = renderer->palette[0];
 33			row[x + 1] = renderer->palette[0];
 34			row[x + 2] = renderer->palette[0];
 35			row[x + 3] = renderer->palette[0];
 36		}
 37	}
 38}
 39
 40void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) {
 41	renderer->d.init = GBVideoSoftwareRendererInit;
 42	renderer->d.deinit = GBVideoSoftwareRendererDeinit;
 43	renderer->d.writeVideoRegister = GBVideoSoftwareRendererWriteVideoRegister;
 44	renderer->d.writePalette = GBVideoSoftwareRendererWritePalette;
 45	renderer->d.writeVRAM = GBVideoSoftwareRendererWriteVRAM;
 46	renderer->d.drawRange = GBVideoSoftwareRendererDrawRange;
 47	renderer->d.finishScanline = GBVideoSoftwareRendererFinishScanline;
 48	renderer->d.finishFrame = GBVideoSoftwareRendererFinishFrame;
 49	renderer->d.getPixels = GBVideoSoftwareRendererGetPixels;
 50	renderer->d.putPixels = GBVideoSoftwareRendererPutPixels;
 51
 52	renderer->temporaryBuffer = 0;
 53}
 54
 55static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) {
 56	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
 57	softwareRenderer->lcdc = 0;
 58	softwareRenderer->scy = 0;
 59	softwareRenderer->scx = 0;
 60	softwareRenderer->wy = 0;
 61	softwareRenderer->currentWy = 0;
 62	softwareRenderer->wx = 0;
 63	softwareRenderer->model = model;
 64
 65	_clearScreen(softwareRenderer);
 66}
 67
 68static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {
 69	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
 70	UNUSED(softwareRenderer);
 71}
 72
 73static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) {
 74	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
 75	switch (address) {
 76	case REG_LCDC:
 77		softwareRenderer->lcdc = value;
 78		break;
 79	case REG_SCY:
 80		softwareRenderer->scy = value;
 81		break;
 82	case REG_SCX:
 83		softwareRenderer->scx = value;
 84		break;
 85	case REG_WY:
 86		softwareRenderer->wy = value;
 87		break;
 88	case REG_WX:
 89		softwareRenderer->wx = value;
 90		break;
 91	}
 92	return value;
 93}
 94
 95static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) {
 96	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
 97#ifdef COLOR_16_BIT
 98#ifdef COLOR_5_6_5
 99	color_t color = 0;
100	color |= (value & 0x001F) << 11;
101	color |= (value & 0x03E0) << 1;
102	color |= (value & 0x7C00) >> 10;
103#else
104	color_t color = value;
105#endif
106#else
107	color_t color = 0;
108	color |= (value << 3) & 0xF8;
109	color |= (value << 6) & 0xF800;
110	color |= (value << 9) & 0xF80000;
111	color |= (color >> 5) & 0x070707;
112#endif
113	softwareRenderer->palette[index] = color;
114	if (renderer->cache) {
115		mTileCacheWritePalette(renderer->cache, index << 1);
116	}
117}
118
119static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) {
120	if (renderer->cache) {
121		mTileCacheWriteVRAM(renderer->cache, address);
122	}
123}
124
125static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax) {
126	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
127	uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP];
128	if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) {
129		maps += GB_SIZE_MAP;
130	}
131	if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) {
132		if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && endX >= softwareRenderer->wx - 7) {
133			if (softwareRenderer->wx - 7 > 0) {
134				GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, softwareRenderer->scx, softwareRenderer->scy + y);
135			}
136
137			maps = &softwareRenderer->d.vram[GB_BASE_MAP];
138			if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) {
139				maps += GB_SIZE_MAP;
140			}
141			GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, softwareRenderer->currentWy);
142		} else {
143			GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx, softwareRenderer->scy + y);
144		}
145	} else {
146		memset(&softwareRenderer->row[startX], 0, endX - startX);
147	}
148
149	if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc)) {
150		size_t i;
151		for (i = 0; i < oamMax; ++i) {
152			GBVideoSoftwareRendererDrawObj(softwareRenderer, &obj[i], startX, endX, y);
153		}
154	}
155	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
156	int x;
157	for (x = startX; x + 7 < (endX & ~7); x += 8) {
158		row[x] = softwareRenderer->palette[softwareRenderer->row[x] & 0x7F];
159		row[x + 1] = softwareRenderer->palette[softwareRenderer->row[x + 1] & 0x7F];
160		row[x + 2] = softwareRenderer->palette[softwareRenderer->row[x + 2] & 0x7F];
161		row[x + 3] = softwareRenderer->palette[softwareRenderer->row[x + 3] & 0x7F];
162		row[x + 4] = softwareRenderer->palette[softwareRenderer->row[x + 4] & 0x7F];
163		row[x + 5] = softwareRenderer->palette[softwareRenderer->row[x + 5] & 0x7F];
164		row[x + 6] = softwareRenderer->palette[softwareRenderer->row[x + 6] & 0x7F];
165		row[x + 7] = softwareRenderer->palette[softwareRenderer->row[x + 7] & 0x7F];
166	}
167	for (; x < endX; ++x) {
168		row[x] = softwareRenderer->palette[softwareRenderer->row[x] & 0x7F];
169	}
170}
171
172static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) {
173	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
174	if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && softwareRenderer->wx - 7 < GB_VIDEO_HORIZONTAL_PIXELS) {
175		++softwareRenderer->currentWy;
176	}
177}
178
179static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) {
180	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
181
182	if (softwareRenderer->temporaryBuffer) {
183		mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
184		softwareRenderer->temporaryBuffer = 0;
185	}
186	if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
187		_clearScreen(softwareRenderer);
188	}
189	softwareRenderer->currentWy = 0;
190}
191
192static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy) {
193	uint8_t* data = renderer->d.vram;
194	uint8_t* attr = &maps[GB_SIZE_VRAM_BANK0];
195	if (!GBRegisterLCDCIsTileData(renderer->lcdc)) {
196		data += 0x1000;
197	}
198	int topY = ((sy >> 3) & 0x1F) * 0x20;
199	int bottomY = sy & 7;
200	if (startX < 0) {
201		startX = 0;
202	}
203	int x;
204	if ((startX + sx) & 7) {
205		int startX2 = startX + 8 - ((startX + sx) & 7);
206		for (x = startX; x < startX2; ++x) {
207			uint8_t* localData = data;
208			int localY = bottomY;
209			int topX = ((x + sx) >> 3) & 0x1F;
210			int bottomX = 7 - ((x + sx) & 7);
211			int bgTile;
212			if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
213				bgTile = maps[topX + topY];
214			} else {
215				bgTile = ((int8_t*) maps)[topX + topY];
216			}
217			int p = 0;
218			if (renderer->model >= GB_MODEL_CGB) {
219				GBObjAttributes attrs = attr[topX + topY];
220				p = GBObjAttributesGetCGBPalette(attrs) * 4;
221				if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
222					p |= 0x80;
223				}
224				if (GBObjAttributesIsBank(attrs)) {
225					localData += GB_SIZE_VRAM_BANK0;
226				}
227				if (GBObjAttributesIsYFlip(attrs)) {
228					localY = 7 - bottomY;
229				}
230				if (GBObjAttributesIsXFlip(attrs)) {
231					bottomX = 7 - bottomX;
232				}
233			}
234			uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
235			uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
236			tileDataUpper >>= bottomX;
237			tileDataLower >>= bottomX;
238			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
239		}
240		startX = startX2;
241	}
242	for (x = startX; x < endX; x += 8) {
243		uint8_t* localData = data;
244		int localY = bottomY;
245		int topX = ((x + sx) >> 3) & 0x1F;
246		int bgTile;
247		if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
248			bgTile = maps[topX + topY];
249		} else {
250			bgTile = ((int8_t*) maps)[topX + topY];
251		}
252		int p = 0;
253		if (renderer->model >= GB_MODEL_CGB) {
254			GBObjAttributes attrs = attr[topX + topY];
255			p = GBObjAttributesGetCGBPalette(attrs) * 4;
256			if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
257				p |= 0x80;
258			}
259			if (GBObjAttributesIsBank(attrs)) {
260				localData += GB_SIZE_VRAM_BANK0;
261			}
262			if (GBObjAttributesIsYFlip(attrs)) {
263				localY = 7 - bottomY;
264			}
265			if (GBObjAttributesIsXFlip(attrs)) {
266				uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
267				uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
268				renderer->row[x + 0] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
269				renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
270				renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
271				renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
272				renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
273				renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
274				renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
275				renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
276				continue;
277			}
278		}
279		uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
280		uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
281		renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
282		renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
283		renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
284		renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
285		renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
286		renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
287		renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
288		renderer->row[x + 0] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
289	}
290}
291
292static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y) {
293	int ix = obj->x - 8;
294	if (endX < ix || startX >= ix + 8) {
295		return;
296	}
297	if (obj->x < endX) {
298		endX = obj->x;
299	}
300	if (obj->x - 8 > startX) {
301		startX = obj->x - 8;
302	}
303	if (startX < 0) {
304		startX = 0;
305	}
306	uint8_t* data = renderer->d.vram;
307	int tileOffset = 0;
308	int bottomY;
309	if (GBObjAttributesIsYFlip(obj->attr)) {
310		bottomY = 7 - ((y - obj->y - 16) & 7);
311		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y < -8) {
312			++tileOffset;
313		}
314	} else {
315		bottomY = (y - obj->y - 16) & 7;
316		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y >= -8) {
317			++tileOffset;
318		}
319	}
320	if (GBRegisterLCDCIsObjSize(renderer->lcdc) && obj->tile & 1) {
321		--tileOffset;
322	}
323	uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0x63 : 0x60;
324	uint8_t mask2 = GBObjAttributesIsPriority(obj->attr) ? 0 : 0x83;
325	int p;
326	if (renderer->model >= GB_MODEL_CGB) {
327		p = (GBObjAttributesGetCGBPalette(obj->attr) + 8) * 4;
328		if (GBObjAttributesIsBank(obj->attr)) {
329			data += GB_SIZE_VRAM_BANK0;
330		}
331		if (!GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
332			mask = 0x60;
333			mask2 = 0x83;
334		}
335	} else {
336		p = (GBObjAttributesGetPalette(obj->attr) + 8) * 4;
337	}
338	int bottomX;
339	int x = startX;
340	if ((x - obj->x) & 7) {
341		for (; x < endX; ++x) {
342			if (GBObjAttributesIsXFlip(obj->attr)) {
343				bottomX = (x - obj->x) & 7;
344			} else {
345				bottomX = 7 - ((x - obj->x) & 7);
346			}
347			int objTile = obj->tile + tileOffset;
348			uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
349			uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
350			tileDataUpper >>= bottomX;
351			tileDataLower >>= bottomX;
352			color_t current = renderer->row[x];
353			if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
354				renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
355			}
356		}
357	} else if (GBObjAttributesIsXFlip(obj->attr)) {
358		int objTile = obj->tile + tileOffset;
359		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
360		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
361		color_t current;
362		current = renderer->row[x];
363		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
364			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
365		}
366		current = renderer->row[x + 1];
367		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
368			renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
369		}
370		current = renderer->row[x + 2];
371		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
372			renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
373		}
374		current = renderer->row[x + 3];
375		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
376			renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
377		}
378		current = renderer->row[x + 4];
379		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
380			renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
381		}
382		current = renderer->row[x + 5];
383		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
384			renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
385		}
386		current = renderer->row[x + 6];
387		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
388			renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
389		}
390		current = renderer->row[x + 7];
391		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
392			renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
393		}
394	} else {
395		int objTile = obj->tile + tileOffset;
396		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
397		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
398		color_t current;
399		current = renderer->row[x + 7];
400		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
401			renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
402		}
403		current = renderer->row[x + 6];
404		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
405			renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
406		}
407		current = renderer->row[x + 5];
408		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
409			renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
410		}
411		current = renderer->row[x + 4];
412		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
413			renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
414		}
415		current = renderer->row[x + 3];
416		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
417			renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
418		}
419		current = renderer->row[x + 2];
420		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
421			renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
422		}
423		current = renderer->row[x + 1];
424		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
425			renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
426		}
427		current = renderer->row[x];
428		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
429			renderer->row[x] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
430		}
431	}
432}
433
434static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) {
435	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
436	// TODO: Share with GBAVideoSoftwareRendererGetPixels
437#ifdef COLOR_16_BIT
438	*stride = GB_VIDEO_HORIZONTAL_PIXELS;
439	if (!softwareRenderer->temporaryBuffer) {
440		softwareRenderer->temporaryBuffer = anonymousMemoryMap(GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
441	}
442	*pixels = softwareRenderer->temporaryBuffer;
443	unsigned y, x;
444	for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) {
445		for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) {
446			color_t inColor = softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + x];
447			uint32_t outColor;
448#ifdef COLOR_5_6_5
449			outColor = (inColor & 0x1F) << 19;
450			outColor |= (inColor & 0x7C0) << 5;
451			outColor |= (inColor & 0xF800) >> 8;
452#else
453			outColor = (inColor & 0x1F) << 3;
454			outColor |= (inColor & 0x3E0) << 6;
455			outColor |= (inColor & 0x7C00) << 9;
456#endif
457			softwareRenderer->temporaryBuffer[GB_VIDEO_HORIZONTAL_PIXELS * y + x] = outColor;
458		}
459	}
460#else
461	*stride = softwareRenderer->outputBufferStride;
462	*pixels = softwareRenderer->outputBuffer;
463#endif
464}
465
466
467static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels) {
468	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
469	// TODO: Share with GBAVideoSoftwareRendererGetPixels
470
471	const color_t* colorPixels = pixels;
472	unsigned i;
473	for (i = 0; i < GB_VIDEO_VERTICAL_PIXELS; ++i) {
474		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], GB_VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
475	}
476}