Flutter Engine
vulkan_surface_pool.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "vulkan_surface_pool.h"
6 
7 #include <algorithm>
8 #include <string>
9 
10 #include "flutter/fml/trace_event.h"
11 #include "third_party/skia/include/gpu/GrDirectContext.h"
12 
13 namespace flutter_runner {
14 
15 namespace {
16 
17 std::string ToString(const SkISize& size) {
18  return "{width: " + std::to_string(size.width()) +
19  ", height: " + std::to_string(size.height()) + "}";
20 }
21 
22 } // namespace
23 
25  sk_sp<GrDirectContext> context,
26  scenic::Session* scenic_session)
27  : vulkan_provider_(vulkan_provider),
28  context_(std::move(context)),
29  scenic_session_(scenic_session) {}
30 
32 
33 std::unique_ptr<VulkanSurface> VulkanSurfacePool::AcquireSurface(
34  const SkISize& size) {
35  auto surface = GetCachedOrCreateSurface(size);
36 
37  if (surface == nullptr) {
38  FML_DLOG(ERROR) << "Could not acquire surface";
39  return nullptr;
40  }
41 
42  if (!surface->FlushSessionAcquireAndReleaseEvents()) {
43  FML_DLOG(ERROR) << "Could not flush acquire/release events for buffer.";
44  return nullptr;
45  }
46 
47  return surface;
48 }
49 
50 std::unique_ptr<VulkanSurface> VulkanSurfacePool::GetCachedOrCreateSurface(
51  const SkISize& size) {
52  TRACE_EVENT2("flutter", "VulkanSurfacePool::GetCachedOrCreateSurface",
53  "width", size.width(), "height", size.height());
54  // First try to find a surface that exactly matches |size|.
55  {
56  auto exact_match_it =
57  std::find_if(available_surfaces_.begin(), available_surfaces_.end(),
58  [&size](const auto& surface) {
59  return surface->IsValid() && surface->GetSize() == size;
60  });
61  if (exact_match_it != available_surfaces_.end()) {
62  auto acquired_surface = std::move(*exact_match_it);
63  available_surfaces_.erase(exact_match_it);
64  TRACE_EVENT_INSTANT0("flutter", "Exact match found");
65  return acquired_surface;
66  }
67  }
68 
69  // Then, look for a surface that has enough |VkDeviceMemory| to hold a
70  // |VkImage| of size |size|, but is currently holding a |VkImage| of a
71  // different size.
72  VulkanImage vulkan_image;
73  if (!CreateVulkanImage(vulkan_provider_, size, &vulkan_image)) {
74  FML_DLOG(ERROR) << "Failed to create a VkImage of size: " << ToString(size);
75  return nullptr;
76  }
77 
78  auto best_it = available_surfaces_.end();
79  for (auto it = available_surfaces_.begin(); it != available_surfaces_.end();
80  ++it) {
81  const auto& surface = *it;
82  if (!surface->IsValid() || surface->GetAllocationSize() <
83  vulkan_image.vk_memory_requirements.size) {
84  continue;
85  }
86  if (best_it == available_surfaces_.end() ||
87  surface->GetAllocationSize() < (*best_it)->GetAllocationSize()) {
88  best_it = it;
89  }
90  }
91 
92  // If no such surface exists, then create a new one.
93  if (best_it == available_surfaces_.end()) {
94  TRACE_EVENT_INSTANT0("flutter", "No available surfaces");
95  return CreateSurface(size);
96  }
97 
98  auto acquired_surface = std::move(*best_it);
99  available_surfaces_.erase(best_it);
100  bool swap_succeeded =
101  acquired_surface->BindToImage(context_, std::move(vulkan_image));
102  if (!swap_succeeded) {
103  FML_DLOG(ERROR) << "Failed to swap VulkanSurface to new VkImage of size: "
104  << ToString(size);
105  TRACE_EVENT_INSTANT0("flutter", "failed to swap, making new");
106  return CreateSurface(size);
107  }
108  TRACE_EVENT_INSTANT0("flutter", "Using differently sized image");
109  FML_DCHECK(acquired_surface->IsValid());
110  trace_surfaces_reused_++;
111  return acquired_surface;
112 }
113 
115  std::unique_ptr<SurfaceProducerSurface> p_surface) {
116  TRACE_EVENT0("flutter", "VulkanSurfacePool::SubmitSurface");
117 
118  // This cast is safe because |VulkanSurface| is the only implementation of
119  // |SurfaceProducerSurface| for Flutter on Fuchsia. Additionally, it is
120  // required, because we need to access |VulkanSurface| specific information
121  // of the surface (such as the amount of VkDeviceMemory it contains).
122  auto vulkan_surface = std::unique_ptr<VulkanSurface>(
123  static_cast<VulkanSurface*>(p_surface.release()));
124  if (!vulkan_surface) {
125  return;
126  }
127 
128  uintptr_t surface_key = reinterpret_cast<uintptr_t>(vulkan_surface.get());
129  auto insert_iterator = pending_surfaces_.insert(std::make_pair(
130  surface_key, // key
131  std::move(vulkan_surface) // value
132  ));
133  if (insert_iterator.second) {
134  insert_iterator.first->second->SignalWritesFinished(std::bind(
135  &VulkanSurfacePool::RecyclePendingSurface, this, surface_key));
136  }
137 }
138 
139 std::unique_ptr<VulkanSurface> VulkanSurfacePool::CreateSurface(
140  const SkISize& size) {
141  TRACE_EVENT2("flutter", "VulkanSurfacePool::CreateSurface", "width",
142  size.width(), "height", size.height());
143  auto surface = std::make_unique<VulkanSurface>(vulkan_provider_, context_,
144  scenic_session_, size);
145  if (!surface->IsValid()) {
146  return nullptr;
147  }
148  trace_surfaces_created_++;
149  return surface;
150 }
151 
152 void VulkanSurfacePool::RecyclePendingSurface(uintptr_t surface_key) {
153  // Before we do anything, we must clear the surface from the collection of
154  // pending surfaces.
155  auto found_in_pending = pending_surfaces_.find(surface_key);
156  if (found_in_pending == pending_surfaces_.end()) {
157  return;
158  }
159 
160  // Grab a hold of the surface to recycle and clear the entry in the pending
161  // surfaces collection.
162  auto surface_to_recycle = std::move(found_in_pending->second);
163  pending_surfaces_.erase(found_in_pending);
164 
165  RecycleSurface(std::move(surface_to_recycle));
166 }
167 
168 void VulkanSurfacePool::RecycleSurface(std::unique_ptr<VulkanSurface> surface) {
169  // The surface may have become invalid (for example it the fences could
170  // not be reset).
171  if (!surface->IsValid()) {
172  return;
173  }
174 
175  TRACE_EVENT0("flutter", "VulkanSurfacePool::RecycleSurface");
176  // Recycle the buffer by putting it in the list of available surfaces if we
177  // have not reached the maximum amount of cached surfaces.
178  if (available_surfaces_.size() < kMaxSurfaces) {
179  available_surfaces_.push_back(std::move(surface));
180  } else {
181  TRACE_EVENT_INSTANT0("flutter", "Too many surfaces in pool, dropping");
182  }
183  TraceStats();
184 }
185 
187  TRACE_EVENT0("flutter", "VulkanSurfacePool::AgeAndCollectOldBuffers");
188 
189  // Remove all surfaces that are no longer valid or are too old.
190  size_t size_before = available_surfaces_.size();
191  available_surfaces_.erase(
192  std::remove_if(available_surfaces_.begin(), available_surfaces_.end(),
193  [&](auto& surface) {
194  return !surface->IsValid() ||
195  surface->AdvanceAndGetAge() >= kMaxSurfaceAge;
196  }),
197  available_surfaces_.end());
198  TRACE_EVENT1("flutter", "AgeAndCollect", "aged surfaces",
199  (size_before - available_surfaces_.size()));
200 
201  // Look for a surface that has both a larger |VkDeviceMemory| allocation
202  // than is necessary for its |VkImage|, and has a stable size history.
203  auto surface_to_remove_it = std::find_if(
204  available_surfaces_.begin(), available_surfaces_.end(),
205  [](const auto& surface) {
206  return surface->IsOversized() && surface->HasStableSizeHistory();
207  });
208  // If we found such a surface, then destroy it and cache a new one that only
209  // uses a necessary amount of memory.
210  if (surface_to_remove_it != available_surfaces_.end()) {
211  TRACE_EVENT_INSTANT0("flutter", "replacing surface with smaller one");
212  auto size = (*surface_to_remove_it)->GetSize();
213  available_surfaces_.erase(surface_to_remove_it);
214  auto new_surface = CreateSurface(size);
215  if (new_surface != nullptr) {
216  available_surfaces_.push_back(std::move(new_surface));
217  } else {
218  FML_DLOG(ERROR) << "Failed to create a new shrunk surface";
219  }
220  }
221 
222  TraceStats();
223 }
224 
226  TRACE_EVENT0("flutter", "VulkanSurfacePool::ShrinkToFit");
227  // Reset all oversized surfaces in |available_surfaces_| so that the old
228  // surfaces and new surfaces don't exist at the same time at any point,
229  // reducing our peak memory footprint.
230  std::vector<SkISize> sizes_to_recreate;
231  for (auto& surface : available_surfaces_) {
232  if (surface->IsOversized()) {
233  sizes_to_recreate.push_back(surface->GetSize());
234  surface.reset();
235  }
236  }
237  available_surfaces_.erase(std::remove(available_surfaces_.begin(),
238  available_surfaces_.end(), nullptr),
239  available_surfaces_.end());
240  for (const auto& size : sizes_to_recreate) {
241  auto surface = CreateSurface(size);
242  if (surface != nullptr) {
243  available_surfaces_.push_back(std::move(surface));
244  } else {
245  FML_DLOG(ERROR) << "Failed to create resized surface";
246  }
247  }
248 
249  TraceStats();
250 }
251 
252 void VulkanSurfacePool::TraceStats() {
253  // Resources held in cached buffers.
254  size_t cached_surfaces_bytes = 0;
255  for (const auto& surface : available_surfaces_) {
256  cached_surfaces_bytes += surface->GetAllocationSize();
257  }
258 
259  // Resources held by Skia.
260  int skia_resources = 0;
261  size_t skia_bytes = 0;
262  context_->getResourceCacheUsage(&skia_resources, &skia_bytes);
263  const size_t skia_cache_purgeable =
264  context_->getResourceCachePurgeableBytes();
265 
266  TRACE_COUNTER("flutter", "SurfacePoolCounts", 0u, "CachedCount",
267  available_surfaces_.size(), //
268  "Created", trace_surfaces_created_, //
269  "Reused", trace_surfaces_reused_, //
270  "PendingInCompositor", pending_surfaces_.size(), //
271  "Retained", 0, //
272  "SkiaCacheResources", skia_resources //
273  );
274 
275  TRACE_COUNTER("flutter", "SurfacePoolBytes", 0u, //
276  "CachedBytes", cached_surfaces_bytes, //
277  "RetainedBytes", 0, //
278  "SkiaCacheBytes", skia_bytes, //
279  "SkiaCachePurgeable", skia_cache_purgeable //
280  );
281 
282  // Reset per present/frame stats.
283  trace_surfaces_created_ = 0;
284  trace_surfaces_reused_ = 0;
285 }
286 
287 } // namespace flutter_runner
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:75
#define FML_DCHECK(condition)
Definition: logging.h:86
#define TRACE_EVENT_INSTANT0(category_group, name)
Definition: trace_event.h:104
VulkanSurfacePool(vulkan::VulkanProvider &vulkan_provider, sk_sp< GrDirectContext > context, scenic::Session *scenic_session)
Definition: ref_ptr.h:252
constexpr std::size_t size(T(&array)[N])
Definition: size.h:13
VkMemoryRequirements vk_memory_requirements
void SubmitSurface(std::unique_ptr< SurfaceProducerSurface > surface)
bool CreateVulkanImage(vulkan::VulkanProvider &vulkan_provider, const SkISize &size, VulkanImage *out_vulkan_image)
std::unique_ptr< VulkanSurface > AcquireSurface(const SkISize &size)
#define TRACE_EVENT1(category_group, name, arg1_name, arg1_val)
Definition: trace_event.h:79
#define TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val)
Definition: trace_event.h:83
#define FML_DLOG(severity)
Definition: logging.h:85