blob: 61859ebaff2ff4a4b464118e66e59da3cd1dadda [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 <fidl/fuchsia.sysmem/cpp/wire.h>
#include <fidl/fuchsia.ui.composition/cpp/wire.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/cpp/macros.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) {
static_assert(DRM_FORMAT_MOD_LINEAR == fuchsia_sysmem::wire::kFormatModifierLinear);
static_assert(I915_FORMAT_MOD_X_TILED == fuchsia_sysmem::wire::kFormatModifierIntelI915XTiled);
static_assert(I915_FORMAT_MOD_Y_TILED == fuchsia_sysmem::wire::kFormatModifierIntelI915YTiled);
static_assert(I915_FORMAT_MOD_Yf_TILED == fuchsia_sysmem::wire::kFormatModifierIntelI915YfTiled);
switch (modifier) {
case fuchsia_sysmem::wire::kFormatModifierLinear:
case fuchsia_sysmem::wire::kFormatModifierIntelI915XTiled:
case fuchsia_sysmem::wire::kFormatModifierIntelI915YTiled:
case fuchsia_sysmem::wire::kFormatModifierIntelI915YfTiled:
return modifier;
case fuchsia_sysmem::wire::kFormatModifierIntelI915YTiledCcs:
return I915_FORMAT_MOD_Y_TILED_CCS;
case fuchsia_sysmem::wire::kFormatModifierIntelI915YfTiledCcs:
return I915_FORMAT_MOD_Yf_TILED_CCS;
case fuchsia_sysmem::wire::kFormatModifierArmLinearTe:
// No DRM format modifier available.
return DRM_FORMAT_MOD_INVALID;
}
FX_CHECK(false) << "Unhandled format modifier";
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 on_fidl_error(::fidl::UnbindInfo info) override { unbind_info_ = info; }
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();
zx_status_t InitScenic();
vk::PhysicalDeviceLimits GetPhysicalDeviceLimits();
// Creates the buffer collection and sets constraints.
vk::Result CreateCollection(vk::ImageConstraintsInfoFUCHSIA* 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,
zx::eventpair* token_out, magma_image_info_t* image_info_out);
// Scenic is used if client asks for presentable images.
bool use_scenic() { return scenic_allocator_.is_valid(); }
void GetFormatFeatures(vk::Format format, bool linear_tiling,
vk::FormatFeatureFlags* features_out) {
auto result = physical_device_.getFormatProperties(format);
if (linear_tiling) {
*features_out = result.linearTilingFeatures;
} else {
*features_out = result.optimalTilingFeatures;
}
}
private:
vk::DispatchLoaderDynamic loader_;
vk::UniqueInstance instance_;
vk::PhysicalDevice physical_device_;
vk::UniqueDevice device_;
fidl::WireSyncClient<fuchsia_ui_composition::Allocator> scenic_allocator_;
fidl::WireSyncClient<fuchsia_sysmem::Allocator> sysmem_allocator_;
fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> local_token_;
fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> vulkan_token_;
fidl::ClientEnd<fuchsia_sysmem::BufferCollectionToken> scenic_token_endpoint_;
std::unique_ptr<AsyncHandler> async_handler_;
fidl::WireSharedClient<fuchsia_sysmem::BufferCollection> collection_;
fuchsia_ui_composition::wire::BufferCollectionImportToken scenic_import_token_;
};
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 %#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 = service::Connect<fuchsia_sysmem::Allocator>();
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));
}
// TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it.
(void)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));
}
if (use_scenic()) {
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();
}
scenic_token_endpoint_ = 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));
}
{
// Sync the local token that was used for Duplicating
auto result = local_token_->Sync();
if (!result.ok()) {
LOG_VERBOSE("Failed to sync token: %d", result.status());
return result.status();
}
}
return ZX_OK;
}
zx_status_t VulkanImageCreator::InitScenic() {
auto client_end = service::Connect<fuchsia_ui_composition::Allocator>();
if (!client_end.is_ok()) {
LOG_VERBOSE("Failed to connect to scenic allocator: %d", client_end.status_value());
return client_end.status_value();
}
scenic_allocator_ =
fidl::WireSyncClient<fuchsia_ui_composition::Allocator>(std::move(*client_end));
return ZX_OK;
}
vk::PhysicalDeviceLimits VulkanImageCreator::GetPhysicalDeviceLimits() {
assert(physical_device_);
vk::PhysicalDeviceProperties props = physical_device_.getProperties();
return props.limits;
}
vk::Result VulkanImageCreator::CreateCollection(
vk::ImageConstraintsInfoFUCHSIA* image_constraints_info,
fuchsia_sysmem::wire::PixelFormatType format, std::vector<uint64_t>& modifiers) {
assert(device_);
if (use_scenic()) {
fuchsia_ui_composition::wire::BufferCollectionExportToken export_token;
zx_status_t status = zx::eventpair::create(0, &export_token.value, &scenic_import_token_.value);
if (status != ZX_OK) {
LOG_VERBOSE("zx::eventpair::create failed: %d", status);
return vk::Result::eErrorInitializationFailed;
}
fidl::Arena allocator;
fuchsia_ui_composition::wire::RegisterBufferCollectionArgs args(allocator);
args.set_export_token(std::move(export_token));
args.set_buffer_collection_token(std::move(scenic_token_endpoint_));
auto result = scenic_allocator_->RegisterBufferCollection(std::move(args));
if (!result.ok()) {
LOG_VERBOSE("RegisterBufferCollection returned %d", result.status());
return vk::Result::eErrorInitializationFailed;
}
if (result->is_error()) {
LOG_VERBOSE("RegisterBufferCollection is_err()");
return vk::Result::eErrorInitializationFailed;
}
}
// Set vulkan constraints.
vk::UniqueHandle<vk::BufferCollectionFUCHSIA, vk::DispatchLoaderDynamic> vk_collection;
{
auto collection_create_info = vk::BufferCollectionCreateInfoFUCHSIA().setCollectionToken(
vulkan_token_.TakeClientEnd().TakeChannel().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 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;
}
}
// Set local constraints.
async_handler_ = std::make_unique<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(local_token_.TakeClientEnd(),
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_.get(),
fidl::ObserveTeardown([&loop = async_handler_->loop()] { loop.Quit(); }));
}
{
fuchsia_sysmem::wire::BufferCollectionConstraints constraints{
.usage =
{
.cpu = fuchsia_sysmem::wire::kCpuUsageReadOften |
fuchsia_sysmem::wire::kCpuUsageWriteOften,
},
.min_buffer_count = 1,
.has_buffer_memory_constraints = true,
.buffer_memory_constraints =
{
.ram_domain_supported = true,
.cpu_domain_supported = true,
},
.image_format_constraints_count = to_uint32(modifiers.size()),
};
const auto& image_create_info = image_constraints_info->pFormatConstraints[0].imageCreateInfo;
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::kSrgb;
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,
zx::eventpair* token_out,
magma_image_info_t* image_info_out) {
auto result = collection_.sync()->WaitForBuffersAllocated();
// 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: %s", unbind_info->FormatDescription().c_str());
return unbind_info->status();
}
const fidl::Status call_result = collection_->Close();
if (!call_result.ok()) {
LOG_VERBOSE("Close: %s", call_result.FormatDescription().c_str());
}
// Run the loop to ensure local unbind completes.
collection_.AsyncTeardown();
// This will be interrupted by `loop.Quit()`.
async_handler_->loop().Run();
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::BufferCollectionInfo2& 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::ImageFormat2> 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;
}
switch (collection_info.settings.buffer_settings.coherency_domain) {
case fuchsia_sysmem::wire::CoherencyDomain::kCpu:
image_info_out->coherency_domain = MAGMA_COHERENCY_DOMAIN_CPU;
break;
case fuchsia_sysmem::wire::CoherencyDomain::kRam:
image_info_out->coherency_domain = MAGMA_COHERENCY_DOMAIN_RAM;
break;
case fuchsia_sysmem::wire::CoherencyDomain::kInaccessible:
image_info_out->coherency_domain = MAGMA_COHERENCY_DOMAIN_INACCESSIBLE;
break;
default:
LOG_VERBOSE("Unhandled coherency domain: %u",
collection_info.settings.buffer_settings.coherency_domain);
return ZX_ERR_INTERNAL;
}
*vmo_out = std::move(collection_info.buffers[0].vmo);
*token_out = std::move(scenic_import_token_.value);
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(uint64_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: %#lx", drm_format);
return vk::Format::eUndefined;
}
static fuchsia_sysmem::wire::PixelFormatType DrmFormatToSysmemFormat(uint64_t drm_format) {
switch (drm_format) {
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
return fuchsia_sysmem::wire::PixelFormatType::kBgra32;
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
return fuchsia_sysmem::wire::PixelFormatType::kR8G8B8A8;
}
LOG_VERBOSE("Unhandle DRM format: %#lx", drm_format);
return fuchsia_sysmem::wire::PixelFormatType::kInvalid;
}
static uint64_t DrmModifierToSysmemModifier(uint64_t modifier) {
switch (modifier) {
case DRM_FORMAT_MOD_LINEAR:
return fuchsia_sysmem::wire::kFormatModifierLinear;
case I915_FORMAT_MOD_X_TILED:
return fuchsia_sysmem::wire::kFormatModifierIntelI915XTiled;
case I915_FORMAT_MOD_Y_TILED:
return fuchsia_sysmem::wire::kFormatModifierIntelI915YTiled;
case I915_FORMAT_MOD_Yf_TILED:
return fuchsia_sysmem::wire::kFormatModifierIntelI915YfTiled;
case I915_FORMAT_MOD_Y_TILED_CCS:
return fuchsia_sysmem::wire::kFormatModifierIntelI915YTiledCcs;
case I915_FORMAT_MOD_Yf_TILED_CCS:
return fuchsia_sysmem::wire::kFormatModifierIntelI915YfTiledCcs;
}
LOG_VERBOSE("Unhandle DRM modifier: %#lx", modifier);
return fuchsia_sysmem::wire::kFormatModifierInvalid;
}
} // 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,
zx::eventpair* token_out) {
assert(create_info);
assert(image_info_out);
assert(vmo_out);
if (static_cast<uint32_t>(create_info->flags) &
~(MAGMA_IMAGE_CREATE_FLAGS_PRESENTABLE | MAGMA_IMAGE_CREATE_FLAGS_VULKAN_USAGE)) {
LOG_VERBOSE("Invalid flags: %#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: %#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::kInvalid) {
LOG_VERBOSE("Invalid format: %#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::kFormatModifierInvalid) {
LOG_VERBOSE("Invalid modifier: %#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;
}
}
if (create_info->flags & MAGMA_IMAGE_CREATE_FLAGS_PRESENTABLE) {
zx_status_t status = image_creator.InitScenic();
if (status != ZX_OK) {
LOG_VERBOSE("Failed to initialize scenic: %d", status);
return MAGMA_STATUS_INTERNAL_ERROR;
}
}
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 vk_usage = {};
vk::FormatFeatureFlags vk_format_features = {};
{
// If linear isn't requested, assume we'll get a tiled format modifier.
bool linear_tiling = sysmem_modifiers.size() == 1 &&
sysmem_modifiers[0] == fuchsia_sysmem::wire::kFormatModifierLinear;
image_creator.GetFormatFeatures(vk_format, linear_tiling, &vk_format_features);
}
if (create_info->flags & MAGMA_IMAGE_CREATE_FLAGS_VULKAN_USAGE) {
// Use the Vulkan usage as provided by the client.
// Don't bother checking these against the supported format features.
vk_usage = static_cast<vk::ImageUsageFlags>(create_info->flags >> 32);
} else {
// For non-ICD clients like GBM, the client API has no fine grained usage.
// To maximize compatibility, we pass as many usages as make sense given the format features.
if (vk_format_features & vk::FormatFeatureFlagBits::eTransferSrc) {
vk_usage |= vk::ImageUsageFlagBits::eTransferSrc;
}
if (vk_format_features & vk::FormatFeatureFlagBits::eTransferDst) {
vk_usage |= vk::ImageUsageFlagBits::eTransferDst;
}
if (vk_format_features & vk::FormatFeatureFlagBits::eSampledImage) {
vk_usage |= vk::ImageUsageFlagBits::eSampled;
}
if (vk_format_features & vk::FormatFeatureFlagBits::eStorageImage) {
vk_usage |= vk::ImageUsageFlagBits::eStorage;
}
if (vk_format_features & vk::FormatFeatureFlagBits::eColorAttachment) {
vk_usage |= vk::ImageUsageFlagBits::eColorAttachment;
vk_usage |= vk::ImageUsageFlagBits::eInputAttachment;
}
if (vk_format_features & vk::FormatFeatureFlagBits::eDepthStencilAttachment) {
vk_usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
vk_usage |= vk::ImageUsageFlagBits::eInputAttachment;
}
// No format features apply here.
vk_usage |= vk::ImageUsageFlagBits::eTransientAttachment;
}
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(vk_usage);
const vk::SysmemColorSpaceFUCHSIA kRgbColorSpace = {
static_cast<uint32_t>(fuchsia_sysmem::wire::ColorSpaceType::kSrgb)};
const vk::SysmemColorSpaceFUCHSIA kYuvColorSpace = {
static_cast<uint32_t>(fuchsia_sysmem::wire::ColorSpaceType::kRec709)};
bool isYuvFormat = vk_format == vk::Format::eG8B8G8R8422Unorm ||
vk_format == vk::Format::eG8B8R82Plane420Unorm ||
vk_format == vk::Format::eG8B8R83Plane420Unorm;
auto format_info = vk::ImageFormatConstraintsInfoFUCHSIA()
.setImageCreateInfo(image_create_info)
.setRequiredFormatFeatures(vk_format_features)
.setSysmemPixelFormat({})
.setColorSpaceCount(1u)
.setPColorSpaces(isYuvFormat ? &kYuvColorSpace : &kRgbColorSpace);
auto image_constraints =
vk::ImageConstraintsInfoFUCHSIA()
.setFlags({})
.setFormatConstraints(format_info)
.setBufferCollectionConstraints(
vk::BufferCollectionConstraintsInfoFUCHSIA().setMinBufferCount(1u).setMaxBufferCount(
1u));
vk::Result result =
image_creator.CreateCollection(&image_constraints, 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, token_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