Files
XCEngine/editor/src/Viewport/ViewportHostService.h

990 lines
35 KiB
C
Raw Normal View History

2026-03-28 17:04:14 +08:00
#pragma once
#include "Core/IEditorContext.h"
#include "Core/ISceneManager.h"
#include "Core/ISelectionManager.h"
2026-03-28 17:04:14 +08:00
#include "IViewportHostService.h"
#include "SceneViewportPicker.h"
#include "SceneViewportCameraController.h"
#include "ViewportHostSurfaceUtils.h"
2026-03-28 17:04:14 +08:00
#include "UI/ImGuiBackendBridge.h"
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/RHI/RHIDevice.h>
2026-03-28 17:04:14 +08:00
#include <XCEngine/RHI/RHIEnums.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHITexture.h>
2026-04-01 16:44:11 +08:00
#include <XCEngine/Rendering/ObjectIdEncoding.h>
#include <XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h>
#include <XCEngine/Rendering/Passes/BuiltinSceneViewPostPassPlan.h>
#include <XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h>
2026-03-28 17:04:14 +08:00
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/SceneRenderer.h>
#include <XCEngine/Scene/Scene.h>
2026-04-01 16:44:11 +08:00
#include <algorithm>
2026-03-28 17:04:14 +08:00
#include <array>
2026-04-01 16:44:11 +08:00
#include <cmath>
2026-03-28 17:04:14 +08:00
#include <cstdint>
2026-04-01 16:44:11 +08:00
#include <cstring>
#include <functional>
2026-03-28 17:04:14 +08:00
#include <memory>
#include <string>
#include <utility>
2026-03-28 17:04:14 +08:00
namespace XCEngine {
namespace Editor {
namespace {
constexpr bool kDebugSceneSelectionMask = false;
class LambdaRenderPass final : public Rendering::RenderPass {
public:
using ExecuteCallback = std::function<bool(const Rendering::RenderPassContext&)>;
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 <typename Callback>
std::unique_ptr<Rendering::RenderPass> MakeLambdaRenderPass(const char* name, Callback&& callback) {
return std::make_unique<LambdaRenderPass>(
name,
LambdaRenderPass::ExecuteCallback(std::forward<Callback>(callback)));
}
inline void SetViewportStatusIfEmpty(std::string& statusText, const char* message) {
if (statusText.empty()) {
statusText = message;
}
}
Rendering::Passes::InfiniteGridPassData BuildInfiniteGridPassData(
const SceneViewportOverlayData& overlay) {
Rendering::Passes::InfiniteGridPassData data = {};
data.valid = overlay.valid;
data.cameraPosition = overlay.cameraPosition;
data.cameraForward = overlay.cameraForward;
data.cameraRight = overlay.cameraRight;
data.cameraUp = overlay.cameraUp;
data.verticalFovDegrees = overlay.verticalFovDegrees;
data.nearClipPlane = overlay.nearClipPlane;
data.farClipPlane = overlay.farClipPlane;
data.orbitDistance = overlay.orbitDistance;
return data;
}
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
2026-03-28 17:04:14 +08:00
class ViewportHostService : public IViewportHostService {
public:
void Initialize(UI::ImGuiBackendBridge& backend, RHI::RHIDevice* device) {
Shutdown();
m_backend = &backend;
m_device = device;
2026-03-28 17:04:14 +08:00
}
void Shutdown() {
for (ViewportEntry& entry : m_entries) {
DestroyViewportResources(entry);
entry = {};
}
m_sceneViewCamera = {};
2026-04-01 16:44:11 +08:00
m_sceneViewLastRenderContext = {};
2026-03-28 17:04:14 +08:00
m_device = nullptr;
m_backend = nullptr;
m_sceneGridPass.Shutdown();
m_sceneSelectionOutlinePass.Shutdown();
2026-03-28 17:04:14 +08:00
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<uint32_t>(requestedSize.x)
: 0u;
entry.requestedHeight = entry.requestedThisFrame
? static_cast<uint32_t>(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<float>(entry.width), static_cast<float>(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;
2026-03-28 18:28:11 +08:00
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;
}
2026-04-01 16:44:11 +08:00
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();
}
2026-03-28 17:50:54 +08:00
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;
}
2026-03-28 17:04:14 +08:00
void RenderRequestedViewports(
IEditorContext& context,
const Rendering::RenderContext& renderContext) override {
if (m_backend == nullptr || m_device == nullptr || !renderContext.IsValid()) {
return;
}
EnsureSceneRenderer();
2026-04-01 16:44:11 +08:00
m_sceneViewLastRenderContext = renderContext;
2026-03-28 17:04:14 +08:00
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);
2026-03-28 17:04:14 +08:00
}
}
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;
2026-04-01 16:44:11 +08:00
RHI::RHITexture* objectIdTexture = nullptr;
RHI::RHIResourceView* objectIdView = nullptr;
RHI::RHIResourceView* objectIdShaderView = nullptr;
2026-03-28 17:04:14 +08:00
D3D12_CPU_DESCRIPTOR_HANDLE imguiCpuHandle = {};
D3D12_GPU_DESCRIPTOR_HANDLE imguiGpuHandle = {};
ImTextureID textureId = {};
RHI::ResourceStates colorState = RHI::ResourceStates::Common;
2026-04-01 16:44:11 +08:00
RHI::ResourceStates objectIdState = RHI::ResourceStates::Common;
bool hasValidObjectIdFrame = false;
2026-03-28 17:04:14 +08:00
std::string statusText;
};
struct SceneViewCameraState {
std::unique_ptr<Components::GameObject> gameObject;
Components::CameraComponent* camera = nullptr;
SceneViewportCameraController controller;
};
struct SceneViewportRenderState {
SceneViewportOverlayData overlay = {};
Rendering::RenderPassSequence postPasses;
std::vector<uint64_t> selectedObjectIds;
};
2026-03-28 17:04:14 +08:00
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<Rendering::SceneRenderer>();
}
}
bool EnsureSceneViewCamera() {
if (m_sceneViewCamera.gameObject != nullptr && m_sceneViewCamera.camera != nullptr) {
return true;
}
m_sceneViewCamera.gameObject = std::make_unique<Components::GameObject>("EditorSceneCamera");
m_sceneViewCamera.camera = m_sceneViewCamera.gameObject->AddComponent<Components::CameraComponent>();
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<float>(count);
}
m_sceneViewCamera.controller.Focus(center);
}
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;
}
2026-04-01 16:44:11 +08:00
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);
if (entry.objectIdView == nullptr) {
return false;
}
entry.objectIdShaderView = m_device->CreateShaderResourceView(
entry.objectIdTexture,
objectIdViewDesc);
return entry.objectIdShaderView != nullptr;
2026-04-01 16:44:11 +08:00
}
bool CreateViewportTextureDescriptor(ViewportEntry& entry) {
return m_backend->CreateTextureDescriptor(
m_device,
entry.colorTexture,
&entry.imguiCpuHandle,
&entry.imguiGpuHandle,
&entry.textureId);
}
2026-03-28 17:04:14 +08:00
bool EnsureViewportResources(ViewportEntry& entry) {
ViewportHostResourceReuseQuery reuseQuery = {};
reuseQuery.kind = entry.kind;
reuseQuery.width = entry.width;
reuseQuery.height = entry.height;
reuseQuery.requestedWidth = entry.requestedWidth;
reuseQuery.requestedHeight = entry.requestedHeight;
reuseQuery.resources.hasColorTexture = entry.colorTexture != nullptr;
reuseQuery.resources.hasColorView = entry.colorView != nullptr;
reuseQuery.resources.hasDepthTexture = entry.depthTexture != nullptr;
reuseQuery.resources.hasDepthView = entry.depthView != nullptr;
reuseQuery.resources.hasObjectIdTexture = entry.objectIdTexture != nullptr;
reuseQuery.resources.hasObjectIdView = entry.objectIdView != nullptr;
reuseQuery.resources.hasObjectIdShaderView = entry.objectIdShaderView != nullptr;
reuseQuery.resources.hasTextureDescriptor = entry.textureId != ImTextureID{};
if (CanReuseViewportResources(reuseQuery)) {
return true;
2026-03-28 17:04:14 +08:00
}
if (entry.requestedWidth == 0 || entry.requestedHeight == 0) {
return false;
2026-03-28 17:04:14 +08:00
}
DestroyViewportResources(entry);
entry.width = entry.requestedWidth;
entry.height = entry.requestedHeight;
if (!CreateViewportColorResources(entry)) {
2026-03-28 17:04:14 +08:00
DestroyViewportResources(entry);
return false;
}
if (!CreateViewportDepthResources(entry)) {
2026-03-28 17:04:14 +08:00
DestroyViewportResources(entry);
return false;
}
if (ViewportRequiresObjectIdResources(entry.kind) &&
2026-04-01 16:44:11 +08:00
!CreateSceneViewportObjectIdResources(entry)) {
DestroyViewportResources(entry);
return false;
}
if (!CreateViewportTextureDescriptor(entry)) {
2026-03-28 17:04:14 +08:00
DestroyViewportResources(entry);
return false;
}
entry.colorState = RHI::ResourceStates::Common;
2026-04-01 16:44:11 +08:00
entry.objectIdState = RHI::ResourceStates::Common;
entry.hasValidObjectIdFrame = false;
2026-03-28 17:04:14 +08:00
return true;
}
Rendering::RenderSurface BuildSurface(const ViewportEntry& entry) const {
return BuildViewportRenderSurface(
entry.width,
entry.height,
entry.colorView,
entry.depthView,
entry.colorState);
}
2026-04-01 16:44:11 +08:00
Rendering::RenderSurface BuildObjectIdSurface(const ViewportEntry& entry) const {
return BuildViewportRenderSurface(
entry.width,
entry.height,
entry.objectIdView,
entry.depthView,
entry.objectIdState);
2026-04-01 16:44:11 +08:00
}
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,
BuildInfiniteGridPassData(overlay));
if (!rendered) {
SetViewportStatusIfEmpty(entry.statusText, "Scene grid pass failed");
}
return rendered;
}));
}
void AddSceneSelectionOutlinePass(
ViewportEntry& entry,
const std::vector<uint64_t>& selectedObjectIds,
Rendering::RenderPassSequence& outPostPasses) {
outPostPasses.AddPass(MakeLambdaRenderPass(
"SceneSelectionOutline",
[this, &entry, selectedObjectIds](const Rendering::RenderPassContext& context) {
const bool rendered = m_sceneSelectionOutlinePass.Render(
context.renderContext,
context.surface,
entry.objectIdShaderView,
selectedObjectIds,
Rendering::Passes::ObjectIdOutlineStyle{
Math::Color(1.0f, 0.4f, 0.0f, 1.0f),
2.0f,
false
});
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 std::vector<uint64_t>& selectedObjectIds,
Rendering::RenderPassSequence& outPostPasses) {
outPostPasses.AddPass(MakeLambdaRenderPass(
"SceneSelectionMaskDebug",
[this, &entry, selectedObjectIds](
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_sceneSelectionOutlinePass.Render(
context.renderContext,
context.surface,
entry.objectIdShaderView,
selectedObjectIds,
Rendering::Passes::ObjectIdOutlineStyle{
Math::Color(1.0f, 0.4f, 0.0f, 1.0f),
2.0f,
true
});
if (!rendered) {
SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask debug pass failed");
}
return rendered;
}));
}
void AddSceneViewPostPassStep(
Rendering::Passes::BuiltinSceneViewPostPassStep step,
ViewportEntry& entry,
const SceneViewportOverlayData& overlay,
const std::vector<uint64_t>& selectedObjectIds,
Rendering::RenderPassSequence& outPostPasses) {
switch (step) {
case Rendering::Passes::BuiltinSceneViewPostPassStep::ColorToRenderTarget:
AddSceneColorToRenderTargetPass(entry, outPostPasses);
break;
case Rendering::Passes::BuiltinSceneViewPostPassStep::InfiniteGrid:
AddSceneInfiniteGridPass(entry, overlay, outPostPasses);
break;
case Rendering::Passes::BuiltinSceneViewPostPassStep::SelectionOutline:
AddSceneSelectionOutlinePass(entry, selectedObjectIds, outPostPasses);
break;
case Rendering::Passes::BuiltinSceneViewPostPassStep::ColorToShaderResource:
AddSceneColorToShaderResourcePass(entry, outPostPasses);
break;
case Rendering::Passes::BuiltinSceneViewPostPassStep::SelectionMaskDebug:
AddSceneSelectionMaskDebugPass(
entry,
selectedObjectIds,
outPostPasses);
break;
default:
break;
}
}
bool BuildSceneViewPostPassSequence(
ViewportEntry& entry,
const SceneViewportOverlayData& overlay,
const std::vector<uint64_t>& selectedObjectIds,
Rendering::RenderPassSequence& outPostPasses) {
const bool hasSelection = !selectedObjectIds.empty();
const bool hasObjectIdShaderView = entry.objectIdShaderView != nullptr;
if (hasSelection &&
!kDebugSceneSelectionMask &&
!hasObjectIdShaderView) {
SetViewportStatusIfEmpty(entry.statusText, "Scene object id shader view is unavailable");
}
const Rendering::Passes::BuiltinSceneViewPostPassPlan plan =
Rendering::Passes::BuildBuiltinSceneViewPostPassPlan({
overlay.valid,
hasSelection,
kDebugSceneSelectionMask,
hasObjectIdShaderView
});
if (!plan.valid) {
return false;
}
for (const Rendering::Passes::BuiltinSceneViewPostPassStep step : plan.steps) {
AddSceneViewPostPassStep(
step,
entry,
overlay,
selectedObjectIds,
outPostPasses);
}
return true;
}
void BuildSceneViewportRenderState(
2026-03-28 17:04:14 +08:00
ViewportEntry& entry,
IEditorContext& context,
const Components::Scene& scene,
SceneViewportRenderState& outState) {
(void)scene;
outState.overlay = GetSceneViewOverlayData();
if (!outState.overlay.valid) {
2026-03-28 17:04:14 +08:00
return;
}
outState.selectedObjectIds = context.GetSelectionManager().GetSelectedEntities();
BuildSceneViewPostPassSequence(
entry,
outState.overlay,
outState.selectedObjectIds,
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);
2026-04-01 16:44:11 +08:00
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);
2026-04-01 16:44:11 +08:00
entry.hasValidObjectIdFrame = false;
return false;
}
SceneViewportRenderState sceneState = {};
BuildSceneViewportRenderState(entry, context, *scene, sceneState);
std::vector<Rendering::CameraRenderRequest> 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);
2026-04-01 16:44:11 +08:00
entry.hasValidObjectIdFrame = false;
return false;
}
if (sceneState.postPasses.GetPassCount() > 0) {
requests[0].postScenePasses = &sceneState.postPasses;
}
2026-04-01 16:44:11 +08:00
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);
2026-04-01 16:44:11 +08:00
entry.hasValidObjectIdFrame = false;
return false;
}
entry.colorState = RHI::ResourceStates::PixelShaderResource;
2026-04-01 16:44:11 +08:00
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;
}
2026-03-28 17:04:14 +08:00
const auto cameras = scene->FindObjectsOfType<Components::CameraComponent>();
if (cameras.empty()) {
entry.statusText = "No camera in scene";
ClearViewport(entry, renderContext, 0.10f, 0.09f, 0.08f, 1.0f);
return false;
2026-03-28 17:04:14 +08:00
}
if (!m_sceneRenderer->Render(*scene, nullptr, renderContext, surface)) {
entry.statusText = "Scene renderer failed";
ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f);
2026-04-01 16:44:11 +08:00
entry.hasValidObjectIdFrame = false;
return false;
2026-03-28 17:04:14 +08:00
}
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);
2026-03-28 17:04:14 +08:00
}
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;
2026-04-01 16:44:11 +08:00
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) {
2026-04-01 16:44:11 +08:00
return false;
}
const uint32_t pixelX = ClampViewportPixelCoordinate(viewportMousePosition.x, entry.width);
const uint32_t pixelY = ClampViewportPixelCoordinate(viewportMousePosition.y, entry.height);
std::array<uint8_t, 4> rgba = {};
if (!m_device->ReadTexturePixelRGBA8(
commandQueue,
entry.objectIdTexture,
2026-04-01 16:44:11 +08:00
entry.objectIdState,
pixelX,
pixelY,
rgba)) {
return false;
}
outEntityId = static_cast<uint64_t>(Rendering::DecodeObjectIdFromColor(
rgba[0],
rgba[1],
rgba[2],
rgba[3]));
return true;
2026-03-28 17:04:14 +08:00
}
void DestroyViewportResources(ViewportEntry& entry) {
if (m_backend != nullptr && entry.imguiCpuHandle.ptr != 0) {
m_backend->FreeTextureDescriptor(entry.imguiCpuHandle, entry.imguiGpuHandle);
}
2026-04-01 16:44:11 +08:00
if (entry.objectIdView != nullptr) {
entry.objectIdView->Shutdown();
delete entry.objectIdView;
entry.objectIdView = nullptr;
}
if (entry.objectIdShaderView != nullptr) {
entry.objectIdShaderView->Shutdown();
delete entry.objectIdShaderView;
entry.objectIdShaderView = nullptr;
}
2026-04-01 16:44:11 +08:00
if (entry.objectIdTexture != nullptr) {
entry.objectIdTexture->Shutdown();
delete entry.objectIdTexture;
entry.objectIdTexture = nullptr;
}
2026-03-28 17:04:14 +08:00
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;
2026-04-01 16:44:11 +08:00
entry.objectIdState = RHI::ResourceStates::Common;
entry.hasValidObjectIdFrame = false;
2026-03-28 17:04:14 +08:00
}
UI::ImGuiBackendBridge* m_backend = nullptr;
RHI::RHIDevice* m_device = nullptr;
2026-03-28 17:04:14 +08:00
std::unique_ptr<Rendering::SceneRenderer> m_sceneRenderer;
2026-04-01 16:44:11 +08:00
Rendering::RenderContext m_sceneViewLastRenderContext = {};
2026-03-28 17:04:14 +08:00
std::array<ViewportEntry, 2> m_entries = {};
SceneViewCameraState m_sceneViewCamera;
Rendering::Passes::BuiltinInfiniteGridPass m_sceneGridPass;
Rendering::Passes::BuiltinObjectIdOutlinePass m_sceneSelectionOutlinePass;
2026-03-28 17:04:14 +08:00
};
} // namespace Editor
} // namespace XCEngine