Skia
Skia库是一个基于2D的跨平台渲染器。所在的目录是external/skia
。
- include/:包含公共头文件
- src/:包含src代码
顶级makefile位于根目录:Android.mk
Android中的调用
Skia
是2d
的渲染器,SkiaGL
和SkiaVulkan
,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
&SkiaVulkan
和OpenGL
的不同的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;
}
这里要先聊一个点:SkiaOpenGLPipeline
和SkiaVulkanPipeline
,这两个类都是继承SkiaPipeline
。目前对这两个的核心区别还没有去分析清楚,后面分析清楚了再写一篇。
C++
class SkiaVulkanPipeline : public SkiaPipeline {
...
}
class SkiaOpenGLPipeline : public SkiaPipeline {
...
}
回到上文,在 CanvasContext::draw()
中渲染相关最终是调用的 mRenderPipeline->draw
,mRenderPipeline
最终指向的是SkiaOpenGLPipeline
类,在这个函数之中,最终调用到renderFrame
函数,renderFrame
函数是SkiaOpenGLPipeline
和SkiaVulkanPipeline
的共同父类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;
}
接下来我们看SkiaPipeline
的renderFrame
,这里面我们看到绘制图层,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
做的指令刷新,而我们最终看到的页面肯定是不是单一的画布,这也就是SurfaceFlinger
和HWC
重新做合成的原因。最终调用是SkBaseDevice
。
SkBaseDevice
我们来看SkBaseDevice;
。它一共有三个派生的子类,我们要回头确定一下我们使用的对应的设备类型, sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget( mRenderThread.getGrContext(), backendRT, kBottomLeft_GrSurfaceOrigin, nullptr, &props));
,对应初始化的实现在SkGpuDevice
,SkGpuDevice
是SkClipStackDevice
的子类。
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*);