Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6d8adec2 authored by Patrick Williams's avatar Patrick Williams Committed by Android (Google) Code Review
Browse files

Merge "Optimize BLAST buffer releases via Unix sockets" into main

parents 1eb56992 ac70bc57
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -255,6 +255,7 @@ filegroup {
        "BitTube.cpp",
        "BLASTBufferQueue.cpp",
        "BufferItemConsumer.cpp",
        "BufferReleaseChannel.cpp",
        "Choreographer.cpp",
        "CompositorTiming.cpp",
        "ConsumerBase.cpp",
+307 −19
Original line number Diff line number Diff line
@@ -38,13 +38,17 @@
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>

#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <chrono>

#include <com_android_graphics_libgui_flags.h>

using namespace com::android::graphics::libgui;
using namespace std::chrono_literals;
using android::base::unique_fd;

namespace {
inline const char* boolToString(bool b) {
@@ -179,8 +183,6 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati
    // explicitly so that dequeueBuffer will block
    mProducer->setDequeueTimeout(std::numeric_limits<int64_t>::max());

    // safe default, most producers are expected to override this
    mProducer->setMaxDequeuedBufferCount(2);
    mBufferItemConsumer = new BLASTBufferItemConsumer(mConsumer,
                                                      GraphicBuffer::USAGE_HW_COMPOSER |
                                                              GraphicBuffer::USAGE_HW_TEXTURE,
@@ -210,6 +212,12 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati
            },
            this);

#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
    std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> bufferReleaseConsumer;
    gui::BufferReleaseChannel::open(mName, bufferReleaseConsumer, mBufferReleaseProducer);
    mBufferReleaseReader.emplace(std::move(bufferReleaseConsumer));
#endif

    BQA_LOGV("BLASTBufferQueue created");
}

@@ -259,6 +267,9 @@ void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width,
    if (surfaceControlChanged) {
        t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure,
                   layer_state_t::eEnableBackpressure);
        if (mBufferReleaseProducer) {
            t.setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer);
        }
        applyTransaction = true;
    }
    mTransformHint = mSurfaceControl->getTransformHint();
@@ -439,6 +450,21 @@ void BLASTBufferQueue::releaseBufferCallback(
    BBQ_TRACE();
    releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount,
                                false /* fakeRelease */);
    if (!mBufferReleaseReader) {
        return;
    }
    // Drain the buffer release channel socket
    while (true) {
        ReleaseCallbackId releaseCallbackId;
        sp<Fence> releaseFence;
        if (status_t status =
                    mBufferReleaseReader->readNonBlocking(releaseCallbackId, releaseFence);
            status != OK) {
            break;
        }
        releaseBufferCallbackLocked(releaseCallbackId, releaseFence, std::nullopt,
                                    false /* fakeRelease */);
    }
}

