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);
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
126static bool _writeData(struct GBAVideoThreadProxyRenderer* proxyRenderer, void* data, size_t length) {
127 while (!RingFIFOWrite(&proxyRenderer->dirtyQueue, data, length)) {
128 mLOG(GBA_VIDEO, WARN, "Can't write 0x%z bytes. Proxy thread asleep?", length);
129 mLOG(GBA_VIDEO, DEBUG, "Queue status: read: %p, write: %p", proxyRenderer->dirtyQueue.readPtr, proxyRenderer->dirtyQueue.writePtr);
130 MutexLock(&proxyRenderer->mutex);
131 if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
132 mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
133 MutexUnlock(&proxyRenderer->mutex);
134 return false;
135 }
136 ConditionWake(&proxyRenderer->toThreadCond);
137 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
138 MutexUnlock(&proxyRenderer->mutex);
139 }
140 return true;
141}
142
143uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
144 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
145 switch (address) {
146 case REG_BG0CNT:
147 case REG_BG1CNT:
148 case REG_BG2CNT:
149 case REG_BG3CNT:
150 value &= 0xFFCF;
151 break;
152 case REG_BG0HOFS:
153 case REG_BG0VOFS:
154 case REG_BG1HOFS:
155 case REG_BG1VOFS:
156 case REG_BG2HOFS:
157 case REG_BG2VOFS:
158 case REG_BG3HOFS:
159 case REG_BG3VOFS:
160 value &= 0x01FF;
161 break;
162 }
163 if (address > REG_BLDY) {
164 return value;
165 }
166
167 struct GBAVideoDirtyInfo dirty = {
168 DIRTY_REGISTER,
169 address,
170 value,
171 0xDEADBEEF,
172 };
173 _writeData(proxyRenderer, &dirty, sizeof(dirty));
174 return value;
175}
176
177void GBAVideoThreadProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
178 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
179 int bit = 1 << (address >> 12);
180 if (proxyRenderer->vramDirtyBitmap & bit) {
181 return;
182 }
183 proxyRenderer->vramDirtyBitmap |= bit;
184}
185
186void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
187 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
188 struct GBAVideoDirtyInfo dirty = {
189 DIRTY_PALETTE,
190 address,
191 value,
192 0xDEADBEEF,
193 };
194 _writeData(proxyRenderer, &dirty, sizeof(dirty));
195 if (renderer->cache) {
196 GBAVideoTileCacheWritePalette(renderer->cache, address);
197 }
198}
199
200void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
201 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
202 struct GBAVideoDirtyInfo dirty = {
203 DIRTY_OAM,
204 oam,
205 proxyRenderer->d.oam->raw[oam],
206 0xDEADBEEF,
207 };
208 _writeData(proxyRenderer, &dirty, sizeof(dirty));
209}
210
211void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
212 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
213 if (proxyRenderer->vramDirtyBitmap) {
214 int bitmap = proxyRenderer->vramDirtyBitmap;
215 proxyRenderer->vramDirtyBitmap = 0;
216 int j;
217 for (j = 0; j < 24; ++j) {
218 if (!(bitmap & (1 << j))) {
219 continue;
220 }
221 struct GBAVideoDirtyInfo dirty = {
222 DIRTY_VRAM,
223 j * 0x1000,
224 0xABCD,
225 0xDEADBEEF,
226 };
227 _writeData(proxyRenderer, &dirty, sizeof(dirty));
228 _writeData(proxyRenderer, &proxyRenderer->d.vram[j * 0x800], 0x1000);
229 }
230 }
231 struct GBAVideoDirtyInfo dirty = {
232 DIRTY_SCANLINE,
233 y,
234 0,
235 0xDEADBEEF,
236 };
237 _writeData(proxyRenderer, &dirty, sizeof(dirty));
238 if ((y & 15) == 15) {
239 ConditionWake(&proxyRenderer->toThreadCond);
240 }
241}
242
243void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
244 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
245 if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
246 mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
247 GBAVideoThreadProxyRendererDeinit(renderer);
248 GBAVideoThreadProxyRendererInit(renderer);
249 return;
250 }
251 MutexLock(&proxyRenderer->mutex);
252 // Insert an extra item into the queue to make sure it gets flushed
253 struct GBAVideoDirtyInfo dirty = {
254 DIRTY_FLUSH,
255 0,
256 0,
257 0xDEADBEEF,
258 };
259 _writeData(proxyRenderer, &dirty, sizeof(dirty));
260 do {
261 ConditionWake(&proxyRenderer->toThreadCond);
262 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
263 } while (proxyRenderer->threadState == PROXY_THREAD_BUSY);
264 proxyRenderer->backend->finishFrame(proxyRenderer->backend);
265 proxyRenderer->vramDirtyBitmap = 0;
266 MutexUnlock(&proxyRenderer->mutex);
267}
268
269static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, const void** pixels) {
270 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
271 MutexLock(&proxyRenderer->mutex);
272 // Insert an extra item into the queue to make sure it gets flushed
273 struct GBAVideoDirtyInfo dirty = {
274 DIRTY_FLUSH,
275 0,
276 0,
277 0xDEADBEEF,
278 };
279 _writeData(proxyRenderer, &dirty, sizeof(dirty));
280 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
281 ConditionWake(&proxyRenderer->toThreadCond);
282 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
283 }
284 proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
285 MutexUnlock(&proxyRenderer->mutex);
286}
287
288static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
289 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
290 MutexLock(&proxyRenderer->mutex);
291 // Insert an extra item into the queue to make sure it gets flushed
292 struct GBAVideoDirtyInfo dirty = {
293 DIRTY_FLUSH,
294 0,
295 0,
296 0xDEADBEEF,
297 };
298 _writeData(proxyRenderer, &dirty, sizeof(dirty));
299 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
300 ConditionWake(&proxyRenderer->toThreadCond);
301 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
302 }
303 proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels);
304 MutexUnlock(&proxyRenderer->mutex);
305}
306
307static THREAD_ENTRY _proxyThread(void* renderer) {
308 struct GBAVideoThreadProxyRenderer* proxyRenderer = renderer;
309 ThreadSetName("Proxy Renderer Thread");
310
311 MutexLock(&proxyRenderer->mutex);
312 struct GBAVideoDirtyInfo item = {0};
313 while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
314 ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
315 if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
316 break;
317 }
318 if (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) {
319 proxyRenderer->threadState = PROXY_THREAD_BUSY;
320 MutexUnlock(&proxyRenderer->mutex);
321 do {
322 switch (item.type) {
323 case DIRTY_REGISTER:
324 proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item.address, item.value);
325 break;
326 case DIRTY_PALETTE:
327 proxyRenderer->paletteProxy[item.address >> 1] = item.value;
328 proxyRenderer->backend->writePalette(proxyRenderer->backend, item.address, item.value);
329 break;
330 case DIRTY_OAM:
331 proxyRenderer->oamProxy.raw[item.address] = item.value;
332 proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address);
333 break;
334 case DIRTY_VRAM:
335 while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000)) {
336 mLOG(GBA_VIDEO, WARN, "Proxy thread can't read VRAM. CPU thread asleep?");
337 MutexLock(&proxyRenderer->mutex);
338 ConditionWake(&proxyRenderer->fromThreadCond);
339 proxyRenderer->threadState = PROXY_THREAD_IDLE;
340 ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
341 proxyRenderer->threadState = PROXY_THREAD_BUSY;
342 MutexUnlock(&proxyRenderer->mutex);
343 }
344 proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address);
345 break;
346 case DIRTY_SCANLINE:
347 proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address);
348 break;
349 case DIRTY_FLUSH:
350 MutexLock(&proxyRenderer->mutex);
351 goto out;
352 default:
353 // FIFO was corrupted
354 MutexLock(&proxyRenderer->mutex);
355 proxyRenderer->threadState = PROXY_THREAD_STOPPED;
356 mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!");
357 goto out;
358 }
359 } while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item)));
360 MutexLock(&proxyRenderer->mutex);
361 }
362 out:
363 ConditionWake(&proxyRenderer->fromThreadCond);
364 if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
365 proxyRenderer->threadState = PROXY_THREAD_IDLE;
366 }
367 }
368 MutexUnlock(&proxyRenderer->mutex);
369
370#ifdef _3DS
371 svcExitThread();
372#endif
373 return 0;
374}
375
376#endif