all repos — mgba @ 36c1fb59be4710543c4a5476a554757efe306ccd

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/math.h>
 11#include <mgba-util/memory.h>
 12
 13static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model);
 14static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer);
 15static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value);
 16static void GBVideoSoftwareRendererWriteSGBPacket(struct GBVideoRenderer* renderer, uint8_t* data);
 17static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value);
 18static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address);
 19static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam);
 20static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax);
 21static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y);
 22static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer);
 23static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels);
 24static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels);
 25
 26static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy);
 27static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y);
 28
 29static void _clearScreen(struct GBVideoSoftwareRenderer* renderer) {
 30	size_t sgbOffset = 0;
 31	if (renderer->model == GB_MODEL_SGB) {
 32		sgbOffset = renderer->outputBufferStride * 40 + 48;
 33	}
 34	int y;
 35	for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) {
 36		color_t* row = &renderer->outputBuffer[renderer->outputBufferStride * y + sgbOffset];
 37		int x;
 38		for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
 39			row[x + 0] = renderer->palette[0];
 40			row[x + 1] = renderer->palette[0];
 41			row[x + 2] = renderer->palette[0];
 42			row[x + 3] = renderer->palette[0];
 43		}
 44	}
 45}
 46
 47static void _regenerateSGBBorder(struct GBVideoSoftwareRenderer* renderer) {
 48	int  i;
 49	for (i = 0; i < 0x40; ++i) {
 50		uint16_t color;
 51		LOAD_16LE(color, 0x800 + i * 2, renderer->d.sgbMapRam);
 52		renderer->d.writePalette(&renderer->d, i + 0x40, color);
 53	}
 54	int x, y;
 55	for (y = 0; y < 224; ++y) {
 56		for (x = 0; x < 256; x += 8) {
 57			uint16_t mapData;
 58			LOAD_16LE(mapData, (x >> 2) + (y & ~7) * 8, renderer->d.sgbMapRam);
 59			if (UNLIKELY(SGBBgAttributesGetTile(mapData) > 0x100)) {
 60				continue;
 61			}
 62			int localY = y & 0x7;
 63			if (SGBBgAttributesIsYFlip(mapData)) {
 64				localY = 7 - y;
 65			}
 66			uint8_t tileData[4];
 67			tileData[0] = renderer->d.sgbCharRam[(SGBBgAttributesGetTile(mapData) * 16 + localY) * 2 + 0x00];
 68			tileData[1] = renderer->d.sgbCharRam[(SGBBgAttributesGetTile(mapData) * 16 + localY) * 2 + 0x01];
 69			tileData[2] = renderer->d.sgbCharRam[(SGBBgAttributesGetTile(mapData) * 16 + localY) * 2 + 0x10];
 70			tileData[3] = renderer->d.sgbCharRam[(SGBBgAttributesGetTile(mapData) * 16 + localY) * 2 + 0x11];
 71			if (!(tileData[0] | tileData[1] | tileData[2] | tileData[3])) {
 72				continue;
 73			}
 74
 75			size_t base = y * renderer->outputBufferStride + x;
 76			int p = SGBBgAttributesGetPalette(mapData) * 0x10;
 77			if (SGBBgAttributesIsXFlip(mapData)) {
 78				renderer->outputBuffer[base + 0] = renderer->palette[p | ((tileData[0] >> 0) & 0x1) | ((tileData[1] << 1) & 0x2) | ((tileData[2] << 2) & 0x4) | ((tileData[3] << 3) & 0x8)];
 79				renderer->outputBuffer[base + 1] = renderer->palette[p | ((tileData[0] >> 1) & 0x1) | ((tileData[1] >> 0) & 0x2) | ((tileData[2] << 1) & 0x4) | ((tileData[3] << 2) & 0x8)];
 80				renderer->outputBuffer[base + 2] = renderer->palette[p | ((tileData[0] >> 2) & 0x1) | ((tileData[1] >> 1) & 0x2) | ((tileData[2] >> 0) & 0x4) | ((tileData[3] << 1) & 0x8)];
 81				renderer->outputBuffer[base + 3] = renderer->palette[p | ((tileData[0] >> 3) & 0x1) | ((tileData[1] >> 2) & 0x2) | ((tileData[2] >> 1) & 0x4) | ((tileData[3] >> 0) & 0x8)];
 82				renderer->outputBuffer[base + 4] = renderer->palette[p | ((tileData[0] >> 4) & 0x1) | ((tileData[1] >> 3) & 0x2) | ((tileData[2] >> 2) & 0x4) | ((tileData[3] >> 1) & 0x8)];
 83				renderer->outputBuffer[base + 5] = renderer->palette[p | ((tileData[0] >> 5) & 0x1) | ((tileData[1] >> 4) & 0x2) | ((tileData[2] >> 3) & 0x4) | ((tileData[3] >> 2) & 0x8)];
 84				renderer->outputBuffer[base + 6] = renderer->palette[p | ((tileData[0] >> 6) & 0x1) | ((tileData[1] >> 5) & 0x2) | ((tileData[2] >> 4) & 0x4) | ((tileData[3] >> 3) & 0x8)];
 85				renderer->outputBuffer[base + 7] = renderer->palette[p | ((tileData[0] >> 7) & 0x1) | ((tileData[1] >> 6) & 0x2) | ((tileData[2] >> 5) & 0x4) | ((tileData[3] >> 4) & 0x8)];
 86			} else {
 87				renderer->outputBuffer[base + 0] = renderer->palette[p | ((tileData[0] >> 7) & 0x1) | ((tileData[1] >> 6) & 0x2) | ((tileData[2] >> 5) & 0x4) | ((tileData[3] >> 4) & 0x8)];
 88				renderer->outputBuffer[base + 1] = renderer->palette[p | ((tileData[0] >> 6) & 0x1) | ((tileData[1] >> 5) & 0x2) | ((tileData[2] >> 4) & 0x4) | ((tileData[3] >> 3) & 0x8)];
 89				renderer->outputBuffer[base + 2] = renderer->palette[p | ((tileData[0] >> 5) & 0x1) | ((tileData[1] >> 4) & 0x2) | ((tileData[2] >> 3) & 0x4) | ((tileData[3] >> 2) & 0x8)];
 90				renderer->outputBuffer[base + 3] = renderer->palette[p | ((tileData[0] >> 4) & 0x1) | ((tileData[1] >> 3) & 0x2) | ((tileData[2] >> 2) & 0x4) | ((tileData[3] >> 1) & 0x8)];
 91				renderer->outputBuffer[base + 4] = renderer->palette[p | ((tileData[0] >> 3) & 0x1) | ((tileData[1] >> 2) & 0x2) | ((tileData[2] >> 1) & 0x4) | ((tileData[3] >> 0) & 0x8)];
 92				renderer->outputBuffer[base + 5] = renderer->palette[p | ((tileData[0] >> 2) & 0x1) | ((tileData[1] >> 1) & 0x2) | ((tileData[2] >> 0) & 0x4) | ((tileData[3] << 1) & 0x8)];
 93				renderer->outputBuffer[base + 6] = renderer->palette[p | ((tileData[0] >> 1) & 0x1) | ((tileData[1] >> 0) & 0x2) | ((tileData[2] << 1) & 0x4) | ((tileData[3] << 2) & 0x8)];
 94				renderer->outputBuffer[base + 7] = renderer->palette[p | ((tileData[0] >> 0) & 0x1) | ((tileData[1] << 1) & 0x2) | ((tileData[2] << 2) & 0x4) | ((tileData[3] << 3) & 0x8)];
 95			}
 96		}
 97	}
 98}
 99
