Files
GTASource/game/scene/texLod.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

1928 lines
55 KiB
C++

//
// Filename: texLod.cpp
// Description: handles streaming & swapping of top mip out of texture dicts
// Written by: JohnW
//
// 5/10/2010 - JohnW: - initial;
//
//
//
#include "scene/texLod.h"
#if __XENON
#include "grcore/texturexenon.h"
#include "system/xtl.h"
#define DBG 0
#include <xgraphics.h>
#undef DBG
#elif __PS3
#include "grcore/gputimer.h"
#include "grcore/im.h"
#include "grcore/texturegcm.h"
#include "grcore/wrapper_gcm.h"
#include "vector/colors.h"
#elif __WIN32PC
#include "grcore/texturepc.h"
#endif
#if RSG_DURANGO || RSG_ORBIS
#include "grcore/orbisdurangoresourcebase.h"
#endif // RSG_DURANGO || RSG_ORBIS
// Rage:
#if __BANK
#include "bank/bkmgr.h"
#include "bank/bank.h"
#include "bank/button.h"
#include "bank/slider.h"
#include "bank/text.h"
#include "grcore/debugdraw.h"
#include "grcore/setup.h"
#endif //__BANK
#include "grcore/stateblock.h"
#include "grcore/texture.h"
#include "system/virtualallocator.h"
//fw
#include "fwscene/search/Search.h"
#include "fwscene/stores/fragmentstore.h"
#include "fwscene/stores/drawablestore.h"
// Game headers:
#include "camera/CamInterface.h"
#include "camera/viewports/ViewportManager.h"
#include "control/replay/Replay.h"
#include "modelinfo/PedModelInfo.h"
#include "peds/Ped.h"
#include "peds/PlayerPed.h"
#include "peds/rendering/pedvariationdebug.h"
#include "renderer/Renderer.h"
#include "scene/Entity.h"
#include "scene/lod/LodScale.h"
#include "scene/world/GameWorld.h"
#include "scene/scene_channel.h"
#include "streamer/SceneStreamerMgr.h"
#include "streaming/streaming.h"
#include "streaming/streamingvisualize.h"
#include "world/GameWorld.h"
// debug
#include "debug/UiGadget/UiGadgetWindow.h"
#include "debug/UiGadget/UiGadgetList.h"
#include "debug/UiGadget/UiGadgetText.h"
#include "debug/TextureViewer/TextureViewer.h"
#include "debug/TextureViewer/TextureViewerSearch.h" // for CTxdRef, GetAssociatedTxds_ModelInfo
#include "debug/GtaPicker.h"
#include "vehicles/vehicle.h"
#include "vehicles/trailer.h"
#include "Network/NetworkInterface.h"
#include "scene/FocusEntity.h"
#if __BANK
#include "modelinfo/VehicleModelInfo.h"
#include "String.h"
fwTxd* CTexLod::m_pHighTxd = NULL;
fwTxd* CTexLod::m_pHigh2Txd = NULL;
fwTxd* CTexLod::m_pLowTxd = NULL;
grcTexture* CTexLod::m_TexTab[4]={NULL};
u32 CTexLod::m_totalMemoryUsed = 0;
bool CTexLod::m_showHDTextureMemUsage = false;
bool CTexLod::m_showHDMemUsage = false;
bool CTexLod::m_onlyExternalRequsts = false;
bool bListHDTxds = false;
bool bColourize = false;
bool g_renderHdArea;
CUiGadgetWindow* CTexLod::ms_pDebugWindow = NULL;
CUiGadgetList* CTexLod::ms_pHDTxdList = NULL;
CUiGadgetList* CTexLod::ms_pTexList = NULL;
fwAssetLocation CTexLod::ms_currentAsset(STORE_ASSET_INVALID, 0);
strLocalIndex CTexLod::ms_debugTxdIdx = strLocalIndex(-1);
strLocalIndex CTexLod::ms_debugTriggerMI = strLocalIndex(fwModelId::MI_INVALID);
u32 assetHashSlotArray[64];
s32 targetTxdListIndex = -1;
// HD tex picker stuff
CEntity* CTexLod::ms_pSelectedEntity = NULL;
float CTexLod::ms_selectedEntityDist = 0.0f;
bool CTexLod::ms_bIsActivated = false;
bool CTexLod::ms_bDisableHDTex = false;
float CTexLod::ms_HDActivationDistance = 1.0f;
#endif //__BANK
atMap<s32, CTxdPairBinding*> CTexLod::ms_boundTxds;
atMap<fwAssetLocation, s32> CTexLod::ms_assetToHDMappings;
atFixedArray<CExternalRequest, MAX_EXTERNAL_HD_TXD_REQUESTS> CTexLod::ms_externalRequests;
#if NG_HD_SPLIT
atArray<CTexLod::sVirtMemData> CTexLod::ms_virtMemToFree;
#endif // NG_HD_SPLIT
bool CTexLod::ms_bEnabled = true;
bool CTexLod::ms_bEnableSwapping = true;
bool CTexLod::ms_bAmbientRequestsDisabled = false;
bool CTexLod::ms_bHasHdArea = false;
spdSphere CTexLod::ms_hdArea;
u32 CTexLod::ms_nextHdAreaUpdate = 0;
bool CTexLod::ms_allowAmbientRequests = true;
// HDAssetManager stuff ---
CHDAssetManager g_HDAssetMgr;
void CHDAssetManager::Init(void)
{
m_cutsceneRequests.Reset();
m_enableDefaultHDRequests = true;
#if __BANK
m_displayHDRequests = false;
#endif //__BANK
}
void CHDAssetManager::Shutdown(void)
{
m_cutsceneRequests.Reset();
}
#if __BANK
void CHDAssetManager::DebugUpdate(void)
{
if (!m_displayHDRequests){
return;
}
grcDebugDraw::AddDebugOutput("");
grcDebugDraw::AddDebugOutput("Current cutscene HD requests");
grcDebugDraw::AddDebugOutput("--------------------------------");
u8 modelType = 0;
for(u32 i=0; i<3; i++)
{
switch(i){
case 0:
modelType = MI_TYPE_PED;
grcDebugDraw::AddDebugOutput(" Peds:");
break;
case 1:
modelType = MI_TYPE_VEHICLE;
grcDebugDraw::AddDebugOutput(" Vehicles:");
break;
case 2:
modelType = MI_TYPE_WEAPON;
grcDebugDraw::AddDebugOutput(" Weapons:");
break;
}
for(u32 j=0; j<m_cutsceneRequests.GetCount(); j++)
{
CBaseModelInfo* pBMI = m_cutsceneRequests[j];
if (pBMI->GetModelType() == modelType)
{
switch(modelType)
{
case MI_TYPE_VEHICLE:
{
CVehicleModelInfo* pVMI = static_cast<CVehicleModelInfo*>(pBMI);
grcDebugDraw::AddDebugOutput(" (%c) : %s",pVMI->GetAreHDFilesLoaded() ? '*' : ' ', pBMI->GetModelName());
break;
}
case MI_TYPE_PED:
{
CPedModelInfo* pPMI = static_cast<CPedModelInfo*>(pBMI);
grcDebugDraw::AddDebugOutput(" (%c) : %s",pPMI->GetAreHDFilesLoaded() ? '*' : ' ', pBMI->GetModelName());
break;
}
case MI_TYPE_WEAPON:
{
CWeaponModelInfo* pWMI = static_cast<CWeaponModelInfo*>(pBMI);
grcDebugDraw::AddDebugOutput(" (%c) : %s",pWMI->GetAreHDFilesLoaded() ? '*' : ' ', pBMI->GetModelName());
break;
}
}
}
}
}
grcDebugDraw::AddDebugOutput("---");
}
#endif //__BANK
void CHDAssetManager::AddCutsceneRequestForHD(atHashString nameHash)
{
if (!m_enableDefaultHDRequests)
{
return;
}
fwArchetype* pArch = fwArchetypeManager::GetArchetypeFromHashKey(nameHash, NULL);
if (pArch)
{
CBaseModelInfo* pBMI = static_cast<CBaseModelInfo*>(pArch);
if (pBMI->GetModelType() == MI_TYPE_WEAPON || pBMI->GetModelType() == MI_TYPE_VEHICLE || pBMI->GetModelType() == MI_TYPE_PED)
{
if (m_cutsceneRequests.Find(pBMI) == -1)
{
m_cutsceneRequests.PushAndGrow(pBMI);
switch(pBMI->GetModelType())
{
case MI_TYPE_VEHICLE:
{
CVehicleModelInfo* pVMI = static_cast<CVehicleModelInfo*>(pBMI);
pVMI->AddToHDInstanceList((size_t)&g_HDAssetMgr);
break;
}
case MI_TYPE_PED:
{
break;
}
case MI_TYPE_WEAPON:
{
CWeaponModelInfo* pWMI = static_cast<CWeaponModelInfo*>(pBMI);
pWMI->ConfirmHDFiles();
pWMI->AddToHDInstanceList((size_t)&g_HDAssetMgr);
break;
}
}
}
}
}
}
void CHDAssetManager::RemoveCutsceneRequestForHD(atHashString UNUSED_PARAM(nameHash))
{
if (!m_enableDefaultHDRequests)
{
return;
}
// fwArchetype* pArch = fwArchetypeManager::GetArchetypeFromHashKey(nameHash, NULL);
// if (pArch)
// {
// CBaseModelInfo* pBMI = static_cast<CBaseModelInfo*>(pArch);
// u8 modelType = pBMI->GetModelType();
//
// switch(modelType)
// {
// case MI_TYPE_VEHICLE:
// {
// break;
// }
// case MI_TYPE_PED:
// {
// break;
// }
// case MI_TYPE_WEAPON:
// {
// break;
// }
// }
// if (pBMI->GetModelType() == MI_TYPE_WEAPON || pBMI->GetModelType() == MI_TYPE_VEHICLE || pBMI->GetModelType() == MI_TYPE_PED)
// {
// m_cutsceneRequests.DeleteMatches(pBMI);
// }
// }
}
bool CHDAssetManager::AreCutsceneRequestsLoaded(void)
{
Assert(false);
// u32 numReq = m_cutsceneRequests.GetCount();
//
// for(u32 i=0; i<numReq; i++)
// {
// CEntity* pEntity = m_cutsceneRequests[i];
// if (pEntity && !pEntity->GetIsCurrentlyHD())
// {
// return(false);
// }
// }
return(true);
}
void CHDAssetManager::FlushCutsceneRequestsHD(void)
{
for(u32 i = 0; i<m_cutsceneRequests.GetCount(); i++)
{
CBaseModelInfo* pBMI = m_cutsceneRequests[i];
u8 modelType = pBMI->GetModelType();
switch(modelType)
{
case MI_TYPE_VEHICLE:
{
CVehicleModelInfo* pVMI = static_cast<CVehicleModelInfo*>(pBMI);
pVMI->RemoveFromHDInstanceList((size_t)&g_HDAssetMgr);
break;
}
case MI_TYPE_WEAPON:
{
CWeaponModelInfo* pVMI = static_cast<CWeaponModelInfo*>(pBMI);
pVMI->RemoveFromHDInstanceList((size_t)&g_HDAssetMgr);
break;
}
}
}
m_cutsceneRequests.Reset();
}
// --- Class CMipSwitcher --- store data when switching one texture to another so that switching can be undone later.
void CMipSwitcher::SetSwapState(bool bSwap NG_HD_SPLIT_ONLY(, strLocalIndex hdTxdIdx, fwAssetLocation targetAsset)){
// skip if already in the desired state
if (bSwap == m_bIsSwapped){
return;
}
#if __PS3
CellGcmTexture* pTexLow = m_pLow->GetTexturePtr();
CellGcmTexture* pTexHigh = m_pHigh->GetTexturePtr();
if (pTexLow && pTexHigh){
if (bSwap){
m_mipMap = pTexLow->mipmap;
m_offset = pTexLow->offset;
m_width = pTexLow->width;
m_height = pTexLow->height;
pTexLow->mipmap = pTexHigh->mipmap;
pTexLow->offset = pTexHigh->offset;
pTexLow->width = pTexHigh->width;
pTexLow->height = pTexHigh->height;
} else {
pTexLow->mipmap = m_mipMap;
pTexLow->offset = m_offset;
pTexLow->width = m_width;
pTexLow->height = m_height;
}
m_bIsSwapped = !m_bIsSwapped;
#if USE_PACKED_GCMTEX
m_pLow->UpdatePackedTexture();
m_pHigh->UpdatePackedTexture();
#endif
}
#elif __XENON
grcTextureObject* pTexLow = m_pLow->GetTexturePtr();
grcTextureObject* pTexHigh = m_pHigh->GetTexturePtr();
if (pTexLow && pTexHigh){
if (bSwap){
// back up texture data
m_mipMap = pTexLow->Format.MaxMipLevel << 4 | pTexLow->Format.MinMipLevel;
m_offset = pTexLow->Format.BaseAddress << 9 | pTexLow->Format.Pitch;
m_mipOffset = pTexLow->Format.MipAddress;
m_width = pTexLow->Format.Size.TwoD.Width;
m_height = pTexLow->Format.Size.TwoD.Height;
if (pTexHigh->Format.MaxMipLevel == 0) {
// Set Tex1 to use high res info
pTexLow->Format.MaxMipLevel += 1;
pTexLow->Format.MipAddress = pTexLow->Format.BaseAddress;
pTexLow->Format.BaseAddress = pTexHigh->Format.BaseAddress;
} else {
// OLD CODEPATH FOR OLD DATA
pTexLow->Format.MaxMipLevel = pTexHigh->Format.MaxMipLevel;
pTexLow->Format.MinMipLevel = pTexHigh->Format.MinMipLevel;
pTexLow->Format.BaseAddress = pTexHigh->Format.BaseAddress;
pTexLow->Format.MipAddress = pTexHigh->Format.MipAddress;
}
pTexLow->Format.Size.TwoD.Width = pTexHigh->Format.Size.TwoD.Width;
pTexLow->Format.Size.TwoD.Height = pTexHigh->Format.Size.TwoD.Height;
pTexLow->Format.Pitch = pTexHigh->Format.Pitch;
m_pLow->GetGcmTexture().width = static_cast<uint16_t>(pTexHigh->Format.Size.TwoD.Width + 1);
m_pLow->GetGcmTexture().height = static_cast<uint16_t>(pTexHigh->Format.Size.TwoD.Height + 1);
} else {
// Restore tex1
pTexLow->Format.MaxMipLevel = m_mipMap >> 4;
pTexLow->Format.MinMipLevel = m_mipMap & 0xF;
pTexLow->Format.BaseAddress = m_offset >> 9;
pTexLow->Format.MipAddress = m_mipOffset;
pTexLow->Format.Size.TwoD.Width = m_width;
pTexLow->Format.Size.TwoD.Height = m_height;
pTexLow->Format.Pitch = m_offset & 0x1FF;
m_pLow->GetGcmTexture().width = static_cast<uint16_t>(m_width + 1);
m_pLow->GetGcmTexture().height = static_cast<uint16_t>(m_height + 1);
}
Assert(pTexLow->Format.BaseAddress != pTexLow->Format.MipAddress);
m_bIsSwapped = !m_bIsSwapped;
}
#elif __WIN32PC
grcTexturePC* pTexLow = (grcTexturePC*) m_pLow.ptr;
grcTexturePC* pTexHigh = (grcTexturePC*) m_pHigh.ptr;
if (pTexLow->GetTexturePtr() && pTexHigh->GetTexturePtr()){
pTexLow->HDOverrideSwap(pTexHigh);
m_bIsSwapped = !m_bIsSwapped;
}
#elif (RSG_DURANGO || RSG_ORBIS) && NG_HD_SPLIT
grcOrbisDurangoTextureBase *pTexLow = (grcOrbisDurangoTextureBase *)m_pLow.ptr;
grcOrbisDurangoTextureBase *pTexHigh = (grcOrbisDurangoTextureBase *)m_pHigh.ptr;
if (pTexLow && pTexHigh) {
void* pDeferredFreePtr = NULL;
size_t deferredFreeSize = 0;
m_pOldHdAddr = grcOrbisDurangoTextureBase::PeformHDOverrideSwap(pTexLow, pTexHigh, m_pOldHdAddr, pDeferredFreePtr, deferredFreeSize);
m_bIsSwapped = !m_bIsSwapped;
CTexLod::AddDeferredFreePtr(pDeferredFreePtr, deferredFreeSize, hdTxdIdx, targetAsset);
}
#endif //#elif __XENON #elif __WIN32PC #elif (RSG_DURANGO || RSG_ORBIS)
}
void CMipSwitcher::Set(grcTexture* pLow, grcTexture* pHigh NG_HD_SPLIT_ONLY(, strLocalIndex hdTxdIdx, fwAssetLocation targetAsset)){
Assert(pLow && pHigh);
if (pLow && pHigh)
{
m_pLow = pLow;
m_pHigh = pHigh;
#if NG_HD_SPLIT
m_pOldHdAddr = NULL;
#endif
SetSwapState(true NG_HD_SPLIT_ONLY(, hdTxdIdx, targetAsset));
}
}
void CMipSwitcher::Clear(NG_HD_SPLIT_ONLY(strLocalIndex hdTxdIdx, fwAssetLocation targetAsset)){
Assert( !m_bIsSwapped || ( m_pLow && m_pHigh ) );
if (m_pLow && m_pHigh)
{
SetSwapState(false NG_HD_SPLIT_ONLY(, hdTxdIdx, targetAsset));
m_pLow = NULL;
m_pHigh = NULL;
}
}
// --- class CTxdPairBinding --- contains all info for switching textures in one txd with textures specified in another so switch can be undone.
// enforce the swap state of all the textures for this binding
void CTxdPairBinding::SetSwapStateAll(bool bSwap)
{
u32 numTex =m_boundTexSwitchers.GetCount();
while(numTex-- > 0){
if (m_boundTexSwitchers[numTex].IsValid()){
m_boundTexSwitchers[numTex].SetSwapState(bSwap NG_HD_SPLIT_ONLY(, m_highTxdIdx, m_targetAsset));
}
}
}
#if __DEV
static int g_MaxTargetTxds = 0;
#endif // __DEV
void CTxdPairBinding::SetHighTxdIdx(s32 highTxdIdx)
{
if (g_TxdStore.GetIsBoundHD(strLocalIndex(highTxdIdx)))
{
Assertf(false,"txd slot=%d (%s) already bound - cannot be bound again", highTxdIdx, g_TxdStore.GetName(strLocalIndex(highTxdIdx)));
return;
}
m_highTxdIdx = highTxdIdx;
}
// switch all the textures in the target asset with the textures with the same name in high Txd
void CTxdPairBinding::ActivateSwap(fwAssetLocation target){
if (m_highTxdIdx == -1)
{
sceneAssert(false);
return;
}
#if __BANK
char buffer[255];
const char* name = strStreamingEngine::GetInfo().GetObjectPath(target.GetStreamingIndex(), buffer, sizeof(buffer));
DIAG_CONTEXT_MESSAGE(name);
#endif
sceneAssert(g_TxdStore.HasObjectLoaded(m_highTxdIdx));
fwTxd* pHighTxd = g_TxdStore.Get(m_highTxdIdx);
sceneAssert(pHighTxd);
s32 numHighTex = pHighTxd->GetCount();
if (!Verifyf(numHighTex > 0, "%d entries in HD txd '%s'!", numHighTex, g_TxdStore.GetName(m_highTxdIdx)))
return;
if (!Verifyf(target.IsValid(), "target is invalid!"))
return;
if (!target.HasLoaded())
{
Assertf(false, "target asset type=%s slot=%d (%s) not loaded - cannot be bound", target.GetStoreTypeStr(), target.GetStoreSlot().Get(), target.GetName());
return;
}
if (target.GetIsBoundHD())
{
Assertf(false,"target asset type=%s slot=%d (%s) already bound - cannot be bound again", target.GetStoreTypeStr(), target.GetStoreSlot().Get(), target.GetName());
return;
}
g_TxdStore.AddRef(m_highTxdIdx, REF_RENDER);
g_TxdStore.SetIsBoundHD(strLocalIndex(m_highTxdIdx), true);
#if !__FINAL
g_TxdStore.Validate(m_highTxdIdx);
#endif // #if !__FINAL
// get the target Txd(s) from the target asset
fwPtrListSingleLink targetTxdList;
target.GetTxdList(targetTxdList);
#if __DEV
const int numUsed = targetTxdList.CountElements();
if (g_MaxTargetTxds < numUsed) {
g_MaxTargetTxds = numUsed;
Displayf("g_MaxTargetTxds = %d", g_MaxTargetTxds);
}
#endif // __DEV
strStreamingModule *pModule = target.GetStreamingModule();
if (pModule)
{
pModule->AddRef(target.GetStoreSlot(), REF_RENDER);
target.SetIsBoundHD(true);
#if !__FINAL
target.Validate();
#endif // !__FINAL
}
// replace textures of the same name in the target txds with the textures in the high txd
Assertf(!m_targetAsset.IsValid(), "target asset type=%s slot=%d (%s) is being overridden with asset type=%s slot=%d (%s)", m_targetAsset.GetStoreTypeStr(), m_targetAsset.GetStoreSlot().Get(), m_targetAsset.GetName(), target.GetStoreTypeStr(), target.GetStoreSlot().Get(), target.GetName());
m_targetAsset = target;
s32 numTargetTxds = targetTxdList.CountElements();
m_boundTexSwitchers.Reserve(numHighTex * numTargetTxds);
m_boundTexSwitchers.Resize(numHighTex * numTargetTxds);
u32 currentTarget = 0;
fwPtrNodeSingleLink* pLowTxdEntry = targetTxdList.GetHeadPtr();
while (pLowTxdEntry != NULL)
{
fwTxd* pLowTxd = (fwTxd*)pLowTxdEntry->GetPtr();
Assert(pLowTxd);
for (s32 i = 0; i < numHighTex; ++i)
{
u32 highTexHash = pHighTxd->GetCode(i);
grcTexture* pLowTex = pLowTxd->LookupLocal(highTexHash);
if (pLowTex){
grcTexture* pHighTex = pHighTxd->LookupLocal(highTexHash);
Assert(pHighTex);
m_boundTexSwitchers[i + currentTarget * numHighTex].Set(pLowTex, pHighTex NG_HD_SPLIT_ONLY(, m_highTxdIdx, m_targetAsset));
}
}
pLowTxdEntry = (fwPtrNodeSingleLink*)pLowTxdEntry->GetNextPtr();
currentTarget++;
}
targetTxdList.Flush();
ResetCooldownTimer();
}
// clear the binding between asset and high Txd & dec ref counts accordingly
CTxdPairBinding::~CTxdPairBinding(){
u32 numTex =m_boundTexSwitchers.GetCount();
while(numTex-- > 0){
m_boundTexSwitchers[numTex].Clear(NG_HD_SPLIT_ONLY(m_highTxdIdx, m_targetAsset));
}
if (m_highTxdIdx != -1)
{
g_TxdStore.ClearRequiredFlag(m_highTxdIdx.Get(), STRFLAG_DONTDELETE);
if (g_TxdStore.HasObjectLoaded(m_highTxdIdx))
{
#if !__FINAL
g_TxdStore.Validate(m_highTxdIdx);
#endif // !__FINAL
if (g_TxdStore.GetIsBoundHD(strLocalIndex(m_highTxdIdx)))
{
gDrawListMgr->AddRefCountedModuleIndex(m_highTxdIdx.Get(), &g_TxdStore);
}
g_TxdStore.SetIsBoundHD(strLocalIndex(m_highTxdIdx), false);
}
}
if (m_targetAsset.IsValid()){
strStreamingModule *pModule = m_targetAsset.GetStreamingModule();
strLocalIndex objIdx = m_targetAsset.GetLocalIndex();
gDrawListMgr->AddRefCountedModuleIndex(objIdx.Get(), pModule);
m_targetAsset.SetIsBoundHD(false);
m_targetAsset.Validate();
}
}
void CTxdPairBinding::Update(void) {
u32 curStep = fwTimer::GetTimeStepInMilliseconds();
//if we are doing less than 30 fps then
//cool down as though we were doing 30 fps
if (curStep > 33)
{
m_cooldownTimer -= 33;
}
else
{
m_cooldownTimer -= curStep;
}
}
u32 CTxdPairBinding::GetTextureMemUsage()
{
u32 totalMemory = 0;
for (int i = 0; i < m_boundTexSwitchers.size(); ++i)
{
CMipSwitcher *pMipSwitchter = &m_boundTexSwitchers[i];
if (pMipSwitchter->GetHighTex())
{
totalMemory += pMipSwitchter->GetHighTex()->GetPhysicalSize();
}
}
return totalMemory;
}
// CTexLodInterface
void CTexLodInterface::SetSwapStateSingle(bool bSwap, fwAssetLocation targetAsset)
{
CTexLod::SetSwapStateSingle(bSwap, targetAsset);
}
// --- class CTexLod : manages mip texture swapping for the game
//
//
//
//
grcTexture* CTexLod::LoadTexture(fwTxd* pTxd, char *texname)
{
grcTexture* pTexture = NULL;
if (pTxd){
pTexture = pTxd->Lookup(texname);
Assertf(pTexture, "Cannot find CTexLod debug texture!");
pTexture->AddRef();
}
return(pTexture);
}
//
//
//
//
inline float GetClampedScaleValue()
{
return(MAX(1.0f, g_LodScale.GetGlobalScale()));
}
void CTexLod::Init()
{
ms_boundTxds.Reset();
ms_assetToHDMappings.Reset();
ms_externalRequests.Reset();
g_HDAssetMgr.Init();
fwTexLod::InitClass(rage_new CTexLodInterface());
}
void CTexLod::InitSession(unsigned UNUSED_PARAM(initMode))
{
ms_bHasHdArea = false;
ms_nextHdAreaUpdate = 0;
}
//
void CTexLod::ShutdownSession(u32 /*shutdownMode*/)
{
FlushAllUpgrades();
#if NG_HD_SPLIT
for (s32 i = 0; i < ms_virtMemToFree.GetCount(); ++i)
ms_virtMemToFree[i].Release();
ms_virtMemToFree.Reset();
#endif // NG_HD_SPLIT
if (CStreaming::GetReloadPackfilesOnRestart())
{
ms_assetToHDMappings.Kill();
}
}
//
//
//
void CTexLod::Shutdown()
{
ms_assetToHDMappings.Kill();
g_HDAssetMgr.Shutdown();
ms_boundTxds.Reset();
ms_externalRequests.Reset();
fwTexLod::ShutdownClass();
}
bool CTexLod::AreFromSameArchive(fwAssetLocation targetAsset, s32* pTxdSlotHigh)
{
if (pTxdSlotHigh)
{
int targetArchive = targetAsset.GetStreamingInfo()->GetHandle();
int highTxdArchive = g_TxdStore.GetStreamingInfo(strLocalIndex(*pTxdSlotHigh))->GetHandle();
if (fiCollection::GetCollectionIndex(targetArchive) == fiCollection::GetCollectionIndex(highTxdArchive))
{
return(true);
}
#if !__FINAL
Warningf("%s and %s may not texLod bind across archives!", targetAsset.GetName(), g_TxdStore.GetName(*pTxdSlotHigh));
const strStreamingFile* targetStreamingFile = strPackfileManager::GetImageFileFromHandle(targetArchive);
const strStreamingFile* highTxdStreamingFile = strPackfileManager::GetImageFileFromHandle(highTxdArchive);
const char* pTargetArchiveName = targetStreamingFile ? (targetStreamingFile->m_name.GetCStr()) : "";
const char* pHighTxdArchiveName = highTxdStreamingFile ? (highTxdStreamingFile->m_name.GetCStr()) : "";
Warningf("Attempt to bind between assets in [%s] and [%s]", pTargetArchiveName, pHighTxdArchiveName);
Assertf(false,"Binding across archive detected"); // check warnings!
#endif //!__FINAL
}
return(false);
}
/// handle upgrading to HD txds through a modelinfo (start issuing streaming requests & storing refs to streamed txds)
//void CTexLod::TriggerHDTxdUpgrade(u32 triggeringMI){
void CTexLod::TriggerHDTxdUpgrade(fwAssetLocation targetAsset, u32 triggeringMI, bool onlyIfLoaded)
{
Assert(triggeringMI != fwModelId::MI_INVALID);
#if __DEV
fwAssetLocation debugAsset = targetAsset;
#endif
// iterate over all txds in chain for this modelinfo
while(targetAsset.IsValid()){
#if __DEV
if(!targetAsset.PreValidate())
{
Assertf(false, "PreValidate failed, we may crash later on. url:bugstar:2730194");
CBaseModelInfo* pMI = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(triggeringMI)));
Displayf("Prevalidate Failed (%s:%s), printing asset tree:", pMI->GetModelName() ? pMI->GetModelName() : "", targetAsset.GetName());
//Something has gone wrong...printing the asset tree
while(debugAsset.IsValid()){
Displayf("%s",debugAsset.GetName());
debugAsset = debugAsset.GetParentAsset();
}
}
#endif
//targeted fix for url:bugstar:2730194, don't know the reason for the asset
//being streamed out but this should stop the crash
if (!targetAsset.PreValidateTexLod() && (CReplayMgr::IsExporting() || CReplayMgr::IsScrubbing()))
{
targetAsset = targetAsset.GetParentAsset();
continue;
}
//Displayf("target:%s",targetAsset.GetName());
s32 *pTxdSlotHigh = ms_assetToHDMappings.Access(targetAsset);
// the target and the high txd must be from the same archive!
bool bFromSameArchive = AreFromSameArchive(targetAsset, pTxdSlotHigh);
if ((pTxdSlotHigh!=NULL) && bFromSameArchive)
{
strLocalIndex txdSlotHigh = strLocalIndex(*pTxdSlotHigh);
//don't load new hd textures if told not to,
//only let already resident ones tick over
if(onlyIfLoaded && !g_TxdStore.HasObjectLoaded(txdSlotHigh))
{
targetAsset = targetAsset.GetParentAsset();
continue;
}
// get the current binding for this txd, or create a new one (and add to our map)
CTxdPairBinding** ppBoundTxd = ms_boundTxds.Access(targetAsset);
CTxdPairBinding* pBoundTxd = NULL;
if (!ppBoundTxd){
pBoundTxd = rage_new(CTxdPairBinding);
Assert(pBoundTxd);
ms_boundTxds.Insert(targetAsset, pBoundTxd);
} else {
pBoundTxd = *ppBoundTxd;
}
Assert(pBoundTxd);
pBoundTxd->ResetCooldownTimer(); // make sure this stays alive now
pBoundTxd->SetTriggeringModelIndex(triggeringMI); // useful info - keep track of which type is keeping this entry active
if (!pBoundTxd->IsAlreadyBound(targetAsset)){
CStreaming::RequestObject(txdSlotHigh, g_TxdStore.GetStreamingModuleId(), STRFLAG_DONTDELETE);
pBoundTxd->SetHighTxdIdx(txdSlotHigh.Get());
if (g_TxdStore.HasObjectLoaded(txdSlotHigh)){
CStreaming::ClearRequiredFlag(txdSlotHigh, g_TxdStore.GetStreamingModuleId(), STRFLAG_DONTDELETE);
pBoundTxd->ActivateSwap(targetAsset);
}
}
}
targetAsset = targetAsset.GetParentAsset();
}
}
// 20 kph
#define HD_UPGRADE_SPEED_LIMIT ((25.0f *( 1000.0f)) / (60.0f * 60.0f))
// search the world looking for models which can trigger an HD txd upgrade, keep all nearby HD txds alive, any which have timed out then clean up.
void CTexLod::Update(){
UpdateInternal();
#if GTA_REPLAY && REPLAY_FORCE_LOAD_SCENE_DURING_EXPORT
if (CReplayMgr::IsExporting())
{
CStreaming::LoadAllRequestedObjects();
UpdateInternal();
}
#endif //GTA_REPLAY
}
void CTexLod::UpdateInternal(){
STRVIS_AUTO_CONTEXT(strStreamingVisualize::TEXLOD);
#if __BANK
DebugUpdate();
#endif // __BANK
if (!ms_bEnableSwapping)
{
return;
}
fwPtrListSingleLink entityList;
Vector3 velocity = CFocusEntityMgr::GetMgr().GetVel();
ms_allowAmbientRequests = true;
if (ms_bEnabled)
{
const float capSpeedSquard = (HD_UPGRADE_SPEED_LIMIT * HD_UPGRADE_SPEED_LIMIT);
// Block all but the external the HD tex requests if the current scene
// isn't in a good state to be taking on additional requests
CStreamingBucketSet &neededBucketSet = g_SceneStreamerMgr.GetStreamingLists().GetNeededEntityList().GetBucketSet();
BucketEntry::BucketEntryArray &neededList = neededBucketSet.GetBucketEntries();
neededBucketSet.WaitForSorting();
int neededListCount = neededList.GetCount();
bool streamerNotReady = (neededListCount > 0 && neededList[neededListCount-1].m_Score >= STREAMBUCKET_VISIBLE_FAR);
if ( velocity.Mag2() > capSpeedSquard
|| streamerNotReady
|| ms_bAmbientRequestsDisabled
)
{
ms_allowAmbientRequests = false;
}
//script must enable this every frame
ms_bAmbientRequestsDisabled = false;
BANK_ONLY(if (!m_onlyExternalRequsts))
{
// use renderlists for HD upgrade - not a world search
int renderListIdx = g_SceneToGBufferPhaseNew->GetEntityListIndex();
fwRenderListDesc& GBufRenderListDesc = gRenderListGroup.GetRenderListForPhase(renderListIdx);
u32 size = GBufRenderListDesc.GetNumberOfEntities(RPASS_VISIBLE);
for(u32 i=0; i<size; i++)
{
// CEntity* pDebugEntity = static_cast<CEntity*>(GBufRenderListDesc.GetEntity(i, RPASS_VISIBLE));
// if (strcmpi(pDebugEntity->GetBaseModelInfo()->GetModelName(), "v_46_mainshell")==0)
// {
// Displayf("shell");
// }
u16 customFlags = GBufRenderListDesc.GetEntityCustomFlags(i, RPASS_VISIBLE);
if (customFlags & fwEntityList::CUSTOM_FLAGS_HD_TEX_CAPABLE)
{
float minDistance = 0.0f;
float distance = GBufRenderListDesc.GetEntitySortVal(i, RPASS_VISIBLE);
u32 baseFlags = GBufRenderListDesc.GetEntityBaseFlags(i, RPASS_VISIBLE);
CEntity* pEntity = static_cast<CEntity*>(GBufRenderListDesc.GetEntity(i, RPASS_VISIBLE));
Debug_RegisterEntityDistance(pEntity, distance);
if (baseFlags & fwEntity::HAS_HD_TEX_DIST)
{
Assertf(pEntity && pEntity->GetIsTypePed(), "Only Peds should have fwEntity::HAS_HD_TEX_DIST flag set");
minDistance = static_cast<CPed*>(pEntity)->GetPedModelInfo()->GetHDDist();
}
else
{
minDistance = pEntity->GetBaseModelInfo()->GetHDTextureDistance();
}
bool requireHighModel = distance < minDistance * GetClampedScaleValue();
#if __BANK
if (pEntity && pEntity->GetIsTypePed())
{
if (CPedVariationDebug::GetForceLdAssets())
requireHighModel = false;
if (CPedVariationDebug::GetForceHdAssets())
requireHighModel = true;
}
#endif // __BANK
if (requireHighModel)
{
CBaseModelInfo* pBMI = pEntity->GetBaseModelInfo();
Assert(pBMI);
if (pBMI->GetHasLoaded())
{
CTexLod::TriggerHDTxdUpgrade(pBMI->GetAssetLocation(), pEntity->GetModelIndex(), !ms_allowAmbientRequests);
Debug_RegisterEntityActivations(pEntity);
}
}
}
}
size = GBufRenderListDesc.GetNumberOfEntities(RPASS_TREE);
for(u32 i=0; i<size; i++)
{
u16 customFlags = GBufRenderListDesc.GetEntityCustomFlags(i, RPASS_TREE);
if (customFlags & fwEntityList::CUSTOM_FLAGS_HD_TEX_CAPABLE)
{
float distance = GBufRenderListDesc.GetEntitySortVal(i, RPASS_TREE);
CEntity* pEntity = static_cast<CEntity*>(GBufRenderListDesc.GetEntity(i, RPASS_TREE));
Debug_RegisterEntityDistance(pEntity, distance);
if (distance < pEntity->GetBaseModelInfo()->GetHDTextureDistance() * GetClampedScaleValue())
{
CBaseModelInfo* pBMI = pEntity->GetBaseModelInfo();
Assert(pBMI);
if (pBMI->GetHasLoaded())
{
CTexLod::TriggerHDTxdUpgrade(pBMI->GetAssetLocation(), pEntity->GetModelIndex(), !ms_allowAmbientRequests);
Debug_RegisterEntityActivations(pEntity);
}
}
}
}
size = GBufRenderListDesc.GetNumberOfEntities(RPASS_DECAL);
for(u32 i=0; i<size; i++)
{
u16 customFlags = GBufRenderListDesc.GetEntityCustomFlags(i, RPASS_DECAL);
if (customFlags & fwEntityList::CUSTOM_FLAGS_HD_TEX_CAPABLE)
{
float distance = GBufRenderListDesc.GetEntitySortVal(i, RPASS_DECAL);
CEntity* pEntity = static_cast<CEntity*>(GBufRenderListDesc.GetEntity(i, RPASS_DECAL));
Debug_RegisterEntityDistance(pEntity, distance);
if (distance < pEntity->GetBaseModelInfo()->GetHDTextureDistance() * GetClampedScaleValue())
{
CBaseModelInfo* pBMI = pEntity->GetBaseModelInfo();
Assert(pBMI);
if (pBMI->GetHasLoaded())
{
CTexLod::TriggerHDTxdUpgrade(pBMI->GetAssetLocation(), pEntity->GetModelIndex(), !ms_allowAmbientRequests);
Debug_RegisterEntityActivations(pEntity);
}
}
}
}
// process cutscene peds HD .txd upgrades
for(u32 i=0; i<g_HDAssetMgr.GetCutsceneRequests().GetCount(); i++)
{
CBaseModelInfo* pBMI = g_HDAssetMgr.GetCutsceneRequests()[i];
if (pBMI && (pBMI->GetModelType()==MI_TYPE_PED) && pBMI->GetHasLoaded())
{
CTexLod::TriggerHDTxdUpgrade(pBMI->GetAssetLocation(), pBMI->GetStreamSlot().Get(), !ms_allowAmbientRequests);
}
}
// process hd area
u32 curTime = fwTimer::GetTimeInMilliseconds();
if (ms_bHasHdArea && curTime >= ms_nextHdAreaUpdate)
{
fwIsSphereIntersecting sphere(ms_hdArea);
u32 searchType = ENTITY_TYPE_MASK_BUILDING|ENTITY_TYPE_MASK_ANIMATED_BUILDING|ENTITY_TYPE_MASK_OBJECT;
CGameWorld::ForAllEntitiesIntersecting(&sphere, TriggerHdForEntity, NULL, searchType, SEARCH_LOCATION_EXTERIORS);
// the trigger will stay for 60 frames, so we do the world search every 1.5 seconds, since all these entities
// get triggered at once.
// we could even do it on a toggle on set/clear hd area if this is too expensive
ms_nextHdAreaUpdate = curTime + 1500;
}
}
// process external requests for HD .txd upgrading
CPed* playerPed = CGameWorld::FindLocalPlayer();
u32 playerTrailerIdx = (u32)strLocalIndex::INVALID_INDEX;
u32 playerVehicleIdx = (u32)strLocalIndex::INVALID_INDEX;
if(playerPed && playerPed->GetMyVehicle())
{
playerVehicleIdx = playerPed->GetMyVehicle()->GetModelIndex();
if(playerPed->GetMyVehicle()->HasTrailer())
{
const CTrailer* pTrailer = playerPed->GetMyVehicle()->GetAttachedTrailerOrDummyTrailer();
if(pTrailer)
{
playerTrailerIdx = pTrailer->GetModelIndex();
}
}
}
for(u32 i=0; i<ms_externalRequests.GetCount(); i++)
{
CExternalRequest& request = ms_externalRequests[i];
CBaseModelInfo* pMI = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(request.m_modelIndex)));
Assert(pMI);
if (pMI && pMI->GetHasLoaded())
{
if (velocity.Mag2() >= capSpeedSquard)
{
if (playerTrailerIdx != request.m_modelIndex && playerVehicleIdx != request.m_modelIndex)
continue;
}
CTexLod::TriggerHDTxdUpgrade(request.m_assetLoc, request.m_modelIndex);
}
}
ms_externalRequests.Reset();
}
#if __BANK
m_totalMemoryUsed = 0;
#endif
//look for HD entries which have timed out
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
it.Start();
while(!it.AtEnd()){
s32 keyToDelete = it.GetKey();
CTxdPairBinding* pPairBinding = it.GetData();
it.Next();
pPairBinding->Update();
if (pPairBinding->IsTimedOut()){
if (pPairBinding->GetHighTxdIdx() != -1)
{
CStreaming::ClearRequiredFlag(pPairBinding->GetHighTxdIdx(), g_TxdStore.GetStreamingModuleId(), STRFLAG_DONTDELETE);
}
delete(pPairBinding);
ms_boundTxds.Delete(keyToDelete);
}
else
{
#if __BANK
m_totalMemoryUsed += pPairBinding->GetTextureMemUsage();
#endif
}
}
#if NG_HD_SPLIT
for (s32 i = 0; i < ms_virtMemToFree.GetCount(); ++i)
{
if (ms_virtMemToFree[i].ptr != NULL)
{
if (ms_virtMemToFree[i].freeCountdown > 0)
{
ms_virtMemToFree[i].freeCountdown--;
}
else
{
ms_virtMemToFree[i].Release();
}
}
}
#endif // NG_HD_SPLIT
}
bool CTexLod::TriggerHdForEntity(CEntity* pEntity, void* UNUSED_PARAM(pData))
{
CBaseModelInfo* pBMI = pEntity->GetBaseModelInfo();
Assert(pBMI);
if (pBMI->GetHasLoaded())
{
CTexLod::TriggerHDTxdUpgrade(pBMI->GetAssetLocation(), pEntity->GetModelIndex());
Debug_RegisterEntityActivations(pEntity);
}
return true;
}
void CTexLod::FlushAllUpgrades()
{
#if __BANK
m_totalMemoryUsed = 0;
#endif
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
it.Start();
while (!it.AtEnd())
{
s32 keyToDelete = it.GetKey();
CTxdPairBinding* pPairBinding = it.GetData();
it.Next();
delete pPairBinding;
ms_boundTxds.Delete(keyToDelete);
}
}
#if NG_HD_SPLIT
void CTexLod::AddDeferredFreePtr(void* ptr, size_t size, strLocalIndex hdTxdIdx, fwAssetLocation targetAsset)
{
if (!ptr)
return;
sVirtMemData* pNewData = NULL;
for (s32 i = 0; i < ms_virtMemToFree.GetCount(); ++i)
{
if (ms_virtMemToFree[i].ptr == NULL)
{
pNewData = &ms_virtMemToFree[i];
break;
}
}
if (!pNewData)
pNewData = &ms_virtMemToFree.Grow();
pNewData->ptr = ptr;
pNewData->size = size;
pNewData->hdTxdIdx = hdTxdIdx;
pNewData->targetAsset = targetAsset;
pNewData->freeCountdown = 4;
if (targetAsset.IsValid())
targetAsset.GetStreamingModule()->AddRef(targetAsset.GetLocalIndex(), REF_RENDER);
if (hdTxdIdx.IsValid())
g_TxdStore.AddRef(hdTxdIdx, REF_RENDER);
}
#endif // NG_HD_SPLIT
#if NG_HD_SPLIT
void CTexLod::sVirtMemData::Release()
{
if (ptr != NULL)
{
sysMemVirtualAllocator::sm_Instance->UnmapVirtual(ptr, size);
sysMemVirtualAllocator::sm_Instance->FreeVirtual(ptr);
ptr = NULL;
if (hdTxdIdx.IsValid())
{
g_TxdStore.RemoveRef(hdTxdIdx, REF_RENDER);
hdTxdIdx.Invalidate();
}
if (targetAsset.IsValid())
{
strStreamingModule *module = targetAsset.GetStreamingModule();
strLocalIndex objIdx = targetAsset.GetLocalIndex();
module->RemoveRef(objIdx, REF_RENDER);
targetAsset.Invalidate();
}
}
}
#endif
void CTexLod::FlushMemRemaps()
{
#if NG_HD_SPLIT
for (s32 i = 0; i < ms_virtMemToFree.GetCount(); ++i)
ms_virtMemToFree[i].Release();
#endif
}
void CTexLod::SetSwapStateSingle(bool bSwap, fwAssetLocation targetAsset)
{
if (!ms_bEnableSwapping){
return;
}
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
for ( it.Start(); !it.AtEnd(); it.Next() )
{
CTxdPairBinding* pPairBinding = it.GetData();
fwAssetLocation pairHdTxdAsset(STORE_ASSET_TXD,pPairBinding->GetHighTxdIdx().Get());
fwAssetLocation pairTargetAsset = pPairBinding->GetTargetAsset();
fwAssetLocation otherAsset;
if (pairTargetAsset == targetAsset)
{
otherAsset = pairHdTxdAsset;
}
else if (pairHdTxdAsset == targetAsset)
{
otherAsset = pairTargetAsset;
}
else
{
continue;
}
// Ignore request to re-enable a swap if the other asset is still being
// defragged. This can occur in rare circumstances when both assets are
// being defragged at the same time, then the
// strStreamingModule::DefragmentComplete call is made for the first
// asset. In this situation, the swap will be re-enabled when either
// the second asset gets a DefragComplete call, or when the defrag frame
// update completes, which ever comes first.
if (!bSwap || !otherAsset.IsBeingDefragged())
{
Assert(!bSwap || !targetAsset.IsBeingDefragged());
pPairBinding->SetSwapStateAll(bSwap);
}
}
}
void CTexLod::SetSwapStateAll(bool bSwap, bool bDefragOnly){
if (!ms_bEnableSwapping){
return;
}
if (bDefragOnly)
{
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
for ( it.Start(); !it.AtEnd(); it.Next() )
{
CTxdPairBinding* pPairBinding = it.GetData();
strLocalIndex highTxdIdx = strLocalIndex(pPairBinding->GetHighTxdIdx());
fwAssetLocation targetAsset = pPairBinding->GetTargetAsset();
if (highTxdIdx != -1 && targetAsset.IsValid())
{
if (g_TxdStore.IsBeingDefragged(highTxdIdx) || targetAsset.IsBeingDefragged())
{
pPairBinding->SetSwapStateAll(bSwap);
}
}
}
}
else
{
Assertf(false,"Shouldn't be using this code path");
#if __BANK
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
for ( it.Start(); !it.AtEnd(); it.Next() )
{
CTxdPairBinding* pPairBinding = it.GetData();
if (bSwap == false)
{
g_TxdStore.AddRef(strLocalIndex(pPairBinding->GetHighTxdIdx()), REF_OTHER);
fwAssetLocation target = pPairBinding->GetTargetAsset();
strStreamingModule *pModule = target.GetStreamingModule();
Assert(pModule);
if (pModule)
{
pModule->AddRef(target.GetStoreSlot(), REF_OTHER);
}
} else {
g_TxdStore.RemoveRef(strLocalIndex(pPairBinding->GetHighTxdIdx()), REF_OTHER);
fwAssetLocation target = pPairBinding->GetTargetAsset();
strStreamingModule *pModule = target.GetStreamingModule();
Assert(pModule);
if (pModule)
{
pModule->RemoveRef(target.GetStoreSlot(), REF_OTHER);
}
}
pPairBinding->SetSwapStateAll(bSwap);
}
#endif //__BANK
}
}
// cleanup all currently bound txds immediately
void CTexLod::UnbindAllBoundTxds(void){
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
for ( it.Start(); !it.AtEnd(); )
{
s32 keyToDelete = it.GetKey();
CTxdPairBinding* pPairBinding = it.GetData();
it.Next();
delete (pPairBinding);
ms_boundTxds.Delete(keyToDelete);
}
ms_boundTxds.Reset();
}
void CTexLod::StoreHDMapping(eStoreType type, u32 assetSlot, s32 txdHDSlot)
{
fwAssetLocation location(type, assetSlot);
// if (stricmp(location.GetName(),"vehshare")==0)
// {
// Displayf("found");
// }
//
// if (stricmp(location.GetName(),"tailgater")==0)
// {
// Displayf("found");
// }
if (Verifyf(location.IsValid() && location.GetStoreSlot().IsValid(), "Asset Location Invalid") && (Verifyf(g_TxdStore.IsValidSlot(strLocalIndex(txdHDSlot)), "Invalid .txd slot for HD mapping")))
{
if (ms_assetToHDMappings.Access(location) == NULL)
{
ms_assetToHDMappings.Insert(location, txdHDSlot);
location.SetIsHDCapable(true);
Assertf(ms_assetToHDMappings.GetNumUsed() < ((RSG_PC || RSG_DURANGO || RSG_ORBIS) ? 32767 : 19000), "Too many HD mappings in TexLod");
}
}
}
// ---- BANK stuff ----
#if __BANK
// the update cell callback can be as simple as...
void CTexLod::UpdateCellForTxd(CUiGadgetText* pResult, u32 row, u32 col, void * UNUSED_PARAM(extraCallbackData) )
{
if (assetHashSlotArray[targetTxdListIndex] == 0){
ms_debugTriggerMI = fwModelId::MI_INVALID;
return;
}
fwAssetLocation loc = static_cast<fwAssetLocation>(assetHashSlotArray[targetTxdListIndex]);
CTxdPairBinding** ppBoundTxd = ms_boundTxds.Access(loc);
if (col == 0){
if (ppBoundTxd && (*ppBoundTxd) && (*ppBoundTxd)->IsTexIndexInTxdBound(row)){
pResult->SetString("*");
} else {
pResult->SetString(" ");
}
}
else if (col == 1)
{
if (ppBoundTxd && (*ppBoundTxd) && (*ppBoundTxd)->GetHighTxdIdx().Get() > -1)
{
fwTxd* pTargetTxd = g_TxdStore.Get(strLocalIndex((*ppBoundTxd)->GetHighTxdIdx()));
if (pTargetTxd)
{
const grcTexture* pTex = pTargetTxd->GetEntry(row);
if (pTex)
{
pResult->SetString(pTex->GetName());
}
}
}
}
else if (col == 2)
{
if (ppBoundTxd && (*ppBoundTxd) && (*ppBoundTxd)->GetHighTxdIdx().Get() > -1)
{
fwTxd* pTargetTxd = g_TxdStore.Get(strLocalIndex((*ppBoundTxd)->GetHighTxdIdx()));
if (pTargetTxd)
{
const grcTexture* pTex = pTargetTxd->GetEntry(row);
if (pTex)
{
char sizeInK[16];
snprintf(sizeInK, 16, "%dK", int((double)pTex->GetPhysicalSize() / 1024.0));
sizeInK[15] = '\0';
pResult->SetString(sizeInK);
}
}
}
}
else
{
pResult->SetString("");
ms_debugTriggerMI = fwModelId::MI_INVALID;
}
}
// the update cell callback can be as simple as...
void CTexLod::UpdateCellForAsset(CUiGadgetText* pResult, u32 row, u32 col, void * UNUSED_PARAM(extraCallbackData) )
{
if (assetHashSlotArray[row] == 0){
return;
}
fwAssetLocation loc = static_cast<fwAssetLocation>(assetHashSlotArray[row]);
CTxdPairBinding** ppBoundTxd = ms_boundTxds.Access(loc);
if (col == 0){
if (ppBoundTxd && (*ppBoundTxd)){
CBaseModelInfo* pMI = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex((*ppBoundTxd)->GetTriggeringMI())));
Assert(pMI);
pResult->SetString(pMI->GetModelName());
}
} else if (col == 1){
pResult->SetString(loc.GetStoreTypeStr());
} else if (col == 2){
pResult->SetString(loc.GetName());
} else if (col == 3){
if (ppBoundTxd && (*ppBoundTxd) && (*ppBoundTxd)->GetHighTxdIdx().Get() > -1){
pResult->SetString(g_TxdStore.GetName(strLocalIndex((*ppBoundTxd)->GetHighTxdIdx())));
} else {
pResult->SetString("<NULL>");
}
} else if (col == 4){
char coolDownStars[7] = "******";
if (ppBoundTxd && (*ppBoundTxd)){
u32 nullPos = (*ppBoundTxd)->GetTimeoutVal();
coolDownStars[nullPos] = '\0';
}
pResult->SetString(coolDownStars);
} else {
if (ppBoundTxd && (*ppBoundTxd))
{
char sizeInK[16];
snprintf(sizeInK, 16, "%dK", int((double)(*ppBoundTxd)->GetTextureMemUsage() / 1024.0));
sizeInK[15] = '\0';
pResult->SetString(sizeInK);
}
}
}
#define MAX_NAME_LEN (64)
char selectedEntityName[MAX_NAME_LEN];
char selectedEntityDistance[MAX_NAME_LEN];
// update the debug window
void CTexLod::DebugUpdate(void){
g_HDAssetMgr.DebugUpdate();
if (g_PickerManager.IsEnabled() && g_PickerManager.IsCurrentOwner("HD tex distance"))
{
ms_pSelectedEntity = reinterpret_cast<CEntity*>(g_PickerManager.GetSelectedEntity());
if (ms_pSelectedEntity)
{
if (!ms_pSelectedEntity->GetIsHDTxdCapable())
{
ms_pSelectedEntity = NULL;
g_PickerManager.ResetList(true);
g_PickerManager.OnDisplayResultsWindow();
selectedEntityName[0] = '\0';
selectedEntityDistance[0] = '\0';
ms_selectedEntityDist = -1.0f;
ms_bIsActivated = false;
} else
{
ms_HDActivationDistance = ms_pSelectedEntity->GetBaseModelInfo()->GetHDTextureDistance();
float HDActivationDistanceScaled = ms_pSelectedEntity->GetBaseModelInfo()->GetHDTextureDistance() * GetClampedScaleValue();
sprintf(selectedEntityName,"%s",ms_pSelectedEntity->GetBaseModelInfo()->GetModelName());
sprintf(selectedEntityDistance,"%.1f (%s)", ms_selectedEntityDist, ms_bIsActivated ? "In range" : "_");
grcDebugDraw::TextFontPush(grcSetup::GetFixedWidthFont());
char tempString[128];
sprintf(tempString, "Model: %s", selectedEntityName);
grcDebugDraw::Text(Vector2(0.25f, 0.8f), Color32(255,80,80), tempString, true, 3.0f, 3.0f);
sprintf(tempString, "curr dist: %s", selectedEntityDistance);
grcDebugDraw::Text(Vector2(0.25f, 0.84f), Color32(255,80,80), tempString, true, 3.0f, 3.0f);
sprintf(tempString, "HD activate dist: %.1f (%.1f)", HDActivationDistanceScaled, ms_HDActivationDistance);
grcDebugDraw::Text(Vector2(0.25f, 0.88f), Color32(255,80,80), tempString, true, 3.0f, 3.0f);
grcDebugDraw::TextFontPop();
}
}
}
if (m_showHDTextureMemUsage)
{
grcDebugDraw::TextFontPush(grcSetup::GetMiniFixedWidthFont());
const atVarString temp("HD Texture Memory Usage - %.2fMB", float((double)m_totalMemoryUsed / (1024.0 * 1024.0)) );
grcDebugDraw::Text(Vector2(270.0f, 580.0f), DD_ePCS_Pixels, CRGBA_White(), temp, true, 1.0f, 1.0f);
grcDebugDraw::TextFontPop();
}
if (m_showHDMemUsage)
{
u32 vechiclePhsyical;
u32 vechicleVirtual;
CVehicleModelInfo::GetHDMemoryUsage(vechiclePhsyical, vechicleVirtual);
u32 totalPhysical = m_totalMemoryUsed + vechiclePhsyical;
grcDebugDraw::TextFontPush(grcSetup::GetMiniFixedWidthFont());
const atVarString temp("Total HD Memory Usage - Physical %.2fMB, Virtual %.2fMB",
float((double)totalPhysical / (1024.0 * 1024.0)),
float((double)vechicleVirtual / (1024.0 * 1024.0)));
grcDebugDraw::Text(Vector2(270.0f, 560.0f), DD_ePCS_Pixels, CRGBA_White(), temp, true, 1.0f, 1.0f);
grcDebugDraw::TextFontPop();
}
if (bListHDTxds){
// create the debug window if not already existing
if (ms_pDebugWindow==NULL){
// this creates a window
CUiColorScheme colorScheme;
ms_pDebugWindow = rage_new CUiGadgetWindow(40, 40, 620, 320, colorScheme);
ms_pDebugWindow->SetTitle("CTexLod HD Active bindings");
// this attaches a scrolly list to the window
float afColumnOffsets[] = { 0.0f, 130.0f, 170.0f, 320.0f, 470.0f, 550.0f };
const char* columnTitles1[] = { "Model", "Type", "Asset Name", "HD Txd", "Cooldown", "HD Size" };
u32 numListEntries = 12;
ms_pHDTxdList = rage_new CUiGadgetList(42, 40+38.0f, 620-20.0f, numListEntries, 6, afColumnOffsets, colorScheme, columnTitles1);
ms_pDebugWindow->AttachChild(ms_pHDTxdList);
ms_pHDTxdList->SetUpdateCellCB(CTexLod::UpdateCellForAsset);
// this attaches a scrolly list to the window
float afColumnOffsets2[] = { 0.0f, 40.0f, 260.0f };
const char* columnTitles2[] = { "Bound", "Texture name", "HD Size" };
numListEntries = 8;
ms_pTexList = rage_new CUiGadgetList(42, 220+38.0f, 620-20.0f, numListEntries, 3, afColumnOffsets2, colorScheme, columnTitles2);
ms_pDebugWindow->AttachChild(ms_pTexList);
ms_pTexList->SetUpdateCellCB(CTexLod::UpdateCellForTxd);
// this attaches all of that to the root gadget
g_UiGadgetRoot.AttachChild(ms_pDebugWindow);
}
s32 currentSelectionSlot = ms_pHDTxdList->GetCurrentIndex();
s32 userSelectSlot = -1;
if (ms_pHDTxdList->UserProcessClick()){
userSelectSlot = ms_pHDTxdList->GetCurrentIndex(); // user clicked, so pick up new slot
}
// if no target slot then make sure target asset is also invalid
if (currentSelectionSlot == -1){
ms_currentAsset.Set(STORE_ASSET_INVALID,0);
}
u32 numEntries = ms_boundTxds.GetNumUsed();
ms_pHDTxdList->SetNumEntries(ms_boundTxds.GetNumUsed()); // this invalidates current index in window!
if (numEntries == 0){
ms_pTexList->SetNumEntries(0);
targetTxdListIndex = -1;
return;
}
u32 i=0;
s32 currentAssetSlot = -1;
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
for ( it.Start(); !it.AtEnd(); it.Next() )
{
s32 assetLocHash = it.GetKey();
if (assetLocHash == ms_currentAsset){
currentAssetSlot = i; // establish slot that currently selected asset is in
}
assetHashSlotArray[i++] = assetLocHash;
}
// if user made a selection - use it in preference to tracking the current asset
if (userSelectSlot != -1){
currentAssetSlot = userSelectSlot;
}
ms_pHDTxdList->SetCurrentIndex(currentAssetSlot);
targetTxdListIndex = currentAssetSlot;
// user select, so update the target asset accordingly
if ((userSelectSlot != -1)){
// use the asset out of the current slot as our target asset
if (currentAssetSlot >= 0){
fwAssetLocation loc = static_cast<fwAssetLocation>(assetHashSlotArray[currentAssetSlot]);
CTxdPairBinding** ppBoundTxd = ms_boundTxds.Access(loc);
if (ppBoundTxd && (*ppBoundTxd) && (*ppBoundTxd)->GetHighTxdIdx().Get() > -1){
fwTxd* pTargetTxd = g_TxdStore.Get(strLocalIndex((*ppBoundTxd)->GetHighTxdIdx()));
ms_debugTxdIdx = (*ppBoundTxd)->GetHighTxdIdx();
ms_debugTriggerMI = (*ppBoundTxd)->GetTriggeringMI();
if (pTargetTxd && (ms_currentAsset!=loc)){
ms_pTexList->SetNumEntries(pTargetTxd->GetCount());
}
}
ms_currentAsset = loc;
}
}
} else {
// destroy debug window when no longer required
if (ms_pDebugWindow){
targetTxdListIndex = -1;
ms_pTexList->DetachFromParent();
ms_pHDTxdList->DetachFromParent();
ms_pDebugWindow->DetachFromParent();
delete(ms_pDebugWindow);
ms_pDebugWindow = NULL;
delete(ms_pHDTxdList);
ms_pHDTxdList = NULL;
delete(ms_pTexList);
ms_pTexList = NULL;
}
}
if (g_renderHdArea)
{
Vec3V pos = ms_hdArea.GetCenter();
grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(pos), ms_hdArea.GetRadiusf(), ms_bHasHdArea ? Color32(0, 0, 255) : Color32(200, 200, 200), false);
}
}
void CTexLod::UnbindAllCB(void){
CTexLod::UnbindAllBoundTxds();
}
bool CTexLod::IsTxdUpgradedToHD(const fwTxd *pfwTxd)
{
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
while (!it.AtEnd())
{
CTxdPairBinding* pPairBinding = it.GetData();
fwPtrListSingleLink txdList;
pPairBinding->GetTargetAsset().GetTxdList(txdList);
#if __DEV
const int numUsed = txdList.CountElements();
if (g_MaxTargetTxds < numUsed) {
g_MaxTargetTxds = numUsed;
Displayf("g_MaxTargetTxds = %d", g_MaxTargetTxds);
}
#endif // __DEV
fwPtrNodeSingleLink* pEntry = txdList.GetHeadPtr();
while (pEntry != NULL)
{
fwTxd *pEntryTxd = (fwTxd*)pEntry->GetPtr();
if (pEntryTxd == pfwTxd)
{
return true;
}
pEntry = (fwPtrNodeSingleLink*)pEntry->GetNextPtr();
}
it.Next();
}
return false;
}
bool CTexLod::IsTextureUpgradedToHD(const grcTexture *pTexture)
{
atMap<s32, CTxdPairBinding*>::Iterator it = ms_boundTxds.CreateIterator();
while (!it.AtEnd())
{
CTxdPairBinding* pPairBinding = it.GetData();
atArray<CMipSwitcher> &boundTexSwitchers = pPairBinding->GetBoundTextureSwitchers();
for (int i = 0; i < boundTexSwitchers.GetCount(); ++i)
{
if (boundTexSwitchers[i].GetLowTex() == pTexture)
{
return true;
}
}
it.Next();
}
return false;
}
// this should be safe to call from either update or render threads, since it doesn't rely on ms_boundTxds
bool CTexLod::IsModelUpgradedToHD(const u32 modelIndex, bool bBaseAssetOnly)
{
const CBaseModelInfo* pMI = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(modelIndex)));
if (pMI)
{
fwAssetLocation targetAsset = pMI->GetAssetLocation();
// iterate over all txds in chain for this modelinfo
while(targetAsset.IsValid()){
if (targetAsset.GetIsBoundHD())
return true;
else if (bBaseAssetOnly)
return false;
targetAsset = targetAsset.GetParentAsset();
}
}
return false;
}
// this should be safe to call from either update or render threads, since it doesn't rely on ms_boundTxds
bool CTexLod::IsModelHDTxdCapable(const u32 modelIndex, bool bBaseAssetOnly)
{
const CBaseModelInfo* pMI = CModelInfo::GetBaseModelInfo(fwModelId((strLocalIndex(modelIndex))));
if (pMI)
{
fwAssetLocation targetAsset = pMI->GetAssetLocation();
// iterate over all txds in chain for this modelinfo
while(targetAsset.IsValid()){
if (targetAsset.GetIsHDCapable())
return true;
else if (bBaseAssetOnly)
return false;
targetAsset = targetAsset.GetParentAsset();
}
}
return false;
}
bool CTexLod::IsEntityCloseEnoughForHDSwitch(const CEntity* pEntity)
{
if (pEntity->GetIsHDTxdCapable())
{
const float camDist = Mag(pEntity->GetTransform().GetPosition() - VECTOR3_TO_VEC3V(camInterface::GetPos())).Getf();
float minDistance = 0.0f;
if (pEntity->IsBaseFlagSet(fwEntity::HAS_HD_TEX_DIST))
{
Assertf(pEntity && pEntity->GetIsTypePed(), "Only Peds should have fwEntity::HAS_HD_TEX_DIST flag set");
minDistance = static_cast<const CPed*>(pEntity)->GetPedModelInfo()->GetHDDist();
}
else
{
minDistance = pEntity->GetBaseModelInfo()->GetHDTextureDistance();
}
return camDist < minDistance * GetClampedScaleValue();
}
return false;
}
// update txd view callbacks
void CTexLod::ShowTargetAssetInTexViewCB(void)
{
if (ms_debugTriggerMI != fwModelId::MI_INVALID)
{
CDebugTextureViewerInterface::SelectModelInfo(ms_debugTriggerMI.Get(), NULL, false, true);
}
}
void CTexLod::ShowTargetHDTxdInTexViewCB(void)
{
if (ms_debugTriggerMI != fwModelId::MI_INVALID)
{
CTxdRef ref(AST_TxdStore, ms_debugTxdIdx.Get(), INDEX_NONE, "");
CDebugTextureViewerInterface::SelectTxd(ref, false, true);
}
}
void EnableHDTexturePickerCB(void)
{
// Enable picker when it changes
if (g_PickerManager.IsEnabled() == false)
{
//if (m_usePicker)
{
g_PickerManager.TakeControlOfPickerWidget("HD tex distance");
fwPickerManagerSettings texLodSettings(ENTITY_RENDER_PICKER, true, true,
ENTITY_TYPE_MASK_BUILDING|ENTITY_TYPE_MASK_VEHICLE|ENTITY_TYPE_MASK_PED|ENTITY_TYPE_MASK_OBJECT, false);
g_PickerManager.SetPickerManagerSettings(&texLodSettings);
g_PickerManager.ResetList(false);
}
g_PickerManager.SetEnabled(true);
} else {
g_PickerManager.SetEnabled(false);
}
}
void CTexLod::HDActivationDistanceCB(void)
{
if (ms_pSelectedEntity)
{
CBaseModelInfo* pMI = ms_pSelectedEntity->GetBaseModelInfo();
Assert(pMI);
if (pMI->GetIsHDTxdCapable())
{
pMI->SetHDTextureDistance(ms_HDActivationDistance);
}
}
}
void CTexLod::DisableHDTexCB(void)
{
if (ms_bDisableHDTex){
CTexLod::SetSwapStateAll(false, false);
CTexLod::EnableStateSwapper(false);
} else {
CTexLod::EnableStateSwapper(true);
CTexLod::SetSwapStateAll(true, false);
}
}
bool CTexLod::AssetHasRequest(fwAssetLocation targetAsset)
{
return ms_boundTxds.Access(targetAsset) != NULL;
}
void CTexLod::AddWidgets(bkBank* pBank)
{
Assert(pBank);
pBank->PushGroup("HD Assets", false);
pBank->AddToggle("Display HD area", &g_renderHdArea);
pBank->AddToggle("Only External Requests", &m_onlyExternalRequsts);
pBank->PushGroup("HD assets for cutscenes",false);
pBank->AddToggle("Enable default HD requests", &g_HDAssetMgr.m_enableDefaultHDRequests);
pBank->AddToggle("Display HD requests", &g_HDAssetMgr.m_displayHDRequests);
pBank->PopGroup();
pBank->AddToggle("Show HD Memory Usage", &m_showHDMemUsage);
pBank->PushGroup("Textures", false);
pBank->AddToggle("Enabled", &CTexLod::ms_bEnabled);
pBank->AddButton("Unbind all", UnbindAllCB);
pBank->AddToggle("Show HD Texture Memory Usage", &m_showHDTextureMemUsage);
pBank->AddToggle("List HD txds", &bListHDTxds);
pBank->AddButton("Show target asset in texture viewer", &ShowTargetAssetInTexViewCB);
pBank->AddButton("Show bound HD Txd in texture viewer", &ShowTargetHDTxdInTexViewCB);
pBank->PopGroup();
CVehicleModelInfo::AddHDWidgets(*pBank);
pBank->PushGroup("HD texture distance edit", false);
pBank->AddButton("Toggle HD texture distance picker", EnableHDTexturePickerCB);
pBank->AddSlider("HD activation distance", &ms_HDActivationDistance, 1.0f, 200.0f, 1.0f, HDActivationDistanceCB);
pBank->AddToggle("Disable HD tex", &ms_bDisableHDTex, DisableHDTexCB);
pBank->AddText("Selected entity name", selectedEntityName, MAX_NAME_LEN, true);
pBank->AddText("Selected entity distance", selectedEntityDistance, MAX_NAME_LEN, true);
pBank->PopGroup();
pBank->PopGroup();
}
#endif //__BANK