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