Refactor material inspector state IO
This commit is contained in:
@@ -24,6 +24,7 @@ set(EDITOR_TEST_SOURCES
|
||||
test_scene_viewport_overlay_renderer.cpp
|
||||
test_scene_viewport_overlay_providers.cpp
|
||||
test_script_component_editor_utils.cpp
|
||||
test_viewport_panel_content.cpp
|
||||
test_viewport_host_surface_utils.cpp
|
||||
test_viewport_object_id_picker.cpp
|
||||
test_viewport_render_targets.cpp
|
||||
@@ -36,6 +37,7 @@ set(EDITOR_TEST_SOURCES
|
||||
test_editor_console_sink.cpp
|
||||
test_editor_script_assembly_builder.cpp
|
||||
test_editor_script_assembly_builder_utils.cpp
|
||||
test_material_inspector_material_state_io.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp
|
||||
@@ -46,6 +48,8 @@ set(EDITOR_TEST_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/editor/src/Core/PlaySessionController.cpp
|
||||
${CMAKE_SOURCE_DIR}/editor/src/Managers/SceneManager.cpp
|
||||
${CMAKE_SOURCE_DIR}/editor/src/Managers/ProjectManager.cpp
|
||||
${CMAKE_SOURCE_DIR}/editor/src/panels/MaterialInspectorMaterialStateIO.cpp
|
||||
${CMAKE_SOURCE_DIR}/editor/src/UI/BuiltInIcons.cpp
|
||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportPicker.cpp
|
||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportMoveGizmo.cpp
|
||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportRotateGizmo.cpp
|
||||
|
||||
308
tests/editor/test_material_inspector_material_state_io.cpp
Normal file
308
tests/editor/test_material_inspector_material_state_io.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "panels/MaterialInspectorMaterialState.h"
|
||||
#include "panels/MaterialInspectorMaterialStateIO.h"
|
||||
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Editor::ApplyMaterialAuthoringPresenceToState;
|
||||
using XCEngine::Editor::BuildMaterialAssetFileText;
|
||||
using XCEngine::Editor::MaterialAssetState;
|
||||
using XCEngine::Editor::MaterialKeywordState;
|
||||
using XCEngine::Editor::MaterialPropertyState;
|
||||
using XCEngine::Editor::MaterialTagEditRow;
|
||||
using XCEngine::Editor::SyncMaterialAssetStateWithShader;
|
||||
using XCEngine::Resources::IResource;
|
||||
using XCEngine::Resources::MaterialBlendFactor;
|
||||
using XCEngine::Resources::MaterialBlendOp;
|
||||
using XCEngine::Resources::MaterialComparisonFunc;
|
||||
using XCEngine::Resources::MaterialCullMode;
|
||||
using XCEngine::Resources::MaterialPropertyType;
|
||||
using XCEngine::Resources::MaterialRenderQueue;
|
||||
using XCEngine::Resources::ResourceGUID;
|
||||
using XCEngine::Resources::ResourceHandle;
|
||||
using XCEngine::Resources::Shader;
|
||||
using XCEngine::Resources::ShaderKeywordDeclaration;
|
||||
using XCEngine::Resources::ShaderKeywordDeclarationType;
|
||||
using XCEngine::Resources::ShaderPass;
|
||||
using XCEngine::Resources::ShaderPropertyDesc;
|
||||
using XCEngine::Resources::ShaderPropertyType;
|
||||
|
||||
template <size_t N>
|
||||
void CopyText(const std::string& value, std::array<char, N>& buffer) {
|
||||
buffer.fill('\0');
|
||||
const size_t copyLength = (std::min)(value.size(), N - 1);
|
||||
if (copyLength > 0) {
|
||||
std::memcpy(buffer.data(), value.data(), copyLength);
|
||||
}
|
||||
buffer[copyLength] = '\0';
|
||||
}
|
||||
|
||||
MaterialPropertyState* FindProperty(MaterialAssetState& state, const char* name) {
|
||||
for (MaterialPropertyState& property : state.properties) {
|
||||
if (property.name == name) {
|
||||
return &property;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Shader> CreateSchemaShader(
|
||||
const char* path,
|
||||
std::initializer_list<const char*> declaredKeywords = {}) {
|
||||
auto shader = std::make_unique<Shader>();
|
||||
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "InspectorTestShader";
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
shader->Initialize(params);
|
||||
|
||||
ShaderPropertyDesc baseColor = {};
|
||||
baseColor.name = "_BaseColor";
|
||||
baseColor.displayName = "Base Color";
|
||||
baseColor.type = ShaderPropertyType::Color;
|
||||
baseColor.defaultValue = "(1,1,1,1)";
|
||||
shader->AddProperty(baseColor);
|
||||
|
||||
ShaderPropertyDesc metallic = {};
|
||||
metallic.name = "_Metallic";
|
||||
metallic.displayName = "Metallic";
|
||||
metallic.type = ShaderPropertyType::Float;
|
||||
metallic.defaultValue = "0.7";
|
||||
shader->AddProperty(metallic);
|
||||
|
||||
ShaderPropertyDesc mode = {};
|
||||
mode.name = "_Mode";
|
||||
mode.displayName = "Mode";
|
||||
mode.type = ShaderPropertyType::Int;
|
||||
mode.defaultValue = "2";
|
||||
shader->AddProperty(mode);
|
||||
|
||||
ShaderPropertyDesc mainTex = {};
|
||||
mainTex.name = "_MainTex";
|
||||
mainTex.displayName = "Main Tex";
|
||||
mainTex.type = ShaderPropertyType::Texture2D;
|
||||
mainTex.defaultValue = "white";
|
||||
shader->AddProperty(mainTex);
|
||||
|
||||
ShaderPass pass = {};
|
||||
pass.name = "ForwardLit";
|
||||
shader->AddPass(pass);
|
||||
|
||||
if (declaredKeywords.size() > 0) {
|
||||
ShaderKeywordDeclaration declaration = {};
|
||||
declaration.type = ShaderKeywordDeclarationType::ShaderFeatureLocal;
|
||||
declaration.options.PushBack("_");
|
||||
for (const char* keyword : declaredKeywords) {
|
||||
declaration.options.PushBack(keyword);
|
||||
}
|
||||
shader->AddPassKeywordDeclaration("ForwardLit", declaration);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
TEST(MaterialInspectorMaterialStateIOTest, ApplyMaterialAuthoringPresenceMarksOnlySourceAuthoredEntries) {
|
||||
MaterialAssetState state;
|
||||
state.keywords = {
|
||||
MaterialKeywordState{ "XC_ALPHA_TEST", false },
|
||||
MaterialKeywordState{ "XC_UNUSED", false }
|
||||
};
|
||||
|
||||
MaterialPropertyState metallic = {};
|
||||
metallic.name = "_Metallic";
|
||||
metallic.type = MaterialPropertyType::Float;
|
||||
state.properties.push_back(metallic);
|
||||
|
||||
MaterialPropertyState baseColor = {};
|
||||
baseColor.name = "_BaseColor";
|
||||
baseColor.type = MaterialPropertyType::Float4;
|
||||
state.properties.push_back(baseColor);
|
||||
|
||||
MaterialPropertyState mainTex = {};
|
||||
mainTex.name = "_MainTex";
|
||||
mainTex.type = MaterialPropertyType::Texture;
|
||||
state.properties.push_back(mainTex);
|
||||
|
||||
ApplyMaterialAuthoringPresenceToState(
|
||||
"{\n"
|
||||
" \"keywords\": [\"XC_ALPHA_TEST\"],\n"
|
||||
" \"properties\": { \"_Metallic\": 0.35 },\n"
|
||||
" \"textures\": { \"_MainTex\": \"Assets/Textures/albedo.png\" }\n"
|
||||
"}\n",
|
||||
state);
|
||||
|
||||
EXPECT_FALSE(state.hasRenderStateOverride);
|
||||
ASSERT_EQ(state.keywords.size(), 2u);
|
||||
EXPECT_TRUE(state.keywords[0].serialized);
|
||||
EXPECT_FALSE(state.keywords[1].serialized);
|
||||
|
||||
ASSERT_NE(FindProperty(state, "_Metallic"), nullptr);
|
||||
ASSERT_NE(FindProperty(state, "_BaseColor"), nullptr);
|
||||
ASSERT_NE(FindProperty(state, "_MainTex"), nullptr);
|
||||
EXPECT_TRUE(FindProperty(state, "_Metallic")->serialized);
|
||||
EXPECT_FALSE(FindProperty(state, "_BaseColor")->serialized);
|
||||
EXPECT_TRUE(FindProperty(state, "_MainTex")->serialized);
|
||||
}
|
||||
|
||||
TEST(MaterialInspectorMaterialStateIOTest, SyncMaterialAssetStateWithShaderPreservesCompatibleOverridesAndDropsStaleData) {
|
||||
auto shader = CreateSchemaShader(
|
||||
"memory://material_inspector_sync_shader",
|
||||
{ "XC_ALPHA_TEST" });
|
||||
ResourceHandle<Shader> shaderHandle(shader.get());
|
||||
|
||||
MaterialAssetState state;
|
||||
|
||||
MaterialPropertyState legacy = {};
|
||||
legacy.name = "_Legacy";
|
||||
legacy.type = MaterialPropertyType::Float;
|
||||
legacy.floatValue[0] = 9.0f;
|
||||
legacy.serialized = true;
|
||||
state.properties.push_back(legacy);
|
||||
|
||||
MaterialPropertyState metallic = {};
|
||||
metallic.name = "_Metallic";
|
||||
metallic.type = MaterialPropertyType::Float;
|
||||
metallic.floatValue[0] = 0.33f;
|
||||
metallic.serialized = true;
|
||||
state.properties.push_back(metallic);
|
||||
|
||||
MaterialPropertyState mainTex = {};
|
||||
mainTex.name = "_MainTex";
|
||||
mainTex.type = MaterialPropertyType::Texture;
|
||||
mainTex.texturePath = "Assets/Textures/albedo.png";
|
||||
mainTex.serialized = true;
|
||||
state.properties.push_back(mainTex);
|
||||
|
||||
state.keywords.push_back(MaterialKeywordState{ "XC_ALPHA_TEST", true });
|
||||
state.keywords.push_back(MaterialKeywordState{ "XC_OLD", true });
|
||||
|
||||
SyncMaterialAssetStateWithShader(shaderHandle, state);
|
||||
|
||||
std::vector<std::string> propertyNames;
|
||||
propertyNames.reserve(state.properties.size());
|
||||
for (const MaterialPropertyState& property : state.properties) {
|
||||
propertyNames.push_back(property.name);
|
||||
}
|
||||
|
||||
const std::vector<std::string> expectedNames = {
|
||||
"_BaseColor",
|
||||
"_Metallic",
|
||||
"_Mode",
|
||||
"_MainTex"
|
||||
};
|
||||
EXPECT_EQ(propertyNames, expectedNames);
|
||||
|
||||
ASSERT_NE(FindProperty(state, "_Metallic"), nullptr);
|
||||
ASSERT_NE(FindProperty(state, "_MainTex"), nullptr);
|
||||
EXPECT_FLOAT_EQ(FindProperty(state, "_Metallic")->floatValue[0], 0.33f);
|
||||
EXPECT_TRUE(FindProperty(state, "_Metallic")->serialized);
|
||||
EXPECT_EQ(FindProperty(state, "_MainTex")->texturePath, "Assets/Textures/albedo.png");
|
||||
EXPECT_TRUE(FindProperty(state, "_MainTex")->serialized);
|
||||
EXPECT_EQ(FindProperty(state, "_Mode")->intValue[0], 2);
|
||||
EXPECT_EQ(FindProperty(state, "_Legacy"), nullptr);
|
||||
|
||||
ASSERT_EQ(state.keywords.size(), 1u);
|
||||
EXPECT_EQ(state.keywords[0].value, "XC_ALPHA_TEST");
|
||||
EXPECT_TRUE(state.keywords[0].serialized);
|
||||
}
|
||||
|
||||
TEST(MaterialInspectorMaterialStateIOTest, BuildMaterialAssetFileTextOmitsNonSerializedDefaultsAndDisabledRenderState) {
|
||||
MaterialAssetState state;
|
||||
CopyText("Assets/Shaders/test.shader", state.shaderPath);
|
||||
state.renderQueue = static_cast<int>(MaterialRenderQueue::Geometry);
|
||||
|
||||
MaterialTagEditRow tag = {};
|
||||
CopyText("RenderType", tag.name);
|
||||
CopyText("Opaque", tag.value);
|
||||
state.tags.push_back(tag);
|
||||
|
||||
state.keywords.push_back(MaterialKeywordState{ "XC_ALPHA_TEST", true });
|
||||
state.keywords.push_back(MaterialKeywordState{ "XC_UNUSED", false });
|
||||
|
||||
MaterialPropertyState metallic = {};
|
||||
metallic.name = "_Metallic";
|
||||
metallic.type = MaterialPropertyType::Float;
|
||||
metallic.floatValue[0] = 0.25f;
|
||||
metallic.serialized = true;
|
||||
state.properties.push_back(metallic);
|
||||
|
||||
MaterialPropertyState baseColor = {};
|
||||
baseColor.name = "_BaseColor";
|
||||
baseColor.type = MaterialPropertyType::Float4;
|
||||
baseColor.floatValue = { 1.0f, 0.2f, 0.1f, 1.0f };
|
||||
baseColor.serialized = false;
|
||||
state.properties.push_back(baseColor);
|
||||
|
||||
MaterialPropertyState mainTex = {};
|
||||
mainTex.name = "_MainTex";
|
||||
mainTex.type = MaterialPropertyType::Texture;
|
||||
mainTex.texturePath = "Assets/Textures/albedo.png";
|
||||
mainTex.serialized = false;
|
||||
state.properties.push_back(mainTex);
|
||||
|
||||
const std::string text = BuildMaterialAssetFileText(state);
|
||||
|
||||
EXPECT_NE(text.find("\"shader\": \"Assets/Shaders/test.shader\""), std::string::npos);
|
||||
EXPECT_NE(text.find("\"renderQueue\": \"geometry\""), std::string::npos);
|
||||
EXPECT_NE(text.find("\"RenderType\": \"Opaque\""), std::string::npos);
|
||||
EXPECT_NE(text.find("\"keywords\": [\"XC_ALPHA_TEST\"]"), std::string::npos);
|
||||
EXPECT_NE(text.find("\"_Metallic\": 0.25"), std::string::npos);
|
||||
EXPECT_EQ(text.find("\"_BaseColor\""), std::string::npos);
|
||||
EXPECT_EQ(text.find("\"textures\""), std::string::npos);
|
||||
EXPECT_EQ(text.find("\"renderState\""), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(MaterialInspectorMaterialStateIOTest, BuildMaterialAssetFileTextWritesSerializedTextureAndRenderStateOverrides) {
|
||||
MaterialAssetState state;
|
||||
CopyText("Assets/Shaders/test.shader", state.shaderPath);
|
||||
state.renderQueue = 2051;
|
||||
state.hasRenderStateOverride = true;
|
||||
state.renderState.cullMode = MaterialCullMode::Back;
|
||||
state.renderState.depthTestEnable = true;
|
||||
state.renderState.depthWriteEnable = false;
|
||||
state.renderState.depthFunc = MaterialComparisonFunc::LessEqual;
|
||||
state.renderState.blendEnable = true;
|
||||
state.renderState.srcBlend = MaterialBlendFactor::SrcAlpha;
|
||||
state.renderState.dstBlend = MaterialBlendFactor::InvSrcAlpha;
|
||||
state.renderState.srcBlendAlpha = MaterialBlendFactor::One;
|
||||
state.renderState.dstBlendAlpha = MaterialBlendFactor::InvSrcAlpha;
|
||||
state.renderState.blendOp = MaterialBlendOp::Add;
|
||||
state.renderState.blendOpAlpha = MaterialBlendOp::Add;
|
||||
state.renderState.colorWriteMask = 7;
|
||||
|
||||
state.keywords.push_back(MaterialKeywordState{ "XC_ALPHA_TEST", true });
|
||||
|
||||
MaterialPropertyState mainTex = {};
|
||||
mainTex.name = "_MainTex";
|
||||
mainTex.type = MaterialPropertyType::Texture;
|
||||
mainTex.texturePath = "Assets/Textures/albedo.png";
|
||||
mainTex.serialized = true;
|
||||
state.properties.push_back(mainTex);
|
||||
|
||||
const std::string text = BuildMaterialAssetFileText(state);
|
||||
|
||||
EXPECT_NE(text.find("\"renderQueue\": 2051"), std::string::npos);
|
||||
EXPECT_NE(text.find("\"keywords\": [\"XC_ALPHA_TEST\"]"), std::string::npos);
|
||||
EXPECT_NE(text.find("\"textures\": {"), std::string::npos);
|
||||
EXPECT_NE(text.find("\"_MainTex\": \"Assets/Textures/albedo.png\""), std::string::npos);
|
||||
EXPECT_NE(text.find("\"renderState\": {"), std::string::npos);
|
||||
EXPECT_NE(text.find("\"cull\": \"back\""), std::string::npos);
|
||||
EXPECT_NE(text.find("\"blendEnable\": true"), std::string::npos);
|
||||
EXPECT_NE(text.find("\"srcBlend\": \"src_alpha\""), std::string::npos);
|
||||
EXPECT_NE(text.find("\"colorWriteMask\": 7"), std::string::npos);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
103
tests/editor/test_viewport_panel_content.cpp
Normal file
103
tests/editor/test_viewport_panel_content.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "panels/ViewportPanelContent.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace {
|
||||
|
||||
class ImGuiContextScope {
|
||||
public:
|
||||
ImGuiContextScope() {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
}
|
||||
|
||||
~ImGuiContextScope() {
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
};
|
||||
|
||||
void BeginTestFrame() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DisplaySize = ImVec2(640.0f, 480.0f);
|
||||
io.DeltaTime = 1.0f / 60.0f;
|
||||
unsigned char* fontPixels = nullptr;
|
||||
int fontWidth = 0;
|
||||
int fontHeight = 0;
|
||||
io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontWidth, &fontHeight);
|
||||
io.Fonts->SetTexID(static_cast<ImTextureID>(1));
|
||||
ImGui::GetStyle().WindowPadding = ImVec2(0.0f, 0.0f);
|
||||
|
||||
ImGui::NewFrame();
|
||||
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
|
||||
ImGui::SetNextWindowSize(ImVec2(320.0f, 240.0f));
|
||||
ASSERT_TRUE(ImGui::Begin(
|
||||
"ViewportPanelContentTestWindow",
|
||||
nullptr,
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoSavedSettings));
|
||||
}
|
||||
|
||||
void EndTestFrame() {
|
||||
ImGui::End();
|
||||
ImGui::EndFrame();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ViewportPanelContentTest, AllowOverlapDefaultItemClickMissesMiddleClickOnFirstFrame) {
|
||||
ImGuiContextScope contextScope;
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
BeginTestFrame();
|
||||
EndTestFrame();
|
||||
|
||||
io.MousePos = ImVec2(32.0f, 32.0f);
|
||||
io.MouseDown[ImGuiMouseButton_Middle] = true;
|
||||
BeginTestFrame();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(16.0f, 16.0f));
|
||||
|
||||
ImGui::SetNextItemAllowOverlap();
|
||||
ImGui::InvisibleButton(
|
||||
"##ViewportOverlapProbe",
|
||||
ImVec2(120.0f, 90.0f),
|
||||
ImGuiButtonFlags_MouseButtonMiddle);
|
||||
|
||||
EXPECT_FALSE(ImGui::IsItemHovered());
|
||||
EXPECT_FALSE(ImGui::IsItemClicked(ImGuiMouseButton_Middle));
|
||||
EXPECT_TRUE(ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByItem));
|
||||
|
||||
EndTestFrame();
|
||||
}
|
||||
|
||||
TEST(ViewportPanelContentTest, InteractionSurfaceCapturesMiddleClickOnFirstHoveredFrame) {
|
||||
ImGuiContextScope contextScope;
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
BeginTestFrame();
|
||||
EndTestFrame();
|
||||
|
||||
io.MousePos = ImVec2(32.0f, 32.0f);
|
||||
io.MouseDown[ImGuiMouseButton_Middle] = true;
|
||||
BeginTestFrame();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(16.0f, 16.0f));
|
||||
|
||||
XCEngine::Editor::ViewportPanelContentResult result = {};
|
||||
XCEngine::Editor::RenderViewportInteractionSurface(
|
||||
result,
|
||||
XCEngine::Editor::EditorViewportKind::Scene,
|
||||
ImVec2(120.0f, 90.0f));
|
||||
|
||||
EXPECT_TRUE(result.hovered);
|
||||
EXPECT_TRUE(result.clickedMiddle);
|
||||
EXPECT_FALSE(result.clickedLeft);
|
||||
EXPECT_FALSE(result.clickedRight);
|
||||
|
||||
EndTestFrame();
|
||||
}
|
||||
Reference in New Issue
Block a user