all repos — mgba @ 70c2c80553035955c82935003c5c939a68410c86

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