diff --git a/libultraship b/libultraship index 6f730662e..70a5fc323 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 6f730662e1a631ef48965b697c0e917802f379b2 +Subproject commit 70a5fc323af9b9721704aeffd604f0f8aaa01538 diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 3a8983168..2c8a0112d 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -497,7 +497,7 @@ extern "C" void Graph_StartFrame() { void RunCommands(Gfx* Commands, const std::vector>& mtx_replacements) { for (const auto& m : mtx_replacements) { - gfx_run(Commands, m); + gfx_run(Commands, m); gfx_end_frame(); } } @@ -1293,10 +1293,12 @@ extern "C" void OTRGfxPrint(const char* str, void* printer, void (*printImpl)(vo } } +// Gets the width of the main ImGui window extern "C" uint32_t OTRGetCurrentWidth() { return OTRGlobals::Instance->context->GetWindow()->GetWidth(); } +// Gets the height of the main ImGui window extern "C" uint32_t OTRGetCurrentHeight() { return OTRGlobals::Instance->context->GetWindow()->GetHeight(); } @@ -1387,6 +1389,16 @@ extern "C" float OTRGetDimensionFromRightEdge(float v) { return (SCREEN_WIDTH / 2 + SCREEN_HEIGHT / 2 * OTRGetAspectRatio() - (SCREEN_WIDTH - v)); } +// Gets the width of the current render target area +extern "C" uint32_t OTRGetGameRenderWidth() { + return gfx_current_dimensions.width; +} + +// Gets the height of the current render target area +extern "C" uint32_t OTRGetGameRenderHeight() { + return gfx_current_dimensions.height; +} + f32 floorf(f32 x); f32 ceilf(f32 x); diff --git a/mm/2s2h/BenPort.h b/mm/2s2h/BenPort.h index b0ec50e64..e530ec50a 100644 --- a/mm/2s2h/BenPort.h +++ b/mm/2s2h/BenPort.h @@ -116,6 +116,8 @@ float OTRGetDimensionFromLeftEdge(float v); float OTRGetDimensionFromRightEdge(float v); int16_t OTRGetRectDimensionFromLeftEdge(float v); int16_t OTRGetRectDimensionFromRightEdge(float v); +uint32_t OTRGetGameRenderWidth(); +uint32_t OTRGetGameRenderHeight(); int AudioPlayer_Buffered(void); int AudioPlayer_GetDesiredBuffered(void); void AudioPlayer_Play(const uint8_t* buf, uint32_t len); diff --git a/mm/2s2h/framebuffer_effects.c b/mm/2s2h/framebuffer_effects.c new file mode 100644 index 000000000..7cfe2c95b --- /dev/null +++ b/mm/2s2h/framebuffer_effects.c @@ -0,0 +1,120 @@ +#include "framebuffer_effects.h" +#include "global.h" + +int gfx_create_framebuffer(uint32_t width, uint32_t height); + +s32 gPauseFrameBuffer = -1; +s32 gBlurFrameBuffer = -1; +s32 gShrinkFrameBuffer = -1; + +void FB_CreateFramebuffers(void) { + if (gPauseFrameBuffer == -1) { + gPauseFrameBuffer = gfx_create_framebuffer(SCREEN_WIDTH, SCREEN_HEIGHT); + } + + if (gBlurFrameBuffer == -1) { + gBlurFrameBuffer = gfx_create_framebuffer(SCREEN_WIDTH, SCREEN_HEIGHT); + } + + if (gShrinkFrameBuffer == -1) { + gShrinkFrameBuffer = gfx_create_framebuffer(SCREEN_WIDTH, SCREEN_HEIGHT); + } +} + +/** + * Copies the current texture data from the source frame buffer to the destination frame buffer + * Setting oncePerFrame ensures that the copy will only happen once every game frame. This + * is important for effects that could be affected by increased frame interpolation (like motion blur). + * A pointer to a boolean is passed to the render for the render to set once the copy has been performed. + */ +void FB_CopyToFramebuffer(Gfx** gfxp, s32 fb_src, s32 fb_dest, u8 oncePerFrame, u8* hasCopied) { + Gfx* gfx = *gfxp; + + gSPMatrix(gfx++, &gIdentityMtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + + gDPSetOtherMode(gfx++, + G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_NONE | G_TL_TILE | + G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE, + G_AC_NONE | G_ZS_PRIM | G_RM_OPA_SURF | G_RM_OPA_SURF2); + + gSPClearGeometryMode(gfx++, G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR); + gSPSetGeometryMode(gfx++, G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH); + + gDPSetBlendColor(gfx++, 255, 255, 255, 8); + gDPSetPrimDepth(gfx++, 0xFFFF, 0xFFFF); + + gDPSetEnvColor(gfx++, 255, 255, 255, 255); + gDPSetCombineLERP(gfx++, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, ENVIRONMENT, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, + ENVIRONMENT); + + gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + + gDPCopyFB(gfx++, fb_dest, fb_src, oncePerFrame, hasCopied); + + *gfxp = gfx; +} + +/** + * Draws the texture data from the specified frame buffer as a full screen image + */ +void FB_DrawFromFramebuffer(Gfx** gfxp, s32 fb, u8 alpha) { + Gfx* gfx = *gfxp; + + gSPMatrix(gfx++, &gIdentityMtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + + gDPSetEnvColor(gfx++, 255, 255, 255, alpha); + + gDPSetOtherMode(gfx++, + G_AD_NOISE | G_CD_NOISE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_NONE | G_TL_TILE | + G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE, + G_AC_NONE | G_ZS_PRIM | G_RM_CLD_SURF | G_RM_CLD_SURF2); + + gSPClearGeometryMode(gfx++, G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR); + gSPSetGeometryMode(gfx++, G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH); + + gDPSetCombineLERP(gfx++, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, ENVIRONMENT, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, + ENVIRONMENT); + + gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + + gDPSetTextureImageFB(gfx++, 0, 0, 0, fb); + gDPImageRectangle(gfx++, OTRGetRectDimensionFromLeftEdge(0) << 2, 0 << 2, 0, 0, + OTRGetRectDimensionFromRightEdge(SCREEN_WIDTH) << 2, SCREEN_HEIGHT << 2, + OTRGetGameRenderWidth(), OTRGetGameRenderHeight(), G_TX_RENDERTILE, + OTRGetGameRenderWidth(), OTRGetGameRenderHeight()); + + *gfxp = gfx; +} + +/** + * Similar to FB_DrawFromFramebuffer, but scales the image relative to the center of the screen + */ +void FB_DrawFromFramebufferScaled(Gfx** gfxp, s32 fb, u8 alpha, float scaleX, float scaleY) { + Gfx* gfx = *gfxp; + + gDPSetEnvColor(gfx++, 255, 255, 255, alpha); + + gDPSetOtherMode(gfx++, + G_AD_NOISE | G_CD_NOISE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_NONE | G_TL_TILE | + G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE, + G_AC_NONE | G_ZS_PRIM | G_RM_CLD_SURF | G_RM_CLD_SURF2); + + gDPSetCombineLERP(gfx++, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, ENVIRONMENT, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, + ENVIRONMENT); + + gDPSetScissor(gfx++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + + gDPSetTextureImageFB(gfx++, 0, 0, 0, fb); + + float x0 = gScreenWidth * 0.5f * scaleX; + float y0 = gScreenHeight * 0.5f * scaleY; + + gDPImageRectangle(gfx++, OTRGetRectDimensionFromLeftEdge(x0) << 2, + (int)(y0) << 2, 0, 0, + OTRGetRectDimensionFromRightEdge((float)(gScreenWidth - x0)) << 2, + (int)((float)(gScreenHeight - y0)) << 2, + OTRGetGameRenderWidth(), OTRGetGameRenderHeight(), G_TX_RENDERTILE, + OTRGetGameRenderWidth(), OTRGetGameRenderHeight()); + + *gfxp = gfx; +} diff --git a/mm/2s2h/framebuffer_effects.h b/mm/2s2h/framebuffer_effects.h new file mode 100644 index 000000000..808af2203 --- /dev/null +++ b/mm/2s2h/framebuffer_effects.h @@ -0,0 +1,15 @@ +#ifndef FRAMEBUFFER_EFFECTS_H +#define FRAMEBUFFER_EFFECTS_H + +#include "ultra64.h" + +extern s32 gPauseFrameBuffer; +extern s32 gBlurFrameBuffer; +extern s32 gShrinkFrameBuffer; + +void FB_CreateFramebuffers(void); +void FB_CopyToFramebuffer(Gfx** gfxp, s32 fb_src, s32 fb_dest, u8 oncePerFrame, u8* hasCopied); +void FB_DrawFromFramebuffer(Gfx** gfxp, s32 fb, u8 alpha); +void FB_DrawFromFramebufferScaled(Gfx** gfxp, s32 fb, u8 alpha, float scaleX, float scaleY); + +#endif // FRAMEBUFFER_EFFECTS_H diff --git a/mm/src/code/sys_cfb.c b/mm/src/code/sys_cfb.c index 66350d666..14cd50b3a 100644 --- a/mm/src/code/sys_cfb.c +++ b/mm/src/code/sys_cfb.c @@ -2,6 +2,7 @@ #include "regs.h" #include "functions.h" #include "macros.h" +#include "2s2h/framebuffer_effects.h" // Variables are put before most headers as a hacky way to bypass bss reordering OSViMode sNotebookViMode; // placeholder name @@ -97,6 +98,9 @@ void SysCfb_Init(void) { sCfbHiRes1 = gFramebufferHiRes1; sCfbHiRes0 = gFramebufferHiRes0; SysCfb_SetLoResMode(); + + // 2S2H [Port] Create the framebuffers to be used by the adjusted framebuffer effects + FB_CreateFramebuffers(); } // Unused diff --git a/mm/src/code/z_play.c b/mm/src/code/z_play.c index ca25ef1ef..51baee7c4 100644 --- a/mm/src/code/z_play.c +++ b/mm/src/code/z_play.c @@ -35,6 +35,7 @@ u8 sMotionBlurStatus; #include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" #include "debug.h" #include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h" +#include "2s2h/framebuffer_effects.h" #include s32 gDbgCamEnabled = false; @@ -56,6 +57,12 @@ void Play_DrawMotionBlur(PlayState* this) { Gfx* gfx; Gfx* gfxHead; + // 2S2H [Port] Frame buffer effects for motion blur + // Track render size when blur is active and that a copy was performed + static u32 lastBlurWidth; + static u32 lastBlurHeight; + static u8 hasCopiedForFrame; + if (R_MOTION_BLUR_PRIORITY_ENABLED) { alpha = R_MOTION_BLUR_PRIORITY_ALPHA; @@ -84,13 +91,25 @@ void Play_DrawMotionBlur(PlayState* this) { this->pauseBgPreRender.fbuf = gfxCtx->curFrameBuffer; this->pauseBgPreRender.fbufSave = this->unk_18E64; - if (sMotionBlurStatus == MOTION_BLUR_PROCESS) { - func_80170AE0(&this->pauseBgPreRender, &gfx, alpha); - } else { - sMotionBlurStatus = MOTION_BLUR_PROCESS; + // 2S2H [Port] When the render size changes, we need to skip the blur for one frame to + // avoid rendering and copying a blank framebuffer + if (sMotionBlurStatus == MOTION_BLUR_PROCESS && + (lastBlurWidth != OTRGetGameRenderWidth() || lastBlurHeight != OTRGetGameRenderHeight())) { + sMotionBlurStatus = MOTION_BLUR_SETUP; } - PreRender_SaveFramebuffer(&this->pauseBgPreRender, &gfx); + if (sMotionBlurStatus == MOTION_BLUR_PROCESS) { + FB_DrawFromFramebuffer(&gfx, gBlurFrameBuffer, alpha); + } else { + sMotionBlurStatus = MOTION_BLUR_PROCESS; + lastBlurWidth = OTRGetGameRenderWidth(); + lastBlurHeight = OTRGetGameRenderHeight(); + } + + hasCopiedForFrame = false; + + // 2S2H [Port] Copy framebuffer only once per game frame to exclude motion blur from interpolation + FB_CopyToFramebuffer(&gfx, 0, gBlurFrameBuffer, true, &hasCopiedForFrame); gSPEndDisplayList(gfx++); @@ -1141,6 +1160,25 @@ void Play_DrawMain(PlayState* this) { u8 sp25B = false; f32 zFar; + // #region 2S2H [Port] Frame buffer effects for pause menu + // Track render size when paused and that a copy was performed + static u32 lastPauseWidth; + static u32 lastPauseHeight; + static u8 hasCapturedPauseBuffer; + u8 recapturePauseBuffer = false; + + // If the size has changed or dropped frames leading to the buffer not being copied, + // set the prerender state back to setup to copy a new frame. + // This requires not rendering kaleido during this copy to avoid kaleido being copied + if ((R_PAUSE_BG_PRERENDER_STATE == PAUSE_BG_PRERENDER_PROCESS || + R_PAUSE_BG_PRERENDER_STATE == PAUSE_BG_PRERENDER_READY) && + (lastPauseWidth != OTRGetGameRenderWidth() || lastPauseHeight != OTRGetGameRenderHeight() || + !hasCapturedPauseBuffer)) { + R_PAUSE_BG_PRERENDER_STATE = PAUSE_BG_PRERENDER_SETUP; + recapturePauseBuffer = true; + } + // #endregion + if (R_PAUSE_BG_PRERENDER_STATE >= PAUSE_BG_PRERENDER_UNK4) { PreRender_ApplyFiltersSlowlyDestroy(&this->pauseBgPreRender); R_PAUSE_BG_PRERENDER_STATE = PAUSE_BG_PRERENDER_OFF; @@ -1265,15 +1303,16 @@ void Play_DrawMain(PlayState* this) { } R_PAUSE_BG_PRERENDER_STATE = PAUSE_BG_PRERENDER_READY; SREG(33) |= 1; + + // 2S2H [Port] Jump to the pause render flow to avoid flashing a blank screen + // since previous framebuffers aren't retained like hardware does + goto PauseRenderDraw; } else { + PauseRenderDraw: if (R_PAUSE_BG_PRERENDER_STATE == PAUSE_BG_PRERENDER_READY) { Gfx* sp8C = POLY_OPA_DISP; - if (this->pauseBgPreRender.filterState == PRERENDER_FILTER_STATE_DONE) { - PreRender_RestoreFramebuffer(&this->pauseBgPreRender, &sp8C); - } else { - func_80170798(&this->pauseBgPreRender, &sp8C); - } + FB_DrawFromFramebuffer(&sp8C, gPauseFrameBuffer, 255); gSPDisplayList(sp8C++, D_0E000000_TO_SEGMENTED(syncSegments)); POLY_OPA_DISP = sp8C; @@ -1407,18 +1446,25 @@ void Play_DrawMain(PlayState* this) { this->pauseBgPreRender.cvgSave = NULL; } - PreRender_SaveFramebuffer(&this->pauseBgPreRender, &sp74); + lastPauseWidth = OTRGetGameRenderWidth(); + lastPauseHeight = OTRGetGameRenderHeight(); + hasCapturedPauseBuffer = false; - if (this->pauseBgPreRender.cvgSave != NULL) { - PreRender_DrawCoverage(&this->pauseBgPreRender, &sp74); - } + FB_CopyToFramebuffer(&sp74, 0, gPauseFrameBuffer, false, &hasCapturedPauseBuffer); gSPEndDisplayList(sp74++); Graph_BranchDlist(sp70, sp74); POLY_OPA_DISP = sp74; this->unk_18B49 = 2; SREG(33) |= 1; - goto SkipPostWorldDraw; + + // 2S2H [Port] Set the state back to ready after the recapture is done + if (recapturePauseBuffer) { + R_PAUSE_BG_PRERENDER_STATE = PAUSE_BG_PRERENDER_READY; + } + + // 2S2H [Port] Continue to render the post world to avoid flashing the HUD + // goto SkipPostWorldDraw; } PostWorldDraw: diff --git a/mm/src/code/z_visfbuf.c b/mm/src/code/z_visfbuf.c index bfdddfab6..bf777d986 100644 --- a/mm/src/code/z_visfbuf.c +++ b/mm/src/code/z_visfbuf.c @@ -14,6 +14,7 @@ #include "z64visfbuf.h" #include "sys_cfb.h" #include +#include "2s2h/framebuffer_effects.h" #define SCALE_MIN 0.032f #define SCALE_MAX 1.0f //!< also unchanged scale @@ -169,7 +170,7 @@ void VisFbuf_ApplyEffects(VisFbuf* this, Gfx** gfxP, void* source, void* img, s3 G_AD_PATTERN | G_CD_MAGICSQ | G_CK_NONE | G_TC_CONV | G_TF_POINT | G_TT_NONE | G_TL_TILE | G_TD_CLAMP | G_TP_NONE | G_CYC_COPY | G_PM_NPRIMITIVE, G_AC_NONE | G_ZS_PIXEL | G_RM_NOOP | G_RM_NOOP2); - VisFbuf_SetBgSimple(&gfx, source, img, width, height, VIS_FBUF_BG_CYC_COPY); + FB_CopyToFramebuffer(&gfx, 0, gShrinkFrameBuffer, false, NULL); gDPPipeSync(gfx++); // fill framebuffer with primColor @@ -185,7 +186,7 @@ void VisFbuf_ApplyEffects(VisFbuf* this, Gfx** gfxP, void* source, void* img, s3 //! @bug VisFbuf_SetBgSimple() sets the current color image back to the frame's default framebuffer at the end, //! so this will always fill in the default framebuffer, whatever are used as `source` and `img`. This does not //! arise in-game since this function is always used with `source = D_0F000000`. - gDPFillRectangle(gfx++, 0, 0, width - 1, height - 1); + gDPFillWideRectangle(gfx++, OTRGetRectDimensionFromLeftEdge(0), 0, OTRGetRectDimensionFromRightEdge(width - 1), height - 1); gDPPipeSync(gfx++); // Set lod and primColor from struct, perform interpolation, draw image with scaling (this is the most general @@ -221,8 +222,11 @@ void VisFbuf_ApplyEffects(VisFbuf* this, Gfx** gfxP, void* source, void* img, s3 { f32 scale = CLAMP_ALT(this->scale, SCALE_MIN, SCALE_MAX); - VisFbuf_SetBgGeneral(&gfx, img, source, width, height, width * 0.5f * (1.0f - scale), - height * 0.5f * (1.0f - scale), scale, scale, VIS_FBUF_BG_CYC_1CYC); + // 2S2H [Port][Widescreen] + // Draw shrunk window using an adjusted horizontal scale accounting for different aspect ratios + FB_DrawFromFramebufferScaled(&gfx, gShrinkFrameBuffer, 255, + (1.0f - scale) * (OTRGetAspectRatio() / ((float)SCREEN_WIDTH / SCREEN_HEIGHT)), + (1.0f - scale)); } gDPPipeSync(gfx++);