#include "Viewport/Passes/SceneViewportSelectionOutlinePass.h" #include "Viewport/ViewportRenderTargets.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace XCEngine::UI::Editor::App { namespace { class SceneViewportSelectionMaskPass final : public ::XCEngine::Rendering::Passes::BuiltinDepthStylePassBase { public: explicit SceneViewportSelectionMaskPass( const ::XCEngine::Containers::String& shaderPath) : BuiltinDepthStylePassBase( ::XCEngine::Rendering::BuiltinMaterialPass::SelectionMask, shaderPath) { } const char* GetName() const override { return "SceneViewportSelectionMaskPass"; } bool Render( const ::XCEngine::Rendering::RenderContext& context, const ::XCEngine::Rendering::RenderSurface& surface, const ::XCEngine::Rendering::RenderSceneData& sceneData, const std::vector& selectedObjectIds) { m_selectedObjectIds.clear(); m_selectedObjectIds.reserve(selectedObjectIds.size()); for (const std::uint64_t selectedObjectId : selectedObjectIds) { if (selectedObjectId != 0u) { m_selectedObjectIds.push_back(selectedObjectId); } } if (m_selectedObjectIds.empty()) { return false; } ::XCEngine::Rendering::RenderSceneData selectionMaskSceneData = sceneData; selectionMaskSceneData.cameraData.clearFlags = ::XCEngine::Rendering::RenderClearFlags::Color; selectionMaskSceneData.cameraData.clearColor = ::XCEngine::Math::Color::Black(); const ::XCEngine::Rendering::RenderPassContext passContext = { context, surface, selectionMaskSceneData, nullptr, nullptr, ::XCEngine::RHI::ResourceStates::Common }; return Execute(passContext); } protected: bool ShouldRenderVisibleItem( const ::XCEngine::Rendering::VisibleRenderItem& visibleItem) const override { if (visibleItem.gameObject == nullptr) { return false; } const std::uint64_t runtimeObjectId = visibleItem.gameObject->GetID(); if (runtimeObjectId == 0u) { return false; } return std::find( m_selectedObjectIds.begin(), m_selectedObjectIds.end(), runtimeObjectId) != m_selectedObjectIds.end(); } private: std::vector m_selectedObjectIds = {}; }; const ::XCEngine::Resources::ShaderPass* FindSelectionOutlineCompatiblePass( const ::XCEngine::Resources::Shader& shader, ::XCEngine::Resources::ShaderBackend backend) { const ::XCEngine::Resources::ShaderPass* outlinePass = shader.FindPass("SelectionOutline"); if (outlinePass != nullptr && ::XCEngine::Rendering::ShaderPassHasGraphicsVariants( shader, outlinePass->name, backend)) { return outlinePass; } const ::XCEngine::Resources::ShaderPass* editorOutlinePass = shader.FindPass("EditorSelectionOutline"); if (editorOutlinePass != nullptr && ::XCEngine::Rendering::ShaderPassHasGraphicsVariants( shader, editorOutlinePass->name, backend)) { return editorOutlinePass; } if (shader.GetPassCount() > 0 && ::XCEngine::Rendering::ShaderPassHasGraphicsVariants( shader, shader.GetPasses()[0].name, backend)) { return &shader.GetPasses()[0]; } return nullptr; } ::XCEngine::RHI::GraphicsPipelineDesc CreatePipelineDesc( ::XCEngine::RHI::RHIType backendType, ::XCEngine::RHI::RHIPipelineLayout* pipelineLayout, const ::XCEngine::Resources::Shader& shader, const ::XCEngine::Containers::String& passName, const ::XCEngine::Rendering::RenderSurface& surface) { ::XCEngine::RHI::GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; pipelineDesc.topologyType = static_cast( ::XCEngine::RHI::PrimitiveTopologyType::Triangle); ::XCEngine::Rendering:: ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc( surface, pipelineDesc); pipelineDesc.depthStencilFormat = static_cast(::XCEngine::RHI::Format::Unknown); const ::XCEngine::Resources::ShaderPass* shaderPass = shader.FindPass(passName); if (shaderPass != nullptr && shaderPass->hasFixedFunctionState) { ::XCEngine::Rendering::ApplyRenderState( shaderPass->fixedFunctionState, pipelineDesc); } else { ::XCEngine::Resources::MaterialRenderState fallbackState = {}; fallbackState.cullMode = ::XCEngine::Resources::MaterialCullMode::None; fallbackState.depthWriteEnable = false; fallbackState.depthTestEnable = false; fallbackState.depthFunc = ::XCEngine::Resources::MaterialComparisonFunc::Always; fallbackState.blendEnable = true; fallbackState.srcBlend = ::XCEngine::Resources::MaterialBlendFactor::SrcAlpha; fallbackState.dstBlend = ::XCEngine::Resources::MaterialBlendFactor::InvSrcAlpha; fallbackState.srcBlendAlpha = ::XCEngine::Resources::MaterialBlendFactor::One; fallbackState.dstBlendAlpha = ::XCEngine::Resources::MaterialBlendFactor::InvSrcAlpha; fallbackState.blendOp = ::XCEngine::Resources::MaterialBlendOp::Add; fallbackState.blendOpAlpha = ::XCEngine::Resources::MaterialBlendOp::Add; fallbackState.colorWriteMask = static_cast( ::XCEngine::RHI::ColorWriteMask::All); ::XCEngine::Rendering::ApplyRenderState( fallbackState, pipelineDesc); } const ::XCEngine::Resources::ShaderBackend backend = ::XCEngine::Rendering::ToShaderBackend(backendType); if (const ::XCEngine::Resources::ShaderStageVariant* vertexVariant = shader.FindVariant( passName, ::XCEngine::Resources::ShaderType::Vertex, backend)) { if (shaderPass != nullptr) { ::XCEngine::Rendering::ApplyShaderStageVariant( shader.GetPath(), *shaderPass, backend, *vertexVariant, pipelineDesc.vertexShader); } } if (const ::XCEngine::Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant( passName, ::XCEngine::Resources::ShaderType::Fragment, backend)) { if (shaderPass != nullptr) { ::XCEngine::Rendering::ApplyShaderStageVariant( shader.GetPath(), *shaderPass, backend, *fragmentVariant, pipelineDesc.fragmentShader); } } return pipelineDesc; } class SceneViewportSelectionOutlinePass final : public ::XCEngine::Rendering::RenderPass { public: SceneViewportSelectionOutlinePass( SceneViewportSelectionOutlinePassRenderer& renderer, ViewportRenderTargets* targets, std::vector selectedObjectIds, const SceneViewportSelectionOutlineStyle& style) : m_renderer(renderer) , m_targets(targets) , m_selectedObjectIds(std::move(selectedObjectIds)) , m_style(style) { } const char* GetName() const override { return "SceneViewportSelectionOutline"; } bool Execute( const ::XCEngine::Rendering::RenderPassContext& context) override { return m_renderer.Render( context.renderContext, context.surface, context.sceneData, *m_targets, m_selectedObjectIds, m_style); } private: SceneViewportSelectionOutlinePassRenderer& m_renderer; ViewportRenderTargets* m_targets = nullptr; std::vector m_selectedObjectIds = {}; SceneViewportSelectionOutlineStyle m_style = {}; }; } // namespace class SceneViewportSelectionOutlinePassRenderer::Impl { public: struct OutlineConstants { ::XCEngine::Math::Vector4 viewportSizeAndTexelSize = ::XCEngine::Math::Vector4::Zero(); ::XCEngine::Math::Vector4 outlineColor = ::XCEngine::Math::Vector4::Zero(); ::XCEngine::Math::Vector4 outlineInfo = ::XCEngine::Math::Vector4::Zero(); ::XCEngine::Math::Vector4 depthParams = ::XCEngine::Math::Vector4::Zero(); }; Impl() = default; void SetShaderPaths( const ::XCEngine::Containers::String& selectionMaskShaderPath, const ::XCEngine::Containers::String& selectionOutlineShaderPath) { m_selectionMaskShaderPath = selectionMaskShaderPath; m_shaderPath = selectionOutlineShaderPath; if (m_selectionMaskPass != nullptr) { m_selectionMaskPass->Shutdown(); } m_selectionMaskPass = std::make_unique( m_selectionMaskShaderPath); DestroyResources(); } void Shutdown() { if (m_selectionMaskPass != nullptr) { m_selectionMaskPass->Shutdown(); } DestroyResources(); } bool Render( const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderSurface& surface, const ::XCEngine::Rendering::RenderSceneData& sceneData, ViewportRenderTargets& targets, const std::vector& selectedObjectIds, const SceneViewportSelectionOutlineStyle& style) { if (m_selectionMaskPass == nullptr) { return false; } ::XCEngine::Rendering::RenderSurface selectionMaskSurface = BuildViewportSelectionMaskSurface(targets); selectionMaskSurface.SetRenderArea(surface.GetRenderArea()); if (!m_selectionMaskPass->Render( renderContext, selectionMaskSurface, sceneData, selectedObjectIds)) { return false; } targets.selectionMaskState = ::XCEngine::RHI::ResourceStates::PixelShaderResource; return RenderOutline( renderContext, surface, targets.selectionMaskShaderView, targets.selectionMaskState, targets.depthShaderView, surface.GetDepthStateAfter(), style); } private: bool RenderOutline( const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderSurface& surface, ::XCEngine::RHI::RHIResourceView* selectionMaskTextureView, ::XCEngine::RHI::ResourceStates selectionMaskState, ::XCEngine::RHI::RHIResourceView* depthTextureView, ::XCEngine::RHI::ResourceStates depthTextureState, const SceneViewportSelectionOutlineStyle& style) { if (!renderContext.IsValid() || selectionMaskTextureView == nullptr || depthTextureView == nullptr) { return false; } const std::vector<::XCEngine::RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments(); if (!::XCEngine::Rendering::HasSingleColorAttachment(surface) || colorAttachments.empty() || colorAttachments[0] == nullptr) { return false; } const ::XCEngine::Math::RectInt renderArea = surface.GetRenderArea(); if (renderArea.width <= 0 || renderArea.height <= 0) { return false; } if (!EnsureInitialized(renderContext, surface)) { return false; } OutlineConstants constants = {}; constants.viewportSizeAndTexelSize = ::XCEngine::Math::Vector4( static_cast(surface.GetWidth()), static_cast(surface.GetHeight()), surface.GetWidth() > 0 ? 1.0f / static_cast(surface.GetWidth()) : 0.0f, surface.GetHeight() > 0 ? 1.0f / static_cast(surface.GetHeight()) : 0.0f); constants.outlineColor = ::XCEngine::Math::Vector4( style.outlineColor.r, style.outlineColor.g, style.outlineColor.b, style.outlineColor.a); constants.outlineInfo = ::XCEngine::Math::Vector4( style.debugSelectionMask ? 1.0f : 0.0f, style.outlineWidthPixels, 0.0f, 0.0f); constants.depthParams = ::XCEngine::Math::Vector4(1.0e-5f, 0.0f, 0.0f, 0.0f); m_constantSet->WriteConstant(0, &constants, sizeof(constants)); m_textureSet->Update(0, selectionMaskTextureView); m_textureSet->Update(1, depthTextureView); ::XCEngine::RHI::RHICommandList* commandList = renderContext.commandList; ::XCEngine::RHI::RHIResourceView* renderTarget = colorAttachments[0]; if (surface.IsAutoTransitionEnabled()) { commandList->TransitionBarrier( renderTarget, surface.GetColorStateAfter(), ::XCEngine::RHI::ResourceStates::RenderTarget); commandList->TransitionBarrier( selectionMaskTextureView, selectionMaskState, ::XCEngine::RHI::ResourceStates::PixelShaderResource); commandList->TransitionBarrier( depthTextureView, depthTextureState, ::XCEngine::RHI::ResourceStates::PixelShaderResource); } commandList->SetRenderTargets(1, &renderTarget, nullptr); const ::XCEngine::RHI::Viewport viewport = { static_cast(renderArea.x), static_cast(renderArea.y), static_cast(renderArea.width), static_cast(renderArea.height), 0.0f, 1.0f }; const ::XCEngine::RHI::Rect scissorRect = { renderArea.x, renderArea.y, renderArea.x + renderArea.width, renderArea.y + renderArea.height }; commandList->SetViewport(viewport); commandList->SetScissorRect(scissorRect); commandList->SetPrimitiveTopology( ::XCEngine::RHI::PrimitiveTopology::TriangleList); commandList->SetPipelineState(m_pipelineState); ::XCEngine::RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, m_textureSet }; commandList->SetGraphicsDescriptorSets( 0, 2, descriptorSets, m_pipelineLayout); commandList->Draw(3, 1, 0, 0); commandList->EndRenderPass(); if (surface.IsAutoTransitionEnabled()) { commandList->TransitionBarrier( renderTarget, ::XCEngine::RHI::ResourceStates::RenderTarget, surface.GetColorStateAfter()); commandList->TransitionBarrier( selectionMaskTextureView, ::XCEngine::RHI::ResourceStates::PixelShaderResource, selectionMaskState); commandList->TransitionBarrier( depthTextureView, ::XCEngine::RHI::ResourceStates::PixelShaderResource, depthTextureState); } return true; } bool EnsureInitialized( const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderSurface& surface) { const ::XCEngine::RHI::Format renderTargetFormat = ::XCEngine::Rendering::ResolveSurfaceColorFormat( surface, 0u); const std::uint32_t renderTargetSampleCount = ::XCEngine::Rendering::ResolveSurfaceSampleCount( surface); if (m_pipelineLayout != nullptr && m_pipelineState != nullptr && m_constantPool != nullptr && m_constantSet != nullptr && m_texturePool != nullptr && m_textureSet != nullptr && m_device == renderContext.device && m_backendType == renderContext.backendType && m_renderTargetFormat == renderTargetFormat && m_renderTargetSampleCount == renderTargetSampleCount) { return true; } if (HasCreatedResources()) { DestroyResources(); } return CreateResources(renderContext, surface); } bool CreateResources( const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderSurface& surface) { if (!renderContext.IsValid()) { return false; } if (!::XCEngine::Rendering::HasSingleColorAttachment(surface) || ::XCEngine::Rendering::ResolveSurfaceColorFormat( surface, 0u) == ::XCEngine::RHI::Format::Unknown) { return false; } if (m_shaderPath.Empty()) { ::XCEngine::Debug::Logger::Get().Error( ::XCEngine::Debug::LogCategory::Rendering, "SceneViewportSelectionOutlinePassRenderer requires a valid shader path"); return false; } ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> shader = ::XCEngine::Resources::ResourceManager::Get() .Load<::XCEngine::Resources::Shader>(m_shaderPath); if (!shader.IsValid()) { ::XCEngine::Debug::Logger::Get().Error( ::XCEngine::Debug::LogCategory::Rendering, "SceneViewportSelectionOutlinePassRenderer failed to load selection-outline shader"); ResetState(); return false; } m_device = renderContext.device; m_backendType = renderContext.backendType; m_shader.emplace(std::move(shader)); const ::XCEngine::Resources::ShaderBackend backend = ::XCEngine::Rendering::ToShaderBackend(m_backendType); const ::XCEngine::Resources::ShaderPass* outlinePass = FindSelectionOutlineCompatiblePass(*m_shader->Get(), backend); if (outlinePass == nullptr) { ::XCEngine::Debug::Logger::Get().Error( ::XCEngine::Debug::LogCategory::Rendering, "SceneViewportSelectionOutlinePassRenderer could not resolve SelectionOutline pass"); DestroyResources(); return false; } ::XCEngine::RHI::DescriptorSetLayoutBinding constantBinding = {}; constantBinding.binding = 0; constantBinding.type = static_cast(::XCEngine::RHI::DescriptorType::CBV); constantBinding.count = 1; ::XCEngine::RHI::DescriptorSetLayoutBinding textureBindings[2] = {}; textureBindings[0].binding = 0; textureBindings[0].type = static_cast(::XCEngine::RHI::DescriptorType::SRV); textureBindings[0].count = 1; textureBindings[1].binding = 1; textureBindings[1].type = static_cast(::XCEngine::RHI::DescriptorType::SRV); textureBindings[1].count = 1; ::XCEngine::RHI::DescriptorSetLayoutDesc constantLayout = {}; constantLayout.bindings = &constantBinding; constantLayout.bindingCount = 1; ::XCEngine::RHI::DescriptorSetLayoutDesc textureLayout = {}; textureLayout.bindings = textureBindings; textureLayout.bindingCount = 2; ::XCEngine::RHI::DescriptorSetLayoutDesc setLayouts[2] = {}; setLayouts[0] = constantLayout; setLayouts[1] = textureLayout; ::XCEngine::RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = setLayouts; pipelineLayoutDesc.setLayoutCount = 2; m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); if (m_pipelineLayout == nullptr) { DestroyResources(); return false; } ::XCEngine::RHI::DescriptorPoolDesc constantPoolDesc = {}; constantPoolDesc.type = ::XCEngine::RHI::DescriptorHeapType::CBV_SRV_UAV; constantPoolDesc.descriptorCount = 1; constantPoolDesc.shaderVisible = false; m_constantPool = m_device->CreateDescriptorPool(constantPoolDesc); if (m_constantPool == nullptr) { DestroyResources(); return false; } m_constantSet = m_constantPool->AllocateSet(constantLayout); if (m_constantSet == nullptr) { DestroyResources(); return false; } ::XCEngine::RHI::DescriptorPoolDesc texturePoolDesc = {}; texturePoolDesc.type = ::XCEngine::RHI::DescriptorHeapType::CBV_SRV_UAV; texturePoolDesc.descriptorCount = 2; texturePoolDesc.shaderVisible = true; m_texturePool = m_device->CreateDescriptorPool(texturePoolDesc); if (m_texturePool == nullptr) { DestroyResources(); return false; } m_textureSet = m_texturePool->AllocateSet(textureLayout); if (m_textureSet == nullptr) { DestroyResources(); return false; } m_pipelineState = m_device->CreatePipelineState( CreatePipelineDesc( m_backendType, m_pipelineLayout, *m_shader->Get(), outlinePass->name, surface)); if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) { DestroyResources(); return false; } m_renderTargetFormat = ::XCEngine::Rendering::ResolveSurfaceColorFormat( surface, 0u); m_renderTargetSampleCount = ::XCEngine::Rendering::ResolveSurfaceSampleCount( surface); return true; } bool HasCreatedResources() const { return m_device != nullptr || m_pipelineLayout != nullptr || m_pipelineState != nullptr || m_constantPool != nullptr || m_constantSet != nullptr || m_texturePool != nullptr || m_textureSet != nullptr || m_shader.has_value(); } void DestroyResources() { if (m_pipelineState != nullptr) { m_pipelineState->Shutdown(); delete m_pipelineState; m_pipelineState = nullptr; } if (m_textureSet != nullptr) { m_textureSet->Shutdown(); delete m_textureSet; m_textureSet = nullptr; } if (m_texturePool != nullptr) { m_texturePool->Shutdown(); delete m_texturePool; m_texturePool = nullptr; } if (m_constantSet != nullptr) { m_constantSet->Shutdown(); delete m_constantSet; m_constantSet = nullptr; } if (m_constantPool != nullptr) { m_constantPool->Shutdown(); delete m_constantPool; m_constantPool = nullptr; } if (m_pipelineLayout != nullptr) { m_pipelineLayout->Shutdown(); delete m_pipelineLayout; m_pipelineLayout = nullptr; } if (m_shader.has_value()) { m_shader.reset(); } ResetState(); } void ResetState() { m_device = nullptr; m_backendType = ::XCEngine::RHI::RHIType::D3D12; m_pipelineLayout = nullptr; m_pipelineState = nullptr; m_constantPool = nullptr; m_constantSet = nullptr; m_texturePool = nullptr; m_textureSet = nullptr; m_shader.reset(); m_renderTargetFormat = ::XCEngine::RHI::Format::Unknown; m_renderTargetSampleCount = 1u; } std::unique_ptr m_selectionMaskPass = {}; ::XCEngine::Containers::String m_selectionMaskShaderPath = {}; ::XCEngine::RHI::RHIDevice* m_device = nullptr; ::XCEngine::RHI::RHIType m_backendType = ::XCEngine::RHI::RHIType::D3D12; ::XCEngine::RHI::RHIPipelineLayout* m_pipelineLayout = nullptr; ::XCEngine::RHI::RHIPipelineState* m_pipelineState = nullptr; ::XCEngine::RHI::RHIDescriptorPool* m_constantPool = nullptr; ::XCEngine::RHI::RHIDescriptorSet* m_constantSet = nullptr; ::XCEngine::RHI::RHIDescriptorPool* m_texturePool = nullptr; ::XCEngine::RHI::RHIDescriptorSet* m_textureSet = nullptr; ::XCEngine::Containers::String m_shaderPath = {}; std::optional< ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>> m_shader = {}; ::XCEngine::RHI::Format m_renderTargetFormat = ::XCEngine::RHI::Format::Unknown; std::uint32_t m_renderTargetSampleCount = 1u; }; SceneViewportSelectionOutlinePassRenderer:: SceneViewportSelectionOutlinePassRenderer() : m_impl(std::make_unique()) { } SceneViewportSelectionOutlinePassRenderer:: ~SceneViewportSelectionOutlinePassRenderer() = default; void SceneViewportSelectionOutlinePassRenderer::Shutdown() { m_impl->Shutdown(); } void SceneViewportSelectionOutlinePassRenderer::SetShaderPaths( const ::XCEngine::Containers::String& selectionMaskShaderPath, const ::XCEngine::Containers::String& selectionOutlineShaderPath) { m_impl->SetShaderPaths( selectionMaskShaderPath, selectionOutlineShaderPath); } bool SceneViewportSelectionOutlinePassRenderer::Render( const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderSurface& surface, const ::XCEngine::Rendering::RenderSceneData& sceneData, ViewportRenderTargets& targets, const std::vector& selectedObjectIds, const SceneViewportSelectionOutlineStyle& style) { return m_impl->Render( renderContext, surface, sceneData, targets, selectedObjectIds, style); } std::unique_ptr<::XCEngine::Rendering::RenderPass> CreateSceneViewportSelectionOutlinePass( SceneViewportSelectionOutlinePassRenderer& renderer, ViewportRenderTargets* targets, const std::vector& selectedObjectIds, const SceneViewportSelectionOutlineStyle& style) { return std::make_unique( renderer, targets, selectedObjectIds, style); } } // namespace XCEngine::UI::Editor::App