100void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) {
101	renderer->d.init = GBVideoSoftwareRendererInit;
102	renderer->d.deinit = GBVideoSoftwareRendererDeinit;
103	renderer->d.writeVideoRegister = GBVideoSoftwareRendererWriteVideoRegister;
104	renderer->d.writeSGBPacket = GBVideoSoftwareRendererWriteSGBPacket;
105	renderer->d.writePalette = GBVideoSoftwareRendererWritePalette;
106	renderer->d.writeVRAM = GBVideoSoftwareRendererWriteVRAM;
107	renderer->d.writeOAM = GBVideoSoftwareRendererWriteOAM;
108	renderer->d.drawRange = GBVideoSoftwareRendererDrawRange;
109	renderer->d.finishScanline = GBVideoSoftwareRendererFinishScanline;
110	renderer->d.finishFrame = GBVideoSoftwareRendererFinishFrame;
111	renderer->d.getPixels = GBVideoSoftwareRendererGetPixels;
112	renderer->d.putPixels = GBVideoSoftwareRendererPutPixels;
113
114	renderer->d.disableBG = false;
115	renderer->d.disableOBJ = false;
116	renderer->d.disableWIN = false;
117
118	renderer->temporaryBuffer = 0;
119}
120
121static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) {
122	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
123	softwareRenderer->lcdc = 0;
124	softwareRenderer->scy = 0;
125	softwareRenderer->scx = 0;
126	softwareRenderer->wy = 0;
127	softwareRenderer->currentWy = 0;
128	softwareRenderer->wx = 0;
129	softwareRenderer->model = model;
130	softwareRenderer->sgbTransfer = 0;
131}
132
133static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {
134	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
135	UNUSED(softwareRenderer);
136}
137
138static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) {
139	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
140	switch (address) {
141	case REG_LCDC:
142		softwareRenderer->lcdc = value;
143		break;
144	case REG_SCY:
145		softwareRenderer->scy = value;
146		break;
147	case REG_SCX:
148		softwareRenderer->scx = value;
149		break;
150	case REG_WY:
151		softwareRenderer->wy = value;
152		break;
153	case REG_WX:
154		softwareRenderer->wx = value;
155		break;
156	}
157	return value;
158}
159
160static void GBVideoSoftwareRendererWriteSGBPacket(struct GBVideoRenderer* renderer, uint8_t* data) {
161	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
162	memcpy(softwareRenderer->sgbPacket, data, sizeof(softwareRenderer->sgbPacket));
163}
164
165static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) {
166	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
167#ifdef COLOR_16_BIT
168#ifdef COLOR_5_6_5
169	color_t color = 0;
170	color |= (value & 0x001F) << 11;
171	color |= (value & 0x03E0) << 1;
172	color |= (value & 0x7C00) >> 10;
173#else
174	color_t color = value;
175#endif
176#else
177	color_t color = 0;
178	color |= (value << 3) & 0xF8;
179	color |= (value << 6) & 0xF800;
180	color |= (value << 9) & 0xF80000;
181	color |= (color >> 5) & 0x070707;
182#endif
183	softwareRenderer->palette[index] = color;
184	if (renderer->cache) {
185		mTileCacheWritePalette(renderer->cache, index << 1);
186	}
187}
188
189static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) {
190	if (renderer->cache) {
191		mTileCacheWriteVRAM(renderer->cache, address);
192	}
193}
194
195static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam) {
196	UNUSED(renderer);
197	UNUSED(oam);
198	// Nothing to do
199}
200
201static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax) {
202	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
203	uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP];
204	if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) {
205		maps += GB_SIZE_MAP;
206	}
207	if (softwareRenderer->d.disableBG) {
208		memset(&softwareRenderer->row[startX], 0, endX - startX);
209	}
210	if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) {
211		if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && endX >= softwareRenderer->wx - 7) {
212			if (softwareRenderer->wx - 7 > 0 && !softwareRenderer->d.disableBG) {
213				GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, softwareRenderer->scx, softwareRenderer->scy + y);
214			}
215
216			maps = &softwareRenderer->d.vram[GB_BASE_MAP];
217			if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) {
218				maps += GB_SIZE_MAP;
219			}
220			if (!softwareRenderer->d.disableWIN) {
221				GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, softwareRenderer->currentWy);
222			}
223		} else if (!softwareRenderer->d.disableBG) {
224			GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx, softwareRenderer->scy + y);
225		}
226	} else if (!softwareRenderer->d.disableBG) {
227		memset(&softwareRenderer->row[startX], 0, endX - startX);
228	}
229
230	if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ) {
231		size_t i;
232		for (i = 0; i < oamMax; ++i) {
233			GBVideoSoftwareRendererDrawObj(softwareRenderer, &obj[i], startX, endX, y);
234		}
235	}
236	size_t sgbOffset = 0;
237	if (softwareRenderer->model == GB_MODEL_SGB) {
238		sgbOffset = softwareRenderer->outputBufferStride * 40 + 48;
239	}
240	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + sgbOffset];
241	int x;
242	switch (softwareRenderer->d.sgbRenderMode) {
243	case 0:
244		for (x = startX; x + 7 < (endX & ~7); x += 8) {
245			row[x] = softwareRenderer->palette[softwareRenderer->row[x] & 0x7F];
246			row[x + 1] = softwareRenderer->palette[softwareRenderer->row[x + 1] & 0x7F];
247			row[x + 2] = softwareRenderer->palette[softwareRenderer->row[x + 2] & 0x7F];
248			row[x + 3] = softwareRenderer->palette[softwareRenderer->row[x + 3] & 0x7F];
249			row[x + 4] = softwareRenderer->palette[softwareRenderer->row[x + 4] & 0x7F];
250			row[x + 5] = softwareRenderer->palette[softwareRenderer->row[x + 5] & 0x7F];
251			row[x + 6] = softwareRenderer->palette[softwareRenderer->row[x + 6] & 0x7F];
252			row[x + 7] = softwareRenderer->palette[softwareRenderer->row[x + 7] & 0x7F];
253		}
254		for (; x < endX; ++x) {
255			row[x] = softwareRenderer->palette[softwareRenderer->row[x] & 0x7F];
256		}
257		break;
258	case 1:
259		return;
260	case 2:
261		for (x = startX; x + 7 < (endX & ~7); x += 8) {
262			row[x] = 0;
263			row[x + 1] = 0;
264			row[x + 2] = 0;
265			row[x + 3] = 0;
266			row[x + 4] = 0;
267			row[x + 5] = 0;
268			row[x + 6] = 0;
269			row[x + 7] = 0;
270		}
271		for (; x < endX; ++x) {
272			row[x] = 0;
273		}
274		return;
275	case 3:
276		for (x = startX; x + 7 < (endX & ~7); x += 8) {
277			row[x] = softwareRenderer->palette[0];
278			row[x + 1] = softwareRenderer->palette[0];
279			row[x + 2] = softwareRenderer->palette[0];
280			row[x + 3] = softwareRenderer->palette[0];
281			row[x + 4] = softwareRenderer->palette[0];
282			row[x + 5] = softwareRenderer->palette[0];
283			row[x + 6] = softwareRenderer->palette[0];
284			row[x + 7] = softwareRenderer->palette[0];
285		}
286		for (; x < endX; ++x) {
287			row[x] = softwareRenderer->palette[0];
288		}
289		return;
290	}
291}
292
293static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) {
294	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
295	if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && softwareRenderer->wx - 7 < GB_VIDEO_HORIZONTAL_PIXELS) {
296		++softwareRenderer->currentWy;
297	}
298	if (softwareRenderer->sgbTransfer == 1) {
299		uint8_t* buffer = NULL;
300		switch (softwareRenderer->sgbPacket[0] >> 3) {
301		case SGB_PAL_TRN:
302			buffer = renderer->sgbPalRam;
303			break;
304		case SGB_CHR_TRN:
305			buffer = &renderer->sgbCharRam[SGB_SIZE_CHAR_RAM / 2 * (softwareRenderer->sgbPacket[1] & 1)];
306			break;
307		case SGB_PCT_TRN:
308			buffer = renderer->sgbMapRam;
309			break;
310		default:
311			break;
312		}
313		if (buffer) {
314			size_t offset = 2 * ((y & 7) + (y >> 3) * GB_VIDEO_HORIZONTAL_PIXELS);
315			if (offset < 0x1000) {
316				int i;
317				for (i = 0; i < GB_VIDEO_HORIZONTAL_PIXELS; i += 8) {
318					if (UNLIKELY(offset + (i << 1) + 1 >= 0x1000)) {
319						break;
320					}
321					uint8_t hi = 0;
322					uint8_t lo = 0;
323					hi |= (softwareRenderer->row[i + 0] & 0x2) << 6;
324					lo |= (softwareRenderer->row[i + 0] & 0x1) << 7;
325					hi |= (softwareRenderer->row[i + 1] & 0x2) << 5;
326					lo |= (softwareRenderer->row[i + 1] & 0x1) << 6;
327					hi |= (softwareRenderer->row[i + 2] & 0x2) << 4;
328					lo |= (softwareRenderer->row[i + 2] & 0x1) << 5;
329					hi |= (softwareRenderer->row[i + 3] & 0x2) << 3;
330					lo |= (softwareRenderer->row[i + 3] & 0x1) << 4;
331					hi |= (softwareRenderer->row[i + 4] & 0x2) << 2;
332					lo |= (softwareRenderer->row[i + 4] & 0x1) << 3;
333					hi |= (softwareRenderer->row[i + 5] & 0x2) << 1;
334					lo |= (softwareRenderer->row[i + 5] & 0x1) << 2;
335					hi |= (softwareRenderer->row[i + 6] & 0x2) << 0;
336					lo |= (softwareRenderer->row[i + 6] & 0x1) << 1;
337					hi |= (softwareRenderer->row[i + 7] & 0x2) >> 1;
338					lo |= (softwareRenderer->row[i + 7] & 0x1) >> 0;
339					buffer[offset + (i << 1) + 0] = lo;
340					buffer[offset + (i << 1) + 1] = hi;
341				}
342			}
343		}
344	}
345}
346
347static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) {
348	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
349
350	if (softwareRenderer->temporaryBuffer) {
351		mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
352		softwareRenderer->temporaryBuffer = 0;
353	}
354	if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
355		_clearScreen(softwareRenderer);
356	}
357	if (softwareRenderer->model == GB_MODEL_SGB) {
358		switch (softwareRenderer->sgbPacket[0] >> 3) {
359		case SGB_PAL_SET:
360			if (softwareRenderer->sgbPacket[9] & 0x40) {
361				renderer->sgbRenderMode = 0;
362			}
363			break;
364		case SGB_ATTR_SET:
365			if (softwareRenderer->sgbPacket[1] & 0x40) {
366				renderer->sgbRenderMode = 0;
367			}
368			break;
369		case SGB_PAL_TRN:
370		case SGB_CHR_TRN:
371		case SGB_PCT_TRN:
372			if (softwareRenderer->sgbTransfer > 0) {
373				// Make sure every buffer sees this if we're multibuffering
374				_regenerateSGBBorder(softwareRenderer);
375			}
376			++softwareRenderer->sgbTransfer;
377			if (softwareRenderer->sgbTransfer == 5) {
378				softwareRenderer->sgbTransfer = 0;
379				softwareRenderer->sgbPacket[0] = 0;
380			}
381		default:
382			break;
383		}
384	}
385	softwareRenderer->currentWy = 0;
386}
387
388static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy) {
389	uint8_t* data = renderer->d.vram;
390	uint8_t* attr = &maps[GB_SIZE_VRAM_BANK0];
391	if (!GBRegisterLCDCIsTileData(renderer->lcdc)) {
392		data += 0x1000;
393	}
394	int topY = ((sy >> 3) & 0x1F) * 0x20;
395	int bottomY = sy & 7;
396	if (startX < 0) {
397		startX = 0;
398	}
399	int x;
400	if ((startX + sx) & 7) {
401		int startX2 = startX + 8 - ((startX + sx) & 7);
402		for (x = startX; x < startX2; ++x) {
403			uint8_t* localData = data;
404			int localY = bottomY;
405			int topX = ((x + sx) >> 3) & 0x1F;
406			int bottomX = 7 - ((x + sx) & 7);
407			int bgTile;
408			if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
409				bgTile = maps[topX + topY];
410			} else {
411				bgTile = ((int8_t*) maps)[topX + topY];
412			}
413			int p = 0;
414			if (renderer->model >= GB_MODEL_CGB) {
415				GBObjAttributes attrs = attr[topX + topY];
416				p = GBObjAttributesGetCGBPalette(attrs) * 4;
417				if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
418					p |= 0x80;
419				}
420				if (GBObjAttributesIsBank(attrs)) {
421					localData += GB_SIZE_VRAM_BANK0;
422				}
423				if (GBObjAttributesIsYFlip(attrs)) {
424					localY = 7 - bottomY;
425				}
426				if (GBObjAttributesIsXFlip(attrs)) {
427					bottomX = 7 - bottomX;
428				}
429			}
430			uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
431			uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
432			tileDataUpper >>= bottomX;
433			tileDataLower >>= bottomX;
434			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
435		}
436		startX = startX2;
437	}
438	for (x = startX; x < endX; x += 8) {
439		uint8_t* localData = data;
440		int localY = bottomY;
441		int topX = ((x + sx) >> 3) & 0x1F;
442		int bgTile;
443		if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
444			bgTile = maps[topX + topY];
445		} else {
446			bgTile = ((int8_t*) maps)[topX + topY];
447		}
448		int p = 0;
449		if (renderer->model >= GB_MODEL_CGB) {
450			GBObjAttributes attrs = attr[topX + topY];
451			p = GBObjAttributesGetCGBPalette(attrs) * 4;
452			if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
453				p |= 0x80;
454			}
455			if (GBObjAttributesIsBank(attrs)) {
456				localData += GB_SIZE_VRAM_BANK0;
457			}
458			if (GBObjAttributesIsYFlip(attrs)) {
459				localY = 7 - bottomY;
460			}
461			if (GBObjAttributesIsXFlip(attrs)) {
462				uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
463				uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
464				renderer->row[x + 0] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
465				renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
466				renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
467				renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
468				renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
469				renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
470				renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
471				renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
472				continue;
473			}
474		}
475		uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
476		uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
477		renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
478		renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
479		renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
480		renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
481		renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
482		renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
483		renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
484		renderer->row[x + 0] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
485	}
486}
487
488static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y) {
489	int ix = obj->x - 8;
490	if (endX < ix || startX >= ix + 8) {
491		return;
492	}
493	if (obj->x < endX) {
494		endX = obj->x;
495	}
496	if (obj->x - 8 > startX) {
497		startX = obj->x - 8;
498	}
499	if (startX < 0) {
500		startX = 0;
501	}
502	uint8_t* data = renderer->d.vram;
503	int tileOffset = 0;
504	int bottomY;
505	if (GBObjAttributesIsYFlip(obj->attr)) {
506		bottomY = 7 - ((y - obj->y - 16) & 7);
507		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y < -8) {
508			++tileOffset;
509		}
510	} else {
511		bottomY = (y - obj->y - 16) & 7;
512		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y >= -8) {
513			++tileOffset;
514		}
515	}
516	if (GBRegisterLCDCIsObjSize(renderer->lcdc) && obj->tile & 1) {
517		--tileOffset;
518	}
519	uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0x63 : 0x60;
520	uint8_t mask2 = GBObjAttributesIsPriority(obj->attr) ? 0 : 0x83;
521	int p;
522	if (renderer->model >= GB_MODEL_CGB) {
523		p = (GBObjAttributesGetCGBPalette(obj->attr) + 8) * 4;
524		if (GBObjAttributesIsBank(obj->attr)) {
525			data += GB_SIZE_VRAM_BANK0;
526		}
527		if (!GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
528			mask = 0x60;
529			mask2 = 0x83;
530		}
531	} else {
532		p = (GBObjAttributesGetPalette(obj->attr) + 8) * 4;
533	}
534	int bottomX;
535	int x = startX;
536	if ((x - obj->x) & 7) {
537		for (; x < endX; ++x) {
538			if (GBObjAttributesIsXFlip(obj->attr)) {
539				bottomX = (x - obj->x) & 7;
540			} else {
541				bottomX = 7 - ((x - obj->x) & 7);
542			}
543			int objTile = obj->tile + tileOffset;
544			uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
545			uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
546			tileDataUpper >>= bottomX;
547			tileDataLower >>= bottomX;
548			color_t current = renderer->row[x];
549			if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
550				renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
551			}
552		}
553	} else if (GBObjAttributesIsXFlip(obj->attr)) {
554		int objTile = obj->tile + tileOffset;
555		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
556		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
557		color_t current;
558		current = renderer->row[x];
559		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
560			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
561		}
562		current = renderer->row[x + 1];
563		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
564			renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
565		}
566		current = renderer->row[x + 2];
567		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
568			renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
569		}
570		current = renderer->row[x + 3];
571		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
572			renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
573		}
574		current = renderer->row[x + 4];
575		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
576			renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
577		}
578		current = renderer->row[x + 5];
579		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
580			renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
581		}
582		current = renderer->row[x + 6];
583		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
584			renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
585		}
586		current = renderer->row[x + 7];
587		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
588			renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
589		}
590	} else {
591		int objTile = obj->tile + tileOffset;
592		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
593		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
594		color_t current;
595		current = renderer->row[x + 7];
596		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
597			renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
598		}
599		current = renderer->row[x + 6];
600		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
601			renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
602		}
603		current = renderer->row[x + 5];
604		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
605			renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
606		}
607		current = renderer->row[x + 4];
608		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
609			renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
610		}
611		current = renderer->row[x + 3];
612		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
613			renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
614		}
615		current = renderer->row[x + 2];
616		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
617			renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
618		}
619		current = renderer->row[x + 1];
620		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
621			renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
622		}
623		current = renderer->row[x];
624		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
625			renderer->row[x] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
626		}
627	}
628}
629
630static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) {
631	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
632	*stride = softwareRenderer->outputBufferStride;
633	*pixels = softwareRenderer->outputBuffer;
634}
635
636static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels) {
637	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
638	// TODO: Share with GBAVideoSoftwareRendererGetPixels
639
640	const color_t* colorPixels = pixels;
641	unsigned i;
642	for (i = 0; i < GB_VIDEO_VERTICAL_PIXELS; ++i) {
643		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], GB_VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
644	}
645}