#include "Rendering/Passes/BuiltinDepthStylePassBase.h" #include "Rendering/Passes/BuiltinDepthOnlyPass.h" #include "Rendering/Passes/BuiltinShadowCasterPass.h" #include "Components/GameObject.h" #include "Components/MeshRendererComponent.h" #include "Core/Asset/ResourceManager.h" #include "Debug/Logger.h" #include "RHI/RHICommandList.h" #include "Rendering/Detail/ShaderVariantUtils.h" #include "Rendering/RenderSceneExtractor.h" #include "Rendering/RenderSurface.h" #include "Resources/BuiltinResources.h" #include "Resources/Material/Material.h" #include "Resources/Mesh/Mesh.h" #include #include #include namespace XCEngine { namespace Rendering { namespace Passes { namespace { Containers::Array BuildLegacyBuiltinDepthStylePassResourceBindings( BuiltinMaterialPass passType) { switch (passType) { case BuiltinMaterialPass::DepthOnly: return BuildLegacyBuiltinDepthOnlyPassResourceBindings(); case BuiltinMaterialPass::ShadowCaster: return BuildLegacyBuiltinShadowCasterPassResourceBindings(); default: return {}; } } bool IsSupportedPerObjectOnlyBindingPlan(const BuiltinPassResourceBindingPlan& bindingPlan) { return bindingPlan.perObject.IsValid() && bindingPlan.bindings.Size() == 1u && bindingPlan.descriptorSetCount == 1u && bindingPlan.usesConstantBuffers && !bindingPlan.usesTextures && !bindingPlan.usesSamplers; } RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, const Resources::Shader& shader, const Containers::String& passName, const Resources::Material* material, const RHI::InputLayoutDesc& inputLayout) { RHI::GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); pipelineDesc.renderTargetCount = 1; pipelineDesc.renderTargetFormats[0] = static_cast(RHI::Format::R8G8B8A8_UNorm); pipelineDesc.depthStencilFormat = static_cast(RHI::Format::D24_UNorm_S8_UInt); pipelineDesc.sampleCount = 1; pipelineDesc.inputLayout = inputLayout; ApplyMaterialRenderState(material, pipelineDesc); pipelineDesc.blendState.blendEnable = false; pipelineDesc.blendState.colorWriteMask = 0; pipelineDesc.depthStencilState.depthTestEnable = true; pipelineDesc.depthStencilState.depthWriteEnable = true; pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::LessEqual); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); } if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); } return pipelineDesc; } } // namespace BuiltinDepthStylePassBase::BuiltinDepthStylePassBase( BuiltinMaterialPass passType, Containers::String builtinShaderPath) : m_passType(passType) , m_builtinShaderPath(std::move(builtinShaderPath)) { } BuiltinDepthStylePassBase::~BuiltinDepthStylePassBase() { Shutdown(); } RHI::InputLayoutDesc BuiltinDepthStylePassBase::BuildCommonInputLayout() { RHI::InputLayoutDesc inputLayout = {}; RHI::InputElementDesc position = {}; position.semanticName = "POSITION"; position.semanticIndex = 0; position.format = static_cast(RHI::Format::R32G32B32_Float); position.inputSlot = 0; position.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, position)); inputLayout.elements.push_back(position); RHI::InputElementDesc normal = {}; normal.semanticName = "NORMAL"; normal.semanticIndex = 0; normal.format = static_cast(RHI::Format::R32G32B32_Float); normal.inputSlot = 0; normal.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, normal)); inputLayout.elements.push_back(normal); RHI::InputElementDesc texcoord = {}; texcoord.semanticName = "TEXCOORD"; texcoord.semanticIndex = 0; texcoord.format = static_cast(RHI::Format::R32G32_Float); texcoord.inputSlot = 0; texcoord.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, uv0)); inputLayout.elements.push_back(texcoord); return inputLayout; } bool BuiltinDepthStylePassBase::Initialize(const RenderContext& context) { return EnsureInitialized(context); } void BuiltinDepthStylePassBase::Shutdown() { DestroyResources(); } bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) { if (!context.renderContext.IsValid()) { return false; } const std::vector& colorAttachments = context.surface.GetColorAttachments(); if (colorAttachments.empty() || colorAttachments[0] == nullptr || context.surface.GetDepthAttachment() == nullptr) { return false; } const Math::RectInt renderArea = context.surface.GetRenderArea(); if (renderArea.width <= 0 || renderArea.height <= 0) { return false; } if (!EnsureInitialized(context.renderContext)) { return false; } RHI::RHICommandList* commandList = context.renderContext.commandList; RHI::RHIResourceView* renderTarget = colorAttachments[0]; if (context.surface.IsAutoTransitionEnabled()) { commandList->TransitionBarrier( renderTarget, context.surface.GetColorStateBefore(), RHI::ResourceStates::RenderTarget); } commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment()); const RHI::Viewport viewport = { static_cast(renderArea.x), static_cast(renderArea.y), static_cast(renderArea.width), static_cast(renderArea.height), 0.0f, 1.0f }; const RHI::Rect scissorRect = { renderArea.x, renderArea.y, renderArea.x + renderArea.width, renderArea.y + renderArea.height }; const RHI::Rect clearRects[] = { scissorRect }; commandList->SetViewport(viewport); commandList->SetScissorRect(scissorRect); const Math::Color clearColor = context.sceneData.cameraData.clearColor; const float clearValues[4] = { clearColor.r, clearColor.g, clearColor.b, clearColor.a }; if (HasRenderClearFlag(context.sceneData.cameraData.clearFlags, RenderClearFlags::Color)) { commandList->ClearRenderTarget(renderTarget, clearValues, 1, clearRects); } if (HasRenderClearFlag(context.sceneData.cameraData.clearFlags, RenderClearFlags::Depth)) { commandList->ClearDepthStencil(context.surface.GetDepthAttachment(), 1.0f, 0, 1, clearRects); } commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); for (const VisibleRenderItem& visibleItem : context.sceneData.visibleItems) { if (!ShouldRenderVisibleItem(visibleItem)) { continue; } DrawVisibleItem(context.renderContext, context.sceneData, visibleItem); } commandList->EndRenderPass(); if (context.surface.IsAutoTransitionEnabled()) { commandList->TransitionBarrier( renderTarget, RHI::ResourceStates::RenderTarget, context.surface.GetColorStateAfter()); } return true; } bool BuiltinDepthStylePassBase::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const { (void)visibleItem; return true; } bool BuiltinDepthStylePassBase::EnsureInitialized(const RenderContext& context) { if (!context.IsValid()) { return false; } if (m_device == context.device && m_backendType == context.backendType && m_builtinShader.IsValid()) { return true; } DestroyResources(); return CreateResources(context); } bool BuiltinDepthStylePassBase::CreateResources(const RenderContext& context) { m_device = context.device; m_backendType = context.backendType; m_builtinShader = Resources::ResourceManager::Get().Load(m_builtinShaderPath); if (!m_builtinShader.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, (Containers::String("BuiltinDepthStylePassBase failed to load builtin shader resource: ") + m_builtinShaderPath).CStr()); DestroyResources(); return false; } return true; } void BuiltinDepthStylePassBase::DestroyResources() { m_resourceCache.Shutdown(); for (auto& descriptorSetEntry : m_perObjectSets) { DestroyOwnedDescriptorSet(descriptorSetEntry.second); } m_perObjectSets.clear(); for (auto& pipelineEntry : m_pipelineStates) { if (pipelineEntry.second != nullptr) { pipelineEntry.second->Shutdown(); delete pipelineEntry.second; } } m_pipelineStates.clear(); for (auto& layoutEntry : m_passResourceLayouts) { DestroyPassResourceLayout(layoutEntry.second); } m_passResourceLayouts.clear(); m_device = nullptr; m_backendType = RHI::RHIType::D3D12; m_builtinShader.Reset(); } BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::ResolveSurfaceShaderPass( const Resources::Material* material) const { ResolvedShaderPass resolved = {}; const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType); auto tryResolveFromShader = [this, backend, &resolved]( const Resources::Shader* shader, const Resources::Material* ownerMaterial) -> bool { if (shader == nullptr) { return false; } auto tryAcceptPass = [this, shader, &resolved](const Resources::ShaderPass& shaderPass) -> bool { BuiltinPassResourceBindingPlan bindingPlan = {}; Containers::String error; if (!TryBuildSupportedBindingPlan(shaderPass, bindingPlan, &error)) { return false; } resolved.shader = shader; resolved.pass = &shaderPass; resolved.passName = shaderPass.name; return true; }; if (ownerMaterial != nullptr && !ownerMaterial->GetShaderPass().Empty()) { const Resources::ShaderPass* explicitPass = shader->FindPass(ownerMaterial->GetShaderPass()); if (explicitPass != nullptr && ShaderPassMatchesBuiltinPass(*explicitPass, m_passType) && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants( *shader, explicitPass->name, backend) && tryAcceptPass(*explicitPass)) { return true; } } for (const Resources::ShaderPass& shaderPass : shader->GetPasses()) { if (!ShaderPassMatchesBuiltinPass(shaderPass, m_passType) || !::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(*shader, shaderPass.name, backend)) { continue; } if (tryAcceptPass(shaderPass)) { return true; } } return false; }; if (material != nullptr && material->GetShader() != nullptr && tryResolveFromShader(material->GetShader(), material)) { return resolved; } if (material != nullptr && IsTransparentRenderQueue(ResolveMaterialRenderQueue(material))) { return {}; } if (m_builtinShader.IsValid() && tryResolveFromShader(m_builtinShader.Get(), nullptr)) { return resolved; } return {}; } bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan( const Resources::ShaderPass& shaderPass, BuiltinPassResourceBindingPlan& outPlan, Containers::String* outError) const { Containers::Array resourceBindings = shaderPass.resources; if (resourceBindings.Empty()) { resourceBindings = BuildLegacyBuiltinDepthStylePassResourceBindings(m_passType); } if (!TryBuildBuiltinPassResourceBindingPlan(resourceBindings, outPlan, outError)) { return false; } if (!IsSupportedPerObjectOnlyBindingPlan(outPlan)) { if (outError != nullptr) { *outError = "Builtin depth-style pass currently requires exactly one PerObject constant-buffer binding"; } return false; } return true; } BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrCreatePassResourceLayout( const RenderContext& context, const ResolvedShaderPass& resolvedShaderPass) { if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return nullptr; } PassLayoutKey passLayoutKey = {}; passLayoutKey.shader = resolvedShaderPass.shader; passLayoutKey.passName = resolvedShaderPass.passName; const auto existing = m_passResourceLayouts.find(passLayoutKey); if (existing != m_passResourceLayouts.end()) { return &existing->second; } PassResourceLayout passLayout = {}; auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* { Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message); DestroyPassResourceLayout(passLayout); return nullptr; }; BuiltinPassResourceBindingPlan bindingPlan = {}; Containers::String bindingPlanError; if (!TryBuildSupportedBindingPlan(*resolvedShaderPass.pass, bindingPlan, &bindingPlanError)) { return failLayout(bindingPlanError.CStr()); } std::vector setLayouts; Containers::String setLayoutError; if (!TryBuildBuiltinPassSetLayouts(bindingPlan, setLayouts, &setLayoutError)) { return failLayout(setLayoutError.CStr()); } if (bindingPlan.perObject.set >= 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); std::vector nativeSetLayouts(setLayouts.size()); for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) { nativeSetLayouts[setIndex] = setLayouts[setIndex].layout; } RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data(); pipelineLayoutDesc.setLayoutCount = static_cast(nativeSetLayouts.size()); passLayout.pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc); if (passLayout.pipelineLayout == nullptr) { return failLayout("Builtin depth-style pass failed to create pipeline layout"); } const auto result = m_passResourceLayouts.emplace(passLayoutKey, passLayout); return &result.first->second; } RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material) { const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return nullptr; } PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { return nullptr; } PipelineStateKey pipelineKey = {}; pipelineKey.renderState = material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState(); pipelineKey.shader = resolvedShaderPass.shader; pipelineKey.passName = resolvedShaderPass.passName; const auto existing = m_pipelineStates.find(pipelineKey); if (existing != m_pipelineStates.end()) { return existing->second; } const RHI::GraphicsPipelineDesc pipelineDesc = CreatePipelineDesc( context.backendType, passLayout->pipelineLayout, *resolvedShaderPass.shader, resolvedShaderPass.passName, material, BuildCommonInputLayout()); RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); if (pipelineState == nullptr || !pipelineState->IsValid()) { if (pipelineState != nullptr) { pipelineState->Shutdown(); delete pipelineState; } return nullptr; } m_pipelineStates.emplace(pipelineKey, pipelineState); return pipelineState; } bool BuiltinDepthStylePassBase::CreateOwnedDescriptorSet( const BuiltinPassSetLayoutMetadata& setLayout, OwnedDescriptorSet& descriptorSet) { RHI::DescriptorPoolDesc poolDesc = {}; poolDesc.type = setLayout.heapType; poolDesc.descriptorCount = CountBuiltinPassHeapDescriptors(setLayout.heapType, setLayout.bindings); poolDesc.shaderVisible = setLayout.shaderVisible; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); if (descriptorSet.pool == nullptr) { return false; } descriptorSet.set = descriptorSet.pool->AllocateSet(setLayout.layout); if (descriptorSet.set == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); return false; } return true; } RHI::RHIDescriptorSet* BuiltinDepthStylePassBase::GetOrCreatePerObjectSet( const PassLayoutKey& passLayoutKey, const PassResourceLayout& passLayout, Core::uint64 objectId) { if (!passLayout.perObject.IsValid() || passLayout.perObjectSetLayout.layout.bindingCount == 0) { return nullptr; } PerObjectSetKey key = {}; key.passLayout = passLayoutKey; key.objectId = objectId; const auto existing = m_perObjectSets.find(key); if (existing != m_perObjectSets.end()) { return existing->second.set; } OwnedDescriptorSet descriptorSet = {}; if (!CreateOwnedDescriptorSet(passLayout.perObjectSetLayout, descriptorSet)) { return nullptr; } const auto result = m_perObjectSets.emplace(key, descriptorSet); return result.first->second.set; } void BuiltinDepthStylePassBase::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) { if (descriptorSet.set != nullptr) { descriptorSet.set->Shutdown(); delete descriptorSet.set; descriptorSet.set = nullptr; } if (descriptorSet.pool != nullptr) { descriptorSet.pool->Shutdown(); delete descriptorSet.pool; descriptorSet.pool = nullptr; } } void BuiltinDepthStylePassBase::DestroyPassResourceLayout(PassResourceLayout& passLayout) { if (passLayout.pipelineLayout != nullptr) { passLayout.pipelineLayout->Shutdown(); delete passLayout.pipelineLayout; passLayout.pipelineLayout = nullptr; } passLayout.perObject = {}; passLayout.perObjectSetLayout = {}; passLayout.firstDescriptorSet = 0; } bool BuiltinDepthStylePassBase::DrawVisibleItem( const RenderContext& context, const RenderSceneData& sceneData, const VisibleRenderItem& visibleItem) { if (visibleItem.mesh == nullptr || visibleItem.gameObject == nullptr) { return false; } const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh); if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) { return false; } const Resources::Material* material = ResolveMaterial(visibleItem); const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return false; } PassLayoutKey passLayoutKey = {}; passLayoutKey.shader = resolvedShaderPass.shader; passLayoutKey.passName = resolvedShaderPass.passName; PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { return false; } RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, material); if (pipelineState == nullptr) { return false; } RHI::RHICommandList* commandList = context.commandList; commandList->SetPipelineState(pipelineState); RHI::RHIResourceView* vertexBuffers[] = { cachedMesh->vertexBufferView }; const uint64_t offsets[] = { 0 }; const uint32_t strides[] = { cachedMesh->vertexStride }; commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides); if (cachedMesh->indexBufferView != nullptr) { commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0); } RHI::RHIDescriptorSet* constantSet = GetOrCreatePerObjectSet( passLayoutKey, *passLayout, visibleItem.gameObject->GetID()); if (constantSet == nullptr) { return false; } const PerObjectConstants constants = { sceneData.cameraData.projection, sceneData.cameraData.view, visibleItem.localToWorld.Transpose() }; constantSet->WriteConstant(passLayout->perObject.binding, &constants, sizeof(constants)); RHI::RHIDescriptorSet* descriptorSets[] = { constantSet }; commandList->SetGraphicsDescriptorSets( passLayout->firstDescriptorSet, 1, descriptorSets, passLayout->pipelineLayout); if (visibleItem.hasSection) { const Containers::Array& sections = visibleItem.mesh->GetSections(); if (visibleItem.sectionIndex >= sections.Size()) { return false; } const Resources::MeshSection& section = sections[visibleItem.sectionIndex]; if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) { commandList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0); } else if (section.vertexCount > 0) { commandList->Draw(section.vertexCount, 1, section.baseVertex, 0); } return true; } if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0) { commandList->DrawIndexed(cachedMesh->indexCount, 1, 0, 0, 0); } else if (cachedMesh->vertexCount > 0) { commandList->Draw(cachedMesh->vertexCount, 1, 0, 0); } return true; } BuiltinDepthOnlyPass::BuiltinDepthOnlyPass() : BuiltinDepthStylePassBase( BuiltinMaterialPass::DepthOnly, Resources::GetBuiltinDepthOnlyShaderPath()) { } RHI::InputLayoutDesc BuiltinDepthOnlyPass::BuildInputLayout() { return BuildCommonInputLayout(); } const char* BuiltinDepthOnlyPass::GetName() const { return "BuiltinDepthOnlyPass"; } BuiltinShadowCasterPass::BuiltinShadowCasterPass() : BuiltinDepthStylePassBase( BuiltinMaterialPass::ShadowCaster, Resources::GetBuiltinShadowCasterShaderPath()) { } RHI::InputLayoutDesc BuiltinShadowCasterPass::BuildInputLayout() { return BuildCommonInputLayout(); } const char* BuiltinShadowCasterPass::GetName() const { return "BuiltinShadowCasterPass"; } bool BuiltinShadowCasterPass::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const { return visibleItem.meshRenderer == nullptr || visibleItem.meshRenderer->GetCastShadows(); } } // namespace Passes } // namespace Rendering } // namespace XCEngine