//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// #include "Material/BsMaterial.h" #include "Material/BsShader.h" #include "Material/BsTechnique.h" #include "Material/BsPass.h" #include "RenderAPI/BsRenderAPI.h" #include "RTTI/BsMaterialRTTI.h" #include "Material/BsMaterialManager.h" #include "Resources/BsResources.h" #include "Math/BsMatrixNxM.h" #include "Math/BsVector3I.h" #include "Math/BsVector4I.h" #include "Serialization/BsMemorySerializer.h" #include "Material/BsMaterialParams.h" #include "Material/BsGpuParamsSet.h" namespace bs { enum MaterialLoadFlags { Load_None = 0, Load_Shader = 1, Load_All = 2 }; template bool isShaderValid(const T& shader) { return false; } template<> bool isShaderValid(const HShader& shader) { return shader.isLoaded(); } template<> bool isShaderValid(const SPtr& shader) { return shader != nullptr; } template struct TMatType { }; template<> struct TMatType { typedef Material Type; }; template<> struct TMatType { typedef ct::Material Type; }; template SPtr::Type> getMaterialPtr(const TMaterial* material) { return std::static_pointer_cast::Type>(static_cast::Type*>(material)->getThisPtr()); } template SPtr::GpuParamsSetType> TMaterial::createParamsSet(UINT32 techniqueIdx) { if (techniqueIdx >= (UINT32)mTechniques.size()) return nullptr; SPtr technique = mTechniques[techniqueIdx]; return bs_shared_ptr_new(technique, mShader, mParams); } template void TMaterial::updateParamsSet(const SPtr& paramsSet, bool updateAll) { paramsSet->update(mParams, updateAll); } template UINT32 TMaterial::findTechnique(const StringID& tag) const { for(UINT32 i = 0; i < (UINT32)mTechniques.size(); i++) { if (mTechniques[i]->hasTag(tag)) return i; } return (UINT32)-1; } template UINT32 TMaterial::getDefaultTechnique() const { for (UINT32 i = 0; i < (UINT32)mTechniques.size(); i++) { if (!mTechniques[i]->hasTags()) return i; } return 0; } template UINT32 TMaterial::getNumPasses(UINT32 techniqueIdx) const { if (mShader == nullptr) return 0; if (techniqueIdx >= (UINT32)mTechniques.size()) return 0; return mTechniques[techniqueIdx]->getNumPasses(); } template SPtr::PassType> TMaterial::getPass(UINT32 passIdx, UINT32 techniqueIdx) const { if (mShader == nullptr) return nullptr; if (techniqueIdx >= (UINT32)mTechniques.size()) return nullptr; if (passIdx < 0 || passIdx >= mTechniques[techniqueIdx]->getNumPasses()) return nullptr; return mTechniques[techniqueIdx]->getPass(passIdx); } template TMaterialParamStruct TMaterial::getParamStruct(const String& name) const { throwIfNotInitialized(); return TMaterialParamStruct(name, getMaterialPtr(this)); } template TMaterialParamTexture TMaterial::getParamTexture(const String& name) const { throwIfNotInitialized(); return TMaterialParamTexture(name, getMaterialPtr(this)); } template TMaterialParamLoadStoreTexture TMaterial::getParamLoadStoreTexture(const String& name) const { throwIfNotInitialized(); return TMaterialParamLoadStoreTexture(name, getMaterialPtr(this)); } template TMaterialParamBuffer TMaterial::getParamBuffer(const String& name) const { throwIfNotInitialized(); return TMaterialParamBuffer(name, getMaterialPtr(this)); } template TMaterialParamSampState TMaterial::getParamSamplerState(const String& name) const { throwIfNotInitialized(); return TMaterialParamSampState(name, getMaterialPtr(this)); } template void TMaterial::initializeTechniques() { mTechniques.clear(); if (isShaderValid(mShader)) { mParams = bs_shared_ptr_new(mShader); mTechniques = mShader->getCompatibleTechniques(); if (mTechniques.size() == 0) return; initDefaultParameters(); } else mParams = nullptr; _markDependenciesDirty(); } template template void TMaterial::setParamValue(const String& name, UINT8* buffer, UINT32 numElements) { TMaterialDataParam param; getParam(name, param); T* ptr = (T*)buffer; for (UINT32 i = 0; i < numElements; i++) param.set(ptr[i], i); } template void TMaterial::initDefaultParameters() { const Map& dataParams = mShader->getDataParams(); for (auto& paramData : dataParams) { if (paramData.second.defaultValueIdx == (UINT32)-1) continue; UINT8* buffer = (UINT8*)mShader->getDefaultValue(paramData.second.defaultValueIdx); if (buffer == nullptr) continue; switch (paramData.second.type) { case GPDT_FLOAT1: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_FLOAT2: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_FLOAT3: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_FLOAT4: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_2X2: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_2X3: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_2X4: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_3X2: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_3X3: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_3X4: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_4X2: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_4X3: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_MATRIX_4X4: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_INT1: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_INT2: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_INT3: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_INT4: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_BOOL: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_COLOR: setParamValue(paramData.first, buffer, paramData.second.arraySize); break; case GPDT_STRUCT: { TMaterialParamStruct param = getParamStruct(paramData.first); UINT32 elementSizeBytes = paramData.second.elementSize * sizeof(UINT32); UINT8* ptr = buffer; for (UINT32 i = 0; i < paramData.second.arraySize; i++) { param.set(ptr, elementSizeBytes, i); ptr += elementSizeBytes; } } break; default: break; } } const Map& textureParams = mShader->getTextureParams(); for (auto& param : textureParams) { if (param.second.defaultValueIdx == (UINT32)-1) continue; TextureType defaultTex = mShader->getDefaultTexture(param.second.defaultValueIdx); getParamTexture(param.first).set(defaultTex); } const Map& samplerParams = mShader->getSamplerParams(); for (auto& param : samplerParams) { if (param.second.defaultValueIdx == (UINT32)-1) continue; SamplerStateType defaultSampler = mShader->getDefaultSampler(param.second.defaultValueIdx); getParamSamplerState(param.first).set(defaultSampler); } } template template void TMaterial::getParam(const String& name, TMaterialDataParam& output) const { throwIfNotInitialized(); output = TMaterialDataParam(name, getMaterialPtr(this)); } template void TMaterial::throwIfNotInitialized() const { if (mShader == nullptr) { BS_EXCEPT(InternalErrorException, "Material does not have shader set."); } if (mTechniques.size() == 0) { BS_EXCEPT(InternalErrorException, "Shader does not contain a supported technique."); } } template class TMaterial < false > ; template class TMaterial < true > ; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; template BS_CORE_EXPORT void TMaterial::getParam(const String&, TMaterialDataParam&) const; Material::Material() :mLoadFlags(Load_None) { } Material::Material(const HShader& shader) :mLoadFlags(Load_None) { mShader = shader; } void Material::initialize() { _markResourcesDirty(); initializeIfLoaded(); Resource::initialize(); } void Material::setShader(const HShader& shader) { if (mShader == shader) return; mShader = shader; mTechniques.clear(); mLoadFlags = Load_None; _markResourcesDirty(); initializeIfLoaded(); } void Material::_markCoreDirty(MaterialDirtyFlags flags) { markCoreDirty((UINT32)flags); } void Material::_markDependenciesDirty() { markDependenciesDirty(); } void Material::_markResourcesDirty() { markListenerResourcesDirty(); } SPtr Material::getCore() const { return std::static_pointer_cast(mCoreSpecific); } SPtr Material::createCore() const { ct::Material* material = nullptr; SPtr shader; if (mShader.isLoaded()) { shader = mShader->getCore(); Vector> techniques(mTechniques.size()); for (UINT32 i = 0; i < (UINT32)mTechniques.size(); i++) techniques[i] = mTechniques[i]->getCore(); SPtr materialParams = bs_shared_ptr_new(shader, mParams); material = new (bs_alloc()) ct::Material(shader, techniques, materialParams); } if (material == nullptr) material = new (bs_alloc()) ct::Material(shader); SPtr materialPtr = bs_shared_ptr(material); materialPtr->_setThisPtr(materialPtr); return materialPtr; } CoreSyncData Material::syncToCore(FrameAlloc* allocator) { bool syncAllParams = getCoreDirtyFlags() == (UINT32)MaterialDirtyFlags::ResourceChanged; UINT32 paramsSize = 0; if (mParams != nullptr) mParams->getSyncData(nullptr, paramsSize, syncAllParams); UINT32 numTechniques = (UINT32)mTechniques.size(); UINT32 size = sizeof(UINT32) * 2 + sizeof(SPtr) + sizeof(SPtr) * numTechniques + paramsSize; UINT8* buffer = allocator->alloc(size); char* dataPtr = (char*)buffer; SPtr* shader = new (dataPtr)SPtr(); if (mShader.isLoaded(false)) *shader = mShader->getCore(); else *shader = nullptr; dataPtr += sizeof(SPtr); dataPtr = rttiWriteElem(numTechniques, dataPtr); for(UINT32 i = 0; i < numTechniques; i++) { SPtr* technique = new (dataPtr)SPtr(); *technique = mTechniques[i]->getCore(); dataPtr += sizeof(SPtr); } dataPtr = rttiWriteElem(paramsSize, dataPtr); if (mParams != nullptr) mParams->getSyncData((UINT8*)dataPtr, paramsSize, syncAllParams); dataPtr += paramsSize; return CoreSyncData(buffer, size); } void Material::getCoreDependencies(Vector& dependencies) { if (mShader.isLoaded()) dependencies.push_back(mShader.get()); if(mParams != nullptr) mParams->getCoreObjectDependencies(dependencies); } void Material::getListenerResources(Vector& resources) { if (mShader != nullptr) resources.push_back(mShader); if (mParams != nullptr) mParams->getResourceDependencies(resources); } void Material::getResourceDependencies(FrameVector& dependencies) const { if (mShader != nullptr) dependencies.push_back(mShader); } void Material::initializeIfLoaded() { if (areDependenciesLoaded()) { if (mLoadFlags != Load_All) { mLoadFlags = Load_All; // Shader about to change, so save parameters, rebuild material and restore parameters SPtr oldParams = mParams; initializeTechniques(); markCoreDirty(); if (mTechniques.size() == 0) // Wasn't initialized return; if(oldParams) setParams(oldParams); } } else { if (mShader.isLoaded() && mLoadFlags == Load_None) { mLoadFlags = Load_Shader; markListenerResourcesDirty(); // Need to register resources dependent on shader now } } } void Material::notifyResourceLoaded(const HResource& resource) { // Ready to initialize as soon as shader loads if (resource->getRTTI()->getRTTIId() == TID_Shader) initializeIfLoaded(); } void Material::notifyResourceChanged(const HResource& resource) { // Need full rebuild if shader changed if (resource->getRTTI()->getRTTIId() == TID_Shader) { mLoadFlags = Load_None; initializeIfLoaded(); } else { // Otherwise just sync changes (most likely just a texture got reimported) _markCoreDirty(MaterialDirtyFlags::ResourceChanged); } } HMaterial Material::clone() { UINT32 bufferSize = 0; MemorySerializer serializer; UINT8* buffer = serializer.encode(this, bufferSize, (void*(*)(UINT32))&bs_alloc); SPtr cloneObj = std::static_pointer_cast(serializer.decode(buffer, bufferSize)); bs_free(buffer); return static_resource_cast(gResources()._createResourceHandle(cloneObj)); } template void copyParam(const SPtr& from, Material* to, const String& name, const MaterialParams::ParamData& paramRef, UINT32 arraySize) { TMaterialDataParam param; to->getParam(name, param); T paramData; for (UINT32 i = 0; i < arraySize; i++) { from->getDataParam(paramRef, i, paramData); param.set(paramData, i); } } void Material::setParams(const SPtr& params) { if (params == nullptr) return; std::function< void(const SPtr&, Material*, const String&, const MaterialParams::ParamData&, UINT32)> copyParamLookup[GPDT_COUNT]; copyParamLookup[GPDT_FLOAT1] = ©Param; copyParamLookup[GPDT_FLOAT2] = ©Param; copyParamLookup[GPDT_FLOAT3] = ©Param; copyParamLookup[GPDT_FLOAT4] = ©Param; copyParamLookup[GPDT_INT1] = ©Param; copyParamLookup[GPDT_INT2] = ©Param; copyParamLookup[GPDT_INT3] = ©Param; copyParamLookup[GPDT_INT4] = ©Param; copyParamLookup[GPDT_MATRIX_2X2] = ©Param; copyParamLookup[GPDT_MATRIX_2X3] = ©Param; copyParamLookup[GPDT_MATRIX_2X4] = ©Param; copyParamLookup[GPDT_MATRIX_3X3] = ©Param; copyParamLookup[GPDT_MATRIX_3X2] = ©Param; copyParamLookup[GPDT_MATRIX_3X4] = ©Param; copyParamLookup[GPDT_MATRIX_4X4] = ©Param; copyParamLookup[GPDT_MATRIX_4X2] = ©Param; copyParamLookup[GPDT_MATRIX_4X3] = ©Param; copyParamLookup[GPDT_BOOL] = ©Param; copyParamLookup[GPDT_COLOR] = ©Param; auto& dataParams = mShader->getDataParams(); for (auto& param : dataParams) { UINT32 arraySize = param.second.arraySize > 1 ? param.second.arraySize : 1; const MaterialParams::ParamData* paramData = nullptr; auto result = params->getParamData(param.first, MaterialParams::ParamType::Data, param.second.type, 0, ¶mData); if (result != MaterialParams::GetParamResult::Success) continue; UINT32 elemsToCopy = std::min(arraySize, paramData->arraySize); auto& copyFunction = copyParamLookup[param.second.type]; if (copyFunction != nullptr) copyFunction(params, this, param.first, *paramData, elemsToCopy); else { if(param.second.type == GPDT_STRUCT) { TMaterialParamStruct curParam = getParamStruct(param.first); UINT32 structSize = params->getStructSize(*paramData); if (param.second.elementSize != structSize) continue; UINT8* structData = (UINT8*)bs_stack_alloc(structSize); for (UINT32 i = 0; i < elemsToCopy; i++) { params->getStructData(*paramData, structData, structSize, i); curParam.set(structData, structSize, i); } bs_stack_free(structData); } } } auto& textureParams = mShader->getTextureParams(); for (auto& param : textureParams) { const MaterialParams::ParamData* paramData = nullptr; auto result = params->getParamData(param.first, MaterialParams::ParamType::Texture, GPDT_UNKNOWN, 0, ¶mData); if (result != MaterialParams::GetParamResult::Success) continue; bool isLoadStore = params->getIsTextureLoadStore(*paramData); if(!isLoadStore) { TMaterialParamTexture curParam = getParamTexture(param.first); HTexture texture; TextureSurface surface; params->getTexture(*paramData, texture, surface); curParam.set(texture); } else { TMaterialParamLoadStoreTexture curParam = getParamLoadStoreTexture(param.first); HTexture texture; TextureSurface surface; params->getLoadStoreTexture(*paramData, texture, surface); curParam.set(texture, surface); } } auto& bufferParams = mShader->getBufferParams(); for (auto& param : bufferParams) { const MaterialParams::ParamData* paramData = nullptr; auto result = params->getParamData(param.first, MaterialParams::ParamType::Buffer, GPDT_UNKNOWN, 0, ¶mData); if (result != MaterialParams::GetParamResult::Success) continue; TMaterialParamBuffer curParam = getParamBuffer(param.first); SPtr buffer; params->getBuffer(*paramData, buffer); curParam.set(buffer); } auto& samplerParams = mShader->getSamplerParams(); for (auto& param : samplerParams) { const MaterialParams::ParamData* paramData = nullptr; auto result = params->getParamData(param.first, MaterialParams::ParamType::Sampler, GPDT_UNKNOWN, 0, ¶mData); if (result != MaterialParams::GetParamResult::Success) continue; TMaterialParamSampState curParam = getParamSamplerState(param.first); SPtr samplerState; params->getSamplerState(*paramData, samplerState); curParam.set(samplerState); } } HMaterial Material::create() { SPtr materialPtr = MaterialManager::instance().create(); return static_resource_cast(gResources()._createResourceHandle(materialPtr)); } HMaterial Material::create(const HShader& shader) { SPtr materialPtr = MaterialManager::instance().create(shader); return static_resource_cast(gResources()._createResourceHandle(materialPtr)); } RTTITypeBase* Material::getRTTIStatic() { return MaterialRTTI::instance(); } RTTITypeBase* Material::getRTTI() const { return Material::getRTTIStatic(); } namespace ct { Material::Material(const SPtr& shader) { setShader(shader); } Material::Material(const SPtr& shader, const Vector>& techniques, const SPtr& materialParams) { mShader = shader; mParams = materialParams; mTechniques = techniques; } void Material::setShader(const SPtr& shader) { mShader = shader; initializeTechniques(); _markCoreDirty(); } void Material::syncToCore(const CoreSyncData& data) { char* dataPtr = (char*)data.getBuffer(); SPtr* shader = (SPtr*)dataPtr; mShader = *shader; shader->~SPtr(); dataPtr += sizeof(SPtr); UINT32 numTechniques; dataPtr = rttiReadElem(numTechniques, dataPtr); mTechniques.resize(numTechniques); for(UINT32 i = 0; i < numTechniques; i++) { SPtr* technique = (SPtr*)dataPtr; mTechniques[i] = *technique; technique->~SPtr(); dataPtr += sizeof(SPtr); } UINT32 paramsSize = 0; dataPtr = rttiReadElem(paramsSize, dataPtr); if (mParams == nullptr) mParams = bs_shared_ptr_new(mShader); mParams->setSyncData((UINT8*)dataPtr, paramsSize); dataPtr += paramsSize; } SPtr Material::create(const SPtr& shader) { Material* material = new (bs_alloc()) Material(shader); SPtr materialPtr = bs_shared_ptr(material); materialPtr->_setThisPtr(materialPtr); materialPtr->initialize(); return materialPtr; } } }