all repos — mgba @ c3b411fb6b99e91f6f8582f699345806099facf2

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