void BLASTBufferQueue::releaseBufferCallbackLocked(
@@ -495,11 +521,11 @@ void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId,
                                     const sp<Fence>& releaseFence) {
    auto it = mSubmitted.find(callbackId);
    if (it == mSubmitted.end()) {
        BQA_LOGE("ERROR: releaseBufferCallback without corresponding submitted buffer %s",
                 callbackId.to_string().c_str());
        return;
    }
    mNumAcquired--;
    updateDequeueShouldBlockLocked();
    mBufferReleaseReader->interruptBlockingRead();
    BBQ_TRACE("frame=%" PRIu64, callbackId.framenumber);
    BQA_LOGV("released %s", callbackId.to_string().c_str());
    mBufferItemConsumer->releaseBuffer(it->second, releaseFence);
@@ -564,6 +590,7 @@ status_t BLASTBufferQueue::acquireNextBufferLocked(

    auto buffer = bufferItem.mGraphicBuffer;
    mNumFrameAvailable--;
    updateDequeueShouldBlockLocked();
    BBQ_TRACE("frame=%" PRIu64, bufferItem.mFrameNumber);

    if (buffer == nullptr) {
@@ -582,6 +609,7 @@ status_t BLASTBufferQueue::acquireNextBufferLocked(
    }

    mNumAcquired++;
    updateDequeueShouldBlockLocked();
    mLastAcquiredFrameNumber = bufferItem.mFrameNumber;
    ReleaseCallbackId releaseCallbackId(buffer->getId(), mLastAcquiredFrameNumber);
    mSubmitted[releaseCallbackId] = bufferItem;
@@ -708,6 +736,7 @@ void BLASTBufferQueue::acquireAndReleaseBuffer() {
        return;
    }
    mNumFrameAvailable--;
    updateDequeueShouldBlockLocked();
    mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
}

@@ -761,7 +790,9 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
        }

        // add to shadow queue
        mNumDequeued--;
        mNumFrameAvailable++;
        updateDequeueShouldBlockLocked();
        if (waitForTransactionCallback && mNumFrameAvailable >= 2) {
            acquireAndReleaseBuffer();
        }
@@ -812,11 +843,21 @@ void BLASTBufferQueue::onFrameReplaced(const BufferItem& item) {
void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) {
    std::lock_guard _lock{mTimestampMutex};
    mDequeueTimestamps[bufferId] = systemTime();
};
    mNumDequeued++;
}

void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) {
    {
        std::lock_guard _lock{mTimestampMutex};
        mDequeueTimestamps.erase(bufferId);
    }

    {
        std::lock_guard lock{mMutex};
        mNumDequeued--;
        updateDequeueShouldBlockLocked();
    }
    mBufferReleaseReader->interruptBlockingRead();
};

bool BLASTBufferQueue::syncNextTransaction(
@@ -888,6 +929,22 @@ bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
    return mSize != bufferSize;
}

void BLASTBufferQueue::updateDequeueShouldBlockLocked() {
    int32_t buffersInUse = mNumDequeued + mNumFrameAvailable + mNumAcquired;
    int32_t maxBufferCount = std::min(mMaxAcquiredBuffers + mMaxDequeuedBuffers, kMaxBufferCount);
    bool bufferAvailable = buffersInUse < maxBufferCount;
    // BLASTBufferQueueProducer should block until a buffer is released if
    // (1) There are no free buffers available.
    // (2) We're not in async mode. In async mode, BufferQueueProducer::dequeueBuffer returns
    //     WOULD_BLOCK instead of blocking when there are no free buffers.
    // (3) We're not in shared buffer mode. In shared buffer mode, both the producer and consumer
    //     can access the same buffer simultaneously. BufferQueueProducer::dequeueBuffer returns
    //     the shared buffer immediately instead of blocking.
    mDequeueShouldBlock = !(bufferAvailable || mAsyncMode || mSharedBufferMode);
    ATRACE_INT("Dequeued", mNumDequeued);
    ATRACE_INT("DequeueShouldBlock", mDequeueShouldBlock);
}

