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