editor: preserve material inspector authored state
This commit is contained in:
@@ -385,3 +385,58 @@ Material 面板不应再次演化成随意堆字段的临时入口。所有 rend
|
|||||||
1. 扩展 `MaterialAssetState`,让其正式承载属性、纹理槽、关键词与 render state override。
|
1. 扩展 `MaterialAssetState`,让其正式承载属性、纹理槽、关键词与 render state override。
|
||||||
2. 重构 `Populate / Apply / Save / Reload` 这四条关键链路。
|
2. 重构 `Populate / Apply / Save / Reload` 这四条关键链路。
|
||||||
3. 彻底消除当前“保存一次就丢属性”的结构性问题。
|
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 的性质是“先把材质状态模型与保存链路做对”,而不是“材质面板功能已经完整”。
|
||||||
|
|||||||
@@ -21,11 +21,13 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
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<double>(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<InspectorPanel::MaterialPropertyState> 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<std::string, size_t> 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<InspectorPanel::MaterialPropertyState> 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(
|
void ApplyResolvedMaterialStateToResource(
|
||||||
const InspectorPanel::MaterialAssetState& state,
|
const InspectorPanel::MaterialAssetState& state,
|
||||||
const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shader,
|
const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shader,
|
||||||
::XCEngine::Resources::Material& material) {
|
::XCEngine::Resources::Material& material) {
|
||||||
material.SetShader(shader);
|
material.SetShader(shader);
|
||||||
material.SetShaderPass(TrimCopy(BufferToString(state.shaderPass)).c_str());
|
|
||||||
material.SetRenderQueue(state.renderQueue);
|
material.SetRenderQueue(state.renderQueue);
|
||||||
material.SetRenderState(state.renderState);
|
material.SetRenderState(state.renderState);
|
||||||
|
material.SetRenderStateOverrideEnabled(state.hasRenderStateOverride);
|
||||||
material.ClearTags();
|
material.ClearTags();
|
||||||
for (const InspectorPanel::MaterialTagEditRow& row : state.tags) {
|
for (const InspectorPanel::MaterialTagEditRow& row : state.tags) {
|
||||||
const std::string tagName = TrimCopy(BufferToString(row.name));
|
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());
|
material.SetTag(tagName.c_str(), TrimCopy(BufferToString(row.value)).c_str());
|
||||||
}
|
}
|
||||||
|
ApplyMaterialKeywordsToResource(state, material);
|
||||||
|
ApplyMaterialPropertiesToResource(state, material);
|
||||||
material.RecalculateMemorySize();
|
material.RecalculateMemorySize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,11 +757,6 @@ std::string BuildMaterialAssetFileText(const InspectorPanel::MaterialAssetState&
|
|||||||
rootEntries.push_back(" \"shader\": \"" + EscapeJsonString(shaderPath) + "\"");
|
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);
|
const int renderQueuePreset = ResolveRenderQueuePresetIndex(state.renderQueue);
|
||||||
if (renderQueuePreset != kCustomRenderQueuePresetIndex) {
|
if (renderQueuePreset != kCustomRenderQueuePresetIndex) {
|
||||||
rootEntries.push_back(
|
rootEntries.push_back(
|
||||||
@@ -537,39 +788,151 @@ std::string BuildMaterialAssetFileText(const InspectorPanel::MaterialAssetState&
|
|||||||
rootEntries.push_back(tagsObject);
|
rootEntries.push_back(tagsObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ::XCEngine::Resources::MaterialRenderState& renderState = state.renderState;
|
if (!state.keywords.empty()) {
|
||||||
std::vector<std::string> renderStateEntries;
|
std::string keywordsArray = " \"keywords\": [";
|
||||||
renderStateEntries.push_back(
|
bool firstKeyword = true;
|
||||||
" \"cull\": \"" + std::string(SerializeCullMode(renderState.cullMode)) + "\"");
|
for (const InspectorPanel::MaterialKeywordState& keyword : state.keywords) {
|
||||||
renderStateEntries.push_back(std::string(" \"depthTest\": ") + (renderState.depthTestEnable ? "true" : "false"));
|
const std::string keywordValue = TrimCopy(keyword.value);
|
||||||
renderStateEntries.push_back(std::string(" \"depthWrite\": ") + (renderState.depthWriteEnable ? "true" : "false"));
|
if (keywordValue.empty()) {
|
||||||
renderStateEntries.push_back(
|
continue;
|
||||||
" \"depthFunc\": \"" + std::string(SerializeComparisonFunc(renderState.depthFunc)) + "\"");
|
}
|
||||||
renderStateEntries.push_back(std::string(" \"blendEnable\": ") + (renderState.blendEnable ? "true" : "false"));
|
if (!firstKeyword) {
|
||||||
renderStateEntries.push_back(
|
keywordsArray += ", ";
|
||||||
" \"srcBlend\": \"" + std::string(SerializeBlendFactor(renderState.srcBlend)) + "\"");
|
}
|
||||||
renderStateEntries.push_back(
|
keywordsArray += "\"" + EscapeJsonString(keywordValue) + "\"";
|
||||||
" \"dstBlend\": \"" + std::string(SerializeBlendFactor(renderState.dstBlend)) + "\"");
|
firstKeyword = false;
|
||||||
renderStateEntries.push_back(
|
}
|
||||||
" \"srcBlendAlpha\": \"" + std::string(SerializeBlendFactor(renderState.srcBlendAlpha)) + "\"");
|
keywordsArray += "]";
|
||||||
renderStateEntries.push_back(
|
if (!firstKeyword) {
|
||||||
" \"dstBlendAlpha\": \"" + std::string(SerializeBlendFactor(renderState.dstBlendAlpha)) + "\"");
|
rootEntries.push_back(keywordsArray);
|
||||||
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 += ",";
|
|
||||||
}
|
}
|
||||||
renderStateObject += "\n";
|
|
||||||
}
|
}
|
||||||
renderStateObject += " }";
|
|
||||||
rootEntries.push_back(renderStateObject);
|
std::vector<std::string> propertyEntries;
|
||||||
|
std::vector<std::string> 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<std::string> 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<std::string> 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";
|
std::string json = "{\n";
|
||||||
for (size_t entryIndex = 0; entryIndex < rootEntries.size(); ++entryIndex) {
|
for (size_t entryIndex = 0; entryIndex < rootEntries.size(); ++entryIndex) {
|
||||||
@@ -649,15 +1012,16 @@ void InspectorPanel::InvalidateMaterialAssetAsyncState() {
|
|||||||
|
|
||||||
void InspectorPanel::PopulateMaterialAssetStateFromResource(::XCEngine::Resources::Material& material) {
|
void InspectorPanel::PopulateMaterialAssetStateFromResource(::XCEngine::Resources::Material& material) {
|
||||||
m_materialAssetState.shaderPath.fill('\0');
|
m_materialAssetState.shaderPath.fill('\0');
|
||||||
m_materialAssetState.shaderPass.fill('\0');
|
|
||||||
m_materialAssetState.renderQueue = material.GetRenderQueue();
|
m_materialAssetState.renderQueue = material.GetRenderQueue();
|
||||||
m_materialAssetState.renderState = material.GetRenderState();
|
m_materialAssetState.renderState = material.GetRenderState();
|
||||||
|
m_materialAssetState.hasRenderStateOverride = material.HasRenderStateOverride();
|
||||||
m_materialAssetState.tags.clear();
|
m_materialAssetState.tags.clear();
|
||||||
|
m_materialAssetState.keywords.clear();
|
||||||
|
m_materialAssetState.properties.clear();
|
||||||
|
|
||||||
if (::XCEngine::Resources::Shader* shader = material.GetShader()) {
|
if (::XCEngine::Resources::Shader* shader = material.GetShader()) {
|
||||||
CopyToCharBuffer(std::string(shader->GetPath().CStr()), m_materialAssetState.shaderPath);
|
CopyToCharBuffer(std::string(shader->GetPath().CStr()), m_materialAssetState.shaderPath);
|
||||||
}
|
}
|
||||||
CopyToCharBuffer(std::string(material.GetShaderPass().CStr()), m_materialAssetState.shaderPass);
|
|
||||||
|
|
||||||
m_materialAssetState.tags.reserve(material.GetTagCount());
|
m_materialAssetState.tags.reserve(material.GetTagCount());
|
||||||
for (Core::uint32 tagIndex = 0; tagIndex < material.GetTagCount(); ++tagIndex) {
|
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.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.loaded = true;
|
||||||
m_materialAssetState.dirty = false;
|
m_materialAssetState.dirty = false;
|
||||||
}
|
}
|
||||||
@@ -1013,7 +1386,7 @@ void InspectorPanel::RenderMaterialAsset() {
|
|||||||
"Shader",
|
"Shader",
|
||||||
BufferToString(m_materialAssetState.shaderPath),
|
BufferToString(m_materialAssetState.shaderPath),
|
||||||
"Select Shader Asset",
|
"Select Shader Asset",
|
||||||
{ ".shader", ".hlsl", ".glsl", ".vert", ".frag", ".comp" });
|
{ ".shader", ".xcshader" });
|
||||||
if (shaderInteraction.clearRequested) {
|
if (shaderInteraction.clearRequested) {
|
||||||
CopyToCharBuffer(std::string(), m_materialAssetState.shaderPath);
|
CopyToCharBuffer(std::string(), m_materialAssetState.shaderPath);
|
||||||
changed = true;
|
changed = true;
|
||||||
@@ -1023,12 +1396,6 @@ void InspectorPanel::RenderMaterialAsset() {
|
|||||||
changed = true;
|
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);
|
int renderQueuePreset = ResolveRenderQueuePresetIndex(m_materialAssetState.renderQueue);
|
||||||
const int newRenderQueuePreset = UI::DrawPropertyCombo(
|
const int newRenderQueuePreset = UI::DrawPropertyCombo(
|
||||||
"Render Queue",
|
"Render Queue",
|
||||||
@@ -1061,6 +1428,9 @@ void InspectorPanel::RenderMaterialAsset() {
|
|||||||
ImGui::Indent(renderStateSection.contentIndent);
|
ImGui::Indent(renderStateSection.contentIndent);
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
changed = UI::DrawPropertyBool("Override", m_materialAssetState.hasRenderStateOverride) || changed;
|
||||||
|
|
||||||
|
ImGui::BeginDisabled(!m_materialAssetState.hasRenderStateOverride);
|
||||||
int cullModeIndex = ResolveCullModeIndex(m_materialAssetState.renderState.cullMode);
|
int cullModeIndex = ResolveCullModeIndex(m_materialAssetState.renderState.cullMode);
|
||||||
const int newCullModeIndex = UI::DrawPropertyCombo(
|
const int newCullModeIndex = UI::DrawPropertyCombo(
|
||||||
"Cull",
|
"Cull",
|
||||||
@@ -1160,6 +1530,7 @@ void InspectorPanel::RenderMaterialAsset() {
|
|||||||
static_cast<Core::uint8>(std::clamp(colorWriteMask, 0, 15));
|
static_cast<Core::uint8>(std::clamp(colorWriteMask, 0, 15));
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
m_materialAssetState.dirty = true;
|
m_materialAssetState.dirty = true;
|
||||||
|
|||||||
@@ -43,15 +43,31 @@ public:
|
|||||||
std::array<char, 128> value{};
|
std::array<char, 128> value{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MaterialKeywordState {
|
||||||
|
std::string value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MaterialPropertyState {
|
||||||
|
std::string name;
|
||||||
|
::XCEngine::Resources::MaterialPropertyType type =
|
||||||
|
::XCEngine::Resources::MaterialPropertyType::Float;
|
||||||
|
std::array<float, 4> floatValue{};
|
||||||
|
std::array<::XCEngine::Core::int32, 4> intValue{};
|
||||||
|
bool boolValue = false;
|
||||||
|
std::string texturePath;
|
||||||
|
};
|
||||||
|
|
||||||
struct MaterialAssetState {
|
struct MaterialAssetState {
|
||||||
std::string assetPath;
|
std::string assetPath;
|
||||||
std::string assetFullPath;
|
std::string assetFullPath;
|
||||||
std::string assetName;
|
std::string assetName;
|
||||||
std::array<char, 260> shaderPath{};
|
std::array<char, 260> shaderPath{};
|
||||||
std::array<char, 128> shaderPass{};
|
|
||||||
int renderQueue = static_cast<int>(::XCEngine::Resources::MaterialRenderQueue::Geometry);
|
int renderQueue = static_cast<int>(::XCEngine::Resources::MaterialRenderQueue::Geometry);
|
||||||
::XCEngine::Resources::MaterialRenderState renderState{};
|
::XCEngine::Resources::MaterialRenderState renderState{};
|
||||||
|
bool hasRenderStateOverride = false;
|
||||||
std::vector<MaterialTagEditRow> tags;
|
std::vector<MaterialTagEditRow> tags;
|
||||||
|
std::vector<MaterialKeywordState> keywords;
|
||||||
|
std::vector<MaterialPropertyState> properties;
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
bool loaded = false;
|
bool loaded = false;
|
||||||
@@ -61,10 +77,12 @@ public:
|
|||||||
assetFullPath.clear();
|
assetFullPath.clear();
|
||||||
assetName.clear();
|
assetName.clear();
|
||||||
shaderPath.fill('\0');
|
shaderPath.fill('\0');
|
||||||
shaderPass.fill('\0');
|
|
||||||
renderQueue = static_cast<int>(::XCEngine::Resources::MaterialRenderQueue::Geometry);
|
renderQueue = static_cast<int>(::XCEngine::Resources::MaterialRenderQueue::Geometry);
|
||||||
renderState = ::XCEngine::Resources::MaterialRenderState();
|
renderState = ::XCEngine::Resources::MaterialRenderState();
|
||||||
|
hasRenderStateOverride = false;
|
||||||
tags.clear();
|
tags.clear();
|
||||||
|
keywords.clear();
|
||||||
|
properties.clear();
|
||||||
errorMessage.clear();
|
errorMessage.clear();
|
||||||
dirty = false;
|
dirty = false;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user