class BBQSurface : public Surface {
private:
    std::mutex mMutex;
@@ -1116,24 +1173,58 @@ public:
                                            producerControlledByApp, output);
    }

    status_t disconnect(int api, DisconnectMode mode) override {
        if (status_t status = BufferQueueProducer::disconnect(api, mode); status != OK) {
            return status;
        }

        sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
        if (!bbq) {
            return OK;
        }

        {
            std::lock_guard lock{bbq->mMutex};
            bbq->mNumDequeued = 0;
            bbq->mNumFrameAvailable = 0;
            bbq->mNumAcquired = 0;
            bbq->mSubmitted.clear();
            bbq->updateDequeueShouldBlockLocked();
        }
        bbq->mBufferReleaseReader->interruptBlockingRead();

        return OK;
    }

    // We want to resize the frame history when changing the size of the buffer queue
    status_t setMaxDequeuedBufferCount(int maxDequeuedBufferCount) override {
        int maxBufferCount;
        status_t status = BufferQueueProducer::setMaxDequeuedBufferCount(maxDequeuedBufferCount,
                                                                         &maxBufferCount);
        // if we can't determine the max buffer count, then just skip growing the history size
        if (status == OK) {
        if (status != OK) {
            return status;
        }

        sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
        if (!bbq) {
            return OK;
        }

        {
            std::lock_guard lock{bbq->mMutex};
            bbq->mMaxDequeuedBuffers = maxDequeuedBufferCount;
            bbq->updateDequeueShouldBlockLocked();
        }
        bbq->mBufferReleaseReader->interruptBlockingRead();

        size_t newFrameHistorySize = maxBufferCount + 2; // +2 because triple buffer rendering
        // optimize away resizing the frame history unless it will grow
        if (newFrameHistorySize > FrameEventHistory::INITIAL_MAX_FRAME_HISTORY) {
                sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
                if (bbq != nullptr) {
            ALOGV("increasing frame history size to %zu", newFrameHistorySize);
            bbq->resizeFrameEventHistory(newFrameHistorySize);
        }
            }
        }
        return status;

        return OK;
    }

    int query(int what, int* value) override {
@@ -1144,6 +1235,125 @@ public:
        return BufferQueueProducer::query(what, value);
    }

    status_t setAsyncMode(bool asyncMode) override {
        if (status_t status = BufferQueueProducer::setAsyncMode(asyncMode); status != NO_ERROR) {
            return status;
        }

        sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
        if (!bbq) {
            return NO_ERROR;
        }

        {
            std::lock_guard lock{bbq->mMutex};
            bbq->mAsyncMode = asyncMode;
            bbq->updateDequeueShouldBlockLocked();
        }

        bbq->mBufferReleaseReader->interruptBlockingRead();
        return NO_ERROR;
    }

    status_t setSharedBufferMode(bool sharedBufferMode) override {
        if (status_t status = BufferQueueProducer::setSharedBufferMode(sharedBufferMode);
            status != NO_ERROR) {
            return status;
        }

        sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
        if (!bbq) {
            return NO_ERROR;
        }

        {
            std::lock_guard lock{bbq->mMutex};
            bbq->mSharedBufferMode = sharedBufferMode;
            bbq->updateDequeueShouldBlockLocked();
        }

        bbq->mBufferReleaseReader->interruptBlockingRead();
        return NO_ERROR;
    }

    status_t detachBuffer(int slot) override {
        if (status_t status = BufferQueueProducer::detachBuffer(slot); status != NO_ERROR) {
            return status;
        }

        sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
        if (!bbq) {
            return NO_ERROR;
        }

        {
            std::lock_guard lock{bbq->mMutex};
            bbq->mNumDequeued--;
            bbq->updateDequeueShouldBlockLocked();
        }

        bbq->mBufferReleaseReader->interruptBlockingRead();
        return NO_ERROR;
    }

    // Override dequeueBuffer to block if there are no free buffers.
    //
    // Buffer releases are communicated via the BufferReleaseChannel. When dequeueBuffer determines
    // a free buffer is not available, it blocks on an epoll file descriptor. Epoll is configured to
    // detect messages on the BufferReleaseChannel's socket and an eventfd. The eventfd is signaled
    // whenever an event other than a buffer release occurs that may change the number of free
    // buffers. dequeueBuffer uses epoll in a similar manner as a condition variable by testing for
    // the availability of a free buffer in a loop, breaking the loop once a free buffer is
    // available.
    //
    // This is an optimization implemented to reduce thread scheduling delays in the previously
    // existing binder release callback. The binder buffer release callback is still used and there
    // are no guarantees around order between buffer releases via binder and the
    // BufferReleaseChannel. If we attempt to a release a buffer here that has already been released
    // via binder, the release is ignored.
    status_t dequeueBuffer(int* outSlot, sp<Fence>* outFence, uint32_t width, uint32_t height,
                           PixelFormat format, uint64_t usage, uint64_t* outBufferAge,
                           FrameEventHistoryDelta* outTimestamps) {
        sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
        if (!bbq || !bbq->mBufferReleaseReader) {
            return BufferQueueProducer::dequeueBuffer(outSlot, outFence, width, height, format,
                                                      usage, outBufferAge, outTimestamps);
        }

        if (bbq->mDequeueShouldBlock) {
            ATRACE_FORMAT("waiting for free buffer");
            auto maxWaitTime = std::chrono::steady_clock::now() + 1s;
            do {
                auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(
                        maxWaitTime - std::chrono::steady_clock::now());
                if (timeout <= 0ms) {
                    break;
                }

                ReleaseCallbackId releaseCallbackId;
                sp<Fence> releaseFence;
                status_t status = bbq->mBufferReleaseReader->readBlocking(releaseCallbackId,
                                                                          releaseFence, timeout);
                if (status == WOULD_BLOCK) {
                    // readBlocking was interrupted. The loop will test if we have a free buffer.
                    continue;
                }

                if (status != OK) {
                    // An error occurred or readBlocking timed out.
                    break;
                }

                std::lock_guard lock{bbq->mMutex};
                bbq->releaseBufferCallbackLocked(releaseCallbackId, releaseFence, std::nullopt,
                                                 false);
            } while (bbq->mDequeueShouldBlock);
        }

        return BufferQueueProducer::dequeueBuffer(outSlot, outFence, width, height, format, usage,
                                                  outBufferAge, outTimestamps);
    }

