blob: 928807b2a2023eb6ec53b99d31b55c58dc8cd8fd [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 "garnet/lib/ui/gfx/resources/memory.h"
#include <trace/event.h>
#include "garnet/lib/ui/gfx/engine/session.h"
#include "src/ui/lib/escher/impl/vulkan_utils.h"
#include "src/ui/lib/escher/util/image_utils.h"
namespace {
// TODO(SCN-1368): 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(SCN-1369): 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
// (garnet/lib/ui/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(SCN-1369): 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::MemoryRequirements reqs = device.getImageMemoryRequirements(image);
device.destroyImage(image);
cached_device = device;
cached_bits = reqs.memoryTypeBits;
return cached_bits;
}
} // namespace
namespace scenic_impl {
namespace gfx {
const ResourceTypeInfo Memory::kTypeInfo = {ResourceType::kMemory, "Memory"};
Memory::Memory(Session* session, ResourceId id,
::fuchsia::ui::gfx::MemoryArgs args)
: Resource(session, id, kTypeInfo),
is_host_(args.memory_type == fuchsia::images::MemoryType::HOST_MEMORY),
shared_vmo_(fxl::MakeRefCounted<fsl::SharedVmo>(std::move(args.vmo),
ZX_VM_PERM_READ)),
allocation_size_(args.allocation_size) {
FXL_DCHECK(args.allocation_size > 0);
}
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, std::move(args)));
if (!retval->is_host()) {
if (!retval->GetGpuMem()) {
// 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;
}
escher::GpuMemPtr Memory::ImportGpuMemory() {
TRACE_DURATION("gfx", "Memory::ImportGpuMemory");
auto vk_device = session()->resource_context().vk_device;
// TODO(SCN-151): 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(
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA,
shared_vmo_->vmo().get(), &handle_properties,
session()->resource_context().vk_loader);
if (err != vk::Result::eSuccess) {
error_reporter()->ERROR()
<< "scenic_impl::gfx::Memory::ImportGpuMemory(): "
"VkGetMemoryFuchsiaHandlePropertiesKHR failed.";
return nullptr;
}
if (handle_properties.memoryTypeBits == 0) {
if (!is_host_) {
error_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 SCN-1012
// for other issues this this flow.
FXL_LOG(INFO) << "Host memory VMO could not be imported to any valid "
"Vulkan memory types.";
}
return nullptr;
}
// TODO(SCN-1012): 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 = session()->resource_context().vk_physical_device;
uint32_t memory_type_bits = handle_properties.memoryTypeBits;
// TODO(SCN-1368): 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);
FXL_CHECK(memory_type_bits != 0)
<< "This platform does not have a single memory pool that is valid for "
"both images and buffers. Please fix SCN-1368.";
#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.
error_reporter()->ERROR()
<< "scenic_impl::gfx::Memory::ImportGpuMemory(): could not find a "
"valid memory type for importation.";
} else {
// TODO(SCN-1012): Error message is UMA specific.
FXL_LOG(INFO)
<< "Host memory VMO could not find a UMA-style memory type.";
}
return nullptr;
}
// Import a VkDeviceMemory from the VMO. VkAllocateMemory takes ownership of
// the VMO handle it is passed.
vk::ImportMemoryZirconHandleInfoFUCHSIA memory_import_info(
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA,
DuplicateVmo().release());
vk::MemoryAllocateInfo memory_allocate_info(size(), memory_type_index);
memory_allocate_info.setPNext(&memory_import_info);
vk::DeviceMemory memory = nullptr;
err = vk_device.allocateMemory(&memory_allocate_info, nullptr, &memory);
if (err != vk::Result::eSuccess) {
error_reporter()->ERROR() << "scenic_impl::gfx::Memory::ImportGpuMemory(): "
"VkAllocateMemory failed.";
return nullptr;
}
// TODO(SCN-1115): 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 */);
}
zx::vmo Memory::DuplicateVmo() {
zx::vmo the_clone;
zx_status_t status =
shared_vmo_->vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &the_clone);
ZX_ASSERT_MSG(status == ZX_OK, "duplicate failed: status=%d", status);
return the_clone;
}
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