all repos — mgba @ ddb2b58e135cc99bc6dd54f857df400463860617

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