Merge "Implement CacheManager for the Skia pipelines."

This commit is contained in:
TreeHugger Robot
2017-06-06 20:37:01 +00:00
committed by Android (Google) Code Review
11 changed files with 444 additions and 25 deletions

View File

@@ -141,6 +141,7 @@ cc_defaults {
"renderstate/Scissor.cpp",
"renderstate/Stencil.cpp",
"renderstate/TextureState.cpp",
"renderthread/CacheManager.cpp",
"renderthread/CanvasContext.cpp",
"renderthread/OpenGLPipeline.cpp",
"renderthread/DrawFrameTask.cpp",
@@ -300,6 +301,7 @@ cc_test {
"tests/unit/BakedOpRendererTests.cpp",
"tests/unit/BakedOpStateTests.cpp",
"tests/unit/BitmapTests.cpp",
"tests/unit/CacheManagerTests.cpp",
"tests/unit/CanvasContextTests.cpp",
"tests/unit/CanvasStateTests.cpp",
"tests/unit/ClipAreaTests.cpp",

View File

@@ -50,8 +50,7 @@ TaskManager* SkiaPipeline::getTaskManager() {
}
void SkiaPipeline::onDestroyHardwareResources() {
// No need to flush the caches here. There is a timer
// which will flush temporary resources over time.
mRenderThread.cacheManager().trimStaleResources();
}
bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {

View File

@@ -45,6 +45,7 @@ class Layer;
class DeferredLayerUpdater;
namespace renderthread {
class CacheManager;
class CanvasContext;
class RenderThread;
}
@@ -55,6 +56,7 @@ class RenderState {
PREVENT_COPY_AND_ASSIGN(RenderState);
friend class renderthread::RenderThread;
friend class Caches;
friend class renderthread::CacheManager;
public:
void onGLContextCreated();
void onGLContextDestroyed();

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "CacheManager.h"
#include "Layer.h"
#include "RenderThread.h"
#include "renderstate/RenderState.h"
#include <gui/Surface.h>
#include <GrContextOptions.h>
#include <math.h>
#include <set>
namespace android {
namespace uirenderer {
namespace renderthread {
// This multiplier was selected based on historical review of cache sizes relative
// to the screen resolution. This is meant to be a conservative default based on
// that analysis. The 4.0f is used because the default pixel format is assumed to
// be ARGB_8888.
#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f)
#define BACKGROUND_RETENTION_PERCENTAGE (0.5f)
// for super large fonts we will draw them as paths so no need to keep linearly
// increasing the font cache size.
#define FONT_CACHE_MIN_MB (0.5f)
#define FONT_CACHE_MAX_MB (4.0f)
CacheManager::CacheManager(const DisplayInfo& display)
: mMaxSurfaceArea(display.w * display.h) {
mVectorDrawableAtlas.reset(new VectorDrawableAtlas);
}
void CacheManager::reset(GrContext* context) {
if (context != mGrContext.get()) {
destroy();
}
if (context) {
mGrContext = sk_ref_sp(context);
mGrContext->getResourceCacheLimits(&mMaxResources, nullptr);
updateContextCacheSizes();
}
}
void CacheManager::destroy() {
// cleanup any caches here as the GrContext is about to go away...
mGrContext.reset(nullptr);
mVectorDrawableAtlas.reset(new VectorDrawableAtlas);
}
void CacheManager::updateContextCacheSizes() {
mMaxResourceBytes = mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER;
mBackgroundResourceBytes = mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE;
mGrContext->setResourceCacheLimits(mMaxResources, mMaxResourceBytes);
}
void CacheManager::configureContext(GrContextOptions* contextOptions) {
contextOptions->fAllowPathMaskCaching = true;
float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f;
float fontCacheMB = 0;
float decimalVal = std::modf(screenMP, &fontCacheMB);
// This is a basic heuristic to size the cache to a multiple of 512 KB
if (decimalVal > 0.8f) {
fontCacheMB += 1.0f;
} else if (decimalVal > 0.5f) {
fontCacheMB += 0.5f;
}
// set limits on min/max size of the cache
fontCacheMB = std::max(FONT_CACHE_MIN_MB, std::min(FONT_CACHE_MAX_MB, fontCacheMB));
// We must currently set the size of the text cache based on the size of the
// display even though we like to be dynamicallysizing it to the size of the window.
// Skia's implementation doesn't provide a mechanism to resize the font cache due to
// the potential cost of recreating the glyphs.
contextOptions->fGlyphCacheTextureMaximumBytes = fontCacheMB * 1024 * 1024;
}
void CacheManager::trimMemory(TrimMemoryMode mode) {
if (!mGrContext) {
return;
}
mGrContext->flush();
switch (mode) {
case TrimMemoryMode::Complete:
mVectorDrawableAtlas.reset(new VectorDrawableAtlas);
mGrContext->freeGpuResources();
break;
case TrimMemoryMode::UiHidden:
mGrContext->purgeUnlockedResources(mMaxResourceBytes - mBackgroundResourceBytes, true);
break;
}
}
void CacheManager::trimStaleResources() {
if (!mGrContext) {
return;
}
mGrContext->flush();
mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
}
VectorDrawableAtlas* CacheManager::acquireVectorDrawableAtlas() {
LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() == nullptr);
LOG_ALWAYS_FATAL_IF(mGrContext == nullptr);
/**
* TODO LIST:
* 1) compute the atlas based on the surfaceArea surface
* 2) identify a way to reuse cache entries
* 3) add ability to repack the cache?
* 4) define memory conditions where we clear the cache (e.g. surface->reset())
*/
return mVectorDrawableAtlas.release();
}
void CacheManager::releaseVectorDrawableAtlas(VectorDrawableAtlas* atlas) {
LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() != nullptr);
mVectorDrawableAtlas.reset(atlas);
mVectorDrawableAtlas->isNewAtlas = false;
}
void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
if (!mGrContext) {
log.appendFormat("No valid cache instance.\n");
return;
}
size_t bytesCached;
mGrContext->getResourceCacheUsage(nullptr, &bytesCached);
log.appendFormat("Caches:\n");
log.appendFormat(" Current / Maximum\n");
log.appendFormat(" VectorDrawableAtlas %6.2f kB / %6.2f kB (entries = %zu)\n",
0.0f, 0.0f, (size_t)0);
if (renderState) {
if (renderState->mActiveLayers.size() > 0) {
log.appendFormat(" Layer Info:\n");
}
size_t layerMemoryTotal = 0;
for (std::set<Layer*>::iterator it = renderState->mActiveLayers.begin();
it != renderState->mActiveLayers.end(); it++) {
const Layer* layer = *it;
const char* layerType = layer->getApi() == Layer::Api::OpenGL ? "GlLayer" : "VkLayer";
log.appendFormat(" %s size %dx%d\n", layerType,
layer->getWidth(), layer->getHeight());
layerMemoryTotal += layer->getWidth() * layer->getHeight() * 4;
}
log.appendFormat(" Layers Total %6.2f kB (numLayers = %zu)\n",
layerMemoryTotal / 1024.0f, renderState->mActiveLayers.size());
}
log.appendFormat("Total memory usage:\n");
log.appendFormat(" %zu bytes, %.2f MB (%.2f MB is purgeable)\n",
bytesCached, bytesCached / 1024.0f / 1024.0f,
mGrContext->getResourceCachePurgeableBytes() / 1024.0f / 1024.0f);
}
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CACHEMANAGER_H
#define CACHEMANAGER_H
#include <GrContext.h>
#include <SkSurface.h>
#include <ui/DisplayInfo.h>
#include <utils/String8.h>
#include <vector>
namespace android {
class Surface;
namespace uirenderer {
class RenderState;
namespace renderthread {
class IRenderPipeline;
class RenderThread;
struct VectorDrawableAtlas {
sk_sp<SkSurface> surface;
bool isNewAtlas = true;
};
class CacheManager {
public:
enum class TrimMemoryMode {
Complete,
UiHidden
};
void configureContext(GrContextOptions* context);
void trimMemory(TrimMemoryMode mode);
void trimStaleResources();
void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
VectorDrawableAtlas* acquireVectorDrawableAtlas();
void releaseVectorDrawableAtlas(VectorDrawableAtlas*);
size_t getCacheSize() const { return mMaxResourceBytes; }
size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
private:
friend class RenderThread;
CacheManager(const DisplayInfo& display);
void reset(GrContext* grContext);
void destroy();
void updateContextCacheSizes();
const size_t mMaxSurfaceArea;
sk_sp<GrContext> mGrContext;
int mMaxResources = 0;
size_t mMaxResourceBytes = 0;
size_t mBackgroundResourceBytes = 0;
struct PipelineProps {
const void* pipelineKey = nullptr;
size_t surfaceArea = 0;
};
std::unique_ptr<VectorDrawableAtlas> mVectorDrawableAtlas;
};
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
#endif /* CACHEMANAGER_H */

View File

@@ -585,15 +585,37 @@ void CanvasContext::destroyHardwareResources() {
}
void CanvasContext::trimMemory(RenderThread& thread, int level) {
// No context means nothing to free
if (!thread.eglManager().hasEglContext()) return;
ATRACE_CALL();
if (level >= TRIM_MEMORY_COMPLETE) {
thread.renderState().flush(Caches::FlushMode::Full);
thread.eglManager().destroy();
} else if (level >= TRIM_MEMORY_UI_HIDDEN) {
thread.renderState().flush(Caches::FlushMode::Moderate);
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
case RenderPipelineType::OpenGL: {
// No context means nothing to free
if (!thread.eglManager().hasEglContext()) return;
ATRACE_CALL();
if (level >= TRIM_MEMORY_COMPLETE) {
thread.renderState().flush(Caches::FlushMode::Full);
thread.eglManager().destroy();
} else if (level >= TRIM_MEMORY_UI_HIDDEN) {
thread.renderState().flush(Caches::FlushMode::Moderate);
}
break;
}
case RenderPipelineType::SkiaGL:
case RenderPipelineType::SkiaVulkan: {
// No context means nothing to free
if (!thread.getGrContext()) return;
ATRACE_CALL();
if (level >= TRIM_MEMORY_COMPLETE) {
thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
thread.eglManager().destroy();
thread.vulkanManager().destroy();
} else if (level >= TRIM_MEMORY_UI_HIDDEN) {
thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
}
break;
}
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
break;
}
}

View File

@@ -138,7 +138,7 @@ void EglManager::initialize() {
GrContextOptions options;
options.fGpuPathRenderers &= ~GrContextOptions::GpuPathRenderers::kDistanceField;
options.fAllowPathMaskCaching = true;
mRenderThread.cacheManager().configureContext(&options);
mRenderThread.setGrContext(GrContext::Create(GrBackend::kOpenGL_GrBackend,
(GrBackendContext)glInterface.get(), options));
}

View File

@@ -469,18 +469,7 @@ uint32_t RenderProxy::frameTimePercentile(int p) {
}
CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) {
args->thread->jankTracker().dump(args->fd);
FILE *file = fdopen(args->fd, "a");
if (Caches::hasInstance()) {
String8 cachesLog;
Caches::getInstance().dumpMemoryUsage(cachesLog);
fprintf(file, "\nCaches:\n%s\n", cachesLog.string());
} else {
fprintf(file, "\nNo caches instance.\n");
}
fprintf(file, "\nPipeline=FrameBuilder\n");
fflush(file);
args->thread->dumpGraphicsMemory(args->fd);
return nullptr;
}

View File

@@ -202,6 +202,45 @@ void RenderThread::initThreadLocals() {
mRenderState = new RenderState(*this);
mJankTracker = new JankTracker(mDisplayInfo);
mVkManager = new VulkanManager(*this);
mCacheManager = new CacheManager(mDisplayInfo);
}
void RenderThread::dumpGraphicsMemory(int fd) {
jankTracker().dump(fd);
String8 cachesOutput;
String8 pipeline;
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
case RenderPipelineType::OpenGL: {
if (Caches::hasInstance()) {
cachesOutput.appendFormat("Caches:\n");
Caches::getInstance().dumpMemoryUsage(cachesOutput);
} else {
cachesOutput.appendFormat("No caches instance.");
}
pipeline.appendFormat("FrameBuilder");
break;
}
case RenderPipelineType::SkiaGL: {
mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
pipeline.appendFormat("Skia (OpenGL)");
break;
}
case RenderPipelineType::SkiaVulkan: {
mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
pipeline.appendFormat("Skia (Vulkan)");
break;
}
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
break;
}
FILE *file = fdopen(fd, "a");
fprintf(file, "\n%s\n", cachesOutput.string());
fprintf(file, "\nPipeline=%s\n", pipeline.string());
fflush(file);
}
Readback& RenderThread::readback() {
@@ -228,6 +267,14 @@ Readback& RenderThread::readback() {
return *mReadback;
}
void RenderThread::setGrContext(GrContext* context) {
mCacheManager->reset(context);
if (mGrContext.get()) {
mGrContext->releaseResourcesAndAbandonContext();
}
mGrContext.reset(context);
}
int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
ALOGE("Display event receiver pipe was closed or an error occurred. "

View File

@@ -20,6 +20,7 @@
#include "RenderTask.h"
#include "../JankTracker.h"
#include "CacheManager.h"
#include "TimeLord.h"
#include <GrContext.h>
@@ -102,11 +103,13 @@ public:
const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; }
GrContext* getGrContext() const { return mGrContext.get(); }
void setGrContext(GrContext* cxt) { mGrContext.reset(cxt); }
void setGrContext(GrContext* cxt);
CacheManager& cacheManager() { return *mCacheManager; }
VulkanManager& vulkanManager() { return *mVkManager; }
sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& skBitmap);
void dumpGraphicsMemory(int fd);
protected:
virtual bool threadLoop() override;
@@ -161,6 +164,7 @@ private:
Readback* mReadback = nullptr;
sk_sp<GrContext> mGrContext;
CacheManager* mCacheManager;
VulkanManager* mVkManager;
};

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include "renderthread/CacheManager.h"
#include "renderthread/EglManager.h"
#include "tests/common/TestUtils.h"
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
static size_t getCacheUsage(GrContext* grContext) {
size_t cacheUsage;
grContext->getResourceCacheUsage(nullptr, &cacheUsage);
return cacheUsage;
}
RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) {
DisplayInfo displayInfo = renderThread.mainDisplayInfo();
GrContext* grContext = renderThread.getGrContext();
ASSERT_TRUE(grContext != nullptr);
// create pairs of offscreen render targets and images until we exceed the backgroundCacheSizeLimit
std::vector<sk_sp<SkSurface>> surfaces;
while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) {
SkImageInfo info = SkImageInfo::MakeA8(displayInfo.w, displayInfo.h);
sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kYes, info);
surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT);
grContext->flush();
surfaces.push_back(surface);
}
ASSERT_TRUE(1 < surfaces.size());
// attempt to trim all memory while we still hold strong refs
renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
// free the surfaces
for (size_t i = 0; i < surfaces.size(); i++) {
ASSERT_TRUE(surfaces[i]->unique());
surfaces[i].reset();
}
// verify that we have enough purgeable bytes
const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes();
ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes);
// UI hidden and make sure only some got purged
renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
ASSERT_TRUE(0 < grContext->getResourceCachePurgeableBytes());
ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() > getCacheUsage(grContext));
// complete and make sure all get purged
renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
}