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 proxyRenderer->threadState = PROXY_THREAD_STOPPED;
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
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 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
92 }
93 memcpy(&proxyRenderer->oamProxy.raw, &renderer->oam->raw, SIZE_OAM);
94 memcpy(proxyRenderer->paletteProxy, renderer->palette, SIZE_PALETTE_RAM);
95 memcpy(proxyRenderer->vramProxy, renderer->vram, SIZE_VRAM);
96 proxyRenderer->backend->reset(proxyRenderer->backend);
97 MutexUnlock(&proxyRenderer->mutex);
98}
99
100void GBAVideoThreadProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
101 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
102 bool waiting = false;
103 MutexLock(&proxyRenderer->mutex);
104 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
105 ConditionWake(&proxyRenderer->toThreadCond);
106 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
107 }
108 if (proxyRenderer->threadState == PROXY_THREAD_IDLE) {
109 proxyRenderer->threadState = PROXY_THREAD_STOPPED;
110 ConditionWake(&proxyRenderer->toThreadCond);
111 waiting = true;
112 }
113 MutexUnlock(&proxyRenderer->mutex);
114 if (waiting) {
115 ThreadJoin(proxyRenderer->thread);
116 }
117 ConditionDeinit(&proxyRenderer->fromThreadCond);
118 ConditionDeinit(&proxyRenderer->toThreadCond);
119 MutexDeinit(&proxyRenderer->mutex);
120 proxyRenderer->backend->deinit(proxyRenderer->backend);
121
122 mappedMemoryFree(proxyRenderer->vramProxy, SIZE_VRAM);
123}
124
125uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
126 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
127 switch (address) {
128 case REG_BG0CNT:
129 case REG_BG1CNT:
130 case REG_BG2CNT:
131 case REG_BG3CNT:
132 value &= 0xFFCF;
133 break;
134 case REG_BG0HOFS:
135 case REG_BG0VOFS:
136 case REG_BG1HOFS:
137 case REG_BG1VOFS:
138 case REG_BG2HOFS:
139 case REG_BG2VOFS:
140 case REG_BG3HOFS:
141 case REG_BG3VOFS:
142 value &= 0x01FF;
143 break;
144 }
145 if (address > REG_BLDY) {
146 return value;
147 }
148
149 struct GBAVideoDirtyInfo dirty = {
150 DIRTY_REGISTER,
151 address,
152 value,
153 0xDEADBEEF,
154 };
155 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
156 return value;
157}
158
159void GBAVideoThreadProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
160 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
161 int bit = 1 << (address >> 12);
162 if (proxyRenderer->vramDirtyBitmap & bit) {
163 return;
164 }
165 proxyRenderer->vramDirtyBitmap |= bit;
166}
167
168void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
169 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
170 struct GBAVideoDirtyInfo dirty = {
171 DIRTY_PALETTE,
172 address,
173 value,
174 0xDEADBEEF,
175 };
176 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
177}
178
179void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
180 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
181 struct GBAVideoDirtyInfo dirty = {
182 DIRTY_OAM,
183 oam,
184 proxyRenderer->d.oam->raw[oam],
185 0xDEADBEEF,
186 };
187 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
188}
189
190void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
191 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
192 if (proxyRenderer->vramDirtyBitmap) {
193 int bitmap = proxyRenderer->vramDirtyBitmap;
194 proxyRenderer->vramDirtyBitmap = 0;
195 int j;
196 for (j = 0; j < 24; ++j) {
197 if (!(bitmap & (1 << j))) {
198 continue;
199 }
200 struct GBAVideoDirtyInfo dirty = {
201 DIRTY_VRAM,
202 j * 0x1000,
203 0xABCD,
204 0xDEADBEEF,
205 };
206 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
207 RingFIFOWrite(&proxyRenderer->dirtyQueue, &proxyRenderer->d.vram[j * 0x800], 0x1000);
208 }
209 }
210 struct GBAVideoDirtyInfo dirty = {
211 DIRTY_SCANLINE,
212 y,
213 0,
214 0xDEADBEEF,
215 };
216 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
217 if ((y & 15) == 15) {
218 ConditionWake(&proxyRenderer->toThreadCond);
219 }
220}
221
222void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
223 struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
224 MutexLock(&proxyRenderer->mutex);
225 // Insert an extra item into the queue to make sure it gets flushed
226 struct GBAVideoDirtyInfo dirty = {
227 DIRTY_FLUSH,
228 0,
229 0,
230 0xDEADBEEF,
231 };
232 RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
233 while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
234 ConditionWake(&proxyRenderer->toThreadCond);
235 ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
236 }
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 while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
284 ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
285 if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
286 break;
287 }
288 proxyRenderer->threadState = PROXY_THREAD_BUSY;
289
290 MutexUnlock(&proxyRenderer->mutex);
291 struct GBAVideoDirtyInfo item;
292 while (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) {
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 // This is only here to ensure the queue gets flushed
314 break;
315 default:
316 // FIFO was corrupted
317 proxyRenderer->threadState = PROXY_THREAD_STOPPED;
318 break;
319 }
320 }
321 MutexLock(&proxyRenderer->mutex);
322 ConditionWake(&proxyRenderer->fromThreadCond);
323 if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
324 proxyRenderer->threadState = PROXY_THREAD_IDLE;
325 }
326 }
327 MutexUnlock(&proxyRenderer->mutex);
328
329#ifdef _3DS
330 svcExitThread();
331#endif
332 return 0;
333}
334
335#endif