src/gba/renderers/thread-proxy.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "thread-proxy.h"
7
8#include "gba/io.h"
9
10#include "util/memory.h"
11
12#ifndef DISABLE_THREADING
13
14enum GBAVideoDirtyType {
15 DIRTY_DUMMY = 0,
16 DIRTY_REGISTER,
17 DIRTY_OAM,
18 DIRTY_PALETTE,
19 DIRTY_VRAM,
20 DIRTY_SCANLINE,
21 DIRTY_FLUSH
22};
23
24struct GBAVideoDirtyInfo {
25 enum GBAVideoDirtyType type;
26 uint32_t address;
27 uint16_t value;
28 uint32_t padding;
29};
30
31static void GBAVideoThreadProxyRendererInit(struct GBAVideoRenderer* renderer);
32static void GBAVideoThreadProxyRendererReset(struct GBAVideoRenderer* renderer);
33static void GBAVideoThreadProxyRendererDeinit(struct GBAVideoRenderer* renderer);
34static uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
35static void GBAVideoThreadProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
36static void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
37static void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
38static void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
39static void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer);
40static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, const void** pixels);
41static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels);
42
43static THREAD_ENTRY _proxyThread(void* renderer);
44
45void GBAVideoThreadProxyRendererCreate(struct GBAVideoThreadProxyRenderer* renderer, struct GBAVideoRenderer* backend) {
46 renderer->d.init = GBAVideoThreadProxyRendererInit;
47 renderer->d.reset = GBAVideoThreadProxyRendererReset;
48 renderer->d.deinit = GBAVideoThreadProxyRendererDeinit;
49 renderer->d.writeVideoRegister = GBAVideoThreadProxyRendererWriteVideoRegister;
50 renderer->d.writeVRAM = GBAVideoThreadProxyRendererWriteVRAM;
51 renderer->d.writeOAM = GBAVideoThreadProxyRendererWriteOAM;
52 renderer->d.writePalette = GBAVideoThreadProxyRendererWritePalette;
53 renderer->d.drawScanline = GBAVideoThreadProxyRendererDrawScanline;
54 renderer->d.finishFrame = GBAVideoThreadProxyRendererFinishFrame;
55 renderer->d.getPixels = GBAVideoThreadProxyRendererGetPixels;
56 renderer->d.putPixels = GBAVideoThreadProxyRendererPutPixels;
57
58 renderer->d.disableBG[0] = false;
59 renderer->d.disableBG[1] = false;
60 renderer->d.disableBG[2] = false;
61 renderer->d.disableBG[3] = false;
62 renderer->d.disableOBJ = false;
63
64 renderer->backend = backend;
65}
66
67void GBAVideoThreadProxyRendererInit(struct GBAVideoRenderer* renderer) {
68 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
69 ConditionInit(&proxyRenderer->fromThreadCond);
70 ConditionInit(&proxyRenderer->toThreadCond);
71 MutexInit(&proxyRenderer->mutex);
72 RingFIFOInit(&proxyRenderer->dirtyQueue, 0x40000, 0x1000);
73
74 proxyRenderer->vramProxy = anonymousMemoryMap(SIZE_VRAM);
75 proxyRenderer->backend->palette = proxyRenderer->paletteProxy;
76 proxyRenderer->backend->vram = proxyRenderer->vramProxy;
77 proxyRenderer->backend->oam = &proxyRenderer->oamProxy;
78 proxyRenderer->backend->cache = NULL;
79
80 proxyRenderer->backend->init(proxyRenderer->backend);
81
82 proxyRenderer->vramDirtyBitmap = 0;
83 proxyRenderer->threadState = PROXY_THREAD_IDLE;
84 ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer);
85}
86
87void GBAVideoThreadProxyRendererReset(struct GBAVideoRenderer* renderer) {
88 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
89 MutexLock(&proxyRenderer->mutex);
90 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
91 ConditionWake(&proxyRenderer->toThreadCond);
92 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
93 }
94 memcpy(&proxyRenderer->oamProxy.raw, &renderer->oam->raw, SIZE_OAM);
95 memcpy(proxyRenderer->paletteProxy, renderer->palette, SIZE_PALETTE_RAM);
96 memcpy(proxyRenderer->vramProxy, renderer->vram, SIZE_VRAM);
97 proxyRenderer->backend->reset(proxyRenderer->backend);
98 MutexUnlock(&proxyRenderer->mutex);
99}
100
101void GBAVideoThreadProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
102 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
103 bool waiting = false;
104 MutexLock(&proxyRenderer->mutex);
105 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
106 ConditionWake(&proxyRenderer->toThreadCond);
107 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
108 }
109 if (proxyRenderer->threadState == PROXY_THREAD_IDLE) {
110 proxyRenderer->threadState = PROXY_THREAD_STOPPED;
111 ConditionWake(&proxyRenderer->toThreadCond);
112 waiting = true;
113 }
114 MutexUnlock(&proxyRenderer->mutex);
115 if (waiting) {
116 ThreadJoin(proxyRenderer->thread);
117 }
118 ConditionDeinit(&proxyRenderer->fromThreadCond);
119 ConditionDeinit(&proxyRenderer->toThreadCond);
120 MutexDeinit(&proxyRenderer->mutex);
121 proxyRenderer->backend->deinit(proxyRenderer->backend);
122
123 mappedMemoryFree(proxyRenderer->vramProxy, SIZE_VRAM);
124}
125
126uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
127 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
128 switch (address) {
129 case REG_BG0CNT:
130 case REG_BG1CNT:
131 case REG_BG2CNT:
132 case REG_BG3CNT:
133 value &= 0xFFCF;
134 break;
135 case REG_BG0HOFS:
136 case REG_BG0VOFS:
137 case REG_BG1HOFS:
138 case REG_BG1VOFS:
139 case REG_BG2HOFS:
140 case REG_BG2VOFS:
141 case REG_BG3HOFS:
142 case REG_BG3VOFS:
143 value &= 0x01FF;
144 break;
145 }
146 if (address > REG_BLDY) {
147 return value;
148 }
149
150 struct GBAVideoDirtyInfo dirty = {
151 DIRTY_REGISTER,
152 address,
153 value,
154 0xDEADBEEF,
155 };
156 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
157 return value;
158}
159
160void GBAVideoThreadProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
161 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
162 int bit = 1 << (address >> 12);
163 if (proxyRenderer->vramDirtyBitmap & bit) {
164 return;
165 }
166 proxyRenderer->vramDirtyBitmap |= bit;
167}
168
169void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
170 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
171 struct GBAVideoDirtyInfo dirty = {
172 DIRTY_PALETTE,
173 address,
174 value,
175 0xDEADBEEF,
176 };
177 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
178 if (renderer->cache) {
179 GBAVideoTileCacheWritePalette(renderer->cache, address);
180 }
181}
182
183void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
184 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
185 struct GBAVideoDirtyInfo dirty = {
186 DIRTY_OAM,
187 oam,
188 proxyRenderer->d.oam->raw[oam],
189 0xDEADBEEF,
190 };
191 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
192}
193
194void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
195 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
196 if (proxyRenderer->vramDirtyBitmap) {
197 int bitmap = proxyRenderer->vramDirtyBitmap;
198 proxyRenderer->vramDirtyBitmap = 0;
199 int j;
200 for (j = 0; j < 24; ++j) {
201 if (!(bitmap & (1 << j))) {
202 continue;
203 }
204 struct GBAVideoDirtyInfo dirty = {
205 DIRTY_VRAM,
206 j * 0x1000,
207 0xABCD,
208 0xDEADBEEF,
209 };
210 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
211 RingFIFOWrite(&proxyRenderer->dirtyQueue, &proxyRenderer->d.vram[j * 0x800], 0x1000);
212 }
213 }
214 struct GBAVideoDirtyInfo dirty = {
215 DIRTY_SCANLINE,
216 y,
217 0,
218 0xDEADBEEF,
219 };
220 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
221 if ((y & 15) == 15) {
222 ConditionWake(&proxyRenderer->toThreadCond);
223 }
224}
225
226void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
227 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
228 MutexLock(&proxyRenderer->mutex);
229 // Insert an extra item into the queue to make sure it gets flushed
230 struct GBAVideoDirtyInfo dirty = {
231 DIRTY_FLUSH,
232 0,
233 0,
234 0xDEADBEEF,
235 };
236 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
237 do {
238 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
239 ConditionWake(&proxyRenderer->toThreadCond);
240 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
241 } while (proxyRenderer->threadState == PROXY_THREAD_BUSY);
242 proxyRenderer->backend->finishFrame(proxyRenderer->backend);
243 proxyRenderer->vramDirtyBitmap = 0;
244 MutexUnlock(&proxyRenderer->mutex);
245}
246
247static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, const void** pixels) {
248 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; MutexLock(&proxyRenderer->mutex);
249 // Insert an extra item into the queue to make sure it gets flushed
250 struct GBAVideoDirtyInfo dirty = {
251 DIRTY_FLUSH,
252 0,
253 0,
254 0xDEADBEEF,
255 };
256 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
257 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
258 ConditionWake(&proxyRenderer->toThreadCond);
259 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
260 }
261 proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
262 MutexUnlock(&proxyRenderer->mutex);
263}
264
265static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
266 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; MutexLock(&proxyRenderer->mutex);
267 // Insert an extra item into the queue to make sure it gets flushed
268 struct GBAVideoDirtyInfo dirty = {
269 DIRTY_FLUSH,
270 0,
271 0,
272 0xDEADBEEF,
273 };
274 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
275 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
276 ConditionWake(&proxyRenderer->toThreadCond);
277 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
278 }
279 proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels);
280 MutexUnlock(&proxyRenderer->mutex);
281}
282
283static THREAD_ENTRY _proxyThread(void* renderer) {
284 struct GBAVideoThreadProxyRenderer* proxyRenderer = renderer;
285 ThreadSetName("Proxy Renderer Thread");
286
287 MutexLock(&proxyRenderer->mutex);
288 struct GBAVideoDirtyInfo item = {0};
289 while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
290 ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
291 if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
292 break;
293 }
294 if (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) {
295 proxyRenderer->threadState = PROXY_THREAD_BUSY;
296 MutexUnlock(&proxyRenderer->mutex);
297 do {
298 switch (item.type) {
299 case DIRTY_REGISTER:
300 proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item.address, item.value);
301 break;
302 case DIRTY_PALETTE:
303 proxyRenderer->paletteProxy[item.address >> 1] = item.value;
304 proxyRenderer->backend->writePalette(proxyRenderer->backend, item.address, item.value);
305 break;
306 case DIRTY_OAM:
307 proxyRenderer->oamProxy.raw[item.address] = item.value;
308 proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address);
309 break;
310 case DIRTY_VRAM:
311 while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000));
312 proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address);
313 break;
314 case DIRTY_SCANLINE:
315 proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address);
316 break;
317 case DIRTY_FLUSH:
318 MutexLock(&proxyRenderer->mutex);
319 goto out;
320 default:
321 // FIFO was corrupted
322 MutexLock(&proxyRenderer->mutex);
323 proxyRenderer->threadState = PROXY_THREAD_STOPPED;
324 goto out;
325 }
326 } while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item)));
327 MutexLock(&proxyRenderer->mutex);
328 }
329 out:
330 ConditionWake(&proxyRenderer->fromThreadCond);
331 if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
332 proxyRenderer->threadState = PROXY_THREAD_IDLE;
333 }
334 }
335 MutexUnlock(&proxyRenderer->mutex);
336
337#ifdef _3DS
338 svcExitThread();
339#endif
340 return 0;
341}
342
343#endif