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 unsigned xBase;
346
347 uint32_t screenBase;
348 uint32_t charBase;
349
350 for (int outX = start; outX < end; ++outX) {
351 if (renderer->flags[outX].finalized) {
352 continue;
353 }
354 xBase = (outX + inX) & 0xF8;
355 if (background->size & 1) {
356 xBase += ((outX + inX) & 0x100) << 5;
357 }
358 screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2);
359 mapData.packed = renderer->d.vram[screenBase];
360 charBase = ((background->charBase + (mapData.tile << 5)) >> 1) + ((inY & 0x7) << 1) + (((outX + inX) >> 2) & 1);
361 uint16_t tileData = renderer->d.vram[charBase];
362 tileData >>= ((outX + inX) & 0x3) << 2;
363 if (tileData & 0xF) {
364 struct PixelFlags flags = {
365 .target1 = background->target1,
366 .target2 = background->target2,
367 .priority = background->priority
368 };
369 _compositeBackground(renderer, outX, (tileData & 0xF) | (mapData.palette << 4), flags);
370 }
371 }
372}
373
374static const int _objSizes[32] = {
375 8, 8,
376 16, 16,
377 32, 32,
378 64, 64,
379 16, 8,
380 32, 8,
381 32, 16,
382 64, 32,
383 8, 16,
384 8, 32,
385 16, 32,
386 32, 64,
387 0, 0,
388 0, 0,
389 0, 0,
390 0, 0
391};
392
393static void _drawSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
394 int width = _objSizes[sprite->shape * 8 + sprite->size * 2];
395 int height = _objSizes[sprite->shape * 8 + sprite->size * 2 + 1];
396 if ((y < sprite->y && (sprite->y + height - 256 < 0 || y >= sprite->y + height - 256)) || y >= sprite->y + height) {
397 return;
398 }
399 (void)(renderer);
400 struct PixelFlags flags = {
401 .priority = sprite->priority,
402 .isSprite = 1,
403 .target1 = renderer->target1Obj || sprite->mode == OBJ_MODE_SEMITRANSPARENT,
404 .target2 = renderer->target2Obj
405 };
406 int x = sprite->x;
407 int inY = y - sprite->y;
408 if (sprite->y + height - 256 >= 0) {
409 inY += 256;
410 }
411 if (sprite->vflip) {
412 inY = height - inY - 1;
413 }
414 unsigned charBase = BASE_TILE + sprite->tile * 0x20;
415 unsigned yBase = (inY & ~0x7) * 0x80 + (inY & 0x7) * 4;
416 for (int outX = x >= 0 ? x : 0; outX < x + width && outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
417 int inX = outX - x;
418 if (sprite->hflip) {
419 inX = width - inX - 1;
420 }
421 if (renderer->flags[outX].isSprite) {
422 continue;
423 }
424 unsigned xBase = (inX & ~0x7) * 4 + ((inX >> 1) & 2);
425 uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
426 tileData = (tileData >> ((inX & 3) << 2)) & 0xF;
427 if (tileData) {
428 if (!renderer->target1Obj) {
429 renderer->row[outX] = renderer->d.palette[0x100 | tileData | (sprite->palette << 4)];
430 } else {
431 renderer->row[outX] = renderer->variantPalette[0x100 | tileData | (sprite->palette << 4)];
432 }
433 renderer->flags[outX] = flags;
434 }
435 }
436}
437
438static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
439 if (renderer->blendEffect == BLEND_BRIGHTEN) {
440 for (int i = 0; i < 512; ++i) {
441 renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
442 }
443 } else if (renderer->blendEffect == BLEND_DARKEN) {
444 for (int i = 0; i < 512; ++i) {
445 renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
446 }
447 } else {
448 for (int i = 0; i < 512; ++i) {
449 renderer->variantPalette[i] = renderer->d.palette[i];
450 }
451 }
452}
453
454static inline uint16_t _brighten(uint16_t c, int y) {
455 union GBAColor color = { .packed = c };
456 color.r = color.r + ((31 - color.r) * y) / 16;
457 color.g = color.g + ((31 - color.g) * y) / 16;
458 color.b = color.b + ((31 - color.b) * y) / 16;
459 return color.packed;
460}
461
462static inline uint16_t _darken(uint16_t c, int y) {
463 union GBAColor color = { .packed = c };
464 color.r = color.r - (color.r * y) / 16;
465 color.g = color.g - (color.g * y) / 16;
466 color.b = color.b - (color.b * y) / 16;
467 return color.packed;
468}
469
470static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
471 union GBAColor ca = { .packed = colorA };
472 union GBAColor cb = { .packed = colorB };
473
474 int r = (ca.r * weightA + cb.r * weightB) / 16;
475 ca.r = r > 0x1F ? 0x1F : r;
476
477 int g = (ca.g * weightA + cb.g * weightB) / 16;
478 ca.g = g > 0x1F ? 0x1F : g;
479
480 int b = (ca.b * weightA + cb.b * weightB) / 16;
481 ca.b = b > 0x1F ? 0x1F : b;
482
483 return ca.packed;
484}
485
486static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
487 qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
488}
489
490static int _backgroundComparator(const void* a, const void* b) {
491 const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
492 const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
493 if (bgA->priority != bgB->priority) {
494 return bgA->priority - bgB->priority;
495 } else {
496 return bgA->index - bgB->index;
497 }
498}