Files
prime/src/Kyoto/Graphics/DolphinCGraphics.cpp

1588 lines
49 KiB
C++

#include "Kyoto/Graphics/CGraphics.hpp"
#include "Kyoto/Alloc/CMemory.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 "rstl/math.hpp"
#include "dolphin/vi.h"
#include <string.h>
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, 1.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;
uint CGraphics::mRenderTimings;
float CGraphics::mSecondsMod900;
CTimeProvider* CGraphics::mpExternalTimeProvider;
int CGraphics::mScreenStretch;
int CGraphics::mScreenPositionX;
int CGraphics::mScreenPositionY;
CViewport CGraphics::mViewport = {0, 0, 640, 480, 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;
GXBool CGraphics::mUseVideoFilter = GX_ENABLE;
float CGraphics::mBrightness = 1.f;
const GXTexMapID CGraphics::kSpareBufferTexMapID = GX_TEXMAP7;
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();
}
void CGraphics::SetPerspective(float fovy, float aspect, float znear, float zfar) {
float t = tan(CRelAngle::FromDegrees(fovy).AsRadians() / 2.f);
mProj = CProjectionState(true, // Is Projection
-(aspect * 2.f * znear * t * 0.5f), // Left
(aspect * 2.f * znear * t * 0.5f), // Right
(znear * 2.f * t * 0.5f), // Top
-(znear * 2.f * t * 0.5f), // Bottom
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},
};
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 x2, y2, x1, y1;
x1 = x - mViewport.mWidth / 2;
y1 = y - mViewport.mHeight / 2;
x2 = x1 + w;
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_NRM, 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);
}
void CGraphics::FullRender() {
CGX::Begin(static_cast< GXPrimitive >(mCurrentPrimitive), GX_VTXFMT0, mNumPrimitives);
switch (vtxDescr.streamFlags) {
case 0:
for (int i = 0; i < mNumPrimitives; i++) {
const Vec& vtx = vtxBuffer[i];
GXPosition3f32(vtx.x, vtx.y, vtx.z);
}
break;
case kHasNormals:
for (int i = 0; i < mNumPrimitives; i++) {
const Vec& vtx = vtxBuffer[i];
GXPosition3f32(vtx.x, vtx.y, vtx.z);
const Vec& nrm = nrmBuffer[i];
GXNormal3f32(nrm.x, nrm.y, nrm.z);
}
break;
case kHasColor:
for (int i = 0; i < mNumPrimitives; i++) {
const Vec& vtx = vtxBuffer[i];
GXPosition3f32(vtx.x, vtx.y, vtx.z);
GXColor1u32(clrBuffer[i]);
}
break;
case kHasTexture:
for (int i = 0; i < mNumPrimitives; i++) {
const Vec& vtx = vtxBuffer[i];
GXPosition3f32(vtx.x, vtx.y, vtx.z);
const Vec2& uv = txtBuffer0[i];
GXTexCoord2f32(uv.x, uv.y);
}
break;
case kHasNormals | kHasTexture:
for (int i = 0; i < mNumPrimitives; i++) {
const Vec& vtx = vtxBuffer[i];
GXPosition3f32(vtx.x, vtx.y, vtx.z);
const Vec& nrm = nrmBuffer[i];
GXNormal3f32(nrm.x, nrm.y, nrm.z);
const Vec2& uv = txtBuffer0[i];
GXTexCoord2f32(uv.x, uv.y);
}
break;
case kHasNormals | kHasColor:
for (int i = 0; i < mNumPrimitives; i++) {
const Vec& vtx = vtxBuffer[i];
GXPosition3f32(vtx.x, vtx.y, vtx.z);
const Vec& nrm = nrmBuffer[i];
GXNormal3f32(nrm.x, nrm.y, nrm.z);
GXColor1u32(clrBuffer[i]);
}
break;
case kHasColor | kHasTexture:
for (int i = 0; i < mNumPrimitives; i++) {
const Vec& vtx = vtxBuffer[i];
GXPosition3f32(vtx.x, vtx.y, vtx.z);
GXColor1u32(clrBuffer[i]);
const Vec2& uv = txtBuffer0[i];
GXTexCoord2f32(uv.x, uv.y);
}
break;
case kHasNormals | kHasColor | kHasTexture:
for (int i = 0; i < mNumPrimitives; i++) {
const Vec& vtx = vtxBuffer[i];
GXPosition3f32(vtx.x, vtx.y, vtx.z);
const Vec& nrm = nrmBuffer[i];
GXNormal3f32(nrm.x, nrm.y, nrm.z);
GXColor1u32(clrBuffer[i]);
const Vec2& uv = txtBuffer0[i];
GXTexCoord2f32(uv.x, uv.y);
}
break;
}
CGX::End();
}
void CGraphics::SetDepthRange(float near, float far) {
mDepthNear = near;
mDepthFar = far;
GXSetViewport(static_cast< float >(mViewport.mLeft), static_cast< float >(mViewport.mTop),
static_cast< float >(mViewport.mWidth), static_cast< float >(mViewport.mHeight),
mDepthNear, mDepthFar);
}
static inline GXTevStageID get_texture_unit(ERglTevStage stage) {
#if NONMATCHING
// one instruction, no branches
return static_cast< GXTevStageID >(stage & (GX_MAX_TEVSTAGE - 1));
#else
if (stage == kTS_Stage0) {
return GX_TEVSTAGE0;
} else if (stage == kTS_Stage1) {
return GX_TEVSTAGE1;
} else if (stage == kTS_Stage2) {
return GX_TEVSTAGE2;
} else if (stage == kTS_Stage3) {
return GX_TEVSTAGE3;
} else if (stage == kTS_Stage4) {
return GX_TEVSTAGE4;
} else if (stage == kTS_Stage5) {
return GX_TEVSTAGE5;
} else if (stage == kTS_Stage6) {
return GX_TEVSTAGE6;
} else if (stage == kTS_Stage7) {
return GX_TEVSTAGE7;
} else if (stage == kTS_Stage8) {
return GX_TEVSTAGE8;
} else if (stage == kTS_Stage9) {
return GX_TEVSTAGE9;
} else if (stage == kTS_Stage10) {
return GX_TEVSTAGE10;
} else if (stage == kTS_Stage11) {
return GX_TEVSTAGE11;
} else if (stage == kTS_Stage12) {
return GX_TEVSTAGE12;
} else if (stage == kTS_Stage13) {
return GX_TEVSTAGE13;
} else if (stage == kTS_Stage14) {
return GX_TEVSTAGE14;
}
// wtf?
return static_cast< GXTevStageID >(stage == kTS_Stage15 ? GX_TEVSTAGE15 : 0);
#endif
}
void CGraphics::SetTevOp(ERglTevStage stage, const CTevCombiners::CTevPass& pass) {
CTevCombiners::SetupPass(get_texture_unit(stage), pass);
}
void CGraphics::SetFog(ERglFogMode mode, float startz, float endz, const CColor& color) {
CGX::SetFog(static_cast< GXFogType >(mode), startz, endz, mProj.GetNear(), mProj.GetFar(),
color.GetGXColor());
}
void CGraphics::ResetGfxStates() { sRenderState.Set(0); }
void CGraphics::SetDefaultVtxAttrFmt() {
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_NRM, GX_NRM_XYZ, GX_S16, 14);
GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_NRM, GX_NRM_XYZ, GX_S16, 14);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT2, GX_VA_TEX0, GX_TEX_ST, GX_U16, 15);
for (int i = 1; i <= 7; ++i) {
GXAttr attr = static_cast< GXAttr >(GX_VA_TEX0 + i);
GXSetVtxAttrFmt(GX_VTXFMT0, attr, GX_TEX_ST, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT1, attr, GX_TEX_ST, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT2, attr, GX_TEX_ST, GX_F32, 0);
}
}
void CGraphics::LoadDolphinSpareTexture(int width, int height, GXTexFmt fmt, void* data,
GXTexMapID texId) {
GXTexObj texObj;
GXInitTexObj(&texObj, data != nullptr ? data : mpSpareBuffer, width, height, fmt, GX_CLAMP,
GX_CLAMP, GX_DISABLE);
GXInitTexObjLOD(&texObj, GX_NEAR, GX_NEAR, 0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1);
GXLoadTexObj(&texObj, texId);
CTexture::InvalidateTexmap(texId);
if (texId == GX_TEXMAP7) {
GXInvalidateTexRegion(&mTexRegions[0]);
}
}
void CGraphics::LoadDolphinSpareTexture(int width, int height, GXCITexFmt fmt, GXTlut tlut,
void* data, GXTexMapID texId) {
GXTexObj texObj;
GXInitTexObjCI(&texObj, data != nullptr ? data : mpSpareBuffer, width, height, fmt, GX_CLAMP,
GX_CLAMP, GX_DISABLE, tlut);
GXInitTexObjLOD(&texObj, GX_NEAR, GX_NEAR, 0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1);
GXLoadTexObj(&texObj, texId);
CTexture::InvalidateTexmap(texId);
if (texId == GX_TEXMAP7) {
GXInvalidateTexRegion(&mTexRegions[0]);
}
}
void CGraphics::TickRenderTimings() {
mRenderTimings = (mRenderTimings + 1) % (900 * 60);
mSecondsMod900 = static_cast< float >(mRenderTimings) / 60.f;
}
float CGraphics::GetSecondsMod900() {
if (mpExternalTimeProvider != nullptr) {
return mpExternalTimeProvider->GetSecondsMod900();
}
return mSecondsMod900;
}
void CGraphics::SetExternalTimeProvider(CTimeProvider* timeProvider) {
mpExternalTimeProvider = timeProvider;
}
void CGraphics::FlushProjection() {
float right = mProj.GetRight();
float left = mProj.GetLeft();
float top = mProj.GetTop();
float bottom = mProj.GetBottom();
float near = mProj.GetNear();
float far = mProj.GetFar();
if (mProj.IsPerspective()) {
Mtx44 mtx;
MTXFrustum(mtx, top, bottom, left, right, near, far);
GXSetProjection(mtx, GX_PERSPECTIVE);
} else {
Mtx44 mtx;
MTXOrtho(mtx, top, bottom, left, right, near, far);
GXSetProjection(mtx, GX_ORTHOGRAPHIC);
}
}
const CGraphics::CProjectionState& CGraphics::GetProjectionState() { return mProj; }
void CGraphics::SetProjectionState(const CProjectionState& proj) {
mProj = proj;
FlushProjection();
}
// TODO non-matching (regswaps)
CGraphics::CClippedScreenRect
CGraphics::ClipScreenRectFromVS(const CVector3f& p1, const CVector3f& p2, ETexelFormat fmt) {
if (!p1.IsNonZero() || !p2.IsNonZero()) {
return CClippedScreenRect();
}
if (p1.GetY() < GetProjectionState().GetNear() || p2.GetY() < GetProjectionState().GetNear()) {
return CClippedScreenRect();
}
if (p1.GetY() > GetProjectionState().GetFar() || p2.GetY() > GetProjectionState().GetFar()) {
return CClippedScreenRect();
}
CVector2i p1p = ProjectPoint(p1);
CVector2i p2p = ProjectPoint(p2);
int minX = rstl::min_val(p1p.GetX(), p2p.GetX());
int minY = rstl::min_val(p1p.GetY(), p2p.GetY());
int maxX = abs(p1p.GetX() - p2p.GetX());
int maxY = abs(p1p.GetY() - p2p.GetY());
int left = minX & 0xfffffffe;
if (left >= mViewport.mLeft + mViewport.mWidth) {
return CClippedScreenRect();
}
int right = minX + maxX + 2 & 0xfffffffe;
if (right <= mViewport.mLeft) {
return CClippedScreenRect();
}
left = rstl::max_val(left, mViewport.mLeft) & 0xfffffffe;
right = rstl::min_val(right, mViewport.mLeft + mViewport.mWidth) + 1 & 0xfffffffe;
int top = minY & 0xfffffffe;
if (top >= mViewport.mTop + mViewport.mHeight) {
return CClippedScreenRect();
}
int bottom = minY + maxY + 2 & 0xfffffffe;
if (bottom <= mViewport.mTop) {
return CClippedScreenRect();
}
top = rstl::max_val(top, mViewport.mTop) & 0xfffffffe;
bottom = rstl::min_val(bottom, mViewport.mTop + mViewport.mHeight) + 1 & 0xfffffffe;
// int height = bottom - top;
float minV = static_cast< float >(minY - top) / static_cast< float >(bottom - top);
float maxV = static_cast< float >(maxY + (minY - top) + 1) / static_cast< float >(bottom - top);
int texAlign = 4;
switch (fmt) {
case kTF_I8:
texAlign = 8;
break;
case kTF_IA8:
case kTF_RGB565:
case kTF_RGB5A3:
texAlign = 4;
break;
case kTF_RGBA8:
texAlign = 2;
break;
}
// int width = right - left;
int texWidth = texAlign + (right - left) - 1 & ~(texAlign - 1);
float minU = static_cast< float >(minX - left) / static_cast< float >(texWidth);
float maxU = static_cast< float >(maxX + (minX - left) + 1) / static_cast< float >(texWidth);
return CClippedScreenRect(left, top, right - left, bottom - top, texWidth, minU, maxU, minV,
maxV);
}
CGraphics::CClippedScreenRect
CGraphics::ClipScreenRectFromMS(const CVector3f& p1, const CVector3f& p2, ETexelFormat fmt) {
return ClipScreenRectFromVS(mViewMatrix.TransposeMultiply(mModelMatrix * p1),
mViewMatrix.TransposeMultiply(mModelMatrix * p2), fmt);
}
float CGraphics::GetFPS() {
BOOL level = OSDisableInterrupts();
float value = rstl::min_val(mFramesPerSecond, mLastFramesPerSecond);
OSRestoreInterrupts(level);
return value;
}
void CGraphics::SetUseVideoFilter(bool b) {
mUseVideoFilter = b;
GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, b ? GX_ENABLE : GX_DISABLE,
mRenderModeObj.vfilter);
}
GXBool CGraphics::GetUseVideoFilter() { return mUseVideoFilter; }
int CGraphics::GetFrameCounter() { return mFrameCounter; }
CVector2i CGraphics::ProjectPoint(const CVector3f& point) {
CVector3f vec = GetPerspectiveProjectionMatrix().MultiplyOneOverW(point);
vec.SetX(vec.GetX() * GetViewport().mHalfWidth + GetViewport().mHalfWidth);
vec.SetY(-vec.GetY() * GetViewport().mHalfHeight + GetViewport().mHalfHeight);
return CVector2i(CCast::FtoL(vec.GetX()), CCast::FtoL(vec.GetY()));
}
void CGraphics::SetProgressiveMode(bool b) {
bool isProgressive = GetProgressiveMode();
OSSetProgressiveMode(b ? OS_PROGRESSIVE_MODE_ON : OS_PROGRESSIVE_MODE_OFF);
if (b == isProgressive) {
return;
}
VISetBlack(TRUE);
VIFlush();
VIWaitForRetrace();
for (int i = 0; i < 10; ++i) {
VIWaitForRetrace();
}
if (b) {
mRenderModeObj.viTVmode = VI_TVMODE_NTSC_PROG;
mRenderModeObj.xFBmode = VI_XFBMODE_SF;
const u8 vfilter[7] = {0x04, 0x04, 0x10, 0x10, 0x10, 0x04, 0x04};
memcpy(mRenderModeObj.vfilter, vfilter, 7);
} else {
mRenderModeObj.viTVmode = VI_TVMODE_NTSC_INT;
mRenderModeObj.xFBmode = VI_XFBMODE_DF;
memcpy(mRenderModeObj.vfilter, GXNtsc480IntDf.vfilter, 7);
}
GXSetCopyFilter(mRenderModeObj.aa, mRenderModeObj.sample_pattern, GX_ENABLE,
mRenderModeObj.vfilter);
VIConfigure(&mRenderModeObj);
VISetBlack(TRUE);
VIFlush();
for (int i = 0; i < 100; ++i) {
VIWaitForRetrace();
}
VISetBlack(FALSE);
VIFlush();
for (int i = 0; i < 2; ++i) {
VIWaitForRetrace();
}
}
bool CGraphics::GetProgressiveMode() { return mRenderModeObj.viTVmode == VI_TVMODE_NTSC_PROG; }
bool CGraphics::CanSetProgressiveMode() { return VIGetDTVStatus() != 0; }
bool CGraphics::GetProgressiveDefault() { return OSGetProgressiveMode() == OS_PROGRESSIVE_MODE_ON; }
void CGraphics::GetScreenPosition(int* stretch, int* xOffset, int* yOffset) {
if (stretch != nullptr) {
*stretch = mScreenStretch;
}
if (xOffset != nullptr) {
*xOffset = mScreenPositionX;
}
if (yOffset != nullptr) {
*yOffset = mScreenPositionY;
}
}
void CGraphics::SetScreenPosition(int stretch, int xOffset, int yOffset) {
int stretchChange = stretch - mScreenStretch;
int xChange = xOffset - mScreenPositionX;
int yChange = yOffset - mScreenPositionY;
if (stretchChange == 0 && xChange == 0 && yChange == 0) {
return;
}
mRenderModeObj.viWidth += stretchChange * 2;
mRenderModeObj.viXOrigin += xChange - stretchChange;
mRenderModeObj.viYOrigin += yChange;
VIConfigure(&mRenderModeObj);
VIFlush();
mScreenStretch = stretch;
mScreenPositionX = xOffset;
mScreenPositionY = yOffset;
}
void CGraphics::SetIsBeginSceneClearFb(bool b) { mIsBeginSceneClearFb = b; }
CGraphicsSys::CGraphicsSys(const COsContext& osContext, const CMemorySys& memorySys, uint fifoSize,
void* fifoBase) {
if (mGraphicsInitialized != true) {
mGraphicsInitialized = CGraphics::Startup(osContext, fifoSize, fifoBase);
}
}
CGraphicsSys::~CGraphicsSys() {
if (mGraphicsInitialized == true) {
CGraphics::Shutdown();
mGraphicsInitialized = false;
}
}
CGraphics::CRenderState::CRenderState() {
x0_ = 0;
x4_ = 0;
}
void CGraphics::CRenderState::Flush() {}
int CGraphics::CRenderState::SetVtxState(const float* pos, const float* nrm, const uint* clr) {
CGX::SetArray(GX_VA_POS, pos, 12);
CGX::SetArray(GX_VA_NRM, nrm, 12);
CGX::SetArray(GX_VA_CLR0, clr, 4);
int result = 1;
if (nrm != nullptr) {
result |= 2;
}
if (clr != nullptr) {
result |= 16;
}
return result;
}
void CGraphics::CRenderState::ResetFlushAll() {
x0_ = 0;
SetVtxState(nullptr, nullptr, nullptr);
for (int i = 0; i < 8; i++) {
CGX::SetArray(static_cast< GXAttr >(GX_VA_TEX0 + i), nullptr, 8);
}
Flush();
}
// static float stripped(int n) { return static_cast<float>(n); }