#include "graphics/host_gpu/vulkanCommon.h" #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnullability-completeness" #pragma clang diagnostic ignored "-Wunused-private-field" #pragma clang diagnostic ignored "-Wunused-variable" #endif #define VMA_IMPLEMENTATION #include #if defined(__clang__) #pragma clang diagnostic pop #endif #include "common/assert.h" #include "common/logging/log.h" #include "common/profiler.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/vma.h" #include #include #include namespace Libs::Graphics { namespace { struct MemoryStats { std::atomic_uint64_t allocated[VK_MAX_MEMORY_TYPES] {}; std::atomic_uint64_t count[VK_MAX_MEMORY_TYPES] {}; }; MemoryStats g_memory_stats; } // namespace void VulkanTrackAllocation(const VulkanMemory& memory) { g_memory_stats.allocated[memory.type] += memory.requirements.size; g_memory_stats.count[memory.type]++; } void VulkanUntrackAllocation(const VulkanMemory& memory) { EXIT_IF(g_memory_stats.allocated[memory.type] < memory.requirements.size); EXIT_IF(g_memory_stats.count[memory.type] == 0); g_memory_stats.allocated[memory.type] -= memory.requirements.size; g_memory_stats.count[memory.type]--; } bool GraphicContext::CreateAllocator() { KYTY_PROFILER_FUNCTION(); EXIT_IF(instance == nullptr || physical_device == nullptr || device == nullptr || allocator != nullptr); VmaVulkanFunctions functions {}; functions.vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr; functions.vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr; VmaAllocatorCreateInfo info {}; info.instance = instance; info.physicalDevice = physical_device; info.device = device; info.pVulkanFunctions = &functions; info.vulkanApiVersion = VULKAN_TARGET_API_VERSION; info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; if (memory_budget_ext_enabled) { info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; } const auto result = static_cast(vmaCreateAllocator(&info, &allocator)); if (result != vk::Result::eSuccess) { LOGF("vmaCreateAllocator failed: %s\n", vk::to_string(result).c_str()); return false; } return true; } void GraphicContext::DestroyAllocator() { if (allocator == nullptr) { return; } vmaDestroyAllocator(allocator); allocator = nullptr; } void GraphicContext::LogMemoryBudget() const { if (allocator == nullptr || physical_device == nullptr) { return; } const auto& properties = GetPhysicalDeviceMemoryProperties(); VmaBudget budgets[VK_MAX_MEMORY_HEAPS] {}; vmaGetHeapBudgets(allocator, budgets); for (uint32_t i = 0; i < properties.memoryHeapCount; i++) { LOGF("VMA heap %u: usage=%" PRIu64 ", budget=%" PRIu64 ", allocation=%" PRIu64 ", blocks=%" PRIu64 "\n", i, static_cast(budgets[i].usage), static_cast(budgets[i].budget), static_cast(budgets[i].statistics.allocationBytes), static_cast(budgets[i].statistics.blockBytes)); } } uint64_t GraphicContext::GetDeviceMemoryUsage() const { if (!CanReportMemoryUsage() || allocator == nullptr) { return 0; } VmaBudget budgets[VK_MAX_MEMORY_HEAPS] {}; vmaGetHeapBudgets(allocator, budgets); const bool discrete = physical_device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu; uint64_t usage = 0; for (uint32_t heap = 0; heap < physical_device_memory_properties.memoryHeapCount; heap++) { const bool device_local = static_cast(physical_device_memory_properties.memoryHeaps[heap].flags & vk::MemoryHeapFlagBits::eDeviceLocal); if (!discrete || device_local) { usage += budgets[heap].usage; } } return usage; } uint64_t GraphicContext::GetTotalMemoryBudget() const { if (allocator == nullptr) { return 0; } VmaBudget budgets[VK_MAX_MEMORY_HEAPS] {}; vmaGetHeapBudgets(allocator, budgets); const bool discrete = physical_device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu; uint64_t budget = 0; uint64_t local = 0; uint64_t usage = 0; for (uint32_t heap = 0; heap < physical_device_memory_properties.memoryHeapCount; heap++) { const auto& properties = physical_device_memory_properties.memoryHeaps[heap]; const bool device_local = static_cast(properties.flags & vk::MemoryHeapFlagBits::eDeviceLocal); if (device_local) { local += properties.size; } if (!discrete || device_local) { budget += CanReportMemoryUsage() ? budgets[heap].budget : properties.size; usage += CanReportMemoryUsage() ? budgets[heap].usage : 0; } } if (discrete) { return budget - std::min(budget / 8, 1024ull * 1024 * 1024); } constexpr uint64_t system_reserve = 8ull * 1024 * 1024 * 1024; const auto available = budget > usage ? budget - usage : uint64_t {0}; return std::max(local, available > system_reserve ? available - system_reserve : uint64_t {0}); } bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) { KYTY_PROFILER_FUNCTION(); EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr); auto& memory = image.memory; VmaAllocationCreateInfo alloc_info {}; alloc_info.requiredFlags = static_cast(memory.property); alloc_info.preferredFlags = static_cast(memory.preferred_property); VmaAllocationInfo allocation_result {}; vk::Image::CType native_image = VK_NULL_HANDLE; const auto result = static_cast( vmaCreateImage(allocator, static_cast(image_info), &alloc_info, &native_image, &memory.allocation, &allocation_result)); image.image = native_image; if (result != vk::Result::eSuccess) { LogMemoryBudget(); return false; } device.getImageMemoryRequirements(image.image, &memory.requirements); memory.type = allocation_result.memoryType; VulkanTrackAllocation(memory); return true; } void GraphicContext::DeleteImage(VulkanImage& image) { KYTY_PROFILER_FUNCTION(); EXIT_IF(allocator == nullptr || image.image == nullptr || image.memory.allocation == nullptr); auto& memory = image.memory; VulkanUntrackAllocation(memory); vmaDestroyImage(allocator, image.image, memory.allocation); image.image = nullptr; memory.allocation = nullptr; } } // namespace Libs::Graphics