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