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 // TODO
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 _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
453 if (renderer->blendEffect == BLEND_BRIGHTEN) {
454 for (int i = 0; i < 512; ++i) {
455 renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
456 }
457 } else if (renderer->blendEffect == BLEND_DARKEN) {
458 for (int i = 0; i < 512; ++i) {
459 renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
460 }
461 } else {
462 for (int i = 0; i < 512; ++i) {
463 renderer->variantPalette[i] = renderer->d.palette[i];
464 }
465 }
466}
467
468static inline uint16_t _brighten(uint16_t c, int y) {
469 union GBAColor color = { .packed = c };
470 color.r = color.r + ((31 - color.r) * y) / 16;
471 color.g = color.g + ((31 - color.g) * y) / 16;
472 color.b = color.b + ((31 - color.b) * y) / 16;
473 return color.packed;
474}
475
476static inline uint16_t _darken(uint16_t c, int y) {
477 union GBAColor color = { .packed = c };
478 color.r = color.r - (color.r * y) / 16;
479 color.g = color.g - (color.g * y) / 16;
480 color.b = color.b - (color.b * y) / 16;
481 return color.packed;
482}
483
484static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
485 union GBAColor ca = { .packed = colorA };
486 union GBAColor cb = { .packed = colorB };
487
488 int r = (ca.r * weightA + cb.r * weightB) / 16;
489 ca.r = r > 0x1F ? 0x1F : r;
490
491 int g = (ca.g * weightA + cb.g * weightB) / 16;
492 ca.g = g > 0x1F ? 0x1F : g;
493
494 int b = (ca.b * weightA + cb.b * weightB) / 16;
495 ca.b = b > 0x1F ? 0x1F : b;
496
497 return ca.packed;
498}
499
500static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
501 qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
502}
503
504static int _backgroundComparator(const void* a, const void* b) {
505 const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
506 const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
507 if (bgA->priority != bgB->priority) {
508 return bgA->priority - bgB->priority;
509 } else {
510 return bgA->index - bgB->index;
511 }
512}