1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "CacheManager.h"
18 
19 #include <GrContextOptions.h>
20 #include <SkExecutor.h>
21 #include <SkGraphics.h>
22 #include <math.h>
23 #include <utils/Trace.h>
24 
25 #include <set>
26 
27 #include "CanvasContext.h"
28 #include "DeviceInfo.h"
29 #include "Layer.h"
30 #include "Properties.h"
31 #include "RenderThread.h"
32 #include "VulkanManager.h"
33 #include "pipeline/skia/ATraceMemoryDump.h"
34 #include "pipeline/skia/ShaderCache.h"
35 #include "pipeline/skia/SkiaMemoryTracer.h"
36 #include "renderstate/RenderState.h"
37 #include "thread/CommonPool.h"
38 
39 namespace android {
40 namespace uirenderer {
41 namespace renderthread {
42 
CacheManager(RenderThread & thread)43 CacheManager::CacheManager(RenderThread& thread)
44         : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {
45     mMaxSurfaceArea = static_cast<size_t>((DeviceInfo::getWidth() * DeviceInfo::getHeight()) *
46                                           mMemoryPolicy.initialMaxSurfaceAreaScale);
47     setupCacheLimits();
48 }
49 
countLeadingZeros(uint32_t mask)50 static inline int countLeadingZeros(uint32_t mask) {
51     // __builtin_clz(0) is undefined, so we have to detect that case.
52     return mask ? __builtin_clz(mask) : 32;
53 }
54 
55 // Return the smallest power-of-2 >= n.
nextPowerOfTwo(uint32_t n)56 static inline uint32_t nextPowerOfTwo(uint32_t n) {
57     return n ? (1 << (32 - countLeadingZeros(n - 1))) : 1;
58 }
59 
setupCacheLimits()60 void CacheManager::setupCacheLimits() {
61     mMaxResourceBytes = mMaxSurfaceArea * mMemoryPolicy.surfaceSizeMultiplier;
62     mBackgroundResourceBytes = mMaxResourceBytes * mMemoryPolicy.backgroundRetentionPercent;
63     // This sets the maximum size for a single texture atlas in the GPU font cache. If
64     // necessary, the cache can allocate additional textures that are counted against the
65     // total cache limits provided to Skia.
66     mMaxGpuFontAtlasBytes = nextPowerOfTwo(mMaxSurfaceArea);
67     // This sets the maximum size of the CPU font cache to be at least the same size as the
68     // total number of GPU font caches (i.e. 4 separate GPU atlases).
69     mMaxCpuFontCacheBytes = std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit());
70     mBackgroundCpuFontCacheBytes = mMaxCpuFontCacheBytes * mMemoryPolicy.backgroundRetentionPercent;
71 
72     SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
73     if (mGrContext) {
74         mGrContext->setResourceCacheLimit(mMaxResourceBytes);
75     }
76 }
77 
reset(sk_sp<GrDirectContext> context)78 void CacheManager::reset(sk_sp<GrDirectContext> context) {
79     if (context != mGrContext) {
80         destroy();
81     }
82 
83     if (context) {
84         mGrContext = std::move(context);
85         mGrContext->setResourceCacheLimit(mMaxResourceBytes);
86         mLastDeferredCleanup = systemTime(CLOCK_MONOTONIC);
87     }
88 }
89 
destroy()90 void CacheManager::destroy() {
91     // cleanup any caches here as the GrContext is about to go away...
92     mGrContext.reset(nullptr);
93 }
94 
95 class CommonPoolExecutor : public SkExecutor {
96 public:
add(std::function<void (void)> func)97     virtual void add(std::function<void(void)> func) override { CommonPool::post(std::move(func)); }
98 };
99 
100 static CommonPoolExecutor sDefaultExecutor;
101 
configureContext(GrContextOptions * contextOptions,const void * identity,ssize_t size)102 void CacheManager::configureContext(GrContextOptions* contextOptions, const void* identity,
103                                     ssize_t size) {
104     contextOptions->fAllowPathMaskCaching = true;
105     contextOptions->fGlyphCacheTextureMaximumBytes = mMaxGpuFontAtlasBytes;
106     contextOptions->fExecutor = &sDefaultExecutor;
107 
108     auto& cache = skiapipeline::ShaderCache::get();
109     cache.initShaderDiskCache(identity, size);
110     contextOptions->fPersistentCache = &cache;
111 }
112 
trimMemory(TrimLevel mode)113 void CacheManager::trimMemory(TrimLevel mode) {
114     if (!mGrContext) {
115         return;
116     }
117 
118     // flush and submit all work to the gpu and wait for it to finish
119     mGrContext->flushAndSubmit(/*syncCpu=*/true);
120 
121     switch (mode) {
122         case TrimLevel::BACKGROUND:
123             mGrContext->freeGpuResources();
124             SkGraphics::PurgeAllCaches();
125             mRenderThread.destroyRenderingContext();
126             break;
127         case TrimLevel::UI_HIDDEN:
128             // Here we purge all the unlocked scratch resources and then toggle the resources cache
129             // limits between the background and max amounts. This causes the unlocked resources
130             // that have persistent data to be purged in LRU order.
131             mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
132             SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
133             mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
134             mGrContext->setResourceCacheLimit(mMaxResourceBytes);
135             SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
136             break;
137         default:
138             break;
139     }
140 }
141 
trimCaches(CacheTrimLevel mode)142 void CacheManager::trimCaches(CacheTrimLevel mode) {
143     switch (mode) {
144         case CacheTrimLevel::FONT_CACHE:
145             SkGraphics::PurgeFontCache();
146             break;
147         case CacheTrimLevel::RESOURCE_CACHE:
148             SkGraphics::PurgeResourceCache();
149             break;
150         case CacheTrimLevel::ALL_CACHES:
151             SkGraphics::PurgeAllCaches();
152             if (mGrContext) {
153                 mGrContext->purgeUnlockedResources(false);
154             }
155             break;
156         default:
157             break;
158     }
159 }
160 
trimStaleResources()161 void CacheManager::trimStaleResources() {
162     if (!mGrContext) {
163         return;
164     }
165     mGrContext->flushAndSubmit();
166     mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
167 }
168 
getMemoryUsage(size_t * cpuUsage,size_t * gpuUsage)169 void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
170     *cpuUsage = 0;
171     *gpuUsage = 0;
172     if (!mGrContext) {
173         return;
174     }
175 
176     skiapipeline::SkiaMemoryTracer cpuTracer("category", true);
177     SkGraphics::DumpMemoryStatistics(&cpuTracer);
178     *cpuUsage += cpuTracer.total();
179 
180     skiapipeline::SkiaMemoryTracer gpuTracer("category", true);
181     mGrContext->dumpMemoryStatistics(&gpuTracer);
182     *gpuUsage += gpuTracer.total();
183 }
184 
dumpMemoryUsage(String8 & log,const RenderState * renderState)185 void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
186     log.appendFormat(R"(Memory policy:
187   Max surface area: %zu
188   Max resource usage: %.2fMB (x%.0f)
189   Background retention: %.0f%% (altUiHidden = %s)
190 )",
191                      mMaxSurfaceArea, mMaxResourceBytes / 1000000.f,
192                      mMemoryPolicy.surfaceSizeMultiplier,
193                      mMemoryPolicy.backgroundRetentionPercent * 100.0f,
194                      mMemoryPolicy.useAlternativeUiHidden ? "true" : "false");
195     if (Properties::isSystemOrPersistent) {
196         log.appendFormat("  IsSystemOrPersistent\n");
197     }
198     log.appendFormat("  GPU Context timeout: %" PRIu64 "\n", ns2s(mMemoryPolicy.contextTimeout));
199     size_t stoppedContexts = 0;
200     for (auto context : mCanvasContexts) {
201         if (context->isStopped()) stoppedContexts++;
202     }
203     log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
204 
205     auto vkInstance = VulkanManager::peekInstance();
206     if (!mGrContext) {
207         if (!vkInstance) {
208             log.appendFormat("No GPU context.\n");
209         } else {
210             log.appendFormat("No GrContext; however %d remaining Vulkan refs",
211                              vkInstance->getStrongCount() - 1);
212         }
213         return;
214     }
215     std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
216             {"skia/sk_resource_cache/bitmap_", "Bitmaps"},
217             {"skia/sk_resource_cache/rrect-blur_", "Masks"},
218             {"skia/sk_resource_cache/rects-blur_", "Masks"},
219             {"skia/sk_resource_cache/tessellated", "Shadows"},
220             {"skia/sk_glyph_cache", "Glyph Cache"},
221     };
222     skiapipeline::SkiaMemoryTracer cpuTracer(cpuResourceMap, false);
223     SkGraphics::DumpMemoryStatistics(&cpuTracer);
224     if (cpuTracer.hasOutput()) {
225         log.appendFormat("CPU Caches:\n");
226         cpuTracer.logOutput(log);
227         log.appendFormat("  Glyph Count: %d \n", SkGraphics::GetFontCacheCountUsed());
228         log.appendFormat("Total CPU memory usage:\n");
229         cpuTracer.logTotals(log);
230     }
231 
232     skiapipeline::SkiaMemoryTracer gpuTracer("category", true);
233     mGrContext->dumpMemoryStatistics(&gpuTracer);
234     if (gpuTracer.hasOutput()) {
235         log.appendFormat("GPU Caches:\n");
236         gpuTracer.logOutput(log);
237     }
238 
239     if (renderState && renderState->mActiveLayers.size() > 0) {
240         log.appendFormat("Layer Info:\n");
241 
242         const char* layerType = Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL
243                                         ? "GlLayer"
244                                         : "VkLayer";
245         size_t layerMemoryTotal = 0;
246         for (std::set<Layer*>::iterator it = renderState->mActiveLayers.begin();
247              it != renderState->mActiveLayers.end(); it++) {
248             const Layer* layer = *it;
249             log.appendFormat("    %s size %dx%d\n", layerType, layer->getWidth(),
250                              layer->getHeight());
251             layerMemoryTotal += layer->getWidth() * layer->getHeight() * 4;
252         }
253         log.appendFormat("  Layers Total         %6.2f KB (numLayers = %zu)\n",
254                          layerMemoryTotal / 1024.0f, renderState->mActiveLayers.size());
255     }
256 
257     log.appendFormat("Total GPU memory usage:\n");
258     gpuTracer.logTotals(log);
259 }
260 
onFrameCompleted()261 void CacheManager::onFrameCompleted() {
262     cancelDestroyContext();
263     mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
264     if (ATRACE_ENABLED()) {
265         static skiapipeline::ATraceMemoryDump tracer;
266         tracer.startFrame();
267         SkGraphics::DumpMemoryStatistics(&tracer);
268         if (mGrContext) {
269             mGrContext->dumpMemoryStatistics(&tracer);
270         }
271         tracer.logTraces();
272     }
273 }
274 
onThreadIdle()275 void CacheManager::onThreadIdle() {
276     if (!mGrContext || mFrameCompletions.size() == 0) return;
277 
278     const nsecs_t now = systemTime(CLOCK_MONOTONIC);
279     // Rate limiting
280     if ((now - mLastDeferredCleanup) < 25_ms) {
281         mLastDeferredCleanup = now;
282         const nsecs_t frameCompleteNanos = mFrameCompletions[0];
283         const nsecs_t frameDiffNanos = now - frameCompleteNanos;
284         const nsecs_t cleanupMillis =
285                 ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
286         mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
287                                            mMemoryPolicy.purgeScratchOnly);
288     }
289 }
290 
scheduleDestroyContext()291 void CacheManager::scheduleDestroyContext() {
292     if (mMemoryPolicy.contextTimeout > 0) {
293         mRenderThread.queue().postDelayed(mMemoryPolicy.contextTimeout,
294                                           [this, genId = mGenerationId] {
295                                               if (mGenerationId != genId) return;
296                                               // GenID should have already stopped this, but just in
297                                               // case
298                                               if (!areAllContextsStopped()) return;
299                                               mRenderThread.destroyRenderingContext();
300                                           });
301     }
302 }
303 
cancelDestroyContext()304 void CacheManager::cancelDestroyContext() {
305     if (mIsDestructionPending) {
306         mIsDestructionPending = false;
307         mGenerationId++;
308     }
309 }
310 
areAllContextsStopped()311 bool CacheManager::areAllContextsStopped() {
312     for (auto context : mCanvasContexts) {
313         if (!context->isStopped()) return false;
314     }
315     return true;
316 }
317 
checkUiHidden()318 void CacheManager::checkUiHidden() {
319     if (!mGrContext) return;
320 
321     if (mMemoryPolicy.useAlternativeUiHidden && areAllContextsStopped()) {
322         trimMemory(TrimLevel::UI_HIDDEN);
323     }
324 }
325 
registerCanvasContext(CanvasContext * context)326 void CacheManager::registerCanvasContext(CanvasContext* context) {
327     mCanvasContexts.push_back(context);
328     cancelDestroyContext();
329 }
330 
unregisterCanvasContext(CanvasContext * context)331 void CacheManager::unregisterCanvasContext(CanvasContext* context) {
332     std::erase(mCanvasContexts, context);
333     checkUiHidden();
334     if (mCanvasContexts.empty()) {
335         scheduleDestroyContext();
336     }
337 }
338 
onContextStopped(CanvasContext * context)339 void CacheManager::onContextStopped(CanvasContext* context) {
340     checkUiHidden();
341     if (mMemoryPolicy.releaseContextOnStoppedOnly && areAllContextsStopped()) {
342         scheduleDestroyContext();
343     }
344 }
345 
notifyNextFrameSize(int width,int height)346 void CacheManager::notifyNextFrameSize(int width, int height) {
347     int frameArea = width * height;
348     if (frameArea > mMaxSurfaceArea) {
349         mMaxSurfaceArea = frameArea;
350         setupCacheLimits();
351     }
352 }
353 
354 } /* namespace renderthread */
355 } /* namespace uirenderer */
356 } /* namespace android */
357