Skia在Android中的作用

Skia

Skia库是一个基于2D的跨平台渲染器。所在的目录是external/skia

  • include/:包含公共头文件
  • src/:包含src代码

顶级makefile位于根目录:Android.mk

Android中的调用

Skia2d的渲染器,SkiaGLSkiaVulkan,2d渲染器全部都是Skia,除了我们直接使用openGL

C++ 复制代码
RenderPipelineType Properties::getRenderPipelineType() {
    if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
        return sRenderPipelineType;
    }
    char prop[PROPERTY_VALUE_MAX];
    property_get(PROPERTY_RENDERER, prop, "skiagl");
    if (!strcmp(prop, "skiagl")) {
        ALOGD("Skia GL Pipeline");
        sRenderPipelineType = RenderPipelineType::SkiaGL;
    } else if (!strcmp(prop, "skiavk")) {
        ALOGD("Skia Vulkan Pipeline");
        sRenderPipelineType = RenderPipelineType::SkiaVulkan;
    } else {  //"opengl"
        ALOGD("HWUI GL Pipeline");
        sRenderPipelineType = RenderPipelineType::OpenGL;
    }
    return sRenderPipelineType;
}
  • 下面是使用skiagl&SkiaVulkanOpenGL的不同的trace流程图。

以默认的skiagl举例,我们去看下具体的渲染流程,Skia是如何具体的使用。RenderThread的流程我们一般都可以从draw()函数开始追踪。mRenderPipeline的赋值是在CanvasContext的构造函数中。

scss 复制代码
void CanvasContext::draw() {
    ATRACE_NAME("CanvasContext::draw");
    ...
    bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                      mContentDrawBounds, mOpaque, mWideColorGamut, mLightInfo,
                                      mRenderNodes, &(profiler()));
                                      
      ...
}

按照流程我们使用的是SkiaGL,最终mRenderPipeline的实现类是SkiaOpenGLPipeline,接下来追踪一下SkiaOpenGLPipeline中的draw函数。

C++ 复制代码
CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
                                     RenderNode* rootRenderNode, IContextFactory* contextFactory) {
    auto renderType = Properties::getRenderPipelineType();

    switch (renderType) {
        case RenderPipelineType::OpenGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                     std::make_unique<OpenGLPipeline>(thread));
        case RenderPipelineType::SkiaGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                     std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
        case RenderPipelineType::SkiaVulkan:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                     std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
        default:
            LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
            break;
    }
    return nullptr;
}

这里要先聊一个点:SkiaOpenGLPipelineSkiaVulkanPipeline,这两个类都是继承SkiaPipeline。目前对这两个的核心区别还没有去分析清楚,后面分析清楚了再写一篇。

C++ 复制代码
class SkiaVulkanPipeline : public SkiaPipeline {
    ...
}
class SkiaOpenGLPipeline : public SkiaPipeline {
    ...
}

回到上文,在 CanvasContext::draw()中渲染相关最终是调用的 mRenderPipeline->drawmRenderPipeline最终指向的是SkiaOpenGLPipeline类,在这个函数之中,最终调用到renderFrame函数,renderFrame函数是SkiaOpenGLPipelineSkiaVulkanPipeline的共同父类SkiaPipeline中实现的。

C++ 复制代码
bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
                              const FrameBuilder::LightGeometry& lightGeometry,
                              LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
                              bool opaque, bool wideColorGamut,
                              const BakedOpRenderer::LightInfo& lightInfo,
                              const std::vector<sp<RenderNode>>& renderNodes,
                              FrameInfoVisualizer* profiler) {
    ATRACE_NAME("SkiaOpenGLPipeline::draw");
    mEglManager.damageFrame(frame, dirty);

    // setup surface for fbo0
    GrGLFramebufferInfo fboInfo;
    fboInfo.fFBOID = 0;
    GrPixelConfig pixelConfig =
            wideColorGamut ? kRGBA_half_GrPixelConfig : kRGBA_8888_GrPixelConfig;

    GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE,
                                    pixelConfig, fboInfo);

    SkSurfaceProps props(0, kUnknown_SkPixelGeometry);

    SkASSERT(mRenderThread.getGrContext() != nullptr);
    //SkSurface 需要关注
    sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(
            mRenderThread.getGrContext(), backendRT, kBottomLeft_GrSurfaceOrigin, nullptr, &props));

    SkiaPipeline::updateLighting(lightGeometry, lightInfo);
    //最终是调用的`renderFrame`,
    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, contentDrawBounds,
                surface);
    layerUpdateQueue->clear();

    ...
    return true;
}

