all repos — mgba @ e7ec349bf95a4a203cee2f264e27ce1c32a43af2

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}
220
221static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) {
222	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
223
224	if (softwareRenderer->temporaryBuffer) {
225		mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
226		softwareRenderer->temporaryBuffer = 0;
227	}
228	if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
229		_clearScreen(softwareRenderer);
230	}
231	softwareRenderer->lastY = GB_VIDEO_VERTICAL_PIXELS;
232	softwareRenderer->currentWy = 0;
233	softwareRenderer->hasWindow = false;
234}
235
236static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy) {
237	uint8_t* data = renderer->d.vram;
238	uint8_t* attr = &maps[GB_SIZE_VRAM_BANK0];
239	if (!GBRegisterLCDCIsTileData(renderer->lcdc)) {
240		data += 0x1000;
241	}
242	int topY = ((sy >> 3) & 0x1F) * 0x20;
243	int bottomY = sy & 7;
244	if (startX < 0) {
245		startX = 0;
246	}
247	int x;
248	if ((startX + sx) & 7) {
249		int startX2 = startX + 8 - ((startX + sx) & 7);
250		for (x = startX; x < startX2; ++x) {
251			uint8_t* localData = data;
252			int localY = bottomY;
253			int topX = ((x + sx) >> 3) & 0x1F;
254			int bottomX = 7 - ((x + sx) & 7);
255			int bgTile;
256			if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
257				bgTile = maps[topX + topY];
258			} else {
259				bgTile = ((int8_t*) maps)[topX + topY];
260			}
261			int p = 0;
262			if (renderer->model >= GB_MODEL_CGB) {
263				GBObjAttributes attrs = attr[topX + topY];
264				p = GBObjAttributesGetCGBPalette(attrs) * 4;
265				if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
266					p |= 0x80;
267				}
268				if (GBObjAttributesIsBank(attrs)) {
269					localData += GB_SIZE_VRAM_BANK0;
270				}
271				if (GBObjAttributesIsYFlip(attrs)) {
272					localY = 7 - bottomY;
273				}
274				if (GBObjAttributesIsXFlip(attrs)) {
275					bottomX = 7 - bottomX;
276				}
277			}
278			uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
279			uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
280			tileDataUpper >>= bottomX;
281			tileDataLower >>= bottomX;
282			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
283		}
284		startX = startX2;
285	}
286	for (x = startX; x < endX; x += 8) {
287		uint8_t* localData = data;
288		int localY = bottomY;
289		int topX = ((x + sx) >> 3) & 0x1F;
290		int bgTile;
291		if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
292			bgTile = maps[topX + topY];
293		} else {
294			bgTile = ((int8_t*) maps)[topX + topY];
295		}
296		int p = 0;
297		if (renderer->model >= GB_MODEL_CGB) {
298			GBObjAttributes attrs = attr[topX + topY];
299			p = GBObjAttributesGetCGBPalette(attrs) * 4;
300			if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
301				p |= 0x80;
302			}
303			if (GBObjAttributesIsBank(attrs)) {
304				localData += GB_SIZE_VRAM_BANK0;
305			}
306			if (GBObjAttributesIsYFlip(attrs)) {
307				localY = 7 - bottomY;
308			}
309			if (GBObjAttributesIsXFlip(attrs)) {
310				uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
311				uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
312				renderer->row[x + 0] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
313				renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
314				renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
315				renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
316				renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
317				renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
318				renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
319				renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
320				continue;
321			}
322		}
323		uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
324		uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
325		renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
326		renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
327		renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
328		renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
329		renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
330		renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
331		renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
332		renderer->row[x + 0] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
333	}
334}
335
336static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y) {
337	int ix = obj->x - 8;
338	if (endX < ix || startX >= ix + 8) {
339		return;
340	}
341	if (obj->x < endX) {
342		endX = obj->x;
343	}
344	if (obj->x - 8 > startX) {
345		startX = obj->x - 8;
346	}
347	if (startX < 0) {
348		startX = 0;
349	}
350	uint8_t* data = renderer->d.vram;
351	int tileOffset = 0;
352	int bottomY;
353	if (GBObjAttributesIsYFlip(obj->attr)) {
354		bottomY = 7 - ((y - obj->y - 16) & 7);
355		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y < -8) {
356			++tileOffset;
357		}
358	} else {
359		bottomY = (y - obj->y - 16) & 7;
360		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y >= -8) {
361			++tileOffset;
362		}
363	}
364	if (GBRegisterLCDCIsObjSize(renderer->lcdc) && obj->tile & 1) {
365		--tileOffset;
366	}
367	uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0x63 : 0x60;
368	uint8_t mask2 = GBObjAttributesIsPriority(obj->attr) ? 0 : 0x83;
369	int p;
370	if (renderer->model >= GB_MODEL_CGB) {
371		p = (GBObjAttributesGetCGBPalette(obj->attr) + 8) * 4;
372		if (GBObjAttributesIsBank(obj->attr)) {
373			data += GB_SIZE_VRAM_BANK0;
374		}
375		if (!GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
376			mask = 0x60;
377			mask2 = 0x83;
378		}
379	} else {
380		p = (GBObjAttributesGetPalette(obj->attr) + 8) * 4;
381	}
382	int bottomX;
383	int x = startX;
384	if ((x - obj->x) & 7) {
385		for (; x < endX; ++x) {
386			if (GBObjAttributesIsXFlip(obj->attr)) {
387				bottomX = (x - obj->x) & 7;
388			} else {
389				bottomX = 7 - ((x - obj->x) & 7);
390			}
391			int objTile = obj->tile + tileOffset;
392			uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
393			uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
394			tileDataUpper >>= bottomX;
395			tileDataLower >>= bottomX;
396			color_t current = renderer->row[x];
397			if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
398				renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
399			}
400		}
401	} else if (GBObjAttributesIsXFlip(obj->attr)) {
402		int objTile = obj->tile + tileOffset;
403		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
404		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
405		color_t current;
406		current = renderer->row[x];
407		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
408			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
409		}
410		current = renderer->row[x + 1];
411		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
412			renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
413		}
414		current = renderer->row[x + 2];
415		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
416			renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
417		}
418		current = renderer->row[x + 3];
419		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
420			renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
421		}
422		current = renderer->row[x + 4];
423		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
424			renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
425		}
426		current = renderer->row[x + 5];
427		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
428			renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
429		}
430		current = renderer->row[x + 6];
431		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
432			renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
433		}
434		current = renderer->row[x + 7];
435		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
436			renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
437		}
438	} else {
439		int objTile = obj->tile + tileOffset;
440		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
441		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
442		color_t current;
443		current = renderer->row[x + 7];
444		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
445			renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
446		}
447		current = renderer->row[x + 6];
448		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
449			renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
450		}
451		current = renderer->row[x + 5];
452		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
453			renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
454		}
455		current = renderer->row[x + 4];
456		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
457			renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
458		}
459		current = renderer->row[x + 3];
460		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
461			renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
462		}
463		current = renderer->row[x + 2];
464		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
465			renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
466		}
467		current = renderer->row[x + 1];
468		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
469			renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
470		}
471		current = renderer->row[x];
472		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
473			renderer->row[x] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
474		}
475	}
476}
477
478static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) {
479	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
480	*stride = softwareRenderer->outputBufferStride;
481	*pixels = softwareRenderer->outputBuffer;
482}
483
484static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels) {
485	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
486	// TODO: Share with GBAVideoSoftwareRendererGetPixels
487
488	const color_t* colorPixels = pixels;
489	unsigned i;
490	for (i = 0; i < GB_VIDEO_VERTICAL_PIXELS; ++i) {
491		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], GB_VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
492	}
493}