Interpret BufferQueue

# The Path of Android Meditation

preface

In previous articles, we introduced the producer and consumer design patterns used in the composition process, and also mentioned a buffer. In this article, we will elaborate on what this buffer is.

First, let’s take a look at the official illustration.

It is well known that SurfaceFlinger uses the producer and consumer model, so how is the data between the producer and consumer encapsulated and transmitted? The answer is a BufferQueue, and we’re going to talk a little bit about what a BufferQueue is and what it does, and then finally we’re going to look at how this producer and consumer model works.

First of all, we looked at how the producer buffer is provided. We saw earlier in the SurfaceFlinger composition that the logic to get the buffer is to call the GraphicBufferProducer’s dequeueBuffer in Surface. CPP, The GraphicBufferProducer is a BpGraphicBufferProducer that uses Binder to communicate with each other

What is a BufferQueue

So first of all, let’s see what this BufferQueue is. Of course, to see what it is, the first thing to look at is the class definition and the official annotations.

class BufferQueue {
public:
    // The maximum number of bindings in a Buffer
    enum { NUM_BUFFER_SLOTS = BufferQueueDefs::NUM_BUFFER_SLOTS };

    // Enumeration value when Slot is not and bound
    enum { INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT };

    // alias in the igraphicBufferconsumer.h header
    enum {
        NO_BUFFER_AVAILABLE = IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
        PRESENT_LATER = IGraphicBufferConsumer::PRESENT_LATER,
    };

    // In asynchronous mode, two slots are reserved to ensure that producers and consumers can run asynchronously.
    enum { MAX_MAX_ACQUIRED_BUFFERS = NUM_BUFFER_SLOTS - 2 };

    typedef ::android::ConsumerListener ConsumerListener;

    // An implementation of ConsumerListener, which maintains a weak reference to the ConsumerListener
    // It can forward the call to the consumer object
    // Use it to avoid circular references to BufferQueue and producer Consumer
    class ProxyConsumerListener : public BnConsumerListener {
    public:
        explicit ProxyConsumerListener(const wp<ConsumerListener>& consumerListener);
        ~ProxyConsumerListener(a)override;
        void onDisconnect(a) override;
        void onFrameAvailable(const BufferItem& item) override;
        void onFrameReplaced(const BufferItem& item) override;
        void onBuffersReleased(a) override;
        void onSidebandStreamChanged(a) override;
        void addAndGetFrameTimestamps(
                const NewFrameEventsEntry* newTimestamps,
                FrameEventHistoryDelta* outDelta) override;
    private:
        // A weak reference to IConsumerListener
        wp<ConsumerListener> mConsumerListener;
    };

    // Slots used by producers and consumers are managed by BufferQueue, and buffers are allocated by Allocator
    static void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
            sp<IGraphicBufferConsumer>* outConsumer,
            bool consumerIsSurfaceFlinger = false);
};
Copy the code

The BufferQueue class is pretty simple, but let me introduce you to some of the nouns and concepts that come up here and in the future.

  • NUM_BUFFER_SLOTS: The maximum length of the BufferSlot array. The number is 32. (the corresponding translation of BufferSlot is the BufferSlot)
  • INVALID_BUFFER_SLOT: enumeration of BufferSlot binding status. The default value is INVALID_BUFFER_SLOT, that is, there is no corresponding BufferSlot.
  • ConsumerListener: The consumer’s listener, which forwards the call to the consumer.

Since there is only one createBufferQueue function in BufferQueue, let’s look at the implementation of createBufferQueue.

1.1 createBufferQueue

void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
        sp<IGraphicBufferConsumer>* outConsumer,
        bool consumerIsSurfaceFlinger) {

    sp<BufferQueueCore> core(new BufferQueueCore());
    sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core, consumerIsSurfaceFlinger));
    sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
    *outProducer = producer;
    *outConsumer = consumer;
}
Copy the code

This function is also very simple, it just creates three objects

  • BufferQueueCore: This is the object that actually works in the BufferQueue
  • BufferQueueProducer: Producer object
  • BufferQueueConsumer: Consumer object

1.2 BufferQueueCore

If we’ve seen producers BufferQueueProducer: : dequeueBuffer the code, we will see mCore this object, it is BufferQueueCore

I’m not going to put any code into the definition of BufferQueueCore, but the important thing about BufferQueueCore is that it maintains four arrays, Now let’s take a look at the buffer array objects held in BufferQueueCore and what they do

1.3 BufferQueueCore Maintains an array

BufferQueueCore maintains four arrays that hold objects called BufferSlots as follows

// mFreeSlots contains all slots that are Free and currently unbound to buffer
std::set<int> mFreeSlots;
// mFreeBuffers contains all Free buffers currently bound to buffer slots
std::list<int> mFreeBuffers;
// mUnusedSlots contains all slots that are Free and not bound to buffer and are not used.
std::list<int> mUnusedSlots;
// mActiveBuffers contains all slots currently bound to non-free buffers
std::set<int> mActiveBuffers;
Copy the code

We’ll just briefly enumerate these four arrays, and we’ll elaborate on them later when we use them. Now let’s look at the BufferSlot object that corresponds to these arrays.

Two BufferSlot

The object BufferSlot has a short structure, so I’ll list it all here

struct BufferSlot {

    BufferSlot()
    : mGraphicBuffer(nullptr),	// A buffer slot corresponds to a GraphicBuffer
      mEglDisplay(EGL_NO_DISPLAY),
      mBufferState(),
      mRequestBufferCalled(false),
      mFrameNumber(0),
      mEglFence(EGL_NO_SYNC_KHR),
      mFence(Fence::NO_FENCE),
      mAcquireCalled(false),
      mNeedsReallocation(false) {}// mGraphicBuffer points to the GraphicBuffer allocated for this Slot, or NULL if not allocated
    sp<GraphicBuffer> mGraphicBuffer;

    // EGLDisplay, used to create EGLSyncKHR
    EGLDisplay mEglDisplay;

    // State of the current buffer slot
    BufferState mBufferState;

    // Tell Procducer if requestBuffer was called
    // Used to debug and find bugs
    bool mRequestBufferCalled;

    // mFrameNumber is the Frame queue number for this slot, which is the LRU sort for the buffer queue
    // Because buffer may issue the release fence signal before it is released
    uint64_t mFrameNumber;

    // mEglFence is an EGL sync object, and its associated slot must be signaled before dequeue
    // It is initialized to EGL_NO_SYNC_KHR at buffer creation time and can be set to the new synchronization object in releaseBuffer
    EGLSyncKHR mEglFence;

