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 _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, int entry, struct PixelFlags flags);
20static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
21
22static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
23static inline uint16_t _brighten(uint16_t color, int y);
24static inline uint16_t _darken(uint16_t color, int y);
25static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB);
26
27static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer);
28static int _backgroundComparator(const void* a, const void* b);
29
30void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
31 renderer->d.init = GBAVideoSoftwareRendererInit;
32 renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
33 renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
34 renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
35 renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
36
37 renderer->d.turbo = 0;
38 renderer->d.framesPending = 0;
39
40 renderer->sortedBg[0] = &renderer->bg[0];
41 renderer->sortedBg[1] = &renderer->bg[1];
42 renderer->sortedBg[2] = &renderer->bg[2];
43 renderer->sortedBg[3] = &renderer->bg[3];
44
45 {
46 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
47 renderer->mutex = mutex;
48 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
49 renderer->cond = cond;
50 }
51}
52
53static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
54 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
55 int i;
56
57 softwareRenderer->dispcnt.packed = 0x0080;
58
59 softwareRenderer->target1Obj = 0;
60 softwareRenderer->target1Bd = 0;
61 softwareRenderer->target2Obj = 0;
62 softwareRenderer->target2Bd = 0;
63 softwareRenderer->blendEffect = BLEND_NONE;
64 memset(softwareRenderer->variantPalette, 0, sizeof(softwareRenderer->variantPalette));
65
66 softwareRenderer->blda = 0;
67 softwareRenderer->bldb = 0;
68 softwareRenderer->bldy = 0;
69
70 for (i = 0; i < 4; ++i) {
71 struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
72 bg->index = i;
73 bg->enabled = 0;
74 bg->priority = 0;
75 bg->charBase = 0;
76 bg->mosaic = 0;
77 bg->multipalette = 0;
78 bg->screenBase = 0;
79 bg->overflow = 0;
80 bg->size = 0;
81 bg->target1 = 0;
82 bg->target2 = 0;
83 bg->x = 0;
84 bg->y = 0;
85 bg->refx = 0;
86 bg->refy = 0;
87 bg->dx = 256;
88 bg->dmx = 0;
89 bg->dy = 0;
90 bg->dmy = 256;
91 bg->sx = 0;
92 bg->sy = 0;
93 }
94
95 pthread_mutex_init(&softwareRenderer->mutex, 0);
96 pthread_cond_init(&softwareRenderer->cond, 0);
97}
98
99static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
100 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
101
102 pthread_mutex_destroy(&softwareRenderer->mutex);
103 pthread_cond_destroy(&softwareRenderer->cond);
104}
105
106static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
107 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
108 switch (address) {
109 case REG_DISPCNT:
110 value &= 0xFFFB;
111 softwareRenderer->dispcnt.packed = value;
112 GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
113 break;
114 case REG_BG0CNT:
115 value &= 0xFFCF;
116 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
117 break;
118 case REG_BG1CNT:
119 value &= 0xFFCF;
120 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
121 break;
122 case REG_BG2CNT:
123 value &= 0xFFCF;
124 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
125 break;
126 case REG_BG3CNT:
127 value &= 0xFFCF;
128 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
129 break;
130 case REG_BG0HOFS:
131 value &= 0x01FF;
132 softwareRenderer->bg[0].x = value;
133 break;
134 case REG_BG0VOFS:
135 value &= 0x01FF;
136 softwareRenderer->bg[0].y = value;
137 break;
138 case REG_BG1HOFS:
139 value &= 0x01FF;
140 softwareRenderer->bg[1].x = value;
141 break;
142 case REG_BG1VOFS:
143 value &= 0x01FF;
144 softwareRenderer->bg[1].y = value;
145 break;
146 case REG_BG2HOFS:
147 value &= 0x01FF;
148 softwareRenderer->bg[2].x = value;
149 break;
150 case REG_BG2VOFS:
151 value &= 0x01FF;
152 softwareRenderer->bg[2].y = value;
153 break;
154 case REG_BG3HOFS:
155 value &= 0x01FF;
156 softwareRenderer->bg[3].x = value;
157 break;
158 case REG_BG3VOFS:
159 value &= 0x01FF;
160 softwareRenderer->bg[3].y = value;
161 break;
162 case REG_BLDCNT:
163 GBAVideoSoftwareRendererWriteBLDCNT(softwareRenderer, value);
164 break;
165 case REG_BLDALPHA:
166 softwareRenderer->blda = value & 0x1F;
167 if (softwareRenderer->blda > 0x10) {
168 softwareRenderer->blda = 0x10;
169 }
170 softwareRenderer->bldb = (value >> 8) & 0x1F;
171 if (softwareRenderer->bldb > 0x10) {
172 softwareRenderer->bldb = 0x10;
173 }
174 break;
175 case REG_BLDY:
176 softwareRenderer->bldy = value & 0x1F;
177 if (softwareRenderer->bldy > 0x10) {
178 softwareRenderer->bldy = 0x10;
179 }
180 _updatePalettes(softwareRenderer);
181 break;
182 default:
183 GBALog(GBA_LOG_STUB, "Stub video register write: %03x", address);
184 }
185 return value;
186}
187
188static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
189 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
190 uint16_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
191 if (softwareRenderer->dispcnt.forcedBlank) {
192 for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
193 row[x] = 0x7FFF;
194 }
195 return;
196 }
197
198 memset(softwareRenderer->flags, 0, sizeof(softwareRenderer->flags));
199 softwareRenderer->row = row;
200
201 for (int i = 0; i < 4; ++i) {
202 if (softwareRenderer->sortedBg[i]->enabled) {
203 _drawBackgroundMode0(softwareRenderer, softwareRenderer->sortedBg[i], y);
204 }
205 }
206}
207
208static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
209 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
210
211 pthread_mutex_lock(&softwareRenderer->mutex);
212 renderer->framesPending++;
213 if (!renderer->turbo) {
214 pthread_cond_wait(&softwareRenderer->cond, &softwareRenderer->mutex);
215 }
216 pthread_mutex_unlock(&softwareRenderer->mutex);
217}
218
219static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
220 renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
221 renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
222 renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
223 renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
224}
225
226static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
227 union GBARegisterBGCNT reg = { .packed = value };
228 bg->priority = reg.priority;
229 bg->charBase = reg.charBase << 14;
230 bg->mosaic = reg.mosaic;
231 bg->multipalette = reg.multipalette;
232 bg->screenBase = reg.screenBase << 11;
233 bg->overflow = reg.overflow;
234 bg->size = reg.size;
235
236 _sortBackgrounds(renderer);
237}
238
239static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
240 union {
241 struct {
242 unsigned target1Bg0 : 1;
243 unsigned target1Bg1 : 1;
244 unsigned target1Bg2 : 1;
245 unsigned target1Bg3 : 1;
246 unsigned target1Obj : 1;
247 unsigned target1Bd : 1;
248 enum BlendEffect effect : 2;
249 unsigned target2Bg0 : 1;
250 unsigned target2Bg1 : 1;
251 unsigned target2Bg2 : 1;
252 unsigned target2Bg3 : 1;
253 unsigned target2Obj : 1;
254 unsigned target2Bd : 1;
255 };
256 uint16_t packed;
257 } bldcnt = { .packed = value };
258
259 enum BlendEffect oldEffect = renderer->blendEffect;
260
261 renderer->bg[0].target1 = bldcnt.target1Bg0;
262 renderer->bg[1].target1 = bldcnt.target1Bg1;
263 renderer->bg[2].target1 = bldcnt.target1Bg2;
264 renderer->bg[3].target1 = bldcnt.target1Bg3;
265 renderer->bg[0].target2 = bldcnt.target2Bg0;
266 renderer->bg[1].target2 = bldcnt.target2Bg1;
267 renderer->bg[2].target2 = bldcnt.target2Bg2;
268 renderer->bg[3].target2 = bldcnt.target2Bg3;
269
270 renderer->blendEffect = bldcnt.effect;
271 renderer->target1Obj = bldcnt.target1Obj;
272 renderer->target1Bd = bldcnt.target1Bd;
273 renderer->target2Obj = bldcnt.target2Obj;
274 renderer->target2Bd = bldcnt.target2Bd;
275
276 if (oldEffect != renderer->blendEffect) {
277 _updatePalettes(renderer);
278 }
279}
280
281static void _composite(struct GBAVideoSoftwareRenderer* renderer, int offset, int entry, struct PixelFlags flags) {
282 if (renderer->blendEffect == BLEND_NONE || (!flags.target1 && !flags.target2)) {
283 renderer->row[offset] = renderer->d.palette[entry];
284 renderer->flags[offset].finalized = 1;
285 } else if (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN) {
286 renderer->row[offset] = renderer->variantPalette[entry];
287 renderer->flags[offset].finalized = 1;
288 } else if (renderer->blendEffect == BLEND_ALPHA) {
289 if (renderer->flags[offset].written) {
290 if (renderer->flags[offset].target1 && flags.target2) {
291 renderer->row[offset] = _mix(renderer->bldb, renderer->d.palette[entry], renderer->blda, renderer->row[offset]);
292 renderer->flags[offset].finalized = 1;
293 } else {
294 renderer->flags[offset].finalized = 1;
295 }
296 } else {
297 renderer->row[offset] = renderer->d.palette[entry];
298 renderer->flags[offset].target1 = flags.target1;
299 }
300 }
301 renderer->flags[offset].written = 1;
302}
303
304static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
305 int start = 0;
306 int end = VIDEO_HORIZONTAL_PIXELS;
307 int inX = start + background->x;
308 int inY = y + background->y;
309 union GBATextMapData mapData;
310
311 unsigned yBase = inY & 0xF8;
312 if (background->size & 2) {
313 yBase += inY & 0x100;
314 } else if (background->size == 3) {
315 yBase += (inY & 0x100) << 1;
316 }
317
318 unsigned xBase;
319
320 uint32_t screenBase;
321 uint32_t charBase;
322
323 for (int outX = start; outX < end; ++outX) {
324 if (renderer->flags[outX].finalized) {
325 continue;
326 }
327 xBase = (outX + inX) & 0xF8;
328 if (background->size & 1) {
329 xBase += ((outX + inX) & 0x100) << 5;
330 }
331 screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2);
332 mapData.packed = renderer->d.vram[screenBase];
333 charBase = ((background->charBase + (mapData.tile << 5)) >> 1) + ((inY & 0x7) << 1) + (((outX + inX) >> 2) & 1);
334 uint16_t tileData = renderer->d.vram[charBase];
335 tileData >>= ((outX + inX) & 0x3) << 2;
336 if (tileData & 0xF) {
337 struct PixelFlags flags = {
338 .finalized = 1,
339 .target1 = background->target1,
340 .target2 = background->target2
341 };
342 _composite(renderer, outX, (tileData & 0xF) | (mapData.palette << 4), flags);
343 }
344 }
345}
346
347static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
348 if (renderer->blendEffect == BLEND_BRIGHTEN) {
349 for (int i = 0; i < 512; ++i) {
350 renderer->variantPalette[i] = _brighten(renderer->d.palette[i], renderer->bldy);
351 }
352 } else if (renderer->blendEffect == BLEND_DARKEN) {
353 for (int i = 0; i < 512; ++i) {
354 renderer->variantPalette[i] = _darken(renderer->d.palette[i], renderer->bldy);
355 }
356 }
357}
358
359static inline uint16_t _brighten(uint16_t c, int y) {
360 union GBAColor color = { .packed = c };
361 color.r = color.r + ((31 - color.r) * y) / 16;
362 color.g = color.g + ((31 - color.g) * y) / 16;
363 color.b = color.b + ((31 - color.b) * y) / 16;
364 return color.packed;
365}
366
367static inline uint16_t _darken(uint16_t c, int y) {
368 union GBAColor color = { .packed = c };
369 color.r = color.r - (color.r * y) / 16;
370 color.g = color.g - (color.g * y) / 16;
371 color.b = color.b - (color.b * y) / 16;
372 return color.packed;
373}
374
375static uint16_t _mix(int weightA, uint16_t colorA, int weightB, uint16_t colorB) {
376 union GBAColor ca = { .packed = colorA };
377 union GBAColor cb = { .packed = colorB };
378
379 int r = (ca.r * weightA + cb.r * weightB) / 16;
380 ca.r = r > 0x1F ? 0x1F : r;
381
382 int g = (ca.g * weightA + cb.g * weightB) / 16;
383 ca.g = g > 0x1F ? 0x1F : g;
384
385 int b = (ca.b * weightA + cb.b * weightB) / 16;
386 ca.b = b > 0x1F ? 0x1F : b;
387
388 return ca.packed;
389}
390
391static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
392 qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
393}
394
395static int _backgroundComparator(const void* a, const void* b) {
396 const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
397 const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
398 if (bgA->priority != bgB->priority) {
399 return bgA->priority - bgB->priority;
400 } else {
401 return bgA->index - bgB->index;
402 }
403}