#include "internal.h" #include #include #include static C3D_RenderTarget *firstTarget, *lastTarget; static C3D_RenderTarget *linkedTarget[3]; static TickCounter gpuTime, cpuTime; #define STAGE_HAS_TRANSFER(n) BIT(0+(n)) #define STAGE_HAS_ANY_TRANSFER (7<<0) #define STAGE_NEED_TRANSFER(n) BIT(3+(n)) #define STAGE_NEED_TOP_TRANSFER (STAGE_NEED_TRANSFER(0)|STAGE_NEED_TRANSFER(1)) #define STAGE_NEED_BOT_TRANSFER STAGE_NEED_TRANSFER(2) #define STAGE_WAIT_TRANSFER BIT(6) static bool initialized; static bool inFrame, inSafeTransfer, measureGpuTime; static u8 frameStage; static float framerate = 60.0f; static float framerateCounter[2] = { 60.0f, 60.0f }; static u32 frameCounter[2]; static bool framerateLimit(int id) { framerateCounter[id] -= framerate; if (framerateCounter[id] <= 0.0f) { framerateCounter[id] += 60.0f; return true; } return false; } static void onVBlank0(C3D_UNUSED void* unused) { gxCmdQueue_s* queue = &C3Di_GetContext()->gxQueue; if (frameStage & STAGE_NEED_TOP_TRANSFER) { gxCmdQueueStop(queue); C3D_RenderTarget *left = linkedTarget[0], *right = linkedTarget[1]; if (left && !(frameStage&STAGE_NEED_TRANSFER(0))) left = NULL; if (right && !(frameStage&STAGE_NEED_TRANSFER(1))) right = NULL; if (gfxIs3D() && !right) right = left; frameStage &= ~STAGE_NEED_TOP_TRANSFER; if (left || right) { frameStage |= STAGE_WAIT_TRANSFER; if (left) C3D_FrameBufTransfer(&left->frameBuf, GFX_TOP, GFX_LEFT, left->transferFlags); if (right) C3D_FrameBufTransfer(&right->frameBuf, GFX_TOP, GFX_RIGHT, right->transferFlags); if (left && left->clearBits) C3D_FrameBufClear(&left->frameBuf, left->clearBits, left->clearColor, left->clearDepth); if (right && right != left && right->clearBits) C3D_FrameBufClear(&right->frameBuf, right->clearBits, right->clearColor, right->clearDepth); gfxConfigScreen(GFX_TOP, false); } gxCmdQueueRun(queue); } if (framerateLimit(0)) frameCounter[0]++; } static void onVBlank1(C3D_UNUSED void* unused) { gxCmdQueue_s* queue = &C3Di_GetContext()->gxQueue; if (frameStage & STAGE_NEED_BOT_TRANSFER) { gxCmdQueueStop(queue); frameStage &= ~STAGE_NEED_BOT_TRANSFER; C3D_RenderTarget* target = linkedTarget[2]; if (target) { frameStage |= STAGE_WAIT_TRANSFER; C3D_FrameBufTransfer(&target->frameBuf, GFX_TOP, GFX_LEFT, target->transferFlags); if (target->clearBits) C3D_FrameBufClear(&target->frameBuf, target->clearBits, target->clearColor, target->clearDepth); gfxConfigScreen(GFX_BOTTOM, false); } gxCmdQueueRun(queue); } if (framerateLimit(1)) frameCounter[1]++; } static void onQueueFinish(gxCmdQueue_s* queue) { if (measureGpuTime) { osTickCounterUpdate(&gpuTime); measureGpuTime = false; } if (inSafeTransfer) { inSafeTransfer = false; if (inFrame) { gxCmdQueueStop(queue); gxCmdQueueClear(queue); } } else if (frameStage & STAGE_WAIT_TRANSFER) frameStage &= ~STAGE_WAIT_TRANSFER; else { u8 needs = frameStage & STAGE_HAS_ANY_TRANSFER; frameStage = (frameStage&~STAGE_HAS_ANY_TRANSFER) | (needs<<3); } } void C3D_FrameSync(void) { u32 cur[2]; u32 start[2] = { frameCounter[0], frameCounter[1] }; do { gspWaitForAnyEvent(); cur[0] = frameCounter[0]; cur[1] = frameCounter[1]; } while (cur[0]==start[0] || cur[1]==start[1]); } static bool C3Di_WaitAndClearQueue(s64 timeout) { gxCmdQueue_s* queue = &C3Di_GetContext()->gxQueue; if (!gxCmdQueueWait(queue, timeout)) return false; if (timeout==0 && frameStage) return false; while (frameStage) gspWaitForAnyEvent(); gxCmdQueueStop(queue); gxCmdQueueClear(queue); return true; } static void C3Di_RenderQueueInit(void) { gspSetEventCallback(GSPGPU_EVENT_VBlank0, onVBlank0, NULL, false); gspSetEventCallback(GSPGPU_EVENT_VBlank1, onVBlank1, NULL, false); gxCmdQueueSetCallback(&C3Di_GetContext()->gxQueue, onQueueFinish, NULL); } static void C3Di_RenderTargetDestroy(C3D_RenderTarget* target); void C3Di_RenderQueueExit(void) { int i; C3D_RenderTarget *a, *next; if (!initialized) return; C3Di_WaitAndClearQueue(-1); for (a = firstTarget; a; a = next) { next = a->next; C3Di_RenderTargetDestroy(a); } gspSetEventCallback(GSPGPU_EVENT_VBlank0, NULL, NULL, false); gspSetEventCallback(GSPGPU_EVENT_VBlank1, NULL, NULL, false); gxCmdQueueSetCallback(&C3Di_GetContext()->gxQueue, NULL, NULL); for (i = 0; i < 3; i ++) linkedTarget[i] = NULL; initialized = false; } void C3Di_RenderQueueWaitDone(void) { if (!initialized) return; C3Di_WaitAndClearQueue(-1); } static bool checkRenderQueueInit(void) { C3D_Context* ctx = C3Di_GetContext(); if (!(ctx->flags & C3DiF_Active)) return false; if (!initialized) { C3Di_RenderQueueInit(); initialized = true; } return true; } float C3D_FrameRate(float fps) { float old = framerate; if (fps > 0.0f && fps <= 60.0f) { framerate = fps; framerateCounter[0] = fps; framerateCounter[1] = fps; } return old; } bool C3D_FrameBegin(u8 flags) { if (inFrame) return false; if (flags & C3D_FRAME_SYNCDRAW) C3D_FrameSync(); if (!C3Di_WaitAndClearQueue((flags & C3D_FRAME_NONBLOCK) ? 0 : -1)) return false; inFrame = true; osTickCounterStart(&cpuTime); return true; } bool C3D_FrameDrawOn(C3D_RenderTarget* target) { if (!inFrame) return false; target->used = true; C3D_SetFrameBuf(&target->frameBuf); C3D_SetViewport(0, 0, target->frameBuf.width, target->frameBuf.height); return true; } void C3D_FrameSplit(u8 flags) { u32 *cmdBuf, cmdBufSize; if (!inFrame) return; if (C3Di_SplitFrame(&cmdBuf, &cmdBufSize)) GX_ProcessCommandList(cmdBuf, cmdBufSize*4, flags); } void C3D_FrameEnd(u8 flags) { C3D_Context* ctx = C3Di_GetContext(); C3D_FrameSplit(flags); inFrame = false; osTickCounterUpdate(&cpuTime); // Flush the entire linear memory if the user did not explicitly mandate to flush the command list if (!(flags & GX_CMDLIST_FLUSH)) { extern u32 __ctru_linear_heap; extern u32 __ctru_linear_heap_size; GSPGPU_FlushDataCache((void*)__ctru_linear_heap, __ctru_linear_heap_size); } int i; C3D_RenderTarget* target; for (i = 2; i >= 0; i --) { target = linkedTarget[i]; if (!target || !target->used) continue; target->used = false; frameStage |= STAGE_HAS_TRANSFER(i); } for (target = firstTarget; target; target = target->next) { if (!target->used || !target->clearBits) continue; target->used = false; C3D_FrameBufClear(&target->frameBuf, target->clearBits, target->clearColor, target->clearDepth); } GPUCMD_SetBuffer(ctx->cmdBuf, ctx->cmdBufSize, 0); measureGpuTime = true; osTickCounterStart(&gpuTime); gxCmdQueueRun(&ctx->gxQueue); } float C3D_GetDrawingTime(void) { return osTickCounterRead(&gpuTime); } float C3D_GetProcessingTime(void) { return osTickCounterRead(&cpuTime); } static C3D_RenderTarget* C3Di_RenderTargetNew(void) { C3D_RenderTarget* target = (C3D_RenderTarget*)malloc(sizeof(C3D_RenderTarget)); if (!target) return NULL; memset(target, 0, sizeof(C3D_RenderTarget)); return target; } static void C3Di_RenderTargetFinishInit(C3D_RenderTarget* target) { target->prev = lastTarget; target->next = NULL; if (lastTarget) lastTarget->next = target; if (!firstTarget) firstTarget = target; lastTarget = target; } C3D_RenderTarget* C3D_RenderTargetCreate(int width, int height, GPU_COLORBUF colorFmt, C3D_DEPTHTYPE depthFmt) { if (!checkRenderQueueInit()) goto _fail0; GPU_DEPTHBUF depthFmtReal = GPU_RB_DEPTH16; void* depthBuf = NULL; void* colorBuf = vramAlloc(C3D_CalcColorBufSize(width,height,colorFmt)); if (!colorBuf) goto _fail0; if (C3D_DEPTHTYPE_OK(depthFmt)) { depthFmtReal = C3D_DEPTHTYPE_VAL(depthFmt); depthBuf = vramAlloc(C3D_CalcDepthBufSize(width,height,depthFmtReal)); if (!depthBuf) goto _fail1; } C3D_RenderTarget* target = C3Di_RenderTargetNew(); if (!target) goto _fail2; C3D_FrameBuf* fb = &target->frameBuf; C3D_FrameBufAttrib(fb, width, height, false); C3D_FrameBufColor(fb, colorBuf, colorFmt); target->ownsColor = true; if (depthBuf) { C3D_FrameBufDepth(fb, depthBuf, depthFmtReal); target->ownsDepth = true; } C3Di_RenderTargetFinishInit(target); return target; _fail2: if (depthBuf) vramFree(depthBuf); _fail1: vramFree(colorBuf); _fail0: return NULL; } C3D_RenderTarget* C3D_RenderTargetCreateFromTex(C3D_Tex* tex, GPU_TEXFACE face, int level, C3D_DEPTHTYPE depthFmt) { if (!checkRenderQueueInit()) return NULL; C3D_RenderTarget* target = C3Di_RenderTargetNew(); if (!target) return NULL; C3D_FrameBuf* fb = &target->frameBuf; C3D_FrameBufTex(fb, tex, face, level); if (C3D_DEPTHTYPE_OK(depthFmt)) { GPU_DEPTHBUF depthFmtReal = C3D_DEPTHTYPE_VAL(depthFmt); void* depthBuf = vramAlloc(C3D_CalcDepthBufSize(fb->width,fb->height,depthFmtReal)); if (!depthBuf) { free(target); return NULL; } C3D_FrameBufDepth(fb, depthBuf, depthFmtReal); target->ownsDepth = true; } C3Di_RenderTargetFinishInit(target); return target; } void C3Di_RenderTargetDestroy(C3D_RenderTarget* target) { if (target->ownsColor) vramFree(target->frameBuf.colorBuf); if (target->ownsDepth) vramFree(target->frameBuf.depthBuf); 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_RenderTargetDelete(C3D_RenderTarget* target) { if (inFrame) svcBreak(USERBREAK_PANIC); // Shouldn't happen. C3Di_WaitAndClearQueue(-1); C3Di_RenderTargetDestroy(target); } void C3D_RenderTargetSetClear(C3D_RenderTarget* target, C3D_ClearBits clearBits, u32 clearColor, u32 clearDepth) { if (!target->frameBuf.colorBuf) clearBits &= ~C3D_CLEAR_COLOR; if (!target->frameBuf.depthBuf) clearBits &= ~C3D_CLEAR_DEPTH; C3D_ClearBits oldClearBits = target->clearBits; target->clearBits = clearBits; target->clearColor = clearColor; target->clearDepth = clearDepth; if (clearBits &~ oldClearBits) C3D_FrameBufClear(&target->frameBuf, clearBits, clearColor, clearDepth); } 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; } void C3D_SafeDisplayTransfer(u32* inadr, u32 indim, u32* outadr, u32 outdim, u32 flags) { C3Di_WaitAndClearQueue(-1); inSafeTransfer = true; GX_DisplayTransfer(inadr, indim, outadr, outdim, flags); gxCmdQueueRun(&C3Di_GetContext()->gxQueue); } void C3D_SafeTextureCopy(u32* inadr, u32 indim, u32* outadr, u32 outdim, u32 size, u32 flags) { C3Di_WaitAndClearQueue(-1); inSafeTransfer = true; GX_TextureCopy(inadr, indim, outadr, outdim, size, flags); gxCmdQueueRun(&C3Di_GetContext()->gxQueue); } void C3D_SafeMemoryFill(u32* buf0a, u32 buf0v, u32* buf0e, u16 control0, u32* buf1a, u32 buf1v, u32* buf1e, u16 control1) { C3Di_WaitAndClearQueue(-1); inSafeTransfer = true; GX_MemoryFill(buf0a, buf0v, buf0e, control0, buf1a, buf1v, buf1e, control1); gxCmdQueueRun(&C3Di_GetContext()->gxQueue); }