    // mFence refers to a fence that signals when the previous buffer owner has finished starting
    // When the buffer is Free, the fence indicates when the consumer has finished reading the buffer
    // Or indicates when the producer has finished writing, if it calls cancelBuffer after writing data
    // When the buffer is QUEUED, it indicates when the producer has finished filling the buffer
    // When the buffer is DEQUEUED/ACQUIRED, the fence is passed to the producer or consumer along with the buffer and set to NO_FENCE
    sp<Fence> mFence;

    // Indicates whether the consumer sees this buffer
    bool mAcquireCalled;

    // Indicates whether the buffer was re-allocated without notifying the producer. 
    If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when dequeued to prevent the producer from usingA stale cached buffer. If so, it needs to set the buffer needs to be reallocated flag upon dequeuing to prevent producers from using stale cached buffers.// Indicates whether buffers were reallocated without notifying the producer
    // If so, it needs to be set to BUFFER_NEEDS_REALLOCATION when the buffer is enqueued, in case the producer uses an obsolete buffer
    bool mNeedsReallocation;
};
Copy the code

The BufferSlot class defines a lot of stuff, but it’s easy to understand, and I don’t need to remember it all, so if you come across it later, you can just go back and look at it. The only thing that needs to be explained is that it’s related to the Fence,

A Fence is an Android graphics system that handles buffer synchronization. As mentioned earlier, the Android graphics system uses the producer and consumer model, so it must involve synchronization between producer and consumer, and since the producer and consumer models are not in the same process, It even involves CPU and GPU synchronization, so it has a Fence mechanism, which I won’t talk about here, but you can see it in detail. Let’s assume that we already know the Fence mechanism, which is fine if we don’t. After reading this article, we’ll understand the Fence mechanism, and then come back to verify it again to better understand how BufferQueue works.

So let’s just focus on the BufferState object in here.

2.1 BufferState

First, let’s look at the structure definition of BufferState. The method in the structure definition is relatively simple, which basically defines five state values.

// BufferState is the running state of the buffer slot.
struct BufferState {

    // All slots are initially Free
    BufferState()
    : mDequeueCount(0),
      mQueueCount(0),
      mAcquireCount(0),
      mShared(false) {}uint32_t mDequeueCount;
    uint32_t mQueueCount;
    uint32_t mAcquireCount;
    bool mShared;

    // A buffer can be in five states
    //
    // | mShared | mDequeueCount | mQueueCount | mAcquireCount |
    // --------|---------|---------------|-------------|---------------|
    // FREE | false | 0 | 0 | 0 |
    // DEQUEUED| false | 1 | 0 | 0 |
    // QUEUED | false | 0 | 1 | 0 |
    // ACQUIRED| false | 0 | 0 | 1 |
    // SHARED | true | any | any | any |

    // FREE: indicates that this buffer can be queued to producers. The slot corresponding to this buffer belongs to BufferQueue.
    // When it is called dequeueBuffer, the state changes to DEQUEUED

    // DEQUEUED: indicates that the buffer has been queued by the producer, but has not been queued or cancelled. The producer can modify the contents of the buffer once a signal is sent to release the fence.
    // The slot owner of this buffer is a producer. When queueBuffer/attachBuffer is called, the state becomes QUEUED,
    / / when invoking cancelBuffer/detachBuffer state becomes FREE

    // QUEUED: indicates that the buffer has been filled by producers and can be QUEUED up for use by consumers
    // The contents of the buffer may continue to change, so the owner of the slot corresponding to the buffer was BufferQueue before the relevant fence signal was sent
    AcquireBuffer acquireBuffer acquireBuffer acquireBuffer acquireBuffer
    // In asynchronous mode, when another buffer is queued, the state of this buffer changes to FREE

    // ACQUIRED: Indicates that the buffer has been ACQUIRED by the consumer, but like the QUEUED state, the consumer cannot access the content until the fence signal is ACQUIRED.
    / / the buffer is consumer, the owner of the corresponding slot in the call releaseBuffer/detachBuffer will change for FREE
    // A standalone buffer can also be entered into the ACQUIRED state by calling attachBuffer

    // SHARED: indicates that the buffer is in SHARED mode. It can be combined with several other states (but not with FREE states).
    // It can also join/leave/get multiple times

    inline bool isFree(a) const {
        return !isAcquired() &&!isDequeued() &&!isQueued(a); }inline bool isDequeued(a) const {
        return mDequeueCount > 0;
    }

    inline bool isQueued(a) const {
        return mQueueCount > 0;
    }

    inline bool isAcquired(a) const {
        return mAcquireCount > 0;
    }

    inline bool isShared(a) const {
        return mShared;
    }

    inline void reset(a) {*this = BufferState(a); }const char* string(a) const;

    inline void dequeue(a) {
        mDequeueCount++;
    }

    inline void detachProducer(a) {
        if (mDequeueCount > 0) { mDequeueCount--; }}inline void attachProducer(a) {
        mDequeueCount++;
    }

    inline void queue(a) {
        if (mDequeueCount > 0) {
            mDequeueCount--;
        }
        mQueueCount++;
    }

    inline void cancel(a) {
        if (mDequeueCount > 0) { mDequeueCount--; }}inline void freeQueued(a) {
        if (mQueueCount > 0) { mQueueCount--; }}inline void acquire(a) {
        if (mQueueCount > 0) {
            mQueueCount--;
        }
        mAcquireCount++;
    }

    inline void acquireNotInQueue(a) {
        mAcquireCount++;
    }

    inline void release(a) {
        if (mAcquireCount > 0) { mAcquireCount--; }}inline void detachConsumer(a) {
        if (mAcquireCount > 0) { mAcquireCount--; }}inline void attachConsumer(a) { mAcquireCount++; }};Copy the code

A brief explanation of the lifetime of a BufferSlot is given in reference to the flow diagram of the BufferQueue buffer provided by Google

  1. The birth of a BufferSlot, every BufferSlot is born FREE, and its owner is the BufferQueue
  2. The producer calls deQueue, the BufferSlot is out of the queue, and the BufferSlot’s state changes to DEQUEUED, and its owner is the producer
    1. In the DEQUEUED state, the producer fills the content into the buffer corresponding to the BufferSlot
    2. Producers can also call cancelBuffer/detachBuffer, buffer state will change for FREE
  3. After the producer has populated the data, queue is called to enqueue the BufferSlot. At this point, the status of the BufferSlot changes to QUEUED, and the owner of the BufferSlot is BufferQueue
  4. The consumer invokes Acquire to obtain the BufferSlot in the BufferQueue. At this time, the status of the BufferSlot changes to ACQUIRED and the owner of the BufferSlot is the consumer
  5. After reading the data, the consumer calls release to release the BufferSlot, and then the state of the BufferSlot changes back to FREE, and the owner of the BufferSlot is BufferQueue

