#include "Rendering/Passes/BuiltinObjectIdPass.h" #include "Components/GameObject.h" #include "Core/Asset/ResourceManager.h" #include "Debug/Logger.h" #include "RHI/RHICommandList.h" #include "RHI/RHIPipelineLayout.h" #include "RHI/RHIPipelineState.h" #include "Rendering/Detail/ShaderVariantUtils.h" #include "Rendering/RenderMaterialUtility.h" #include "Resources/BuiltinResources.h" #include "Resources/Mesh/Mesh.h" #include #include namespace XCEngine { namespace Rendering { namespace Passes { namespace { const Resources::ShaderPass* FindObjectIdCompatiblePass( const Resources::Shader& shader, Resources::ShaderBackend backend) { for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ObjectId) && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) { return &shaderPass; } } const Resources::ShaderPass* objectIdPass = shader.FindPass("ObjectId"); if (objectIdPass != nullptr && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, objectIdPass->name, backend)) { return objectIdPass; } const Resources::ShaderPass* editorObjectIdPass = shader.FindPass("EditorObjectId"); if (editorObjectIdPass != nullptr && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, editorObjectIdPass->name, backend)) { return editorObjectIdPass; } if (shader.GetPassCount() > 0 && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) { return &shader.GetPasses()[0]; } return nullptr; } RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, const Resources::Shader& shader, const Containers::String& passName) { 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 = BuiltinObjectIdPass::BuildInputLayout(); pipelineDesc.rasterizerState.fillMode = static_cast(RHI::FillMode::Solid); pipelineDesc.rasterizerState.cullMode = static_cast(RHI::CullMode::None); pipelineDesc.rasterizerState.frontFace = static_cast(RHI::FrontFace::CounterClockwise); pipelineDesc.rasterizerState.depthClipEnable = true; pipelineDesc.blendState.blendEnable = false; pipelineDesc.blendState.colorWriteMask = static_cast(RHI::ColorWriteMask::All); 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 BuiltinObjectIdPass::~BuiltinObjectIdPass() { Shutdown(); } RHI::InputLayoutDesc BuiltinObjectIdPass::BuildInputLayout() { 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 BuiltinObjectIdPass::Render( const RenderContext& context, const RenderSurface& surface, const RenderSceneData& sceneData) { if (!context.IsValid()) { return false; } const std::vector& colorAttachments = surface.GetColorAttachments(); if (colorAttachments.empty() || colorAttachments[0] == nullptr || surface.GetDepthAttachment() == nullptr) { return false; } const Math::RectInt renderArea = surface.GetRenderArea(); if (renderArea.width <= 0 || renderArea.height <= 0) { return false; } if (!EnsureInitialized(context)) { return false; } RHI::RHICommandList* commandList = context.commandList; RHI::RHIResourceView* renderTarget = colorAttachments[0]; if (surface.IsAutoTransitionEnabled()) { commandList->TransitionBarrier( renderTarget, surface.GetColorStateBefore(), RHI::ResourceStates::RenderTarget); } commandList->SetRenderTargets(1, &renderTarget, 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 float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; const RHI::Rect clearRects[] = { scissorRect }; commandList->SetViewport(viewport); commandList->SetScissorRect(scissorRect); commandList->ClearRenderTarget(renderTarget, clearColor, 1, clearRects); commandList->ClearDepthStencil(surface.GetDepthAttachment(), 1.0f, 0, 1, clearRects); commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); commandList->SetPipelineState(m_pipelineState); for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) { DrawVisibleItem(context, sceneData, visibleItem); } commandList->EndRenderPass(); if (surface.IsAutoTransitionEnabled()) { commandList->TransitionBarrier( renderTarget, RHI::ResourceStates::RenderTarget, surface.GetColorStateAfter()); } return true; } void BuiltinObjectIdPass::Shutdown() { DestroyResources(); } bool BuiltinObjectIdPass::EnsureInitialized(const RenderContext& context) { if (!context.IsValid()) { return false; } if (m_pipelineLayout != nullptr && m_pipelineState != nullptr && m_device == context.device && m_backendType == context.backendType) { return true; } DestroyResources(); return CreateResources(context); } bool BuiltinObjectIdPass::CreateResources(const RenderContext& context) { m_device = context.device; m_backendType = context.backendType; m_builtinObjectIdShader = Resources::ResourceManager::Get().Load( Resources::GetBuiltinObjectIdShaderPath()); if (!m_builtinObjectIdShader.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinObjectIdPass failed to load builtin object-id shader resource"); DestroyResources(); return false; } const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType); const Resources::ShaderPass* objectIdPass = FindObjectIdCompatiblePass(*m_builtinObjectIdShader.Get(), backend); if (objectIdPass == nullptr) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinObjectIdPass could not resolve a valid ObjectId shader pass"); DestroyResources(); return false; } Containers::Array resourceBindings = objectIdPass->resources; if (resourceBindings.Empty()) { resourceBindings = BuildLegacyBuiltinObjectIdPassResourceBindings(); } BuiltinPassResourceBindingPlan bindingPlan = {}; Containers::String bindingPlanError; if (!TryBuildBuiltinPassResourceBindingPlan(resourceBindings, bindingPlan, &bindingPlanError)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, (Containers::String("BuiltinObjectIdPass failed to resolve pass resource bindings: ") + bindingPlanError).CStr()); DestroyResources(); return false; } if (!bindingPlan.perObject.IsValid() || bindingPlan.bindings.Size() != 1u || bindingPlan.descriptorSetCount != 1u || !bindingPlan.usesConstantBuffers || bindingPlan.usesTextures || bindingPlan.usesSamplers) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinObjectIdPass requires exactly one PerObject constant-buffer binding"); DestroyResources(); return false; } m_perObjectBinding = bindingPlan.perObject; m_firstDescriptorSet = bindingPlan.firstDescriptorSet; std::vector setLayouts; Containers::String setLayoutError; if (!TryBuildBuiltinPassSetLayouts(bindingPlan, setLayouts, &setLayoutError)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, (Containers::String("BuiltinObjectIdPass failed to build descriptor set layouts: ") + setLayoutError).CStr()); DestroyResources(); return false; } if (m_perObjectBinding.set >= setLayouts.size()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinObjectIdPass produced an invalid PerObject descriptor set index"); DestroyResources(); return false; } m_perObjectSetLayout = setLayouts[m_perObjectBinding.set]; RefreshBuiltinPassSetLayoutMetadata(m_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()); m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); if (m_pipelineLayout == nullptr) { DestroyResources(); return false; } m_pipelineState = m_device->CreatePipelineState( CreatePipelineDesc( m_backendType, m_pipelineLayout, *m_builtinObjectIdShader.Get(), objectIdPass->name)); if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) { if (m_pipelineState != nullptr) { m_pipelineState->Shutdown(); delete m_pipelineState; m_pipelineState = nullptr; } DestroyResources(); return false; } return true; } void BuiltinObjectIdPass::DestroyResources() { m_resourceCache.Shutdown(); for (auto& descriptorSetEntry : m_perObjectSets) { DestroyOwnedDescriptorSet(descriptorSetEntry.second); } m_perObjectSets.clear(); if (m_pipelineState != nullptr) { m_pipelineState->Shutdown(); delete m_pipelineState; m_pipelineState = nullptr; } if (m_pipelineLayout != nullptr) { m_pipelineLayout->Shutdown(); delete m_pipelineLayout; m_pipelineLayout = nullptr; } m_device = nullptr; m_backendType = RHI::RHIType::D3D12; m_perObjectBinding = {}; m_perObjectSetLayout = {}; m_firstDescriptorSet = 0; m_builtinObjectIdShader.Reset(); } RHI::RHIDescriptorSet* BuiltinObjectIdPass::GetOrCreatePerObjectSet(uint64_t objectId) { if (m_perObjectBinding.IsValid() == false || m_perObjectSetLayout.layout.bindingCount == 0) { return nullptr; } const auto existing = m_perObjectSets.find(objectId); if (existing != m_perObjectSets.end()) { return existing->second.set; } RHI::DescriptorPoolDesc poolDesc = {}; poolDesc.type = m_perObjectSetLayout.heapType; poolDesc.descriptorCount = CountBuiltinPassHeapDescriptors(m_perObjectSetLayout.heapType, m_perObjectSetLayout.bindings); poolDesc.shaderVisible = m_perObjectSetLayout.shaderVisible; OwnedDescriptorSet descriptorSet = {}; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); if (descriptorSet.pool == nullptr) { return nullptr; } descriptorSet.set = descriptorSet.pool->AllocateSet(m_perObjectSetLayout.layout); if (descriptorSet.set == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); return nullptr; } const auto result = m_perObjectSets.emplace(objectId, descriptorSet); return result.first->second.set; } void BuiltinObjectIdPass::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; } } bool BuiltinObjectIdPass::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; } RHI::RHICommandList* commandList = context.commandList; 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); } const uint64_t objectId = visibleItem.gameObject->GetID(); RHI::RHIDescriptorSet* constantSet = GetOrCreatePerObjectSet(objectId); if (constantSet == nullptr) { return false; } const PerObjectConstants constants = { sceneData.cameraData.projection, sceneData.cameraData.view, visibleItem.localToWorld.Transpose(), EncodeObjectIdToColor(objectId) }; constantSet->WriteConstant(m_perObjectBinding.binding, &constants, sizeof(constants)); RHI::RHIDescriptorSet* descriptorSets[] = { constantSet }; commandList->SetGraphicsDescriptorSets(m_firstDescriptorSet, 1, descriptorSets, m_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; } } // namespace Passes } // namespace Rendering } // namespace XCEngine