#include #include #include #include #include #include #include #include #include #include namespace XCEngine { namespace Resources { namespace { constexpr size_t kMaterialConstantSlotSize = 16; bool HasVirtualPathScheme(const Containers::String& path) { return std::string(path.CStr()).find("://") != std::string::npos; } bool IsPackedMaterialPropertyType(MaterialPropertyType type) { switch (type) { case MaterialPropertyType::Float: case MaterialPropertyType::Float2: case MaterialPropertyType::Float3: case MaterialPropertyType::Float4: case MaterialPropertyType::Int: case MaterialPropertyType::Int2: case MaterialPropertyType::Int3: case MaterialPropertyType::Int4: case MaterialPropertyType::Bool: return true; case MaterialPropertyType::Texture: case MaterialPropertyType::Cubemap: default: return false; } } Core::uint32 GetPackedMaterialPropertySize(MaterialPropertyType type) { switch (type) { case MaterialPropertyType::Float: case MaterialPropertyType::Int: case MaterialPropertyType::Bool: return sizeof(Core::uint32); case MaterialPropertyType::Float2: case MaterialPropertyType::Int2: return sizeof(Core::uint32) * 2; case MaterialPropertyType::Float3: case MaterialPropertyType::Int3: return sizeof(Core::uint32) * 3; case MaterialPropertyType::Float4: case MaterialPropertyType::Int4: return sizeof(Core::uint32) * 4; case MaterialPropertyType::Texture: case MaterialPropertyType::Cubemap: default: return 0; } } void RemoveTextureBindingByName( Containers::Array& textureBindings, const Containers::String& name) { for (size_t bindingIndex = 0; bindingIndex < textureBindings.Size(); ++bindingIndex) { if (textureBindings[bindingIndex].name == name) { if (bindingIndex != textureBindings.Size() - 1) { textureBindings[bindingIndex] = std::move(textureBindings.Back()); textureBindings[bindingIndex].slot = static_cast(bindingIndex); } textureBindings.PopBack(); break; } } for (size_t bindingIndex = 0; bindingIndex < textureBindings.Size(); ++bindingIndex) { textureBindings[bindingIndex].slot = static_cast(bindingIndex); } } void EnsureTextureProperty(Containers::HashMap& properties, const Containers::String& name, MaterialPropertyType type = MaterialPropertyType::Texture) { MaterialProperty prop; prop.name = name; prop.type = type; prop.refCount = 1; properties.Insert(name, prop); } bool IsTextureMaterialPropertyType(MaterialPropertyType type) { return type == MaterialPropertyType::Texture || type == MaterialPropertyType::Cubemap; } MaterialPropertyType GetMaterialPropertyTypeForShaderProperty(ShaderPropertyType type) { switch (type) { case ShaderPropertyType::Float: case ShaderPropertyType::Range: return MaterialPropertyType::Float; case ShaderPropertyType::Int: return MaterialPropertyType::Int; case ShaderPropertyType::Vector: case ShaderPropertyType::Color: return MaterialPropertyType::Float4; case ShaderPropertyType::TextureCube: return MaterialPropertyType::Cubemap; case ShaderPropertyType::Texture2D: default: return MaterialPropertyType::Texture; } } bool IsMaterialPropertyCompatibleWithShaderProperty( MaterialPropertyType materialType, ShaderPropertyType shaderType) { switch (shaderType) { case ShaderPropertyType::Float: case ShaderPropertyType::Range: return materialType == MaterialPropertyType::Float; case ShaderPropertyType::Int: return materialType == MaterialPropertyType::Int; case ShaderPropertyType::Vector: case ShaderPropertyType::Color: return materialType == MaterialPropertyType::Float4; case ShaderPropertyType::Texture2D: return materialType == MaterialPropertyType::Texture; case ShaderPropertyType::TextureCube: return materialType == MaterialPropertyType::Texture || materialType == MaterialPropertyType::Cubemap; default: return false; } } std::string TrimCopy(const std::string& text) { const auto begin = std::find_if_not(text.begin(), text.end(), [](unsigned char ch) { return std::isspace(ch) != 0; }); if (begin == text.end()) { return std::string(); } const auto end = std::find_if_not(text.rbegin(), text.rend(), [](unsigned char ch) { return std::isspace(ch) != 0; }).base(); return std::string(begin, end); } bool TryParseFloatList(const Containers::String& value, float* outValues, size_t maxValues, size_t& outCount) { outCount = 0; std::string text = TrimCopy(std::string(value.CStr())); if (text.empty()) { return false; } if ((text.front() == '(' && text.back() == ')') || (text.front() == '[' && text.back() == ']') || (text.front() == '{' && text.back() == '}')) { text = text.substr(1, text.size() - 2); } const char* cursor = text.c_str(); char* endPtr = nullptr; while (*cursor != '\0' && outCount < maxValues) { while (*cursor != '\0' && (std::isspace(static_cast(*cursor)) != 0 || *cursor == ',')) { ++cursor; } if (*cursor == '\0') { break; } const float parsed = std::strtof(cursor, &endPtr); if (endPtr == cursor) { return false; } outValues[outCount++] = parsed; cursor = endPtr; } while (*cursor != '\0') { if (std::isspace(static_cast(*cursor)) == 0 && *cursor != ',') { return false; } ++cursor; } return outCount > 0; } bool TryParseFloatDefault(const Containers::String& value, float& outValue) { float values[4] = {}; size_t count = 0; if (!TryParseFloatList(value, values, 4, count)) { return false; } outValue = values[0]; return true; } bool TryParseIntDefault(const Containers::String& value, Core::int32& outValue) { const std::string text = TrimCopy(std::string(value.CStr())); if (text.empty()) { return false; } char* endPtr = nullptr; const long parsed = std::strtol(text.c_str(), &endPtr, 10); if (endPtr == text.c_str()) { return false; } while (*endPtr != '\0') { if (std::isspace(static_cast(*endPtr)) == 0) { return false; } ++endPtr; } outValue = static_cast(parsed); return true; } bool TryBuildDefaultMaterialProperty(const ShaderPropertyDesc& shaderProperty, MaterialProperty& outProperty) { outProperty = MaterialProperty(); outProperty.name = shaderProperty.name; outProperty.type = GetMaterialPropertyTypeForShaderProperty(shaderProperty.type); outProperty.refCount = 1; switch (shaderProperty.type) { case ShaderPropertyType::Float: case ShaderPropertyType::Range: TryParseFloatDefault(shaderProperty.defaultValue, outProperty.value.floatValue[0]); return true; case ShaderPropertyType::Int: TryParseIntDefault(shaderProperty.defaultValue, outProperty.value.intValue[0]); return true; case ShaderPropertyType::Vector: case ShaderPropertyType::Color: { float values[4] = {}; size_t count = 0; if (TryParseFloatList(shaderProperty.defaultValue, values, 4, count)) { for (size_t index = 0; index < count && index < 4; ++index) { outProperty.value.floatValue[index] = values[index]; } } return true; } case ShaderPropertyType::Texture2D: case ShaderPropertyType::TextureCube: return true; default: return false; } } void WritePackedMaterialProperty(Core::uint8* destination, const MaterialProperty& property) { std::memset(destination, 0, kMaterialConstantSlotSize); switch (property.type) { case MaterialPropertyType::Float: std::memcpy(destination, property.value.floatValue, sizeof(float)); break; case MaterialPropertyType::Float2: std::memcpy(destination, property.value.floatValue, sizeof(float) * 2); break; case MaterialPropertyType::Float3: std::memcpy(destination, property.value.floatValue, sizeof(float) * 3); break; case MaterialPropertyType::Float4: std::memcpy(destination, property.value.floatValue, sizeof(float) * 4); break; case MaterialPropertyType::Int: std::memcpy(destination, property.value.intValue, sizeof(Core::int32)); break; case MaterialPropertyType::Int2: std::memcpy(destination, property.value.intValue, sizeof(Core::int32) * 2); break; case MaterialPropertyType::Int3: std::memcpy(destination, property.value.intValue, sizeof(Core::int32) * 3); break; case MaterialPropertyType::Int4: std::memcpy(destination, property.value.intValue, sizeof(Core::int32) * 4); break; case MaterialPropertyType::Bool: { const Core::uint32 boolValue = property.value.boolValue ? 1u : 0u; std::memcpy(destination, &boolValue, sizeof(boolValue)); break; } case MaterialPropertyType::Texture: case MaterialPropertyType::Cubemap: default: break; } } } // namespace Material::Material() = default; Material::~Material() { // Imported materials can own nested handles and container state; explicitly // resetting them here avoids teardown-order issues during destruction. m_shader.Reset(); m_tags = Containers::Array(); m_keywordSet = ShaderKeywordSet(); m_properties = Containers::HashMap(); m_constantLayout = Containers::Array(); m_constantBufferData = Containers::Array(); m_textureBindings = Containers::Array(); } void Material::Release() { m_shader.Reset(); m_renderQueue = static_cast(MaterialRenderQueue::Geometry); m_renderState = MaterialRenderState(); m_hasRenderStateOverride = false; m_tags.Clear(); m_keywordSet.enabledKeywords.Clear(); m_properties.Clear(); m_constantLayout.Clear(); m_textureBindings.Clear(); m_constantBufferData.Clear(); m_changeVersion = 1; m_isValid = false; UpdateMemorySize(); } void Material::SetShader(const ResourceHandle& shader) { m_shader = shader; SyncShaderSchemaProperties(true); SyncShaderSchemaKeywords(true); MarkChanged(true); } void Material::SetRenderQueue(Core::int32 renderQueue) { m_renderQueue = renderQueue; MarkChanged(false); } void Material::SetRenderQueue(MaterialRenderQueue renderQueue) { SetRenderQueue(static_cast(renderQueue)); } void Material::SetRenderState(const MaterialRenderState& renderState) { m_renderState = renderState; m_hasRenderStateOverride = true; MarkChanged(false); } void Material::SetRenderStateOverrideEnabled(bool enabled) { m_hasRenderStateOverride = enabled; MarkChanged(false); } void Material::SetTag(const Containers::String& name, const Containers::String& value) { for (MaterialTagEntry& tag : m_tags) { if (tag.name == name) { tag.value = value; MarkChanged(false); return; } } MaterialTagEntry tag; tag.name = name; tag.value = value; m_tags.PushBack(tag); MarkChanged(false); } Containers::String Material::GetTag(const Containers::String& name) const { for (const MaterialTagEntry& tag : m_tags) { if (tag.name == name) { return tag.value; } } return Containers::String(); } bool Material::HasTag(const Containers::String& name) const { for (const MaterialTagEntry& tag : m_tags) { if (tag.name == name) { return true; } } return false; } void Material::RemoveTag(const Containers::String& name) { for (size_t tagIndex = 0; tagIndex < m_tags.Size(); ++tagIndex) { if (m_tags[tagIndex].name == name) { if (tagIndex != m_tags.Size() - 1) { m_tags[tagIndex] = std::move(m_tags.Back()); } m_tags.PopBack(); MarkChanged(false); return; } } } void Material::ClearTags() { m_tags.Clear(); MarkChanged(false); } Containers::String Material::GetTagName(Core::uint32 index) const { return index < m_tags.Size() ? m_tags[index].name : Containers::String(); } Containers::String Material::GetTagValue(Core::uint32 index) const { return index < m_tags.Size() ? m_tags[index].value : Containers::String(); } void Material::EnableKeyword(const Containers::String& keyword) { const Containers::String normalizedKeyword = NormalizeShaderKeywordToken(keyword); if (normalizedKeyword.Empty()) { return; } if (m_shader.Get() != nullptr && !m_shader->DeclaresKeyword(normalizedKeyword)) { return; } for (const Containers::String& existingKeyword : m_keywordSet.enabledKeywords) { if (existingKeyword == normalizedKeyword) { return; } } m_keywordSet.enabledKeywords.PushBack(normalizedKeyword); std::sort( m_keywordSet.enabledKeywords.begin(), m_keywordSet.enabledKeywords.end(), CompareShaderKeywordTokens); MarkChanged(false); } void Material::DisableKeyword(const Containers::String& keyword) { const Containers::String normalizedKeyword = NormalizeShaderKeywordToken(keyword); if (normalizedKeyword.Empty()) { return; } for (size_t keywordIndex = 0; keywordIndex < m_keywordSet.enabledKeywords.Size(); ++keywordIndex) { if (m_keywordSet.enabledKeywords[keywordIndex] == normalizedKeyword) { if (keywordIndex != m_keywordSet.enabledKeywords.Size() - 1) { m_keywordSet.enabledKeywords[keywordIndex] = std::move(m_keywordSet.enabledKeywords.Back()); } m_keywordSet.enabledKeywords.PopBack(); std::sort( m_keywordSet.enabledKeywords.begin(), m_keywordSet.enabledKeywords.end(), CompareShaderKeywordTokens); MarkChanged(false); return; } } } void Material::SetKeywordEnabled(const Containers::String& keyword, bool enabled) { if (enabled) { EnableKeyword(keyword); } else { DisableKeyword(keyword); } } bool Material::IsKeywordEnabled(const Containers::String& keyword) const { const Containers::String normalizedKeyword = NormalizeShaderKeywordToken(keyword); if (normalizedKeyword.Empty()) { return false; } for (const Containers::String& existingKeyword : m_keywordSet.enabledKeywords) { if (existingKeyword == normalizedKeyword) { return true; } } return false; } void Material::ClearKeywords() { if (m_keywordSet.enabledKeywords.Empty()) { return; } m_keywordSet.enabledKeywords.Clear(); MarkChanged(false); } Containers::String Material::GetKeyword(Core::uint32 index) const { return index < m_keywordSet.enabledKeywords.Size() ? m_keywordSet.enabledKeywords[index] : Containers::String(); } void Material::SetFloat(const Containers::String& name, float value) { if (!CanAssignPropertyType(name, MaterialPropertyType::Float)) { return; } RemoveTextureBindingByName(m_textureBindings, name); MaterialProperty prop; prop.name = name; prop.type = MaterialPropertyType::Float; prop.value.floatValue[0] = value; prop.refCount = 1; m_properties.Insert(name, prop); MarkChanged(true); } void Material::SetFloat2(const Containers::String& name, const Math::Vector2& value) { if (!CanAssignPropertyType(name, MaterialPropertyType::Float2)) { return; } RemoveTextureBindingByName(m_textureBindings, name); MaterialProperty prop; prop.name = name; prop.type = MaterialPropertyType::Float2; prop.value.floatValue[0] = value.x; prop.value.floatValue[1] = value.y; prop.refCount = 1; m_properties.Insert(name, prop); MarkChanged(true); } void Material::SetFloat3(const Containers::String& name, const Math::Vector3& value) { if (!CanAssignPropertyType(name, MaterialPropertyType::Float3)) { return; } RemoveTextureBindingByName(m_textureBindings, name); MaterialProperty prop; prop.name = name; prop.type = MaterialPropertyType::Float3; prop.value.floatValue[0] = value.x; prop.value.floatValue[1] = value.y; prop.value.floatValue[2] = value.z; prop.refCount = 1; m_properties.Insert(name, prop); MarkChanged(true); } void Material::SetFloat4(const Containers::String& name, const Math::Vector4& value) { if (!CanAssignPropertyType(name, MaterialPropertyType::Float4)) { return; } RemoveTextureBindingByName(m_textureBindings, name); MaterialProperty prop; prop.name = name; prop.type = MaterialPropertyType::Float4; prop.value.floatValue[0] = value.x; prop.value.floatValue[1] = value.y; prop.value.floatValue[2] = value.z; prop.value.floatValue[3] = value.w; prop.refCount = 1; m_properties.Insert(name, prop); MarkChanged(true); } void Material::SetInt(const Containers::String& name, Core::int32 value) { if (!CanAssignPropertyType(name, MaterialPropertyType::Int)) { return; } RemoveTextureBindingByName(m_textureBindings, name); MaterialProperty prop; prop.name = name; prop.type = MaterialPropertyType::Int; prop.value.intValue[0] = value; prop.refCount = 1; m_properties.Insert(name, prop); MarkChanged(true); } void Material::SetBool(const Containers::String& name, bool value) { if (!CanAssignPropertyType(name, MaterialPropertyType::Bool)) { return; } RemoveTextureBindingByName(m_textureBindings, name); MaterialProperty prop; prop.name = name; prop.type = MaterialPropertyType::Bool; prop.value.boolValue = value; prop.refCount = 1; m_properties.Insert(name, prop); MarkChanged(true); } void Material::SetTexture(const Containers::String& name, const ResourceHandle& texture) { const ShaderPropertyDesc* shaderProperty = FindShaderPropertyDesc(name); const MaterialPropertyType propertyType = shaderProperty != nullptr ? GetMaterialPropertyTypeForShaderProperty(shaderProperty->type) : MaterialPropertyType::Texture; if (!CanAssignPropertyType(name, propertyType)) { return; } EnsureTextureProperty(m_properties, name, propertyType); AssetRef textureRef; Containers::String texturePath = texture.Get() != nullptr ? texture->GetPath() : Containers::String(); if (!texturePath.Empty() && !HasVirtualPathScheme(texturePath)) { ResourceManager::Get().TryGetAssetRef(texturePath, ResourceType::Texture, textureRef); } for (auto& binding : m_textureBindings) { if (binding.name == name) { binding.texture = texture; binding.textureRef = textureRef; binding.texturePath = texturePath; binding.pendingLoad.reset(); MarkChanged(false); return; } } MaterialTextureBinding binding; binding.name = name; binding.slot = static_cast(m_textureBindings.Size()); binding.texture = texture; binding.textureRef = textureRef; binding.texturePath = texturePath; m_textureBindings.PushBack(binding); MarkChanged(false); } void Material::SetTextureAssetRef(const Containers::String& name, const AssetRef& textureRef, const Containers::String& texturePath) { if (!textureRef.IsValid() && texturePath.Empty()) { RemoveProperty(name); return; } const ShaderPropertyDesc* shaderProperty = FindShaderPropertyDesc(name); const MaterialPropertyType propertyType = shaderProperty != nullptr ? GetMaterialPropertyTypeForShaderProperty(shaderProperty->type) : MaterialPropertyType::Texture; if (!CanAssignPropertyType(name, propertyType)) { return; } EnsureTextureProperty(m_properties, name, propertyType); for (auto& binding : m_textureBindings) { if (binding.name == name) { binding.texture.Reset(); binding.textureRef = textureRef; binding.texturePath = texturePath; binding.pendingLoad.reset(); MarkChanged(false); return; } } MaterialTextureBinding binding; binding.name = name; binding.slot = static_cast(m_textureBindings.Size()); binding.textureRef = textureRef; binding.texturePath = texturePath; m_textureBindings.PushBack(binding); MarkChanged(false); } void Material::SetTexturePath(const Containers::String& name, const Containers::String& texturePath) { if (texturePath.Empty()) { RemoveProperty(name); return; } const ShaderPropertyDesc* shaderProperty = FindShaderPropertyDesc(name); const MaterialPropertyType propertyType = shaderProperty != nullptr ? GetMaterialPropertyTypeForShaderProperty(shaderProperty->type) : MaterialPropertyType::Texture; if (!CanAssignPropertyType(name, propertyType)) { return; } EnsureTextureProperty(m_properties, name, propertyType); for (auto& binding : m_textureBindings) { if (binding.name == name) { binding.texture.Reset(); binding.textureRef.Reset(); binding.texturePath = texturePath; binding.pendingLoad.reset(); MarkChanged(false); return; } } MaterialTextureBinding binding; binding.name = name; binding.slot = static_cast(m_textureBindings.Size()); binding.textureRef.Reset(); binding.texturePath = texturePath; m_textureBindings.PushBack(binding); MarkChanged(false); } float Material::GetFloat(const Containers::String& name) const { auto* prop = m_properties.Find(name); if (prop && prop->type == MaterialPropertyType::Float) { return prop->value.floatValue[0]; } return 0.0f; } Math::Vector2 Material::GetFloat2(const Containers::String& name) const { auto* prop = m_properties.Find(name); if (prop && prop->type == MaterialPropertyType::Float2) { return Math::Vector2(prop->value.floatValue[0], prop->value.floatValue[1]); } return Math::Vector2::Zero(); } Math::Vector3 Material::GetFloat3(const Containers::String& name) const { auto* prop = m_properties.Find(name); if (prop && prop->type == MaterialPropertyType::Float3) { return Math::Vector3(prop->value.floatValue[0], prop->value.floatValue[1], prop->value.floatValue[2]); } return Math::Vector3::Zero(); } Math::Vector4 Material::GetFloat4(const Containers::String& name) const { auto* prop = m_properties.Find(name); if (prop && prop->type == MaterialPropertyType::Float4) { return Math::Vector4(prop->value.floatValue[0], prop->value.floatValue[1], prop->value.floatValue[2], prop->value.floatValue[3]); } return Math::Vector4::Zero(); } Core::int32 Material::GetInt(const Containers::String& name) const { auto* prop = m_properties.Find(name); if (prop && prop->type == MaterialPropertyType::Int) { return prop->value.intValue[0]; } return 0; } bool Material::GetBool(const Containers::String& name) const { auto* prop = m_properties.Find(name); if (prop && prop->type == MaterialPropertyType::Bool) { return prop->value.boolValue; } return false; } ResourceHandle Material::GetTexture(const Containers::String& name) const { Material* material = const_cast(this); material->ResolvePendingTextureBindings(); for (Core::uint32 bindingIndex = 0; bindingIndex < material->m_textureBindings.Size(); ++bindingIndex) { MaterialTextureBinding& binding = material->m_textureBindings[bindingIndex]; if (binding.name == name) { if (binding.texture.Get() == nullptr && binding.pendingLoad == nullptr && (!binding.texturePath.Empty() || binding.textureRef.IsValid())) { material->BeginAsyncTextureLoad(bindingIndex); } return binding.texture; } } return ResourceHandle(); } Containers::String Material::GetTextureBindingName(Core::uint32 index) const { return index < m_textureBindings.Size() ? m_textureBindings[index].name : Containers::String(); } AssetRef Material::GetTextureBindingAssetRef(Core::uint32 index) const { return index < m_textureBindings.Size() ? m_textureBindings[index].textureRef : AssetRef(); } Containers::String Material::GetTextureBindingPath(Core::uint32 index) const { return index < m_textureBindings.Size() ? m_textureBindings[index].texturePath : Containers::String(); } ResourceHandle Material::GetTextureBindingLoadedTexture(Core::uint32 index) const { return index < m_textureBindings.Size() ? m_textureBindings[index].texture : ResourceHandle(); } ResourceHandle Material::GetTextureBindingTexture(Core::uint32 index) const { Material* material = const_cast(this); material->ResolvePendingTextureBinding(index); if (index < material->m_textureBindings.Size()) { MaterialTextureBinding& binding = material->m_textureBindings[index]; if (binding.texture.Get() == nullptr && binding.pendingLoad == nullptr && (!binding.texturePath.Empty() || binding.textureRef.IsValid())) { material->BeginAsyncTextureLoad(index); } return binding.texture; } return ResourceHandle(); } std::vector Material::GetProperties() const { std::vector properties; const auto pairs = m_properties.GetPairs(); properties.reserve(pairs.Size()); for (const auto& pair : pairs) { properties.push_back(pair.second); } return properties; } const MaterialConstantFieldDesc* Material::FindConstantField(const Containers::String& name) const { for (const MaterialConstantFieldDesc& field : m_constantLayout) { if (field.name == name) { return &field; } } return nullptr; } void Material::UpdateConstantBuffer() { std::vector packedProperties; if (m_shader.Get() != nullptr && !m_shader->GetProperties().Empty()) { packedProperties.reserve(m_shader->GetProperties().Size()); for (const ShaderPropertyDesc& shaderProperty : m_shader->GetProperties()) { const MaterialProperty* property = m_properties.Find(shaderProperty.name); if (property == nullptr || !IsPackedMaterialPropertyType(property->type) || !IsMaterialPropertyCompatibleWithShaderProperty(property->type, shaderProperty.type)) { continue; } packedProperties.push_back(*property); } } else { const auto pairs = m_properties.GetPairs(); packedProperties.reserve(pairs.Size()); for (const auto& pair : pairs) { if (IsPackedMaterialPropertyType(pair.second.type)) { packedProperties.push_back(pair.second); } } std::sort( packedProperties.begin(), packedProperties.end(), [](const MaterialProperty& left, const MaterialProperty& right) { return std::strcmp(left.name.CStr(), right.name.CStr()) < 0; }); } m_constantLayout.Clear(); m_constantLayout.Reserve(packedProperties.size()); Core::uint32 currentOffset = 0; for (const MaterialProperty& property : packedProperties) { MaterialConstantFieldDesc field; field.name = property.name; field.type = property.type; field.offset = currentOffset; field.size = GetPackedMaterialPropertySize(property.type); field.alignedSize = static_cast(kMaterialConstantSlotSize); m_constantLayout.PushBack(field); currentOffset += field.alignedSize; } m_constantBufferData.Clear(); m_constantBufferData.Resize(static_cast(currentOffset)); if (!packedProperties.empty()) { std::memset(m_constantBufferData.Data(), 0, m_constantBufferData.Size()); } for (size_t propertyIndex = 0; propertyIndex < packedProperties.size(); ++propertyIndex) { WritePackedMaterialProperty( m_constantBufferData.Data() + propertyIndex * kMaterialConstantSlotSize, packedProperties[propertyIndex]); } UpdateMemorySize(); } void Material::RecalculateMemorySize() { UpdateMemorySize(); } void Material::BeginAsyncTextureLoad(Core::uint32 index) { if (index >= m_textureBindings.Size()) { return; } MaterialTextureBinding& binding = m_textureBindings[index]; if (binding.texture.Get() != nullptr || binding.pendingLoad != nullptr) { return; } if (binding.texturePath.Empty() && binding.textureRef.IsValid()) { ResourceManager::Get().TryResolveAssetPath(binding.textureRef, binding.texturePath); } if (binding.texturePath.Empty()) { return; } binding.pendingLoad = std::make_shared(); std::weak_ptr weakState = binding.pendingLoad; const Containers::String texturePath = binding.texturePath; ResourceManager::Get().LoadAsync( texturePath, ResourceType::Texture, [weakState](LoadResult result) { if (std::shared_ptr state = weakState.lock()) { state->resource = result.resource; state->errorMessage = result.errorMessage; state->completed = true; } }); } void Material::ResolvePendingTextureBinding(Core::uint32 index) { if (index >= m_textureBindings.Size()) { return; } MaterialTextureBinding& binding = m_textureBindings[index]; if (!binding.pendingLoad || !binding.pendingLoad->completed) { return; } std::shared_ptr completedLoad = std::move(binding.pendingLoad); binding.pendingLoad.reset(); if (completedLoad->resource == nullptr) { return; } binding.texture = ResourceHandle(static_cast(completedLoad->resource)); if (binding.texture.Get() != nullptr) { if (binding.texturePath.Empty()) { binding.texturePath = binding.texture->GetPath(); } } } void Material::ResolvePendingTextureBindings() { for (Core::uint32 bindingIndex = 0; bindingIndex < m_textureBindings.Size(); ++bindingIndex) { ResolvePendingTextureBinding(bindingIndex); } } bool Material::HasProperty(const Containers::String& name) const { return m_properties.Contains(name); } void Material::RemoveProperty(const Containers::String& name) { if (ResetPropertyToShaderDefault(name)) { MarkChanged(true); return; } const MaterialProperty* property = m_properties.Find(name); const bool removeTextureBinding = property != nullptr && (property->type == MaterialPropertyType::Texture || property->type == MaterialPropertyType::Cubemap); m_properties.Erase(name); if (removeTextureBinding) { RemoveTextureBindingByName(m_textureBindings, name); } MarkChanged(true); } void Material::ClearAllProperties() { m_properties.Clear(); m_constantLayout.Clear(); m_textureBindings.Clear(); m_constantBufferData.Clear(); SyncShaderSchemaProperties(false); MarkChanged(true); } const ShaderPropertyDesc* Material::FindShaderPropertyDesc(const Containers::String& name) const { if (m_shader.Get() == nullptr) { return nullptr; } return m_shader->FindProperty(name); } bool Material::CanAssignPropertyType(const Containers::String& name, MaterialPropertyType type) const { const ShaderPropertyDesc* shaderProperty = FindShaderPropertyDesc(name); if (shaderProperty == nullptr) { return m_shader.Get() == nullptr; } return IsMaterialPropertyCompatibleWithShaderProperty(type, shaderProperty->type); } bool Material::ResetPropertyToShaderDefault(const Containers::String& name) { const ShaderPropertyDesc* shaderProperty = FindShaderPropertyDesc(name); if (shaderProperty == nullptr) { return false; } MaterialProperty defaultProperty; if (!TryBuildDefaultMaterialProperty(*shaderProperty, defaultProperty)) { return false; } RemoveTextureBindingByName(m_textureBindings, name); m_properties.Insert(name, defaultProperty); return true; } void Material::SyncShaderSchemaProperties(bool removeUnknownProperties) { if (m_shader.Get() == nullptr) { return; } if (removeUnknownProperties) { std::vector unknownProperties; const auto pairs = m_properties.GetPairs(); unknownProperties.reserve(pairs.Size()); for (const auto& pair : pairs) { if (FindShaderPropertyDesc(pair.first) == nullptr) { unknownProperties.push_back(pair.first); } } for (const Containers::String& propertyName : unknownProperties) { m_properties.Erase(propertyName); RemoveTextureBindingByName(m_textureBindings, propertyName); } } for (const ShaderPropertyDesc& shaderProperty : m_shader->GetProperties()) { const MaterialProperty* property = m_properties.Find(shaderProperty.name); if (property == nullptr || !IsMaterialPropertyCompatibleWithShaderProperty(property->type, shaderProperty.type)) { ResetPropertyToShaderDefault(shaderProperty.name); continue; } if (!IsTextureMaterialPropertyType(property->type)) { RemoveTextureBindingByName(m_textureBindings, shaderProperty.name); } } } void Material::SyncShaderSchemaKeywords(bool removeUnknownKeywords) { if (m_shader.Get() == nullptr || !removeUnknownKeywords) { return; } for (size_t keywordIndex = 0; keywordIndex < m_keywordSet.enabledKeywords.Size();) { if (m_shader->DeclaresKeyword(m_keywordSet.enabledKeywords[keywordIndex])) { ++keywordIndex; continue; } if (keywordIndex != m_keywordSet.enabledKeywords.Size() - 1) { m_keywordSet.enabledKeywords[keywordIndex] = std::move(m_keywordSet.enabledKeywords.Back()); } m_keywordSet.enabledKeywords.PopBack(); } std::sort( m_keywordSet.enabledKeywords.begin(), m_keywordSet.enabledKeywords.end(), CompareShaderKeywordTokens); } void Material::MarkChanged(bool updateConstantBuffer) { if (updateConstantBuffer) { UpdateConstantBuffer(); } else { UpdateMemorySize(); } ++m_changeVersion; } void Material::UpdateMemorySize() { m_memorySize = m_constantBufferData.Size() + m_constantLayout.Size() * sizeof(MaterialConstantFieldDesc) + sizeof(MaterialRenderState) + m_tags.Size() * sizeof(MaterialTagEntry) + m_keywordSet.enabledKeywords.Size() * sizeof(Containers::String) + m_textureBindings.Size() * sizeof(MaterialTextureBinding) + m_properties.Size() * sizeof(MaterialProperty) + m_name.Length() + m_path.Length(); for (const MaterialTagEntry& tag : m_tags) { m_memorySize += tag.name.Length(); m_memorySize += tag.value.Length(); } for (const Containers::String& keyword : m_keywordSet.enabledKeywords) { m_memorySize += keyword.Length(); } for (const MaterialConstantFieldDesc& field : m_constantLayout) { m_memorySize += field.name.Length(); } for (const auto& binding : m_textureBindings) { m_memorySize += binding.name.Length(); m_memorySize += binding.texturePath.Length(); } } } // namespace Resources } // namespace XCEngine