Note: for all buffer flows, we are only talking about ownership. In a graphical system, buffers have ownership as well as usage rights. We can think of it as a library, different people come to borrow books, the borrower has the right to use the book, but no ownership of the book.

With these concepts in mind, let’s walk through the workflow of the BufferQueue in the order of the producer and consumer models

Three dequeueBuffer

Begin from producers to apply to the BufferQueue BufferSlot, then this function is BufferQueueProducer: : dequeueBuffer. Because this function is longer, let’s break it up into parts.

3.1 The first part of the dequeueBuffer

status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,uint32_t width, uint32_t height, PixelFormat format,uint64_t usage, uint64_t* outBufferAge,FrameEventHistoryDelta* outTimestamps) {{std::lock_guard<std::mutex> lock(mCore->mMutex);
        mConsumerName = mCore->mConsumerName;
	// First determine if the state of the buffer is abandoned. MIsAbandoned initializes to false
      	// In consumerDisconnect it is changed to true
        if (mCore->mIsAbandoned) {
            return NO_INIT;
        }
	// Then check whether the buffer is connected to the Surface, if not, return directly
        if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
            returnNO_INIT; }}// Check whether the width and height required by the producer are valid, either all or none
    if((width && ! height) || (! width && height)) {return BAD_VALUE;
    }

    // Set the default values of the parameters
    status_t returnFlags = NO_ERROR;
    EGLDisplay eglDisplay = EGL_NO_DISPLAY;
    EGLSyncKHR eglFence = EGL_NO_SYNC_KHR;
    bool attachedByConsumer = false;

    {
        std::unique_lock<std::mutex> lock(mCore->mMutex);

        // If no Buffer is currently available but is currently being allocated, wait for the allocation to complete without re-allocating
        if (mCore->mFreeBuffers.empty() && mCore->mIsAllocating) {
            mDequeueWaitingForAllocation = true;
            mCore->waitWhileAllocatingLocked(lock);
            mDequeueWaitingForAllocation = false;
            mDequeueWaitingForAllocationCondition.notify_all(a); }if (format == 0) {
            format = mCore->mDefaultBufferFormat;
        }

        // Turn on the consumer request flag
        usage |= mCore->mConsumerUsageBits;

      	// If both width and height are 0, use the default width and height
        const booluseDefaultSize = ! width && ! height;if (useDefaultSize) {
            width = mCore->mDefaultWidth;
            height = mCore->mDefaultHeight;
        }
      
        intfound = BufferItem::INVALID_BUFFER_SLOT; . }Copy the code

Note the system just judge mFreeBuffer array, if mFreeBuffer array is empty, and mIsAllocating is true, is called waitWhileAllocatingLocked in waiting.

The number of buffers used in SurfaceFlinger is limited and will block if the buffer is currently being allocated. And mIsAllocating indicates whether or not allocating, if allocating, it does not repeat allocating, but blocks.

Let’s look at the second part of the code.

3.2 Part 2 of the dequeueBuffer

status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,uint32_t width, uint32_t height, PixelFormat format,uint64_t usage, uint64_t* outBufferAge,FrameEventHistoryDelta* outTimestamps) {...// Initialize the value of found as an invalid buffer slot
        int found = BufferItem::INVALID_BUFFER_SLOT;
        // Note that there is a loop, which means that if no buffer slot is found, the loop will continue
        while (found == BufferItem::INVALID_BUFFER_SLOT) {
            / / call waitForFreeSlotThenRelock began to search for available buffer tank
            status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Dequeue, lock, &found);
            if(status ! = NO_ERROR) {return status;
            }

            if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
              	// It is normal to not execute the code up to this point
                return -EBUSY;
            }

            // Notice that the graphic buffer corresponding to the BufferSlot found is assigned to buffer
            // As we said earlier in BufferSlot, a BufferSlot corresponds to a GraphicBuffer.
            // It does not say that the buffer has been allocated, which means that the buffer may not have been allocated
            const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);

            // If we are not allowed to allocate new buffers now
            if(! mCore->mAllowAllocation) {// buffer needs another buffer
                if (buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) {
                    // If it is in shared buffer mode, it will be returned directly
                    if (mCore->mSharedBufferSlot == found) {
                        return BAD_VALUE;
                    }
                    // I found a buffer, but now I want a buffer, what should I do
                    // Just put it back and try again. Notice that we're putting in the mFreeSlots array
                    mCore->mFreeSlots.insert(found);
                    mCore->clearBufferSlotLocked(found);
                    found = BufferItem::INVALID_BUFFER_SLOT;
                    continue; }}}// After executing the while loop, found either already has buffers or can be allocated buffers
  	// As usual, whether or not, put in first
        // Assign the address of the mGraphicBuffer found in the while to buffer
        const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
        if (mCore->mSharedBufferSlot == found &&
                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) {
            // Determine the shared buffer mode first
            return BAD_VALUE;
        }

  	// This is the buffers array for mActiveBuffers
  	// What type of BufferSlot does the four arrays hold in [1.3]
        if(mCore->mSharedBufferSlot ! = found) { mCore->mActiveBuffers.insert(found);
        }
        // Save the found result
        *outSlot = found;
}
Copy the code

The second part, which focuses on finding mSlots, has been commented in detail. Lookup process is implemented by invoking waitForFreeSlotThenRelock, concrete process of the search to look behind us. Take a look at the third part of the dequeueBuffer code

3.3 Part 3 of the dequeueBuffer

. *outSlot = found; attachedByConsumer = mSlots[found].mNeedsReallocation; mSlots[found].mNeedsReallocation =false;
    // Modify the state of the Buffer found. Remember the official Google image
    mSlots[found].mBufferState.dequeue(a);// Now check whether the buffer already exists or does not need to be reallocated
    if ((buffer == nullptr) ||
            buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))
    {
      	// If not, it needs to be reassigned
        mSlots[found].mAcquireCalled = false;
        mSlots[found].mGraphicBuffer = nullptr;
        mSlots[found].mRequestBufferCalled = false;
        mSlots[found].mEglDisplay = EGL_NO_DISPLAY;
        mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
        mSlots[found].mFence = Fence::NO_FENCE;
        mCore->mBufferAge = 0;
        mCore->mIsAllocating = true;
	// returnFlags Turns on the switch to be reassigned
        returnFlags |= BUFFER_NEEDS_REALLOCATION;
    } else {
      	// Set mBufferAge, which will be the frame number for this buffer to queue
        mCore->mBufferAge = mCore->mFrameCounter + 1 - mSlots[found].mFrameNumber;
    }

    eglDisplay = mSlots[found].mEglDisplay;
    eglFence = mSlots[found].mEglFence;
    // Do not return a Fence in shared buffer mode, except for the first frame
    // When looking for a BufferSlot, it returns a Fence,
    // The Fence determines whether the last owner of the BufferSlot finished processing the Fence, i.e. whether the current owner has the right to use it
    *outFence = (mCore->mSharedBufferMode &&
            mCore->mSharedBufferSlot == found) ?
            Fence::NO_FENCE : mSlots[found].mFence;
    mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
    mSlots[found].mFence = Fence::NO_FENCE;

    // If shared buffer mode is enabled, the first slot to be queued from the cache is marked as a shared buffer
    if (mCore->mSharedBufferMode && mCore->mSharedBufferSlot ==
            BufferQueueCore::INVALID_BUFFER_SLOT) {
        mCore->mSharedBufferSlot = found;
        mSlots[found].mBufferState.mShared = true; }}// Autolock scope
