Formalize renderer material contracts and harden backpack import
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user