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