GBA Video: Another attempt at patching the threaded renderer race conditions

This commit is contained in:
Jeffrey Pfau 2016-08-14 19:40:24 -07:00
parent 812d063b73
commit ac11542226
4 changed files with 66 additions and 34 deletions

View File

@ -69,7 +69,7 @@ void GBAVideoThreadProxyRendererInit(struct GBAVideoRenderer* renderer) {
ConditionInit(&proxyRenderer->fromThreadCond); ConditionInit(&proxyRenderer->fromThreadCond);
ConditionInit(&proxyRenderer->toThreadCond); ConditionInit(&proxyRenderer->toThreadCond);
MutexInit(&proxyRenderer->mutex); MutexInit(&proxyRenderer->mutex);
RingFIFOInit(&proxyRenderer->dirtyQueue, 0x48000, 0x1000); RingFIFOInit(&proxyRenderer->dirtyQueue, 0x40000);
proxyRenderer->vramProxy = anonymousMemoryMap(SIZE_VRAM); proxyRenderer->vramProxy = anonymousMemoryMap(SIZE_VRAM);
proxyRenderer->backend->palette = proxyRenderer->paletteProxy; proxyRenderer->backend->palette = proxyRenderer->paletteProxy;
@ -123,6 +123,23 @@ void GBAVideoThreadProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
mappedMemoryFree(proxyRenderer->vramProxy, SIZE_VRAM); mappedMemoryFree(proxyRenderer->vramProxy, SIZE_VRAM);
} }
static bool _writeData(struct GBAVideoThreadProxyRenderer* proxyRenderer, void* data, size_t length) {
while (!RingFIFOWrite(&proxyRenderer->dirtyQueue, data, length)) {
mLOG(GBA_VIDEO, WARN, "Can't write 0x%z bytes. Proxy thread asleep?", length);
mLOG(GBA_VIDEO, DEBUG, "Queue status: read: %p, write: %p", proxyRenderer->dirtyQueue.readPtr, proxyRenderer->dirtyQueue.writePtr);
MutexLock(&proxyRenderer->mutex);
if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
MutexUnlock(&proxyRenderer->mutex);
return false;
}
ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
MutexUnlock(&proxyRenderer->mutex);
}
return true;
}
uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
switch (address) { switch (address) {
@ -153,7 +170,7 @@ uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer*
value, value,
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); _writeData(proxyRenderer, &dirty, sizeof(dirty));
return value; return value;
} }
@ -174,7 +191,7 @@ void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer,
value, value,
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); _writeData(proxyRenderer, &dirty, sizeof(dirty));
if (renderer->cache) { if (renderer->cache) {
GBAVideoTileCacheWritePalette(renderer->cache, address); GBAVideoTileCacheWritePalette(renderer->cache, address);
} }
@ -188,7 +205,7 @@ void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint
proxyRenderer->d.oam->raw[oam], proxyRenderer->d.oam->raw[oam],
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); _writeData(proxyRenderer, &dirty, sizeof(dirty));
} }
void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
@ -207,14 +224,8 @@ void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer,
0xABCD, 0xABCD,
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); _writeData(proxyRenderer, &dirty, sizeof(dirty));
while (!RingFIFOWrite(&proxyRenderer->dirtyQueue, &proxyRenderer->d.vram[j * 0x800], 0x1000)) { _writeData(proxyRenderer, &proxyRenderer->d.vram[j * 0x800], 0x1000);
ConditionWake(&proxyRenderer->toThreadCond);
if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
mLOG(GBA_VIDEO, FATAL, "Proxy thread stopped prematurely!");
return;
}
}
} }
} }
struct GBAVideoDirtyInfo dirty = { struct GBAVideoDirtyInfo dirty = {
@ -223,7 +234,7 @@ void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer,
0, 0,
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); _writeData(proxyRenderer, &dirty, sizeof(dirty));
if ((y & 15) == 15) { if ((y & 15) == 15) {
ConditionWake(&proxyRenderer->toThreadCond); ConditionWake(&proxyRenderer->toThreadCond);
} }
@ -232,7 +243,9 @@ void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer,
void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) { void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) { if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {
mLOG(GBA_VIDEO, FATAL, "Proxy thread stopped prematurely!"); mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!");
GBAVideoThreadProxyRendererDeinit(renderer);
GBAVideoThreadProxyRendererInit(renderer);
return; return;
} }
MutexLock(&proxyRenderer->mutex); MutexLock(&proxyRenderer->mutex);
@ -243,9 +256,8 @@ void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
0, 0,
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); _writeData(proxyRenderer, &dirty, sizeof(dirty));
do { 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); } while (proxyRenderer->threadState == PROXY_THREAD_BUSY);
@ -255,7 +267,8 @@ void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
} }
static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, const void** pixels) { static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, const void** pixels) {
struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; MutexLock(&proxyRenderer->mutex); struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
MutexLock(&proxyRenderer->mutex);
// Insert an extra item into the queue to make sure it gets flushed // Insert an extra item into the queue to make sure it gets flushed
struct GBAVideoDirtyInfo dirty = { struct GBAVideoDirtyInfo dirty = {
DIRTY_FLUSH, DIRTY_FLUSH,
@ -263,7 +276,7 @@ static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* render
0, 0,
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); _writeData(proxyRenderer, &dirty, sizeof(dirty));
while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
ConditionWake(&proxyRenderer->toThreadCond); ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
@ -273,7 +286,8 @@ static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* render
} }
static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) { static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; MutexLock(&proxyRenderer->mutex); struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
MutexLock(&proxyRenderer->mutex);
// Insert an extra item into the queue to make sure it gets flushed // Insert an extra item into the queue to make sure it gets flushed
struct GBAVideoDirtyInfo dirty = { struct GBAVideoDirtyInfo dirty = {
DIRTY_FLUSH, DIRTY_FLUSH,
@ -281,7 +295,7 @@ static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* render
0, 0,
0xDEADBEEF, 0xDEADBEEF,
}; };
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty)); _writeData(proxyRenderer, &dirty, sizeof(dirty));
while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { while (proxyRenderer->threadState == PROXY_THREAD_BUSY) {
ConditionWake(&proxyRenderer->toThreadCond); ConditionWake(&proxyRenderer->toThreadCond);
ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex);
@ -319,7 +333,13 @@ static THREAD_ENTRY _proxyThread(void* renderer) {
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)) {
mLOG(GBA_VIDEO, WARN, "Proxy thread can't read VRAM. CPU thread asleep?");
MutexLock(&proxyRenderer->mutex);
ConditionWake(&proxyRenderer->fromThreadCond); ConditionWake(&proxyRenderer->fromThreadCond);
proxyRenderer->threadState = PROXY_THREAD_IDLE;
ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex);
proxyRenderer->threadState = PROXY_THREAD_BUSY;
MutexUnlock(&proxyRenderer->mutex);
} }
proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address); proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address);
break; break;
@ -333,7 +353,7 @@ static THREAD_ENTRY _proxyThread(void* renderer) {
// FIFO was corrupted // FIFO was corrupted
MutexLock(&proxyRenderer->mutex); MutexLock(&proxyRenderer->mutex);
proxyRenderer->threadState = PROXY_THREAD_STOPPED; proxyRenderer->threadState = PROXY_THREAD_STOPPED;
mLOG(GBA_VIDEO, FATAL, "Proxy thread queue got corrupted!"); mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!");
goto out; goto out;
} }
} while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))); } while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item)));