接下来我们看SkiaPipelinerenderFrame,这里面我们看到绘制图层,SkCanvas的初始化,最终我们看到到最后的刷新指令函数。我们就跟着刷新指令的流程去一探Skia

C++ 复制代码
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                               const std::vector<sp<RenderNode>>& nodes, bool opaque,
                               bool wideColorGamut, const Rect& contentDrawBounds,
                               sk_sp<SkSurface> surface) {
    ATRACE_BEGIN("SkiaPipeline::renderFrame");
    renderVectorDrawableCache();

    // draw all layers up front
    renderLayersImpl(layers, opaque, wideColorGamut);

    // initialize the canvas for the current frame, that might be a recording canvas if SKP
    // capture is enabled.
    std::unique_ptr<SkPictureRecorder> recorder;
    
    SkCanvas* canvas = tryCapture(surface.get());

    renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas);

    endCapture(surface.get());

    if (CC_UNLIKELY(Properties::debugOverdraw)) {
        renderOverdraw(layers, clip, nodes, contentDrawBounds, surface);
    }
    ATRACE_END();

    ATRACE_BEGIN("flush commands");
    surface->getCanvas()->flush();
    ATRACE_END();
}

SkSurface

上面的代码中SkCanvas这个画布的创建是通过tryCapture函数,我们简单看一下,这个主要是进行一个调试使用,打开对应的属性控制就可以将对应的指令进行存储,调试,分析,优化。一般情况下就是直接返回了surface->getCanvas(),也就是主角SkSurface

C++ 复制代码
SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
        bool recordingPicture = mCaptureSequence > 0;
        char prop[PROPERTY_VALUE_MAX] = {'\0'};
        if (!recordingPicture) {
            property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
            recordingPicture = prop[0] != '0' &&
                               mCapturedFile != prop;  // ensure we capture only once per filename
            if (recordingPicture) {
                mCapturedFile = prop;
                mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
            }
        }
        if (recordingPicture) {
            mRecorder.reset(new SkPictureRecorder());
            return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
                                             SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
        }
    }
    return surface->getCanvas();
}

在这里我们看到frameworks/libs/hwui/android.bp文件中,我们看到对应的skia引用。

bp 复制代码
include_dirs: [
    "external/skia/include/private",
    "external/skia/src/core",
    "external/skia/src/effects",
    "external/skia/src/image",
    "external/skia/src/utils",
    "external/skia/src/gpu",
    "external/skia/src/shaders",
],
c++ 复制代码
class SK_API SkSurface : public SkRefCnt {

    /** Returns SkCanvas that draws into SkSurface. Subsequent calls return the same SkCanvas.
        SkCanvas returned is managed and owned by SkSurface, and is deleted when SkSurface
        is deleted.

        @return  drawing SkCanvas for SkSurface
    */
    SkCanvas* getCanvas();

}
C++ 复制代码
SkCanvas* SkSurface::getCanvas() {
    return asSB(this)->getCachedCanvas();
}
C++ 复制代码
std::unique_ptr<SkCanvas>   fCachedCanvas;

SkCanvas* SkSurface_Base::getCachedCanvas() {
    if (nullptr == fCachedCanvas) {
        fCachedCanvas = std::unique_ptr<SkCanvas>(this->onNewCanvas());
        if (fCachedCanvas) {
            fCachedCanvas->setSurfaceBase(this);
        }
    }
    return fCachedCanvas.get();
}



SkCanvas* SkSurface_Gpu::onNewCanvas() {
    SkCanvas::InitFlags flags = SkCanvas::kDefault_InitFlags;
    flags = static_cast<SkCanvas::InitFlags>(flags | SkCanvas::kConservativeRasterClip_InitFlag);

    return new SkCanvas(fDevice.get(), flags);
}

最终其实就是一个SkCanvas,这里做一个缓存,避免重复的Allocate。接下来我们看下对应的SkCanvas中的flush函数,这里我们看到是一个一个的SkCanvas做的指令刷新,而我们最终看到的页面肯定是不是单一的画布,这也就是SurfaceFlingerHWC重新做合成的原因。最终调用是SkBaseDevice

SkBaseDevice

