美文网首页一路向下之AOSP研究
Android-View绘制原理(10)-SkCanvas

Android-View绘制原理(10)-SkCanvas

作者: 代码多哥 | 来源:发表于2023-07-30 19:27 被阅读0次

    上一篇文章介绍了在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-View绘制原理(10)-SkCanvas

        本文链接:https://www.haomeiwen.com/subject/iuvspdtx.html