diff --git a/Engine/include/Engine/Renderer/Frustum.h b/Engine/include/Engine/Renderer/Frustum.h index 5114ca0..9b2e6b8 100644 --- a/Engine/include/Engine/Renderer/Frustum.h +++ b/Engine/include/Engine/Renderer/Frustum.h @@ -6,7 +6,10 @@ // 平面从 view-proj 矩阵的行组合中直接抽出(无需分解参数), // 用于按视图剔除(相机视图 / 光源视图各自独立一份)。 // 注意:near/far 平面按 Vulkan [0,1] 深度约定提取 -//(z_clip ∈ [0,1]:near = row2,far = row3 - row2)。 +//(z_clip ∈ [0,1]:near = row2,far = row3 - row2)—— +// 因此必须传已做 [0,1] 重映射的矩阵(相机 proj / lightSpace 均是)。 +// 平面在提取后归一化:包围球测试的 radius 是欧氏长度, +// 未归一化的平面距离会被透视因数放大,导致视锥边缘误剔。 // #ifndef HELLOVULKAN_FRUSTUM_H @@ -20,11 +23,11 @@ namespace Engine { struct Frustum { // 依次:left, right, bottom, top, near, far - // xyz = 平面法线(未归一化,仅做符号/点积测试),w = 偏移 + // xyz = 单位法线,w = 原点到平面的有符号距离 std::array planes{}; // 从 view-proj 矩阵提取 6 平面(列主序 glm 矩阵的"行"组合)。 - // 注意必须传未做 [0,1] 深度重映射的矩阵,提取公式才成立。 + // 必须传 [0,1] 深度重映射后的矩阵。 static Frustum FromMatrix(const glm::mat4& m) { Frustum f; @@ -43,6 +46,14 @@ namespace Engine { f.planes[3] = r3 - r1; // top: y' <= w f.planes[4] = r2; // near: z' >= 0(Vulkan [0,1]) f.planes[5] = r3 - r2; // far: z' <= w + + // 归一化:法线长度把透视/缩放因数从距离里除掉 + for (auto& p : f.planes) { + const f32 len = glm::length(glm::vec3(p)); + if (len > 1e-8f) { + p /= len; + } + } return f; } diff --git a/Engine/include/Engine/Renderer/Mesh.h b/Engine/include/Engine/Renderer/Mesh.h index 427946d..dc26873 100644 --- a/Engine/include/Engine/Renderer/Mesh.h +++ b/Engine/include/Engine/Renderer/Mesh.h @@ -21,10 +21,11 @@ namespace Engine class Mesh { public: + // material 非拥有指针:Model 的材质缓存拥有(同贴图共享一份) Mesh(Device& device, MemoryAllocator& allocator, const std::vector& vertices, const std::vector& indices, - Scope material); + Material* material); ~Mesh(); Mesh(const Mesh&) = delete; @@ -34,7 +35,7 @@ namespace Engine // 同 mesh 的 N 个实体一次 draw 完成,模型矩阵经实例索引取 void Draw(vk::CommandBuffer cmd, u32 instanceCount = 1, u32 firstInstance = 0) const; - Material* GetMaterial() const { return m_Material.get(); } + Material* GetMaterial() const { return m_Material; } u32 GetIndexCount() const { return m_IndexCount; } // 局部空间包围球(构造时从顶点算出),视锥剔除用 @@ -47,7 +48,7 @@ namespace Engine private: Scope m_VertexBuffer; Scope m_IndexBuffer; - Scope m_Material; + Material* m_Material; u32 m_IndexCount = 0; Bounds m_Bounds; diff --git a/Engine/include/Engine/Renderer/Model.h b/Engine/include/Engine/Renderer/Model.h index 730baba..da8717f 100644 --- a/Engine/include/Engine/Renderer/Model.h +++ b/Engine/include/Engine/Renderer/Model.h @@ -25,6 +25,7 @@ namespace Engine class MemoryAllocator; class DescriptorManager; class Texture; + class Material; class Model { @@ -61,17 +62,23 @@ namespace Engine Texture* GetOrLoadTexture(const std::string& path); + // 材质缓存入口:同贴图路径共享同一 Material(一个描述符集), + // 空路径/缺文件折到兜底材质。路径已做反斜杠归一化。 + Material* GetOrCreateMaterial(const std::string& path); + Device& m_Device; MemoryAllocator& m_Allocator; DescriptorManager& m_DescriptorManager; vk::DescriptorSetLayout m_TextureLayout; - std::vector> m_Meshes; - std::string m_ModelPath; - - // 纹理缓存:同一路径只加载一次 + // 缓存声明在 m_Meshes 之前:mesh 持 Material* 裸指针, + // 析构序 = meshes 先于材质/纹理缓存 + std::unordered_map> m_MaterialCache; std::unordered_map> m_TextureCache; Scope m_DefaultTexture; + + std::vector> m_Meshes; + std::string m_ModelPath; }; } // namespace Engine diff --git a/Engine/include/Engine/Vulkan/DescriptorManager.h b/Engine/include/Engine/Vulkan/DescriptorManager.h index 605104a..28d19dc 100644 --- a/Engine/include/Engine/Vulkan/DescriptorManager.h +++ b/Engine/include/Engine/Vulkan/DescriptorManager.h @@ -54,12 +54,17 @@ namespace Engine { private: Device& m_Device; - vk::DescriptorPool m_Pool; - // 描述符池容量:需 ≥ 场景全部材质集合数 + 每帧集合。 - // SunTemple(984 mesh 材质)把 1000 打爆过;材质去重(同贴图共享 - // Material)属于材质系统任务,在那之前留足余量 - static constexpr u32 kMaxSets = 4096; + // 可增长描述符池(分块):场景材质集数量不可预估(1G 资产场景 + // 可能上万),固定上限迟早打爆。耗尽时追加一块继续分配—— + // 对齐 UE/Unity 的"growable pool/bindless 堆"思路的 Vulkan 版。 + // 材质去重(同贴图共享 Material)仍是大场景的第一道闸门。 + std::vector m_Pools; + + // 每块的容量(set 数;各类型描述符数同值) + static constexpr u32 kChunkSets = 4096; + + vk::DescriptorPool CreateChunk(); }; } // namespace Engine diff --git a/Engine/src/Renderer/Mesh.cpp b/Engine/src/Renderer/Mesh.cpp index e8ee7f2..d97e2b3 100644 --- a/Engine/src/Renderer/Mesh.cpp +++ b/Engine/src/Renderer/Mesh.cpp @@ -18,8 +18,8 @@ namespace Engine Mesh::Mesh(Device& device, MemoryAllocator& allocator, const std::vector& vertices, const std::vector& indices, - Scope material) - : m_Material(std::move(material)) + Material* material) + : m_Material(material) { if (vertices.empty() || indices.empty()) { diff --git a/Engine/src/Renderer/Model.cpp b/Engine/src/Renderer/Model.cpp index 4a6b753..b01625f 100644 --- a/Engine/src/Renderer/Model.cpp +++ b/Engine/src/Renderer/Model.cpp @@ -59,14 +59,12 @@ Model::Model(Device& device, MemoryAllocator& allocator, }; const std::vector indices = {0, 1, 2, 0, 2, 3}; - auto mat = CreateScope( - m_Device, m_DescriptorManager, m_TextureLayout, tex.get()); + // 贴图/材质生命周期随模型(缓存持有) + m_DefaultTexture = std::move(tex); + Material* mat = GetOrCreateMaterial(""); // 兜底材质 m_Meshes.push_back(CreateScope( - m_Device, m_Allocator, vertices, indices, std::move(mat))); - - // 贴图生命周期随模型 - m_DefaultTexture = std::move(tex); + m_Device, m_Allocator, vertices, indices, mat)); } Scope Model::CreatePlane(Device& device, MemoryAllocator& allocator, @@ -134,6 +132,34 @@ Texture* Model::GetOrLoadTexture(const std::string& rawPath) return ptr; } +Material* Model::GetOrCreateMaterial(const std::string& rawPath) +{ + // 路径归一化 + 空路径/缺文件统一折到兜底材质(缓存键 "") + std::string path = rawPath; + std::replace(path.begin(), path.end(), '\\', '/'); + + if (path.empty() || !std::filesystem::exists(path)) { + if (!rawPath.empty()) { + ENGINE_WARN("Texture not found, using default: {}", path); + } + path.clear(); + } + + if (auto it = m_MaterialCache.find(path); it != m_MaterialCache.end()) { + return it->second.get(); + } + + Texture* tex = path.empty() + ? m_DefaultTexture.get() + : GetOrLoadTexture(path); + + auto mat = CreateScope( + m_Device, m_DescriptorManager, m_TextureLayout, tex); + Material* ptr = mat.get(); + m_MaterialCache.emplace(path, std::move(mat)); + return ptr; +} + Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) { std::vector vertices; @@ -189,16 +215,11 @@ Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) } } - // 从缓存拿,没有才加载 - Texture* tex = texturePath.empty() - ? m_DefaultTexture.get() - : GetOrLoadTexture(texturePath); - - auto mat = CreateScope( - m_Device, m_DescriptorManager, m_TextureLayout, tex); + // 材质缓存:同贴图 → 同一 Material(一个描述符集),mesh 数 ≠ 集数 + Material* mat = GetOrCreateMaterial(texturePath); return CreateScope( - m_Device, m_Allocator, vertices, indices, std::move(mat)); + m_Device, m_Allocator, vertices, indices, mat); } } // namespace Engine \ No newline at end of file diff --git a/Engine/src/Vulkan/DescriptorManager.cpp b/Engine/src/Vulkan/DescriptorManager.cpp index bb15978..a62bae5 100644 --- a/Engine/src/Vulkan/DescriptorManager.cpp +++ b/Engine/src/Vulkan/DescriptorManager.cpp @@ -11,24 +11,32 @@ namespace Engine DescriptorManager::DescriptorManager(Device& device) : m_Device(device) { - std::array poolSizes = { - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kMaxSets), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, kMaxSets), - vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, kMaxSets), - }; - - vk::DescriptorPoolCreateInfo poolInfo; - poolInfo.setMaxSets(kMaxSets) - .setPoolSizes(poolSizes) - .setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet); - - m_Pool = m_Device.GetDevice().createDescriptorPool(poolInfo); - ENGINE_INFO("DescriptorManager created"); + m_Pools.push_back(CreateChunk()); + ENGINE_INFO("DescriptorManager created (growable, chunk = {} sets)", + kChunkSets); } DescriptorManager::~DescriptorManager() { - m_Device.GetDevice().destroyDescriptorPool(m_Pool); + for (auto pool : m_Pools) { + m_Device.GetDevice().destroyDescriptorPool(pool); + } + } + + vk::DescriptorPool DescriptorManager::CreateChunk() + { + std::array poolSizes = { + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kChunkSets), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, kChunkSets), + vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, kChunkSets), + }; + + vk::DescriptorPoolCreateInfo poolInfo; + poolInfo.setMaxSets(kChunkSets) + .setPoolSizes(poolSizes) + .setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet); + + return m_Device.GetDevice().createDescriptorPool(poolInfo); } vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector& bindings) @@ -52,12 +60,24 @@ namespace Engine vk::DescriptorSet DescriptorManager::AllocateDescriptorSet(vk::DescriptorSetLayout layout) { - vk::DescriptorSetAllocateInfo allocInfo; - allocInfo.setDescriptorPool(m_Pool) - .setSetLayouts(layout); - - auto sets = m_Device.GetDevice().allocateDescriptorSets(allocInfo); - return sets[0]; + // 从最新块分配;耗尽(OutOfPool/Fragmented)则追加一块重试 + for (;;) { + try { + vk::DescriptorSetAllocateInfo allocInfo; + allocInfo.setDescriptorPool(m_Pools.back()) + .setSetLayouts(layout); + auto sets = m_Device.GetDevice().allocateDescriptorSets(allocInfo); + return sets[0]; + } catch (const vk::OutOfPoolMemoryError&) { + m_Pools.push_back(CreateChunk()); + ENGINE_INFO("DescriptorManager: pool chunk grown -> {} chunks", + m_Pools.size()); + } catch (const vk::FragmentedPoolError&) { + m_Pools.push_back(CreateChunk()); + ENGINE_INFO("DescriptorManager: pool fragmented, new chunk -> {}", + m_Pools.size()); + } + } } void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,