blob: 2fcbed2f104ca13b018a9e865bfc62dc71faf7b4 [file] [log] [blame]
// Copyright 2020 The Chromium 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 "gpu/vulkan/vulkan_image.h"
#include <vulkan/vulkan.h>
#include <algorithm>
#include "base/macros.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "build/build_config.h"
#include "gpu/vulkan/vulkan_device_queue.h"
#include "gpu/vulkan/vulkan_function_pointers.h"
#if defined(OS_FUCHSIA)
#include "gpu/vulkan/fuchsia/vulkan_fuchsia_ext.h"
#endif
#if defined(OS_ANDROID)
#include "base/android/android_hardware_buffer_compat.h"
#endif
namespace gpu {
namespace {
base::Optional<uint32_t> FindMemoryTypeIndex(
VkPhysicalDevice physical_device,
const VkMemoryRequirements* requirements,
VkMemoryPropertyFlags flags) {
VkPhysicalDeviceMemoryProperties properties;
vkGetPhysicalDeviceMemoryProperties(physical_device, &properties);
constexpr uint32_t kMaxIndex = 31;
for (uint32_t i = 0; i <= kMaxIndex; i++) {
if (((1u << i) & requirements->memoryTypeBits) == 0)
continue;
if ((properties.memoryTypes[i].propertyFlags & flags) != flags)
continue;
return i;
}
NOTREACHED();
return base::nullopt;
}
} // namespace
// static
std::unique_ptr<VulkanImage> VulkanImage::Create(
VulkanDeviceQueue* device_queue,
const gfx::Size& size,
VkFormat format,
VkImageUsageFlags usage,
VkImageCreateFlags flags,
VkImageTiling image_tiling,
void* vk_image_create_info_next,
void* vk_memory_allocation_info_next) {
auto image = std::make_unique<VulkanImage>(util::PassKey<VulkanImage>());
if (!image->Initialize(device_queue, size, format, usage, flags, image_tiling,
vk_image_create_info_next,
vk_memory_allocation_info_next,
nullptr /* requirements */)) {
return nullptr;
}
return image;
}
// static
std::unique_ptr<VulkanImage> VulkanImage::CreateWithExternalMemory(
VulkanDeviceQueue* device_queue,
const gfx::Size& size,
VkFormat format,
VkImageUsageFlags usage,
VkImageCreateFlags flags,
VkImageTiling image_tiling) {
auto image = std::make_unique<VulkanImage>(util::PassKey<VulkanImage>());
if (!image->InitializeWithExternalMemory(device_queue, size, format, usage,
flags, image_tiling)) {
return nullptr;
}
return image;
}
// static
std::unique_ptr<VulkanImage> VulkanImage::CreateFromGpuMemoryBufferHandle(
VulkanDeviceQueue* device_queue,
gfx::GpuMemoryBufferHandle gmb_handle,
const gfx::Size& size,
VkFormat format,
VkImageUsageFlags usage,
VkImageCreateFlags flags,
VkImageTiling image_tiling) {
auto image = std::make_unique<VulkanImage>(util::PassKey<VulkanImage>());
if (!image->InitializeFromGpuMemoryBufferHandle(
device_queue, std::move(gmb_handle), size, format, usage, flags,
image_tiling)) {
return nullptr;
}
return image;
}
// static
std::unique_ptr<VulkanImage> VulkanImage::Create(
VulkanDeviceQueue* device_queue,
VkImage vk_image,
VkDeviceMemory vk_device_memory,
const gfx::Size& size,
VkFormat format,
VkImageTiling image_tiling,
VkDeviceSize device_size,
uint32_t memory_type_index,
base::Optional<VulkanYCbCrInfo>& ycbcr_info) {
auto image = std::make_unique<VulkanImage>(util::PassKey<VulkanImage>());
image->device_queue_ = device_queue;
image->image_ = vk_image;
image->device_memory_ = vk_device_memory;
image->size_ = size;
image->format_ = format;
image->image_tiling_ = image_tiling;
image->device_size_ = device_size;
image->memory_type_index_ = memory_type_index;
image->ycbcr_info_ = ycbcr_info;
return image;
}
VulkanImage::VulkanImage(util::PassKey<VulkanImage> pass_key) {}
VulkanImage::~VulkanImage() {
DCHECK(!device_queue_);
DCHECK(image_ == VK_NULL_HANDLE);
DCHECK(device_memory_ == VK_NULL_HANDLE);
}
void VulkanImage::Destroy() {
if (!device_queue_)
return;
VkDevice vk_device = device_queue_->GetVulkanDevice();
if (image_ != VK_NULL_HANDLE) {
vkDestroyImage(vk_device, image_, nullptr /* pAllocator */);
image_ = VK_NULL_HANDLE;
}
if (device_memory_ != VK_NULL_HANDLE) {
vkFreeMemory(vk_device, device_memory_, nullptr /* pAllocator */);
device_memory_ = VK_NULL_HANDLE;
}
device_queue_ = nullptr;
}
#if defined(OS_POSIX)
base::ScopedFD VulkanImage::GetMemoryFd(
VkExternalMemoryHandleTypeFlagBits handle_type) {
VkMemoryGetFdInfoKHR get_fd_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
.memory = device_memory_,
.handleType = handle_type,
};
VkDevice device = device_queue_->GetVulkanDevice();
int memory_fd = -1;
vkGetMemoryFdKHR(device, &get_fd_info, &memory_fd);
if (memory_fd < 0) {
DLOG(ERROR) << "Unable to extract file descriptor out of external VkImage";
return base::ScopedFD();
}
return base::ScopedFD(memory_fd);
}
#endif
#if defined(OS_FUCHSIA)
zx::vmo VulkanImage::GetMemoryZirconHandle() {
DCHECK(handle_types_ &
VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA);
VkMemoryGetZirconHandleInfoFUCHSIA get_handle_info = {
.sType = VK_STRUCTURE_TYPE_TEMP_MEMORY_GET_ZIRCON_HANDLE_INFO_FUCHSIA,
.memory = device_memory_,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA,
};
VkDevice device = device_queue_->GetVulkanDevice();
zx::vmo vmo;
VkResult result = vkGetMemoryZirconHandleFUCHSIA(device, &get_handle_info,
vmo.reset_and_get_address());
if (result != VK_SUCCESS) {
DLOG(ERROR) << "vkGetMemoryFuchsiaHandleKHR failed: " << result;
vmo.reset();
}
return vmo;
}
#endif
bool VulkanImage::Initialize(VulkanDeviceQueue* device_queue,
const gfx::Size& size,
VkFormat format,
VkImageUsageFlags usage,
VkImageCreateFlags flags,
VkImageTiling image_tiling,
void* vk_image_create_info_next,
void* vk_memory_allocation_info_next,
const VkMemoryRequirements* requirements) {
DCHECK(!device_queue_);
DCHECK(image_ == VK_NULL_HANDLE);
DCHECK(device_memory_ == VK_NULL_HANDLE);
device_queue_ = device_queue;
size_ = size;
format_ = format;
image_tiling_ = image_tiling;
VkImageCreateInfo create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = vk_image_create_info_next,
.flags = flags,
.imageType = VK_IMAGE_TYPE_2D,
.format = format_,
.extent = {size.width(), size.height(), 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = image_tiling_,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkDevice vk_device = device_queue->GetVulkanDevice();
VkResult result =
vkCreateImage(vk_device, &create_info, nullptr /* pAllocator */, &image_);
if (result != VK_SUCCESS) {
DLOG(ERROR) << "vkCreateImage failed result:" << result;
device_queue_ = VK_NULL_HANDLE;
return false;
}
VkMemoryRequirements tmp_requirements;
if (!requirements) {
vkGetImageMemoryRequirements(vk_device, image_, &tmp_requirements);
if (!tmp_requirements.memoryTypeBits) {
DLOG(ERROR) << "vkGetImageMemoryRequirements failed";
Destroy();
return false;
}
requirements = &tmp_requirements;
}
device_size_ = requirements->size;
// Some vulkan implementations require dedicated memory for sharing memory
// object between vulkan instances.
VkMemoryDedicatedAllocateInfoKHR dedicated_memory_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR,
.pNext = vk_memory_allocation_info_next,
.image = image_,
};
auto index =
FindMemoryTypeIndex(device_queue->GetVulkanPhysicalDevice(), requirements,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (!index) {
DLOG(ERROR) << "Cannot find validate memory type index.";
Destroy();
return false;
}
memory_type_index_ = index.value();
VkMemoryAllocateInfo memory_allocate_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &dedicated_memory_info,
.allocationSize = device_size_,
.memoryTypeIndex = memory_type_index_,
};
result = vkAllocateMemory(vk_device, &memory_allocate_info,
nullptr /* pAllocator */, &device_memory_);
if (result != VK_SUCCESS) {
DLOG(ERROR) << "vkAllocateMemory failed result:" << result;
Destroy();
return false;
}
result = vkBindImageMemory(vk_device, image_, device_memory_,
0 /* memoryOffset */);
if (result != VK_SUCCESS) {
DLOG(ERROR) << "Failed to bind memory to external VkImage: " << result;
Destroy();
return false;
}
return true;
}
bool VulkanImage::InitializeWithExternalMemory(VulkanDeviceQueue* device_queue,
const gfx::Size& size,
VkFormat format,
VkImageUsageFlags usage,
VkImageCreateFlags flags,
VkImageTiling image_tiling) {
#if defined(OS_FUCHSIA)
constexpr auto kHandleType =
VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA;
#else
constexpr auto kHandleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
#endif
VkPhysicalDeviceImageFormatInfo2 format_info_2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
.format = format,
.type = VK_IMAGE_TYPE_2D,
.tiling = image_tiling,
.usage = usage,
.flags = flags,
};
VkPhysicalDeviceExternalImageFormatInfo external_info = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
.handleType = kHandleType,
};
format_info_2.pNext = &external_info;
VkImageFormatProperties2 image_format_properties_2 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
};
VkExternalImageFormatProperties external_image_format_properties = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES,
};
image_format_properties_2.pNext = &external_image_format_properties;
auto result = vkGetPhysicalDeviceImageFormatProperties2(
device_queue->GetVulkanPhysicalDevice(), &format_info_2,
&image_format_properties_2);
if (result != VK_SUCCESS) {
DLOG(ERROR) << "External memory is not supported."
<< " format:" << format << " image_tiling:" << image_tiling
<< " usage:" << usage << " flags:" << flags;
return false;
}
const auto& external_format_properties =
external_image_format_properties.externalMemoryProperties;
if (!(external_format_properties.externalMemoryFeatures &
VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT)) {
DLOG(ERROR) << "External memroy cannot be exported."
<< " format:" << format << " image_tiling:" << image_tiling
<< " usage:" << usage << " flags:" << flags;
return false;
}
handle_types_ = external_format_properties.compatibleHandleTypes;
DCHECK(handle_types_ & kHandleType);
VkExternalMemoryImageCreateInfoKHR external_image_create_info = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_KHR,
.handleTypes = handle_types_,
};
VkExportMemoryAllocateInfoKHR external_memory_allocate_info = {
.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR,
.handleTypes = handle_types_,
};
return Initialize(device_queue, size, format, usage, flags, image_tiling,
&external_image_create_info, &external_memory_allocate_info,
nullptr /* requirements */);
}
bool VulkanImage::InitializeFromGpuMemoryBufferHandle(
VulkanDeviceQueue* device_queue,
gfx::GpuMemoryBufferHandle gmb_handle,
const gfx::Size& size,
VkFormat format,
VkImageUsageFlags usage,
VkImageCreateFlags flags,
VkImageTiling image_tiling) {
#if !defined(OS_ANDROID)
NOTIMPLEMENTED();
return false;
#else
// TODO(penghuang): Move this method to vulkan_image_android.cc when more
// platforms are supported.
if (gmb_handle.type != gfx::GpuMemoryBufferType::ANDROID_HARDWARE_BUFFER) {
DLOG(ERROR) << "gmb_handle.type is not supported. type:" << gmb_handle.type;
return false;
}
DCHECK(gmb_handle.android_hardware_buffer.is_valid());
auto& ahb_handle = gmb_handle.android_hardware_buffer;
// To obtain format properties of an Android hardware buffer, include an
// instance of VkAndroidHardwareBufferFormatPropertiesANDROID in the pNext
// chain of the VkAndroidHardwareBufferPropertiesANDROID instance passed to
// vkGetAndroidHardwareBufferPropertiesANDROID.
VkAndroidHardwareBufferFormatPropertiesANDROID ahb_format_props = {
VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
};
VkAndroidHardwareBufferPropertiesANDROID ahb_props = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
.pNext = &ahb_format_props,
};
VkDevice vk_device = device_queue->GetVulkanDevice();
VkResult result = vkGetAndroidHardwareBufferPropertiesANDROID(
vk_device, ahb_handle.get(), &ahb_props);
if (result != VK_SUCCESS) {
LOG(ERROR)
<< "GetAhbProps: vkGetAndroidHardwareBufferPropertiesANDROID failed : "
<< result;
return false;
}
// To create an image with an external format, include an instance of
// VkExternalFormatANDROID in the pNext chain of VkImageCreateInfo.
VkExternalFormatANDROID external_format = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
// If externalFormat is zero, the effect is as if the
// VkExternalFormatANDROID structure was not present. Otherwise, the image
// will have the specified external format.
.externalFormat = 0,
};
// If image has an external format, format must be VK_FORMAT_UNDEFINED.
if (ahb_format_props.format == VK_FORMAT_UNDEFINED) {
// externalFormat must be 0 or a value returned in the externalFormat member
// of VkAndroidHardwareBufferFormatPropertiesANDROID by an earlier call to
// vkGetAndroidHardwareBufferPropertiesANDROID.
external_format.externalFormat = ahb_format_props.externalFormat;
}
// To define a set of external memory handle types that may be used as backing
// store for an image, add a VkExternalMemoryImageCreateInfo structure to the
// pNext chain of the VkImageCreateInfo structure.
VkExternalMemoryImageCreateInfo external_memory_image_info = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.pNext = &external_format,
.handleTypes =
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
// Get the AHB description.
AHardwareBuffer_Desc ahb_desc = {};
base::AndroidHardwareBufferCompat::GetInstance().Describe(ahb_handle.get(),
&ahb_desc);
// Intended usage of the image.
VkImageUsageFlags usage_flags = 0;
// Get Vulkan Image usage flag equivalence of AHB usage.
if (ahb_desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
usage_flags = usage_flags | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
}
if (ahb_desc.usage & AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT) {
usage_flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
}
// TODO(vikassoni) : AHARDWAREBUFFER_USAGE_GPU_CUBE_MAP is supported from API
// level 28 which is not part of current android_ndk version in chromium. Add
// equivalent VK usage later.
if (!usage_flags) {
LOG(ERROR) << "No valid usage flags found";
return false;
}
VkImageCreateFlags create_flags = 0;
if (ahb_desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
create_flags = VK_IMAGE_CREATE_PROTECTED_BIT;
}
// To import memory created outside of the current Vulkan instance from an
// Android hardware buffer, add a VkImportAndroidHardwareBufferInfoANDROID
// structure to the pNext chain of the VkMemoryAllocateInfo structure.
VkImportAndroidHardwareBufferInfoANDROID ahb_import_info = {
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
.buffer = ahb_handle.get(),
};
VkMemoryRequirements requirements = {
.size = ahb_props.allocationSize,
.memoryTypeBits = ahb_props.memoryTypeBits,
};
if (!Initialize(device_queue, size, ahb_format_props.format, usage_flags,
create_flags, VK_IMAGE_TILING_OPTIMAL,
&external_memory_image_info, &ahb_import_info,
&requirements)) {
return false;
}
if (ahb_format_props.format == VK_FORMAT_UNDEFINED) {
ycbcr_info_.emplace(VK_FORMAT_UNDEFINED, ahb_format_props.externalFormat,
ahb_format_props.suggestedYcbcrModel,
ahb_format_props.suggestedYcbcrRange,
ahb_format_props.suggestedXChromaOffset,
ahb_format_props.suggestedYChromaOffset,
ahb_format_props.formatFeatures);
}
return true;
#endif // defined(OS_ANDROID)
}
} // namespace gpu