rendering: add builtin alpha-test pass support

This commit is contained in:
2026-04-06 21:05:50 +08:00
parent eea38d57d1
commit 9568cf0a16
26 changed files with 856 additions and 104 deletions

View File

@@ -11,7 +11,9 @@
#include "Rendering/RenderSurface.h"
#include "Resources/Material/Material.h"
#include "Resources/Mesh/Mesh.h"
#include "Resources/Texture/Texture.h"
#include <algorithm>
#include <vector>
namespace XCEngine {
@@ -37,6 +39,40 @@ bool IsSupportedPerObjectOnlyBindingPlan(const BuiltinPassResourceBindingPlan& b
!bindingPlan.usesSamplers;
}
bool UsesContiguousDescriptorSets(const BuiltinPassResourceBindingPlan& bindingPlan) {
if (bindingPlan.bindings.Empty()) {
return true;
}
std::vector<bool> usedSets(static_cast<size_t>(bindingPlan.maxSetIndex) + 1u, false);
Core::uint32 minSet = UINT32_MAX;
Core::uint32 maxSet = 0;
for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) {
usedSets[binding.location.set] = true;
minSet = std::min(minSet, binding.location.set);
maxSet = std::max(maxSet, binding.location.set);
}
for (Core::uint32 setIndex = minSet; setIndex <= maxSet; ++setIndex) {
if (!usedSets[setIndex]) {
return false;
}
}
return true;
}
bool IsSupportedAlphaTestBindingPlan(const BuiltinPassResourceBindingPlan& bindingPlan) {
return bindingPlan.perObject.IsValid() &&
bindingPlan.material.IsValid() &&
bindingPlan.baseColorTexture.IsValid() &&
bindingPlan.linearClampSampler.IsValid() &&
!bindingPlan.lighting.IsValid() &&
!bindingPlan.shadowReceiver.IsValid() &&
!bindingPlan.shadowMapTexture.IsValid() &&
!bindingPlan.shadowMapSampler.IsValid();
}
uint32_t ResolveSurfaceColorAttachmentCount(const RenderSurface& surface) {
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
return (!colorAttachments.empty() && colorAttachments[0] != nullptr) ? 1u : 0u;
@@ -131,16 +167,66 @@ bool BuiltinDepthStylePassBase::CreateResources(const RenderContext& context) {
return false;
}
RHI::SamplerDesc samplerDesc = {};
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.addressV = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.addressW = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
samplerDesc.mipLodBias = 0.0f;
samplerDesc.maxAnisotropy = 1;
samplerDesc.comparisonFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
samplerDesc.minLod = 0.0f;
samplerDesc.maxLod = 1000.0f;
m_sampler = context.device->CreateSampler(samplerDesc);
if (m_sampler == nullptr) {
DestroyResources();
return false;
}
const unsigned char whitePixels2D[4] = {
255, 255, 255, 255
};
RHI::TextureDesc fallback2DDesc = {};
fallback2DDesc.width = 1;
fallback2DDesc.height = 1;
fallback2DDesc.depth = 1;
fallback2DDesc.mipLevels = 1;
fallback2DDesc.arraySize = 1;
fallback2DDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
fallback2DDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
fallback2DDesc.sampleCount = 1;
fallback2DDesc.sampleQuality = 0;
fallback2DDesc.flags = 0;
m_fallbackTexture2D = context.device->CreateTexture(
fallback2DDesc,
whitePixels2D,
sizeof(whitePixels2D),
sizeof(whitePixels2D));
if (m_fallbackTexture2D == nullptr) {
DestroyResources();
return false;
}
RHI::ResourceViewDesc fallback2DViewDesc = {};
fallback2DViewDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
fallback2DViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
fallback2DViewDesc.mipLevel = 0;
m_fallbackTexture2DView = context.device->CreateShaderResourceView(m_fallbackTexture2D, fallback2DViewDesc);
if (m_fallbackTexture2DView == nullptr) {
DestroyResources();
return false;
}
return true;
}
void BuiltinDepthStylePassBase::DestroyResources() {
m_resourceCache.Shutdown();
for (auto& descriptorSetEntry : m_perObjectSets) {
DestroyOwnedDescriptorSet(descriptorSetEntry.second);
for (auto& descriptorSetEntry : m_dynamicDescriptorSets) {
DestroyOwnedDescriptorSet(descriptorSetEntry.second.descriptorSet);
}
m_perObjectSets.clear();
m_dynamicDescriptorSets.clear();
for (auto& pipelineEntry : m_pipelineStates) {
if (pipelineEntry.second != nullptr) {
@@ -155,6 +241,24 @@ void BuiltinDepthStylePassBase::DestroyResources() {
}
m_passResourceLayouts.clear();
if (m_fallbackTexture2DView != nullptr) {
m_fallbackTexture2DView->Shutdown();
delete m_fallbackTexture2DView;
m_fallbackTexture2DView = nullptr;
}
if (m_fallbackTexture2D != nullptr) {
m_fallbackTexture2D->Shutdown();
delete m_fallbackTexture2D;
m_fallbackTexture2D = nullptr;
}
if (m_sampler != nullptr) {
m_sampler->Shutdown();
delete m_sampler;
m_sampler = nullptr;
}
m_device = nullptr;
m_backendType = RHI::RHIType::D3D12;
m_builtinShader.Reset();
@@ -235,7 +339,7 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
}
if (m_builtinShader.IsValid() &&
tryResolveFromShader(m_builtinShader.Get(), nullptr)) {
tryResolveFromShader(m_builtinShader.Get(), material)) {
return resolved;
}
@@ -259,9 +363,19 @@ bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan(
return false;
}
if (!IsSupportedPerObjectOnlyBindingPlan(outPlan)) {
if (!UsesContiguousDescriptorSets(outPlan)) {
if (outError != nullptr) {
*outError = "Builtin depth-style pass currently requires exactly one PerObject constant-buffer binding";
*outError = "Builtin depth-style pass requires contiguous descriptor set indices";
}
return false;
}
if (!IsSupportedPerObjectOnlyBindingPlan(outPlan) &&
!IsSupportedAlphaTestBindingPlan(outPlan)) {
if (outError != nullptr) {
*outError =
"Builtin depth-style pass requires either PerObject-only bindings or the alpha-test contract "
"(PerObject + Material + BaseColorTexture + LinearClampSampler)";
}
return false;
}
@@ -304,24 +418,26 @@ BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrC
return failLayout(contextualError.CStr());
}
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
Containers::String setLayoutError;
if (!TryBuildBuiltinPassSetLayouts(bindingPlan, setLayouts, &setLayoutError)) {
if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) {
return failLayout(setLayoutError.CStr());
}
if (bindingPlan.perObject.set >= setLayouts.size()) {
if (bindingPlan.perObject.set >= passLayout.setLayouts.size()) {
return failLayout("Builtin depth-style pass produced an invalid PerObject descriptor set index");
}
passLayout.perObject = bindingPlan.perObject;
passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet;
passLayout.perObjectSetLayout = setLayouts[bindingPlan.perObject.set];
RefreshBuiltinPassSetLayoutMetadata(passLayout.perObjectSetLayout);
passLayout.descriptorSetCount = bindingPlan.descriptorSetCount;
passLayout.staticDescriptorSets.resize(passLayout.setLayouts.size());
passLayout.perObject = bindingPlan.perObject;
passLayout.material = bindingPlan.material;
passLayout.baseColorTexture = bindingPlan.baseColorTexture;
passLayout.linearClampSampler = bindingPlan.linearClampSampler;
std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(setLayouts.size());
for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) {
nativeSetLayouts[setIndex] = setLayouts[setIndex].layout;
std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(passLayout.setLayouts.size());
for (size_t setIndex = 0; setIndex < passLayout.setLayouts.size(); ++setIndex) {
nativeSetLayouts[setIndex] = passLayout.setLayouts[setIndex].layout;
}
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
@@ -333,7 +449,7 @@ BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrC
}
const auto result = m_passResourceLayouts.emplace(passLayoutKey, std::move(passLayout));
RefreshBuiltinPassSetLayoutMetadata(result.first->second.perObjectSetLayout);
RefreshBuiltinPassSetLayouts(result.first->second.setLayouts);
return &result.first->second;
}
@@ -412,31 +528,98 @@ bool BuiltinDepthStylePassBase::CreateOwnedDescriptorSet(
return true;
}
RHI::RHIDescriptorSet* BuiltinDepthStylePassBase::GetOrCreatePerObjectSet(
RHI::RHIDescriptorSet* BuiltinDepthStylePassBase::GetOrCreateStaticDescriptorSet(
const PassLayoutKey& passLayoutKey,
PassResourceLayout& passLayout,
Core::uint32 setIndex) {
(void)passLayoutKey;
if (setIndex >= passLayout.setLayouts.size() ||
setIndex >= passLayout.staticDescriptorSets.size()) {
return nullptr;
}
const BuiltinPassSetLayoutMetadata& setLayout = passLayout.setLayouts[setIndex];
if (setLayout.layout.bindingCount == 0) {
return nullptr;
}
OwnedDescriptorSet& descriptorSet = passLayout.staticDescriptorSets[setIndex];
if (descriptorSet.set == nullptr) {
if (!CreateOwnedDescriptorSet(setLayout, descriptorSet)) {
return nullptr;
}
if (setLayout.usesLinearClampSampler) {
if (!passLayout.linearClampSampler.IsValid() ||
passLayout.linearClampSampler.set != setIndex ||
m_sampler == nullptr) {
DestroyOwnedDescriptorSet(descriptorSet);
return nullptr;
}
descriptorSet.set->UpdateSampler(passLayout.linearClampSampler.binding, m_sampler);
}
}
return descriptorSet.set;
}
BuiltinDepthStylePassBase::CachedDescriptorSet* BuiltinDepthStylePassBase::GetOrCreateDynamicDescriptorSet(
const PassLayoutKey& passLayoutKey,
const PassResourceLayout& passLayout,
Core::uint64 objectId) {
if (!passLayout.perObject.IsValid() ||
passLayout.perObjectSetLayout.layout.bindingCount == 0) {
return nullptr;
}
PerObjectSetKey key = {};
const BuiltinPassSetLayoutMetadata& setLayout,
Core::uint32 setIndex,
Core::uint64 objectId,
const Resources::Material* material,
const MaterialConstantPayloadView& materialConstants,
RHI::RHIResourceView* baseColorTextureView) {
DynamicDescriptorSetKey key = {};
key.passLayout = passLayoutKey;
key.setIndex = setIndex;
key.objectId = objectId;
key.material = material;
const auto existing = m_perObjectSets.find(key);
if (existing != m_perObjectSets.end()) {
return existing->second.set;
CachedDescriptorSet& cachedDescriptorSet = m_dynamicDescriptorSets[key];
if (cachedDescriptorSet.descriptorSet.set == nullptr) {
if (!CreateOwnedDescriptorSet(setLayout, cachedDescriptorSet.descriptorSet)) {
return nullptr;
}
}
OwnedDescriptorSet descriptorSet = {};
if (!CreateOwnedDescriptorSet(passLayout.perObjectSetLayout, descriptorSet)) {
return nullptr;
const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0;
if (setLayout.usesMaterial) {
if (!passLayout.material.IsValid() ||
passLayout.material.set != setIndex ||
!materialConstants.IsValid()) {
return nullptr;
}
if (cachedDescriptorSet.materialVersion != materialVersion) {
cachedDescriptorSet.descriptorSet.set->WriteConstant(
passLayout.material.binding,
materialConstants.data,
materialConstants.size);
}
}
const auto result = m_perObjectSets.emplace(key, descriptorSet);
return result.first->second.set;
if (setLayout.usesBaseColorTexture) {
if (baseColorTextureView == nullptr ||
!passLayout.baseColorTexture.IsValid() ||
passLayout.baseColorTexture.set != setIndex) {
return nullptr;
}
if (cachedDescriptorSet.baseColorTextureView != baseColorTextureView) {
cachedDescriptorSet.descriptorSet.set->Update(
passLayout.baseColorTexture.binding,
baseColorTextureView);
}
}
cachedDescriptorSet.materialVersion = materialVersion;
cachedDescriptorSet.baseColorTextureView = baseColorTextureView;
return &cachedDescriptorSet;
}
void BuiltinDepthStylePassBase::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) {
@@ -454,15 +637,46 @@ void BuiltinDepthStylePassBase::DestroyOwnedDescriptorSet(OwnedDescriptorSet& de
}
void BuiltinDepthStylePassBase::DestroyPassResourceLayout(PassResourceLayout& passLayout) {
for (OwnedDescriptorSet& descriptorSet : passLayout.staticDescriptorSets) {
DestroyOwnedDescriptorSet(descriptorSet);
}
passLayout.staticDescriptorSets.clear();
if (passLayout.pipelineLayout != nullptr) {
passLayout.pipelineLayout->Shutdown();
delete passLayout.pipelineLayout;
passLayout.pipelineLayout = nullptr;
}
passLayout.setLayouts.clear();
passLayout.perObject = {};
passLayout.perObjectSetLayout = {};
passLayout.material = {};
passLayout.baseColorTexture = {};
passLayout.linearClampSampler = {};
passLayout.firstDescriptorSet = 0;
passLayout.descriptorSetCount = 0;
}
RHI::RHIResourceView* BuiltinDepthStylePassBase::ResolveTextureView(
const Resources::Texture* texture) {
if (texture == nullptr) {
return nullptr;
}
const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture);
if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) {
return cachedTexture->shaderResourceView;
}
return nullptr;
}
RHI::RHIResourceView* BuiltinDepthStylePassBase::ResolveTextureView(
const VisibleRenderItem& visibleItem) {
const Resources::Material* material = ResolveMaterial(visibleItem);
const Resources::Texture* texture = ResolveBuiltinBaseColorTexture(material);
RHI::RHIResourceView* textureView = ResolveTextureView(texture);
return textureView != nullptr ? textureView : m_fallbackTexture2DView;
}
bool BuiltinDepthStylePassBase::DrawVisibleItem(
@@ -531,18 +745,6 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0);
}
RHI::RHIDescriptorSet* constantSet = GetOrCreatePerObjectSet(
passLayoutKey,
*passLayout,
visibleItem.gameObject->GetID());
if (constantSet == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase failed to allocate descriptor set for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
const Math::Matrix4x4 projectionMatrix =
m_passType == BuiltinMaterialPass::ShadowCaster
? sceneData.cameraData.viewProjection
@@ -556,14 +758,122 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
viewMatrix,
visibleItem.localToWorld.Transpose()
};
constantSet->WriteConstant(passLayout->perObject.binding, &constants, sizeof(constants));
RHI::RHIDescriptorSet* descriptorSets[] = { constantSet };
commandList->SetGraphicsDescriptorSets(
passLayout->firstDescriptorSet,
1,
descriptorSets,
passLayout->pipelineLayout);
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)
};
}
RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(visibleItem);
if (passLayout->baseColorTexture.IsValid() && baseColorTextureView == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase failed to resolve base color texture for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
if (passLayout->descriptorSetCount > 0) {
std::vector<RHI::RHIDescriptorSet*> descriptorSets(passLayout->descriptorSetCount, nullptr);
for (Core::uint32 descriptorOffset = 0; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) {
const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset;
if (setIndex >= passLayout->setLayouts.size()) {
return false;
}
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
if (setLayout.layout.bindingCount == 0) {
return false;
}
RHI::RHIDescriptorSet* descriptorSet = nullptr;
if (setLayout.usesPerObject ||
setLayout.usesMaterial ||
setLayout.usesBaseColorTexture) {
const Core::uint64 objectId =
setLayout.usesPerObject ? visibleItem.gameObject->GetID() : 0;
const Resources::Material* materialKey =
(setLayout.usesMaterial || setLayout.usesBaseColorTexture) ? material : nullptr;
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
passLayoutKey,
*passLayout,
setLayout,
setIndex,
objectId,
materialKey,
materialConstants,
baseColorTextureView);
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase failed to allocate dynamic descriptor set for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
descriptorSet = cachedDescriptorSet->descriptorSet.set;
if (setLayout.usesPerObject) {
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
return false;
}
descriptorSet->WriteConstant(
passLayout->perObject.binding,
&constants,
sizeof(constants));
}
} else {
descriptorSet = GetOrCreateStaticDescriptorSet(passLayoutKey, *passLayout, setIndex);
if (descriptorSet == nullptr) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
(Containers::String("BuiltinDepthStylePassBase failed to allocate static descriptor set for ") +
visibleItem.gameObject->GetName().c_str()).CStr());
return false;
}
}
descriptorSets[descriptorOffset] = descriptorSet;
}
commandList->SetGraphicsDescriptorSets(
passLayout->firstDescriptorSet,
passLayout->descriptorSetCount,
descriptorSets.data(),
passLayout->pipelineLayout);
}
if (visibleItem.hasSection) {
const Containers::Array<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();

View File

@@ -183,7 +183,7 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfac
if (builtinShaderHandle->IsValid()) {
const Resources::Shader* builtinShader = builtinShaderHandle->Get();
if (const Resources::ShaderPass* shaderPass =
FindCompatibleSurfacePass(*builtinShader, sceneData, nullptr, pass, backend)) {
FindCompatibleSurfacePass(*builtinShader, sceneData, material, pass, backend)) {
resolved.shader = builtinShader;
resolved.pass = shaderPass;
resolved.passName = shaderPass->name;
@@ -705,18 +705,32 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
if (!materialConstants.IsValid()) {
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material);
fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor;
static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantField = {
Containers::String("baseColorFactor"),
Resources::MaterialPropertyType::Float4,
0,
sizeof(FallbackPerMaterialConstants),
sizeof(FallbackPerMaterialConstants)
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 = {
&kFallbackMaterialConstantField,
1,
kFallbackMaterialConstantFields,
2,
sizeof(fallbackMaterialConstants)
};
}