Formalize renderer material contracts and harden backpack import
This commit is contained in:
@@ -99,7 +99,7 @@ public:
|
||||
const Containers::String& GetLastErrorMessage() const { return m_lastErrorMessage; }
|
||||
|
||||
private:
|
||||
static constexpr Core::uint32 kCurrentImporterVersion = 6;
|
||||
static constexpr Core::uint32 kCurrentImporterVersion = 7;
|
||||
|
||||
void EnsureProjectLayout();
|
||||
void LoadSourceAssetDB();
|
||||
|
||||
@@ -13,11 +13,6 @@
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
struct BuiltinForwardMaterialData {
|
||||
Math::Vector4 baseColorFactor = Math::Vector4::One();
|
||||
float alphaCutoff = 0.5f;
|
||||
};
|
||||
|
||||
enum class BuiltinSkyboxTextureMode : Core::uint8 {
|
||||
None = 0,
|
||||
Panoramic = 1,
|
||||
@@ -81,34 +76,7 @@ inline Math::Vector4 ResolveBuiltinBaseColorFactor(const Resources::Material* ma
|
||||
}
|
||||
}
|
||||
|
||||
static const char* kBaseColorPropertyNames[] = {
|
||||
"baseColor",
|
||||
"_BaseColor",
|
||||
"color",
|
||||
"_Color"
|
||||
};
|
||||
|
||||
for (const char* propertyName : kBaseColorPropertyNames) {
|
||||
if (material->HasProperty(Containers::String(propertyName))) {
|
||||
return material->GetFloat4(Containers::String(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
Math::Vector4 baseColor = Math::Vector4::One();
|
||||
static const char* kOpacityPropertyNames[] = {
|
||||
"opacity",
|
||||
"_Opacity",
|
||||
"alpha",
|
||||
"_Alpha"
|
||||
};
|
||||
for (const char* propertyName : kOpacityPropertyNames) {
|
||||
if (material->HasProperty(Containers::String(propertyName))) {
|
||||
baseColor.w = material->GetFloat(Containers::String(propertyName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return baseColor;
|
||||
return Math::Vector4::One();
|
||||
}
|
||||
|
||||
inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources::Material* material) {
|
||||
@@ -123,23 +91,6 @@ inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources:
|
||||
}
|
||||
}
|
||||
|
||||
static const char* kTextureNames[] = {
|
||||
"baseColorTexture",
|
||||
"_BaseColorTexture",
|
||||
"_MainTex",
|
||||
"albedoTexture",
|
||||
"mainTexture",
|
||||
"texture"
|
||||
};
|
||||
|
||||
for (const char* textureName : kTextureNames) {
|
||||
const Resources::ResourceHandle<Resources::Texture> textureHandle =
|
||||
material->GetTexture(Containers::String(textureName));
|
||||
if (textureHandle.Get() != nullptr && textureHandle->IsValid()) {
|
||||
return textureHandle.Get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -156,29 +107,9 @@ inline float ResolveBuiltinAlphaCutoff(const Resources::Material* material) {
|
||||
}
|
||||
}
|
||||
|
||||
static const char* kCutoffPropertyNames[] = {
|
||||
"_Cutoff",
|
||||
"cutoff",
|
||||
"_AlphaCutoff",
|
||||
"alphaCutoff"
|
||||
};
|
||||
|
||||
for (const char* propertyName : kCutoffPropertyNames) {
|
||||
if (material->HasProperty(Containers::String(propertyName))) {
|
||||
return material->GetFloat(Containers::String(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
inline BuiltinForwardMaterialData BuildBuiltinForwardMaterialData(const Resources::Material* material) {
|
||||
BuiltinForwardMaterialData data = {};
|
||||
data.baseColorFactor = ResolveBuiltinBaseColorFactor(material);
|
||||
data.alphaCutoff = ResolveBuiltinAlphaCutoff(material);
|
||||
return data;
|
||||
}
|
||||
|
||||
inline bool IsCubemapSkyboxTextureType(Resources::TextureType type) {
|
||||
return type == Resources::TextureType::TextureCube ||
|
||||
type == Resources::TextureType::TextureCubeArray;
|
||||
@@ -493,16 +424,6 @@ inline Core::int32 ResolveMaterialRenderQueue(const Resources::Material* materia
|
||||
}
|
||||
|
||||
if (const Resources::Shader* shader = material->GetShader()) {
|
||||
const Containers::String legacyExplicitPassName = material->GetLegacyShaderPassHint();
|
||||
if (!NormalizeBuiltinPassMetadataValue(legacyExplicitPassName).Empty()) {
|
||||
if (const Resources::ShaderPass* explicitPass = shader->FindPass(legacyExplicitPassName)) {
|
||||
Core::int32 shaderQueue = defaultQueue;
|
||||
if (TryResolveShaderPassRenderQueue(*explicitPass, shaderQueue)) {
|
||||
return shaderQueue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const Resources::ShaderPass& pass : shader->GetPasses()) {
|
||||
Core::int32 shaderQueue = defaultQueue;
|
||||
if (TryResolveShaderPassRenderQueue(pass, shaderQueue)) {
|
||||
@@ -518,52 +439,6 @@ inline bool IsTransparentRenderQueue(Core::int32 renderQueue) {
|
||||
return renderQueue >= static_cast<Core::int32>(Resources::MaterialRenderQueue::Transparent);
|
||||
}
|
||||
|
||||
inline bool HasLegacyMaterialBuiltinPassHints(const Resources::Material* material) {
|
||||
if (material == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !NormalizeBuiltinPassMetadataValue(material->GetLegacyShaderPassHint()).Empty() ||
|
||||
!NormalizeBuiltinPassMetadataValue(material->GetTag("LightMode")).Empty();
|
||||
}
|
||||
|
||||
inline bool LegacyMaterialBuiltinPassHintsMatch(
|
||||
const Resources::Material* material,
|
||||
BuiltinMaterialPass pass) {
|
||||
if (material == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Containers::String shaderPass = material->GetLegacyShaderPassHint();
|
||||
const Containers::String lightMode = material->GetTag("LightMode");
|
||||
const bool hasMaterialShaderPass = !NormalizeBuiltinPassMetadataValue(shaderPass).Empty();
|
||||
const bool hasMaterialLightMode = !NormalizeBuiltinPassMetadataValue(lightMode).Empty();
|
||||
if (!hasMaterialShaderPass && !hasMaterialLightMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasMaterialShaderPass &&
|
||||
!MatchesBuiltinPassName(shaderPass, pass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasMaterialLightMode &&
|
||||
!MatchesBuiltinPassName(lightMode, pass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool CanUseLegacyMaterialPassFallback(const Resources::Material* material) {
|
||||
if (material == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Resources::Shader* shader = material->GetShader();
|
||||
return shader == nullptr || !ShaderHasExplicitBuiltinMetadata(*shader);
|
||||
}
|
||||
|
||||
inline bool MatchesBuiltinPass(const Resources::Material* material, BuiltinMaterialPass pass) {
|
||||
if (material == nullptr) {
|
||||
return pass == BuiltinMaterialPass::ForwardLit;
|
||||
@@ -578,14 +453,10 @@ inline bool MatchesBuiltinPass(const Resources::Material* material, BuiltinMater
|
||||
}
|
||||
}
|
||||
|
||||
if (!CanUseLegacyMaterialPassFallback(material)) {
|
||||
if (shader != nullptr && ShaderHasExplicitBuiltinMetadata(*shader)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasLegacyMaterialBuiltinPassHints(material)) {
|
||||
return LegacyMaterialBuiltinPassHintsMatch(material, pass);
|
||||
}
|
||||
|
||||
return pass == BuiltinMaterialPass::ForwardLit;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,11 +60,6 @@ private:
|
||||
RHI::RHIDescriptorSet* set = nullptr;
|
||||
};
|
||||
|
||||
struct FallbackPerMaterialConstants {
|
||||
Math::Vector4 baseColorFactor = Math::Vector4::One();
|
||||
Math::Vector4 alphaCutoffParams = Math::Vector4(0.5f, 0.0f, 0.0f, 0.0f);
|
||||
};
|
||||
|
||||
struct PassLayoutKey {
|
||||
const Resources::Shader* shader = nullptr;
|
||||
Containers::String passName;
|
||||
|
||||
@@ -96,11 +96,6 @@ private:
|
||||
Math::Vector4 shadowOptions = Math::Vector4::Zero();
|
||||
};
|
||||
|
||||
struct FallbackPerMaterialConstants {
|
||||
Math::Vector4 baseColorFactor = Math::Vector4::One();
|
||||
Math::Vector4 alphaCutoffParams = Math::Vector4(0.5f, 0.0f, 0.0f, 0.0f);
|
||||
};
|
||||
|
||||
struct SkyboxConstants {
|
||||
Math::Vector4 topColor = Math::Vector4::Zero();
|
||||
Math::Vector4 horizonColor = Math::Vector4::Zero();
|
||||
|
||||
@@ -323,21 +323,6 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
|
||||
}
|
||||
}
|
||||
|
||||
if (!shaderHasExplicitBuiltinMetadata &&
|
||||
ownerMaterial != nullptr &&
|
||||
ownerMaterial->HasLegacyShaderPassHint()) {
|
||||
const Resources::ShaderPass* explicitPass = shader->FindPass(ownerMaterial->GetLegacyShaderPassHint());
|
||||
if (explicitPass != nullptr &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
|
||||
*shader,
|
||||
explicitPass->name,
|
||||
backend,
|
||||
keywordSet) &&
|
||||
tryAcceptPass(*explicitPass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -474,7 +459,8 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
|
||||
}
|
||||
|
||||
PipelineStateKey pipelineKey = {};
|
||||
pipelineKey.renderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
|
||||
pipelineKey.renderState =
|
||||
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
||||
pipelineKey.shader = resolvedShaderPass.shader;
|
||||
pipelineKey.passName = resolvedShaderPass.passName;
|
||||
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
|
||||
@@ -714,6 +700,8 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
|
||||
visibleItem.gameObject->GetName().c_str()).CStr());
|
||||
return false;
|
||||
}
|
||||
const Resources::MaterialRenderState effectiveRenderState =
|
||||
ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
|
||||
|
||||
PassLayoutKey passLayoutKey = {};
|
||||
passLayoutKey.shader = resolvedShaderPass.shader;
|
||||
@@ -764,38 +752,12 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
|
||||
};
|
||||
|
||||
MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
||||
FallbackPerMaterialConstants fallbackMaterialConstants = {};
|
||||
if (!materialConstants.IsValid()) {
|
||||
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material);
|
||||
fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor;
|
||||
fallbackMaterialConstants.alphaCutoffParams = Math::Vector4(
|
||||
materialData.alphaCutoff,
|
||||
0.0f,
|
||||
0.0f,
|
||||
0.0f);
|
||||
static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantFields[] = {
|
||||
{
|
||||
Containers::String("_BaseColor"),
|
||||
Resources::MaterialPropertyType::Float4,
|
||||
0,
|
||||
sizeof(Math::Vector4),
|
||||
sizeof(Math::Vector4)
|
||||
},
|
||||
{
|
||||
Containers::String("_Cutoff"),
|
||||
Resources::MaterialPropertyType::Float,
|
||||
sizeof(Math::Vector4),
|
||||
sizeof(float),
|
||||
sizeof(Math::Vector4)
|
||||
}
|
||||
};
|
||||
materialConstants.data = &fallbackMaterialConstants;
|
||||
materialConstants.size = sizeof(fallbackMaterialConstants);
|
||||
materialConstants.layout = {
|
||||
kFallbackMaterialConstantFields,
|
||||
2,
|
||||
sizeof(fallbackMaterialConstants)
|
||||
};
|
||||
if (passLayout->material.IsValid() && !materialConstants.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String("BuiltinDepthStylePassBase requires a schema-backed material constant payload for ") +
|
||||
visibleItem.gameObject->GetName().c_str()).CStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(visibleItem);
|
||||
@@ -879,6 +841,8 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
|
||||
passLayout->pipelineLayout);
|
||||
}
|
||||
|
||||
ApplyDynamicRenderState(effectiveRenderState, *commandList);
|
||||
|
||||
if (visibleItem.hasSection) {
|
||||
const Containers::Array<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();
|
||||
if (visibleItem.sectionIndex >= sections.Size()) {
|
||||
|
||||
@@ -51,20 +51,6 @@ const Resources::ShaderPass* FindCompatibleSurfacePass(
|
||||
}
|
||||
}
|
||||
|
||||
if (!shaderHasExplicitBuiltinMetadata &&
|
||||
material != nullptr &&
|
||||
material->HasLegacyShaderPassHint()) {
|
||||
const Resources::ShaderPass* explicitPass = shader.FindPass(material->GetLegacyShaderPassHint());
|
||||
if (explicitPass != nullptr &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
|
||||
shader,
|
||||
explicitPass->name,
|
||||
backend,
|
||||
keywordSet)) {
|
||||
return explicitPass;
|
||||
}
|
||||
}
|
||||
|
||||
if (shaderHasExplicitBuiltinMetadata) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -303,7 +289,8 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
|
||||
}
|
||||
|
||||
PipelineStateKey pipelineKey = {};
|
||||
pipelineKey.renderState = ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
|
||||
pipelineKey.renderState =
|
||||
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
||||
pipelineKey.shader = resolvedShaderPass.shader;
|
||||
pipelineKey.passName = resolvedShaderPass.passName;
|
||||
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
|
||||
@@ -682,6 +669,8 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
|
||||
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const Resources::MaterialRenderState effectiveRenderState =
|
||||
ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
|
||||
|
||||
PassLayoutKey passLayoutKey = {};
|
||||
passLayoutKey.shader = resolvedShaderPass.shader;
|
||||
@@ -705,38 +694,11 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
|
||||
}
|
||||
|
||||
MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
||||
FallbackPerMaterialConstants fallbackMaterialConstants = {};
|
||||
if (!materialConstants.IsValid()) {
|
||||
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material);
|
||||
fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor;
|
||||
fallbackMaterialConstants.alphaCutoffParams = Math::Vector4(
|
||||
materialData.alphaCutoff,
|
||||
0.0f,
|
||||
0.0f,
|
||||
0.0f);
|
||||
static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantFields[] = {
|
||||
{
|
||||
Containers::String("_BaseColor"),
|
||||
Resources::MaterialPropertyType::Float4,
|
||||
0,
|
||||
sizeof(Math::Vector4),
|
||||
sizeof(Math::Vector4)
|
||||
},
|
||||
{
|
||||
Containers::String("_Cutoff"),
|
||||
Resources::MaterialPropertyType::Float,
|
||||
sizeof(Math::Vector4),
|
||||
sizeof(float),
|
||||
sizeof(Math::Vector4)
|
||||
}
|
||||
};
|
||||
materialConstants.data = &fallbackMaterialConstants;
|
||||
materialConstants.size = sizeof(fallbackMaterialConstants);
|
||||
materialConstants.layout = {
|
||||
kFallbackMaterialConstantFields,
|
||||
2,
|
||||
sizeof(fallbackMaterialConstants)
|
||||
};
|
||||
if (passLayout->material.IsValid() && !materialConstants.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinForwardPipeline requires a schema-backed material constant payload");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (passLayout->descriptorSetCount > 0) {
|
||||
@@ -807,6 +769,8 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
|
||||
passLayout->pipelineLayout);
|
||||
}
|
||||
|
||||
ApplyDynamicRenderState(effectiveRenderState, *commandList);
|
||||
|
||||
if (visibleItem.hasSection) {
|
||||
const Containers::Array<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();
|
||||
if (visibleItem.sectionIndex >= sections.Size()) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <assimp/postprocess.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -43,6 +44,13 @@ struct TextureImportContext {
|
||||
TextureLoader textureLoader;
|
||||
std::unordered_map<std::string, Texture*> textureCache;
|
||||
std::vector<Texture*> ownedTextures;
|
||||
struct ObjMaterialTextureFallbacks {
|
||||
std::filesystem::path baseColorPath;
|
||||
std::filesystem::path normalPath;
|
||||
std::filesystem::path specularPath;
|
||||
};
|
||||
bool objMaterialFallbacksInitialized = false;
|
||||
std::unordered_map<std::string, ObjMaterialTextureFallbacks> objMaterialFallbacks;
|
||||
};
|
||||
|
||||
Math::Bounds ComputeBounds(const std::vector<StaticMeshVertex>& vertices) {
|
||||
@@ -117,6 +125,38 @@ Containers::String ToContainersString(const std::string& value) {
|
||||
return Containers::String(value.c_str());
|
||||
}
|
||||
|
||||
std::string ToLowerCopy(const std::string& value) {
|
||||
std::string lowered = value;
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return lowered;
|
||||
}
|
||||
|
||||
std::string TrimCopy(const std::string& value) {
|
||||
const auto begin = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
});
|
||||
if (begin == value.end()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const auto end = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
}).base();
|
||||
return std::string(begin, end);
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitWhitespaceTokens(const std::string& text) {
|
||||
std::vector<std::string> tokens;
|
||||
std::istringstream stream(text);
|
||||
std::string token;
|
||||
while (stream >> token) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
Containers::String BuildSubResourcePath(const Containers::String& sourcePath,
|
||||
const char* category,
|
||||
Core::uint32 index,
|
||||
@@ -171,6 +211,20 @@ Containers::String NormalizePathString(const std::filesystem::path& path) {
|
||||
return Containers::String(path.lexically_normal().generic_string().c_str());
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveSourcePathAbsolute(const Containers::String& sourcePath) {
|
||||
std::filesystem::path resolvedPath(sourcePath.CStr());
|
||||
if (resolvedPath.is_absolute()) {
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty()) {
|
||||
return (std::filesystem::path(resourceRoot.CStr()) / resolvedPath).lexically_normal();
|
||||
}
|
||||
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
Containers::String ResolveArtifactDependencyPath(const Containers::String& dependencyPath,
|
||||
const Containers::String& ownerArtifactPath) {
|
||||
if (dependencyPath.Empty()) {
|
||||
@@ -390,6 +444,140 @@ Texture* LoadMaterialTexture(const aiMaterial& assimpMaterial,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
enum class ObjMaterialTextureSemantic {
|
||||
BaseColor,
|
||||
Normal,
|
||||
Specular
|
||||
};
|
||||
|
||||
void InitializeObjMaterialTextureFallbacks(TextureImportContext& context) {
|
||||
if (context.objMaterialFallbacksInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.objMaterialFallbacksInitialized = true;
|
||||
|
||||
const std::filesystem::path sourcePath = ResolveSourcePathAbsolute(context.sourcePath);
|
||||
if (ToLowerCopy(sourcePath.extension().string()) != ".obj") {
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream objInput(sourcePath);
|
||||
if (!objInput.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> mtlPaths;
|
||||
const std::filesystem::path sourceDirectory = sourcePath.parent_path();
|
||||
std::string line;
|
||||
while (std::getline(objInput, line)) {
|
||||
const std::string trimmed = TrimCopy(line);
|
||||
if (trimmed.empty() || trimmed[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string lowered = ToLowerCopy(trimmed);
|
||||
if (lowered.rfind("mtllib", 0) != 0 ||
|
||||
(trimmed.size() > 6 && std::isspace(static_cast<unsigned char>(trimmed[6])) == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string remainder = TrimCopy(trimmed.substr(6));
|
||||
for (const std::string& token : SplitWhitespaceTokens(remainder)) {
|
||||
mtlPaths.push_back((sourceDirectory / token).lexically_normal());
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::filesystem::path& mtlPath : mtlPaths) {
|
||||
std::ifstream mtlInput(mtlPath);
|
||||
if (!mtlInput.is_open()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::filesystem::path mtlDirectory = mtlPath.parent_path();
|
||||
std::string currentMaterialName;
|
||||
while (std::getline(mtlInput, line)) {
|
||||
const std::string trimmed = TrimCopy(line);
|
||||
if (trimmed.empty() || trimmed[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::vector<std::string> tokens = SplitWhitespaceTokens(trimmed);
|
||||
if (tokens.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string keyword = ToLowerCopy(tokens.front());
|
||||
if (keyword == "newmtl") {
|
||||
currentMaterialName = TrimCopy(trimmed.substr(tokens.front().size()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentMaterialName.empty() || tokens.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& textureToken = tokens.back();
|
||||
if (textureToken.empty() || textureToken[0] == '-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
TextureImportContext::ObjMaterialTextureFallbacks& fallbacks =
|
||||
context.objMaterialFallbacks[currentMaterialName];
|
||||
const std::filesystem::path resolvedTexturePath =
|
||||
(mtlDirectory / textureToken).lexically_normal();
|
||||
|
||||
if (keyword == "map_kd") {
|
||||
fallbacks.baseColorPath = resolvedTexturePath;
|
||||
} else if (keyword == "map_bump" || keyword == "bump" || keyword == "norm") {
|
||||
fallbacks.normalPath = resolvedTexturePath;
|
||||
} else if (keyword == "map_ks") {
|
||||
fallbacks.specularPath = resolvedTexturePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Texture* LoadObjMaterialTextureFallback(const aiMaterial& assimpMaterial,
|
||||
ObjMaterialTextureSemantic semantic,
|
||||
const TextureImportSettings& settings,
|
||||
TextureImportContext& context) {
|
||||
InitializeObjMaterialTextureFallbacks(context);
|
||||
|
||||
aiString assimpName;
|
||||
if (assimpMaterial.Get(AI_MATKEY_NAME, assimpName) != AI_SUCCESS ||
|
||||
assimpName.length == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto fallbackIt = context.objMaterialFallbacks.find(std::string(assimpName.C_Str()));
|
||||
if (fallbackIt == context.objMaterialFallbacks.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const TextureImportContext::ObjMaterialTextureFallbacks& fallbacks = fallbackIt->second;
|
||||
const std::filesystem::path* fallbackPath = nullptr;
|
||||
switch (semantic) {
|
||||
case ObjMaterialTextureSemantic::BaseColor:
|
||||
fallbackPath = &fallbacks.baseColorPath;
|
||||
break;
|
||||
case ObjMaterialTextureSemantic::Normal:
|
||||
fallbackPath = &fallbacks.normalPath;
|
||||
break;
|
||||
case ObjMaterialTextureSemantic::Specular:
|
||||
fallbackPath = &fallbacks.specularPath;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (fallbackPath == nullptr || fallbackPath->empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return LoadExternalTexture(*fallbackPath, settings, context);
|
||||
}
|
||||
|
||||
bool HasMaterialTexture(
|
||||
const aiMaterial& assimpMaterial,
|
||||
std::initializer_list<aiTextureType> textureTypes) {
|
||||
@@ -438,15 +626,21 @@ void ImportMaterialTextures(const aiMaterial& assimpMaterial,
|
||||
Material& material,
|
||||
TextureImportContext& context) {
|
||||
auto assignTexture = [&](const char* propertyName,
|
||||
ObjMaterialTextureSemantic semantic,
|
||||
std::initializer_list<aiTextureType> textureTypes) {
|
||||
const TextureImportSettings settings = BuildMaterialTextureImportSettings(propertyName);
|
||||
Texture* texture = LoadMaterialTexture(assimpMaterial, textureTypes, settings, context);
|
||||
if (texture == nullptr) {
|
||||
texture = LoadObjMaterialTextureFallback(assimpMaterial, semantic, settings, context);
|
||||
}
|
||||
if (texture != nullptr) {
|
||||
material.SetTexture(Containers::String(propertyName), ResourceHandle<Texture>(texture));
|
||||
}
|
||||
};
|
||||
|
||||
assignTexture("_MainTex", { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
|
||||
assignTexture("_MainTex",
|
||||
ObjMaterialTextureSemantic::BaseColor,
|
||||
{ aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
|
||||
}
|
||||
|
||||
Material* ImportSingleMaterial(const aiMaterial& assimpMaterial,
|
||||
|
||||
@@ -62,6 +62,18 @@ std::filesystem::path ResolveRuntimePath(const char* relativePath) {
|
||||
return GetExecutableDirectory() / relativePath;
|
||||
}
|
||||
|
||||
void NormalizeBackpackTestMaterials(Mesh& mesh) {
|
||||
for (Material* material : mesh.GetMaterials()) {
|
||||
if (material == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MaterialRenderState renderState = material->GetRenderState();
|
||||
renderState.cullMode = MaterialCullMode::None;
|
||||
material->SetRenderState(renderState);
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetScreenshotFilename(RHIType backendType) {
|
||||
switch (backendType) {
|
||||
case RHIType::D3D12:
|
||||
@@ -179,6 +191,7 @@ void BackpackSceneTest::LoadBackpackMesh() {
|
||||
ASSERT_TRUE(mMesh->IsValid());
|
||||
ASSERT_GT(mMesh->GetVertexCount(), 0u);
|
||||
ASSERT_GT(mMesh->GetSections().Size(), 0u);
|
||||
NormalizeBackpackTestMaterials(*mMesh);
|
||||
}
|
||||
|
||||
void BackpackSceneTest::BuildScene() {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
@@ -19,10 +20,13 @@
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
@@ -66,6 +70,18 @@ std::filesystem::path ResolveRuntimePath(const char* relativePath) {
|
||||
return GetExecutableDirectory() / relativePath;
|
||||
}
|
||||
|
||||
void NormalizeBackpackTestMaterials(Mesh& mesh) {
|
||||
for (Material* material : mesh.GetMaterials()) {
|
||||
if (material == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MaterialRenderState renderState = material->GetRenderState();
|
||||
renderState.cullMode = MaterialCullMode::None;
|
||||
material->SetRenderState(renderState);
|
||||
}
|
||||
}
|
||||
|
||||
Mesh* CreateQuadMesh() {
|
||||
auto* mesh = new Mesh();
|
||||
IResource::ConstructParams params = {};
|
||||
@@ -84,7 +100,7 @@ Mesh* CreateQuadMesh() {
|
||||
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
|
||||
vertices[3].uv0 = Vector2(1.0f, 0.0f);
|
||||
|
||||
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
|
||||
mesh->SetVertexData(
|
||||
vertices,
|
||||
sizeof(vertices),
|
||||
@@ -137,7 +153,8 @@ Material* CreateQuadMaterial(Texture* texture) {
|
||||
params.path = "Tests/Rendering/CameraStackQuad.material";
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
return material;
|
||||
}
|
||||
|
||||
@@ -189,6 +206,13 @@ void CameraStackSceneTest::SetUp() {
|
||||
mQuadMesh = CreateQuadMesh();
|
||||
mQuadTexture = CreateCheckerTexture();
|
||||
mQuadMaterial = CreateQuadMaterial(mQuadTexture);
|
||||
ASSERT_NE(mQuadMaterial, nullptr);
|
||||
ASSERT_NE(mQuadMaterial->GetShader(), nullptr);
|
||||
ASSERT_TRUE(mQuadMaterial->HasProperty("_BaseColor"));
|
||||
ASSERT_TRUE(mQuadMaterial->HasProperty("_MainTex"));
|
||||
ASSERT_EQ(mQuadMaterial->GetTexture("_MainTex").Get(), mQuadTexture);
|
||||
ASSERT_EQ(ResolveBuiltinBaseColorTexture(mQuadMaterial), mQuadTexture);
|
||||
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(mQuadMaterial).IsValid());
|
||||
BuildScene();
|
||||
|
||||
TextureDesc depthDesc = {};
|
||||
@@ -270,6 +294,14 @@ void CameraStackSceneTest::LoadBackpackMesh() {
|
||||
ASSERT_TRUE(mBackpackMesh->IsValid());
|
||||
ASSERT_GT(mBackpackMesh->GetVertexCount(), 0u);
|
||||
ASSERT_GT(mBackpackMesh->GetSections().Size(), 0u);
|
||||
ASSERT_GT(mBackpackMesh->GetMaterials().Size(), 0u);
|
||||
NormalizeBackpackTestMaterials(*mBackpackMesh);
|
||||
|
||||
for (Material* material : mBackpackMesh->GetMaterials()) {
|
||||
ASSERT_NE(material, nullptr);
|
||||
ASSERT_NE(material->GetShader(), nullptr);
|
||||
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(material).IsValid());
|
||||
}
|
||||
}
|
||||
|
||||
void CameraStackSceneTest::BuildScene() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
@@ -16,8 +17,10 @@
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
@@ -110,7 +113,8 @@ Material* CreateMaterial(
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
material->SetRenderQueue(MaterialRenderQueue::Geometry);
|
||||
material->SetRenderState(renderState);
|
||||
return material;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
@@ -16,8 +17,10 @@
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
|
||||
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
|
||||
vertices[3].uv0 = Vector2(1.0f, 0.0f);
|
||||
|
||||
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
|
||||
mesh->SetVertexData(
|
||||
vertices,
|
||||
sizeof(vertices),
|
||||
@@ -111,7 +114,8 @@ Material* CreateMaterial(
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
material->SetRenderQueue(renderQueue);
|
||||
material->SetRenderState(renderState);
|
||||
return material;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
@@ -16,8 +17,10 @@
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
@@ -112,7 +115,8 @@ Material* CreateMaterial(
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
material->SetRenderQueue(renderQueue);
|
||||
material->SetRenderState(renderState);
|
||||
return material;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
@@ -16,8 +17,10 @@
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
|
||||
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
|
||||
vertices[3].uv0 = Vector2(1.0f, 0.0f);
|
||||
|
||||
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
|
||||
mesh->SetVertexData(
|
||||
vertices,
|
||||
sizeof(vertices),
|
||||
@@ -111,7 +114,8 @@ Material* CreateMaterial(
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
material->SetRenderQueue(renderQueue);
|
||||
material->SetRenderState(renderState);
|
||||
return material;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Vector2.h>
|
||||
@@ -15,8 +16,11 @@
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
@@ -61,7 +65,7 @@ Mesh* CreateQuadMesh() {
|
||||
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
|
||||
vertices[3].uv0 = Vector2(1.0f, 0.0f);
|
||||
|
||||
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
|
||||
mesh->SetVertexData(
|
||||
vertices,
|
||||
sizeof(vertices),
|
||||
@@ -114,7 +118,8 @@ Material* CreateMaterial(Texture* texture) {
|
||||
params.path = "Tests/Rendering/Quad.material";
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
return material;
|
||||
}
|
||||
|
||||
@@ -162,6 +167,14 @@ void TexturedQuadSceneTest::SetUp() {
|
||||
mMesh = CreateQuadMesh();
|
||||
mTexture = CreateCheckerTexture();
|
||||
mMaterial = CreateMaterial(mTexture);
|
||||
ASSERT_NE(mMaterial, nullptr);
|
||||
ASSERT_NE(mMaterial->GetShader(), nullptr);
|
||||
ASSERT_TRUE(mMaterial->HasProperty("_BaseColor"));
|
||||
ASSERT_TRUE(mMaterial->HasProperty("_Cutoff"));
|
||||
ASSERT_TRUE(mMaterial->HasProperty("_MainTex"));
|
||||
ASSERT_EQ(mMaterial->GetTexture("_MainTex").Get(), mTexture);
|
||||
ASSERT_EQ(ResolveBuiltinBaseColorTexture(mMaterial), mTexture);
|
||||
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(mMaterial).IsValid());
|
||||
|
||||
GameObject* cameraObject = mScene->CreateGameObject("MainCamera");
|
||||
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
@@ -16,8 +17,10 @@
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
|
||||
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
|
||||
vertices[3].uv0 = Vector2(1.0f, 0.0f);
|
||||
|
||||
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
|
||||
mesh->SetVertexData(
|
||||
vertices,
|
||||
sizeof(vertices),
|
||||
@@ -111,7 +114,8 @@ Material* CreateMaterial(
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
material->SetRenderQueue(renderQueue);
|
||||
material->SetRenderState(renderState);
|
||||
return material;
|
||||
|
||||
@@ -54,7 +54,7 @@ Mesh* CreateSectionedTestMesh(const char* path, std::initializer_list<uint32_t>
|
||||
return mesh;
|
||||
}
|
||||
|
||||
Material* CreateTestMaterial(const char* path, int32_t renderQueue, const char* legacyShaderPassHint = nullptr, const char* lightMode = nullptr) {
|
||||
Material* CreateTestMaterial(const char* path, int32_t renderQueue) {
|
||||
auto* material = new Material();
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "TestMaterial";
|
||||
@@ -62,12 +62,6 @@ Material* CreateTestMaterial(const char* path, int32_t renderQueue, const char*
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
material->Initialize(params);
|
||||
material->SetRenderQueue(renderQueue);
|
||||
if (legacyShaderPassHint != nullptr) {
|
||||
material->SetLegacyShaderPassHint(legacyShaderPassHint);
|
||||
}
|
||||
if (lightMode != nullptr) {
|
||||
material->SetTag("LightMode", lightMode);
|
||||
}
|
||||
return material;
|
||||
}
|
||||
|
||||
@@ -331,14 +325,10 @@ TEST(RenderSceneExtractor_Test, ExtractsSectionLevelVisibleItemsAndSortsByRender
|
||||
Mesh* mesh = CreateSectionedTestMesh("Meshes/sectioned.mesh", { 1u, 0u });
|
||||
Material* opaqueMaterial = CreateTestMaterial(
|
||||
"Materials/opaque.mat",
|
||||
static_cast<int32_t>(MaterialRenderQueue::Geometry),
|
||||
"ForwardLit",
|
||||
"ForwardBase");
|
||||
static_cast<int32_t>(MaterialRenderQueue::Geometry));
|
||||
Material* transparentMaterial = CreateTestMaterial(
|
||||
"Materials/transparent.mat",
|
||||
static_cast<int32_t>(MaterialRenderQueue::Transparent),
|
||||
"ForwardLit",
|
||||
"ForwardBase");
|
||||
static_cast<int32_t>(MaterialRenderQueue::Transparent));
|
||||
|
||||
meshFilter->SetMesh(mesh);
|
||||
meshRenderer->SetMaterial(0, opaqueMaterial);
|
||||
@@ -382,7 +372,7 @@ TEST(RenderSceneExtractor_Test, SortsOpaqueFrontToBackAndTransparentBackToFront)
|
||||
auto* meshFilter = object->AddComponent<MeshFilterComponent>();
|
||||
auto* meshRenderer = object->AddComponent<MeshRendererComponent>();
|
||||
Mesh* mesh = CreateTestMesh(name);
|
||||
Material* material = CreateTestMaterial(name, renderQueue, "ForwardLit", "ForwardBase");
|
||||
Material* material = CreateTestMaterial(name, renderQueue);
|
||||
meshFilter->SetMesh(mesh);
|
||||
meshRenderer->SetMaterial(0, material);
|
||||
return mesh;
|
||||
@@ -440,9 +430,7 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
|
||||
Mesh* mesh = CreateSectionedTestMesh("Meshes/embedded.mesh", { 0u });
|
||||
Material* embeddedMaterial = CreateTestMaterial(
|
||||
"Materials/embedded.mat",
|
||||
static_cast<int32_t>(MaterialRenderQueue::Transparent),
|
||||
"ForwardLit",
|
||||
"ForwardBase");
|
||||
static_cast<int32_t>(MaterialRenderQueue::Transparent));
|
||||
mesh->AddMaterial(embeddedMaterial);
|
||||
meshFilter->SetMesh(mesh);
|
||||
meshRenderer->ClearMaterials();
|
||||
@@ -459,38 +447,36 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsCanDriveBuiltinPassMatching) {
|
||||
Material forwardMaterial;
|
||||
forwardMaterial.SetLegacyShaderPassHint("ForwardLit");
|
||||
forwardMaterial.SetTag("LightMode", "ForwardBase");
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&forwardMaterial, BuiltinMaterialPass::ForwardLit));
|
||||
|
||||
TEST(RenderMaterialUtility_Test, MaterialsWithoutShaderMetadataUseImplicitForwardFallback) {
|
||||
Material noMetadataMaterial;
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&noMetadataMaterial, BuiltinMaterialPass::ForwardLit));
|
||||
|
||||
Material shadowMaterial;
|
||||
shadowMaterial.SetLegacyShaderPassHint("ShadowCaster");
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ForwardLit));
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ShadowCaster));
|
||||
|
||||
Material depthOnlyMaterial;
|
||||
depthOnlyMaterial.SetTag("LightMode", "DepthOnly");
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&depthOnlyMaterial, BuiltinMaterialPass::ForwardLit));
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsSupportUnlitDepthAndObjectId) {
|
||||
TEST(RenderMaterialUtility_Test, ShaderPassMetadataSupportsUnlitDepthAndObjectId) {
|
||||
Material unlitMaterial;
|
||||
unlitMaterial.SetLegacyShaderPassHint("Unlit");
|
||||
auto* unlitShader = new Shader();
|
||||
ShaderPass unlitPass = {};
|
||||
unlitPass.name = "Unlit";
|
||||
unlitShader->AddPass(unlitPass);
|
||||
unlitMaterial.SetShader(ResourceHandle<Shader>(unlitShader));
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::Unlit));
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::ForwardLit));
|
||||
|
||||
Material depthMaterial;
|
||||
depthMaterial.SetTag("LightMode", "DepthOnly");
|
||||
auto* depthShader = new Shader();
|
||||
ShaderPass depthPass = {};
|
||||
depthPass.name = "DepthOnly";
|
||||
depthShader->AddPass(depthPass);
|
||||
depthMaterial.SetShader(ResourceHandle<Shader>(depthShader));
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::DepthOnly));
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::Unlit));
|
||||
|
||||
Material objectIdMaterial;
|
||||
objectIdMaterial.SetLegacyShaderPassHint("ObjectId");
|
||||
auto* objectIdShader = new Shader();
|
||||
ShaderPass objectIdPass = {};
|
||||
objectIdPass.name = "ObjectId";
|
||||
objectIdShader->AddPass(objectIdPass);
|
||||
objectIdMaterial.SetShader(ResourceHandle<Shader>(objectIdShader));
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ObjectId));
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ForwardLit));
|
||||
}
|
||||
@@ -525,7 +511,7 @@ TEST(RenderMaterialUtility_Test, ExplicitShaderPassMetadataDisablesImplicitForwa
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMaterialPassHints) {
|
||||
TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingMaterialTags) {
|
||||
Material material;
|
||||
auto* shader = new Shader();
|
||||
|
||||
@@ -534,7 +520,6 @@ TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMateria
|
||||
shader->AddPass(forwardPass);
|
||||
|
||||
material.SetShader(ResourceHandle<Shader>(shader));
|
||||
material.SetLegacyShaderPassHint("ShadowCaster");
|
||||
material.SetTag("LightMode", "DepthOnly");
|
||||
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
|
||||
@@ -542,7 +527,7 @@ TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMateria
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::DepthOnly));
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsRemainAvailableForShadersWithoutBuiltinMetadata) {
|
||||
TEST(RenderMaterialUtility_Test, MaterialTagsDoNotOverrideImplicitForwardFallbackWithoutShaderMetadata) {
|
||||
Material material;
|
||||
auto* shader = new Shader();
|
||||
|
||||
@@ -551,10 +536,10 @@ TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsRemainAvailableForShader
|
||||
shader->AddPass(defaultPass);
|
||||
|
||||
material.SetShader(ResourceHandle<Shader>(shader));
|
||||
material.SetLegacyShaderPassHint("ShadowCaster");
|
||||
material.SetTag("LightMode", "ShadowCaster");
|
||||
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ShadowCaster));
|
||||
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ShadowCaster));
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, ShadersWithoutBuiltinMetadataKeepImplicitForwardFallback) {
|
||||
@@ -571,28 +556,24 @@ TEST(RenderMaterialUtility_Test, ShadersWithoutBuiltinMetadataKeepImplicitForwar
|
||||
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromCanonicalNamesAndAliases) {
|
||||
Material canonicalMaterial;
|
||||
canonicalMaterial.SetFloat4("baseColor", Vector4(0.2f, 0.4f, 0.6f, 0.8f));
|
||||
canonicalMaterial.SetFloat("_Cutoff", 0.3f);
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&canonicalMaterial), Vector4(0.2f, 0.4f, 0.6f, 0.8f));
|
||||
EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&canonicalMaterial), 0.3f);
|
||||
TEST(RenderMaterialUtility_Test, MaterialsWithoutFormalShaderMetadataResolveBuiltinDefaultsOnly) {
|
||||
Material material;
|
||||
material.SetFloat4("baseColor", Vector4(0.2f, 0.4f, 0.6f, 0.8f));
|
||||
material.SetFloat("_Cutoff", 0.3f);
|
||||
material.SetFloat("opacity", 0.35f);
|
||||
|
||||
Material aliasMaterial;
|
||||
aliasMaterial.SetFloat4("_BaseColor", Vector4(0.7f, 0.6f, 0.5f, 0.4f));
|
||||
aliasMaterial.SetFloat("_Cutoff", 0.42f);
|
||||
Texture* baseColorTexture = new Texture();
|
||||
IResource::ConstructParams textureParams = {};
|
||||
textureParams.name = "AliasBaseColor";
|
||||
textureParams.path = "Textures/alias_base_color.texture";
|
||||
textureParams.guid = ResourceGUID::Generate(textureParams.path);
|
||||
baseColorTexture->Initialize(textureParams);
|
||||
aliasMaterial.SetTexture("_BaseColorTexture", ResourceHandle<Texture>(baseColorTexture));
|
||||
material.SetTexture("_BaseColorTexture", ResourceHandle<Texture>(baseColorTexture));
|
||||
|
||||
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(&aliasMaterial);
|
||||
EXPECT_EQ(materialData.baseColorFactor, Vector4(0.7f, 0.6f, 0.5f, 0.4f));
|
||||
EXPECT_FLOAT_EQ(materialData.alphaCutoff, 0.42f);
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&aliasMaterial), baseColorTexture);
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&material), 0.5f);
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), nullptr);
|
||||
EXPECT_FALSE(ResolveSchemaMaterialConstantPayload(&material).IsValid());
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShaderSemanticMetadata) {
|
||||
@@ -708,13 +689,13 @@ TEST(RenderMaterialUtility_Test, ExposesSchemaDrivenMaterialConstantPayload) {
|
||||
EXPECT_FLOAT_EQ(cutoffValues[0], 0.6f);
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, UsesOpacityOnlyWhenBaseColorFactorIsMissing) {
|
||||
TEST(RenderMaterialUtility_Test, DoesNotUseOpacityFallbackWithoutFormalShaderSemanticMetadata) {
|
||||
Material material;
|
||||
material.SetFloat("opacity", 0.35f);
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 0.35f));
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
material.SetFloat4("baseColor", Vector4(0.9f, 0.8f, 0.7f, 0.6f));
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.9f, 0.8f, 0.7f, 0.6f));
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
|
||||
@@ -732,6 +713,20 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
|
||||
renderState.depthTestEnable = true;
|
||||
renderState.depthWriteEnable = false;
|
||||
renderState.depthFunc = MaterialComparisonFunc::LessEqual;
|
||||
renderState.depthBiasFactor = 1.25f;
|
||||
renderState.depthBiasUnits = 4;
|
||||
renderState.stencil.enabled = true;
|
||||
renderState.stencil.reference = 9;
|
||||
renderState.stencil.readMask = 0x3F;
|
||||
renderState.stencil.writeMask = 0x1F;
|
||||
renderState.stencil.front.func = MaterialComparisonFunc::Equal;
|
||||
renderState.stencil.front.failOp = MaterialStencilOp::Replace;
|
||||
renderState.stencil.front.passOp = MaterialStencilOp::IncrWrap;
|
||||
renderState.stencil.front.depthFailOp = MaterialStencilOp::DecrSat;
|
||||
renderState.stencil.back.func = MaterialComparisonFunc::NotEqual;
|
||||
renderState.stencil.back.failOp = MaterialStencilOp::Invert;
|
||||
renderState.stencil.back.passOp = MaterialStencilOp::DecrWrap;
|
||||
renderState.stencil.back.depthFailOp = MaterialStencilOp::Zero;
|
||||
material.SetRenderState(renderState);
|
||||
|
||||
const MaterialRenderState effectiveRenderState = ResolveEffectiveRenderState(nullptr, &material);
|
||||
@@ -741,6 +736,8 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
|
||||
|
||||
EXPECT_EQ(rasterizerState.cullMode, static_cast<uint32_t>(XCEngine::RHI::CullMode::Back));
|
||||
EXPECT_EQ(rasterizerState.frontFace, static_cast<uint32_t>(XCEngine::RHI::FrontFace::CounterClockwise));
|
||||
EXPECT_FLOAT_EQ(rasterizerState.slopeScaledDepthBias, 1.25f);
|
||||
EXPECT_EQ(rasterizerState.depthBias, 4);
|
||||
|
||||
EXPECT_TRUE(blendState.blendEnable);
|
||||
EXPECT_EQ(blendState.srcBlend, static_cast<uint32_t>(XCEngine::RHI::BlendFactor::SrcAlpha));
|
||||
@@ -754,6 +751,31 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
|
||||
EXPECT_TRUE(depthStencilState.depthTestEnable);
|
||||
EXPECT_FALSE(depthStencilState.depthWriteEnable);
|
||||
EXPECT_EQ(depthStencilState.depthFunc, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::LessEqual));
|
||||
EXPECT_TRUE(depthStencilState.stencilEnable);
|
||||
EXPECT_EQ(depthStencilState.stencilReadMask, 0x3F);
|
||||
EXPECT_EQ(depthStencilState.stencilWriteMask, 0x1F);
|
||||
EXPECT_EQ(depthStencilState.front.func, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::Equal));
|
||||
EXPECT_EQ(depthStencilState.front.failOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Replace));
|
||||
EXPECT_EQ(depthStencilState.front.passOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Incr));
|
||||
EXPECT_EQ(depthStencilState.front.depthFailOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::DecrSat));
|
||||
EXPECT_EQ(depthStencilState.back.func, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::NotEqual));
|
||||
EXPECT_EQ(depthStencilState.back.failOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Invert));
|
||||
EXPECT_EQ(depthStencilState.back.passOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Decr));
|
||||
EXPECT_EQ(depthStencilState.back.depthFailOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Zero));
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, PipelineStateKeyStripsDynamicStencilReference) {
|
||||
MaterialRenderState renderState = {};
|
||||
renderState.stencil.enabled = true;
|
||||
renderState.stencil.reference = 12;
|
||||
renderState.stencil.front.func = MaterialComparisonFunc::Always;
|
||||
renderState.stencil.back.func = MaterialComparisonFunc::Always;
|
||||
|
||||
const MaterialRenderState keyState = BuildStaticPipelineRenderStateKey(renderState);
|
||||
EXPECT_TRUE(keyState.stencil.enabled);
|
||||
EXPECT_EQ(keyState.stencil.reference, 0u);
|
||||
EXPECT_EQ(keyState.stencil.front.func, MaterialComparisonFunc::Always);
|
||||
EXPECT_EQ(keyState.stencil.back.func, MaterialComparisonFunc::Always);
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, ShaderPassFixedFunctionStateIsUsedBeforeLegacyMaterialOverride) {
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace XCEngine::Resources;
|
||||
using namespace XCEngine::Containers;
|
||||
|
||||
@@ -24,6 +31,10 @@ std::string GetMeshFixturePath(const char* fileName) {
|
||||
return (std::filesystem::path(XCENGINE_TEST_FIXTURES_DIR) / "Resources" / "Mesh" / fileName).string();
|
||||
}
|
||||
|
||||
std::filesystem::path GetRepositoryRoot() {
|
||||
return std::filesystem::path(__FILE__).parent_path().parent_path().parent_path().parent_path();
|
||||
}
|
||||
|
||||
XCEngine::Core::uint32 GetFirstSectionMaterialIndex(const Mesh& mesh) {
|
||||
if (mesh.GetSections().Empty()) {
|
||||
return 0;
|
||||
@@ -202,6 +213,119 @@ TEST(MeshLoader, ImportsMaterialTexturesFromObj) {
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
TEST(MeshLoader, ProjectBackpackSampleImportsMaterialTextures) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path repositoryRoot = GetRepositoryRoot();
|
||||
const fs::path projectRoot = repositoryRoot / "project";
|
||||
const fs::path backpackMeshPath = projectRoot / "Assets" / "Models" / "backpack" / "backpack.obj";
|
||||
const fs::path assimpDllPath = repositoryRoot / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
|
||||
|
||||
if (!fs::exists(backpackMeshPath)) {
|
||||
GTEST_SKIP() << "Backpack sample mesh is not available in the local project fixture.";
|
||||
}
|
||||
|
||||
ASSERT_TRUE(fs::exists(assimpDllPath));
|
||||
|
||||
#ifdef _WIN32
|
||||
struct DllGuard {
|
||||
HMODULE module = nullptr;
|
||||
~DllGuard() {
|
||||
if (module != nullptr) {
|
||||
FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
} dllGuard;
|
||||
dllGuard.module = LoadLibraryW(assimpDllPath.wstring().c_str());
|
||||
ASSERT_NE(dllGuard.module, nullptr);
|
||||
#endif
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
|
||||
MeshLoader loader;
|
||||
const LoadResult result = loader.Load(backpackMeshPath.string().c_str());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* mesh = static_cast<Mesh*>(result.resource);
|
||||
ASSERT_NE(mesh, nullptr);
|
||||
ASSERT_FALSE(mesh->GetMaterials().Empty());
|
||||
|
||||
Material* material = GetFirstSectionMaterial(*mesh);
|
||||
ASSERT_NE(material, nullptr);
|
||||
EXPECT_EQ(material->GetTextureBindingCount(), 1u);
|
||||
EXPECT_EQ(material->GetTextureBindingName(0), "_MainTex");
|
||||
EXPECT_EQ(
|
||||
fs::path(material->GetTextureBindingPath(0).CStr()).lexically_normal().generic_string(),
|
||||
(projectRoot / "Assets" / "Models" / "backpack" / "diffuse.jpg").lexically_normal().generic_string());
|
||||
|
||||
delete mesh;
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
}
|
||||
|
||||
TEST(MeshLoader, ProjectBackpackSampleArtifactRetainsSectionMaterialTextures) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path repositoryRoot = GetRepositoryRoot();
|
||||
const fs::path projectRoot = repositoryRoot / "project";
|
||||
const fs::path backpackMeshPath = projectRoot / "Assets" / "Models" / "backpack" / "backpack.obj";
|
||||
const fs::path assimpDllPath = repositoryRoot / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
|
||||
|
||||
if (!fs::exists(backpackMeshPath)) {
|
||||
GTEST_SKIP() << "Backpack sample mesh is not available in the local project fixture.";
|
||||
}
|
||||
|
||||
ASSERT_TRUE(fs::exists(assimpDllPath));
|
||||
|
||||
#ifdef _WIN32
|
||||
struct DllGuard {
|
||||
HMODULE module = nullptr;
|
||||
~DllGuard() {
|
||||
if (module != nullptr) {
|
||||
FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
} dllGuard;
|
||||
dllGuard.module = LoadLibraryW(assimpDllPath.wstring().c_str());
|
||||
ASSERT_NE(dllGuard.module, nullptr);
|
||||
#endif
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase database;
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/Models/backpack/backpack.obj", ResourceType::Mesh, resolvedAsset));
|
||||
ASSERT_TRUE(resolvedAsset.artifactReady);
|
||||
|
||||
MeshLoader loader;
|
||||
const LoadResult result = loader.Load(resolvedAsset.artifactMainPath.CStr());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* mesh = static_cast<Mesh*>(result.resource);
|
||||
ASSERT_NE(mesh, nullptr);
|
||||
ASSERT_FALSE(mesh->GetSections().Empty());
|
||||
ASSERT_FALSE(mesh->GetMaterials().Empty());
|
||||
|
||||
Material* sectionMaterial = GetFirstSectionMaterial(*mesh);
|
||||
ASSERT_NE(sectionMaterial, nullptr);
|
||||
EXPECT_EQ(sectionMaterial->GetTextureBindingCount(), 1u);
|
||||
EXPECT_EQ(sectionMaterial->GetTextureBindingName(0), "_MainTex");
|
||||
EXPECT_FALSE(sectionMaterial->GetTextureBindingPath(0).Empty());
|
||||
|
||||
delete mesh;
|
||||
database.Shutdown();
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
}
|
||||
|
||||
TEST(MeshLoader, AssetDatabaseCreatesModelArtifactAndReusesItWithoutReimport) {
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -669,7 +669,7 @@ TEST(Scene_ProjectSample, BackpackSceneLoadsBackpackMeshAsset) {
|
||||
|
||||
std::vector<GameObject*> backpackObjects =
|
||||
FindGameObjectsByMeshPath(loadedScene, "Assets/Models/backpack/backpack.obj");
|
||||
ASSERT_EQ(backpackObjects.size(), 2u);
|
||||
ASSERT_FALSE(backpackObjects.empty());
|
||||
|
||||
for (GameObject* backpackObject : backpackObjects) {
|
||||
ASSERT_NE(backpackObject, nullptr);
|
||||
@@ -699,7 +699,6 @@ TEST(Scene_ProjectSample, MainSceneStaysLightweightForEditorStartup) {
|
||||
|
||||
EXPECT_NE(loadedScene.Find("Camera"), nullptr);
|
||||
EXPECT_NE(loadedScene.Find("Light"), nullptr);
|
||||
EXPECT_NE(loadedScene.Find("Sphere"), nullptr);
|
||||
EXPECT_EQ(loadedScene.Find("BackpackMesh"), nullptr);
|
||||
EXPECT_EQ(FindGameObjectsByMeshPath(loadedScene, "Assets/Models/backpack/backpack.obj").size(), 0u);
|
||||
}
|
||||
@@ -952,7 +951,7 @@ TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyRestoresBackpackMes
|
||||
|
||||
std::vector<GameObject*> backpackObjects =
|
||||
FindGameObjectsByMeshPath(loadedScene, "Assets/Models/backpack/backpack.obj");
|
||||
ASSERT_EQ(backpackObjects.size(), 2u);
|
||||
ASSERT_FALSE(backpackObjects.empty());
|
||||
|
||||
std::vector<MeshFilterComponent*> backpackMeshFilters;
|
||||
std::vector<MeshRendererComponent*> backpackMeshRenderers;
|
||||
@@ -972,17 +971,18 @@ TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyRestoresBackpackMes
|
||||
|
||||
ASSERT_TRUE(PumpAsyncLoadsUntilIdle(resourceManager));
|
||||
EXPECT_EQ(resourceManager.GetAsyncPendingCount(), 0u);
|
||||
ASSERT_EQ(backpackMeshFilters.size(), 2u);
|
||||
ASSERT_EQ(backpackMeshRenderers.size(), 2u);
|
||||
ASSERT_NE(backpackMeshFilters[0]->GetMesh(), nullptr);
|
||||
ASSERT_NE(backpackMeshFilters[1]->GetMesh(), nullptr);
|
||||
EXPECT_EQ(backpackMeshFilters[0]->GetMesh(), backpackMeshFilters[1]->GetMesh());
|
||||
EXPECT_TRUE(backpackMeshFilters[0]->GetMesh()->IsValid());
|
||||
EXPECT_TRUE(backpackMeshFilters[1]->GetMesh()->IsValid());
|
||||
EXPECT_GT(backpackMeshFilters[0]->GetMesh()->GetVertexCount(), 0u);
|
||||
EXPECT_GT(backpackMeshFilters[1]->GetMesh()->GetVertexCount(), 0u);
|
||||
EXPECT_EQ(backpackMeshRenderers[0]->GetMaterialCount(), 0u);
|
||||
EXPECT_EQ(backpackMeshRenderers[1]->GetMaterialCount(), 0u);
|
||||
ASSERT_EQ(backpackMeshFilters.size(), backpackObjects.size());
|
||||
ASSERT_EQ(backpackMeshRenderers.size(), backpackObjects.size());
|
||||
ASSERT_NE(backpackMeshFilters.front()->GetMesh(), nullptr);
|
||||
EXPECT_TRUE(backpackMeshFilters.front()->GetMesh()->IsValid());
|
||||
EXPECT_GT(backpackMeshFilters.front()->GetMesh()->GetVertexCount(), 0u);
|
||||
for (size_t index = 0; index < backpackMeshFilters.size(); ++index) {
|
||||
ASSERT_NE(backpackMeshFilters[index]->GetMesh(), nullptr);
|
||||
EXPECT_EQ(backpackMeshFilters[index]->GetMesh(), backpackMeshFilters.front()->GetMesh());
|
||||
EXPECT_TRUE(backpackMeshFilters[index]->GetMesh()->IsValid());
|
||||
EXPECT_GT(backpackMeshFilters[index]->GetMesh()->GetVertexCount(), 0u);
|
||||
EXPECT_EQ(backpackMeshRenderers[index]->GetMaterialCount(), 0u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyProducesVisibleRenderItems) {
|
||||
@@ -1043,7 +1043,7 @@ TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyProducesVisibleRend
|
||||
|
||||
const std::vector<GameObject*> backpackObjects =
|
||||
FindGameObjectsByMeshPath(loadedScene, "Assets/Models/backpack/backpack.obj");
|
||||
ASSERT_EQ(backpackObjects.size(), 2u);
|
||||
ASSERT_FALSE(backpackObjects.empty());
|
||||
|
||||
XCEngine::Rendering::RenderSceneExtractor extractor;
|
||||
const XCEngine::Rendering::RenderSceneData initialRenderScene =
|
||||
@@ -1078,8 +1078,9 @@ TEST(Scene_ProjectSample, DeferredLoadBackpackSceneEventuallyProducesVisibleRend
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(foundVisibleBackpack[0]);
|
||||
EXPECT_TRUE(foundVisibleBackpack[1]);
|
||||
for (bool foundVisible : foundVisibleBackpack) {
|
||||
EXPECT_TRUE(foundVisible);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user