Refine editor viewport and interaction workflow
This commit is contained in:
@@ -4,7 +4,12 @@
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "IViewportHostService.h"
|
||||
#include "SceneViewportPicker.h"
|
||||
#include "SceneViewportCameraController.h"
|
||||
#include "SceneViewportInfiniteGridPass.h"
|
||||
#include "SceneViewportSelectionMaskPass.h"
|
||||
#include "SceneViewportSelectionOutlinePass.h"
|
||||
#include "SceneViewportSelectionUtils.h"
|
||||
#include "UI/ImGuiBackendBridge.h"
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
@@ -27,6 +32,12 @@
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr bool kDebugSceneSelectionMask = false;
|
||||
|
||||
} // namespace
|
||||
|
||||
class ViewportHostService : public IViewportHostService {
|
||||
public:
|
||||
void Initialize(UI::ImGuiBackendBridge& backend, RHI::RHIDevice* device) {
|
||||
@@ -44,6 +55,9 @@ public:
|
||||
m_sceneViewCamera = {};
|
||||
m_device = nullptr;
|
||||
m_backend = nullptr;
|
||||
m_sceneGridPass.Shutdown();
|
||||
m_sceneSelectionMaskPass.Shutdown();
|
||||
m_sceneSelectionOutlinePass.Shutdown();
|
||||
m_sceneRenderer.reset();
|
||||
}
|
||||
|
||||
@@ -91,6 +105,7 @@ public:
|
||||
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;
|
||||
@@ -116,6 +131,27 @@ public:
|
||||
ApplySceneViewCameraController();
|
||||
}
|
||||
|
||||
uint64_t PickSceneViewEntity(
|
||||
IEditorContext& context,
|
||||
const ImVec2& viewportSize,
|
||||
const ImVec2& viewportMousePosition) override {
|
||||
if (!EnsureSceneViewCamera()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Components::Scene* scene = context.GetSceneManager().GetScene();
|
||||
if (scene == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
SceneViewportOverlayData GetSceneViewOverlayData() const override {
|
||||
SceneViewportOverlayData data = {};
|
||||
if (m_sceneViewCamera.gameObject == nullptr || m_sceneViewCamera.camera == nullptr) {
|
||||
@@ -159,7 +195,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
RenderViewportEntry(entry, scene, renderContext);
|
||||
RenderViewportEntry(entry, context, scene, renderContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,10 +211,14 @@ private:
|
||||
RHI::RHIResourceView* colorView = nullptr;
|
||||
RHI::RHITexture* depthTexture = nullptr;
|
||||
RHI::RHIResourceView* depthView = nullptr;
|
||||
RHI::RHITexture* selectionMaskTexture = nullptr;
|
||||
RHI::RHIResourceView* selectionMaskView = nullptr;
|
||||
RHI::RHIResourceView* selectionMaskShaderView = nullptr;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE imguiCpuHandle = {};
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE imguiGpuHandle = {};
|
||||
ImTextureID textureId = {};
|
||||
RHI::ResourceStates colorState = RHI::ResourceStates::Common;
|
||||
RHI::ResourceStates selectionMaskState = RHI::ResourceStates::Common;
|
||||
std::string statusText;
|
||||
};
|
||||
|
||||
@@ -277,6 +317,10 @@ private:
|
||||
entry.colorView != nullptr &&
|
||||
entry.depthTexture != nullptr &&
|
||||
entry.depthView != nullptr &&
|
||||
(entry.kind != EditorViewportKind::Scene ||
|
||||
(entry.selectionMaskTexture != nullptr &&
|
||||
entry.selectionMaskView != nullptr &&
|
||||
entry.selectionMaskShaderView != nullptr)) &&
|
||||
entry.textureId != ImTextureID{}) {
|
||||
return true;
|
||||
}
|
||||
@@ -338,6 +382,40 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.kind == EditorViewportKind::Scene) {
|
||||
RHI::TextureDesc selectionMaskDesc = {};
|
||||
selectionMaskDesc.width = entry.width;
|
||||
selectionMaskDesc.height = entry.height;
|
||||
selectionMaskDesc.depth = 1;
|
||||
selectionMaskDesc.mipLevels = 1;
|
||||
selectionMaskDesc.arraySize = 1;
|
||||
selectionMaskDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
selectionMaskDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
|
||||
selectionMaskDesc.sampleCount = 1;
|
||||
selectionMaskDesc.sampleQuality = 0;
|
||||
selectionMaskDesc.flags = 0;
|
||||
entry.selectionMaskTexture = m_device->CreateTexture(selectionMaskDesc);
|
||||
if (entry.selectionMaskTexture == nullptr) {
|
||||
DestroyViewportResources(entry);
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::ResourceViewDesc selectionMaskViewDesc = {};
|
||||
selectionMaskViewDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
selectionMaskViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
|
||||
entry.selectionMaskView = m_device->CreateRenderTargetView(entry.selectionMaskTexture, selectionMaskViewDesc);
|
||||
if (entry.selectionMaskView == nullptr) {
|
||||
DestroyViewportResources(entry);
|
||||
return false;
|
||||
}
|
||||
|
||||
entry.selectionMaskShaderView = m_device->CreateShaderResourceView(entry.selectionMaskTexture, selectionMaskViewDesc);
|
||||
if (entry.selectionMaskShaderView == nullptr) {
|
||||
DestroyViewportResources(entry);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_backend->AllocateTextureDescriptor(&entry.imguiCpuHandle, &entry.imguiGpuHandle);
|
||||
if (entry.imguiCpuHandle.ptr == 0 || entry.imguiGpuHandle.ptr == 0) {
|
||||
DestroyViewportResources(entry);
|
||||
@@ -357,6 +435,7 @@ private:
|
||||
|
||||
entry.textureId = static_cast<ImTextureID>(entry.imguiGpuHandle.ptr);
|
||||
entry.colorState = RHI::ResourceStates::Common;
|
||||
entry.selectionMaskState = RHI::ResourceStates::Common;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -369,8 +448,18 @@ private:
|
||||
return surface;
|
||||
}
|
||||
|
||||
Rendering::RenderSurface BuildSelectionMaskSurface(const ViewportEntry& entry) const {
|
||||
Rendering::RenderSurface surface(entry.width, entry.height);
|
||||
surface.SetColorAttachment(entry.selectionMaskView);
|
||||
surface.SetDepthAttachment(entry.depthView);
|
||||
surface.SetColorStateBefore(entry.selectionMaskState);
|
||||
surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
|
||||
return surface;
|
||||
}
|
||||
|
||||
void RenderViewportEntry(
|
||||
ViewportEntry& entry,
|
||||
IEditorContext& context,
|
||||
const Components::Scene* scene,
|
||||
const Rendering::RenderContext& renderContext) {
|
||||
if (entry.colorView == nullptr || entry.depthView == nullptr) {
|
||||
@@ -378,12 +467,6 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
if (scene == nullptr) {
|
||||
entry.statusText = "No active scene";
|
||||
ClearViewport(entry, renderContext, 0.07f, 0.08f, 0.10f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
Rendering::RenderSurface surface = BuildSurface(entry);
|
||||
|
||||
if (entry.kind == EditorViewportKind::Scene) {
|
||||
@@ -394,14 +477,130 @@ private:
|
||||
}
|
||||
|
||||
ApplySceneViewCameraController();
|
||||
if (!m_sceneRenderer->Render(*scene, m_sceneViewCamera.camera, renderContext, surface)) {
|
||||
if (scene == nullptr) {
|
||||
entry.statusText = "No active scene";
|
||||
ClearViewport(entry, renderContext, 0.07f, 0.08f, 0.10f, 1.0f);
|
||||
} else if (!m_sceneRenderer->Render(*scene, m_sceneViewCamera.camera, renderContext, surface)) {
|
||||
entry.statusText = "Scene renderer failed";
|
||||
ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f);
|
||||
return;
|
||||
} else {
|
||||
entry.colorState = RHI::ResourceStates::PixelShaderResource;
|
||||
entry.statusText.clear();
|
||||
}
|
||||
|
||||
entry.colorState = RHI::ResourceStates::PixelShaderResource;
|
||||
entry.statusText.clear();
|
||||
const SceneViewportOverlayData overlay = GetSceneViewOverlayData();
|
||||
if (overlay.valid) {
|
||||
RHI::RHICommandList* commandList = renderContext.commandList;
|
||||
bool hasSelection = false;
|
||||
Rendering::RenderCameraData cameraData = {};
|
||||
std::vector<Rendering::VisibleRenderItem> selectionRenderables;
|
||||
|
||||
if (scene != nullptr) {
|
||||
selectionRenderables = CollectSceneViewportSelectionRenderables(
|
||||
*scene,
|
||||
context.GetSelectionManager().GetSelectedEntities(),
|
||||
overlay.cameraPosition);
|
||||
if (!selectionRenderables.empty()) {
|
||||
hasSelection = true;
|
||||
cameraData = BuildSceneViewportCameraData(
|
||||
*m_sceneViewCamera.camera,
|
||||
entry.width,
|
||||
entry.height);
|
||||
}
|
||||
}
|
||||
|
||||
commandList->TransitionBarrier(
|
||||
entry.colorView,
|
||||
entry.colorState,
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
entry.colorState = RHI::ResourceStates::RenderTarget;
|
||||
|
||||
if (kDebugSceneSelectionMask && hasSelection) {
|
||||
const float debugClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
RHI::RHIResourceView* colorView = entry.colorView;
|
||||
commandList->SetRenderTargets(1, &colorView, entry.depthView);
|
||||
commandList->ClearRenderTarget(colorView, debugClearColor);
|
||||
|
||||
if (!m_sceneSelectionMaskPass.Render(
|
||||
renderContext,
|
||||
surface,
|
||||
cameraData,
|
||||
selectionRenderables) &&
|
||||
entry.statusText.empty()) {
|
||||
entry.statusText = "Scene selection mask debug pass failed";
|
||||
}
|
||||
|
||||
commandList->TransitionBarrier(
|
||||
entry.colorView,
|
||||
entry.colorState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
entry.colorState = RHI::ResourceStates::PixelShaderResource;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasSelection) {
|
||||
if (entry.selectionMaskView == nullptr || entry.selectionMaskShaderView == nullptr) {
|
||||
entry.statusText = entry.statusText.empty()
|
||||
? "Scene selection mask target is unavailable"
|
||||
: entry.statusText;
|
||||
} else {
|
||||
Rendering::RenderSurface selectionMaskSurface = BuildSelectionMaskSurface(entry);
|
||||
commandList->TransitionBarrier(
|
||||
entry.selectionMaskView,
|
||||
entry.selectionMaskState,
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
entry.selectionMaskState = RHI::ResourceStates::RenderTarget;
|
||||
|
||||
const float maskClearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
RHI::RHIResourceView* maskView = entry.selectionMaskView;
|
||||
commandList->SetRenderTargets(1, &maskView, entry.depthView);
|
||||
commandList->ClearRenderTarget(maskView, maskClearColor);
|
||||
|
||||
if (!m_sceneSelectionMaskPass.Render(
|
||||
renderContext,
|
||||
selectionMaskSurface,
|
||||
cameraData,
|
||||
selectionRenderables) &&
|
||||
entry.statusText.empty()) {
|
||||
entry.statusText = "Scene selection mask pass failed";
|
||||
}
|
||||
|
||||
commandList->TransitionBarrier(
|
||||
entry.selectionMaskView,
|
||||
entry.selectionMaskState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
entry.selectionMaskState = RHI::ResourceStates::PixelShaderResource;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_sceneGridPass.Render(renderContext, surface, overlay)) {
|
||||
entry.statusText = entry.statusText.empty()
|
||||
? "Scene grid pass failed"
|
||||
: entry.statusText;
|
||||
}
|
||||
|
||||
if (hasSelection &&
|
||||
!m_sceneSelectionOutlinePass.Render(
|
||||
renderContext,
|
||||
surface,
|
||||
entry.selectionMaskShaderView) &&
|
||||
entry.statusText.empty()) {
|
||||
entry.statusText = "Scene selection outline pass failed";
|
||||
}
|
||||
|
||||
commandList->TransitionBarrier(
|
||||
entry.colorView,
|
||||
entry.colorState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
entry.colorState = RHI::ResourceStates::PixelShaderResource;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (scene == nullptr) {
|
||||
entry.statusText = "No active scene";
|
||||
ClearViewport(entry, renderContext, 0.07f, 0.08f, 0.10f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -456,6 +655,24 @@ private:
|
||||
m_backend->FreeTextureDescriptor(entry.imguiCpuHandle, entry.imguiGpuHandle);
|
||||
}
|
||||
|
||||
if (entry.selectionMaskShaderView != nullptr) {
|
||||
entry.selectionMaskShaderView->Shutdown();
|
||||
delete entry.selectionMaskShaderView;
|
||||
entry.selectionMaskShaderView = nullptr;
|
||||
}
|
||||
|
||||
if (entry.selectionMaskView != nullptr) {
|
||||
entry.selectionMaskView->Shutdown();
|
||||
delete entry.selectionMaskView;
|
||||
entry.selectionMaskView = nullptr;
|
||||
}
|
||||
|
||||
if (entry.selectionMaskTexture != nullptr) {
|
||||
entry.selectionMaskTexture->Shutdown();
|
||||
delete entry.selectionMaskTexture;
|
||||
entry.selectionMaskTexture = nullptr;
|
||||
}
|
||||
|
||||
if (entry.depthView != nullptr) {
|
||||
entry.depthView->Shutdown();
|
||||
delete entry.depthView;
|
||||
@@ -486,6 +703,7 @@ private:
|
||||
entry.imguiGpuHandle = {};
|
||||
entry.textureId = {};
|
||||
entry.colorState = RHI::ResourceStates::Common;
|
||||
entry.selectionMaskState = RHI::ResourceStates::Common;
|
||||
}
|
||||
|
||||
UI::ImGuiBackendBridge* m_backend = nullptr;
|
||||
@@ -493,6 +711,9 @@ private:
|
||||
std::unique_ptr<Rendering::SceneRenderer> m_sceneRenderer;
|
||||
std::array<ViewportEntry, 2> m_entries = {};
|
||||
SceneViewCameraState m_sceneViewCamera;
|
||||
SceneViewportInfiniteGridPass m_sceneGridPass;
|
||||
SceneViewportSelectionMaskPass m_sceneSelectionMaskPass;
|
||||
SceneViewportSelectionOutlinePass m_sceneSelectionOutlinePass;
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
|
||||
Reference in New Issue
Block a user