all repos — mgba @ d03528d4daf55a2b64f47562c36663ba2318d0fe

mGBA Game Boy Advance Emulator

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