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