#pragma once #include "Core/IEditorContext.h" #include "Core/ISceneManager.h" #include "Core/ISelectionManager.h" #include "IViewportHostService.h" #include "SceneViewportPicker.h" #include "SceneViewportCameraController.h" #include "SceneViewportInfiniteGridPass.h" #include "SceneViewportPostPassPlan.h" #include "SceneViewportSelectionMaskPass.h" #include "SceneViewportSelectionOutlinePass.h" #include "SceneViewportSelectionUtils.h" #include "UI/ImGuiBackendBridge.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace XCEngine { namespace Editor { namespace { constexpr bool kDebugSceneSelectionMask = false; class LambdaRenderPass final : public Rendering::RenderPass { public: using ExecuteCallback = std::function; LambdaRenderPass(std::string name, ExecuteCallback executeCallback) : m_name(std::move(name)) , m_executeCallback(std::move(executeCallback)) { } const char* GetName() const override { return m_name.c_str(); } bool Execute(const Rendering::RenderPassContext& context) override { return m_executeCallback != nullptr && m_executeCallback(context); } private: std::string m_name; ExecuteCallback m_executeCallback; }; template std::unique_ptr MakeLambdaRenderPass(const char* name, Callback&& callback) { return std::make_unique( name, LambdaRenderPass::ExecuteCallback(std::forward(callback))); } inline void SetViewportStatusIfEmpty(std::string& statusText, const char* message) { if (statusText.empty()) { statusText = message; } } inline uint32_t ClampViewportPixelCoordinate(float value, uint32_t extent) { if (extent == 0) { return 0; } const float maxCoordinate = static_cast(extent - 1u); const float clamped = (std::max)(0.0f, (std::min)(value, maxCoordinate)); return static_cast(std::floor(clamped)); } Math::Vector3 GetSceneViewportOrientationAxisVector(SceneViewportOrientationAxis axis) { switch (axis) { case SceneViewportOrientationAxis::PositiveX: return Math::Vector3::Right(); case SceneViewportOrientationAxis::NegativeX: return Math::Vector3::Left(); case SceneViewportOrientationAxis::PositiveY: return Math::Vector3::Up(); case SceneViewportOrientationAxis::NegativeY: return Math::Vector3::Down(); case SceneViewportOrientationAxis::PositiveZ: return Math::Vector3::Forward(); case SceneViewportOrientationAxis::NegativeZ: return Math::Vector3::Back(); default: return Math::Vector3::Zero(); } } } // namespace class ViewportHostService : public IViewportHostService { public: void Initialize(UI::ImGuiBackendBridge& backend, RHI::RHIDevice* device) { Shutdown(); m_backend = &backend; m_device = device ? static_cast(device) : nullptr; } void Shutdown() { for (ViewportEntry& entry : m_entries) { DestroyViewportResources(entry); entry = {}; } m_sceneViewCamera = {}; m_sceneViewLastRenderContext = {}; m_device = nullptr; m_backend = nullptr; m_sceneGridPass.Shutdown(); m_sceneSelectionMaskPass.Shutdown(); m_sceneSelectionOutlinePass.Shutdown(); m_sceneRenderer.reset(); } void BeginFrame() override { for (ViewportEntry& entry : m_entries) { entry.requestedThisFrame = false; entry.requestedWidth = 0; entry.requestedHeight = 0; } } EditorViewportFrame RequestViewport(EditorViewportKind kind, const ImVec2& requestedSize) override { ViewportEntry& entry = GetEntry(kind); entry.requestedThisFrame = requestedSize.x > 1.0f && requestedSize.y > 1.0f; entry.requestedWidth = entry.requestedThisFrame ? static_cast(requestedSize.x) : 0u; entry.requestedHeight = entry.requestedThisFrame ? static_cast(requestedSize.y) : 0u; if (entry.requestedThisFrame && m_backend != nullptr && m_device != nullptr) { EnsureViewportResources(entry); } EditorViewportFrame frame = {}; frame.textureId = entry.textureId; frame.requestedSize = requestedSize; frame.renderSize = ImVec2(static_cast(entry.width), static_cast(entry.height)); frame.hasTexture = entry.textureId != ImTextureID{}; frame.wasRequested = entry.requestedThisFrame; frame.statusText = entry.statusText; return frame; } void UpdateSceneViewInput(IEditorContext& context, const SceneViewportInput& input) override { if (!EnsureSceneViewCamera()) { return; } if (input.focusSelectionRequested) { FocusSceneView(context); } SceneViewportCameraInputState controllerInput = {}; controllerInput.viewportHeight = input.viewportSize.y; controllerInput.zoomDelta = input.hovered ? input.mouseWheel : 0.0f; controllerInput.flySpeedDelta = input.hovered ? input.flySpeedDelta : 0.0f; controllerInput.deltaTime = input.deltaTime; controllerInput.moveForward = input.moveForward; controllerInput.moveRight = input.moveRight; controllerInput.moveUp = input.moveUp; controllerInput.fastMove = input.fastMove; if (input.looking) { controllerInput.lookDeltaX = input.mouseDelta.x; controllerInput.lookDeltaY = input.mouseDelta.y; } if (input.orbiting) { controllerInput.orbitDeltaX = input.mouseDelta.x; controllerInput.orbitDeltaY = input.mouseDelta.y; } if (input.panning) { controllerInput.panDeltaX = input.mouseDelta.x; controllerInput.panDeltaY = input.mouseDelta.y; } m_sceneViewCamera.controller.ApplyInput(controllerInput); ApplySceneViewCameraController(); } uint64_t PickSceneViewEntity( IEditorContext& context, const ImVec2& viewportSize, const ImVec2& viewportMousePosition) override { if (!EnsureSceneViewCamera()) { return 0; } const Components::Scene* scene = context.GetSceneManager().GetScene(); if (scene == nullptr) { return 0; } ViewportEntry& entry = GetEntry(EditorViewportKind::Scene); uint64_t objectIdEntity = 0; if (TryPickSceneViewEntityWithObjectId( entry, viewportSize, viewportMousePosition, objectIdEntity)) { return objectIdEntity; } SceneViewportPickRequest request = {}; request.scene = scene; request.overlay = GetSceneViewOverlayData(); request.viewportSize = Math::Vector2(viewportSize.x, viewportSize.y); request.viewportPosition = Math::Vector2(viewportMousePosition.x, viewportMousePosition.y); return PickSceneViewportEntity(request).entityId; } void AlignSceneViewToOrientationAxis(SceneViewportOrientationAxis axis) override { if (!EnsureSceneViewCamera()) { return; } const Math::Vector3 axisDirection = GetSceneViewportOrientationAxisVector(axis); if (axisDirection.SqrMagnitude() <= Math::EPSILON) { return; } // To make the clicked cone face the screen, the camera must look back along that axis. m_sceneViewCamera.controller.AnimateToForward(axisDirection * -1.0f); ApplySceneViewCameraController(); } SceneViewportOverlayData GetSceneViewOverlayData() const override { SceneViewportOverlayData data = {}; if (m_sceneViewCamera.gameObject == nullptr || m_sceneViewCamera.camera == nullptr) { return data; } const Components::TransformComponent* transform = m_sceneViewCamera.gameObject->GetTransform(); if (transform == nullptr) { return data; } data.valid = true; data.cameraPosition = transform->GetPosition(); data.cameraForward = transform->GetForward(); data.cameraRight = transform->GetRight(); data.cameraUp = transform->GetUp(); data.verticalFovDegrees = m_sceneViewCamera.camera->GetFieldOfView(); data.nearClipPlane = m_sceneViewCamera.camera->GetNearClipPlane(); data.farClipPlane = m_sceneViewCamera.camera->GetFarClipPlane(); data.orbitDistance = m_sceneViewCamera.controller.GetDistance(); return data; } void RenderRequestedViewports( IEditorContext& context, const Rendering::RenderContext& renderContext) override { if (m_backend == nullptr || m_device == nullptr || !renderContext.IsValid()) { return; } EnsureSceneRenderer(); m_sceneViewLastRenderContext = renderContext; const auto* scene = context.GetSceneManager().GetScene(); for (ViewportEntry& entry : m_entries) { if (!entry.requestedThisFrame) { continue; } if (!EnsureViewportResources(entry)) { entry.statusText = "Failed to create viewport render targets"; continue; } RenderViewportEntry(entry, context, scene, renderContext); } } private: struct ViewportEntry { EditorViewportKind kind = EditorViewportKind::Scene; uint32_t width = 0; uint32_t height = 0; uint32_t requestedWidth = 0; uint32_t requestedHeight = 0; bool requestedThisFrame = false; RHI::RHITexture* colorTexture = nullptr; RHI::RHIResourceView* colorView = nullptr; RHI::RHITexture* depthTexture = nullptr; RHI::RHIResourceView* depthView = nullptr; RHI::RHITexture* selectionMaskTexture = nullptr; RHI::RHIResourceView* selectionMaskView = nullptr; RHI::RHIResourceView* selectionMaskShaderView = nullptr; RHI::RHITexture* objectIdTexture = nullptr; RHI::RHIResourceView* objectIdView = nullptr; D3D12_CPU_DESCRIPTOR_HANDLE imguiCpuHandle = {}; D3D12_GPU_DESCRIPTOR_HANDLE imguiGpuHandle = {}; ImTextureID textureId = {}; RHI::ResourceStates colorState = RHI::ResourceStates::Common; RHI::ResourceStates selectionMaskState = RHI::ResourceStates::Common; RHI::ResourceStates objectIdState = RHI::ResourceStates::Common; bool hasValidObjectIdFrame = false; std::string statusText; }; struct SceneViewCameraState { std::unique_ptr gameObject; Components::CameraComponent* camera = nullptr; SceneViewportCameraController controller; }; struct SceneViewportRenderState { SceneViewportOverlayData overlay = {}; Rendering::RenderPassSequence postPasses; Rendering::RenderCameraData cameraData = {}; std::vector selectionRenderables; }; ViewportEntry& GetEntry(EditorViewportKind kind) { const size_t index = kind == EditorViewportKind::Scene ? 0u : 1u; m_entries[index].kind = kind; return m_entries[index]; } void EnsureSceneRenderer() { if (!m_sceneRenderer) { m_sceneRenderer = std::make_unique(); } } bool EnsureSceneViewCamera() { if (m_sceneViewCamera.gameObject != nullptr && m_sceneViewCamera.camera != nullptr) { return true; } m_sceneViewCamera.gameObject = std::make_unique("EditorSceneCamera"); m_sceneViewCamera.camera = m_sceneViewCamera.gameObject->AddComponent(); if (m_sceneViewCamera.camera == nullptr) { m_sceneViewCamera.gameObject.reset(); return false; } m_sceneViewCamera.camera->SetPrimary(false); m_sceneViewCamera.camera->SetProjectionType(Components::CameraProjectionType::Perspective); m_sceneViewCamera.camera->SetFieldOfView(60.0f); m_sceneViewCamera.camera->SetNearClipPlane(0.03f); m_sceneViewCamera.camera->SetFarClipPlane(2000.0f); m_sceneViewCamera.controller.Reset(); ApplySceneViewCameraController(); return true; } void ApplySceneViewCameraController() { if (m_sceneViewCamera.gameObject == nullptr) { return; } m_sceneViewCamera.controller.ApplyTo(*m_sceneViewCamera.gameObject->GetTransform()); } void FocusSceneView(IEditorContext& context) { Components::GameObject* target = nullptr; const uint64_t selectedEntity = context.GetSelectionManager().GetSelectedEntity(); if (selectedEntity != 0) { target = context.GetSceneManager().GetEntity(selectedEntity); } if (target != nullptr) { m_sceneViewCamera.controller.Focus(target->GetTransform()->GetPosition()); return; } const auto& roots = context.GetSceneManager().GetRootEntities(); if (roots.empty()) { m_sceneViewCamera.controller.Focus(Math::Vector3::Zero()); return; } Math::Vector3 center = Math::Vector3::Zero(); uint32_t count = 0; for (const Components::GameObject* root : roots) { if (root == nullptr) { continue; } center += root->GetTransform()->GetPosition(); ++count; } if (count > 0) { center /= static_cast(count); } m_sceneViewCamera.controller.Focus(center); } RHI::TextureDesc BuildViewportTextureDesc(uint32_t width, uint32_t height, RHI::Format format) const { RHI::TextureDesc desc = {}; desc.width = width; desc.height = height; desc.depth = 1; desc.mipLevels = 1; desc.arraySize = 1; desc.format = static_cast(format); desc.textureType = static_cast(RHI::TextureType::Texture2D); desc.sampleCount = 1; desc.sampleQuality = 0; desc.flags = 0; return desc; } RHI::ResourceViewDesc BuildViewportTextureViewDesc(RHI::Format format) const { RHI::ResourceViewDesc desc = {}; desc.format = static_cast(format); desc.dimension = RHI::ResourceViewDimension::Texture2D; return desc; } bool CreateViewportColorResources(ViewportEntry& entry) { const RHI::TextureDesc colorDesc = BuildViewportTextureDesc(entry.width, entry.height, RHI::Format::R8G8B8A8_UNorm); entry.colorTexture = m_device->CreateTexture(colorDesc); if (entry.colorTexture == nullptr) { return false; } const RHI::ResourceViewDesc colorViewDesc = BuildViewportTextureViewDesc(RHI::Format::R8G8B8A8_UNorm); entry.colorView = m_device->CreateRenderTargetView(entry.colorTexture, colorViewDesc); return entry.colorView != nullptr; } bool CreateViewportDepthResources(ViewportEntry& entry) { const RHI::TextureDesc depthDesc = BuildViewportTextureDesc(entry.width, entry.height, RHI::Format::D24_UNorm_S8_UInt); entry.depthTexture = m_device->CreateTexture(depthDesc); if (entry.depthTexture == nullptr) { return false; } const RHI::ResourceViewDesc depthViewDesc = BuildViewportTextureViewDesc(RHI::Format::D24_UNorm_S8_UInt); entry.depthView = m_device->CreateDepthStencilView(entry.depthTexture, depthViewDesc); return entry.depthView != nullptr; } bool CreateSceneViewportSelectionMaskResources(ViewportEntry& entry) { const RHI::TextureDesc selectionMaskDesc = BuildViewportTextureDesc(entry.width, entry.height, RHI::Format::R8G8B8A8_UNorm); entry.selectionMaskTexture = m_device->CreateTexture(selectionMaskDesc); if (entry.selectionMaskTexture == nullptr) { return false; } const RHI::ResourceViewDesc selectionMaskViewDesc = BuildViewportTextureViewDesc(RHI::Format::R8G8B8A8_UNorm); entry.selectionMaskView = m_device->CreateRenderTargetView( entry.selectionMaskTexture, selectionMaskViewDesc); if (entry.selectionMaskView == nullptr) { return false; } entry.selectionMaskShaderView = m_device->CreateShaderResourceView( entry.selectionMaskTexture, selectionMaskViewDesc); return entry.selectionMaskShaderView != nullptr; } bool CreateSceneViewportObjectIdResources(ViewportEntry& entry) { const RHI::TextureDesc objectIdDesc = BuildViewportTextureDesc(entry.width, entry.height, RHI::Format::R8G8B8A8_UNorm); entry.objectIdTexture = m_device->CreateTexture(objectIdDesc); if (entry.objectIdTexture == nullptr) { return false; } const RHI::ResourceViewDesc objectIdViewDesc = BuildViewportTextureViewDesc(RHI::Format::R8G8B8A8_UNorm); entry.objectIdView = m_device->CreateRenderTargetView( entry.objectIdTexture, objectIdViewDesc); return entry.objectIdView != nullptr; } bool CreateViewportTextureDescriptor(ViewportEntry& entry) { m_backend->AllocateTextureDescriptor(&entry.imguiCpuHandle, &entry.imguiGpuHandle); if (entry.imguiCpuHandle.ptr == 0 || entry.imguiGpuHandle.ptr == 0) { return false; } auto* nativeTexture = static_cast(entry.colorTexture); D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = 1; m_device->GetDevice()->CreateShaderResourceView( nativeTexture->GetResource(), &srvDesc, entry.imguiCpuHandle); entry.textureId = static_cast(entry.imguiGpuHandle.ptr); return true; } bool EnsureViewportResources(ViewportEntry& entry) { if (entry.requestedWidth == 0 || entry.requestedHeight == 0) { return false; } if (entry.width == entry.requestedWidth && entry.height == entry.requestedHeight && entry.colorTexture != nullptr && entry.colorView != nullptr && entry.depthTexture != nullptr && entry.depthView != nullptr && (entry.kind != EditorViewportKind::Scene || (entry.selectionMaskTexture != nullptr && entry.selectionMaskView != nullptr && entry.selectionMaskShaderView != nullptr && entry.objectIdTexture != nullptr && entry.objectIdView != nullptr)) && entry.textureId != ImTextureID{}) { return true; } DestroyViewportResources(entry); entry.width = entry.requestedWidth; entry.height = entry.requestedHeight; if (!CreateViewportColorResources(entry)) { DestroyViewportResources(entry); return false; } if (!CreateViewportDepthResources(entry)) { DestroyViewportResources(entry); return false; } if (entry.kind == EditorViewportKind::Scene && !CreateSceneViewportSelectionMaskResources(entry)) { DestroyViewportResources(entry); return false; } if (entry.kind == EditorViewportKind::Scene && !CreateSceneViewportObjectIdResources(entry)) { DestroyViewportResources(entry); return false; } if (!CreateViewportTextureDescriptor(entry)) { DestroyViewportResources(entry); return false; } entry.colorState = RHI::ResourceStates::Common; entry.selectionMaskState = RHI::ResourceStates::Common; entry.objectIdState = RHI::ResourceStates::Common; entry.hasValidObjectIdFrame = false; return true; } Rendering::RenderSurface BuildSurface(const ViewportEntry& entry) const { Rendering::RenderSurface surface(entry.width, entry.height); surface.SetColorAttachment(entry.colorView); surface.SetDepthAttachment(entry.depthView); surface.SetColorStateBefore(entry.colorState); surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); return surface; } Rendering::RenderSurface BuildSelectionMaskSurface(const ViewportEntry& entry) const { Rendering::RenderSurface surface(entry.width, entry.height); surface.SetColorAttachment(entry.selectionMaskView); surface.SetDepthAttachment(entry.depthView); surface.SetColorStateBefore(entry.selectionMaskState); surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); return surface; } Rendering::RenderSurface BuildObjectIdSurface(const ViewportEntry& entry) const { Rendering::RenderSurface surface(entry.width, entry.height); surface.SetColorAttachment(entry.objectIdView); surface.SetDepthAttachment(entry.depthView); surface.SetColorStateBefore(entry.objectIdState); surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); return surface; } void AddSceneSelectionMaskPass( ViewportEntry& entry, const Rendering::RenderSurface& selectionMaskSurface, const Rendering::RenderCameraData& cameraData, const std::vector& selectionRenderables, Rendering::RenderPassSequence& outPostPasses) { outPostPasses.AddPass(MakeLambdaRenderPass( "SceneSelectionMask", [this, &entry, selectionMaskSurface, &cameraData, &selectionRenderables]( const Rendering::RenderPassContext& context) mutable { context.renderContext.commandList->TransitionBarrier( entry.selectionMaskView, entry.selectionMaskState, RHI::ResourceStates::RenderTarget); entry.selectionMaskState = RHI::ResourceStates::RenderTarget; const float maskClearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; RHI::RHIResourceView* maskView = entry.selectionMaskView; context.renderContext.commandList->SetRenderTargets(1, &maskView, entry.depthView); context.renderContext.commandList->ClearRenderTarget(maskView, maskClearColor); const bool rendered = m_sceneSelectionMaskPass.Render( context.renderContext, selectionMaskSurface, cameraData, selectionRenderables); if (!rendered) { SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask pass failed"); } context.renderContext.commandList->TransitionBarrier( entry.selectionMaskView, entry.selectionMaskState, RHI::ResourceStates::PixelShaderResource); entry.selectionMaskState = RHI::ResourceStates::PixelShaderResource; return rendered; })); } void AddSceneColorToRenderTargetPass( ViewportEntry& entry, Rendering::RenderPassSequence& outPostPasses) { outPostPasses.AddPass(MakeLambdaRenderPass( "SceneColorToRenderTarget", [&entry](const Rendering::RenderPassContext& context) { context.renderContext.commandList->TransitionBarrier( entry.colorView, context.surface.GetColorStateAfter(), RHI::ResourceStates::RenderTarget); entry.colorState = RHI::ResourceStates::RenderTarget; return true; })); } void AddSceneInfiniteGridPass( ViewportEntry& entry, const SceneViewportOverlayData& overlay, Rendering::RenderPassSequence& outPostPasses) { outPostPasses.AddPass(MakeLambdaRenderPass( "SceneInfiniteGrid", [this, overlay, &entry](const Rendering::RenderPassContext& context) { const bool rendered = m_sceneGridPass.Render( context.renderContext, context.surface, overlay); if (!rendered) { SetViewportStatusIfEmpty(entry.statusText, "Scene grid pass failed"); } return rendered; })); } void AddSceneSelectionOutlinePass( ViewportEntry& entry, Rendering::RenderPassSequence& outPostPasses) { outPostPasses.AddPass(MakeLambdaRenderPass( "SceneSelectionOutline", [this, &entry](const Rendering::RenderPassContext& context) { const bool rendered = m_sceneSelectionOutlinePass.Render( context.renderContext, context.surface, entry.selectionMaskShaderView); if (!rendered) { SetViewportStatusIfEmpty(entry.statusText, "Scene selection outline pass failed"); } return rendered; })); } void AddSceneColorToShaderResourcePass( ViewportEntry& entry, Rendering::RenderPassSequence& outPostPasses) { outPostPasses.AddPass(MakeLambdaRenderPass( "SceneColorToShaderResource", [&entry](const Rendering::RenderPassContext& context) { context.renderContext.commandList->TransitionBarrier( entry.colorView, RHI::ResourceStates::RenderTarget, context.surface.GetColorStateAfter()); entry.colorState = context.surface.GetColorStateAfter(); return true; })); } void AddSceneSelectionMaskDebugPass( ViewportEntry& entry, const Rendering::RenderCameraData& cameraData, const std::vector& selectionRenderables, Rendering::RenderPassSequence& outPostPasses) { outPostPasses.AddPass(MakeLambdaRenderPass( "SceneSelectionMaskDebug", [this, &entry, &cameraData, &selectionRenderables]( const Rendering::RenderPassContext& context) { const float debugClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; RHI::RHIResourceView* colorView = entry.colorView; context.renderContext.commandList->SetRenderTargets(1, &colorView, entry.depthView); context.renderContext.commandList->ClearRenderTarget(colorView, debugClearColor); const bool rendered = m_sceneSelectionMaskPass.Render( context.renderContext, context.surface, cameraData, selectionRenderables); if (!rendered) { SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask debug pass failed"); } return rendered; })); } void AddSceneViewPostPassStep( SceneViewportPostPassStep step, ViewportEntry& entry, const SceneViewportOverlayData& overlay, const Rendering::RenderSurface& selectionMaskSurface, const Rendering::RenderCameraData& cameraData, const std::vector& selectionRenderables, Rendering::RenderPassSequence& outPostPasses) { switch (step) { case SceneViewportPostPassStep::SelectionMask: AddSceneSelectionMaskPass( entry, selectionMaskSurface, cameraData, selectionRenderables, outPostPasses); break; case SceneViewportPostPassStep::ColorToRenderTarget: AddSceneColorToRenderTargetPass(entry, outPostPasses); break; case SceneViewportPostPassStep::InfiniteGrid: AddSceneInfiniteGridPass(entry, overlay, outPostPasses); break; case SceneViewportPostPassStep::SelectionOutline: AddSceneSelectionOutlinePass(entry, outPostPasses); break; case SceneViewportPostPassStep::ColorToShaderResource: AddSceneColorToShaderResourcePass(entry, outPostPasses); break; case SceneViewportPostPassStep::SelectionMaskDebug: AddSceneSelectionMaskDebugPass( entry, cameraData, selectionRenderables, outPostPasses); break; default: break; } } bool BuildSceneViewPostPassSequence( ViewportEntry& entry, const SceneViewportOverlayData& overlay, const Rendering::RenderCameraData& cameraData, const std::vector& selectionRenderables, Rendering::RenderPassSequence& outPostPasses) { const bool hasSelection = !selectionRenderables.empty(); const bool hasSelectionMaskRenderTarget = entry.selectionMaskView != nullptr; const bool hasSelectionMaskShaderView = entry.selectionMaskShaderView != nullptr; if (hasSelection && !kDebugSceneSelectionMask && (!hasSelectionMaskRenderTarget || !hasSelectionMaskShaderView)) { SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask target is unavailable"); } const SceneViewportPostPassPlan plan = BuildSceneViewportPostPassPlan({ overlay.valid, hasSelection, kDebugSceneSelectionMask, hasSelectionMaskRenderTarget, hasSelectionMaskShaderView }); if (!plan.valid) { return false; } Rendering::RenderSurface selectionMaskSurface = {}; if (plan.usesSelectionMaskSurface) { selectionMaskSurface = BuildSelectionMaskSurface(entry); } for (const SceneViewportPostPassStep step : plan.steps) { AddSceneViewPostPassStep( step, entry, overlay, selectionMaskSurface, cameraData, selectionRenderables, outPostPasses); } return true; } void BuildSceneViewportRenderState( ViewportEntry& entry, IEditorContext& context, const Components::Scene& scene, SceneViewportRenderState& outState) { outState.overlay = GetSceneViewOverlayData(); if (!outState.overlay.valid) { return; } outState.selectionRenderables = CollectSceneViewportSelectionRenderables( scene, context.GetSelectionManager().GetSelectedEntities(), outState.overlay.cameraPosition); if (!outState.selectionRenderables.empty()) { outState.cameraData = BuildSceneViewportCameraData( *m_sceneViewCamera.camera, entry.width, entry.height); } BuildSceneViewPostPassSequence( entry, outState.overlay, outState.cameraData, outState.selectionRenderables, outState.postPasses); } bool RenderSceneViewportEntry( ViewportEntry& entry, IEditorContext& context, const Components::Scene* scene, const Rendering::RenderContext& renderContext, Rendering::RenderSurface& surface) { surface.SetClearColorOverride(Math::Color(0.27f, 0.27f, 0.27f, 1.0f)); if (!EnsureSceneViewCamera()) { entry.statusText = "Scene view camera is unavailable"; ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f); entry.hasValidObjectIdFrame = false; return false; } ApplySceneViewCameraController(); entry.statusText.clear(); if (scene == nullptr) { entry.statusText = "No active scene"; ClearViewport(entry, renderContext, 0.07f, 0.08f, 0.10f, 1.0f); entry.hasValidObjectIdFrame = false; return false; } SceneViewportRenderState sceneState = {}; BuildSceneViewportRenderState(entry, context, *scene, sceneState); std::vector requests = m_sceneRenderer->BuildRenderRequests(*scene, m_sceneViewCamera.camera, renderContext, surface); if (requests.empty()) { SetViewportStatusIfEmpty(entry.statusText, "Scene renderer failed"); ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f); entry.hasValidObjectIdFrame = false; return false; } if (sceneState.postPasses.GetPassCount() > 0) { requests[0].postScenePasses = &sceneState.postPasses; } if (entry.objectIdView != nullptr) { requests[0].objectId.surface = BuildObjectIdSurface(entry); requests[0].objectId.surface.SetRenderArea(requests[0].surface.GetRenderArea()); } if (!m_sceneRenderer->Render(requests)) { SetViewportStatusIfEmpty(entry.statusText, "Scene renderer failed"); ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f); entry.hasValidObjectIdFrame = false; return false; } entry.colorState = RHI::ResourceStates::PixelShaderResource; entry.objectIdState = RHI::ResourceStates::PixelShaderResource; entry.hasValidObjectIdFrame = requests[0].objectId.IsRequested(); return true; } bool RenderGameViewportEntry( ViewportEntry& entry, const Components::Scene* scene, const Rendering::RenderContext& renderContext, const Rendering::RenderSurface& surface) { if (scene == nullptr) { entry.statusText = "No active scene"; ClearViewport(entry, renderContext, 0.07f, 0.08f, 0.10f, 1.0f); return false; } const auto cameras = scene->FindObjectsOfType(); if (cameras.empty()) { entry.statusText = "No camera in scene"; ClearViewport(entry, renderContext, 0.10f, 0.09f, 0.08f, 1.0f); return false; } if (!m_sceneRenderer->Render(*scene, nullptr, renderContext, surface)) { entry.statusText = "Scene renderer failed"; ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f); entry.hasValidObjectIdFrame = false; return false; } entry.colorState = RHI::ResourceStates::PixelShaderResource; entry.statusText.clear(); return true; } void RenderViewportEntry( ViewportEntry& entry, IEditorContext& context, const Components::Scene* scene, const Rendering::RenderContext& renderContext) { if (entry.colorView == nullptr || entry.depthView == nullptr) { entry.statusText = "Viewport render target is unavailable"; return; } Rendering::RenderSurface surface = BuildSurface(entry); if (entry.kind == EditorViewportKind::Scene) { RenderSceneViewportEntry(entry, context, scene, renderContext, surface); return; } RenderGameViewportEntry(entry, scene, renderContext, surface); } void ClearViewport( ViewportEntry& entry, const Rendering::RenderContext& renderContext, float r, float g, float b, float a) { RHI::RHICommandList* commandList = renderContext.commandList; if (commandList == nullptr) { entry.statusText = "Viewport command list is unavailable"; return; } const float clearColor[4] = { r, g, b, a }; RHI::RHIResourceView* colorView = entry.colorView; commandList->TransitionBarrier( colorView, entry.colorState, RHI::ResourceStates::RenderTarget); commandList->SetRenderTargets(1, &colorView, entry.depthView); commandList->ClearRenderTarget(colorView, clearColor); commandList->ClearDepthStencil(entry.depthView, 1.0f, 0); commandList->TransitionBarrier( colorView, RHI::ResourceStates::RenderTarget, RHI::ResourceStates::PixelShaderResource); entry.colorState = RHI::ResourceStates::PixelShaderResource; entry.hasValidObjectIdFrame = false; } bool TryPickSceneViewEntityWithObjectId( ViewportEntry& entry, const ImVec2& viewportSize, const ImVec2& viewportMousePosition, uint64_t& outEntityId) { outEntityId = 0; if (m_device == nullptr || m_sceneViewLastRenderContext.commandQueue == nullptr || entry.objectIdTexture == nullptr || !entry.hasValidObjectIdFrame || viewportSize.x <= 1.0f || viewportSize.y <= 1.0f || viewportMousePosition.x < 0.0f || viewportMousePosition.y < 0.0f || viewportMousePosition.x > viewportSize.x || viewportMousePosition.y > viewportSize.y) { return false; } auto* commandQueue = m_sceneViewLastRenderContext.commandQueue; if (commandQueue == nullptr) { return false; } const uint32_t pixelX = ClampViewportPixelCoordinate(viewportMousePosition.x, entry.width); const uint32_t pixelY = ClampViewportPixelCoordinate(viewportMousePosition.y, entry.height); std::array rgba = {}; if (!m_device->ReadTexturePixelRGBA8( commandQueue, entry.objectIdTexture, entry.objectIdState, pixelX, pixelY, rgba)) { return false; } outEntityId = static_cast(Rendering::DecodeObjectIdFromColor( rgba[0], rgba[1], rgba[2], rgba[3])); return true; } void DestroyViewportResources(ViewportEntry& entry) { if (m_backend != nullptr && entry.imguiCpuHandle.ptr != 0) { m_backend->FreeTextureDescriptor(entry.imguiCpuHandle, entry.imguiGpuHandle); } if (entry.selectionMaskShaderView != nullptr) { entry.selectionMaskShaderView->Shutdown(); delete entry.selectionMaskShaderView; entry.selectionMaskShaderView = nullptr; } if (entry.selectionMaskView != nullptr) { entry.selectionMaskView->Shutdown(); delete entry.selectionMaskView; entry.selectionMaskView = nullptr; } if (entry.selectionMaskTexture != nullptr) { entry.selectionMaskTexture->Shutdown(); delete entry.selectionMaskTexture; entry.selectionMaskTexture = nullptr; } if (entry.objectIdView != nullptr) { entry.objectIdView->Shutdown(); delete entry.objectIdView; entry.objectIdView = nullptr; } if (entry.objectIdTexture != nullptr) { entry.objectIdTexture->Shutdown(); delete entry.objectIdTexture; entry.objectIdTexture = nullptr; } if (entry.depthView != nullptr) { entry.depthView->Shutdown(); delete entry.depthView; entry.depthView = nullptr; } if (entry.depthTexture != nullptr) { entry.depthTexture->Shutdown(); delete entry.depthTexture; entry.depthTexture = nullptr; } if (entry.colorView != nullptr) { entry.colorView->Shutdown(); delete entry.colorView; entry.colorView = nullptr; } if (entry.colorTexture != nullptr) { entry.colorTexture->Shutdown(); delete entry.colorTexture; entry.colorTexture = nullptr; } entry.width = 0; entry.height = 0; entry.imguiCpuHandle = {}; entry.imguiGpuHandle = {}; entry.textureId = {}; entry.colorState = RHI::ResourceStates::Common; entry.selectionMaskState = RHI::ResourceStates::Common; entry.objectIdState = RHI::ResourceStates::Common; entry.hasValidObjectIdFrame = false; } UI::ImGuiBackendBridge* m_backend = nullptr; RHI::D3D12Device* m_device = nullptr; std::unique_ptr m_sceneRenderer; Rendering::RenderContext m_sceneViewLastRenderContext = {}; std::array m_entries = {}; SceneViewCameraState m_sceneViewCamera; SceneViewportInfiniteGridPass m_sceneGridPass; SceneViewportSelectionMaskPass m_sceneSelectionMaskPass; SceneViewportSelectionOutlinePass m_sceneSelectionOutlinePass; }; } // namespace Editor } // namespace XCEngine