880 lines
33 KiB
C++
880 lines
33 KiB
C++
#pragma once
|
|
|
|
#include "Core/IEditorContext.h"
|
|
#include "Core/ISceneManager.h"
|
|
#include "Core/ISelectionManager.h"
|
|
#include "IViewportHostService.h"
|
|
#include "Passes/SceneViewportEditorOverlayPass.h"
|
|
#include "Passes/SceneViewportGridPass.h"
|
|
#include "Passes/SceneViewportSelectionOutlinePass.h"
|
|
#include "SceneViewportCameraController.h"
|
|
#include "SceneViewportEditorOverlayData.h"
|
|
#include "SceneViewportOverlayHandleBuilder.h"
|
|
#include "SceneViewportOverlayBuilder.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/Components/LightComponent.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/Passes/BuiltinInfiniteGridPass.h>
|
|
#include <XCEngine/Rendering/RenderContext.h>
|
|
#include <XCEngine/Rendering/RenderSurface.h>
|
|
#include <XCEngine/Rendering/SceneRenderer.h>
|
|
#include <XCEngine/Scene/Scene.h>
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace XCEngine {
|
|
namespace Editor {
|
|
|
|
namespace {
|
|
|
|
constexpr bool kDebugSceneSelectionMask = false;
|
|
constexpr uint64_t kSceneViewportOverlaySignatureOffsetBasis = 14695981039346656037ull;
|
|
constexpr uint64_t kSceneViewportOverlaySignaturePrime = 1099511628211ull;
|
|
|
|
void HashSceneViewportOverlayBytes(uint64_t& hash, const void* data, size_t size) {
|
|
const auto* bytes = static_cast<const uint8_t*>(data);
|
|
for (size_t index = 0; index < size; ++index) {
|
|
hash ^= static_cast<uint64_t>(bytes[index]);
|
|
hash *= kSceneViewportOverlaySignaturePrime;
|
|
}
|
|
}
|
|
|
|
template <typename TValue>
|
|
void HashSceneViewportOverlayValue(uint64_t& hash, const TValue& value) {
|
|
HashSceneViewportOverlayBytes(hash, &value, sizeof(TValue));
|
|
}
|
|
|
|
void HashSceneViewportOverlayFloat(uint64_t& hash, float value) {
|
|
uint32_t bits = 0u;
|
|
std::memcpy(&bits, &value, sizeof(bits));
|
|
HashSceneViewportOverlayValue(hash, bits);
|
|
}
|
|
|
|
void HashSceneViewportOverlayVector3(uint64_t& hash, const Math::Vector3& value) {
|
|
HashSceneViewportOverlayFloat(hash, value.x);
|
|
HashSceneViewportOverlayFloat(hash, value.y);
|
|
HashSceneViewportOverlayFloat(hash, value.z);
|
|
}
|
|
|
|
void HashSceneViewportOverlayQuaternion(uint64_t& hash, const Math::Quaternion& value) {
|
|
HashSceneViewportOverlayFloat(hash, value.x);
|
|
HashSceneViewportOverlayFloat(hash, value.y);
|
|
HashSceneViewportOverlayFloat(hash, value.z);
|
|
HashSceneViewportOverlayFloat(hash, value.w);
|
|
}
|
|
|
|
void HashSceneViewportOverlayRect(uint64_t& hash, const Math::Rect& value) {
|
|
HashSceneViewportOverlayFloat(hash, value.x);
|
|
HashSceneViewportOverlayFloat(hash, value.y);
|
|
HashSceneViewportOverlayFloat(hash, value.width);
|
|
HashSceneViewportOverlayFloat(hash, value.height);
|
|
}
|
|
|
|
void HashSceneViewportOverlayTransform(uint64_t& hash, const Components::TransformComponent& transform) {
|
|
HashSceneViewportOverlayVector3(hash, transform.GetPosition());
|
|
HashSceneViewportOverlayQuaternion(hash, transform.GetRotation());
|
|
HashSceneViewportOverlayVector3(hash, transform.GetScale());
|
|
}
|
|
|
|
uint64_t BuildSceneViewEditorOverlayContentSignature(
|
|
const Components::Scene* scene,
|
|
const std::vector<uint64_t>& selectedObjectIds) {
|
|
uint64_t hash = kSceneViewportOverlaySignatureOffsetBasis;
|
|
|
|
HashSceneViewportOverlayValue(hash, static_cast<uint64_t>(selectedObjectIds.size()));
|
|
for (uint64_t entityId : selectedObjectIds) {
|
|
HashSceneViewportOverlayValue(hash, entityId);
|
|
}
|
|
|
|
if (scene == nullptr) {
|
|
return hash;
|
|
}
|
|
|
|
for (Components::CameraComponent* camera : scene->FindObjectsOfType<Components::CameraComponent>()) {
|
|
Components::GameObject* gameObject = camera != nullptr ? camera->GetGameObject() : nullptr;
|
|
HashSceneViewportOverlayValue(hash, static_cast<uint8_t>(1u));
|
|
HashSceneViewportOverlayValue(hash, gameObject != nullptr ? gameObject->GetID() : 0ull);
|
|
HashSceneViewportOverlayValue(hash, camera != nullptr && camera->IsEnabled());
|
|
HashSceneViewportOverlayValue(hash, gameObject != nullptr && gameObject->IsActiveInHierarchy());
|
|
if (camera == nullptr ||
|
|
gameObject == nullptr ||
|
|
!camera->IsEnabled() ||
|
|
!gameObject->IsActiveInHierarchy() ||
|
|
gameObject->GetTransform() == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
HashSceneViewportOverlayTransform(hash, *gameObject->GetTransform());
|
|
HashSceneViewportOverlayValue(hash, static_cast<uint32_t>(camera->GetProjectionType()));
|
|
HashSceneViewportOverlayFloat(hash, camera->GetFieldOfView());
|
|
HashSceneViewportOverlayFloat(hash, camera->GetOrthographicSize());
|
|
HashSceneViewportOverlayFloat(hash, camera->GetNearClipPlane());
|
|
HashSceneViewportOverlayFloat(hash, camera->GetFarClipPlane());
|
|
HashSceneViewportOverlayRect(hash, camera->GetViewportRect());
|
|
}
|
|
|
|
for (Components::LightComponent* light : scene->FindObjectsOfType<Components::LightComponent>()) {
|
|
Components::GameObject* gameObject = light != nullptr ? light->GetGameObject() : nullptr;
|
|
HashSceneViewportOverlayValue(hash, static_cast<uint8_t>(2u));
|
|
HashSceneViewportOverlayValue(hash, gameObject != nullptr ? gameObject->GetID() : 0ull);
|
|
HashSceneViewportOverlayValue(hash, light != nullptr && light->IsEnabled());
|
|
HashSceneViewportOverlayValue(hash, gameObject != nullptr && gameObject->IsActiveInHierarchy());
|
|
if (light == nullptr ||
|
|
gameObject == nullptr ||
|
|
!light->IsEnabled() ||
|
|
!gameObject->IsActiveInHierarchy() ||
|
|
gameObject->GetTransform() == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
HashSceneViewportOverlayTransform(hash, *gameObject->GetTransform());
|
|
HashSceneViewportOverlayValue(hash, static_cast<uint32_t>(light->GetLightType()));
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
bool AreEqualSceneViewportVector3(const Math::Vector3& lhs, const Math::Vector3& rhs) {
|
|
constexpr float kEpsilon = 1e-4f;
|
|
return std::abs(lhs.x - rhs.x) <= kEpsilon &&
|
|
std::abs(lhs.y - rhs.y) <= kEpsilon &&
|
|
std::abs(lhs.z - rhs.z) <= kEpsilon;
|
|
}
|
|
|
|
bool AreEqualSceneViewportOverlayData(
|
|
const SceneViewportOverlayData& lhs,
|
|
const SceneViewportOverlayData& rhs) {
|
|
constexpr float kEpsilon = 1e-4f;
|
|
return lhs.valid == rhs.valid &&
|
|
AreEqualSceneViewportVector3(lhs.cameraPosition, rhs.cameraPosition) &&
|
|
AreEqualSceneViewportVector3(lhs.cameraForward, rhs.cameraForward) &&
|
|
AreEqualSceneViewportVector3(lhs.cameraRight, rhs.cameraRight) &&
|
|
AreEqualSceneViewportVector3(lhs.cameraUp, rhs.cameraUp) &&
|
|
std::abs(lhs.verticalFovDegrees - rhs.verticalFovDegrees) <= kEpsilon &&
|
|
std::abs(lhs.nearClipPlane - rhs.nearClipPlane) <= kEpsilon &&
|
|
std::abs(lhs.farClipPlane - rhs.farClipPlane) <= kEpsilon &&
|
|
std::abs(lhs.orbitDistance - rhs.orbitDistance) <= kEpsilon;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void Shutdown() {
|
|
for (ViewportEntry& entry : m_entries) {
|
|
DestroyViewportRenderTargets(m_backend, entry.renderTargets);
|
|
entry = {};
|
|
}
|
|
|
|
m_sceneViewportSelectionOutlineRenderer.Shutdown();
|
|
m_sceneViewportGridRenderer.Shutdown();
|
|
m_sceneViewportEditorOverlayRenderer.Shutdown();
|
|
m_sceneViewCamera = {};
|
|
ResetSceneViewEditorOverlayFrameData();
|
|
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 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.renderTargets.textureId;
|
|
frame.requestedSize = requestedSize;
|
|
frame.renderSize = ImVec2(
|
|
static_cast<float>(entry.renderTargets.width),
|
|
static_cast<float>(entry.renderTargets.height));
|
|
frame.hasTexture = entry.renderTargets.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;
|
|
}
|
|
|
|
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_sceneViewEditorOverlayFrameData;
|
|
}
|
|
|
|
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 ResetSceneViewEditorOverlayFrameData() {
|
|
m_sceneViewEditorOverlayFrameData = {};
|
|
m_sceneViewEditorOverlayScene = nullptr;
|
|
m_sceneViewEditorOverlaySelectedObjectIds.clear();
|
|
m_sceneViewEditorOverlayViewportWidth = 0u;
|
|
m_sceneViewEditorOverlayViewportHeight = 0u;
|
|
m_sceneViewEditorOverlayContentSignature = 0u;
|
|
m_sceneViewEditorOverlayCached = false;
|
|
}
|
|
|
|
void ResolveSceneViewEditorOverlayViewportSize(
|
|
const ViewportEntry& entry,
|
|
uint32_t& outWidth,
|
|
uint32_t& outHeight) const {
|
|
outWidth = entry.requestedWidth > 0u ? entry.requestedWidth : entry.renderTargets.width;
|
|
outHeight = entry.requestedHeight > 0u ? entry.requestedHeight : entry.renderTargets.height;
|
|
}
|
|
|
|
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 m_sceneViewTransformGizmoOverlayDirty ||
|
|
!m_sceneViewEditorOverlayCached ||
|
|
m_sceneViewEditorOverlayScene != scene ||
|
|
m_sceneViewEditorOverlayViewportWidth != viewportWidth ||
|
|
m_sceneViewEditorOverlayViewportHeight != viewportHeight ||
|
|
m_sceneViewEditorOverlaySelectedObjectIds != selectedObjectIds ||
|
|
m_sceneViewEditorOverlayContentSignature != contentSignature ||
|
|
!AreEqualSceneViewportOverlayData(m_sceneViewEditorOverlayFrameData.overlay, overlay);
|
|
}
|
|
|
|
void EnsureSceneViewEditorOverlayFrameData(IEditorContext& context) {
|
|
if (!EnsureSceneViewCamera()) {
|
|
ResetSceneViewEditorOverlayFrameData();
|
|
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 =
|
|
BuildSceneViewEditorOverlayContentSignature(scene, selectedObjectIds);
|
|
if (!ShouldRebuildSceneViewEditorOverlayFrameData(
|
|
scene,
|
|
overlay,
|
|
viewportWidth,
|
|
viewportHeight,
|
|
selectedObjectIds,
|
|
contentSignature)) {
|
|
return;
|
|
}
|
|
|
|
m_sceneViewEditorOverlayFrameData = {};
|
|
m_sceneViewEditorOverlayFrameData.overlay = overlay;
|
|
if (scene != nullptr && overlay.valid && viewportWidth > 0u && viewportHeight > 0u) {
|
|
m_sceneViewEditorOverlayFrameData = m_sceneViewportOverlayBuilder.Build(
|
|
context,
|
|
overlay,
|
|
viewportWidth,
|
|
viewportHeight,
|
|
selectedObjectIds,
|
|
&m_sceneViewTransformGizmoOverlayState);
|
|
}
|
|
|
|
m_sceneViewEditorOverlayScene = scene;
|
|
m_sceneViewEditorOverlaySelectedObjectIds = selectedObjectIds;
|
|
m_sceneViewEditorOverlayViewportWidth = viewportWidth;
|
|
m_sceneViewEditorOverlayViewportHeight = viewportHeight;
|
|
m_sceneViewEditorOverlayContentSignature = contentSignature;
|
|
m_sceneViewEditorOverlayCached = true;
|
|
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,
|
|
const Components::Scene& scene,
|
|
SceneViewportRenderState& outState) {
|
|
(void)scene;
|
|
outState.overlay = GetSceneViewOverlayData();
|
|
if (!outState.overlay.valid) {
|
|
return;
|
|
}
|
|
|
|
const std::vector<uint64_t> selectedObjectIds = context.GetSelectionManager().GetSelectedEntities();
|
|
const SceneViewportOverlayFrameData& editorOverlayFrameData =
|
|
GetSceneViewEditorOverlayFrameData(context);
|
|
SceneViewportRenderPlanBuildResult renderPlan =
|
|
BuildSceneViewportRenderPlan(
|
|
entry.renderTargets,
|
|
outState.overlay,
|
|
selectedObjectIds,
|
|
editorOverlayFrameData,
|
|
[this](const SceneViewportGridPassData& data) {
|
|
return CreateSceneViewportGridPass(
|
|
m_sceneViewportGridRenderer,
|
|
data);
|
|
},
|
|
[this](
|
|
RHI::RHIResourceView* objectIdTextureView,
|
|
const std::vector<uint64_t>& selectedObjectIds,
|
|
const SceneViewportSelectionOutlineStyle& style) {
|
|
return CreateSceneViewportSelectionOutlinePass(
|
|
m_sceneViewportSelectionOutlineRenderer,
|
|
objectIdTextureView,
|
|
selectedObjectIds,
|
|
style);
|
|
},
|
|
[this](const SceneViewportOverlayFrameData& frameData) {
|
|
return CreateSceneViewportEditorOverlayPass(
|
|
m_sceneViewportEditorOverlayRenderer,
|
|
frameData);
|
|
},
|
|
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, *scene, sceneState);
|
|
|
|
std::vector<Rendering::CameraRenderRequest> requests =
|
|
m_sceneRenderer->BuildRenderRequests(*scene, m_sceneViewCamera.camera, renderContext, surface);
|
|
if (requests.empty()) {
|
|
ApplyViewportRenderFailure(
|
|
entry,
|
|
renderContext,
|
|
BuildSceneViewportRenderFailurePolicy(
|
|
SceneViewportRenderFailure::SceneRendererFailed));
|
|
return false;
|
|
}
|
|
|
|
ApplySceneViewportRenderPlan(entry.renderTargets, sceneState.renderPlan, requests[0]);
|
|
|
|
if (!m_sceneRenderer->Render(requests)) {
|
|
ApplyViewportRenderFailure(
|
|
entry,
|
|
renderContext,
|
|
BuildSceneViewportRenderFailurePolicy(
|
|
SceneViewportRenderFailure::SceneRendererFailed));
|
|
return false;
|
|
}
|
|
|
|
MarkSceneViewportRenderSuccess(entry.renderTargets, requests[0]);
|
|
const Core::uint32 pendingAsyncLoads = Resources::ResourceManager::Get().GetAsyncPendingCount();
|
|
if (pendingAsyncLoads > 0) {
|
|
entry.statusText = "Loading scene assets... (" + std::to_string(pendingAsyncLoads) + ")";
|
|
}
|
|
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 ImVec2& viewportSize,
|
|
const ImVec2& 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;
|
|
SceneViewportOverlayFrameData m_sceneViewEditorOverlayFrameData = {};
|
|
SceneViewportTransformGizmoOverlayState m_sceneViewTransformGizmoOverlayState = {};
|
|
const Components::Scene* m_sceneViewEditorOverlayScene = nullptr;
|
|
std::vector<uint64_t> m_sceneViewEditorOverlaySelectedObjectIds = {};
|
|
uint32_t m_sceneViewEditorOverlayViewportWidth = 0u;
|
|
uint32_t m_sceneViewEditorOverlayViewportHeight = 0u;
|
|
uint64_t m_sceneViewEditorOverlayContentSignature = 0u;
|
|
bool m_sceneViewEditorOverlayCached = false;
|
|
bool m_sceneViewTransformGizmoOverlayDirty = false;
|
|
SceneViewportGridPassRenderer m_sceneViewportGridRenderer;
|
|
SceneViewportSelectionOutlinePassRenderer m_sceneViewportSelectionOutlineRenderer;
|
|
SceneViewportEditorOverlayPassRenderer m_sceneViewportEditorOverlayRenderer;
|
|
};
|
|
|
|
} // namespace Editor
|
|
} // namespace XCEngine
|