all repos — mgba @ 8ea524d9e61334d7d94dd12b9f8a1cd84f0b67ec

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