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