all repos — mgba @ 0207048679df30de14212500e8ce42a24aeaef2b

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		if (softwareRenderer->sgbBorders && !renderer->sgbRenderMode) {
382			_regenerateSGBBorder(softwareRenderer);
383		}
384		break;
385	case SGB_MASK_EN:
386		if (!renderer->sgbRenderMode) {
387			_regenerateSGBBorder(softwareRenderer);
388		}
389	}
390}
391
392static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) {
393	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
394	color_t color = mColorFrom555(value);
395	if (softwareRenderer->model == GB_MODEL_SGB) {
396		if (index < 0x10 && index && !(index & 3)) {
397			color = softwareRenderer->palette[0];
398		} else if (index >= 0x40 && !(index & 0xF)) {
399			color = softwareRenderer->palette[0];
400		}
401	}
402	softwareRenderer->palette[index] = color;
403	if (renderer->cache) {
404		mCacheSetWritePalette(renderer->cache, index, color);
405	}
406
407	if (softwareRenderer->model == GB_MODEL_SGB && !index && GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
408		renderer->writePalette(renderer, 0x04, value);
409		renderer->writePalette(renderer, 0x08, value);
410		renderer->writePalette(renderer, 0x0C, value);
411		renderer->writePalette(renderer, 0x40, value);
412		renderer->writePalette(renderer, 0x50, value);
413		renderer->writePalette(renderer, 0x60, value);
414		renderer->writePalette(renderer, 0x70, value);
415		if (!renderer->sgbRenderMode) {
416			_regenerateSGBBorder(softwareRenderer);
417		}
418	}
419}
420
421static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) {
422	if (renderer->cache) {
423		mCacheSetWriteVRAM(renderer->cache, address);
424	}
425}
426
427static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam) {
428	UNUSED(renderer);
429	UNUSED(oam);
430	// Nothing to do
431}
432
433static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax) {
434	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
435	softwareRenderer->lastY = y;
436	uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP];
437	if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) {
438		maps += GB_SIZE_MAP;
439	}
440	if (softwareRenderer->d.disableBG) {
441		memset(&softwareRenderer->row[startX], 0, endX - startX);
442	}
443	if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) {
444		int wy = softwareRenderer->wy + softwareRenderer->currentWy;
445		if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && wy <= y && endX >= softwareRenderer->wx - 7) {
446			if (softwareRenderer->wx - 7 > 0 && !softwareRenderer->d.disableBG) {
447				GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, softwareRenderer->scx, softwareRenderer->scy + y);
448			}
449
450			maps = &softwareRenderer->d.vram[GB_BASE_MAP];
451			if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) {
452				maps += GB_SIZE_MAP;
453			}
454			if (!softwareRenderer->d.disableWIN) {
455				GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, y - wy);
456			}
457		} else if (!softwareRenderer->d.disableBG) {
458			GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx, softwareRenderer->scy + y);
459		}
460	} else if (!softwareRenderer->d.disableBG) {
461		memset(&softwareRenderer->row[startX], 0, endX - startX);
462	}
463
464	if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ) {
465		size_t i;
466		for (i = 0; i < oamMax; ++i) {
467			GBVideoSoftwareRendererDrawObj(softwareRenderer, &obj[i], startX, endX, y);
468		}
469	}
470
471	size_t sgbOffset = 0;
472	if (softwareRenderer->model == GB_MODEL_SGB && softwareRenderer->sgbBorders) {
473		sgbOffset = softwareRenderer->outputBufferStride * 40 + 48;
474	}
475	color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + sgbOffset];
476	int x = startX;
477	int p = 0;
478	switch (softwareRenderer->d.sgbRenderMode) {
479	case 0:
480		if (softwareRenderer->model == GB_MODEL_SGB) {
481			p = softwareRenderer->d.sgbAttributes[(startX >> 5) + 5 * (y >> 3)];
482			p >>= 6 - ((x / 4) & 0x6);
483			p &= 3;
484			p <<= 2;
485		}
486		for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
487			row[x] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & 0x7F]];
488		}
489		for (; x + 7 < (endX & ~7); x += 8) {
490			if (softwareRenderer->model == GB_MODEL_SGB) {
491				p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)];
492				p >>= 6 - ((x / 4) & 0x6);
493				p &= 3;
494				p <<= 2;
495			}
496			row[x + 0] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & 0x7F]];
497			row[x + 1] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 1] & 0x7F]];
498			row[x + 2] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 2] & 0x7F]];
499			row[x + 3] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 3] & 0x7F]];
500			row[x + 4] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 4] & 0x7F]];
501			row[x + 5] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 5] & 0x7F]];
502			row[x + 6] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 6] & 0x7F]];
503			row[x + 7] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 7] & 0x7F]];
504		}
505		if (softwareRenderer->model == GB_MODEL_SGB) {
506			p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)];
507			p >>= 6 - ((x / 4) & 0x6);
508			p &= 3;
509			p <<= 2;
510		}
511		for (; x < endX; ++x) {
512			row[x] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & 0x7F]];
513		}
514		break;
515	case 1:
516		break;
517	case 2:
518		for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
519			row[x] = 0;
520		}
521		for (; x + 7 < (endX & ~7); x += 8) {
522			row[x] = 0;
523			row[x + 1] = 0;
524			row[x + 2] = 0;
525			row[x + 3] = 0;
526			row[x + 4] = 0;
527			row[x + 5] = 0;
528			row[x + 6] = 0;
529			row[x + 7] = 0;
530		}
531		for (; x < endX; ++x) {
532			row[x] = 0;
533		}
534		break;
535	case 3:
536		for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
537			row[x] = softwareRenderer->palette[0];
538		}
539		for (; x + 7 < (endX & ~7); x += 8) {
540			row[x] = softwareRenderer->palette[0];
541			row[x + 1] = softwareRenderer->palette[0];
542			row[x + 2] = softwareRenderer->palette[0];
543			row[x + 3] = softwareRenderer->palette[0];
544			row[x + 4] = softwareRenderer->palette[0];
545			row[x + 5] = softwareRenderer->palette[0];
546			row[x + 6] = softwareRenderer->palette[0];
547			row[x + 7] = softwareRenderer->palette[0];
548		}
549		for (; x < endX; ++x) {
550			row[x] = softwareRenderer->palette[0];
551		}
552		break;
553	}
554}
555
556static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) {
557	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
558
559	if (softwareRenderer->sgbTransfer == 1) {
560		size_t offset = 2 * ((y & 7) + (y >> 3) * GB_VIDEO_HORIZONTAL_PIXELS);
561		if (offset >= 0x1000) {
562			return;
563		}
564		uint8_t* buffer = NULL;
565		switch (softwareRenderer->sgbCommandHeader >> 3) {
566		case SGB_PAL_TRN:
567			buffer = renderer->sgbPalRam;
568			break;
569		case SGB_CHR_TRN:
570			buffer = &renderer->sgbCharRam[SGB_SIZE_CHAR_RAM / 2 * (softwareRenderer->sgbPacket[1] & 1)];
571			break;
572		case SGB_PCT_TRN:
573			buffer = renderer->sgbMapRam;
574			break;
575		case SGB_ATTR_TRN:
576			buffer = renderer->sgbAttributeFiles;
577			break;
578		default:
579			break;
580		}
581		if (buffer) {
582			int i;
583			for (i = 0; i < GB_VIDEO_HORIZONTAL_PIXELS; i += 8) {
584				if (UNLIKELY(offset + (i << 1) + 1 >= 0x1000)) {
585					break;
586				}
587				uint8_t hi = 0;
588				uint8_t lo = 0;
589				hi |= (softwareRenderer->row[i + 0] & 0x2) << 6;
590				lo |= (softwareRenderer->row[i + 0] & 0x1) << 7;
591				hi |= (softwareRenderer->row[i + 1] & 0x2) << 5;
592				lo |= (softwareRenderer->row[i + 1] & 0x1) << 6;
593				hi |= (softwareRenderer->row[i + 2] & 0x2) << 4;
594				lo |= (softwareRenderer->row[i + 2] & 0x1) << 5;
595				hi |= (softwareRenderer->row[i + 3] & 0x2) << 3;
596				lo |= (softwareRenderer->row[i + 3] & 0x1) << 4;
597				hi |= (softwareRenderer->row[i + 4] & 0x2) << 2;
598				lo |= (softwareRenderer->row[i + 4] & 0x1) << 3;
599				hi |= (softwareRenderer->row[i + 5] & 0x2) << 1;
600				lo |= (softwareRenderer->row[i + 5] & 0x1) << 2;
601				hi |= (softwareRenderer->row[i + 6] & 0x2) << 0;
602				lo |= (softwareRenderer->row[i + 6] & 0x1) << 1;
603				hi |= (softwareRenderer->row[i + 7] & 0x2) >> 1;
604				lo |= (softwareRenderer->row[i + 7] & 0x1) >> 0;
605				buffer[offset + (i << 1) + 0] = lo;
606				buffer[offset + (i << 1) + 1] = hi;
607			}
608		}
609	}
610}
611
612static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) {
613	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
614
615	if (softwareRenderer->temporaryBuffer) {
616		mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
617		softwareRenderer->temporaryBuffer = 0;
618	}
619	if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
620		_clearScreen(softwareRenderer);
621	}
622	if (softwareRenderer->model == GB_MODEL_SGB) {
623		switch (softwareRenderer->sgbCommandHeader >> 3) {
624		case SGB_PAL_SET:
625		case SGB_ATTR_SET:
626			if (softwareRenderer->sgbPacket[1] & 0x40) {
627				renderer->sgbRenderMode = 0;
628			}
629			break;
630		case SGB_PAL_TRN:
631		case SGB_CHR_TRN:
632		case SGB_PCT_TRN:
633			if (softwareRenderer->sgbTransfer > 0 && softwareRenderer->sgbBorders && !renderer->sgbRenderMode) {
634				// Make sure every buffer sees this if we're multibuffering
635				_regenerateSGBBorder(softwareRenderer);
636			}
637			// Fall through
638		case SGB_ATTR_TRN:
639			++softwareRenderer->sgbTransfer;
640			if (softwareRenderer->sgbTransfer == 5) {
641				softwareRenderer->sgbCommandHeader = 0;
642			}
643			break;
644		default:
645			break;
646		}
647	}
648	softwareRenderer->lastY = GB_VIDEO_VERTICAL_PIXELS;
649	softwareRenderer->currentWy = 0;
650	softwareRenderer->hasWindow = false;
651}
652
653static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy) {
654	uint8_t* data = renderer->d.vram;
655	uint8_t* attr = &maps[GB_SIZE_VRAM_BANK0];
656	if (!GBRegisterLCDCIsTileData(renderer->lcdc)) {
657		data += 0x1000;
658	}
659	int topY = ((sy >> 3) & 0x1F) * 0x20;
660	int bottomY = sy & 7;
661	if (startX < 0) {
662		startX = 0;
663	}
664	int x;
665	if ((startX + sx) & 7) {
666		int startX2 = startX + 8 - ((startX + sx) & 7);
667		for (x = startX; x < startX2; ++x) {
668			uint8_t* localData = data;
669			int localY = bottomY;
670			int topX = ((x + sx) >> 3) & 0x1F;
671			int bottomX = 7 - ((x + sx) & 7);
672			int bgTile;
673			if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
674				bgTile = maps[topX + topY];
675			} else {
676				bgTile = ((int8_t*) maps)[topX + topY];
677			}
678			int p = 0;
679			if (renderer->model >= GB_MODEL_CGB) {
680				GBObjAttributes attrs = attr[topX + topY];
681				p = GBObjAttributesGetCGBPalette(attrs) * 4;
682				if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
683					p |= 0x80;
684				}
685				if (GBObjAttributesIsBank(attrs)) {
686					localData += GB_SIZE_VRAM_BANK0;
687				}
688				if (GBObjAttributesIsYFlip(attrs)) {
689					localY = 7 - bottomY;
690				}
691				if (GBObjAttributesIsXFlip(attrs)) {
692					bottomX = 7 - bottomX;
693				}
694			}
695			uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
696			uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
697			tileDataUpper >>= bottomX;
698			tileDataLower >>= bottomX;
699			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
700		}
701		startX = startX2;
702	}
703	for (x = startX; x < endX; x += 8) {
704		uint8_t* localData = data;
705		int localY = bottomY;
706		int topX = ((x + sx) >> 3) & 0x1F;
707		int bgTile;
708		if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
709			bgTile = maps[topX + topY];
710		} else {
711			bgTile = ((int8_t*) maps)[topX + topY];
712		}
713		int p = 0;
714		if (renderer->model >= GB_MODEL_CGB) {
715			GBObjAttributes attrs = attr[topX + topY];
716			p = GBObjAttributesGetCGBPalette(attrs) * 4;
717			if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
718				p |= 0x80;
719			}
720			if (GBObjAttributesIsBank(attrs)) {
721				localData += GB_SIZE_VRAM_BANK0;
722			}
723			if (GBObjAttributesIsYFlip(attrs)) {
724				localY = 7 - bottomY;
725			}
726			if (GBObjAttributesIsXFlip(attrs)) {
727				uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
728				uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
729				renderer->row[x + 0] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
730				renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
731				renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
732				renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
733				renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
734				renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
735				renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
736				renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
737				continue;
738			}
739		}
740		uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
741		uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
742		renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
743		renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
744		renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
745		renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
746		renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
747		renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
748		renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
749		renderer->row[x + 0] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
750	}
751}
752
753static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y) {
754	int ix = obj->x - 8;
755	if (endX < ix || startX >= ix + 8) {
756		return;
757	}
758	if (obj->x < endX) {
759		endX = obj->x;
760	}
761	if (obj->x - 8 > startX) {
762		startX = obj->x - 8;
763	}
764	if (startX < 0) {
765		startX = 0;
766	}
767	uint8_t* data = renderer->d.vram;
768	int tileOffset = 0;
769	int bottomY;
770	if (GBObjAttributesIsYFlip(obj->attr)) {
771		bottomY = 7 - ((y - obj->y - 16) & 7);
772		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y < -8) {
773			++tileOffset;
774		}
775	} else {
776		bottomY = (y - obj->y - 16) & 7;
777		if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - obj->y >= -8) {
778			++tileOffset;
779		}
780	}
781	if (GBRegisterLCDCIsObjSize(renderer->lcdc) && obj->tile & 1) {
782		--tileOffset;
783	}
784	uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0x63 : 0x60;
785	uint8_t mask2 = GBObjAttributesIsPriority(obj->attr) ? 0 : 0x83;
786	int p;
787	if (renderer->model >= GB_MODEL_CGB) {
788		p = (GBObjAttributesGetCGBPalette(obj->attr) + 8) * 4;
789		if (GBObjAttributesIsBank(obj->attr)) {
790			data += GB_SIZE_VRAM_BANK0;
791		}
792		if (!GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
793			mask = 0x60;
794			mask2 = 0x83;
795		}
796	} else {
797		p = (GBObjAttributesGetPalette(obj->attr) + 8) * 4;
798	}
799	int bottomX;
800	int x = startX;
801	if ((x - obj->x) & 7) {
802		for (; x < endX; ++x) {
803			if (GBObjAttributesIsXFlip(obj->attr)) {
804				bottomX = (x - obj->x) & 7;
805			} else {
806				bottomX = 7 - ((x - obj->x) & 7);
807			}
808			int objTile = obj->tile + tileOffset;
809			uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
810			uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
811			tileDataUpper >>= bottomX;
812			tileDataLower >>= bottomX;
813			color_t current = renderer->row[x];
814			if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
815				renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
816			}
817		}
818	} else if (GBObjAttributesIsXFlip(obj->attr)) {
819		int objTile = obj->tile + tileOffset;
820		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
821		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
822		color_t current;
823		current = renderer->row[x];
824		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
825			renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
826		}
827		current = renderer->row[x + 1];
828		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
829			renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
830		}
831		current = renderer->row[x + 2];
832		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
833			renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
834		}
835		current = renderer->row[x + 3];
836		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
837			renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
838		}
839		current = renderer->row[x + 4];
840		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
841			renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
842		}
843		current = renderer->row[x + 5];
844		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
845			renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
846		}
847		current = renderer->row[x + 6];
848		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
849			renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
850		}
851		current = renderer->row[x + 7];
852		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
853			renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
854		}
855	} else {
856		int objTile = obj->tile + tileOffset;
857		uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
858		uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
859		color_t current;
860		current = renderer->row[x + 7];
861		if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= 0x80) {
862			renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
863		}
864		current = renderer->row[x + 6];
865		if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= 0x80) {
866			renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
867		}
868		current = renderer->row[x + 5];
869		if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= 0x80) {
870			renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
871		}
872		current = renderer->row[x + 4];
873		if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= 0x80) {
874			renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
875		}
876		current = renderer->row[x + 3];
877		if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= 0x80) {
878			renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
879		}
880		current = renderer->row[x + 2];
881		if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= 0x80) {
882			renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
883		}
884		current = renderer->row[x + 1];
885		if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= 0x80) {
886			renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
887		}
888		current = renderer->row[x];
889		if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= 0x80) {
890			renderer->row[x] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
891		}
892	}
893}
894
895static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) {
896	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
897	*stride = softwareRenderer->outputBufferStride;
898	*pixels = softwareRenderer->outputBuffer;
899}
900
901static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels) {
902	struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
903	// TODO: Share with GBAVideoSoftwareRendererGetPixels
904
905	const color_t* colorPixels = pixels;
906	unsigned i;
907	for (i = 0; i < GB_VIDEO_VERTICAL_PIXELS; ++i) {
908		memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], GB_VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
909	}
910}