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