From 39a71a5fbf24bb6c0e76a24552f2f2a18bbb5409 Mon Sep 17 00:00:00 2001 From: fincs Date: Tue, 5 Jan 2016 17:30:05 +0100 Subject: [PATCH] Implement render queue system --- include/c3d/renderqueue.h | 42 +++++ include/citro3d.h | 1 + source/base.c | 40 ++++- source/context.h | 7 +- source/renderqueue.c | 354 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 435 insertions(+), 9 deletions(-) create mode 100644 include/c3d/renderqueue.h create mode 100644 source/renderqueue.c diff --git a/include/c3d/renderqueue.h b/include/c3d/renderqueue.h new file mode 100644 index 0000000..5ccec65 --- /dev/null +++ b/include/c3d/renderqueue.h @@ -0,0 +1,42 @@ +#pragma once +#include "renderbuffer.h" + +typedef struct C3D_RenderTarget_tag C3D_RenderTarget; + +struct C3D_RenderTarget_tag +{ + C3D_RenderTarget *next, *prev, *link, *frame[2]; + C3D_RenderBuf renderBuf; + u32 transferFlags; + + u8 clearBits; + bool drawOk, transferOk; + + bool linked; + gfxScreen_t screen; + gfx3dSide_t side; +}; + +// Flags for C3D_FrameBegin +enum +{ + C3D_FRAME_SYNCDRAW = BIT(0), // Do not render the frame until the previous has finished rendering + C3D_FRAME_NONBLOCK = BIT(1), // Return false instead of waiting for the GPU to finish rendering +}; + +bool C3D_FrameBegin(u8 flags); +bool C3D_FrameDrawOn(C3D_RenderTarget* target); +void C3D_FrameEnd(u8 flags); + +// Flags for C3D_RenderTargetSetClear (only C3D_CLEAR_ALL implemented atm) +enum +{ + C3D_CLEAR_COLOR = BIT(0), + C3D_CLEAR_DEPTH = BIT(1), + C3D_CLEAR_ALL = C3D_CLEAR_COLOR | C3D_CLEAR_DEPTH, +}; + +C3D_RenderTarget* C3D_RenderTargetCreate(int width, int height, int colorFmt, int depthFmt); +void C3D_RenderTargetDelete(C3D_RenderTarget* target); +void C3D_RenderTargetSetClear(C3D_RenderTarget* target, u32 clearBits, u32 clearColor, u32 clearDepth); +void C3D_RenderTargetSetOutput(C3D_RenderTarget* target, gfxScreen_t screen, gfx3dSide_t side, u32 transferFlags); diff --git a/include/citro3d.h b/include/citro3d.h index dce7012..a9f2215 100644 --- a/include/citro3d.h +++ b/include/citro3d.h @@ -23,6 +23,7 @@ extern "C" { #include "c3d/light.h" #include "c3d/renderbuffer.h" +#include "c3d/renderqueue.h" #ifdef __cplusplus } diff --git a/source/base.c b/source/base.c index 7e3956a..9d400f0 100644 --- a/source/base.c +++ b/source/base.c @@ -73,13 +73,14 @@ bool C3D_Init(size_t cmdBufSize) if (ctx->flags & C3DiF_Active) return false; - ctx->cmdBufSize = cmdBufSize; - ctx->cmdBuf = linearAlloc(cmdBufSize); + ctx->cmdBufSize = cmdBufSize/8; // Half of the size of the cmdbuf, in words + ctx->cmdBuf = (u32*)linearAlloc(cmdBufSize); if (!ctx->cmdBuf) return false; GPUCMD_SetBuffer(ctx->cmdBuf, ctx->cmdBufSize, 0); ctx->flags = C3DiF_Active | C3DiF_TexEnvBuf | C3DiF_TexEnvAll | C3DiF_Effect | C3DiF_TexAll; + ctx->renderQueueExit = NULL; // TODO: replace with direct struct access C3D_DepthMap(-1.0f, 0.0f); @@ -149,6 +150,7 @@ void C3Di_UpdateContext(void) { ctx->flags &= ~C3DiF_DrawUsed; GPUCMD_AddWrite(GPUREG_FRAMEBUFFER_FLUSH, 1); + GPUCMD_AddWrite(GPUREG_EARLYDEPTH_CLEAR, 1); } C3Di_RenderBufBind(ctx->rb); } @@ -251,13 +253,10 @@ void C3Di_UpdateContext(void) C3D_UpdateUniforms(GPU_GEOMETRY_SHADER); } -void C3D_FlushAsync(void) +void C3Di_FinalizeFrame(u32** pBuf, u32* pSize) { C3D_Context* ctx = C3Di_GetContext(); - if (!(ctx->flags & C3DiF_Active)) - return; - if (ctx->flags & C3DiF_DrawUsed) { ctx->flags &= ~C3DiF_DrawUsed; @@ -267,8 +266,30 @@ void C3D_FlushAsync(void) } GPUCMD_Finalize(); - GPUCMD_FlushAndRun(); - GPUCMD_SetBuffer(ctx->cmdBuf, ctx->cmdBufSize, 0); + GPUCMD_GetBuffer(pBuf, NULL, pSize); + *pSize *= 4; + + ctx->flags ^= C3DiF_CmdBuffer; + u32* buf = ctx->cmdBuf; + if (ctx->flags & C3DiF_CmdBuffer) + buf += ctx->cmdBufSize; + GPUCMD_SetBuffer(buf, ctx->cmdBufSize, 0); +} + +void C3D_FlushAsync(void) +{ + if (!(C3Di_GetContext()->flags & C3DiF_Active)) + return; + + u32* cmdBuf; + u32 cmdBufSize; + C3Di_FinalizeFrame(&cmdBuf, &cmdBufSize); + + //take advantage of GX_FlushCacheRegions to flush gsp heap + extern u32 __ctru_linear_heap; + extern u32 __ctru_linear_heap_size; + GX_FlushCacheRegions(cmdBuf, cmdBufSize, (u32 *) __ctru_linear_heap, __ctru_linear_heap_size, NULL, 0); + GX_ProcessCommandList(cmdBuf, cmdBufSize, 0x0); } void C3D_Fini(void) @@ -278,6 +299,9 @@ void C3D_Fini(void) if (!(ctx->flags & C3DiF_Active)) return; + if (ctx->renderQueueExit) + ctx->renderQueueExit(); + aptUnhook(&hookCookie); linearFree(ctx->cmdBuf); ctx->flags = 0; diff --git a/source/context.h b/source/context.h index d28782c..d1270e2 100644 --- a/source/context.h +++ b/source/context.h @@ -26,7 +26,7 @@ typedef struct typedef struct { - void* cmdBuf; + u32* cmdBuf; size_t cmdBufSize; u32 flags; @@ -49,6 +49,8 @@ typedef struct u16 fixedAttribDirty, fixedAttribEverDirty; C3D_FVec fixedAttribs[12]; + void (* renderQueueExit)(void); + } C3D_Context; enum @@ -66,6 +68,7 @@ enum C3DiF_LightEnv = BIT(10), C3DiF_VshCode = BIT(11), C3DiF_GshCode = BIT(12), + C3DiF_CmdBuffer = BIT(13), #define C3DiF_Tex(n) BIT(23+(n)) C3DiF_TexAll = 7 << 23, @@ -91,3 +94,5 @@ void C3Di_LightMtlBlend(C3D_Light* light); void C3Di_DirtyUniforms(GPU_SHADER_TYPE type); void C3Di_LoadShaderUniforms(shaderInstance_s* si); void C3Di_ClearShaderUniforms(GPU_SHADER_TYPE type); + +void C3Di_FinalizeFrame(u32** pBuf, u32* pSize); diff --git a/source/renderqueue.c b/source/renderqueue.c new file mode 100644 index 0000000..34fff0d --- /dev/null +++ b/source/renderqueue.c @@ -0,0 +1,354 @@ +#include "context.h" +#include +#include +#include + +static C3D_RenderTarget *firstTarget, *lastTarget; +static C3D_RenderTarget *linkedTarget[3]; +static C3D_RenderTarget *transferQueue, *clearQueue; + +static struct +{ + C3D_RenderTarget* targetList; + u32* cmdBuf; + u32 cmdBufSize; + u8 flags; +} queuedFrame[2]; +static u8 queueSwap, queuedCount, queuedState; + +static bool inFrame; + +static void onRenderFinish(void* unused); +static void onTransferFinish(void* unused); +static void onClearDone(void* unused); + +static void performDraw(void) +{ + gspSetEventCallback(GSPGPU_EVENT_P3D, onRenderFinish, NULL, true); + GX_ProcessCommandList(queuedFrame[queueSwap].cmdBuf, queuedFrame[queueSwap].cmdBufSize, queuedFrame[queueSwap].flags); +} + +static void performTransfer(void) +{ + C3D_RenderBuf* renderBuf = &transferQueue->renderBuf; + u32* frameBuf = (u32*)gfxGetFramebuffer(transferQueue->screen, transferQueue->side, NULL, NULL); + if (transferQueue->side == GFX_LEFT) + gfxConfigScreen(transferQueue->screen, false); + gspSetEventCallback(GSPGPU_EVENT_PPF, onTransferFinish, NULL, true); + C3D_RenderBufTransferAsync(renderBuf, frameBuf, transferQueue->transferFlags); +} + +static void performClear(void) +{ + C3D_RenderBuf* renderBuf = &clearQueue->renderBuf; + // TODO: obey renderBuf->clearBits + gspSetEventCallback(renderBuf->colorBuf.data ? GSPGPU_EVENT_PSC0 : GSPGPU_EVENT_PSC1, onClearDone, NULL, true); + C3D_RenderBufClearAsync(renderBuf); +} + +static void updateFrameQueue(void) +{ + C3D_RenderTarget* a; + if (queuedState>0) return; // Still rendering + + // Check that all targets are OK to be drawn on + for (a = queuedFrame[queueSwap].targetList; a; a = a->frame[queueSwap]) + if (!a->drawOk) + return; // Nope, we can't start rendering yet + + // Start rendering the frame + queuedState=1; + for (a = queuedFrame[queueSwap].targetList; a; a = a->frame[queueSwap]) + a->drawOk = false; + performDraw(); +} + +static void transferTarget(C3D_RenderTarget* target) +{ + C3D_RenderTarget* a; + target->transferOk = false; + target->link = NULL; + if (!transferQueue) + { + transferQueue = target; + performTransfer(); + return; + } + for (a = transferQueue; a->link; a = a->link); + a->link = target; +} + +static void clearTarget(C3D_RenderTarget* target) +{ + C3D_RenderTarget* a; + target->link = NULL; + if (!clearQueue) + { + clearQueue = target; + performClear(); + return; + } + for (a = clearQueue; a->link; a = a->link); + a->link = target; +} + +static void onVBlank0(void* unused) +{ + if (!linkedTarget[0]) return; + + if (gfxIs3D()) + { + if (linkedTarget[1] && linkedTarget[1]->transferOk) + transferTarget(linkedTarget[1]); + else if (linkedTarget[0]->transferOk) + { + // Use a temporary copy of the left framebuffer to fill in the missing right image. + static C3D_RenderTarget temp; + memcpy(&temp, linkedTarget[0], sizeof(temp)); + temp.side = GFX_RIGHT; + temp.clearBits = false; + transferTarget(&temp); + } + } + if (linkedTarget[0]->transferOk) + transferTarget(linkedTarget[0]); +} + +static void onVBlank1(void* unused) +{ + if (linkedTarget[2] && linkedTarget[2]->transferOk) + transferTarget(linkedTarget[2]); +} + +void onRenderFinish(void* unused) +{ + C3D_RenderTarget *a, *next; + + // The following check should never trigger + if (queuedState!=1) svcBreak(USERBREAK_PANIC); + + for (a = queuedFrame[queueSwap].targetList; a; a = next) + { + next = a->frame[queueSwap]; + a->frame[queueSwap] = NULL; + if (a->linked) + a->transferOk = true; + else if (a->clearBits) + clearTarget(a); + else + a->drawOk = true; + } + + // Consume the frame that has been just rendered + memset(&queuedFrame[queueSwap], 0, sizeof(queuedFrame[queueSwap])); + queueSwap ^= 1; + queuedCount--; + queuedState = 0; + + // Update the frame queue if there are still frames to render + if (queuedCount>0) + updateFrameQueue(); +} + +void onTransferFinish(void* unused) +{ + C3D_RenderTarget* target = transferQueue; + transferQueue = target->link; + if (target->clearBits) + clearTarget(target); + else + target->drawOk = true; + if (transferQueue) + performTransfer(); + if (target->drawOk && queuedCount>0 && queuedState==0) + updateFrameQueue(); +} + +void onClearDone(void* unused) +{ + C3D_RenderTarget* target = clearQueue; + clearQueue = target->link; + target->drawOk = true; + if (clearQueue) + performClear(); + if (queuedCount>0 && queuedState==0) + updateFrameQueue(); +} + +static void C3Di_RenderQueueInit(void) +{ + gspSetEventCallback(GSPGPU_EVENT_VBlank0, onVBlank0, NULL, false); + gspSetEventCallback(GSPGPU_EVENT_VBlank1, onVBlank1, NULL, false); +} + +static void C3Di_RenderQueueExit(void) +{ + int i; + C3D_RenderTarget *a, *next; + + if (inFrame) return; + + for (a = firstTarget; a; a = next) + { + next = a->next; + C3D_RenderTargetDelete(a); + } + + gspSetEventCallback(GSPGPU_EVENT_VBlank0, NULL, NULL, false); + gspSetEventCallback(GSPGPU_EVENT_VBlank1, NULL, NULL, false); + + for (i = 0; i < 3; i ++) + linkedTarget[i] = NULL; + + memset(queuedFrame, 0, sizeof(queuedFrame)); + queueSwap = 0; + queuedCount = 0; + queuedState = 0; +} + +bool checkRenderQueueInit(void) +{ + C3D_Context* ctx = C3Di_GetContext(); + + if (!(ctx->flags & C3DiF_Active)) + return false; + + if (!ctx->renderQueueExit) + { + C3Di_RenderQueueInit(); + ctx->renderQueueExit = C3Di_RenderQueueExit; + } + + return true; +} + +bool C3D_FrameBegin(u8 flags) +{ + if (inFrame) return false; + int maxCount = (flags & C3D_FRAME_SYNCDRAW) ? 1 : 2; + while (queuedCount >= maxCount) + { + if (flags & C3D_FRAME_NONBLOCK) + return false; + gspWaitForP3D(); + } + inFrame = true; + return true; +} + +bool C3D_FrameDrawOn(C3D_RenderTarget* target) +{ + if (!inFrame) return false; + + // Queue the target in the frame if it hasn't already been. + int pos = queueSwap^queuedCount; + if (!target->frame[pos]) + { + if (!queuedFrame[pos].targetList) + queuedFrame[pos].targetList = target; + else + { + C3D_RenderTarget* a; + for (a = queuedFrame[pos].targetList; a->frame[pos]; a = a->frame[pos]); + a->frame[pos] = target; + } + } + + C3D_RenderBufBind(&target->renderBuf); + return true; +} + +void C3D_FrameEnd(u8 flags) +{ + if (!inFrame) return; + inFrame = false; + + int pos = queueSwap^queuedCount; + if (!queuedFrame[pos].targetList) return; + + // Add the frame to the queue + queuedCount++; + C3Di_FinalizeFrame(&queuedFrame[pos].cmdBuf, &queuedFrame[pos].cmdBufSize); + queuedFrame[pos].flags = flags; + + // Flush the entire linear memory if the user did not explicitly mandate to flush the command list + if (!(flags & GX_CMDLIST_FLUSH)) + { + // Take advantage of GX_FlushCacheRegions to flush gsp heap + extern u32 __ctru_linear_heap; + extern u32 __ctru_linear_heap_size; + GX_FlushCacheRegions(queuedFrame[queueSwap].cmdBuf, queuedFrame[queueSwap].cmdBufSize, (u32 *) __ctru_linear_heap, __ctru_linear_heap_size, NULL, 0); + } + + // Update the frame queue + updateFrameQueue(); +} + +C3D_RenderTarget* C3D_RenderTargetCreate(int width, int height, int colorFmt, int depthFmt) +{ + if (!checkRenderQueueInit()) return NULL; + C3D_RenderTarget* target = (C3D_RenderTarget*)malloc(sizeof(C3D_RenderTarget)); + if (!target) return NULL; + memset(target, 0, sizeof(C3D_RenderTarget)); + if (!C3D_RenderBufInit(&target->renderBuf, width, height, colorFmt, depthFmt)) + { + free(target); + return NULL; + } + + target->drawOk = true; + target->prev = lastTarget; + target->next = NULL; + if (lastTarget) + lastTarget->next = target; + if (!firstTarget) + firstTarget = target; + lastTarget = target; + + return target; +} + +void C3D_RenderTargetDelete(C3D_RenderTarget* target) +{ + while (!target->drawOk) + gspWaitForAnyEvent(); + C3D_RenderBufDelete(&target->renderBuf); + C3D_RenderTarget** prevNext = target->prev ? &target->prev->next : &firstTarget; + C3D_RenderTarget** nextPrev = target->next ? &target->next->prev : &lastTarget; + *prevNext = target->next; + *nextPrev = target->prev; + free(target); +} + +void C3D_RenderTargetSetClear(C3D_RenderTarget* target, u32 clearBits, u32 clearColor, u32 clearDepth) +{ + if (!target->drawOk) return; + + if (target->renderBuf.colorBuf.data==NULL) clearBits &= ~C3D_CLEAR_COLOR; + if (target->renderBuf.depthBuf.data==NULL) clearBits &= ~C3D_CLEAR_DEPTH; + + u32 oldClearBits = target->clearBits; + target->clearBits = clearBits & 0xFF; + target->renderBuf.clearColor = clearColor; + target->renderBuf.clearDepth = clearDepth; + + if (clearBits &~ oldClearBits) + { + target->drawOk = false; + clearTarget(target); + } +} + +void C3D_RenderTargetSetOutput(C3D_RenderTarget* target, gfxScreen_t screen, gfx3dSide_t side, u32 transferFlags) +{ + int id = 0; + if (screen==GFX_BOTTOM) id = 2; + else if (side==GFX_RIGHT) id = 1; + if (linkedTarget[id]) + linkedTarget[id]->linked = false; + linkedTarget[id] = target; + target->linked = true; + target->transferFlags = transferFlags; + target->screen = screen; + target->side = side; +}