Formalize scene viewport overlay sprite resource lifecycle

This commit is contained in:
2026-04-04 17:10:15 +08:00
parent 98b307bc6d
commit c6b835a390
10 changed files with 434 additions and 184 deletions

View File

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

View File

@@ -1,7 +1,6 @@
#include "Passes/SceneViewportEditorOverlayPass.h"
#include "Viewport/SceneViewportMath.h"
#include "Viewport/SceneViewportResourcePaths.h"
#include <XCEngine/RHI/RHIBuffer.h>
#include <XCEngine/RHI/RHICommandList.h>
@@ -18,18 +17,13 @@
#include <array>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <vector>
#include <stb_image.h>
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<stbi_uc>& 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_t>(size));
input.seekg(0, std::ios::beg);
return input.read(reinterpret_cast<char*>(outBytes.data()), size).good();
}
bool DecodeTextureFile(
const std::filesystem::path& filePath,
std::vector<stbi_uc>& outPixels,
int& outWidth,
int& outHeight) {
std::vector<stbi_uc> fileData = {};
if (!ReadFileBytes(filePath, fileData)) {
return false;
}
int channels = 0;
stbi_uc* pixels = stbi_load_from_memory(
fileData.data(),
static_cast<int>(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<size_t>(outWidth) * static_cast<size_t>(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<OverlaySpriteVertex> spriteVertices = {};
std::array<OverlaySpriteBatchRange, kOverlaySpriteTextureCount> depthTestedSpriteBatches = {};
std::array<OverlaySpriteBatchRange, kOverlaySpriteTextureCount> alwaysOnTopSpriteBatches = {};
std::array<OverlaySpriteBatchRange, kSceneViewportOverlaySpriteResourceCount> depthTestedSpriteBatches = {};
std::array<OverlaySpriteBatchRange, kSceneViewportOverlaySpriteResourceCount> alwaysOnTopSpriteBatches = {};
if (spriteVertexCount > 0u) {
spriteVertices.reserve(spriteVertexCount);
const auto appendSpriteBatches =
[&spriteVertices](
const std::vector<const SceneViewportOverlaySpritePrimitive*>& sprites,
std::array<OverlaySpriteBatchRange, kOverlaySpriteTextureCount>& outBatches) {
for (size_t textureIndex = 0; textureIndex < kOverlaySpriteTextureCount; ++textureIndex) {
std::array<OverlaySpriteBatchRange, kSceneViewportOverlaySpriteResourceCount>& outBatches) {
for (size_t textureIndex = 0; textureIndex < kSceneViewportOverlaySpriteResourceCount; ++textureIndex) {
OverlaySpriteBatchRange range = {};
range.firstVertex = static_cast<uint32_t>(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<OverlaySpriteBatchRange, kOverlaySpriteTextureCount>& batches) {
const std::array<OverlaySpriteBatchRange, kSceneViewportOverlaySpriteResourceCount>& 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<uint32_t>(kOverlaySpriteTextureCount);
texturePoolDesc.descriptorCount = static_cast<uint32_t>(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<uint32_t>(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<stbi_uc> pixels = {};
int width = 0;
int height = 0;
if (!DecodeTextureFile(ResolveOverlaySpriteTexturePath(textureKind), pixels, width, height)) {
return false;
}
RHI::TextureDesc textureDesc = {};
textureDesc.width = static_cast<uint32_t>(width);
textureDesc.height = static_cast<uint32_t>(height);
textureDesc.depth = 1;
textureDesc.mipLevels = 1;
textureDesc.arraySize = 1;
textureDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
textureDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
textureDesc.sampleCount = 1;
textureDesc.sampleQuality = 0;
textureDesc.flags = 0;
resources.texture = m_device->CreateTexture(
textureDesc,
pixels.data(),
pixels.size(),
static_cast<uint32_t>(width * 4));
if (resources.texture == nullptr) {
return false;
}
RHI::ResourceViewDesc viewDesc = {};
viewDesc.format = static_cast<uint32_t>(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();

View File

@@ -1,12 +1,12 @@
#pragma once
#include "Viewport/SceneViewportEditorOverlayData.h"
#include "Viewport/SceneViewportOverlaySpriteResources.h"
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderPass.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
@@ -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<OverlaySpriteTextureResources, 2> m_overlaySpriteTextures = {};
SceneViewportOverlaySpriteResourceCache m_overlaySpriteResources = {};
};
std::unique_ptr<Rendering::RenderPass> CreateSceneViewportEditorOverlayPass(

View File

@@ -0,0 +1,209 @@
#include "Viewport/SceneViewportOverlaySpriteResources.h"
#include <XCEngine/RHI/RHIDescriptorPool.h>
#include <XCEngine/RHI/RHIDescriptorSet.h>
#include <XCEngine/RHI/RHIDevice.h>
#include <XCEngine/RHI/RHIEnums.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHITexture.h>
#include <filesystem>
#include <fstream>
#include <vector>
#include <stb_image.h>
namespace XCEngine {
namespace Editor {
namespace {
bool ReadFileBytes(const std::filesystem::path& filePath, std::vector<stbi_uc>& 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_t>(size));
input.seekg(0, std::ios::beg);
return input.read(reinterpret_cast<char*>(outBytes.data()), size).good();
}
bool DecodeTextureFile(
const std::filesystem::path& filePath,
SceneViewportOverlaySpritePixels& outPixels) {
outPixels = {};
std::vector<stbi_uc> 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<int>(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<uint32_t>(width);
outPixels.height = static_cast<uint32_t>(height);
outPixels.rgbaPixels.assign(
pixels,
pixels + static_cast<size_t>(width) * static_cast<size_t>(height) * 4u);
stbi_image_free(pixels);
return outPixels.IsValid();
}
RHI::DescriptorSetLayoutDesc BuildSpriteTextureLayout() {
RHI::DescriptorSetLayoutBinding textureBinding = {};
textureBinding.binding = 0;
textureBinding.type = static_cast<uint32_t>(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<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
textureDesc.textureType = static_cast<uint32_t>(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<uint32_t>(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

View File

@@ -0,0 +1,126 @@
#pragma once
#include "SceneViewportEditorOverlayData.h"
#include "SceneViewportResourcePaths.h"
#include <XCEngine/Core/Containers/String.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <vector>
namespace XCEngine {
namespace RHI {
class RHIDescriptorPool;
class RHIDescriptorSet;
class RHIDevice;
class RHIResourceView;
class RHITexture;
} // namespace RHI
namespace Editor {
inline constexpr std::array<SceneViewportOverlaySpriteTextureKind, 2> 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<uint8_t> rgbaPixels = {};
uint32_t width = 0u;
uint32_t height = 0u;
bool IsValid() const {
const uint64_t expectedBytes =
static_cast<uint64_t>(width) *
static_cast<uint64_t>(height) *
4u;
return width > 0u &&
height > 0u &&
expectedBytes > 0u &&
expectedBytes <= static_cast<uint64_t>((std::numeric_limits<size_t>::max)()) &&
rgbaPixels.size() == static_cast<size_t>(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<ResourceEntry, kSceneViewportOverlaySpriteResourceCount> m_resources = {};
};
} // namespace Editor
} // namespace XCEngine

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
#include <gtest/gtest.h>
#include "Viewport/SceneViewportOverlaySpriteResources.h"
#include <filesystem>
#include <string>
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<size_t>(pixels.width) * static_cast<size_t>(pixels.height) * 4u);
}
}
} // namespace

View File

@@ -1,5 +1,6 @@
#include <gtest/gtest.h>
#include "Viewport/SceneViewportResourcePaths.h"
#include "Viewport/SceneViewportShaderPaths.h"
#include <XCEngine/Core/Asset/ResourceManager.h>
@@ -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) {

View File

@@ -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<NoopRenderPass>();
@@ -273,7 +276,7 @@ TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportRenderPlanCollectsPostSceneA
[&selectionOutlinePassFactoryCallCount](
RHIResourceView* objectIdTextureView,
const std::vector<uint64_t>& 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<NoopRenderPass>();
},
[](
RHIResourceView*,
const std::vector<uint64_t>&,
const XCEngine::Rendering::Passes::ObjectIdOutlineStyle&) {
const SceneViewportSelectionOutlineStyle&) {
return std::make_unique<NoopRenderPass>();
},
[](const SceneViewportOverlayFrameData&) {