Copy the code

The third part is the update of mSlots status after mSlots is found. If the BufferSlot found needs to be rebuffered, the member variable of the BufferSlot is initialized

3.4 dequeueBuffer Part 4

// If the BUFFER_NEEDS_REALLOCATION flag is enabled, a new GraphicBuffer object is created
if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
    sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
            width, height, format, BQ_LAYER_COUNT, usage,
            {mConsumerName.string(), mConsumerName.size()});

    status_t error = graphicBuffer->initCheck(a); {std::lock_guard<std::mutex> lock(mCore->mMutex);

        if(error == NO_ERROR && ! mCore->mIsAbandoned) { graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
          	// Put the created buffer into BufferSlot
            mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
        }

        mCore->mIsAllocating = false;
      	// Remember the first part of the block, if allocating, block, here is the wake up operation
        mCore->mIsAllocatingCondition.notify_all(a);if(error ! = NO_ERROR) { mCore->mFreeSlots.insert(*outSlot);
            mCore->clearBufferSlotLocked(*outSlot);
            return error;
        }

        if (mCore->mIsAbandoned) {
            mCore->mFreeSlots.insert(*outSlot);
            mCore->clearBufferSlotLocked(*outSlot);
            returnNO_INIT; }}}if (attachedByConsumer) {
    returnFlags |= BUFFER_NEEDS_REALLOCATION;
}

if(eglFence ! = EGL_NO_SYNC_KHR) { EGLint result =eglClientWaitSyncKHR(eglDisplay, eglFence, 0.1000000000);
    // If something goes wrong, log the error, but return the buffer without accessing it synchronously. It is too late to abort the exit operation.
    eglDestroySyncKHR(eglDisplay, eglFence);
}

if (outBufferAge) {
    // Return the frame number
    *outBufferAge = mCore->mBufferAge;
}
// 
addAndGetFrameTimestamps(nullptr, outTimestamps);

return returnFlags;
Copy the code

The fourth part of the code is also relatively simple, the only thing that needs attention is GraphicBuffer creation and memory allocation. However, GraphicBuffer memory allocation is a very complicated process, I will not go into the details here, see [Read GraphicBuffer] todo

Next, take a look at BufferSlot waitForFreeSlotThenRelock lookup process

Search for BufferSlot

4.1 waitForFreeSlotThenRelock

WaitForFreeSlotThenRelock this function is simpler, it mainly through two functions to obtain a buffer

  1. GetFreeBufferLocked: It gets buffers from the FreeBuffer array
  2. GetFreeSlotLocked: It gets the buffer from the FreeSlot array
status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller,
        std::unique_lock<std::mutex>& lock, int* found) const {
    auto callerString = (caller == FreeSlotCaller::Dequeue) ?
            "dequeueBuffer" : "attachBuffer";
    bool tryAgain = true;
    while (tryAgain) {
        if (mCore->mIsAbandoned) {
            // The BufferQueue is not in the right state
            return NO_INIT;
        }

        int dequeuedCount = 0;
        int acquiredCount = 0;
        // Count the number of DEQUEUED and ACQUIRED bufferslots in ActiveBuffers
        for (int s : mCore->mActiveBuffers) {
            if (mSlots[s].mBufferState.isDequeued()) {
                ++dequeuedCount;
            }
            if (mSlots[s].mBufferState.isAcquired()) { ++acquiredCount; }}// When the buffer is enqueued, check whether the number of bufferslots in the producer's Dequeue exceeds mMaxDequeuedBufferCount
        if (mCore->mBufferHasBeenQueued &&
                dequeuedCount >= mCore->mMaxDequeuedBufferCount) {
            return INVALID_OPERATION;
        }

      	// The above are all statistics and some parameter judgment, now really start to search
      	// Initialize a value as usual
        *found = BufferQueueCore::INVALID_BUFFER_SLOT;

        // If we quickly disconnect and reconnect, we may be in a state where slot is empty,
        // But there are many buffers in the queue. This can cause us to run out of memory when we overtake the user. If it looks like too many buffers are queuing, wait
      	// Get the maximum number of buffers first, usually 2 with double buffers or 3 with three buffers
        const int maxBufferCount = mCore->getMaxBufferCountLocked(a);// If too many bufferslots are enqueued, tooManyBuffers will be used
        bool tooManyBuffers = mCore->mQueue.size(a) >static_cast<size_t>(maxBufferCount);
        if (tooManyBuffers) {
        } else {
            // If the mode is shared buffer and a shared buffer exists, the shared buffer is used
            if(mCore->mSharedBufferMode && mCore->mSharedBufferSlot ! = BufferQueueCore::INVALID_BUFFER_SLOT) { *found = mCore->mSharedBufferSlot; }else {
                if (caller == FreeSlotCaller::Dequeue) {
                    // If Dequeue is called, FreeBuffer is preferred
                    // Because slots in FreeBuffer are already bound to buffers
                    // There is no need to reallocate buffer
                    int slot = getFreeBufferLocked(a);if(slot ! = BufferQueueCore::INVALID_BUFFER_SLOT) { *found = slot; }else if (mCore->mAllowAllocation) {
                        *found = getFreeSlotLocked();
                    }
                } else {
                    // If attach is called, FreeSlot is preferred
                    int slot = getFreeSlotLocked(a);if(slot ! = BufferQueueCore::INVALID_BUFFER_SLOT) { *found = slot; }else {
                        *found = getFreeBufferLocked(a); }}}}// If not found, or tooManyBuffers, try again
        tryAgain = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) ||
                   tooManyBuffers;
        if (tryAgain) {
            if ((mCore->mDequeueBufferCannotBlock || mCore->mAsyncMode) &&
                    (acquiredCount <= mCore->mMaxAcquiredBufferCount)) {
                // An error is returned if the mode is non-blocking (producers and consumers are controlled by the application)
                return WOULD_BLOCK;
            }
            // Wait for lock, return if timeout, retry if no timeout
            if (mDequeueTimeout >= 0) {
                std::cv_status result = mCore->mDequeueCondition.wait_for(lock,
                        std::chrono::nanoseconds(mDequeueTimeout));
                if (result == std::cv_status::timeout) {
                    returnTIMED_OUT; }}else {
              	// Stuck in waiting, waiting to wake up
                mCore->mDequeueCondition.wait(lock); }}}// while (tryAgain)

    return NO_ERROR;
}
Copy the code

