blob: bab90803e95406fb914364c29a4f6ea3ae7fa7f1 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "src/ui/scenic/lib/gfx/resources/memory.h"
#include <lib/trace/event.h>
#include <zircon/status.h>
#include "src/ui/lib/escher/impl/vulkan_utils.h"
#include "src/ui/lib/escher/util/image_utils.h"
#include "src/ui/scenic/lib/gfx/engine/session.h"
namespace {
// TODO(fxbug.dev/24562): This is a hack until we solve the memory importation bug. On
// x86 platforms, vk::Buffers come out of a separate memory pool. These helper
// functions help make sure that there is a single valid memory pool, for
// both images and buffers, by creating a dummy representative buffer/image.
uint32_t GetBufferMemoryBits(vk::Device device) {
static vk::Device cached_device;
static uint32_t cached_bits;
if (cached_device == device) {
return cached_bits;
}
constexpr vk::DeviceSize kUnimportantBufferSize = 30000;
vk::BufferCreateInfo buffer_create_info;
buffer_create_info.size = kUnimportantBufferSize;
// TODO(fxbug.dev/24563): Buffer creation parameters currently need to be the same
// across all Scenic import flows, as well as in client export objects.
buffer_create_info.usage =
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst |
vk::BufferUsageFlagBits::eStorageTexelBuffer | vk::BufferUsageFlagBits::eStorageBuffer |
vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer;
buffer_create_info.sharingMode = vk::SharingMode::eExclusive;
auto vk_buffer = escher::ESCHER_CHECKED_VK_RESULT(device.createBuffer(buffer_create_info));
vk::MemoryRequirements reqs = device.getBufferMemoryRequirements(vk_buffer);
device.destroyBuffer(vk_buffer);
cached_device = device;
cached_bits = reqs.memoryTypeBits;
return cached_bits;
}
uint32_t GetImageMemoryBits(vk::Device device) {
static vk::Device cached_device;
static uint32_t cached_bits;
if (cached_device == device) {
return cached_bits;
}
constexpr uint32_t kUnimportantImageSize = 1024;
escher::ImageInfo info;
info.format = vk::Format::eB8G8R8A8Unorm;
info.width = kUnimportantImageSize;
info.height = kUnimportantImageSize;
// The image creation parameters need to be the same as those in scenic
// (src/ui/scenic/lib/gfx/resources/gpu_image.cc and
// src/ui/lib/escher/util/image_utils.cc) or else the different vulkan
// devices may interpret the bytes differently.
// TODO(fxbug.dev/24563): Use API to coordinate this with scenic.
info.usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eColorAttachment;
vk::Image image = escher::image_utils::CreateVkImage(device, info, vk::ImageLayout::eUndefined);
vk::MemoryRequirements reqs = device.getImageMemoryRequirements(image);
device.destroyImage(image);
cached_device = device;
cached_bits = reqs.memoryTypeBits;
return cached_bits;
}
// Initialize |alloc_info| and |memory_import_info| with the given parameters.
//
// Returns true if it succeeds setting the |alloc_info| and
// |memory_import_info|, otherwise it returns false and outputs error message
// to |reporter|.
bool InitializeMemoryAllocateInfo(const scenic_impl::gfx::ResourceContext& resource_context,
const zx::vmo* vmo, bool is_host, uint64_t size,
scenic_impl::ErrorReporter* reporter,
vk::MemoryAllocateInfo* alloc_info,
vk::ImportMemoryZirconHandleInfoFUCHSIA* memory_import_info) {
FX_DCHECK(alloc_info);
FX_DCHECK(memory_import_info);
// We first check the rights of vmo to ensure that it has read, write and
// duplicate rights.
zx_info_handle_basic_t vmo_info;
auto get_info_status =
vmo->get_info(ZX_INFO_HANDLE_BASIC, &vmo_info, sizeof(vmo_info), nullptr, nullptr);
if (get_info_status != ZX_OK) {
reporter->ERROR()
<< "scenic_impl::gfx::Memory::ImportGpuMemory(): Cannot get VMO info, status: "
<< zx_status_get_string(get_info_status);
return false;
}
// Currently Magma doesn't support import of read-only VMOs. In order to make
// the behavior of ImportGpuMemory() consistent among different Vulkan ICDs,
// we enforce that the imported vmo should have both read and write rights for
// all device memory.
// Therefore, we require all VMOs to have read and write rights.
if (!is_host && !(vmo_info.rights & ZX_RIGHT_READ)) {
reporter->ERROR() << "scenic_impl::gfx::Memory::ImportGpuMemory(): VMO doesn't have "
"right ZX_RIGHT_READ";
return false;
}
if (!is_host && !(vmo_info.rights & ZX_RIGHT_WRITE)) {
reporter->ERROR() << "scenic_impl::gfx::Memory::ImportGpuMemory(): VMO doesn't have "
"right ZX_RIGHT_WRITE";
return false;
}
auto vk_device = resource_context.vk_device;
// TODO(fxbug.dev/23406): If we're allowed to import the same vmo twice to two
// different resources, we may need to change driver semantics so that you
// can import a VMO twice. Referencing the test bug for now, since it should
// uncover the bug.
vk::MemoryZirconHandlePropertiesFUCHSIA handle_properties;
vk::Result err = vk_device.getMemoryZirconHandlePropertiesFUCHSIA(
#if VK_HEADER_VERSION > 173
vk::ExternalMemoryHandleTypeFlagBits::eZirconVmoFUCHSIA, vmo->get(), &handle_properties,
#else
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA, vmo->get(), &handle_properties,
#endif
resource_context.vk_loader);
if (err != vk::Result::eSuccess) {
reporter->ERROR() << "scenic_impl::gfx::Memory::ImportGpuMemory(): "
"VkGetMemoryFuchsiaHandlePropertiesKHR failed.";
return false;
}
if (handle_properties.memoryTypeBits == 0) {
if (!is_host) {
reporter->ERROR() << "scenic_impl::gfx::Memory::ImportGpuMemory(): "
"VkGetMemoryFuchsiaHandlePropertiesKHR "
"returned zero valid memory types.";
} else {
// Importing read-only host memory into the Vulkan driver should not work,
// but it is not an error to try to do so. Returning a nullptr here should
// not result in a closed session channel, as this flow should only happen
// when Scenic is attempting to optimize image importation. See fxbug.dev/24225
// for other issues this this flow.
FX_LOGS(INFO) << "Host memory VMO could not be imported to any valid Vulkan memory types.";
}
return false;
}
// TODO(fxbug.dev/24225): This function is only used on host memory when we are
// performing a zero-copy import. So it is currently hardcoded to look for a
// valid UMA-style memory pool -- one that can be used as both host and device
// memory.
vk::MemoryPropertyFlags required_flags =
is_host ? vk::MemoryPropertyFlagBits::eDeviceLocal | vk::MemoryPropertyFlagBits::eHostVisible
: vk::MemoryPropertyFlagBits::eDeviceLocal;
auto vk_physical_device = resource_context.vk_physical_device;
uint32_t memory_type_bits = handle_properties.memoryTypeBits;
// TODO(fxbug.dev/24562): This code should be unnecessary once we have a code flow that
// understands how the memory is expected to be used.
#if __x86_64__
memory_type_bits &= GetBufferMemoryBits(vk_device);
memory_type_bits &= GetImageMemoryBits(vk_device);
FX_CHECK(memory_type_bits != 0)
<< "This platform does not have a single memory pool that is valid for "
"both images and buffers. Please fix fxbug.dev/24562.";
#endif // __x86_64__
uint32_t memory_type_index =
escher::impl::GetMemoryTypeIndex(vk_physical_device, memory_type_bits, required_flags);
vk::PhysicalDeviceMemoryProperties memory_types = vk_physical_device.getMemoryProperties();
if (memory_type_index >= memory_types.memoryTypeCount) {
if (!is_host) {
// Because vkGetMemoryZirconHandlePropertiesFUCHSIA may work on normal CPU
// memory on UMA platforms, importation failure is only an error for
// device memory.
reporter->ERROR() << "scenic_impl::gfx::Memory::ImportGpuMemory(): could not find a "
"valid memory type for importation.";
} else {
// TODO(fxbug.dev/24225): Error message is UMA specific.
FX_LOGS(INFO) << "Host memory VMO could not find a UMA-style memory type.";
}
return false;
}
// Import a VkDeviceMemory from the VMO. VkAllocateMemory takes ownership of
// the VMO handle it is passed.
zx::vmo duplicated_vmo;
zx_status_t status = vmo->duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicated_vmo);
if (status != ZX_OK) {
reporter->ERROR() << "scenic_impl::gfx::Memory::ImportGpuMemory(): cannot duplicate VMO, "
"status: "
<< zx_status_get_string(status);
return false;
}
*memory_import_info = vk::ImportMemoryZirconHandleInfoFUCHSIA(
#if VK_HEADER_VERSION > 173
vk::ExternalMemoryHandleTypeFlagBits::eZirconVmoFUCHSIA, duplicated_vmo.release());
#else
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA, duplicated_vmo.release());
#endif
*alloc_info = vk::MemoryAllocateInfo(size, memory_type_index);
alloc_info->setPNext(memory_import_info);
return true;
}
} // namespace
namespace scenic_impl {
namespace gfx {
const ResourceTypeInfo Memory::kTypeInfo = {ResourceType::kMemory, "Memory"};
Memory::Memory(Session* session, ResourceId id, bool is_host, zx::vmo vmo, uint64_t allocation_size)
: Resource(session, session->id(), id, kTypeInfo),
is_host_(is_host),
shared_vmo_(vmo ? fxl::MakeRefCounted<fsl::SharedVmo>(std::move(vmo), ZX_VM_PERM_READ)
: nullptr),
allocation_size_(allocation_size) {}
MemoryPtr Memory::New(Session* session, ResourceId id, ::fuchsia::ui::gfx::MemoryArgs args,
ErrorReporter* error_reporter) {
if (args.allocation_size == 0) {
error_reporter->ERROR() << "Memory::New(): allocation_size argument (" << args.allocation_size
<< ") is not valid.";
return nullptr;
}
uint64_t size;
auto status = args.vmo.get_size(&size);
if (status != ZX_OK) {
error_reporter->ERROR() << "Memory::New(): zx_vmo_get_size failed (err=" << status << ").";
return nullptr;
}
if (args.allocation_size > size) {
error_reporter->ERROR() << "Memory::New(): allocation_size (" << args.allocation_size
<< ") is larger than the size of the corresponding vmo (" << size
<< ").";
return nullptr;
}
auto retval = fxl::AdoptRef(
new Memory(session, id, args.memory_type == fuchsia::images::MemoryType::HOST_MEMORY,
std::move(args.vmo), args.allocation_size));
if (!retval->is_host()) {
if (!retval->GetGpuMem(error_reporter)) {
// Device memory must be able to be imported to the GPU. If not, this
// command is an error and the client should be notified. GetGpuMem() will
// provide a valid error message, but this factory must fail in order to
// signal to the command applier that the channel should be closed.
return nullptr;
}
}
return retval;
}
MemoryPtr Memory::New(Session* session, ResourceId id, vk::MemoryAllocateInfo alloc_info,
ErrorReporter* error_reporter) {
auto retval = fxl::AdoptRef(
new Memory(session, id, /*is_host=*/false, zx::vmo(), alloc_info.allocationSize));
if (!retval->GetGpuMem(error_reporter, &alloc_info)) {
// It is an error if we cannot map GPU memory through this factory function.
return nullptr;
}
return retval;
}
escher::GpuMemPtr Memory::ImportGpuMemory(ErrorReporter* reporter,
vk::MemoryAllocateInfo* alloc_info) {
TRACE_DURATION("gfx", "Memory::ImportGpuMemory");
vk::MemoryAllocateInfo vmo_alloc_info;
vk::ImportMemoryZirconHandleInfoFUCHSIA memory_import_info;
if (!alloc_info) {
const bool retval = InitializeMemoryAllocateInfo(resource_context(), &shared_vmo_->vmo(),
is_host(), allocation_size_, reporter,
&vmo_alloc_info, &memory_import_info);
if (!retval) {
return nullptr;
}
alloc_info = &vmo_alloc_info;
}
auto vk_device = resource_context().vk_device;
vk::DeviceMemory memory = nullptr;
vk::Result err = vk_device.allocateMemory(alloc_info, nullptr, &memory);
if (err != vk::Result::eSuccess) {
reporter->ERROR() << "scenic_impl::gfx::Memory::ImportGpuMemory(): "
"VkAllocateMemory failed.";
return nullptr;
}
// TODO(fxbug.dev/24322): If we can rely on all memory being importable into Vulkan
// (either as host or device memory), then we can always make a GpuMem
// object, and rely on its mapped pointer accessor instead of storing our
// own local uint8_t*.
return escher::GpuMem::AdoptVkMemory(vk_device, vk::DeviceMemory(memory), vk::DeviceSize(size()),
is_host() /* needs_mapped_ptr */);
}
uint32_t Memory::HasSharedMemoryPools(vk::Device device, vk::PhysicalDevice physical_device) {
vk::MemoryPropertyFlags required_flags =
vk::MemoryPropertyFlagBits::eDeviceLocal | vk::MemoryPropertyFlagBits::eHostVisible;
uint32_t memory_type_bits = GetBufferMemoryBits(device) & GetImageMemoryBits(device);
uint32_t memory_type_index =
escher::impl::GetMemoryTypeIndex(physical_device, memory_type_bits, required_flags);
vk::PhysicalDeviceMemoryProperties memory_types = physical_device.getMemoryProperties();
return memory_type_index < memory_types.memoryTypeCount;
}
} // namespace gfx
} // namespace scenic_impl