src/gba/renderers/video-software.c (view raw)
1#include "video-software.h"
2
3#include "gba.h"
4#include "gba-io.h"
5
6#include <stdlib.h>
7#include <string.h>
8
9static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
10static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
11static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
12static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
13static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
14
15static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
16static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
17static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
18
19static void _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, uint16_t color, struct PixelFlags flags);
20static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
21static void _drawTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y);
22static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y);
23
24static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
25static inline uint16_t _brighten(uint16_t color, int y);
26static inline uint16_t _darken(uint16_t color, int y);
27static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB);
28
29static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer);
30static int _backgroundComparator(const void* a, const void* b);
31
32void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
33 renderer->d.init = GBAVideoSoftwareRendererInit;
34 renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
35 renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
36 renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
37 renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
38
39 renderer->d.turbo = 0;
40 renderer->d.framesPending = 0;
41
42 renderer->sortedBg[0] = &renderer->bg[0];
43 renderer->sortedBg[1] = &renderer->bg[1];
44 renderer->sortedBg[2] = &renderer->bg[2];
45 renderer->sortedBg[3] = &renderer->bg[3];
46
47 {
48 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
49 renderer->mutex = mutex;
50 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
51 renderer->upCond = cond;
52 renderer->downCond = cond;
53 }
54}
55
56static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
57 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
58 int i;
59
60 softwareRenderer->dispcnt.packed = 0x0080;
61
62 softwareRenderer->target1Obj = 0;
63 softwareRenderer->target1Bd = 0;
64 softwareRenderer->target2Obj = 0;
65 softwareRenderer->target2Bd = 0;
66 softwareRenderer->blendEffect = BLEND_NONE;
67 memset(softwareRenderer->variantPalette, 0, sizeof(softwareRenderer->variantPalette));
68
69 softwareRenderer->blda = 0;
70 softwareRenderer->bldb = 0;
71 softwareRenderer->bldy = 0;
72
73 for (i = 0; i < 4; ++i) {
74 struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
75 bg->index = i;
76 bg->enabled = 0;
77 bg->priority = 0;
78 bg->charBase = 0;
79 bg->mosaic = 0;
80 bg->multipalette = 0;
81 bg->screenBase = 0;
82 bg->overflow = 0;
83 bg->size = 0;
84 bg->target1 = 0;
85 bg->target2 = 0;
86 bg->x = 0;
87 bg->y = 0;
88 bg->refx = 0;
89 bg->refy = 0;
90 bg->dx = 256;
91 bg->dmx = 0;
92 bg->dy = 0;
93 bg->dmy = 256;
94 bg->sx = 0;
95 bg->sy = 0;
96 }
97
98 pthread_mutex_init(&softwareRenderer->mutex, 0);
99 pthread_cond_init(&softwareRenderer->upCond, 0);
100 pthread_cond_init(&softwareRenderer->downCond, 0);
101}
102
103static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
104 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
105
106 pthread_mutex_destroy(&softwareRenderer->mutex);
107 pthread_cond_destroy(&softwareRenderer->upCond);
108 pthread_cond_destroy(&softwareRenderer->downCond);
109}
110
111static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
112 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
113 switch (address) {
114 case REG_DISPCNT:
115 value &= 0xFFFB;
116 softwareRenderer->dispcnt.packed = value;
117 GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
118 break;
119 case REG_BG0CNT:
120 value &= 0xFFCF;
121 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
122 break;
123 case REG_BG1CNT:
124 value &= 0xFFCF;
125 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
126 break;
127 case REG_BG2CNT:
128 value &= 0xFFCF;
129 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
130 break;
131 case REG_BG3CNT:
132 value &= 0xFFCF;
133 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
134 break;
135 case REG_BG0HOFS:
136 value &= 0x01FF;
137 softwareRenderer->bg[0].x = value;
138 break;
139 case REG_BG0VOFS:
140 value &= 0x01FF;
141 softwareRenderer->bg[0].y = value;
142 break;
143 case REG_BG1HOFS:
144 value &= 0x01FF;
145 softwareRenderer->bg[1].x = value;
146 break;
147 case REG_BG1VOFS:
148 value &= 0x01FF;
149 softwareRenderer->bg[1].y = value;
150 break;
151 case REG_BG2HOFS:
152 value &= 0x01FF;
153 softwareRenderer->bg[2].x = value;
154 break;
155 case REG_BG2VOFS:
156 value &= 0x01FF;
157 softwareRenderer->bg[2].y = value;
158 break;
159 case REG_BG3HOFS:
160 value &= 0x01FF;
161 softwareRenderer->bg[3].x = value;
162 break;
163 case REG_BG3VOFS:
164 value &= 0x01FF;
165 softwareRenderer->bg[3].y = value;
166 break;
167 case REG_BLDCNT:
168 GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
169 break;
170 case REG_BLDALPHA:
171 softwareRenderer->blda = value & 0x1F;
172 if (softwareRenderer->blda > 0x10) {
173 softwareRenderer->blda = 0x10;
174 }
175 softwareRenderer->bldb = (value >> 8) & 0x1F;
176 if (softwareRenderer->bldb > 0x10) {
177 softwareRenderer->bldb = 0x10;
178 }
179 break;
180 case REG_BLDY:
181 softwareRenderer->bldy = value & 0x1F;
182 if (softwareRenderer->bldy > 0x10) {
183 softwareRenderer->bldy = 0x10;
184 }
185 _updatePalettes(softwareRenderer);
186 break;
187 default:
188 GBALog(GBA_LOG_STUB, "Stub video register write: %03x", address);
189 }
190 return value;
191}
192
193static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
194 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
195 uint16_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
196 if (softwareRenderer->dispcnt.forcedBlank) {
197 for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
198 row[x] = 0x7FFF;
199 }
200 return;
201 }
202
203 memset(softwareRenderer->flags, 0, sizeof(softwareRenderer->flags));
204 softwareRenderer->row = row;
205
206 if (softwareRenderer->dispcnt.objEnable) {
207 for (int i = 0; i < 128; ++i) {
208 struct GBAObj* sprite = &renderer->oam->obj[i];
209 if (sprite->transformed) {
210 _drawTransformedSprite(softwareRenderer, &renderer->oam->tobj[i], y);
211 } else if (!sprite->disable) {
212 _drawSprite(softwareRenderer, sprite, y);
213 }
214 }
215 }
216
217 for (int i = 0; i < 4; ++i) {
218 if (softwareRenderer->sortedBg[i]->enabled) {
219 _drawBackgroundMode0(softwareRenderer, softwareRenderer->sortedBg[i], y);
220 }
221 }
222}
223
224static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
225 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
226
227 pthread_mutex_lock(&softwareRenderer->mutex);
228 renderer->framesPending++;
229 pthread_cond_broadcast(&softwareRenderer->upCond);
230 if (!renderer->turbo) {
231 pthread_cond_wait(&softwareRenderer->downCond, &softwareRenderer->mutex);
232 }
233 pthread_mutex_unlock(&softwareRenderer->mutex);
234}
235
236static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
237 renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
238 renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
239 renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
240 renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
241}
242
243static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
244 union GBARegisterBGCNT reg = { .packed = value };
245 bg->priority = reg.priority;
246 bg->charBase = reg.charBase << 14;
247 bg->mosaic = reg.mosaic;
248 bg->multipalette = reg.multipalette;
249 bg->screenBase = reg.screenBase << 11;
250 bg->overflow = reg.overflow;
251 bg->size = reg.size;
252
253 _sortBackgrounds(renderer);
254}
255
256static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
257 union {
258 struct {
259 unsigned target1Bg0 : 1;
260 unsigned target1Bg1 : 1;
261 unsigned target1Bg2 : 1;
262 unsigned target1Bg3 : 1;
263 unsigned target1Obj : 1;
264 unsigned target1Bd : 1;
265 enum BlendEffect effect : 2;
266 unsigned target2Bg0 : 1;
267 unsigned target2Bg1 : 1;
268 unsigned target2Bg2 : 1;
269 unsigned target2Bg3 : 1;
270 unsigned target2Obj : 1;
271 unsigned target2Bd : 1;
272 };
273 uint16_t packed;
274 } bldcnt = { .packed = value };
275
276 enum BlendEffect oldEffect = renderer->blendEffect;
277
278 renderer->bg[0].target1 = bldcnt.target1Bg0;
279 renderer->bg[1].target1 = bldcnt.target1Bg1;
280 renderer->bg[2].target1 = bldcnt.target1Bg2;
281 renderer->bg[3].target1 = bldcnt.target1Bg3;
282 renderer->bg[0].target2 = bldcnt.target2Bg0;
283 renderer->bg[1].target2 = bldcnt.target2Bg1;
284 renderer->bg[2].target2 = bldcnt.target2Bg2;
285 renderer->bg[3].target2 = bldcnt.target2Bg3;
286
287 renderer->blendEffect = bldcnt.effect;
288 renderer->target1Obj = bldcnt.target1Obj;
289 renderer->target1Bd = bldcnt.target1Bd;
290 renderer->target2Obj = bldcnt.target2Obj;
291 renderer->target2Bd = bldcnt.target2Bd;
292
293 if (oldEffect != renderer->blendEffect) {
294 _updatePalettes(renderer);
295 }
296}
297
298static void _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, uint16_t color, struct PixelFlags flags) {
299 struct PixelFlags currentFlags = renderer->flags[offset];
300 if (currentFlags.isSprite && flags.priority >= currentFlags.priority) {
301 if (currentFlags.target1) {
302 if (currentFlags.written && currentFlags.target2) {
303 renderer->row[offset] = _mix(renderer->blda, renderer->row[offset], renderer->bldb, renderer->spriteLayer[offset]);
304 } else if (flags.target2) {
305 renderer->row[offset] = _mix(renderer->bldb, color, renderer->blda, renderer->spriteLayer[offset]);
306 }
307 } else if (!currentFlags.written) {
308 renderer->row[offset] = renderer->spriteLayer[offset];
309 }
310 renderer->flags[offset].finalized = 1;
311 return;
312 }
313 if (renderer->blendEffect != BLEND_ALPHA) {
314 renderer->row[offset] = color;
315 renderer->flags[offset].finalized = 1;
316 } else if (renderer->blendEffect == BLEND_ALPHA) {
317 if (currentFlags.written) {
318 if (currentFlags.target1 && flags.target2) {
319 renderer->row[offset] = _mix(renderer->bldb, color, renderer->blda, renderer->row[offset]);
320 }
321 renderer->flags[offset].finalized = 1;
322 } else {
323 renderer->row[offset] = color;
324 renderer->flags[offset].target1 = flags.target1;
325 }
326 }
327 renderer->flags[offset].written = 1;
328}
329
330#define BACKGROUND_DRAW_PIXEL_16 \
331 { \
332 uint16_t color; \
333 charBase = ((background->charBase + (mapData.tile << 5)) >> 1) + (localY << 1) + ((localX >> 2) & 1); \
334 uint16_t tileData = renderer->d.vram[charBase]; \
335 tileData >>= (localX & 0x3) << 2; \
336 if (!background->target1 || renderer->blendEffect == BLEND_NONE || renderer->blendEffect == BLEND_ALPHA) { \
337 color = renderer->d.palette[(tileData & 0xF) | (mapData.palette << 4)]; \
338 } else { \
339 color = renderer->variantPalette[(tileData & 0xF) | (mapData.palette << 4)]; \
340 } \
341 if (tileData & 0xF) { \
342 _composite(renderer, outX, color, flags); \
343 } \
344 }
345
346#define BACKGROUND_DRAW_PIXEL_256 \
347 { \
348 uint16_t color; \
349 charBase = ((background->charBase + (mapData.tile << 6)) >> 1) + (localY << 2) + ((localX >> 1) & 3); \
350 uint16_t tileData = renderer->d.vram[charBase]; \
351 tileData >>= (localX & 0x1) << 3; \
352 if (!background->target1 || renderer->blendEffect == BLEND_NONE || renderer->blendEffect == BLEND_ALPHA) { \
353 color = renderer->d.palette[tileData & 0xFF]; \
354 } else if (renderer->blendEffect == BLEND_DARKEN) { \
355 color = _darken(renderer->d.palette[tileData & 0xFF], renderer->bldy); \
356 } else if (renderer->blendEffect == BLEND_BRIGHTEN) { \
357 color = _brighten(renderer->d.palette[tileData & 0xFF], renderer->bldy); \
358 } \
359 if (tileData & 0xFF) { \
360 _composite(renderer, outX, color, flags); \
361 } \
362 }
363
364#define BACKGROUND_TEXT_SELECT_CHARACTER \
365 { \
366 if (renderer->flags[outX].finalized) { \
367 continue; \
368 } \
369 localX = outX + inX; \
370 xBase = localX & 0xF8; \
371 if (background->size & 1) { \
372 xBase += (localX & 0x100) << 5; \
373 } \
374 if (oldXBase != xBase) { \
375 oldXBase = xBase; \
376 screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2); \
377 mapData.packed = renderer->d.vram[screenBase]; \
378 if (!mapData.vflip) { \
379 localY = inY & 0x7; \
380 } else { \
381 localY = 7 - (inY & 0x7); \
382 } \
383 } \
384 if (!mapData.hflip) { \
385 localX = localX & 0x7; \
386 } else { \
387 localX = 7 - (localX & 0x7); \
388 } \
389 }
390
391static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
392 int start = 0;
393 int end = VIDEO_HORIZONTAL_PIXELS;
394 int inX = start + background->x;
395 int inY = y + background->y;
396 union GBATextMapData mapData;
397
398 unsigned yBase = inY & 0xF8;
399 if (background->size & 2) {
400 yBase += inY & 0x100;
401 } else if (background->size == 3) {
402 yBase += (inY & 0x100) << 1;
403 }
404
405 int localX;
406 int localY;
407
408 unsigned xBase;
409
410 uint32_t screenBase;
411 uint32_t charBase;
412 struct PixelFlags flags = {
413 .target1 = background->target1 && renderer->blendEffect == BLEND_ALPHA,
414 .target2 = background->target2,
415 .priority = background->priority
416 };
417
418 unsigned int oldXBase = 1;
419
420 if (!background->multipalette) {
421 for (int outX = start; outX < end; ++outX) {
422 BACKGROUND_TEXT_SELECT_CHARACTER;
423 BACKGROUND_DRAW_PIXEL_16;
424 }
425 } else {
426 for (int outX = start; outX < end; ++outX) {
427 BACKGROUND_TEXT_SELECT_CHARACTER;
428 BACKGROUND_DRAW_PIXEL_256;
429 }
430 }
431}
432
433static const int _objSizes[32] = {
434 8, 8,
435 16, 16,
436 32, 32,
437 64, 64,
438 16, 8,
439 32, 8,
440 32, 16,
441 64, 32,
442 8, 16,
443 8, 32,
444 16, 32,
445 32, 64,
446 0, 0,
447 0, 0,
448 0, 0,
449 0, 0
450};
451
452static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
453 int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
454 int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
455 if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
456 return;
457 }
458 struct PixelFlags flags = {
459 .priority = sprite->priority,
460 .isSprite = 1,
461 .target1 = (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
462 .target2 = renderer->target2Obj
463 };
464 int x = sprite->x;
465 int inY = y - sprite->y;
466 if (sprite->y + height - 256 >= 0) {
467 inY += 256;
468 }
469 if (sprite->vflip) {
470 inY = height - inY - 1;
471 }
472 unsigned charBase = BASE_TILE + sprite->tile * 0x20;
473 unsigned yBase = (inY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (inY & 0x7) * 4;
474 for (int outX = x >= 0 ? x : 0; outX < x + width && outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
475 int inX = outX - x;
476 if (sprite->hflip) {
477 inX = width - inX - 1;
478 }
479 if (renderer->flags[outX].isSprite) {
480 continue;
481 }
482 unsigned xBase = (inX & ~0x7) * 4 + ((inX >> 1) & 2);
483 uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
484 tileData = (tileData >> ((inX & 3) << 2)) & 0xF;
485 if (tileData) {
486 if (!renderer->target1Obj) {
487 renderer->spriteLayer[outX] = renderer->d.palette[0x100 | tileData | (sprite->palette << 4)];
488 } else {
489 renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
490 }
491 renderer->flags[outX] = flags;
492 }
493 }
494}
495
496static void _drawTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y) {
497 int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
498 int totalWidth = width << sprite->doublesize;
499 int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
500 int totalHeight = height << sprite->doublesize;
501 if ((y < sprite->y && (sprite->y + totalHeight - 256 < 0 || y >= sprite->y + totalHeight - 256)) || y >= sprite->y + totalHeight) {
502 return;
503 }
504 struct PixelFlags flags = {
505 .priority = sprite->priority,
506 .isSprite = 1,
507 .target1 = (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
508 .target2 = renderer->target2Obj
509 };
510 int x = sprite->x;
511 unsigned charBase = BASE_TILE + sprite->tile * 0x20;
512 struct GBAOAMMatrix* mat = &renderer->d.oam->mat[sprite->matIndex];
513 for (int outX = x >= 0 ? x : 0; outX < x + totalWidth && outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
514 if (renderer->flags[outX].isSprite) {
515 continue;
516 }
517 int inY = y - sprite->y;
518 int inX = outX - x;
519 int localX = ((mat->a * (inX - (totalWidth >> 1)) + mat->b * (inY - (totalHeight >> 1))) >> 8) + (width >> 1);
520 int localY = ((mat->c * (inX - (totalWidth >> 1)) + mat->d * (inY - (totalHeight >> 1))) >> 8) + (height >> 1);
521
522 if (localX < 0 || localX >= width || localY < 0 || localY >= height) {
523 continue;
524 }
525
526 unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (localY & 0x7) * 4;
527 unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
528 uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
529 tileData = (tileData >> ((localX & 3) << 2)) & 0xF;
530 if (tileData) {
531 if (!renderer->target1Obj) {
532 renderer->spriteLayer[outX] = renderer->d.palette[0x100 | tileData | (sprite->palette << 4)];
533 } else {
534 renderer->spriteLayer[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
535 }
536 renderer->flags[outX] = flags;
537 }
538 }
539}
540
541static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
542 if (renderer->blendEffect == BLEND_BRIGHTEN) {
543 for (int i = 0; i < 512; ++i) {
544 renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
545 }
546 } else if (renderer->blendEffect == BLEND_DARKEN) {
547 for (int i = 0; i < 512; ++i) {
548 renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
549 }
550 } else {
551 for (int i = 0; i < 512; ++i) {
552 renderer->variantPalette[i] = renderer->d.palette[i];
553 }
554 }
555}
556
557static inline uint16_t _brighten(uint16_t c, int y) {
558 union GBAColor color = { .packed = c };
559 color.r = color.r + ((31 - color.r) * y) / 16;
560 color.g = color.g + ((31 - color.g) * y) / 16;
561 color.b = color.b + ((31 - color.b) * y) / 16;
562 return color.packed;
563}
564
565static inline uint16_t _darken(uint16_t c, int y) {
566 union GBAColor color = { .packed = c };
567 color.r = color.r - (color.r * y) / 16;
568 color.g = color.g - (color.g * y) / 16;
569 color.b = color.b - (color.b * y) / 16;
570 return color.packed;
571}
572
573static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
574 union GBAColor ca = { .packed = colorA };
575 union GBAColor cb = { .packed = colorB };
576
577 int r = (ca.r * weightA + cb.r * weightB) / 16;
578 ca.r = r > 0x1F ? 0x1F : r;
579
580 int g = (ca.g * weightA + cb.g * weightB) / 16;
581 ca.g = g > 0x1F ? 0x1F : g;
582
583 int b = (ca.b * weightA + cb.b * weightB) / 16;
584 ca.b = b > 0x1F ? 0x1F : b;
585
586 return ca.packed;
587}
588
589static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
590 qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
591}
592
593static int _backgroundComparator(const void* a, const void* b) {
594 const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
595 const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
596 if (bgA->priority != bgB->priority) {
597 return bgA->priority - bgB->priority;
598 } else {
599 return bgA->index - bgB->index;
600 }
601}