Although is BufferQueueProducer waitForFreeSlotThenRelock call, but the real implementation is BufferQueueCore. The specific search process can be divided into the following steps

  1. DequeueBuffer () gets a BufferSlot first from mFreeBuffers.
  2. If mFreeBuffers are empty, the BufferSlot is fetched from mFreeSlots and a buffer of the specified size is allocated to it.
  3. Change the BufferSlot state from FREE to DEQUEUED, and then migrate the slot from mFreeSlots to mActiveBuffers.
  4. The obtained slot is returned to the caller as an output parameter. If the GraphicBuffer bound to the BufferSlot is reassigned, the return value is BUFFER_NEEDS_REALLOCATION, otherwise NO_ERROR

When we use dequeueBuffer to get bufferslots, we get buffers from the FreeBuffer array first because slots are already bound to buffers, so we don’t have to apply for buffers and bind them. Of course, if the FreeBuffer array is empty, or if no buffer is available, it will be applied from the FreeSlot array, in which case a GraphicBuffer will be applied and bound. This logic is already seen in the dequeueBuffer.

In addition, in waitForFreeSlotThenRelock, we also see a situation that is tooManyBuffers, so what appear tooManyBuffers next time? Take the more common three buffers, one for producers, one for consumers, and one for SurfaceFlinger compositing. TooManyBuffers will appear if the producer pushes the GraphicBuffer into the BufferQueue before the consumer has time to consume it.

Of course, the normal situation is that the producer does not generate content in time, the drawing time is too long, so that the GraphicBuffer is not transmitted to the BufferQueue in time, resulting in the display of frame loss. However, if we look at the source code of the system, it is not good for the producer to generate too slowly, nor is it good for the producer to generate too fast.

4.2 getFreeBufferLocked and getFreeSlotLocked

In waitForFreeSlotThenRelock, we also see getFreeBufferLocked and getFreeSlotLocked these two functions. But they are relatively simple, and I won’t go into them here

int BufferQueueProducer::getFreeBufferLocked(a) const {
    if (mCore->mFreeBuffers.empty()) {
        return BufferQueueCore::INVALID_BUFFER_SLOT;
    }
    int slot = mCore->mFreeBuffers.front(a); mCore->mFreeBuffers.pop_front(a);return slot;
}

int BufferQueueProducer::getFreeSlotLocked(a) const {
    if (mCore->mFreeSlots.empty()) {
        return BufferQueueCore::INVALID_BUFFER_SLOT;
    }
    int slot = *(mCore->mFreeSlots.begin());
    mCore->mFreeSlots.erase(slot);
    return slot;
}
Copy the code

At this point, the basic flow of a dequeueBuffer is covered. What a producer does with a BufferSlot is not the focus of this article. Let’s look at enqueueing a BufferSlot once the producer is finished producing.

Five queueBuffer

QueueBuffer is the process by which a BufferSlot is transferred to a BufferQueue after the producer has finished producing. This function, let’s break it up into parts.

5.1 The first part of the queueBuffer

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {


    int64_t requestedPresentTimestamp;
    bool isAutoTimestamp;
    android_dataspace dataSpace;
    Rect crop(Rect::EMPTY_RECT);
    int scalingMode;
    uint32_t transform;
    uint32_t stickyTransform;
    sp<Fence> acquireFence;
    bool getFrameTimestamps = false;

    / / input as input parameters, remove in the input variables here, save to requestedPresentTimestamp incoming parameters and so on
    input.deflate(&requestedPresentTimestamp, &isAutoTimestamp, &dataSpace,
            &crop, &scalingMode, &transform, &acquireFence, &stickyTransform,
            &getFrameTimestamps);
    const Region& surfaceDamage = input.getSurfaceDamage(a);const HdrMetadata& hdrMetadata = input.getHdrMetadata(a);if (acquireFence == nullptr) {
        return BAD_VALUE;
    }

    auto acquireFenceTime = std::make_shared<FenceTime>(acquireFence);

    // The size of the Buffer does not match the size of the screen
    switch (scalingMode) {
        case NATIVE_WINDOW_SCALING_MODE_FREEZE:
        case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
        case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
        case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
            break;
        default:
            return BAD_VALUE;
    }

    // Define some variables
    sp<IConsumerListener> frameAvailableListener;
    sp<IConsumerListener> frameReplacedListener;
    int callbackTicket = 0;
    uint64_t currentFrameNumber = 0;

    // Define a BufferItem
    BufferItem item;
}
Copy the code

The first part of the code is also simpler

  1. Define a set of variables for later use
  2. The input is a QueueBufferInput structure that encapsulates bufferSlot-related variables, which are extracted from the input
  3. ScalingMode specifies the BufferSlot scalingMode. If the size of the Buffer does not match the size of the screen, it will return BAD_VALUE
  4. Defines a BufferItem object

