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.
1575 lines
52 KiB
C++
1575 lines
52 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.
|
|
|
|
// Description : Material Manager Implementation
|
|
|
|
|
|
#include "Cry3DEngine_precompiled.h"
|
|
#include "MatMan.h"
|
|
#include "3dEngine.h"
|
|
#include "ObjMan.h"
|
|
#include "IRenderer.h"
|
|
#include "SurfaceTypeManager.h"
|
|
#include "CGFContent.h"
|
|
#include <CryPath.h>
|
|
#include <IResourceManager.h>
|
|
#include "MaterialUtils.h" // from crycommon
|
|
#include <Pak/CryPakUtils.h>
|
|
#include <AzFramework/Asset/AssetSystemBus.h>
|
|
|
|
#define MATERIAL_EXT ".mtl"
|
|
#define MATERIAL_NODRAW "nodraw"
|
|
|
|
#define MATERIAL_DECALS_FOLDER "Materials/Decals"
|
|
#define MATERIAL_DECALS_SEARCH_WILDCARD "*.mtl"
|
|
#define MTL_LEVEL_CACHE_PAK "mtl.pak"
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
struct MaterialHelpers CMatMan::s_materialHelpers;
|
|
|
|
int CMatMan::e_sketch_mode = 0;
|
|
int CMatMan::e_pre_sketch_spec = 0;
|
|
int CMatMan::e_texeldensity = 0;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Default textures declarations
|
|
//------------------------------------------------------------------------------
|
|
#if !defined(_RELEASE)
|
|
// Texture names to be used for error / process loading indications
|
|
static const char* szReplaceMe = "EngineAssets/TextureMsg/ReplaceMe.tif";
|
|
static const char* szTextureCompiling = "EngineAssets/TextureMsg/TextureCompiling.tif";
|
|
static const char* szShaderCompiling = "EngineAssets/TextureMsg/ShaderCompiling.tif";
|
|
static const char* szGeomNotBreakable = "EngineAssets/TextureMsg/GeomNotBreakable.tif";
|
|
#else
|
|
// Some of the textures here will direct to the regular DefaultSolids_diff to prevent eye catching
|
|
// bug textures in release mode
|
|
static const char* szReplaceMe = "EngineAssets/TextureMsg/ReplaceMeRelease.tif";
|
|
static const char* szTextureCompiling = "EngineAssets/TextureMsg/DefaultSolids_diff.tif";
|
|
static const char* szShaderCompiling = "EngineAssets/TextureMsg/DefaultSolids_diff.tif";
|
|
static const char* szGeomNotBreakable = "EngineAssets/TextureMsg/ReplaceMeRelease.tif";
|
|
#endif
|
|
|
|
static const char* szDefaultSolid = "EngineAssets/TextureMsg/DefaultSolids_diff.tif";
|
|
static const char* szNormalDefault = "EngineAssets/Textures/white_ddn.dds";
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
static void OnSketchModeChange(ICVar* pVar)
|
|
{
|
|
int mode = pVar->GetIVal();
|
|
((CMatMan*)gEnv->p3DEngine->GetMaterialManager())->SetSketchMode(mode);
|
|
}
|
|
|
|
static void OnDebugTexelDensityChange(ICVar* pVar)
|
|
{
|
|
int mode = pVar->GetIVal();
|
|
((CMatMan*)gEnv->p3DEngine->GetMaterialManager())->SetTexelDensityDebug(mode);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CMatMan::CMatMan()
|
|
{
|
|
m_bInitialized = false;
|
|
m_bLoadSurfaceTypesInInit = true;
|
|
m_pListener = NULL;
|
|
m_pDefaultMtl = NULL;
|
|
m_pDefaultTerrainLayersMtl = NULL;
|
|
m_pDefaultLayersMtl = NULL;
|
|
m_pDefaultHelperMtl = NULL;
|
|
m_pNoDrawMtl = NULL;
|
|
|
|
m_pSurfaceTypeManager = new CSurfaceTypeManager(GetSystem());
|
|
|
|
REGISTER_CVAR_CB(e_sketch_mode, 0, VF_CHEAT, "Enables Sketch mode drawing", OnSketchModeChange);
|
|
REGISTER_CVAR_CB(e_texeldensity, 0, VF_CHEAT,
|
|
"Enables texel density debug\n"
|
|
" 1: Objects texel density\n"
|
|
" 2: Objects texel density with colored mipmaps\n"
|
|
" 3: Terrain texel density\n"
|
|
" 4: Terrain texel density with colored mipmaps\n",
|
|
OnDebugTexelDensityChange);
|
|
|
|
m_pXmlParser = GetISystem()->GetXmlUtils()->CreateXmlParser();
|
|
|
|
// Connect for LegacyAssetEventBus::Handler
|
|
BusConnect(AZ_CRC("mtl", 0xb01910e0));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CMatMan::~CMatMan()
|
|
{
|
|
delete m_pSurfaceTypeManager;
|
|
int nNotUsed = 0, nNotUsedParents = 0;
|
|
|
|
m_pDefaultMtl = nullptr;
|
|
m_pDefaultTerrainLayersMtl = nullptr;
|
|
m_pDefaultLayersMtl = nullptr;
|
|
m_pDefaultHelperMtl = nullptr;
|
|
m_pNoDrawMtl = nullptr;
|
|
|
|
if (nNotUsed)
|
|
{
|
|
PrintMessage("Warning: CMatMan::~CMatMan: %d(%d) of %" PRISIZE_T " materials was not used in level",
|
|
nNotUsedParents, nNotUsed, m_mtlNameMap.size());
|
|
}
|
|
|
|
|
|
// Disconnect for LegacyAssetEventBus::Handler
|
|
BusDisconnect();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
AZStd::string CMatMan::UnifyName(const char* sMtlName) const
|
|
{
|
|
if (strlen(sMtlName) > AZ_MAX_PATH_LEN)
|
|
{
|
|
AZ_Error("Rendering", false, "Error attempting to generate material identifier from the input '%s'. The length of the string exceeds the maximum path length. If you are using script canvas or lua to find or load a material, ensure you are using a valid path to a material as input.", sMtlName);
|
|
return AZStd::string();
|
|
}
|
|
|
|
char name[AZ_MAX_PATH_LEN];
|
|
|
|
azstrcpy(name, AZ_MAX_PATH_LEN, sMtlName);
|
|
MaterialUtils::UnifyMaterialName(name);
|
|
|
|
return name;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::CreateMaterial(const char* sMtlName, int nMtlFlags)
|
|
{
|
|
CMatInfo* pMat = new CMatInfo;
|
|
|
|
//m_mtlSet.insert( pMat );
|
|
pMat->SetName(sMtlName);
|
|
pMat->SetFlags(nMtlFlags | pMat->GetFlags());
|
|
|
|
if (!(nMtlFlags & MTL_FLAG_PURE_CHILD))
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
m_mtlNameMap[ UnifyName(sMtlName) ] = pMat;
|
|
}
|
|
|
|
if (nMtlFlags & MTL_FLAG_NON_REMOVABLE)
|
|
{
|
|
// Add reference to this material to prevent its deletion.
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_nonRemovablesMutex);
|
|
m_nonRemovables.push_back(pMat);
|
|
}
|
|
return pMat;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// A placeholder material while the original is loading.
|
|
// Add more edge case handling for various material types if required
|
|
_smart_ptr<IMaterial> CMatMan::CreateMaterialPlaceholder(const char* materialName, int nMtlFlags, const char* textureName, _smart_ptr<IMaterial> existingMtl)
|
|
{
|
|
SInputShaderResources sr;
|
|
SShaderItem si;
|
|
|
|
sr.m_LMaterial.m_Opacity = 1;
|
|
sr.m_LMaterial.m_Diffuse.set(1, 1, 1, 1);
|
|
sr.m_LMaterial.m_Specular.set(0, 0, 0, 0);
|
|
|
|
// This will create texture data insertion to the table for the diffuse slot
|
|
sr.m_TexturesResourcesMap[EFTT_DIFFUSE].m_Name = textureName;
|
|
|
|
if (nMtlFlags & MTL_FLAG_IS_TERRAIN)
|
|
si = GetRenderer()->EF_LoadShaderItem("Terrain.Layer", true, 0, &sr);
|
|
else if (nMtlFlags & MTL_FLAG_IS_SKY)
|
|
si = GetRenderer()->EF_LoadShaderItem("SkyHDR", true, 0, &sr);
|
|
else
|
|
si = GetRenderer()->EF_LoadShaderItem("Illum", true, 0, &sr);
|
|
|
|
if (si.m_pShaderResources)
|
|
si.m_pShaderResources->SetMaterialName(materialName);
|
|
|
|
if (existingMtl)
|
|
{
|
|
//For existing material we need to clear sub-materials, set flags and assign the new shader item.
|
|
existingMtl->SetSubMtlCount(0);
|
|
existingMtl->SetFlags(nMtlFlags);
|
|
existingMtl->AssignShaderItem(si);
|
|
|
|
//Note: All PURE_CHILD materials are sub-materials, but not all sub-materials are PURE_CHILD.
|
|
//You can have one sub-material that shares some properties with another sub-material in the same parent, and overrides other properties. A sub-material that does this is not a pure child.
|
|
//But since it is seldom used, this should cover most cases when we want to know whether the material is a root.
|
|
if (!(nMtlFlags & MTL_FLAG_PURE_CHILD))
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
m_mtlNameMap[UnifyName(materialName)] = existingMtl;
|
|
}
|
|
|
|
return existingMtl;
|
|
}
|
|
else
|
|
{
|
|
_smart_ptr<IMaterial> pMtl = CreateMaterial(materialName);
|
|
pMtl->AssignShaderItem(si);
|
|
return pMtl;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::NotifyCreateMaterial(_smart_ptr<IMaterial> pMtl)
|
|
{
|
|
if (m_pListener)
|
|
{
|
|
m_pListener->OnCreateMaterial(pMtl);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CMatMan::Unregister(_smart_ptr<IMaterial> pMat, bool deleteEditorMaterial)
|
|
{
|
|
assert(pMat);
|
|
if (m_pListener && deleteEditorMaterial)
|
|
{
|
|
m_pListener->OnDeleteMaterial(pMat);
|
|
}
|
|
|
|
if (!(pMat->GetFlags() & MTL_FLAG_PURE_CHILD))
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
|
|
AZStd::string unifiedName = UnifyName(pMat->GetName());
|
|
m_pendingMaterialLoads.erase(unifiedName);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::RenameMaterial(_smart_ptr<IMaterial> pMtl, const char* sNewName)
|
|
{
|
|
assert(pMtl);
|
|
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
const char* sName = pMtl->GetName();
|
|
AZStd::unique_ptr<ManualResetEvent> resetEvent = nullptr;
|
|
|
|
if (*sName != '\0')
|
|
{
|
|
AZStd::string unifiedName = UnifyName(pMtl->GetName());
|
|
|
|
if (m_pendingMaterialLoads.find(unifiedName) != m_pendingMaterialLoads.end())
|
|
{
|
|
resetEvent = std::move(m_pendingMaterialLoads[unifiedName]);
|
|
m_pendingMaterialLoads.erase(unifiedName);
|
|
}
|
|
|
|
m_mtlNameMap.erase(unifiedName);
|
|
}
|
|
|
|
pMtl->SetName(sNewName);
|
|
AZStd::string newUnifiedName = UnifyName(sNewName);
|
|
|
|
m_mtlNameMap[newUnifiedName] = pMtl;
|
|
if (resetEvent != nullptr)
|
|
{
|
|
m_pendingMaterialLoads[newUnifiedName] = std::move(resetEvent);
|
|
}
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::FindMaterial(const char* sMtlName) const
|
|
{
|
|
AZStd::string name = UnifyName(sMtlName);
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
|
|
MtlNameMap::const_iterator it = m_mtlNameMap.find(name);
|
|
|
|
if (it == m_mtlNameMap.end())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//------------------------------------------------------------------------------
|
|
_smart_ptr<IMaterial> CMatMan::LoadMaterial(const char* sMtlName, bool bMakeIfNotFound, bool bNonremovable, unsigned long nLoadingFlags)
|
|
{
|
|
return LoadMaterialInternal(sMtlName, bMakeIfNotFound, bNonremovable, nLoadingFlags);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//------------------------------------------------------------------------------
|
|
_smart_ptr<IMaterial> CMatMan::LoadMaterialInternal(const char* sMtlName, bool bMakeIfNotFound, bool bNonremovable, unsigned long nLoadingFlags)
|
|
{
|
|
if (!m_bInitialized)
|
|
{
|
|
InitDefaults();
|
|
}
|
|
|
|
if (m_pDefaultMtl && GetCVars()->e_StatObjPreload == 2)
|
|
{
|
|
return m_pDefaultMtl;
|
|
}
|
|
|
|
AZStd::string name = UnifyName(sMtlName);
|
|
_smart_ptr<IMaterial> pMtl = nullptr;
|
|
|
|
UniqueManualEvent uniqueManualEvent = CheckMaterialCache(name, pMtl);
|
|
|
|
if (pMtl != nullptr)
|
|
{
|
|
return pMtl;
|
|
}
|
|
|
|
// Failed to retrieve from cache and failed to get 'permission' to safely load, abort load
|
|
if (!uniqueManualEvent.HasControl())
|
|
{
|
|
if (bMakeIfNotFound)
|
|
{
|
|
pMtl = CreateMaterialPlaceholder(name.c_str(), nLoadingFlags, szDefaultSolid);
|
|
return pMtl;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
LOADING_TIME_PROFILE_SECTION; // Only profile actually loading of the material.
|
|
|
|
CRY_DEFINE_ASSET_SCOPE("Material", sMtlName);
|
|
|
|
_smart_ptr<IMaterial> materialRawPointer = pMtl;
|
|
|
|
AZStd::string filename = name;
|
|
auto extPos = filename.find('.');
|
|
|
|
if (extPos == AZStd::string::npos)
|
|
{
|
|
filename += MATERIAL_EXT;
|
|
}
|
|
|
|
|
|
bool fileExists = AZ::IO::FileIOBase::GetInstance()->Exists(filename.c_str());
|
|
if (!fileExists)
|
|
{
|
|
// If the material doesn't exist check if it's queued or being compiled. If so it means the file will become available shortly (as
|
|
// GetAssetStatus will push it to the top of the queue) and hot loading will take care of the file. If it's in a broken state,
|
|
// remove it as if loading failed.
|
|
AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown;
|
|
AzFramework::AssetSystemRequestBus::BroadcastResult(status, &AzFramework::AssetSystemRequestBus::Events::GetAssetStatus, filename);
|
|
|
|
switch (status)
|
|
{
|
|
case AzFramework::AssetSystem::AssetStatus_Queued:
|
|
// Fall through
|
|
case AzFramework::AssetSystem::AssetStatus_Compiling:
|
|
{
|
|
AZStd::string unifiedName = UnifyName(filename.c_str());
|
|
pMtl = CreateMaterialPlaceholder(unifiedName.c_str(), nLoadingFlags, szDefaultSolid );
|
|
break;
|
|
}
|
|
|
|
case AzFramework::AssetSystem::AssetStatus_Compiled:
|
|
// If the materials compiled it could be that between the check if it exists and getting the status it completed compilation.
|
|
// In this case, check the status again and load as normal if found, otherwise consider it an error.
|
|
if (AZ::IO::FileIOBase::GetInstance()->Exists(filename.c_str()))
|
|
{
|
|
fileExists = true;
|
|
break;
|
|
}
|
|
// else fall through
|
|
|
|
case AzFramework::AssetSystem::AssetStatus_Unknown:
|
|
// Fall through
|
|
case AzFramework::AssetSystem::AssetStatus_Missing:
|
|
// Fall through
|
|
case AzFramework::AssetSystem::AssetStatus_Failed:
|
|
// Fall through
|
|
default:
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
uniqueManualEvent.Set();
|
|
m_pendingMaterialLoads.erase(name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fileExists)
|
|
{
|
|
// If the material already exists load it from the cache. If there's a build in flight the material will get reloaded
|
|
// when building finishes and if it's not in flight anymore the latest material will be loaded.
|
|
XmlNodeRef mtlNode = GetSystem()->LoadXmlFromFile(filename.c_str());
|
|
|
|
if (mtlNode)
|
|
{
|
|
pMtl = MakeMaterialFromXml(name, mtlNode, false, 0, nullptr, nLoadingFlags);
|
|
|
|
if (pMtl && e_sketch_mode != 0)
|
|
{
|
|
((CMatInfo*)pMtl.get())->SetSketchMode(e_sketch_mode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Loading has failed so evict from pending list.
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
uniqueManualEvent.Set();
|
|
m_pendingMaterialLoads.erase(name);
|
|
}
|
|
}
|
|
|
|
if (bNonremovable && pMtl)
|
|
{ // mark as non-removable material on specific cases (probes..)
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_nonRemovablesMutex);
|
|
m_nonRemovables.push_back((CMatInfo*)pMtl.get());
|
|
}
|
|
|
|
if (!pMtl && bMakeIfNotFound)
|
|
{
|
|
pMtl = CreateMaterialPlaceholder(name.c_str(), nLoadingFlags, szDefaultSolid);
|
|
}
|
|
|
|
return pMtl;
|
|
}
|
|
|
|
//! Let the first thread load the material, block the rest until its done so they can just use the cached version
|
|
template<typename T>
|
|
UniqueManualEvent CMatMan::CheckMaterialCache(const AZStd::string& name, T& cachedMaterial)
|
|
{
|
|
bool hasControl = false;
|
|
ManualResetEvent* manualResetEvent;
|
|
|
|
AZStd::unique_lock<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
|
|
auto iterator = m_pendingMaterialLoads.find(name);
|
|
|
|
if (iterator != m_pendingMaterialLoads.end())
|
|
{
|
|
manualResetEvent = iterator->second.get();
|
|
}
|
|
else
|
|
{
|
|
// Event not found, create one
|
|
hasControl = true;
|
|
manualResetEvent = new ManualResetEvent();
|
|
m_pendingMaterialLoads.emplace(name, AZStd::unique_ptr<ManualResetEvent>(manualResetEvent));
|
|
}
|
|
|
|
if (!hasControl)
|
|
{
|
|
manualResetEvent->Wait();
|
|
|
|
MtlNameMap::const_iterator it = m_mtlNameMap.find(name);
|
|
|
|
if (it != m_mtlNameMap.end())
|
|
{
|
|
cachedMaterial = it->second;
|
|
}
|
|
else
|
|
{
|
|
cachedMaterial = nullptr;
|
|
}
|
|
}
|
|
|
|
return UniqueManualEvent(manualResetEvent, hasControl);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::MakeMaterialFromXml(const AZStd::string& sMtlName, XmlNodeRef node, bool bForcePureChild, uint16 sortPrio /*= 0*/, _smart_ptr<IMaterial> pExistingMtl /*= 0*/, unsigned long nLoadingFlags /*= 0*/, _smart_ptr<IMaterial> pParentMtl /*= 0*/)
|
|
{
|
|
int mtlFlags = 0;
|
|
CryFixedStringT<128> shaderName;
|
|
uint64 nShaderGenMask = 0;
|
|
SInputShaderResources sr;
|
|
|
|
assert(node != 0);
|
|
|
|
sr.m_SortPrio = sortPrio;
|
|
|
|
// Loading
|
|
node->getAttr("MtlFlags", mtlFlags);
|
|
mtlFlags &= (MTL_FLAGS_SAVE_MASK); // Clean flags that are not supposed to be save/loaded.
|
|
if (bForcePureChild)
|
|
{
|
|
mtlFlags |= MTL_FLAG_PURE_CHILD;
|
|
}
|
|
|
|
_smart_ptr<IMaterial> pMtl = pExistingMtl;
|
|
if (!pMtl)
|
|
{
|
|
pMtl = CreateMaterial(sMtlName.c_str(), mtlFlags);
|
|
}
|
|
else
|
|
{
|
|
pMtl->SetFlags(mtlFlags | pMtl->GetFlags());
|
|
pMtl->SetDirty(false);
|
|
}
|
|
|
|
uint32 dccMaterialHash = 0;
|
|
node->getAttr("DccMaterialHash", dccMaterialHash);
|
|
pMtl->SetDccMaterialHash(dccMaterialHash);
|
|
|
|
if (!(mtlFlags & MTL_FLAG_MULTI_SUBMTL))
|
|
{
|
|
shaderName = node->getAttr("Shader");
|
|
|
|
if (!(mtlFlags & MTL_64BIT_SHADERGENMASK))
|
|
{
|
|
uint32 nShaderGenMask32 = 0;
|
|
node->getAttr("GenMask", nShaderGenMask32);
|
|
nShaderGenMask = nShaderGenMask32;
|
|
|
|
// Remap 32bit flags to 64 bit version
|
|
nShaderGenMask = GetRenderer()->EF_GetRemapedShaderMaskGen((const char*) shaderName, nShaderGenMask);
|
|
mtlFlags |= MTL_64BIT_SHADERGENMASK;
|
|
}
|
|
else
|
|
{
|
|
node->getAttr("GenMask", nShaderGenMask);
|
|
}
|
|
|
|
if (node->haveAttr("StringGenMask"))
|
|
{
|
|
const char* pszShaderGenMask = node->getAttr("StringGenMask");
|
|
nShaderGenMask = GetRenderer()->EF_GetShaderGlobalMaskGenFromString((const char*) shaderName, pszShaderGenMask, nShaderGenMask); // get common mask gen
|
|
}
|
|
else
|
|
{
|
|
// version doesn't have string gen mask yet ? Remap flags if needed
|
|
nShaderGenMask = GetRenderer()->EF_GetRemapedShaderMaskGen((const char*) shaderName, nShaderGenMask, ((mtlFlags & MTL_64BIT_SHADERGENMASK) != 0));
|
|
}
|
|
mtlFlags |= MTL_64BIT_SHADERGENMASK;
|
|
|
|
const char* surfaceType = node->getAttr("SurfaceType");
|
|
pMtl->SetSurfaceType(surfaceType);
|
|
|
|
if (azstricmp(shaderName, "nodraw") == 0)
|
|
{
|
|
mtlFlags |= MTL_FLAG_NODRAW;
|
|
}
|
|
|
|
pMtl->SetFlags(mtlFlags | pMtl->GetFlags());
|
|
|
|
s_materialHelpers.SetLightingFromXml(sr, node);
|
|
s_materialHelpers.SetTexturesFromXml(sr, node);
|
|
s_materialHelpers.MigrateXmlLegacyData(sr, node);
|
|
|
|
// Next warn about textures with drive letter in them
|
|
for (auto &iter : sr.m_TexturesResourcesMap )
|
|
{
|
|
SEfResTexture* pTexture = &(iter.second);
|
|
const char* name = pTexture->m_Name;
|
|
if (name && (strchr(name, ':') != nullptr))
|
|
{
|
|
CryLog("Invalid texture '%s' found in material '%s'", name, sMtlName.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Check if we have a link name
|
|
//////////////////////////////////////////////////////////////////////////
|
|
XmlNodeRef pLinkName = node->findChild("MaterialLinkName");
|
|
if (pLinkName)
|
|
{
|
|
const char* szLinkName = pLinkName->getAttr("name");
|
|
pMtl->SetMaterialLinkName(szLinkName);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Check if we have vertex deform.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
s_materialHelpers.SetVertexDeformFromXml(sr, node);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Load public parameters.
|
|
XmlNodeRef publicVarsNode = node->findChild("PublicParams");
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Reload shader item with new resources and shader.
|
|
if (!(mtlFlags & MTL_FLAG_MULTI_SUBMTL))
|
|
{
|
|
sr.m_szMaterialName = sMtlName.c_str();
|
|
|
|
LoadMaterialShader(pMtl, pParentMtl, shaderName.c_str(), nShaderGenMask, sr, publicVarsNode);
|
|
pMtl->SetShaderName(shaderName);
|
|
}
|
|
else
|
|
{
|
|
//Release any shader item if assigned for material group
|
|
pMtl->ReleaseCurrentShaderItem();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Load material layers data
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
if (pMtl && pMtl->GetShaderItem().m_pShader && pMtl->GetShaderItem().m_pShaderResources)
|
|
{
|
|
XmlNodeRef pMtlLayersNode = node->findChild("MaterialLayers");
|
|
if (pMtlLayersNode)
|
|
{
|
|
int nLayerCount = min((int) MTL_LAYER_MAX_SLOTS, (int) pMtlLayersNode->getChildCount());
|
|
if (nLayerCount)
|
|
{
|
|
uint8 nMaterialLayerFlags = 0;
|
|
|
|
pMtl->SetLayerCount(nLayerCount);
|
|
for (int l(0); l < nLayerCount; ++l)
|
|
{
|
|
XmlNodeRef pLayerNode = pMtlLayersNode->getChild(l);
|
|
if (pLayerNode)
|
|
{
|
|
if (const char* pszShaderName = pLayerNode->getAttr("Name"))
|
|
{
|
|
bool bNoDraw = false;
|
|
pLayerNode->getAttr("NoDraw", bNoDraw);
|
|
|
|
uint8 nLayerFlags = 0;
|
|
if (bNoDraw)
|
|
{
|
|
nLayerFlags |= MTL_LAYER_USAGE_NODRAW;
|
|
|
|
if (!azstricmp(pszShaderName, "frozenlayerwip"))
|
|
{
|
|
nMaterialLayerFlags |= MTL_LAYER_FROZEN;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nLayerFlags &= ~MTL_LAYER_USAGE_NODRAW;
|
|
}
|
|
|
|
bool bFadeOut = false;
|
|
pLayerNode->getAttr("FadeOut", bFadeOut);
|
|
if (bFadeOut)
|
|
{
|
|
nLayerFlags |= MTL_LAYER_USAGE_FADEOUT;
|
|
}
|
|
else
|
|
{
|
|
nLayerFlags &= ~MTL_LAYER_USAGE_FADEOUT;
|
|
}
|
|
|
|
XmlNodeRef pPublicsParamsNode = pLayerNode->findChild("PublicParams");
|
|
sr.m_szMaterialName = sMtlName.c_str();
|
|
LoadMaterialLayerSlot(l, pMtl, pszShaderName, sr, pPublicsParamsNode, nLayerFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
SShaderItem& pShaderItemBase = pMtl->GetShaderItem();
|
|
if (pShaderItemBase.m_pShaderResources)
|
|
{
|
|
pShaderItemBase.m_pShaderResources->SetMtlLayerNoDrawFlags(nMaterialLayerFlags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Serialize sub materials.
|
|
XmlNodeRef childsNode = node->findChild("SubMaterials");
|
|
if (childsNode)
|
|
{
|
|
int nSubMtls = childsNode->getChildCount();
|
|
pMtl->SetSubMtlCount(nSubMtls);
|
|
for (int i = 0; i < nSubMtls; i++)
|
|
{
|
|
XmlNodeRef mtlNode = childsNode->getChild(i);
|
|
if (mtlNode->isTag("Material"))
|
|
{
|
|
const char* name = mtlNode->getAttr("Name");
|
|
_smart_ptr<IMaterial> pChildMtl = MakeMaterialFromXml(name, mtlNode, true, uint16(nSubMtls - i - 1), 0, nLoadingFlags, pMtl);
|
|
if (pChildMtl)
|
|
{
|
|
pMtl->SetSubMtl(i, pChildMtl);
|
|
}
|
|
else
|
|
{
|
|
pMtl->SetSubMtl(i, m_pDefaultMtl);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char* name = mtlNode->getAttr("Name");
|
|
if (name[0])
|
|
{
|
|
_smart_ptr<IMaterial> pChildMtl = LoadMaterial(name, true, false, nLoadingFlags);
|
|
if (pChildMtl)
|
|
{
|
|
pMtl->SetSubMtl(i, pChildMtl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
NotifyCreateMaterial(pMtl);
|
|
return pMtl;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CMatMan::LoadMaterialShader(_smart_ptr<IMaterial> pMtl, _smart_ptr<IMaterial> pParentMtl, const char* sShader, uint64 nShaderGenMask, SInputShaderResources& sr, XmlNodeRef& publicsNode)
|
|
{
|
|
// Mark material invalid by default.
|
|
sr.m_ResFlags = pMtl->GetFlags();
|
|
|
|
// Set public params.
|
|
if (publicsNode)
|
|
{
|
|
// Copy public params from the shader.
|
|
//sr.m_ShaderParams = shaderItem.m_pShader->GetPublicParams();
|
|
// Parse public parameters, and assign them to source shader resources.
|
|
ParsePublicParams(sr, publicsNode);
|
|
//shaderItem.m_pShaderResources->SetShaderParams(&sr, shaderItem.m_pShader);
|
|
}
|
|
|
|
SShaderItem shaderItem = gEnv->pRenderer->EF_LoadShaderItem(sShader, false, 0, &sr, nShaderGenMask);
|
|
if (!shaderItem.m_pShader || (shaderItem.m_pShader->GetFlags() & EF_NOTFOUND) != 0)
|
|
{
|
|
Warning("Failed to load shader \"%s\" in material \"%s\"", sShader, pMtl->GetName());
|
|
if (!shaderItem.m_pShader)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
pMtl->AssignShaderItem(shaderItem);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CMatMan::LoadMaterialLayerSlot(uint32 nSlot, _smart_ptr<IMaterial> pMtl, const char* szShaderName, SInputShaderResources& pBaseResources, XmlNodeRef& pPublicsNode, uint8 nLayerFlags)
|
|
{
|
|
if (!pMtl || pMtl->GetLayer(nSlot) || !pPublicsNode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// need to handle no draw case
|
|
if (azstricmp(szShaderName, "nodraw") == 0)
|
|
{
|
|
// no shader = skip layer
|
|
return false;
|
|
}
|
|
|
|
// Get base material/shaderItem info
|
|
SInputShaderResources pInputResources;
|
|
SShaderItem& pShaderItemBase = pMtl->GetShaderItem();
|
|
|
|
uint32 nMaskGenBase = (uint32)pShaderItemBase.m_pShader->GetGenerationMask();
|
|
SShaderGen* pShaderGenBase = pShaderItemBase.m_pShader->GetGenerationParams();
|
|
|
|
// copy diffuse and bump textures names
|
|
pInputResources.m_szMaterialName = pBaseResources.m_szMaterialName;
|
|
|
|
// The following copies the entire texture data for this slot as it did not exist in the map
|
|
if (pBaseResources.GetTextureResource(EFTT_DIFFUSE))
|
|
{
|
|
pInputResources.m_TexturesResourcesMap[EFTT_DIFFUSE].m_Name = pBaseResources.m_TexturesResourcesMap[EFTT_DIFFUSE].m_Name;
|
|
}
|
|
|
|
if (pBaseResources.GetTextureResource(EFTT_NORMALS))
|
|
{
|
|
pInputResources.m_TexturesResourcesMap[EFTT_NORMALS].m_Name = pBaseResources.m_TexturesResourcesMap[EFTT_NORMALS].m_Name;
|
|
}
|
|
|
|
// Names validity - if the texture slot doesn't exist or no name replace with default textures
|
|
if (pInputResources.m_TexturesResourcesMap[EFTT_DIFFUSE].m_Name.empty())
|
|
{
|
|
pInputResources.m_TexturesResourcesMap[EFTT_DIFFUSE].m_Name = szReplaceMe;
|
|
}
|
|
|
|
if (pInputResources.m_TexturesResourcesMap[EFTT_NORMALS].m_Name.empty())
|
|
{
|
|
pInputResources.m_TexturesResourcesMap[EFTT_NORMALS].m_Name = szNormalDefault;
|
|
}
|
|
// Load layer shader item
|
|
IShader* pNewShader = gEnv->pRenderer->EF_LoadShader(szShaderName, 0);
|
|
if (!pNewShader)
|
|
{
|
|
Warning("Failed to load material layer shader %s in Material %s", szShaderName, pMtl->GetName());
|
|
return false;
|
|
}
|
|
|
|
// mask generation for base material shader
|
|
uint32 nMaskGenLayer = 0;
|
|
SShaderGen* pShaderGenLayer = pNewShader->GetGenerationParams();
|
|
if (pShaderGenBase && pShaderGenLayer)
|
|
{
|
|
for (unsigned nLayerBit(0); nLayerBit < pShaderGenLayer->m_BitMask.size(); ++nLayerBit)
|
|
{
|
|
SShaderGenBit* pLayerBit = pShaderGenLayer->m_BitMask[nLayerBit];
|
|
|
|
for (unsigned nBaseBit(0); nBaseBit < pShaderGenBase->m_BitMask.size(); ++nBaseBit)
|
|
{
|
|
SShaderGenBit* pBaseBit = pShaderGenBase->m_BitMask[nBaseBit];
|
|
|
|
// Need to check if flag name is common to both shaders (since flags values can be different), if so activate it on this layer
|
|
if (nMaskGenBase & pBaseBit->m_Mask)
|
|
{
|
|
if (!pLayerBit->m_ParamName.empty() && !pBaseBit->m_ParamName.empty())
|
|
{
|
|
if (pLayerBit->m_ParamName == pBaseBit->m_ParamName)
|
|
{
|
|
nMaskGenLayer |= pLayerBit->m_Mask;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reload with proper flags
|
|
IShader* pShader = gEnv->pRenderer->EF_LoadShader(szShaderName, 0, nMaskGenLayer);
|
|
if (!pShader)
|
|
{
|
|
Warning("Failed to load material layer shader %s in Material %s", szShaderName, pMtl->GetName());
|
|
SAFE_RELEASE(pNewShader);
|
|
return false;
|
|
}
|
|
SAFE_RELEASE(pNewShader);
|
|
|
|
// Copy public params from the shader.
|
|
//pInputResources.m_ShaderParams = pShaderItem.m_pShader->GetPublicParams();
|
|
|
|
// Copy resources from base material
|
|
SShaderItem ShaderItem(pShader, pShaderItemBase.m_pShaderResources->Clone());
|
|
|
|
ParsePublicParams(pInputResources, pPublicsNode);
|
|
|
|
// Parse public parameters, and assign them to source shader resources.
|
|
ShaderItem.m_pShaderResources->SetShaderParams(&pInputResources, ShaderItem.m_pShader);
|
|
|
|
IMaterialLayer* pCurrMtlLayer = pMtl->CreateLayer();
|
|
|
|
pCurrMtlLayer->SetFlags(nLayerFlags);
|
|
pCurrMtlLayer->SetShaderItem(pMtl, ShaderItem);
|
|
|
|
// Clone returns an instance with a refcount of 1, and SetShaderItem increments it, so
|
|
// we need to release the cloned ref.
|
|
SAFE_RELEASE(ShaderItem.m_pShaderResources);
|
|
SAFE_RELEASE(ShaderItem.m_pShader);
|
|
|
|
pMtl->SetLayer(nSlot, pCurrMtlLayer);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
static void shGetVector4(const char* buf, float v[4])
|
|
{
|
|
if (!buf)
|
|
{
|
|
return;
|
|
}
|
|
int res = azsscanf(buf, "%f,%f,%f,%f", &v[0], &v[1], &v[2], &v[3]);
|
|
assert(res);
|
|
}
|
|
|
|
void CMatMan::ParsePublicParams(SInputShaderResources& sr, XmlNodeRef paramsNode)
|
|
{
|
|
sr.m_ShaderParams.clear();
|
|
|
|
int nA = paramsNode->getNumAttributes();
|
|
if (!nA)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < nA; i++)
|
|
{
|
|
const char* key = NULL, * val = NULL;
|
|
paramsNode->getAttributeByIndex(i, &key, &val);
|
|
SShaderParam Param;
|
|
assert(val && key);
|
|
Param.m_Name = key;
|
|
Param.m_Value.m_Color[0] = Param.m_Value.m_Color[1] = Param.m_Value.m_Color[2] = Param.m_Value.m_Color[3] = 0;
|
|
shGetVector4(val, Param.m_Value.m_Color);
|
|
Param.m_Type = eType_FCOLOR;
|
|
sr.m_ShaderParams.push_back(Param);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ISurfaceType* CMatMan::GetSurfaceTypeByName(const char* sSurfaceTypeName, const char* sWhy)
|
|
{
|
|
return m_pSurfaceTypeManager->GetSurfaceTypeByName(sSurfaceTypeName, sWhy);
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int CMatMan::GetSurfaceTypeIdByName(const char* sSurfaceTypeName, const char* sWhy)
|
|
{
|
|
ISurfaceType* pSurfaceType = m_pSurfaceTypeManager->GetSurfaceTypeByName(sSurfaceTypeName, sWhy);
|
|
if (pSurfaceType)
|
|
{
|
|
return pSurfaceType->GetId();
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::GetDefaultLayersMaterial()
|
|
{
|
|
if (!m_bInitialized)
|
|
{
|
|
InitDefaults();
|
|
}
|
|
|
|
return m_pDefaultLayersMtl;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::GetDefaultHelperMaterial()
|
|
{
|
|
if (!m_bInitialized)
|
|
{
|
|
InitDefaults();
|
|
}
|
|
|
|
return m_pDefaultHelperMtl;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::GetLoadedMaterials(AZStd::vector<_smart_ptr<IMaterial>>* pData, uint32& nObjCount) const
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
nObjCount = m_mtlNameMap.size();
|
|
|
|
if (!pData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MtlNameMap::const_iterator it, end = m_mtlNameMap.end();
|
|
|
|
for (it = m_mtlNameMap.begin(); it != end; ++it)
|
|
{
|
|
_smart_ptr<IMaterial> pMat = it->second;
|
|
|
|
pData->push_back(pMat);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::CloneMaterial(_smart_ptr<IMaterial> pSrcMtl, int nSubMtl)
|
|
{
|
|
if (pSrcMtl->GetFlags() & MTL_FLAG_MULTI_SUBMTL)
|
|
{
|
|
_smart_ptr<IMaterial> pMultiMat = new CMatInfo;
|
|
|
|
//m_mtlSet.insert( pMat );
|
|
pMultiMat->SetName(pSrcMtl->GetName());
|
|
pMultiMat->SetFlags(pMultiMat->GetFlags() | MTL_FLAG_MULTI_SUBMTL);
|
|
|
|
bool bCloneAllSubMtls = nSubMtl < 0;
|
|
|
|
int nSubMtls = pSrcMtl->GetSubMtlCount();
|
|
pMultiMat->SetSubMtlCount(nSubMtls);
|
|
for (int i = 0; i < nSubMtls; i++)
|
|
{
|
|
CMatInfo* pChildSrcMtl = (CMatInfo*)pSrcMtl->GetSubMtl(i).get();
|
|
if (!pChildSrcMtl)
|
|
{
|
|
continue;
|
|
}
|
|
if (bCloneAllSubMtls)
|
|
{
|
|
pMultiMat->SetSubMtl(i, pChildSrcMtl->Clone());
|
|
}
|
|
else
|
|
{
|
|
pMultiMat->SetSubMtl(i, pChildSrcMtl);
|
|
if (i == nSubMtls)
|
|
{
|
|
// Clone this slot.
|
|
pMultiMat->SetSubMtl(i, pChildSrcMtl->Clone());
|
|
}
|
|
}
|
|
}
|
|
return pMultiMat;
|
|
}
|
|
else
|
|
{
|
|
_smart_ptr<IMaterial> pMat = reinterpret_cast<CMatInfo*>(pSrcMtl.get())->Clone();
|
|
return pMat;
|
|
}
|
|
}
|
|
|
|
void CMatMan::CopyMaterial(_smart_ptr<IMaterial> pMtlSrc, _smart_ptr<IMaterial> pMtlDest, EMaterialCopyFlags flags)
|
|
{
|
|
((CMatInfo*)pMtlSrc.get())->Copy(pMtlDest, flags);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::CloneMultiMaterial(_smart_ptr<IMaterial> pSrcMtl, const char* sSubMtlName)
|
|
{
|
|
if (pSrcMtl->GetFlags() & MTL_FLAG_MULTI_SUBMTL)
|
|
{
|
|
_smart_ptr<IMaterial> pMultiMat = new CMatInfo;
|
|
|
|
//m_mtlSet.insert( pMat );
|
|
pMultiMat->SetName(pSrcMtl->GetName());
|
|
pMultiMat->SetFlags(pMultiMat->GetFlags() | MTL_FLAG_MULTI_SUBMTL);
|
|
|
|
bool bCloneAllSubMtls = sSubMtlName == 0;
|
|
|
|
int nSubMtls = pSrcMtl->GetSubMtlCount();
|
|
pMultiMat->SetSubMtlCount(nSubMtls);
|
|
for (int i = 0; i < nSubMtls; i++)
|
|
{
|
|
CMatInfo* pChildSrcMtl = (CMatInfo*)pSrcMtl->GetSubMtl(i).get();
|
|
if (!pChildSrcMtl)
|
|
{
|
|
continue;
|
|
}
|
|
if (bCloneAllSubMtls)
|
|
{
|
|
pMultiMat->SetSubMtl(i, pChildSrcMtl->Clone());
|
|
}
|
|
else
|
|
{
|
|
pMultiMat->SetSubMtl(i, pChildSrcMtl);
|
|
if (azstricmp(pChildSrcMtl->GetName(), sSubMtlName) == 0)
|
|
{
|
|
// Clone this slot.
|
|
pMultiMat->SetSubMtl(i, pChildSrcMtl->Clone());
|
|
}
|
|
}
|
|
}
|
|
return pMultiMat;
|
|
}
|
|
else
|
|
{
|
|
return ((CMatInfo*)pSrcMtl.get())->Clone();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::DoLoadSurfaceTypesInInit(bool doLoadSurfaceTypesInInit)
|
|
{
|
|
m_bLoadSurfaceTypesInInit = doLoadSurfaceTypesInInit;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::InitDefaults()
|
|
{
|
|
if (m_bInitialized)
|
|
{
|
|
return;
|
|
}
|
|
m_bInitialized = true;
|
|
|
|
LOADING_TIME_PROFILE_SECTION;
|
|
|
|
SYNCHRONOUS_LOADING_TICK();
|
|
|
|
if (m_bLoadSurfaceTypesInInit)
|
|
{
|
|
m_pSurfaceTypeManager->LoadSurfaceTypes();
|
|
}
|
|
|
|
if (!m_pDefaultMtl)
|
|
{
|
|
// This line is REQUIRED by the buildbot testing framework to determine when tests have formally started. Please inform WillW or Morgan before changing this.
|
|
CryLogAlways("Initializing default materials...");
|
|
m_pDefaultMtl = CreateMaterialPlaceholder("Default", 0, szReplaceMe);
|
|
}
|
|
|
|
if (!m_pDefaultTerrainLayersMtl)
|
|
{
|
|
m_pDefaultTerrainLayersMtl = CreateMaterialPlaceholder("DefaultTerrainLayer", MTL_FLAG_IS_TERRAIN, szReplaceMe);
|
|
}
|
|
|
|
if (!m_pDefaultLayersMtl)
|
|
{
|
|
m_pDefaultLayersMtl = LoadMaterial("Materials/material_layers_default", false);
|
|
}
|
|
|
|
if (!m_pNoDrawMtl)
|
|
{
|
|
m_pNoDrawMtl = new CMatInfo;
|
|
m_pNoDrawMtl->SetFlags(MTL_FLAG_NODRAW);
|
|
m_pNoDrawMtl->SetName(MATERIAL_NODRAW);
|
|
SShaderItem si;
|
|
si.m_pShader = GetRenderer()->EF_LoadShader(MATERIAL_NODRAW, 0);
|
|
m_pNoDrawMtl->AssignShaderItem(si);
|
|
|
|
AZStd::string unifiedName = UnifyName(m_pNoDrawMtl->GetName());
|
|
auto* resetEvent = new ManualResetEvent();
|
|
resetEvent->Set();
|
|
|
|
m_mtlNameMap[unifiedName] = m_pNoDrawMtl;
|
|
m_pendingMaterialLoads.emplace(unifiedName, AZStd::unique_ptr<ManualResetEvent>(resetEvent));
|
|
}
|
|
|
|
if (!m_pDefaultHelperMtl)
|
|
{
|
|
m_pDefaultHelperMtl = new CMatInfo;
|
|
m_pDefaultHelperMtl->SetName("DefaultHelper");
|
|
SInputShaderResources sr;
|
|
sr.m_LMaterial.m_Opacity = 1;
|
|
sr.m_LMaterial.m_Diffuse.set(1, 1, 1, 1);
|
|
// Notice that the following line creates a texture data slot, inserts it and set the texture name
|
|
sr.m_TexturesResourcesMap[EFTT_DIFFUSE].m_Name = szReplaceMe;
|
|
SShaderItem si = GetRenderer()->EF_LoadShaderItem("Helper", true, 0, &sr);
|
|
if (si.m_pShaderResources)
|
|
{
|
|
si.m_pShaderResources->SetMaterialName("DefaultHelper");
|
|
}
|
|
m_pDefaultHelperMtl->AssignShaderItem(si);
|
|
}
|
|
|
|
SLICE_AND_SLEEP();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::LoadCGFMaterial(CMaterialCGF* pMaterialCGF, const char* sCgfFilename, unsigned long nLoadingFlags)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
LOADING_TIME_PROFILE_SECTION;
|
|
|
|
CryPathString sMtlName = pMaterialCGF->name;
|
|
if (sMtlName.find('/') == stack_string::npos)
|
|
{
|
|
// If no slashes in the name assume it is in same folder as a cgf.
|
|
sMtlName = PathUtil::AddSlash(PathUtil::GetPath(stack_string(sCgfFilename))) + sMtlName;
|
|
}
|
|
else
|
|
{
|
|
sMtlName = PathUtil::MakeGamePath(sMtlName);
|
|
}
|
|
return LoadMaterial(sMtlName.c_str(), true, false, nLoadingFlags);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::SetSketchMode(int mode)
|
|
{
|
|
if (mode != 0)
|
|
{
|
|
gEnv->pConsole->ExecuteString("exec sketch_on");
|
|
}
|
|
else
|
|
{
|
|
gEnv->pConsole->ExecuteString("exec sketch_off");
|
|
}
|
|
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
|
|
for (MtlNameMap::iterator it = m_mtlNameMap.begin(); it != m_mtlNameMap.end(); ++it)
|
|
{
|
|
CMatInfo* pMtl = (CMatInfo*)it->second.get();
|
|
pMtl->SetSketchMode(mode);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::SetTexelDensityDebug(int mode)
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
|
|
for (MtlNameMap::iterator it = m_mtlNameMap.begin(); it != m_mtlNameMap.end(); ++it)
|
|
{
|
|
CMatInfo* pMtl = (CMatInfo*)it->second.get();
|
|
pMtl->SetTexelDensityDebug(mode);
|
|
}
|
|
}
|
|
|
|
|
|
namespace
|
|
{
|
|
static bool IsPureChild(_smart_ptr<IMaterial> pMtl)
|
|
{
|
|
return (pMtl->GetFlags() & MTL_FLAG_PURE_CHILD) ? true : false;
|
|
}
|
|
static bool IsMultiSubMaterial(_smart_ptr<IMaterial> pMtl)
|
|
{
|
|
return (pMtl->GetFlags() & MTL_FLAG_MULTI_SUBMTL) ? true : false;
|
|
};
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::ReloadMaterial(_smart_ptr<IMaterial> pMtl)
|
|
{
|
|
AZStd::string name = UnifyName(pMtl->GetName());
|
|
|
|
AZStd::string filename = name;
|
|
auto extPos = filename.find('.');
|
|
if (extPos == AZStd::string::npos)
|
|
{
|
|
filename += MATERIAL_EXT;
|
|
}
|
|
|
|
XmlNodeRef mtlNode = GetSystem()->LoadXmlFromFile(filename.c_str());
|
|
|
|
if (mtlNode)
|
|
{
|
|
// This should reload the Material's data in-place without modifying any
|
|
// material management registration. Otherwise we would have to send some
|
|
// kind of messages about the material being replaced to every object
|
|
// with a pointer to it.
|
|
MakeMaterialFromXml(name, mtlNode, false, 0, pMtl);
|
|
}
|
|
else
|
|
{
|
|
AZ_Warning("Material System", false, "Failed to re-load %s", filename.c_str());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CMatMan::LoadMaterialFromXml(const char* sMtlName, XmlNodeRef mtlNode)
|
|
{
|
|
AZStd::string name = UnifyName(sMtlName);
|
|
|
|
AZStd::unique_lock<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
MtlNameMap::const_iterator it = m_mtlNameMap.find(name);
|
|
|
|
_smart_ptr<IMaterial> pMtl = 0;
|
|
|
|
if (it != m_mtlNameMap.end())
|
|
{
|
|
pMtl = it->second;
|
|
pMtl = MakeMaterialFromXml(name, mtlNode, false, 0, pMtl);
|
|
return pMtl;
|
|
}
|
|
|
|
if (!pMtl)
|
|
{
|
|
pMtl = MakeMaterialFromXml(name, mtlNode, false);
|
|
}
|
|
|
|
return pMtl;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CMatMan::SaveMaterial(XmlNodeRef node, _smart_ptr<IMaterial> pMtl)
|
|
{
|
|
// Saving.
|
|
node->setAttr("MtlFlags", pMtl->GetFlags());
|
|
node->setAttr("DccMaterialHash", pMtl->GetDccMaterialHash());
|
|
|
|
SShaderItem& si = pMtl->GetShaderItem(0);
|
|
SInputShaderResources m_shaderResources = SInputShaderResources(si.m_pShaderResources);
|
|
|
|
if (!IsMultiSubMaterial(pMtl))
|
|
{
|
|
node->setAttr("Shader", si.m_pShader->GetName());
|
|
node->setAttr("GenMask", si.m_pShader->GetGenerationMask());
|
|
node->setAttr("SurfaceType", pMtl->GetSurfaceType() ? pMtl->GetSurfaceType()->GetName() : NULL);
|
|
|
|
SInputShaderResources& sr = m_shaderResources;
|
|
//if (!m_shaderName.IsEmpty() && (azstricmp(m_shaderName,"nodraw") != 0))
|
|
{
|
|
s_materialHelpers.SetXmlFromLighting(sr, node);
|
|
s_materialHelpers.SetXmlFromTextures(sr, node);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Save out the link name if present
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const char* szLinkName = pMtl->GetMaterialLinkName();
|
|
if (szLinkName != 0 && strlen(szLinkName))
|
|
{
|
|
XmlNodeRef pLinkName = node->newChild("MaterialLinkName");
|
|
pLinkName->setAttr("name", szLinkName);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Check if we have vertex deform.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
s_materialHelpers.SetXmlFromVertexDeform(m_shaderResources, node);
|
|
|
|
if (pMtl->GetSubMtlCount() > 0)
|
|
{
|
|
// Serialize sub materials.
|
|
XmlNodeRef childsNode = node->newChild("SubMaterials");
|
|
for (int i = 0; i < pMtl->GetSubMtlCount(); i++)
|
|
{
|
|
_smart_ptr<IMaterial> pSubMtl = pMtl->GetSubMtl(i);
|
|
if (pSubMtl && IsPureChild(pSubMtl))
|
|
{
|
|
XmlNodeRef mtlNode = childsNode->newChild("Material");
|
|
mtlNode->setAttr("Name", pSubMtl->GetName());
|
|
SaveMaterial(mtlNode, pSubMtl);
|
|
}
|
|
else
|
|
{
|
|
XmlNodeRef mtlNode = childsNode->newChild("MaterialRef");
|
|
if (pSubMtl)
|
|
{
|
|
mtlNode->setAttr("Name", pSubMtl->GetName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Save public parameters.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
if (m_publicVarsCache)
|
|
{
|
|
node->addChild( m_publicVarsCache );
|
|
}
|
|
else */
|
|
|
|
if (!m_shaderResources.m_ShaderParams.empty())
|
|
{
|
|
XmlNodeRef publicsNode = node->newChild("PublicParams");
|
|
s_materialHelpers.SetXmlFromShaderParams(m_shaderResources, publicsNode);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Save material layers data
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
bool bMaterialLayers = false;
|
|
for (int l(0); l < MTL_LAYER_MAX_SLOTS; ++l)
|
|
{
|
|
const IMaterialLayer* pLayer = pMtl->GetLayer(l);
|
|
if (pLayer && pLayer->GetShaderItem().m_pShader && strlen(pLayer->GetShaderItem().m_pShader->GetName()) != 0)
|
|
{
|
|
bMaterialLayers = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bMaterialLayers)
|
|
{
|
|
XmlNodeRef mtlLayersNode = node->newChild("MaterialLayers");
|
|
for (int l(0); l < MTL_LAYER_MAX_SLOTS; ++l)
|
|
{
|
|
XmlNodeRef layerNode = mtlLayersNode->newChild("Layer");
|
|
const IMaterialLayer* pLayer = pMtl->GetLayer(l);
|
|
if (pLayer && pLayer->GetShaderItem().m_pShader && strlen(pLayer->GetShaderItem().m_pShader->GetName()) != 0)
|
|
{
|
|
SInputShaderResources shaderRes(pLayer->GetShaderItem().m_pShaderResources);
|
|
|
|
layerNode->setAttr("Name", pLayer->GetShaderItem().m_pShader->GetName());
|
|
layerNode->setAttr("NoDraw", pLayer->GetShaderItem().m_pShader->GetFlags() & MTL_LAYER_USAGE_NODRAW);
|
|
layerNode->setAttr("FadeOut", pLayer->GetShaderItem().m_pShader->GetFlags() & MTL_LAYER_USAGE_FADEOUT);
|
|
|
|
if (!shaderRes.m_ShaderParams.empty())
|
|
{
|
|
XmlNodeRef publicsNode = layerNode->newChild("PublicParams");
|
|
s_materialHelpers.SetXmlFromShaderParams(shaderRes, publicsNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::PreloadLevelMaterials()
|
|
{
|
|
LOADING_TIME_PROFILE_SECTION;
|
|
|
|
//bool bMtlfCacheExist = GetISystem()->GetIResourceManager()->LoadLevelCachePak( MTL_LEVEL_CACHE_PAK,"" );
|
|
//if (!bMtlfCacheExist)
|
|
//return;
|
|
|
|
PrintMessage("==== Starting Loading Level Materials ====");
|
|
float fStartTime = GetCurAsyncTimeSec();
|
|
|
|
auto pResList = GetISystem()->GetIResourceManager()->GetLevelResourceList();
|
|
|
|
if (!pResList)
|
|
{
|
|
Error("Error loading level Materials: resource list is NULL");
|
|
return;
|
|
}
|
|
|
|
int nCounter = 0;
|
|
int nInLevelCacheCount = 0;
|
|
|
|
_smart_ptr<IXmlParser> pXmlParser = GetISystem()->GetXmlUtils()->CreateXmlParser();
|
|
|
|
// Request objects loading from Streaming System.
|
|
CryPathString mtlName;
|
|
CryPathString mtlFilename;
|
|
CryPathString mtlCacheFilename;
|
|
for (const char* sName = pResList->GetFirst(); sName != NULL; sName = pResList->GetNext())
|
|
{
|
|
if (strstr(sName, ".mtl") == 0 && strstr(sName, ".binmtl") == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
mtlFilename = sName;
|
|
|
|
mtlName = sName;
|
|
PathUtil::RemoveExtension(mtlName);
|
|
|
|
if (FindMaterial(mtlName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Load this material as un-removable.
|
|
_smart_ptr<IMaterial> pMtl = LoadMaterial(mtlName, false, true);
|
|
if (pMtl)
|
|
{
|
|
nCounter++;
|
|
}
|
|
|
|
//This loop can take a few seconds, so we should refresh the loading screen and call the loading tick functions to ensure that no big gaps in coverage occur.
|
|
SYNCHRONOUS_LOADING_TICK();
|
|
}
|
|
|
|
//GetISystem()->GetIResourceManager()->UnloadLevelCachePak( MTL_LEVEL_CACHE_PAK );
|
|
PrintMessage("==== Finished loading level Materials: %d mtls loaded (%d from LevelCache) in %.1f sec ====", nCounter, nInLevelCacheCount, GetCurAsyncTimeSec() - fStartTime);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::PreloadDecalMaterials()
|
|
{
|
|
LOADING_TIME_PROFILE_SECTION;
|
|
|
|
float fStartTime = GetCurAsyncTimeSec();
|
|
|
|
bool bVerboseLogging = GetCVars()->e_StatObjPreload > 1;
|
|
int nCounter = 0;
|
|
|
|
// Wildcards load.
|
|
CryPathString sPath = PathUtil::Make(CryPathString(MATERIAL_DECALS_FOLDER), CryPathString(MATERIAL_DECALS_SEARCH_WILDCARD));
|
|
PrintMessage("===== Loading all Decal materials from a folder: %s =====", sPath.c_str());
|
|
|
|
AZStd::vector<AZStd::string> mtlFiles;
|
|
SDirectoryEnumeratorHelper dirHelper;
|
|
dirHelper.ScanDirectoryRecursive(gEnv->pCryPak, "", MATERIAL_DECALS_FOLDER, MATERIAL_DECALS_SEARCH_WILDCARD, mtlFiles);
|
|
|
|
for (int i = 0, num = (int)mtlFiles.size(); i < num; i++)
|
|
{
|
|
CryPathString sMtlName{ mtlFiles[i].data(), mtlFiles[i].size() };
|
|
PathUtil::RemoveExtension(sMtlName);
|
|
|
|
if (bVerboseLogging)
|
|
{
|
|
CryLog("Preloading Decal Material: %s", sMtlName.c_str());
|
|
}
|
|
|
|
_smart_ptr<IMaterial> pMtl = LoadMaterial(sMtlName.c_str(), false, true); // Load material as non-removable
|
|
if (pMtl)
|
|
{
|
|
nCounter++;
|
|
}
|
|
}
|
|
PrintMessage("==== Finished Loading Decal Materials: %d mtls loaded in %.1f sec ====", nCounter, GetCurAsyncTimeSec() - fStartTime);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CMatMan::ShutDown()
|
|
{
|
|
CryLogAlways("shutting down mat man\n");
|
|
{
|
|
AZStd::unique_lock<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
|
|
m_pXmlParser = 0;
|
|
|
|
m_mtlNameMap.clear();
|
|
m_pendingMaterialLoads.clear();
|
|
}
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_nonRemovablesMutex);
|
|
stl::free_container(m_nonRemovables);
|
|
}
|
|
|
|
// Free default materials
|
|
m_pDefaultMtl = nullptr;
|
|
m_pDefaultTerrainLayersMtl = nullptr;
|
|
m_pNoDrawMtl = nullptr;
|
|
m_pDefaultHelperMtl = nullptr;
|
|
m_pDefaultLayersMtl = nullptr;
|
|
|
|
|
|
|
|
m_pSurfaceTypeManager->RemoveAll();
|
|
m_bInitialized = false;
|
|
}
|
|
|
|
|
|
void CMatMan::GetMemoryUsage(ICrySizer* pSizer) const
|
|
{
|
|
pSizer->AddObject(this, sizeof(*this));
|
|
pSizer->AddObject(m_pDefaultMtl);
|
|
pSizer->AddObject(m_pDefaultLayersMtl);
|
|
pSizer->AddObject(m_pDefaultTerrainLayersMtl);
|
|
pSizer->AddObject(m_pNoDrawMtl);
|
|
pSizer->AddObject(m_pDefaultHelperMtl);
|
|
pSizer->AddObject(m_pSurfaceTypeManager);
|
|
pSizer->AddObject(m_pXmlParser);
|
|
|
|
pSizer->AddObject(m_mtlNameMap);
|
|
pSizer->AddObject(m_pendingMaterialLoads);
|
|
pSizer->AddObject(m_nonRemovables);
|
|
}
|
|
|
|
void CMatMan::UpdateShaderItems()
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
|
|
for (MtlNameMap::iterator iter = m_mtlNameMap.begin(); iter != m_mtlNameMap.end(); ++iter)
|
|
{
|
|
CMatInfo* pMaterial = static_cast<CMatInfo*>(iter->second.get());
|
|
pMaterial->UpdateShaderItems();
|
|
}
|
|
}
|
|
|
|
void CMatMan::RefreshMaterialRuntime()
|
|
{
|
|
RefreshShaderResourceConstants();
|
|
}
|
|
|
|
void CMatMan::RefreshShaderResourceConstants()
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_materialMapMutex);
|
|
|
|
for (MtlNameMap::iterator iter = m_mtlNameMap.begin(); iter != m_mtlNameMap.end(); ++iter)
|
|
{
|
|
CMatInfo* pMaterial = static_cast<CMatInfo*>(iter->second.get());
|
|
pMaterial->RefreshShaderResourceConstants();
|
|
}
|
|
}
|
|
|
|
// override from LegacyAssetEventBus::Handler
|
|
// Notifies listeners that a file changed
|
|
void CMatMan::OnFileChanged(AZStd::string assetPath)
|
|
{
|
|
_smart_ptr<IMaterial> mat = FindMaterial(assetPath.c_str());
|
|
|
|
//Reload material pointer in place if the material is found
|
|
if (mat)
|
|
{
|
|
ReloadMaterial(mat);
|
|
}
|
|
}
|
|
|
|
void CMatMan::OnFileRemoved(AZStd::string assetPath)
|
|
{
|
|
_smart_ptr<IMaterial> mat = FindMaterial(assetPath.c_str());
|
|
|
|
//Reload the material in place to a place holder
|
|
if (mat)
|
|
{
|
|
Unregister(mat);
|
|
CreateMaterialPlaceholder(mat->GetName(), mat->GetFlags(), szDefaultSolid, mat);
|
|
}
|
|
}
|
|
|