#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; void TrackAllocationImpl(const VulkanMemory& memory) { g_memory_stats.allocated[memory.type] += memory.requirements.size; g_memory_stats.count[memory.type]++; } void UntrackAllocationImpl(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]--; } } // namespace void VulkanTrackAllocation(const VulkanMemory& memory) { TrackAllocationImpl(memory); } void VulkanUntrackAllocation(const VulkanMemory& memory) { UntrackAllocationImpl(memory); } 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 = memory_budget_ext_enabled ? VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT : 0; const auto result = static_cast(vmaCreateAllocator(&info, &allocator)); if (result != vk::Result::eSuccess) { LOGF("vmaCreateAllocator failed: %s\n", VulkanToString(result).c_str()); return false; } return true; } void GraphicContext::DestroyAllocator() { if (allocator == nullptr) { return; } vmaDestroyAllocator(allocator); allocator = nullptr; } uint64_t VulkanNextMemoryUniqueId() { static std::atomic_uint64_t sequence = 0; return ++sequence; } 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}); } void GraphicContext::CreateBuffer(uint64_t size, VulkanBuffer& buffer) { KYTY_PROFILER_FUNCTION(); EXIT_IF(allocator == nullptr || buffer.buffer != nullptr || buffer.memory.allocation != nullptr || size == 0); vk::BufferCreateInfo buffer_info {}; buffer_info.sType = vk::StructureType::eBufferCreateInfo; buffer_info.size = size; buffer_info.usage = buffer.usage; buffer_info.sharingMode = vk::SharingMode::eExclusive; VmaAllocationCreateInfo alloc_info {}; alloc_info.requiredFlags = static_cast(buffer.memory.property); alloc_info.preferredFlags = static_cast(buffer.memory.preferred_property); vk::Buffer::CType native_buffer = VK_NULL_HANDLE; const auto result = static_cast(vmaCreateBuffer( allocator, static_cast(buffer_info), &alloc_info, &native_buffer, &buffer.memory.allocation, &buffer.memory.allocation_info)); buffer.buffer = native_buffer; if (result != vk::Result::eSuccess) { LogMemoryBudget(); } EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); device.getBufferMemoryRequirements(buffer.buffer, &buffer.memory.requirements); buffer.memory.type = buffer.memory.allocation_info.memoryType; buffer.memory.memory = buffer.memory.allocation_info.deviceMemory; buffer.memory.offset = buffer.memory.allocation_info.offset; buffer.memory.unique_id = VulkanNextMemoryUniqueId(); buffer.buffer_size = size; VulkanTrackAllocation(buffer.memory); } void GraphicContext::DeleteBuffer(VulkanBuffer& buffer) { KYTY_PROFILER_FUNCTION(); EXIT_IF(allocator == nullptr || buffer.buffer == nullptr || buffer.memory.allocation == nullptr); VulkanUntrackAllocation(buffer.memory); vmaDestroyBuffer(allocator, buffer.buffer, buffer.memory.allocation); buffer.buffer = nullptr; buffer.memory.memory = nullptr; buffer.memory.allocation = nullptr; buffer.memory.allocation_info = {}; buffer.memory.offset = 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); 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, &memory.allocation_info)); image.image = native_image; if (result != vk::Result::eSuccess) { LogMemoryBudget(); return false; } device.getImageMemoryRequirements(image.image, &memory.requirements); memory.type = memory.allocation_info.memoryType; memory.memory = memory.allocation_info.deviceMemory; memory.offset = memory.allocation_info.offset; memory.unique_id = VulkanNextMemoryUniqueId(); 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.memory = nullptr; memory.allocation = nullptr; memory.allocation_info = {}; memory.offset = 0; } void GraphicContext::MapMemory(VulkanMemory& memory, void*& data) { KYTY_PROFILER_FUNCTION(); EXIT_IF(allocator == nullptr || memory.allocation == nullptr); EXIT_NOT_IMPLEMENTED(static_cast(vmaMapMemory(allocator, memory.allocation, &data)) != vk::Result::eSuccess); } void GraphicContext::UnmapMemory(VulkanMemory& memory) { KYTY_PROFILER_FUNCTION(); EXIT_IF(allocator == nullptr || memory.allocation == nullptr); vmaUnmapMemory(allocator, memory.allocation); } } // namespace Libs::Graphics