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