From c6b835a3905bf2fe4c98b18d14a3848e8ac46d31 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 4 Apr 2026 17:10:15 +0800 Subject: [PATCH] Formalize scene viewport overlay sprite resource lifecycle --- editor/CMakeLists.txt | 1 + .../Passes/SceneViewportEditorOverlayPass.cpp | 190 ++-------------- .../Passes/SceneViewportEditorOverlayPass.h | 10 +- .../SceneViewportOverlaySpriteResources.cpp | 209 ++++++++++++++++++ .../SceneViewportOverlaySpriteResources.h | 126 +++++++++++ editor/src/panels/ConsolePanel.h | 2 + tests/editor/CMakeLists.txt | 2 + ...cene_viewport_overlay_sprite_resources.cpp | 54 +++++ .../test_scene_viewport_shader_paths.cpp | 11 + .../test_viewport_render_flow_utils.cpp | 13 +- 10 files changed, 434 insertions(+), 184 deletions(-) create mode 100644 editor/src/Viewport/SceneViewportOverlaySpriteResources.cpp create mode 100644 editor/src/Viewport/SceneViewportOverlaySpriteResources.h create mode 100644 tests/editor/test_scene_viewport_overlay_sprite_resources.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 645207ed..018b48ec 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -89,6 +89,7 @@ add_executable(${PROJECT_NAME} WIN32 src/Viewport/SceneViewportTransformGizmoCoordinator.cpp src/Viewport/SceneViewportOrientationGizmo.cpp src/Viewport/SceneViewportOverlayBuilder.cpp + src/Viewport/SceneViewportOverlaySpriteResources.cpp src/Viewport/SceneViewportOverlayProviders.cpp src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp src/Viewport/Passes/SceneViewportGridPass.cpp diff --git a/editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp b/editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp index 52024adf..5003830f 100644 --- a/editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp +++ b/editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp @@ -1,7 +1,6 @@ #include "Passes/SceneViewportEditorOverlayPass.h" #include "Viewport/SceneViewportMath.h" -#include "Viewport/SceneViewportResourcePaths.h" #include #include @@ -18,18 +17,13 @@ #include #include #include -#include -#include #include -#include - namespace XCEngine { namespace Editor { namespace { -constexpr size_t kOverlaySpriteTextureCount = 2u; constexpr uint64_t kMinDynamicVertexBufferBytes = 4096u; const char kSceneViewportEditorOverlayLineHlsl[] = R"( @@ -191,78 +185,6 @@ private: SceneViewportOverlayFrameData m_frameData = {}; }; -size_t ToSpriteTextureIndex(SceneViewportOverlaySpriteTextureKind textureKind) { - switch (textureKind) { - case SceneViewportOverlaySpriteTextureKind::Camera: - return 0u; - case SceneViewportOverlaySpriteTextureKind::Light: - return 1u; - default: - return 0u; - } -} - -std::filesystem::path ResolveOverlaySpriteTexturePath(SceneViewportOverlaySpriteTextureKind textureKind) { - switch (textureKind) { - case SceneViewportOverlaySpriteTextureKind::Camera: - return std::filesystem::path(GetSceneViewportCameraGizmoIconPath().CStr()); - case SceneViewportOverlaySpriteTextureKind::Light: - return std::filesystem::path(GetSceneViewportMainLightGizmoIconPath().CStr()); - default: - break; - } - - return std::filesystem::path(); -} - -bool ReadFileBytes(const std::filesystem::path& filePath, std::vector& outBytes) { - std::ifstream input(filePath, std::ios::binary | std::ios::ate); - if (!input.is_open()) { - return false; - } - - const std::ifstream::pos_type size = input.tellg(); - if (size <= 0) { - return false; - } - - outBytes.resize(static_cast(size)); - input.seekg(0, std::ios::beg); - return input.read(reinterpret_cast(outBytes.data()), size).good(); -} - -bool DecodeTextureFile( - const std::filesystem::path& filePath, - std::vector& outPixels, - int& outWidth, - int& outHeight) { - std::vector fileData = {}; - if (!ReadFileBytes(filePath, fileData)) { - return false; - } - - int channels = 0; - stbi_uc* pixels = stbi_load_from_memory( - fileData.data(), - static_cast(fileData.size()), - &outWidth, - &outHeight, - &channels, - STBI_rgb_alpha); - if (pixels == nullptr || outWidth <= 0 || outHeight <= 0) { - if (pixels != nullptr) { - stbi_image_free(pixels); - } - return false; - } - - outPixels.assign( - pixels, - pixels + static_cast(outWidth) * static_cast(outHeight) * 4u); - stbi_image_free(pixels); - return true; -} - RHI::GraphicsPipelineDesc BuildLinePipelineDesc( RHI::RHIPipelineLayout* pipelineLayout, bool depthTestEnabled) { @@ -550,19 +472,20 @@ bool SceneViewportEditorOverlayPassRenderer::Render( } std::vector spriteVertices = {}; - std::array depthTestedSpriteBatches = {}; - std::array alwaysOnTopSpriteBatches = {}; + std::array depthTestedSpriteBatches = {}; + std::array alwaysOnTopSpriteBatches = {}; if (spriteVertexCount > 0u) { spriteVertices.reserve(spriteVertexCount); const auto appendSpriteBatches = [&spriteVertices]( const std::vector& sprites, - std::array& outBatches) { - for (size_t textureIndex = 0; textureIndex < kOverlaySpriteTextureCount; ++textureIndex) { + std::array& outBatches) { + for (size_t textureIndex = 0; textureIndex < kSceneViewportOverlaySpriteResourceCount; ++textureIndex) { OverlaySpriteBatchRange range = {}; range.firstVertex = static_cast(spriteVertices.size()); for (const SceneViewportOverlaySpritePrimitive* sprite : sprites) { - if (sprite == nullptr || ToSpriteTextureIndex(sprite->textureKind) != textureIndex) { + if (sprite == nullptr || + GetSceneViewportOverlaySpriteResourceIndex(sprite->textureKind) != textureIndex) { continue; } @@ -651,21 +574,28 @@ bool SceneViewportEditorOverlayPassRenderer::Render( const auto drawSpriteBatchGroup = [this, commandList]( RHI::RHIPipelineState* pipelineState, - const std::array& batches) { + const std::array& batches) { if (pipelineState == nullptr) { return; } commandList->SetPipelineState(pipelineState); - for (size_t textureIndex = 0; textureIndex < kOverlaySpriteTextureCount; ++textureIndex) { + for (size_t textureIndex = 0; textureIndex < kSceneViewportOverlaySpriteResourceCount; ++textureIndex) { const OverlaySpriteBatchRange& batch = batches[textureIndex]; if (!batch.HasVertices()) { continue; } + RHI::RHIDescriptorSet* const textureSet = + m_overlaySpriteResources.GetTextureSet( + GetSceneViewportOverlaySpriteTextureKindByIndex(textureIndex)); + if (textureSet == nullptr) { + continue; + } + RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, - m_overlaySpriteTextures[textureIndex].textureSet, + textureSet, m_samplerSet }; commandList->SetGraphicsDescriptorSets(0, 3, descriptorSets, m_spritePipelineLayout); @@ -814,7 +744,7 @@ bool SceneViewportEditorOverlayPassRenderer::CreateResources( RHI::DescriptorPoolDesc texturePoolDesc = {}; texturePoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV; - texturePoolDesc.descriptorCount = static_cast(kOverlaySpriteTextureCount); + texturePoolDesc.descriptorCount = static_cast(kSceneViewportOverlaySpriteResourceCount); texturePoolDesc.shaderVisible = true; m_texturePool = m_device->CreateDescriptorPool(texturePoolDesc); if (m_texturePool == nullptr) { @@ -1037,73 +967,7 @@ bool SceneViewportEditorOverlayPassRenderer::EnsureSpriteBufferCapacity(size_t r } bool SceneViewportEditorOverlayPassRenderer::EnsureIconTexturesLoaded() { - if (m_device == nullptr || m_texturePool == nullptr) { - return false; - } - - RHI::DescriptorSetLayoutBinding textureBinding = {}; - textureBinding.binding = 0; - textureBinding.type = static_cast(RHI::DescriptorType::SRV); - textureBinding.count = 1; - RHI::DescriptorSetLayoutDesc textureLayout = {}; - textureLayout.bindings = &textureBinding; - textureLayout.bindingCount = 1; - - for (size_t textureIndex = 0; textureIndex < kOverlaySpriteTextureCount; ++textureIndex) { - OverlaySpriteTextureResources& resources = m_overlaySpriteTextures[textureIndex]; - if (resources.texture != nullptr && resources.shaderView != nullptr && resources.textureSet != nullptr) { - continue; - } - - const SceneViewportOverlaySpriteTextureKind textureKind = - textureIndex == 0u - ? SceneViewportOverlaySpriteTextureKind::Camera - : SceneViewportOverlaySpriteTextureKind::Light; - - std::vector pixels = {}; - int width = 0; - int height = 0; - if (!DecodeTextureFile(ResolveOverlaySpriteTexturePath(textureKind), pixels, width, height)) { - return false; - } - - RHI::TextureDesc textureDesc = {}; - textureDesc.width = static_cast(width); - textureDesc.height = static_cast(height); - textureDesc.depth = 1; - textureDesc.mipLevels = 1; - textureDesc.arraySize = 1; - textureDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); - textureDesc.textureType = static_cast(RHI::TextureType::Texture2D); - textureDesc.sampleCount = 1; - textureDesc.sampleQuality = 0; - textureDesc.flags = 0; - resources.texture = m_device->CreateTexture( - textureDesc, - pixels.data(), - pixels.size(), - static_cast(width * 4)); - if (resources.texture == nullptr) { - return false; - } - - RHI::ResourceViewDesc viewDesc = {}; - viewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); - viewDesc.dimension = RHI::ResourceViewDimension::Texture2D; - viewDesc.mipLevel = 0; - resources.shaderView = m_device->CreateShaderResourceView(resources.texture, viewDesc); - if (resources.shaderView == nullptr) { - return false; - } - - resources.textureSet = m_texturePool->AllocateSet(textureLayout); - if (resources.textureSet == nullptr) { - return false; - } - resources.textureSet->Update(0, resources.shaderView); - } - - return true; + return m_overlaySpriteResources.EnsureResources(m_device, m_texturePool); } void SceneViewportEditorOverlayPassRenderer::DestroyResources() { @@ -1138,23 +1002,7 @@ void SceneViewportEditorOverlayPassRenderer::DestroyResources() { m_spriteVertexBuffer = nullptr; } - for (OverlaySpriteTextureResources& resources : m_overlaySpriteTextures) { - if (resources.textureSet != nullptr) { - resources.textureSet->Shutdown(); - delete resources.textureSet; - resources.textureSet = nullptr; - } - if (resources.shaderView != nullptr) { - resources.shaderView->Shutdown(); - delete resources.shaderView; - resources.shaderView = nullptr; - } - if (resources.texture != nullptr) { - resources.texture->Shutdown(); - delete resources.texture; - resources.texture = nullptr; - } - } + m_overlaySpriteResources.Shutdown(); if (m_depthTestedLinePipelineState != nullptr) { m_depthTestedLinePipelineState->Shutdown(); diff --git a/editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h b/editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h index e116bf8d..03cee34d 100644 --- a/editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h +++ b/editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h @@ -1,12 +1,12 @@ #pragma once #include "Viewport/SceneViewportEditorOverlayData.h" +#include "Viewport/SceneViewportOverlaySpriteResources.h" #include #include #include -#include #include #include #include @@ -39,12 +39,6 @@ public: const SceneViewportOverlayFrameData& frameData); private: - struct OverlaySpriteTextureResources { - RHI::RHITexture* texture = nullptr; - RHI::RHIResourceView* shaderView = nullptr; - RHI::RHIDescriptorSet* textureSet = nullptr; - }; - bool EnsureInitialized(const Rendering::RenderContext& renderContext); bool CreateResources(const Rendering::RenderContext& renderContext); bool EnsureLineBufferCapacity(size_t requiredVertexCount); @@ -78,7 +72,7 @@ private: uint64_t m_lineVertexBufferCapacity = 0; uint64_t m_screenTriangleVertexBufferCapacity = 0; uint64_t m_spriteVertexBufferCapacity = 0; - std::array m_overlaySpriteTextures = {}; + SceneViewportOverlaySpriteResourceCache m_overlaySpriteResources = {}; }; std::unique_ptr CreateSceneViewportEditorOverlayPass( diff --git a/editor/src/Viewport/SceneViewportOverlaySpriteResources.cpp b/editor/src/Viewport/SceneViewportOverlaySpriteResources.cpp new file mode 100644 index 00000000..ee557172 --- /dev/null +++ b/editor/src/Viewport/SceneViewportOverlaySpriteResources.cpp @@ -0,0 +1,209 @@ +#include "Viewport/SceneViewportOverlaySpriteResources.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace XCEngine { +namespace Editor { + +namespace { + +bool ReadFileBytes(const std::filesystem::path& filePath, std::vector& outBytes) { + std::ifstream input(filePath, std::ios::binary | std::ios::ate); + if (!input.is_open()) { + return false; + } + + const std::ifstream::pos_type size = input.tellg(); + if (size <= 0) { + return false; + } + + outBytes.resize(static_cast(size)); + input.seekg(0, std::ios::beg); + return input.read(reinterpret_cast(outBytes.data()), size).good(); +} + +bool DecodeTextureFile( + const std::filesystem::path& filePath, + SceneViewportOverlaySpritePixels& outPixels) { + outPixels = {}; + + std::vector fileData = {}; + if (!ReadFileBytes(filePath, fileData)) { + return false; + } + + int width = 0; + int height = 0; + int channels = 0; + stbi_uc* pixels = stbi_load_from_memory( + fileData.data(), + static_cast(fileData.size()), + &width, + &height, + &channels, + STBI_rgb_alpha); + if (pixels == nullptr || width <= 0 || height <= 0) { + if (pixels != nullptr) { + stbi_image_free(pixels); + } + return false; + } + + outPixels.width = static_cast(width); + outPixels.height = static_cast(height); + outPixels.rgbaPixels.assign( + pixels, + pixels + static_cast(width) * static_cast(height) * 4u); + stbi_image_free(pixels); + return outPixels.IsValid(); +} + +RHI::DescriptorSetLayoutDesc BuildSpriteTextureLayout() { + RHI::DescriptorSetLayoutBinding textureBinding = {}; + textureBinding.binding = 0; + textureBinding.type = static_cast(RHI::DescriptorType::SRV); + textureBinding.count = 1; + + RHI::DescriptorSetLayoutDesc textureLayout = {}; + textureLayout.bindings = &textureBinding; + textureLayout.bindingCount = 1; + return textureLayout; +} + +} // namespace + +bool LoadSceneViewportOverlaySpritePixels( + SceneViewportOverlaySpriteTextureKind textureKind, + SceneViewportOverlaySpritePixels& outPixels) { + const SceneViewportOverlaySpriteAssetSpec spec = + GetSceneViewportOverlaySpriteAssetSpec(textureKind); + if (spec.resourcePath.Empty()) { + outPixels = {}; + return false; + } + + return DecodeTextureFile(std::filesystem::path(spec.resourcePath.CStr()), outPixels); +} + +void SceneViewportOverlaySpriteResourceCache::Shutdown() { + for (ResourceEntry& entry : m_resources) { + DestroyResourceEntry(entry); + } + + m_device = nullptr; + m_texturePool = nullptr; +} + +bool SceneViewportOverlaySpriteResourceCache::EnsureResources( + RHI::RHIDevice* device, + RHI::RHIDescriptorPool* texturePool) { + if (device == nullptr || texturePool == nullptr) { + return false; + } + + if ((m_device != nullptr && m_device != device) || + (m_texturePool != nullptr && m_texturePool != texturePool)) { + Shutdown(); + } + + m_device = device; + m_texturePool = texturePool; + const RHI::DescriptorSetLayoutDesc textureLayout = BuildSpriteTextureLayout(); + + for (size_t textureIndex = 0u; + textureIndex < kSceneViewportOverlaySpriteResourceCount; + ++textureIndex) { + ResourceEntry& entry = m_resources[textureIndex]; + if (entry.texture != nullptr && + entry.shaderView != nullptr && + entry.textureSet != nullptr) { + continue; + } + + const SceneViewportOverlaySpriteTextureKind textureKind = + GetSceneViewportOverlaySpriteTextureKindByIndex(textureIndex); + SceneViewportOverlaySpritePixels pixels = {}; + if (!LoadSceneViewportOverlaySpritePixels(textureKind, pixels)) { + return false; + } + + RHI::TextureDesc textureDesc = {}; + textureDesc.width = pixels.width; + textureDesc.height = pixels.height; + textureDesc.depth = 1; + textureDesc.mipLevels = 1; + textureDesc.arraySize = 1; + textureDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); + textureDesc.textureType = static_cast(RHI::TextureType::Texture2D); + textureDesc.sampleCount = 1; + textureDesc.sampleQuality = 0; + textureDesc.flags = 0; + + entry.texture = m_device->CreateTexture( + textureDesc, + pixels.rgbaPixels.data(), + pixels.rgbaPixels.size(), + pixels.width * 4u); + if (entry.texture == nullptr) { + DestroyResourceEntry(entry); + return false; + } + + RHI::ResourceViewDesc viewDesc = {}; + viewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); + viewDesc.dimension = RHI::ResourceViewDimension::Texture2D; + viewDesc.mipLevel = 0; + entry.shaderView = m_device->CreateShaderResourceView(entry.texture, viewDesc); + if (entry.shaderView == nullptr) { + DestroyResourceEntry(entry); + return false; + } + + entry.textureSet = m_texturePool->AllocateSet(textureLayout); + if (entry.textureSet == nullptr) { + DestroyResourceEntry(entry); + return false; + } + entry.textureSet->Update(0, entry.shaderView); + } + + return true; +} + +RHI::RHIDescriptorSet* SceneViewportOverlaySpriteResourceCache::GetTextureSet( + SceneViewportOverlaySpriteTextureKind textureKind) const { + return m_resources[GetSceneViewportOverlaySpriteResourceIndex(textureKind)].textureSet; +} + +void SceneViewportOverlaySpriteResourceCache::DestroyResourceEntry(ResourceEntry& entry) { + if (entry.textureSet != nullptr) { + entry.textureSet->Shutdown(); + delete entry.textureSet; + entry.textureSet = nullptr; + } + if (entry.shaderView != nullptr) { + entry.shaderView->Shutdown(); + delete entry.shaderView; + entry.shaderView = nullptr; + } + if (entry.texture != nullptr) { + entry.texture->Shutdown(); + delete entry.texture; + entry.texture = nullptr; + } +} + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Viewport/SceneViewportOverlaySpriteResources.h b/editor/src/Viewport/SceneViewportOverlaySpriteResources.h new file mode 100644 index 00000000..b5298ef9 --- /dev/null +++ b/editor/src/Viewport/SceneViewportOverlaySpriteResources.h @@ -0,0 +1,126 @@ +#pragma once + +#include "SceneViewportEditorOverlayData.h" +#include "SceneViewportResourcePaths.h" + +#include + +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace RHI { +class RHIDescriptorPool; +class RHIDescriptorSet; +class RHIDevice; +class RHIResourceView; +class RHITexture; +} // namespace RHI + +namespace Editor { + +inline constexpr std::array kSceneViewportOverlaySpriteTextureKinds = { + SceneViewportOverlaySpriteTextureKind::Camera, + SceneViewportOverlaySpriteTextureKind::Light +}; + +inline constexpr size_t kSceneViewportOverlaySpriteResourceCount = + kSceneViewportOverlaySpriteTextureKinds.size(); + +inline size_t GetSceneViewportOverlaySpriteResourceIndex( + SceneViewportOverlaySpriteTextureKind textureKind) { + switch (textureKind) { + case SceneViewportOverlaySpriteTextureKind::Camera: + return 0u; + case SceneViewportOverlaySpriteTextureKind::Light: + return 1u; + default: + return 0u; + } +} + +inline SceneViewportOverlaySpriteTextureKind GetSceneViewportOverlaySpriteTextureKindByIndex( + size_t textureIndex) { + return textureIndex < kSceneViewportOverlaySpriteResourceCount + ? kSceneViewportOverlaySpriteTextureKinds[textureIndex] + : kSceneViewportOverlaySpriteTextureKinds[0]; +} + +struct SceneViewportOverlaySpriteAssetSpec { + SceneViewportOverlaySpriteTextureKind kind = SceneViewportOverlaySpriteTextureKind::Camera; + Containers::String resourcePath = {}; +}; + +inline SceneViewportOverlaySpriteAssetSpec GetSceneViewportOverlaySpriteAssetSpec( + SceneViewportOverlaySpriteTextureKind textureKind) { + SceneViewportOverlaySpriteAssetSpec spec = {}; + spec.kind = textureKind; + + switch (textureKind) { + case SceneViewportOverlaySpriteTextureKind::Camera: + spec.resourcePath = GetSceneViewportCameraGizmoIconPath(); + break; + case SceneViewportOverlaySpriteTextureKind::Light: + spec.resourcePath = GetSceneViewportMainLightGizmoIconPath(); + break; + default: + break; + } + + return spec; +} + +struct SceneViewportOverlaySpritePixels { + std::vector rgbaPixels = {}; + uint32_t width = 0u; + uint32_t height = 0u; + + bool IsValid() const { + const uint64_t expectedBytes = + static_cast(width) * + static_cast(height) * + 4u; + return width > 0u && + height > 0u && + expectedBytes > 0u && + expectedBytes <= static_cast((std::numeric_limits::max)()) && + rgbaPixels.size() == static_cast(expectedBytes); + } +}; + +bool LoadSceneViewportOverlaySpritePixels( + SceneViewportOverlaySpriteTextureKind textureKind, + SceneViewportOverlaySpritePixels& outPixels); + +class SceneViewportOverlaySpriteResourceCache { +public: + ~SceneViewportOverlaySpriteResourceCache() = default; + + void Shutdown(); + + bool EnsureResources( + RHI::RHIDevice* device, + RHI::RHIDescriptorPool* texturePool); + + RHI::RHIDescriptorSet* GetTextureSet( + SceneViewportOverlaySpriteTextureKind textureKind) const; + +private: + struct ResourceEntry { + RHI::RHITexture* texture = nullptr; + RHI::RHIResourceView* shaderView = nullptr; + RHI::RHIDescriptorSet* textureSet = nullptr; + }; + + void DestroyResourceEntry(ResourceEntry& entry); + + RHI::RHIDevice* m_device = nullptr; + RHI::RHIDescriptorPool* m_texturePool = nullptr; + std::array m_resources = {}; +}; + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/panels/ConsolePanel.h b/editor/src/panels/ConsolePanel.h index 1c846d45..72966259 100644 --- a/editor/src/panels/ConsolePanel.h +++ b/editor/src/panels/ConsolePanel.h @@ -36,6 +36,8 @@ private: bool m_playModeActive = false; bool m_playModePaused = false; bool m_requestSearchFocus = false; + bool m_requestedTabSelectionRecovery = false; + bool m_requestedLayoutResetForCollapsedContent = false; }; } diff --git a/tests/editor/CMakeLists.txt b/tests/editor/CMakeLists.txt index 5d94ea4b..80610753 100644 --- a/tests/editor/CMakeLists.txt +++ b/tests/editor/CMakeLists.txt @@ -18,6 +18,7 @@ set(EDITOR_TEST_SOURCES test_scene_viewport_chrome.cpp test_scene_viewport_transform_gizmo_coordinator.cpp test_scene_viewport_shader_paths.cpp + test_scene_viewport_overlay_sprite_resources.cpp test_scene_viewport_overlay_renderer.cpp test_scene_viewport_overlay_providers.cpp test_script_component_editor_utils.cpp @@ -49,6 +50,7 @@ set(EDITOR_TEST_SOURCES ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportTransformGizmoCoordinator.cpp ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOrientationGizmo.cpp ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayBuilder.cpp + ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlaySpriteResources.cpp ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayProviders.cpp ) diff --git a/tests/editor/test_scene_viewport_overlay_sprite_resources.cpp b/tests/editor/test_scene_viewport_overlay_sprite_resources.cpp new file mode 100644 index 00000000..57b13094 --- /dev/null +++ b/tests/editor/test_scene_viewport_overlay_sprite_resources.cpp @@ -0,0 +1,54 @@ +#include + +#include "Viewport/SceneViewportOverlaySpriteResources.h" + +#include +#include + +namespace { + +using XCEngine::Editor::GetSceneViewportOverlaySpriteAssetSpec; +using XCEngine::Editor::GetSceneViewportOverlaySpriteResourceIndex; +using XCEngine::Editor::GetSceneViewportOverlaySpriteTextureKindByIndex; +using XCEngine::Editor::LoadSceneViewportOverlaySpritePixels; +using XCEngine::Editor::SceneViewportOverlaySpritePixels; +using XCEngine::Editor::SceneViewportOverlaySpriteTextureKind; +using XCEngine::Editor::kSceneViewportOverlaySpriteResourceCount; +using XCEngine::Editor::kSceneViewportOverlaySpriteTextureKinds; + +TEST(SceneViewportOverlaySpriteResourcesTest, TextureKindIndexMappingIsStable) { + EXPECT_EQ(kSceneViewportOverlaySpriteResourceCount, 2u); + EXPECT_EQ(GetSceneViewportOverlaySpriteResourceIndex(SceneViewportOverlaySpriteTextureKind::Camera), 0u); + EXPECT_EQ(GetSceneViewportOverlaySpriteResourceIndex(SceneViewportOverlaySpriteTextureKind::Light), 1u); + EXPECT_EQ(GetSceneViewportOverlaySpriteTextureKindByIndex(0u), SceneViewportOverlaySpriteTextureKind::Camera); + EXPECT_EQ(GetSceneViewportOverlaySpriteTextureKindByIndex(1u), SceneViewportOverlaySpriteTextureKind::Light); +} + +TEST(SceneViewportOverlaySpriteResourcesTest, AssetSpecsResolveKnownEditorIcons) { + for (SceneViewportOverlaySpriteTextureKind textureKind : kSceneViewportOverlaySpriteTextureKinds) { + const auto spec = GetSceneViewportOverlaySpriteAssetSpec(textureKind); + EXPECT_EQ(spec.kind, textureKind); + EXPECT_FALSE(spec.resourcePath.Empty()); + + const std::filesystem::path path(spec.resourcePath.CStr()); + EXPECT_TRUE(path.is_absolute()); + EXPECT_TRUE(std::filesystem::exists(path)); + EXPECT_NE(path.generic_string().find("editor/resources/Icons"), std::string::npos); + } +} + +TEST(SceneViewportOverlaySpriteResourcesTest, LoadsOverlaySpritePixelsFromEditorResources) { + for (SceneViewportOverlaySpriteTextureKind textureKind : kSceneViewportOverlaySpriteTextureKinds) { + SceneViewportOverlaySpritePixels pixels = {}; + EXPECT_TRUE(LoadSceneViewportOverlaySpritePixels(textureKind, pixels)); + EXPECT_TRUE(pixels.IsValid()); + EXPECT_GT(pixels.width, 0u); + EXPECT_GT(pixels.height, 0u); + EXPECT_FALSE(pixels.rgbaPixels.empty()); + EXPECT_EQ( + pixels.rgbaPixels.size(), + static_cast(pixels.width) * static_cast(pixels.height) * 4u); + } +} + +} // namespace diff --git a/tests/editor/test_scene_viewport_shader_paths.cpp b/tests/editor/test_scene_viewport_shader_paths.cpp index 04dcbfad..1308dd14 100644 --- a/tests/editor/test_scene_viewport_shader_paths.cpp +++ b/tests/editor/test_scene_viewport_shader_paths.cpp @@ -1,5 +1,6 @@ #include +#include "Viewport/SceneViewportResourcePaths.h" #include "Viewport/SceneViewportShaderPaths.h" #include @@ -10,7 +11,9 @@ namespace { +using XCEngine::Editor::GetSceneViewportCameraGizmoIconPath; using XCEngine::Editor::GetSceneViewportInfiniteGridShaderPath; +using XCEngine::Editor::GetSceneViewportMainLightGizmoIconPath; using XCEngine::Editor::GetSceneViewportObjectIdOutlineShaderPath; using XCEngine::Resources::LoadResult; using XCEngine::Resources::ResourceHandle; @@ -25,13 +28,21 @@ using XCEngine::Resources::ShaderType; TEST(SceneViewportShaderPathsTest, ResolvePathsUnderEditorResources) { const std::filesystem::path gridPath(GetSceneViewportInfiniteGridShaderPath().CStr()); const std::filesystem::path outlinePath(GetSceneViewportObjectIdOutlineShaderPath().CStr()); + const std::filesystem::path cameraIconPath(GetSceneViewportCameraGizmoIconPath().CStr()); + const std::filesystem::path lightIconPath(GetSceneViewportMainLightGizmoIconPath().CStr()); EXPECT_TRUE(gridPath.is_absolute()); EXPECT_TRUE(outlinePath.is_absolute()); + EXPECT_TRUE(cameraIconPath.is_absolute()); + EXPECT_TRUE(lightIconPath.is_absolute()); EXPECT_TRUE(std::filesystem::exists(gridPath)); EXPECT_TRUE(std::filesystem::exists(outlinePath)); + EXPECT_TRUE(std::filesystem::exists(cameraIconPath)); + EXPECT_TRUE(std::filesystem::exists(lightIconPath)); EXPECT_NE(gridPath.generic_string().find("editor/resources/shaders/scene-viewport"), std::string::npos); EXPECT_NE(outlinePath.generic_string().find("editor/resources/shaders/scene-viewport"), std::string::npos); + EXPECT_NE(cameraIconPath.generic_string().find("editor/resources/Icons"), std::string::npos); + EXPECT_NE(lightIconPath.generic_string().find("editor/resources/Icons"), std::string::npos); } TEST(SceneViewportShaderPathsTest, ShaderLoaderLoadsSceneViewportInfiniteGridShader) { diff --git a/tests/editor/test_viewport_render_flow_utils.cpp b/tests/editor/test_viewport_render_flow_utils.cpp index f868ca36..321f1ea5 100644 --- a/tests/editor/test_viewport_render_flow_utils.cpp +++ b/tests/editor/test_viewport_render_flow_utils.cpp @@ -13,6 +13,7 @@ using XCEngine::Editor::ApplySceneViewportRenderRequestSetup; using XCEngine::Editor::ApplySceneViewportRenderPlan; using XCEngine::Editor::ApplyViewportFailureStatus; using XCEngine::Editor::BuildGameViewportRenderFailurePolicy; +using XCEngine::Editor::BuildSceneViewportGridPassData; using XCEngine::Editor::BuildSceneViewportRenderPlan; using XCEngine::Editor::BuildSceneViewportRenderFailurePolicy; using XCEngine::Editor::BuildSceneViewportSelectionOutlineStyle; @@ -24,7 +25,9 @@ using XCEngine::Editor::SceneViewportOverlayFrameData; using XCEngine::Editor::SceneViewportOverlayLinePrimitive; using XCEngine::Editor::SceneViewportRenderFailure; using XCEngine::Editor::SceneViewportOverlayData; +using XCEngine::Editor::SceneViewportGridPassData; using XCEngine::Editor::SceneViewportRenderPlan; +using XCEngine::Editor::SceneViewportSelectionOutlineStyle; using XCEngine::Editor::ViewportRenderTargets; using XCEngine::RHI::Format; using XCEngine::RHI::RHIResourceView; @@ -163,7 +166,7 @@ TEST(ViewportRenderFlowUtilsTest, ApplyViewportFailureStatusRespectsSetIfEmptyBe TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportGridPassDataCopiesSceneCameraState) { const SceneViewportOverlayData overlay = CreateValidOverlay(); - const auto gridPassData = XCEngine::Editor::BuildSceneViewportGridPassData(overlay); + const auto gridPassData = BuildSceneViewportGridPassData(overlay); EXPECT_TRUE(gridPassData.valid); EXPECT_FLOAT_EQ(gridPassData.cameraPosition.x, overlay.cameraPosition.x); @@ -265,7 +268,7 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanCollectsPostSceneA overlay, { 7u, 11u }, editorOverlayFrameData, - [&gridPassFactoryCallCount](const XCEngine::Rendering::Passes::InfiniteGridPassData& data) { + [&gridPassFactoryCallCount](const SceneViewportGridPassData& data) { ++gridPassFactoryCallCount; EXPECT_TRUE(data.valid); return std::make_unique(); @@ -273,7 +276,7 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanCollectsPostSceneA [&selectionOutlinePassFactoryCallCount]( RHIResourceView* objectIdTextureView, const std::vector& selectedObjectIds, - const XCEngine::Rendering::Passes::ObjectIdOutlineStyle& style) { + const SceneViewportSelectionOutlineStyle& style) { ++selectionOutlinePassFactoryCallCount; EXPECT_NE(objectIdTextureView, nullptr); EXPECT_EQ(selectedObjectIds.size(), 2u); @@ -304,13 +307,13 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanWarnsWhenSelection overlay, { 42u }, {}, - [](const XCEngine::Rendering::Passes::InfiniteGridPassData&) { + [](const SceneViewportGridPassData&) { return std::make_unique(); }, []( RHIResourceView*, const std::vector&, - const XCEngine::Rendering::Passes::ObjectIdOutlineStyle&) { + const SceneViewportSelectionOutlineStyle&) { return std::make_unique(); }, [](const SceneViewportOverlayFrameData&) {