5.2 queueBuffer Part 2

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {
    BufferItem item;
    {
        std::lock_guard<std::mutex> lock(mCore->mMutex);
        // General state judgment
        if (mCore->mIsAbandoned) {
            return NO_INIT;
        }
        if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
            return NO_INIT;
        }
        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
            return BAD_VALUE;
        } else if(! mSlots[slot].mBufferState.isDequeued()) {
            return BAD_VALUE;
        } else if(! mSlots[slot].mRequestBufferCalled) {return BAD_VALUE;
        }

        // If shared buffer mode has just been enabled and the first slot of the buffer is queued, it is marked as a shared buffer
        if (mCore->mSharedBufferMode && mCore->mSharedBufferSlot ==
                BufferQueueCore::INVALID_BUFFER_SLOT) {
            mCore->mSharedBufferSlot = slot;
            mSlots[slot].mBufferState.mShared = true;
        }


        // Save the GraphicBuffer of BufferSlot to GraphicBuffer
        const sp<GraphicBuffer>& graphicBuffer(mSlots[slot].mGraphicBuffer);

        // Get the width and height of GraphicBuffer
        Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());

        // Define the clipping area
        Rect croppedRect(Rect::EMPTY_RECT);
        // Clipping the contents of bufferRect in the clipping region
        crop.intersect(bufferRect, &croppedRect);
        // croppedRect is crop
        if(croppedRect ! = crop) {return BAD_VALUE;
        }

        // If dataSpace is Unknown, use the default value
        if (dataSpace == HAL_DATASPACE_UNKNOWN) {
            dataSpace = mCore->mDefaultBufferDataSpace;
        }

        // Pass the Fence, which subsequent consumers use to determine whether they have the right to use it
        mSlots[slot].mFence = acquireFence;
        // Change the status of BufferSlot to QUEUE
        mSlots[slot].mBufferState.queue(a);// Increase the frame counter and store its local version on the lock on mCore->mMutex for external use
        ++mCore->mFrameCounter;
        currentFrameNumber = mCore->mFrameCounter;
        mSlots[slot].mFrameNumber = currentFrameNumber;

        // Encapsulate the argument into a BufferItem
        item.mAcquireCalled = mSlots[slot].mAcquireCalled;
        item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
        item.mCrop = crop;
        item.mTransform = transform &
                ~static_cast<uint32_t>(NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY); item.mTransformToDisplayInverse = (transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) ! =0;
        item.mScalingMode = static_cast<uint32_t>(scalingMode);
        item.mTimestamp = requestedPresentTimestamp;
        item.mIsAutoTimestamp = isAutoTimestamp;
        item.mDataSpace = dataSpace;
        item.mHdrMetadata = hdrMetadata;
        item.mFrameNumber = currentFrameNumber;
        item.mSlot = slot;
        item.mFence = acquireFence;
        item.mFenceTime = acquireFenceTime;
        item.mIsDroppable = mCore->mAsyncMode ||
                (mConsumerIsSurfaceFlinger && mCore->mQueueBufferCanDrop) ||
                (mCore->mLegacyBufferDrop && mCore->mQueueBufferCanDrop) ||
                (mCore->mSharedBufferMode && mCore->mSharedBufferSlot == slot);
        item.mSurfaceDamage = surfaceDamage;
        item.mQueuedBuffer = true; item.mAutoRefresh = mCore->mSharedBufferMode && mCore->mAutoRefresh; item.mApi = mCore->mConnectedApi; mStickyTransform = stickyTransform; .Copy the code

The second part of the code is also simpler. It constructs a BufferItem and sets the value from the input passed in to the BufferItem. The only thing to notice here is the clipping of the GraphicBuffer content, because the GraphicBuffer might be out of the area to display, so you need to crop out the content that meets the criteria.

bool Rect::intersect(const Rect& with, Rect* result) const {
    result->left = max(left, with.left);
    result->top = max(top, with.top);
    result->right = min(right, with.right);
    result->bottom = min(bottom, with.bottom);
    return! (result->isEmpty());
}
Copy the code

5.3 queueBuffer Part 3

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {... mStickyTransform = stickyTransform;// Cache shared buffer data so that BufferItem can be recreated
        if (mCore->mSharedBufferMode) {
            mCore->mSharedBufferCache.crop = crop;
            mCore->mSharedBufferCache.transform = transform;
            mCore->mSharedBufferCache.scalingMode = static_cast<uint32_t>(
                    scalingMode);
            mCore->mSharedBufferCache.dataspace = dataSpace;
        }

        output->bufferReplaced = false;

        // Enqueue BufferItem. There are several cases
        if (mCore->mQueue.empty()) {
            // When BufferQueue is empty, we can ignore MDEQUEUEBUFERCANNOTBLOCK and enqueue this buffer
            mCore->mQueue.push_back(item);
            frameAvailableListener = mCore->mConsumerListener;
        } else {
            // When the queue is not empty, we need to look at the last buffer in the queue to see if we need to replace it
            const BufferItem& last = mCore->mQueue.itemAt(
                    mCore->mQueue.size() - 1);
            if (last.mIsDroppable) {

                if(! last.mIsStale) { mSlots[last.mSlot].mBufferState.freeQueued(a);// After leaving shared buffer mode, the shared buffer will still be around. Mark it as no longer shared if this operation causes it to be free.
                    // After leaving shared buffer mode, the shared buffer still exists. If this operation results in it being idle, it is marked as no longer shared
                    if(! mCore->mSharedBufferMode && mSlots[last.mSlot].mBufferState.isFree()) {
                        mSlots[last.mSlot].mBufferState.mShared = false;
                    }
                    // Do not put shared buffers on the free list
                    if(! mSlots[last.mSlot].mBufferState.isShared()) {
                        mCore->mActiveBuffers.erase(last.mSlot);
                        mCore->mFreeBuffers.push_back(last.mSlot);
                        output->bufferReplaced = true; }}// If the last buffer is overwritable, it is overwritten with the incoming buffer
                mCore->mQueue.editItemAt(mCore->mQueue.size() - 1) = item;
                frameReplacedListener = mCore->mConsumerListener;
            } else {
                // If the last buffer is not overwritable, join the queue directly
                mCore->mQueue.push_back(item); frameAvailableListener = mCore->mConsumerListener; }}// Modify the BufferQueue variable to send notifications
        mCore->mBufferHasBeenQueued = true;	// BufferQueue Specifies whether any buffers have been queued
        mCore->mDequeueCondition.notify_all(a);// Notify the thread associated with the mDequeueCondition lock
        mCore->mLastQueuedSlot = slot;	// Save slot to mLastQueuedSlot

        // Save the parameter to be returned to output
        output->width = mCore->mDefaultWidth;
        output->height = mCore->mDefaultHeight;
        output->transformHint = mCore->mTransformHint;
  			// Buffer now waiting to be consumed
        output->numPendingBuffers = static_cast<uint32_t>(mCore->mQueue.size());
  			// Next frame number
        output->nextFrameNumber = mCore->mFrameCounter + 1;

        mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());

        // Get the ticket of the callback function
        callbackTicket = mNextCallbackTicket++;

    }
Copy the code

The third part enlists the created BufferItem. There are a few different scenarios for joining the team

  1. If BufferQueue is empty, it is directly queued
  2. BufferQueue isn’t empty
    1. Whether the contents of the last frame are overwritable, if so, the contents of the last frame are discarded and the new BufferItem is enqueued
    2. If the contents of the last frame cannot be overridden, the new BufferItem is enqueued

Then modify the BufferQueue variable and issue a notification. Remember that there are several cases of waiting in a dequeueBuffer [see 3.3], where a thread is stuck waiting if no BufferSlot is found or a tooManyBuffer occurs. The bottom line is that producers apply buffers too quickly.

