diff --git a/editor/src/Viewport/ViewportHostService.h b/editor/src/Viewport/ViewportHostService.h index 75659c3c..a3b02ef3 100644 --- a/editor/src/Viewport/ViewportHostService.h +++ b/editor/src/Viewport/ViewportHostService.h @@ -6,6 +6,7 @@ #include "IViewportHostService.h" #include "SceneViewportPicker.h" #include "SceneViewportCameraController.h" +#include "ViewportHostSurfaceUtils.h" #include "UI/ImGuiBackendBridge.h" #include @@ -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(extent - 1u); - const float clamped = (std::max)(0.0f, (std::min)(value, maxCoordinate)); - return static_cast(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(format); - desc.textureType = static_cast(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(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( diff --git a/editor/src/Viewport/ViewportHostSurfaceUtils.h b/editor/src/Viewport/ViewportHostSurfaceUtils.h new file mode 100644 index 00000000..178225a0 --- /dev/null +++ b/editor/src/Viewport/ViewportHostSurfaceUtils.h @@ -0,0 +1,118 @@ +#pragma once + +#include "IViewportHostService.h" + +#include +#include +#include +#include + +#include +#include +#include + +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(extent - 1u); + const float clamped = (std::max)(0.0f, (std::min)(value, maxCoordinate)); + return static_cast(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(format); + desc.textureType = static_cast(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(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 diff --git a/tests/editor/CMakeLists.txt b/tests/editor/CMakeLists.txt index 4d12d8d4..157ee46c 100644 --- a/tests/editor/CMakeLists.txt +++ b/tests/editor/CMakeLists.txt @@ -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 diff --git a/tests/editor/test_viewport_host_surface_utils.cpp b/tests/editor/test_viewport_host_surface_utils.cpp new file mode 100644 index 00000000..bbc4b4f9 --- /dev/null +++ b/tests/editor/test_viewport_host_surface_utils.cpp @@ -0,0 +1,151 @@ +#include + +#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(Format::D24_UNorm_S8_UInt)); + EXPECT_EQ(textureDesc.textureType, static_cast(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(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