我们来看SkBaseDevice;。它一共有三个派生的子类,我们要回头确定一下我们使用的对应的设备类型, sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget( mRenderThread.getGrContext(), backendRT, kBottomLeft_GrSurfaceOrigin, nullptr, &props));,对应初始化的实现在SkGpuDeviceSkGpuDeviceSkClipStackDevice的子类。

Android中也大多数情况下使用过的都是SkGpuDevice类型

接下来接着追踪SkGpuDevice中的flush函数。

C++ 复制代码
void SkCanvas::flush() {
    this->onFlush();
}

void SkCanvas::onFlush() {
    SkBaseDevice* device = this->getDevice();
    if (device) {
        device->flush();
    }
}

void SkGpuDevice::flush() {
    this->flushAndSignalSemaphores(0, nullptr);
}


GrSemaphoresSubmitted SkGpuDevice::flushAndSignalSemaphores(int numSemaphores,
                                                            GrBackendSemaphore signalSemaphores[]) {
    ASSERT_SINGLE_OWNER

    return fRenderTargetContext->prepareForExternalIO(numSemaphores, signalSemaphores);
}


GrSemaphoresSubmitted GrRenderTargetContext::prepareForExternalIO(
        int numSemaphores, GrBackendSemaphore backendSemaphores[]) {
    ASSERT_SINGLE_OWNER
    if (this->drawingManager()->wasAbandoned()) { return GrSemaphoresSubmitted::kNo; }
    SkDEBUGCODE(this->validate();)
    GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "prepareForExternalIO", fContext);

    return this->drawingManager()->prepareSurfaceForExternalIO(fRenderTargetProxy.get(),
                                                               numSemaphores,
                                                               backendSemaphores);
}

GrDrawingManager

C++ 复制代码
GrSemaphoresSubmitted GrDrawingManager::prepareSurfaceForExternalIO(
        GrSurfaceProxy* proxy, int numSemaphores, GrBackendSemaphore backendSemaphores[]) {
    if (this->wasAbandoned()) {
        return GrSemaphoresSubmitted::kNo;
    }
    SkASSERT(proxy);

    GrSemaphoresSubmitted result = GrSemaphoresSubmitted::kNo;
    // 这里就是我们传入SkSurfaceProps,如果这里面存在未处理的I/O操作,就做一个提交操作
    if (proxy->priv().hasPendingIO() || numSemaphores) {
        result = this->flush(proxy, numSemaphores, backendSemaphores);
    }

    if (!proxy->instantiate(fContext->contextPriv().resourceProvider())) {
        return result;
    }

    GrGpu* gpu = fContext->contextPriv().getGpu();
    GrSurface* surface = proxy->priv().peekSurface();

    if (gpu && surface->asRenderTarget()) {
        gpu->resolveRenderTarget(surface->asRenderTarget());
    }
    return result;
}
C++ 复制代码
GrSemaphoresSubmitted flush(GrSurfaceProxy* proxy,
                            int numSemaphores = 0,
                            GrBackendSemaphore backendSemaphores[] = nullptr) {
    return this->internalFlush(proxy, GrResourceCache::FlushType::kExternal,
                               numSemaphores, backendSemaphores);
}
GrSemaphoresSubmitted internalFlush(GrSurfaceProxy*,
                                    GrResourceCache::FlushType,
                                    int numSemaphores,
                                    GrBackendSemaphore backendSemaphores[]);
