refactor: extract viewport host surface utilities

This commit is contained in:
2026-04-01 19:47:15 +08:00
parent d58962ad5e
commit bc6e20de48
4 changed files with 302 additions and 59 deletions

View File

@@ -6,6 +6,7 @@
#include "IViewportHostService.h"
#include "SceneViewportPicker.h"
#include "SceneViewportCameraController.h"
#include "ViewportHostSurfaceUtils.h"
#include "UI/ImGuiBackendBridge.h"
#include <XCEngine/Components/CameraComponent.h>
@@ -75,16 +76,6 @@ inline void SetViewportStatusIfEmpty(std::string& statusText, const char* messag
}
}
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));
}
Rendering::Passes::InfiniteGridPassData BuildInfiniteGridPassData(
const SceneViewportOverlayData& overlay) {
Rendering::Passes::InfiniteGridPassData data = {};
@@ -422,28 +413,6 @@ private:
m_sceneViewCamera.controller.Focus(center);
}
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);
@@ -505,22 +474,26 @@ private:
}
bool EnsureViewportResources(ViewportEntry& entry) {
if (entry.requestedWidth == 0 || entry.requestedHeight == 0) {
return false;
ViewportHostResourceReuseQuery reuseQuery = {};
reuseQuery.kind = entry.kind;
reuseQuery.width = entry.width;
reuseQuery.height = entry.height;
reuseQuery.requestedWidth = entry.requestedWidth;
reuseQuery.requestedHeight = entry.requestedHeight;
reuseQuery.resources.hasColorTexture = entry.colorTexture != nullptr;
reuseQuery.resources.hasColorView = entry.colorView != nullptr;
reuseQuery.resources.hasDepthTexture = entry.depthTexture != nullptr;
reuseQuery.resources.hasDepthView = entry.depthView != nullptr;
reuseQuery.resources.hasObjectIdTexture = entry.objectIdTexture != nullptr;
reuseQuery.resources.hasObjectIdView = entry.objectIdView != nullptr;
reuseQuery.resources.hasObjectIdShaderView = entry.objectIdShaderView != nullptr;
reuseQuery.resources.hasTextureDescriptor = entry.textureId != ImTextureID{};
if (CanReuseViewportResources(reuseQuery)) {
return true;
}
if (entry.width == entry.requestedWidth &&
entry.height == entry.requestedHeight &&
entry.colorTexture != nullptr &&
entry.colorView != nullptr &&
entry.depthTexture != nullptr &&
entry.depthView != nullptr &&
(entry.kind != EditorViewportKind::Scene ||
(entry.objectIdTexture != nullptr &&
entry.objectIdView != nullptr &&
entry.objectIdShaderView != nullptr)) &&
entry.textureId != ImTextureID{}) {
return true;
if (entry.requestedWidth == 0 || entry.requestedHeight == 0) {
return false;
}
DestroyViewportResources(entry);
@@ -538,7 +511,7 @@ private:
return false;
}
if (entry.kind == EditorViewportKind::Scene &&
if (ViewportRequiresObjectIdResources(entry.kind) &&
!CreateSceneViewportObjectIdResources(entry)) {
DestroyViewportResources(entry);
return false;
@@ -555,21 +528,21 @@ private:
}
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;
return BuildViewportRenderSurface(
entry.width,
entry.height,
entry.colorView,
entry.depthView,
entry.colorState);
}
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;
return BuildViewportRenderSurface(
entry.width,
entry.height,
entry.objectIdView,
entry.depthView,
entry.objectIdState);
}
void AddSceneColorToRenderTargetPass(

View File

@@ -0,0 +1,118 @@
#pragma once
#include "IViewportHostService.h"
#include <XCEngine/RHI/RHIEnums.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHITypes.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
namespace XCEngine {
namespace Editor {
struct ViewportHostResourcePresence {
bool hasColorTexture = false;
bool hasColorView = false;
bool hasDepthTexture = false;
bool hasDepthView = false;
bool hasObjectIdTexture = false;
bool hasObjectIdView = false;
bool hasObjectIdShaderView = false;
bool hasTextureDescriptor = false;
};
struct ViewportHostResourceReuseQuery {
EditorViewportKind kind = EditorViewportKind::Scene;
uint32_t width = 0;
uint32_t height = 0;
uint32_t requestedWidth = 0;
uint32_t requestedHeight = 0;
ViewportHostResourcePresence resources = {};
};
inline bool ViewportRequiresObjectIdResources(EditorViewportKind kind) {
return kind == EditorViewportKind::Scene;
}
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));
}
inline bool CanReuseViewportResources(const ViewportHostResourceReuseQuery& query) {
if (query.requestedWidth == 0 || query.requestedHeight == 0) {
return false;
}
if (query.width != query.requestedWidth || query.height != query.requestedHeight) {
return false;
}
if (!query.resources.hasColorTexture ||
!query.resources.hasColorView ||
!query.resources.hasDepthTexture ||
!query.resources.hasDepthView ||
!query.resources.hasTextureDescriptor) {
return false;
}
if (!ViewportRequiresObjectIdResources(query.kind)) {
return true;
}
return query.resources.hasObjectIdTexture &&
query.resources.hasObjectIdView &&
query.resources.hasObjectIdShaderView;
}
inline RHI::TextureDesc BuildViewportTextureDesc(
uint32_t width,
uint32_t height,
RHI::Format format) {
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;
}
inline RHI::ResourceViewDesc BuildViewportTextureViewDesc(RHI::Format format) {
RHI::ResourceViewDesc desc = {};
desc.format = static_cast<uint32_t>(format);
desc.dimension = RHI::ResourceViewDimension::Texture2D;
return desc;
}
inline Rendering::RenderSurface BuildViewportRenderSurface(
uint32_t width,
uint32_t height,
RHI::RHIResourceView* colorView,
RHI::RHIResourceView* depthView,
RHI::ResourceStates colorStateBefore,
RHI::ResourceStates colorStateAfter = RHI::ResourceStates::PixelShaderResource) {
Rendering::RenderSurface surface(width, height);
surface.SetColorAttachment(colorView);
surface.SetDepthAttachment(depthView);
surface.SetColorStateBefore(colorStateBefore);
surface.SetColorStateAfter(colorStateAfter);
return surface;
}
} // namespace Editor
} // namespace XCEngine

