Android View绘制原理 - SkCanvas
  mkIZDEUN7jdf 2023年11月02日 90 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仍然只是起到一个记录的作用,并未发生像素渲染。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
mkIZDEUN7jdf