• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/GrContext.h"
12 
13 namespace flutter_runner {
14 
15 namespace {
16 
ToString(const SkISize & size)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 
VulkanSurfacePool(vulkan::VulkanProvider & vulkan_provider,sk_sp<GrContext> context,scenic::Session * scenic_session)24 VulkanSurfacePool::VulkanSurfacePool(vulkan::VulkanProvider& vulkan_provider,
25                                      sk_sp<GrContext> context,
26                                      scenic::Session* scenic_session)
27     : vulkan_provider_(vulkan_provider),
28       context_(std::move(context)),
29       scenic_session_(scenic_session) {}
30 
~VulkanSurfacePool()31 VulkanSurfacePool::~VulkanSurfacePool() {}
32 
AcquireSurface(const SkISize & size)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 
GetCachedOrCreateSurface(const SkISize & size)50 std::unique_ptr<VulkanSurface> VulkanSurfacePool::GetCachedOrCreateSurface(
51     const SkISize& size) {
52   // First try to find a surface that exactly matches |size|.
53   {
54     auto exact_match_it =
55         std::find_if(available_surfaces_.begin(), available_surfaces_.end(),
56                      [&size](const auto& surface) {
57                        return surface->IsValid() && surface->GetSize() == size;
58                      });
59     if (exact_match_it != available_surfaces_.end()) {
60       auto acquired_surface = std::move(*exact_match_it);
61       available_surfaces_.erase(exact_match_it);
62       return acquired_surface;
63     }
64   }
65 
66   // Then, look for a surface that has enough |VkDeviceMemory| to hold a
67   // |VkImage| of size |size|, but is currently holding a |VkImage| of a
68   // different size.
69   VulkanImage vulkan_image;
70   if (!CreateVulkanImage(vulkan_provider_, size, &vulkan_image)) {
71     FML_DLOG(ERROR) << "Failed to create a VkImage of size: " << ToString(size);
72     return nullptr;
73   }
74 
75   auto best_it = available_surfaces_.end();
76   for (auto it = available_surfaces_.begin(); it != available_surfaces_.end();
77        ++it) {
78     const auto& surface = *it;
79     if (!surface->IsValid() || surface->GetAllocationSize() <
80                                    vulkan_image.vk_memory_requirements.size) {
81       continue;
82     }
83     if (best_it == available_surfaces_.end() ||
84         surface->GetAllocationSize() < (*best_it)->GetAllocationSize()) {
85       best_it = it;
86     }
87   }
88 
89   // If no such surface exists, then create a new one.
90   if (best_it == available_surfaces_.end()) {
91     return CreateSurface(size);
92   }
93 
94   auto acquired_surface = std::move(*best_it);
95   available_surfaces_.erase(best_it);
96   bool swap_succeeded =
97       acquired_surface->BindToImage(context_, std::move(vulkan_image));
98   if (!swap_succeeded) {
99     FML_DLOG(ERROR) << "Failed to swap VulkanSurface to new VkImage of size: "
100                     << ToString(size);
101     return CreateSurface(size);
102   }
103   FML_DCHECK(acquired_surface->IsValid());
104   trace_surfaces_reused_++;
105   return acquired_surface;
106 }
107 
SubmitSurface(std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface> p_surface)108 void VulkanSurfacePool::SubmitSurface(
109     std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
110         p_surface) {
111   TRACE_EVENT0("flutter", "VulkanSurfacePool::SubmitSurface");
112 
113   // This cast is safe because |VulkanSurface| is the only implementation of
114   // |SurfaceProducerSurface| for Flutter on Fuchsia.  Additionally, it is
115   // required, because we need to access |VulkanSurface| specific information
116   // of the surface (such as the amount of VkDeviceMemory it contains).
117   auto vulkan_surface = std::unique_ptr<VulkanSurface>(
118       static_cast<VulkanSurface*>(p_surface.release()));
119   if (!vulkan_surface) {
120     return;
121   }
122 
123   const flutter::LayerRasterCacheKey& retained_key =
124       vulkan_surface->GetRetainedKey();
125   if (retained_key.id() != 0) {
126     // Add the surface to |retained_surfaces_| if its retained key has a valid
127     // layer id (|retained_key.id()|).
128     //
129     // We have to add the entry to |retained_surfaces_| map early when it's
130     // still pending (|is_pending| = true). Otherwise (if we add the surface
131     // later when |SignalRetainedReady| is called), Flutter would fail to find
132     // the retained node before the painting is done (which could take multiple
133     // frames). Flutter would then create a new |VulkanSurface| for the layer
134     // upon the failed lookup. The new |VulkanSurface| would invalidate this
135     // surface, and before the new |VulkanSurface| is done painting, another
136     // newer |VulkanSurface| is likely to be created to replace the new
137     // |VulkanSurface|. That would make the retained rendering much less useful
138     // in improving the performance.
139     auto insert_iterator = retained_surfaces_.insert(std::make_pair(
140         retained_key, RetainedSurface({true, std::move(vulkan_surface)})));
141     if (insert_iterator.second) {
142       insert_iterator.first->second.vk_surface->SignalWritesFinished(std::bind(
143           &VulkanSurfacePool::SignalRetainedReady, this, retained_key));
144     }
145   } else {
146     uintptr_t surface_key = reinterpret_cast<uintptr_t>(vulkan_surface.get());
147     auto insert_iterator = pending_surfaces_.insert(std::make_pair(
148         surface_key,               // key
149         std::move(vulkan_surface)  // value
150         ));
151     if (insert_iterator.second) {
152       insert_iterator.first->second->SignalWritesFinished(std::bind(
153           &VulkanSurfacePool::RecyclePendingSurface, this, surface_key));
154     }
155   }
156 }
157 
CreateSurface(const SkISize & size)158 std::unique_ptr<VulkanSurface> VulkanSurfacePool::CreateSurface(
159     const SkISize& size) {
160   TRACE_EVENT0("flutter", "VulkanSurfacePool::CreateSurface");
161   auto surface = std::make_unique<VulkanSurface>(vulkan_provider_, context_,
162                                                  scenic_session_, size);
163   if (!surface->IsValid()) {
164     return nullptr;
165   }
166   trace_surfaces_created_++;
167   return surface;
168 }
169 
RecyclePendingSurface(uintptr_t surface_key)170 void VulkanSurfacePool::RecyclePendingSurface(uintptr_t surface_key) {
171   // Before we do anything, we must clear the surface from the collection of
172   // pending surfaces.
173   auto found_in_pending = pending_surfaces_.find(surface_key);
174   if (found_in_pending == pending_surfaces_.end()) {
175     return;
176   }
177 
178   // Grab a hold of the surface to recycle and clear the entry in the pending
179   // surfaces collection.
180   auto surface_to_recycle = std::move(found_in_pending->second);
181   pending_surfaces_.erase(found_in_pending);
182 
183   RecycleSurface(std::move(surface_to_recycle));
184 }
185 
RecycleSurface(std::unique_ptr<VulkanSurface> surface)186 void VulkanSurfacePool::RecycleSurface(std::unique_ptr<VulkanSurface> surface) {
187   // The surface may have become invalid (for example it the fences could
188   // not be reset).
189   if (!surface->IsValid()) {
190     return;
191   }
192 
193   // Recycle the buffer by putting it in the list of available surfaces if we
194   // have not reached the maximum amount of cached surfaces.
195   if (available_surfaces_.size() < kMaxSurfaces) {
196     available_surfaces_.push_back(std::move(surface));
197   }
198 }
199 
RecycleRetainedSurface(const flutter::LayerRasterCacheKey & key)200 void VulkanSurfacePool::RecycleRetainedSurface(
201     const flutter::LayerRasterCacheKey& key) {
202   auto it = retained_surfaces_.find(key);
203   if (it == retained_surfaces_.end()) {
204     return;
205   }
206 
207   // The surface should not be pending.
208   FML_DCHECK(!it->second.is_pending);
209 
210   auto surface_to_recycle = std::move(it->second.vk_surface);
211   retained_surfaces_.erase(it);
212   RecycleSurface(std::move(surface_to_recycle));
213 }
214 
SignalRetainedReady(flutter::LayerRasterCacheKey key)215 void VulkanSurfacePool::SignalRetainedReady(flutter::LayerRasterCacheKey key) {
216   retained_surfaces_[key].is_pending = false;
217 }
218 
AgeAndCollectOldBuffers()219 void VulkanSurfacePool::AgeAndCollectOldBuffers() {
220   TRACE_EVENT0("flutter", "VulkanSurfacePool::AgeAndCollectOldBuffers");
221 
222   // Remove all surfaces that are no longer valid or are too old.
223   available_surfaces_.erase(
224       std::remove_if(available_surfaces_.begin(), available_surfaces_.end(),
225                      [&](auto& surface) {
226                        return !surface->IsValid() ||
227                               surface->AdvanceAndGetAge() >= kMaxSurfaceAge;
228                      }),
229       available_surfaces_.end());
230 
231   // Look for a surface that has both a larger |VkDeviceMemory| allocation
232   // than is necessary for its |VkImage|, and has a stable size history.
233   auto surface_to_remove_it = std::find_if(
234       available_surfaces_.begin(), available_surfaces_.end(),
235       [](const auto& surface) {
236         return surface->IsOversized() && surface->HasStableSizeHistory();
237       });
238   // If we found such a surface, then destroy it and cache a new one that only
239   // uses a necessary amount of memory.
240   if (surface_to_remove_it != available_surfaces_.end()) {
241     auto size = (*surface_to_remove_it)->GetSize();
242     available_surfaces_.erase(surface_to_remove_it);
243     auto new_surface = CreateSurface(size);
244     if (new_surface != nullptr) {
245       available_surfaces_.push_back(std::move(new_surface));
246     } else {
247       FML_DLOG(ERROR) << "Failed to create a new shrunk surface";
248     }
249   }
250 
251   // Recycle retained surfaces that are not used and not pending in this frame.
252   //
253   // It's safe to recycle any retained surfaces that are not pending no matter
254   // whether they're used or not. Hence if there's memory pressure, feel free to
255   // recycle all retained surfaces that are not pending.
256   std::vector<flutter::LayerRasterCacheKey> recycle_keys;
257   for (auto& [key, retained_surface] : retained_surfaces_) {
258     if (retained_surface.is_pending ||
259         retained_surface.vk_surface->IsUsedInRetainedRendering()) {
260       // Reset the flag for the next frame
261       retained_surface.vk_surface->ResetIsUsedInRetainedRendering();
262     } else {
263       recycle_keys.push_back(key);
264     }
265   }
266   for (auto& key : recycle_keys) {
267     RecycleRetainedSurface(key);
268   }
269 
270   TraceStats();
271 }
272 
ShrinkToFit()273 void VulkanSurfacePool::ShrinkToFit() {
274   // Reset all oversized surfaces in |available_surfaces_| so that the old
275   // surfaces and new surfaces don't exist at the same time at any point,
276   // reducing our peak memory footprint.
277   std::vector<SkISize> sizes_to_recreate;
278   for (auto& surface : available_surfaces_) {
279     if (surface->IsOversized()) {
280       sizes_to_recreate.push_back(surface->GetSize());
281       surface.reset();
282     }
283   }
284   available_surfaces_.erase(std::remove(available_surfaces_.begin(),
285                                         available_surfaces_.end(), nullptr),
286                             available_surfaces_.end());
287   for (const auto& size : sizes_to_recreate) {
288     auto surface = CreateSurface(size);
289     if (surface != nullptr) {
290       available_surfaces_.push_back(std::move(surface));
291     } else {
292       FML_DLOG(ERROR) << "Failed to create resized surface";
293     }
294   }
295 
296   TraceStats();
297 }
298 
TraceStats()299 void VulkanSurfacePool::TraceStats() {
300   // Resources held in cached buffers.
301   size_t cached_surfaces = 0;
302   size_t cached_surfaces_bytes = 0;
303 
304   for (const auto& surface : available_surfaces_) {
305     cached_surfaces++;
306     cached_surfaces_bytes += surface->GetAllocationSize();
307   }
308 
309   // Resources held by Skia.
310   int skia_resources = 0;
311   size_t skia_bytes = 0;
312   context_->getResourceCacheUsage(&skia_resources, &skia_bytes);
313   const size_t skia_cache_purgeable =
314       context_->getResourceCachePurgeableBytes();
315 
316   TRACE_COUNTER("flutter", "SurfacePool", 0u,                     //
317                 "CachedCount", cached_surfaces,                   //
318                 "CachedBytes", cached_surfaces_bytes,             //
319                 "Created", trace_surfaces_created_,               //
320                 "Reused", trace_surfaces_reused_,                 //
321                 "PendingInCompositor", pending_surfaces_.size(),  //
322                 "Retained", retained_surfaces_.size(),            //
323                 "SkiaCacheResources", skia_resources,             //
324                 "SkiaCacheBytes", skia_bytes,                     //
325                 "SkiaCachePurgeable", skia_cache_purgeable        //
326   );
327 
328   // Reset per present/frame stats.
329   trace_surfaces_created_ = 0;
330   trace_surfaces_reused_ = 0;
331 }
332 
333 }  // namespace flutter_runner
334