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
8static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
9static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
10static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
11static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
12static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
13
14static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
15static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
16
17static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
18
19static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer);
20static int _backgroundComparator(const void* a, const void* b);
21
22void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
23 renderer->d.init = GBAVideoSoftwareRendererInit;
24 renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
25 renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
26 renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
27 renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
28
29 renderer->sortedBg[0] = &renderer->bg[0];
30 renderer->sortedBg[1] = &renderer->bg[1];
31 renderer->sortedBg[2] = &renderer->bg[2];
32 renderer->sortedBg[3] = &renderer->bg[3];
33
34 {
35 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
36 renderer->mutex = mutex;
37 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
38 renderer->cond = cond;
39 }
40}
41
42static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
43 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
44 int i;
45
46 softwareRenderer->dispcnt.packed = 0x0080;
47
48 for (i = 0; i < 4; ++i) {
49 struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
50 bg->index = i;
51 bg->enabled = 0;
52 bg->priority = 0;
53 bg->charBase = 0;
54 bg->mosaic = 0;
55 bg->multipalette = 0;
56 bg->screenBase = 0;
57 bg->overflow = 0;
58 bg->size = 0;
59 bg->x = 0;
60 bg->y = 0;
61 bg->refx = 0;
62 bg->refy = 0;
63 bg->dx = 256;
64 bg->dmx = 0;
65 bg->dy = 0;
66 bg->dmy = 256;
67 bg->sx = 0;
68 bg->sy = 0;
69 }
70
71 pthread_mutex_init(&softwareRenderer->mutex, 0);
72 pthread_cond_init(&softwareRenderer->cond, 0);
73}
74
75static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer) {
76 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
77
78 pthread_mutex_destroy(&softwareRenderer->mutex);
79 pthread_cond_destroy(&softwareRenderer->cond);
80}
81
82static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
83 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
84 switch (address) {
85 case REG_DISPCNT:
86 value &= 0xFFFB;
87 softwareRenderer->dispcnt.packed = value;
88 GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
89 break;
90 case REG_BG0CNT:
91 value &= 0xFFCF;
92 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value);
93 break;
94 case REG_BG1CNT:
95 value &= 0xFFCF;
96 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[1], value);
97 break;
98 case REG_BG2CNT:
99 value &= 0xFFCF;
100 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[2], value);
101 break;
102 case REG_BG3CNT:
103 value &= 0xFFCF;
104 GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[3], value);
105 break;
106 case REG_BG0HOFS:
107 value &= 0x01FF;
108 softwareRenderer->bg[0].x = value;
109 break;
110 case REG_BG0VOFS:
111 value &= 0x01FF;
112 softwareRenderer->bg[0].y = value;
113 break;
114 case REG_BG1HOFS:
115 value &= 0x01FF;
116 softwareRenderer->bg[1].x = value;
117 break;
118 case REG_BG1VOFS:
119 value &= 0x01FF;
120 softwareRenderer->bg[1].y = value;
121 break;
122 case REG_BG2HOFS:
123 value &= 0x01FF;
124 softwareRenderer->bg[2].x = value;
125 break;
126 case REG_BG2VOFS:
127 value &= 0x01FF;
128 softwareRenderer->bg[2].y = value;
129 break;
130 case REG_BG3HOFS:
131 value &= 0x01FF;
132 softwareRenderer->bg[3].x = value;
133 break;
134 case REG_BG3VOFS:
135 value &= 0x01FF;
136 softwareRenderer->bg[3].y = value;
137 break;
138 default:
139 GBALog(GBA_LOG_STUB, "Stub video register write: %03x", address);
140 }
141 return value;
142}
143
144static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
145 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
146 uint16_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
147 if (softwareRenderer->dispcnt.forcedBlank) {
148 for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
149 row[x] = 0x7FFF;
150 }
151 return;
152 }
153
154 for (int i = 0; i < 4; ++i) {
155 if (softwareRenderer->sortedBg[i]->enabled) {
156 _drawBackgroundMode0(softwareRenderer, softwareRenderer->sortedBg[i], y);
157 }
158 }
159 for (int x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
160 for (int i = 0; i < 4; ++i) {
161 if (softwareRenderer->sortedBg[i]->enabled && softwareRenderer->sortedBg[i]->internalBuffer[x] != 0x8000) {
162 row[x] = softwareRenderer->sortedBg[i]->internalBuffer[x];
163 break;
164 }
165 }
166 }
167}
168
169static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
170 struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
171
172 pthread_mutex_lock(&softwareRenderer->mutex);
173 pthread_cond_wait(&softwareRenderer->cond, &softwareRenderer->mutex);
174 pthread_mutex_unlock(&softwareRenderer->mutex);
175}
176
177static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
178 renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
179 renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
180 renderer->bg[2].enabled = renderer->dispcnt.bg2Enable;
181 renderer->bg[3].enabled = renderer->dispcnt.bg3Enable;
182}
183
184static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
185 union GBARegisterBGCNT reg = { .packed = value };
186 bg->priority = reg.priority;
187 bg->charBase = reg.charBase << 14;
188 bg->mosaic = reg.mosaic;
189 bg->multipalette = reg.multipalette;
190 bg->screenBase = reg.screenBase << 11;
191 bg->overflow = reg.overflow;
192 bg->size = reg.size;
193
194 _sortBackgrounds(renderer);
195}
196
197static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
198 int start = 0;
199 int end = VIDEO_HORIZONTAL_PIXELS;
200 int inX = start + background->x;
201 int inY = y + background->y;
202 union GBATextMapData mapData;
203
204 unsigned yBase = inY & 0xF8;
205 if (background->size & 2) {
206 yBase += inY & 0x100;
207 } else if (background->size == 3) {
208 yBase += (inY & 0x100) << 1;
209 }
210
211 unsigned xBase;
212
213 uint32_t screenBase;
214 uint32_t charBase;
215
216 for (int outX = start; outX < end; ++outX) {
217 xBase = (outX + inX) & 0xF8;
218 if (background->size & 1) {
219 xBase += ((outX + inX) & 0x100) << 5;
220 }
221 screenBase = (background->screenBase >> 1) + (xBase >> 3) + (yBase << 2);
222 mapData.packed = renderer->d.vram[screenBase];
223 charBase = ((background->charBase + mapData.tile << 5) >> 1) + ((inY & 0x7) << 1) + (((outX + inX) >> 2) & 1);
224 uint16_t tileData = renderer->d.vram[charBase];
225 tileData >>= ((outX + inX) & 0x3) << 2;
226 if (tileData & 0xF) {
227 background->internalBuffer[outX] = renderer->d.palette[(tileData & 0xF) | (mapData.palette << 4)];
228 } else {
229 background->internalBuffer[outX] = 0x8000;
230 }
231 }
232}
233
234static void _sortBackgrounds(struct GBAVideoSoftwareRenderer* renderer) {
235 qsort(renderer->sortedBg, 4, sizeof(struct GBAVideoSoftwareBackground*), _backgroundComparator);
236}
237
238static int _backgroundComparator(const void* a, const void* b) {
239 const struct GBAVideoSoftwareBackground* bgA = *(const struct GBAVideoSoftwareBackground**) a;
240 const struct GBAVideoSoftwareBackground* bgB = *(const struct GBAVideoSoftwareBackground**) b;
241 if (bgA->priority != bgB->priority) {
242 return bgA->priority - bgB->priority;
243 } else {
244 return bgA->index - bgB->index;
245 }
246}