首页 > 其他分享 >Android View绘制原理 - SkCanvas

Android View绘制原理 - SkCanvas

时间:2023-08-02 14:36:08浏览次数:42  
标签:std ... const SkCanvas device Android 绘制 View

上一篇文章介绍了在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仍然只是起到一个记录的作用,并未发生像素渲染。

标签:std,...,const,SkCanvas,device,Android,绘制,View
From: https://blog.51cto.com/u_16175630/6937963

相关文章

  • 直播商城系统源码,自定义View实现方向控制控件,可拖拽中间圆
    直播商城系统源码,自定义View实现方向控制控件,可拖拽中间圆 publicclassDirectionViewextendsViewimplementsView.OnTouchListener{  privateintwidth;  privateintheight;  privateinthalfWidth;  privateinthalfHeight;  privateintsmal......
  • 视频直播源码,android:textColor设置无效
    视频直播源码,android:textColor设置无效title_color.xml <?xmlversion="1.0"encoding="utf-8"?><selectorxmlns:android="http://schemas.android.com/apk/res/android">  <itemandroid:color="@color/txt_blue"/>......
  • Android接收扫码数据
    xx@OverridepublicbooleandispatchKeyEvent(KeyEventevent){if(event.getKeyCode()==KeyEvent.KEYCODE_ENTER){StringretStr=this.getLastScanCodeString();if(!StringUtil.isEmpty(retStr)){//......
  • Android App 隐藏标题栏+状态栏+导航栏
    1.隐藏当前Activity标题栏在当前Activity中调用:this.requestWindowFeature(Window.FEATURE_NO_TITLE);2.隐藏当前Activity状态栏(StatusBar)2.1Android4.0andLowerpublicclassMainActivityextendsActivity{@OverrideprotectedvoidonCreate(Bundle......
  • Interview - UML图
     继承关系 引用关系强引用- 成员变量 例子: player——>weapon 弱引用- 局部变量、返回值、参数  关联关系聚合关系aggregation-两个对象之间可以分割菱形在总的那一方 组合关系composition -两个对象之间不可分割,一个消失另一个也得消失......
  • 从入门到精通,大厂内部整理Android学习路线
    前言当今随着互联网的日益发展,许多开发者也想来这个行业尝尝甜头,甚至没有基础的开发者也进门了,因此,这个Android零基础教程就分享出来。但是对于Android新手入门,没有一个好的学习方向,学习规划,学习教程,是万万不行的。新手入门就来就处于啥也不知道的状态,而网上的教程太过于分散,没有完......
  • Rust 在Window上交叉编译Android库问题 error: linking with
    报错:error:linkingwith`D:/NDK/android-ndk-r25c/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android30-clang.cmd`failed:exitcode:255|=note:"D:/NDK/android-ndk-r......
  • 不忘初心 Windows11 Insider Preview 25915.1000 Canary预览版 无更新 纯净精简 2023.
    此版不能更新补丁,并开启按流量计费,此版保留Hyper和linux,让人期待的任务栏图标从不合并功能此版已经回归,母版来自UUPWindows11InsiderPreview25915.1000Canary频道预览版,本版本自动跳过硬件检测,优化后台进程和服务,精简一些日常不常用的组件,速度和性能比原版更胜一筹,为了保证稳......
  • 【金九银十面试冲刺】Android开发面试指南(简历、投递、刷题、复盘)
    前言无论是社招还是校招中,应聘者总是要经过层层的考核才能被聘用。然而,在招聘时,设置的编程以及非技术面试问题,真的有必要吗?如此就能考核出一位开发者的真实水平?说到底就是考验你的技术以及态度。态度大于一切。但我这里的态度分为两种。业务态度和沟通态度。面试官正是笔试这一关来......
  • 1688商品评论API接口-(item_review-获得1688商品评论API接口)
    一、1688商品评论API接口-(item_review-获得1688商品评论API接口)代码对接如下:1.公共参数名称类型必须描述keyString是调用key(必须以GET方式拼接在URL中)secretString是调用密钥(点击获取测试key和secret)api_nameString是API接口名称(包括在请求地址中)[item_search,item_get,item_sear......