#include "Kyoto/Graphics/CGraphics.hpp" #include "Kyoto/Basics/COsContext.hpp" #include "Kyoto/Basics/CStopwatch.hpp" #include "Kyoto/CFrameDelayedKiller.hpp" #include "Kyoto/Graphics/CGX.hpp" #include "Kyoto/Graphics/CGraphicsSys.hpp" #include "Kyoto/Graphics/CTexture.hpp" #include "Kyoto/Math/CRelAngle.hpp" #include "dolphin/vi.h" #include bool CGraphicsSys::mGraphicsInitialized; static CStopwatch sFPSTimer; static uchar sSpareFrameBuffer[640 * 448]; // clang-format off CTevCombiners::CTevPass CGraphics::kEnvModulateConstColor( CTevCombiners::ColorPass( CTevCombiners::kCS_Zero, CTevCombiners::kCS_RasterColor, CTevCombiners::kCS_RegisterC0, CTevCombiners::kCS_Zero ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_RasterAlpha, CTevCombiners::kAS_RegisterA0, CTevCombiners::kAS_Zero ) ); CTevCombiners::CTevPass CGraphics::kEnvConstColor( CTevCombiners::ColorPass( CTevCombiners::kCS_Zero, CTevCombiners::kCS_Zero, CTevCombiners::kCS_Zero, CTevCombiners::kCS_RegisterC0 ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_RegisterA0 ) ); CTevCombiners::CTevPass CGraphics::kEnvModulate( CTevCombiners::ColorPass( CTevCombiners::kCS_Zero, CTevCombiners::kCS_RasterColor, CTevCombiners::kCS_TextureColor, CTevCombiners::kCS_Zero ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_RasterAlpha, CTevCombiners::kAS_TextureAlpha, CTevCombiners::kAS_Zero ) ); CTevCombiners::CTevPass CGraphics::kEnvDecal( CTevCombiners::ColorPass( CTevCombiners::kCS_RasterColor, CTevCombiners::kCS_TextureColor, CTevCombiners::kCS_TextureAlpha, CTevCombiners::kCS_Zero ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_RasterAlpha ) ); CTevCombiners::CTevPass CGraphics::kEnvBlend( CTevCombiners::ColorPass( CTevCombiners::kCS_RasterColor, CTevCombiners::kCS_One, CTevCombiners::kCS_TextureColor, CTevCombiners::kCS_Zero ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_TextureAlpha, CTevCombiners::kAS_RasterAlpha, CTevCombiners::kAS_Zero ) ); CTevCombiners::CTevPass CGraphics::kEnvReplace( CTevCombiners::ColorPass( CTevCombiners::kCS_Zero, CTevCombiners::kCS_Zero, CTevCombiners::kCS_Zero, CTevCombiners::kCS_TextureColor ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_TextureAlpha ) ); static CTevCombiners::CTevPass kEnvBlendCTandCConCF( CTevCombiners::ColorPass( CTevCombiners::kCS_RegisterC0, CTevCombiners::kCS_TextureColor, CTevCombiners::kCS_RasterColor, CTevCombiners::kCS_Zero ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_RasterAlpha ) ); CTevCombiners::CTevPass CGraphics::kEnvModulateAlpha( CTevCombiners::ColorPass( CTevCombiners::kCS_Zero, CTevCombiners::kCS_Zero, CTevCombiners::kCS_Zero, CTevCombiners::kCS_RasterColor ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_TextureAlpha, CTevCombiners::kAS_RasterAlpha, CTevCombiners::kAS_Zero ) ); CTevCombiners::CTevPass CGraphics::kEnvModulateColor( CTevCombiners::ColorPass( CTevCombiners::kCS_Zero, CTevCombiners::kCS_TextureColor, CTevCombiners::kCS_RasterColor, CTevCombiners::kCS_Zero ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_Konst, CTevCombiners::kAS_RasterAlpha, CTevCombiners::kAS_Zero ) ); CTevCombiners::CTevPass CGraphics::kEnvModulateColorByAlpha( CTevCombiners::ColorPass( CTevCombiners::kCS_Zero, CTevCombiners::kCS_PreviousColor, CTevCombiners::kCS_PreviousAlpha, CTevCombiners::kCS_Zero ), CTevCombiners::AlphaPass( CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_Zero, CTevCombiners::kAS_PreviousAlpha ) ); // clang-format on CGraphics::CRenderState CGraphics::sRenderState; VecPtr CGraphics::vtxBuffer; VecPtr CGraphics::nrmBuffer; Vec2Ptr CGraphics::txtBuffer0; Vec2Ptr CGraphics::txtBuffer1; uint* CGraphics::clrBuffer; bool CGraphics::mJustReset; ERglCullMode CGraphics::mCullMode; int CGraphics::mNumLightsActive; float CGraphics::mDepthNear; VecPtr CGraphics::mpVtxBuffer; VecPtr CGraphics::mpNrmBuffer; Vec2Ptr CGraphics::mpTxtBuffer0; Vec2Ptr CGraphics::mpTxtBuffer1; uint* CGraphics::mpClrBuffer; struct CGXLightParams { int x0_; int x4_; int x8_; int xc_; int x10_; CGXLightParams() : x0_(4), x4_(0), x8_(0), xc_(2), x10_(2) {} }; CGXLightParams mLightParams[8]; struct { Vec vtx; Vec nrm; Vec2 uv0; Vec2 uv1; uint color; ushort textureUsed; uchar streamFlags; } vtxDescr; CVector3f CGraphics::kDefaultPositionVector(0.f, 0.f, 0.f); CVector3f CGraphics::kDefaultDirectionVector(0.f, 0.f, 0.f); CGraphics::CProjectionState CGraphics::mProj(true, -1.f, 1.f, 1.f, -1.f, 1.f, 100.f); CTransform4f CGraphics::mViewMatrix = CTransform4f::Identity(); CTransform4f CGraphics::mModelMatrix = CTransform4f::Identity(); CColor CGraphics::mClearColor = CColor::Black(); CVector3f CGraphics::mViewPoint(0.f, 0.f, 0.f); GXLightObj CGraphics::mLightObj[8]; GXTexRegion CGraphics::mTexRegions[GX_MAX_TEXMAP]; GXTexRegion CGraphics::mTexRegionsCI[GX_MAX_TEXMAP / 2]; GXRenderModeObj CGraphics::mRenderModeObj; Mtx CGraphics::mGXViewPointMatrix; Mtx CGraphics::mGXModelMatrix; Mtx CGraphics::mGxModelView; Mtx CGraphics::mCameraMtx; int CGraphics::mNumPrimitives; int CGraphics::mFrameCounter; float CGraphics::mFramesPerSecond; float CGraphics::mLastFramesPerSecond; int CGraphics::mNumBreakpointsWaiting; int CGraphics::mFlippingState; bool CGraphics::mLastFrameUsedAbove; bool CGraphics::mInterruptLastFrameUsedAbove; uchar CGraphics::mLightActive; uchar CGraphics::mLightsWereOn; void* CGraphics::mpFrameBuf1; void* CGraphics::mpFrameBuf2; void* CGraphics::mpCurrenFrameBuf; int CGraphics::mSpareBufferSize; void* CGraphics::mpSpareBuffer; int CGraphics::mSpareBufferTexCacheSize; GXTexRegionCallback CGraphics::mGXDefaultTexRegionCallback; void* CGraphics::mpFifo; GXFifoObj* CGraphics::mpFifoObj; int CGraphics::mRenderTimings; float CGraphics::mSecondsMod900; CTimeProvider* CGraphics::mpExternalTimeProvider; int lbl_805A9408; int lbl_805A940C; int lbl_805A9410; CViewport CGraphics::mViewport = {0, 0, 640, 448, 320.f, 240.f}; ELightType CGraphics::mLightTypes[8] = { kLT_Directional, kLT_Directional, kLT_Directional, kLT_Directional, kLT_Directional, kLT_Directional, kLT_Directional, kLT_Directional, }; const CTevCombiners::CTevPass& CGraphics::kEnvPassthru = CTevCombiners::kEnvPassthru; bool CGraphics::mIsBeginSceneClearFb = true; ERglEnum CGraphics::mDepthFunc = kE_LEqual; ERglPrimitive CGraphics::mCurrentPrimitive = kP_Points; float CGraphics::mDepthFar = 1.f; u32 CGraphics::mClearDepthValue = GX_MAX_Z24; bool CGraphics::mIsGXModelMatrixIdentity = true; bool CGraphics::mFirstFrame = true; bool CGraphics::mUseVideoFilter = true; float CGraphics::mBrightness = 1.f; // Vec2 CGraphics::mBrightnessRange = {0.f, 2.f}; bool CGraphics::Startup(const COsContext& osContext, uint fifoSize, void* fifoBase) { mpFifo = fifoBase; mpFifoObj = GXInit(fifoBase, fifoSize); GXFifoObj fifoObj; GXInitFifoBase(&fifoObj, mpFifo, fifoSize); GXSetCPUFifo(&fifoObj); GXSetGPFifo(&fifoObj); GXInitFifoLimits(mpFifoObj, fifoSize - 0x4000, fifoSize - 0x10000); GXSetCPUFifo(mpFifoObj); GXSetGPFifo(mpFifoObj); GXSetMisc(GX_MT_XF_FLUSH, 8); GXSetDither(GX_FALSE); CGX::ResetGXStates(); InitGraphicsVariables(); ConfigureFrameBuffer(osContext); for (int i = 0; i < ARRAY_SIZE(mTexRegions); i++) { GXInitTexCacheRegion(&mTexRegions[i], false, 0x8000 * i, GX_TEXCACHE_32K, 0x80000 + (0x8000 * i), GX_TEXCACHE_32K); } for (int i = 0; i < ARRAY_SIZE(mTexRegionsCI); i++) { GXInitTexCacheRegion(&mTexRegionsCI[i], false, (8 + (2 * i)) << 0xF, GX_TEXCACHE_32K, (9 + (2 * i)) << 0xF, GX_TEXCACHE_32K); } mGXDefaultTexRegionCallback = GXSetTexRegionCallback(TexRegionCallback); mSpareBufferSize = ARRAY_SIZE(sSpareFrameBuffer); mpSpareBuffer = sSpareFrameBuffer; mSpareBufferTexCacheSize = 0x10000; return true; } GXTexRegion* CGraphics::TexRegionCallback(const GXTexObj* obj, GXTexMapID id) { static int nextTexRgn = 0; static int nextTexRgnCI = 0; if (id == GX_TEXMAP7) { return &mTexRegions[0]; } else { GXTexFmt fmt = GXGetTexObjFmt(obj); if (fmt != GX_TF_C4 && fmt != GX_TF_C8 && fmt != GX_TF_C14X2) { if (nextTexRgn == 0) { ++nextTexRgn; } return &mTexRegions[nextTexRgn++ & 7]; } else { return &mTexRegionsCI[nextTexRgnCI++ & 3]; } } } void CGraphics::InitGraphicsVariables() { for (int i = 0; i < ARRAY_SIZE(mLightTypes); ++i) { mLightTypes[i] = kLT_Directional; } mLightActive = 0; SetDepthWriteMode(false, mDepthFunc, false); SetCullMode(kCM_None); SetAmbientColor(CColor(0.2f, 0.2f, 0.2f, 1.f)); mIsGXModelMatrixIdentity = false; SetIdentityViewPointMatrix(); SetIdentityModelMatrix(); SetViewport(0, 0, mViewport.mWidth, mViewport.mHeight); SetPerspective(60.f, static_cast< float >(mViewport.mWidth) / static_cast< float >(mViewport.mHeight), mProj.GetNear(), mProj.GetFar()); SetCopyClear(mClearColor, 1.f); const GXColor white = {0xFF, 0xFF, 0xFF, 0xFF}; CGX::SetChanMatColor(CGX::Channel0, white); sRenderState.ResetFlushAll(); } void CGraphics::Shutdown() { GXSetTexRegionCallback(mGXDefaultTexRegionCallback); } void CGraphics::InitGraphicsDefaults() { SetDepthRange(0.f, 1.f); mIsGXModelMatrixIdentity = false; SetModelMatrix(mModelMatrix); SetViewPointMatrix(mViewMatrix); SetDepthWriteMode(false, mDepthFunc, false); SetCullMode(mCullMode); SetViewport(mViewport.mLeft, mViewport.mTop, mViewport.mWidth, mViewport.mHeight); FlushProjection(); CTevCombiners::Init(); DisableAllLights(); SetDefaultVtxAttrFmt(); } void CGraphics::ConfigureFrameBuffer(const COsContext& osContext) { mRenderModeObj = osContext.GetRenderModeObj(); mpFrameBuf1 = osContext.GetFramebuf1(); mpFrameBuf2 = osContext.GetFramebuf2(); VIConfigure(&mRenderModeObj); VISetNextFrameBuffer(mpFrameBuf1); mpCurrenFrameBuf = mpFrameBuf2; GXSetViewport(0.f, 0.f, static_cast< float >(mRenderModeObj.fbWidth), static_cast< float >(mRenderModeObj.efbHeight), 0.f, 1.f); GXSetScissor(0, 0, mRenderModeObj.fbWidth, mRenderModeObj.efbHeight); GXSetDispCopySrc(0, 0, mRenderModeObj.fbWidth, mRenderModeObj.efbHeight); GXSetDispCopyDst(mRenderModeObj.fbWidth, mRenderModeObj.efbHeight); GXSetDispCopyYScale(static_cast< float >(mRenderModeObj.xfbHeight) / static_cast< float >(mRenderModeObj.efbHeight)); GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, GX_ENABLE, mRenderModeObj.vfilter); if (mRenderModeObj.aa) { GXSetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR); } else { GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR); } GXSetDispCopyGamma(GX_GM_1_0); GXCopyDisp(mpCurrenFrameBuf, true); VIFlush(); VIWaitForRetrace(); VIWaitForRetrace(); mViewport.mWidth = mRenderModeObj.fbWidth; mViewport.mHeight = mRenderModeObj.efbHeight; InitGraphicsDefaults(); } void CGraphics::EnableLight(ERglLight light) { CGX::SetNumChans(1); int lightsWereOn = mLightActive; GXLightID lightId = static_cast< GXLightID >(1 << light); if ((lightsWereOn & lightId) == GX_LIGHT_NULL) { mLightActive |= lightId; CGX::SetChanCtrl(CGX::Channel0, true, GX_SRC_REG, GX_SRC_REG, static_cast< GXLightID >((lightsWereOn | lightId) & (GX_MAX_LIGHT - 1)), GX_DF_CLAMP, GX_AF_SPOT); ++mNumLightsActive; } mLightsWereOn = mLightActive; } static inline GXLightID get_hw_light_index(ERglLight light) { #if NONMATCHING // one instruction, no branches return static_cast< GXLightID >((light << 1) & (GX_MAX_LIGHT - 1)); #else if (light == kLight0) { return GX_LIGHT0; } else if (light == kLight1) { return GX_LIGHT1; } else if (light == kLight2) { return GX_LIGHT2; } else if (light == kLight3) { return GX_LIGHT3; } else if (light == kLight4) { return GX_LIGHT4; } else if (light == kLight5) { return GX_LIGHT5; } else if (light == kLight6) { return GX_LIGHT6; } // wtf? return static_cast< GXLightID >(light == kLight7 ? GX_LIGHT7 : 0); #endif } void CGraphics::LoadLight(ERglLight light, const CLight& info) { GXLightID lightId = get_hw_light_index(light); ELightType type = info.GetType(); CVector3f pos = info.GetPosition(); CVector3f dir = info.GetDirection(); switch (type) { case kLT_Spot: { MTXMultVec(mCameraMtx, reinterpret_cast< VecPtr >(&pos), reinterpret_cast< VecPtr >(&pos)); GXLightObj* obj = &mLightObj[light]; GXInitLightPos(obj, pos.GetX(), pos.GetY(), pos.GetZ()); MTXMultVecSR(mCameraMtx, reinterpret_cast< VecPtr >(&dir), reinterpret_cast< VecPtr >(&dir)); GXInitLightDir(obj, dir.GetX(), dir.GetY(), dir.GetZ()); GXInitLightAttn(obj, 1.f, 0.f, 0.f, info.GetAttenuationConstant(), info.GetAttenuationLinear(), info.GetAttenuationQuadratic()); GXInitLightSpot(obj, info.GetSpotCutoff(), GX_SP_COS2); break; } case kLT_Point: case kLT_LocalAmbient: { MTXMultVec(mCameraMtx, reinterpret_cast< VecPtr >(&pos), reinterpret_cast< VecPtr >(&pos)); GXInitLightPos(&mLightObj[light], pos.GetX(), pos.GetY(), pos.GetZ()); GXInitLightAttn(&mLightObj[light], 1.f, 0.f, 0.f, info.GetAttenuationConstant(), info.GetAttenuationLinear(), info.GetAttenuationQuadratic()); break; } case kLT_Directional: { MTXMultVecSR(mCameraMtx, reinterpret_cast< VecPtr >(&dir), reinterpret_cast< VecPtr >(&dir)); dir = -dir; GXInitLightPos(&mLightObj[light], dir.GetX() * 1048576.f, dir.GetY() * 1048576.f, dir.GetZ() * 1048576.f); GXInitLightAttn(&mLightObj[light], 1.f, 0.f, 0.f, 1.f, 0.f, 0.f); break; } case kLT_Custom: { MTXMultVec(mCameraMtx, reinterpret_cast< VecPtr >(&pos), reinterpret_cast< VecPtr >(&pos)); GXLightObj* obj = &mLightObj[light]; GXInitLightPos(obj, pos.GetX(), pos.GetY(), pos.GetZ()); MTXMultVecSR(mCameraMtx, reinterpret_cast< VecPtr >(&dir), reinterpret_cast< VecPtr >(&dir)); GXInitLightDir(obj, dir.GetX(), dir.GetY(), dir.GetZ()); GXInitLightAttn(obj, info.GetAngleAttenuationConstant(), info.GetAngleAttenuationLinear(), info.GetAngleAttenuationQuadratic(), info.GetAttenuationConstant(), info.GetAttenuationLinear(), info.GetAttenuationQuadratic()); break; } default: break; } GXInitLightColor(&mLightObj[light], info.GetColor().GetGXColor()); GXLoadLightObjImm(&mLightObj[light], lightId); mLightTypes[light] = info.GetType(); } void CGraphics::DisableAllLights() { mNumLightsActive = 0; mLightActive = 0; CGX::SetChanCtrl(CGX::Channel0, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); } // https://en.wikipedia.org/wiki/Hamming_weight static inline uint popcount8(uint b) { b = (b & 0x55) + ((b & 0xAA) >> 1); b = (b & 0x33) + ((b & 0xCC) >> 2); return (static_cast< uchar >(b) & 0xF) + (static_cast< uchar >(b) >> 4); } void CGraphics::SetLightState(uchar lights) { GXAttnFn attnFn = GX_AF_NONE; if (lights != 0) { attnFn = GX_AF_SPOT; } GXDiffuseFn diffFn = GX_DF_NONE; if (lights != 0) { diffFn = GX_DF_CLAMP; } CGX::SetChanCtrl(CGX::Channel0, lights != 0 ? GX_ENABLE : GX_DISABLE, GX_SRC_REG, (vtxDescr.streamFlags & 2) != 0 ? GX_SRC_VTX : GX_SRC_REG, static_cast< GXLightID >(lights), diffFn, attnFn); mLightActive = lights; mNumLightsActive = popcount8(lights); } void CGraphics::SetViewMatrix() { Mtx mtx; MTXTrans(mtx, -mViewPoint.GetX(), -mViewPoint.GetY(), -mViewPoint.GetZ()); MTXConcat(mGXViewPointMatrix, mtx, mCameraMtx); if (mIsGXModelMatrixIdentity) { MTXCopy(mCameraMtx, mGxModelView); } else { MTXConcat(mCameraMtx, mGXModelMatrix, mGxModelView); } GXLoadPosMtxImm(mGxModelView, GX_PNMTX0); Mtx nrmMtx; MTXInvXpose(mGxModelView, nrmMtx); GXLoadNrmMtxImm(nrmMtx, GX_PNMTX0); } void CGraphics::SetViewPointMatrix(const CTransform4f& xf) { mViewMatrix = xf; mGXViewPointMatrix[0][0] = xf.Get00(); mGXViewPointMatrix[0][1] = xf.Get10(); mGXViewPointMatrix[0][2] = xf.Get20(); mGXViewPointMatrix[0][3] = 0.f; mGXViewPointMatrix[1][0] = xf.Get02(); mGXViewPointMatrix[1][1] = xf.Get12(); mGXViewPointMatrix[1][2] = xf.Get22(); mGXViewPointMatrix[1][3] = 0.f; mGXViewPointMatrix[2][0] = -xf.Get01(); mGXViewPointMatrix[2][1] = -xf.Get11(); mGXViewPointMatrix[2][2] = -xf.Get21(); mGXViewPointMatrix[2][3] = 0.f; mViewPoint = xf.GetTranslation(); SetViewMatrix(); } void CGraphics::SetIdentityViewPointMatrix() { mViewMatrix = CTransform4f::Identity(); MTXIdentity(mGXViewPointMatrix); mGXViewPointMatrix[2][2] = 0.f; mGXViewPointMatrix[1][1] = 0.f; mGXViewPointMatrix[1][2] = 1.f; mGXViewPointMatrix[2][1] = -1.f; mViewPoint = CVector3f::Zero(); SetViewMatrix(); } void CGraphics::SetModelMatrix(const CTransform4f& xf) { if (&xf == &CTransform4f::Identity()) { if (!mIsGXModelMatrixIdentity) { mModelMatrix = xf; mIsGXModelMatrixIdentity = true; SetViewMatrix(); } return; } mModelMatrix = xf; mIsGXModelMatrixIdentity = false; mGXModelMatrix[0][0] = xf.Get00(); mGXModelMatrix[0][1] = xf.Get01(); mGXModelMatrix[0][2] = xf.Get02(); mGXModelMatrix[0][3] = xf.Get03(); mGXModelMatrix[1][0] = xf.Get10(); mGXModelMatrix[1][1] = xf.Get11(); mGXModelMatrix[1][2] = xf.Get12(); mGXModelMatrix[1][3] = xf.Get13(); mGXModelMatrix[2][0] = xf.Get20(); mGXModelMatrix[2][1] = xf.Get21(); mGXModelMatrix[2][2] = xf.Get22(); mGXModelMatrix[2][3] = xf.Get23(); SetViewMatrix(); } void CGraphics::SetIdentityModelMatrix() { if (!mIsGXModelMatrixIdentity) { mModelMatrix = CTransform4f::Identity(); mIsGXModelMatrixIdentity = true; SetViewMatrix(); } } void CGraphics::SetOrtho(float left, float right, float top, float bottom, float znear, float zfar) { mProj = CProjectionState(false, left, right, top, bottom, znear, zfar); FlushProjection(); } // TODO non-matching (regswaps) void CGraphics::SetPerspective(float fovy, float aspect, float znear, float zfar) { float t = tan(CRelAngle::FromDegrees(fovy).AsRadians() / 2.f); float top = znear * 2.f * t * 0.5f; float right = aspect * 2.f * znear * t * 0.5f; mProj = CProjectionState(true, -right, right, top, -top, znear, zfar); FlushProjection(); } CMatrix4f CGraphics::GetPerspectiveProjectionMatrix() { #if NONMATCHING // construct in place return CMatrix4f( #else CMatrix4f mtx( #endif // clang-format off (mProj.GetNear() * 2.f) / (mProj.GetRight() - mProj.GetLeft()), -(mProj.GetRight() + mProj.GetLeft()) / (mProj.GetRight() - mProj.GetLeft()), 0.f, 0.f, 0.f, -(mProj.GetTop() + mProj.GetBottom()) / (mProj.GetTop() - mProj.GetBottom()), (mProj.GetNear() * 2.f) / (mProj.GetTop() - mProj.GetBottom()), 0.f, 0.f, (mProj.GetFar() + mProj.GetNear()) / (mProj.GetFar() - mProj.GetNear()), 0.f, -(mProj.GetFar() * 2.f * mProj.GetNear()) / (mProj.GetFar() - mProj.GetNear()), 0.f, 1.f, 0.f, 0.f // clang-format on ); #if !NONMATCHING return mtx; #endif } CMatrix4f CGraphics::CalculatePerspectiveMatrix(float fovy, float aspect, float znear, float zfar) { float t = tan(CRelAngle::FromDegrees(fovy).AsRadians() / 2.f); float right = aspect * 2.f * znear * t * 0.5f; float left = -right; float top = znear * 2.f * t * 0.5f; float bottom = -top; #if NONMATCHING // construct in place return CMatrix4f( #else CMatrix4f mtx( #endif // clang-format off (2.f * znear) / (right - left), -(right + left) / (right - left), 0.f, 0.f, 0.f, -(top + bottom) / (top - bottom), (2.f * znear) / (top - bottom), 0.f, 0.f, (zfar + znear) / (zfar - znear), 0.f, -(2.f * zfar * znear) / (zfar - znear), 0.f, 1.f, 0.f, 0.f // clang-format on ); #if !NONMATCHING return mtx; #endif } void CGraphics::SetViewport(int left, int bottom, int width, int height) { mViewport.mLeft = left; mViewport.mTop = mRenderModeObj.efbHeight - (bottom + height); mViewport.mWidth = width; mViewport.mHeight = height; mViewport.mHalfWidth = static_cast< float >(width / 2); mViewport.mHalfHeight = static_cast< float >(height / 2); GXSetViewport(static_cast< float >(mViewport.mLeft), static_cast< float >(mViewport.mTop), static_cast< float >(mViewport.mWidth), static_cast< float >(mViewport.mHeight), mDepthNear, mDepthFar); } void CGraphics::SetScissor(int left, int bottom, int width, int height) { GXSetScissor(left, mRenderModeObj.efbHeight - (bottom + height), width, height); } void CGraphics::SetAmbientColor(const CColor& color) { CGX::SetChanAmbColor(CGX::Channel0, color.GetGXColor()); CGX::SetChanAmbColor(CGX::Channel1, color.GetGXColor()); } void CGraphics::SetCopyClear(const CColor& color, float depth) { mClearColor = color; mClearDepthValue = static_cast< u32 >(depth * GX_MAX_Z24); GXSetCopyClear(color.GetGXColor(), mClearDepthValue); } void CGraphics::SetClearColor(const CColor& color) { mClearColor = color; GXSetCopyClear(color.GetGXColor(), mClearDepthValue); } void CGraphics::ClearBackAndDepthBuffers() { GXInvalidateTexAll(); if (mRenderModeObj.field_rendering) { GXSetViewportJitter(0.f, 0.f, static_cast< float >(mRenderModeObj.fbWidth), static_cast< float >(mRenderModeObj.xfbHeight), 0.f, 1.f, VIGetNextField()); } else { GXSetViewport(0.f, 0.f, static_cast< float >(mRenderModeObj.fbWidth), static_cast< float >(mRenderModeObj.xfbHeight), 0.f, 1.f); } GXInvalidateVtxCache(); } void CGraphics::BeginScene() { ClearBackAndDepthBuffers(); } void CGraphics::SwapBuffers() { GXDisableBreakPt(); mFlippingState = 1; } void CGraphics::VideoPreCallback(u32 retraceCount) { if (mNumBreakpointsWaiting != 0 && mFlippingState == 1) { if (mFirstFrame) { VISetBlack(GX_FALSE); mFirstFrame = false; } VISetNextFrameBuffer(mpCurrenFrameBuf); VIFlush(); void* frameBuf = mpCurrenFrameBuf == mpFrameBuf1 ? mpFrameBuf2 : mpFrameBuf1; mFlippingState = 2; mpCurrenFrameBuf = frameBuf; } } void CGraphics::VideoPostCallback(u32 retraceCount) { if (mNumBreakpointsWaiting != 0) { if (mFlippingState == 2) { --mNumBreakpointsWaiting; mFlippingState = 0; CStopwatch& timer = sFPSTimer; float elapsed = timer.GetElapsedTime(); mLastFramesPerSecond = mFramesPerSecond; mFramesPerSecond = 1.f / elapsed; timer.Reset(); mInterruptLastFrameUsedAbove = VIGetNextField() == 1; } } } void CGraphics::EndScene() { CGX::SetZMode(true, GX_LEQUAL, true); volatile int& numBreakPt = const_cast< volatile int& >(mNumBreakpointsWaiting); while (numBreakPt > 0) { OSYieldThread(); } ++mNumBreakpointsWaiting; void*& frameBuf = mpCurrenFrameBuf; float brightness = CMath::Clamp(0.f, mBrightness, 2.f); static const u8 copyFilter[7] = {0x00, 0x00, 0x15, 0x16, 0x15, 0x00, 0x00}; const u8* inFilter = mUseVideoFilter ? mRenderModeObj.vfilter : copyFilter; u8 vfilter[7]; for (int i = 0; i < 7; i++) { vfilter[i] = static_cast< u8 >(static_cast< float >(inFilter[i]) * brightness); } GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, true, vfilter); GXCopyDisp(frameBuf, mIsBeginSceneClearFb ? GX_TRUE : GX_FALSE); GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, mUseVideoFilter ? GX_ENABLE : GX_DISABLE, mRenderModeObj.vfilter); GXSetBreakPtCallback(SwapBuffers); VISetPreRetraceCallback(VideoPreCallback); VISetPostRetraceCallback(VideoPostCallback); GXFlush(); GXFifoObj* fifo = GXGetGPFifo(); void* readPtr; void* writePtr; GXGetFifoPtrs(fifo, &readPtr, &writePtr); GXEnableBreakPt(writePtr); mLastFrameUsedAbove = mInterruptLastFrameUsedAbove; ++mFrameCounter; CFrameDelayedKiller::sub_8036cb90(); } void CGraphics::SetDepthWriteMode(bool test, ERglEnum comp, bool write) { mDepthFunc = comp; CGX::SetZMode(static_cast< uchar >(test), static_cast< GXCompare >(comp), static_cast< uchar >(write)); } void CGraphics::SetCullMode(ERglCullMode cullMode) { mCullMode = cullMode; GXSetCullMode(static_cast< GXCullMode >(cullMode)); } void CGraphics::SetBlendMode(ERglBlendMode mode, ERglBlendFactor src, ERglBlendFactor dst, ERglLogicOp op) { CGX::SetBlendMode(static_cast< GXBlendMode >(mode), static_cast< GXBlendFactor >(src), static_cast< GXBlendFactor >(dst), static_cast< GXLogicOp >(op)); } void CGraphics::SetAlphaCompare(ERglAlphaFunc comp0, uchar ref0, ERglAlphaOp op, ERglAlphaFunc comp1, uchar ref1) { CGX::SetAlphaCompare(static_cast< GXCompare >(comp0), static_cast< uchar >(ref0), static_cast< GXAlphaOp >(op), static_cast< GXCompare >(comp1), static_cast< uchar >(ref1)); } static const GXVtxDescList skPosColorTexDirect[] = { {GX_VA_POS, GX_DIRECT}, {GX_VA_CLR0, GX_DIRECT}, {GX_VA_TEX0, GX_DIRECT}, {GX_VA_NULL, GX_DIRECT}, }; // TODO non-matching (regswaps) void CGraphics::Render2D(const CTexture& tex, int x, int y, int w, int h, const CColor& col) { Mtx44 proj; MTXOrtho(proj, mViewport.mHeight / 2, -mViewport.mHeight / 2, -mViewport.mWidth / 2, mViewport.mWidth / 2, -1.f, -10.f); GXSetProjection(proj, GX_ORTHOGRAPHIC); uint c = col.GetColor_u32(); Mtx mtx; MTXIdentity(mtx); GXLoadPosMtxImm(mtx, GX_PNMTX0); float x1 = x - mViewport.mWidth / 2; float y1 = y - mViewport.mHeight / 2; float x2 = x1 + w; float y2 = y1 + h; // Save state + setup CGX::SetVtxDescv(skPosColorTexDirect); SetTevStates(6); mLightsWereOn = mLightActive; if (mLightActive != 0) { DisableAllLights(); } ERglCullMode cullMode = mCullMode; SetCullMode(kCM_None); tex.Load(GX_TEXMAP0, CTexture::kCM_Repeat); // Draw CGX::Begin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4); GXPosition3f32(x1, y1, 1.f); GXColor1u32(c); GXTexCoord2f32(0.f, 0.f); GXPosition3f32(x2, y1, 1.f); GXColor1u32(c); GXTexCoord2f32(1.f, 0.f); GXPosition3f32(x1, y2, 1.f); GXColor1u32(c); GXTexCoord2f32(0.f, 1.f); GXPosition3f32(x2, y2, 1.f); GXColor1u32(c); GXTexCoord2f32(1.f, 1.f); CGX::End(); // Restore state if (mLightsWereOn != 0) { SetLightState(mLightsWereOn); } FlushProjection(); mIsGXModelMatrixIdentity = false; SetModelMatrix(mModelMatrix); SetCullMode(cullMode); } void CGraphics::DrawPrimitive(ERglPrimitive primitive, const float* pos, const CVector3f& normal, const CColor& col, int numVerts) { StreamBegin(primitive); StreamNormal(reinterpret_cast< const float* >(&normal)); StreamColor(col); for (int i = 0; i < numVerts; ++i) { StreamVertex(pos + i * 3); } StreamEnd(); } #define STREAM_PRIM_BUFFER_SIZE 240 #define VTX_BUFFER_ADDR LC_BASE #if NONMATCHING // Bug fix: these should be 3 times larger to avoid overflow // Likely the result of bad pointer arithmetic #define NRM_BUFFER_ADDR (VTX_BUFFER_ADDR + ((STREAM_PRIM_BUFFER_SIZE + 1) * sizeof(Vec))) #define TXT0_BUFFER_ADDR (NRM_BUFFER_ADDR + ((STREAM_PRIM_BUFFER_SIZE + 1) * sizeof(Vec))) #else #define NRM_BUFFER_ADDR (VTX_BUFFER_ADDR + ((STREAM_PRIM_BUFFER_SIZE + 1) * sizeof(float))) #define TXT0_BUFFER_ADDR (NRM_BUFFER_ADDR + ((STREAM_PRIM_BUFFER_SIZE + 1) * sizeof(float))) #endif #define TXT1_BUFFER_ADDR (TXT0_BUFFER_ADDR + ((STREAM_PRIM_BUFFER_SIZE + 1) * sizeof(Vec2))) #define CLR_BUFFER_ADDR (TXT1_BUFFER_ADDR + ((STREAM_PRIM_BUFFER_SIZE + 1) * sizeof(Vec2))) static const uchar kHasNormals = 1; static const uchar kHasColor = 2; static const uchar kHasTexture = 4; void CGraphics::StreamBegin(ERglPrimitive primitive) { vtxBuffer = reinterpret_cast< VecPtr >(VTX_BUFFER_ADDR); nrmBuffer = reinterpret_cast< VecPtr >(NRM_BUFFER_ADDR); txtBuffer0 = reinterpret_cast< Vec2Ptr >(TXT0_BUFFER_ADDR); txtBuffer1 = reinterpret_cast< Vec2Ptr >(TXT1_BUFFER_ADDR); clrBuffer = reinterpret_cast< uint* >(CLR_BUFFER_ADDR); ResetVertexDataStream(true); mCurrentPrimitive = primitive; vtxDescr.streamFlags = kHasColor; } void CGraphics::StreamVertex(float x, float y, float z) { vtxDescr.vtx.x = x; vtxDescr.vtx.y = y; vtxDescr.vtx.z = z; UpdateVertexDataStream(); } void CGraphics::StreamVertex(const float* vtx) { vtxDescr.vtx.x = vtx[0]; vtxDescr.vtx.y = vtx[1]; vtxDescr.vtx.z = vtx[2]; UpdateVertexDataStream(); } void CGraphics::StreamVertex(const CVector3f& vtx) { vtxDescr.vtx.x = vtx.GetX(); vtxDescr.vtx.y = vtx.GetY(); vtxDescr.vtx.z = vtx.GetZ(); UpdateVertexDataStream(); } void CGraphics::StreamNormal(const float* nrm) { vtxDescr.nrm.x = nrm[0]; vtxDescr.nrm.y = nrm[1]; vtxDescr.nrm.z = nrm[2]; vtxDescr.streamFlags |= kHasNormals; } void CGraphics::StreamColor(uint color) { vtxDescr.color = color; vtxDescr.streamFlags |= kHasColor; } void CGraphics::StreamColor(const CColor& color) { vtxDescr.color = color.GetColor_u32(); vtxDescr.streamFlags |= kHasColor; } void CGraphics::StreamColor(float r, float g, float b, float a) { // clang-format off vtxDescr.color = (static_cast< uchar >(r * 255.f) << 24) | (static_cast< uchar >(g * 255.f) << 16) | (static_cast< uchar >(b * 255.f) << 8) | static_cast< uchar >(a * 255.f); // clang-format on vtxDescr.streamFlags |= kHasColor; } void CGraphics::StreamTexcoord(const CVector2f& uv) { vtxDescr.uv0.x = uv.GetX(); vtxDescr.uv0.y = uv.GetY(); vtxDescr.streamFlags |= kHasTexture; vtxDescr.textureUsed |= 1; } void CGraphics::StreamTexcoord(float u, float v) { vtxDescr.uv0.x = u; vtxDescr.uv0.y = v; vtxDescr.streamFlags |= kHasTexture; vtxDescr.textureUsed |= 1; } void CGraphics::StreamEnd() { if (mNumPrimitives != 0) { FlushStream(); } vtxBuffer = nullptr; vtxDescr.streamFlags = 0; vtxDescr.textureUsed = 0; nrmBuffer = nullptr; txtBuffer0 = nullptr; txtBuffer1 = nullptr; clrBuffer = nullptr; } void CGraphics::SetLineWidth(float w, ERglTexOffset offs) { CGX::SetLineWidth(static_cast< uchar >(w * 6.f), static_cast< GXTexOffset >(offs)); } void CGraphics::UpdateVertexDataStream() { ++mNumPrimitives; mpVtxBuffer->x = vtxDescr.vtx.x; mpVtxBuffer->y = vtxDescr.vtx.y; mpVtxBuffer->z = vtxDescr.vtx.z; ++mpVtxBuffer; if ((vtxDescr.streamFlags & kHasNormals) != 0) { mpNrmBuffer->x = vtxDescr.nrm.x; mpNrmBuffer->y = vtxDescr.nrm.y; mpNrmBuffer->z = vtxDescr.nrm.z; ++mpNrmBuffer; } if ((vtxDescr.streamFlags & kHasTexture) != 0) { mpTxtBuffer0->x = vtxDescr.uv0.x; mpTxtBuffer0->y = vtxDescr.uv0.y; ++mpTxtBuffer0; mpTxtBuffer1->x = vtxDescr.uv1.x; mpTxtBuffer1->y = vtxDescr.uv1.y; ++mpTxtBuffer1; } if ((vtxDescr.streamFlags & kHasColor) != 0) { *mpClrBuffer = vtxDescr.color; ++mpClrBuffer; } mJustReset = 0; if (mNumPrimitives == STREAM_PRIM_BUFFER_SIZE) { FlushStream(); ResetVertexDataStream(false); } } void CGraphics::ResetVertexDataStream(bool initial) { mpVtxBuffer = vtxBuffer; mpNrmBuffer = nrmBuffer; mpTxtBuffer0 = txtBuffer0; mpTxtBuffer1 = txtBuffer1; mpClrBuffer = clrBuffer; mNumPrimitives = 0; if (initial) { return; } switch (mCurrentPrimitive) { case kP_TriangleFan: mpVtxBuffer = vtxBuffer + 1; memcpy(mpVtxBuffer, &vtxDescr.vtx, sizeof(Vec)); ++mpVtxBuffer; if ((vtxDescr.streamFlags & kHasNormals) != 0) { ++mpNrmBuffer; memcpy(mpNrmBuffer, &vtxDescr.nrm, sizeof(Vec)); ++mpNrmBuffer; } if ((vtxDescr.streamFlags & kHasTexture) != 0) { ++mpTxtBuffer0; memcpy(mpTxtBuffer0, &vtxDescr.uv0, sizeof(Vec2)); ++mpTxtBuffer0; ++mpTxtBuffer1; memcpy(mpTxtBuffer1, &vtxDescr.uv1, sizeof(Vec2)); ++mpTxtBuffer1; } if ((vtxDescr.streamFlags & kHasColor) != 0) { ++mpClrBuffer; *mpClrBuffer = vtxDescr.color; ++mpClrBuffer; } mNumPrimitives += 2; break; default: break; } mJustReset = 1; } void CGraphics::FlushStream() { GXVtxDescList vtxDesc[10]; GXVtxDescList* curDesc = vtxDesc; const GXVtxDescList vtxDescPos = {GX_VA_POS, GX_DIRECT}; *curDesc++ = vtxDescPos; if ((vtxDescr.streamFlags & kHasNormals) != 0) { const GXVtxDescList vtxDescNrm = {GX_VA_CLR0, GX_DIRECT}; *curDesc++ = vtxDescNrm; } if ((vtxDescr.streamFlags & kHasColor) != 0) { const GXVtxDescList vtxDescClr0 = {GX_VA_CLR0, GX_DIRECT}; *curDesc++ = vtxDescClr0; } if ((vtxDescr.streamFlags & kHasTexture) != 0) { const GXVtxDescList vtxDescTex0 = {GX_VA_TEX0, GX_DIRECT}; *curDesc++ = vtxDescTex0; } const GXVtxDescList vtxDescNull = {GX_VA_NULL, GX_NONE}; *curDesc = vtxDescNull; CGX::SetVtxDescv(vtxDesc); SetTevStates(vtxDescr.streamFlags); FullRender(); } void CGraphics::SetTevStates(uchar flags) { switch (flags) { case 0: case kHasNormals: case kHasColor: case kHasNormals | kHasColor: CGX::SetNumChans(1); CGX::SetNumTexGens(0); CGX::SetNumTevStages(1); CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); break; case kHasTexture: case kHasNormals | kHasTexture: case kHasColor | kHasTexture: case kHasNormals | kHasColor | kHasTexture: CGX::SetNumChans(1); if ((vtxDescr.textureUsed & 3) != 0) { CGX::SetNumTexGens(2); } else { CGX::SetNumTexGens(1); } CGX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); CGX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0); break; } CGX::SetNumIndStages(0); CGX::SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, false, GX_PTIDENTITY); CGX::SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY, false, GX_PTIDENTITY); uint light = mLightActive; GXAttnFn attnFn = GX_AF_NONE; if (light != 0) { attnFn = GX_AF_SPOT; } GXDiffuseFn diffFn = GX_DF_NONE; if (light != 0) { diffFn = GX_DF_CLAMP; } CGX::SetChanCtrl(CGX::Channel0, light ? GX_ENABLE : GX_DISABLE, GX_SRC_REG, (flags & kHasColor) ? GX_SRC_VTX : GX_SRC_REG, static_cast< GXLightID >(light), diffFn, attnFn); }