blob: f0bd4705555228d3347c4acacf9a6e218edd9783 [file] [log] [blame]
// Copyright 2021 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 "magma_image.h"
#include <fuchsia/sysmem/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/image-format-llcpp/image-format-llcpp.h>
#include <lib/service/llcpp/service.h>
#include <lib/syslog/global.h>
#include <lib/zx/channel.h>
#include <cassert>
#include <src/lib/fsl/handles/object_info.h>
#include "drm_fourcc.h"
#include <vulkan/vulkan.hpp>
#define LOG_VERBOSE(msg, ...) \
if (false) \
FX_LOGF(INFO, "magma_image", msg, ##__VA_ARGS__)
namespace {
static uint32_t to_uint32(uint64_t value) {
assert(value <= std::numeric_limits<uint32_t>::max());
return static_cast<uint32_t>(value);
}
static uint64_t SysmemModifierToDrmModifier(uint64_t modifier) {
switch (modifier) {
case fuchsia_sysmem::wire::FORMAT_MODIFIER_LINEAR:
return DRM_FORMAT_MOD_LINEAR;
case fuchsia_sysmem::wire::FORMAT_MODIFIER_INTEL_I915_X_TILED:
return I915_FORMAT_MOD_X_TILED;
case fuchsia_sysmem::wire::FORMAT_MODIFIER_INTEL_I915_Y_TILED:
return I915_FORMAT_MOD_Y_TILED;
case fuchsia_sysmem::wire::FORMAT_MODIFIER_INTEL_I915_YF_TILED:
return I915_FORMAT_MOD_Yf_TILED;
}
return DRM_FORMAT_MOD_INVALID;
}
// Use async fidl to receive epitaph on buffer collection.
class AsyncHandler : public fidl::WireAsyncEventHandler<fuchsia_sysmem::BufferCollection> {
public:
AsyncHandler() : loop_(&kAsyncLoopConfigNeverAttachToThread) {}
void Unbound(::fidl::UnbindInfo info) override {
unbind_info_ = info;
loop_.Quit();
}
async::Loop& loop() { return loop_; }
auto& unbind_info() { return unbind_info_; }
private:
async::Loop loop_;
std::optional<fidl::UnbindInfo> unbind_info_;
};
class VulkanImageCreator {
public:
vk::Result InitVulkan(uint32_t physical_device_index);
zx_status_t InitSysmem();
vk::PhysicalDeviceLimits GetPhysicalDeviceLimits();
// Creates the buffer collection and sets constraints.
vk::Result CreateCollection(vk::ImageCreateInfo* image_create_info,
fuchsia_sysmem::wire::PixelFormatType format,
std::vector<uint64_t>& modifiers);
zx_status_t GetImageInfo(uint32_t width, uint32_t height, zx::vmo* vmo_out,
magma_image_info_t* image_info_out);
private:
vk::DispatchLoaderDynamic loader_;
vk::UniqueInstance instance_;
vk::PhysicalDevice physical_device_;
vk::UniqueDevice device_;
fidl::WireSyncClient<fuchsia_sysmem::Allocator> sysmem_allocator_;
fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> local_token_;
fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> vulkan_token_;
std::shared_ptr<AsyncHandler> async_handler_;
fidl::Client<fuchsia_sysmem::BufferCollection> collection_;
};
vk::Result VulkanImageCreator::InitVulkan(uint32_t physical_device_index) {
{
auto app_info =
vk::ApplicationInfo().setPApplicationName("magma_image").setApiVersion(VK_API_VERSION_1_1);
auto instance_info = vk::InstanceCreateInfo().setPApplicationInfo(&app_info);
auto result = vk::createInstanceUnique(instance_info);
if (result.result != vk::Result::eSuccess) {
LOG_VERBOSE("Failed to create instance: %s", vk::to_string(result.result).data());
return result.result;
}
instance_ = std::move(result.value);
}
assert(instance_);
{
auto [result, physical_devices] = instance_->enumeratePhysicalDevices();
if (result != vk::Result::eSuccess) {
LOG_VERBOSE("Failed to enumerate physical devices: %s", vk::to_string(result).data());
return result;
}
if (physical_device_index >= physical_devices.size()) {
LOG_VERBOSE("Invalid physical device index: %d (%zd)", physical_device_index,
physical_devices.size());
return vk::Result::eErrorInitializationFailed;
}
physical_device_ = physical_devices[physical_device_index];
}
assert(physical_device_);
{
const vk::QueueFlags& queue_flags = vk::QueueFlagBits::eGraphics;
const auto queue_families = physical_device_.getQueueFamilyProperties();
size_t queue_family_index = queue_families.size();
for (size_t i = 0; i < queue_families.size(); ++i) {
if (queue_families[i].queueFlags & queue_flags) {
queue_family_index = i;
break;
}
}
if (queue_family_index == queue_families.size()) {
LOG_VERBOSE("Failed to find queue family with flags 0x%x",
static_cast<uint32_t>(queue_flags));
return vk::Result::eErrorInitializationFailed;
}
std::array<const char*, 1> device_extensions{VK_FUCHSIA_BUFFER_COLLECTION_EXTENSION_NAME};
const float queue_priority = 0.0f;
auto queue_create_info = vk::DeviceQueueCreateInfo()
.setQueueFamilyIndex(to_uint32(queue_family_index))
.setQueueCount(1)
.setPQueuePriorities(&queue_priority);
auto device_create_info = vk::DeviceCreateInfo()
.setQueueCreateInfoCount(1)
.setPQueueCreateInfos(&queue_create_info)
.setEnabledExtensionCount(device_extensions.size())
.setPpEnabledExtensionNames(device_extensions.data());
auto result = physical_device_.createDeviceUnique(device_create_info, nullptr);
if (result.result != vk::Result::eSuccess) {
LOG_VERBOSE("Failed to create device: %s", vk::to_string(result.result).data());
return result.result;
}
device_ = std::move(result.value);
}
assert(device_);
loader_.init(instance_.get(), device_.get());
return vk::Result::eSuccess;
}
zx_status_t VulkanImageCreator::InitSysmem() {
{
auto client_end_directory = service::OpenServiceRoot();
if (!client_end_directory.is_ok()) {
LOG_VERBOSE("Failed to open service root: %d", client_end_directory.status_value());
return client_end_directory.status_value();
}
auto client_end = service::ConnectAt<fuchsia_sysmem::Allocator>(*client_end_directory);
if (!client_end.is_ok()) {
LOG_VERBOSE("Failed to connect to sysmem allocator: %d", client_end.status_value());
return client_end.status_value();
}
sysmem_allocator_ = fidl::WireSyncClient<fuchsia_sysmem::Allocator>(std::move(*client_end));
}
sysmem_allocator_.SetDebugClientInfo(fidl::StringView::FromExternal(fsl::GetCurrentProcessName()),
fsl::GetCurrentProcessKoid());
{
auto endpoints = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>();
if (!endpoints.is_ok()) {
LOG_VERBOSE("Failed to create endpoints: %d", endpoints.status_value());
return endpoints.status_value();
}
auto result = sysmem_allocator_.AllocateSharedCollection(std::move(endpoints->server));
if (!result.ok()) {
LOG_VERBOSE("Failed to allocate shared collection: %d", result.status());
return result.status();
}
local_token_ =
fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken>(std::move(endpoints->client));
}
{
auto endpoints = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>();
if (!endpoints.is_ok()) {
LOG_VERBOSE("Failed to create endpoints: %d", endpoints.status_value());
return endpoints.status_value();
}
constexpr uint32_t kNoRightsAttentuation = ~0;
auto result = local_token_.Duplicate(kNoRightsAttentuation, std::move(endpoints->server));
if (!result.ok()) {
LOG_VERBOSE("Failed to duplicate token: %d", result.status());
return result.status();
}
vulkan_token_ =
fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken>(std::move(endpoints->client));
}
{
auto result = vulkan_token_.Sync();
if (!result.ok()) {
LOG_VERBOSE("Failed to sync token: %d", result.status());
return result.status();
}
}
return ZX_OK;
}
vk::PhysicalDeviceLimits VulkanImageCreator::GetPhysicalDeviceLimits() {
assert(physical_device_);
vk::PhysicalDeviceProperties props = physical_device_.getProperties();
return props.limits;
}
vk::Result VulkanImageCreator::CreateCollection(vk::ImageCreateInfo* image_create_info,
fuchsia_sysmem::wire::PixelFormatType format,
std::vector<uint64_t>& modifiers) {
assert(device_);
vk::UniqueHandle<vk::BufferCollectionFUCHSIA, vk::DispatchLoaderDynamic> vk_collection;
{
auto collection_create_info = vk::BufferCollectionCreateInfoFUCHSIA().setCollectionToken(
vulkan_token_.mutable_channel()->release());
auto result = device_->createBufferCollectionFUCHSIAUnique(collection_create_info,
nullptr /*pAllocator*/, loader_);
if (result.result != vk::Result::eSuccess) {
LOG_VERBOSE("Failed to create buffer collection: %d", result.result);
return result.result;
}
vk_collection = std::move(result.value);
}
assert(vk_collection);
{
auto image_constraints_info = vk::ImageConstraintsInfoFUCHSIA()
.setCreateInfoCount(1)
.setPCreateInfos(image_create_info)
.setMinBufferCount(1)
.setMaxBufferCount(1);
auto result = device_->setBufferCollectionImageConstraintsFUCHSIA(
vk_collection.get(), image_constraints_info, loader_);
if (result != vk::Result::eSuccess) {
LOG_VERBOSE("Failed to set constraints: %s", vk::to_string(result).data());
return result;
}
}
async_handler_ = std::make_shared<AsyncHandler>();
{
auto endpoints = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollection>();
if (!endpoints.is_ok()) {
LOG_VERBOSE("Failed to create endpoints: %d", endpoints.status_value());
return vk::Result::eErrorInitializationFailed;
}
auto result = sysmem_allocator_.BindSharedCollection(std::move(local_token_.client_end()),
std::move(endpoints->server));
if (!result.ok()) {
LOG_VERBOSE("Failed to bind shared collection: %d", result.status());
return vk::Result::eErrorInitializationFailed;
}
collection_.Bind(std::move(endpoints->client), async_handler_->loop().dispatcher(),
async_handler_);
}
{
fuchsia_sysmem::wire::BufferCollectionConstraints constraints{
.usage.cpu =
fuchsia_sysmem::wire::cpuUsageReadOften | fuchsia_sysmem::wire::cpuUsageWriteOften,
.min_buffer_count = 1,
.has_buffer_memory_constraints = true,
.buffer_memory_constraints.cpu_domain_supported = true,
.image_format_constraints_count = to_uint32(modifiers.size()),
};
for (uint32_t index = 0; index < modifiers.size(); index++) {
fuchsia_sysmem::wire::ImageFormatConstraints& image_constraints =
constraints.image_format_constraints[index];
image_constraints = fuchsia_sysmem::wire::ImageFormatConstraints();
image_constraints.min_coded_width = image_create_info->extent.width;
image_constraints.min_coded_height = image_create_info->extent.height;
image_constraints.max_coded_width = image_create_info->extent.width;
image_constraints.max_coded_height = image_create_info->extent.height;
image_constraints.min_bytes_per_row = 0; // Rely on Vulkan to specify
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0].type = fuchsia_sysmem::wire::ColorSpaceType::SRGB;
image_constraints.pixel_format.type = format;
image_constraints.pixel_format.has_format_modifier = true;
image_constraints.pixel_format.format_modifier.value = modifiers[index];
}
auto result = collection_->SetConstraints(true, constraints);
if (!result.ok()) {
LOG_VERBOSE("Failed to set constraints: %d", result.status());
return vk::Result::eErrorInitializationFailed;
}
}
return vk::Result::eSuccess;
}
zx_status_t VulkanImageCreator::GetImageInfo(uint32_t width, uint32_t height, zx::vmo* vmo_out,
magma_image_info_t* image_info_out) {
auto result = collection_->WaitForBuffersAllocated_Sync();
// Process any epitaphs to detect any allocation errors
async_handler_->loop().RunUntilIdle();
auto unbind_info = async_handler_->unbind_info();
if (unbind_info && unbind_info->status != ZX_OK) {
LOG_VERBOSE("Unbind: reason %d status %d", unbind_info->reason, unbind_info->status);
return unbind_info->status;
}
// Run the loop to ensure local unbind completes and async_handler_ is freed
collection_.Unbind();
async_handler_->loop().RunUntilIdle();
if (!result.ok()) {
LOG_VERBOSE("WaitForBuffersAllocated failed: %d", result.status());
return result.status();
}
auto response = result.Unwrap();
if (response->status != ZX_OK) {
LOG_VERBOSE("Buffer allocation failed: %d", response->status);
return response->status;
}
fuchsia_sysmem::wire::BufferCollectionInfo_2& collection_info = response->buffer_collection_info;
if (collection_info.buffer_count != 1) {
LOG_VERBOSE("Incorrect buffer collection count: %d", collection_info.buffer_count);
return ZX_ERR_INTERNAL;
}
if (!collection_info.buffers[0].vmo.is_valid()) {
LOG_VERBOSE("Invalid vmo");
return ZX_ERR_INTERNAL;
}
if (collection_info.buffers[0].vmo_usable_start != 0) {
LOG_VERBOSE("Unsupported vmo usable start: %lu", collection_info.buffers[0].vmo_usable_start);
return ZX_ERR_INTERNAL;
}
std::optional<fuchsia_sysmem::wire::ImageFormat_2> image_format =
image_format::ConstraintsToFormat(collection_info.settings.image_format_constraints, width,
height);
if (!image_format) {
LOG_VERBOSE("Failed to get image format");
return ZX_ERR_INTERNAL;
}
for (uint32_t plane = 0; plane < MAGMA_MAX_IMAGE_PLANES; ++plane) {
uint64_t offset;
if (!ImageFormatPlaneByteOffset(*image_format, plane, &offset)) {
image_info_out->plane_offsets[plane] = 0;
} else {
image_info_out->plane_offsets[plane] = to_uint32(offset);
}
uint32_t row_bytes;
if (!image_format::GetPlaneRowBytes(*image_format, plane, &row_bytes)) {
image_info_out->plane_strides[plane] = 0;
} else {
image_info_out->plane_strides[plane] = row_bytes;
}
}
if (image_format->pixel_format.has_format_modifier) {
image_info_out->drm_format_modifier =
SysmemModifierToDrmModifier(image_format->pixel_format.format_modifier.value);
} else {
image_info_out->drm_format_modifier = DRM_FORMAT_MOD_LINEAR;
}
*vmo_out = std::move(collection_info.buffers[0].vmo);
return ZX_OK;
}
static magma_status_t MagmaStatus(vk::Result result) {
switch (result) {
case vk::Result::eSuccess:
return MAGMA_STATUS_OK;
case vk::Result::eTimeout:
return MAGMA_STATUS_TIMED_OUT;
case vk::Result::eErrorDeviceLost:
return MAGMA_STATUS_CONNECTION_LOST;
case vk::Result::eErrorOutOfHostMemory:
case vk::Result::eErrorOutOfDeviceMemory:
case vk::Result::eErrorMemoryMapFailed:
return MAGMA_STATUS_MEMORY_ERROR;
case vk::Result::eErrorFormatNotSupported:
return MAGMA_STATUS_INVALID_ARGS;
default:
return MAGMA_STATUS_INTERNAL_ERROR;
}
}
static vk::Format DrmFormatToVulkanFormat(uint32_t drm_format) {
switch (drm_format) {
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
return vk::Format::eB8G8R8A8Unorm;
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
return vk::Format::eR8G8B8A8Unorm;
}
LOG_VERBOSE("Unhandle DRM format: 0x%x", drm_format);
return vk::Format::eUndefined;
}
static fuchsia_sysmem::wire::PixelFormatType DrmFormatToSysmemFormat(uint32_t drm_format) {
switch (drm_format) {
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
return fuchsia_sysmem::wire::PixelFormatType::BGRA32;
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
return fuchsia_sysmem::wire::PixelFormatType::R8G8B8A8;
}
LOG_VERBOSE("Unhandle DRM format: 0x%x", drm_format);
return fuchsia_sysmem::wire::PixelFormatType::INVALID;
}
static uint64_t DrmModifierToSysmemModifier(uint64_t modifier) {
switch (modifier) {
case DRM_FORMAT_MOD_LINEAR:
return fuchsia_sysmem::wire::FORMAT_MODIFIER_LINEAR;
case I915_FORMAT_MOD_X_TILED:
return fuchsia_sysmem::wire::FORMAT_MODIFIER_INTEL_I915_X_TILED;
case I915_FORMAT_MOD_Y_TILED:
return fuchsia_sysmem::wire::FORMAT_MODIFIER_INTEL_I915_Y_TILED;
case I915_FORMAT_MOD_Yf_TILED:
return fuchsia_sysmem::wire::FORMAT_MODIFIER_INTEL_I915_YF_TILED;
}
LOG_VERBOSE("Unhandle DRM modifier: 0x%x", modifier);
return fuchsia_sysmem::wire::FORMAT_MODIFIER_INVALID;
}
} // namespace
namespace magma_image {
magma_status_t CreateDrmImage(uint32_t physical_device_index,
const magma_image_create_info_t* create_info,
magma_image_info_t* image_info_out, zx::vmo* vmo_out) {
assert(create_info);
assert(image_info_out);
assert(vmo_out);
if (create_info->flags & ~MAGMA_IMAGE_CREATE_FLAGS_PRESENTABLE) {
LOG_VERBOSE("Invalid flags: 0x%lx", create_info->flags);
return MAGMA_STATUS_INVALID_ARGS;
}
vk::Format vk_format = DrmFormatToVulkanFormat(create_info->drm_format);
if (vk_format == vk::Format::eUndefined) {
LOG_VERBOSE("Invalid format: 0x%lx", create_info->drm_format);
return MAGMA_STATUS_INVALID_ARGS;
}
auto sysmem_format = DrmFormatToSysmemFormat(create_info->drm_format);
if (sysmem_format == fuchsia_sysmem::wire::PixelFormatType::INVALID) {
LOG_VERBOSE("Invalid format: 0x%lx", create_info->drm_format);
return MAGMA_STATUS_INVALID_ARGS;
}
std::vector<uint64_t> sysmem_modifiers;
// Convert modifiers provided by client.
{
bool terminator_found = false;
for (uint32_t i = 0; i < MAGMA_MAX_DRM_FORMAT_MODIFIERS; i++) {
if (create_info->drm_format_modifiers[i] == DRM_FORMAT_MOD_INVALID) {
terminator_found = true;
break;
}
uint64_t modifier = DrmModifierToSysmemModifier(create_info->drm_format_modifiers[i]);
if (modifier == fuchsia_sysmem::wire::FORMAT_MODIFIER_INVALID) {
LOG_VERBOSE("Invalid modifier: 0x%lx", create_info->drm_format_modifiers[i]);
return MAGMA_STATUS_INVALID_ARGS;
}
sysmem_modifiers.push_back(modifier);
}
if (!terminator_found) {
LOG_VERBOSE("Missing modifier terminator");
return MAGMA_STATUS_INVALID_ARGS;
}
}
VulkanImageCreator image_creator;
{
vk::Result result = image_creator.InitVulkan(physical_device_index);
if (result != vk::Result::eSuccess) {
LOG_VERBOSE("Failed to initialize Vulkan");
return MagmaStatus(result);
}
}
{
vk::PhysicalDeviceLimits limits = image_creator.GetPhysicalDeviceLimits();
if (create_info->width > limits.maxImageDimension2D ||
create_info->height > limits.maxImageDimension2D) {
LOG_VERBOSE("Invalid width %u or height %u (%u)", create_info->width, create_info->height,
limits.maxImageDimension2D);
return MAGMA_STATUS_INVALID_ARGS;
}
}
zx_status_t status = image_creator.InitSysmem();
if (status != ZX_OK) {
LOG_VERBOSE("Failed to initialize sysmem: %d", status);
return MAGMA_STATUS_INTERNAL_ERROR;
}
vk::ImageUsageFlags usage =
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eStorage |
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment;
auto image_create_info = vk::ImageCreateInfo()
.setFormat(vk_format)
.setImageType(vk::ImageType::e2D)
.setMipLevels(1)
.setArrayLayers(1)
.setExtent({create_info->width, create_info->height, 1})
.setTiling(vk::ImageTiling::eOptimal)
.setSharingMode(vk::SharingMode::eExclusive)
.setUsage(usage);
vk::Result result =
image_creator.CreateCollection(&image_create_info, sysmem_format, sysmem_modifiers);
if (result != vk::Result::eSuccess) {
LOG_VERBOSE("Failed to create collection: %s", vk::to_string(result).data());
return MagmaStatus(result);
}
status =
image_creator.GetImageInfo(create_info->width, create_info->height, vmo_out, image_info_out);
if (status != ZX_OK) {
LOG_VERBOSE("GetImageInfo failed: %d", status);
if (status == ZX_ERR_NOT_SUPPORTED)
return MAGMA_STATUS_INVALID_ARGS;
return MAGMA_STATUS_INTERNAL_ERROR;
}
return MAGMA_STATUS_OK;
}
} // namespace magma_image