flutter_flutter/shell/platform/fuchsia/flutter/vulkan_surface_pool.cc
James Robinson 0832dfde5a [flow][fuchsia] Add more tracing to layers and Fuchsia surface pool (#13864)
This adds more trace events to more layer operations and enhances the
trace counters for the Fuchsia vulkan surface pool to include retained
surface counts, emit stats on recycle events that might change the
surface count, and by separating counters which measure bytes from
counters which measure counts to make analysis simpler.
2019-11-14 15:31:37 -08:00

356 lines
13 KiB
C++

// Copyright 2013 The Flutter 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 "vulkan_surface_pool.h"
#include <algorithm>
#include <string>
#include "flutter/fml/trace_event.h"
#include "third_party/skia/include/gpu/GrContext.h"
namespace flutter_runner {
namespace {
std::string ToString(const SkISize& size) {
return "{width: " + std::to_string(size.width()) +
", height: " + std::to_string(size.height()) + "}";
}
} // namespace
VulkanSurfacePool::VulkanSurfacePool(vulkan::VulkanProvider& vulkan_provider,
sk_sp<GrContext> context,
scenic::Session* scenic_session)
: vulkan_provider_(vulkan_provider),
context_(std::move(context)),
scenic_session_(scenic_session) {}
VulkanSurfacePool::~VulkanSurfacePool() {}
std::unique_ptr<VulkanSurface> VulkanSurfacePool::AcquireSurface(
const SkISize& size) {
auto surface = GetCachedOrCreateSurface(size);
if (surface == nullptr) {
FML_DLOG(ERROR) << "Could not acquire surface";
return nullptr;
}
if (!surface->FlushSessionAcquireAndReleaseEvents()) {
FML_DLOG(ERROR) << "Could not flush acquire/release events for buffer.";
return nullptr;
}
return surface;
}
std::unique_ptr<VulkanSurface> VulkanSurfacePool::GetCachedOrCreateSurface(
const SkISize& size) {
TRACE_EVENT0("flutter", "VulkanSurfacePool::GetCachedOrCreateSurface");
// First try to find a surface that exactly matches |size|.
{
auto exact_match_it =
std::find_if(available_surfaces_.begin(), available_surfaces_.end(),
[&size](const auto& surface) {
return surface->IsValid() && surface->GetSize() == size;
});
if (exact_match_it != available_surfaces_.end()) {
auto acquired_surface = std::move(*exact_match_it);
available_surfaces_.erase(exact_match_it);
TRACE_EVENT_INSTANT0("flutter", "Exact match found");
return acquired_surface;
}
}
// Then, look for a surface that has enough |VkDeviceMemory| to hold a
// |VkImage| of size |size|, but is currently holding a |VkImage| of a
// different size.
VulkanImage vulkan_image;
if (!CreateVulkanImage(vulkan_provider_, size, &vulkan_image)) {
FML_DLOG(ERROR) << "Failed to create a VkImage of size: " << ToString(size);
return nullptr;
}
auto best_it = available_surfaces_.end();
for (auto it = available_surfaces_.begin(); it != available_surfaces_.end();
++it) {
const auto& surface = *it;
if (!surface->IsValid() || surface->GetAllocationSize() <
vulkan_image.vk_memory_requirements.size) {
continue;
}
if (best_it == available_surfaces_.end() ||
surface->GetAllocationSize() < (*best_it)->GetAllocationSize()) {
best_it = it;
}
}
// If no such surface exists, then create a new one.
if (best_it == available_surfaces_.end()) {
TRACE_EVENT_INSTANT0("flutter", "No available surfaces");
return CreateSurface(size);
}
auto acquired_surface = std::move(*best_it);
available_surfaces_.erase(best_it);
bool swap_succeeded =
acquired_surface->BindToImage(context_, std::move(vulkan_image));
if (!swap_succeeded) {
FML_DLOG(ERROR) << "Failed to swap VulkanSurface to new VkImage of size: "
<< ToString(size);
TRACE_EVENT_INSTANT0("flutter", "failed to swap, making new");
return CreateSurface(size);
}
TRACE_EVENT_INSTANT0("flutter", "Using differently sized image");
FML_DCHECK(acquired_surface->IsValid());
trace_surfaces_reused_++;
return acquired_surface;
}
void VulkanSurfacePool::SubmitSurface(
std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
p_surface) {
TRACE_EVENT0("flutter", "VulkanSurfacePool::SubmitSurface");
// This cast is safe because |VulkanSurface| is the only implementation of
// |SurfaceProducerSurface| for Flutter on Fuchsia. Additionally, it is
// required, because we need to access |VulkanSurface| specific information
// of the surface (such as the amount of VkDeviceMemory it contains).
auto vulkan_surface = std::unique_ptr<VulkanSurface>(
static_cast<VulkanSurface*>(p_surface.release()));
if (!vulkan_surface) {
return;
}
const flutter::LayerRasterCacheKey& retained_key =
vulkan_surface->GetRetainedKey();
if (retained_key.id() != 0) {
// Add the surface to |retained_surfaces_| if its retained key has a valid
// layer id (|retained_key.id()|).
//
// We have to add the entry to |retained_surfaces_| map early when it's
// still pending (|is_pending| = true). Otherwise (if we add the surface
// later when |SignalRetainedReady| is called), Flutter would fail to find
// the retained node before the painting is done (which could take multiple
// frames). Flutter would then create a new |VulkanSurface| for the layer
// upon the failed lookup. The new |VulkanSurface| would invalidate this
// surface, and before the new |VulkanSurface| is done painting, another
// newer |VulkanSurface| is likely to be created to replace the new
// |VulkanSurface|. That would make the retained rendering much less useful
// in improving the performance.
auto insert_iterator = retained_surfaces_.insert(std::make_pair(
retained_key, RetainedSurface({true, std::move(vulkan_surface)})));
if (insert_iterator.second) {
insert_iterator.first->second.vk_surface->SignalWritesFinished(std::bind(
&VulkanSurfacePool::SignalRetainedReady, this, retained_key));
}
} else {
uintptr_t surface_key = reinterpret_cast<uintptr_t>(vulkan_surface.get());
auto insert_iterator = pending_surfaces_.insert(std::make_pair(
surface_key, // key
std::move(vulkan_surface) // value
));
if (insert_iterator.second) {
insert_iterator.first->second->SignalWritesFinished(std::bind(
&VulkanSurfacePool::RecyclePendingSurface, this, surface_key));
}
}
}
std::unique_ptr<VulkanSurface> VulkanSurfacePool::CreateSurface(
const SkISize& size) {
TRACE_EVENT0("flutter", "VulkanSurfacePool::CreateSurface");
auto surface = std::make_unique<VulkanSurface>(vulkan_provider_, context_,
scenic_session_, size);
if (!surface->IsValid()) {
return nullptr;
}
trace_surfaces_created_++;
return surface;
}
void VulkanSurfacePool::RecyclePendingSurface(uintptr_t surface_key) {
// Before we do anything, we must clear the surface from the collection of
// pending surfaces.
auto found_in_pending = pending_surfaces_.find(surface_key);
if (found_in_pending == pending_surfaces_.end()) {
return;
}
// Grab a hold of the surface to recycle and clear the entry in the pending
// surfaces collection.
auto surface_to_recycle = std::move(found_in_pending->second);
pending_surfaces_.erase(found_in_pending);
RecycleSurface(std::move(surface_to_recycle));
}
void VulkanSurfacePool::RecycleSurface(std::unique_ptr<VulkanSurface> surface) {
// The surface may have become invalid (for example it the fences could
// not be reset).
if (!surface->IsValid()) {
return;
}
TRACE_EVENT0("flutter", "VulkanSurfacePool::RecycleSurface");
// Recycle the buffer by putting it in the list of available surfaces if we
// have not reached the maximum amount of cached surfaces.
if (available_surfaces_.size() < kMaxSurfaces) {
available_surfaces_.push_back(std::move(surface));
} else {
TRACE_EVENT_INSTANT0("flutter", "Too many surfaces in pool, dropping");
}
TraceStats();
}
void VulkanSurfacePool::RecycleRetainedSurface(
const flutter::LayerRasterCacheKey& key) {
auto it = retained_surfaces_.find(key);
if (it == retained_surfaces_.end()) {
return;
}
// The surface should not be pending.
FML_DCHECK(!it->second.is_pending);
auto surface_to_recycle = std::move(it->second.vk_surface);
retained_surfaces_.erase(it);
RecycleSurface(std::move(surface_to_recycle));
}
void VulkanSurfacePool::SignalRetainedReady(flutter::LayerRasterCacheKey key) {
retained_surfaces_[key].is_pending = false;
}
void VulkanSurfacePool::AgeAndCollectOldBuffers() {
TRACE_EVENT0("flutter", "VulkanSurfacePool::AgeAndCollectOldBuffers");
// Remove all surfaces that are no longer valid or are too old.
size_t size_before = available_surfaces_.size();
available_surfaces_.erase(
std::remove_if(available_surfaces_.begin(), available_surfaces_.end(),
[&](auto& surface) {
return !surface->IsValid() ||
surface->AdvanceAndGetAge() >= kMaxSurfaceAge;
}),
available_surfaces_.end());
TRACE_EVENT1("flutter", "AgeAndCollect", "aged surfaces",
(size_before - available_surfaces_.size()));
// Look for a surface that has both a larger |VkDeviceMemory| allocation
// than is necessary for its |VkImage|, and has a stable size history.
auto surface_to_remove_it = std::find_if(
available_surfaces_.begin(), available_surfaces_.end(),
[](const auto& surface) {
return surface->IsOversized() && surface->HasStableSizeHistory();
});
// If we found such a surface, then destroy it and cache a new one that only
// uses a necessary amount of memory.
if (surface_to_remove_it != available_surfaces_.end()) {
TRACE_EVENT_INSTANT0("flutter", "replacing surface with smaller one");
auto size = (*surface_to_remove_it)->GetSize();
available_surfaces_.erase(surface_to_remove_it);
auto new_surface = CreateSurface(size);
if (new_surface != nullptr) {
available_surfaces_.push_back(std::move(new_surface));
} else {
FML_DLOG(ERROR) << "Failed to create a new shrunk surface";
}
}
// Recycle retained surfaces that are not used and not pending in this frame.
//
// It's safe to recycle any retained surfaces that are not pending no matter
// whether they're used or not. Hence if there's memory pressure, feel free to
// recycle all retained surfaces that are not pending.
std::vector<flutter::LayerRasterCacheKey> recycle_keys;
for (auto& [key, retained_surface] : retained_surfaces_) {
if (retained_surface.is_pending ||
retained_surface.vk_surface->IsUsedInRetainedRendering()) {
// Reset the flag for the next frame
retained_surface.vk_surface->ResetIsUsedInRetainedRendering();
} else {
recycle_keys.push_back(key);
}
}
for (auto& key : recycle_keys) {
RecycleRetainedSurface(key);
}
TraceStats();
}
void VulkanSurfacePool::ShrinkToFit() {
TRACE_EVENT0("flutter", "VulkanSurfacePool::ShrinkToFit");
// Reset all oversized surfaces in |available_surfaces_| so that the old
// surfaces and new surfaces don't exist at the same time at any point,
// reducing our peak memory footprint.
std::vector<SkISize> sizes_to_recreate;
for (auto& surface : available_surfaces_) {
if (surface->IsOversized()) {
sizes_to_recreate.push_back(surface->GetSize());
surface.reset();
}
}
available_surfaces_.erase(std::remove(available_surfaces_.begin(),
available_surfaces_.end(), nullptr),
available_surfaces_.end());
for (const auto& size : sizes_to_recreate) {
auto surface = CreateSurface(size);
if (surface != nullptr) {
available_surfaces_.push_back(std::move(surface));
} else {
FML_DLOG(ERROR) << "Failed to create resized surface";
}
}
TraceStats();
}
void VulkanSurfacePool::TraceStats() {
// Resources held in cached buffers.
size_t cached_surfaces_bytes = 0;
size_t retained_surfaces_bytes = 0;
for (const auto& surface : available_surfaces_) {
cached_surfaces_bytes += surface->GetAllocationSize();
}
for (const auto& retained_entry : retained_surfaces_) {
retained_surfaces_bytes +=
retained_entry.second.vk_surface->GetAllocationSize();
}
// Resources held by Skia.
int skia_resources = 0;
size_t skia_bytes = 0;
context_->getResourceCacheUsage(&skia_resources, &skia_bytes);
const size_t skia_cache_purgeable =
context_->getResourceCachePurgeableBytes();
TRACE_COUNTER("flutter", "SurfacePoolCounts", 0u, "CachedCount",
available_surfaces_.size(), //
"Created", trace_surfaces_created_, //
"Reused", trace_surfaces_reused_, //
"PendingInCompositor", pending_surfaces_.size(), //
"Retained", retained_surfaces_.size(), //
"SkiaCacheResources", skia_resources //
);
TRACE_COUNTER("flutter", "SurfacePoolBytes", 0u, //
"CachedBytes", cached_surfaces_bytes, //
"RetainedBytes", retained_surfaces_bytes, //
"SkiaCacheBytes", skia_bytes, //
"SkiaCachePurgeable", skia_cache_purgeable //
);
// Reset per present/frame stats.
trace_surfaces_created_ = 0;
trace_surfaces_reused_ = 0;
}
} // namespace flutter_runner