all repos — mgba @ 16c0132e8fd44f8e5d189540927a6a98cdde7aa7

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/cache-set.h>
  9#include <mgba/internal/gb/io.h>
 10#include <mgba/internal/gb/renderers/cache-set.h>
 11#include <mgba-util/math.h>
 12#include <mgba-util/memory.h>
 13
 14static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model, bool borders);
 15static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer);
 16static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value);
 17static void GBVideoSoftwareRendererWriteSGBPacket(struct GBVideoRenderer* renderer, uint8_t* data);
 18static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value);
 19static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address);
 20static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam);
 21static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax);
 22static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y);
 23static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer);
 24static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels);
 25static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels);
 26
 27static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy);
 28static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y);
 29
 30static void _clearScreen(struct GBVideoSoftwareRenderer* renderer) {
 31	size_t sgbOffset = 0;
 32	if (renderer->model == GB_MODEL_SGB && renderer->sgbBorders) {
 33		sgbOffset = renderer->outputBufferStride * 40 + 48;
 34	}
 35	int y;
 36	for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) {
 37		color_t* row = &renderer->outputBuffer[renderer->outputBufferStride * y + sgbOffset];
 38		int x;
 39		for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
 40			row[x + 0] = renderer->palette[0];
 41			row[x + 1] = renderer->palette[0];
 42			row[x + 2] = renderer->palette[0];
 43			row[x + 3] = renderer->palette[0];
 44		}
 45	}
 46}
 47
 48static void _regenerateSGBBorder(struct GBVideoSoftwareRenderer* renderer) {
 49	int i;
 50	for (i = 0; i < 0x40; ++i) {
 51		uint16_t color;
 52		LOAD_16LE(color, 0x800 + i * 2, renderer->d.sgbMapRam);
 53		renderer->d.writePalette(&renderer->d, i + 0x40, color);
 54	}
 55	int x, y;
 56	for (y = 0; y < 224; ++y) {
 57		for (x = 0; x < 256; x += 8) {
 58			if (x >= 48 && x < 208 && y >= 40 && y < 104) {
 59				continue;
 60			}
 61			uint16_t mapData;
 62			LOAD_16LE(mapData, (x >> 2) + (y & ~7) * 8, renderer->d.sgbMapRam);
 63			if (UNLIKELY(SGBBgAttributesGetTile(mapData) >= 0x100)) {
 64				continue;
 65			}
 66
 67			int localY = y & 0x7;
 68			if (SGBBgAttributesIsYFlip(mapData)) {
 69				localY = 7 - localY;
 70			}
 71			uint8_t tileData[4];
 72			tileData[0] = renderer->d.sgbCharRam[(SGBBgAttributesGetTile(mapData) * 16 + localY) * 2 + 0x00];
 73			tileData[1] = renderer->d.sgbCharRam[(SGBBgAttributesGetTile(mapData) * 16 + localY) * 2 + 0x01];
 74			tileData[2] = renderer->d.sgbCharRam[(SGBBgAttributesGetTile(mapData) * 16 + localY) * 2 + 0x10];
 75			tileData[3] = renderer->d.sgbCharRam[(SGBBgAttributesGetTile(mapData) * 16 + localY) * 2 + 0x11];
 76
 77			size_t base = y * renderer->outputBufferStride + x;
 78			int paletteBase = SGBBgAttributesGetPalette(mapData) * 0x10;
 79			int colorSelector;
 80
 81			if (SGBBgAttributesIsXFlip(mapData)) {
 82				for (i = 0; i < 8; ++i) {
 83					colorSelector = (tileData[0] >> i & 0x1) << 0 | (tileData[1] >> i & 0x1) << 1 | (tileData[2] >> i & 0x1) << 2 | (tileData[3] >> i & 0x1) << 3;
 84					renderer->outputBuffer[base + i] = renderer->palette[paletteBase | colorSelector];
 85				}
 86			} else {
 87				for (i = 7; i >= 0; --i) {
 88					colorSelector = (tileData[0] >> i & 0x1) << 0 | (tileData[1] >> i & 0x1) << 1 | (tileData[2] >> i & 0x1) << 2 | (tileData[3] >> i & 0x1) << 3;
 89					renderer->outputBuffer[base + 7 - i] = renderer->palette[paletteBase | colorSelector];
 90				}
 91			}
 92		}
 93	}
 94}
 95
 96static inline void _setAttribute(uint8_t* sgbAttributes, unsigned x, unsigned y, int palette) {
 97	int p = sgbAttributes[(x >> 2) + 5 * y];
 98	p &= ~(3 << (2 * (3 - (x & 3))));
 99	p |= palette << (2 * (3 - (x & 3)));
100	sgbAttributes[(x >> 2) + 5 * y] = p;
101}
102
103static void _parseAttrBlock(struct GBVideoSoftwareRenderer* renderer, int start) {
104	uint8_t block[6];
105	memcpy(block, &renderer->sgbPacket[start], 6);
106	unsigned x0 = block[2];
107	unsigned x1 = block[4];
108	unsigned y0 = block[3];
109	unsigned y1 = block[5];
110	unsigned x, y;
111	int pIn = block[1] & 3;
112	int pPerim = (block[1] >> 2) & 3;
113	int pOut = (block[1] >> 4) & 3;
114
115	for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS / 8; ++y) {
116		for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS / 8; ++x) {
117			if (y > y0 && y < y1 && x > x0 && x < x1) {
118				if (block[0] & 1) {
119					_setAttribute(renderer->d.sgbAttributes, x, y, pIn);
120				}
121			} else if (y < y0 || y > y1 || x < x0 || x > x1) {
122				if (block[0] & 4) {
123					_setAttribute(renderer->d.sgbAttributes, x, y, pOut);
124				}
125			} else {
126				if (block[0] & 2) {
127					_setAttribute(renderer->d.sgbAttributes, x, y, pPerim);
128				} else if (block[0] & 1) {
129					_setAttribute(renderer->d.sgbAttributes, x, y, pIn);
130				} else if (block[0] & 4) {
131					_setAttribute(renderer->d.sgbAttributes, x, y, pOut);
132				}
133			}
134		}
135	}
136}
137
138static bool _inWindow(struct GBVideoSoftwareRenderer* renderer) {
139	return GBRegisterLCDCIsWindow(renderer->lcdc) && GB_VIDEO_HORIZONTAL_PIXELS + 7 > renderer->wx;
140}
141
142void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) {
143	renderer->d.init = GBVideoSoftwareRendererInit;
144	renderer->d.deinit = GBVideoSoftwareRendererDeinit;
145	renderer->d.writeVideoRegister = GBVideoSoftwareRendererWriteVideoRegister;
146	renderer->d.writeSGBPacket = GBVideoSoftwareRendererWriteSGBPacket;
147	renderer->d.writePalette = GBVideoSoftwareRendererWritePalette;
148	renderer->d.writeVRAM = GBVideoSoftwareRendererWriteVRAM;
149	renderer->d.writeOAM = GBVideoSoftwareRendererWriteOAM;
150	renderer->d.drawRange = GBVideoSoftwareRendererDrawRange;
151	renderer->d.finishScanline = GBVideoSoftwareRendererFinishScanline;
152	renderer->d.finishFrame = GBVideoSoftwareRendererFinishFrame;
153	renderer->d.getPixels = GBVideoSoftwareRendererGetPixels;
154	renderer->d.putPixels = GBVideoSoftwareRendererPutPixels;
155
156	renderer->d.disableBG = false;
157	renderer->d.disableOBJ = false;
158	renderer->d.disableWIN = false;
159
160	renderer->temporaryBuffer = 0;
161}
162
163static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum GBModel model, bool sgbBorders) {
164	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
165	softwareRenderer->lcdc = 0;
166	softwareRenderer->scy = 0;
167	softwareRenderer->scx = 0;
168	softwareRenderer->wy = 0;
169	softwareRenderer->currentWy = 0;
170	softwareRenderer->lastY = 0;
171	softwareRenderer->hasWindow = false;
172	softwareRenderer->wx = 0;
173	softwareRenderer->model = model;
174	softwareRenderer->sgbTransfer = 0;
175	softwareRenderer->sgbCommandHeader = 0;
176	softwareRenderer->sgbBorders = sgbBorders;
177	int i;
178	for (i = 0; i < 64; ++i) {
179		softwareRenderer->lookup[i] = i;
180		softwareRenderer->lookup[i] = i;
181		softwareRenderer->lookup[i] = i;
182		softwareRenderer->lookup[i] = i;
183	}
184}
185
186static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {
187	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
188	UNUSED(softwareRenderer);
189}
190
191static void GBVideoSoftwareRendererUpdateWindow(struct GBVideoSoftwareRenderer* renderer, bool before, bool after) {
192	if (renderer->lastY >= GB_VIDEO_VERTICAL_PIXELS || after == before) {
193		return;
194	}
195	if (renderer->lastY >= renderer->wy) {
196		if (!after) {
197			renderer->currentWy -= renderer->lastY;
198			renderer->hasWindow = true;
199		} else {
200			if (!renderer->hasWindow) {
201				renderer->currentWy = renderer->lastY - renderer->wy;
202			} else {
203				renderer->currentWy += renderer->lastY;
204			}
205		}
206	}
207}
208
209static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) {
210	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
211	if (renderer->cache) {
212		GBVideoCacheWriteVideoRegister(renderer->cache, address, value);
213	}
214	bool wasWindow = _inWindow(softwareRenderer);
215	switch (address) {
216	case REG_LCDC:
217		softwareRenderer->lcdc = value;
218		GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer));
219		break;
220	case REG_SCY:
221		softwareRenderer->scy = value;
222		break;
223	case REG_SCX:
224		softwareRenderer->scx = value;
225		break;
226	case REG_WY:
227		softwareRenderer->wy = value;
228		GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer));
229		break;
230	case REG_WX:
231		softwareRenderer->wx = value;
232		GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer));
233		break;
234	case REG_BGP:
235		softwareRenderer->lookup[0] = value & 3;
236		softwareRenderer->lookup[1] = (value >> 2) & 3;
237		softwareRenderer->lookup[2] = (value >> 4) & 3;
238		softwareRenderer->lookup[3] = (value >> 6) & 3;
239		break;
240	case REG_OBP0:
241		softwareRenderer->lookup[0x20 + 0] = value & 3;
242		softwareRenderer->lookup[0x20 + 1] = (value >> 2) & 3;
243		softwareRenderer->lookup[0x20 + 2] = (value >> 4) & 3;
244		softwareRenderer->lookup[0x20 + 3] = (value >> 6) & 3;
245		break;
246	case REG_OBP1:
247		softwareRenderer->lookup[0x24 + 0] = value & 3;
248		softwareRenderer->lookup[0x24 + 1] = (value >> 2) & 3;
249		softwareRenderer->lookup[0x24 + 2] = (value >> 4) & 3;
250		softwareRenderer->lookup[0x24 + 3] = (value >> 6) & 3;
251		break;
252	}
253	return value;
254}
255
256static void GBVideoSoftwareRendererWriteSGBPacket(struct GBVideoRenderer* renderer, uint8_t* data) {
257	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
258	memcpy(softwareRenderer->sgbPacket, data, sizeof(softwareRenderer->sgbPacket));
259	int i;
260	softwareRenderer->sgbCommandHeader = data[0];
261	softwareRenderer->sgbTransfer = 0;
262	int set;
263	int sets;
264	int attrX;
265	int attrY;
266	int attrDirection;
267	int pBefore;
268	int pAfter;
269	int pDiv;
270	switch (softwareRenderer->sgbCommandHeader >> 3) {
271	case SGB_PAL_SET:
272		softwareRenderer->sgbPacket[1] = data[9];
273		if (!(data[9] & 0x80)) {
274			break;
275		}
276		// Fall through
277	case SGB_ATTR_SET:
278		set = softwareRenderer->sgbPacket[1] & 0x3F;
279		if (set <= 0x2C) {
280			memcpy(renderer->sgbAttributes, &renderer->sgbAttributeFiles[set * 90], 90);
281		}
282		break;
283	case SGB_ATTR_BLK:
284		sets = softwareRenderer->sgbPacket[1];
285		i = 2;
286		for (; i < (softwareRenderer->sgbCommandHeader & 7) << 4 && sets; i += 6, --sets) {
287			_parseAttrBlock(softwareRenderer, i);
288		}
289		break;
290	case SGB_ATTR_DIV:
291		pAfter = softwareRenderer->sgbPacket[1] & 3;
292		pBefore = (softwareRenderer->sgbPacket[1] >> 2) & 3;
293		pDiv = (softwareRenderer->sgbPacket[1] >> 4) & 3;
294		attrX = softwareRenderer->sgbPacket[2];
295		if (softwareRenderer->sgbPacket[1] & 0x40) {
296			if (attrX > GB_VIDEO_VERTICAL_PIXELS / 8) {
297				attrX = GB_VIDEO_VERTICAL_PIXELS / 8;
298			}
299			int j;
300			for (j = 0; j < attrX; ++j) {
301				for (i = 0; i < GB_VIDEO_HORIZONTAL_PIXELS / 8; ++i) {
302					_setAttribute(renderer->sgbAttributes, i, j, pBefore);
303				}
304			}
305			if (attrX < GB_VIDEO_VERTICAL_PIXELS / 8) {
306				for (i = 0; i < GB_VIDEO_HORIZONTAL_PIXELS / 8; ++i) {
307					_setAttribute(renderer->sgbAttributes, i, attrX, pDiv);
308				}
309
310			}
311			for (; j < GB_VIDEO_VERTICAL_PIXELS / 8; ++j) {
312				for (i = 0; i < GB_VIDEO_HORIZONTAL_PIXELS / 8; ++i) {
313					_setAttribute(renderer->sgbAttributes, i, j, pAfter);
314				}
315			}
316		} else {
317			if (attrX > GB_VIDEO_HORIZONTAL_PIXELS / 8) {
318				attrX = GB_VIDEO_HORIZONTAL_PIXELS / 8;
319			}
320			int j;
321			for (j = 0; j < attrX; ++j) {
322				for (i = 0; i < GB_VIDEO_HORIZONTAL_PIXELS / 8; ++i) {
323					_setAttribute(renderer->sgbAttributes, j, i, pBefore);
324				}
325			}
326			if (attrX < GB_VIDEO_HORIZONTAL_PIXELS / 8) {
327				for (i = 0; i < GB_VIDEO_VERTICAL_PIXELS / 8; ++i) {
328					_setAttribute(renderer->sgbAttributes, attrX, i, pDiv);
329				}
330
331			}
332			for (; j < GB_VIDEO_HORIZONTAL_PIXELS / 8; ++j) {
333				for (i = 0; i < GB_VIDEO_VERTICAL_PIXELS / 8; ++i) {
334					_setAttribute(renderer->sgbAttributes, j, i, pAfter);
335				}
336			}
337		}
338		break;
339	case SGB_ATTR_CHR:
340		attrX = softwareRenderer->sgbPacket[1];
341		attrY = softwareRenderer->sgbPacket[2];
342		if (attrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) {
343			attrX = 0;
344		}
345		if (attrY >= GB_VIDEO_VERTICAL_PIXELS / 8) {
346			attrY = 0;
347		}
348		sets = softwareRenderer->sgbPacket[3];
349		sets |= softwareRenderer->sgbPacket[4] << 8;
350		attrDirection = softwareRenderer->sgbPacket[5];
351		i = 6;
352		for (; i < (softwareRenderer->sgbCommandHeader & 7) << 4 && sets; ++i) {
353			int j;
354			for (j = 0; j < 4 && sets; ++j, --sets) {
355				uint8_t p = softwareRenderer->sgbPacket[i] >> (6 - j * 2);
356				_setAttribute(renderer->sgbAttributes, attrX, attrY, p & 3);
357				if (attrDirection) {
358					++attrY;
359					if (attrY >= GB_VIDEO_VERTICAL_PIXELS / 8) {
360						attrY = 0;
361						++attrX;
362					}
363					if (attrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) {
364						attrX = 0;
365					}
366				} else {
367					++attrX;
368					if (attrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) {
369						attrX = 0;
370						++attrY;
371					}
372					if (attrY >= GB_VIDEO_VERTICAL_PIXELS / 8) {
373						attrY = 0;
374					}
375				}
376			}
377		}
378
379		break;
380	case SGB_ATRC_EN:
381	case SGB_MASK_EN:
382		if (softwareRenderer->sgbBorders && !renderer->sgbRenderMode) {
383			_regenerateSGBBorder(softwareRenderer);
384		}
385	}
386}
387
388static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) {
389	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
390	color_t color = mColorFrom555(value);
391	if (softwareRenderer->model == GB_MODEL_SGB) {
392		if (index < 0x10 && index && !(index & 3)) {
393			color = softwareRenderer->palette[0];
394		} else if (index >= 0x40 && !(index & 0xF)) {
395			color = softwareRenderer->palette[0];
396		}
397	}
398	softwareRenderer->palette[index] = color;
399	if (renderer->cache) {
400		mCacheSetWritePalette(renderer->cache, index, color);
401	}
402
403	if (softwareRenderer->model == GB_MODEL_SGB && !index && GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
404		renderer->writePalette(renderer, 0x04, value);
405		renderer->writePalette(renderer, 0x08, value);
406		renderer->writePalette(renderer, 0x0C, value);
407		renderer->writePalette(renderer, 0x40, value);
408		renderer->writePalette(renderer, 0x50, value);
409		renderer->writePalette(renderer, 0x60, value);
410		renderer->writePalette(renderer, 0x70, value);
411		if (softwareRenderer->sgbBorders && !renderer->sgbRenderMode) {
412			_regenerateSGBBorder(softwareRenderer);
413		}
414	}
415}
416
417static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) {
418	if (renderer->cache) {
419		mCacheSetWriteVRAM(renderer->cache, address);
420	}
421}
422
423static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam) {
424	UNUSED(renderer);
425	UNUSED(oam);
426	// Nothing to do
427}
428
429static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax) {
430	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
431	softwareRenderer->lastY = y;
432	uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP];
433	if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) {
434		maps += GB_SIZE_MAP;
435	}
436	if (softwareRenderer->d.disableBG) {
437		memset(&softwareRenderer->row[startX], 0, endX - startX);
438	}
439	if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) {
440		int wy = softwareRenderer->wy + softwareRenderer->currentWy;
441		if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && wy <= y && endX >= softwareRenderer->wx - 7) {
442			if (softwareRenderer->wx - 7 > 0 && !softwareRenderer->d.disableBG) {
443				GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, softwareRenderer->scx, softwareRenderer->scy + y);
444			}
445
446			maps = &softwareRenderer->d.vram[GB_BASE_MAP];
447			if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) {
448				maps += GB_SIZE_MAP;
449			}
450			if (!softwareRenderer->d.disableWIN) {
451				GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, y - wy);
452			}
453		} else if (!softwareRenderer->d.disableBG) {
454			GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx, softwareRenderer->scy + y);
455		}
456	} else if (!softwareRenderer->d.disableBG) {
457		memset(&softwareRenderer->row[startX], 0, endX - startX);
458	}
459
460	if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ) {
461		size_t i;
462		for (i = 0; i < oamMax; ++i) {
463			GBVideoSoftwareRendererDrawObj(softwareRenderer, &obj[i], startX, endX, y);
464		}
465	}
466
467	size_t sgbOffset = 0;
468	if (softwareRenderer->model == GB_MODEL_SGB && softwareRenderer->sgbBorders) {
469		sgbOffset = softwareRenderer->outputBufferStride * 40 + 48;
470	}
471	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + sgbOffset];
472	int x = startX;
473	int p = 0;
474	switch (softwareRenderer->d.sgbRenderMode) {
475	case 0:
476		if (softwareRenderer->model == GB_MODEL_SGB) {
477			p = softwareRenderer->d.sgbAttributes[(startX >> 5) + 5 * (y >> 3)];
478			p >>= 6 - ((x / 4) & 0x6);
479			p &= 3;
480			p <<= 2;
481		}
482		for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
483			row[x] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & 0x7F]];
484		}
485		for (; x + 7 < (endX & ~7); x += 8) {
486			if (softwareRenderer->model == GB_MODEL_SGB) {
487				p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)];
488				p >>= 6 - ((x / 4) & 0x6);
489				p &= 3;
490				p <<= 2;
491			}
492			row[x + 0] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & 0x7F]];
493			row[x + 1] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 1] & 0x7F]];
494			row[x + 2] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 2] & 0x7F]];
495			row[x + 3] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 3] & 0x7F]];
496			row[x + 4] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 4] & 0x7F]];
497			row[x + 5] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 5] & 0x7F]];
498			row[x + 6] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 6] & 0x7F]];
499			row[x + 7] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 7] & 0x7F]];
500		}
501		if (softwareRenderer->model == GB_MODEL_SGB) {
502			p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)];
503			p >>= 6 - ((x / 4) & 0x6);
504			p &= 3;
505			p <<= 2;
506		}
507		for (; x < endX; ++x) {
508			row[x] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & 0x7F]];
509		}
510		break;
511	case 1:
512		break;
513	case 2:
514		for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
515			row[x] = 0;
516		}
517		for (; x + 7 < (endX & ~7); x += 8) {
518			row[x] = 0;
519			row[x + 1] = 0;
520			row[x + 2] = 0;
521			row[x + 3] = 0;
522			row[x + 4] = 0;
523			row[x + 5] = 0;
524			row[x + 6] = 0;
525			row[x + 7] = 0;
526		}
527		for (; x < endX; ++x) {
528			row[x] = 0;
529		}
530		break;
531	case 3:
532		for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
533			row[x] = softwareRenderer->palette[0];
534		}
535		for (; x + 7 < (endX & ~7); x += 8) {
536			row[x] = softwareRenderer->palette[0];
537			row[x + 1] = softwareRenderer->palette[0];
538			row[x + 2] = softwareRenderer->palette[0];
539			row[x + 3] = softwareRenderer->palette[0];
540			row[x + 4] = softwareRenderer->palette[0];
541			row[x + 5] = softwareRenderer->palette[0];
542			row[x + 6] = softwareRenderer->palette[0];
543			row[x + 7] = softwareRenderer->palette[0];
544		}
545		for (; x < endX; ++x) {
546			row[x] = softwareRenderer->palette[0];
547		}
548		break;
549	}
550}
551
552static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) {
553	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
554
555	if (softwareRenderer->sgbTransfer == 1) {
556		size_t offset = 2 * ((y & 7) + (y >> 3) * GB_VIDEO_HORIZONTAL_PIXELS);
557		if (offset >= 0x1000) {
558			return;
559		}
560		uint8_t* buffer = NULL;
561		switch (softwareRenderer->sgbCommandHeader >> 3) {
562		case SGB_PAL_TRN:
563			buffer = renderer->sgbPalRam;
564			break;
565		case SGB_CHR_TRN:
566			buffer = &renderer->sgbCharRam[SGB_SIZE_CHAR_RAM / 2 * (softwareRenderer->sgbPacket[1] & 1)];
567			break;
568		case SGB_PCT_TRN:
569			buffer = renderer->sgbMapRam;
570			break;
571		case SGB_ATTR_TRN:
572			buffer = renderer->sgbAttributeFiles;
573			break;
574		default:
575			break;
576		}
577		if (buffer) {
578			int i;
579			for (i = 0; i < GB_VIDEO_HORIZONTAL_PIXELS; i += 8) {
580				if (UNLIKELY(offset + (i << 1) + 1 >= 0x1000)) {
581					break;
582				}
583				uint8_t hi = 0;
584				uint8_t lo = 0;
585				hi |= (softwareRenderer->row[i + 0] & 0x2) << 6;
586				lo |= (softwareRenderer->row[i + 0] & 0x1) << 7;
587				hi |= (softwareRenderer->row[i + 1] & 0x2) << 5;
588				lo |= (softwareRenderer->row[i + 1] & 0x1) << 6;
589				hi |= (softwareRenderer->row[i + 2] & 0x2) << 4;
590				lo |= (softwareRenderer->row[i + 2] & 0x1) << 5;
591				hi |= (softwareRenderer->row[i + 3] & 0x2) << 3;
592				lo |= (softwareRenderer->row[i + 3] & 0x1) << 4;
593				hi |= (softwareRenderer->row[i + 4] & 0x2) << 2;
594				lo |= (softwareRenderer->row[i + 4] & 0x1) << 3;
595				hi |= (softwareRenderer->row[i + 5] & 0x2) << 1;
596				lo |= (softwareRenderer->row[i + 5] & 0x1) << 2;
597				hi |= (softwareRenderer->row[i + 6] & 0x2) << 0;
598				lo |= (softwareRenderer->row[i + 6] & 0x1) << 1;
599				hi |= (softwareRenderer->row[i + 7] & 0x2) >> 1;
600				lo |= (softwareRenderer->row[i + 7] & 0x1) >> 0;
601				buffer[offset + (i << 1) + 0] = lo;
602				buffer[offset + (i << 1) + 1] = hi;
603			}
604		}
605	}
606}
607
608static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) {
609	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
610
611	if (softwareRenderer->temporaryBuffer) {
612		mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
613		softwareRenderer->temporaryBuffer = 0;
614	}
615	if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
616		_clearScreen(softwareRenderer);
617	}
618	if (softwareRenderer->model == GB_MODEL_SGB) {
619		switch (softwareRenderer->sgbCommandHeader >> 3) {
620		case SGB_PAL_SET:
621		case SGB_ATTR_SET:
622			if (softwareRenderer->sgbPacket[1] & 0x40) {
623				renderer->sgbRenderMode = 0;
624			}
625			break;
626		case SGB_PAL_TRN:
627		case SGB_CHR_TRN:
628		case SGB_PCT_TRN:
629			if (softwareRenderer->sgbTransfer > 0 && softwareRenderer->sgbBorders && !renderer->sgbRenderMode) {
630				// Make sure every buffer sees this if we're multibuffering
631				_regenerateSGBBorder(softwareRenderer);
632			}
633			// Fall through
634		case SGB_ATTR_TRN:
635			++softwareRenderer->sgbTransfer;
636			if (softwareRenderer->sgbTransfer == 5) {
637				softwareRenderer->sgbCommandHeader = 0;
638			}
639			break;
640		default:
641			break;
642		}
643	}
644	softwareRenderer->lastY = GB_VIDEO_VERTICAL_PIXELS;
645	softwareRenderer->currentWy = 0;
646	softwareRenderer->hasWindow = false;
647}
648
649static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy) {
650	uint8_t* data = renderer->d.vram;
651	uint8_t* attr = &maps[GB_SIZE_VRAM_BANK0];
652	if (!GBRegisterLCDCIsTileData(renderer->lcdc)) {
653		data += 0x1000;
654	}
655	int topY = ((sy >> 3) & 0x1F) * 0x20;
656	int bottomY = sy & 7;
657	if (startX < 0) {
658		startX = 0;
659	}
660	int x;
661	if ((startX + sx) & 7) {
662		int startX2 = startX + 8 - ((startX + sx) & 7);
663		for (x = startX; x < startX2; ++x) {
664			uint8_t* localData = data;
665			int localY = bottomY;
666			int topX = ((x + sx) >> 3) & 0x1F;
667			int bottomX = 7 - ((x + sx) & 7);
668			int bgTile;
669			if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
670				bgTile = maps[topX + topY];
671			} else {
672				bgTile = ((int8_t*) maps)[topX + topY];
673			}
674			int p = 0;
675			if (renderer->model >= GB_MODEL_CGB) {
676				GBObjAttributes attrs = attr[topX + topY];
677				p = GBObjAttributesGetCGBPalette(attrs) * 4;
678				if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
679					p |= 0x80;
680				}
681				if (GBObjAttributesIsBank(attrs)) {
682					localData += GB_SIZE_VRAM_BANK0;
683				}
684				if (GBObjAttributesIsYFlip(attrs)) {
685					localY = 7 - bottomY;
686				}
687				if (GBObjAttributesIsXFlip(attrs)) {
688					bottomX = 7 - bottomX;
689				}
690			}
691			uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
692			uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
693			tileDataUpper >>= bottomX;
694			tileDataLower >>= bottomX;
695			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
696		}
697		startX = startX2;
698	}
699	for (x = startX; x < endX; x += 8) {
700		uint8_t* localData = data;
701		int localY = bottomY;
702		int topX = ((x + sx) >> 3) & 0x1F;
703		int bgTile;
704		if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
705			bgTile = maps[topX + topY];
706		} else {
707			bgTile = ((int8_t*) maps)[topX + topY];
708		}
709		int p = 0;
710		if (renderer->model >= GB_MODEL_CGB) {
711			GBObjAttributes attrs = attr[topX + topY];
712			p = GBObjAttributesGetCGBPalette(attrs) * 4;
713			if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
714				p |= 0x80;
715			}
716			if (GBObjAttributesIsBank(attrs)) {
717				localData += GB_SIZE_VRAM_BANK0;
718			}
719			if (GBObjAttributesIsYFlip(attrs)) {
720				localY = 7 - bottomY;
721			}
722			if (GBObjAttributesIsXFlip(attrs)) {
723				uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
724				uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
725				renderer->row[x + 0] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
726				renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
727				renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
728				renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
729				renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
730				renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
731				renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
732				renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
733				continue;
734			}
735		}
736		uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
737		uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
738		renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
739		renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
740		renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
741		renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
742		renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
743		renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
744		renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
745		renderer->row[x + 0] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
746	}
747}
748
749static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y) {
750	int ix = obj->x - 8;
751	if (endX < ix || startX >= ix + 8) {
752		return;
753	}
754	if (obj->x < endX) {
755		endX = obj->x;
756	}
757	if (obj->x - 8 > startX) {
758		startX = obj->x - 8;
759	}
760	if (startX < 0) {
761		startX = 0;
762	}
763	uint8_t* data = renderer->d.vram;
764	int tileOffset = 0;
765	int bottomY;
766	if (GBObjAttributesIsYFlip(obj->attr)) {
767		bottomY = 7 - ((y - obj->y - 16) & 7);
768		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y < -8) {
769			++tileOffset;
770		}
771	} else {
772		bottomY = (y - obj->y - 16) & 7;
773		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y >= -8) {
774			++tileOffset;
775		}
776	}
777	if (GBRegisterLCDCIsObjSize(renderer->lcdc) && obj->tile & 1) {
778		--tileOffset;
779	}
780	uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0x63 : 0x60;
781	uint8_t mask2 = GBObjAttributesIsPriority(obj->attr) ? 0 : 0x83;
782	int p;
783	if (renderer->model >= GB_MODEL_CGB) {
784		p = (GBObjAttributesGetCGBPalette(obj->attr) + 8) * 4;
785		if (GBObjAttributesIsBank(obj->attr)) {
786			data += GB_SIZE_VRAM_BANK0;
787		}
788		if (!GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
789			mask = 0x60;
790			mask2 = 0x83;
791		}
792	} else {
793		p = (GBObjAttributesGetPalette(obj->attr) + 8) * 4;
794	}
795	int bottomX;
796	int x = startX;
797	if ((x - obj->x) & 7) {
798		for (; x < endX; ++x) {
799			if (GBObjAttributesIsXFlip(obj->attr)) {
800				bottomX = (x - obj->x) & 7;
801			} else {
802				bottomX = 7 - ((x - obj->x) & 7);
803			}
804			int objTile = obj->tile + tileOffset;
805			uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
806			uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
807			tileDataUpper >>= bottomX;
808			tileDataLower >>= bottomX;
809			color_t current = renderer->row[x];
810			if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
811				renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
812			}
813		}
814	} else if (GBObjAttributesIsXFlip(obj->attr)) {
815		int objTile = obj->tile + tileOffset;
816		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
817		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
818		color_t current;
819		current = renderer->row[x];
820		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
821			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
822		}
823		current = renderer->row[x + 1];
824		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
825			renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
826		}
827		current = renderer->row[x + 2];
828		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
829			renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
830		}
831		current = renderer->row[x + 3];
832		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
833			renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
834		}
835		current = renderer->row[x + 4];
836		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
837			renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
838		}
839		current = renderer->row[x + 5];
840		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
841			renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
842		}
843		current = renderer->row[x + 6];
844		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
845			renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
846		}
847		current = renderer->row[x + 7];
848		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
849			renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
850		}
851	} else {
852		int objTile = obj->tile + tileOffset;
853		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
854		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
855		color_t current;
856		current = renderer->row[x + 7];
857		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
858			renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
859		}
860		current = renderer->row[x + 6];
861		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
862			renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
863		}
864		current = renderer->row[x + 5];
865		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
866			renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
867		}
868		current = renderer->row[x + 4];
869		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
870			renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
871		}
872		current = renderer->row[x + 3];
873		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
874			renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
875		}
876		current = renderer->row[x + 2];
877		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
878			renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
879		}
880		current = renderer->row[x + 1];
881		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
882			renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
883		}
884		current = renderer->row[x];
885		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
886			renderer->row[x] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
887		}
888	}
889}
890
891static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) {
892	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
893	*stride = softwareRenderer->outputBufferStride;
894	*pixels = softwareRenderer->outputBuffer;
895}
896
897static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels) {
898	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
899	// TODO: Share with GBAVideoSoftwareRendererGetPixels
900
901	const color_t* colorPixels = pixels;
902	unsigned i;
903	for (i = 0; i < GB_VIDEO_VERTICAL_PIXELS; ++i) {
904		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], GB_VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
905	}
906}