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 inX = sprite->x;
404 int inY = y - sprite->y;
405 if (sprite->y + height - 256 >= 0) {
406 inY += 256;
407 }
408 unsigned charBase = BASE_TILE + sprite->tile * 0x20;
409 unsigned yBase = (inY & ~0x7) * 0x80 + (inY & 0x7) * 4;
410 for (int outX = inX >= 0 ? inX : 0; outX < inX + width && outX < VIDEO_HORIZONTAL_PIXELS; ++outX) {
411 int x = outX - inX;
412 if (renderer->flags[outX].isSprite) {
413 continue;
414 }
415 unsigned xBase = (x & ~0x7) * 4 + ((x >> 1) & 2);
416 uint16_t tileData = renderer->d.vram[(yBase + charBase + xBase) >> 1];
417 tileData = (tileData >> ((x & 3) << 2)) & 0xF;
418 if (tileData) {
419 renderer->row[outX] = renderer->d.palette[0x100 | tileData | (sprite->palette << 4)];
420 renderer->flags[outX] = flags;
421 }
422 }
423}
424
425static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
426 if (renderer->blendEffect == BLEND_BRIGHTEN) {
427 for (int i = 0; i < 512; ++i) {
428 renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
429 }
430 } else if (renderer->blendEffect == BLEND_DARKEN) {
431 for (int i = 0; i < 512; ++i) {
432 renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
433 }
434 }
435}
436
437static inline uint16_t _brighten(uint16_t c, int y) {
438 union GBAColor color = { .packed = c };
439 color.r = color.r + ((31 - color.r) * y) / 16;
440 color.g = color.g + ((31 - color.g) * y) / 16;
441 color.b = color.b + ((31 - color.b) * y) / 16;
442 return color.packed;
443}
444
445static inline uint16_t _darken(uint16_t c, int y) {
446 union GBAColor color = { .packed = c };
447 color.r = color.r - (color.r * y) / 16;
448 color.g = color.g - (color.g * y) / 16;
449 color.b = color.b - (color.b * y) / 16;
450 return color.packed;
451}
452
453static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
454 union GBAColor ca = { .packed = colorA };
455 union GBAColor cb = { .packed = colorB };
456
457 int r = (ca.r * weightA + cb.r * weightB) / 16;
458 ca.r = r > 0x1F ? 0x1F : r;
459
460 int g = (ca.g * weightA + cb.g * weightB) / 16;
461 ca.g = g > 0x1F ? 0x1F : g;
462
463 int b = (ca.b * weightA + cb.b * weightB) / 16;
464 ca.b = b > 0x1F ? 0x1F : b;
465
466 return ca.packed;
467}
468
469static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
470 qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
471}
472
473static int _backgroundComparator(const void* a, const void* b) {
474 const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
475 const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
476 if (bgA->priority != bgB->priority) {
477 return bgA->priority - bgB->priority;
478 } else {
479 return bgA->index - bgB->index;
480 }
481}