软件绘制源码流程分析

2022-10-09 12:55:21 浏览数 (1)

theme: condensed-night-purple

引言: 之前的文章中提到过软件绘制是会调用drawSoftware方法进行绘制的。在这个方法里面调用了Surface.lockCanvas和unlockAndPost方法。这篇文章就分析这两个方法

Surface.lockCanvas

大致流程:

调用surface的lockcanvas方法获取到Canvas。native层拿到surface对象通过lock方法获取到ANativieWindowBuffer,然后创建C层的Skiacanvas对象与Java层的关联起来,最后创建新的surface对象给到java层。

代码语言:javascript复制
// 得到c层的 surface 对象    
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
//声明 buffer
ANativeWindow_Buffer buffer;
// 获取GraphicBuffer 
status_t err = surface->lock(&amp;buffer, dirtyRectPtr);

// 创建 c层的SkiaCanvas对象,并且与java层的绑定
graphics::Canvas canvas(env, canvasObj);
// 设置buffer绑定到ANativeWindow_Buffer的bits结构中
canvas.setBuffer(&amp;buffer, static_cast<int32_t>(surface->getBuffersDataSpace()));

Surface.lock

lock方法中 首先声明NativeWindowBuffer对象,然后调用dequeBuffe获取到ANativeWindwoBuffer对象接着请求SF真正创建GraphBuffer对象并返回NativeWindowBuffer

代码语言:javascript复制
 // 声明 ANativeWindowBuffer 对象
    ANativeWindowBuffer* out;
    
    //1  调用dequeueBuffer获取 ANativeWindowBuffer 对象
    status_t err = dequeueBuffer(&amp;out, &amp;fenceFd);
    // 2 创建GraphicBuffer 对象
    sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
    //3.返回dequeueBuffer获取的ANativeWindowBuffer 对象
    return err;

请求SF进程获取GraphBuffer

c层的surface对象调用lock方法传入ANatieWindow_Buffer,这块Buffer记录着lock方法返回的GraphBuffer绘制内存地址,绘制区域大小坐标等信息, ANatieWindow_Buffer内部变量bits用于之后和SkiaCanvas绑定用于直接渲染。

dequeneBuffer
代码语言:javascript复制
// 从共享的位置mSharedBufferSlot,拿出GraphicBuffer
sp<GraphicBuffer>&amp; gbuf(mSlots[mSharedBufferSlot].buffer);
int buf = -1; //mSlots中对应的下标,需下一步 dequeneSF进程分配

// 跨进程 从SF进程的mSlots数组(BufferQueue)中分配一块 GraphicBuffer
    status_t result = mGraphicBufferProducer->dequeueBuffer(
    &amp;buf, &amp;fence, dqInput.width,
    dqInput.height, dqInput.format,
    dqInput.usage, &amp;mBufferAge,
    dqInput.getTimestamps ?
     &amp;frameTimestamps : nullptr);
// 得到GraphicBuffer
sp<GraphicBuffer>&amp; gbuf(mSlots[buf].buffer);
//请求 buffer
result = mGraphicBufferProducer->requestBuffer(buf, &amp;gbuf);

请求SF进程返回一个下标对应Slots中的位置,Slots才是SF中真正的GraphBuffer。然后把这个slot位置发送给SF返回真正可以用的Buffer空间,但是此时还并没有分配内存;当调用GraphBufferProducer对象的requestBuffer方法时将SF返回的那块SF中slots内存空间和对应的下标slot传入参数调用时 就可以真正的请求分配对应mSlot位置的GraphBuffer内存空间。

dequeBuffer主要是跨进程到SF中获取真正存储缓存区的mSlots中可以用的下标(空闲),然后app利用这个下标像SF申请一块空间只不过还没有创建,接着APP使用producer的request真正申请一块内存

在Surface中有成员变量 mSlots来存储获取到的GraphicBuffer对象。GraphicBuffer就是表示内存缓冲区。 而与之对应的在SF进程中的 BufferQueueProducer.cpp中也有对应mSlots数组。内部就存储着GraphicBuffer内存缓冲区。 真正 分配内存是在SF进程中完成的。App进程只是映射到了对应的内存。

由于APP进程与SF进程通过 匿名共享内存来实现共享GraphicBuffer缓冲区。 因此,app进程只要通过 mGraphicBufferProducer就能把数据写入到SF进程提供的GraphicBuffer内存空间中。

获取canvas

创建canvas对象,canvas的构造方法中表示native层没有bitamp不占内存,空画布,然后调用createCanvas方法返回来一个SkiaCanvas对象,内部通过SKiaBitmap承载绘制内容

Java层的Canvas对应native层的 SkiaCanvas 对象。内部通过 SKBitmap 来承载绘制内容。

代码语言:javascript复制
  //获取SkiaBitmap绑定到上面ANativeWindowBuffer中的bits属性中
  if (surface.get() != nullptr) {
        if (outBitmap) {
            outBitmap->setInfo(imageInfo, rowBytes);
            
            // 关键: 将bits指针设置给SKBitmap
            outBitmap->setPixels(buffer->bits);
        }
        return true;
绑定SkiaBitmap到Buffer中的bits属性

Skiavas对象的mCanvas获取到SKBitmap对象然后和ANativeWindowBuffer的bits指针绑定,也就是SKBitmap指向了分配好的内存空间

然后Java层的canvas对应的是Native层的SkiaCanvas绑定。之后就可以对这块GraphBuffer进行绘制了,因为APP的GraphBufer映射的是SF进程中mSlots的位置所以是匿名共享内存没有交换时间(硬件绘制需要转换为OP树同步至RenderThread)

unLockAndPost

代码语言:javascript复制
 // 设置一个空的bitmap,释放canvas的画布SKBitmap。也就是说此时SKBitmap不在引用到buffer了。
 nativeCanvas->setBitmap(SkBitmap());
status_t err = mLockedBuffer->unlockAsync(&amp;fd); //解除buffer的绑定
 // 加入队列 
err = queueBuffer(mLockedBuffer.get(), fd);
  • 清空Canvas的 SKBitmap画布,解除与buffer的引用。
  • 解除surface的buffer与 SF进程的GraphicBuffer的绑定。
  • 将SF的GraphicBuffer入队BufferQueue,请求下一个vsync信号,通知SF来进行合成消费。

0 人点赞