View File

@@ -10,6 +10,7 @@ set(EDITOR_TEST_SOURCES
test_scene_viewport_scale_gizmo.cpp
test_scene_viewport_picker.cpp
test_scene_viewport_overlay_renderer.cpp
test_viewport_host_surface_utils.cpp
${CMAKE_SOURCE_DIR}/editor/src/Core/UndoManager.cpp
${CMAKE_SOURCE_DIR}/editor/src/Managers/SceneManager.cpp
${CMAKE_SOURCE_DIR}/editor/src/Managers/ProjectManager.cpp

View File

@@ -0,0 +1,151 @@
#include <gtest/gtest.h>
#include "Viewport/ViewportHostSurfaceUtils.h"
namespace {
using XCEngine::Editor::BuildViewportRenderSurface;
using XCEngine::Editor::BuildViewportTextureDesc;
using XCEngine::Editor::BuildViewportTextureViewDesc;
using XCEngine::Editor::CanReuseViewportResources;
using XCEngine::Editor::ClampViewportPixelCoordinate;
using XCEngine::Editor::EditorViewportKind;
using XCEngine::Editor::ViewportHostResourceReuseQuery;
using XCEngine::Editor::ViewportRequiresObjectIdResources;
using XCEngine::RHI::Format;
using XCEngine::RHI::RHIResourceView;
using XCEngine::RHI::ResourceStates;
using XCEngine::RHI::ResourceViewDimension;
using XCEngine::RHI::ResourceViewType;
class DummyResourceView final : public RHIResourceView {
public:
void Shutdown() override {
}
void* GetNativeHandle() override {
return nullptr;
}
bool IsValid() const override {
return true;
}
ResourceViewType GetViewType() const override {
return ResourceViewType::RenderTarget;
}
ResourceViewDimension GetDimension() const override {
return ResourceViewDimension::Texture2D;
}
Format GetFormat() const override {
return Format::R8G8B8A8_UNorm;
}
};
TEST(ViewportHostSurfaceUtilsTest, ClampViewportPixelCoordinateClampsAndFloors) {
EXPECT_EQ(ClampViewportPixelCoordinate(-3.0f, 1280), 0u);
EXPECT_EQ(ClampViewportPixelCoordinate(0.99f, 1280), 0u);
EXPECT_EQ(ClampViewportPixelCoordinate(10.8f, 1280), 10u);
EXPECT_EQ(ClampViewportPixelCoordinate(5000.0f, 1280), 1279u);
EXPECT_EQ(ClampViewportPixelCoordinate(12.0f, 0), 0u);
}
TEST(ViewportHostSurfaceUtilsTest, ViewportReuseRequiresObjectIdOnlyForSceneViewport) {
ViewportHostResourceReuseQuery gameQuery = {};
gameQuery.kind = EditorViewportKind::Game;
gameQuery.width = 1280;
gameQuery.height = 720;
gameQuery.requestedWidth = 1280;
gameQuery.requestedHeight = 720;
gameQuery.resources.hasColorTexture = true;
gameQuery.resources.hasColorView = true;
gameQuery.resources.hasDepthTexture = true;
gameQuery.resources.hasDepthView = true;
gameQuery.resources.hasTextureDescriptor = true;
EXPECT_FALSE(ViewportRequiresObjectIdResources(EditorViewportKind::Game));
EXPECT_TRUE(CanReuseViewportResources(gameQuery));
ViewportHostResourceReuseQuery sceneQuery = gameQuery;
sceneQuery.kind = EditorViewportKind::Scene;
EXPECT_TRUE(ViewportRequiresObjectIdResources(EditorViewportKind::Scene));
EXPECT_FALSE(CanReuseViewportResources(sceneQuery));
sceneQuery.resources.hasObjectIdTexture = true;
sceneQuery.resources.hasObjectIdView = true;
sceneQuery.resources.hasObjectIdShaderView = true;
EXPECT_TRUE(CanReuseViewportResources(sceneQuery));
}
TEST(ViewportHostSurfaceUtilsTest, ViewportReuseRejectsMismatchedOrMissingResources) {
ViewportHostResourceReuseQuery query = {};
query.kind = EditorViewportKind::Scene;
query.width = 1280;
query.height = 720;
query.requestedWidth = 1280;
query.requestedHeight = 720;
query.resources.hasColorTexture = true;
query.resources.hasColorView = true;
query.resources.hasDepthTexture = true;
query.resources.hasDepthView = true;
query.resources.hasObjectIdTexture = true;
query.resources.hasObjectIdView = true;
query.resources.hasObjectIdShaderView = true;
query.resources.hasTextureDescriptor = true;
EXPECT_TRUE(CanReuseViewportResources(query));
query.requestedWidth = 0;
EXPECT_FALSE(CanReuseViewportResources(query));
query.requestedWidth = 1280;
query.width = 1024;
EXPECT_FALSE(CanReuseViewportResources(query));
query.width = 1280;
query.resources.hasTextureDescriptor = false;
EXPECT_FALSE(CanReuseViewportResources(query));
}
TEST(ViewportHostSurfaceUtilsTest, BuildViewportTextureDescriptorsUseExpectedDefaults) {
const auto textureDesc = BuildViewportTextureDesc(640, 360, Format::D24_UNorm_S8_UInt);
EXPECT_EQ(textureDesc.width, 640u);
EXPECT_EQ(textureDesc.height, 360u);
EXPECT_EQ(textureDesc.depth, 1u);
EXPECT_EQ(textureDesc.mipLevels, 1u);
EXPECT_EQ(textureDesc.arraySize, 1u);
EXPECT_EQ(textureDesc.format, static_cast<uint32_t>(Format::D24_UNorm_S8_UInt));
EXPECT_EQ(textureDesc.textureType, static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D));
EXPECT_EQ(textureDesc.sampleCount, 1u);
EXPECT_EQ(textureDesc.sampleQuality, 0u);
EXPECT_EQ(textureDesc.flags, 0u);
const auto viewDesc = BuildViewportTextureViewDesc(Format::R8G8B8A8_UNorm);
EXPECT_EQ(viewDesc.format, static_cast<uint32_t>(Format::R8G8B8A8_UNorm));
EXPECT_EQ(viewDesc.dimension, ResourceViewDimension::Texture2D);
}
TEST(ViewportHostSurfaceUtilsTest, BuildViewportRenderSurfaceCarriesAttachmentsAndStates) {
DummyResourceView colorView;
DummyResourceView depthView;
const auto surface = BuildViewportRenderSurface(
800,
600,
&colorView,
&depthView,
ResourceStates::Common,
ResourceStates::PixelShaderResource);
ASSERT_EQ(surface.GetWidth(), 800u);
ASSERT_EQ(surface.GetHeight(), 600u);
ASSERT_EQ(surface.GetColorAttachments().size(), 1u);
EXPECT_EQ(surface.GetColorAttachments()[0], &colorView);
EXPECT_EQ(surface.GetDepthAttachment(), &depthView);
EXPECT_EQ(surface.GetColorStateBefore(), ResourceStates::Common);
EXPECT_EQ(surface.GetColorStateAfter(), ResourceStates::PixelShaderResource);
}
} // namespace