935 lines
34 KiB
C++
935 lines
34 KiB
C++
#pragma once
|
|
|
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
|
#include <XCEngine/Core/Types.h>
|
|
#include <XCEngine/RHI/RHITypes.h>
|
|
#include <XCEngine/Resources/Material/Material.h>
|
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
|
#include <XCEngine/Rendering/VisibleRenderObject.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
namespace XCEngine {
|
|
namespace Rendering {
|
|
|
|
enum class BuiltinMaterialPass : Core::uint32 {
|
|
ForwardLit = 0,
|
|
Unlit,
|
|
DepthOnly,
|
|
ShadowCaster,
|
|
ObjectId,
|
|
Forward = ForwardLit
|
|
};
|
|
|
|
struct PassResourceBindingLocation {
|
|
Core::uint32 set = UINT32_MAX;
|
|
Core::uint32 binding = UINT32_MAX;
|
|
|
|
bool IsValid() const {
|
|
return set != UINT32_MAX && binding != UINT32_MAX;
|
|
}
|
|
};
|
|
|
|
enum class BuiltinPassResourceSemantic : Core::uint8 {
|
|
Unknown = 0,
|
|
PerObject,
|
|
Material,
|
|
Lighting,
|
|
ShadowReceiver,
|
|
BaseColorTexture,
|
|
ShadowMapTexture,
|
|
LinearClampSampler,
|
|
ShadowMapSampler
|
|
};
|
|
|
|
struct BuiltinPassResourceBindingDesc {
|
|
BuiltinPassResourceSemantic semantic = BuiltinPassResourceSemantic::Unknown;
|
|
Resources::ShaderResourceType resourceType = Resources::ShaderResourceType::ConstantBuffer;
|
|
PassResourceBindingLocation location = {};
|
|
};
|
|
|
|
struct BuiltinPassResourceBindingPlan {
|
|
Containers::Array<BuiltinPassResourceBindingDesc> bindings;
|
|
Core::uint32 maxSetIndex = 0;
|
|
Core::uint32 firstDescriptorSet = 0;
|
|
Core::uint32 descriptorSetCount = 0;
|
|
bool usesConstantBuffers = false;
|
|
bool usesTextures = false;
|
|
bool usesSamplers = false;
|
|
PassResourceBindingLocation perObject = {};
|
|
PassResourceBindingLocation material = {};
|
|
PassResourceBindingLocation lighting = {};
|
|
PassResourceBindingLocation shadowReceiver = {};
|
|
PassResourceBindingLocation baseColorTexture = {};
|
|
PassResourceBindingLocation linearClampSampler = {};
|
|
PassResourceBindingLocation shadowMapTexture = {};
|
|
PassResourceBindingLocation shadowMapSampler = {};
|
|
|
|
const BuiltinPassResourceBindingDesc* FindBinding(BuiltinPassResourceSemantic semantic) const {
|
|
for (const BuiltinPassResourceBindingDesc& binding : bindings) {
|
|
if (binding.semantic == semantic) {
|
|
return &binding;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
struct BuiltinPassSetLayoutMetadata {
|
|
std::vector<RHI::DescriptorSetLayoutBinding> bindings;
|
|
RHI::DescriptorSetLayoutDesc layout = {};
|
|
RHI::DescriptorHeapType heapType = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
|
bool shaderVisible = false;
|
|
bool usesPerObject = false;
|
|
bool usesMaterial = false;
|
|
bool usesLighting = false;
|
|
bool usesShadowReceiver = false;
|
|
bool usesTexture = false;
|
|
bool usesBaseColorTexture = false;
|
|
bool usesShadowMapTexture = false;
|
|
bool usesSampler = false;
|
|
bool usesLinearClampSampler = false;
|
|
bool usesShadowMapSampler = false;
|
|
};
|
|
|
|
inline Containers::String NormalizeBuiltinPassMetadataValue(const Containers::String& value) {
|
|
return value.Trim().ToLower();
|
|
}
|
|
|
|
inline bool IsForwardPassName(const Containers::String& value) {
|
|
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
|
|
return normalized.Empty() ||
|
|
normalized == Containers::String("forward") ||
|
|
normalized == Containers::String("forwardbase") ||
|
|
normalized == Containers::String("forwardlit") ||
|
|
normalized == Containers::String("forwardonly");
|
|
}
|
|
|
|
inline bool IsUnlitPassName(const Containers::String& value) {
|
|
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
|
|
return normalized == Containers::String("unlit") ||
|
|
normalized == Containers::String("forwardunlit") ||
|
|
normalized == Containers::String("srpdefaultunlit");
|
|
}
|
|
|
|
inline bool IsDepthOnlyPassName(const Containers::String& value) {
|
|
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
|
|
return normalized == Containers::String("depthonly") ||
|
|
normalized == Containers::String("depth");
|
|
}
|
|
|
|
inline bool IsShadowCasterPassName(const Containers::String& value) {
|
|
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
|
|
return normalized == Containers::String("shadowcaster") ||
|
|
normalized == Containers::String("shadow");
|
|
}
|
|
|
|
inline bool IsObjectIdPassName(const Containers::String& value) {
|
|
const Containers::String normalized = value.Trim().ToLower();
|
|
return normalized == Containers::String("objectid") ||
|
|
normalized == Containers::String("editorobjectid");
|
|
}
|
|
|
|
inline bool MatchesBuiltinPassName(
|
|
const Containers::String& value,
|
|
BuiltinMaterialPass pass) {
|
|
switch (pass) {
|
|
case BuiltinMaterialPass::ForwardLit:
|
|
return IsForwardPassName(value);
|
|
case BuiltinMaterialPass::Unlit:
|
|
return IsUnlitPassName(value);
|
|
case BuiltinMaterialPass::DepthOnly:
|
|
return IsDepthOnlyPassName(value);
|
|
case BuiltinMaterialPass::ShadowCaster:
|
|
return IsShadowCasterPassName(value);
|
|
case BuiltinMaterialPass::ObjectId:
|
|
return IsObjectIdPassName(value);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline bool ShaderPassHasExplicitBuiltinMetadata(const Resources::ShaderPass& shaderPass) {
|
|
if (!shaderPass.name.Empty() &&
|
|
shaderPass.name != Containers::String("Default")) {
|
|
return true;
|
|
}
|
|
|
|
for (const Resources::ShaderPassTagEntry& tag : shaderPass.tags) {
|
|
if (NormalizeBuiltinPassMetadataValue(tag.name) == Containers::String("lightmode")) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline bool ShaderPassMatchesBuiltinPass(
|
|
const Resources::ShaderPass& shaderPass,
|
|
BuiltinMaterialPass pass) {
|
|
bool hasMetadata = false;
|
|
|
|
if (!shaderPass.name.Empty() &&
|
|
shaderPass.name != Containers::String("Default")) {
|
|
hasMetadata = true;
|
|
if (!MatchesBuiltinPassName(shaderPass.name, pass)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (const Resources::ShaderPassTagEntry& tag : shaderPass.tags) {
|
|
if (NormalizeBuiltinPassMetadataValue(tag.name) != Containers::String("lightmode")) {
|
|
continue;
|
|
}
|
|
|
|
hasMetadata = true;
|
|
if (!MatchesBuiltinPassName(tag.value, pass)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return hasMetadata;
|
|
}
|
|
|
|
inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
|
|
const Resources::ShaderResourceBindingDesc& binding) {
|
|
Containers::String semantic = NormalizeBuiltinPassMetadataValue(binding.semantic);
|
|
if (semantic.Empty()) {
|
|
semantic = NormalizeBuiltinPassMetadataValue(binding.name);
|
|
}
|
|
|
|
if (semantic == Containers::String("perobject") ||
|
|
semantic == Containers::String("perobjectconstants")) {
|
|
return BuiltinPassResourceSemantic::PerObject;
|
|
}
|
|
|
|
if (semantic == Containers::String("material") ||
|
|
semantic == Containers::String("materialconstants")) {
|
|
return BuiltinPassResourceSemantic::Material;
|
|
}
|
|
|
|
if (semantic == Containers::String("lighting") ||
|
|
semantic == Containers::String("lightingconstants")) {
|
|
return BuiltinPassResourceSemantic::Lighting;
|
|
}
|
|
|
|
if (semantic == Containers::String("shadowreceiver") ||
|
|
semantic == Containers::String("shadowreceiverconstants")) {
|
|
return BuiltinPassResourceSemantic::ShadowReceiver;
|
|
}
|
|
|
|
if (semantic == Containers::String("basecolortexture") ||
|
|
semantic == Containers::String("maintex")) {
|
|
return BuiltinPassResourceSemantic::BaseColorTexture;
|
|
}
|
|
|
|
if (semantic == Containers::String("shadowmaptexture") ||
|
|
semantic == Containers::String("shadowmap")) {
|
|
return BuiltinPassResourceSemantic::ShadowMapTexture;
|
|
}
|
|
|
|
if (semantic == Containers::String("linearclampsampler")) {
|
|
return BuiltinPassResourceSemantic::LinearClampSampler;
|
|
}
|
|
|
|
if (semantic == Containers::String("shadowmapsampler") ||
|
|
semantic == Containers::String("shadowsampler")) {
|
|
return BuiltinPassResourceSemantic::ShadowMapSampler;
|
|
}
|
|
|
|
return BuiltinPassResourceSemantic::Unknown;
|
|
}
|
|
|
|
inline bool IsBuiltinPassResourceTypeCompatible(
|
|
BuiltinPassResourceSemantic semantic,
|
|
Resources::ShaderResourceType type) {
|
|
switch (semantic) {
|
|
case BuiltinPassResourceSemantic::PerObject:
|
|
case BuiltinPassResourceSemantic::Material:
|
|
case BuiltinPassResourceSemantic::Lighting:
|
|
case BuiltinPassResourceSemantic::ShadowReceiver:
|
|
return type == Resources::ShaderResourceType::ConstantBuffer;
|
|
case BuiltinPassResourceSemantic::BaseColorTexture:
|
|
case BuiltinPassResourceSemantic::ShadowMapTexture:
|
|
return type == Resources::ShaderResourceType::Texture2D ||
|
|
type == Resources::ShaderResourceType::TextureCube;
|
|
case BuiltinPassResourceSemantic::LinearClampSampler:
|
|
case BuiltinPassResourceSemantic::ShadowMapSampler:
|
|
return type == Resources::ShaderResourceType::Sampler;
|
|
case BuiltinPassResourceSemantic::Unknown:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline bool TryBuildBuiltinPassResourceBindingPlan(
|
|
const Containers::Array<Resources::ShaderResourceBindingDesc>& bindings,
|
|
BuiltinPassResourceBindingPlan& outPlan,
|
|
Containers::String* outError = nullptr) {
|
|
outPlan = {};
|
|
|
|
auto fail = [&outError](const char* message) {
|
|
if (outError != nullptr) {
|
|
*outError = message;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (bindings.Empty()) {
|
|
return true;
|
|
}
|
|
|
|
outPlan.bindings.Reserve(bindings.Size());
|
|
Core::uint32 minBoundSet = UINT32_MAX;
|
|
Core::uint32 maxBoundSet = 0;
|
|
|
|
for (const Resources::ShaderResourceBindingDesc& binding : bindings) {
|
|
const BuiltinPassResourceSemantic semantic = ResolveBuiltinPassResourceSemantic(binding);
|
|
if (semantic == BuiltinPassResourceSemantic::Unknown) {
|
|
return fail("Unsupported builtin pass resource semantic");
|
|
}
|
|
if (!IsBuiltinPassResourceTypeCompatible(semantic, binding.type)) {
|
|
return fail("Builtin pass resource semantic/type combination is invalid");
|
|
}
|
|
|
|
PassResourceBindingLocation* location = nullptr;
|
|
switch (semantic) {
|
|
case BuiltinPassResourceSemantic::PerObject:
|
|
location = &outPlan.perObject;
|
|
break;
|
|
case BuiltinPassResourceSemantic::Material:
|
|
location = &outPlan.material;
|
|
break;
|
|
case BuiltinPassResourceSemantic::Lighting:
|
|
location = &outPlan.lighting;
|
|
break;
|
|
case BuiltinPassResourceSemantic::ShadowReceiver:
|
|
location = &outPlan.shadowReceiver;
|
|
break;
|
|
case BuiltinPassResourceSemantic::BaseColorTexture:
|
|
location = &outPlan.baseColorTexture;
|
|
break;
|
|
case BuiltinPassResourceSemantic::LinearClampSampler:
|
|
location = &outPlan.linearClampSampler;
|
|
break;
|
|
case BuiltinPassResourceSemantic::ShadowMapTexture:
|
|
location = &outPlan.shadowMapTexture;
|
|
break;
|
|
case BuiltinPassResourceSemantic::ShadowMapSampler:
|
|
location = &outPlan.shadowMapSampler;
|
|
break;
|
|
case BuiltinPassResourceSemantic::Unknown:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (location == nullptr) {
|
|
return fail("Builtin pass resource semantic could not be mapped");
|
|
}
|
|
if (location->IsValid()) {
|
|
return fail("Builtin pass resource semantic appears more than once");
|
|
}
|
|
|
|
for (const BuiltinPassResourceBindingDesc& existingBinding : outPlan.bindings) {
|
|
if (existingBinding.location.set == binding.set &&
|
|
existingBinding.location.binding == binding.binding) {
|
|
return fail("Builtin pass resource set/binding pair appears more than once");
|
|
}
|
|
}
|
|
|
|
*location = { binding.set, binding.binding };
|
|
|
|
BuiltinPassResourceBindingDesc resolvedBinding = {};
|
|
resolvedBinding.semantic = semantic;
|
|
resolvedBinding.resourceType = binding.type;
|
|
resolvedBinding.location = *location;
|
|
outPlan.bindings.PushBack(resolvedBinding);
|
|
|
|
outPlan.maxSetIndex = std::max(outPlan.maxSetIndex, binding.set);
|
|
minBoundSet = std::min(minBoundSet, binding.set);
|
|
maxBoundSet = std::max(maxBoundSet, binding.set);
|
|
|
|
switch (binding.type) {
|
|
case Resources::ShaderResourceType::ConstantBuffer:
|
|
outPlan.usesConstantBuffers = true;
|
|
break;
|
|
case Resources::ShaderResourceType::Texture2D:
|
|
case Resources::ShaderResourceType::TextureCube:
|
|
outPlan.usesTextures = true;
|
|
break;
|
|
case Resources::ShaderResourceType::Sampler:
|
|
outPlan.usesSamplers = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
outPlan.firstDescriptorSet = minBoundSet;
|
|
outPlan.descriptorSetCount = maxBoundSet - minBoundSet + 1u;
|
|
return true;
|
|
}
|
|
|
|
inline RHI::DescriptorType ToBuiltinPassDescriptorType(Resources::ShaderResourceType type) {
|
|
switch (type) {
|
|
case Resources::ShaderResourceType::ConstantBuffer:
|
|
return RHI::DescriptorType::CBV;
|
|
case Resources::ShaderResourceType::Texture2D:
|
|
case Resources::ShaderResourceType::TextureCube:
|
|
return RHI::DescriptorType::SRV;
|
|
case Resources::ShaderResourceType::Sampler:
|
|
return RHI::DescriptorType::Sampler;
|
|
default:
|
|
return RHI::DescriptorType::CBV;
|
|
}
|
|
}
|
|
|
|
inline RHI::DescriptorHeapType ResolveBuiltinPassDescriptorHeapType(Resources::ShaderResourceType type) {
|
|
return type == Resources::ShaderResourceType::Sampler
|
|
? RHI::DescriptorHeapType::Sampler
|
|
: RHI::DescriptorHeapType::CBV_SRV_UAV;
|
|
}
|
|
|
|
inline bool IsBuiltinPassShaderVisibleSet(const std::vector<RHI::DescriptorSetLayoutBinding>& bindings) {
|
|
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
|
|
const RHI::DescriptorType descriptorType = static_cast<RHI::DescriptorType>(binding.type);
|
|
if (descriptorType == RHI::DescriptorType::SRV ||
|
|
descriptorType == RHI::DescriptorType::UAV ||
|
|
descriptorType == RHI::DescriptorType::Sampler) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline Core::uint32 CountBuiltinPassHeapDescriptors(
|
|
RHI::DescriptorHeapType heapType,
|
|
const std::vector<RHI::DescriptorSetLayoutBinding>& bindings) {
|
|
Core::uint32 descriptorCount = 0;
|
|
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
|
|
const RHI::DescriptorType descriptorType = static_cast<RHI::DescriptorType>(binding.type);
|
|
if (heapType == RHI::DescriptorHeapType::Sampler) {
|
|
if (descriptorType == RHI::DescriptorType::Sampler) {
|
|
descriptorCount += binding.count > 0 ? binding.count : 1u;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (descriptorType != RHI::DescriptorType::Sampler) {
|
|
descriptorCount += binding.count > 0 ? binding.count : 1u;
|
|
}
|
|
}
|
|
|
|
return descriptorCount > 0 ? descriptorCount : 1u;
|
|
}
|
|
|
|
inline void RefreshBuiltinPassSetLayoutMetadata(BuiltinPassSetLayoutMetadata& setLayout) {
|
|
std::sort(
|
|
setLayout.bindings.begin(),
|
|
setLayout.bindings.end(),
|
|
[](const RHI::DescriptorSetLayoutBinding& left, const RHI::DescriptorSetLayoutBinding& right) {
|
|
return left.binding < right.binding;
|
|
});
|
|
setLayout.shaderVisible = IsBuiltinPassShaderVisibleSet(setLayout.bindings);
|
|
setLayout.layout.bindings = setLayout.bindings.empty() ? nullptr : setLayout.bindings.data();
|
|
setLayout.layout.bindingCount = static_cast<Core::uint32>(setLayout.bindings.size());
|
|
}
|
|
|
|
inline void RefreshBuiltinPassSetLayouts(std::vector<BuiltinPassSetLayoutMetadata>& setLayouts) {
|
|
for (BuiltinPassSetLayoutMetadata& setLayout : setLayouts) {
|
|
RefreshBuiltinPassSetLayoutMetadata(setLayout);
|
|
}
|
|
}
|
|
|
|
inline bool TryBuildBuiltinPassSetLayouts(
|
|
const BuiltinPassResourceBindingPlan& bindingPlan,
|
|
std::vector<BuiltinPassSetLayoutMetadata>& outSetLayouts,
|
|
Containers::String* outError = nullptr) {
|
|
outSetLayouts.clear();
|
|
|
|
auto fail = [&outError](const char* message) {
|
|
if (outError != nullptr) {
|
|
*outError = message;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (bindingPlan.bindings.Empty()) {
|
|
return true;
|
|
}
|
|
|
|
outSetLayouts.resize(static_cast<size_t>(bindingPlan.maxSetIndex) + 1u);
|
|
for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) {
|
|
if (binding.location.set >= outSetLayouts.size()) {
|
|
return fail("Builtin pass encountered an invalid descriptor set index");
|
|
}
|
|
|
|
const RHI::DescriptorType descriptorType = ToBuiltinPassDescriptorType(binding.resourceType);
|
|
const RHI::DescriptorHeapType heapType = ResolveBuiltinPassDescriptorHeapType(binding.resourceType);
|
|
BuiltinPassSetLayoutMetadata& setLayout = outSetLayouts[binding.location.set];
|
|
|
|
if (!setLayout.bindings.empty() && setLayout.heapType != heapType) {
|
|
return fail("Builtin pass does not support mixing sampler and non-sampler bindings in one set");
|
|
}
|
|
|
|
for (const RHI::DescriptorSetLayoutBinding& existingBinding : setLayout.bindings) {
|
|
if (existingBinding.binding == binding.location.binding) {
|
|
return fail("Builtin pass encountered duplicate bindings inside one descriptor set");
|
|
}
|
|
}
|
|
|
|
if (setLayout.bindings.empty()) {
|
|
setLayout.heapType = heapType;
|
|
}
|
|
|
|
RHI::DescriptorSetLayoutBinding layoutBinding = {};
|
|
layoutBinding.binding = binding.location.binding;
|
|
layoutBinding.type = static_cast<Core::uint32>(descriptorType);
|
|
layoutBinding.count = 1;
|
|
setLayout.bindings.push_back(layoutBinding);
|
|
|
|
switch (binding.semantic) {
|
|
case BuiltinPassResourceSemantic::PerObject:
|
|
setLayout.usesPerObject = true;
|
|
break;
|
|
case BuiltinPassResourceSemantic::Material:
|
|
setLayout.usesMaterial = true;
|
|
break;
|
|
case BuiltinPassResourceSemantic::Lighting:
|
|
setLayout.usesLighting = true;
|
|
break;
|
|
case BuiltinPassResourceSemantic::ShadowReceiver:
|
|
setLayout.usesShadowReceiver = true;
|
|
break;
|
|
case BuiltinPassResourceSemantic::BaseColorTexture:
|
|
setLayout.usesTexture = true;
|
|
setLayout.usesBaseColorTexture = true;
|
|
break;
|
|
case BuiltinPassResourceSemantic::ShadowMapTexture:
|
|
setLayout.usesTexture = true;
|
|
setLayout.usesShadowMapTexture = true;
|
|
break;
|
|
case BuiltinPassResourceSemantic::LinearClampSampler:
|
|
setLayout.usesSampler = true;
|
|
setLayout.usesLinearClampSampler = true;
|
|
break;
|
|
case BuiltinPassResourceSemantic::ShadowMapSampler:
|
|
setLayout.usesSampler = true;
|
|
setLayout.usesShadowMapSampler = true;
|
|
break;
|
|
case BuiltinPassResourceSemantic::Unknown:
|
|
default:
|
|
return fail("Builtin pass encountered an unsupported resource semantic");
|
|
}
|
|
}
|
|
|
|
RefreshBuiltinPassSetLayouts(outSetLayouts);
|
|
return true;
|
|
}
|
|
|
|
struct BuiltinForwardMaterialData {
|
|
Math::Vector4 baseColorFactor = Math::Vector4::One();
|
|
};
|
|
|
|
struct MaterialConstantLayoutView {
|
|
const Resources::MaterialConstantFieldDesc* fields = nullptr;
|
|
size_t count = 0;
|
|
size_t size = 0;
|
|
|
|
bool IsValid() const {
|
|
return fields != nullptr && count > 0 && size > 0;
|
|
}
|
|
};
|
|
|
|
struct MaterialConstantPayloadView {
|
|
const void* data = nullptr;
|
|
size_t size = 0;
|
|
MaterialConstantLayoutView layout = {};
|
|
|
|
bool IsValid() const {
|
|
return data != nullptr && size > 0 && layout.IsValid() && layout.size == size;
|
|
}
|
|
};
|
|
|
|
inline const Resources::ShaderPropertyDesc* FindShaderPropertyBySemantic(
|
|
const Resources::Material* material,
|
|
const Containers::String& semantic) {
|
|
if (material == nullptr || material->GetShader() == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
const Containers::String normalizedSemantic = NormalizeBuiltinPassMetadataValue(semantic);
|
|
for (const Resources::ShaderPropertyDesc& property : material->GetShader()->GetProperties()) {
|
|
if (NormalizeBuiltinPassMetadataValue(property.semantic) == normalizedSemantic) {
|
|
return &property;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
inline Math::Vector4 ResolveBuiltinBaseColorFactor(const Resources::Material* material) {
|
|
if (material == nullptr) {
|
|
return Math::Vector4::One();
|
|
}
|
|
|
|
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "BaseColor")) {
|
|
if (material->HasProperty(property->name) &&
|
|
(property->type == Resources::ShaderPropertyType::Color ||
|
|
property->type == Resources::ShaderPropertyType::Vector)) {
|
|
return material->GetFloat4(property->name);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources::Material* material) {
|
|
if (material == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "BaseColorTexture")) {
|
|
const Resources::ResourceHandle<Resources::Texture> textureHandle = material->GetTexture(property->name);
|
|
if (textureHandle.Get() != nullptr && textureHandle->IsValid()) {
|
|
return textureHandle.Get();
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
inline BuiltinForwardMaterialData BuildBuiltinForwardMaterialData(const Resources::Material* material) {
|
|
BuiltinForwardMaterialData data = {};
|
|
data.baseColorFactor = ResolveBuiltinBaseColorFactor(material);
|
|
return data;
|
|
}
|
|
|
|
inline MaterialConstantPayloadView ResolveSchemaMaterialConstantPayload(const Resources::Material* material) {
|
|
if (material == nullptr || material->GetShader() == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
const Containers::Array<Resources::MaterialConstantFieldDesc>& constantLayout = material->GetConstantLayout();
|
|
const Containers::Array<Core::uint8>& constantBufferData = material->GetConstantBufferData();
|
|
if (constantLayout.Empty() || constantBufferData.Empty()) {
|
|
return {};
|
|
}
|
|
|
|
MaterialConstantLayoutView layoutView = {};
|
|
layoutView.fields = constantLayout.Data();
|
|
layoutView.count = constantLayout.Size();
|
|
layoutView.size = constantBufferData.Size();
|
|
|
|
return { constantBufferData.Data(), constantBufferData.Size(), layoutView };
|
|
}
|
|
|
|
inline const Resources::Material* ResolveMaterial(
|
|
const Components::MeshRendererComponent* meshRenderer,
|
|
const Resources::Mesh* mesh,
|
|
Core::uint32 materialIndex) {
|
|
if (meshRenderer != nullptr && materialIndex < meshRenderer->GetMaterialCount()) {
|
|
if (const Resources::Material* material = meshRenderer->GetMaterial(materialIndex)) {
|
|
return material;
|
|
}
|
|
}
|
|
|
|
if (mesh != nullptr && materialIndex < mesh->GetMaterials().Size()) {
|
|
if (const Resources::Material* material = mesh->GetMaterials()[materialIndex]) {
|
|
return material;
|
|
}
|
|
}
|
|
|
|
if (meshRenderer != nullptr && meshRenderer->GetMaterialCount() > 0) {
|
|
if (const Resources::Material* material = meshRenderer->GetMaterial(0)) {
|
|
return material;
|
|
}
|
|
}
|
|
|
|
if (mesh != nullptr && mesh->GetMaterials().Size() > 0) {
|
|
return mesh->GetMaterials()[0];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
inline const Resources::Material* ResolveMaterial(const VisibleRenderItem& visibleItem) {
|
|
if (visibleItem.material != nullptr) {
|
|
return visibleItem.material;
|
|
}
|
|
|
|
return ResolveMaterial(visibleItem.meshRenderer, visibleItem.mesh, visibleItem.materialIndex);
|
|
}
|
|
|
|
inline Core::int32 ResolveMaterialRenderQueue(const Resources::Material* material) {
|
|
return material != nullptr
|
|
? material->GetRenderQueue()
|
|
: static_cast<Core::int32>(Resources::MaterialRenderQueue::Geometry);
|
|
}
|
|
|
|
inline bool IsTransparentRenderQueue(Core::int32 renderQueue) {
|
|
return renderQueue >= static_cast<Core::int32>(Resources::MaterialRenderQueue::Transparent);
|
|
}
|
|
|
|
inline bool MatchesBuiltinPass(const Resources::Material* material, BuiltinMaterialPass pass) {
|
|
if (material == nullptr) {
|
|
return pass == BuiltinMaterialPass::ForwardLit;
|
|
}
|
|
|
|
const Containers::String shaderPass = material->GetShaderPass();
|
|
const Containers::String lightMode = material->GetTag("LightMode");
|
|
const bool hasMaterialShaderPass = !NormalizeBuiltinPassMetadataValue(shaderPass).Empty();
|
|
const bool hasMaterialLightMode = !NormalizeBuiltinPassMetadataValue(lightMode).Empty();
|
|
if (hasMaterialShaderPass || hasMaterialLightMode) {
|
|
if (hasMaterialShaderPass &&
|
|
!MatchesBuiltinPassName(shaderPass, pass)) {
|
|
return false;
|
|
}
|
|
if (hasMaterialLightMode &&
|
|
!MatchesBuiltinPassName(lightMode, pass)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const Resources::Shader* shader = material->GetShader();
|
|
if (shader != nullptr) {
|
|
bool shaderHasExplicitBuiltinMetadata = false;
|
|
for (const Resources::ShaderPass& shaderPassEntry : shader->GetPasses()) {
|
|
if (ShaderPassMatchesBuiltinPass(shaderPassEntry, pass)) {
|
|
return true;
|
|
}
|
|
|
|
if (ShaderPassHasExplicitBuiltinMetadata(shaderPassEntry)) {
|
|
shaderHasExplicitBuiltinMetadata = true;
|
|
}
|
|
}
|
|
|
|
if (shaderHasExplicitBuiltinMetadata) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return pass == BuiltinMaterialPass::ForwardLit;
|
|
}
|
|
|
|
inline RHI::CullMode ToRHICullMode(Resources::MaterialCullMode mode) {
|
|
switch (mode) {
|
|
case Resources::MaterialCullMode::Front:
|
|
return RHI::CullMode::Front;
|
|
case Resources::MaterialCullMode::Back:
|
|
return RHI::CullMode::Back;
|
|
case Resources::MaterialCullMode::None:
|
|
default:
|
|
return RHI::CullMode::None;
|
|
}
|
|
}
|
|
|
|
inline RHI::ComparisonFunc ToRHIComparisonFunc(Resources::MaterialComparisonFunc func) {
|
|
switch (func) {
|
|
case Resources::MaterialComparisonFunc::Never:
|
|
return RHI::ComparisonFunc::Never;
|
|
case Resources::MaterialComparisonFunc::Equal:
|
|
return RHI::ComparisonFunc::Equal;
|
|
case Resources::MaterialComparisonFunc::LessEqual:
|
|
return RHI::ComparisonFunc::LessEqual;
|
|
case Resources::MaterialComparisonFunc::Greater:
|
|
return RHI::ComparisonFunc::Greater;
|
|
case Resources::MaterialComparisonFunc::NotEqual:
|
|
return RHI::ComparisonFunc::NotEqual;
|
|
case Resources::MaterialComparisonFunc::GreaterEqual:
|
|
return RHI::ComparisonFunc::GreaterEqual;
|
|
case Resources::MaterialComparisonFunc::Always:
|
|
return RHI::ComparisonFunc::Always;
|
|
case Resources::MaterialComparisonFunc::Less:
|
|
default:
|
|
return RHI::ComparisonFunc::Less;
|
|
}
|
|
}
|
|
|
|
inline RHI::BlendFactor ToRHIBlendFactor(Resources::MaterialBlendFactor factor) {
|
|
switch (factor) {
|
|
case Resources::MaterialBlendFactor::Zero:
|
|
return RHI::BlendFactor::Zero;
|
|
case Resources::MaterialBlendFactor::SrcColor:
|
|
return RHI::BlendFactor::SrcColor;
|
|
case Resources::MaterialBlendFactor::InvSrcColor:
|
|
return RHI::BlendFactor::InvSrcColor;
|
|
case Resources::MaterialBlendFactor::SrcAlpha:
|
|
return RHI::BlendFactor::SrcAlpha;
|
|
case Resources::MaterialBlendFactor::InvSrcAlpha:
|
|
return RHI::BlendFactor::InvSrcAlpha;
|
|
case Resources::MaterialBlendFactor::DstAlpha:
|
|
return RHI::BlendFactor::DstAlpha;
|
|
case Resources::MaterialBlendFactor::InvDstAlpha:
|
|
return RHI::BlendFactor::InvDstAlpha;
|
|
case Resources::MaterialBlendFactor::DstColor:
|
|
return RHI::BlendFactor::DstColor;
|
|
case Resources::MaterialBlendFactor::InvDstColor:
|
|
return RHI::BlendFactor::InvDstColor;
|
|
case Resources::MaterialBlendFactor::SrcAlphaSat:
|
|
return RHI::BlendFactor::SrcAlphaSat;
|
|
case Resources::MaterialBlendFactor::BlendFactor:
|
|
return RHI::BlendFactor::BlendFactor;
|
|
case Resources::MaterialBlendFactor::InvBlendFactor:
|
|
return RHI::BlendFactor::InvBlendFactor;
|
|
case Resources::MaterialBlendFactor::Src1Color:
|
|
return RHI::BlendFactor::Src1Color;
|
|
case Resources::MaterialBlendFactor::InvSrc1Color:
|
|
return RHI::BlendFactor::InvSrc1Color;
|
|
case Resources::MaterialBlendFactor::Src1Alpha:
|
|
return RHI::BlendFactor::Src1Alpha;
|
|
case Resources::MaterialBlendFactor::InvSrc1Alpha:
|
|
return RHI::BlendFactor::InvSrc1Alpha;
|
|
case Resources::MaterialBlendFactor::One:
|
|
default:
|
|
return RHI::BlendFactor::One;
|
|
}
|
|
}
|
|
|
|
inline RHI::BlendOp ToRHIBlendOp(Resources::MaterialBlendOp op) {
|
|
switch (op) {
|
|
case Resources::MaterialBlendOp::Subtract:
|
|
return RHI::BlendOp::Subtract;
|
|
case Resources::MaterialBlendOp::ReverseSubtract:
|
|
return RHI::BlendOp::ReverseSubtract;
|
|
case Resources::MaterialBlendOp::Min:
|
|
return RHI::BlendOp::Min;
|
|
case Resources::MaterialBlendOp::Max:
|
|
return RHI::BlendOp::Max;
|
|
case Resources::MaterialBlendOp::Add:
|
|
default:
|
|
return RHI::BlendOp::Add;
|
|
}
|
|
}
|
|
|
|
inline RHI::RasterizerDesc BuildRasterizerState(const Resources::Material* material) {
|
|
RHI::RasterizerDesc desc = {};
|
|
desc.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
|
desc.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
|
desc.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
|
desc.depthClipEnable = true;
|
|
|
|
if (material != nullptr) {
|
|
const Resources::MaterialRenderState& renderState = material->GetRenderState();
|
|
desc.cullMode = static_cast<uint32_t>(ToRHICullMode(renderState.cullMode));
|
|
}
|
|
|
|
return desc;
|
|
}
|
|
|
|
inline RHI::BlendDesc BuildBlendState(const Resources::Material* material) {
|
|
RHI::BlendDesc desc = {};
|
|
if (material != nullptr) {
|
|
const Resources::MaterialRenderState& renderState = material->GetRenderState();
|
|
desc.blendEnable = renderState.blendEnable;
|
|
desc.srcBlend = static_cast<uint32_t>(ToRHIBlendFactor(renderState.srcBlend));
|
|
desc.dstBlend = static_cast<uint32_t>(ToRHIBlendFactor(renderState.dstBlend));
|
|
desc.srcBlendAlpha = static_cast<uint32_t>(ToRHIBlendFactor(renderState.srcBlendAlpha));
|
|
desc.dstBlendAlpha = static_cast<uint32_t>(ToRHIBlendFactor(renderState.dstBlendAlpha));
|
|
desc.blendOp = static_cast<uint32_t>(ToRHIBlendOp(renderState.blendOp));
|
|
desc.blendOpAlpha = static_cast<uint32_t>(ToRHIBlendOp(renderState.blendOpAlpha));
|
|
desc.colorWriteMask = renderState.colorWriteMask;
|
|
}
|
|
|
|
return desc;
|
|
}
|
|
|
|
inline RHI::DepthStencilStateDesc BuildDepthStencilState(const Resources::Material* material) {
|
|
RHI::DepthStencilStateDesc desc = {};
|
|
desc.depthTestEnable = true;
|
|
desc.depthWriteEnable = true;
|
|
desc.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Less);
|
|
desc.stencilEnable = false;
|
|
|
|
if (material != nullptr) {
|
|
const Resources::MaterialRenderState& renderState = material->GetRenderState();
|
|
desc.depthTestEnable = renderState.depthTestEnable;
|
|
desc.depthWriteEnable = renderState.depthWriteEnable;
|
|
desc.depthFunc = static_cast<uint32_t>(ToRHIComparisonFunc(renderState.depthFunc));
|
|
}
|
|
|
|
return desc;
|
|
}
|
|
|
|
inline void ApplyMaterialRenderState(const Resources::Material* material, RHI::GraphicsPipelineDesc& pipelineDesc) {
|
|
pipelineDesc.rasterizerState = BuildRasterizerState(material);
|
|
pipelineDesc.blendState = BuildBlendState(material);
|
|
pipelineDesc.depthStencilState = BuildDepthStencilState(material);
|
|
}
|
|
|
|
struct MaterialRenderStateHash {
|
|
size_t operator()(const Resources::MaterialRenderState& state) const noexcept {
|
|
size_t hash = 2166136261u;
|
|
auto combine = [&hash](size_t value) {
|
|
hash ^= value + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
|
};
|
|
|
|
combine(static_cast<size_t>(state.blendEnable));
|
|
combine(static_cast<size_t>(state.srcBlend));
|
|
combine(static_cast<size_t>(state.dstBlend));
|
|
combine(static_cast<size_t>(state.srcBlendAlpha));
|
|
combine(static_cast<size_t>(state.dstBlendAlpha));
|
|
combine(static_cast<size_t>(state.blendOp));
|
|
combine(static_cast<size_t>(state.blendOpAlpha));
|
|
combine(static_cast<size_t>(state.colorWriteMask));
|
|
combine(static_cast<size_t>(state.depthTestEnable));
|
|
combine(static_cast<size_t>(state.depthWriteEnable));
|
|
combine(static_cast<size_t>(state.depthFunc));
|
|
combine(static_cast<size_t>(state.cullMode));
|
|
return hash;
|
|
}
|
|
};
|
|
|
|
} // namespace Rendering
|
|
} // namespace XCEngine
|