| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "impeller/renderer/backend/vulkan/allocator_vk.h" |
| |
| #include <memory> |
| |
| #include "flutter/fml/memory/ref_ptr.h" |
| #include "flutter/fml/trace_event.h" |
| #include "impeller/core/formats.h" |
| #include "impeller/renderer/backend/vulkan/device_buffer_vk.h" |
| #include "impeller/renderer/backend/vulkan/formats_vk.h" |
| #include "impeller/renderer/backend/vulkan/texture_vk.h" |
| #include "vulkan/vulkan_enums.hpp" |
| |
| namespace impeller { |
| |
| static constexpr vk::Flags<vk::MemoryPropertyFlagBits> |
| ToVKBufferMemoryPropertyFlags(StorageMode mode) { |
| switch (mode) { |
| case StorageMode::kHostVisible: |
| return vk::MemoryPropertyFlagBits::eHostVisible; |
| case StorageMode::kDevicePrivate: |
| return vk::MemoryPropertyFlagBits::eDeviceLocal; |
| case StorageMode::kDeviceTransient: |
| return vk::MemoryPropertyFlagBits::eLazilyAllocated; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static VmaAllocationCreateFlags ToVmaAllocationBufferCreateFlags( |
| StorageMode mode, |
| bool readback) { |
| VmaAllocationCreateFlags flags = 0; |
| switch (mode) { |
| case StorageMode::kHostVisible: |
| if (!readback) { |
| flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; |
| } else { |
| flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; |
| } |
| flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; |
| return flags; |
| case StorageMode::kDevicePrivate: |
| FML_DCHECK(!readback); |
| return flags; |
| case StorageMode::kDeviceTransient: |
| FML_DCHECK(!readback); |
| return flags; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static PoolVMA CreateBufferPool(VmaAllocator allocator) { |
| vk::BufferCreateInfo buffer_info; |
| buffer_info.usage = vk::BufferUsageFlagBits::eVertexBuffer | |
| vk::BufferUsageFlagBits::eIndexBuffer | |
| vk::BufferUsageFlagBits::eUniformBuffer | |
| vk::BufferUsageFlagBits::eStorageBuffer | |
| vk::BufferUsageFlagBits::eTransferSrc | |
| vk::BufferUsageFlagBits::eTransferDst; |
| buffer_info.size = 1u; // doesn't matter |
| buffer_info.sharingMode = vk::SharingMode::eExclusive; |
| auto buffer_info_native = |
| static_cast<vk::BufferCreateInfo::NativeType>(buffer_info); |
| |
| VmaAllocationCreateInfo allocation_info = {}; |
| allocation_info.usage = VMA_MEMORY_USAGE_AUTO; |
| allocation_info.preferredFlags = static_cast<VkMemoryPropertyFlags>( |
| ToVKBufferMemoryPropertyFlags(StorageMode::kHostVisible)); |
| allocation_info.flags = ToVmaAllocationBufferCreateFlags( |
| StorageMode::kHostVisible, /*readback=*/false); |
| |
| uint32_t memTypeIndex; |
| auto result = vk::Result{vmaFindMemoryTypeIndexForBufferInfo( |
| allocator, &buffer_info_native, &allocation_info, &memTypeIndex)}; |
| if (result != vk::Result::eSuccess) { |
| return {}; |
| } |
| |
| VmaPoolCreateInfo pool_create_info = {}; |
| pool_create_info.memoryTypeIndex = memTypeIndex; |
| pool_create_info.flags = VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT; |
| |
| VmaPool pool = {}; |
| result = vk::Result{::vmaCreatePool(allocator, &pool_create_info, &pool)}; |
| if (result != vk::Result::eSuccess) { |
| return {}; |
| } |
| return {allocator, pool}; |
| } |
| |
| AllocatorVK::AllocatorVK(std::weak_ptr<Context> context, |
| uint32_t vulkan_api_version, |
| const vk::PhysicalDevice& physical_device, |
| const std::shared_ptr<DeviceHolderVK>& device_holder, |
| const vk::Instance& instance, |
| const CapabilitiesVK& capabilities) |
| : context_(std::move(context)), device_holder_(device_holder) { |
| auto limits = physical_device.getProperties().limits; |
| max_texture_size_.width = max_texture_size_.height = |
| limits.maxImageDimension2D; |
| physical_device.getMemoryProperties(&memory_properties_); |
| |
| VmaVulkanFunctions proc_table = {}; |
| |
| #define BIND_VMA_PROC(x) proc_table.x = VULKAN_HPP_DEFAULT_DISPATCHER.x; |
| #define BIND_VMA_PROC_KHR(x) \ |
| proc_table.x##KHR = VULKAN_HPP_DEFAULT_DISPATCHER.x \ |
| ? VULKAN_HPP_DEFAULT_DISPATCHER.x \ |
| : VULKAN_HPP_DEFAULT_DISPATCHER.x##KHR; |
| BIND_VMA_PROC(vkGetInstanceProcAddr); |
| BIND_VMA_PROC(vkGetDeviceProcAddr); |
| BIND_VMA_PROC(vkGetPhysicalDeviceProperties); |
| BIND_VMA_PROC(vkGetPhysicalDeviceMemoryProperties); |
| BIND_VMA_PROC(vkAllocateMemory); |
| BIND_VMA_PROC(vkFreeMemory); |
| BIND_VMA_PROC(vkMapMemory); |
| BIND_VMA_PROC(vkUnmapMemory); |
| BIND_VMA_PROC(vkFlushMappedMemoryRanges); |
| BIND_VMA_PROC(vkInvalidateMappedMemoryRanges); |
| BIND_VMA_PROC(vkBindBufferMemory); |
| BIND_VMA_PROC(vkBindImageMemory); |
| BIND_VMA_PROC(vkGetBufferMemoryRequirements); |
| BIND_VMA_PROC(vkGetImageMemoryRequirements); |
| BIND_VMA_PROC(vkCreateBuffer); |
| BIND_VMA_PROC(vkDestroyBuffer); |
| BIND_VMA_PROC(vkCreateImage); |
| BIND_VMA_PROC(vkDestroyImage); |
| BIND_VMA_PROC(vkCmdCopyBuffer); |
| BIND_VMA_PROC_KHR(vkGetBufferMemoryRequirements2); |
| BIND_VMA_PROC_KHR(vkGetImageMemoryRequirements2); |
| BIND_VMA_PROC_KHR(vkBindBufferMemory2); |
| BIND_VMA_PROC_KHR(vkBindImageMemory2); |
| BIND_VMA_PROC_KHR(vkGetPhysicalDeviceMemoryProperties2); |
| #undef BIND_VMA_PROC_KHR |
| #undef BIND_VMA_PROC |
| |
| VmaAllocatorCreateInfo allocator_info = {}; |
| allocator_info.vulkanApiVersion = vulkan_api_version; |
| allocator_info.physicalDevice = physical_device; |
| allocator_info.device = device_holder->GetDevice(); |
| allocator_info.instance = instance; |
| allocator_info.pVulkanFunctions = &proc_table; |
| |
| VmaAllocator allocator = {}; |
| auto result = vk::Result{::vmaCreateAllocator(&allocator_info, &allocator)}; |
| if (result != vk::Result::eSuccess) { |
| VALIDATION_LOG << "Could not create memory allocator"; |
| return; |
| } |
| staging_buffer_pool_.reset(CreateBufferPool(allocator)); |
| created_buffer_pool_ &= staging_buffer_pool_.is_valid(); |
| allocator_.reset(allocator); |
| supports_memoryless_textures_ = |
| capabilities.SupportsDeviceTransientTextures(); |
| is_valid_ = true; |
| } |
| |
| AllocatorVK::~AllocatorVK() = default; |
| |
| // |Allocator| |
| bool AllocatorVK::IsValid() const { |
| return is_valid_; |
| } |
| |
| // |Allocator| |
| ISize AllocatorVK::GetMaxTextureSizeSupported() const { |
| return max_texture_size_; |
| } |
| |
| int32_t AllocatorVK::FindMemoryTypeIndex( |
| uint32_t memory_type_bits_requirement, |
| vk::PhysicalDeviceMemoryProperties& memory_properties) { |
| int32_t type_index = -1; |
| vk::MemoryPropertyFlagBits required_properties = |
| vk::MemoryPropertyFlagBits::eDeviceLocal; |
| |
| const uint32_t memory_count = memory_properties.memoryTypeCount; |
| for (uint32_t memory_index = 0; memory_index < memory_count; ++memory_index) { |
| const uint32_t memory_type_bits = (1 << memory_index); |
| const bool is_required_memory_type = |
| memory_type_bits_requirement & memory_type_bits; |
| |
| const auto properties = |
| memory_properties.memoryTypes[memory_index].propertyFlags; |
| const bool has_required_properties = |
| (properties & required_properties) == required_properties; |
| |
| if (is_required_memory_type && has_required_properties) { |
| return static_cast<int32_t>(memory_index); |
| } |
| } |
| |
| return type_index; |
| } |
| |
| vk::ImageUsageFlags AllocatorVK::ToVKImageUsageFlags( |
| PixelFormat format, |
| TextureUsageMask usage, |
| StorageMode mode, |
| bool supports_memoryless_textures) { |
| vk::ImageUsageFlags vk_usage; |
| |
| switch (mode) { |
| case StorageMode::kHostVisible: |
| case StorageMode::kDevicePrivate: |
| break; |
| case StorageMode::kDeviceTransient: |
| if (supports_memoryless_textures) { |
| vk_usage |= vk::ImageUsageFlagBits::eTransientAttachment; |
| } |
| break; |
| } |
| |
| if (usage & TextureUsage::kRenderTarget) { |
| if (PixelFormatIsDepthStencil(format)) { |
| vk_usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; |
| } else { |
| vk_usage |= vk::ImageUsageFlagBits::eColorAttachment; |
| vk_usage |= vk::ImageUsageFlagBits::eInputAttachment; |
| } |
| } |
| |
| if (usage & TextureUsage::kShaderRead) { |
| vk_usage |= vk::ImageUsageFlagBits::eSampled; |
| } |
| |
| if (usage & TextureUsage::kShaderWrite) { |
| vk_usage |= vk::ImageUsageFlagBits::eStorage; |
| } |
| |
| if (mode != StorageMode::kDeviceTransient) { |
| // Add transfer usage flags to support blit passes only if image isn't |
| // device transient. |
| vk_usage |= vk::ImageUsageFlagBits::eTransferSrc | |
| vk::ImageUsageFlagBits::eTransferDst; |
| } |
| |
| return vk_usage; |
| } |
| |
| static constexpr VmaMemoryUsage ToVMAMemoryUsage() { |
| return VMA_MEMORY_USAGE_AUTO; |
| } |
| |
| static constexpr vk::Flags<vk::MemoryPropertyFlagBits> |
| ToVKTextureMemoryPropertyFlags(StorageMode mode, |
| bool supports_memoryless_textures) { |
| switch (mode) { |
| case StorageMode::kHostVisible: |
| return vk::MemoryPropertyFlagBits::eHostVisible | |
| vk::MemoryPropertyFlagBits::eDeviceLocal; |
| case StorageMode::kDevicePrivate: |
| return vk::MemoryPropertyFlagBits::eDeviceLocal; |
| case StorageMode::kDeviceTransient: |
| if (supports_memoryless_textures) { |
| return vk::MemoryPropertyFlagBits::eLazilyAllocated | |
| vk::MemoryPropertyFlagBits::eDeviceLocal; |
| } |
| return vk::MemoryPropertyFlagBits::eDeviceLocal; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static VmaAllocationCreateFlags ToVmaAllocationCreateFlags(StorageMode mode) { |
| VmaAllocationCreateFlags flags = 0; |
| switch (mode) { |
| case StorageMode::kHostVisible: |
| return flags; |
| case StorageMode::kDevicePrivate: |
| return flags; |
| case StorageMode::kDeviceTransient: |
| return flags; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| class AllocatedTextureSourceVK final : public TextureSourceVK { |
| public: |
| AllocatedTextureSourceVK(std::weak_ptr<ResourceManagerVK> resource_manager, |
| const TextureDescriptor& desc, |
| VmaAllocator allocator, |
| vk::Device device, |
| bool supports_memoryless_textures) |
| : TextureSourceVK(desc), resource_(std::move(resource_manager)) { |
| FML_DCHECK(desc.format != PixelFormat::kUnknown); |
| vk::ImageCreateInfo image_info; |
| image_info.flags = ToVKImageCreateFlags(desc.type); |
| image_info.imageType = vk::ImageType::e2D; |
| image_info.format = ToVKImageFormat(desc.format); |
| image_info.extent = VkExtent3D{ |
| static_cast<uint32_t>(desc.size.width), // width |
| static_cast<uint32_t>(desc.size.height), // height |
| 1u // depth |
| }; |
| image_info.samples = ToVKSampleCount(desc.sample_count); |
| image_info.mipLevels = desc.mip_count; |
| image_info.arrayLayers = ToArrayLayerCount(desc.type); |
| image_info.tiling = vk::ImageTiling::eOptimal; |
| image_info.initialLayout = vk::ImageLayout::eUndefined; |
| image_info.usage = AllocatorVK::ToVKImageUsageFlags( |
| desc.format, desc.usage, desc.storage_mode, |
| supports_memoryless_textures); |
| image_info.sharingMode = vk::SharingMode::eExclusive; |
| |
| VmaAllocationCreateInfo alloc_nfo = {}; |
| |
| alloc_nfo.usage = ToVMAMemoryUsage(); |
| alloc_nfo.preferredFlags = |
| static_cast<VkMemoryPropertyFlags>(ToVKTextureMemoryPropertyFlags( |
| desc.storage_mode, supports_memoryless_textures)); |
| alloc_nfo.flags = ToVmaAllocationCreateFlags(desc.storage_mode); |
| |
| auto create_info_native = |
| static_cast<vk::ImageCreateInfo::NativeType>(image_info); |
| |
| VkImage vk_image = VK_NULL_HANDLE; |
| VmaAllocation allocation = {}; |
| VmaAllocationInfo allocation_info = {}; |
| { |
| auto result = vk::Result{::vmaCreateImage(allocator, // |
| &create_info_native, // |
| &alloc_nfo, // |
| &vk_image, // |
| &allocation, // |
| &allocation_info // |
| )}; |
| if (result != vk::Result::eSuccess) { |
| VALIDATION_LOG << "Unable to allocate Vulkan Image: " |
| << vk::to_string(result) |
| << " Type: " << TextureTypeToString(desc.type) |
| << " Mode: " << StorageModeToString(desc.storage_mode) |
| << " Usage: " << TextureUsageMaskToString(desc.usage) |
| << " [VK]Flags: " << vk::to_string(image_info.flags) |
| << " [VK]Format: " << vk::to_string(image_info.format) |
| << " [VK]Usage: " << vk::to_string(image_info.usage) |
| << " [VK]Mem. Flags: " |
| << vk::to_string(vk::MemoryPropertyFlags( |
| alloc_nfo.preferredFlags)); |
| return; |
| } |
| } |
| |
| auto image = vk::Image{vk_image}; |
| |
| vk::ImageViewCreateInfo view_info = {}; |
| view_info.image = image; |
| view_info.viewType = ToVKImageViewType(desc.type); |
| view_info.format = image_info.format; |
| view_info.subresourceRange.aspectMask = ToVKImageAspectFlags(desc.format); |
| view_info.subresourceRange.levelCount = image_info.mipLevels; |
| view_info.subresourceRange.layerCount = ToArrayLayerCount(desc.type); |
| |
| // Vulkan does not have an image format that is equivalent to |
| // `MTLPixelFormatA8Unorm`, so we use `R8Unorm` instead. Given that the |
| // shaders expect that alpha channel to be set in the cases, we swizzle. |
| // See: https://github.com/flutter/flutter/issues/115461 for more details. |
| if (desc.format == PixelFormat::kA8UNormInt) { |
| view_info.components.a = vk::ComponentSwizzle::eR; |
| view_info.components.r = vk::ComponentSwizzle::eA; |
| } |
| |
| auto [result, image_view] = device.createImageViewUnique(view_info); |
| if (result != vk::Result::eSuccess) { |
| VALIDATION_LOG << "Unable to create an image view for allocation: " |
| << vk::to_string(result); |
| return; |
| } |
| // Create a specialized view for render target attachments. |
| view_info.subresourceRange.levelCount = 1u; |
| auto [rt_result, rt_image_view] = device.createImageViewUnique(view_info); |
| if (rt_result != vk::Result::eSuccess) { |
| VALIDATION_LOG << "Unable to create an image view for allocation: " |
| << vk::to_string(rt_result); |
| return; |
| } |
| |
| resource_.Swap(ImageResource(ImageVMA{allocator, allocation, image}, |
| std::move(image_view), |
| std::move(rt_image_view))); |
| is_valid_ = true; |
| } |
| |
| ~AllocatedTextureSourceVK() = default; |
| |
| bool IsValid() const { return is_valid_; } |
| |
| vk::Image GetImage() const override { return resource_->image.get().image; } |
| |
| vk::ImageView GetImageView() const override { |
| return resource_->image_view.get(); |
| } |
| |
| vk::ImageView GetRenderTargetView() const override { |
| return resource_->rt_image_view.get(); |
| } |
| |
| bool IsSwapchainImage() const override { return false; } |
| |
| private: |
| struct ImageResource { |
| UniqueImageVMA image; |
| vk::UniqueImageView image_view; |
| vk::UniqueImageView rt_image_view; |
| |
| ImageResource() = default; |
| |
| ImageResource(ImageVMA p_image, |
| vk::UniqueImageView p_image_view, |
| vk::UniqueImageView p_rt_image_view) |
| : image(p_image), |
| image_view(std::move(p_image_view)), |
| rt_image_view(std::move(p_rt_image_view)) {} |
| |
| ImageResource(ImageResource&& o) = default; |
| |
| ImageResource(const ImageResource&) = delete; |
| |
| ImageResource& operator=(const ImageResource&) = delete; |
| }; |
| |
| UniqueResourceVKT<ImageResource> resource_; |
| bool is_valid_ = false; |
| |
| AllocatedTextureSourceVK(const AllocatedTextureSourceVK&) = delete; |
| |
| AllocatedTextureSourceVK& operator=(const AllocatedTextureSourceVK&) = delete; |
| }; |
| |
| // |Allocator| |
| std::shared_ptr<Texture> AllocatorVK::OnCreateTexture( |
| const TextureDescriptor& desc) { |
| if (!IsValid()) { |
| return nullptr; |
| } |
| auto device_holder = device_holder_.lock(); |
| if (!device_holder) { |
| return nullptr; |
| } |
| auto context = context_.lock(); |
| if (!context) { |
| return nullptr; |
| } |
| auto source = std::make_shared<AllocatedTextureSourceVK>( |
| ContextVK::Cast(*context).GetResourceManager(), // |
| desc, // |
| allocator_.get(), // |
| device_holder->GetDevice(), // |
| supports_memoryless_textures_ // |
| ); |
| if (!source->IsValid()) { |
| return nullptr; |
| } |
| return std::make_shared<TextureVK>(context_, std::move(source)); |
| } |
| |
| // |Allocator| |
| std::shared_ptr<DeviceBuffer> AllocatorVK::OnCreateBuffer( |
| const DeviceBufferDescriptor& desc) { |
| vk::BufferCreateInfo buffer_info; |
| buffer_info.usage = vk::BufferUsageFlagBits::eVertexBuffer | |
| vk::BufferUsageFlagBits::eIndexBuffer | |
| vk::BufferUsageFlagBits::eUniformBuffer | |
| vk::BufferUsageFlagBits::eStorageBuffer | |
| vk::BufferUsageFlagBits::eTransferSrc | |
| vk::BufferUsageFlagBits::eTransferDst; |
| buffer_info.size = desc.size; |
| buffer_info.sharingMode = vk::SharingMode::eExclusive; |
| auto buffer_info_native = |
| static_cast<vk::BufferCreateInfo::NativeType>(buffer_info); |
| |
| VmaAllocationCreateInfo allocation_info = {}; |
| allocation_info.usage = ToVMAMemoryUsage(); |
| allocation_info.preferredFlags = static_cast<VkMemoryPropertyFlags>( |
| ToVKBufferMemoryPropertyFlags(desc.storage_mode)); |
| allocation_info.flags = |
| ToVmaAllocationBufferCreateFlags(desc.storage_mode, desc.readback); |
| if (created_buffer_pool_ && desc.storage_mode == StorageMode::kHostVisible && |
| !desc.readback) { |
| allocation_info.pool = staging_buffer_pool_.get().pool; |
| } |
| |
| VkBuffer buffer = {}; |
| VmaAllocation buffer_allocation = {}; |
| VmaAllocationInfo buffer_allocation_info = {}; |
| auto result = vk::Result{::vmaCreateBuffer(allocator_.get(), // |
| &buffer_info_native, // |
| &allocation_info, // |
| &buffer, // |
| &buffer_allocation, // |
| &buffer_allocation_info // |
| )}; |
| |
| if (result != vk::Result::eSuccess) { |
| VALIDATION_LOG << "Unable to allocate a device buffer: " |
| << vk::to_string(result); |
| return {}; |
| } |
| |
| return std::make_shared<DeviceBufferVK>( |
| desc, // |
| context_, // |
| UniqueBufferVMA{BufferVMA{allocator_.get(), // |
| buffer_allocation, // |
| vk::Buffer{buffer}}}, // |
| buffer_allocation_info // |
| ); |
| } |
| |
| size_t AllocatorVK::DebugGetHeapUsage() const { |
| auto count = memory_properties_.memoryHeapCount; |
| std::vector<VmaBudget> budgets(count); |
| vmaGetHeapBudgets(allocator_.get(), budgets.data()); |
| size_t total_usage = 0; |
| for (auto i = 0u; i < count; i++) { |
| const VmaBudget& budget = budgets[i]; |
| total_usage += budget.usage; |
| } |
| // Convert bytes to MB. |
| total_usage *= 1e-6; |
| return total_usage; |
| } |
| |
| void AllocatorVK::DebugTraceMemoryStatistics() const { |
| #ifdef IMPELLER_DEBUG |
| FML_TRACE_COUNTER("flutter", "AllocatorVK", |
| reinterpret_cast<int64_t>(this), // Trace Counter ID |
| "MemoryBudgetUsageMB", DebugGetHeapUsage()); |
| #endif // IMPELLER_DEBUG |
| } |
| |
| } // namespace impeller |