c++ 复制代码
GrSemaphoresSubmitted GrRenderTargetContext::prepareForExternalIO(
        int numSemaphores, GrBackendSemaphore backendSemaphores[]) {
    ASSERT_SINGLE_OWNER
    if (this->drawingManager()->wasAbandoned()) { return GrSemaphoresSubmitted::kNo; }
    SkDEBUGCODE(this->validate();)
    GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "prepareForExternalIO", fContext);

    return this->drawingManager()->prepareSurfaceForExternalIO(fRenderTargetProxy.get(),
                                                               numSemaphores,
                                                               backendSemaphores);
}
C++ 复制代码
GrSemaphoresSubmitted GrDrawingManager::internalFlush(GrSurfaceProxy*,
                                                      GrResourceCache::FlushType type,
                                                      int numSemaphores,
                                                      GrBackendSemaphore backendSemaphores[]) {
    GR_CREATE_TRACE_MARKER_CONTEXT("GrDrawingManager", "internalFlush", fContext);

    if (fFlushing || this->wasAbandoned()) {
        return GrSemaphoresSubmitted::kNo;
    }
    fFlushing = true;

    for (int i = 0; i < fOpLists.count(); ++i) {
        // Semi-usually the GrOpLists are already closed at this point, but sometimes Ganesh
        // needs to flush mid-draw. In that case, the SkGpuDevice's GrOpLists won't be closed
        // but need to be flushed anyway. Closing such GrOpLists here will mean new
        // GrOpLists will be created to replace them if the SkGpuDevice(s) write to them again.
        fOpLists[i]->makeClosed(*fContext->caps());
    }

    ...
    if (fSortRenderTargets) {
        SkDEBUGCODE(bool result =) SkTTopoSort<GrOpList, GrOpList::TopoSortTraits>(&fOpLists);
        SkASSERT(result);
    }

    GrGpu* gpu = fContext->contextPriv().getGpu();

    GrOpFlushState flushState(gpu, fContext->contextPriv().resourceProvider(),
                              &fTokenTracker);

    GrOnFlushResourceProvider onFlushProvider(this);
    // TODO: AFAICT the only reason fFlushState is on GrDrawingManager rather than on the
    // stack here is to preserve the flush tokens.

    // Prepare any onFlush op lists (e.g. atlases).
    if (!fOnFlushCBObjects.empty()) {
        fFlushingOpListIDs.reset(fOpLists.count());
        for (int i = 0; i < fOpLists.count(); ++i) {
            fFlushingOpListIDs[i] = fOpLists[i]->uniqueID();
        }
        SkSTArray<4, sk_sp<GrRenderTargetContext>> renderTargetContexts;
        for (GrOnFlushCallbackObject* onFlushCBObject : fOnFlushCBObjects) {
            onFlushCBObject->preFlush(&onFlushProvider,
                                      fFlushingOpListIDs.begin(), fFlushingOpListIDs.count(),
                                      &renderTargetContexts);
            for (const sk_sp<GrRenderTargetContext>& rtc : renderTargetContexts) {
                sk_sp<GrRenderTargetOpList> onFlushOpList = sk_ref_sp(rtc->getRTOpList());
                if (!onFlushOpList) {
                    continue;   // Odd - but not a big deal
                }
#ifdef SK_DEBUG
                // OnFlush callbacks are already invoked during flush, and are therefore expected to
                // handle resource allocation & usage on their own. (No deferred or lazy proxies!)
                onFlushOpList->visitProxies_debugOnly([](GrSurfaceProxy* p) {
                    SkASSERT(!p->asTextureProxy() || !p->asTextureProxy()->texPriv().isDeferred());
                    SkASSERT(GrSurfaceProxy::LazyState::kNot == p->lazyInstantiationState());
                });
#endif
                onFlushOpList->makeClosed(*fContext->caps());
                onFlushOpList->prepare(&flushState);
                fOnFlushCBOpLists.push_back(std::move(onFlushOpList));
            }
            renderTargetContexts.reset();
        }
    }

#if 0
    // Enable this to print out verbose GrOp information
    for (int i = 0; i < fOpLists.count(); ++i) {
        SkDEBUGCODE(fOpLists[i]->dump();)
    }
#endif

    int startIndex, stopIndex;
    bool flushed = false;

    {
        GrResourceAllocator alloc(fContext->contextPriv().resourceProvider());
        for (int i = 0; i < fOpLists.count(); ++i) {
            fOpLists[i]->gatherProxyIntervals(&alloc);
            alloc.markEndOfOpList(i);
        }

        GrResourceAllocator::AssignError error = GrResourceAllocator::AssignError::kNoError;
        while (alloc.assign(&startIndex, &stopIndex, &error)) {
            if (GrResourceAllocator::AssignError::kFailedProxyInstantiation == error) {
                for (int i = startIndex; i < stopIndex; ++i) {
                    fOpLists[i]->purgeOpsWithUninstantiatedProxies();
                }
            }
            //送显
            if (this->executeOpLists(startIndex, stopIndex, &flushState)) {
                flushed = true;
            }
        }
    }

    fOpLists.reset();
    //结束绘制
    GrSemaphoresSubmitted result = gpu->finishFlush(numSemaphores, backendSemaphores);

    // We always have to notify the cache when it requested a flush so it can reset its state.
    if (flushed || type == GrResourceCache::FlushType::kCacheRequested) {
        fContext->contextPriv().getResourceCache()->notifyFlushOccurred(type);
    }
    for (GrOnFlushCallbackObject* onFlushCBObject : fOnFlushCBObjects) {
        onFlushCBObject->postFlush(fTokenTracker.nextTokenToFlush(), fFlushingOpListIDs.begin(),
                                   fFlushingOpListIDs.count());
    }
    fFlushingOpListIDs.reset();
    fFlushing = false;

    return result;
}
C++ 复制代码
bool GrDrawingManager::executeOpLists(int startIndex, int stopIndex, GrOpFlushState* flushState) {
    SkASSERT(startIndex <= stopIndex && stopIndex <= fOpLists.count());

    GrResourceProvider* resourceProvider = fContext->contextPriv().resourceProvider();
    bool anyOpListsExecuted = false;
    ...

        // TODO: handle this instantiation via lazy surface proxies?
        // Instantiate all deferred proxies (being built on worker threads) so we can upload them
        fOpLists[i]->instantiateDeferredProxies(fContext->contextPriv().resourceProvider());
        fOpLists[i]->prepare(flushState);
    }

    // Upload all data to the GPU  这里
    flushState->preExecuteDraws();

    // Execute the onFlush op lists first, if any.
    for (sk_sp<GrOpList>& onFlushOpList : fOnFlushCBOpLists) {
        if (!onFlushOpList->execute(flushState)) {
            SkDebugf("WARNING: onFlushOpList failed to execute.\n");
        }
        SkASSERT(onFlushOpList->unique());
        onFlushOpList = nullptr;
    }
    fOnFlushCBOpLists.reset();

    // Execute the normal op lists.
    for (int i = startIndex; i < stopIndex; ++i) {
        if (!fOpLists[i]) {
            continue;
        }

        if (fOpLists[i]->execute(flushState)) {
            anyOpListsExecuted = true;
        }
    }

    SkASSERT(!flushState->commandBuffer());
    SkASSERT(fTokenTracker.nextDrawToken() == fTokenTracker.nextTokenToFlush());

    // We reset the flush state before the OpLists so that the last resources to be freed are those
    // that are written to in the OpLists. This helps to make sure the most recently used resources
    // are the last to be purged by the resource cache.
    flushState->reset();
    ...
    return anyOpListsExecuted;
}

