Route scene viewport overlays through render passes

This commit is contained in:
2026-03-30 03:52:59 +08:00
parent 2a31628db1
commit b92f9bfa70
2 changed files with 305 additions and 112 deletions

View File

@@ -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

View File

@@ -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;
}