private:
    const wp<BLASTBufferQueue> mBLASTBufferQueue;
};
@@ -1173,6 +1383,16 @@ void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer
    *outConsumer = consumer;
}

void BLASTBufferQueue::onFirstRef() {
    // safe default, most producers are expected to override this
    //
    // This is done in onFirstRef instead of BLASTBufferQueue's constructor because
    // BBQBufferQueueProducer::setMaxDequeuedBufferCount promotes a weak pointer to BLASTBufferQueue
    // to a strong pointer. If this is done in the constructor, then when the strong pointer goes
    // out of scope, it's the last reference so BLASTBufferQueue is deleted.
    mProducer->setMaxDequeuedBufferCount(2);
}

void BLASTBufferQueue::resizeFrameEventHistory(size_t newSize) {
    // This can be null during creation of the buffer queue, but resizing won't do anything at that
    // point in time, so just ignore. This can go away once the class relationships and lifetimes of
@@ -1222,4 +1442,72 @@ void BLASTBufferQueue::setTransactionHangCallback(
    mTransactionHangCallback = callback;
}

BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(
        std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> endpoint)
      : mEndpoint(std::move(endpoint)) {
    mEpollFd = android::base::unique_fd(epoll_create1(0));
    if (!mEpollFd.ok()) {
        ALOGE("Failed to create buffer release epoll file descriptor. errno=%d message='%s'", errno,
              strerror(errno));
    }

    epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = mEndpoint->getFd();
    if (epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mEndpoint->getFd(), &event) == -1) {
        ALOGE("Failed to register buffer release consumer file descriptor with epoll. errno=%d "
              "message='%s'",
              errno, strerror(errno));
    }

    mEventFd = android::base::unique_fd(eventfd(0, EFD_NONBLOCK));
    event.data.fd = mEventFd.get();
    if (epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mEventFd.get(), &event) == -1) {
        ALOGE("Failed to register buffer release eventfd with epoll. errno=%d message='%s'", errno,
              strerror(errno));
    }
}

status_t BLASTBufferQueue::BufferReleaseReader::readNonBlocking(ReleaseCallbackId& outId,
                                                                sp<Fence>& outFence) {
    std::lock_guard lock{mMutex};
    return mEndpoint->readReleaseFence(outId, outFence);
}

