GBA Video: Better threaded renderer using a new RingFIFO

This commit is contained in:
Jeffrey Pfau 2015-08-29 20:08:00 -07:00
parent 0cfff99652
commit e1ffc68582
4 changed files with 168 additions and 63 deletions

View File

@ -9,7 +9,24 @@
#include "util/memory.h" #include "util/memory.h"
DEFINE_VECTOR(GBAVideoDirtyQueue, struct GBAVideoDirtyInfo); #ifndef DISABLE_THREADING
enum GBAVideoDirtyType {
DIRTY_DUMMY = 0,
DIRTY_REGISTER,
DIRTY_OAM,
DIRTY_PALETTE,
DIRTY_VRAM,
DIRTY_SCANLINE,
DIRTY_FLUSH
};
struct GBAVideoDirtyInfo {
enum GBAVideoDirtyType type;
uint32_t address;
uint16_t value;
uint32_t padding;
};
static void GBAVideoThreadProxyRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoThreadProxyRendererInit(struct GBAVideoRenderer* renderer);
static void GBAVideoThreadProxyRendererReset(struct GBAVideoRenderer* renderer); static void GBAVideoThreadProxyRendererReset(struct GBAVideoRenderer* renderer);
@ -52,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);
GBAVideoDirtyQueueInit(&proxyRenderer->dirtyQueue, 1024); RingFIFOInit(&proxyRenderer->dirtyQueue, 0x200000, 0x1000);
proxyRenderer->threadState = PROXY_THREAD_STOPPED; proxyRenderer->threadState = PROXY_THREAD_STOPPED;
proxyRenderer->vramProxy = anonymousMemoryMap(SIZE_VRAM); proxyRenderer->vramProxy = anonymousMemoryMap(SIZE_VRAM);
@ -137,9 +154,10 @@ uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer*
struct GBAVideoDirtyInfo dirty = { struct GBAVideoDirtyInfo dirty = {
DIRTY_REGISTER, DIRTY_REGISTER,
address, address,
value value,
0xDEADBEEF,
}; };
*GBAVideoDirtyQueueAppend(&proxyRenderer->dirtyQueue) = dirty; RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
ConditionWake(&proxyRenderer->toThreadCond); ConditionWake(&proxyRenderer->toThreadCond);
return value; return value;
} }
@ -158,15 +176,15 @@ void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer,
struct GBAVideoDirtyInfo dirty = { struct GBAVideoDirtyInfo dirty = {
DIRTY_PALETTE, DIRTY_PALETTE,
address, address,
value value,
0xDEADBEEF,
}; };
*GBAVideoDirtyQueueAppend(&proxyRenderer->dirtyQueue) = dirty; RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
ConditionWake(&proxyRenderer->toThreadCond); ConditionWake(&proxyRenderer->toThreadCond);
} }
void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) { void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
proxyRenderer->oamProxy.raw[oam] = proxyRenderer->d.oam->raw[oam];
int bit = 1 << (oam & 31); int bit = 1 << (oam & 31);
int base = oam >> 5; int base = oam >> 5;
if (proxyRenderer->oamDirtyBitmap[base] & bit) { if (proxyRenderer->oamDirtyBitmap[base] & bit) {
@ -176,20 +194,40 @@ void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint
struct GBAVideoDirtyInfo dirty = { struct GBAVideoDirtyInfo dirty = {
DIRTY_OAM, DIRTY_OAM,
oam, oam,
0 proxyRenderer->d.oam->raw[oam],
0xDEADBEEF,
}; };
*GBAVideoDirtyQueueAppend(&proxyRenderer->dirtyQueue) = dirty; RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
ConditionWake(&proxyRenderer->toThreadCond); ConditionWake(&proxyRenderer->toThreadCond);
} }
void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer;
if (proxyRenderer->vramDirtyBitmap) {
int bitmap = proxyRenderer->vramDirtyBitmap;
proxyRenderer->vramDirtyBitmap = 0;
int j;
for (j = 0; j < 24; ++j) {
if (!(bitmap & (1 << j))) {
continue;
}
struct GBAVideoDirtyInfo dirty = {
DIRTY_VRAM,
j * 0x1000,
0xABCD,
0xDEADBEEF,
};
RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
RingFIFOWrite(&proxyRenderer->dirtyQueue, &proxyRenderer->d.vram[j * 0x800], 0x1000);
}
}
struct GBAVideoDirtyInfo dirty = { struct GBAVideoDirtyInfo dirty = {
DIRTY_SCANLINE, DIRTY_SCANLINE,
y, y,
0 0,
0xDEADBEEF,
}; };
*GBAVideoDirtyQueueAppend(&proxyRenderer->dirtyQueue) = dirty; RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
ConditionWake(&proxyRenderer->toThreadCond); ConditionWake(&proxyRenderer->toThreadCond);
} }
@ -200,16 +238,15 @@ void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
struct GBAVideoDirtyInfo dirty = { struct GBAVideoDirtyInfo dirty = {
DIRTY_FLUSH, DIRTY_FLUSH,
0, 0,
0 0,
0xDEADBEEF,
}; };
*GBAVideoDirtyQueueAppend(&proxyRenderer->dirtyQueue) = dirty; RingFIFOWrite(&proxyRenderer->dirtyQueue, &dirty, sizeof(dirty));
do { do {
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);
proxyRenderer->backend->finishFrame(proxyRenderer->backend); proxyRenderer->backend->finishFrame(proxyRenderer->backend);
GBAVideoDirtyQueueClear(&proxyRenderer->dirtyQueue);
proxyRenderer->queueCleared = true;
proxyRenderer->vramDirtyBitmap = 0; proxyRenderer->vramDirtyBitmap = 0;
memset(proxyRenderer->oamDirtyBitmap, 0, sizeof(proxyRenderer->oamDirtyBitmap)); memset(proxyRenderer->oamDirtyBitmap, 0, sizeof(proxyRenderer->oamDirtyBitmap));
MutexUnlock(&proxyRenderer->mutex); MutexUnlock(&proxyRenderer->mutex);
@ -228,52 +265,42 @@ static THREAD_ENTRY _proxyThread(void* renderer) {
ThreadSetName("Proxy Renderer Thread"); ThreadSetName("Proxy Renderer Thread");
MutexLock(&proxyRenderer->mutex); MutexLock(&proxyRenderer->mutex);
size_t i = 0;
while (1) { while (1) {
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; proxyRenderer->threadState = PROXY_THREAD_BUSY;
if (proxyRenderer->queueCleared) {
proxyRenderer->queueCleared = false;
i = 0;
}
if (!GBAVideoDirtyQueueSize(&proxyRenderer->dirtyQueue)) {
continue;
}
MutexUnlock(&proxyRenderer->mutex); MutexUnlock(&proxyRenderer->mutex);
for (; i < GBAVideoDirtyQueueSize(&proxyRenderer->dirtyQueue) - 1; ++i) { struct GBAVideoDirtyInfo item;
struct GBAVideoDirtyInfo* item = GBAVideoDirtyQueueGetPointer(&proxyRenderer->dirtyQueue, i); 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->backend->writeOAM(proxyRenderer->backend, item->address); proxyRenderer->oamProxy.raw[item.address] = item.value;
proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address);
break;
case DIRTY_VRAM:
while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000));
proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address);
break; break;
case DIRTY_SCANLINE: case DIRTY_SCANLINE:
if (proxyRenderer->vramDirtyBitmap) { proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address);
int bitmap = proxyRenderer->vramDirtyBitmap;
proxyRenderer->vramDirtyBitmap = 0;
int j;
for (j = 0; j < 24; ++j) {
if (!(bitmap & (1 << j))) {
continue;
}
proxyRenderer->backend->writeVRAM(proxyRenderer->backend, j * 0x1000);
memcpy(&proxyRenderer->vramProxy[j * 0x800], &proxyRenderer->d.vram[j * 0x800], 0x1000);
}
}
proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address);
break; break;
case DIRTY_FLUSH: case DIRTY_FLUSH:
// This is only here to ensure the queue gets flushed // This is only here to ensure the queue gets flushed
break; break;
default:
// FIFO was corrupted
abort();
break;
} }
} }
MutexLock(&proxyRenderer->mutex); MutexLock(&proxyRenderer->mutex);
@ -284,3 +311,5 @@ static THREAD_ENTRY _proxyThread(void* renderer) {
return 0; return 0;
} }
#endif

