Complete material inspector keyword and reset flow

This commit is contained in:
2026-04-08 01:01:28 +08:00
parent b5bc9d41cb
commit 077a6b0a51
4 changed files with 213 additions and 49 deletions

View File

@@ -448,7 +448,7 @@ void ApplyMaterialKeywordsToResource(
material.ClearKeywords();
for (const InspectorPanel::MaterialKeywordState& keyword : state.keywords) {
const std::string keywordValue = TrimCopy(keyword.value);
if (!keywordValue.empty()) {
if (!keywordValue.empty() && keyword.serialized) {
material.EnableKeyword(keywordValue.c_str());
}
}
@@ -595,6 +595,7 @@ bool DrawFloat4Property(const char* label, float values[4]) {
bool DrawShaderDrivenMaterialProperty(
const ::XCEngine::Resources::ShaderPropertyDesc& shaderProperty,
const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle,
InspectorPanel::MaterialAssetState& state) {
InspectorPanel::MaterialPropertyState* propertyState = FindMaterialPropertyState(state, shaderProperty.name);
if (propertyState == nullptr) {
@@ -605,62 +606,63 @@ bool DrawShaderDrivenMaterialProperty(
? shaderProperty.name.CStr()
: shaderProperty.displayName.CStr();
bool changed = false;
switch (shaderProperty.type) {
case ::XCEngine::Resources::ShaderPropertyType::Float:
case ::XCEngine::Resources::ShaderPropertyType::Range:
if (!UI::DrawPropertyFloat(label, propertyState->floatValue[0], 0.01f)) {
return false;
if (UI::DrawPropertyFloat(label, propertyState->floatValue[0], 0.01f)) {
propertyState->serialized = true;
changed = true;
}
propertyState->serialized = true;
return true;
break;
case ::XCEngine::Resources::ShaderPropertyType::Int: {
int value = propertyState->intValue[0];
if (!UI::DrawPropertyInt(label, value, 1)) {
return false;
if (UI::DrawPropertyInt(label, value, 1)) {
propertyState->intValue[0] = value;
propertyState->serialized = true;
changed = true;
}
propertyState->intValue[0] = value;
propertyState->serialized = true;
return true;
break;
}
case ::XCEngine::Resources::ShaderPropertyType::Color:
if (!UI::DrawPropertyColor4(label, propertyState->floatValue.data())) {
return false;
if (UI::DrawPropertyColor4(label, propertyState->floatValue.data())) {
propertyState->serialized = true;
changed = true;
}
propertyState->serialized = true;
return true;
break;
case ::XCEngine::Resources::ShaderPropertyType::Vector: {
if (propertyState->type == ::XCEngine::Resources::MaterialPropertyType::Float2) {
::XCEngine::Math::Vector2 value(propertyState->floatValue[0], propertyState->floatValue[1]);
if (!UI::DrawPropertyVec2(label, value)) {
return false;
if (UI::DrawPropertyVec2(label, value)) {
propertyState->floatValue[0] = value.x;
propertyState->floatValue[1] = value.y;
propertyState->serialized = true;
changed = true;
}
propertyState->floatValue[0] = value.x;
propertyState->floatValue[1] = value.y;
propertyState->serialized = true;
return true;
break;
}
if (propertyState->type == ::XCEngine::Resources::MaterialPropertyType::Float3) {
::XCEngine::Math::Vector3 value(
propertyState->floatValue[0],
propertyState->floatValue[1],
propertyState->floatValue[2]);
if (!UI::DrawPropertyVec3(label, value)) {
return false;
if (UI::DrawPropertyVec3(label, value)) {
propertyState->floatValue[0] = value.x;
propertyState->floatValue[1] = value.y;
propertyState->floatValue[2] = value.z;
propertyState->serialized = true;
changed = true;
}
propertyState->floatValue[0] = value.x;
propertyState->floatValue[1] = value.y;
propertyState->floatValue[2] = value.z;
break;
}
if (DrawFloat4Property(label, propertyState->floatValue.data())) {
propertyState->serialized = true;
return true;
changed = true;
}
if (!DrawFloat4Property(label, propertyState->floatValue.data())) {
return false;
}
propertyState->serialized = true;
return true;
break;
}
case ::XCEngine::Resources::ShaderPropertyType::Texture2D:
@@ -672,25 +674,39 @@ bool DrawShaderDrivenMaterialProperty(
"Select Texture Asset",
{ ".png", ".jpg", ".jpeg", ".bmp", ".tga", ".dds", ".hdr", ".ppm" });
if (textureInteraction.clearRequested) {
if (propertyState->texturePath.empty()) {
return false;
if (!propertyState->texturePath.empty()) {
propertyState->texturePath.clear();
propertyState->serialized = false;
changed = true;
}
propertyState->texturePath.clear();
propertyState->serialized = false;
return true;
}
if (!textureInteraction.assignedPath.empty() &&
textureInteraction.assignedPath != propertyState->texturePath) {
} else if (!textureInteraction.assignedPath.empty() &&
textureInteraction.assignedPath != propertyState->texturePath) {
propertyState->texturePath = textureInteraction.assignedPath;
propertyState->serialized = true;
return true;
changed = true;
}
return false;
break;
}
default:
return false;
}
ImGui::PushID(label);
if (propertyState->serialized) {
if (ImGui::SmallButton("Reset to Default")) {
if (ResetMaterialPropertyStateToShaderDefault(shaderHandle, propertyState->name, state)) {
changed = true;
}
}
ImGui::SameLine();
UI::DrawHintText("Material Override");
} else {
UI::DrawHintText("Inherited from Shader");
}
ImGui::PopID();
return changed;
}
std::string BuildMaterialLoadFailureMessage(const ::XCEngine::Resources::LoadResult& result) {
@@ -805,10 +821,16 @@ void InspectorPanel::PopulateMaterialAssetStateFromResource(::XCEngine::Resource
for (Core::uint32 keywordIndex = 0; keywordIndex < material.GetKeywordCount(); ++keywordIndex) {
MaterialKeywordState keywordState;
keywordState.value = std::string(material.GetKeyword(keywordIndex).CStr());
keywordState.serialized = true;
m_materialAssetState.keywords.push_back(std::move(keywordState));
}
m_materialAssetState.properties = CollectMaterialPropertyStates(material);
if (::XCEngine::Resources::Shader* shader = material.GetShader()) {
SyncMaterialAssetStateWithShader(
::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>(shader),
m_materialAssetState);
}
const std::string sourceText = ReadTextFileOrEmpty(m_materialAssetState.assetFullPath);
if (!sourceText.empty()) {
@@ -1234,7 +1256,9 @@ void InspectorPanel::RenderMaterialAsset() {
} else {
bool changed = false;
for (const ::XCEngine::Resources::ShaderPropertyDesc& shaderProperty : materialShaderHandle->GetProperties()) {
changed = DrawShaderDrivenMaterialProperty(shaderProperty, m_materialAssetState) || changed;
changed =
DrawShaderDrivenMaterialProperty(shaderProperty, materialShaderHandle, m_materialAssetState) ||
changed;
if (!shaderProperty.defaultValue.Empty()) {
const std::string defaultText =
std::string("Default: ") + shaderProperty.defaultValue.CStr();
@@ -1251,6 +1275,35 @@ void InspectorPanel::RenderMaterialAsset() {
UI::EndComponentSection(propertiesSection);
}
const UI::ComponentSectionResult keywordsSection = UI::BeginComponentSection(
"MaterialAssetKeywords",
"Keywords");
if (keywordsSection.open) {
ImGui::Indent(keywordsSection.contentIndent);
if (!hasMaterialShader) {
UI::DrawHintText("Select a shader to expose shader keywords.");
} else if (m_materialAssetState.keywords.empty()) {
UI::DrawHintText("Selected shader does not declare editable keywords.");
} else {
bool changed = false;
for (MaterialKeywordState& keyword : m_materialAssetState.keywords) {
bool enabled = keyword.serialized;
if (UI::DrawPropertyBool(keyword.value.c_str(), enabled)) {
keyword.serialized = enabled;
changed = true;
}
}
if (changed) {
m_materialAssetState.dirty = true;
SaveMaterialAsset();
}
}
UI::EndComponentSection(keywordsSection);
}
const UI::ComponentSectionResult renderStateSection = UI::BeginComponentSection(
"MaterialAssetRenderState",
"Render State");

View File

@@ -423,6 +423,52 @@ std::vector<MaterialPropertyState> BuildShaderDefaultPropertyStates(
return CollectMaterialPropertyStates(scratchMaterial);
}
std::vector<MaterialKeywordState> BuildShaderKeywordStates(
const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle,
const std::vector<MaterialKeywordState>& previousKeywords) {
if (!shaderHandle.IsValid() || shaderHandle.Get() == nullptr) {
return {};
}
std::unordered_map<std::string, MaterialKeywordState> previousKeywordStates;
previousKeywordStates.reserve(previousKeywords.size());
for (const MaterialKeywordState& keyword : previousKeywords) {
const std::string keywordValue = TrimCopy(keyword.value);
if (!keywordValue.empty()) {
previousKeywordStates.emplace(keywordValue, keyword);
}
}
std::vector<MaterialKeywordState> keywordStates;
std::unordered_set<std::string> seenKeywords;
for (const ::XCEngine::Resources::ShaderPass& pass : shaderHandle->GetPasses()) {
for (const ::XCEngine::Resources::ShaderKeywordDeclaration& declaration : pass.keywordDeclarations) {
for (const Containers::String& option : declaration.options) {
const Containers::String normalizedKeyword =
::XCEngine::Resources::NormalizeShaderKeywordToken(option);
if (normalizedKeyword.Empty()) {
continue;
}
const std::string keywordValue(normalizedKeyword.CStr());
if (!seenKeywords.insert(keywordValue).second) {
continue;
}
MaterialKeywordState state;
state.value = keywordValue;
const auto previousKeywordIt = previousKeywordStates.find(keywordValue);
if (previousKeywordIt != previousKeywordStates.end()) {
state.serialized = previousKeywordIt->second.serialized;
}
keywordStates.push_back(std::move(state));
}
}
}
return keywordStates;
}
constexpr int kCustomRenderQueuePresetIndex = 5;
int ResolveRenderQueuePresetIndex(int renderQueue) {
@@ -652,16 +698,40 @@ void SyncMaterialAssetStateWithShader(
CopyMaterialPropertyValue(previousPropertyIt->second, property);
}
state.properties = std::move(nextProperties);
state.keywords = BuildShaderKeywordStates(shaderHandle, state.keywords);
}
std::vector<MaterialKeywordState> nextKeywords;
nextKeywords.reserve(state.keywords.size());
for (const MaterialKeywordState& keyword : state.keywords) {
const std::string keywordValue = TrimCopy(keyword.value);
if (!keywordValue.empty() && shaderHandle->DeclaresKeyword(keywordValue.c_str())) {
nextKeywords.push_back(keyword);
bool ResetMaterialPropertyStateToShaderDefault(
const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle,
const std::string& propertyName,
MaterialAssetState& state) {
if (!shaderHandle.IsValid() || shaderHandle.Get() == nullptr || propertyName.empty()) {
return false;
}
MaterialPropertyState* stateProperty = nullptr;
for (MaterialPropertyState& property : state.properties) {
if (property.name == propertyName) {
stateProperty = &property;
break;
}
}
state.keywords = std::move(nextKeywords);
if (stateProperty == nullptr) {
return false;
}
const std::vector<MaterialPropertyState> defaultProperties = BuildShaderDefaultPropertyStates(shaderHandle);
for (const MaterialPropertyState& defaultProperty : defaultProperties) {
if (defaultProperty.name != propertyName || defaultProperty.type != stateProperty->type) {
continue;
}
CopyMaterialPropertyValue(defaultProperty, *stateProperty);
stateProperty->serialized = false;
return true;
}
return false;
}
void ApplyMaterialAuthoringPresenceToState(

View File

@@ -24,6 +24,11 @@ void SyncMaterialAssetStateWithShader(
const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle,
MaterialAssetState& state);
bool ResetMaterialPropertyStateToShaderDefault(
const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shaderHandle,
const std::string& propertyName,
MaterialAssetState& state);
void ApplyMaterialAuthoringPresenceToState(
const std::string& sourceText,
MaterialAssetState& state);