From 7711a8151e6e65e80c5bdc4b60b03725796eb95d Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 7 Apr 2026 21:35:23 +0800 Subject: [PATCH] editor: preserve material inspector authored state --- ...ctor与Shader属性面板收口计划_2026-04-07.md | 55 +++ editor/src/panels/InspectorPanel.cpp | 463 ++++++++++++++++-- editor/src/panels/InspectorPanel.h | 22 +- 3 files changed, 492 insertions(+), 48 deletions(-) diff --git a/docs/plan/Material Inspector与Shader属性面板收口计划_2026-04-07.md b/docs/plan/Material Inspector与Shader属性面板收口计划_2026-04-07.md index 8df78a41..4b21822b 100644 --- a/docs/plan/Material Inspector与Shader属性面板收口计划_2026-04-07.md +++ b/docs/plan/Material Inspector与Shader属性面板收口计划_2026-04-07.md @@ -385,3 +385,58 @@ Material 面板不应再次演化成随意堆字段的临时入口。所有 rend 1. 扩展 `MaterialAssetState`,让其正式承载属性、纹理槽、关键词与 render state override。 2. 重构 `Populate / Apply / Save / Reload` 这四条关键链路。 3. 彻底消除当前“保存一次就丢属性”的结构性问题。 + +## 12. Phase 2 执行结果 + +状态:已完成 + +本阶段已经完成 Material Inspector 数据模型的第一轮正式收口,重点是先把状态模型与保存链路做正确,而不是提前堆属性 UI。 + +### 12.1 已完成内容 + +- `MaterialAssetState` 已扩展为正式承载: + - `keywords` + - `properties` + - `texture bindings` + - `renderState override` +- `PopulateMaterialAssetStateFromResource()` 已从运行时 `Material` 同步: + - Shader 路径 + - Render Queue + - Render State + - Render State Override 标志 + - Tags + - Keywords + - Properties / Texture Bindings +- `ApplyResolvedMaterialStateToResource()` 已开始完整写回: + - Shader + - Render Queue + - Render State + - Render State Override + - Tags + - Keywords + - Properties / Texture Bindings +- `BuildMaterialAssetFileText()` 已开始正式写出: + - `keywords` + - `properties` + - `textures` + - 仅在启用 override 时才写 `renderState` +- Inspector 已增加 `Render State Override` 开关,避免默认把材质强行推进到显式 override 路径。 + +### 12.2 本阶段解决的核心问题 + +本阶段已经解决了最危险的结构性问题: + +- 当前 Inspector 再保存材质时,不会像之前那样天然丢掉 `properties / textures / keywords`。 +- Render State 是否为显式 override,已经不再是隐藏副作用,而成为正式状态的一部分。 + +### 12.3 本阶段仍未完成的部分 + +以下能力还没有在本阶段完成,这是下一阶段的工作重点: + +- 基于 Shader schema 的属性 UI 自动生成 +- 各属性类型的可视化编辑控件 +- 默认值/覆盖值的明确表现 +- 纹理槽的正式资源选择 UI +- 关键词与属性在 Inspector 中的可视化编辑 + +因此,Phase 2 的性质是“先把材质状态模型与保存链路做对”,而不是“材质面板功能已经完整”。 diff --git a/editor/src/panels/InspectorPanel.cpp b/editor/src/panels/InspectorPanel.cpp index bbeb2841..d21a5a4c 100644 --- a/editor/src/panels/InspectorPanel.cpp +++ b/editor/src/panels/InspectorPanel.cpp @@ -21,11 +21,13 @@ #include #include +#include #include #include #include #include #include +#include namespace XCEngine { namespace Editor { @@ -430,14 +432,266 @@ const char* SerializeBlendFactor(::XCEngine::Resources::MaterialBlendFactor fact } } +const char* SerializeStencilOp(::XCEngine::Resources::MaterialStencilOp op) { + switch (op) { + case ::XCEngine::Resources::MaterialStencilOp::Zero: + return "zero"; + case ::XCEngine::Resources::MaterialStencilOp::Replace: + return "replace"; + case ::XCEngine::Resources::MaterialStencilOp::IncrSat: + return "incrsat"; + case ::XCEngine::Resources::MaterialStencilOp::DecrSat: + return "decrsat"; + case ::XCEngine::Resources::MaterialStencilOp::Invert: + return "invert"; + case ::XCEngine::Resources::MaterialStencilOp::IncrWrap: + return "incrwrap"; + case ::XCEngine::Resources::MaterialStencilOp::DecrWrap: + return "decrwrap"; + case ::XCEngine::Resources::MaterialStencilOp::Keep: + default: + return "keep"; + } +} + +bool IsTextureMaterialPropertyType(::XCEngine::Resources::MaterialPropertyType type) { + return type == ::XCEngine::Resources::MaterialPropertyType::Texture || + type == ::XCEngine::Resources::MaterialPropertyType::Cubemap; +} + +int GetMaterialPropertyComponentCount(::XCEngine::Resources::MaterialPropertyType type) { + switch (type) { + case ::XCEngine::Resources::MaterialPropertyType::Float2: + case ::XCEngine::Resources::MaterialPropertyType::Int2: + return 2; + case ::XCEngine::Resources::MaterialPropertyType::Float3: + case ::XCEngine::Resources::MaterialPropertyType::Int3: + return 3; + case ::XCEngine::Resources::MaterialPropertyType::Float4: + case ::XCEngine::Resources::MaterialPropertyType::Int4: + return 4; + case ::XCEngine::Resources::MaterialPropertyType::Float: + case ::XCEngine::Resources::MaterialPropertyType::Int: + case ::XCEngine::Resources::MaterialPropertyType::Bool: + case ::XCEngine::Resources::MaterialPropertyType::Texture: + case ::XCEngine::Resources::MaterialPropertyType::Cubemap: + default: + return 1; + } +} + +std::string FormatJsonFloat(float value) { + char buffer[32] = {}; + std::snprintf(buffer, sizeof(buffer), "%.6g", static_cast(value)); + return std::string(buffer); +} + +std::string BuildMaterialPropertyValueText(const InspectorPanel::MaterialPropertyState& property) { + using PropertyType = ::XCEngine::Resources::MaterialPropertyType; + + switch (property.type) { + case PropertyType::Float: + return FormatJsonFloat(property.floatValue[0]); + case PropertyType::Float2: + case PropertyType::Float3: + case PropertyType::Float4: { + const int componentCount = GetMaterialPropertyComponentCount(property.type); + std::string text = "["; + for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) { + if (componentIndex > 0) { + text += ", "; + } + text += FormatJsonFloat(property.floatValue[componentIndex]); + } + text += "]"; + return text; + } + case PropertyType::Int: + return std::to_string(property.intValue[0]); + case PropertyType::Int2: + case PropertyType::Int3: + case PropertyType::Int4: { + const int componentCount = GetMaterialPropertyComponentCount(property.type); + std::string text = "["; + for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) { + if (componentIndex > 0) { + text += ", "; + } + text += std::to_string(property.intValue[componentIndex]); + } + text += "]"; + return text; + } + case PropertyType::Bool: + return property.boolValue ? "true" : "false"; + case PropertyType::Texture: + case PropertyType::Cubemap: + default: + return std::string(); + } +} + +bool TryResolveMaterialTextureBindingPath( + const ::XCEngine::Resources::Material& material, + const Containers::String& propertyName, + std::string& outPath) { + for (Core::uint32 bindingIndex = 0; bindingIndex < material.GetTextureBindingCount(); ++bindingIndex) { + if (material.GetTextureBindingName(bindingIndex) != propertyName) { + continue; + } + + Containers::String bindingPath = material.GetTextureBindingPath(bindingIndex); + if (bindingPath.Empty()) { + const ::XCEngine::Resources::AssetRef assetRef = material.GetTextureBindingAssetRef(bindingIndex); + if (assetRef.IsValid()) { + ::XCEngine::Resources::ResourceManager::Get().TryResolveAssetPath(assetRef, bindingPath); + } + } + + outPath = std::string(bindingPath.CStr()); + return true; + } + + return false; +} + +InspectorPanel::MaterialPropertyState BuildMaterialPropertyState( + const ::XCEngine::Resources::MaterialProperty& property, + const ::XCEngine::Resources::Material& material) { + InspectorPanel::MaterialPropertyState state; + state.name = std::string(property.name.CStr()); + state.type = property.type; + for (size_t index = 0; index < state.floatValue.size(); ++index) { + state.floatValue[index] = property.value.floatValue[index]; + state.intValue[index] = property.value.intValue[index]; + } + state.boolValue = property.value.boolValue; + if (IsTextureMaterialPropertyType(property.type)) { + TryResolveMaterialTextureBindingPath(material, property.name, state.texturePath); + } + return state; +} + +std::vector CollectMaterialPropertyStates( + const ::XCEngine::Resources::Material& material) { + std::vector<::XCEngine::Resources::MaterialProperty> properties = material.GetProperties(); + const ::XCEngine::Resources::Shader* shader = material.GetShader(); + if (shader != nullptr && !shader->GetProperties().Empty()) { + std::unordered_map shaderPropertyOrder; + shaderPropertyOrder.reserve(shader->GetProperties().Size()); + for (size_t propertyIndex = 0; propertyIndex < shader->GetProperties().Size(); ++propertyIndex) { + shaderPropertyOrder.emplace(std::string(shader->GetProperties()[propertyIndex].name.CStr()), propertyIndex); + } + + std::sort( + properties.begin(), + properties.end(), + [&shaderPropertyOrder](const auto& left, const auto& right) { + const std::string leftName(left.name.CStr()); + const std::string rightName(right.name.CStr()); + const auto leftIt = shaderPropertyOrder.find(leftName); + const auto rightIt = shaderPropertyOrder.find(rightName); + const size_t leftOrder = leftIt != shaderPropertyOrder.end() ? leftIt->second : shaderPropertyOrder.size(); + const size_t rightOrder = rightIt != shaderPropertyOrder.end() ? rightIt->second : shaderPropertyOrder.size(); + if (leftOrder != rightOrder) { + return leftOrder < rightOrder; + } + return leftName < rightName; + }); + } else { + std::sort( + properties.begin(), + properties.end(), + [](const auto& left, const auto& right) { + return std::strcmp(left.name.CStr(), right.name.CStr()) < 0; + }); + } + + std::vector states; + states.reserve(properties.size()); + for (const ::XCEngine::Resources::MaterialProperty& property : properties) { + states.push_back(BuildMaterialPropertyState(property, material)); + } + return states; +} + +void ApplyMaterialKeywordsToResource( + const InspectorPanel::MaterialAssetState& state, + ::XCEngine::Resources::Material& material) { + material.ClearKeywords(); + for (const InspectorPanel::MaterialKeywordState& keyword : state.keywords) { + const std::string keywordValue = TrimCopy(keyword.value); + if (!keywordValue.empty()) { + material.EnableKeyword(keywordValue.c_str()); + } + } +} + +void ApplyMaterialPropertiesToResource( + const InspectorPanel::MaterialAssetState& state, + ::XCEngine::Resources::Material& material) { + material.ClearAllProperties(); + for (const InspectorPanel::MaterialPropertyState& property : state.properties) { + if (property.name.empty()) { + continue; + } + + const Containers::String propertyName(property.name.c_str()); + switch (property.type) { + case ::XCEngine::Resources::MaterialPropertyType::Float: + material.SetFloat(propertyName, property.floatValue[0]); + break; + case ::XCEngine::Resources::MaterialPropertyType::Float2: + material.SetFloat2( + propertyName, + ::XCEngine::Math::Vector2(property.floatValue[0], property.floatValue[1])); + break; + case ::XCEngine::Resources::MaterialPropertyType::Float3: + material.SetFloat3( + propertyName, + ::XCEngine::Math::Vector3( + property.floatValue[0], + property.floatValue[1], + property.floatValue[2])); + break; + case ::XCEngine::Resources::MaterialPropertyType::Float4: + material.SetFloat4( + propertyName, + ::XCEngine::Math::Vector4( + property.floatValue[0], + property.floatValue[1], + property.floatValue[2], + property.floatValue[3])); + break; + case ::XCEngine::Resources::MaterialPropertyType::Int: + material.SetInt(propertyName, property.intValue[0]); + break; + case ::XCEngine::Resources::MaterialPropertyType::Bool: + material.SetBool(propertyName, property.boolValue); + break; + case ::XCEngine::Resources::MaterialPropertyType::Texture: + case ::XCEngine::Resources::MaterialPropertyType::Cubemap: + if (!property.texturePath.empty()) { + material.SetTexturePath(propertyName, property.texturePath.c_str()); + } + break; + case ::XCEngine::Resources::MaterialPropertyType::Int2: + case ::XCEngine::Resources::MaterialPropertyType::Int3: + case ::XCEngine::Resources::MaterialPropertyType::Int4: + default: + break; + } + } +} + void ApplyResolvedMaterialStateToResource( const InspectorPanel::MaterialAssetState& state, const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shader, ::XCEngine::Resources::Material& material) { material.SetShader(shader); - material.SetShaderPass(TrimCopy(BufferToString(state.shaderPass)).c_str()); material.SetRenderQueue(state.renderQueue); material.SetRenderState(state.renderState); + material.SetRenderStateOverrideEnabled(state.hasRenderStateOverride); material.ClearTags(); for (const InspectorPanel::MaterialTagEditRow& row : state.tags) { const std::string tagName = TrimCopy(BufferToString(row.name)); @@ -446,6 +700,8 @@ void ApplyResolvedMaterialStateToResource( } material.SetTag(tagName.c_str(), TrimCopy(BufferToString(row.value)).c_str()); } + ApplyMaterialKeywordsToResource(state, material); + ApplyMaterialPropertiesToResource(state, material); material.RecalculateMemorySize(); } @@ -501,11 +757,6 @@ std::string BuildMaterialAssetFileText(const InspectorPanel::MaterialAssetState& rootEntries.push_back(" \"shader\": \"" + EscapeJsonString(shaderPath) + "\""); } - const std::string shaderPass = TrimCopy(BufferToString(state.shaderPass)); - if (!shaderPass.empty()) { - rootEntries.push_back(" \"shaderPass\": \"" + EscapeJsonString(shaderPass) + "\""); - } - const int renderQueuePreset = ResolveRenderQueuePresetIndex(state.renderQueue); if (renderQueuePreset != kCustomRenderQueuePresetIndex) { rootEntries.push_back( @@ -537,39 +788,151 @@ std::string BuildMaterialAssetFileText(const InspectorPanel::MaterialAssetState& rootEntries.push_back(tagsObject); } - const ::XCEngine::Resources::MaterialRenderState& renderState = state.renderState; - std::vector renderStateEntries; - renderStateEntries.push_back( - " \"cull\": \"" + std::string(SerializeCullMode(renderState.cullMode)) + "\""); - renderStateEntries.push_back(std::string(" \"depthTest\": ") + (renderState.depthTestEnable ? "true" : "false")); - renderStateEntries.push_back(std::string(" \"depthWrite\": ") + (renderState.depthWriteEnable ? "true" : "false")); - renderStateEntries.push_back( - " \"depthFunc\": \"" + std::string(SerializeComparisonFunc(renderState.depthFunc)) + "\""); - renderStateEntries.push_back(std::string(" \"blendEnable\": ") + (renderState.blendEnable ? "true" : "false")); - renderStateEntries.push_back( - " \"srcBlend\": \"" + std::string(SerializeBlendFactor(renderState.srcBlend)) + "\""); - renderStateEntries.push_back( - " \"dstBlend\": \"" + std::string(SerializeBlendFactor(renderState.dstBlend)) + "\""); - renderStateEntries.push_back( - " \"srcBlendAlpha\": \"" + std::string(SerializeBlendFactor(renderState.srcBlendAlpha)) + "\""); - renderStateEntries.push_back( - " \"dstBlendAlpha\": \"" + std::string(SerializeBlendFactor(renderState.dstBlendAlpha)) + "\""); - renderStateEntries.push_back( - " \"blendOp\": \"" + std::string(SerializeBlendOp(renderState.blendOp)) + "\""); - renderStateEntries.push_back( - " \"blendOpAlpha\": \"" + std::string(SerializeBlendOp(renderState.blendOpAlpha)) + "\""); - renderStateEntries.push_back(" \"colorWriteMask\": " + std::to_string(renderState.colorWriteMask)); - - std::string renderStateObject = " \"renderState\": {\n"; - for (size_t renderStateIndex = 0; renderStateIndex < renderStateEntries.size(); ++renderStateIndex) { - renderStateObject += renderStateEntries[renderStateIndex]; - if (renderStateIndex + 1 < renderStateEntries.size()) { - renderStateObject += ","; + if (!state.keywords.empty()) { + std::string keywordsArray = " \"keywords\": ["; + bool firstKeyword = true; + for (const InspectorPanel::MaterialKeywordState& keyword : state.keywords) { + const std::string keywordValue = TrimCopy(keyword.value); + if (keywordValue.empty()) { + continue; + } + if (!firstKeyword) { + keywordsArray += ", "; + } + keywordsArray += "\"" + EscapeJsonString(keywordValue) + "\""; + firstKeyword = false; + } + keywordsArray += "]"; + if (!firstKeyword) { + rootEntries.push_back(keywordsArray); } - renderStateObject += "\n"; } - renderStateObject += " }"; - rootEntries.push_back(renderStateObject); + + std::vector propertyEntries; + std::vector textureEntries; + for (const InspectorPanel::MaterialPropertyState& property : state.properties) { + if (property.name.empty()) { + continue; + } + + if (IsTextureMaterialPropertyType(property.type)) { + const std::string texturePath = TrimCopy(property.texturePath); + if (!texturePath.empty()) { + textureEntries.push_back( + " \"" + EscapeJsonString(property.name) + "\": \"" + + EscapeJsonString(texturePath) + "\""); + } + continue; + } + + const std::string propertyValueText = BuildMaterialPropertyValueText(property); + if (!propertyValueText.empty()) { + propertyEntries.push_back( + " \"" + EscapeJsonString(property.name) + "\": " + propertyValueText); + } + } + + if (!propertyEntries.empty()) { + std::string propertiesObject = " \"properties\": {\n"; + for (size_t propertyIndex = 0; propertyIndex < propertyEntries.size(); ++propertyIndex) { + propertiesObject += propertyEntries[propertyIndex]; + if (propertyIndex + 1 < propertyEntries.size()) { + propertiesObject += ","; + } + propertiesObject += "\n"; + } + propertiesObject += " }"; + rootEntries.push_back(propertiesObject); + } + + if (!textureEntries.empty()) { + std::string texturesObject = " \"textures\": {\n"; + for (size_t textureIndex = 0; textureIndex < textureEntries.size(); ++textureIndex) { + texturesObject += textureEntries[textureIndex]; + if (textureIndex + 1 < textureEntries.size()) { + texturesObject += ","; + } + texturesObject += "\n"; + } + texturesObject += " }"; + rootEntries.push_back(texturesObject); + } + + if (state.hasRenderStateOverride) { + const ::XCEngine::Resources::MaterialRenderState& renderState = state.renderState; + std::vector renderStateEntries; + renderStateEntries.push_back( + " \"cull\": \"" + std::string(SerializeCullMode(renderState.cullMode)) + "\""); + renderStateEntries.push_back(std::string(" \"depthTest\": ") + (renderState.depthTestEnable ? "true" : "false")); + renderStateEntries.push_back(std::string(" \"depthWrite\": ") + (renderState.depthWriteEnable ? "true" : "false")); + renderStateEntries.push_back( + " \"depthFunc\": \"" + std::string(SerializeComparisonFunc(renderState.depthFunc)) + "\""); + renderStateEntries.push_back(std::string(" \"blendEnable\": ") + (renderState.blendEnable ? "true" : "false")); + renderStateEntries.push_back( + " \"srcBlend\": \"" + std::string(SerializeBlendFactor(renderState.srcBlend)) + "\""); + renderStateEntries.push_back( + " \"dstBlend\": \"" + std::string(SerializeBlendFactor(renderState.dstBlend)) + "\""); + renderStateEntries.push_back( + " \"srcBlendAlpha\": \"" + std::string(SerializeBlendFactor(renderState.srcBlendAlpha)) + "\""); + renderStateEntries.push_back( + " \"dstBlendAlpha\": \"" + std::string(SerializeBlendFactor(renderState.dstBlendAlpha)) + "\""); + renderStateEntries.push_back( + " \"blendOp\": \"" + std::string(SerializeBlendOp(renderState.blendOp)) + "\""); + renderStateEntries.push_back( + " \"blendOpAlpha\": \"" + std::string(SerializeBlendOp(renderState.blendOpAlpha)) + "\""); + renderStateEntries.push_back(" \"colorWriteMask\": " + std::to_string(renderState.colorWriteMask)); + if (renderState.depthBiasFactor != 0.0f || renderState.depthBiasUnits != 0) { + renderStateEntries.push_back( + " \"offset\": [" + + FormatJsonFloat(renderState.depthBiasFactor) + ", " + + std::to_string(renderState.depthBiasUnits) + "]"); + } + if (renderState.stencil.enabled) { + std::vector stencilEntries; + stencilEntries.push_back(" \"enabled\": true"); + stencilEntries.push_back(" \"ref\": " + std::to_string(renderState.stencil.reference)); + stencilEntries.push_back(" \"readMask\": " + std::to_string(renderState.stencil.readMask)); + stencilEntries.push_back(" \"writeMask\": " + std::to_string(renderState.stencil.writeMask)); + stencilEntries.push_back( + " \"comp\": \"" + std::string(SerializeComparisonFunc(renderState.stencil.front.func)) + "\""); + stencilEntries.push_back( + " \"pass\": \"" + std::string(SerializeStencilOp(renderState.stencil.front.passOp)) + "\""); + stencilEntries.push_back( + " \"fail\": \"" + std::string(SerializeStencilOp(renderState.stencil.front.failOp)) + "\""); + stencilEntries.push_back( + " \"zFail\": \"" + std::string(SerializeStencilOp(renderState.stencil.front.depthFailOp)) + "\""); + stencilEntries.push_back( + " \"compBack\": \"" + std::string(SerializeComparisonFunc(renderState.stencil.back.func)) + "\""); + stencilEntries.push_back( + " \"passBack\": \"" + std::string(SerializeStencilOp(renderState.stencil.back.passOp)) + "\""); + stencilEntries.push_back( + " \"failBack\": \"" + std::string(SerializeStencilOp(renderState.stencil.back.failOp)) + "\""); + stencilEntries.push_back( + " \"zFailBack\": \"" + std::string(SerializeStencilOp(renderState.stencil.back.depthFailOp)) + "\""); + + std::string stencilObject = " \"stencil\": {\n"; + for (size_t stencilIndex = 0; stencilIndex < stencilEntries.size(); ++stencilIndex) { + stencilObject += stencilEntries[stencilIndex]; + if (stencilIndex + 1 < stencilEntries.size()) { + stencilObject += ","; + } + stencilObject += "\n"; + } + stencilObject += " }"; + renderStateEntries.push_back(stencilObject); + } + + std::string renderStateObject = " \"renderState\": {\n"; + for (size_t renderStateIndex = 0; renderStateIndex < renderStateEntries.size(); ++renderStateIndex) { + renderStateObject += renderStateEntries[renderStateIndex]; + if (renderStateIndex + 1 < renderStateEntries.size()) { + renderStateObject += ","; + } + renderStateObject += "\n"; + } + renderStateObject += " }"; + rootEntries.push_back(renderStateObject); + } std::string json = "{\n"; for (size_t entryIndex = 0; entryIndex < rootEntries.size(); ++entryIndex) { @@ -649,15 +1012,16 @@ void InspectorPanel::InvalidateMaterialAssetAsyncState() { void InspectorPanel::PopulateMaterialAssetStateFromResource(::XCEngine::Resources::Material& material) { m_materialAssetState.shaderPath.fill('\0'); - m_materialAssetState.shaderPass.fill('\0'); m_materialAssetState.renderQueue = material.GetRenderQueue(); m_materialAssetState.renderState = material.GetRenderState(); + m_materialAssetState.hasRenderStateOverride = material.HasRenderStateOverride(); m_materialAssetState.tags.clear(); + m_materialAssetState.keywords.clear(); + m_materialAssetState.properties.clear(); if (::XCEngine::Resources::Shader* shader = material.GetShader()) { CopyToCharBuffer(std::string(shader->GetPath().CStr()), m_materialAssetState.shaderPath); } - CopyToCharBuffer(std::string(material.GetShaderPass().CStr()), m_materialAssetState.shaderPass); m_materialAssetState.tags.reserve(material.GetTagCount()); for (Core::uint32 tagIndex = 0; tagIndex < material.GetTagCount(); ++tagIndex) { @@ -667,6 +1031,15 @@ void InspectorPanel::PopulateMaterialAssetStateFromResource(::XCEngine::Resource m_materialAssetState.tags.push_back(row); } + m_materialAssetState.keywords.reserve(material.GetKeywordCount()); + for (Core::uint32 keywordIndex = 0; keywordIndex < material.GetKeywordCount(); ++keywordIndex) { + MaterialKeywordState keywordState; + keywordState.value = std::string(material.GetKeyword(keywordIndex).CStr()); + m_materialAssetState.keywords.push_back(std::move(keywordState)); + } + + m_materialAssetState.properties = CollectMaterialPropertyStates(material); + m_materialAssetState.loaded = true; m_materialAssetState.dirty = false; } @@ -1013,7 +1386,7 @@ void InspectorPanel::RenderMaterialAsset() { "Shader", BufferToString(m_materialAssetState.shaderPath), "Select Shader Asset", - { ".shader", ".hlsl", ".glsl", ".vert", ".frag", ".comp" }); + { ".shader", ".xcshader" }); if (shaderInteraction.clearRequested) { CopyToCharBuffer(std::string(), m_materialAssetState.shaderPath); changed = true; @@ -1023,12 +1396,6 @@ void InspectorPanel::RenderMaterialAsset() { changed = true; } - changed = UI::DrawPropertyRow("Pass", UI::InspectorPropertyLayout(), [&](const UI::PropertyLayoutMetrics& layout) { - UI::AlignPropertyControlVertically(layout, ImGui::GetFrameHeight()); - ImGui::SetNextItemWidth(layout.controlWidth); - return ImGui::InputText("##ShaderPass", m_materialAssetState.shaderPass.data(), m_materialAssetState.shaderPass.size()); - }) || changed; - int renderQueuePreset = ResolveRenderQueuePresetIndex(m_materialAssetState.renderQueue); const int newRenderQueuePreset = UI::DrawPropertyCombo( "Render Queue", @@ -1061,6 +1428,9 @@ void InspectorPanel::RenderMaterialAsset() { ImGui::Indent(renderStateSection.contentIndent); bool changed = false; + changed = UI::DrawPropertyBool("Override", m_materialAssetState.hasRenderStateOverride) || changed; + + ImGui::BeginDisabled(!m_materialAssetState.hasRenderStateOverride); int cullModeIndex = ResolveCullModeIndex(m_materialAssetState.renderState.cullMode); const int newCullModeIndex = UI::DrawPropertyCombo( "Cull", @@ -1160,6 +1530,7 @@ void InspectorPanel::RenderMaterialAsset() { static_cast(std::clamp(colorWriteMask, 0, 15)); changed = true; } + ImGui::EndDisabled(); if (changed) { m_materialAssetState.dirty = true; diff --git a/editor/src/panels/InspectorPanel.h b/editor/src/panels/InspectorPanel.h index 85822d3b..a5b3ddae 100644 --- a/editor/src/panels/InspectorPanel.h +++ b/editor/src/panels/InspectorPanel.h @@ -43,15 +43,31 @@ public: std::array value{}; }; + struct MaterialKeywordState { + std::string value; + }; + + struct MaterialPropertyState { + std::string name; + ::XCEngine::Resources::MaterialPropertyType type = + ::XCEngine::Resources::MaterialPropertyType::Float; + std::array floatValue{}; + std::array<::XCEngine::Core::int32, 4> intValue{}; + bool boolValue = false; + std::string texturePath; + }; + struct MaterialAssetState { std::string assetPath; std::string assetFullPath; std::string assetName; std::array shaderPath{}; - std::array shaderPass{}; int renderQueue = static_cast(::XCEngine::Resources::MaterialRenderQueue::Geometry); ::XCEngine::Resources::MaterialRenderState renderState{}; + bool hasRenderStateOverride = false; std::vector tags; + std::vector keywords; + std::vector properties; std::string errorMessage; bool dirty = false; bool loaded = false; @@ -61,10 +77,12 @@ public: assetFullPath.clear(); assetName.clear(); shaderPath.fill('\0'); - shaderPass.fill('\0'); renderQueue = static_cast(::XCEngine::Resources::MaterialRenderQueue::Geometry); renderState = ::XCEngine::Resources::MaterialRenderState(); + hasRenderStateOverride = false; tags.clear(); + keywords.clear(); + properties.clear(); errorMessage.clear(); dirty = false; loaded = false;