diff --git a/docs/RHI抽象层设计与实现.md b/docs/RHI抽象层设计与实现.md index be73e582..22f2c66b 100644 --- a/docs/RHI抽象层设计与实现.md +++ b/docs/RHI抽象层设计与实现.md @@ -539,7 +539,57 @@ public: - OpenGL:GLuint - 解决:GetNativeHandle() 返回 void* -### 5.9 根签名 vs OpenGL 资源绑定 +### 5.9 同步栅栏(RHIFence)抽象设计 + +#### 5.9.1 设计理念对应 + +| 差异点 | 设计理念 | 处理方案 | +|--------|---------|---------| +| 初始化参数差异 | 求同存异 | 后端各自实现初始化 | +| 句柄类型差异 | 底层逃逸 | 统一返回 void* | +| 状态查询 | 求同存异 | 统一 IsSignaled/GetCompletedValue 接口 | + +#### 5.9.2 现有实现对比 + +| 功能 | D3D12Fence | OpenGLFence | +|------|-------------|-------------| +| 初始化 | Initialize(device, value) | Initialize(signaled) | +| Signal | Signal(value) | Signal()/Signal(value) | +| Wait | Wait(value) | Wait(timeoutNs) | +| 状态 | GetCompletedValue() | IsSignaled()/GetStatus() | +| 句柄 | ID3D12Fence* | GLsync | + +#### 5.9.3 抽象接口定义 + +```cpp +class RHIFence { +public: + virtual ~RHIFence() = default; + + virtual void Shutdown() = 0; + + virtual void Signal(uint64_t value) = 0; + virtual void Wait(uint64_t value) = 0; + virtual uint64_t GetCompletedValue() = 0; + virtual bool IsSignaled() const = 0; + + virtual void* GetNativeHandle() = 0; +}; +``` + +#### 5.9.4 差异处理策略 + +1. **初始化参数差异(求同存异)** + - D3D12:需要 device 参数 + - OpenGL:不需要 device + - 解决:后端各自实现 Initialize() + +2. **句柄类型差异(底层逃逸)** + - D3D12:ID3D12Fence* + - OpenGL:GLsync + - 解决:GetNativeHandle() 返回 void* + +### 5.10 根签名 vs OpenGL 资源绑定 - **D3D12**:显式 `RootSignature` 定义资源绑定规则 - **OpenGL**:隐式通过 `glUniformLocation`、`glBindTextureUnit` 绑定 - **解决方案**: diff --git a/docs/plan/第四阶段计划_资源系统.md b/docs/plan/第四阶段计划_资源系统.md index 65b4fc7b..7d68319f 100644 --- a/docs/plan/第四阶段计划_资源系统.md +++ b/docs/plan/第四阶段计划_资源系统.md @@ -3653,6 +3653,1214 @@ private: --- +## 12. 测试设计 + +> 本节详细说明资源系统的测试设计,确保每个阶段完成后都能立即进行测试验收。测试采用Google Test框架,与现有项目保持一致。 + +### 12.1 测试目录结构 + +``` +tests/ +├── Resources/ # 新增资源系统测试 +│ ├── CMakeLists.txt # 构建配置 +│ ├── fixtures/ +│ │ └── ResourcesTestFixture.h # 测试夹具基类 +│ ├── test_resource_types.cpp # 资源类型测试 +│ ├── test_resource_guid.cpp # GUID生成测试 +│ ├── test_iresource.cpp # 资源接口测试 +│ ├── test_resource_handle.cpp # 资源句柄测试 +│ ├── test_resource_cache.cpp # 缓存测试 +│ ├── test_async_loader.cpp # 异步加载测试 +│ ├── test_texture.cpp # 纹理资源测试 +│ ├── test_mesh.cpp # 网格资源测试 +│ ├── test_material.cpp # 材质资源测试 +│ ├── test_audio_clip.cpp # 音频资源测试 +│ ├── test_resource_manager.cpp # 资源管理器测试 +│ ├── test_resource_filesystem.cpp # 文件系统测试 +│ ├── test_resource_dependency.cpp # 依赖管理测试 +│ └── test_data/ +│ ├── test_texture.png # 测试用纹理 +│ ├── test_mesh.obj # 测试用模型 +│ └── test_material.json # 测试用材质 +``` + +### 12.2 测试夹具基类 + +**文件**: `tests/Resources/fixtures/ResourcesTestFixture.h` + +```cpp +#pragma once + +#include +#include +#include +#include + +namespace XCEngine { +namespace Resources { +namespace Test { + +class ResourcesTestFixture : public ::testing::Test { +protected: + static void SetUpTestSuite() { + // 初始化资源系统 + ResourceManager::Get().Initialize(); + ResourceManager::Get().SetResourceRoot("tests/Resources/test_data/"); + AsyncLoader::Get().Initialize(2); + } + + static void TearDownTestSuite() { + ResourceManager::Get().UnloadAll(); + AsyncLoader::Get().Shutdown(); + ResourceManager::Get().Shutdown(); + } + + void SetUp() override { + // 每个测试前清空缓存 + ResourceManager::Get().UnloadAll(); + ResourceManager::Get().FlushCache(); + } + + void TearDown() override { + // 每个测试后确保清空资源 + ResourceManager::Get().UnloadAll(); + } + + // 辅助方法:获取测试数据目录 + Containers::String GetTestDataPath(const Containers::String& filename) { + return Containers::String("tests/Resources/test_data/") + filename; + } +}; + +} // namespace Test +} // namespace Resources +} // namespace XCEngine +``` + +### 12.3 第一阶段测试:基础框架(天1-3完成后) + +#### 测试 12.3.1 资源类型枚举测试 + +**文件**: `tests/Resources/test_resource_types.cpp` + +```cpp +#include +#include + +using namespace XCEngine::Resources; + +namespace { + +TEST(Resources_Types, ResourceType_EnumValues) { + EXPECT_EQ(static_cast(ResourceType::Unknown), 0); + EXPECT_EQ(static_cast(ResourceType::Texture), 1); + EXPECT_EQ(static_cast(ResourceType::Mesh), 2); + EXPECT_EQ(static_cast(ResourceType::Material), 3); + EXPECT_EQ(static_cast(ResourceType::Shader), 4); + EXPECT_EQ(static_cast(ResourceType::AudioClip), 5); + EXPECT_EQ(static_cast(ResourceType::Binary), 6); +} + +TEST(Resources_Types, GetResourceTypeName) { + EXPECT_STREQ(GetResourceTypeName(ResourceType::Texture), "Texture"); + EXPECT_STREQ(GetResourceTypeName(ResourceType::Mesh), "Mesh"); + EXPECT_STREQ(GetResourceTypeName(ResourceType::Material), "Material"); + EXPECT_STREQ(GetResourceTypeName(ResourceType::Unknown), "Unknown"); +} + +TEST(Resources_Types, GetResourceType_TemplateSpecializations) { + EXPECT_EQ(GetResourceType(), ResourceType::Texture); + EXPECT_EQ(GetResourceType(), ResourceType::Mesh); + EXPECT_EQ(GetResourceType(), ResourceType::Material); + EXPECT_EQ(GetResourceType(), ResourceType::Shader); + EXPECT_EQ(GetResourceType(), ResourceType::AudioClip); + EXPECT_EQ(GetResourceType(), ResourceType::Binary); +} + +} // namespace +``` + +#### 测试 12.3.2 ResourceGUID测试 + +**文件**: `tests/Resources/test_resource_guid.cpp` + +```cpp +#include +#include + +using namespace XCEngine::Resources; + +namespace { + +TEST(Resources_GUID, DefaultConstructor) { + ResourceGUID guid; + EXPECT_FALSE(guid.IsValid()); + EXPECT_EQ(guid.value, 0); +} + +TEST(Resources_GUID, ValueConstructor) { + ResourceGUID guid(12345); + EXPECT_TRUE(guid.IsValid()); + EXPECT_EQ(guid.value, 12345); +} + +TEST(Resources_GUID, Generate_FromCString) { + ResourceGUID guid1 = ResourceGUID::Generate("textures/player.png"); + ResourceGUID guid2 = ResourceGUID::Generate("textures/player.png"); + + // 相同路径应生成相同GUID + EXPECT_EQ(guid1, guid2); + EXPECT_TRUE(guid1.IsValid()); +} + +TEST(Resources_GUID, Generate_FromString) { + Containers::String path = "models/player.fbx"; + ResourceGUID guid = ResourceGUID::Generate(path); + EXPECT_TRUE(guid.IsValid()); +} + +TEST(Resources_GUID, Generate_DifferentPaths) { + ResourceGUID guid1 = ResourceGUID::Generate("textures/a.png"); + ResourceGUID guid2 = ResourceGUID::Generate("textures/b.png"); + + // 不同路径应生成不同GUID + EXPECT_NE(guid1, guid2); +} + +TEST(Resources_GUID, ToString) { + ResourceGUID guid(0x1234567890ABCDEF); + Containers::String str = guid.ToString(); + + EXPECT_EQ(str.Size(), 16u); + EXPECT_STREQ(str.CStr(), "1234567890abcdef"); +} + +TEST(Resources_GUID, MakeResourceGUID_Helper) { + ResourceGUID guid = MakeResourceGUID("test/path"); + EXPECT_TRUE(guid.IsValid()); +} + +TEST(Resources_GUID, EqualityOperators) { + ResourceGUID guid1(100); + ResourceGUID guid2(100); + ResourceGUID guid3(200); + + EXPECT_TRUE(guid1 == guid2); + EXPECT_TRUE(guid1 != guid3); +} + +} // namespace +``` + +#### 测试 12.3.3 资源句柄测试 + +**文件**: `tests/Resources/test_resource_handle.cpp` + +```cpp +#include +#include +#include + +using namespace XCEngine::Resources; + +// 测试用伪资源 +class TestResource : public IResource { +public: + TestResource(const Containers::String& name) { + ConstructParams params; + params.name = name; + params.path = name; + params.guid = ResourceGUID::Generate(name.CStr()); + params.memorySize = 1024; + Initialize(params); + } + + ResourceType GetType() const override { return ResourceType::Binary; } + const Containers::String& GetName() const override { return m_name; } + const Containers::String& GetPath() const override { return m_path; } + ResourceGUID GetGUID() const override { return m_guid; } + bool IsValid() const override { return m_isValid; } + size_t GetMemorySize() const override { return m_memorySize; } + void Release() override { delete this; } +}; + +namespace { + +TEST(Resources_Handle, DefaultConstructor) { + ResourceHandle handle; + EXPECT_FALSE(handle.IsValid()); + EXPECT_EQ(handle.Get(), nullptr); +} + +TEST(Resources_Handle, ConstructorWithValidResource) { + TestResource* resource = new TestResource("test_resource"); + ResourceHandle handle(resource); + + EXPECT_TRUE(handle.IsValid()); + EXPECT_EQ(handle.Get(), resource); + EXPECT_EQ(handle->GetName().CStr(), Containers::String("test_resource")); +} + +TEST(Resources_Handle, CopyConstructor_AddsRef) { + TestResource* resource = new TestResource("test_resource"); + ResourceHandle handle1(resource); + + Core::uint32 refCountBefore = ResourceManager::Get().GetRefCount(resource->GetGUID()); + + ResourceHandle handle2(handle1); + + Core::uint32 refCountAfter = ResourceManager::Get().GetRefCount(resource->GetGUID()); + EXPECT_EQ(refCountAfter, refCountBefore + 1); +} + +TEST(Resources_Handle, MoveConstructor_DoesNotAddRef) { + TestResource* resource = new TestResource("test_resource"); + ResourceHandle handle1(resource); + + Core::uint32 refCountBefore = ResourceManager::Get().GetRefCount(resource->GetGUID()); + + ResourceHandle handle2(std::move(handle1)); + + // 移动语义不应增加引用计数 + EXPECT_EQ(handle1.Get(), nullptr); + EXPECT_EQ(handle2.Get(), resource); +} + +TEST(Resources_Handle, Destructor_ReleasesRef) { + TestResource* resource = new TestResource("test_resource"); + { + ResourceHandle handle(resource); + EXPECT_TRUE(handle.IsValid()); + } + // handle销毁后应释放引用 + // 注意:此时资源应该被卸载(因为引用计数为0) +} + +TEST(Resources_Handle, Reset) { + TestResource* resource = new TestResource("test_resource"); + ResourceHandle handle(resource); + + EXPECT_TRUE(handle.IsValid()); + handle.Reset(); + + EXPECT_FALSE(handle.IsValid()); + EXPECT_EQ(handle.Get(), nullptr); +} + +TEST(Resources_Handle, ArrowOperator) { + TestResource* resource = new TestResource("test_resource"); + ResourceHandle handle(resource); + + EXPECT_STREQ(handle->GetName().CStr(), "test_resource"); + EXPECT_EQ(handle->GetGUID(), resource->GetGUID()); +} + +TEST(Resources_Handle, BoolOperator) { + ResourceHandle emptyHandle; + TestResource* resource = new TestResource("test"); + ResourceHandle validHandle(resource); + + EXPECT_FALSE(emptyHandle); + EXPECT_TRUE(validHandle); +} + +TEST(Resources_Handle, GetGUID) { + TestResource* resource = new TestResource("test_resource"); + ResourceHandle handle(resource); + + EXPECT_EQ(handle.GetGUID(), resource->GetGUID()); + EXPECT_TRUE(handle.GetGUID().IsValid()); +} + +TEST(Resources_Handle, GetResourceType) { + TestResource* resource = new TestResource("test_resource"); + ResourceHandle handle(resource); + + EXPECT_EQ(handle.GetResourceType(), ResourceType::Binary); +} + +TEST(Resources_Handle, EqualityOperators) { + TestResource* resource = new TestResource("test"); + ResourceHandle handle1(resource); + ResourceHandle handle2(resource); + + EXPECT_TRUE(handle1 == handle2); + + TestResource* resource2 = new TestResource("test2"); + ResourceHandle handle3(resource2); + + EXPECT_TRUE(handle1 != handle3); +} + +} // namespace +``` + +### 12.4 第二阶段测试:缓存系统(天4-5完成后) + +#### 测试 12.4.1 资源缓存测试 + +**文件**: `tests/Resources/test_resource_cache.cpp` + +```cpp +#include +#include +#include "fixtures/ResourcesTestFixture.h" + +using namespace XCEngine::Resources; + +namespace { + +class ResourceCache_Test : public ResourcesTestFixture { +protected: + void SetUp() override { + ResourcesTestFixture::SetUp(); + m_cache = new ResourceCache(); + } + + void TearDown() override { + delete m_cache; + ResourcesTestFixture::TearDown(); + } + + ResourceCache* m_cache; +}; + +TEST_F(ResourceCache_Test, AddResource) { + TestResource* resource = new TestResource("test1"); + ResourceGUID guid = resource->GetGUID(); + + m_cache->Add(guid, resource); + + EXPECT_EQ(m_cache->GetSize(), 1u); + EXPECT_EQ(m_cache->GetMemoryUsage(), resource->GetMemorySize()); +} + +TEST_F(ResourceCache_Test, FindResource) { + TestResource* resource = new TestResource("test1"); + ResourceGUID guid = resource->GetGUID(); + + m_cache->Add(guid, resource); + + IResource* found = m_cache->Find(guid); + EXPECT_EQ(found, resource); +} + +TEST_F(ResourceCache_Test, FindNonExistent) { + ResourceGUID guid(9999); + IResource* found = m_cache->Find(guid); + EXPECT_EQ(found, nullptr); +} + +TEST_F(ResourceCache_Test, RemoveResource) { + TestResource* resource = new TestResource("test1"); + ResourceGUID guid = resource->GetGUID(); + + m_cache->Add(guid, resource); + m_cache->Remove(guid); + + EXPECT_EQ(m_cache->GetSize(), 0u); + EXPECT_EQ(m_cache->Find(guid), nullptr); +} + +TEST_F(ResourceCache_Test, Touch_UpdatesAccessTime) { + TestResource* resource = new TestResource("test1"); + ResourceGUID guid = resource->GetGUID(); + + m_cache->Add(guid, resource); + m_cache->Touch(guid); + + // Touch后应能找到资源 + EXPECT_EQ(m_cache->Find(guid), resource); +} + +TEST_F(ResourceCache_Test, MemoryBudget) { + m_cache->SetMemoryBudget(1024); // 1KB + + EXPECT_EQ(m_cache->GetMemoryBudget(), 1024u); +} + +TEST_F(ResourceCache_Test, OnMemoryPressure_EvictUnreferenced) { + m_cache->SetMemoryBudget(512); + + // 添加多个资源 + for (int i = 0; i < 5; i++) { + TestResource* resource = new TestResource(Containers::String("test") + Containers::String::FromInt(i)); + m_cache->Add(resource->GetGUID(), resource); + } + + // 触发内存压力 + m_cache->OnMemoryPressure(1024); + + // 应该有资源被淘汰 + // 具体数量取决于LRU策略 +} + +TEST_F(ResourceCache_Test, Clear) { + for (int i = 0; i < 3; i++) { + TestResource* resource = new TestResource(Containers::String("test") + Containers::String::FromInt(i)); + m_cache->Add(resource->GetGUID(), resource); + } + + m_cache->Clear(); + + EXPECT_EQ(m_cache->GetSize(), 0u); + EXPECT_EQ(m_cache->GetMemoryUsage(), 0u); +} + +TEST_F(ResourceCache_Test, GetLRUList) { + // 添加资源并访问 + TestResource* r1 = new TestResource("test1"); + TestResource* r2 = new TestResource("test2"); + TestResource* r3 = new TestResource("test3"); + + m_cache->Add(r1->GetGUID(), r1); + m_cache->Add(r2->GetGUID(), r2); + m_cache->Add(r3->GetGUID(), r3); + + // 访问r1使其最新 + m_cache->Touch(r1->GetGUID()); + + auto lru = m_cache->GetLRUList(2); + + // r1应该是最新的,不应在LRU列表前几位 + EXPECT_GE(lru.Size(), 0u); +} + +} // namespace +``` + +### 12.5 第三阶段测试:异步加载(天6-7完成后) + +#### 测试 12.5.1 异步加载器测试 + +**文件**: `tests/Resources/test_async_loader.cpp` + +```cpp +#include +#include +#include "fixtures/ResourcesTestFixture.h" +#include + +using namespace XCEngine::Resources; + +namespace { + +class AsyncLoader_Test : public ResourcesTestFixture { +protected: + void SetUp() override { + ResourcesTestFixture::SetUp(); + } +}; + +TEST_F(AsyncLoader_Test, Submit_IncrementsPendingCount) { + AsyncLoader& loader = AsyncLoader::Get(); + + Core::uint32 pendingBefore = loader.GetPendingCount(); + + // 注意:需要先注册加载器,否则会立即失败 + // 这里测试API本身 + EXPECT_GE(pendingBefore, 0u); +} + +TEST_F(AsyncLoader_Test, Update_ProcessesCompletedRequests) { + AsyncLoader& loader = AsyncLoader::Get(); + + // 调用Update处理完成队列 + loader.Update(); + + // 不应崩溃 + EXPECT_TRUE(true); +} + +TEST_F(AsyncLoader_Test, IsLoading) { + AsyncLoader& loader = AsyncLoader::Get(); + + bool isLoading = loader.IsLoading(); + + EXPECT_FALSE(isLoading); // 初始状态 +} + +TEST_F(AsyncLoader_Test, GetProgress) { + AsyncLoader& loader = AsyncLoader::Get(); + + float progress = loader.GetProgress(); + + // 0.0 - 1.0之间 + EXPECT_GE(progress, 0.0f); + EXPECT_LE(progress, 1.0f); +} + +TEST_F(AsyncLoader_Test, CancelAll) { + AsyncLoader& loader = AsyncLoader::Get(); + + loader.CancelAll(); + + EXPECT_EQ(loader.GetPendingCount(), 0u); +} + +TEST_F(AsyncLoader_Test, CallbackInvocation) { + std::atomic callbackCount{0}; + + // 模拟回调 + auto callback = [&callbackCount](LoadResult result) { + callbackCount++; + }; + + // 调用Update确保没有pending请求 + AsyncLoader::Get().Update(); + + EXPECT_EQ(callbackCount, 0); +} + +} // namespace +``` + +### 12.6 第四阶段测试:具体资源类型(天8-11完成后) + +#### 测试 12.6.1 纹理资源测试 + +**文件**: `tests/Resources/test_texture.cpp` + +```cpp +#include +#include +#include +#include "fixtures/ResourcesTestFixture.h" + +using namespace XCEngine::Resources; + +namespace { + +class Texture_Test : public ResourcesTestFixture { +protected: + void SetUp() override { + ResourcesTestFixture::SetUp(); + } +}; + +TEST_F(Texture_Test, DefaultConstructor) { + Texture texture; + + EXPECT_FALSE(texture.IsValid()); + EXPECT_EQ(texture.GetType(), ResourceType::Texture); +} + +TEST_F(Texture_Test, GetDimensions_Default) { + Texture texture; + + EXPECT_EQ(texture.GetWidth(), 0u); + EXPECT_EQ(texture.GetHeight(), 0u); + EXPECT_EQ(texture.GetDepth(), 1u); + EXPECT_EQ(texture.GetMipLevels(), 1u); +} + +TEST_F(Texture_Test, GetTextureType_Default) { + Texture texture; + + EXPECT_EQ(texture.GetTextureType(), TextureType::Texture2D); +} + +TEST_F(Texture_Test, GetFormat_Default) { + Texture texture; + + EXPECT_EQ(texture.GetFormat(), TextureFormat::RGBA8_UNORM); +} + +TEST_F(Texture_Test, Create) { + Texture texture; + + bool result = texture.Create( + 256, 256, 1, 1, + TextureType::Texture2D, + TextureFormat::RGBA8_UNORM, + nullptr, 0 + ); + + EXPECT_TRUE(result); + EXPECT_EQ(texture.GetWidth(), 256u); + EXPECT_EQ(texture.GetHeight(), 256u); + EXPECT_TRUE(texture.IsValid()); +} + +TEST_F(Texture_Test, CreateWithMipmaps) { + Texture texture; + + bool result = texture.Create( + 256, 256, 1, 4, + TextureType::Texture2D, + TextureFormat::RGBA8_UNORM, + nullptr, 0 + ); + + EXPECT_TRUE(result); + EXPECT_EQ(texture.GetMipLevels(), 4u); +} + +TEST_F(Texture_Test, GetMemorySize) { + Texture texture; + + // 256x256 RGBA8 = 256*256*4 = 262144 bytes + texture.Create(256, 256, 1, 1, TextureType::Texture2D, + TextureFormat::RGBA8_UNORM, nullptr, 0); + + size_t memorySize = texture.GetMemorySize(); + EXPECT_GT(memorySize, 0u); +} + +TEST_F(Texture_Test, GetName) { + Texture texture; + ConstructParams params; + params.name = "TestTexture"; + params.path = "textures/test.png"; + params.guid = ResourceGUID::Generate("test"); + params.memorySize = 1024; + texture.Initialize(params); + + EXPECT_STREQ(texture.GetName().CStr(), "TestTexture"); +} + +TEST_F(Texture_Test, GetPath) { + Texture texture; + ConstructParams params; + params.name = "TestTexture"; + params.path = "textures/test.png"; + params.guid = ResourceGUID::Generate("test"); + params.memorySize = 1024; + texture.Initialize(params); + + EXPECT_STREQ(texture.GetPath().CStr(), "textures/test.png"); +} + +TEST_F(Texture_Test, TextureLoader_CanLoad) { + TextureLoader loader; + + EXPECT_TRUE(loader.CanLoad("textures/test.png")); + EXPECT_TRUE(loader.CanLoad("textures/test.jpg")); + EXPECT_TRUE(loader.CanLoad("textures/test.tga")); + EXPECT_FALSE(loader.CanLoad("models/test.obj")); +} + +TEST_F(Texture_Test, TextureLoader_GetSupportedExtensions) { + TextureLoader loader; + auto extensions = loader.GetSupportedExtensions(); + + EXPECT_GE(extensions.Size(), 5u); +} + +TEST_F(Texture_Test, TextureLoader_GetResourceType) { + TextureLoader loader; + + EXPECT_EQ(loader.GetResourceType(), ResourceType::Texture); +} + +TEST_F(Texture_Test, TextureLoader_GetDefaultSettings) { + TextureLoader loader; + ImportSettings* settings = loader.GetDefaultSettings(); + + EXPECT_NE(settings, nullptr); + EXPECT_TRUE(dynamic_cast(settings) != nullptr); + + delete settings; +} + +TEST_F(Texture_Test, TextureImportSettings_Defaults) { + TextureImportSettings settings; + + EXPECT_EQ(settings.GetTextureType(), TextureType::Texture2D); + EXPECT_TRUE(settings.GetGenerateMipmaps()); + EXPECT_EQ(settings.GetMaxAnisotropy(), 16u); +} + +TEST_F(Texture_Test, TextureImportSettings_Modify) { + TextureImportSettings settings; + + settings.SetTextureType(TextureType::TextureCube); + settings.SetGenerateMipmaps(false); + settings.SetMaxAnisotropy(8); + settings.SetSRGB(true); + + EXPECT_EQ(settings.GetTextureType(), TextureType::TextureCube); + EXPECT_FALSE(settings.GetGenerateMipmaps()); + EXPECT_EQ(settings.GetMaxAnisotropy(), 8u); + EXPECT_TRUE(settings.GetSRGB()); +} + +} // namespace +``` + +#### 测试 12.6.2 网格资源测试 + +**文件**: `tests/Resources/test_mesh.cpp` + +```cpp +#include +#include +#include "fixtures/ResourcesTestFixture.h" + +using namespace XCEngine::Resources; + +namespace { + +class Mesh_Test : public ResourcesTestFixture {}; + +TEST_F(Mesh_Test, DefaultConstructor) { + Mesh mesh; + + EXPECT_FALSE(mesh.IsValid()); + EXPECT_EQ(mesh.GetType(), ResourceType::Mesh); +} + +TEST_F(Mesh_Test, GetVertexCount_Default) { + Mesh mesh; + + EXPECT_EQ(mesh.GetVertexCount(), 0u); +} + +TEST_F(Mesh_Test, GetIndexCount_Default) { + Mesh mesh; + + EXPECT_EQ(mesh.GetIndexCount(), 0u); +} + +TEST_F(Mesh_Test, SetVertexData) { + Mesh mesh; + + // 简单顶点数据:位置 + float vertices[] = { + 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f + }; + + mesh.SetVertexData(vertices, sizeof(vertices), 3, 12, VertexAttribute::Position); + + EXPECT_EQ(mesh.GetVertexCount(), 3u); + EXPECT_EQ(mesh.GetVertexStride(), 12u); + EXPECT_TRUE(mesh.GetVertexAttributes() & VertexAttribute::Position); +} + +TEST_F(Mesh_Test, SetIndexData) { + Mesh mesh; + + // 三角形索引 + uint16_t indices[] = {0, 1, 2}; + + mesh.SetIndexData(indices, sizeof(indices), 3, false); + + EXPECT_EQ(mesh.GetIndexCount(), 3u); + EXPECT_FALSE(mesh.IsUse32BitIndex()); +} + +TEST_F(Mesh_Test, SetIndexData_32Bit) { + Mesh mesh; + + uint32_t indices[] = {0, 1, 2, 0, 2, 3}; + + mesh.SetIndexData(indices, sizeof(indices), 6, true); + + EXPECT_EQ(mesh.GetIndexCount(), 6u); + EXPECT_TRUE(mesh.IsUse32BitIndex()); +} + +TEST_F(Mesh_Test, AddSection) { + Mesh mesh; + + MeshSection section; + section.baseVertex = 0; + section.vertexCount = 3; + section.startIndex = 0; + section.indexCount = 3; + section.materialID = 0; + + mesh.AddSection(section); + + EXPECT_EQ(mesh.GetSections().Size(), 1u); +} + +TEST_F(Mesh_Test, BoundingBox) { + Mesh mesh; + + Math::AABB box; + box.Min = Math::Vector3(0, 0, 0); + box.Max = Math::Vector3(1, 1, 1); + + mesh.SetBoundingBox(box); + + EXPECT_EQ(mesh.GetBoundingBox().Min, Math::Vector3(0, 0, 0)); + EXPECT_EQ(mesh.GetBoundingBox().Max, Math::Vector3(1, 1, 1)); +} + +TEST_F(Mesh_Test, VertexAttribute_Enum) { + // 测试顶点属性标志 + VertexAttribute attrs = VertexAttribute::Position | VertexAttribute::Normal | VertexAttribute::UV0; + + EXPECT_TRUE(attrs & VertexAttribute::Position); + EXPECT_TRUE(attrs & VertexAttribute::Normal); + EXPECT_TRUE(attrs & VertexAttribute::UV0); + EXPECT_FALSE(attrs & VertexAttribute::Tangent); +} + +} // namespace +``` + +#### 测试 12.6.3 材质资源测试 + +**文件**: `tests/Resources/test_material.cpp` + +```cpp +#include +#include +#include "fixtures/ResourcesTestFixture.h" + +using namespace XCEngine::Resources; + +namespace { + +class Material_Test : public ResourcesTestFixture {}; + +TEST_F(Material_Test, DefaultConstructor) { + Material material; + + EXPECT_FALSE(material.IsValid()); + EXPECT_EQ(material.GetType(), ResourceType::Material); +} + +TEST_F(Material_Test, SetFloat) { + Material material; + + material.SetFloat("Metallic", 0.5f); + + EXPECT_FLOAT_EQ(material.GetFloat("Metallic"), 0.5f); +} + +TEST_F(Material_Test, SetFloat2) { + Material material; + + material.SetFloat2("UVScale", Math::Vector2(2.0f, 3.0f)); + + Math::Vector2 value = material.GetFloat2("UVScale"); + EXPECT_FLOAT_EQ(value.x, 2.0f); + EXPECT_FLOAT_EQ(value.y, 3.0f); +} + +TEST_F(Material_Test, SetFloat3) { + Material material; + + material.SetFloat3("Albedo", Math::Vector3(1.0f, 0.0f, 0.0f)); + + Math::Vector3 value = material.GetFloat3("Albedo"); + EXPECT_FLOAT_EQ(value.x, 1.0f); + EXPECT_FLOAT_EQ(value.y, 0.0f); + EXPECT_FLOAT_EQ(value.z, 0.0f); +} + +TEST_F(Material_Test, SetFloat4) { + Material material; + + material.SetFloat4("Color", Math::Vector4(1.0f, 1.0f, 1.0f, 0.5f)); + + Math::Vector4 value = material.GetFloat4("Color"); + EXPECT_FLOAT_EQ(value.w, 0.5f); +} + +TEST_F(Material_Test, SetInt) { + Material material; + + material.SetInt("RenderQueue", 2000); + + EXPECT_EQ(material.GetInt("RenderQueue"), 2000); +} + +TEST_F(Material_Test, SetBool) { + Material material; + + material.SetBool("EnableInstancing", true); + + EXPECT_TRUE(material.GetBool("EnableInstancing")); +} + +TEST_F(Material_Test, SetTexture) { + Material material; + + // 创建测试纹理 + Texture* tex = new Texture(); + tex->Initialize({"TestTex", "test.png", ResourceGUID::Generate("test"), 1024}); + + ResourceHandle handle(tex); + material.SetTexture("MainTex", handle); + + // 验证纹理设置成功 + // 注意:需要实现GetTexture方法 +} + +TEST_F(Material_Test, PropertyNotFound) { + Material material; + + // 查询不存在的属性应有默认值 + EXPECT_FLOAT_EQ(material.GetFloat("NonExistent"), 0.0f); +} + +} // namespace +``` + +### 12.7 第五阶段测试:资源管理器(天3-5完成后) + +#### 测试 12.7.1 资源管理器测试 + +**文件**: `tests/Resources/test_resource_manager.cpp` + +```cpp +#include +#include +#include "fixtures/ResourcesTestFixture.h" + +using namespace XCEngine::Resources; + +namespace { + +class ResourceManager_Test : public ResourcesTestFixture {}; + +TEST_F(ResourceManager_Test, GetSingleton) { + ResourceManager& manager = ResourceManager::Get(); + + EXPECT_EQ(&manager, &ResourceManager::Get()); +} + +TEST_F(ResourceManager_Test, SetResourceRoot) { + ResourceManager& manager = ResourceManager::Get(); + + manager.SetResourceRoot("resources/"); + + EXPECT_STREQ(manager.GetResourceRoot().CStr(), "resources/"); +} + +TEST_F(ResourceManager_Test, SetMemoryBudget) { + ResourceManager& manager = ResourceManager::Get(); + + manager.SetMemoryBudget(1024 * 1024); // 1MB + + EXPECT_EQ(manager.GetMemoryBudget(), 1024u * 1024u); +} + +TEST_F(ResourceManager_Test, GetMemoryUsage_Initial) { + ResourceManager& manager = ResourceManager::Get(); + + EXPECT_EQ(manager.GetMemoryUsage(), 0u); +} + +TEST_F(ResourceManager_Test, RegisterLoader) { + ResourceManager& manager = ResourceManager::Get(); + + // 纹理加载器应该已经注册 + IResourceLoader* loader = manager.GetLoader(ResourceType::Texture); + EXPECT_NE(loader, nullptr); +} + +TEST_F(ResourceManager_Test, GetLoader_NotFound) { + ResourceManager& manager = ResourceManager::Get(); + + IResourceLoader* loader = manager.GetLoader(ResourceType::Unknown); + EXPECT_EQ(loader, nullptr); +} + +TEST_F(ResourceManager_Test, AddRef_Release) { + ResourceGUID guid(12345); + + ResourceManager::Get().AddRef(guid); + EXPECT_EQ(ResourceManager::Get().GetRefCount(guid), 1u); + + ResourceManager::Get().AddRef(guid); + EXPECT_EQ(ResourceManager::Get().GetRefCount(guid), 2u); + + ResourceManager::Get().Release(guid); + EXPECT_EQ(ResourceManager::Get().GetRefCount(guid), 1u); + + ResourceManager::Get().Release(guid); + // 引用计数为0时应返回0 +} + +TEST_F(ResourceManager_Test, UnloadAll) { + ResourceManager& manager = ResourceManager::Get(); + + // 添加一些引用 + ResourceGUID guid1(100); + ResourceGUID guid2(200); + manager.AddRef(guid1); + manager.AddRef(guid2); + + manager.UnloadAll(); + + // 所有引用计数应被清除 + EXPECT_EQ(manager.GetRefCount(guid1), 0u); + EXPECT_EQ(manager.GetRefCount(guid2), 0u); +} + +TEST_F(ResourceManager_Test, Exists) { + ResourceManager& manager = ResourceManager::Get(); + + // 未加载的资源应返回false + EXPECT_FALSE(manager.Exists("nonexistent.png")); +} + +TEST_F(ResourceManager_Test, ResolvePath) { + ResourceManager& manager = ResourceManager::Get(); + manager.SetResourceRoot("myresources/"); + + Containers::String resolved = manager.ResolvePath("textures/test.png"); + + EXPECT_STREQ(resolved.CStr(), "myresources//textures/test.png"); +} + +} // namespace +``` + +### 12.8 测试运行脚本 + +**文件**: `tests/Resources/run_tests.bat` + +```bat +@echo off +echo ======================================== +echo XCEngine Resources System Tests +echo ======================================== + +cd /d %~dp0\..\..\build +if exist "tests\Resources\xcengine_resources_tests.exe" ( + echo Running Resources tests... + tests\Resources\xcengine_resources_tests.exe + + if %ERRORLEVEL% EQU 0 ( + echo. + echo [SUCCESS] All resource tests passed! + ) else ( + echo. + echo [FAILED] Some tests failed! + exit /b 1 + ) +) else ( + echo Error: Test executable not found! + echo Please build the project first. + exit /b 1 +) +``` + +### 12.9 测试CMakeLists.txt + +**文件**: `tests/Resources/CMakeLists.txt` + +```cmake +# ============================================================ +# Resources Library Tests +# ============================================================ + +set(RESOURCES_TEST_SOURCES + test_resource_types.cpp + test_resource_guid.cpp + test_iresource.cpp + test_resource_handle.cpp + test_resource_cache.cpp + test_async_loader.cpp + test_texture.cpp + test_mesh.cpp + test_material.cpp + test_audio_clip.cpp + test_resource_manager.cpp + test_resource_filesystem.cpp + test_resource_dependency.cpp +) + +add_executable(xcengine_resources_tests ${RESOURCES_TEST_SOURCES}) + +if(MSVC) + set_target_properties(xcengine_resources_tests PROPERTIES + LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib" + ) +endif() + +target_link_libraries(xcengine_resources_tests + PRIVATE + XCEngine + GTest::gtest + GTest::gtest_main +) + +target_include_directories(xcengine_resources_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/tests/fixtures + ${CMAKE_SOURCE_DIR}/tests/Resources/fixtures +) + +# 复制测试数据到输出目录 +file(COPY ${CMAKE_SOURCE_DIR}/tests/Resources/test_data + DESTINATION ${CMAKE_BINARY_DIR}/tests/Resources/) + +add_test(NAME ResourcesTests COMMAND xcengine_resources_tests) +``` + +### 12.10 阶段测试验收清单 + +每个阶段完成后,运行对应测试验证功能正确性: + +| 阶段 | 完成后运行测试 | 验收标准 | +|------|---------------|----------| +| **天1-3: 基础框架** | `test_resource_types.cpp`
`test_resource_guid.cpp`
`test_resource_handle.cpp` | ✅ ResourceType枚举正确
✅ GUID生成稳定
✅ 句柄引用计数正确 | +| **天4-5: 缓存系统** | `test_resource_cache.cpp` | ✅ LRU淘汰正常
✅ 内存预算生效
✅ 内存压力响应 | +| **天6-7: 异步加载** | `test_async_loader.cpp` | ✅ 任务提交正常
✅ 回调正常
✅ 进度计算正确 | +| **天8-9: 纹理** | `test_texture.cpp` | ✅ 创建/设置属性正常
✅ 加载器识别格式
✅ 导入设置生效 | +| **天10: 网格** | `test_mesh.cpp` | ✅ 顶点/索引数据正常
✅ 子网格正常
✅ 包围盒正常 | +| **天11: 材质** | `test_material.cpp` | ✅ 属性设置/获取正常
✅ 类型转换正确 | +| **天12-13: 文件系统** | `test_resource_filesystem.cpp` | ✅ 目录遍历正常
✅ 归档加载正常 | +| **天14-15: 依赖管理** | `test_resource_dependency.cpp` | ✅ 依赖关系正确
✅ 循环检测正常 | +| **天16-17: 集成** | 全部测试 | ✅ 所有测试通过
✅ 内存无泄漏 | + +### 12.11 每日测试执行流程 + +```bash +# 每天完成代码后,在build目录执行: +cd build + +# 编译测试 +cmake --build . --target xcengine_resources_tests + +# 运行当天相关测试 +ctest -R "Resources_类型名" -V + +# 例如: +# 第3天完成后运行: +ctest -R "Resources_Types|Resources_GUID|Resources_Handle" -V + +# 第5天完成后运行: +ctest -R "Resources_Cache" -V + +# 全部测试通过后进行下一阶段 +``` + +### 12.12 测试数据准备 + +需要在 `tests/Resources/test_data/` 目录下准备以下测试文件: + +``` +test_data/ +├── textures/ +│ ├── test_256x256.png # 256x256 PNG测试纹理 +│ ├── test_512x512.jpg # 512x512 JPG测试纹理 +│ └── test_red.tga # 纯红色TGA +├── models/ +│ ├── cube.obj # 简单立方体 +│ ├── sphere.obj # 球体 +│ └── plane.obj # 平面 +├── materials/ +│ ├── test_material.json # 测试材质 +│ └── test_pbr.json # PBR材质 +├── audio/ +│ ├── test_mono.wav # 单声道WAV +│ └── test_stereo.wav # 立体声WAV +└── archives/ + └── test.xcrap # 测试资源包 +``` + +--- + ## 13. 关键技术点 ### 13.1 引用计数与缓存淘汰的交互 diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12Fence.h b/engine/include/XCEngine/RHI/D3D12/D3D12Fence.h index 956fb990..b2993ff8 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12Fence.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12Fence.h @@ -3,26 +3,28 @@ #include #include -#include +#include "../RHIFence.h" using Microsoft::WRL::ComPtr; namespace XCEngine { namespace RHI { -class D3D12Fence { +class D3D12Fence : public RHIFence { public: D3D12Fence(); - ~D3D12Fence(); + ~D3D12Fence() override; bool Initialize(ID3D12Device* device, uint64_t initialValue = 0); - void Shutdown(); + void Shutdown() override; - void Signal(uint64_t value); - void Wait(uint64_t value); - uint64_t GetCompletedValue(); + void Signal(uint64_t value) override; + void Wait(uint64_t value) override; + uint64_t GetCompletedValue() const override; + bool IsSignaled() const override { return GetCompletedValue() > 0; } void* GetEventHandle() { return m_eventHandle; } + void* GetNativeHandle() override { return m_fence.Get(); } ID3D12Fence* GetFence() const { return m_fence.Get(); } private: diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLFence.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLFence.h index 9cce69aa..c1a945cd 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLFence.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLFence.h @@ -3,6 +3,8 @@ #include #include +#include "../RHIFence.h" + namespace XCEngine { namespace RHI { @@ -12,24 +14,26 @@ enum class FenceStatus { Error }; -class OpenGLFence { +class OpenGLFence : public RHIFence { public: OpenGLFence(); - ~OpenGLFence(); + ~OpenGLFence() override; bool Initialize(bool signaled = false); - void Shutdown(); + void Shutdown() override; - void Signal(); - void Signal(uint64_t value); - void Wait(uint64_t timeoutNs = UINT64_MAX); + void Signal() override; + void Signal(uint64_t value) override; + void Wait(uint64_t value) override; void Reset(); - bool IsSignaled() const; + bool IsSignaled() const override; FenceStatus GetStatus() const; - uint64_t GetCompletedValue() const; + uint64_t GetCompletedValue() const override; uint64_t GetCurrentValue() const { return m_fenceValue; } + void* GetNativeHandle() override { return m_sync; } + private: void* m_sync; uint64_t m_fenceValue; diff --git a/engine/include/XCEngine/RHI/RHIFence.h b/engine/include/XCEngine/RHI/RHIFence.h new file mode 100644 index 00000000..47a7a423 --- /dev/null +++ b/engine/include/XCEngine/RHI/RHIFence.h @@ -0,0 +1,24 @@ +#pragma once + +#include "RHITypes.h" + +namespace XCEngine { +namespace RHI { + +class RHIFence { +public: + virtual ~RHIFence() = default; + + virtual void Shutdown() = 0; + + virtual void Signal() = 0; + virtual void Signal(uint64_t value) = 0; + virtual void Wait(uint64_t value) = 0; + virtual uint64_t GetCompletedValue() const = 0; + virtual bool IsSignaled() const = 0; + + virtual void* GetNativeHandle() = 0; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/D3D12/D3D12Fence.cpp b/engine/src/RHI/D3D12/D3D12Fence.cpp index 57465e81..d3f8b728 100644 --- a/engine/src/RHI/D3D12/D3D12Fence.cpp +++ b/engine/src/RHI/D3D12/D3D12Fence.cpp @@ -44,7 +44,7 @@ void D3D12Fence::Wait(uint64_t value) { } } -uint64_t D3D12Fence::GetCompletedValue() { +uint64_t D3D12Fence::GetCompletedValue() const { return m_fence->GetCompletedValue(); }