GBA Video: Fix threaded rendering race conditions

This commit is contained in:
Jeffrey Pfau 2016-08-09 22:30:35 -07:00
parent 492c2612b9
commit 30f124fae4
2 changed files with 56 additions and 42 deletions

View File

@ -70,7 +70,6 @@ void GBAVideoThreadProxyRendererInit(struct GBAVideoRenderer* renderer) {
ConditionInit(&proxyRenderer->toThreadCond); ConditionInit(&proxyRenderer->toThreadCond);
MutexInit(&proxyRenderer->mutex); MutexInit(&proxyRenderer->mutex);
RingFIFOInit(&proxyRenderer->dirtyQueue, 0x40000, 0x1000); RingFIFOInit(&proxyRenderer->dirtyQueue, 0x40000, 0x1000);
proxyRenderer->threadState = PROXY_THREAD_STOPPED;
proxyRenderer->vramProxy = anonymousMemoryMap(SIZE_VRAM); proxyRenderer->vramProxy = anonymousMemoryMap(SIZE_VRAM);
proxyRenderer->backend->palette = proxyRenderer->paletteProxy; proxyRenderer->backend->palette = proxyRenderer->paletteProxy;
@ -230,10 +229,11 @@ void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { do {
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
ConditionWake(&proxyRenderer->toThreadCond); ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
} } while (proxyRenderer->threadState == PROXY_THREAD_BUSY);
proxyRenderer->backend->finishFrame(proxyRenderer->backend); proxyRenderer->backend->finishFrame(proxyRenderer->backend);
proxyRenderer->vramDirtyBitmap = 0; proxyRenderer->vramDirtyBitmap = 0;
MutexUnlock(&proxyRenderer->mutex); MutexUnlock(&proxyRenderer->mutex);
@ -280,45 +280,48 @@ static THREAD_ENTRY _proxyThread(void* renderer) {
ThreadSetName("Proxy Renderer Thread"); ThreadSetName("Proxy Renderer Thread");
MutexLock(&proxyRenderer->mutex); MutexLock(&proxyRenderer->mutex);
struct GBAVideoDirtyInfo item = {0};
while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) { while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex); ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) { if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
break; break;
} }
proxyRenderer->threadState = PROXY_THREAD_BUSY; if (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) {
proxyRenderer->threadState = PROXY_THREAD_BUSY;
MutexUnlock(&proxyRenderer->mutex); MutexUnlock(&proxyRenderer->mutex);
struct GBAVideoDirtyInfo item; do {
while (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) { switch (item.type) {
switch (item.type) { case DIRTY_REGISTER:
case DIRTY_REGISTER: proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item.address, item.value);
proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item.address, item.value); break;
break; case DIRTY_PALETTE:
case DIRTY_PALETTE: proxyRenderer->paletteProxy[item.address >> 1] = item.value;
proxyRenderer->paletteProxy[item.address >> 1] = item.value; proxyRenderer->backend->writePalette(proxyRenderer->backend, item.address, item.value);
proxyRenderer->backend->writePalette(proxyRenderer->backend, item.address, item.value); break;
break; case DIRTY_OAM:
case DIRTY_OAM: proxyRenderer->oamProxy.raw[item.address] = item.value;
proxyRenderer->oamProxy.raw[item.address] = item.value; proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address);
proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address); break;
break; case DIRTY_VRAM:
case DIRTY_VRAM: while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000));
while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000)); proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address);
proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address); break;
break; case DIRTY_SCANLINE:
case DIRTY_SCANLINE: proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address);
proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address); break;
break; case DIRTY_FLUSH:
case DIRTY_FLUSH: MutexLock(&proxyRenderer->mutex);
// This is only here to ensure the queue gets flushed goto out;
break; default:
default: // FIFO was corrupted
// FIFO was corrupted MutexLock(&proxyRenderer->mutex);
proxyRenderer->threadState = PROXY_THREAD_STOPPED; proxyRenderer->threadState = PROXY_THREAD_STOPPED;
break; goto out;
} }
} while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item)));
MutexLock(&proxyRenderer->mutex);
} }
MutexLock(&proxyRenderer->mutex); out:
ConditionWake(&proxyRenderer->fromThreadCond); ConditionWake(&proxyRenderer->fromThreadCond);
if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) { if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
proxyRenderer->threadState = PROXY_THREAD_IDLE; proxyRenderer->threadState = PROXY_THREAD_IDLE;

View File

@ -7,6 +7,15 @@
#include "util/memory.h" #include "util/memory.h"
#ifndef _MSC_VER
#define ATOMIC_STORE(DST, SRC) __atomic_store_n(&DST, SRC, __ATOMIC_RELEASE)
#define ATOMIC_LOAD(DST, SRC) DST = __atomic_load_n(&SRC, __ATOMIC_ACQUIRE)
#else
// TODO
#define ATOMIC_STORE(DST, SRC) DST = SRC
#define ATOMIC_LOAD(DST, SRC) DST = SRC
#endif
void RingFIFOInit(struct RingFIFO* buffer, size_t capacity, size_t maxalloc) { void RingFIFOInit(struct RingFIFO* buffer, size_t capacity, size_t maxalloc) {
buffer->data = anonymousMemoryMap(capacity); buffer->data = anonymousMemoryMap(capacity);
buffer->capacity = capacity; buffer->capacity = capacity;
@ -24,13 +33,14 @@ size_t RingFIFOCapacity(const struct RingFIFO* buffer) {
} }
void RingFIFOClear(struct RingFIFO* buffer) { void RingFIFOClear(struct RingFIFO* buffer) {
buffer->readPtr = buffer->data; ATOMIC_STORE(buffer->readPtr, buffer->data);
buffer->writePtr = buffer->data; ATOMIC_STORE(buffer->writePtr, buffer->data);
} }
size_t RingFIFOWrite(struct RingFIFO* buffer, const void* value, size_t length) { size_t RingFIFOWrite(struct RingFIFO* buffer, const void* value, size_t length) {
void* data = buffer->writePtr; void* data = buffer->writePtr;
void* end = buffer->readPtr; void* end;
ATOMIC_LOAD(end, buffer->readPtr);
size_t remaining; size_t remaining;
if ((intptr_t) data - (intptr_t) buffer->data + buffer->maxalloc >= buffer->capacity) { if ((intptr_t) data - (intptr_t) buffer->data + buffer->maxalloc >= buffer->capacity) {
data = buffer->data; data = buffer->data;
@ -46,13 +56,14 @@ size_t RingFIFOWrite(struct RingFIFO* buffer, const void* value, size_t length)
if (value) { if (value) {
memcpy(data, value, length); memcpy(data, value, length);
} }
buffer->writePtr = (void*) ((intptr_t) data + length); ATOMIC_STORE(buffer->writePtr, (void*) ((intptr_t) data + length));
return length; return length;
} }
size_t RingFIFORead(struct RingFIFO* buffer, void* output, size_t length) { size_t RingFIFORead(struct RingFIFO* buffer, void* output, size_t length) {
void* data = buffer->readPtr; void* data = buffer->readPtr;
void* end = buffer->writePtr; void* end;
ATOMIC_LOAD(end, buffer->writePtr);
size_t remaining; size_t remaining;
if ((intptr_t) data - (intptr_t) buffer->data + buffer->maxalloc >= buffer->capacity) { if ((intptr_t) data - (intptr_t) buffer->data + buffer->maxalloc >= buffer->capacity) {
data = buffer->data; data = buffer->data;
@ -68,6 +79,6 @@ size_t RingFIFORead(struct RingFIFO* buffer, void* output, size_t length) {
if (output) { if (output) {
memcpy(output, data, length); memcpy(output, data, length);
} }
buffer->readPtr = (void*) ((intptr_t) data + length); ATOMIC_STORE(buffer->readPtr, (void*) ((intptr_t) data + length));
return length; return length;
} }