上一篇文章介绍了在Android结构中的各种Canvas,其间C层的RecordingCanas承上启下,在SkiaRecordingCanvas的制作办法会经过调用它的mRecorder来记载,而这个mRecorder的类型正好便是SkCanvas,准确的说是它的子类RecordingCanas。而各种制作办法会对应生成一个Op方针来描述这个制作操作,RecordingCanvas将这个op方针分配到它持有的DisplayListData的fBytes上,从而完成记载。 到这儿也仅仅是完成记载,离真实运用GPU依照Op描述数据烘托素还很远。这篇我们进入skia库来剖析SKCanvas对制作操作的处理。

SKCanvas有好几个结构办法,依据不同的场景能够生成根据不同制作方针的方针,制作的方针被笼统为SkBaseDevice,它有许多的子类,代表不同的制作方针,比方SkBitmapDevice,制作到一个SKBitmap上; SkNoPixelsDevice是一个虚拟的不会制作成像素点的设备,比方前面介绍的SKCanvas的子类RecordingCanvas便是运用的SkNoPixelsDevice,它就仅仅将制作命令记载到一个二进制数组,并不烘托像素;还有SkGpuDevice,这才是代表运用GPU进行像素烘托的方针设备。所以要完成像素烘托,必须要要在某个当地生成一个运用SkGpuDevice的SkCanvas的方针。

1 SkCanvas

下面是几个结构函数的界说:

这是外部传入Device的结构办法,会将device传入init办法进行初始化

SkCanvas::SkCanvas(sk_sp<SkBaseDevice> device)
    : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
    , fProps(device->surfaceProps())
{
    inc_canvas();
    this->init(device);
}

这是RecordingCanvas继承的结构办法,init传入null进行初始化,内部会生成一个SkNoPixelsDevice

SkCanvas::SkCanvas()
    : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
    , fProps()
{
    inc_canvas();
    this->init(nullptr);
}

这是运用SkBitmap作为制作方针的结构办法,它会生成一个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);
}

所以看到,结构函数内部都会调用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都有制作方针设备,然后将device保存到fBaseDevice
然后以device初始化一个MCRec,然后保存到fMCStack. MCRec的界说如下:看起来时记载一个Layer的制作,每个layer将对应一个fBackImage。而SkCanvas中fMCStack是一个栈,所以layer将以栈的方式来保存。每个layer都持有一个SkBaseDevice 和 一个BackImage。初始化时即默许包括一个layer,即便没有调用过saveLayer办法。所以fSaveCount也被设置为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也有许多的对应制作办法,流程也差不多,最终,我们也来看看SkCanvas的制作矩形的办法drawRect

void SkCanvas::drawRect(const SkRect& r, const SkPaint& paint) {
    ...
    this->onDrawRect(r.makeSorted(), paint);
}

持续调用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对应的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中由会去调用fSurfaceDrawContext的drawRect办法。它的类型是GrSurfaceDrawContext

external/skia/src/gpu/SkGpuDevice.h

private:
    std::unique_ptr<GrSurfaceDrawContext> fSurfaceDrawContext;

它是在结构的时分从外部传入的
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;
    } 
    ...
}

持续调用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);
}

持续调用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));
    }
}

持续调用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之后,调用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);
}

持续调用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 总结

本文接着上一篇文章,持续剖析了skia层的SkCanvas, 它能够接受多种制作方针设备,比方它的子类RecordingCanvas运用的是SkNoPixelsDevice,因而只能记载而不能烘托成像素;需要烘托成像素需要运用比方SkGpuDevice。SkCanvas除了device这个重要特点外,还有一个fMCStack用于保存制作Layer,并且默许会创建一个Layer,制作时,制作办法都是作用于栈顶的Layer。接着剖析了典型的制作办法drawRect,它穿越了多个类,最终生成一个OpChain方针保存GrOpsTask的fOpChains调集。因而到现在方位,SkCanvas仍然仅仅起到一个记载的作用,并未产生像素烘托。

关注大众号:Android老皮!!!欢迎大家来找我讨论交流