Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Graphics/GraphicsEngine/interface/GraphicsTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -4109,12 +4109,17 @@ struct EngineVkCreateInfo DILIGENT_DERIVE(EngineCreateInfo)
/// Device-local memory is used for USAGE_DEFAULT and USAGE_IMMUTABLE
/// GPU resources, such as buffers and textures.
///
/// Allocations larger than this size use separate pages that are not shared
/// with other resources and are released independently of the memory reserve.
///
/// If there is no available GPU memory, the resource will fail to be created.
Uint32 DeviceLocalMemoryPageSize DEFAULT_INITIALIZER(16 << 20);

/// Allocation granularity for host-visible memory.

/// Host-visible memory is primarily used to upload data to GPU resources.
/// Allocations larger than this size use separate pages that are not shared
/// with other resources and are released independently of the memory reserve.
Uint32 HostVisibleMemoryPageSize DEFAULT_INITIALIZER(16 << 20);

/// Amount of device-local memory reserved by the engine.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,15 @@ MemoryAllocation MemoryManager::Allocate(VkDeviceSize Size, VkDeviceSize Alignme
MemoryPageIndex PageIdx{MemoryTypeIndex, HostVisible, AllocateFlags};
std::lock_guard<std::mutex> Lock{m_PagesMtx};

const VkDeviceSize BasePageSize = HostVisible ? m_HostVisiblePageSize : m_DeviceLocalPageSize;

