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

765 lines
27 KiB
C++

#pragma once
#include "Core/IEditorContext.h"
#include "Core/ISceneManager.h"
#include "Core/ISelectionManager.h"
#include "IViewportHostService.h"
#include "SceneViewportCameraController.h"
#include "SceneViewportEditorOverlayData.h"
#include "SceneViewportOverlayHandleBuilder.h"
#include "SceneViewportOverlayFrameCache.h"
#include "SceneViewportOverlayBuilder.h"
#include "SceneViewportRenderPassBundle.h"
#include "SceneViewportRenderPlan.h"
#include "ViewportHostRenderFlowUtils.h"
#include "ViewportHostRenderTargets.h"
#include "ViewportObjectIdPicker.h"
#include "UI/ImGuiBackendBridge.h"
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/RHI/RHIDevice.h>
#include <XCEngine/RHI/RHIEnums.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Scene/Scene.h>
#include <array>
#include <chrono>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace XCEngine {
namespace Editor {
namespace {
constexpr bool kDebugSceneSelectionMask = false;
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();
}
}
std::uint64_t GetCurrentSceneLoadStatusTimeMs() {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
}
std::uint64_t ResolveSceneLoadStatusElapsedMs(const SceneLoadProgressSnapshot& status) {
if (!status.HasValue() || status.startedAtMs == 0) {
return 0;
}
if (status.inProgress) {
const std::uint64_t nowMs = GetCurrentSceneLoadStatusTimeMs();
return nowMs >= status.startedAtMs ? nowMs - status.startedAtMs : 0;
}
if (status.streamingCompletedAtMs >= status.startedAtMs && status.streamingCompletedAtMs != 0) {
return status.streamingCompletedAtMs - status.startedAtMs;
}
if (status.firstFrameAtMs >= status.startedAtMs && status.firstFrameAtMs != 0) {
return status.firstFrameAtMs - status.startedAtMs;
}
if (status.structureReadyAtMs >= status.startedAtMs && status.structureReadyAtMs != 0) {
return status.structureReadyAtMs - status.startedAtMs;
}
return 0;
}
std::string BuildViewportSceneLoadStatusText(const SceneLoadProgressSnapshot& status) {
if (!status.HasValue() || !status.inProgress || status.message.empty()) {
return {};
}
std::string text = status.message;
const std::uint64_t elapsedMs = ResolveSceneLoadStatusElapsedMs(status);
if (elapsedMs > 0) {
text += " (";
text += std::to_string(elapsedMs);
text += " ms)";
}
return text;
}
} // namespace
class ViewportHostService : public IViewportHostService {
public:
void Initialize(UI::ImGuiBackendBridge& backend, RHI::RHIDevice* device) {
Shutdown();
m_backend = &backend;
m_device = device;
}
void Shutdown() {
for (ViewportEntry& entry : m_entries) {
DestroyViewportRenderTargets(m_backend, entry.renderTargets);
entry = {};
}
m_sceneViewportRenderPassBundle.Shutdown();
m_sceneViewCamera = {};
ResetSceneViewportOverlayFrameCacheState(m_sceneViewEditorOverlayFrameCache);
m_sceneViewTransformGizmoOverlayState = {};
m_sceneViewTransformGizmoOverlayDirty = false;
m_sceneViewLastRenderContext = {};
m_device = nullptr;
m_backend = nullptr;
m_sceneRenderer.reset();
}
void BeginFrame() override {
for (ViewportEntry& entry : m_entries) {
entry.requestedThisFrame = false;
entry.requestedWidth = 0;
entry.requestedHeight = 0;
}
m_sceneViewTransformGizmoOverlayState = {};
m_sceneViewTransformGizmoOverlayDirty = true;
}
EditorViewportFrame RequestViewport(
EditorViewportKind kind,
const ::XCEngine::UI::UISize& requestedSize) override {
ViewportEntry& entry = GetEntry(kind);
entry.requestedThisFrame = requestedSize.width > 1.0f && requestedSize.height > 1.0f;
entry.requestedWidth = entry.requestedThisFrame
? static_cast<uint32_t>(requestedSize.width)
: 0u;
entry.requestedHeight = entry.requestedThisFrame
? static_cast<uint32_t>(requestedSize.height)
: 0u;
if (entry.requestedThisFrame && m_backend != nullptr && m_device != nullptr) {
EnsureViewportResources(entry);
}
EditorViewportFrame frame = {};
frame.texture = entry.renderTargets.textureHandle;
frame.requestedSize = requestedSize;
frame.renderSize = ::XCEngine::UI::UISize(
static_cast<float>(entry.renderTargets.width),
static_cast<float>(entry.renderTargets.height));
frame.hasTexture = entry.renderTargets.textureHandle.IsValid();
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.height;
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 ::XCEngine::UI::UISize& viewportSize,
const ::XCEngine::UI::UIPoint& viewportMousePosition) override {
if (!EnsureSceneViewCamera()) {
return 0;
}
if (context.GetSceneManager().GetScene() == nullptr) {
return 0;
}
ViewportEntry& entry = GetEntry(EditorViewportKind::Scene);
const ViewportObjectIdPickResult objectIdPick =
PickSceneViewEntityWithObjectId(
entry,
viewportSize,
viewportMousePosition);
if (objectIdPick.status == ViewportObjectIdPickStatus::ReadbackFailed) {
SetViewportStatusIfEmpty(entry.statusText, "Scene object id readback failed");
}
return objectIdPick.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;
}
const SceneViewportOverlayFrameData& GetSceneViewEditorOverlayFrameData(IEditorContext& context) override {
EnsureSceneViewEditorOverlayFrameData(context);
return m_sceneViewEditorOverlayFrameCache.frameData;
}
void SetSceneViewTransformGizmoOverlayState(
const SceneViewportTransformGizmoOverlayState& state) override {
m_sceneViewTransformGizmoOverlayState = state;
m_sceneViewTransformGizmoOverlayDirty = true;
}
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 requestedWidth = 0;
uint32_t requestedHeight = 0;
bool requestedThisFrame = false;
ViewportRenderTargets renderTargets = {};
std::string statusText;
};
struct SceneViewCameraState {
std::unique_ptr<Components::GameObject> gameObject;
Components::CameraComponent* camera = nullptr;
SceneViewportCameraController controller;
};
struct SceneViewportRenderState {
SceneViewportOverlayData overlay = {};
SceneViewportRenderPlan renderPlan = {};
};
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 EnsureViewportResources(ViewportEntry& entry) {
const ViewportHostResourceReuseQuery reuseQuery =
BuildViewportRenderTargetsReuseQuery(
entry.kind,
entry.renderTargets,
entry.requestedWidth,
entry.requestedHeight);
if (CanReuseViewportResources(reuseQuery)) {
return true;
}
if (entry.requestedWidth == 0 || entry.requestedHeight == 0) {
return false;
}
return CreateViewportRenderTargets(
entry.kind,
entry.requestedWidth,
entry.requestedHeight,
m_device,
m_backend,
entry.renderTargets);
}
Rendering::RenderSurface BuildSurface(const ViewportEntry& entry) const {
return BuildViewportColorSurface(entry.renderTargets);
}
void ResolveSceneViewEditorOverlayViewportSize(
const ViewportEntry& entry,
uint32_t& outWidth,
uint32_t& outHeight) const {
ResolveSceneViewportOverlayFrameViewportSize(
entry.requestedWidth,
entry.requestedHeight,
entry.renderTargets.width,
entry.renderTargets.height,
outWidth,
outHeight);
}
bool ShouldRebuildSceneViewEditorOverlayFrameData(
const Components::Scene* scene,
const SceneViewportOverlayData& overlay,
uint32_t viewportWidth,
uint32_t viewportHeight,
const std::vector<uint64_t>& selectedObjectIds,
uint64_t contentSignature) const {
return ShouldRebuildSceneViewportOverlayFrameCache(
m_sceneViewEditorOverlayFrameCache,
scene,
overlay,
viewportWidth,
viewportHeight,
selectedObjectIds,
contentSignature,
m_sceneViewTransformGizmoOverlayDirty);
}
void EnsureSceneViewEditorOverlayFrameData(IEditorContext& context) {
if (!EnsureSceneViewCamera()) {
ResetSceneViewportOverlayFrameCacheState(m_sceneViewEditorOverlayFrameCache);
return;
}
const ViewportEntry& entry = GetEntry(EditorViewportKind::Scene);
uint32_t viewportWidth = 0u;
uint32_t viewportHeight = 0u;
ResolveSceneViewEditorOverlayViewportSize(entry, viewportWidth, viewportHeight);
const Components::Scene* scene = context.GetSceneManager().GetScene();
const SceneViewportOverlayData overlay = GetSceneViewOverlayData();
const std::vector<uint64_t> selectedObjectIds = context.GetSelectionManager().GetSelectedEntities();
const uint64_t contentSignature =
BuildSceneViewportOverlayContentSignature(scene, selectedObjectIds);
if (!ShouldRebuildSceneViewEditorOverlayFrameData(
scene,
overlay,
viewportWidth,
viewportHeight,
selectedObjectIds,
contentSignature)) {
return;
}
SceneViewportOverlayFrameData frameData = {};
frameData.overlay = overlay;
if (scene != nullptr && overlay.valid && viewportWidth > 0u && viewportHeight > 0u) {
frameData = m_sceneViewportOverlayBuilder.Build(
context,
overlay,
viewportWidth,
viewportHeight,
selectedObjectIds,
&m_sceneViewTransformGizmoOverlayState);
}
UpdateSceneViewportOverlayFrameCacheState(
m_sceneViewEditorOverlayFrameCache,
scene,
viewportWidth,
viewportHeight,
selectedObjectIds,
contentSignature,
frameData);
m_sceneViewTransformGizmoOverlayDirty = false;
}
void ApplyViewportRenderFailure(
ViewportEntry& entry,
const Rendering::RenderContext& renderContext,
const ViewportRenderFallbackPolicy& policy) {
ApplyViewportFailureStatus(entry.statusText, policy);
if (policy.invalidateObjectIdFrame) {
InvalidateViewportObjectIdFrame(entry.renderTargets);
}
if (!policy.shouldClear) {
return;
}
ClearViewport(
entry,
renderContext,
policy.clearColor.r,
policy.clearColor.g,
policy.clearColor.b,
policy.clearColor.a);
}
void BuildSceneViewportRenderState(
ViewportEntry& entry,
IEditorContext& context,
SceneViewportRenderState& outState) {
outState.overlay = GetSceneViewOverlayData();
if (!outState.overlay.valid) {
return;
}
const std::vector<uint64_t> selectedObjectIds = context.GetSelectionManager().GetSelectedEntities();
const SceneViewportOverlayFrameData& editorOverlayFrameData =
GetSceneViewEditorOverlayFrameData(context);
SceneViewportRenderPlanBuildResult renderPlan =
m_sceneViewportRenderPassBundle.BuildRenderPlan(
entry.renderTargets,
outState.overlay,
selectedObjectIds,
editorOverlayFrameData,
kDebugSceneSelectionMask);
outState.renderPlan = std::move(renderPlan.plan);
if (renderPlan.warningStatusText != nullptr) {
SetViewportStatusIfEmpty(entry.statusText, renderPlan.warningStatusText);
}
}
bool RenderSceneViewportEntry(
ViewportEntry& entry,
IEditorContext& context,
const Components::Scene* scene,
const Rendering::RenderContext& renderContext,
Rendering::RenderSurface& surface) {
if (!EnsureSceneViewCamera()) {
ApplyViewportRenderFailure(
entry,
renderContext,
BuildSceneViewportRenderFailurePolicy(
SceneViewportRenderFailure::MissingSceneViewCamera));
return false;
}
ApplySceneViewCameraController();
entry.statusText.clear();
if (scene == nullptr) {
ApplyViewportRenderFailure(
entry,
renderContext,
BuildSceneViewportRenderFailurePolicy(
SceneViewportRenderFailure::NoActiveScene));
return false;
}
SceneViewportRenderState sceneState = {};
BuildSceneViewportRenderState(entry, context, sceneState);
std::vector<Rendering::CameraFramePlan> plans =
m_sceneRenderer->BuildFramePlans(*scene, m_sceneViewCamera.camera, renderContext, surface);
if (plans.empty()) {
ApplyViewportRenderFailure(
entry,
renderContext,
BuildSceneViewportRenderFailurePolicy(
SceneViewportRenderFailure::SceneRendererFailed));
return false;
}
ApplySceneViewportRenderPlan(entry.renderTargets, sceneState.renderPlan, plans[0]);
if (!m_sceneRenderer->Render(plans)) {
ApplyViewportRenderFailure(
entry,
renderContext,
BuildSceneViewportRenderFailurePolicy(
SceneViewportRenderFailure::SceneRendererFailed));
return false;
}
MarkSceneViewportRenderSuccess(entry.renderTargets, plans[0]);
const Core::uint32 pendingAsyncLoads = Resources::ResourceManager::Get().GetAsyncPendingCount();
context.GetSceneManager().NotifySceneViewportFramePresented(pendingAsyncLoads);
if (entry.statusText.empty()) {
entry.statusText = BuildViewportSceneLoadStatusText(
context.GetSceneManager().GetSceneLoadProgress());
}
return true;
}
bool RenderGameViewportEntry(
ViewportEntry& entry,
const Components::Scene* scene,
const Rendering::RenderContext& renderContext,
const Rendering::RenderSurface& surface) {
if (scene == nullptr) {
ApplyViewportRenderFailure(
entry,
renderContext,
BuildGameViewportRenderFailurePolicy(
GameViewportRenderFailure::NoActiveScene));
return false;
}
const auto cameras = scene->FindObjectsOfType<Components::CameraComponent>();
if (cameras.empty()) {
ApplyViewportRenderFailure(
entry,
renderContext,
BuildGameViewportRenderFailurePolicy(
GameViewportRenderFailure::NoCameraInScene));
return false;
}
if (!m_sceneRenderer->Render(*scene, nullptr, renderContext, surface)) {
ApplyViewportRenderFailure(
entry,
renderContext,
BuildGameViewportRenderFailurePolicy(
GameViewportRenderFailure::SceneRendererFailed));
return false;
}
MarkGameViewportRenderSuccess(entry.renderTargets);
entry.statusText.clear();
return true;
}
void RenderViewportEntry(
ViewportEntry& entry,
IEditorContext& context,
const Components::Scene* scene,
const Rendering::RenderContext& renderContext) {
if (entry.renderTargets.colorView == nullptr || entry.renderTargets.depthView == nullptr) {
ApplyViewportFailureStatus(
entry.statusText,
BuildViewportRenderTargetUnavailablePolicy());
InvalidateViewportObjectIdFrame(entry.renderTargets);
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;
}
ViewportRenderTargets& targets = entry.renderTargets;
const float clearColor[4] = { r, g, b, a };
RHI::RHIResourceView* colorView = targets.colorView;
commandList->TransitionBarrier(
colorView,
targets.colorState,
RHI::ResourceStates::RenderTarget);
commandList->SetRenderTargets(1, &colorView, targets.depthView);
commandList->ClearRenderTarget(colorView, clearColor);
commandList->ClearDepthStencil(targets.depthView, 1.0f, 0);
commandList->TransitionBarrier(
colorView,
RHI::ResourceStates::RenderTarget,
RHI::ResourceStates::PixelShaderResource);
targets.colorState = RHI::ResourceStates::PixelShaderResource;
targets.hasValidObjectIdFrame = false;
}
ViewportObjectIdPickResult PickSceneViewEntityWithObjectId(
ViewportEntry& entry,
const ::XCEngine::UI::UISize& viewportSize,
const ::XCEngine::UI::UIPoint& viewportMousePosition) {
if (m_device == nullptr) {
return {};
}
ViewportObjectIdPickContext pickContext = {};
pickContext.commandQueue = m_sceneViewLastRenderContext.commandQueue;
pickContext.texture = entry.renderTargets.objectIdTexture;
pickContext.textureState = entry.renderTargets.objectIdState;
pickContext.textureWidth = entry.renderTargets.width;
pickContext.textureHeight = entry.renderTargets.height;
pickContext.hasValidFrame = entry.renderTargets.hasValidObjectIdFrame;
pickContext.viewportSize = viewportSize;
pickContext.viewportMousePosition = viewportMousePosition;
return PickViewportObjectIdEntity(
pickContext,
[this](const ViewportObjectIdReadbackRequest& request, std::array<uint8_t, 4>& outRgba) {
return m_device != nullptr &&
m_device->ReadTexturePixelRGBA8(
request.commandQueue,
request.texture,
request.textureState,
request.pixelX,
request.pixelY,
outRgba);
});
}
UI::ImGuiBackendBridge* m_backend = nullptr;
RHI::RHIDevice* m_device = nullptr;
std::unique_ptr<Rendering::SceneRenderer> m_sceneRenderer;
SceneViewportOverlayBuilder m_sceneViewportOverlayBuilder = {};
Rendering::RenderContext m_sceneViewLastRenderContext = {};
std::array<ViewportEntry, 2> m_entries = {};
SceneViewCameraState m_sceneViewCamera;
SceneViewportTransformGizmoOverlayState m_sceneViewTransformGizmoOverlayState = {};
SceneViewportOverlayFrameCacheState m_sceneViewEditorOverlayFrameCache = {};
bool m_sceneViewTransformGizmoOverlayDirty = false;
SceneViewportRenderPassBundle m_sceneViewportRenderPassBundle;
};
} // namespace Editor
} // namespace XCEngine