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