Route scene viewport overlays through render passes
This commit is contained in:
@@ -8,24 +8,68 @@ namespace UI {
|
||||
|
||||
class ConsoleFilterState {
|
||||
public:
|
||||
bool& ShowInfo() {
|
||||
return m_showInfo;
|
||||
bool& ShowLog() {
|
||||
return m_showLog;
|
||||
}
|
||||
|
||||
bool ShowLog() const {
|
||||
return m_showLog;
|
||||
}
|
||||
|
||||
bool& ShowWarning() {
|
||||
return m_showWarning;
|
||||
}
|
||||
|
||||
bool ShowWarning() const {
|
||||
return m_showWarning;
|
||||
}
|
||||
|
||||
bool& ShowError() {
|
||||
return m_showError;
|
||||
}
|
||||
|
||||
bool ShowError() const {
|
||||
return m_showError;
|
||||
}
|
||||
|
||||
bool& Collapse() {
|
||||
return m_collapse;
|
||||
}
|
||||
|
||||
bool Collapse() const {
|
||||
return m_collapse;
|
||||
}
|
||||
|
||||
bool& ClearOnPlay() {
|
||||
return m_clearOnPlay;
|
||||
}
|
||||
|
||||
bool ClearOnPlay() const {
|
||||
return m_clearOnPlay;
|
||||
}
|
||||
|
||||
bool& ErrorPause() {
|
||||
return m_errorPause;
|
||||
}
|
||||
|
||||
bool ErrorPause() const {
|
||||
return m_errorPause;
|
||||
}
|
||||
|
||||
bool& ShowInfo() {
|
||||
return ShowLog();
|
||||
}
|
||||
|
||||
bool ShowInfo() const {
|
||||
return ShowLog();
|
||||
}
|
||||
|
||||
bool Allows(::XCEngine::Debug::LogLevel level) const {
|
||||
switch (level) {
|
||||
case ::XCEngine::Debug::LogLevel::Verbose:
|
||||
case ::XCEngine::Debug::LogLevel::Debug:
|
||||
case ::XCEngine::Debug::LogLevel::Info:
|
||||
return m_showInfo;
|
||||
return m_showLog;
|
||||
case ::XCEngine::Debug::LogLevel::Warning:
|
||||
return m_showWarning;
|
||||
case ::XCEngine::Debug::LogLevel::Error:
|
||||
@@ -37,9 +81,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_showInfo = true;
|
||||
bool m_showLog = true;
|
||||
bool m_showWarning = true;
|
||||
bool m_showError = true;
|
||||
bool m_collapse = false;
|
||||
bool m_clearOnPlay = false;
|
||||
bool m_errorPause = false;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
|
||||
@@ -26,8 +26,10 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
@@ -36,6 +38,60 @@ namespace {
|
||||
|
||||
constexpr bool kDebugSceneSelectionMask = false;
|
||||
|
||||
class LambdaRenderPass final : public Rendering::RenderPass {
|
||||
public:
|
||||
using ExecuteCallback = std::function<bool(const Rendering::RenderPassContext&)>;
|
||||
|
||||
LambdaRenderPass(std::string name, ExecuteCallback executeCallback)
|
||||
: m_name(std::move(name))
|
||||
, m_executeCallback(std::move(executeCallback)) {
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return m_name.c_str();
|
||||
}
|
||||
|
||||
bool Execute(const Rendering::RenderPassContext& context) override {
|
||||
return m_executeCallback != nullptr && m_executeCallback(context);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
ExecuteCallback m_executeCallback;
|
||||
};
|
||||
|
||||
template <typename Callback>
|
||||
std::unique_ptr<Rendering::RenderPass> MakeLambdaRenderPass(const char* name, Callback&& callback) {
|
||||
return std::make_unique<LambdaRenderPass>(
|
||||
name,
|
||||
LambdaRenderPass::ExecuteCallback(std::forward<Callback>(callback)));
|
||||
}
|
||||
|
||||
inline void SetViewportStatusIfEmpty(std::string& statusText, const char* message) {
|
||||
if (statusText.empty()) {
|
||||
statusText = message;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -152,6 +208,21 @@ public:
|
||||
return PickSceneViewportEntity(request).entityId;
|
||||
}
|
||||
|
||||
void AlignSceneViewToOrientationAxis(SceneViewportOrientationAxis axis) override {
|
||||
if (!EnsureSceneViewCamera()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Math::Vector3 axisDirection = GetSceneViewportOrientationAxisVector(axis);
|
||||
if (axisDirection.SqrMagnitude() <= Math::EPSILON) {
|
||||
return;
|
||||
}
|
||||
|
||||
// To make the clicked cone face the screen, the camera must look back along that axis.
|
||||
m_sceneViewCamera.controller.AnimateToForward(axisDirection * -1.0f);
|
||||
ApplySceneViewCameraController();
|
||||
}
|
||||
|
||||
SceneViewportOverlayData GetSceneViewOverlayData() const override {
|
||||
SceneViewportOverlayData data = {};
|
||||
if (m_sceneViewCamera.gameObject == nullptr || m_sceneViewCamera.camera == nullptr) {
|
||||
@@ -457,6 +528,150 @@ private:
|
||||
return surface;
|
||||
}
|
||||
|
||||
bool BuildSceneViewPostPassSequence(
|
||||
ViewportEntry& entry,
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const Rendering::RenderCameraData& cameraData,
|
||||
const std::vector<Rendering::VisibleRenderItem>& selectionRenderables,
|
||||
Rendering::RenderPassSequence& outPostPasses) {
|
||||
if (!overlay.valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool hasSelection = !selectionRenderables.empty();
|
||||
if (hasSelection && kDebugSceneSelectionMask) {
|
||||
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;
|
||||
}));
|
||||
outPostPasses.AddPass(MakeLambdaRenderPass(
|
||||
"SceneSelectionMaskDebug",
|
||||
[this, &entry, &cameraData, &selectionRenderables](
|
||||
const Rendering::RenderPassContext& context) {
|
||||
const float debugClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
RHI::RHIResourceView* colorView = entry.colorView;
|
||||
context.renderContext.commandList->SetRenderTargets(1, &colorView, entry.depthView);
|
||||
context.renderContext.commandList->ClearRenderTarget(colorView, debugClearColor);
|
||||
|
||||
const bool rendered = m_sceneSelectionMaskPass.Render(
|
||||
context.renderContext,
|
||||
context.surface,
|
||||
cameraData,
|
||||
selectionRenderables);
|
||||
if (!rendered) {
|
||||
SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask debug pass failed");
|
||||
}
|
||||
return rendered;
|
||||
}));
|
||||
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;
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasSelection) {
|
||||
if (entry.selectionMaskView == nullptr || entry.selectionMaskShaderView == nullptr) {
|
||||
SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask target is unavailable");
|
||||
} else {
|
||||
Rendering::RenderSurface selectionMaskSurface = BuildSelectionMaskSurface(entry);
|
||||
outPostPasses.AddPass(MakeLambdaRenderPass(
|
||||
"SceneSelectionMask",
|
||||
[this, &entry, selectionMaskSurface, &cameraData, &selectionRenderables](
|
||||
const Rendering::RenderPassContext& context) mutable {
|
||||
context.renderContext.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;
|
||||
context.renderContext.commandList->SetRenderTargets(1, &maskView, entry.depthView);
|
||||
context.renderContext.commandList->ClearRenderTarget(maskView, maskClearColor);
|
||||
|
||||
const bool rendered = m_sceneSelectionMaskPass.Render(
|
||||
context.renderContext,
|
||||
selectionMaskSurface,
|
||||
cameraData,
|
||||
selectionRenderables);
|
||||
if (!rendered) {
|
||||
SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask pass failed");
|
||||
}
|
||||
|
||||
context.renderContext.commandList->TransitionBarrier(
|
||||
entry.selectionMaskView,
|
||||
entry.selectionMaskState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
entry.selectionMaskState = RHI::ResourceStates::PixelShaderResource;
|
||||
return rendered;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}));
|
||||
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;
|
||||
}));
|
||||
|
||||
if (hasSelection && entry.selectionMaskShaderView != nullptr) {
|
||||
outPostPasses.AddPass(MakeLambdaRenderPass(
|
||||
"SceneSelectionOutline",
|
||||
[this, &entry](const Rendering::RenderPassContext& context) {
|
||||
const bool rendered = m_sceneSelectionOutlinePass.Render(
|
||||
context.renderContext,
|
||||
context.surface,
|
||||
entry.selectionMaskShaderView);
|
||||
if (!rendered) {
|
||||
SetViewportStatusIfEmpty(entry.statusText, "Scene selection outline pass failed");
|
||||
}
|
||||
return rendered;
|
||||
}));
|
||||
}
|
||||
|
||||
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;
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderViewportEntry(
|
||||
ViewportEntry& entry,
|
||||
IEditorContext& context,
|
||||
@@ -470,6 +685,7 @@ private:
|
||||
Rendering::RenderSurface surface = BuildSurface(entry);
|
||||
|
||||
if (entry.kind == EditorViewportKind::Scene) {
|
||||
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);
|
||||
@@ -477,124 +693,54 @@ private:
|
||||
}
|
||||
|
||||
ApplySceneViewCameraController();
|
||||
entry.statusText.clear();
|
||||
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();
|
||||
}
|
||||
|
||||
const SceneViewportOverlayData overlay = GetSceneViewOverlayData();
|
||||
Rendering::RenderPassSequence sceneViewPostPasses;
|
||||
Rendering::RenderCameraData cameraData = {};
|
||||
std::vector<Rendering::VisibleRenderItem> selectionRenderables;
|
||||
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);
|
||||
}
|
||||
selectionRenderables = CollectSceneViewportSelectionRenderables(
|
||||
*scene,
|
||||
context.GetSelectionManager().GetSelectedEntities(),
|
||||
overlay.cameraPosition);
|
||||
if (!selectionRenderables.empty()) {
|
||||
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;
|
||||
BuildSceneViewPostPassSequence(
|
||||
entry,
|
||||
overlay,
|
||||
cameraData,
|
||||
selectionRenderables,
|
||||
sceneViewPostPasses);
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneViewPostPasses.GetPassCount() > 0) {
|
||||
requests[0].postScenePasses = &sceneViewPostPasses;
|
||||
}
|
||||
|
||||
if (!m_sceneRenderer->Render(requests)) {
|
||||
SetViewportStatusIfEmpty(entry.statusText, "Scene renderer failed");
|
||||
ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f);
|
||||
return;
|
||||
}
|
||||
entry.colorState = RHI::ResourceStates::PixelShaderResource;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user