all repos — mgba @ fa884d071ecaa3e05ff20b45a67bf9500dd3d6b6

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	proxyRenderer->backend->vram = proxyRenderer->vramProxy;
 79	proxyRenderer->backend->oam = &proxyRenderer->oamProxy;
 80	proxyRenderer->backend->cache = NULL;
 81
 82	proxyRenderer->backend->init(proxyRenderer->backend);
 83
 84	proxyRenderer->vramDirtyBitmap = 0;
 85	proxyRenderer->threadState = PROXY_THREAD_IDLE;
 86	ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer);
 87}
 88
 89void GBAVideoThreadProxyRendererReset(struct GBAVideoRenderer* renderer) {
 90	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
 91	MutexLock(&proxyRenderer->mutex);
 92	while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
 93		ConditionWake(&proxyRenderer->toThreadCond);
 94		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
 95	}
 96	memcpy(&proxyRenderer->oamProxy.raw, &renderer->oam->raw, SIZE_OAM);
 97	memcpy(proxyRenderer->paletteProxy, renderer->palette, SIZE_PALETTE_RAM);
 98	memcpy(proxyRenderer->vramProxy, renderer->vram, SIZE_VRAM);
 99	proxyRenderer->backend->reset(proxyRenderer->backend);
100	MutexUnlock(&proxyRenderer->mutex);
101}
102
103void GBAVideoThreadProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
104	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
105	bool waiting = false;
106	MutexLock(&proxyRenderer->mutex);
107	while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
108		ConditionWake(&proxyRenderer->toThreadCond);
109		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
110	}
111	if (proxyRenderer->threadState == PROXY_THREAD_IDLE) {
112		proxyRenderer->threadState = PROXY_THREAD_STOPPED;
113		ConditionWake(&proxyRenderer->toThreadCond);
114		waiting = true;
115	}
116	MutexUnlock(&proxyRenderer->mutex);
117	if (waiting) {
118		ThreadJoin(proxyRenderer->thread);
119	}
120	ConditionDeinit(&proxyRenderer->fromThreadCond);
121	ConditionDeinit(&proxyRenderer->toThreadCond);
122	MutexDeinit(&proxyRenderer->mutex);
123	proxyRenderer->backend->deinit(proxyRenderer->backend);
124
125	mappedMemoryFree(proxyRenderer->vramProxy, SIZE_VRAM);
126}
127
128void _proxyThreadRecover(struct GBAVideoThreadProxyRenderer* proxyRenderer) {
129	MutexLock(&proxyRenderer->mutex);
130	if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
131		MutexUnlock(&proxyRenderer->mutex);
132		return;
133	}
134	RingFIFOClear(&proxyRenderer->dirtyQueue);
135	MutexUnlock(&proxyRenderer->mutex);
136	ThreadJoin(proxyRenderer->thread);
137	proxyRenderer->threadState = PROXY_THREAD_IDLE;
138	ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer);
139}
140
141static bool _writeData(struct GBAVideoThreadProxyRenderer* proxyRenderer, void* data, size_t length) {
142	while (!RingFIFOWrite(&proxyRenderer->dirtyQueue, data, length)) {
143		mLOG(GBA_VIDEO, DEBUG, "Can't write %"PRIz"u bytes. Proxy thread asleep?", length);
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	if (renderer->cache) {
199		mTileCacheWriteVRAM(renderer->cache, address);
200	}
201}
202
203void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
204	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
205	struct GBAVideoDirtyInfo dirty = {
206		DIRTY_PALETTE,
207		address,
208		value,
209		0xDEADBEEF,
210	};
211	_writeData(proxyRenderer, &dirty, sizeof(dirty));
212	if (renderer->cache) {
213		mTileCacheWritePalette(renderer->cache, address);
214	}
215}
216
217void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
218	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
219	struct GBAVideoDirtyInfo dirty = {
220		DIRTY_OAM,
221		oam,
222		proxyRenderer->d.oam->raw[oam],
223		0xDEADBEEF,
224	};
225	_writeData(proxyRenderer, &dirty, sizeof(dirty));
226}
227
228void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
229	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
230	if (proxyRenderer->vramDirtyBitmap) {
231		int bitmap = proxyRenderer->vramDirtyBitmap;
232		proxyRenderer->vramDirtyBitmap = 0;
233		int j;
234		for (j = 0; j < 24; ++j) {
235			if (!(bitmap & (1 << j))) {
236				continue;
237			}
238			struct GBAVideoDirtyInfo dirty = {
239				DIRTY_VRAM,
240				j * 0x1000,
241				0xABCD,
242				0xDEADBEEF,
243			};
244			_writeData(proxyRenderer, &dirty, sizeof(dirty));
245			_writeData(proxyRenderer, &proxyRenderer->d.vram[j * 0x800], 0x1000);
246		}
247	}
248	struct GBAVideoDirtyInfo dirty = {
249		DIRTY_SCANLINE,
250		y,
251		0,
252		0xDEADBEEF,
253	};
254	_writeData(proxyRenderer, &dirty, sizeof(dirty));
255	if ((y & 15) == 15) {
256		ConditionWake(&proxyRenderer->toThreadCond);
257	}
258}
259
260void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
261	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
262	if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
263		mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
264		_proxyThreadRecover(proxyRenderer);
265		return;
266	}
267	MutexLock(&proxyRenderer->mutex);
268	// Insert an extra item into the queue to make sure it gets flushed
269	struct GBAVideoDirtyInfo dirty = {
270		DIRTY_FLUSH,
271		0,
272		0,
273		0xDEADBEEF,
274	};
275	_writeData(proxyRenderer, &dirty, sizeof(dirty));
276	do {
277		ConditionWake(&proxyRenderer->toThreadCond);
278		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
279	} while (proxyRenderer->threadState == PROXY_THREAD_BUSY);
280	proxyRenderer->backend->finishFrame(proxyRenderer->backend);
281	proxyRenderer->vramDirtyBitmap = 0;
282	MutexUnlock(&proxyRenderer->mutex);
283}
284
285static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
286	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
287	MutexLock(&proxyRenderer->mutex);
288	// Insert an extra item into the queue to make sure it gets flushed
289	struct GBAVideoDirtyInfo dirty = {
290		DIRTY_FLUSH,
291		0,
292		0,
293		0xDEADBEEF,
294	};
295	_writeData(proxyRenderer, &dirty, sizeof(dirty));
296	while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
297		ConditionWake(&proxyRenderer->toThreadCond);
298		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
299	}
300	proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
301	MutexUnlock(&proxyRenderer->mutex);
302}
303
304static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
305	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
306	MutexLock(&proxyRenderer->mutex);
307	// Insert an extra item into the queue to make sure it gets flushed
308	struct GBAVideoDirtyInfo dirty = {
309		DIRTY_FLUSH,
310		0,
311		0,
312		0xDEADBEEF,
313	};
314	_writeData(proxyRenderer, &dirty, sizeof(dirty));
315	while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
316		ConditionWake(&proxyRenderer->toThreadCond);
317		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
318	}
319	proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels);
320	MutexUnlock(&proxyRenderer->mutex);
321}
322
323static THREAD_ENTRY _proxyThread(void* renderer) {
324	struct GBAVideoThreadProxyRenderer* proxyRenderer = renderer;
325	ThreadSetName("Proxy Renderer Thread");
326
327	MutexLock(&proxyRenderer->mutex);
328	struct GBAVideoDirtyInfo item = {0};
329	while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
330		ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
331		if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
332			break;
333		}
334		if (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) {
335			proxyRenderer->threadState = PROXY_THREAD_BUSY;
336			MutexUnlock(&proxyRenderer->mutex);
337			do {
338				switch (item.type) {
339				case DIRTY_REGISTER:
340					proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item.address, item.value);
341					break;
342				case DIRTY_PALETTE:
343					proxyRenderer->paletteProxy[item.address >> 1] = item.value;
344					proxyRenderer->backend->writePalette(proxyRenderer->backend, item.address, item.value);
345					break;
346				case DIRTY_OAM:
347					proxyRenderer->oamProxy.raw[item.address] = item.value;
348					proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address);
349					break;
350				case DIRTY_VRAM:
351					while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000)) {
352						mLOG(GBA_VIDEO, DEBUG, "Proxy thread can't read VRAM. CPU thread asleep?");
353						MutexLock(&proxyRenderer->mutex);
354						ConditionWake(&proxyRenderer->fromThreadCond);
355						ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
356						MutexUnlock(&proxyRenderer->mutex);
357					}
358					proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address);
359					break;
360				case DIRTY_SCANLINE:
361					proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address);
362					break;
363				case DIRTY_FLUSH:
364					MutexLock(&proxyRenderer->mutex);
365					goto out;
366				default:
367					// FIFO was corrupted
368					MutexLock(&proxyRenderer->mutex);
369					proxyRenderer->threadState = PROXY_THREAD_STOPPED;
370					mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!");
371					goto out;
372				}
373			} while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item)));
374			MutexLock(&proxyRenderer->mutex);
375		}
376		out:
377		ConditionWake(&proxyRenderer->fromThreadCond);
378		if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
379			proxyRenderer->threadState = PROXY_THREAD_IDLE;
380		}
381	}
382	MutexUnlock(&proxyRenderer->mutex);
383
384#ifdef _3DS
385	svcExitThread();
386#endif
387	return 0;
388}
389
390#endif