Android-View绘制原理(14)-RenderPipeline

在上一篇关于帧绘制的原理中,做好了EGLSuface切换,同步好了UI的更新,为需要进行GPU绘制的RenderNode创好了SKSurface, 最后通过ANativeWindow为下一帧调用了dequeueBuffer。所有的资源和数据都准备好了,从而可以进行绘制,这个任务将由RenderPipeline来完成。我们先不考虑Fence的逻辑,直接接着上一篇文章,从CanvasContext.draw方法出发。

1 CanvasContext.draw

frameworks/base/libs/hwui/renderthread/CanvasContext.cpp

nsecs_t CanvasContext::draw() {
    ...
    Frame frame = mRenderPipeline->getFrame();
    SkRect windowDirty = computeDirtyRect(frame, &dirty);

    bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                      mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
                                      &(profiler()));
    ...
   bool didSwap =  mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);

    ...
     for (auto& func : mFrameCompleteCallbacks) {
            std::invoke(func, frameCompleteNr);
        }
        mFrameCompleteCallbacks.clear();
    }
   ...
    cleanupResources();
    mRenderThread.cacheManager().onFrameCompleted();
    return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
}

这个draw方法比较复杂,这里摘取主要逻辑来分析一下。可以看到具体的绘制是继续委托到mRenderPipeline去完成的,仅仅分析一下使用OpenGL绘制的情况,因此对应的SkiaOpenGLPipeline。这里主要由这个几个步骤

  • 创建Frame对象
  • 调用mRenderPipeline->draw进行绘制
  • mRenderPipeline->swapBuffers 切换GragphicBuffer
  • 回调FrameCompleteCalback。

下面就按这个流程来分析

2 mRenderPipeline->getFrame

Frame是一个帧的模型

class Frame {
public:
    Frame(int32_t width, int32_t height, int32_t bufferAge)
            : mWidth(width), mHeight(height), mBufferAge(bufferAge) {}

    int32_t width() const { return mWidth; }
    int32_t height() const { return mHeight; }
    int32_t bufferAge() const { return mBufferAge; }

private:
    Frame() {}
    friend class EglManager;

    int32_t mWidth;
    int32_t mHeight;
    int32_t mBufferAge;

    EGLSurface mSurface;

    // Maps from 0,0 in top-left to 0,0 in bottom-left
    // If out is not an int32_t[4] you're going to have a bad time
    void map(const SkRect& in, int32_t* out) const;
};

它封装的是一个EGLSurface, 前面分析过,EGLSurface关联着一个ANativeWindow, 也就是一个Surface对象,所以Frame可以代表一个Surface对象。

frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp

Frame SkiaOpenGLPipeline::getFrame() {
    LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
                        "drawRenderNode called on a context with no surface!");
    return mEglManager.beginFrame(mEglSurface);
}

进入到mEglManager的beginFrame方法

Frame EglManager::beginFrame(EGLSurface surface) {
    LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE, "Tried to beginFrame on EGL_NO_SURFACE!");
    makeCurrent(surface);
    Frame frame;
    frame.mSurface = surface;
    eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, &frame.mWidth);
    eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, &frame.mHeight);
    frame.mBufferAge = queryBufferAge(surface);
    eglBeginFrame(mEglDisplay, surface);
    return frame;
}

将这个surface切换到当前后,新创建一个Frame对象,并将surface赋给这个frame对象,后设在对象的长宽属性等,然后返回这个新的Frame对象。

3 mRenderPipeline->draw

使用OpenGL来绘制的时候,mRenderPipeline是一个SkiaOpenGLPipeline对象,它是SkiaPipeline的子类,我们来分析一下它的draw方法

frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp

bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
                              const LightGeometry& lightGeometry,
                              LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
                              bool opaque, const LightInfo& lightInfo,
                              const std::vector<sp<RenderNode>>& renderNodes,
                              FrameInfoVisualizer* profiler) {
    ...
    GrGLFramebufferInfo fboInfo;
    fboInfo.fFBOID = 0;
    ....
    GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
    ...
    SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
     sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(
            mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType,
            mSurfaceColorSpace, &props));
     ...
    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
                SkMatrix::I());

    ...
    {
        ATRACE_NAME("flush commands");
        surface->flushAndSubmit();
    }
    layerUpdateQueue->clear();
    
    return true;
}

这里首先生成了一个GrGLFramebufferInfo和GrBackendRenderTarget,它是属于skia库里的api,但是只包含一些j简单的属性信息。

external/skia/include/gpu/gl/GrGLTypes.h

struct GrGLFramebufferInfo {
    GrGLuint fFBOID;
    GrGLenum fFormat = 0;

    bool operator==(const GrGLFramebufferInfo& that) const {
        return fFBOID == that.fFBOID && fFormat == that.fFormat;
    }
};

external/skia/src/gpu/GrBackendSurface.cpp

