blob: 3a68c554b5b9c1f7d0a20bf9ec57e58aeaa02784 [file] [log] [blame]
// Copyright 2017 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 "flutter/content_handler/vulkan_rasterizer.h"
#include <fcntl.h>
#include <magenta/device/vfs.h>
#include <unistd.h>
#include <chrono>
#include <thread>
#include <utility>
#include "lib/ftl/files/unique_fd.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/include/gpu/vk/GrVkTypes.h"
#include "third_party/skia/src/gpu/vk/GrVkUtil.h"
namespace flutter_runner {
namespace {
constexpr char kDisplayDriverClass[] = "/dev/class/display";
void WaitForFirstDisplayDriver() {
ftl::UniqueFD fd(open(kDisplayDriverClass, O_DIRECTORY | O_RDONLY));
if (fd.get() < 0) {
FTL_DLOG(ERROR) << "Failed to open " << kDisplayDriverClass;
return;
}
// Create the directory watch channel.
mx_handle_t watcher;
if (ioctl_vfs_watch_dir(fd.get(), &watcher) < 0) {
FTL_DLOG(ERROR) << "Failed to create directory watcher for "
<< kDisplayDriverClass;
return;
}
// Wait for 1 second for the display driver to appear before falling back to
// software.
mx_object_wait_one(watcher, MX_CHANNEL_READABLE | MX_CHANNEL_PEER_CLOSED,
mx_deadline_after(MX_SEC(1)), nullptr);
mx_handle_close(watcher);
}
} // namespace
VulkanRasterizer::VulkanSurfaceProducer::VulkanSurfaceProducer() {
valid_ = Initialize();
if (!valid_)
FTL_LOG(ERROR) << "VulkanSurfaceProducer failed to initialize";
}
VulkanRasterizer::VulkanSurfaceProducer::~VulkanSurfaceProducer() {
for (auto& surface_info : pending_surfaces_)
mtl::MessageLoop::GetCurrent()->RemoveHandler(
surface_info.second.handler_key);
}
std::unique_ptr<VulkanRasterizer::VulkanSurfaceProducer::Surface>
VulkanRasterizer::VulkanSurfaceProducer::CreateSurface(uint32_t width,
uint32_t height) {
VkResult vk_result;
VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_B8G8R8A8_UNORM,
.extent = VkExtent3D{width, height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage vk_image;
vk_result = VK_CALL_LOG_ERROR(vkCreateImage(
backend_context_->fDevice, &image_create_info, nullptr, &vk_image));
if (vk_result)
return nullptr;
VkMemoryRequirements memory_reqs;
vkGetImageMemoryRequirements(backend_context_->fDevice, vk_image,
&memory_reqs);
uint32_t memory_type = 0;
for (; memory_type < 32; memory_type++) {
if ((memory_reqs.memoryTypeBits & (1 << memory_type)))
break;
}
VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = nullptr,
.allocationSize = memory_reqs.size,
.memoryTypeIndex = memory_type,
};
VkDeviceMemory vk_memory;
vk_result = VK_CALL_LOG_ERROR(vkAllocateMemory(
backend_context_->fDevice, &alloc_info, NULL, &vk_memory));
if (vk_result)
return nullptr;
vk_result = VK_CALL_LOG_ERROR(
vkBindImageMemory(backend_context_->fDevice, vk_image, vk_memory, 0));
if (vk_result)
return nullptr;
const GrVkImageInfo image_info = {
.fImage = vk_image,
.fAlloc = {vk_memory, 0, memory_reqs.size, 0},
.fImageTiling = image_create_info.tiling,
.fImageLayout = image_create_info.initialLayout,
.fFormat = image_create_info.format,
.fLevelCount = image_create_info.mipLevels,
};
GrBackendRenderTargetDesc desc;
desc.fWidth = width;
desc.fHeight = height;
desc.fConfig = kSBGRA_8888_GrPixelConfig;
desc.fOrigin = kTopLeft_GrSurfaceOrigin;
desc.fSampleCnt = 0;
desc.fStencilBits = 0;
desc.fRenderTargetHandle = reinterpret_cast<GrBackendObject>(&image_info);
SkSurfaceProps props(SkSurfaceProps::InitType::kLegacyFontHost_InitType);
auto sk_surface = SkSurface::MakeFromBackendRenderTarget(context_.get(), desc,
nullptr, &props);
if (!sk_surface) {
FTL_LOG(ERROR) << "MakeFromBackendRenderTarget Failed";
return nullptr;
}
uint32_t vmo_handle;
vk_result = VK_CALL_LOG_ERROR(vkExportDeviceMemoryMAGMA(
backend_context_->fDevice, vk_memory, &vmo_handle));
if (vk_result)
return nullptr;
mx::vmo vmo(vmo_handle);
size_t vmo_size;
vmo.get_size(&vmo_size);
FTL_DCHECK(vmo_size >= memory_reqs.size);
mx::eventpair retention_events[2];
auto mx_status =
mx::eventpair::create(0, &retention_events[0], &retention_events[1]);
if (mx_status) {
FTL_LOG(ERROR) << "Failed to create retention eventpair";
return nullptr;
}
if (!sk_surface || sk_surface->getCanvas() == nullptr) {
FTL_LOG(ERROR) << "surface invalid";
return nullptr;
}
return std::make_unique<Surface>(backend_context_, sk_surface, std::move(vmo),
std::move(retention_events[0]),
std::move(retention_events[1]), vk_image,
vk_memory);
}
sk_sp<SkSurface> VulkanRasterizer::VulkanSurfaceProducer::ProduceSurface(
SkISize size,
mozart::ImagePtr* out_image) {
if (size.isEmpty()) {
FTL_LOG(ERROR) << "Attempting to create surface with empty size";
return nullptr;
}
// these casts are safe because of the early out on frame_size.isEmpty()
auto width = static_cast<uint32_t>(size.width());
auto height = static_cast<uint32_t>(size.height());
std::unique_ptr<Surface> surface;
// try and find a Swapchain with surfaces of the right size
auto it = available_surfaces_.find(MakeSizeKey(width, height));
if (it == available_surfaces_.end()) {
// No matching Swapchain exists, create a new surfaces
surface = CreateSurface(width, height);
} else {
auto& swapchain = it->second;
if (swapchain.queue.size() == 0) {
// matching Swapchain exists, but does not have any buffers available in
// it
surface = CreateSurface(width, height);
} else {
surface = std::move(swapchain.queue.front());
swapchain.queue.pop();
swapchain.tick_count = 0;
// Need to do some skia foo here to clear all the canvas state from the
// last frame
surface->sk_surface->getCanvas()->restoreToCount(0);
surface->sk_surface->getCanvas()->save();
surface->sk_surface->getCanvas()->resetMatrix();
}
}
if (!surface) {
FTL_LOG(ERROR) << "Failed to produce surface";
return nullptr;
}
mx_status_t status;
auto buffer = mozart::Buffer::New();
status = surface->vmo.duplicate(MX_RIGHT_SAME_RIGHTS, &buffer->vmo);
if (status) {
FTL_LOG(ERROR) << "failed to duplicate vmo";
return nullptr;
}
buffer->memory_type = mozart::Buffer::MemoryType::VK_DEVICE_MEMORY;
mx::eventpair fence_event;
status = mx::eventpair::create(0, &fence_event, &buffer->fence);
if (status) {
FTL_LOG(ERROR) << "failed to create fence eventpair";
return nullptr;
}
mtl::MessageLoop::HandlerKey handler_key =
mtl::MessageLoop::GetCurrent()->AddHandler(this, fence_event.get(),
MX_EPAIR_PEER_CLOSED);
status = surface->remote_retention_event.duplicate(MX_RIGHT_SAME_RIGHTS,
&buffer->retention);
if (status) {
FTL_LOG(ERROR) << "failed to duplicate retention eventpair";
return nullptr;
}
auto image = mozart::Image::New();
image->size = mozart::Size::New();
image->size->width = width;
image->size->height = height;
image->stride = 4 * width;
image->pixel_format = mozart::Image::PixelFormat::B8G8R8A8;
image->alpha_format = mozart::Image::AlphaFormat::OPAQUE;
image->color_space = mozart::Image::ColorSpace::SRGB;
image->buffer = std::move(buffer);
*out_image = std::move(image);
auto sk_surface = surface->sk_surface;
PendingSurfaceInfo info;
info.handler_key = handler_key;
info.surface = std::move(surface);
info.production_fence = std::move(fence_event);
outstanding_surfaces_.push_back(std::move(info));
return sk_surface;
}
bool VulkanRasterizer::VulkanSurfaceProducer::FinishFrame() {
mx_status_t status;
// Finish Rendering
context_->flush();
VkResult result =
VK_CALL_LOG_ERROR(vkQueueWaitIdle(backend_context_->fQueue));
if (result)
return false;
for (auto& info : outstanding_surfaces_) {
// info.surface->sk_surface->prepareForExternalIO();
// Signal the compositor
status = info.production_fence.signal_peer(0u, MX_EPAIR_SIGNALED);
if (status) {
FTL_LOG(ERROR) << "failed to signal fence event";
return false;
}
pending_surfaces_.insert(
std::make_pair(info.production_fence.get(), std::move(info)));
}
outstanding_surfaces_.clear();
return true;
}
void VulkanRasterizer::VulkanSurfaceProducer::Tick() {
for (auto it = available_surfaces_.begin();
it != available_surfaces_.end();) {
auto& swapchain = it->second;
swapchain.tick_count++;
if (swapchain.tick_count > Swapchain::kMaxTickBeforeDiscard)
it = available_surfaces_.erase(it);
else
it++;
}
}
void VulkanRasterizer::VulkanSurfaceProducer::OnHandleReady(
mx_handle_t handle,
mx_signals_t pending) {
FTL_DCHECK(pending & MX_EPAIR_PEER_CLOSED);
auto it = pending_surfaces_.find(handle);
FTL_DCHECK(it != pending_surfaces_.end());
// Add the newly available buffer to the swapchain.
PendingSurfaceInfo& info = it->second;
mtl::MessageLoop::GetCurrent()->RemoveHandler(info.handler_key);
// try and find a Swapchain with surfaces of the right size
size_key_t key = MakeSizeKey(info.surface->sk_surface->width(),
info.surface->sk_surface->height());
auto swapchain_it = available_surfaces_.find(key);
if (swapchain_it == available_surfaces_.end()) {
// No matching Swapchain exists, create one
Swapchain swapchain;
if (swapchain.queue.size() + 1 <= Swapchain::kMaxSurfaces) {
swapchain.queue.push(std::move(info.surface));
}
available_surfaces_.insert(std::make_pair(key, std::move(swapchain)));
} else {
auto& swapchain = swapchain_it->second;
if (swapchain.queue.size() + 1 <= Swapchain::kMaxSurfaces) {
swapchain.queue.push(std::move(info.surface));
}
}
pending_surfaces_.erase(it);
}
bool VulkanRasterizer::VulkanSurfaceProducer::Initialize() {
vk_ = ftl::MakeRefCounted<vulkan::VulkanProcTable>();
std::vector<std::string> extensions = {VK_KHR_SURFACE_EXTENSION_NAME};
application_ = std::make_unique<vulkan::VulkanApplication>(
*vk_, "Flutter", std::move(extensions));
if (!application_->IsValid() || !vk_->AreInstanceProcsSetup()) {
// Make certain the application instance was created and it setup the
// instance proc table entries.
FTL_LOG(ERROR) << "Instance proc addresses have not been setup.";
return false;
}
// Create the device.
logical_device_ = application_->AcquireFirstCompatibleLogicalDevice();
if (logical_device_ == nullptr || !logical_device_->IsValid() ||
!vk_->AreDeviceProcsSetup()) {
// Make certain the device was created and it setup the device proc table
// entries.
FTL_LOG(ERROR) << "Device proc addresses have not been setup.";
return false;
}
if (!vk_->HasAcquiredMandatoryProcAddresses()) {
FTL_LOG(ERROR) << "Failed to acquire mandatory proc addresses";
return false;
}
if (!vk_->IsValid()) {
FTL_LOG(ERROR) << "VulkanProcTable invalid";
return false;
}
auto interface = vk_->CreateSkiaInterface();
if (interface == nullptr || !interface->validate(0)) {
FTL_LOG(ERROR) << "interface invalid";
return false;
}
uint32_t skia_features = 0;
if (!logical_device_->GetPhysicalDeviceFeaturesSkia(&skia_features)) {
FTL_LOG(ERROR) << "Failed to get physical device features";
return false;
}
backend_context_ = sk_make_sp<GrVkBackendContext>();
backend_context_->fInstance = application_->GetInstance();
backend_context_->fPhysicalDevice =
logical_device_->GetPhysicalDeviceHandle();
backend_context_->fDevice = logical_device_->GetHandle();
backend_context_->fQueue = logical_device_->GetQueueHandle();
backend_context_->fGraphicsQueueIndex =
logical_device_->GetGraphicsQueueIndex();
backend_context_->fMinAPIVersion = application_->GetAPIVersion();
backend_context_->fFeatures = skia_features;
backend_context_->fInterface.reset(interface.release());
logical_device_->ReleaseDeviceOwnership();
application_->ReleaseInstanceOwnership();
context_.reset(GrContext::Create(
kVulkan_GrBackend,
reinterpret_cast<GrBackendContext>(backend_context_.get())));
FTL_DLOG(INFO) << "Successfully initialized VulkanRasterizer";
return true;
}
VulkanRasterizer::VulkanRasterizer() : compositor_context_(nullptr) {
WaitForFirstDisplayDriver();
surface_producer_.reset(new VulkanSurfaceProducer());
}
VulkanRasterizer::~VulkanRasterizer() = default;
bool VulkanRasterizer::IsValid() const {
return surface_producer_ && surface_producer_->IsValid();
}
void VulkanRasterizer::SetScene(fidl::InterfaceHandle<mozart::Scene> scene) {
scene_.Bind(std::move(scene));
}
void VulkanRasterizer::Draw(std::unique_ptr<flow::LayerTree> layer_tree,
ftl::Closure callback) {
Draw(std::move(layer_tree));
callback();
}
bool VulkanRasterizer::Draw(std::unique_ptr<flow::LayerTree> layer_tree) {
if (layer_tree == nullptr) {
FTL_LOG(ERROR) << "Layer tree was not valid.";
return false;
}
if (!scene_) {
FTL_LOG(ERROR) << "Scene was not valid.";
return false;
}
compositor_context_.engine_time().SetLapTime(layer_tree->construction_time());
const SkISize& frame_size = layer_tree->frame_size();
auto update = mozart::SceneUpdate::New();
// TODO(abarth): Support incremental updates.
update->clear_resources = true;
update->clear_nodes = true;
if (frame_size.isEmpty()) {
update->nodes.insert(mozart::kSceneRootNodeId, mozart::Node::New());
// Publish the updated scene contents.
// TODO(jeffbrown): We should set the metadata's presentation_time here too.
scene_->Update(std::move(update));
auto metadata = mozart::SceneMetadata::New();
metadata->version = layer_tree->scene_version();
scene_->Publish(std::move(metadata));
FTL_LOG(ERROR) << "Publishing empty node";
return false;
}
flow::CompositorContext::ScopedFrame frame = compositor_context_.AcquireFrame(
nullptr, nullptr, true /* instrumentation enabled */);
layer_tree->Preroll(frame);
flow::SceneUpdateContext context(update.get(), surface_producer_.get());
auto root_node = mozart::Node::New();
root_node->hit_test_behavior = mozart::HitTestBehavior::New();
layer_tree->UpdateScene(context, root_node.get());
update->nodes.insert(mozart::kSceneRootNodeId, std::move(root_node));
// Publish the updated scene contents.
// TODO(jeffbrown): We should set the metadata's presentation_time here too.
scene_->Update(std::move(update));
auto metadata = mozart::SceneMetadata::New();
metadata->version = layer_tree->scene_version();
scene_->Publish(std::move(metadata));
// Draw the contents of the scene to a surface.
// We do this after publishing to take advantage of pipelining.
context.ExecutePaintTasks(frame);
if (!surface_producer_->FinishFrame()) {
FTL_LOG(ERROR) << "Failed to Finish Frame";
return false;
}
surface_producer_->Tick();
return true;
}
} // namespace flutter_runner