5.4 queueBuffer Part 4

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {...// If the user is SurfaceFinger, the GraphicBuffer may not be cleared.
    The BufferQueue is guaranteed to be in the SurfaceFinger process and will not be called by the binder
    if(! mConsumerIsSurfaceFlinger) { item.mGraphicBuffer.clear(a); }The callback does not hold the no primary buffer queue lock, but does hold the corresponding callback lock, so it can also ensure that the callback is in order
    int connectedApi;
    sp<Fence> lastQueuedFence;

    {
        std::unique_lock<std::mutex> lock(mCallbackMutex);
        while(callbackTicket ! = mCurrentCallbackTicket) { mCallbackCondition.wait(lock);
        }

        // Notify the listener that a BufferSlot is available for consumption
        if(frameAvailableListener ! =nullptr) {
            frameAvailableListener->onFrameAvailable(item);
        } else if(frameReplacedListener ! =nullptr) {
            frameReplacedListener->onFrameReplaced(item);
        }

        connectedApi = mCore->mConnectedApi;
        lastQueuedFence = std::move(mLastQueueBufferFence);

        mLastQueueBufferFence = std::move(acquireFence);
        mLastQueuedCrop = item.mCrop;
        mLastQueuedTransform = item.mTransform;

        ++mCurrentCallbackTicket;
        mCallbackCondition.notify_all(a); }// Update and get FrameEventHistory
    nsecs_t postedTime = systemTime(SYSTEM_TIME_MONOTONIC);
    NewFrameEventsEntry newFrameEventsEntry = {
        currentFrameNumber,
        postedTime,
        requestedPresentTimestamp,
        std::move(acquireFenceTime)
    };
  
  	// Make a note
    addAndGetFrameTimestamps(&newFrameEventsEntry,
            getFrameTimestamps ? &output->frameTimestamps : nullptr);

    // Wait without lock
    if (connectedApi == NATIVE_WINDOW_API_EGL) {
        // Wait here to allow two full buffers to join, but not a third.
        lastQueuedFence->waitForever("Throttling EGL Production");
    }

    return NO_ERROR;
}
Copy the code

The fourth part mainly carries out some callbacks and notifications, and determines some mLast related variables.

5.5 queueBuffer summary

Compared to dequeueBuffer, queueBuffer’s logic is much simpler. It simply parses some of the arguments passed in by the producer, creates a corresponding BufferItem in the BufferQueue, and then processes each item according to whether the last frame can be overridden, and then changes some states. Finally callback notification.

Six acquireBuffer

After looking at the two functions of the producer BufferQueueProducer, let’s look at the two functions of the consumer BufferQueueConsumer. The first is acquireBuffer.

6.1 acquireBuffer Part 1

status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
        nsecs_t expectedPresent, uint64_t maxFrameNumber) {
	
    int numDroppedBuffers = 0;
    sp<IProducerListener> listener;
    {
        std::unique_lock<std::mutex> lock(mCore->mMutex);

        // Calculate the Acquired Buffer first
        int numAcquiredBuffers = 0;
        for (int s : mCore->mActiveBuffers) {
            if (mSlots[s].mBufferState.isAcquired()) { ++numAcquiredBuffers; }}// If the AcquiredBuffer exceeds the maximum mMaxAcquiredBufferCount limit, this parameter is returned
        // The maximum number allowed here is more than 1, so that consumers can use the new Buffer to replace the old Buffer
        if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
            return INVALID_OPERATION;
        }

        boolsharedBufferAvailable = mCore->mSharedBufferMode && mCore->mAutoRefresh && mCore->mSharedBufferSlot ! = BufferQueueCore::INVALID_BUFFER_SLOT;In asynchronous mode, the list is guaranteed to be one buffer deep, while in synchronous mode, we use the oldest buffer.
        if (mCore->mQueue.empty() && !sharedBufferAvailable) {
            return NO_BUFFER_AVAILABLE;
        }

        // Get the iterator of the BufferQueue queue
        BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
}
Copy the code

The logic of the first part is simple

  • First, determine whether the current acquire buffer has exceeded the maximum number of buffers that can acquire. Normally, this number is 1, but 1 is added to this number so that the consumer can replace the old buffer with the new one
  • Non-shared mode, if the buffer queue is empty, returns no buffer available

6.2 acquireBuffer Part 2

status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
        nsecs_t expectedPresent, uint64_t maxFrameNumber) {...// If expectedPresent is specified, we may not want to return the buffer yet.
        // If it is specified and multiple buffers are queued, we may want to remove a buffer.
        // If we are in shared buffer mode and the queue is empty, skip this step, because in this case we will only return the shared buffer.
        if(expectedPresent ! =0 && !mCore->mQueue.empty()) {
            The expectedPresent parameter tells you when the buffer is expected to be displayed on the screen
            If the buffer's desiredPresent time is earlier than the expectedPresent time, the buffer will be displayed on time or as soon as possible
            If you want to display it, acquire and return it, and if you don't want to display it until the expectedPresent time, return PRESENT_LATER
            // Note: The code assumes a positive monotonic time value from the system clock.

            // First check if we can drop frames. If the timestamp is automatically generated by Surface, we will skip this check.
            // If the application does not explicitly generate timestamps, it may not want to discard frames based on their timestamps.
            // mIsAutoTimestamp indicates whether this buffer has an automatically generated timestamp
            while (mCore->mQueue.size(a) >1 && !mCore->mQueue[0].mIsAutoTimestamp) {
                const BufferItem& bufferItem(mCore->mQueue[1]);

                // If deleting entry[0] would leave us with a buffer that the consumer is not ready for, do not delete it.
                if (maxFrameNumber && bufferItem.mFrameNumber > maxFrameNumber) {
                    break;
                }

                // If entry[1] is timely, delete entry[0] (and repeat). We apply an additional criterion here: if our desiredPresent is within +/ -1 of the expectedPresent, we only remove the earlier buffer.
                // Otherwise, a false desiredPresent time (e.g., 0 or a small relative timestamp), which usually means "ignore the timestamp and get it now", will cause us to lose frames.

                // If entry[1] 's fence is not already signaled, do not delete the earlier buffer.
                if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
                        desiredPresent > expectedPresent) {
                    // This buffer is set to be displayed in the near future, or desiredPresent is garbage. Either way, we don't want to throw away the previous buffer in order to get it on screen faster.
                    break;
                }

                if(! front->mIsStale) {// the queue head buffer is still in mSlots, so mark the slot as free
                    mSlots[front->mSlot].mBufferState.freeQueued(a);// After leaving shared buffer mode, the shared buffer still exists. If this action results in it being free, it is marked as no longer shared.
                    if(! mCore->mSharedBufferMode && mSlots[front->mSlot].mBufferState.isFree()) {
                        mSlots[front->mSlot].mBufferState.mShared = false;
                    }

                    // Do not put shared buffers in the free list
                    if(! mSlots[front->mSlot].mBufferState.isShared()) {
                        mCore->mActiveBuffers.erase(front->mSlot);
                        mCore->mFreeBuffers.push_back(front->mSlot);
                    }

                    listener = mCore->mConnectedProducerListener;
                    ++numDroppedBuffers;
                }

                mCore->mQueue.erase(front);
                front = mCore->mQueue.begin(a); }// Check to see if you are ready to get the front-end buffer
            nsecs_t desiredPresent = front->mTimestamp;
            bool bufferIsDue = desiredPresent <= expectedPresent ||
                    desiredPresent > expectedPresent + MAX_REASONABLE_NSEC;
            bool consumerIsReady = maxFrameNumber > 0 ?
                    front->mFrameNumber <= maxFrameNumber : true;
            if(! bufferIsDue || ! consumerIsReady) {returnPRESENT_LATER; }}Copy the code

