2026-03-28 17:04:14 +08:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include "Core/IEditorContext.h"
|
|
|
|
|
#include "Core/ISceneManager.h"
|
2026-03-28 17:40:14 +08:00
|
|
|
#include "Core/ISelectionManager.h"
|
2026-03-28 17:04:14 +08:00
|
|
|
#include "IViewportHostService.h"
|
2026-03-29 15:12:38 +08:00
|
|
|
#include "SceneViewportPicker.h"
|
2026-03-28 17:40:14 +08:00
|
|
|
#include "SceneViewportCameraController.h"
|
2026-03-29 15:12:38 +08:00
|
|
|
#include "SceneViewportInfiniteGridPass.h"
|
2026-03-31 21:54:00 +08:00
|
|
|
#include "SceneViewportPostPassPlan.h"
|
2026-03-29 15:12:38 +08:00
|
|
|
#include "SceneViewportSelectionOutlinePass.h"
|
2026-03-28 17:04:14 +08:00
|
|
|
#include "UI/ImGuiBackendBridge.h"
|
|
|
|
|
|
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
2026-03-28 17:40:14 +08:00
|
|
|
#include <XCEngine/Components/GameObject.h>
|
2026-03-28 17:04:14 +08:00
|
|
|
#include <XCEngine/RHI/D3D12/D3D12Device.h>
|
|
|
|
|
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
|
|
|
|
#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>
|
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>
|
2026-03-30 03:52:59 +08:00
|
|
|
#include <functional>
|
2026-03-28 17:04:14 +08:00
|
|
|
#include <memory>
|
|
|
|
|
#include <string>
|
2026-03-30 03:52:59 +08:00
|
|
|
#include <utility>
|
2026-03-28 17:04:14 +08:00
|
|
|
|
|
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Editor {
|
|
|
|
|
|
2026-03-29 15:12:38 +08:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
constexpr bool kDebugSceneSelectionMask = false;
|
|
|
|
|
|
2026-03-30 03:52:59 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 16:44:11 +08:00
|
|
|
inline uint32_t ClampViewportPixelCoordinate(float value, uint32_t extent) {
|
|
|
|
|
if (extent == 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float maxCoordinate = static_cast<float>(extent - 1u);
|
|
|
|
|
const float clamped = (std::max)(0.0f, (std::min)(value, maxCoordinate));
|
|
|
|
|
return static_cast<uint32_t>(std::floor(clamped));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 03:52:59 +08:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-29 15:12:38 +08:00
|
|
|
} // 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 ? static_cast<RHI::D3D12Device*>(device) : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Shutdown() {
|
|
|
|
|
for (ViewportEntry& entry : m_entries) {
|
|
|
|
|
DestroyViewportResources(entry);
|
|
|
|
|
entry = {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 17:40:14 +08:00
|
|
|
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;
|
2026-03-29 15:12:38 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 17:40:14 +08:00
|
|
|
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;
|
2026-03-29 15:12:38 +08:00
|
|
|
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;
|
2026-03-28 17:40:14 +08:00
|
|
|
|
2026-03-28 18:21:18 +08:00
|
|
|
if (input.looking) {
|
|
|
|
|
controllerInput.lookDeltaX = input.mouseDelta.x;
|
|
|
|
|
controllerInput.lookDeltaY = input.mouseDelta.y;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 17:40:14 +08:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-29 15:12:38 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-29 15:12:38 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 03:52:59 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-29 15:12:38 +08:00
|
|
|
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;
|
2026-04-01 17:33:07 +08:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-28 17:40:14 +08:00
|
|
|
struct SceneViewCameraState {
|
|
|
|
|
std::unique_ptr<Components::GameObject> gameObject;
|
|
|
|
|
Components::CameraComponent* camera = nullptr;
|
|
|
|
|
SceneViewportCameraController controller;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
struct SceneViewportRenderState {
|
|
|
|
|
SceneViewportOverlayData overlay = {};
|
|
|
|
|
Rendering::RenderPassSequence postPasses;
|
2026-04-01 17:33:07 +08:00
|
|
|
std::vector<uint64_t> selectedObjectIds;
|
2026-03-31 22:10:27 +08:00
|
|
|
};
|
|
|
|
|
|
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>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 17:40:14 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
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<uint32_t>(format);
|
|
|
|
|
desc.textureType = static_cast<uint32_t>(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<uint32_t>(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;
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
2026-04-01 17:33:07 +08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
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<RHI::D3D12Texture*>(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<ImTextureID>(entry.imguiGpuHandle.ptr);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 17:04:14 +08:00
|
|
|
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 &&
|
2026-03-29 15:12:38 +08:00
|
|
|
(entry.kind != EditorViewportKind::Scene ||
|
2026-04-01 17:33:07 +08:00
|
|
|
(entry.objectIdTexture != nullptr &&
|
|
|
|
|
entry.objectIdView != nullptr &&
|
|
|
|
|
entry.objectIdShaderView != nullptr)) &&
|
2026-03-28 17:04:14 +08:00
|
|
|
entry.textureId != ImTextureID{}) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DestroyViewportResources(entry);
|
|
|
|
|
|
|
|
|
|
entry.width = entry.requestedWidth;
|
|
|
|
|
entry.height = entry.requestedHeight;
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
if (!CreateViewportColorResources(entry)) {
|
2026-03-28 17:04:14 +08:00
|
|
|
DestroyViewportResources(entry);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
if (!CreateViewportDepthResources(entry)) {
|
2026-03-28 17:04:14 +08:00
|
|
|
DestroyViewportResources(entry);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 16:44:11 +08:00
|
|
|
if (entry.kind == EditorViewportKind::Scene &&
|
|
|
|
|
!CreateSceneViewportObjectIdResources(entry)) {
|
|
|
|
|
DestroyViewportResources(entry);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 17:40:14 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 16:44:11 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 22:04:57 +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,
|
|
|
|
|
overlay);
|
|
|
|
|
if (!rendered) {
|
|
|
|
|
SetViewportStatusIfEmpty(entry.statusText, "Scene grid pass failed");
|
|
|
|
|
}
|
|
|
|
|
return rendered;
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AddSceneSelectionOutlinePass(
|
|
|
|
|
ViewportEntry& entry,
|
2026-04-01 17:33:07 +08:00
|
|
|
const std::vector<uint64_t>& selectedObjectIds,
|
2026-03-31 22:04:57 +08:00
|
|
|
Rendering::RenderPassSequence& outPostPasses) {
|
|
|
|
|
outPostPasses.AddPass(MakeLambdaRenderPass(
|
|
|
|
|
"SceneSelectionOutline",
|
2026-04-01 17:33:07 +08:00
|
|
|
[this, &entry, selectedObjectIds](const Rendering::RenderPassContext& context) {
|
2026-03-31 22:04:57 +08:00
|
|
|
const bool rendered = m_sceneSelectionOutlinePass.Render(
|
|
|
|
|
context.renderContext,
|
|
|
|
|
context.surface,
|
2026-04-01 17:33:07 +08:00
|
|
|
entry.objectIdShaderView,
|
|
|
|
|
selectedObjectIds,
|
|
|
|
|
false);
|
2026-03-31 22:04:57 +08:00
|
|
|
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,
|
2026-04-01 17:33:07 +08:00
|
|
|
const std::vector<uint64_t>& selectedObjectIds,
|
2026-03-31 22:04:57 +08:00
|
|
|
Rendering::RenderPassSequence& outPostPasses) {
|
|
|
|
|
outPostPasses.AddPass(MakeLambdaRenderPass(
|
|
|
|
|
"SceneSelectionMaskDebug",
|
2026-04-01 17:33:07 +08:00
|
|
|
[this, &entry, selectedObjectIds](
|
2026-03-31 22:04:57 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-04-01 17:33:07 +08:00
|
|
|
const bool rendered = m_sceneSelectionOutlinePass.Render(
|
2026-03-31 22:04:57 +08:00
|
|
|
context.renderContext,
|
|
|
|
|
context.surface,
|
2026-04-01 17:33:07 +08:00
|
|
|
entry.objectIdShaderView,
|
|
|
|
|
selectedObjectIds,
|
|
|
|
|
true);
|
2026-03-31 22:04:57 +08:00
|
|
|
if (!rendered) {
|
|
|
|
|
SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask debug pass failed");
|
|
|
|
|
}
|
|
|
|
|
return rendered;
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AddSceneViewPostPassStep(
|
|
|
|
|
SceneViewportPostPassStep step,
|
|
|
|
|
ViewportEntry& entry,
|
|
|
|
|
const SceneViewportOverlayData& overlay,
|
2026-04-01 17:33:07 +08:00
|
|
|
const std::vector<uint64_t>& selectedObjectIds,
|
2026-03-31 22:04:57 +08:00
|
|
|
Rendering::RenderPassSequence& outPostPasses) {
|
|
|
|
|
switch (step) {
|
|
|
|
|
case SceneViewportPostPassStep::ColorToRenderTarget:
|
|
|
|
|
AddSceneColorToRenderTargetPass(entry, outPostPasses);
|
|
|
|
|
break;
|
|
|
|
|
case SceneViewportPostPassStep::InfiniteGrid:
|
|
|
|
|
AddSceneInfiniteGridPass(entry, overlay, outPostPasses);
|
|
|
|
|
break;
|
|
|
|
|
case SceneViewportPostPassStep::SelectionOutline:
|
2026-04-01 17:33:07 +08:00
|
|
|
AddSceneSelectionOutlinePass(entry, selectedObjectIds, outPostPasses);
|
2026-03-31 22:04:57 +08:00
|
|
|
break;
|
|
|
|
|
case SceneViewportPostPassStep::ColorToShaderResource:
|
|
|
|
|
AddSceneColorToShaderResourcePass(entry, outPostPasses);
|
|
|
|
|
break;
|
|
|
|
|
case SceneViewportPostPassStep::SelectionMaskDebug:
|
|
|
|
|
AddSceneSelectionMaskDebugPass(
|
|
|
|
|
entry,
|
2026-04-01 17:33:07 +08:00
|
|
|
selectedObjectIds,
|
2026-03-31 22:04:57 +08:00
|
|
|
outPostPasses);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 03:52:59 +08:00
|
|
|
bool BuildSceneViewPostPassSequence(
|
|
|
|
|
ViewportEntry& entry,
|
|
|
|
|
const SceneViewportOverlayData& overlay,
|
2026-04-01 17:33:07 +08:00
|
|
|
const std::vector<uint64_t>& selectedObjectIds,
|
2026-03-30 03:52:59 +08:00
|
|
|
Rendering::RenderPassSequence& outPostPasses) {
|
2026-04-01 17:33:07 +08:00
|
|
|
const bool hasSelection = !selectedObjectIds.empty();
|
|
|
|
|
const bool hasObjectIdShaderView = entry.objectIdShaderView != nullptr;
|
2026-03-31 21:54:00 +08:00
|
|
|
|
|
|
|
|
if (hasSelection &&
|
|
|
|
|
!kDebugSceneSelectionMask &&
|
2026-04-01 17:33:07 +08:00
|
|
|
!hasObjectIdShaderView) {
|
|
|
|
|
SetViewportStatusIfEmpty(entry.statusText, "Scene object id shader view is unavailable");
|
2026-03-31 21:54:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SceneViewportPostPassPlan plan = BuildSceneViewportPostPassPlan({
|
|
|
|
|
overlay.valid,
|
|
|
|
|
hasSelection,
|
|
|
|
|
kDebugSceneSelectionMask,
|
2026-04-01 17:33:07 +08:00
|
|
|
hasObjectIdShaderView
|
2026-03-31 21:54:00 +08:00
|
|
|
});
|
|
|
|
|
if (!plan.valid) {
|
2026-03-30 03:52:59 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 21:54:00 +08:00
|
|
|
for (const SceneViewportPostPassStep step : plan.steps) {
|
2026-03-31 22:04:57 +08:00
|
|
|
AddSceneViewPostPassStep(
|
|
|
|
|
step,
|
|
|
|
|
entry,
|
|
|
|
|
overlay,
|
2026-04-01 17:33:07 +08:00
|
|
|
selectedObjectIds,
|
2026-03-31 22:04:57 +08:00
|
|
|
outPostPasses);
|
2026-03-30 03:52:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
void BuildSceneViewportRenderState(
|
2026-03-28 17:04:14 +08:00
|
|
|
ViewportEntry& entry,
|
2026-03-29 15:12:38 +08:00
|
|
|
IEditorContext& context,
|
2026-03-31 22:10:27 +08:00
|
|
|
const Components::Scene& scene,
|
|
|
|
|
SceneViewportRenderState& outState) {
|
2026-04-01 17:33:07 +08:00
|
|
|
(void)scene;
|
2026-03-31 22:10:27 +08:00
|
|
|
outState.overlay = GetSceneViewOverlayData();
|
|
|
|
|
if (!outState.overlay.valid) {
|
2026-03-28 17:04:14 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 17:33:07 +08:00
|
|
|
outState.selectedObjectIds = context.GetSelectionManager().GetSelectedEntities();
|
2026-03-28 17:40:14 +08:00
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
BuildSceneViewPostPassSequence(
|
|
|
|
|
entry,
|
|
|
|
|
outState.overlay,
|
2026-04-01 17:33:07 +08:00
|
|
|
outState.selectedObjectIds,
|
2026-03-31 22:10:27 +08:00
|
|
|
outState.postPasses);
|
|
|
|
|
}
|
2026-03-28 17:40:14 +08:00
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
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;
|
2026-03-31 22:10:27 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-28 17:40:14 +08:00
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
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;
|
2026-03-31 22:10:27 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
SceneViewportRenderState sceneState = {};
|
|
|
|
|
BuildSceneViewportRenderState(entry, context, *scene, sceneState);
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
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;
|
2026-03-31 22:10:27 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-29 15:12:38 +08:00
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
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;
|
2026-03-31 22:10:27 +08:00
|
|
|
return false;
|
2026-03-29 15:12:38 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 22:10:27 +08:00
|
|
|
entry.colorState = RHI::ResourceStates::PixelShaderResource;
|
2026-04-01 16:44:11 +08:00
|
|
|
entry.objectIdState = RHI::ResourceStates::PixelShaderResource;
|
|
|
|
|
entry.hasValidObjectIdFrame = requests[0].objectId.IsRequested();
|
2026-03-31 22:10:27 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RenderGameViewportEntry(
|
|
|
|
|
ViewportEntry& entry,
|
|
|
|
|
const Components::Scene* scene,
|
|
|
|
|
const Rendering::RenderContext& renderContext,
|
|
|
|
|
const Rendering::RenderSurface& surface) {
|
2026-03-29 15:12:38 +08:00
|
|
|
if (scene == nullptr) {
|
|
|
|
|
entry.statusText = "No active scene";
|
|
|
|
|
ClearViewport(entry, renderContext, 0.07f, 0.08f, 0.10f, 1.0f);
|
2026-03-31 22:10:27 +08:00
|
|
|
return false;
|
2026-03-28 17:40:14 +08:00
|
|
|
}
|
|
|
|
|
|
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);
|
2026-03-31 22:10:27 +08:00
|
|
|
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;
|
2026-03-31 22:10:27 +08:00
|
|
|
return false;
|
2026-03-28 17:04:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entry.colorState = RHI::ResourceStates::PixelShaderResource;
|
|
|
|
|
entry.statusText.clear();
|
2026-03-31 22:10:27 +08:00
|
|
|
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 =
|
2026-04-01 17:05:48 +08:00
|
|
|
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 = {};
|
2026-04-01 17:05:48 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 17:33:07 +08:00
|
|
|
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::D3D12Device* m_device = nullptr;
|
|
|
|
|
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 = {};
|
2026-03-28 17:40:14 +08:00
|
|
|
SceneViewCameraState m_sceneViewCamera;
|
2026-03-29 15:12:38 +08:00
|
|
|
SceneViewportInfiniteGridPass m_sceneGridPass;
|
|
|
|
|
SceneViewportSelectionOutlinePass m_sceneSelectionOutlinePass;
|
2026-03-28 17:04:14 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace Editor
|
|
|
|
|
} // namespace XCEngine
|