all repos — mgba @ ac115422264c242439c19aaf383b6b743a309c69

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