auto range = m_Pages.equal_range(PageIdx);
for (auto page_it = range.first; page_it != range.second; ++page_it)
{
// Oversized pages belong to a single allocation and must never be shared.
if (page_it->second.GetPageSize() > BasePageSize)
continue;

Allocation = page_it->second.Allocate(Size, Alignment);
if (Allocation.Page != nullptr)
break;
Expand All @@ -184,9 +190,8 @@ MemoryAllocation MemoryManager::Allocate(VkDeviceSize Size, VkDeviceSize Alignme
size_t stat_ind = HostVisible ? 1 : 0;
if (Allocation.Page == nullptr)
{
VkDeviceSize PageSize = HostVisible ? m_HostVisiblePageSize : m_DeviceLocalPageSize;
while (PageSize < Size)
PageSize *= 2;
// The suballocator rounds the requested size up to Alignment, even at offset zero.
const VkDeviceSize PageSize = std::max(BasePageSize, Diligent::AlignUp(Size, Alignment));

m_CurrAllocatedSize[stat_ind] += PageSize;
m_PeakAllocatedSize[stat_ind] = std::max(m_PeakAllocatedSize[stat_ind], m_CurrAllocatedSize[stat_ind]);
Expand Down Expand Up @@ -214,8 +219,6 @@ MemoryAllocation MemoryManager::Allocate(VkDeviceSize Size, VkDeviceSize Alignme
void MemoryManager::ShrinkMemory()
{
std::lock_guard<std::mutex> Lock{m_PagesMtx};
if (m_CurrAllocatedSize[0] <= m_DeviceLocalReserveSize && m_CurrAllocatedSize[1] <= m_HostVisibleReserveSize)
return;

auto it = m_Pages.begin();
while (it != m_Pages.end())
Expand All @@ -225,7 +228,8 @@ void MemoryManager::ShrinkMemory()
MemoryPage& Page = curr_it->second;
bool IsHostVisible = Page.GetCPUMemory() != nullptr;
VkDeviceSize ReserveSize = IsHostVisible ? m_HostVisibleReserveSize : m_DeviceLocalReserveSize;
if (Page.IsEmpty() && m_CurrAllocatedSize[IsHostVisible ? 1 : 0] > ReserveSize)
VkDeviceSize BasePageSize = IsHostVisible ? m_HostVisiblePageSize : m_DeviceLocalPageSize;
if (Page.IsEmpty() && (Page.GetPageSize() > BasePageSize || m_CurrAllocatedSize[IsHostVisible ? 1 : 0] > ReserveSize))
{
VkDeviceSize PageSize = Page.GetPageSize();
m_CurrAllocatedSize[IsHostVisible ? 1 : 0] -= PageSize;
Expand Down
3 changes: 3 additions & 0 deletions Tests/DiligentCoreAPITest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ if(TARGET Diligent-HLSL2GLSLConverterLib)
endif()

if(VULKAN_SUPPORTED)
target_include_directories(DiligentCoreAPITest PRIVATE ../../Graphics/GraphicsEngineVulkan/include)
target_link_libraries(DiligentCoreAPITest PRIVATE Diligent-GraphicsEngineNextGenBase Diligent-GraphicsEngineVk-static)
target_compile_definitions(DiligentCoreAPITest PRIVATE DILIGENT_USE_VOLK=1)
if(PLATFORM_MACOS)
if(VULKAN_LIB_PATH)
# Configure rpath so that the executable can find vulkan library
Expand Down
150 changes: 150 additions & 0 deletions Tests/DiligentCoreAPITest/src/Vulkan/MemoryManagerTestVk.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright 2026 Diligent Graphics LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* In no event and under no legal theory, whether in tort (including negligence),
* contract, or otherwise, unless required by applicable law (such as deliberate
* and grossly negligent acts) or agreed to in writing, shall any Contributor be
* liable for any damages, including any direct, indirect, special, incidental,
* or consequential damages of any character arising as a result of this License or
* out of the use or inability to use the software (including but not limited to damages
* for loss of goodwill, work stoppage, computer failure or malfunction, or any and
* all other commercial damages or losses), even if such Contributor has been advised
* of the possibility of such damages.
*/

#include "volk.h"

#include "GPUTestingEnvironment.hpp"
#include "RenderDeviceVkImpl.hpp"
#include "DefaultRawMemoryAllocator.hpp"
#include "gtest/gtest.h"

namespace Diligent
{
namespace Testing
{
namespace
{

class TrackingMemoryManager : public VulkanUtilities::MemoryManager
{
public:
using MemoryManager::MemoryManager;
size_t LivePages = 0;

private:
void OnNewPageCreated(VulkanUtilities::MemoryPage&) override { ++LivePages; }
void OnPageDestroy(VulkanUtilities::MemoryPage&) override { --LivePages; }
};

TEST(VulkanMemoryManagerTest, OversizedPages)
{
auto* pEnv = GPUTestingEnvironment::GetInstance();
if (pEnv->GetDevice()->GetDeviceInfo().Type != RENDER_DEVICE_TYPE_VULKAN)
GTEST_SKIP();

auto* pDevice = static_cast<RenderDeviceVkImpl*>(pEnv->GetDevice());
constexpr VkDeviceSize PageSize = 64 << 10;
for (bool HostVisible : {false, true})
{
SCOPED_TRACE(HostVisible ? "host-visible" : "device-local");
TrackingMemoryManager Mgr{"Oversized page test", pDevice->GetLogicalDevice(),
pDevice->GetPhysicalDevice(), DefaultRawMemoryAllocator::GetAllocator(),
PageSize, PageSize, 16 * PageSize, 16 * PageSize};
const VkMemoryPropertyFlags Props = HostVisible ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
const uint32_t Type = pDevice->GetPhysicalDevice().GetMemoryTypeIndex(~0u, Props);
ASSERT_NE(Type, VulkanUtilities::PhysicalDevice::InvalidMemoryTypeIndex);

{
auto Large = Mgr.Allocate(3 * PageSize / 2, 256, Type, HostVisible, 0);
ASSERT_TRUE(Large);
EXPECT_EQ(Large.Page->GetPageSize(), 3 * PageSize / 2);
auto* LargePage = Large.Page;
{
auto Small = Mgr.Allocate(256, 256, Type, HostVisible, 0);
ASSERT_TRUE(Small);
EXPECT_NE(Small.Page, LargePage);
{
auto OtherSmall = Mgr.Allocate(256, 256, Type, HostVisible, 0);
ASSERT_TRUE(OtherSmall);
EXPECT_EQ(OtherSmall.Page, Small.Page);
}
Mgr.ShrinkMemory();
EXPECT_EQ(Mgr.LivePages, 2u); // Live allocations must survive collection.
{
auto Released = std::move(Large);
}
Mgr.ShrinkMemory();
EXPECT_EQ(Mgr.LivePages, 1u); // Oversized pages bypass the reserve.
{
auto Larger = Mgr.Allocate(2 * PageSize, 256, Type, HostVisible, 0);
ASSERT_TRUE(Larger);
EXPECT_EQ(Larger.Page->GetPageSize(), 2 * PageSize);
EXPECT_NE(Larger.Page, Small.Page);
}
Mgr.ShrinkMemory();
EXPECT_EQ(Mgr.LivePages, 1u);
}
}
Mgr.ShrinkMemory();
EXPECT_EQ(Mgr.LivePages, 1u); // Empty shared pages still obey the reserve.
{
auto Boundary = Mgr.Allocate(PageSize, 256, Type, HostVisible, 0);
ASSERT_TRUE(Boundary);
EXPECT_EQ(Mgr.LivePages, 1u);
EXPECT_EQ(Boundary.Page->GetPageSize(), PageSize);
}
}
}

TEST(VulkanMemoryManagerTest, AllocationAlignment)
{
auto* pEnv = GPUTestingEnvironment::GetInstance();
if (pEnv->GetDevice()->GetDeviceInfo().Type != RENDER_DEVICE_TYPE_VULKAN)
GTEST_SKIP();

auto* pDevice = static_cast<RenderDeviceVkImpl*>(pEnv->GetDevice());
constexpr VkDeviceSize PageSize = 64 << 10;
for (bool HostVisible : {false, true})
{
SCOPED_TRACE(HostVisible ? "host-visible" : "device-local");
TrackingMemoryManager Mgr{"Alignment test", pDevice->GetLogicalDevice(),
pDevice->GetPhysicalDevice(), DefaultRawMemoryAllocator::GetAllocator(),
PageSize, PageSize, 0, 0};
const VkMemoryPropertyFlags Props = HostVisible ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
const uint32_t Type = pDevice->GetPhysicalDevice().GetMemoryTypeIndex(~0u, Props);
ASSERT_NE(Type, VulkanUtilities::PhysicalDevice::InvalidMemoryTypeIndex);

const VkDeviceSize Sizes[] = {PageSize + 1, 1};
const VkDeviceSize Alignments[] = {256, 2 * PageSize};
for (size_t i = 0; i < 2; ++i)
{
SCOPED_TRACE(i);
{
auto Allocation = Mgr.Allocate(Sizes[i], Alignments[i], Type, HostVisible, 0);
ASSERT_TRUE(Allocation);
EXPECT_EQ(Allocation.UnalignedOffset, 0u);
EXPECT_GE(Allocation.Size, AlignUp(Sizes[i], Alignments[i]));
EXPECT_GE(Allocation.Page->GetPageSize(), Allocation.Size);
}
Mgr.ShrinkMemory();
EXPECT_EQ(Mgr.LivePages, 0u);
}
}
}

} // namespace
} // namespace Testing
} // namespace Diligent