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

上一篇文章介紹了在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ā)生像素渲染。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容