mirror of https://github.com/mgba-emu/mgba.git
GBA Video: Another attempt at patching the threaded renderer race conditions
This commit is contained in:
parent
812d063b73
commit
ac11542226
|
@ -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)));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue