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, unsigned* stride, const void** pixels);
42static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, 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 while (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, WARN, "Can't write 0x%zu bytes. Proxy thread asleep?", length);
143 mLOG(GBA_VIDEO, DEBUG, "Queue status: read: %p, write: %p", proxyRenderer->dirtyQueue.readPtr, proxyRenderer->dirtyQueue.writePtr);
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}
199
200void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
201 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
202 struct GBAVideoDirtyInfo dirty = {
203 DIRTY_PALETTE,
204 address,
205 value,
206 0xDEADBEEF,
207 };
208 _writeData(proxyRenderer, &dirty, sizeof(dirty));
209 if (renderer->cache) {
210 GBAVideoTileCacheWritePalette(renderer->cache, address);
211 }
212}
213
214void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
215 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
216 struct GBAVideoDirtyInfo dirty = {
217 DIRTY_OAM,
218 oam,
219 proxyRenderer->d.oam->raw[oam],
220 0xDEADBEEF,
221 };
222 _writeData(proxyRenderer, &dirty, sizeof(dirty));
223}
224
225void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
226 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
227 if (proxyRenderer->vramDirtyBitmap) {
228 int bitmap = proxyRenderer->vramDirtyBitmap;
229 proxyRenderer->vramDirtyBitmap = 0;
230 int j;
231 for (j = 0; j < 24; ++j) {
232 if (!(bitmap & (1 << j))) {
233 continue;
234 }
235 struct GBAVideoDirtyInfo dirty = {
236 DIRTY_VRAM,
237 j * 0x1000,
238 0xABCD,
239 0xDEADBEEF,
240 };
241 _writeData(proxyRenderer, &dirty, sizeof(dirty));
242 _writeData(proxyRenderer, &proxyRenderer->d.vram[j * 0x800], 0x1000);
243 }
244 }
245 struct GBAVideoDirtyInfo dirty = {
246 DIRTY_SCANLINE,
247 y,
248 0,
249 0xDEADBEEF,
250 };
251 _writeData(proxyRenderer, &dirty, sizeof(dirty));
252 if ((y & 15) == 15) {
253 ConditionWake(&proxyRenderer->toThreadCond);
254 }
255}
256
257void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
258 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
259 if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
260 mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
261 _proxyThreadRecover(proxyRenderer);
262 return;
263 }
264 MutexLock(&proxyRenderer->mutex);
265 // Insert an extra item into the queue to make sure it gets flushed
266 struct GBAVideoDirtyInfo dirty = {
267 DIRTY_FLUSH,
268 0,
269 0,
270 0xDEADBEEF,
271 };
272 _writeData(proxyRenderer, &dirty, sizeof(dirty));
273 do {
274 ConditionWake(&proxyRenderer->toThreadCond);
275 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
276 } while (proxyRenderer->threadState == PROXY_THREAD_BUSY);
277 proxyRenderer->backend->finishFrame(proxyRenderer->backend);
278 proxyRenderer->vramDirtyBitmap = 0;
279 MutexUnlock(&proxyRenderer->mutex);
280}
281
282static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, const void** pixels) {
283 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
284 MutexLock(&proxyRenderer->mutex);
285 // Insert an extra item into the queue to make sure it gets flushed
286 struct GBAVideoDirtyInfo dirty = {
287 DIRTY_FLUSH,
288 0,
289 0,
290 0xDEADBEEF,
291 };
292 _writeData(proxyRenderer, &dirty, sizeof(dirty));
293 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
294 ConditionWake(&proxyRenderer->toThreadCond);
295 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
296 }
297 proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
298 MutexUnlock(&proxyRenderer->mutex);
299}
300
301static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
302 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
303 MutexLock(&proxyRenderer->mutex);
304 // Insert an extra item into the queue to make sure it gets flushed
305 struct GBAVideoDirtyInfo dirty = {
306 DIRTY_FLUSH,
307 0,
308 0,
309 0xDEADBEEF,
310 };
311 _writeData(proxyRenderer, &dirty, sizeof(dirty));
312 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
313 ConditionWake(&proxyRenderer->toThreadCond);
314 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
315 }
316 proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels);
317 MutexUnlock(&proxyRenderer->mutex);
318}
319
320static THREAD_ENTRY _proxyThread(void* renderer) {
321 struct GBAVideoThreadProxyRenderer* proxyRenderer = renderer;
322 ThreadSetName("Proxy Renderer Thread");
323
324 MutexLock(&proxyRenderer->mutex);
325 struct GBAVideoDirtyInfo item = {0};
326 while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
327 ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
328 if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
329 break;
330 }
331 if (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) {
332 proxyRenderer->threadState = PROXY_THREAD_BUSY;
333 MutexUnlock(&proxyRenderer->mutex);
334 do {
335 switch (item.type) {
336 case DIRTY_REGISTER:
337 proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item.address, item.value);
338 break;
339 case DIRTY_PALETTE:
340 proxyRenderer->paletteProxy[item.address >> 1] = item.value;
341 proxyRenderer->backend->writePalette(proxyRenderer->backend, item.address, item.value);
342 break;
343 case DIRTY_OAM:
344 proxyRenderer->oamProxy.raw[item.address] = item.value;
345 proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address);
346 break;
347 case DIRTY_VRAM:
348 while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000)) {
349 mLOG(GBA_VIDEO, WARN, "Proxy thread can't read VRAM. CPU thread asleep?");
350 MutexLock(&proxyRenderer->mutex);
351 ConditionWake(&proxyRenderer->fromThreadCond);
352 proxyRenderer->threadState = PROXY_THREAD_IDLE;
353 ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
354 proxyRenderer->threadState = PROXY_THREAD_BUSY;
355 MutexUnlock(&proxyRenderer->mutex);
356 }
357 proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address);
358 break;
359 case DIRTY_SCANLINE:
360 proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address);
361 break;
362 case DIRTY_FLUSH:
363 MutexLock(&proxyRenderer->mutex);
364 goto out;
365 default:
366 // FIFO was corrupted
367 MutexLock(&proxyRenderer->mutex);
368 proxyRenderer->threadState = PROXY_THREAD_STOPPED;
369 mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!");
370 goto out;
371 }
372 } while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item)));
373 MutexLock(&proxyRenderer->mutex);
374 }
375 out:
376 ConditionWake(&proxyRenderer->fromThreadCond);
377 if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
378 proxyRenderer->threadState = PROXY_THREAD_IDLE;
379 }
380 }
381 MutexUnlock(&proxyRenderer->mutex);
382
383#ifdef _3DS
384 svcExitThread();
385#endif
386 return 0;
387}
388
389#endif