美文网首页androidSurfaceFlinger
BlastBufferQueue 原理解读

BlastBufferQueue 原理解读

作者: 大天使之剑 | 来源:发表于2022-03-09 22:02 被阅读0次

    一、重点API功能介绍

    Google针对新的同步机制,在BBQ对象JAVA层面设计了一系列功能接口,列举功能更新较大几个接口:

    1. setNextTransaction(@Nullable SurfaceControl.Transaction t)

    提供用于下一次缓冲区要更新的事务。 BBQ 不会立即提交此事务,通过该接口将下一帧的提交控制在调用者手中,调用者可以将其用于更高级别的同步。

    1. mergeWithNextTransaction(SurfaceControl.Transaction t, long frameNumber)

    将传入的事务合并到 BBQ 中的下一个事务。 当具有指定帧号的下一帧可用时,将直接与含有Buffer的事务进行合并并提交。

    1. setTransactionCompleteCallback(long frameNumber @Nullable TransactionCompleteCallback completeCallback)

    客户端可以监听Buffer的合成状态,在 SurfaceFlinger 中已应用包含带有 framenumber 的缓冲区的事务时触发回调,通知客户端合成完成。

    与之前Android版本不同的是,Surface对象的创建、Buffer size与Surface size的更新也支持直接通过BBQ进行操作。

    二、BBQ初始化

    Android 12 Google将BufferQueue(简称BQ)组件从SF端移动到了客户端,BQ组件的初始化也放在BBQ的初始化中。通过类名可以看出BBQ更像是BQ的装饰者,在BQ本来功能特性的基础上添加了同步的功能。

    BBQ 模型

    通过官图大概了解,整个生产消费模型都在客户端,图形缓冲区的出队、入队、获取等操作都在客户端完成,预示着生产着模型从远程通讯变成了本地通讯, 消费者监听器也从SF端的ContentsChangedListener。带来的改变就是客户端需要通过事务Transaction来向SF端提交Buffer与图层的属性。

    接下来以应用显示流程为例,梳理下BBQ的初始化流程:

    流程的梳理过程所贴的代码,为了突出重点会删除部分逻辑。

    1. 创建BBQ对象

    应用端通过方法relayoutWindow向WMS服务申请窗口布局,创建应用对应SurfaceControl,随后根据SurfaceControl创建BlastBufferQueue:

    frameworks/base/core/java/android/view/ViewRootImpl.java

    1958    Surface getOrCreateBLASTSurface() {
    1959        if (!mSurfaceControl.isValid()) {
    1960            return null;
    1961        }
    1962
    1963        Surface ret = null;
    1964        if (mBlastBufferQueue == null) {
                    //第一次获取会创建BBQ
    1965            mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
    1966                mSurfaceSize.x, mSurfaceSize.y,
    1967                mWindowAttributes.format);
    1968            // 通过BBQ创建Surface对象
    1970            ret = mBlastBufferQueue.createSurface();
    1971        } else {
                    //BBQ已创建,则更新Buffer与Layer的几何属性
    1972            mBlastBufferQueue.update(mSurfaceControl,
    1973                mSurfaceSize.x, mSurfaceSize.y,
    1974                mWindowAttributes.format);
    1975        }
    1976
    1977        return ret;
    1978    }
    
    
    1. 通过JNI方法创建对应的Native对象

    BBQ主要核心逻辑的初始化都放在了Native对象的构造函数,做了以下几件事:

    1. 创建图形缓冲区生产消费模型;
    2. 连接本地的图形缓冲区消费者;
    3. 设置图形缓冲区监听器。

    frameworks/native/libs/gui/BLASTBufferQueue.cpp

    133BLASTBufferQueue::BLASTBufferQueue(const std::string& name, const sp<SurfaceControl>& surface,
    134                                   int width, int height, int32_t format)
    135      : mSurfaceControl(surface),
    136        mSize(width, height),
    137        mRequestedSize(mSize),
    138        mFormat(format),
    139        mNextTransaction(nullptr) {
           // 创建图形缓冲区生产消费模型
    140    createBufferQueue(&mProducer, &mConsumer);
    141    // 由于适配器在客户端进程中,显式设置 dequeue 超时,以便 dequeueBuffer 流程阻塞
    143    mProducer->setDequeueTimeout(std::numeric_limits<int64_t>::max());
    145    // 默认设置最大dequeue buffer 数量
    146    mProducer->setMaxDequeuedBufferCount(2);
           // 初始化图形缓冲区消费者
    147    mBufferItemConsumer = new BLASTBufferItemConsumer(mConsumer,
    148                                                      GraphicBuffer::USAGE_HW_COMPOSER |
    149                                                              GraphicBuffer::USAGE_HW_TEXTURE,
    150                                                      1, false);
           //设置消费者标识
    156    mBufferItemConsumer->setName(String8(consumerName.c_str()));
           //添加图形缓冲区入队监听
    157    mBufferItemConsumer->setFrameAvailableListener(this);
           //添加图形缓冲区消费者释放监听
    158    mBufferItemConsumer->setBufferFreedListener(this);
           //设置缓冲区Size
    159    mBufferItemConsumer->setDefaultBufferSize(mSize.width, mSize.height);
    160    mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format));
    161    mBufferItemConsumer->setBlastBufferQueue(this);
    162    //通过SF端计算并获取最大可消费缓冲区数量
    163    ComposerService::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers);
    164    mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers);
    165
    166    mTransformHint = mSurfaceControl->getTransformHint();
    167    mBufferItemConsumer->setTransformHint(mTransformHint);
    168    SurfaceComposerClient::Transaction()
    169            .setFlags(surface, layer_state_t::eEnableBackpressure,
    170                      layer_state_t::eEnableBackpressure)
    171            .setApplyToken(mApplyToken)
    172            .apply();
    173    mNumAcquired = 0;
    174    mNumFrameAvailable = 0;
    177}
    
    
    1. 创建图形缓冲区生产消费模型

    frameworks/native/libs/gui/BLASTBufferQueue.cpp

    797void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
    798                                         sp<IGraphicBufferConsumer>* outConsumer) {
           //创建BufferQueue核心类,主要负责缓冲区的调度工作
    802    sp<BufferQueueCore> core(new BufferQueueCore());
           //创建生产者模型,等待图形生产者连接使用
    805    sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core));
           //创建消费者模型,等待图形消费者连接使用
    809    sp<BufferQueueConsumer> consumer(new BufferQueueConsumer(core));
    810    consumer->setAllowExtraAcquire(true);
    
    814    *outProducer = producer;
    815    *outConsumer = consumer;
    816}
    
    
    1. 连接本地的图形缓冲区消费者

    BLASTBufferItemConsumer(简称BBIC)继承自ConsumerBase,创建BBIC的同时,消费者模型与消费者监听器建立起了连接:

    frameworks/native/libs/gui/ConsumerBase.cpp

    58ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :
    59        mAbandoned(false),
    60        mConsumer(bufferQueue),
    61        mPrevFinalReleaseFence(Fence::NO_FENCE) {
    62    //进行类型转换,将 ConsumerBase 转换为 ConsumerListener
    69    wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
          //嵌套一层监听器代理
    70    sp<IConsumerListener> proxy = new BufferQueue::ProxyConsumerListener(listener);
    71    //连接消费者模型
    72    status_t err = mConsumer->consumerConnect(proxy, controlledByApp);
    73    if (err != NO_ERROR) {
    76    } else {
    77        mConsumer->setConsumerName(mName);
    78    }
    79}
    
    

    frameworks/native/libs/gui/BufferQueueConsumer.cpp

    510status_t BufferQueueConsumer::connect(
    511        const sp<IConsumerListener>& consumerListener, bool controlledByApp) {
           ...
    528    //将BufferQueue::ProxyConsumerListener 连接至 BufferQueueCore.mConsumerListener
    529    mCore->mConsumerListener = consumerListener;
    530    mCore->mConsumerControlledByApp = controlledByApp;
    531
    532    return NO_ERROR;
    533}
    
    

    这一步也就让 BBIC 建立了对Buffer状态的监听。接下来看BBQ如何有选择性的监听Buffer的状态。

    1. 设置图形缓冲区监听器

    BBIC 拥有监听Buffer所有状态的能力,BBQ对Buffer特定状态的监听离不开 BBIC,因此,BBQ 继承了两个抽象类 ConsumerBaseBufferItemConsumer,分别针对 Buffer 消费状态与生产状态进行监听。

    frameworks/native/libs/gui/BufferQueueConsumer.cpp

    133BLASTBufferQueue::BLASTBufferQueue(const std::string& name, const sp<SurfaceControl>& surface,
    134                                   int width, int height, int32_t format)
    135      : mSurfaceControl(surface),
    136        mSize(width, height),
    137        mRequestedSize(mSize),
    138        mFormat(format),
    139        mNextTransaction(nullptr) {
           //设置消费者标识
    156    mBufferItemConsumer->setName(String8(consumerName.c_str()));
           //添加图形缓冲区可消费状态监听
    157    mBufferItemConsumer->setFrameAvailableListener(this);
           //添加图形缓冲区可生产状态监听
    158    mBufferItemConsumer->setBufferFreedListener(this);
           //设置缓冲区Size
    159    mBufferItemConsumer->setDefaultBufferSize(mSize.width, mSize.height);
    160    mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format));
    177}
    
    

    BBQ初始化完成,消费者模型建立完成,由于BBQ动态监听缓冲区的状态,如果有可消费的缓冲区,BBQ会触发缓冲区的事务提交:

    546void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
           //如果客户通过setNextTransaction函数设置了自定义事务,触发线程阻塞
    550    const bool nextTransactionSet = mNextTransaction != nullptr;
    551    if (nextTransactionSet) {
    552        while (mNumFrameAvailable > 0 || maxBuffersAcquired(false /* includeExtraAcquire */)) {
    553            BQA_LOGV("waiting in onFrameAvailable...");
    554            mCallbackCV.wait(_lock);
    555        }
    556    }
    557    // add to shadow queue
    558    mNumFrameAvailable++;
           //执行缓冲区的提交流程
    564    processNextBufferLocked(nextTransactionSet /* useNextTransaction */);
    565}
    
    
    1. 创建Surface对象

    通过梳理BBQ的初始化,对消费者端的大概流程有了一定的认识,接下来梳理下生产者方的代表,也就是Surface。Android 显示的的内容来源于各种绘制模块,而这些绘制模块需要与BQ建立连接,获取Buffer用以绘制,这样才能将绘制的画像通过BBQ提交给SF合成。Surface作为生产者模型与绘制模块之间桥梁,相关的流程掌握显得尤为重要。

    绘制模块指的是那些图像生产者,如以使用SurfaceView、GlSurfaceView、TextureView控件为代表的Video模块、Camera模块、游戏应用等,以及使用软件绘制、硬件加速绘制为代表的普通控件。

    回到创建BBQ的流程,在ViewRootImpl.getOrCreateBLASTSurface方法中,创建完BBQ,紧接着会创建Surface对象,直接看Native 对象的构造函数:

    66Surface::Surface(const sp<IGraphicBufferProducer>& bufferProducer, bool controlledByApp,
    67                 const sp<IBinder>& surfaceControlHandle)
    68      : mGraphicBufferProducer(bufferProducer),
    69        mCrop(Rect::EMPTY_RECT),
    70        mBufferAge(0),
    71        mGenerationNumber(0),
    72        mSharedBufferMode(false),
    73        mAutoRefresh(false),
    74        mAutoPrerotation(false),
    75        mSharedBufferSlot(BufferItem::INVALID_BUFFER_SLOT),
    76        mSharedBufferHasBeenQueued(false),
    77        mQueriedSupportedTimestamps(false),
    78        mFrameTimestampsSupportsPresent(false),
    79        mEnableFrameTimestamps(false),
    80        mFrameEventHistory(std::make_unique<ProducerFrameEventHistory>()) {
    81    // Initialize the ANativeWindow function pointers.
    82    ANativeWindow::setSwapInterval  = hook_setSwapInterval;
    83    ANativeWindow::dequeueBuffer    = hook_dequeueBuffer;
    84    ANativeWindow::cancelBuffer     = hook_cancelBuffer;
    85    ANativeWindow::queueBuffer      = hook_queueBuffer;
    86    ANativeWindow::query            = hook_query;
    87    ANativeWindow::perform          = hook_perform;
    88
    89    ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED;
    90    ANativeWindow::cancelBuffer_DEPRECATED  = hook_cancelBuffer_DEPRECATED;
    91    ANativeWindow::lockBuffer_DEPRECATED    = hook_lockBuffer_DEPRECATED;
    92    ANativeWindow::queueBuffer_DEPRECATED   = hook_queueBuffer_DEPRECATED;
    116    mSurfaceControlHandle = surfaceControlHandle;
    117}
    
    

    首先Surface的创建会传入生产者模型 GraphicBufferProducer,这样Surface对象拥有了操作缓冲区的能力,同时在构造函数中Surface提供了一系列hook为首的函数,连接到ANativeWindow的函数指针,为的是给EGL模块提供对缓冲区操作的入口。而hook函数会直接调用内部的本地函数,以hook_queueBuffer 为例:

    379int Surface::hook_dequeueBuffer(ANativeWindow* window,
    380        ANativeWindowBuffer** buffer, int* fenceFd) {
    381    Surface* c = getSelf(window);
    382    {
    383        std::shared_lock<std::shared_mutex> lock(c->mInterceptorMutex);
    384        if (c->mDequeueInterceptor != nullptr) {
    385            auto interceptor = c->mDequeueInterceptor;
    386            auto data = c->mDequeueInterceptorData;
    387            return interceptor(window, Surface::dequeueBufferInternal, data, buffer, fenceFd);
    388        }
    389    }
    390    return c->dequeueBuffer(buffer, fenceFd);
    391}
    
    
    632int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    
           ....
    661    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, dqInput.width,
    662                                                            dqInput.height, dqInput.format,
    663                                                            dqInput.usage, &mBufferAge,
    664                                                            dqInput.getTimestamps ?
    665                                                                    &frameTimestamps : nullptr);
    680
    739    .....
    740    return OK;
    741}
    
    

    同时软件绘制不需要通过hook函数来中转,当上层通过Surface.lockCanvas方法获取画布时会直接调用本地函数函数 Surface::dequeueBuffer

    Surface只是绘制的中介,还需要与绘制模块进行连接后,绘制模块才能获取缓冲区和绘制图像数据,关于绘制模块如何连接到Surface,这里不做记录。

    三、重点API功能原理解析

    结合第一节的关于BBQ 重点API功能介绍与BBQ的初始化流程,回过头看下这三个API功能是如何实现的。

    1. setNextTransaction 功能原理解析

    首先看 setNextTransaction 函数,调用者通过该接口可以实现将当前帧 Buffer 的提交权利控制在自己手中,同时可以加入其他图层想要的更新,然后提交,放在同一帧生效。可以思考下,如果当前帧的控制权交给了调用者,是否会导致下一帧的紊乱呢?看下这块流程:

    582 void BLASTBufferQueue::setNextTransaction(SurfaceComposerClient::Transaction* t) {
    583    std::lock_guard _lock{mMutex};
    584    mNextTransaction = t;
    585}
    
    546 void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
           //如果客户通过setNextTransaction函数设置了自定义事务,触发线程阻塞
    550    const bool nextTransactionSet = mNextTransaction != nullptr;
    551    if (nextTransactionSet) {
    552        while (mNumFrameAvailable > 0 || maxBuffersAcquired(false /* includeExtraAcquire */)) {
    554            mCallbackCV.wait(_lock);
    555        }
    556    }
    557    // add to shadow queue
    558    mNumFrameAvailable++;
           //执行缓冲区的提交流程
    564    processNextBufferLocked(nextTransactionSet /* useNextTransaction */);
    565}
    
    339void BLASTBufferQueue::releaseBufferCallback(const ReleaseCallbackId& id,
    340                                             const sp<Fence>& releaseFence, uint32_t transformHint,
    341                                    uint32_t currentMaxAcquiredBufferCount) {
    362    ...
           //唤醒线程
    386    mCallbackCV.notify_all();
    387}
    
    

    这里BBQ做了线程阻塞的机制,当绘制模块绘制完成下一帧,并将Buffer放回了缓冲区队列,触发BBQ的 onFrameAvailable回调,如果调用者使用了setNextTransaction函数传入了自定义事务,那么就会在 onFrameAvailable函数中阻塞住线程, 暂停执行下一帧的 processNextBufferLocked。而唤醒线程的任务交给了 releaseBufferCallback 函数。

    当前帧会执行绘制提交函数 processNextBufferLocked,但是不会立即提交,会将事务控制在自己手中。可以看到,releaseBufferCallback的回调函数会通过 t->setBuffer传递到SF端。

    389void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {
    403
    404    SurfaceComposerClient::Transaction localTransaction;
    405    bool applyTransaction = true;
    406    SurfaceComposerClient::Transaction* t = &localTransaction;
           //如果客户通过setNextTransaction函数设置了自定义事务,applyTransaction 为false
    407    if (mNextTransaction != nullptr && useNextTransaction) {
    408        t = mNextTransaction;
    409        mNextTransaction = nullptr;
    410        applyTransaction = false;
    411    }
    412    ...
    525    //存在客户端设置的自定义事务,放弃自此的缓冲区提交,将提交权给与客户端
    526    if (applyTransaction) {
    527        t->setApplyToken(mApplyToken).apply();
    528    }
    537}
    
    

    也就是说当调用者主动提交事务后,SF端合成完成后会回调该通知,唤醒线程。否则会一直阻塞等待调用者提交。

    大概流程如图示:

    setNextTransaction 功能流程图

    根据BBQ相关文档提示:

    该机制在同步单个帧时阻塞在 UI 线程中很好,但在尝试同步多个帧时效果不佳。 它最终会减慢渲染速度。 相反,在 RenderThread 级别处理同步以允许 UI 线程继续处理帧

    因此多帧同步还是有优化空间。

    1. mergeWithNextTransaction 功能原理解析

    将调用者传入的事务合并到 BBQ 中的下一个事务。 当具有指定帧号的下一帧可用时,将直接与含有Buffer的事务进行合并并提交。也就是说将调用者事务所包含的其他对图层属性的更新合入到BBQ的事务中,与BBQ的事务在指定帧数一同生效。这个怎么实现的呢?

    697void BLASTBufferQueue::mergeWithNextTransaction(SurfaceComposerClient::Transaction* t,
    698                                                uint64_t frameNumber) {
    699    std::lock_guard _lock{mMutex};
    700    if (mLastAcquiredFrameNumber >= frameNumber) {
    701        // Apply the transaction since we have already acquired the desired frame.
    702        t->apply();
    703    } else {
    704        mPendingTransactions.emplace_back(frameNumber, *t);
    705        // Clear the transaction so it can't be applied elsewhere.
    706        t->clear();
    707    }
    708}
    
    

    这个函数会将调用者传入的事务都保存在 mPendingTransactions 集合中,当执行到下一帧的
    processNextBufferLocked 函数时,将集合中的事务都合入到BBQ事务中,然后直接提交:

    389void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {
    403
    404    ...
    479
           //如果上层通过mergeWithNextTransaction方法传入了事务,会被保存在mPendingTransactions
           //在下一帧提交时统一合入到 t 事务中
    511    auto mergeTransaction =
    512            [&t, currentFrameNumber = bufferItem.mFrameNumber](
    513                    std::tuple<uint64_t, SurfaceComposerClient::Transaction> pendingTransaction) {
    514                auto& [targetFrameNumber, transaction] = pendingTransaction;
    515                if (currentFrameNumber < targetFrameNumber) {
    516                    return false;
    517                }
    518                t->merge(std::move(transaction));
    519                return true;
    520            };
    521
    522    mPendingTransactions.erase(std::remove_if(mPendingTransactions.begin(),
    523                                              mPendingTransactions.end(), mergeTransaction),
    524                               mPendingTransactions.end());
    525    //存在客户端设置的自定义事务,放弃自此的缓冲区提交,将提交权给与客户端
    526    if (applyTransaction) {
    527        t->setApplyToken(mApplyToken).apply();
    528    }
    537}
    
    

    大概流程如图示:

    mergeWithNextTransaction 功能流程图
    1. setTransactionCompleteCallback 功能原理解析

    客户端可以监听Buffer的合成状态,在 SurfaceFlinger 中已应用包含带有 frameNumber 的缓冲区的事务时触发回调,通知调用者合成完成。

    611void BLASTBufferQueue::setTransactionCompleteCallback(
    612        uint64_t frameNumber, std::function<void(int64_t)>&& transactionCompleteCallback) {
    613    std::lock_guard _lock{mMutex};
    614    if (transactionCompleteCallback == nullptr) {
    615        mTransactionCompleteCallback = nullptr;
    616    }
    620}
    
    
    389void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {
    403
           //设置缓冲区acquire Fence
    475    t->setAcquireFence(mSurfaceControl,
    476                       bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE);
           //设置缓冲区合成完成的回调通知
    477    t->addTransactionCompletedCallback(transactionCallbackThunk, static_cast<void*>(this));
    478    mSurfaceControlsWithPendingCallback.push(mSurfaceControl);
    479
    525    //存在客户端设置的自定义事务,放弃自此的缓冲区提交,将提交权给与客户端
    526    if (applyTransaction) {
    527        t->setApplyToken(mApplyToken).apply();
    528    }
    537}
    
    

    通过 t->addTransactionCompletedCallback 将 transactionCallbackThunk 回调函数传给了SF,当合成完成会触发回调,并通知调用者状态。

    四、总结

    根据上面流程的梳理,用一张图总结下BBQ与相关模块之间的结构关系:

    BBQ模块关系图

    相关文章

      网友评论

        本文标题:BlastBufferQueue 原理解读

        本文链接:https://www.haomeiwen.com/subject/jaexdrtx.html