mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* increase Vulkan GrContext resource cache limits to match android * [content handler] tune VulkanSurfaceProducer allocation and recycling heuristics
501 lines
16 KiB
C++
501 lines
16 KiB
C++
// 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())));
|
|
|
|
context_->setResourceCacheLimits(vulkan::kGrCacheMaxCount,
|
|
vulkan::kGrCacheMaxByteSize);
|
|
|
|
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
|