Complete material inspector keyword and reset flow
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -22,6 +22,7 @@ using XCEngine::Editor::MaterialAssetState;
|
||||
using XCEngine::Editor::MaterialKeywordState;
|
||||
using XCEngine::Editor::MaterialPropertyState;
|
||||
using XCEngine::Editor::MaterialTagEditRow;
|
||||
using XCEngine::Editor::ResetMaterialPropertyStateToShaderDefault;
|
||||
using XCEngine::Editor::SyncMaterialAssetStateWithShader;
|
||||
using XCEngine::Resources::IResource;
|
||||
using XCEngine::Resources::MaterialBlendFactor;
|
||||
@@ -219,6 +220,41 @@ TEST(MaterialInspectorMaterialStateIOTest, SyncMaterialAssetStateWithShaderPrese
|
||||
EXPECT_TRUE(state.keywords[0].serialized);
|
||||
}
|
||||
|
||||
TEST(MaterialInspectorMaterialStateIOTest, SyncMaterialAssetStateWithShaderAddsDeclaredKeywordsAsDisabledEntries) {
|
||||
auto shader = CreateSchemaShader(
|
||||
"memory://material_inspector_keywords_shader",
|
||||
{ "XC_ALPHA_TEST", "XC_DETAIL" });
|
||||
ResourceHandle<Shader> shaderHandle(shader.get());
|
||||
|
||||
MaterialAssetState state;
|
||||
state.keywords.push_back(MaterialKeywordState{ "XC_ALPHA_TEST", true });
|
||||
|
||||
SyncMaterialAssetStateWithShader(shaderHandle, state);
|
||||
|
||||
ASSERT_EQ(state.keywords.size(), 2u);
|
||||
EXPECT_EQ(state.keywords[0].value, "XC_ALPHA_TEST");
|
||||
EXPECT_TRUE(state.keywords[0].serialized);
|
||||
EXPECT_EQ(state.keywords[1].value, "XC_DETAIL");
|
||||
EXPECT_FALSE(state.keywords[1].serialized);
|
||||
}
|
||||
|
||||
TEST(MaterialInspectorMaterialStateIOTest, ResetMaterialPropertyStateToShaderDefaultRestoresDefaultAndClearsOverride) {
|
||||
auto shader = CreateSchemaShader("memory://material_inspector_reset_shader");
|
||||
ResourceHandle<Shader> shaderHandle(shader.get());
|
||||
|
||||
MaterialAssetState state;
|
||||
SyncMaterialAssetStateWithShader(shaderHandle, state);
|
||||
|
||||
MaterialPropertyState* metallic = FindProperty(state, "_Metallic");
|
||||
ASSERT_NE(metallic, nullptr);
|
||||
metallic->floatValue[0] = 0.15f;
|
||||
metallic->serialized = true;
|
||||
|
||||
ASSERT_TRUE(ResetMaterialPropertyStateToShaderDefault(shaderHandle, "_Metallic", state));
|
||||
EXPECT_FLOAT_EQ(metallic->floatValue[0], 0.7f);
|
||||
EXPECT_FALSE(metallic->serialized);
|
||||
}
|
||||
|
||||
TEST(MaterialInspectorMaterialStateIOTest, BuildMaterialAssetFileTextOmitsNonSerializedDefaultsAndDisabledRenderState) {
|
||||
MaterialAssetState state;
|
||||
CopyText("Assets/Shaders/test.shader", state.shaderPath);
|
||||
|
||||
Reference in New Issue
Block a user