status_t BLASTBufferQueue::BufferReleaseReader::readBlocking(ReleaseCallbackId& outId,
                                                             sp<Fence>& outFence,
                                                             std::chrono::milliseconds timeout) {
    epoll_event event;
    int eventCount = epoll_wait(mEpollFd.get(), &event, 1 /* maxevents */, timeout.count());

    if (eventCount == -1) {
        ALOGE("epoll_wait error while waiting for buffer release. errno=%d message='%s'", errno,
              strerror(errno));
        return UNKNOWN_ERROR;
    }

    if (eventCount == 0) {
        return TIMED_OUT;
    }

    if (event.data.fd == mEventFd.get()) {
        uint64_t value;
        if (read(mEventFd.get(), &value, sizeof(uint64_t)) == -1 && errno != EWOULDBLOCK) {
            ALOGE("error while reading from eventfd. errno=%d message='%s'", errno,
                  strerror(errno));
        }
        return WOULD_BLOCK;
    }

    std::lock_guard lock{mMutex};
    return mEndpoint->readReleaseFence(outId, outFence);
}

void BLASTBufferQueue::BufferReleaseReader::interruptBlockingRead() {
    uint64_t value = 1;
    if (write(mEventFd.get(), &value, sizeof(uint64_t)) == -1) {
        ALOGE("failed to notify dequeue event. errno=%d message='%s'", errno, strerror(errno));
    }
}

} // namespace android
+364 −0

File added.

Preview size limit exceeded, changes collapsed.

+17 −0
Original line number Diff line number Diff line
@@ -194,6 +194,12 @@ status_t layer_state_t::write(Parcel& output) const
    SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio);
    SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio);
    SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint));

    const bool hasBufferReleaseChannel = (bufferReleaseChannel != nullptr);
    SAFE_PARCEL(output.writeBool, hasBufferReleaseChannel);
    if (hasBufferReleaseChannel) {
        SAFE_PARCEL(output.writeParcelable, *bufferReleaseChannel);
    }
    return NO_ERROR;
}

@@ -339,6 +345,12 @@ status_t layer_state_t::read(const Parcel& input)
    SAFE_PARCEL(input.readInt32, &tmpInt32);
    cachingHint = static_cast<gui::CachingHint>(tmpInt32);

    bool hasBufferReleaseChannel;
    SAFE_PARCEL(input.readBool, &hasBufferReleaseChannel);
    if (hasBufferReleaseChannel) {
        bufferReleaseChannel = std::make_shared<gui::BufferReleaseChannel::ProducerEndpoint>();
        SAFE_PARCEL(input.readParcelable, bufferReleaseChannel.get());
    }
    return NO_ERROR;
}

@@ -718,6 +730,10 @@ void layer_state_t::merge(const layer_state_t& other) {
    if (other.what & eFlushJankData) {
        what |= eFlushJankData;
    }
    if (other.what & eBufferReleaseChannelChanged) {
        what |= eBufferReleaseChannelChanged;
        bufferReleaseChannel = other.bufferReleaseChannel;
    }
    if ((other.what & what) != other.what) {
        ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
              "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
@@ -797,6 +813,7 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const {
    CHECK_DIFF(diff, eColorChanged, other, color.rgb);
    CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic);
    CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled);
    if (other.what & eBufferReleaseChannelChanged) diff |= eBufferReleaseChannelChanged;
    return diff;
}

+16 −0
Original line number Diff line number Diff line
@@ -2394,6 +2394,22 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDropI
    return *this;
}

SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBufferReleaseChannel(
        const sp<SurfaceControl>& sc,
        const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel) {
    layer_state_t* s = getLayerState(sc);
    if (!s) {
        mStatus = BAD_INDEX;
        return *this;
    }

    s->what |= layer_state_t::eBufferReleaseChannelChanged;
    s->bufferReleaseChannel = channel;

    registerSurfaceControlForCallback(sc);
    return *this;
}

// ---------------------------------------------------------------------------

DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
Loading