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