all repos — mgba @ 8a3d54c769b94e247998b8ac4d8e9243c9c43578

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