all repos — mgba @ 12687faad214b6424c216cc911ebafe239678e7c

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, size_t* stride, const void** pixels);
 42static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const 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	if (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, DEBUG, "Can't write %"PRIz"u bytes. Proxy thread asleep?", length);
143		MutexLock(&proxyRenderer->mutex);
144		if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
145			mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
146			MutexUnlock(&proxyRenderer->mutex);
147			return false;
148		}
149		ConditionWake(&proxyRenderer->toThreadCond);
150		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
151		MutexUnlock(&proxyRenderer->mutex);
152	}
153	return true;
154}
155
156uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
157	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
158	switch (address) {
159	case REG_BG0CNT:
160	case REG_BG1CNT:
161	case REG_BG2CNT:
162	case REG_BG3CNT:
163		value &= 0xFFCF;
164		break;
165	case REG_BG0HOFS:
166	case REG_BG0VOFS:
167	case REG_BG1HOFS:
168	case REG_BG1VOFS:
169	case REG_BG2HOFS:
170	case REG_BG2VOFS:
171	case REG_BG3HOFS:
172	case REG_BG3VOFS:
173		value &= 0x01FF;
174		break;
175	}
176	if (address > REG_BLDY) {
177		return value;
178	}
179
180	struct GBAVideoDirtyInfo dirty = {
181		DIRTY_REGISTER,
182		address,
183		value,
184		0xDEADBEEF,
185	};
186	_writeData(proxyRenderer, &dirty, sizeof(dirty));
187	return value;
188}
189
190void GBAVideoThreadProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
191	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
192	int bit = 1 << (address >> 12);
193	if (proxyRenderer->vramDirtyBitmap & bit) {
194		return;
195	}
196	proxyRenderer->vramDirtyBitmap |= bit;
197}
198
199void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
200	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
201	struct GBAVideoDirtyInfo dirty = {
202		DIRTY_PALETTE,
203		address,
204		value,
205		0xDEADBEEF,
206	};
207	_writeData(proxyRenderer, &dirty, sizeof(dirty));
208	if (renderer->cache) {
209		GBAVideoTileCacheWritePalette(renderer->cache, address);
210	}
211}
212
213void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
214	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
215	struct GBAVideoDirtyInfo dirty = {
216		DIRTY_OAM,
217		oam,
218		proxyRenderer->d.oam->raw[oam],
219		0xDEADBEEF,
220	};
221	_writeData(proxyRenderer, &dirty, sizeof(dirty));
222}
223
224void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
225	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
226	if (proxyRenderer->vramDirtyBitmap) {
227		int bitmap = proxyRenderer->vramDirtyBitmap;
228		proxyRenderer->vramDirtyBitmap = 0;
229		int j;
230		for (j = 0; j < 24; ++j) {
231			if (!(bitmap & (1 << j))) {
232				continue;
233			}
234			struct GBAVideoDirtyInfo dirty = {
235				DIRTY_VRAM,
236				j * 0x1000,
237				0xABCD,
238				0xDEADBEEF,
239			};
240			_writeData(proxyRenderer, &dirty, sizeof(dirty));
241			_writeData(proxyRenderer, &proxyRenderer->d.vram[j * 0x800], 0x1000);
242		}
243	}
244	struct GBAVideoDirtyInfo dirty = {
245		DIRTY_SCANLINE,
246		y,
247		0,
248		0xDEADBEEF,
249	};
250	_writeData(proxyRenderer, &dirty, sizeof(dirty));
251	if ((y & 15) == 15) {
252		ConditionWake(&proxyRenderer->toThreadCond);
253	}
254}
255
256void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
257	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
258	if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
259		mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
260		_proxyThreadRecover(proxyRenderer);
261		return;
262	}
263	MutexLock(&proxyRenderer->mutex);
264	// Insert an extra item into the queue to make sure it gets flushed
265	struct GBAVideoDirtyInfo dirty = {
266		DIRTY_FLUSH,
267		0,
268		0,
269		0xDEADBEEF,
270	};
271	_writeData(proxyRenderer, &dirty, sizeof(dirty));
272	do {
273		ConditionWake(&proxyRenderer->toThreadCond);
274		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
275	} while (proxyRenderer->threadState == PROXY_THREAD_BUSY);
276	proxyRenderer->backend->finishFrame(proxyRenderer->backend);
277	proxyRenderer->vramDirtyBitmap = 0;
278	MutexUnlock(&proxyRenderer->mutex);
279}
280
281static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
282	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
283	MutexLock(&proxyRenderer->mutex);
284	// Insert an extra item into the queue to make sure it gets flushed
285	struct GBAVideoDirtyInfo dirty = {
286		DIRTY_FLUSH,
287		0,
288		0,
289		0xDEADBEEF,
290	};
291	_writeData(proxyRenderer, &dirty, sizeof(dirty));
292	while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
293		ConditionWake(&proxyRenderer->toThreadCond);
294		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
295	}
296	proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
297	MutexUnlock(&proxyRenderer->mutex);
298}
299
300static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
301	struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
302	MutexLock(&proxyRenderer->mutex);
303	// Insert an extra item into the queue to make sure it gets flushed
304	struct GBAVideoDirtyInfo dirty = {
305		DIRTY_FLUSH,
306		0,
307		0,
308		0xDEADBEEF,
309	};
310	_writeData(proxyRenderer, &dirty, sizeof(dirty));
311	while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
312		ConditionWake(&proxyRenderer->toThreadCond);
313		ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
314	}
315	proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels);
316	MutexUnlock(&proxyRenderer->mutex);
317}
318
319static THREAD_ENTRY _proxyThread(void* renderer) {
320	struct GBAVideoThreadProxyRenderer* proxyRenderer = renderer;
321	ThreadSetName("Proxy Renderer Thread");
322
323	MutexLock(&proxyRenderer->mutex);
324	struct GBAVideoDirtyInfo item = {0};
325	while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
326		ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
327		if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
328			break;
329		}
330		if (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) {
331			proxyRenderer->threadState = PROXY_THREAD_BUSY;
332			MutexUnlock(&proxyRenderer->mutex);
333			do {
334				switch (item.type) {
335				case DIRTY_REGISTER:
336					proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item.address, item.value);
337					break;
338				case DIRTY_PALETTE:
339					proxyRenderer->paletteProxy[item.address >> 1] = item.value;
340					proxyRenderer->backend->writePalette(proxyRenderer->backend, item.address, item.value);
341					break;
342				case DIRTY_OAM:
343					proxyRenderer->oamProxy.raw[item.address] = item.value;
344					proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address);
345					break;
346				case DIRTY_VRAM:
347					while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000)) {
348						mLOG(GBA_VIDEO, DEBUG, "Proxy thread can't read VRAM. CPU thread asleep?");
349						MutexLock(&proxyRenderer->mutex);
350						ConditionWake(&proxyRenderer->fromThreadCond);
351						ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
352						MutexUnlock(&proxyRenderer->mutex);
353					}
354					proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address);
355					break;
356				case DIRTY_SCANLINE:
357					proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address);
358					break;
359				case DIRTY_FLUSH:
360					MutexLock(&proxyRenderer->mutex);
361					goto out;
362				default:
363					// FIFO was corrupted
364					MutexLock(&proxyRenderer->mutex);
365					proxyRenderer->threadState = PROXY_THREAD_STOPPED;
366					mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!");
367					goto out;
368				}
369			} while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item)));
370			MutexLock(&proxyRenderer->mutex);
371		}
372		out:
373		ConditionWake(&proxyRenderer->fromThreadCond);
374		if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
375			proxyRenderer->threadState = PROXY_THREAD_IDLE;
376		}
377	}
378	MutexUnlock(&proxyRenderer->mutex);
379
380#ifdef _3DS
381	svcExitThread();
382#endif
383	return 0;
384}
385
386#endif