View File

@ -233,7 +233,7 @@ void mPSP2LoadROM(struct mGUIRunner* runner) {
break; break;
} }
RingFIFOInit(&audioContext.buffer, PSP2_AUDIO_BUFFER_SIZE * sizeof(struct GBAStereoSample), PSP2_SAMPLES * 4); RingFIFOInit(&audioContext.buffer, PSP2_AUDIO_BUFFER_SIZE * sizeof(struct GBAStereoSample));
MutexInit(&audioContext.mutex); MutexInit(&audioContext.mutex);
ConditionInit(&audioContext.cond); ConditionInit(&audioContext.cond);
audioContext.running = true; audioContext.running = true;

View File

@ -16,10 +16,9 @@
#define ATOMIC_LOAD(DST, SRC) DST = SRC #define ATOMIC_LOAD(DST, SRC) DST = SRC
#endif #endif
void RingFIFOInit(struct RingFIFO* buffer, size_t capacity, size_t maxalloc) { void RingFIFOInit(struct RingFIFO* buffer, size_t capacity) {
buffer->data = anonymousMemoryMap(capacity); buffer->data = anonymousMemoryMap(capacity);
buffer->capacity = capacity; buffer->capacity = capacity;
buffer->maxalloc = maxalloc;
RingFIFOClear(buffer); RingFIFOClear(buffer);
} }
@ -41,15 +40,24 @@ size_t RingFIFOWrite(struct RingFIFO* buffer, const void* value, size_t length)
void* data = buffer->writePtr; void* data = buffer->writePtr;
void* end; void* end;
ATOMIC_LOAD(end, buffer->readPtr); ATOMIC_LOAD(end, buffer->readPtr);
size_t remaining;
if ((intptr_t) data - (intptr_t) buffer->data + buffer->maxalloc >= buffer->capacity) { // Wrap around if we can't fit enough in here
if ((intptr_t) data - (intptr_t) buffer->data + length >= buffer->capacity) {
if (end == buffer->data) {
// Oops! If we wrap now, it'll appear empty
return 0;
}
data = buffer->data; data = buffer->data;
} }
size_t remaining;
if (data >= end) { if (data >= end) {
remaining = (intptr_t) buffer->data + buffer->capacity - (intptr_t) data; uintptr_t bufferEnd = (uintptr_t) buffer->data + buffer->capacity;
remaining = bufferEnd - (uintptr_t) data;
} else { } else {
remaining = (intptr_t) end - (intptr_t) data; remaining = (uintptr_t) end - (uintptr_t) data;
} }
// Note that we can't hit the end pointer
if (remaining <= length) { if (remaining <= length) {
return 0; return 0;
} }
@ -64,16 +72,21 @@ size_t RingFIFORead(struct RingFIFO* buffer, void* output, size_t length) {
void* data = buffer->readPtr; void* data = buffer->readPtr;
void* end; void* end;
ATOMIC_LOAD(end, buffer->writePtr); ATOMIC_LOAD(end, buffer->writePtr);
size_t remaining;
if ((intptr_t) data - (intptr_t) buffer->data + buffer->maxalloc >= buffer->capacity) { // Wrap around if we can't fit enough in here
if ((intptr_t) data - (intptr_t) buffer->data + length >= buffer->capacity) {
data = buffer->data; data = buffer->data;
} }
size_t remaining;
if (data > end) { if (data > end) {
remaining = (intptr_t) buffer->data + buffer->capacity - (intptr_t) data; uintptr_t bufferEnd = (uintptr_t) buffer->data + buffer->capacity;
remaining = bufferEnd - (uintptr_t) data;
} else { } else {
remaining = (intptr_t) end - (intptr_t) data; remaining = (intptr_t) end - (intptr_t) data;
} }
if (remaining <= length) { // If the pointers touch, it's empty
if (remaining < length) {
return 0; return 0;
} }
if (output) { if (output) {

View File

@ -11,12 +11,11 @@
struct RingFIFO { struct RingFIFO {
void* data; void* data;
size_t capacity; size_t capacity;
size_t maxalloc;
void* readPtr; void* readPtr;
void* writePtr; void* writePtr;
}; };
void RingFIFOInit(struct RingFIFO* buffer, size_t capacity, size_t maxalloc); void RingFIFOInit(struct RingFIFO* buffer, size_t capacity);
void RingFIFODeinit(struct RingFIFO* buffer); void RingFIFODeinit(struct RingFIFO* buffer);
size_t RingFIFOCapacity(const struct RingFIFO* buffer); size_t RingFIFOCapacity(const struct RingFIFO* buffer);
void RingFIFOClear(struct RingFIFO* buffer); void RingFIFOClear(struct RingFIFO* buffer);