The second part is to determine whether the old buffer can be replaced by the new buffer. If the old buffer has an auto-generated timestamp and the new buffer is about to be displayed, the new buffer is used directly in this case, or the old buffer is used if the new buffer will not be used for much longer.

6.3 acquireBuffer Part 3

status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
        nsecs_t expectedPresent, uint64_t maxFrameNumber) {

        int slot = BufferQueueCore::INVALID_BUFFER_SLOT;

        if (sharedBufferAvailable && mCore->mQueue.empty()) {
            // Make sure the buffer is allocated before fetching it
            mCore->waitWhileAllocatingLocked(lock);

            slot = mCore->mSharedBufferSlot;

            // Recreate the BufferItem for the shared buffer from the data cached during the last queue.
            outBuffer->mGraphicBuffer = mSlots[slot].mGraphicBuffer;
            outBuffer->mFence = Fence::NO_FENCE;
            outBuffer->mFenceTime = FenceTime::NO_FENCE;
            outBuffer->mCrop = mCore->mSharedBufferCache.crop;
            outBuffer->mTransform = mCore->mSharedBufferCache.transform &
                    ~static_cast<uint32_t>( NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY); outBuffer->mScalingMode = mCore->mSharedBufferCache.scalingMode; outBuffer->mDataSpace = mCore->mSharedBufferCache.dataspace; outBuffer->mFrameNumber = mCore->mFrameCounter; outBuffer->mSlot = slot; outBuffer->mAcquireCalled = mSlots[slot].mAcquireCalled; outBuffer->mTransformToDisplayInverse = (mCore->mSharedBufferCache.transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) ! =0;
            outBuffer->mSurfaceDamage = Region::INVALID_REGION;
            outBuffer->mQueuedBuffer = false;
            outBuffer->mIsStale = false;
            outBuffer->mAutoRefresh = mCore->mSharedBufferMode &&
                    mCore->mAutoRefresh;
        } else {
            slot = front->mSlot;
            *outBuffer = *front;
        }


        if(! outBuffer->mIsStale) { mSlots[slot].mAcquireCalled =true;
            // If a BufferItem was not previously in the queue, do not reduce the queue count. This happens in shared buffer mode when the queue is empty and a BufferItem is created on it.
            if (mCore->mQueue.empty()) {
                mSlots[slot].mBufferState.acquireNotInQueue(a); }else {
                mSlots[slot].mBufferState.acquire(a); } mSlots[slot].mFence = Fence::NO_FENCE; }// If the buffer was previously acquired by the consumer, set the mGraphicBuffer to NULL to avoid unnecessary remapping of the buffer on the consumer side
        if (outBuffer->mAcquireCalled) {
            outBuffer->mGraphicBuffer = nullptr;
        }

        mCore->mQueue.erase(front);

        // We may release a Slot when discarding the old buffer, or the producer may be blocked
        mCore->mDequeueCondition.notify_all(a); mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());

    }

    / / callback
    if(listener ! =nullptr) {
        for (int i = 0; i < numDroppedBuffers; ++i) {
            listener->onBufferReleased();
        }
    }

    return NO_ERROR;
}
Copy the code

6.4 acquireBuffer summary

The logic of the consumer is much simpler than that of the producer, and acquireBuffer does the following

  1. First, determine the number of buffers already in the ACQUIRED state. If the number exceeds the limit, no new buffers will be returned. But I’m going to add one to the maximum number, in order to replace the old buffer with the new one
  2. Determines whether the buffer has an automatically generated timestamp that is used to discard the old buffer. Based on this timestamp, the old buffer can be replaced with the new buffer
  3. The defined buffer is returned, the Slot status is changed to ACQUIRED, the other blocked threads are notified, and the callback function is called.

Seven releaseBuffer

status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
        const sp<Fence>& releaseFence, EGLDisplay eglDisplay,
        EGLSyncKHR eglFence) {


    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
            releaseFence == nullptr) {
        // Slot is invalid or releaseFence is null
        return BAD_VALUE;
    }

    sp<IProducerListener> listener;
    {
        std::lock_guard<std::mutex> lock(mCore->mMutex);

        // If the frame sequence changes due to buffer reallocation, we can ignore the old releaseBuffer.
        // Ignore this for shared buffers, where the frame numbers are easily out of sync because the buffers are queued and fetched simultaneously.
        if(frameNumber ! = mSlots[slot].mFrameNumber && ! mSlots[slot].mBufferState.isShared()) {
            return STALE_BUFFER_SLOT;
        }

        // Check the status of the Slot. The status of the Slot must be ACQUIRED
        if(! mSlots[slot].mBufferState.isAcquired()) {
            return BAD_VALUE;
        }

        mSlots[slot].mEglDisplay = eglDisplay;
        mSlots[slot].mEglFence = eglFence;
        mSlots[slot].mFence = releaseFence;
        mSlots[slot].mBufferState.release(a);// After leaving shared buffer mode, the shared buffer still exists. If this action results in it being FREE, it is marked as no longer shared.
        if(! mCore->mSharedBufferMode && mSlots[slot].mBufferState.isFree()) {
            mSlots[slot].mBufferState.mShared = false;
        }
        // Do not put the shared buffer in the FREE list
        if(! mSlots[slot].mBufferState.isShared()) {
            mCore->mActiveBuffers.erase(slot);
            mCore->mFreeBuffers.push_back(slot);
        }

        listener = mCore->mConnectedProducerListener;
        mCore->mDequeueCondition.notify_all(a); }// No lock callback
    if(listener ! =nullptr) {
        listener->onBufferReleased(a); }return NO_ERROR;
}
Copy the code

A releaseBuffer is the process of releasing the buffer after the consumer has finished using it. After the releaseBuffer function is called, mSlot’s state becomes FREE.

Eight summary

The work flow and principle of BufferQueue in SurfaceFlinger are basically clear. In connection with the previous work flow of SurfaceFlinger synthesis, the most critical process has been basically learned. Of course, that’s not even the tip of the iceberg for SurfaceFlinger’s work. There’s a long way to go.