diff --git a/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp b/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp index 9d0221ff..6f84801e 100644 --- a/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp +++ b/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp @@ -1,5 +1,7 @@ #include "Passes/SceneViewportSelectionOutlinePass.h" +#include "Viewport/ViewportHostRenderTargets.h" + namespace XCEngine { namespace Editor { @@ -9,11 +11,11 @@ class SceneViewportSelectionOutlinePass final : public Rendering::RenderPass { public: SceneViewportSelectionOutlinePass( SceneViewportSelectionOutlinePassRenderer& renderer, - RHI::RHIResourceView* objectIdTextureView, + ViewportRenderTargets* targets, std::vector selectedObjectIds, const SceneViewportSelectionOutlineStyle& style) : m_renderer(renderer) - , m_objectIdTextureView(objectIdTextureView) + , m_targets(targets) , m_selectedObjectIds(std::move(selectedObjectIds)) , m_style(style) { } @@ -26,14 +28,15 @@ public: return m_renderer.Render( context.renderContext, context.surface, - m_objectIdTextureView, + context.sceneData, + *m_targets, m_selectedObjectIds, m_style); } private: SceneViewportSelectionOutlinePassRenderer& m_renderer; - RHI::RHIResourceView* m_objectIdTextureView = nullptr; + ViewportRenderTargets* m_targets = nullptr; std::vector m_selectedObjectIds = {}; SceneViewportSelectionOutlineStyle m_style = {}; }; @@ -41,35 +44,51 @@ private: } // namespace SceneViewportSelectionOutlinePassRenderer::SceneViewportSelectionOutlinePassRenderer() - : m_outlinePass() { + : m_selectionMaskPass() + , m_outlinePass() { } void SceneViewportSelectionOutlinePassRenderer::Shutdown() { + m_selectionMaskPass.Shutdown(); m_outlinePass.Shutdown(); } bool SceneViewportSelectionOutlinePassRenderer::Render( const Rendering::RenderContext& renderContext, const Rendering::RenderSurface& surface, - RHI::RHIResourceView* objectIdTextureView, + const Rendering::RenderSceneData& sceneData, + ViewportRenderTargets& targets, const std::vector& selectedObjectIds, const SceneViewportSelectionOutlineStyle& style) { + Rendering::RenderSurface selectionMaskSurface = BuildViewportSelectionMaskSurface(targets); + selectionMaskSurface.SetRenderArea(surface.GetRenderArea()); + + if (!m_selectionMaskPass.Render( + renderContext, + selectionMaskSurface, + sceneData, + selectedObjectIds)) { + return false; + } + + targets.selectionMaskState = RHI::ResourceStates::PixelShaderResource; + return m_outlinePass.Render( renderContext, surface, - objectIdTextureView, - selectedObjectIds, + targets.selectionMaskShaderView, + targets.depthShaderView, ToBuiltinSceneViewportSelectionOutlineStyle(style)); } std::unique_ptr CreateSceneViewportSelectionOutlinePass( SceneViewportSelectionOutlinePassRenderer& renderer, - RHI::RHIResourceView* objectIdTextureView, + ViewportRenderTargets* targets, const std::vector& selectedObjectIds, const SceneViewportSelectionOutlineStyle& style) { return std::make_unique( renderer, - objectIdTextureView, + targets, selectedObjectIds, style); } diff --git a/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h b/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h index 02238517..7cfe9ad9 100644 --- a/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h +++ b/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h @@ -2,7 +2,8 @@ #include "Viewport/SceneViewportPassSpecs.h" -#include +#include +#include #include #include #include @@ -14,6 +15,8 @@ namespace XCEngine { namespace Editor { +struct ViewportRenderTargets; + class SceneViewportSelectionOutlinePassRenderer { public: SceneViewportSelectionOutlinePassRenderer(); @@ -24,17 +27,19 @@ public: bool Render( const Rendering::RenderContext& renderContext, const Rendering::RenderSurface& surface, - RHI::RHIResourceView* objectIdTextureView, + const Rendering::RenderSceneData& sceneData, + ViewportRenderTargets& targets, const std::vector& selectedObjectIds, const SceneViewportSelectionOutlineStyle& style); private: - Rendering::Passes::BuiltinObjectIdOutlinePass m_outlinePass; + Rendering::Passes::BuiltinSelectionMaskPass m_selectionMaskPass; + Rendering::Passes::BuiltinSelectionOutlinePass m_outlinePass; }; std::unique_ptr CreateSceneViewportSelectionOutlinePass( SceneViewportSelectionOutlinePassRenderer& renderer, - RHI::RHIResourceView* objectIdTextureView, + ViewportRenderTargets* targets, const std::vector& selectedObjectIds, const SceneViewportSelectionOutlineStyle& style); diff --git a/editor/src/Viewport/SceneViewportPassSpecs.h b/editor/src/Viewport/SceneViewportPassSpecs.h index 777b5d07..22a0e907 100644 --- a/editor/src/Viewport/SceneViewportPassSpecs.h +++ b/editor/src/Viewport/SceneViewportPassSpecs.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace XCEngine { namespace Editor { @@ -41,9 +41,9 @@ struct SceneViewportSelectionOutlineStyle { bool debugSelectionMask = false; }; -inline Rendering::Passes::ObjectIdOutlineStyle ToBuiltinSceneViewportSelectionOutlineStyle( +inline Rendering::Passes::SelectionOutlineStyle ToBuiltinSceneViewportSelectionOutlineStyle( const SceneViewportSelectionOutlineStyle& style) { - Rendering::Passes::ObjectIdOutlineStyle builtinStyle = {}; + Rendering::Passes::SelectionOutlineStyle builtinStyle = {}; builtinStyle.outlineColor = style.outlineColor; builtinStyle.outlineWidthPixels = style.outlineWidthPixels; builtinStyle.debugSelectionMask = style.debugSelectionMask; diff --git a/editor/src/Viewport/SceneViewportRenderPassBundle.cpp b/editor/src/Viewport/SceneViewportRenderPassBundle.cpp index 3e96436e..0fd78022 100644 --- a/editor/src/Viewport/SceneViewportRenderPassBundle.cpp +++ b/editor/src/Viewport/SceneViewportRenderPassBundle.cpp @@ -10,7 +10,7 @@ void SceneViewportRenderPassBundle::Shutdown() { } SceneViewportRenderPlanBuildResult SceneViewportRenderPassBundle::BuildRenderPlan( - const ViewportRenderTargets& targets, + ViewportRenderTargets& targets, const SceneViewportOverlayData& overlay, const std::vector& selectedObjectIds, const SceneViewportOverlayFrameData& editorOverlayFrameData, @@ -24,12 +24,12 @@ SceneViewportRenderPlanBuildResult SceneViewportRenderPassBundle::BuildRenderPla return CreateSceneViewportGridPass(m_gridRenderer, data); }, [this]( - RHI::RHIResourceView* objectIdTextureView, + ViewportRenderTargets* outlineTargets, const std::vector& selectionIds, const SceneViewportSelectionOutlineStyle& style) { return CreateSceneViewportSelectionOutlinePass( m_selectionOutlineRenderer, - objectIdTextureView, + outlineTargets, selectionIds, style); }, diff --git a/editor/src/Viewport/SceneViewportRenderPassBundle.h b/editor/src/Viewport/SceneViewportRenderPassBundle.h index b04fbc7e..5fbe8d0d 100644 --- a/editor/src/Viewport/SceneViewportRenderPassBundle.h +++ b/editor/src/Viewport/SceneViewportRenderPassBundle.h @@ -15,7 +15,7 @@ public: void Shutdown(); SceneViewportRenderPlanBuildResult BuildRenderPlan( - const ViewportRenderTargets& targets, + ViewportRenderTargets& targets, const SceneViewportOverlayData& overlay, const std::vector& selectedObjectIds, const SceneViewportOverlayFrameData& editorOverlayFrameData, diff --git a/editor/src/Viewport/SceneViewportRenderPlan.h b/editor/src/Viewport/SceneViewportRenderPlan.h index f1913785..83645ff5 100644 --- a/editor/src/Viewport/SceneViewportRenderPlan.h +++ b/editor/src/Viewport/SceneViewportRenderPlan.h @@ -37,7 +37,7 @@ using SceneViewportOverlayPassFactory = using SceneViewportGridPassFactory = std::function(const SceneViewportGridPassData&)>; using SceneViewportSelectionOutlinePassFactory = std::function( - RHI::RHIResourceView*, + ViewportRenderTargets*, const std::vector&, const SceneViewportSelectionOutlineStyle&)>; @@ -47,7 +47,7 @@ struct SceneViewportRenderPlanBuildResult { }; inline SceneViewportRenderPlanBuildResult BuildSceneViewportRenderPlan( - const ViewportRenderTargets& targets, + ViewportRenderTargets& targets, const SceneViewportOverlayData& overlay, const std::vector& selectedObjectIds, const SceneViewportOverlayFrameData& editorOverlayFrameData, @@ -69,18 +69,21 @@ inline SceneViewportRenderPlanBuildResult BuildSceneViewportRenderPlan( } if (!selectedObjectIds.empty()) { - if (targets.objectIdShaderView != nullptr && + if (targets.selectionMaskView != nullptr && + targets.selectionMaskShaderView != nullptr && + targets.depthView != nullptr && + targets.depthShaderView != nullptr && selectionOutlinePassFactory != nullptr) { std::unique_ptr selectionOutlinePass = selectionOutlinePassFactory( - targets.objectIdShaderView, + &targets, selectedObjectIds, BuildSceneViewportSelectionOutlineStyle(debugSelectionMask)); if (selectionOutlinePass != nullptr) { result.plan.postScenePasses.AddPass(std::move(selectionOutlinePass)); } } else if (!debugSelectionMask) { - result.warningStatusText = "Scene object id shader view is unavailable"; + result.warningStatusText = "Scene selection outline resources are unavailable"; } } diff --git a/editor/src/Viewport/ViewportHostRenderFlowUtils.h b/editor/src/Viewport/ViewportHostRenderFlowUtils.h index 479463bb..03696e9a 100644 --- a/editor/src/Viewport/ViewportHostRenderFlowUtils.h +++ b/editor/src/Viewport/ViewportHostRenderFlowUtils.h @@ -152,7 +152,7 @@ inline void ApplySceneViewportRenderRequestSetup( request.postScenePasses = postPasses; } - if (targets.objectIdView == nullptr) { + if (targets.objectIdView == nullptr || targets.objectIdDepthView == nullptr) { return; } @@ -165,6 +165,7 @@ inline void MarkSceneViewportRenderSuccess( const Rendering::CameraRenderRequest& request) { targets.colorState = RHI::ResourceStates::PixelShaderResource; targets.objectIdState = RHI::ResourceStates::PixelShaderResource; + targets.selectionMaskState = RHI::ResourceStates::PixelShaderResource; targets.hasValidObjectIdFrame = request.objectId.IsRequested(); } diff --git a/editor/src/Viewport/ViewportHostRenderTargets.h b/editor/src/Viewport/ViewportHostRenderTargets.h index dc5fdb9f..b21a3b8b 100644 --- a/editor/src/Viewport/ViewportHostRenderTargets.h +++ b/editor/src/Viewport/ViewportHostRenderTargets.h @@ -18,14 +18,21 @@ struct ViewportRenderTargets { RHI::RHIResourceView* colorView = nullptr; RHI::RHITexture* depthTexture = nullptr; RHI::RHIResourceView* depthView = nullptr; + RHI::RHIResourceView* depthShaderView = nullptr; RHI::RHITexture* objectIdTexture = nullptr; + RHI::RHITexture* objectIdDepthTexture = nullptr; + RHI::RHIResourceView* objectIdDepthView = nullptr; RHI::RHIResourceView* objectIdView = nullptr; RHI::RHIResourceView* objectIdShaderView = nullptr; + RHI::RHITexture* selectionMaskTexture = nullptr; + RHI::RHIResourceView* selectionMaskView = nullptr; + RHI::RHIResourceView* selectionMaskShaderView = nullptr; D3D12_CPU_DESCRIPTOR_HANDLE imguiCpuHandle = {}; D3D12_GPU_DESCRIPTOR_HANDLE imguiGpuHandle = {}; ::XCEngine::UI::UITextureHandle textureHandle = {}; RHI::ResourceStates colorState = RHI::ResourceStates::Common; RHI::ResourceStates objectIdState = RHI::ResourceStates::Common; + RHI::ResourceStates selectionMaskState = RHI::ResourceStates::Common; bool hasValidObjectIdFrame = false; }; @@ -44,9 +51,15 @@ inline ViewportHostResourceReuseQuery BuildViewportRenderTargetsReuseQuery( query.resources.hasColorView = targets.colorView != nullptr; query.resources.hasDepthTexture = targets.depthTexture != nullptr; query.resources.hasDepthView = targets.depthView != nullptr; + query.resources.hasDepthShaderView = targets.depthShaderView != nullptr; query.resources.hasObjectIdTexture = targets.objectIdTexture != nullptr; + query.resources.hasObjectIdDepthTexture = targets.objectIdDepthTexture != nullptr; + query.resources.hasObjectIdDepthView = targets.objectIdDepthView != nullptr; query.resources.hasObjectIdView = targets.objectIdView != nullptr; query.resources.hasObjectIdShaderView = targets.objectIdShaderView != nullptr; + query.resources.hasSelectionMaskTexture = targets.selectionMaskTexture != nullptr; + query.resources.hasSelectionMaskView = targets.selectionMaskView != nullptr; + query.resources.hasSelectionMaskShaderView = targets.selectionMaskShaderView != nullptr; query.resources.hasTextureDescriptor = targets.textureHandle.IsValid(); return query; } @@ -65,10 +78,19 @@ inline Rendering::RenderSurface BuildViewportObjectIdSurface(const ViewportRende targets.width, targets.height, targets.objectIdView, - targets.depthView, + targets.objectIdDepthView, targets.objectIdState); } +inline Rendering::RenderSurface BuildViewportSelectionMaskSurface(const ViewportRenderTargets& targets) { + return BuildViewportRenderSurface( + targets.width, + targets.height, + targets.selectionMaskView, + targets.depthView, + targets.selectionMaskState); +} + namespace Detail { template @@ -111,7 +133,17 @@ inline bool CreateViewportDepthResources( const RHI::ResourceViewDesc depthViewDesc = BuildViewportTextureViewDesc(RHI::Format::D24_UNorm_S8_UInt); targets.depthView = device->CreateDepthStencilView(targets.depthTexture, depthViewDesc); - return targets.depthView != nullptr; + if (targets.depthView == nullptr) { + return false; + } + + RHI::ResourceViewDesc depthShaderViewDesc = {}; + depthShaderViewDesc.dimension = RHI::ResourceViewDimension::Texture2D; + depthShaderViewDesc.mipLevel = 0; + targets.depthShaderView = device->CreateShaderResourceView( + targets.depthTexture, + depthShaderViewDesc); + return targets.depthShaderView != nullptr; } inline bool CreateViewportObjectIdResources( @@ -136,7 +168,48 @@ inline bool CreateViewportObjectIdResources( targets.objectIdShaderView = device->CreateShaderResourceView( targets.objectIdTexture, objectIdViewDesc); - return targets.objectIdShaderView != nullptr; + if (targets.objectIdShaderView == nullptr) { + return false; + } + + const RHI::TextureDesc objectIdDepthDesc = + BuildViewportTextureDesc(targets.width, targets.height, RHI::Format::D24_UNorm_S8_UInt); + targets.objectIdDepthTexture = device->CreateTexture(objectIdDepthDesc); + if (targets.objectIdDepthTexture == nullptr) { + return false; + } + + const RHI::ResourceViewDesc objectIdDepthViewDesc = + BuildViewportTextureViewDesc(RHI::Format::D24_UNorm_S8_UInt); + targets.objectIdDepthView = device->CreateDepthStencilView( + targets.objectIdDepthTexture, + objectIdDepthViewDesc); + return targets.objectIdDepthView != nullptr; +} + +inline bool CreateViewportSelectionMaskResources( + RHI::RHIDevice* device, + ViewportRenderTargets& targets) { + const RHI::TextureDesc selectionMaskDesc = + BuildViewportTextureDesc(targets.width, targets.height, RHI::Format::R8G8B8A8_UNorm); + targets.selectionMaskTexture = device->CreateTexture(selectionMaskDesc); + if (targets.selectionMaskTexture == nullptr) { + return false; + } + + const RHI::ResourceViewDesc selectionMaskViewDesc = + BuildViewportTextureViewDesc(RHI::Format::R8G8B8A8_UNorm); + targets.selectionMaskView = device->CreateRenderTargetView( + targets.selectionMaskTexture, + selectionMaskViewDesc); + if (targets.selectionMaskView == nullptr) { + return false; + } + + targets.selectionMaskShaderView = device->CreateShaderResourceView( + targets.selectionMaskTexture, + selectionMaskViewDesc); + return targets.selectionMaskShaderView != nullptr; } inline bool CreateViewportTextureDescriptor( @@ -170,7 +243,13 @@ inline void DestroyViewportRenderTargets( Detail::ShutdownAndDelete(targets.objectIdView); Detail::ShutdownAndDelete(targets.objectIdShaderView); + Detail::ShutdownAndDelete(targets.objectIdDepthView); + Detail::ShutdownAndDelete(targets.objectIdDepthTexture); Detail::ShutdownAndDelete(targets.objectIdTexture); + Detail::ShutdownAndDelete(targets.selectionMaskView); + Detail::ShutdownAndDelete(targets.selectionMaskShaderView); + Detail::ShutdownAndDelete(targets.selectionMaskTexture); + Detail::ShutdownAndDelete(targets.depthShaderView); Detail::ShutdownAndDelete(targets.depthView); Detail::ShutdownAndDelete(targets.depthTexture); Detail::ShutdownAndDelete(targets.colorView); @@ -183,6 +262,7 @@ inline void DestroyViewportRenderTargets( targets.textureHandle = {}; targets.colorState = RHI::ResourceStates::Common; targets.objectIdState = RHI::ResourceStates::Common; + targets.selectionMaskState = RHI::ResourceStates::Common; targets.hasValidObjectIdFrame = false; } @@ -204,7 +284,8 @@ inline bool CreateViewportRenderTargets( if (!Detail::CreateViewportColorResources(device, targets) || !Detail::CreateViewportDepthResources(device, targets) || (ViewportRequiresObjectIdResources(kind) && - !Detail::CreateViewportObjectIdResources(device, targets)) || + (!Detail::CreateViewportObjectIdResources(device, targets) || + !Detail::CreateViewportSelectionMaskResources(device, targets))) || !Detail::CreateViewportTextureDescriptor(backend, device, targets)) { DestroyViewportRenderTargets(backend, targets); return false; @@ -212,6 +293,7 @@ inline bool CreateViewportRenderTargets( targets.colorState = RHI::ResourceStates::Common; targets.objectIdState = RHI::ResourceStates::Common; + targets.selectionMaskState = RHI::ResourceStates::Common; targets.hasValidObjectIdFrame = false; return true; } diff --git a/editor/src/Viewport/ViewportHostSurfaceUtils.h b/editor/src/Viewport/ViewportHostSurfaceUtils.h index 178225a0..4b9108ae 100644 --- a/editor/src/Viewport/ViewportHostSurfaceUtils.h +++ b/editor/src/Viewport/ViewportHostSurfaceUtils.h @@ -19,9 +19,15 @@ struct ViewportHostResourcePresence { bool hasColorView = false; bool hasDepthTexture = false; bool hasDepthView = false; + bool hasDepthShaderView = false; bool hasObjectIdTexture = false; + bool hasObjectIdDepthTexture = false; + bool hasObjectIdDepthView = false; bool hasObjectIdView = false; bool hasObjectIdShaderView = false; + bool hasSelectionMaskTexture = false; + bool hasSelectionMaskView = false; + bool hasSelectionMaskShaderView = false; bool hasTextureDescriptor = false; }; @@ -70,8 +76,14 @@ inline bool CanReuseViewportResources(const ViewportHostResourceReuseQuery& quer } return query.resources.hasObjectIdTexture && + query.resources.hasObjectIdDepthTexture && + query.resources.hasObjectIdDepthView && query.resources.hasObjectIdView && - query.resources.hasObjectIdShaderView; + query.resources.hasObjectIdShaderView && + query.resources.hasDepthShaderView && + query.resources.hasSelectionMaskTexture && + query.resources.hasSelectionMaskView && + query.resources.hasSelectionMaskShaderView; } inline RHI::TextureDesc BuildViewportTextureDesc( diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index c93ad3a5..59e73793 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -484,10 +484,12 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinSelectionMaskPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinColorScalePostProcessPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinFinalColorPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraRenderer.cpp @@ -499,10 +501,12 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinShadowCasterPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPassResources.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinSelectionMaskPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinColorScalePostProcessPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinFinalColorPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinVolumetricPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Extraction/RenderSceneExtractor.cpp diff --git a/engine/assets/builtin/shaders/selection-mask.shader b/engine/assets/builtin/shaders/selection-mask.shader new file mode 100644 index 00000000..8f8c6048 --- /dev/null +++ b/engine/assets/builtin/shaders/selection-mask.shader @@ -0,0 +1,78 @@ +Shader "Builtin Selection Mask" +{ + Properties + { + _BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)] + _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] + _MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] + } + HLSLINCLUDE + // XC_BUILTIN_SELECTION_MASK_D3D12_SHARED + cbuffer PerObjectConstants + { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + }; + + cbuffer MaterialConstants + { + float4 gBaseColorFactor; + float4 gAlphaCutoffParams; + }; + + Texture2D BaseColorTexture; + SamplerState LinearClampSampler; + + struct VSInput + { + float3 position : POSITION; + float3 normal : NORMAL; + float2 texcoord : TEXCOORD0; + }; + + struct PSInput + { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; + }; + + PSInput MainVS(VSInput input) + { + PSInput output; + const float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); + const float4 positionVS = mul(gViewMatrix, positionWS); + output.position = mul(gProjectionMatrix, positionVS); + output.texcoord = input.texcoord; + return output; + } + + float4 MainPS(PSInput input) : SV_TARGET + { + // XC_BUILTIN_SELECTION_MASK_D3D12_PS + #ifdef XC_ALPHA_TEST + const float4 baseColor = + BaseColorTexture.Sample(LinearClampSampler, input.texcoord) * gBaseColorFactor; + clip(baseColor.a - gAlphaCutoffParams.x); + #endif + return float4(1.0, 1.0, 1.0, 1.0); + } + ENDHLSL + SubShader + { + Pass + { + Name "SelectionMask" + Tags { "LightMode" = "SelectionMask" } + Cull Back + ZWrite Off + ZTest LEqual + HLSLPROGRAM + #pragma target 4.5 + #pragma vertex MainVS + #pragma fragment MainPS + #pragma shader_feature_local _ XC_ALPHA_TEST + ENDHLSL + } + } +} diff --git a/engine/assets/builtin/shaders/selection-outline.shader b/engine/assets/builtin/shaders/selection-outline.shader new file mode 100644 index 00000000..11c4bb48 --- /dev/null +++ b/engine/assets/builtin/shaders/selection-outline.shader @@ -0,0 +1,131 @@ +Shader "Builtin Selection Outline" +{ + HLSLINCLUDE + // XC_BUILTIN_SELECTION_OUTLINE_D3D12_SHARED + cbuffer OutlineConstants + { + float4 gViewportSizeAndTexelSize; + float4 gOutlineColor; + float4 gOutlineInfo; + float4 gDepthParams; + }; + + Texture2D gSelectionMaskTexture; + Texture2D gDepthTexture; + + struct VSOutput + { + float4 position : SV_POSITION; + }; + + VSOutput MainVS(uint vertexId : SV_VertexID) + { + // XC_BUILTIN_SELECTION_OUTLINE_D3D12_VS + static const float2 positions[3] = { + float2(-1.0, -1.0), + float2(-1.0, 3.0), + float2( 3.0, -1.0) + }; + + VSOutput output; + output.position = float4(positions[vertexId], 0.0, 1.0); + return output; + } + + int2 ClampPixelCoord(int2 pixelCoord) + { + const int2 maxCoord = int2( + max((int)gViewportSizeAndTexelSize.x - 1, 0), + max((int)gViewportSizeAndTexelSize.y - 1, 0)); + return clamp(pixelCoord, int2(0, 0), maxCoord); + } + + float LoadSelectionMask(int2 pixelCoord) + { + return gSelectionMaskTexture.Load(int3(ClampPixelCoord(pixelCoord), 0)).r; + } + + float LoadDepth(int2 pixelCoord) + { + return gDepthTexture.Load(int3(ClampPixelCoord(pixelCoord), 0)).r; + } + + bool IsSelectedPixel(float maskValue) + { + return maskValue > 0.5; + } + + float4 MainPS(VSOutput input) : SV_TARGET + { + // XC_BUILTIN_SELECTION_OUTLINE_D3D12_PS + const int2 pixelCoord = int2(input.position.xy); + const bool debugSelectionMask = gOutlineInfo.x > 0.5; + const bool centerSelected = IsSelectedPixel(LoadSelectionMask(pixelCoord)); + + if (debugSelectionMask) { + return centerSelected ? float4(1.0, 1.0, 1.0, 1.0) : float4(0.0, 0.0, 0.0, 1.0); + } + + if (centerSelected) { + discard; + } + + const float centerDepth = LoadDepth(pixelCoord); + const int outlineWidth = max((int)gOutlineInfo.y, 1); + const float depthThreshold = max(gDepthParams.x, 1.0e-6f); + + float outline = 0.0; + [loop] + for (int y = -2; y <= 2; ++y) { + [loop] + for (int x = -2; x <= 2; ++x) { + if (x == 0 && y == 0) { + continue; + } + + const float distancePixels = length(float2((float)x, (float)y)); + if (distancePixels > outlineWidth) { + continue; + } + + const int2 sampleCoord = pixelCoord + int2(x, y); + if (!IsSelectedPixel(LoadSelectionMask(sampleCoord))) { + continue; + } + + const float selectedDepth = LoadDepth(sampleCoord); + if (!(centerDepth > selectedDepth + depthThreshold)) { + continue; + } + + const float weight = saturate( + 1.0 - ((distancePixels - 1.0) / max((float)outlineWidth, 1.0))); + outline = max(outline, weight); + } + } + + if (outline <= 0.001) { + discard; + } + + return float4(gOutlineColor.rgb, gOutlineColor.a * outline); + } + ENDHLSL + SubShader + { + Pass + { + Name "SelectionOutline" + Tags { "LightMode" = "SelectionOutline" } + Cull Off + ZWrite Off + ZTest Always + Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha + HLSLPROGRAM + #pragma target 4.5 + #pragma vertex MainVS + #pragma fragment MainPS + ENDHLSL + } + } +} diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h index 63d9a5fc..4d38225b 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h @@ -226,7 +226,8 @@ inline bool TryBuildBuiltinPassDefaultResourceBindings( if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::Unlit) || ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::DepthOnly) || - ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ShadowCaster)) { + ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ShadowCaster) || + ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::SelectionMask)) { AppendBuiltinPassResourceBinding( outBindings, "PerObjectConstants", @@ -318,10 +319,17 @@ inline bool TryBuildBuiltinPassDefaultResourceBindings( "PerObject"); AppendBuiltinPassResourceBinding( outBindings, - "MaterialConstants", + "LightingConstants", Resources::ShaderResourceType::ConstantBuffer, 1u, 0u, + "Lighting"); + AppendBuiltinPassResourceBinding( + outBindings, + "MaterialConstants", + Resources::ShaderResourceType::ConstantBuffer, + 2u, + 0u, "Material"); return true; } diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h index f5315389..711e6679 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h @@ -23,6 +23,8 @@ inline const char* GetBuiltinPassCanonicalName(BuiltinMaterialPass pass) { return "shadowcaster"; case BuiltinMaterialPass::ObjectId: return "objectid"; + case BuiltinMaterialPass::SelectionMask: + return "selectionmask"; case BuiltinMaterialPass::Skybox: return "skybox"; case BuiltinMaterialPass::Volumetric: diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h index 8a2770cb..654f3404 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h @@ -18,6 +18,7 @@ enum class BuiltinMaterialPass : Core::uint32 { DepthOnly, ShadowCaster, ObjectId, + SelectionMask, Skybox, Volumetric, PostProcess, diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinSelectionMaskPass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinSelectionMaskPass.h new file mode 100644 index 00000000..12c5c43a --- /dev/null +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinSelectionMaskPass.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine { +namespace Rendering { + +struct RenderSceneData; +struct VisibleRenderItem; + +namespace Passes { + +class BuiltinSelectionMaskPass final : public BuiltinDepthStylePassBase { +public: + BuiltinSelectionMaskPass(); + ~BuiltinSelectionMaskPass() override = default; + + static RHI::InputLayoutDesc BuildInputLayout(); + + const char* GetName() const override; + + bool Render( + const RenderContext& context, + const RenderSurface& surface, + const RenderSceneData& sceneData, + const std::vector& selectedObjectIds); + +protected: + bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const override; + +private: + std::vector m_selectedObjectIds = {}; +}; + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass.h new file mode 100644 index 00000000..bab60938 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +struct SelectionOutlineStyle { + Math::Color outlineColor = Math::Color(1.0f, 0.4f, 0.0f, 1.0f); + float outlineWidthPixels = 2.0f; + bool debugSelectionMask = false; +}; + +class BuiltinSelectionOutlinePass { +public: + explicit BuiltinSelectionOutlinePass(Containers::String shaderPath = Containers::String()); + ~BuiltinSelectionOutlinePass() = default; + BuiltinSelectionOutlinePass(const BuiltinSelectionOutlinePass&) = delete; + BuiltinSelectionOutlinePass& operator=(const BuiltinSelectionOutlinePass&) = delete; + BuiltinSelectionOutlinePass(BuiltinSelectionOutlinePass&&) = delete; + BuiltinSelectionOutlinePass& operator=(BuiltinSelectionOutlinePass&&) = delete; + + void SetShaderPath(const Containers::String& shaderPath); + const Containers::String& GetShaderPath() const; + + void Shutdown(); + + bool Render( + const RenderContext& renderContext, + const RenderSurface& surface, + RHI::RHIResourceView* selectionMaskTextureView, + RHI::RHIResourceView* depthTextureView, + const SelectionOutlineStyle& style = {}); + +private: + struct OutlineConstants { + Math::Vector4 viewportSizeAndTexelSize = Math::Vector4::Zero(); + Math::Vector4 outlineColor = Math::Vector4::Zero(); + Math::Vector4 outlineInfo = Math::Vector4::Zero(); + Math::Vector4 depthParams = Math::Vector4::Zero(); + }; + + bool EnsureInitialized(const RenderContext& renderContext); + bool CreateResources(const RenderContext& renderContext); + void DestroyResources(); + bool HasCreatedResources() const; + void ResetState(); + + RHI::RHIDevice* m_device = nullptr; + RHI::RHIType m_backendType = RHI::RHIType::D3D12; + RHI::RHIPipelineLayout* m_pipelineLayout = nullptr; + RHI::RHIPipelineState* m_pipelineState = nullptr; + RHI::RHIDescriptorPool* m_constantPool = nullptr; + RHI::RHIDescriptorSet* m_constantSet = nullptr; + RHI::RHIDescriptorPool* m_texturePool = nullptr; + RHI::RHIDescriptorSet* m_textureSet = nullptr; + Containers::String m_shaderPath; + std::optional> m_builtinSelectionOutlineShader; +}; + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Resources/BuiltinResources.h b/engine/include/XCEngine/Resources/BuiltinResources.h index 09236a8a..55dbd1d4 100644 --- a/engine/include/XCEngine/Resources/BuiltinResources.h +++ b/engine/include/XCEngine/Resources/BuiltinResources.h @@ -35,6 +35,8 @@ Containers::String GetBuiltinDepthOnlyShaderPath(); Containers::String GetBuiltinShadowCasterShaderPath(); Containers::String GetBuiltinObjectIdShaderPath(); Containers::String GetBuiltinObjectIdOutlineShaderPath(); +Containers::String GetBuiltinSelectionMaskShaderPath(); +Containers::String GetBuiltinSelectionOutlineShaderPath(); Containers::String GetBuiltinSkyboxShaderPath(); Containers::String GetBuiltinVolumetricShaderPath(); Containers::String GetBuiltinColorScalePostProcessShaderPath(); diff --git a/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp b/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp index b51bd755..ba1aa005 100644 --- a/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp +++ b/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp @@ -360,7 +360,9 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve return resolved; } - if (material != nullptr && IsTransparentRenderQueue(ResolveMaterialRenderQueue(material))) { + if (material != nullptr && + IsTransparentRenderQueue(ResolveMaterialRenderQueue(material)) && + m_passType != BuiltinMaterialPass::SelectionMask) { return {}; } diff --git a/engine/src/Rendering/Passes/BuiltinSelectionMaskPass.cpp b/engine/src/Rendering/Passes/BuiltinSelectionMaskPass.cpp new file mode 100644 index 00000000..ef5dce32 --- /dev/null +++ b/engine/src/Rendering/Passes/BuiltinSelectionMaskPass.cpp @@ -0,0 +1,65 @@ +#include "Rendering/Passes/BuiltinSelectionMaskPass.h" + +#include "Components/GameObject.h" +#include "Resources/BuiltinResources.h" + +#include +#include + +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +BuiltinSelectionMaskPass::BuiltinSelectionMaskPass() + : BuiltinDepthStylePassBase( + BuiltinMaterialPass::SelectionMask, + Resources::GetBuiltinSelectionMaskShaderPath()) { +} + +RHI::InputLayoutDesc BuiltinSelectionMaskPass::BuildInputLayout() { + return BuildCommonInputLayout(); +} + +const char* BuiltinSelectionMaskPass::GetName() const { + return "BuiltinSelectionMaskPass"; +} + +bool BuiltinSelectionMaskPass::Render( + const RenderContext& context, + const RenderSurface& surface, + const RenderSceneData& sceneData, + const std::vector& selectedObjectIds) { + m_selectedObjectIds = selectedObjectIds; + if (m_selectedObjectIds.empty()) { + return false; + } + + RenderSceneData selectionMaskSceneData = sceneData; + selectionMaskSceneData.cameraData.clearFlags = RenderClearFlags::Color; + selectionMaskSceneData.cameraData.clearColor = Math::Color::Black(); + + const RenderPassContext passContext = { + context, + surface, + selectionMaskSceneData, + nullptr, + nullptr + }; + return Execute(passContext); +} + +bool BuiltinSelectionMaskPass::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const { + if (visibleItem.gameObject == nullptr) { + return false; + } + + const uint64_t objectId = visibleItem.gameObject->GetID(); + return std::find(m_selectedObjectIds.begin(), m_selectedObjectIds.end(), objectId) != + m_selectedObjectIds.end(); +} + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp b/engine/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp new file mode 100644 index 00000000..7ade070c --- /dev/null +++ b/engine/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp @@ -0,0 +1,429 @@ +#include "Rendering/Passes/BuiltinSelectionOutlinePass.h" + +#include "Core/Asset/ResourceManager.h" +#include "Debug/Logger.h" +#include "Rendering/Detail/ShaderVariantUtils.h" +#include "Rendering/Materials/RenderMaterialStateUtils.h" +#include "RHI/RHICommandList.h" +#include "RHI/RHIDevice.h" + +#include + +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +namespace { + +const Resources::ShaderPass* FindSelectionOutlineCompatiblePass( + const Resources::Shader& shader, + Resources::ShaderBackend backend) { + const Resources::ShaderPass* outlinePass = shader.FindPass("SelectionOutline"); + if (outlinePass != nullptr && + ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, outlinePass->name, backend)) { + return outlinePass; + } + + const Resources::ShaderPass* editorOutlinePass = shader.FindPass("EditorSelectionOutline"); + if (editorOutlinePass != nullptr && + ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants( + shader, + editorOutlinePass->name, + backend)) { + return editorOutlinePass; + } + + 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::Unknown); + pipelineDesc.sampleCount = 1; + + const Resources::ShaderPass* shaderPass = shader.FindPass(passName); + if (shaderPass != nullptr && shaderPass->hasFixedFunctionState) { + ::XCEngine::Rendering::ApplyRenderState(shaderPass->fixedFunctionState, pipelineDesc); + } else { + Resources::MaterialRenderState fallbackState = {}; + fallbackState.cullMode = Resources::MaterialCullMode::None; + fallbackState.depthWriteEnable = false; + fallbackState.depthTestEnable = false; + fallbackState.depthFunc = Resources::MaterialComparisonFunc::Always; + fallbackState.blendEnable = true; + fallbackState.srcBlend = Resources::MaterialBlendFactor::SrcAlpha; + fallbackState.dstBlend = Resources::MaterialBlendFactor::InvSrcAlpha; + fallbackState.srcBlendAlpha = Resources::MaterialBlendFactor::One; + fallbackState.dstBlendAlpha = Resources::MaterialBlendFactor::InvSrcAlpha; + fallbackState.blendOp = Resources::MaterialBlendOp::Add; + fallbackState.blendOpAlpha = Resources::MaterialBlendOp::Add; + fallbackState.colorWriteMask = static_cast(RHI::ColorWriteMask::All); + ::XCEngine::Rendering::ApplyRenderState(fallbackState, pipelineDesc); + } + + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); + if (const Resources::ShaderStageVariant* vertexVariant = + shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + shader.GetPath(), + *shaderPass, + backend, + *vertexVariant, + pipelineDesc.vertexShader); + } + } + if (const Resources::ShaderStageVariant* fragmentVariant = + shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + shader.GetPath(), + *shaderPass, + backend, + *fragmentVariant, + pipelineDesc.fragmentShader); + } + } + + return pipelineDesc; +} + +} // namespace + +BuiltinSelectionOutlinePass::BuiltinSelectionOutlinePass(Containers::String shaderPath) + : m_shaderPath(shaderPath.Empty() ? Resources::GetBuiltinSelectionOutlineShaderPath() : std::move(shaderPath)) { + ResetState(); +} + +void BuiltinSelectionOutlinePass::SetShaderPath(const Containers::String& shaderPath) { + if (m_shaderPath == shaderPath) { + return; + } + + DestroyResources(); + m_shaderPath = shaderPath; +} + +const Containers::String& BuiltinSelectionOutlinePass::GetShaderPath() const { + return m_shaderPath; +} + +void BuiltinSelectionOutlinePass::Shutdown() { + DestroyResources(); +} + +bool BuiltinSelectionOutlinePass::Render( + const RenderContext& renderContext, + const RenderSurface& surface, + RHI::RHIResourceView* selectionMaskTextureView, + RHI::RHIResourceView* depthTextureView, + const SelectionOutlineStyle& style) { + if (!renderContext.IsValid() || + renderContext.backendType != RHI::RHIType::D3D12 || + selectionMaskTextureView == nullptr || + depthTextureView == nullptr) { + return false; + } + + if (!EnsureInitialized(renderContext)) { + return false; + } + + const std::vector& colorAttachments = surface.GetColorAttachments(); + if (colorAttachments.empty() || colorAttachments[0] == nullptr) { + return false; + } + + OutlineConstants constants = {}; + constants.viewportSizeAndTexelSize = 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 = Math::Vector4( + style.outlineColor.r, + style.outlineColor.g, + style.outlineColor.b, + style.outlineColor.a); + constants.outlineInfo = Math::Vector4( + style.debugSelectionMask ? 1.0f : 0.0f, + style.outlineWidthPixels, + 0.0f, + 0.0f); + constants.depthParams = 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); + + RHI::RHICommandList* commandList = renderContext.commandList; + RHI::RHIResourceView* renderTarget = colorAttachments[0]; + commandList->TransitionBarrier( + renderTarget, + surface.GetColorStateAfter(), + RHI::ResourceStates::RenderTarget); + commandList->TransitionBarrier( + depthTextureView, + RHI::ResourceStates::DepthWrite, + RHI::ResourceStates::PixelShaderResource); + commandList->SetRenderTargets(1, &renderTarget, nullptr); + + const Math::RectInt renderArea = surface.GetRenderArea(); + 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 + }; + + commandList->SetViewport(viewport); + commandList->SetScissorRect(scissorRect); + commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); + commandList->SetPipelineState(m_pipelineState); + + RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, m_textureSet }; + commandList->SetGraphicsDescriptorSets(0, 2, descriptorSets, m_pipelineLayout); + commandList->Draw(3, 1, 0, 0); + + commandList->TransitionBarrier( + renderTarget, + RHI::ResourceStates::RenderTarget, + surface.GetColorStateAfter()); + commandList->TransitionBarrier( + depthTextureView, + RHI::ResourceStates::PixelShaderResource, + RHI::ResourceStates::DepthWrite); + return true; +} + +bool BuiltinSelectionOutlinePass::EnsureInitialized(const RenderContext& renderContext) { + 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) { + return true; + } + + if (HasCreatedResources()) { + DestroyResources(); + } + return CreateResources(renderContext); +} + +bool BuiltinSelectionOutlinePass::CreateResources(const RenderContext& renderContext) { + if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) { + return false; + } + + if (m_shaderPath.Empty()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinSelectionOutlinePass requires an injected shader path before resource creation"); + return false; + } + + Resources::ResourceHandle shader = Resources::ResourceManager::Get().Load( + m_shaderPath); + if (!shader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinSelectionOutlinePass failed to load configured selection-outline shader resource"); + ResetState(); + return false; + } + + m_device = renderContext.device; + m_backendType = renderContext.backendType; + m_builtinSelectionOutlineShader.emplace(std::move(shader)); + + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType); + const Resources::ShaderPass* outlinePass = + FindSelectionOutlineCompatiblePass(*m_builtinSelectionOutlineShader->Get(), backend); + if (outlinePass == nullptr) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinSelectionOutlinePass could not resolve a valid SelectionOutline shader pass"); + DestroyResources(); + return false; + } + + RHI::DescriptorSetLayoutBinding constantBinding = {}; + constantBinding.binding = 0; + constantBinding.type = static_cast(RHI::DescriptorType::CBV); + constantBinding.count = 1; + + RHI::DescriptorSetLayoutBinding textureBindings[2] = {}; + textureBindings[0].binding = 0; + textureBindings[0].type = static_cast(RHI::DescriptorType::SRV); + textureBindings[0].count = 1; + textureBindings[1].binding = 1; + textureBindings[1].type = static_cast(RHI::DescriptorType::SRV); + textureBindings[1].count = 1; + + RHI::DescriptorSetLayoutDesc constantLayout = {}; + constantLayout.bindings = &constantBinding; + constantLayout.bindingCount = 1; + + RHI::DescriptorSetLayoutDesc textureLayout = {}; + textureLayout.bindings = textureBindings; + textureLayout.bindingCount = 2; + + RHI::DescriptorSetLayoutDesc setLayouts[2] = {}; + setLayouts[0] = constantLayout; + setLayouts[1] = textureLayout; + + RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; + pipelineLayoutDesc.setLayouts = setLayouts; + pipelineLayoutDesc.setLayoutCount = 2; + m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); + if (m_pipelineLayout == nullptr) { + DestroyResources(); + return false; + } + + RHI::DescriptorPoolDesc constantPoolDesc = {}; + constantPoolDesc.type = 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; + } + + RHI::DescriptorPoolDesc texturePoolDesc = {}; + texturePoolDesc.type = 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_builtinSelectionOutlineShader->Get(), + outlinePass->name)); + if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) { + DestroyResources(); + return false; + } + + return true; +} + +bool BuiltinSelectionOutlinePass::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_builtinSelectionOutlineShader.has_value(); +} + +void BuiltinSelectionOutlinePass::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_builtinSelectionOutlineShader.has_value()) { + m_builtinSelectionOutlineShader.reset(); + } + + ResetState(); +} + +void BuiltinSelectionOutlinePass::ResetState() { + m_device = nullptr; + m_backendType = RHI::RHIType::D3D12; + m_pipelineLayout = nullptr; + m_pipelineState = nullptr; + m_constantPool = nullptr; + m_constantSet = nullptr; + m_texturePool = nullptr; + m_textureSet = nullptr; + m_builtinSelectionOutlineShader.reset(); +} + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Resources/BuiltinResources.cpp b/engine/src/Resources/BuiltinResources.cpp index c70b2972..92301be9 100644 --- a/engine/src/Resources/BuiltinResources.cpp +++ b/engine/src/Resources/BuiltinResources.cpp @@ -33,6 +33,8 @@ constexpr const char* kBuiltinDepthOnlyShaderPath = "builtin://shaders/depth-onl constexpr const char* kBuiltinShadowCasterShaderPath = "builtin://shaders/shadow-caster"; constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id"; constexpr const char* kBuiltinObjectIdOutlineShaderPath = "builtin://shaders/object-id-outline"; +constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selection-mask"; +constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline"; constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox"; constexpr const char* kBuiltinVolumetricShaderPath = "builtin://shaders/volumetric"; constexpr const char* kBuiltinColorScalePostProcessShaderPath = @@ -60,6 +62,10 @@ constexpr const char* kBuiltinObjectIdShaderAssetRelativePath = "engine/assets/builtin/shaders/object-id.shader"; constexpr const char* kBuiltinObjectIdOutlineShaderAssetRelativePath = "engine/assets/builtin/shaders/object-id-outline.shader"; +constexpr const char* kBuiltinSelectionMaskShaderAssetRelativePath = + "engine/assets/builtin/shaders/selection-mask.shader"; +constexpr const char* kBuiltinSelectionOutlineShaderAssetRelativePath = + "engine/assets/builtin/shaders/selection-outline.shader"; constexpr const char* kBuiltinSkyboxShaderAssetRelativePath = "engine/assets/builtin/shaders/skybox.shader"; constexpr const char* kBuiltinVolumetricShaderAssetRelativePath = @@ -149,6 +155,12 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS if (builtinShaderPath == Containers::String(kBuiltinObjectIdOutlineShaderPath)) { return kBuiltinObjectIdOutlineShaderAssetRelativePath; } + if (builtinShaderPath == Containers::String(kBuiltinSelectionMaskShaderPath)) { + return kBuiltinSelectionMaskShaderAssetRelativePath; + } + if (builtinShaderPath == Containers::String(kBuiltinSelectionOutlineShaderPath)) { + return kBuiltinSelectionOutlineShaderAssetRelativePath; + } if (builtinShaderPath == Containers::String(kBuiltinSkyboxShaderPath)) { return kBuiltinSkyboxShaderAssetRelativePath; } @@ -710,6 +722,14 @@ Shader* BuildBuiltinObjectIdOutlineShader(const Containers::String& path) { return TryLoadBuiltinShaderFromAsset(path); } +Shader* BuildBuiltinSelectionMaskShader(const Containers::String& path) { + return TryLoadBuiltinShaderFromAsset(path); +} + +Shader* BuildBuiltinSelectionOutlineShader(const Containers::String& path) { + return TryLoadBuiltinShaderFromAsset(path); +} + Shader* BuildBuiltinSkyboxShader(const Containers::String& path) { return TryLoadBuiltinShaderFromAsset(path); } @@ -735,9 +755,6 @@ Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) { params.memorySize = 0; material->Initialize(params); - MaterialRenderState renderState = {}; - renderState.cullMode = MaterialCullMode::Back; - material->SetRenderState(renderState); material->SetRenderQueue(MaterialRenderQueue::Geometry); material->SetShader(ResourceManager::Get().Load(GetBuiltinForwardLitShaderPath())); material->SetTexture( @@ -823,6 +840,14 @@ bool TryGetBuiltinShaderPathByShaderName( outPath = GetBuiltinObjectIdOutlineShaderPath(); return true; } + if (shaderName == "Builtin Selection Mask") { + outPath = GetBuiltinSelectionMaskShaderPath(); + return true; + } + if (shaderName == "Builtin Selection Outline") { + outPath = GetBuiltinSelectionOutlineShaderPath(); + return true; + } if (shaderName == "Builtin Skybox") { outPath = GetBuiltinSkyboxShaderPath(); return true; @@ -902,6 +927,14 @@ Containers::String GetBuiltinObjectIdOutlineShaderPath() { return Containers::String(kBuiltinObjectIdOutlineShaderPath); } +Containers::String GetBuiltinSelectionMaskShaderPath() { + return Containers::String(kBuiltinSelectionMaskShaderPath); +} + +Containers::String GetBuiltinSelectionOutlineShaderPath() { + return Containers::String(kBuiltinSelectionOutlineShaderPath); +} + Containers::String GetBuiltinSkyboxShaderPath() { return Containers::String(kBuiltinSkyboxShaderPath); } @@ -1018,6 +1051,10 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) { shader = BuildBuiltinObjectIdShader(path); } else if (path == GetBuiltinObjectIdOutlineShaderPath()) { shader = BuildBuiltinObjectIdOutlineShader(path); + } else if (path == GetBuiltinSelectionMaskShaderPath()) { + shader = BuildBuiltinSelectionMaskShader(path); + } else if (path == GetBuiltinSelectionOutlineShaderPath()) { + shader = BuildBuiltinSelectionOutlineShader(path); } else if (path == GetBuiltinSkyboxShaderPath()) { shader = BuildBuiltinSkyboxShader(path); } else if (path == GetBuiltinVolumetricShaderPath()) { diff --git a/tests/Resources/Material/test_material_loader.cpp b/tests/Resources/Material/test_material_loader.cpp index 4003e434..b68a72ad 100644 --- a/tests/Resources/Material/test_material_loader.cpp +++ b/tests/Resources/Material/test_material_loader.cpp @@ -180,6 +180,19 @@ TEST(MaterialLoader, ResourceManagerRegistersMaterialAndShaderLoaders) { manager.Shutdown(); } +TEST(MaterialLoader, BuiltinDefaultPrimitiveMaterialLeavesRenderStateOverrideDisabled) { + ResourceManager& manager = ResourceManager::Get(); + manager.Initialize(); + + const auto materialHandle = manager.Load(GetBuiltinDefaultPrimitiveMaterialPath()); + ASSERT_TRUE(materialHandle.IsValid()); + ASSERT_NE(materialHandle->GetShader(), nullptr); + EXPECT_EQ(materialHandle->GetShader()->GetPath(), GetBuiltinForwardLitShaderPath()); + EXPECT_FALSE(materialHandle->HasRenderStateOverride()); + + manager.Shutdown(); +} + TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) { ResourceManager& manager = ResourceManager::Get(); manager.Initialize(); diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index 99e74e90..3a1d3c32 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -69,6 +69,8 @@ TEST(ShaderLoader, CanLoad) { EXPECT_TRUE(loader.CanLoad(GetBuiltinUnlitShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdOutlineShaderPath())); + EXPECT_TRUE(loader.CanLoad(GetBuiltinSelectionMaskShaderPath())); + EXPECT_TRUE(loader.CanLoad(GetBuiltinSelectionOutlineShaderPath())); EXPECT_FALSE(loader.CanLoad("test.vert")); EXPECT_FALSE(loader.CanLoad("test.frag")); EXPECT_FALSE(loader.CanLoad("test.glsl")); @@ -2516,6 +2518,27 @@ TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsAuthoringVariants) { delete shader; } +TEST(ShaderLoader, LoadBuiltinSelectionMaskShaderBuildsAuthoringVariants) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinSelectionMaskShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + ASSERT_TRUE(shader->IsValid()); + + const ShaderPass* alphaTestPass = shader->FindPass("SelectionMask"); + ASSERT_NE(alphaTestPass, nullptr); + EXPECT_EQ(alphaTestPass->resources.Size(), 4u); + ASSERT_EQ(alphaTestPass->keywordDeclarations.Size(), 1u); + EXPECT_EQ(alphaTestPass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::ShaderFeatureLocal); + ASSERT_EQ(alphaTestPass->keywordDeclarations[0].options.Size(), 2u); + EXPECT_EQ(alphaTestPass->keywordDeclarations[0].options[1], "XC_ALPHA_TEST"); + + delete shader; +} + TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsAuthoringVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinDepthOnlyShaderPath()); diff --git a/tests/editor/test_scene_viewport_render_pass_bundle.cpp b/tests/editor/test_scene_viewport_render_pass_bundle.cpp index 00653c3b..408ffab9 100644 --- a/tests/editor/test_scene_viewport_render_pass_bundle.cpp +++ b/tests/editor/test_scene_viewport_render_pass_bundle.cpp @@ -78,10 +78,16 @@ SceneViewportOverlayFrameData CreateOverlayFrameDataWithLine(const SceneViewport } TEST(SceneViewportRenderPassBundleTest, BuildRenderPlanCreatesGridOutlineAndOverlayPasses) { - DummyResourceView objectIdShaderView(ResourceViewType::ShaderResource); + DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); + DummyResourceView depthShaderView(ResourceViewType::ShaderResource, Format::D24_UNorm_S8_UInt); + DummyResourceView selectionMaskView(ResourceViewType::RenderTarget); + DummyResourceView selectionMaskShaderView(ResourceViewType::ShaderResource); ViewportRenderTargets targets = {}; - targets.objectIdShaderView = &objectIdShaderView; + targets.depthView = &depthView; + targets.depthShaderView = &depthShaderView; + targets.selectionMaskView = &selectionMaskView; + targets.selectionMaskShaderView = &selectionMaskShaderView; const SceneViewportOverlayData overlay = CreateValidOverlay(); const SceneViewportOverlayFrameData editorOverlayFrameData = @@ -100,12 +106,13 @@ TEST(SceneViewportRenderPassBundleTest, BuildRenderPlanCreatesGridOutlineAndOver EXPECT_EQ(result.warningStatusText, nullptr); } -TEST(SceneViewportRenderPassBundleTest, BuildRenderPlanReportsMissingObjectIdShaderView) { +TEST(SceneViewportRenderPassBundleTest, BuildRenderPlanReportsMissingSelectionOutlineResources) { const SceneViewportOverlayData overlay = CreateValidOverlay(); + ViewportRenderTargets targets = {}; SceneViewportRenderPassBundle bundle; const auto result = bundle.BuildRenderPlan( - {}, + targets, overlay, { 42u }, {}, @@ -113,7 +120,7 @@ TEST(SceneViewportRenderPassBundleTest, BuildRenderPlanReportsMissingObjectIdSha EXPECT_EQ(result.plan.postScenePasses.GetPassCount(), 1u); EXPECT_EQ(result.plan.overlayPasses.GetPassCount(), 0u); - EXPECT_STREQ(result.warningStatusText, "Scene object id shader view is unavailable"); + EXPECT_STREQ(result.warningStatusText, "Scene selection outline resources are unavailable"); } } // namespace diff --git a/tests/editor/test_scene_viewport_shader_paths.cpp b/tests/editor/test_scene_viewport_shader_paths.cpp index f11be50c..edb54f05 100644 --- a/tests/editor/test_scene_viewport_shader_paths.cpp +++ b/tests/editor/test_scene_viewport_shader_paths.cpp @@ -18,11 +18,15 @@ using XCEngine::Editor::GetSceneViewportInfiniteGridShaderPath; using XCEngine::Editor::GetSceneViewportPointLightGizmoIconPath; using XCEngine::Editor::GetSceneViewportSpotLightGizmoIconPath; using XCEngine::Resources::GetBuiltinObjectIdOutlineShaderPath; +using XCEngine::Resources::GetBuiltinSelectionMaskShaderPath; +using XCEngine::Resources::GetBuiltinSelectionOutlineShaderPath; using XCEngine::Resources::LoadResult; using XCEngine::Resources::ResourceHandle; using XCEngine::Resources::ResourceManager; using XCEngine::Resources::Shader; using XCEngine::Resources::ShaderBackend; +using XCEngine::Resources::ShaderKeywordDeclarationType; +using XCEngine::Resources::ShaderKeywordSet; using XCEngine::Resources::ShaderLoader; using XCEngine::Resources::ShaderPass; using XCEngine::Resources::ShaderStageVariant; @@ -110,4 +114,61 @@ TEST(SceneViewportShaderPathsTest, ResourceManagerLoadsSceneViewportOutlineShade manager.Shutdown(); } +TEST(SceneViewportShaderPathsTest, ResourceManagerLoadsSelectionOutlineShadersByResolvedPath) { + ResourceManager& manager = ResourceManager::Get(); + manager.Shutdown(); + + const ResourceHandle maskShaderHandle = manager.Load(GetBuiltinSelectionMaskShaderPath()); + ASSERT_TRUE(maskShaderHandle.IsValid()); + + const ShaderPass* maskPass = maskShaderHandle->FindPass("SelectionMask"); + ASSERT_NE(maskPass, nullptr); + ASSERT_EQ(maskPass->variants.Size(), 4u); + ASSERT_EQ(maskPass->keywordDeclarations.Size(), 1u); + EXPECT_EQ(maskPass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::ShaderFeatureLocal); + ASSERT_EQ(maskPass->keywordDeclarations[0].options.Size(), 2u); + EXPECT_EQ(maskPass->keywordDeclarations[0].options[0], "_"); + EXPECT_EQ(maskPass->keywordDeclarations[0].options[1], "XC_ALPHA_TEST"); + EXPECT_TRUE(maskShaderHandle->PassDeclaresKeyword("SelectionMask", "XC_ALPHA_TEST")); + + const ShaderStageVariant* maskFragment = maskShaderHandle->FindVariant( + "SelectionMask", + ShaderType::Fragment, + ShaderBackend::D3D12); + ASSERT_NE(maskFragment, nullptr); + EXPECT_NE( + std::string(maskFragment->sourceCode.CStr()).find("XC_BUILTIN_SELECTION_MASK_D3D12_PS"), + std::string::npos); + + ShaderKeywordSet alphaKeywords = {}; + alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST"); + const ShaderStageVariant* alphaMaskFragment = maskShaderHandle->FindVariant( + "SelectionMask", + ShaderType::Fragment, + ShaderBackend::D3D12, + alphaKeywords); + ASSERT_NE(alphaMaskFragment, nullptr); + EXPECT_NE( + std::string(alphaMaskFragment->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"), + std::string::npos); + + const ResourceHandle outlineShaderHandle = manager.Load(GetBuiltinSelectionOutlineShaderPath()); + ASSERT_TRUE(outlineShaderHandle.IsValid()); + + const ShaderPass* outlinePass = outlineShaderHandle->FindPass("SelectionOutline"); + ASSERT_NE(outlinePass, nullptr); + ASSERT_EQ(outlinePass->variants.Size(), 2u); + + const ShaderStageVariant* outlineFragment = outlineShaderHandle->FindVariant( + "SelectionOutline", + ShaderType::Fragment, + ShaderBackend::D3D12); + ASSERT_NE(outlineFragment, nullptr); + EXPECT_NE( + std::string(outlineFragment->sourceCode.CStr()).find("XC_BUILTIN_SELECTION_OUTLINE_D3D12_PS"), + std::string::npos); + + manager.Shutdown(); +} + } // namespace diff --git a/tests/editor/test_viewport_host_surface_utils.cpp b/tests/editor/test_viewport_host_surface_utils.cpp index bbc4b4f9..82ee5287 100644 --- a/tests/editor/test_viewport_host_surface_utils.cpp +++ b/tests/editor/test_viewport_host_surface_utils.cpp @@ -73,9 +73,15 @@ TEST(ViewportHostSurfaceUtilsTest, ViewportReuseRequiresObjectIdOnlyForSceneView EXPECT_TRUE(ViewportRequiresObjectIdResources(EditorViewportKind::Scene)); EXPECT_FALSE(CanReuseViewportResources(sceneQuery)); + sceneQuery.resources.hasDepthShaderView = true; sceneQuery.resources.hasObjectIdTexture = true; + sceneQuery.resources.hasObjectIdDepthTexture = true; + sceneQuery.resources.hasObjectIdDepthView = true; sceneQuery.resources.hasObjectIdView = true; sceneQuery.resources.hasObjectIdShaderView = true; + sceneQuery.resources.hasSelectionMaskTexture = true; + sceneQuery.resources.hasSelectionMaskView = true; + sceneQuery.resources.hasSelectionMaskShaderView = true; EXPECT_TRUE(CanReuseViewportResources(sceneQuery)); } @@ -90,9 +96,15 @@ TEST(ViewportHostSurfaceUtilsTest, ViewportReuseRejectsMismatchedOrMissingResour query.resources.hasColorView = true; query.resources.hasDepthTexture = true; query.resources.hasDepthView = true; + query.resources.hasDepthShaderView = true; query.resources.hasObjectIdTexture = true; + query.resources.hasObjectIdDepthTexture = true; + query.resources.hasObjectIdDepthView = true; query.resources.hasObjectIdView = true; query.resources.hasObjectIdShaderView = true; + query.resources.hasSelectionMaskTexture = true; + query.resources.hasSelectionMaskView = true; + query.resources.hasSelectionMaskShaderView = true; query.resources.hasTextureDescriptor = true; EXPECT_TRUE(CanReuseViewportResources(query)); diff --git a/tests/editor/test_viewport_render_flow_utils.cpp b/tests/editor/test_viewport_render_flow_utils.cpp index b8e148a6..43c6f76e 100644 --- a/tests/editor/test_viewport_render_flow_utils.cpp +++ b/tests/editor/test_viewport_render_flow_utils.cpp @@ -190,6 +190,7 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportSelectionOutlineStyleApplies TEST(ViewportRenderFlowUtilsTest, ApplySceneRenderRequestSetupAttachesOptionalPassesAndObjectIdSurface) { DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); + DummyResourceView objectIdDepthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); DummyResourceView objectIdView(ResourceViewType::RenderTarget); DummyResourceView objectIdShaderView(ResourceViewType::ShaderResource); @@ -197,6 +198,7 @@ TEST(ViewportRenderFlowUtilsTest, ApplySceneRenderRequestSetupAttachesOptionalPa targets.width = 800; targets.height = 600; targets.depthView = &depthView; + targets.objectIdDepthView = &objectIdDepthView; targets.objectIdView = &objectIdView; targets.objectIdShaderView = &objectIdShaderView; targets.objectIdState = ResourceStates::Common; @@ -217,7 +219,7 @@ TEST(ViewportRenderFlowUtilsTest, ApplySceneRenderRequestSetupAttachesOptionalPa EXPECT_TRUE(request.objectId.IsRequested()); ASSERT_EQ(request.objectId.surface.GetColorAttachments().size(), 1u); EXPECT_EQ(request.objectId.surface.GetColorAttachments()[0], &objectIdView); - EXPECT_EQ(request.objectId.surface.GetDepthAttachment(), &depthView); + EXPECT_EQ(request.objectId.surface.GetDepthAttachment(), &objectIdDepthView); const auto requestArea = request.surface.GetRenderArea(); const auto objectIdArea = request.objectId.surface.GetRenderArea(); @@ -247,9 +249,18 @@ TEST(ViewportRenderFlowUtilsTest, ApplySceneRenderRequestSetupSkipsUnavailableOp } TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanCollectsPostSceneAndOverlayPasses) { + DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); + DummyResourceView depthShaderView(ResourceViewType::ShaderResource, Format::D24_UNorm_S8_UInt); + DummyResourceView selectionMaskView(ResourceViewType::RenderTarget); + DummyResourceView selectionMaskShaderView(ResourceViewType::ShaderResource); + DummyResourceView objectIdShaderView(ResourceViewType::ShaderResource); ViewportRenderTargets targets = {}; + targets.depthView = &depthView; + targets.depthShaderView = &depthShaderView; + targets.selectionMaskView = &selectionMaskView; + targets.selectionMaskShaderView = &selectionMaskShaderView; targets.objectIdShaderView = &objectIdShaderView; const SceneViewportOverlayData overlay = CreateValidOverlay(); @@ -274,11 +285,16 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanCollectsPostSceneA return std::make_unique(); }, [&selectionOutlinePassFactoryCallCount]( - RHIResourceView* objectIdTextureView, + ViewportRenderTargets* outlineTargets, const std::vector& selectedObjectIds, const SceneViewportSelectionOutlineStyle& style) { ++selectionOutlinePassFactoryCallCount; - EXPECT_NE(objectIdTextureView, nullptr); + EXPECT_NE(outlineTargets, nullptr); + if (outlineTargets == nullptr) { + return std::unique_ptr(); + } + EXPECT_NE(outlineTargets->selectionMaskShaderView, nullptr); + EXPECT_NE(outlineTargets->depthShaderView, nullptr); EXPECT_EQ(selectedObjectIds.size(), 2u); EXPECT_FLOAT_EQ(style.outlineWidthPixels, 2.0f); return std::make_unique(); @@ -301,6 +317,7 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanCollectsPostSceneA TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanSkipsRhiOverlayPassWhenFrameContainsOnlySceneIcons) { const SceneViewportOverlayData overlay = CreateValidOverlay(); + ViewportRenderTargets targets = {}; SceneViewportOverlayFrameData editorOverlayFrameData = {}; editorOverlayFrameData.overlay = overlay; @@ -310,7 +327,7 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanSkipsRhiOverlayPas size_t overlayFactoryCallCount = 0u; const auto result = BuildSceneViewportRenderPlan( - {}, + targets, overlay, {}, editorOverlayFrameData, @@ -318,7 +335,7 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanSkipsRhiOverlayPas return std::make_unique(); }, []( - RHIResourceView*, + ViewportRenderTargets*, const std::vector&, const SceneViewportSelectionOutlineStyle&) { return std::make_unique(); @@ -337,8 +354,9 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanSkipsRhiOverlayPas TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanWarnsWhenSelectionOutlineCannotAccessObjectIdTexture) { const SceneViewportOverlayData overlay = CreateValidOverlay(); + ViewportRenderTargets targets = {}; const auto result = BuildSceneViewportRenderPlan( - {}, + targets, overlay, { 42u }, {}, @@ -346,7 +364,7 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanWarnsWhenSelection return std::make_unique(); }, []( - RHIResourceView*, + ViewportRenderTargets*, const std::vector&, const SceneViewportSelectionOutlineStyle&) { return std::make_unique(); @@ -357,11 +375,12 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanWarnsWhenSelection false); EXPECT_EQ(result.plan.postScenePasses.GetPassCount(), 1u); - EXPECT_STREQ(result.warningStatusText, "Scene object id shader view is unavailable"); + EXPECT_STREQ(result.warningStatusText, "Scene selection outline resources are unavailable"); } TEST(ViewportRenderFlowUtilsTest, ApplySceneViewportRenderPlanAttachesPlannedPassesAndClearState) { DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); + DummyResourceView objectIdDepthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); DummyResourceView objectIdView(ResourceViewType::RenderTarget); DummyResourceView objectIdShaderView(ResourceViewType::ShaderResource); @@ -369,6 +388,7 @@ TEST(ViewportRenderFlowUtilsTest, ApplySceneViewportRenderPlanAttachesPlannedPas targets.width = 800; targets.height = 600; targets.depthView = &depthView; + targets.objectIdDepthView = &objectIdDepthView; targets.objectIdView = &objectIdView; targets.objectIdShaderView = &objectIdShaderView; @@ -395,14 +415,19 @@ TEST(ViewportRenderFlowUtilsTest, ApplySceneViewportRenderPlanAttachesPlannedPas TEST(ViewportRenderFlowUtilsTest, MarkSceneRenderSuccessMovesTargetsToShaderResourceState) { DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); DummyResourceView objectIdView(ResourceViewType::RenderTarget); + DummyResourceView objectIdDepthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); + DummyResourceView selectionMaskView(ResourceViewType::RenderTarget); ViewportRenderTargets targets = {}; targets.width = 640; targets.height = 360; targets.depthView = &depthView; + targets.objectIdDepthView = &objectIdDepthView; targets.objectIdView = &objectIdView; + targets.selectionMaskView = &selectionMaskView; targets.colorState = ResourceStates::Common; targets.objectIdState = ResourceStates::Common; + targets.selectionMaskState = ResourceStates::Common; XCEngine::Rendering::CameraRenderRequest request = {}; request.surface = RenderSurface(640, 360); @@ -411,16 +436,19 @@ TEST(ViewportRenderFlowUtilsTest, MarkSceneRenderSuccessMovesTargetsToShaderReso MarkSceneViewportRenderSuccess(targets, request); EXPECT_EQ(targets.colorState, ResourceStates::PixelShaderResource); EXPECT_EQ(targets.objectIdState, ResourceStates::PixelShaderResource); + EXPECT_EQ(targets.selectionMaskState, ResourceStates::PixelShaderResource); EXPECT_TRUE(targets.hasValidObjectIdFrame); ViewportRenderTargets noObjectIdTargets = {}; noObjectIdTargets.colorState = ResourceStates::Common; noObjectIdTargets.objectIdState = ResourceStates::Common; + noObjectIdTargets.selectionMaskState = ResourceStates::Common; XCEngine::Rendering::CameraRenderRequest noObjectIdRequest = {}; MarkSceneViewportRenderSuccess(noObjectIdTargets, noObjectIdRequest); EXPECT_EQ(noObjectIdTargets.colorState, ResourceStates::PixelShaderResource); EXPECT_EQ(noObjectIdTargets.objectIdState, ResourceStates::PixelShaderResource); + EXPECT_EQ(noObjectIdTargets.selectionMaskState, ResourceStates::PixelShaderResource); EXPECT_FALSE(noObjectIdTargets.hasValidObjectIdFrame); } diff --git a/tests/editor/test_viewport_render_targets.cpp b/tests/editor/test_viewport_render_targets.cpp index bb4034db..c2a4d551 100644 --- a/tests/editor/test_viewport_render_targets.cpp +++ b/tests/editor/test_viewport_render_targets.cpp @@ -8,6 +8,7 @@ namespace { using XCEngine::Editor::BuildViewportColorSurface; using XCEngine::Editor::BuildViewportObjectIdSurface; +using XCEngine::Editor::BuildViewportSelectionMaskSurface; using XCEngine::Editor::BuildViewportRenderTargetsReuseQuery; using XCEngine::Editor::DestroyViewportRenderTargets; using XCEngine::Editor::EditorViewportKind; @@ -78,10 +79,16 @@ TEST(ViewportRenderTargetsTest, BuildReuseQueryReflectsCurrentResourcePresence) targets.colorView = reinterpret_cast(static_cast(0x2)); targets.depthTexture = reinterpret_cast(static_cast(0x3)); targets.depthView = reinterpret_cast(static_cast(0x4)); - targets.objectIdTexture = reinterpret_cast(static_cast(0x5)); - targets.objectIdView = reinterpret_cast(static_cast(0x6)); - targets.objectIdShaderView = reinterpret_cast(static_cast(0x7)); - targets.textureHandle.nativeHandle = 0x8; + targets.depthShaderView = reinterpret_cast(static_cast(0x5)); + targets.objectIdTexture = reinterpret_cast(static_cast(0x6)); + targets.objectIdDepthTexture = reinterpret_cast(static_cast(0x7)); + targets.objectIdDepthView = reinterpret_cast(static_cast(0x8)); + targets.objectIdView = reinterpret_cast(static_cast(0x9)); + targets.objectIdShaderView = reinterpret_cast(static_cast(0xA)); + targets.selectionMaskTexture = reinterpret_cast(static_cast(0xB)); + targets.selectionMaskView = reinterpret_cast(static_cast(0xC)); + targets.selectionMaskShaderView = reinterpret_cast(static_cast(0xD)); + targets.textureHandle.nativeHandle = 0xE; targets.textureHandle.width = 1280; targets.textureHandle.height = 720; @@ -94,25 +101,36 @@ TEST(ViewportRenderTargetsTest, BuildReuseQueryReflectsCurrentResourcePresence) EXPECT_TRUE(query.resources.hasColorView); EXPECT_TRUE(query.resources.hasDepthTexture); EXPECT_TRUE(query.resources.hasDepthView); + EXPECT_TRUE(query.resources.hasDepthShaderView); EXPECT_TRUE(query.resources.hasObjectIdTexture); + EXPECT_TRUE(query.resources.hasObjectIdDepthTexture); + EXPECT_TRUE(query.resources.hasObjectIdDepthView); EXPECT_TRUE(query.resources.hasObjectIdView); EXPECT_TRUE(query.resources.hasObjectIdShaderView); + EXPECT_TRUE(query.resources.hasSelectionMaskTexture); + EXPECT_TRUE(query.resources.hasSelectionMaskView); + EXPECT_TRUE(query.resources.hasSelectionMaskShaderView); EXPECT_TRUE(query.resources.hasTextureDescriptor); } TEST(ViewportRenderTargetsTest, BuildSurfaceUsesTargetAttachmentsAndStates) { DummyResourceView colorView(ResourceViewType::RenderTarget); DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); + DummyResourceView objectIdDepthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); DummyResourceView objectIdView(ResourceViewType::RenderTarget); + DummyResourceView selectionMaskView(ResourceViewType::RenderTarget); ViewportRenderTargets targets = {}; targets.width = 800; targets.height = 600; targets.colorView = &colorView; targets.depthView = &depthView; + targets.objectIdDepthView = &objectIdDepthView; targets.objectIdView = &objectIdView; + targets.selectionMaskView = &selectionMaskView; targets.colorState = ResourceStates::Common; targets.objectIdState = ResourceStates::PixelShaderResource; + targets.selectionMaskState = ResourceStates::PixelShaderResource; const auto colorSurface = BuildViewportColorSurface(targets); ASSERT_EQ(colorSurface.GetColorAttachments().size(), 1u); @@ -123,8 +141,14 @@ TEST(ViewportRenderTargetsTest, BuildSurfaceUsesTargetAttachmentsAndStates) { const auto objectIdSurface = BuildViewportObjectIdSurface(targets); ASSERT_EQ(objectIdSurface.GetColorAttachments().size(), 1u); EXPECT_EQ(objectIdSurface.GetColorAttachments()[0], &objectIdView); - EXPECT_EQ(objectIdSurface.GetDepthAttachment(), &depthView); + EXPECT_EQ(objectIdSurface.GetDepthAttachment(), &objectIdDepthView); EXPECT_EQ(objectIdSurface.GetColorStateBefore(), ResourceStates::PixelShaderResource); + + const auto selectionMaskSurface = BuildViewportSelectionMaskSurface(targets); + ASSERT_EQ(selectionMaskSurface.GetColorAttachments().size(), 1u); + EXPECT_EQ(selectionMaskSurface.GetColorAttachments()[0], &selectionMaskView); + EXPECT_EQ(selectionMaskSurface.GetDepthAttachment(), &depthView); + EXPECT_EQ(selectionMaskSurface.GetColorStateBefore(), ResourceStates::PixelShaderResource); } TEST(ViewportRenderTargetsTest, DestroyViewportRenderTargetsShutsDownAndClearsState) { @@ -132,9 +156,15 @@ TEST(ViewportRenderTargetsTest, DestroyViewportRenderTargetsShutsDownAndClearsSt auto* colorView = new DummyResourceView(ResourceViewType::RenderTarget); auto* depthTexture = new DummyTexture(Format::D24_UNorm_S8_UInt); auto* depthView = new DummyResourceView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); + auto* depthShaderView = new DummyResourceView(ResourceViewType::ShaderResource, Format::D24_UNorm_S8_UInt); auto* objectIdTexture = new DummyTexture(); + auto* objectIdDepthTexture = new DummyTexture(Format::D24_UNorm_S8_UInt); + auto* objectIdDepthView = new DummyResourceView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt); auto* objectIdView = new DummyResourceView(ResourceViewType::RenderTarget); auto* objectIdShaderView = new DummyResourceView(ResourceViewType::ShaderResource); + auto* selectionMaskTexture = new DummyTexture(); + auto* selectionMaskView = new DummyResourceView(ResourceViewType::RenderTarget); + auto* selectionMaskShaderView = new DummyResourceView(ResourceViewType::ShaderResource); ViewportRenderTargets targets = {}; targets.width = 640; @@ -143,9 +173,15 @@ TEST(ViewportRenderTargetsTest, DestroyViewportRenderTargetsShutsDownAndClearsSt targets.colorView = colorView; targets.depthTexture = depthTexture; targets.depthView = depthView; + targets.depthShaderView = depthShaderView; targets.objectIdTexture = objectIdTexture; + targets.objectIdDepthTexture = objectIdDepthTexture; + targets.objectIdDepthView = objectIdDepthView; targets.objectIdView = objectIdView; targets.objectIdShaderView = objectIdShaderView; + targets.selectionMaskTexture = selectionMaskTexture; + targets.selectionMaskView = selectionMaskView; + targets.selectionMaskShaderView = selectionMaskShaderView; targets.imguiCpuHandle.ptr = 123; targets.imguiGpuHandle.ptr = 456; targets.textureHandle.nativeHandle = 789; @@ -153,6 +189,7 @@ TEST(ViewportRenderTargetsTest, DestroyViewportRenderTargetsShutsDownAndClearsSt targets.textureHandle.height = 360; targets.colorState = ResourceStates::RenderTarget; targets.objectIdState = ResourceStates::PixelShaderResource; + targets.selectionMaskState = ResourceStates::PixelShaderResource; targets.hasValidObjectIdFrame = true; DestroyViewportRenderTargets(nullptr, targets); @@ -161,18 +198,30 @@ TEST(ViewportRenderTargetsTest, DestroyViewportRenderTargetsShutsDownAndClearsSt EXPECT_TRUE(colorView->shutdownCalled); EXPECT_TRUE(depthTexture->shutdownCalled); EXPECT_TRUE(depthView->shutdownCalled); + EXPECT_TRUE(depthShaderView->shutdownCalled); EXPECT_TRUE(objectIdTexture->shutdownCalled); + EXPECT_TRUE(objectIdDepthTexture->shutdownCalled); + EXPECT_TRUE(objectIdDepthView->shutdownCalled); EXPECT_TRUE(objectIdView->shutdownCalled); EXPECT_TRUE(objectIdShaderView->shutdownCalled); + EXPECT_TRUE(selectionMaskTexture->shutdownCalled); + EXPECT_TRUE(selectionMaskView->shutdownCalled); + EXPECT_TRUE(selectionMaskShaderView->shutdownCalled); EXPECT_EQ(targets.width, 0u); EXPECT_EQ(targets.height, 0u); EXPECT_EQ(targets.colorTexture, nullptr); EXPECT_EQ(targets.colorView, nullptr); EXPECT_EQ(targets.depthTexture, nullptr); EXPECT_EQ(targets.depthView, nullptr); + EXPECT_EQ(targets.depthShaderView, nullptr); EXPECT_EQ(targets.objectIdTexture, nullptr); + EXPECT_EQ(targets.objectIdDepthTexture, nullptr); + EXPECT_EQ(targets.objectIdDepthView, nullptr); EXPECT_EQ(targets.objectIdView, nullptr); EXPECT_EQ(targets.objectIdShaderView, nullptr); + EXPECT_EQ(targets.selectionMaskTexture, nullptr); + EXPECT_EQ(targets.selectionMaskView, nullptr); + EXPECT_EQ(targets.selectionMaskShaderView, nullptr); EXPECT_EQ(targets.imguiCpuHandle.ptr, 0u); EXPECT_EQ(targets.imguiGpuHandle.ptr, 0u); EXPECT_EQ(targets.textureHandle.nativeHandle, 0u); @@ -180,6 +229,7 @@ TEST(ViewportRenderTargetsTest, DestroyViewportRenderTargetsShutsDownAndClearsSt EXPECT_EQ(targets.textureHandle.height, 0u); EXPECT_EQ(targets.colorState, ResourceStates::Common); EXPECT_EQ(targets.objectIdState, ResourceStates::Common); + EXPECT_EQ(targets.selectionMaskState, ResourceStates::Common); EXPECT_FALSE(targets.hasValidObjectIdFrame); }