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] = value & 3;
304 softwareRenderer->lookup[PAL_HIGHLIGHT_BG + 1] = (value >> 2) & 3;
305 softwareRenderer->lookup[PAL_HIGHLIGHT_BG + 2] = (value >> 4) & 3;
306 softwareRenderer->lookup[PAL_HIGHLIGHT_BG + 3] = (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] = value & 3;
314 softwareRenderer->lookup[PAL_HIGHLIGHT_OBJ + 1] = (value >> 2) & 3;
315 softwareRenderer->lookup[PAL_HIGHLIGHT_OBJ + 2] = (value >> 4) & 3;
316 softwareRenderer->lookup[PAL_HIGHLIGHT_OBJ + 3] = (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] = value & 3;
324 softwareRenderer->lookup[PAL_HIGHLIGHT_OBJ + 5] = (value >> 2) & 3;
325 softwareRenderer->lookup[PAL_HIGHLIGHT_OBJ + 6] = (value >> 4) & 3;
326 softwareRenderer->lookup[PAL_HIGHLIGHT_OBJ + 7] = (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 uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP];
569 if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) {
570 maps += GB_SIZE_MAP;
571 }
572 if (softwareRenderer->d.disableBG) {
573 memset(&softwareRenderer->row[startX], 0, endX - startX);
574 }
575 if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) {
576 int wy = softwareRenderer->wy + softwareRenderer->currentWy;
577 int wx = softwareRenderer->wx + softwareRenderer->currentWx - 7;
578 if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && wy == y && wx <= endX) {
579 softwareRenderer->hasWindow = true;
580 }
581 if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->hasWindow && wx <= endX && !softwareRenderer->d.disableWIN) {
582 if (wx > 0 && !softwareRenderer->d.disableBG) {
583 GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, wx, softwareRenderer->scx - softwareRenderer->offsetScx, softwareRenderer->scy + y - softwareRenderer->offsetScy, renderer->highlightBG);
584 }
585
586 maps = &softwareRenderer->d.vram[GB_BASE_MAP];
587 if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) {
588 maps += GB_SIZE_MAP;
589 }
590 GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, wx, endX, -wx - softwareRenderer->offsetWx, y - wy - softwareRenderer->offsetWy, renderer->highlightWIN);
591 } else if (!softwareRenderer->d.disableBG) {
592 GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx - softwareRenderer->offsetScx, softwareRenderer->scy + y - softwareRenderer->offsetScy, renderer->highlightBG);
593 }
594 } else if (!softwareRenderer->d.disableBG) {
595 memset(&softwareRenderer->row[startX], 0, endX - startX);
596 }
597
598 if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ) {
599 if (startX == 0) {
600 _cleanOAM(softwareRenderer, y);
601 }
602 int i;
603 for (i = 0; i < softwareRenderer->objMax; ++i) {
604 GBVideoSoftwareRendererDrawObj(softwareRenderer, &softwareRenderer->obj[i], startX, endX, y);
605 }
606 }
607
608 unsigned highlightAmount = (renderer->highlightAmount + 6) >> 4;
609 if (softwareRenderer->lastHighlightAmount != highlightAmount) {
610 softwareRenderer->lastHighlightAmount = highlightAmount;
611 int i;
612 for (i = 0; i < PAL_SGB_BORDER; ++i) {
613 if (i >= PAL_OBJ && (i & 3) == 0) {
614 continue;
615 }
616 softwareRenderer->palette[i + PAL_HIGHLIGHT] = mColorMix5Bit(0x10 - highlightAmount, softwareRenderer->palette[i], highlightAmount, renderer->highlightColor);
617 }
618 }
619
620 size_t sgbOffset = 0;
621 if (softwareRenderer->model & GB_MODEL_SGB && softwareRenderer->sgbBorders) {
622 sgbOffset = softwareRenderer->outputBufferStride * 40 + 48;
623 }
624 color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + sgbOffset];
625 int x = startX;
626 int p = 0;
627 switch (softwareRenderer->d.sgbRenderMode) {
628 case 0:
629 if (softwareRenderer->model & GB_MODEL_SGB) {
630 p = softwareRenderer->d.sgbAttributes[(startX >> 5) + 5 * (y >> 3)];
631 p >>= 6 - ((x / 4) & 0x6);
632 p &= 3;
633 p <<= 2;
634 }
635 for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
636 row[x] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & OBJ_PRIO_MASK]];
637 }
638 for (; x + 7 < (endX & ~7); x += 8) {
639 if (softwareRenderer->model & GB_MODEL_SGB) {
640 p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)];
641 p >>= 6 - ((x / 4) & 0x6);
642 p &= 3;
643 p <<= 2;
644 }
645 row[x + 0] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & OBJ_PRIO_MASK]];
646 row[x + 1] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 1] & OBJ_PRIO_MASK]];
647 row[x + 2] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 2] & OBJ_PRIO_MASK]];
648 row[x + 3] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 3] & OBJ_PRIO_MASK]];
649 row[x + 4] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 4] & OBJ_PRIO_MASK]];
650 row[x + 5] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 5] & OBJ_PRIO_MASK]];
651 row[x + 6] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 6] & OBJ_PRIO_MASK]];
652 row[x + 7] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 7] & OBJ_PRIO_MASK]];
653 }
654 if (softwareRenderer->model & GB_MODEL_SGB) {
655 p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)];
656 p >>= 6 - ((x / 4) & 0x6);
657 p &= 3;
658 p <<= 2;
659 }
660 for (; x < endX; ++x) {
661 row[x] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & OBJ_PRIO_MASK]];
662 }
663 break;
664 case 1:
665 break;
666 case 2:
667 for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
668 row[x] = 0;
669 }
670 for (; x + 7 < (endX & ~7); x += 8) {
671 row[x] = 0;
672 row[x + 1] = 0;
673 row[x + 2] = 0;
674 row[x + 3] = 0;
675 row[x + 4] = 0;
676 row[x + 5] = 0;
677 row[x + 6] = 0;
678 row[x + 7] = 0;
679 }
680 for (; x < endX; ++x) {
681 row[x] = 0;
682 }
683 break;
684 case 3:
685 for (; x < ((startX + 7) & ~7) && x < endX; ++x) {
686 row[x] = softwareRenderer->palette[0];
687 }
688 for (; x + 7 < (endX & ~7); x += 8) {
689 row[x] = softwareRenderer->palette[0];
690 row[x + 1] = softwareRenderer->palette[0];
691 row[x + 2] = softwareRenderer->palette[0];
692 row[x + 3] = softwareRenderer->palette[0];
693 row[x + 4] = softwareRenderer->palette[0];
694 row[x + 5] = softwareRenderer->palette[0];
695 row[x + 6] = softwareRenderer->palette[0];
696 row[x + 7] = softwareRenderer->palette[0];
697 }
698 for (; x < endX; ++x) {
699 row[x] = softwareRenderer->palette[0];
700 }
701 break;
702 }
703}
704
705static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) {
706 struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
707
708 softwareRenderer->lastX = 0;
709 softwareRenderer->currentWx = 0;
710
711 if (softwareRenderer->sgbTransfer == 1) {
712 size_t offset = 2 * ((y & 7) + (y >> 3) * GB_VIDEO_HORIZONTAL_PIXELS);
713 if (offset >= 0x1000) {
714 return;
715 }
716 uint8_t* buffer = NULL;
717 switch (softwareRenderer->sgbCommandHeader >> 3) {
718 case SGB_PAL_TRN:
719 buffer = renderer->sgbPalRam;
720 break;
721 case SGB_CHR_TRN:
722 buffer = &renderer->sgbCharRam[SGB_SIZE_CHAR_RAM / 2 * (softwareRenderer->sgbPacket[1] & 1)];
723 break;
724 case SGB_PCT_TRN:
725 buffer = renderer->sgbMapRam;
726 break;
727 case SGB_ATTR_TRN:
728 buffer = renderer->sgbAttributeFiles;
729 break;
730 default:
731 break;
732 }
733 if (buffer) {
734 int i;
735 for (i = 0; i < GB_VIDEO_HORIZONTAL_PIXELS; i += 8) {
736 if (UNLIKELY(offset + (i << 1) + 1 >= 0x1000)) {
737 break;
738 }
739 uint8_t hi = 0;
740 uint8_t lo = 0;
741 hi |= (softwareRenderer->row[i + 0] & 0x2) << 6;
742 lo |= (softwareRenderer->row[i + 0] & 0x1) << 7;
743 hi |= (softwareRenderer->row[i + 1] & 0x2) << 5;
744 lo |= (softwareRenderer->row[i + 1] & 0x1) << 6;
745 hi |= (softwareRenderer->row[i + 2] & 0x2) << 4;
746 lo |= (softwareRenderer->row[i + 2] & 0x1) << 5;
747 hi |= (softwareRenderer->row[i + 3] & 0x2) << 3;
748 lo |= (softwareRenderer->row[i + 3] & 0x1) << 4;
749 hi |= (softwareRenderer->row[i + 4] & 0x2) << 2;
750 lo |= (softwareRenderer->row[i + 4] & 0x1) << 3;
751 hi |= (softwareRenderer->row[i + 5] & 0x2) << 1;
752 lo |= (softwareRenderer->row[i + 5] & 0x1) << 2;
753 hi |= (softwareRenderer->row[i + 6] & 0x2) << 0;
754 lo |= (softwareRenderer->row[i + 6] & 0x1) << 1;
755 hi |= (softwareRenderer->row[i + 7] & 0x2) >> 1;
756 lo |= (softwareRenderer->row[i + 7] & 0x1) >> 0;
757 buffer[offset + (i << 1) + 0] = lo;
758 buffer[offset + (i << 1) + 1] = hi;
759 }
760 }
761 }
762}
763
764static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) {
765 struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
766
767 if (softwareRenderer->temporaryBuffer) {
768 mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4);
769 softwareRenderer->temporaryBuffer = 0;
770 }
771 if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
772 _clearScreen(softwareRenderer);
773 }
774 if (softwareRenderer->model & GB_MODEL_SGB) {
775 switch (softwareRenderer->sgbCommandHeader >> 3) {
776 case SGB_PAL_SET:
777 case SGB_ATTR_SET:
778 if (softwareRenderer->sgbPacket[1] & 0x40) {
779 renderer->sgbRenderMode = 0;
780 }
781 break;
782 case SGB_PAL_TRN:
783 case SGB_CHR_TRN:
784 case SGB_PCT_TRN:
785 case SGB_ATRC_EN:
786 case SGB_MASK_EN:
787 if (softwareRenderer->sgbBorders && !renderer->sgbRenderMode) {
788 // Make sure every buffer sees this if we're multibuffering
789 _regenerateSGBBorder(softwareRenderer);
790 }
791 // Fall through
792 case SGB_ATTR_TRN:
793 ++softwareRenderer->sgbTransfer;
794 if (softwareRenderer->sgbTransfer == 5) {
795 softwareRenderer->sgbCommandHeader = 0;
796 }
797 break;
798 default:
799 break;
800 }
801 }
802 softwareRenderer->lastY = GB_VIDEO_VERTICAL_PIXELS;
803 softwareRenderer->lastX = 0;
804 softwareRenderer->currentWy = 0;
805 softwareRenderer->currentWx = 0;
806 softwareRenderer->hasWindow = false;
807}
808
809static void GBVideoSoftwareRendererEnableSGBBorder(struct GBVideoRenderer* renderer, bool enable) {
810 struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
811 if (softwareRenderer->model & GB_MODEL_SGB) {
812 if (enable == softwareRenderer->sgbBorders) {
813 return;
814 }
815 softwareRenderer->sgbBorders = enable;
816 if (softwareRenderer->sgbBorders && !renderer->sgbRenderMode) {
817 _regenerateSGBBorder(softwareRenderer);
818 }
819 }
820}
821
822static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy, bool highlight) {
823 uint8_t* data = renderer->d.vram;
824 uint8_t* attr = &maps[GB_SIZE_VRAM_BANK0];
825 if (!GBRegisterLCDCIsTileData(renderer->lcdc)) {
826 data += 0x1000;
827 }
828 int topY = ((sy >> 3) & 0x1F) * 0x20;
829 int bottomY = sy & 7;
830 if (startX < 0) {
831 startX = 0;
832 }
833 int x;
834 if ((startX + sx) & 7) {
835 int startX2 = startX + 8 - ((startX + sx) & 7);
836 for (x = startX; x < startX2; ++x) {
837 uint8_t* localData = data;
838 int localY = bottomY;
839 int topX = ((x + sx) >> 3) & 0x1F;
840 int bottomX = 7 - ((x + sx) & 7);
841 int bgTile;
842 if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
843 bgTile = maps[topX + topY];
844 } else {
845 bgTile = ((int8_t*) maps)[topX + topY];
846 }
847 int p = highlight ? PAL_HIGHLIGHT_BG : PAL_BG;
848 if (renderer->model >= GB_MODEL_CGB) {
849 GBObjAttributes attrs = attr[topX + topY];
850 p |= GBObjAttributesGetCGBPalette(attrs) * 4;
851 if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
852 p |= OBJ_PRIORITY;
853 }
854 if (GBObjAttributesIsBank(attrs)) {
855 localData += GB_SIZE_VRAM_BANK0;
856 }
857 if (GBObjAttributesIsYFlip(attrs)) {
858 localY = 7 - bottomY;
859 }
860 if (GBObjAttributesIsXFlip(attrs)) {
861 bottomX = 7 - bottomX;
862 }
863 }
864 uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
865 uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
866 tileDataUpper >>= bottomX;
867 tileDataLower >>= bottomX;
868 renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
869 }
870 startX = startX2;
871 }
872 for (x = startX; x < endX; x += 8) {
873 uint8_t* localData = data;
874 int localY = bottomY;
875 int topX = ((x + sx) >> 3) & 0x1F;
876 int bgTile;
877 if (GBRegisterLCDCIsTileData(renderer->lcdc)) {
878 bgTile = maps[topX + topY];
879 } else {
880 bgTile = ((int8_t*) maps)[topX + topY];
881 }
882 int p = highlight ? PAL_HIGHLIGHT_BG : PAL_BG;
883 if (renderer->model >= GB_MODEL_CGB) {
884 GBObjAttributes attrs = attr[topX + topY];
885 p |= GBObjAttributesGetCGBPalette(attrs) * 4;
886 if (GBObjAttributesIsPriority(attrs) && GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
887 p |= OBJ_PRIORITY;
888 }
889 if (GBObjAttributesIsBank(attrs)) {
890 localData += GB_SIZE_VRAM_BANK0;
891 }
892 if (GBObjAttributesIsYFlip(attrs)) {
893 localY = 7 - bottomY;
894 }
895 if (GBObjAttributesIsXFlip(attrs)) {
896 uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
897 uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
898 renderer->row[x + 0] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
899 renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
900 renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
901 renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
902 renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
903 renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
904 renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
905 renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
906 continue;
907 }
908 }
909 uint8_t tileDataLower = localData[(bgTile * 8 + localY) * 2];
910 uint8_t tileDataUpper = localData[(bgTile * 8 + localY) * 2 + 1];
911 renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
912 renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
913 renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
914 renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
915 renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
916 renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
917 renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
918 renderer->row[x + 0] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
919 }
920}
921
922static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBVideoRendererSprite* obj, int startX, int endX, int y) {
923 int objX = obj->obj.x + renderer->objOffsetX;
924 int ix = objX - 8;
925 if (endX < ix || startX >= ix + 8) {
926 return;
927 }
928 if (objX < endX) {
929 endX = objX;
930 }
931 if (objX - 8 > startX) {
932 startX = objX - 8;
933 }
934 if (startX < 0) {
935 startX = 0;
936 }
937 uint8_t* data = renderer->d.vram;
938 int tileOffset = 0;
939 int bottomY;
940 int objY = obj->obj.y + renderer->objOffsetY;
941 if (GBObjAttributesIsYFlip(obj->obj.attr)) {
942 bottomY = 7 - ((y - objY - 16) & 7);
943 if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - objY < -8) {
944 ++tileOffset;
945 }
946 } else {
947 bottomY = (y - objY - 16) & 7;
948 if (GBRegisterLCDCIsObjSize(renderer->lcdc) && y - objY >= -8) {
949 ++tileOffset;
950 }
951 }
952 if (GBRegisterLCDCIsObjSize(renderer->lcdc) && obj->obj.tile & 1) {
953 --tileOffset;
954 }
955 unsigned mask = GBObjAttributesIsPriority(obj->obj.attr) ? 0x63 : 0x60;
956 unsigned mask2 = GBObjAttributesIsPriority(obj->obj.attr) ? 0 : (OBJ_PRIORITY | 3);
957 int p = renderer->d.highlightOBJ[obj->index] ? PAL_HIGHLIGHT_OBJ : PAL_OBJ;
958 if (renderer->model >= GB_MODEL_CGB) {
959 p |= GBObjAttributesGetCGBPalette(obj->obj.attr) * 4;
960 if (GBObjAttributesIsBank(obj->obj.attr)) {
961 data += GB_SIZE_VRAM_BANK0;
962 }
963 if (!GBRegisterLCDCIsBgEnable(renderer->lcdc)) {
964 mask = 0x60;
965 mask2 = OBJ_PRIORITY | 3;
966 }
967 } else {
968 p |= (GBObjAttributesGetPalette(obj->obj.attr) + 8) * 4;
969 }
970 int bottomX;
971 int x = startX;
972 int objTile = obj->obj.tile + tileOffset;
973 if ((x - objX) & 7) {
974 for (; x < endX; ++x) {
975 if (GBObjAttributesIsXFlip(obj->obj.attr)) {
976 bottomX = (x - objX) & 7;
977 } else {
978 bottomX = 7 - ((x - objX) & 7);
979 }
980 uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
981 uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
982 tileDataUpper >>= bottomX;
983 tileDataLower >>= bottomX;
984 unsigned current = renderer->row[x];
985 if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
986 renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
987 }
988 }
989 } else if (GBObjAttributesIsXFlip(obj->obj.attr)) {
990 uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
991 uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
992 unsigned current;
993 current = renderer->row[x];
994 if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
995 renderer->row[x] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
996 }
997 current = renderer->row[x + 1];
998 if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
999 renderer->row[x + 1] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
1000 }
1001 current = renderer->row[x + 2];
1002 if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1003 renderer->row[x + 2] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
1004 }
1005 current = renderer->row[x + 3];
1006 if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1007 renderer->row[x + 3] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
1008 }
1009 current = renderer->row[x + 4];
1010 if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1011 renderer->row[x + 4] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
1012 }
1013 current = renderer->row[x + 5];
1014 if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1015 renderer->row[x + 5] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
1016 }
1017 current = renderer->row[x + 6];
1018 if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1019 renderer->row[x + 6] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
1020 }
1021 current = renderer->row[x + 7];
1022 if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1023 renderer->row[x + 7] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
1024 }
1025 } else {
1026 uint8_t tileDataLower = data[(objTile * 8 + bottomY) * 2];
1027 uint8_t tileDataUpper = data[(objTile * 8 + bottomY) * 2 + 1];
1028 unsigned current;
1029 current = renderer->row[x + 7];
1030 if (((tileDataUpper | tileDataLower) & 1) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1031 renderer->row[x + 7] = p | ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
1032 }
1033 current = renderer->row[x + 6];
1034 if (((tileDataUpper | tileDataLower) & 2) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1035 renderer->row[x + 6] = p | (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
1036 }
1037 current = renderer->row[x + 5];
1038 if (((tileDataUpper | tileDataLower) & 4) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1039 renderer->row[x + 5] = p | ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
1040 }
1041 current = renderer->row[x + 4];
1042 if (((tileDataUpper | tileDataLower) & 8) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1043 renderer->row[x + 4] = p | ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
1044 }
1045 current = renderer->row[x + 3];
1046 if (((tileDataUpper | tileDataLower) & 16) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1047 renderer->row[x + 3] = p | ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
1048 }
1049 current = renderer->row[x + 2];
1050 if (((tileDataUpper | tileDataLower) & 32) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1051 renderer->row[x + 2] = p | ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
1052 }
1053 current = renderer->row[x + 1];
1054 if (((tileDataUpper | tileDataLower) & 64) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1055 renderer->row[x + 1] = p | ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
1056 }
1057 current = renderer->row[x];
1058 if (((tileDataUpper | tileDataLower) & 128) && !(current & mask) && (current & mask2) <= OBJ_PRIORITY) {
1059 renderer->row[x] = p | ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
1060 }
1061 }
1062}
1063
1064static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) {
1065 struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
1066 *stride = softwareRenderer->outputBufferStride;
1067 *pixels = softwareRenderer->outputBuffer;
1068}
1069
1070static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels) {
1071 struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
1072 // TODO: Share with GBAVideoSoftwareRendererGetPixels
1073
1074 const color_t* colorPixels = pixels;
1075 unsigned i;
1076 for (i = 0; i < GB_VIDEO_VERTICAL_PIXELS; ++i) {
1077 memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], GB_VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
1078 }
1079}