上一篇文章介紹了在Android框架中的各種Canvas,其中C層的RecordingCanas承上啟下,在SkiaRecordingCanvas的繪制方法會通過調(diào)用它的mRecorder來記錄,而這個mRecorder的類型正好就是SkCanvas,準確的說是它的子類RecordingCanas。而各種繪制方法會對應(yīng)生成一個Op對象來描述這個繪制操作,RecordingCanvas將這個op對象分配到它持有的DisplayListData的fBytes上,從而完成記錄。 到這里也僅僅是完成記錄,離真正使用GPU按照Op描述數(shù)據(jù)渲染素還很遠。這篇我們進入skia庫來分析SKCanvas對繪制操作的處理。
SKCanvas有好幾個構(gòu)造方法,根據(jù)不同的場景可以生成基于不同繪制目標的對象,繪制的目標被抽象為SkBaseDevice,它有很多的子類,代表不同的繪制目標,比如SkBitmapDevice,繪制到一個SKBitmap上; SkNoPixelsDevice是一個虛擬的不會繪制成像素點的設(shè)備,比如前面介紹的SKCanvas的子類RecordingCanvas就是使用的SkNoPixelsDevice,它就只是將繪制命令記錄到一個二進制數(shù)組,并不渲染像素;還有SkGpuDevice,這才是代表使用GPU進行像素渲染的目標設(shè)備。所以要完成像素渲染,必須要要在某個地方生成一個使用SkGpuDevice的SkCanvas的對象。
1 SkCanvas
下面是幾個構(gòu)造函數(shù)的定義:
這是外部傳入Device的構(gòu)造方法,會將device傳入init方法進行初始化
SkCanvas::SkCanvas(sk_sp<SkBaseDevice> device)
: fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
, fProps(device->surfaceProps())
{
inc_canvas();
this->init(device);
}
這是RecordingCanvas繼承的構(gòu)造方法,init傳入null進行初始化,內(nèi)部會生成一個SkNoPixelsDevice
SkCanvas::SkCanvas()
: fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
, fProps()
{
inc_canvas();
this->init(nullptr);
}
這是使用SkBitmap作為繪制目標的構(gòu)造方法,它會生成一個SkBitmapDevice,用于繪制
SkCanvas::SkCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props)
: fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
, fProps(props)
{
inc_canvas();
sk_sp<SkBaseDevice> device(new SkBitmapDevice(bitmap, fProps, nullptr, nullptr));
this->init(device);
}
所以看到,構(gòu)造函數(shù)內(nèi)部都會調(diào)用init方法來初始化,下面分析一下這個init方法
void SkCanvas::init(sk_sp<SkBaseDevice> device) {
...
if (!device) {
device = sk_make_sp<SkNoPixelsDevice>(SkIRect::MakeEmpty(), fProps);
}
// From this point on, SkCanvas will always have a device
SkASSERT(device);
fSaveCount = 1;
fMCRec = new (fMCStack.push_back()) MCRec(device.get());
fMarkerStack = sk_make_sp<SkMarkerStack>();
// The root device and the canvas should always have the same pixel geometry
SkASSERT(fProps.pixelGeometry() == device->surfaceProps().pixelGeometry());
device->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect);
device->setMarkerStack(fMarkerStack.get());
fSurfaceBase = nullptr;
fBaseDevice = std::move(device);
fScratchGlyphRunBuilder = std::make_unique<SkGlyphRunBuilder>();
fQuickRejectBounds = this->computeDeviceClipBounds();
}
如果device為空,則生成一個SkNoPixelsDevice對象以確保每個SKCanvas都有繪制目標設(shè)備,然后將device保存到fBaseDevice
然后以device初始化一個MCRec,然后保存到fMCStack. MCRec的定義如下:看起來時記錄一個Layer的繪制,每個layer將對應(yīng)一個fBackImage。而SkCanvas中fMCStack是一個棧,所以layer將以棧的方式來保存。每個layer都持有一個SkBaseDevice 和 一個BackImage。初始化時即默認包含一個layer,即便沒有調(diào)用過saveLayer方法。所以fSaveCount也被設(shè)置為1。
class SkCanvas::MCRec {
public:
// If not null, this MCRec corresponds with the saveLayer() record that made the layer.
// The base "layer" is not stored here, since it is stored inline in SkCanvas and has no
// restoration behavior.
std::unique_ptr<Layer> fLayer;
// This points to the device of the top-most layer (which may be lower in the stack), or
// to the canvas's fBaseDevice. The MCRec does not own the device.
SkBaseDevice* fDevice;
std::unique_ptr<BackImage> fBackImage;
SkM44 fMatrix;
int fDeferredSaveCount;
MCRec(SkBaseDevice* device)
: fLayer(nullptr)
, fDevice(device)
, fBackImage(nullptr)
, fDeferredSaveCount(0) {
SkASSERT(fDevice);
fMatrix.setIdentity();
inc_rec();
...
}
2 drawRect
SkCanvas也有很多的對應(yīng)繪制方法,流程也差不多,最后,我們也來看看SkCanvas的繪制矩形的方法drawRect
void SkCanvas::drawRect(const SkRect& r, const SkPaint& paint) {
...
this->onDrawRect(r.makeSorted(), paint);
}
繼續(xù)調(diào)用onDrawRect方法
void SkCanvas::onDrawRect(const SkRect& r, const SkPaint& paint) {
SkASSERT(r.isSorted());
if (this->internalQuickReject(r, paint)) {
return;
}
AutoLayerForImageFilter layer(this, paint, &r, CheckForOverwrite::kYes);
this->topDevice()->drawRect(r, layer.paint());
}
SkBaseDevice* SkCanvas::topDevice() const {
SkASSERT(fMCRec->fDevice);
return fMCRec->fDevice;
}
它使用的時topDevice,就是最上面一個layer對應(yīng)的device。因為我們的想要看一下如何進行像素渲染的,因此看一下SkGpuDevice的情況
external/skia/src/gpu/SkGpuDevice.cpp
void SkGpuDevice::drawRect(const SkRect& rect, const SkPaint& paint) {
...
fSurfaceDrawContext->drawRect(this->clip(), std::move(grPaint),
fSurfaceDrawContext->chooseAA(paint), this->localToDevice(), rect,
&style);
}
在SkGpuDevice中由會去調(diào)用fSurfaceDrawContext的drawRect方法。它的類型是GrSurfaceDrawContext
external/skia/src/gpu/SkGpuDevice.h
private:
std::unique_ptr<GrSurfaceDrawContext> fSurfaceDrawContext;
它是在構(gòu)造的時候從外部傳入的
external/skia/src/gpu/SkGpuDevice.cpp
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))
...
}
我們直接去看一下GrSurfaceDrawContext的drawRect方法, 且僅僅看看Fill的情況
void GrSurfaceDrawContext::drawRect(const GrClip* clip,
GrPaint&& paint,
GrAA aa,
const SkMatrix& viewMatrix,
const SkRect& rect,
const GrStyle* style) {
...
AutoCheckFlush acf(this->drawingManager());
const SkStrokeRec& stroke = style->strokeRec();
if (stroke.getStyle() == SkStrokeRec::kFill_Style) {
// Fills the rect, using rect as its own local coordinates
this->fillRectToRect(clip, std::move(paint), aa, viewMatrix, rect, rect);
return;
}
...
}
繼續(xù)調(diào)用fillRectToRect
void GrSurfaceDrawContext::fillRectToRect(const GrClip* clip,
GrPaint&& paint,
GrAA aa,
const SkMatrix& viewMatrix,
const SkRect& rectToDraw,
const SkRect& localRect) {
DrawQuad quad{GrQuad::MakeFromRect(rectToDraw, viewMatrix), GrQuad(localRect),
aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone};
...
this->drawFilledQuad(clip, std::move(paint), aa, &quad);
}
繼續(xù)調(diào)用drawFilledQuad
void GrSurfaceDrawContext::drawFilledQuad(const GrClip* clip,
GrPaint&& paint,
GrAA aa,
DrawQuad* quad,
const GrUserStencilSettings* ss) {
...
this->addDrawOp(finalClip, GrFillRectOp::Make(fContext, std::move(paint), aaType,
quad, ss));
}
}
繼續(xù)調(diào)用addDrawOp
void GrSurfaceDrawContext::addDrawOp(const GrClip* clip,
GrOp::Owner op,
const std::function<WillAddOpFn>& willAddFn) {
GrDrawOp* drawOp = (GrDrawOp*)op.get();
...
auto opsTask = this->getOpsTask();
...
opsTask->addDrawOp(this->drawingManager(), std::move(op), fixedFunctionFlags, analysis,
std::move(appliedClip), dstProxyView,
GrTextureResolveManager(this->drawingManager()), *this->caps());
...
}
這里的GrDrawOp 是一個GrFillRectOp對象,表示繪制填充矩形,這和Android中的Op是差不多的概念,僅僅是描述對象。最后將這個描述對象添加到了opsTask。 getOpsTask是定義在GrSurfaceDrawContext的父類GrSurfaceFillContext中的方法
GrOpsTask* GrSurfaceFillContext::getOpsTask() {
if (!fOpsTask || fOpsTask->isClosed()) {
sk_sp<GrOpsTask> newOpsTask = this->drawingManager()->newOpsTask(
this->writeSurfaceView(), this->arenas(), fFlushTimeOpsTask);
this->willReplaceOpsTask(fOpsTask.get(), newOpsTask.get());
fOpsTask = std::move(newOpsTask);
}
SkASSERT(!fOpsTask->isClosed());
return fOpsTask.get();
}
獲得了一個GrOpsTask之后,調(diào)用addDrawOp方法
external/skia/src/gpu/GrOpsTask.cpp
void GrOpsTask::addDrawOp(GrDrawingManager* drawingMgr, GrOp::Owner op,
GrDrawOp::FixedFunctionFlags fixedFunctionFlags,
const GrProcessorSet::Analysis& processorAnalysis, GrAppliedClip&& clip,
const DstProxyView& dstProxyView,
GrTextureResolveManager textureResolveManager, const GrCaps& caps) {
...
this->recordOp(std::move(op), processorAnalysis, clip.doesClip() ? &clip : nullptr,
&dstProxyView, caps);
}
繼續(xù)調(diào)用recordOp方法
void GrOpsTask::recordOp(
GrOp::Owner op, GrProcessorSet::Analysis processorAnalysis, GrAppliedClip* clip,
const DstProxyView* dstProxyView, const GrCaps& caps) {
...
GrSurfaceProxy* proxy = this->target(0);
...
fOpChains.emplace_back(std::move(op), processorAnalysis, clip, dstProxyView);
}
fOpChains是一個OpChain的集合,因此最后recordOp是生成了一個OpChain對象,并放入到fOpChains中。
external/skia/src/gpu/GrOpsTask.h
SkSTArray<25, OpChain> fOpChains;
3 總結(jié)
本文接著上一篇文章,繼續(xù)分析了skia層的SkCanvas, 它可以接受多種繪制目標設(shè)備,比如它的子類RecordingCanvas使用的是SkNoPixelsDevice,因此只能記錄而不能渲染成像素;需要渲染成像素需要使用比如SkGpuDevice。SkCanvas除了device這個重要屬性外,還有一個fMCStack用于保存繪制Layer,并且默認會創(chuàng)建一個Layer,繪制時,繪制方法都是作用于棧頂?shù)腖ayer。接著分析了典型的繪制方法drawRect,它穿越了多個類,最后生成一個OpChain對象保存GrOpsTask的fOpChains集合。因此到目前位置,SkCanvas仍然只是起到一個記錄的作用,并未發(fā)生像素渲染。