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 <string.h>
7
8static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
9static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
10static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
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 _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y);
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 uint32_t _brighten(uint32_t color, int y);
26static inline uint32_t _darken(uint32_t color, int y);
27static uint32_t _mix(int weightA, uint32_t colorA, int weightB, uint32_t colorB);
28
29void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
30 renderer->d.init = GBAVideoSoftwareRendererInit;
31 renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
32 renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
33 renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
34 renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
35 renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
36
37 renderer->d.turbo = 0;
38 renderer->d.framesPending = 0;
39 renderer->d.frameskip = 0;
40
41 {
42 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
43 renderer->mutex = mutex;
44 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
45 renderer->upCond = cond;
46 renderer->downCond = cond;
47 }
48}
49
50static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
51 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
52 int i;
53
54 softwareRenderer->dispcnt.packed = 0x0080;
55
56 softwareRenderer->target1Obj = 0;
57 softwareRenderer->target1Bd = 0;
58 softwareRenderer->target2Obj = 0;
59 softwareRenderer->target2Bd = 0;
60 softwareRenderer->blendEffect = BLEND_NONE;
61 memset(softwareRenderer->normalPalette, 0, sizeof(softwareRenderer->normalPalette));
62 memset(softwareRenderer->variantPalette, 0, sizeof(softwareRenderer->variantPalette));
63
64 softwareRenderer->blda = 0;
65 softwareRenderer->bldb = 0;
66 softwareRenderer->bldy = 0;
67
68 for (i = 0; i < 4; ++i) {
69 struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
70 bg->index = i;
71 bg->enabled = 0;
72 bg->priority = 0;
73 bg->charBase = 0;
74 bg->mosaic = 0;
75 bg->multipalette = 0;
76 bg->screenBase = 0;
77 bg->overflow = 0;
78 bg->size = 0;
79 bg->target1 = 0;
80 bg->target2 = 0;
81 bg->x = 0;
82 bg->y = 0;
83 bg->refx = 0;
84 bg->refy = 0;
85 bg->dx = 256;
86 bg->dmx = 0;
87 bg->dy = 0;
88 bg->dmy = 256;
89 bg->sx = 0;
90 bg->sy = 0;
91 }
92
93 pthread_mutex_init(&softwareRenderer->mutex, 0);
94 pthread_cond_init(&softwareRenderer->upCond, 0);
95 pthread_cond_init(&softwareRenderer->downCond, 0);
96}
97
98static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
99 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
100
101 pthread_mutex_destroy(&softwareRenderer->mutex);
102 pthread_cond_destroy(&softwareRenderer->upCond);
103 pthread_cond_destroy(&softwareRenderer->downCond);
104}
105
106static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
107 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
108 switch (address) {
109 case REG_DISPCNT:
110 value &= 0xFFFB;
111 softwareRenderer->dispcnt.packed = value;
112 GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
113 break;
114 case REG_BG0CNT:
115 value &= 0xFFCF;
116 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
117 break;
118 case REG_BG1CNT:
119 value &= 0xFFCF;
120 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
121 break;
122 case REG_BG2CNT:
123 value &= 0xFFCF;
124 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
125 break;
126 case REG_BG3CNT:
127 value &= 0xFFCF;
128 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
129 break;
130 case REG_BG0HOFS:
131 value &= 0x01FF;
132 softwareRenderer->bg[0].x = value;
133 break;
134 case REG_BG0VOFS:
135 value &= 0x01FF;
136 softwareRenderer->bg[0].y = value;
137 break;
138 case REG_BG1HOFS:
139 value &= 0x01FF;
140 softwareRenderer->bg[1].x = value;
141 break;
142 case REG_BG1VOFS:
143 value &= 0x01FF;
144 softwareRenderer->bg[1].y = value;
145 break;
146 case REG_BG2HOFS:
147 value &= 0x01FF;
148 softwareRenderer->bg[2].x = value;
149 break;
150 case REG_BG2VOFS:
151 value &= 0x01FF;
152 softwareRenderer->bg[2].y = value;
153 break;
154 case REG_BG3HOFS:
155 value &= 0x01FF;
156 softwareRenderer->bg[3].x = value;
157 break;
158 case REG_BG3VOFS:
159 value &= 0x01FF;
160 softwareRenderer->bg[3].y = value;
161 break;
162 case REG_BLDCNT:
163 GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
164 break;
165 case REG_BLDALPHA:
166 softwareRenderer->blda = value & 0x1F;
167 if (softwareRenderer->blda > 0x10) {
168 softwareRenderer->blda = 0x10;
169 }
170 softwareRenderer->bldb = (value >> 8) & 0x1F;
171 if (softwareRenderer->bldb > 0x10) {
172 softwareRenderer->bldb = 0x10;
173 }
174 break;
175 case REG_BLDY:
176 softwareRenderer->bldy = value & 0x1F;
177 if (softwareRenderer->bldy > 0x10) {
178 softwareRenderer->bldy = 0x10;
179 }
180 _updatePalettes(softwareRenderer);
181 break;
182 default:
183 GBALog(GBA_LOG_STUB, "Stub video register write: %03x", address);
184 }
185 return value;
186}
187
188static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
189 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
190 uint32_t color32 = 0;
191 color32 |= (value << 3) & 0xF8;
192 color32 |= (value << 6) & 0xF800;
193 color32 |= (value << 9) & 0xF80000;
194 softwareRenderer->normalPalette[address >> 1] = color32;
195 if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) {
196 softwareRenderer->variantPalette[address >> 1] = _brighten(color32, softwareRenderer->bldy);
197 } else if (softwareRenderer->blendEffect == BLEND_DARKEN) {
198 softwareRenderer->variantPalette[address >> 1] = _darken(color32, softwareRenderer->bldy);
199 }
200}
201
202static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
203 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
204 if (renderer->frameskip > 0) {
205 return;
206 }
207 uint32_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
208 if (softwareRenderer->dispcnt.forcedBlank) {
209 for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
210 row[x] = GBA_COLOR_WHITE;
211 }
212 return;
213 } else {
214 uint32_t backdrop = FLAG_UNWRITTEN | FLAG_PRIORITY | FLAG_IS_BACKGROUND;
215 if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA) {
216 backdrop |= softwareRenderer->normalPalette[0];
217 } else {
218 backdrop |= softwareRenderer->variantPalette[0];
219 }
220 for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
221 row[x] = backdrop;
222 }
223 }
224
225 softwareRenderer->row = row;
226
227 softwareRenderer->start = 0;
228 softwareRenderer->end = VIDEO_HORIZONTAL_PIXELS;
229 _drawScanline(softwareRenderer, y);
230}
231
232static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
233 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
234
235 pthread_mutex_lock(&softwareRenderer->mutex);
236 if (renderer->frameskip > 0) {
237 --renderer->frameskip;
238 } else {
239 renderer->framesPending++;
240 pthread_cond_broadcast(&softwareRenderer->upCond);
241 if (!renderer->turbo) {
242 pthread_cond_wait(&softwareRenderer->downCond, &softwareRenderer->mutex);
243 }
244 }
245 pthread_mutex_unlock(&softwareRenderer->mutex);
246}
247
248static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
249 renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
250 renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
251 renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
252 renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
253}
254
255static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
256 (void)(renderer);
257 union GBARegisterBGCNT reg = { .packed = value };
258 bg->priority = reg.priority;
259 bg->charBase = reg.charBase << 14;
260 bg->mosaic = reg.mosaic;
261 bg->multipalette = reg.multipalette;
262 bg->screenBase = reg.screenBase << 11;
263 bg->overflow = reg.overflow;
264 bg->size = reg.size;
265}
266
267static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
268 union {
269 struct {
270 unsigned target1Bg0 : 1;
271 unsigned target1Bg1 : 1;
272 unsigned target1Bg2 : 1;
273 unsigned target1Bg3 : 1;
274 unsigned target1Obj : 1;
275 unsigned target1Bd : 1;
276 enum BlendEffect effect : 2;
277 unsigned target2Bg0 : 1;
278 unsigned target2Bg1 : 1;
279 unsigned target2Bg2 : 1;
280 unsigned target2Bg3 : 1;
281 unsigned target2Obj : 1;
282 unsigned target2Bd : 1;
283 };
284 uint16_t packed;
285 } bldcnt = { .packed = value };
286
287 enum BlendEffect oldEffect = renderer->blendEffect;
288
289 renderer->bg[0].target1 = bldcnt.target1Bg0;
290 renderer->bg[1].target1 = bldcnt.target1Bg1;
291 renderer->bg[2].target1 = bldcnt.target1Bg2;
292 renderer->bg[3].target1 = bldcnt.target1Bg3;
293 renderer->bg[0].target2 = bldcnt.target2Bg0;
294 renderer->bg[1].target2 = bldcnt.target2Bg1;
295 renderer->bg[2].target2 = bldcnt.target2Bg2;
296 renderer->bg[3].target2 = bldcnt.target2Bg3;
297
298 renderer->blendEffect = bldcnt.effect;
299 renderer->target1Obj = bldcnt.target1Obj;
300 renderer->target1Bd = bldcnt.target1Bd;
301 renderer->target2Obj = bldcnt.target2Obj;
302 renderer->target2Bd = bldcnt.target2Bd;
303
304 if (oldEffect != renderer->blendEffect) {
305 _updatePalettes(renderer);
306 }
307}
308
309static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
310 int i;
311 if (renderer->dispcnt.objEnable) {
312 for (i = 0; i < 128; ++i) {
313 struct GBAObj* sprite = &renderer->d.oam->obj[i];
314 if (sprite->transformed) {
315 _drawTransformedSprite(renderer, &renderer->d.oam->tobj[i], y);
316 } else if (!sprite->disable) {
317 _drawSprite(renderer, sprite, y);
318 }
319 }
320 }
321
322 int priority;
323 for (priority = 0; priority < 4; ++priority) {
324 for (i = 0; i < 4; ++i) {
325 if (renderer->bg[i].enabled && renderer->bg[i].priority == priority) {
326 _drawBackgroundMode0(renderer, &renderer->bg[i], y);
327 }
328 }
329 }
330}
331
332static void _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, uint32_t color) {
333 uint32_t current = renderer->row[offset];
334 // We stash the priority on the top bits so we cn do a one-operator comparison
335 // The lower the number, the higher the priority, and sprites take precendence over backgrounds
336 // We want to do special processing if the color pixel is target 1, however
337 if (color < current) {
338 if (current & FLAG_UNWRITTEN) {
339 renderer->row[offset] = color;
340 } else if (!(color & FLAG_TARGET_1) || !(current & FLAG_TARGET_2)) {
341 renderer->row[offset] = color | FLAG_FINALIZED;
342 } else {
343 renderer->row[offset] = _mix(renderer->bldb, current, renderer->blda, color) | FLAG_FINALIZED;
344 }
345 } else {
346 if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) {
347 renderer->row[offset] = _mix(renderer->blda, current, renderer->bldb, color) | FLAG_FINALIZED;
348 } else {
349 renderer->row[offset] = current | FLAG_FINALIZED;
350 }
351 }
352}
353
354#define BACKGROUND_DRAW_PIXEL_16_NORMAL \
355 if (tileData & 0xF && !(renderer->row[outX] & FLAG_FINALIZED)) { \
356 _composite(renderer, outX, renderer->normalPalette[(tileData & 0xF) | (mapData.palette << 4)] | flags); \
357 } \
358 tileData >>= 4;
359
360#define BACKGROUND_DRAW_PIXEL_16_VARIANT \
361 if (tileData & 0xF && !(renderer->row[outX] & FLAG_FINALIZED)) { \
362 _composite(renderer, outX, renderer->variantPalette[(tileData & 0xF) | (mapData.palette << 4)] | flags); \
363 } \
364 tileData >>= 4;
365
366#define BACKGROUND_DRAW_PIXEL_256_NORMAL \
367 if (tileData & 0xFF && !(renderer->row[outX] & FLAG_FINALIZED)) { \
368 _composite(renderer, outX, renderer->normalPalette[tileData & 0xFF] | flags); \
369 } \
370 tileData >>= 8;
371
372#define BACKGROUND_DRAW_PIXEL_256_VARIANT \
373 if (tileData & 0xFF && !(renderer->row[outX] & FLAG_FINALIZED)) { \
374 _composite(renderer, outX, renderer->variantPalette[tileData & 0xFF] | flags); \
375 } \
376 tileData >>= 8;
377
378#define BACKGROUND_TEXT_SELECT_CHARACTER \
379 localX = tileX * 8 + inX; \
380 xBase = localX & 0xF8; \
381 if (background->size & 1) { \
382 xBase += (localX & 0x100) << 5; \
383 } \
384 screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2); \
385 mapData.packed = renderer->d.vram[screenBase]; \
386 if (!mapData.vflip) { \
387 localY = inY & 0x7; \
388 } else { \
389 localY = 7 - (inY & 0x7); \
390 }
391
392#define BACKGROUND_MODE_0_TILE_16_LOOP(TYPE) \
393 for (; tileX < 30; ++tileX) { \
394 BACKGROUND_TEXT_SELECT_CHARACTER; \
395 charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY; \
396 uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
397 if (tileData) { \
398 if (!mapData.hflip) { \
399 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
400 ++outX; \
401 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
402 ++outX; \
403 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
404 ++outX; \
405 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
406 ++outX; \
407 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
408 ++outX; \
409 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
410 ++outX; \
411 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
412 ++outX; \
413 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
414 ++outX; \
415 } else { \
416 outX += 7; \
417 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
418 --outX; \
419 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
420 --outX; \
421 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
422 --outX; \
423 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
424 --outX; \
425 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
426 --outX; \
427 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
428 --outX; \
429 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
430 --outX; \
431 BACKGROUND_DRAW_PIXEL_16_ ## TYPE; \
432 outX += 8; \
433 } \
434 } else { \
435 outX += 8; \
436 } \
437 }
438
439#define BACKGROUND_MODE_0_TILE_256_LOOP(TYPE) \
440 for (; tileX < 30; ++tileX) { \
441 BACKGROUND_TEXT_SELECT_CHARACTER; \
442 charBase = ((background->charBase + (mapData.tile << 6)) >> 2) + (localY << 1); \
443 if (!mapData.hflip) { \
444 uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
445 if (tileData) { \
446 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
447 ++outX; \
448 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
449 ++outX; \
450 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
451 ++outX; \
452 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
453 ++outX; \
454 } else { \
455 outX += 4; \
456 } \
457 tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
458 if (tileData) { \
459 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
460 ++outX; \
461 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
462 ++outX; \
463 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
464 ++outX; \
465 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
466 ++outX; \
467 } else { \
468 outX += 4; \
469 } \
470 } else { \
471 uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
472 if (tileData) { \
473 outX += 3; \
474 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
475 --outX; \
476 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
477 --outX; \
478 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
479 --outX; \
480 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
481 outX += 4; \
482 } else { \
483 outX += 4; \
484 } \
485 tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
486 if (tileData) { \
487 outX += 3; \
488 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
489 --outX; \
490 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
491 --outX; \
492 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
493 --outX; \
494 BACKGROUND_DRAW_PIXEL_256_ ## TYPE; \
495 outX += 4; \
496 } else { \
497 outX += 4; \
498 } \
499 } \
500 }
501
502static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
503 int inX = background->x;
504 int inY = y + background->y;
505 union GBATextMapData mapData;
506
507 unsigned yBase = inY & 0xF8;
508 if (background->size & 2) {
509 yBase += inY & 0x100;
510 } else if (background->size == 3) {
511 yBase += (inY & 0x100) << 1;
512 }
513
514 int localX;
515 int localY;
516
517 unsigned xBase;
518
519 int flags = (background->priority << OFFSET_PRIORITY) | FLAG_IS_BACKGROUND;
520 flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA);
521 flags |= FLAG_TARGET_2 * background->target2;
522
523 uint32_t screenBase;
524 uint32_t charBase;
525 int variant = background->target1 && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
526
527 int outX = 0;
528 int tileX = 0;
529 if (inX & 0x7) {
530 uint32_t tileData;
531 BACKGROUND_TEXT_SELECT_CHARACTER;
532
533 int end = 0x8 - (inX & 0x7);
534 if (!background->multipalette) {
535 charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
536 tileData = ((uint32_t*)renderer->d.vram)[charBase];
537 if (!mapData.hflip) {
538 tileData >>= 4 * (inX & 0x7);
539 if (!variant) {
540 for (outX = 0; outX < end; ++outX) {
541 BACKGROUND_DRAW_PIXEL_16_NORMAL;
542 }
543 } else {
544 for (outX = 0; outX < end; ++outX) {
545 BACKGROUND_DRAW_PIXEL_16_VARIANT;
546 }
547 }
548 } else {
549 if (!variant) {
550 for (outX = end; outX--;) {
551 BACKGROUND_DRAW_PIXEL_16_NORMAL;
552 }
553 } else {
554 for (outX = end; outX--;) {
555 BACKGROUND_DRAW_PIXEL_16_VARIANT;
556 }
557 }
558 }
559 } else {
560 // TODO: hflip
561 charBase = ((background->charBase + (mapData.tile << 6)) >> 2) + (localY << 1);
562 outX = 0;
563 int end2 = end - 4;
564 int shift = inX & 0x3;
565 if (end2 > 0) {
566 tileData = ((uint32_t*)renderer->d.vram)[charBase];
567 tileData >>= 8 * shift;
568 shift = 0;
569 if (!variant) {
570 for (; outX < end2; ++outX) {
571 BACKGROUND_DRAW_PIXEL_256_NORMAL;
572 }
573 } else {
574 for (; outX < end2; ++outX) {
575 BACKGROUND_DRAW_PIXEL_256_VARIANT;
576 }
577 }
578 }
579
580 tileData = ((uint32_t*)renderer->d.vram)[charBase + 1];
581 tileData >>= 8 * shift;
582 if (!variant) {
583 for (; outX < end; ++outX) {
584 BACKGROUND_DRAW_PIXEL_256_NORMAL;
585 }
586 } else {
587 for (; outX < end; ++outX) {
588 BACKGROUND_DRAW_PIXEL_256_VARIANT;
589 }
590 }
591 }
592
593 tileX = 30;
594 BACKGROUND_TEXT_SELECT_CHARACTER;
595 if (!background->multipalette) {
596 charBase = ((background->charBase + (mapData.tile << 5)) >> 2) + localY;
597 tileData = ((uint32_t*)renderer->d.vram)[charBase];
598 if (!mapData.hflip) {
599 if (!variant) {
600 for (outX = VIDEO_HORIZONTAL_PIXELS - 8 + end; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
601 BACKGROUND_DRAW_PIXEL_16_NORMAL;
602 }
603 } else {
604 for (outX = VIDEO_HORIZONTAL_PIXELS - 8 + end; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
605 BACKGROUND_DRAW_PIXEL_16_VARIANT;
606 }
607 }
608 } else {
609 tileData >>= 4 * end;
610 if (!variant) {
611 for (outX = VIDEO_HORIZONTAL_PIXELS - 1; outX > VIDEO_HORIZONTAL_PIXELS - 8; --outX) {
612 BACKGROUND_DRAW_PIXEL_16_NORMAL;
613 }
614 } else {
615 for (outX = VIDEO_HORIZONTAL_PIXELS - 1; outX > VIDEO_HORIZONTAL_PIXELS - 8; --outX) {
616 BACKGROUND_DRAW_PIXEL_16_VARIANT;
617 }
618 }
619 }
620 } else {
621 // TODO: hflip
622 charBase = ((background->charBase + (mapData.tile << 6)) >> 2) + (localY << 1);
623 outX = VIDEO_HORIZONTAL_PIXELS - 8 + end;
624 int end2 = 4 - end;
625 if (end2 > 0) {
626 tileData = ((uint32_t*)renderer->d.vram)[charBase];
627 if (!variant) {
628 for (; outX < VIDEO_HORIZONTAL_PIXELS - end2; ++outX) {
629 BACKGROUND_DRAW_PIXEL_256_NORMAL;
630 }
631 } else {
632 for (; outX < VIDEO_HORIZONTAL_PIXELS - end2; ++outX) {
633 BACKGROUND_DRAW_PIXEL_256_VARIANT;
634 }
635 }
636 ++charBase;
637 }
638
639 tileData = ((uint32_t*)renderer->d.vram)[charBase];
640 if (!variant) {
641 for (; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
642 BACKGROUND_DRAW_PIXEL_256_NORMAL;
643 }
644 } else {
645 for (; outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
646 BACKGROUND_DRAW_PIXEL_256_VARIANT;
647 }
648 }
649 }
650
651 tileX = 1;
652 outX = end;
653 }
654
655 if (!background->multipalette) {
656 if (!variant) {
657 BACKGROUND_MODE_0_TILE_16_LOOP(NORMAL);
658 } else {
659 BACKGROUND_MODE_0_TILE_16_LOOP(VARIANT);
660 }
661 } else {
662 if (!variant) {
663 BACKGROUND_MODE_0_TILE_256_LOOP(NORMAL);
664 } else {
665 BACKGROUND_MODE_0_TILE_256_LOOP(VARIANT);
666 }
667 }
668}
669
670static const int _objSizes[32] = {
671 8, 8,
672 16, 16,
673 32, 32,
674 64, 64,
675 16, 8,
676 32, 8,
677 32, 16,
678 64, 32,
679 8, 16,
680 8, 32,
681 16, 32,
682 32, 64,
683 0, 0,
684 0, 0,
685 0, 0,
686 0, 0
687};
688
689#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \
690 SPRITE_YBASE_ ## DEPTH(inY); \
691 for (int outX = x >= start ? x : start; outX < x + width && outX < end; ++outX) { \
692 int inX = outX - x; \
693 if (sprite->hflip) { \
694 inX = width - inX - 1; \
695 } \
696 if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
697 continue; \
698 } \
699 SPRITE_XBASE_ ## DEPTH(inX); \
700 SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \
701 }
702
703#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \
704 for (int outX = x >= start ? x : start; outX < x + totalWidth && outX < end; ++outX) { \
705 if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
706 continue; \
707 } \
708 int inY = y - sprite->y; \
709 int inX = outX - x; \
710 int localX = ((mat->a * (inX - (totalWidth >> 1)) + mat->b * (inY - (totalHeight >> 1))) >> 8) + (width >> 1); \
711 int localY = ((mat->c * (inX - (totalWidth >> 1)) + mat->d * (inY - (totalHeight >> 1))) >> 8) + (height >> 1); \
712 \
713 if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \
714 continue; \
715 } \
716 \
717 SPRITE_YBASE_ ## DEPTH(localY); \
718 SPRITE_XBASE_ ## DEPTH(localX); \
719 SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
720 }
721
722#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
723#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width >> 1 : 0x80) + (localY & 0x7) * 4;
724
725#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
726 uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
727 tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
728 if (tileData) { \
729 renderer->row[outX] = renderer->normalPalette[0x100 | tileData | (sprite->palette << 4)] | flags; \
730 }
731
732#define SPRITE_DRAW_PIXEL_16_VARIANT(localX) \
733 uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
734 tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
735 if (tileData) { \
736 renderer->row[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)] | flags; \
737 }
738
739#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
740#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (renderer->dispcnt.objCharacterMapping ? width : 0x100) + (localY & 0x7) * 8;
741
742#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
743 uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
744 tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
745 if (tileData) { \
746 renderer->row[outX] = renderer->normalPalette[0x100 | tileData] | flags; \
747 }
748
749#define SPRITE_DRAW_PIXEL_256_VARIANT(localX) \
750 uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1]; \
751 tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
752 if (tileData) { \
753 renderer->row[outX] = renderer->variantPalette[0x100 | tileData] | flags; \
754 }
755
756static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
757 int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
758 int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
759 int start = renderer->start;
760 int end = renderer->end;
761 if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
762 return;
763 }
764 int flags = sprite->priority << OFFSET_PRIORITY;
765 flags |= FLAG_TARGET_1 * ((renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT);
766 flags |= FLAG_TARGET_2 *renderer->target2Obj;
767 int x = sprite->x;
768 int inY = y - sprite->y;
769 if (sprite->y + height - 256 >= 0) {
770 inY += 256;
771 }
772 if (sprite->vflip) {
773 inY = height - inY - 1;
774 }
775 unsigned charBase = BASE_TILE + sprite->tile * 0x20;
776 int variant = renderer->target1Obj && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
777 if (!sprite->multipalette) {
778 if (!variant) {
779 SPRITE_NORMAL_LOOP(16, NORMAL);
780 } else {
781 SPRITE_NORMAL_LOOP(16, VARIANT);
782 }
783 } else {
784 if (!variant) {
785 SPRITE_NORMAL_LOOP(256, NORMAL);
786 } else {
787 SPRITE_NORMAL_LOOP(256, VARIANT);
788 }
789 }
790}
791
792static void _drawTransformedSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBATransformedObj* sprite, int y) {
793 int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
794 int totalWidth = width << sprite->doublesize;
795 int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
796 int totalHeight = height << sprite->doublesize;
797 int start = renderer->start;
798 int end = renderer->end;
799 if ((y < sprite->y && (sprite->y + totalHeight - 256 < 0 || y >= sprite->y + totalHeight - 256)) || y >= sprite->y + totalHeight) {
800 return;
801 }
802 int flags = sprite->priority << OFFSET_PRIORITY;
803 flags |= FLAG_TARGET_1 * ((renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || sprite->mode == OBJ_MODE_SEMITRANSPARENT);
804 flags |= FLAG_TARGET_2 *renderer->target2Obj;
805 int x = sprite->x;
806 unsigned charBase = BASE_TILE + sprite->tile * 0x20;
807 struct GBAOAMMatrix* mat = &renderer->d.oam->mat[sprite->matIndex];
808 int variant = renderer->target1Obj && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
809 if (!sprite->multipalette) {
810 if (!variant) {
811 SPRITE_TRANSFORMED_LOOP(16, NORMAL);
812 } else {
813 SPRITE_TRANSFORMED_LOOP(16, VARIANT);
814 }
815 } else {
816 if (!variant) {
817 SPRITE_TRANSFORMED_LOOP(256, NORMAL);
818 } else {
819 SPRITE_TRANSFORMED_LOOP(256, VARIANT);
820 }
821 }
822}
823
824static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
825 if (renderer->blendEffect == BLEND_BRIGHTEN) {
826 for (int i = 0; i < 512; ++i) {
827 renderer->variantPalette[i] = _brighten(renderer->normalPalette[i], renderer->bldy);
828 }
829 } else if (renderer->blendEffect == BLEND_DARKEN) {
830 for (int i = 0; i < 512; ++i) {
831 renderer->variantPalette[i] = _darken(renderer->normalPalette[i], renderer->bldy);
832 }
833 } else {
834 for (int i = 0; i < 512; ++i) {
835 renderer->variantPalette[i] = renderer->normalPalette[i];
836 }
837 }
838}
839
840static inline uint32_t _brighten(uint32_t color, int y) {
841 uint32_t c = 0;
842 uint32_t a;
843 a = color & 0xF8;
844 c |= (a + ((0xF8 - a) * y) / 16) & 0xF8;
845
846 a = color & 0xF800;
847 c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
848
849 a = color & 0xF80000;
850 c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000;
851 return c;
852}
853
854static inline uint32_t _darken(uint32_t color, int y) {
855 uint32_t c = 0;
856 uint32_t a;
857 a = color & 0xF8;
858 c |= (a - (a * y) / 16) & 0xF8;
859
860 a = color & 0xF800;
861 c |= (a - (a * y) / 16) & 0xF800;
862
863 a = color & 0xF80000;
864 c |= (a - (a * y) / 16) & 0xF80000;
865 return c;
866}
867
868static uint32_t _mix(int weightA, uint32_t colorA, int weightB, uint32_t colorB) {
869 uint32_t c = 0;
870 uint32_t a, b;
871 a = colorA & 0xF8;
872 b = colorB & 0xF8;
873 c |= ((a * weightA + b * weightB) / 16) & 0x1F8;
874 if (c & 0x00000100) {
875 c = 0x000000F8;
876 }
877
878 a = colorA & 0xF800;
879 b = colorB & 0xF800;
880 c |= ((a * weightA + b * weightB) / 16) & 0x1F800;
881 if (c & 0x00010000) {
882 c = (c & 0x000000F8) | 0x0000F800;
883 }
884
885 a = colorA & 0xF80000;
886 b = colorB & 0xF80000;
887 c |= ((a * weightA + b * weightB) / 16) & 0x1F80000;
888 if (c & 0x01000000) {
889 c = (c & 0x0000F8F8) | 0x00F80000;
890 }
891 return c;
892}