You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/Cry3DEngine/Material.cpp

1456 lines
45 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "Cry3DEngine_precompiled.h"
#include "MatMan.h"
#include <IRenderer.h>
#include "VisAreas.h"
DEFINE_INTRUSIVE_LINKED_LIST(CMatInfo)
//////////////////////////////////////////////////////////////////////////
void CMaterialLayer::SetShaderItem(const _smart_ptr<IMaterial> pParentMtl, const SShaderItem& pShaderItem)
{
assert(pParentMtl && "CMaterialLayer::SetShaderItem invalid material");
if (pShaderItem.m_pShader)
{
pShaderItem.m_pShader->AddRef();
}
if (pShaderItem.m_pShaderResources)
{
pShaderItem.m_pShaderResources->AddRef();
CMatInfo* pParentMatInfo = (CMatInfo*)(pParentMtl.get());
pShaderItem.m_pShaderResources->SetMaterialName(pParentMatInfo->m_sUniqueMaterialName);
}
gEnv->pRenderer->ClearShaderItem(&m_pShaderItem);
SAFE_RELEASE(m_pShaderItem.m_pShader);
SAFE_RELEASE(m_pShaderItem.m_pShaderResources);
m_pShaderItem = pShaderItem;
gEnv->pRenderer->UpdateShaderItem(&m_pShaderItem, nullptr);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialLayer::GetMemoryUsage(ICrySizer* pSizer)
{
SIZER_COMPONENT_NAME(pSizer, "MaterialLayer");
pSizer->AddObject(this, sizeof(*this));
}
//////////////////////////////////////////////////////////////////////////
size_t CMaterialLayer::GetResourceMemoryUsage(ICrySizer* pSizer)
{
size_t nResourceMemory(0);
// Pushing some context to use the Macro.
{
SIZER_COMPONENT_NAME(pSizer, "Textures");
IRenderShaderResources* piResources(m_pShaderItem.m_pShaderResources);
if (piResources)
{
//////////////////////////////////////////////////////////////////////////
// Texture
for (size_t nCount = 0; nCount < EFTT_MAX; ++nCount)
{
SEfResTexture* piTextureResource(piResources->GetTextureResource(nCount));
if (piTextureResource)
{
ITexture* piTexture = piTextureResource->m_Sampler.m_pITex;
if (piTexture)
{
SIZER_COMPONENT_NAME(pSizer, "MemoryTexture");
size_t nCurrentResourceMemoryUsage = piTexture->GetDataSize();
nResourceMemory += nCurrentResourceMemoryUsage;
pSizer->AddObject(piTexture, nCurrentResourceMemoryUsage);
IResourceCollector* pColl = pSizer->GetResourceCollector();
if (pColl)
{
pColl->AddResource(piTexture->GetName(), nCurrentResourceMemoryUsage);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
}
}
return nResourceMemory;
}
//////////////////////////////////////////////////////////////////////////
CMatInfo::CMatInfo()
{
m_nRefCount = 0;
m_Flags = 0;
m_nSurfaceTypeId = 0;
m_pMaterialLayers = 0;
m_ucDefautMappingAxis = 0;
m_fDefautMappingScale = 1.f;
#ifdef SUPPORT_MATERIAL_SKETCH
m_pPreSketchShader = 0;
m_nPreSketchTechnique = 0;
#endif
#ifdef SUPPORT_MATERIAL_EDITING
m_pUserData = NULL;
#endif
m_pActiveLayer = NULL;
m_shaderName = "Non-Initialized Shader name";
ZeroStruct(m_streamZoneInfo);
// Used to know when a .dccmtl file has been changed,
// requiring the source material to be updated
m_dccMaterialHash = 0;
m_isDirty = false;
}
//////////////////////////////////////////////////////////////////////////
CMatInfo::~CMatInfo()
{
ShutDown();
}
void CMatInfo::AddRef()
{
CryInterlockedIncrement(&m_nRefCount);
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::Release()
{
if (CryInterlockedDecrement(&m_nRefCount) <= 0)
{
delete this;
}
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::ShutDown()
{
SAFE_DELETE(m_pMaterialLayers);
ReleaseCurrentShaderItem();
m_subMtls.clear();
}
//////////////////////////////////////////////////////////////////////////
IMaterialHelpers& CMatInfo::GetMaterialHelpers()
{
return GetMatMan()->s_materialHelpers;
}
IMaterialManager* CMatInfo::GetMaterialManager()
{
return GetMatMan();
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetName(const char* sName)
{
m_sMaterialName = sName;
m_sUniqueMaterialName = m_sMaterialName;
if (m_shaderItem.m_pShaderResources)
{
m_shaderItem.m_pShaderResources->SetMaterialName(m_sUniqueMaterialName); // Only for correct warning message purposes.
}
if (m_Flags & MTL_FLAG_MULTI_SUBMTL)
{
for (int i = 0, num = m_subMtls.size(); i < num; i++)
{
if (m_subMtls[i] && (m_subMtls[i]->m_Flags & MTL_FLAG_PURE_CHILD))
{
m_subMtls[i]->m_sUniqueMaterialName = m_sMaterialName;
if (m_subMtls[i]->m_shaderItem.m_pShaderResources)
{
m_subMtls[i]->m_shaderItem.m_pShaderResources->SetMaterialName(m_sUniqueMaterialName);
}
}
if (m_subMtls[i] && strstr(m_subMtls[i]->m_sUniqueMaterialName, MTL_SPECIAL_NAME_RAYCAST_PROXY) != 0)
{
m_subMtls[i]->m_Flags |= MTL_FLAG_RAYCAST_PROXY;
m_subMtls[i]->m_Flags |= MTL_FLAG_NODRAW;
}
}
}
if (strstr(sName, MTL_SPECIAL_NAME_COLLISION_PROXY) != 0 || strstr(sName, MTL_SPECIAL_NAME_COLLISION_PROXY_VEHICLE) != 0)
{
m_Flags |= MTL_FLAG_COLLISION_PROXY;
}
else if (strstr(sName, MTL_SPECIAL_NAME_RAYCAST_PROXY) != 0)
{
m_Flags |= MTL_FLAG_RAYCAST_PROXY;
m_Flags |= MTL_FLAG_NODRAW;
}
}
//////////////////////////////////////////////////////////////////////////
bool CMatInfo::IsDefault()
{
return this == GetMatMan()->GetDefaultMaterial();
}
//////////////////////////////////////////////////////////////////////////
bool CMatInfo::IsMaterialGroup() const
{
return (m_Flags & MTL_FLAG_MULTI_SUBMTL) != 0 || m_subMtls.size() > 0;
}
//////////////////////////////////////////////////////////////////////////
bool CMatInfo::IsSubMaterial() const
{
return (m_Flags & MTL_FLAG_PURE_CHILD) != 0;
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::UpdateFlags()
{
m_Flags &= ~(MTL_FLAG_REQUIRE_FORWARD_RENDERING | MTL_FLAG_REQUIRE_NEAREST_CUBEMAP);
if (m_shaderItem.m_pShader)
{
IRenderShaderResources* pRendShaderResources = m_shaderItem.m_pShaderResources;
const bool bAlphaBlended = (m_shaderItem.m_pShader->GetFlags() & (EF_NODRAW | EF_DECAL)) || (pRendShaderResources && pRendShaderResources->IsTransparent());
const bool bIsHair = (m_shaderItem.m_pShader->GetFlags2() & EF2_HAIR) != 0;
const bool bIsGlass = m_shaderItem.m_pShader->GetShaderType() == eST_Glass;
const bool bIsWater = m_shaderItem.m_pShader->GetShaderType() == eST_Water;
const bool bIsEye = strcmp(m_shaderItem.m_pShader->GetName(), "Eye") == 0;
const bool bIsFur = m_shaderItem.m_pShader->GetShaderDrawType() == eSHDT_Fur;
if (bAlphaBlended && !(m_shaderItem.m_pShader->GetFlags2() & EF2_NODRAW) && !(m_shaderItem.m_pShader->GetFlags() & EF_DECAL))
{
m_Flags |= MTL_FLAG_REQUIRE_FORWARD_RENDERING;
}
else if (bIsHair || bIsGlass || bIsFur)
{
m_Flags |= MTL_FLAG_REQUIRE_FORWARD_RENDERING;
}
if ((bAlphaBlended || bIsHair || bIsGlass || bIsWater || bIsEye) &&
(pRendShaderResources) &&
(pRendShaderResources->GetTextureResource(EFTT_ENV)) &&
(pRendShaderResources->GetTextureResource(EFTT_ENV)->m_Sampler.m_eTexType == eTT_NearestCube))
{
m_Flags |= MTL_FLAG_REQUIRE_NEAREST_CUBEMAP;
}
// Make sure to refresh sectors
static int nLastUpdateFrameId = 0;
if (gEnv->IsEditing() && GetVisAreaManager() && nLastUpdateFrameId != GetRenderer()->GetFrameID())
{
nLastUpdateFrameId = GetRenderer()->GetFrameID();
}
}
}
void CMatInfo::ReleaseCurrentShaderItem()
{
// Clear the renderer's shader resources to nullptr before releasing the shader item so there are no dangling pointers
gEnv->pRenderer->ClearShaderItem(&m_shaderItem);
SAFE_RELEASE(m_shaderItem.m_pShader);
SAFE_RELEASE(m_shaderItem.m_pShaderResources);
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetShaderItem(const SShaderItem& _ShaderItem)
{
if (_ShaderItem.m_pShader)
{
_ShaderItem.m_pShader->AddRef();
}
if (_ShaderItem.m_pShaderResources)
{
_ShaderItem.m_pShaderResources->AddRef();
_ShaderItem.m_pShaderResources->SetMaterialName(m_sUniqueMaterialName);
}
ReleaseCurrentShaderItem();
m_shaderItem = _ShaderItem;
gEnv->pRenderer->UpdateShaderItem(&m_shaderItem, this);
UpdateFlags();
int sketchMode = m_pMatMan->GetSketchMode();
if (sketchMode)
{
SetSketchMode(sketchMode);
}
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::AssignShaderItem(const SShaderItem& _ShaderItem)
{
//if (_ShaderItem.m_pShader)
// _ShaderItem.m_pShader->AddRef();
if (_ShaderItem.m_pShaderResources)
{
_ShaderItem.m_pShaderResources->SetMaterialName(m_sUniqueMaterialName);
}
ReleaseCurrentShaderItem();
m_shaderItem = _ShaderItem;
gEnv->pRenderer->UpdateShaderItem(&m_shaderItem, this);
UpdateFlags();
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetSurfaceType(const char* sSurfaceTypeName)
{
m_nSurfaceTypeId = 0;
ISurfaceType* pSurfaceType = GetMatMan()->GetSurfaceTypeByName(sSurfaceTypeName, m_sMaterialName);
if (pSurfaceType)
{
m_nSurfaceTypeId = pSurfaceType->GetId();
}
}
ISurfaceType* CMatInfo::GetSurfaceType()
{
return GetMatMan()->GetSurfaceType(m_nSurfaceTypeId, m_sMaterialName);
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetSubMtlCount(int numSubMtl)
{
AUTO_LOCK(GetSubMaterialResizeLock());
if (numSubMtl > 0)
{
m_Flags |= MTL_FLAG_MULTI_SUBMTL;
}
else if(numSubMtl == 0)
{
m_Flags &= ~MTL_FLAG_MULTI_SUBMTL;
}
else
{
AZ_Assert(false, "SetSubMtlCount called with negative value for material %s.", m_sMaterialName.c_str());
return;
}
m_subMtls.resize(numSubMtl);
}
//////////////////////////////////////////////////////////////////////////
bool CMatInfo::IsStreamedIn(const int nMinPrecacheRoundIds[MAX_STREAM_PREDICTION_ZONES], IRenderMesh* pRenderMesh) const
{
if (pRenderMesh)
{
TRenderChunkArray* pChunks = &pRenderMesh->GetChunks();
if (pChunks != NULL)
{
for (uint32 i = 0, size = pChunks->size(); i < size; i++)
{
if (!AreChunkTexturesStreamedIn(&(*pChunks)[i], nMinPrecacheRoundIds))
{
return false;
}
}
}
pChunks = &pRenderMesh->GetChunksSkinned();
for (uint32 i = 0, size = pChunks->size(); i < size; i++)
{
if (!AreChunkTexturesStreamedIn(&(*pChunks)[i], nMinPrecacheRoundIds))
{
return false;
}
}
}
else
{
if (!AreChunkTexturesStreamedIn(NULL, nMinPrecacheRoundIds))
{
return false;
}
}
return true;
}
bool CMatInfo::AreChunkTexturesStreamedIn(CRenderChunk* pRenderChunk, const int nMinPrecacheRoundIds[MAX_STREAM_PREDICTION_ZONES]) const
{
const CMatInfo* pMaterial = this;
if (pRenderChunk && pRenderChunk->pRE && pRenderChunk->nNumIndices && pRenderChunk->nNumVerts)
{ // chunk is defined and have valid geometry
if (pRenderChunk->m_nMatID < (uint16)m_subMtls.size())
{
pMaterial = (CMatInfo*)m_subMtls[pRenderChunk->m_nMatID];
}
if (pMaterial != NULL)
{
return pMaterial->AreTexturesStreamedIn(nMinPrecacheRoundIds);
}
}
else if (!pRenderChunk)
{
if (!AreTexturesStreamedIn(nMinPrecacheRoundIds))
{
return false;
}
for (int nSubMtl = 0; nSubMtl < (int)m_subMtls.size(); ++nSubMtl)
{
pMaterial = m_subMtls[nSubMtl];
if (pMaterial != NULL)
{
if (!pMaterial->AreTexturesStreamedIn(nMinPrecacheRoundIds))
{
return false;
}
}
}
}
return true;
}
bool CMatInfo::AreTexturesStreamedIn(const int nMinPrecacheRoundIds[MAX_STREAM_PREDICTION_ZONES]) const
{
// Check this material
if (IRenderShaderResources* pShaderResources = GetShaderItem().m_pShaderResources)
{
for (auto iter = pShaderResources->GetTexturesResourceMap()->begin(); iter != pShaderResources->GetTexturesResourceMap()->end(); ++iter)
{
SEfResTexture* pTextureResource = &(iter->second);
if (ITexture* pTexture = pTextureResource->m_Sampler.m_pITex)
{
if (!pTexture->IsStreamedIn(nMinPrecacheRoundIds))
{
return false;
}
}
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetSubMtl(int nSlot, _smart_ptr<IMaterial> pMtl)
{
if (nSlot < 0 || nSlot >= static_cast<int>(m_subMtls.size()))
{
AZ_Error("Rendering", false, "SetSubMtl inserting material '%s' outside the range of m_subMtls in '%s'. Call SetSubMtlCount first to increase the size of m_subMtls.", pMtl->GetName(), m_sMaterialName.c_str());
return;
}
if (pMtl && pMtl->IsMaterialGroup())
{
AZ_Error("Rendering", false, "SetSubMtl attempting to insert a material group '%s' as a sub-material of '%s'. Only individual materials can be sub-materials.", pMtl->GetName(), m_sMaterialName.c_str());
return;
}
m_subMtls[nSlot] = (CMatInfo*)pMtl.get();
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetLayerCount(uint32 nCount)
{
if (!m_pMaterialLayers)
{
m_pMaterialLayers = new MatLayers;
}
m_pMaterialLayers->resize(nCount);
}
//////////////////////////////////////////////////////////////////////////
uint32 CMatInfo::GetLayerCount() const
{
if (m_pMaterialLayers)
{
return (uint32) m_pMaterialLayers->size();
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetLayer(uint32 nSlot, IMaterialLayer* pLayer)
{
assert(m_pMaterialLayers);
assert(nSlot < (uint32) m_pMaterialLayers->size());
if (m_pMaterialLayers && pLayer && nSlot < (uint32) m_pMaterialLayers->size())
{
(*m_pMaterialLayers)[nSlot] = static_cast< CMaterialLayer*>(pLayer);
}
}
//////////////////////////////////////////////////////////////////////////
const IMaterialLayer* CMatInfo::GetLayer(uint8 nLayersMask, [[maybe_unused]] uint8 nLayersUsageMask) const
{
if (m_pMaterialLayers && nLayersMask)
{
int nSlotCount = m_pMaterialLayers->size();
for (int nSlot = 0; nSlot < nSlotCount; ++nSlot)
{
CMaterialLayer* pLayer = static_cast< CMaterialLayer*>((*m_pMaterialLayers)[nSlot]);
if (nLayersMask & (1 << nSlot))
{
if (pLayer)
{
m_pActiveLayer = pLayer;
return m_pActiveLayer;
}
m_pActiveLayer = 0;
return 0;
}
}
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
const IMaterialLayer* CMatInfo::GetLayer(uint32 nSlot) const
{
if (m_pMaterialLayers && nSlot < (uint32) m_pMaterialLayers->size())
{
return static_cast< CMaterialLayer*>((*m_pMaterialLayers)[nSlot]);
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
IMaterialLayer* CMatInfo::CreateLayer()
{
return new CMaterialLayer;
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetUserData([[maybe_unused]] void* pUserData)
{
#ifdef SUPPORT_MATERIAL_EDITING
m_pUserData = pUserData;
#endif
}
//////////////////////////////////////////////////////////////////////////
void* CMatInfo::GetUserData() const
{
#ifdef SUPPORT_MATERIAL_EDITING
return m_pUserData;
#else
CryFatalError("CMatInfo::GetUserData not supported on this platform");
return NULL;
#endif
}
//////////////////////////////////////////////////////////////////////////
int CMatInfo::FillSurfaceTypeIds(int pSurfaceIdsTable[])
{
if (m_subMtls.empty() || !(m_Flags & MTL_FLAG_MULTI_SUBMTL))
{
pSurfaceIdsTable[0] = m_nSurfaceTypeId;
return 1; // Not Multi material.
}
for (int i = 0; i < (int)m_subMtls.size(); i++)
{
if (m_subMtls[i] != 0)
{
pSurfaceIdsTable[i] = m_subMtls[i]->m_nSurfaceTypeId;
}
else
{
pSurfaceIdsTable[i] = 0;
}
}
return m_subMtls.size();
}
void CMatInfo::Copy(_smart_ptr<IMaterial> pMtlDest, EMaterialCopyFlags flags)
{
CMatInfo* pMatInfo = static_cast<CMatInfo*>(pMtlDest.get());
if (flags & MTL_COPY_NAME)
{
pMatInfo->m_sMaterialName = m_sMaterialName;
pMatInfo->m_sUniqueMaterialName = m_sUniqueMaterialName;
}
pMatInfo->m_nSurfaceTypeId = m_nSurfaceTypeId;
pMatInfo->m_Flags = m_Flags;
if (GetShaderItem().m_pShaderResources)
{
//
SShaderItem& siSrc(GetShaderItem());
SInputShaderResources isr(siSrc.m_pShaderResources);
//
SShaderItem& siDstTex(pMatInfo->GetShaderItem());
SInputShaderResources idsTex(siDstTex.m_pShaderResources);
if (!(flags & MTL_COPY_TEXTURES))
{
isr.m_TexturesResourcesMap = idsTex.m_TexturesResourcesMap;
}
SShaderItem siDst(GetRenderer()->EF_LoadShaderItem(siSrc.m_pShader->GetName(), false, 0, &isr, siSrc.m_pShader->GetGenerationMask()));
pMatInfo->AssignShaderItem(siDst);
siDst.m_pShaderResources->CloneConstants(siSrc.m_pShaderResources);
}
}
//////////////////////////////////////////////////////////////////////////
_smart_ptr<CMatInfo> CMatInfo::Clone()
{
_smart_ptr<CMatInfo> pMatInfo = new CMatInfo;
pMatInfo->m_sMaterialName = m_sMaterialName;
pMatInfo->m_sUniqueMaterialName = m_sUniqueMaterialName;
pMatInfo->m_nSurfaceTypeId = m_nSurfaceTypeId;
pMatInfo->m_Flags = m_Flags;
SShaderItem& siSrc(GetShaderItem());
SInputShaderResources isr(siSrc.m_pShaderResources);
SShaderItem siDst(GetRenderer()->EF_LoadShaderItem(siSrc.m_pShader->GetName(), false, 0, &isr, siSrc.m_pShader->GetGenerationMask()));
pMatInfo->AssignShaderItem(siDst);
siDst.m_pShaderResources->CloneConstants(siSrc.m_pShaderResources);
// Necessary to delete all the data allocated in the renderer
GetRenderer()->EF_ReleaseInputShaderResource(&isr);
return pMatInfo;
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::GetMemoryUsage(ICrySizer* pSizer) const
{
SIZER_COMPONENT_NAME(pSizer, "Material");
pSizer->AddObject(this, sizeof(*this));
if (m_pMaterialLayers)
{
int n = m_pMaterialLayers->size();
for (int i = 0; i < n; i++)
{
CMaterialLayer* pMaterialLayer = (*m_pMaterialLayers)[i];
if (pMaterialLayer)
{
pMaterialLayer->GetMemoryUsage(pSizer);
}
}
}
// all sub material
{
pSizer->AddObject(m_subMtls);
}
}
//////////////////////////////////////////////////////////////////////////
size_t CMatInfo::GetResourceMemoryUsage(ICrySizer* pSizer)
{
size_t nTotalResourceMemoryUsage(0);
if (m_pMaterialLayers)
{
int n = m_pMaterialLayers->size();
for (int i = 0; i < n; i++)
{
CMaterialLayer* pMaterialLayer = (*m_pMaterialLayers)[i];
if (pMaterialLayer)
{
nTotalResourceMemoryUsage += pMaterialLayer->GetResourceMemoryUsage(pSizer);
}
}
}
if (m_shaderItem.m_pShaderResources)
{
nTotalResourceMemoryUsage += m_shaderItem.m_pShaderResources->GetResourceMemoryUsage(pSizer);
}
// all sub material
{
int iCnt = GetSubMtlCount();
for (int i = 0; i < iCnt; ++i)
{
_smart_ptr<IMaterial> piMaterial(GetSubMtl(i));
if (piMaterial)
{
nTotalResourceMemoryUsage += piMaterial->GetResourceMemoryUsage(pSizer);
}
}
}
return nTotalResourceMemoryUsage;
}
//////////////////////////////////////////////////////////////////////////
bool CMatInfo::SetGetMaterialParamFloat(const char* sParamName, float& v, bool bGet, bool allowShaderParam /*= false*/, int materialIndex /*= 0*/)
{
//For a material group, we need to set the param to a sub-material specified by materialIndex
if (IsMaterialGroup())
{
int subMtlCount = GetSubMtlCount();
if (materialIndex >= 0 && materialIndex < subMtlCount)
{
IMaterial* subMtl = GetSubMtl(materialIndex);
if (subMtl)
{
return subMtl->SetGetMaterialParamFloat(sParamName, v, bGet, allowShaderParam);
}
else
{
AZ_Error("Rendering", false, "Attempted to access an invalid Sub-Material at index %d.", materialIndex);
return false;
}
}
else
{
AZ_Error("Rendering", false, "Attempted to access an invalid Sub-Material at index %d. %d Materials are available.", materialIndex, subMtlCount);
return false;
}
}
else if (materialIndex > 0)
{
AZ_Warning("Rendering", false, "Setting a parameter on a single Material does not require a Material Index.");
}
//For a single material, we need to make sure it has all the shader resources we need
IRenderShaderResources* pRendShaderRes = m_shaderItem.m_pShaderResources;
if (!pRendShaderRes)
{
AZ_Warning("Rendering", false, "Attempted to access params on a Material that has no Shader Resources.");
return false;
}
const bool bEmissive = pRendShaderRes->IsEmissive();
const bool bTransparent = pRendShaderRes->IsTransparent();
bool bOk = GetMaterialHelpers().SetGetMaterialParamFloat(*pRendShaderRes, sParamName, v, bGet);
if (!bOk && allowShaderParam)
{
auto& shaderParams = pRendShaderRes->GetParameters();
if (bGet)
{
for (int i = 0; i < shaderParams.size(); ++i)
{
SShaderParam& param = shaderParams[i];
if (azstricmp(param.m_Name.c_str(), sParamName) == 0)
{
v = 0.0f;
switch (param.m_Type)
{
case eType_BOOL:
v = param.m_Value.m_Bool;
bOk = true;
break;
case eType_BYTE:
v = param.m_Value.m_Byte;
bOk = true;
break;
case eType_SHORT:
v = param.m_Value.m_Short;
bOk = true;
break;
case eType_INT:
v = (float)param.m_Value.m_Int;
bOk = true;
break;
case eType_HALF:
case eType_FLOAT:
v = param.m_Value.m_Float;
bOk = true;
break;
default:
AZ_Warning(nullptr, false, "Unsupported param type %d in %s", param.m_Type, AZ_FUNCTION_SIGNATURE);
}
break;
}
}
}
else
{
UParamVal val;
val.m_Float = v;
bOk = SShaderParam::SetParam(sParamName, &shaderParams, val);
}
}
if (bOk && m_shaderItem.m_pShader && !bGet)
{
// since "glow" is a post effect it needs to be updated here
// If unit opacity changed, the transparency preprocess flag must be updated. (This causes SShaderItem::Update() -> SShaderItem::PostLoad(),
// updating the transparency preprocess flag (SShaderItem::m_nPreprocessFlags, FB_TRANSPARENT) based on CShaderResources::IsTransparent())
if (bEmissive != pRendShaderRes->IsEmissive() ||
bTransparent != pRendShaderRes->IsTransparent())
{
GetRenderer()->ForceUpdateShaderItem(&m_shaderItem, this);
}
pRendShaderRes->UpdateConstants(m_shaderItem.m_pShader);
}
m_isDirty |= (bOk && !bGet);
return bOk;
}
bool CMatInfo::SetGetMaterialParamVec3(const char* sParamName, Vec3& v, bool bGet, bool allowShaderParam /*= false*/, int materialIndex /*= 0*/)
{
static const float defaultAlpha = 1.0f;
Vec4 vec4(v, defaultAlpha);
const bool bOk = SetGetMaterialParamVec4(sParamName, vec4, bGet, allowShaderParam, materialIndex);
if(bOk && bGet)
{
v = Vec3(vec4);
}
m_isDirty |= (bOk && !bGet);
return bOk;
}
bool CMatInfo::SetGetMaterialParamVec4(const char* sParamName, Vec4& v, bool bGet, bool allowShaderParam /*= false*/, int materialIndex /*= 0*/)
{
//For a material group, we need to set the param to a sub-material specified by materialIndex
if (IsMaterialGroup())
{
int subMtlCount = GetSubMtlCount();
if (materialIndex >= 0 && materialIndex < subMtlCount)
{
IMaterial* subMtl = GetSubMtl(materialIndex);
if (subMtl)
{
return subMtl->SetGetMaterialParamVec4(sParamName, v, bGet, allowShaderParam);
}
else
{
AZ_Error("Rendering", false, "Attempted to access an invalid Sub-Material at index %d.", materialIndex);
return false;
}
}
else
{
AZ_Error("Rendering", false, "Attempted to access an invalid Sub-Material at index %d. %d Materials are available.", materialIndex, subMtlCount);
return false;
}
}
else if (materialIndex > 0)
{
AZ_Warning("Rendering", false, "Setting a parameter on a single Material does not require a Material Index.");
}
//For a single material, we need to make sure it has all the shader resources we need
IRenderShaderResources* pRendShaderRes = m_shaderItem.m_pShaderResources;
if (!pRendShaderRes)
{
AZ_Warning("Rendering", false, "Attempted to access params on a Material that has no Shader Resources.");
return false;
}
static const float defaultAlpha = 1.0f;
// Note we are only passing XYZ to the IMaterialHelpers here because it only deals with with "diffuse", "specular", and "emissive_color", which don't use the W/alpha channel.
Vec3 vec3(v);
bool bOk = GetMaterialHelpers().SetGetMaterialParamVec3(*pRendShaderRes, sParamName, vec3, bGet);
if (bOk && bGet)
{
v = Vec4(vec3, defaultAlpha);
}
if (!bOk && allowShaderParam)
{
auto& shaderParams = pRendShaderRes->GetParameters();
if (bGet)
{
for (int i = 0; i < shaderParams.size(); ++i)
{
SShaderParam& param = shaderParams[i];
if (azstricmp(param.m_Name.c_str(), sParamName) == 0)
{
if (param.m_Type == eType_VECTOR)
{
v = Vec4(param.m_Value.m_Vector[0], param.m_Value.m_Vector[1], param.m_Value.m_Vector[2], defaultAlpha);
bOk = true;
}
else if (param.m_Type == eType_FCOLOR)
{
v = Vec4(param.m_Value.m_Color[0], param.m_Value.m_Color[1], param.m_Value.m_Color[2], param.m_Value.m_Color[3]);
bOk = true;
}
else
{
AZ_Warning(nullptr, false, "Unsupported param type %d in %s", param.m_Type, AZ_FUNCTION_SIGNATURE);
}
}
}
}
else
{
UParamVal val;
val.m_Color[0] = v.x;
val.m_Color[1] = v.y;
val.m_Color[2] = v.z;
val.m_Color[3] = v.w;
bOk = SShaderParam::SetParam(sParamName, &shaderParams, val);
}
}
if (bOk && m_shaderItem.m_pShader && !bGet)
{
pRendShaderRes->UpdateConstants(m_shaderItem.m_pShader);
}
m_isDirty |= (bOk && !bGet);
return bOk;
}
void CMatInfo::SetDirty(bool dirty /*= true*/)
{
m_isDirty = dirty;
}
bool CMatInfo::IsDirty() const
{
bool isChildrenDirty = false;
if (IsMaterialGroup())
{
for (int i = 0 ; i < m_subMtls.size(); i ++)
{
if (m_subMtls[i])
{
if (m_subMtls[i]->IsMaterialGroup())
{
AZ_Assert(!m_subMtls[i]->IsMaterialGroup(), "Sub-material '%s' in material '%s' is a material group. Material groups cannot be sub-materials. This could lead to a cycle and infinite recursion in CMatInfo::IsDirty().", m_subMtls[i]->GetName(), m_sMaterialName.c_str());
// Exit early to prevent a possible infinite recursion.
// Return true to conservatively indicate that this material should be re-loaded
return true;
}
isChildrenDirty |= m_subMtls[i]->IsDirty();
}
}
}
return m_isDirty | isChildrenDirty;
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetSketchMode([[maybe_unused]] int mode)
{
#ifdef SUPPORT_MATERIAL_SKETCH
if (mode == 0)
{
if (m_pPreSketchShader)
{
m_shaderItem.m_pShader = m_pPreSketchShader;
m_shaderItem.m_nTechnique = m_nPreSketchTechnique;
m_pPreSketchShader = 0;
m_nPreSketchTechnique = 0;
}
}
else
{
if (m_shaderItem.m_pShader && m_shaderItem.m_pShader != m_pPreSketchShader)
{
EShaderType shaderType = m_shaderItem.m_pShader->GetShaderType();
// nGenerationMask = (uint32)m_shaderItem.m_pShader->GetGenerationMask();
// Do not replace this shader types.
switch (shaderType)
{
case eST_Shadow:
case eST_Water:
case eST_FX:
case eST_PostProcess:
case eST_HDR:
case eST_Sky:
// For this shaders do not replace them.
return;
}
}
if (!m_pPreSketchShader)
{
m_pPreSketchShader = m_shaderItem.m_pShader;
m_nPreSketchTechnique = m_shaderItem.m_nTechnique;
}
//m_shaderItem.m_pShader = ((CMatMan*)GetMatMan())->GetDefaultHelperMaterial()->GetShaderItem().m_pShader;
if (mode == 1)
{
m_shaderItem.m_pShader = gEnv->pRenderer->EF_LoadShader("Sketch");
m_shaderItem.m_nTechnique = 0;
}
else if (mode == 2)
{
m_shaderItem.m_pShader = gEnv->pRenderer->EF_LoadShader("Sketch.Fast");
m_shaderItem.m_nTechnique = 0;
}
else if (mode == 4)
{
SShaderItem tmp = gEnv->pRenderer->EF_LoadShaderItem("Sketch.TexelsPerMeter", false);
m_shaderItem.m_pShader = tmp.m_pShader;
m_shaderItem.m_nTechnique = tmp.m_nTechnique;
}
if (m_shaderItem.m_pShader)
{
m_shaderItem.m_pShader->AddRef();
}
}
for (int i = 0; i < (int)m_subMtls.size(); i++)
{
if (m_subMtls[i])
{
m_subMtls[i]->SetSketchMode(mode);
}
}
#endif
}
void CMatInfo::SetTexelDensityDebug([[maybe_unused]] int mode)
{
#ifdef SUPPORT_MATERIAL_SKETCH
if (m_shaderItem.m_pShader)
{
EShaderType shaderType = m_pPreSketchShader ? m_pPreSketchShader->GetShaderType() : m_shaderItem.m_pShader->GetShaderType();
switch (shaderType)
{
case eST_Shadow:
case eST_Water:
case eST_FX:
case eST_PostProcess:
case eST_HDR:
case eST_Sky:
// For this shaders do not replace them.
mode = 0;
break;
default:
if (mode == 1 || mode == 2)
{
break;
}
mode = 0;
break;
}
if (mode == 0)
{
if (m_pPreSketchShader)
{
m_shaderItem.m_pShader = m_pPreSketchShader;
m_shaderItem.m_nTechnique = m_nPreSketchTechnique;
m_pPreSketchShader = 0;
m_nPreSketchTechnique = 0;
}
}
else
{
if (!m_pPreSketchShader)
{
m_pPreSketchShader = m_shaderItem.m_pShader;
m_nPreSketchTechnique = m_shaderItem.m_nTechnique;
}
SShaderItem tmp;
if (mode == 3 || mode == 4)
{
tmp = gEnv->pRenderer->EF_LoadShaderItem("SketchTerrain.TexelDensityTerrainLayer", false);
}
else
{
tmp = gEnv->pRenderer->EF_LoadShaderItem("Sketch.TexelDensity", false);
}
m_shaderItem.m_pShader = tmp.m_pShader;
m_shaderItem.m_nTechnique = tmp.m_nTechnique;
}
}
for (int i = 0; i < (int)m_subMtls.size(); i++)
{
if (m_subMtls[i])
{
m_subMtls[i]->SetTexelDensityDebug(mode);
}
}
#endif
}
void CMatInfo::PrecacheMaterial(const float _fEntDistance, IRenderMesh* pRenderMesh, bool bFullUpdate, bool bDrawNear)
{
// FUNCTION_PROFILER_3DENGINE;
LOADING_TIME_PROFILE_SECTION;
int nFlags = 0;
float fEntDistance;
if (bDrawNear)
{
fEntDistance = _fEntDistance;
nFlags |= FPR_HIGHPRIORITY;
}
else
{
fEntDistance = max(GetFloatCVar(e_StreamPredictionMinReportDistance), _fEntDistance);
}
float fMipFactor = fEntDistance * fEntDistance;
// updating texture streaming distances
if (pRenderMesh)
{
TRenderChunkArray* pChunks = &pRenderMesh->GetChunks();
if (pChunks != NULL)
{
for (uint32 i = 0, size = pChunks->size(); i < size; i++)
{
PrecacheChunkTextures(fMipFactor, nFlags, &(*pChunks)[i], bFullUpdate);
}
}
pChunks = &pRenderMesh->GetChunksSkinned();
for (uint32 i = 0, size = pChunks->size(); i < size; i++)
{
PrecacheChunkTextures(fMipFactor, nFlags, &(*pChunks)[i], bFullUpdate);
}
}
else
{
PrecacheChunkTextures(fMipFactor, nFlags, NULL, bFullUpdate);
}
}
void CMatInfo::DisableTextureStreaming()
{
const int numSubMaterials = max(GetSubMtlCount(), 1);
for (int subMaterialId = 0; subMaterialId < numSubMaterials; ++subMaterialId)
{
_smart_ptr<IMaterial> subMaterial = GetSafeSubMtl(subMaterialId);
if (subMaterial)
{
const SShaderItem& shaderItem = subMaterial->GetShaderItem();
IRenderShaderResources* pSHRes = shaderItem.m_pShaderResources;
if (pSHRes)
{
// Iterate through each texture in the material
for ( auto& iter : *(pSHRes->GetTexturesResourceMap()) )
{
SEfResTexture* pTextureRes = &(iter.second);
uint16 textureSlot = iter.first;
uint32 textureFlags = FT_DONT_STREAM;
if (textureSlot == EFTT_SMOOTHNESS || textureSlot == EFTT_SECOND_SMOOTHNESS)
{
textureFlags |= FT_ALPHA;
}
// Calling load texture here will not actually re-create/re-load an existing texture. It will simply toggle streaming off
ITexture* texture = gEnv->pRenderer->EF_LoadTexture(pTextureRes->m_Name.c_str(), textureFlags);
// Call release to decrement the ref count, otherwise the texture will leak when switching between maps
if (texture)
{
texture->Release();
}
}
}
}
}
}
void CMatInfo::RequestTexturesLoading(const float fMipFactor)
{
PrecacheTextures(fMipFactor, FPR_STARTLOADING, false);
}
void CMatInfo::PrecacheTextures(const float fMipFactor, const int nFlags, bool bFullUpdate)
{
SStreamingPredictionZone& rZone = m_streamZoneInfo[bFullUpdate ? 1 : 0];
int bHighPriority = (nFlags & FPR_HIGHPRIORITY) != 0;
rZone.fMinMipFactor = min(rZone.fMinMipFactor, fMipFactor);
rZone.bHighPriority |= bHighPriority;
int nRoundId = bFullUpdate ? GetObjManager()->GetUpdateStreamingPrioriryRoundIdFast() : GetObjManager()->GetUpdateStreamingPrioriryRoundId();
// TODO: fix fast update
if (rZone.nRoundId != nRoundId)
{
int nCurrentFlags = Get3DEngine()->IsShadersSyncLoad() ? FPR_SYNCRONOUS : 0;
nCurrentFlags |= bFullUpdate ? FPR_SINGLE_FRAME_PRIORITY_UPDATE : 0;
SShaderItem& rSI = m_shaderItem;
if (rSI.m_pShader && rSI.m_pShaderResources && !(rSI.m_pShader->GetFlags() & EF_NODRAW))
{
if (rZone.nRoundId == (nRoundId - 1))
{
nCurrentFlags |= rZone.bHighPriority ? FPR_HIGHPRIORITY : 0;
GetRenderer()->EF_PrecacheResource(&rSI, rZone.fMinMipFactor, 0, nCurrentFlags, nRoundId, 1); // accumulated value is valid
}
else
{
nCurrentFlags |= (nFlags & FPR_HIGHPRIORITY);
GetRenderer()->EF_PrecacheResource(&rSI, fMipFactor, 0, nCurrentFlags, nRoundId, 1); // accumulated value is not valid, pass current value
}
}
rZone.nRoundId = nRoundId;
rZone.fMinMipFactor = fMipFactor;
rZone.bHighPriority = bHighPriority;
}
}
void CMatInfo::PrecacheChunkTextures(const float fMipFactorDef, const int nFlags, CRenderChunk* pRenderChunk, bool bFullUpdate)
{
// FUNCTION_PROFILER_3DENGINE;
CMatInfo* pMaterial = this;
if (pRenderChunk && pRenderChunk->pRE && pRenderChunk->nNumIndices && pRenderChunk->nNumVerts)
{ // chunk is defined and have valid geometry
if (pRenderChunk->m_nMatID < (uint16)m_subMtls.size())
{
pMaterial = (CMatInfo*)m_subMtls[pRenderChunk->m_nMatID];
}
if (pMaterial != NULL)
{
float fMipFactor = GetCVars()->e_StreamPredictionTexelDensity ? (fMipFactorDef * pRenderChunk->m_texelAreaDensity) : fMipFactorDef;
pMaterial->PrecacheTextures(fMipFactor, nFlags, bFullUpdate);
}
}
else if (!pRenderChunk)
{ // chunk is not set - load all sub-materials
float fMipFactor = fMipFactorDef;
PrecacheTextures(fMipFactor, nFlags, bFullUpdate);
for (int nSubMtl = 0; nSubMtl < (int)m_subMtls.size(); ++nSubMtl)
{
pMaterial = m_subMtls[nSubMtl];
if (pMaterial != NULL)
{
pMaterial->PrecacheTextures(fMipFactor, nFlags, bFullUpdate);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
int CMatInfo::GetTextureMemoryUsage(ICrySizer* pSizer, int nSubMtlSlot)
{
int textureSize = 0;
std::set<ITexture*> used;
int nSlotStart = 0;
int nSlotEnd = (int)m_subMtls.size();
if (nSubMtlSlot >= 0)
{
nSlotStart = nSubMtlSlot;
nSlotEnd = nSubMtlSlot + 1;
}
if (nSlotEnd >= (int)m_subMtls.size())
{
nSlotEnd = (int)m_subMtls.size();
}
if (nSlotEnd == 0)
{
nSlotEnd = 1;
}
for (int i = nSlotStart; i < nSlotEnd; i++)
{
IRenderShaderResources* pRes = m_shaderItem.m_pShaderResources;
if (i < (int)m_subMtls.size() && m_subMtls[i] != NULL && (m_Flags & MTL_FLAG_MULTI_SUBMTL))
{
SShaderItem shaderItem = m_subMtls[i]->m_shaderItem;
if (!shaderItem.m_pShaderResources)
{
continue;
}
pRes = shaderItem.m_pShaderResources;
}
if (!pRes)
{
continue;
}
for ( auto& iter : *(pRes->GetTexturesResourceMap()) )
{
SEfResTexture* pResTexure = &(iter.second);
if (!pResTexure->m_Sampler.m_pITex)
{
continue;
}
ITexture* pTexture = pResTexure->m_Sampler.m_pITex;
if (!pTexture)
{
continue;
}
if (used.find(pTexture) != used.end()) // Already used in size calculation.
{
continue;
}
used.insert(pTexture);
int nTexSize = pTexture->GetDataSize();
textureSize += nTexSize;
if (pSizer)
{
pSizer->AddObject(pTexture, nTexSize);
}
}
}
return textureSize;
}
void CMatInfo::SetKeepLowResSysCopyForDiffTex()
{
int nSlotStart = 0;
int nSlotEnd = (int)m_subMtls.size();
if (nSlotEnd == 0)
{
nSlotEnd = 1;
}
for (int i = nSlotStart; i < nSlotEnd; i++)
{
IRenderShaderResources* pRes = m_shaderItem.m_pShaderResources;
if (i < (int)m_subMtls.size() && m_subMtls[i] != NULL && (m_Flags & MTL_FLAG_MULTI_SUBMTL))
{
SShaderItem shaderItem = m_subMtls[i]->m_shaderItem;
if (!shaderItem.m_pShaderResources)
{
continue;
}
pRes = shaderItem.m_pShaderResources;
}
if (!pRes)
{
continue;
}
{
SEfResTexture* pResTexure = pRes->GetTextureResource(EFTT_DIFFUSE);
if (!pResTexure || !pResTexure->m_Sampler.m_pITex)
{
continue;
}
ITexture* pTexture = pResTexure->m_Sampler.m_pITex;
if (!pTexture)
{
continue;
}
pTexture->SetKeepSystemCopy(true);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::SetMaterialLinkName([[maybe_unused]] const char* name)
{
#ifdef SUPPORT_MATERIAL_EDITING
if (name)
{
m_sMaterialLinkName = name;
}
else
{
m_sMaterialLinkName.clear();
}
#endif
}
//////////////////////////////////////////////////////////////////////////
const char* CMatInfo::GetMaterialLinkName() const
{
#ifdef SUPPORT_MATERIAL_EDITING
return m_sMaterialLinkName.c_str();
#else
CryFatalError("CMatInfo::GetMaterialLinkName not supported on this platform");
return "";
#endif
}
//////////////////////////////////////////////////////////////////////////
CryCriticalSection& CMatInfo::GetSubMaterialResizeLock()
{
static CryCriticalSection s_sSubMaterialResizeLock;
return s_sSubMaterialResizeLock;
}
//////////////////////////////////////////////////////////////////////////
void CMatInfo::UpdateShaderItems()
{
IRenderer* pRenderer = gEnv->pRenderer;
pRenderer->UpdateShaderItem(&m_shaderItem, this);
for (int i = 0; i < (int)m_subMtls.size(); ++i)
{
CMatInfo* pSubMaterial = m_subMtls[i];
if (pSubMaterial)
{
pRenderer->UpdateShaderItem(&pSubMaterial->m_shaderItem, this);
}
}
}
void CMatInfo::RefreshShaderResourceConstants()
{
IRenderer* pRenderer = gEnv->pRenderer;
pRenderer->RefreshShaderResourceConstants(&m_shaderItem, this);
for (int i = 0; i < (int)m_subMtls.size(); ++i)
{
CMatInfo* pSubMaterial = m_subMtls[i];
if (pSubMaterial)
{
pRenderer->RefreshShaderResourceConstants(&pSubMaterial->m_shaderItem, this);
}
}
}