GrBackendRenderTarget::GrBackendRenderTarget(int width,
                                             int height,
                                             int sampleCnt,
                                             int stencilBits,
                                             const GrGLFramebufferInfo& glInfo)
        : fWidth(width)
        , fHeight(height)
        , fSampleCnt(std::max(1, sampleCnt))
        , fStencilBits(stencilBits)
        , fBackend(GrBackendApi::kOpenGL)
        , fGLInfo(glInfo) {
    fIsValid = SkToBool(glInfo.fFormat); // the glInfo must have a valid format
}

随后调用SkSurface::MakeFromBackendRenderTarget生成一个SkSurface,这是Skia在GPU上申请用于绘制的surface。我们来看看这个sksurface 的生成流程。

sk_sp<SkSurface> SkSurface::MakeFromBackendRenderTarget(GrRecordingContext* context,
                                                        const GrBackendRenderTarget& rt,
                                                        GrSurfaceOrigin origin,
                                                        SkColorType colorType,
                                                        sk_sp<SkColorSpace> colorSpace,
                                                        const SkSurfaceProps* props,
                                                        SkSurface::RenderTargetReleaseProc relProc,
                                                        SkSurface::ReleaseContext releaseContext) {
    ...
    auto sdc = GrSurfaceDrawContext::MakeFromBackendRenderTarget(context,
                                                                 grColorType,
                                                                 std::move(colorSpace),
                                                                 rt,
                                                                 origin,
                                                                 SkSurfacePropsCopyOrDefault(props),
                                                                 std::move(releaseHelper));
    if (!sdc) {
        return nullptr;
    }

    auto device = SkGpuDevice::Make(std::move(sdc), SkGpuDevice::kUninit_InitContents);
    if (!device) {
        return nullptr;
    }

    return sk_make_sp<SkSurface_Gpu>(std::move(device));
}

首先生成一个GrSurfaceDrawContext的对象sdc,由它来持有上面生成的GrBackendRenderTarget和GrRecordingContext,

sk_sp<SkGpuDevice> SkGpuDevice::Make(std::unique_ptr<GrSurfaceDrawContext> surfaceDrawContext,
                                     InitContents init) {
    if (!surfaceDrawContext) {
        return nullptr;
    }

    GrRecordingContext* rContext = surfaceDrawContext->recordingContext();
    if (rContext->abandoned()) {
        return nullptr;
    }

    SkColorType ct = GrColorTypeToSkColorType(surfaceDrawContext->colorInfo().colorType());

    unsigned flags;
    if (!rContext->colorTypeSupportedAsSurface(ct) ||
        !CheckAlphaTypeAndGetFlags(nullptr, init, &flags)) {
        return nullptr;
    }
    return sk_sp<SkGpuDevice>(new SkGpuDevice(std::move(surfaceDrawContext), flags));
}

SkGpuDevice::SkGpuDevice(std::unique_ptr<GrSurfaceDrawContext> surfaceDrawContext, unsigned flags)
        : INHERITED(make_info(surfaceDrawContext.get(), SkToBool(flags & kIsOpaque_Flag)),
                    surfaceDrawContext->surfaceProps())
        , fContext(sk_ref_sp(surfaceDrawContext->recordingContext()))
        , fSurfaceDrawContext(std::move(surfaceDrawContext))
#if !defined(SK_DISABLE_NEW_GR_CLIP_STACK)
        , fClip(SkIRect::MakeSize(fSurfaceDrawContext->dimensions()),
                &this->asMatrixProvider(),
                force_aa_clip(fSurfaceDrawContext.get())) {
#else
        , fClip(fSurfaceDrawContext->dimensions(), &this->cs(), &this->asMatrixProvider()) {
#endif
    if (flags & kNeedClear_Flag) {
        this->clearAll();
    }
}

这里创将了一个SkGpuDevice对象,它的成员变量 fContext 是通过外部传入的 surfaceDrawContext 调用 recordingContext 方法的得来的,而这个surfaceDrawContext就是上面的 sdc 局部变量,它的 recordingContext 实质上来自 mRenderThread.getGrContext() 方法。因此 SkGpuDevice的fContext指向的是mRenderThread.getGrContext()返回的对象

之后此构建了一个SkSurface_Gpu对象,它是SkSurface的子类,因为传入的是SkGpuDevice,所以绘制命令将通过它提交到GPU进行像素渲染。准备好了SkSurface之后,调用renderFrame在该SkSurface上绘制。最后调用 surface->flushAndSubmit();提交到GPU。这里的内容比较多,在后面的文章中再展开。

4 总结

本文分析了帧绘制的流程,这个抽象成了一个RenderPipeline,根据使用不同渲染引擎,提供了SkiaOpenGLPipeline和SkiaVulkanPipeline两个实现,本文仅仅分析SkiaOpenGLPipeline,它的绘制总共被分成了3个步骤:

  • 创建SkSurface
  • renderFrame 将记录的描述数据记录的SkSurface
  • flushAndSubmit 提交绘制命令到GPU进行像素渲染

在更大的视角上看,绘制的步骤包含:

  • 绘制前准备Frame模型
  • SkiaOpenGLPipeline 调用GPU绘制
  • mRenderPipeline->swapBuffers 通知HWComposer进行屏幕合成
  • 回调FrameCompleteCalback结尾
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容