总结

最后追踪到这里了,就已经基本上是Skia库的核心代码了,在这里我看到这里注释,看到了一个前端后端的含义,原本对这里并不是特别的清楚。这里摘抄一下别的文章的原文

在 Skia 图形库中,分为前端和后端,前端通常指的是图形库提供的接口和功能,用于创建和操作图形对象、设定图形属性、以及定义图形场景;后端指的是图形库的渲染引擎,负责将前端定义的图形场景渲染到屏幕上,后端通常涉及图形硬件的交互。

以上不同的Gpu的类型也对应这不同的渲染后端

C++ 复制代码
/**
 * Create an instance of GrGpu that matches the specified backend. If the requested backend is
 * not supported (at compile-time or run-time) this returns nullptr. The context will not be
 * fully constructed and should not be used by GrGpu until after this function returns.
 */
static sk_sp<GrGpu> Make(GrBackend, GrBackendContext, const GrContextOptions&, GrContext*);
相关推荐
MavenTalk10 分钟前
前端技术选型之uniapp
android·前端·flutter·ios·uni-app·前端开发
坚定信念,勇往无前17 分钟前
uni-app运行 安卓模拟器 MuMu模拟器
android·uni-app
斯普信专业组38 分钟前
RabbitMQ原理架构解析:消息传递的核心机制
架构·rabbitmq·ruby
贝克街的天才1 小时前
据说在代码里拼接查询条件不够优雅?Magic-1.0.2 发布
java·后端·开源
运维&陈同学1 小时前
【kafka01】消息队列与微服务之Kafka详解
运维·分布式·后端·微服务·云原生·容器·架构·kafka
Moment1 小时前
毕业半年,终于拥有了两个近 500 star 的开源项目了 🤭🤭🤭
前端·后端·开源
Leo.yuan1 小时前
101页PDF | 德勤_XX集团信息化顶层规划设计信息化总体解决方案(限免下载)
信息可视化·架构·数字化转型·智能工厂
Allen Bright3 小时前
Redis主从架构
数据库·redis·架构
ZOMI酱3 小时前
【AI系统】昇腾 AI 架构介绍
人工智能·架构
SuperherRo3 小时前
基础入门-Web应用&架构搭建&域名源码&站库分离&MVC模型&解析受限&对应路径
架构·源码·域名·web·解析·路径