-
egl 关于缓存的优化
涉及文件:lib/egl/egl_cache.h lib/egl/egl_cache.cpp lib/egl/FileBlobCache.h lib/egl/FileBlobCache.cpp lib/egl/BlobCache.h lib/egl/BlobCache.cpp 其中FileBlobCache 继承至BlobCache BlobCache 可以将要存的内容按关键字存在对应的内存数组中,并保存在固定的文件里。在下次启动的时候并加载进内存。 属于内容存储模块。
FileBlobCache 里面有一个
std::string mFilename;
为保存 egl 保存的缓存内存 存在设备的文件对应路劲
在具体设备中路劲如:
/data/user_de/0/com.tclhz.gallery/code_cache/com.android.opengl.shaders_cache 保存了tcl相册 的缓存内存内容
再看看 egl 怎么使用的:
其在egl_cache.cpp 中有如下代码:
void egl_cache_t::initialize(egl_display_t* display) {
std::lock_guardstd::mutex lock(mMutex);
egl_connection_t* const cnx = &gEGLImpl;
if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
const char* exts = display->disp.queryString.extensions;
size_t bcExtLen = strlen(BC_EXT_STR);
size_t extsLen = strlen(exts);
bool equal = !strcmp(BC_EXT_STR, exts);
bool atStart = !strncmp(BC_EXT_STR " ", exts, bcExtLen + 1);
bool atEnd = (bcExtLen + 1) < extsLen &&
!strcmp(" " BC_EXT_STR, exts + extsLen - (bcExtLen + 1));
bool inMiddle = strstr(exts, " " BC_EXT_STR " ") != nullptr;
if (equal || atStart || atEnd || inMiddle) {
PFNEGLSETBLOBCACHEFUNCSANDROIDPROC eglSetBlobCacheFuncsANDROID;
****//获取 egl 设置缓存的接口
eglSetBlobCacheFuncsANDROID = reinterpret_cast<PFNEGLSETBLOBCACHEFUNCSANDROIDPROC>(
cnx->egl.eglGetProcAddress("eglSetBlobCacheFuncsANDROID"));****
if (eglSetBlobCacheFuncsANDROID == nullptr) {
ALOGE("EGL_ANDROID_blob_cache advertised, "
"but unable to get eglSetBlobCacheFuncsANDROID");
return;
}
//设置egl缓存的接口
eglSetBlobCacheFuncsANDROID(display->disp.dpy, android::setBlob, android::getBlob);
EGLint err = cnx->egl.eglGetError();
if (err != EGL_SUCCESS) {
ALOGE("eglSetBlobCacheFuncsANDROID resulted in an error: "
"%#x",
err);
}
}
}
mInitialized = true;
}
然后交由egl 实现库处理 保存哪些内容。
然后在egl_cache
void egl_cache_t::terminate() {
std::lock_guard<std::mutex> lock(mMutex);
if (mBlobCache) {
**mBlobCache->writeToFile();**
}
mBlobCache = nullptr;
}
保存到设备。
下次创建FileBlobCache的时候 从文件内读出内容 直接将缓存可复用内容加载进内存 直接使用。
具体保存了啥 不是很清楚,由于是实现库中处理保存那些内容,libEGL只提供保存方式(存储方法,主要为BlobCache)。 但从com.android.opengl.shaders_cache 文件命名来看 主要是保存的应该是shader链接后的代码,下次创建shader的时候不再需要执行链接 编译操作。
- 硬件渲染hwui关于缓存的优化 (只关注opengl 优化)
说到底 hwui 本质上是skia 库的 vk 或者 opengl 等的一种实现方式,只是 如 使用opengl 相当于 skia 库使用 GPU 绘制图像。 这里我们主要讨论hwui 中 RenderThread 使用opengl时候 存在哪些缓存优化.
主要涉及文件:
/frameworks/base/libs/hwui/renderthread/RenderThread.h
/frameworks/base/libs/hwui/renderthread/RenderThread.cpp
/frameworks/base/libs/hwui/renderthread/CacheManager.h
/frameworks/base/libs/hwui/renderthread/CacheManager.cpp
/frameworks/base/libs/hwui/pipeline/skia/ShaderCache.h
/frameworks/base/libs/hwui/pipeline/skia/ShaderCache.cpp
lib/egl/FileBlobCache.h
lib/egl/FileBlobCache.cpp
lib/egl/BlobCache.h
lib/egl/BlobCache.cpp
/external/skia/src/gpu/gl/builders/GrGLProgramBuilder.cpp
lib/egl/FileBlobCache.h
lib/egl/FileBlobCache.cpp
lib/egl/BlobCache.h
lib/egl/BlobCache.cpp 这三个文件 依旧是前面存储数据的存储部分。 相对简单 我们只需要知道FileBlobCache 负责存储内容到设备,BlobCache 主要负责缓存数据到内存。 存在set 和 get接口(不过顺便说一句,这里保存数据 和 从文件读数据到设备到内存的方式还是挺有意思的,直接以数据结构的形式存储还是相当不错的,值得一看,就不讲细节代码了,太麻烦)
ShaderCache 里面有个FileBlobCache 成员变量
std::unique_ptr mBlobCache;
负责缓存内容的读写,和读写设备存储的缓存内容。这里缓存的是啥 估计从名字大家能想到 就是opengl的Shader相关的数据,具体是啥后面再说。
好 那 ShaderCache和 skia opegl hwui 怎么联系起来的呢。
hwui 整体逻辑结构我就不讲了 和这里关系不大,而且也相对复杂,说不完的哈哈
hwui 硬件渲染的时候 其OpenGLPipeline (opengl 绘制Pipeline )在绘制前 会先请求RenderThread准备好GL绘制环境,具体有些啥 也挺多的 你大致理解为准备好 我们的skia opengl 环境就好
然后在RenderThread.cpp 就有如下:
void RenderThread::requireGlContext() {
if (mEglManager->hasEglContext()) {
return;
}
mEglManager->initialize();
sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface());
LOG_ALWAYS_FATAL_IF(!glInterface.get());
GrContextOptions options;
// 这里的options 我看可能和性能优化有关 下一小节具体看看各个参数是什么鬼
initGrContextOptions(options);
auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
auto size = glesVersion ? strlen(glesVersion) : -1;
// 缓存的主要来看 这一行
cacheManager().configureContext(&options, glesVersion, size);
sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
LOG_ALWAYS_FATAL_IF(!grContext.get());
setGrContext(grContext);
}
也就是RenderThread 有一个
CacheManager* mCacheManager;
这个里面我看还有其他有几个主要接口
void trimMemory(TrimMemoryMode mode);
void trimStaleResources();
void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
void onFrameCompleted();
这里面
void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
void onFrameCompleted();
和缓存统计数据 我这个是读缓存信息 具体有哪些信息 可以从后面清除缓存 来看 这个和下面内容一致 就不看了
重点是
void trimMemory(TrimMemoryMode mode);
void trimStaleResources();
这两个可能和内存优化有关 后面会简单看看并说说看看具体干了啥。
好 继续看 cacheManager().configureContext(&options, glesVersion, size);
void CacheManager::configureContext(GrContextOptions* contextOptions, const void* identity,
ssize_t size) {
contextOptions->fAllowPathMaskCaching = true;
contextOptions->fGlyphCacheTextureMaximumBytes = mMaxGpuFontAtlasBytes;
contextOptions->fExecutor = &sDefaultExecutor;
auto& cache = skiapipeline::ShaderCache::get();
// 初始化cache 管理对象
cache.initShaderDiskCache(identity, size);
// 创建 gl context 的参数
contextOptions->fPersistentCache = &cache;
contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
}
这里红色三行 可以看到 一个进程只有一个ShaderCache 且将cache 复制给fPersistentCache
最终作为创建grContext 的参数 并且initShaderDiskCache 会创建对应的FileBlobCache ,FileBlobCache在创建的过程中 会尝试(如果已经保存了内存到文件)从文件加载缓存到内存
sk_sp grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
由grContext 也即为libskia 库初始化context使用
那接下来就看libskia (其实这里也挺好玩,这不就是相当于直接改了libskia库实现了么,我记得好像以前的安卓版本不是这样的)做了些啥
options 的 fPersistentCache 最终会在 skia 库中 创建 GrDirectContext 初始化函数bool GrDirectContext::init()
中使用 将 其保存在GrDirectContext 的成员变量中
fPersistentCache = this->options().fPersistentCache;
这样libskia 库就有其缓存管理对象
那剩下的就只剩下 libskia如何使用这个fPersistentCache了
其实使用这个玩意目前只有/external/skia/src/gpu/gl/builders/GrGLProgramBuilder.cpp
从名字就能看出来 就是创建GLProgram 时候使用,就看看怎么用的吧
对于缓存的操作无非就是读写用
首先看写是怎么写的,写了些啥内容吧:
void GrGLProgramBuilder::storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID,
const SkSL::String shaders[], bool isSkSL,
SkSL::Program::Settings* settings) {
if (!this->gpu()->getContext()->priv().getPersistentCache()) {
return;
}
sk_sp<SkData> key = SkData::MakeWithoutCopy(this->desc().asKey(), this->desc().keyLength());
SkString description = GrProgramDesc::Describe(fProgramInfo, *fGpu->caps());
if (fGpu->glCaps().programBinarySupport()) {
// binary cache
GrGLsizei length = 0;
GL_CALL(GetProgramiv(programID, GL_PROGRAM_BINARY_LENGTH, &length));
if (length > 0) {
SkBinaryWriteBuffer writer;
writer.writeInt(GrPersistentCacheUtils::GetCurrentVersion());
writer.writeUInt(kGLPB_Tag);
writer.writePad32(&inputs, sizeof(inputs));
SkAutoSMalloc<2048> binary(length);
GrGLenum binaryFormat;
// 获取program 编译链接后的程序Binary 这个GetProgramBinary 对opengl 有一点了解的 应该都会知道 就是 program 链接和编译后的二进制结果
// 下次创建program时候直接使用这个Binary 就可以省去了耗时的链接和编译过程了
GL_CALL(GetProgramBinary(programID, length, &length, &binaryFormat, binary.get()));
writer.writeUInt(binaryFormat);
writer.writeInt(length);
writer.writePad32(binary.get(), length);
auto data = writer.snapshotAsData();
// 写入缓存
this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data, description);
}
} else {
。。。。。。。。。。。
}
那如何读的呢 :
读比较简单:
在创建program的开始
sk_sp<GrGLProgram> GrGLProgramBuilder::CreateProgram(
GrDirectContext* dContext,
const GrProgramDesc& desc,
const GrProgramInfo& programInfo,
const GrGLPrecompiledProgram* precompiledProgram) {
TRACE_EVENT0_ALWAYS("skia.shaders", "shader_compile");
GrAutoLocaleSetter als("C");
GrGLGpu* glGpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());
// create a builder. This will be handed off to effects so they can use it to add
// uniforms, varyings, textures, etc
GrGLProgramBuilder builder(glGpu, desc, programInfo);
auto persistentCache = dContext->priv().getPersistentCache();
if (persistentCache && !precompiledProgram) {
sk_sp<SkData> key = SkData::MakeWithoutCopy(desc.asKey(), desc.keyLength());
// 读缓存到fCached,接下来看这玩意怎么用的
builder.fCached = persistentCache->load(*key);
// the eventual end goal is to completely skip emitAndInstallProcs on a cache hit, but it's
// doing necessary setup in addition to generating the SkSL code. Currently we are only able
// to skip the SkSL->GLSL step on a cache hit.
}
if (!builder.emitAndInstallProcs()) {
return nullptr;
}
// 下一步用缓存 创建 program
return builder.finalize(precompiledProgram);
}
好 看看怎么用的
sk_sp<GrGLProgram> GrGLProgramBuilder::finalize(const GrGLPrecompiledProgram* precompiledProgram) {
TRACE_EVENT0("skia.shaders", TRACE_FUNC);
// verify we can get a program id
GrGLuint programID;
if (precompiledProgram) {
programID = precompiledProgram->fProgramID;
} else {
GL_CALL_RET(programID, CreateProgram());
}
if (0 == programID) {
return nullptr;
}
if (this->gpu()->glCaps().programBinarySupport() &&
this->gpu()->glCaps().programParameterSupport() &&
this->gpu()->getContext()->priv().getPersistentCache() &&
!precompiledProgram) {
GL_CALL(ProgramParameteri(programID, GR_GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GR_GL_TRUE));
}
this->finalizeShaders();
// compile shaders and bind attributes / uniforms
auto errorHandler = this->gpu()->getContext()->priv().getShaderErrorHandler();
const GrGeometryProcessor& geomProc = this->geometryProcessor();
SkSL::Program::Settings settings;
settings.fFlipY = this->origin() != kTopLeft_GrSurfaceOrigin;
settings.fSharpenTextures =
this->gpu()->getContext()->priv().options().fSharpenMipmappedTextures;
settings.fFragColorIsInOut = this->fragColorIsInOut();
SkSL::Program::Inputs inputs;
SkTDArray<GrGLuint> shadersToDelete;
bool checkLinked = !fGpu->glCaps().skipErrorChecks();
bool cached = fCached.get() != nullptr;
bool usedProgramBinaries = false;
SkSL::String glsl[kGrShaderTypeCount];
SkSL::String* sksl[kGrShaderTypeCount] = {
&fVS.fCompilerString,
&fGS.fCompilerString,
&fFS.fCompilerString,
};
SkSL::String cached_sksl[kGrShaderTypeCount];
if (precompiledProgram) {
// This is very similar to when we get program binaries. We even set that flag, as it's
// used to prevent other compile work later, and to force re-querying uniform locations.
this->addInputVars(precompiledProgram->fInputs);
this->computeCountsAndStrides(programID, geomProc, false);
usedProgramBinaries = true;
} else if (cached) {
TRACE_EVENT0_ALWAYS("skia.shaders", "cache_hit");
SkReadBuffer reader(fCached->data(), fCached->size());
SkFourByteTag shaderType = GrPersistentCacheUtils::GetType(&reader);
switch (shaderType) {
case kGLPB_Tag: {
// Program binary cache hit. We may opt not to use this if we don't trust program
// binaries on this driver
if (!fGpu->glCaps().programBinarySupport()) {
cached = false;
break;
}
reader.readPad32(&inputs, sizeof(inputs));
GrGLenum binaryFormat = reader.readUInt();
GrGLsizei length = reader.readInt();
// 读到 binary
const void* binary = reader.skip(length);
if (!reader.isValid()) {
break;
}
this->gpu()→clearErrorsAndCheckForOOM();
// 通过glProgramBinary 创建program
GR_GL_CALL_NOERRCHECK(this->gpu()->glInterface(),
ProgramBinary(programID, binaryFormat,
const_cast<void*>(binary), length));
if (this->gpu()->getErrorAndCheckForOOM() == GR_GL_NO_ERROR) {
if (checkLinked) {
cached = this->checkLinkStatus(programID, errorHandler, nullptr, nullptr);
}
if (cached) {
this->addInputVars(inputs);
this->computeCountsAndStrides(programID, geomProc, false);
}
} else {
cached = false;
}
usedProgramBinaries = cached;
break;
}
。。。。。。。。。。。。
}
if (!reader.isValid()) {
cached = false;
}
}
if (!usedProgramBinaries) {
.....................
//省略 无缓存的时候 创建program方法
}
this->resolveProgramResourceLocations(programID, usedProgramBinaries);
cleanup_shaders(fGpu, shadersToDelete);
// We temporarily can't cache tessellation shaders while using back door GLSL.
//
// We also can't cache SkSL or GLSL if we were given a precompiled program, but there's not
// much point in doing so.
if (!cached && !geomProc.willUseTessellationShaders() && !precompiledProgram) {
// FIXME: Remove the check for tessellation shaders in the above 'if' once the back door
// GLSL mechanism is removed.
(void)&GrGLSLGeometryProcessor::getTessControlShaderGLSL;
bool isSkSL = false;
if (fGpu->getContext()->priv().options().fShaderCacheStrategy ==
GrContextOptions::ShaderCacheStrategy::kSkSL) {
for (int i = 0; i < kGrShaderTypeCount; ++i) {
glsl[i] = GrShaderUtils::PrettyPrint(*sksl[i]);
}
isSkSL = true;
}
// 如果未缓存 创建program的时候缓存内容
this->storeShaderInCache(inputs, programID, glsl, isSkSL, &settings);
}
return this->createProgram(programID);
}
好了 简单了 这就是hwui 更准确的说是 skia gpu 库对opengl program的优化的全部
相对还是比较简单 主要就是对program 的二进制结果 缓存 并利用vendor库实现的glProgramBinary 创建program
到此 hwui shder缓存就说完了