View File

@ -8,15 +8,7 @@
#include "gba/video.h" #include "gba/video.h"
#include "util/threading.h" #include "util/threading.h"
#include "util/vector.h" #include "util/ring-fifo.h"
enum GBAVideoDirtyType {
DIRTY_REGISTER,
DIRTY_OAM,
DIRTY_PALETTE,
DIRTY_SCANLINE,
DIRTY_FLUSH
};
enum GBAVideoThreadProxyState { enum GBAVideoThreadProxyState {
PROXY_THREAD_STOPPED = 0, PROXY_THREAD_STOPPED = 0,
@ -24,14 +16,6 @@ enum GBAVideoThreadProxyState {
PROXY_THREAD_BUSY PROXY_THREAD_BUSY
}; };
struct GBAVideoDirtyInfo {
enum GBAVideoDirtyType type;
uint32_t address;
uint16_t value;
};
DECLARE_VECTOR(GBAVideoDirtyQueue, struct GBAVideoDirtyInfo);
struct GBAVideoThreadProxyRenderer { struct GBAVideoThreadProxyRenderer {
struct GBAVideoRenderer d; struct GBAVideoRenderer d;
struct GBAVideoRenderer* backend; struct GBAVideoRenderer* backend;
@ -42,15 +26,14 @@ struct GBAVideoThreadProxyRenderer {
Mutex mutex; Mutex mutex;
enum GBAVideoThreadProxyState threadState; enum GBAVideoThreadProxyState threadState;
struct GBAVideoDirtyQueue dirtyQueue; struct RingFIFO dirtyQueue;
uint32_t vramDirtyBitmap; uint32_t vramDirtyBitmap;
uint32_t oamDirtyBitmap[16]; uint32_t oamDirtyBitmap[16];
uint16_t* vramProxy; uint16_t* vramProxy;
union GBAOAM oamProxy; union GBAOAM oamProxy;
uint16_t paletteProxy[512]; uint16_t paletteProxy[512];
bool queueCleared;
}; };
void GBAVideoThreadProxyRendererCreate(struct GBAVideoThreadProxyRenderer* renderer, struct GBAVideoRenderer* backend); void GBAVideoThreadProxyRendererCreate(struct GBAVideoThreadProxyRenderer* renderer, struct GBAVideoRenderer* backend);

67
src/util/ring-fifo.c Normal file
View File

@ -0,0 +1,67 @@
/* Copyright (c) 2013-2014 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ring-fifo.h"
void RingFIFOInit(struct RingFIFO* buffer, size_t capacity, size_t maxalloc) {
buffer->data = malloc(capacity);
buffer->capacity = capacity;
buffer->maxalloc = maxalloc;
RingFIFOClear(buffer);
}
void RingFIFODeinit(struct RingFIFO* buffer) {
free(buffer->data);
buffer->data = 0;
}
size_t RingFIFOCapacity(const struct RingFIFO* buffer) {
return buffer->capacity;
}
void RingFIFOClear(struct RingFIFO* buffer) {
buffer->readPtr = buffer->data;
buffer->writePtr = buffer->data;
}
size_t RingFIFOWrite(struct RingFIFO* buffer, const void* value, size_t length) {
void* data = buffer->writePtr;
void* end = buffer->readPtr;
size_t remaining;
if ((intptr_t) data - (intptr_t) buffer->data + buffer->maxalloc >= buffer->capacity) {
data = buffer->data;
}
if (data >= end) {
remaining = (intptr_t) buffer->data + buffer->capacity - (intptr_t) data;
} else {
remaining = (intptr_t) end - (intptr_t) data;
}
if (remaining <= length) {
return 0;
}
memcpy(data, value, length);
buffer->writePtr = (void*) ((intptr_t) data + length);
return length;
}
size_t RingFIFORead(struct RingFIFO* buffer, void* output, size_t length) {
void* data = buffer->readPtr;
void* end = buffer->writePtr;
size_t remaining;
if ((intptr_t) data - (intptr_t) buffer->data + buffer->maxalloc >= buffer->capacity) {
data = buffer->data;
}
if (data > end) {
remaining = (intptr_t) buffer->data + buffer->capacity - (intptr_t) data;
} else {
remaining = (intptr_t) end - (intptr_t) data;
}
if (remaining <= length) {
return 0;
}
memcpy(output, data, length);
buffer->readPtr = (void*) ((intptr_t) data + length);
return length;
}

26
src/util/ring-fifo.h Normal file
View File

@ -0,0 +1,26 @@
/* Copyright (c) 2013-2014 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef RING_FIFO_H
#define RING_FIFO_H
#include "util/common.h"
struct RingFIFO {
void* data;
size_t capacity;
size_t maxalloc;
void* readPtr;
void* writePtr;
};
void RingFIFOInit(struct RingFIFO* buffer, size_t capacity, size_t maxalloc);
void RingFIFODeinit(struct RingFIFO* buffer);
size_t RingFIFOCapacity(const struct RingFIFO* buffer);
void RingFIFOClear(struct RingFIFO* buffer);
size_t RingFIFOWrite(struct RingFIFO* buffer, const void* value, size_t length);
size_t RingFIFORead(struct RingFIFO* buffer, void* output, size_t length);
#endif