安卓源码绘制关于cache缓存优化简单了解(libegl 和 libskia库)

  1. 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的时候不再需要执行链接 编译操作。

  1. 硬件渲染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缓存就说完了

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值