blob: 93104a1c91944774dacedbcc4dcc19fd3075f5a6 [file] [log] [blame]
// 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"
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) {
VmaAllocationCreateFlags flags = 0;
switch (mode) {
case StorageMode::kHostVisible:
flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
return flags;
case StorageMode::kDevicePrivate:
return flags;
case StorageMode::kDeviceTransient:
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);
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<DeviceHolder>& device_holder,
const vk::Instance& instance,
PFN_vkGetInstanceProcAddr get_instance_proc_address,
PFN_vkGetDeviceProcAddr get_device_proc_address,
const CapabilitiesVK& capabilities)
: context_(std::move(context)), device_holder_(device_holder) {
TRACE_EVENT0("impeller", "CreateAllocatorVK");
vk_ = fml::MakeRefCounted<vulkan::VulkanProcTable>(get_instance_proc_address);
auto instance_handle = vulkan::VulkanHandle<VkInstance>(instance);
if (!vk_->SetupInstanceProcAddresses(instance_handle)) {
return;
}
auto device_handle =
vulkan::VulkanHandle<VkDevice>(device_holder->GetDevice());
if (!vk_->SetupDeviceProcAddresses(device_handle)) {
return;
}
auto limits = physical_device.getProperties().limits;
max_texture_size_.width = max_texture_size_.height =
limits.maxImageDimension2D;
VmaVulkanFunctions proc_table = {};
proc_table.vkGetInstanceProcAddr = get_instance_proc_address;
proc_table.vkGetDeviceProcAddr = get_device_proc_address;
#define PROVIDE_PROC(tbl, proc, provider) tbl.vk##proc = provider->proc;
PROVIDE_PROC(proc_table, GetPhysicalDeviceProperties, vk_);
PROVIDE_PROC(proc_table, GetPhysicalDeviceMemoryProperties, vk_);
PROVIDE_PROC(proc_table, AllocateMemory, vk_);
PROVIDE_PROC(proc_table, FreeMemory, vk_);
PROVIDE_PROC(proc_table, MapMemory, vk_);
PROVIDE_PROC(proc_table, UnmapMemory, vk_);
PROVIDE_PROC(proc_table, FlushMappedMemoryRanges, vk_);
PROVIDE_PROC(proc_table, InvalidateMappedMemoryRanges, vk_);
PROVIDE_PROC(proc_table, BindBufferMemory, vk_);
PROVIDE_PROC(proc_table, BindImageMemory, vk_);
PROVIDE_PROC(proc_table, GetBufferMemoryRequirements, vk_);
PROVIDE_PROC(proc_table, GetImageMemoryRequirements, vk_);
PROVIDE_PROC(proc_table, CreateBuffer, vk_);
PROVIDE_PROC(proc_table, DestroyBuffer, vk_);
PROVIDE_PROC(proc_table, CreateImage, vk_);
PROVIDE_PROC(proc_table, DestroyImage, vk_);
PROVIDE_PROC(proc_table, CmdCopyBuffer, vk_);
#define PROVIDE_PROC_COALESCE(tbl, proc, provider) \
tbl.vk##proc##KHR = provider->proc ? provider->proc : provider->proc##KHR;
// See the following link for why we have to pick either KHR version or
// promoted non-KHR version:
// https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/issues/203
PROVIDE_PROC_COALESCE(proc_table, GetBufferMemoryRequirements2, vk_);
PROVIDE_PROC_COALESCE(proc_table, GetImageMemoryRequirements2, vk_);
PROVIDE_PROC_COALESCE(proc_table, BindBufferMemory2, vk_);
PROVIDE_PROC_COALESCE(proc_table, BindImageMemory2, vk_);
PROVIDE_PROC_COALESCE(proc_table, GetPhysicalDeviceMemoryProperties2, vk_);
#undef PROVIDE_PROC_COALESCE
#undef PROVIDE_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_;
}
static constexpr vk::ImageUsageFlags 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 & static_cast<TextureUsageMask>(TextureUsage::kRenderTarget)) {
if (PixelFormatIsDepthStencil(format)) {
vk_usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
} else {
vk_usage |= vk::ImageUsageFlagBits::eColorAttachment;
}
}
if (usage & static_cast<TextureUsageMask>(TextureUsage::kShaderRead)) {
vk_usage |= vk::ImageUsageFlagBits::eSampled;
// Device transient images can only be used as attachments. The caller
// specified incorrect usage flags and is attempting to read a device
// transient image in a shader. Unset the transient attachment flag. See:
// https://github.com/flutter/flutter/issues/121633
if (mode == StorageMode::kDeviceTransient) {
vk_usage &= ~vk::ImageUsageFlagBits::eTransientAttachment;
}
}
if (usage & static_cast<TextureUsageMask>(TextureUsage::kShaderWrite)) {
vk_usage |= vk::ImageUsageFlagBits::eStorage;
// Device transient images can only be used as attachments. The caller
// specified incorrect usage flags and is attempting to read a device
// transient image in a shader. Unset the transient attachment flag. See:
// https://github.com/flutter/flutter/issues/121633
if (mode == StorageMode::kDeviceTransient) {
vk_usage &= ~vk::ImageUsageFlagBits::eTransientAttachment;
}
}
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)) {
TRACE_EVENT0("impeller", "CreateDeviceTexture");
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 =
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;
}
resource_.Reset(ImageResource(ImageVMA{allocator, allocation, image},
std::move(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();
}
private:
struct ImageResource {
UniqueImageVMA image;
vk::UniqueImageView image_view;
ImageResource() = default;
ImageResource(ImageVMA p_image, vk::UniqueImageView p_image_view)
: image(p_image), image_view(std::move(p_image_view)) {}
ImageResource(ImageResource&& o) {
std::swap(image, o.image);
std::swap(image_view, o.image_view);
}
FML_DISALLOW_COPY_AND_ASSIGN(ImageResource);
};
UniqueResourceVKT<ImageResource> resource_;
bool is_valid_ = false;
FML_DISALLOW_COPY_AND_ASSIGN(AllocatedTextureSourceVK);
};
// |Allocator|
std::shared_ptr<Texture> AllocatorVK::OnCreateTexture(
const TextureDescriptor& desc) {
TRACE_EVENT0("impeller", "AllocatorVK::OnCreateTexture");
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));
}
void AllocatorVK::DidAcquireSurfaceFrame() {
frame_count_++;
raster_thread_id_ = std::this_thread::get_id();
}
// |Allocator|
std::shared_ptr<DeviceBuffer> AllocatorVK::OnCreateBuffer(
const DeviceBufferDescriptor& desc) {
TRACE_EVENT0("impeller", "AllocatorVK::OnCreateBuffer");
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);
if (created_buffer_pool_ && desc.storage_mode == StorageMode::kHostVisible &&
raster_thread_id_ == std::this_thread::get_id()) {
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 //
);
}
} // namespace impeller