| /* | |
| * Copyright (C) 2019 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #undef LOG_TAG | |
| #define LOG_TAG "BLASTBufferQueue" | |
| #define ATRACE_TAG ATRACE_TAG_GRAPHICS | |
| //#define LOG_NDEBUG 0 | |
| #include <com_android_graphics_libgui_flags.h> | |
| #include <cutils/atomic.h> | |
| #include <ftl/fake_guard.h> | |
| #include <gui/BLASTBufferQueue.h> | |
| #include <gui/BufferItemConsumer.h> | |
| #include <gui/BufferQueueConsumer.h> | |
| #include <gui/BufferQueueCore.h> | |
| #include <gui/BufferQueueProducer.h> | |
| #include <sys/epoll.h> | |
| #include <sys/eventfd.h> | |
| #include <gui/FrameRateUtils.h> | |
| #include <gui/GLConsumer.h> | |
| #include <gui/IProducerListener.h> | |
| #include <gui/Surface.h> | |
| #include <gui/TraceUtils.h> | |
| #include <utils/Singleton.h> | |
| #include <utils/Trace.h> | |
| #include <private/gui/ComposerService.h> | |
| #include <private/gui/ComposerServiceAIDL.h> | |
| #include <android-base/thread_annotations.h> | |
| #include <com_android_graphics_libgui_flags.h> | |
| using namespace com::android::graphics::libgui; | |
| using namespace std::chrono_literals; | |
| namespace { | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| template <class Mutex> | |
| class UnlockGuard { | |
| public: | |
| explicit UnlockGuard(Mutex& lock) : mLock{lock} { mLock.unlock(); } | |
| ~UnlockGuard() { mLock.lock(); } | |
| UnlockGuard(const UnlockGuard&) = delete; | |
| UnlockGuard& operator=(const UnlockGuard&) = delete; | |
| private: | |
| Mutex& mLock; | |
| }; | |
| #endif | |
| inline const char* boolToString(bool b) { | |
| return b ? "true" : "false"; | |
| } | |
| } // namespace | |
| namespace android { | |
| // Macros to include adapter info in log messages | |
| #define BQA_LOGD(x, ...) \ | |
| ALOGD("[%s](f:%u,a:%u) " x, mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__) | |
| #define BQA_LOGV(x, ...) \ | |
| ALOGV("[%s](f:%u,a:%u) " x, mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__) | |
| // enable logs for a single layer | |
| //#define BQA_LOGV(x, ...) \ | |
| // ALOGV_IF((strstr(mName.c_str(), "SurfaceView") != nullptr), "[%s](f:%u,a:%u) " x, \ | |
| // mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__) | |
| #define BQA_LOGE(x, ...) \ | |
| ALOGE("[%s](f:%u,a:%u) " x, mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__) | |
| #define BBQ_TRACE(x, ...) \ | |
| ATRACE_FORMAT("%s - %s(f:%u,a:%u)" x, __FUNCTION__, mName.c_str(), mNumFrameAvailable, \ | |
| mNumAcquired, ##__VA_ARGS__) | |
| #define UNIQUE_LOCK_WITH_ASSERTION(mutex) \ | |
| std::unique_lock _lock{mutex}; \ | |
| base::ScopedLockAssertion assumeLocked(mutex); | |
| void BLASTBufferItemConsumer::onDisconnect() { | |
| Mutex::Autolock lock(mMutex); | |
| mPreviouslyConnected = mCurrentlyConnected; | |
| mCurrentlyConnected = false; | |
| if (mPreviouslyConnected) { | |
| mDisconnectEvents.push(mCurrentFrameNumber); | |
| } | |
| mFrameEventHistory.onDisconnect(); | |
| } | |
| void BLASTBufferItemConsumer::addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps, | |
| FrameEventHistoryDelta* outDelta) { | |
| Mutex::Autolock lock(mMutex); | |
| if (newTimestamps) { | |
| // BufferQueueProducer only adds a new timestamp on | |
| // queueBuffer | |
| mCurrentFrameNumber = newTimestamps->frameNumber; | |
| mFrameEventHistory.addQueue(*newTimestamps); | |
| } | |
| if (outDelta) { | |
| // frame event histories will be processed | |
| // only after the producer connects and requests | |
| // deltas for the first time. Forward this intent | |
| // to SF-side to turn event processing back on | |
| mPreviouslyConnected = mCurrentlyConnected; | |
| mCurrentlyConnected = true; | |
| mFrameEventHistory.getAndResetDelta(outDelta); | |
| } | |
| } | |
| void BLASTBufferItemConsumer::updateFrameTimestamps( | |
| uint64_t frameNumber, uint64_t previousFrameNumber, nsecs_t refreshStartTime, | |
| const sp<Fence>& glDoneFence, const sp<Fence>& presentFence, | |
| const sp<Fence>& prevReleaseFence, CompositorTiming compositorTiming, nsecs_t latchTime, | |
| nsecs_t dequeueReadyTime) { | |
| Mutex::Autolock lock(mMutex); | |
| // if the producer is not connected, don't bother updating, | |
| // the next producer that connects won't access this frame event | |
| if (!mCurrentlyConnected) return; | |
| std::shared_ptr<FenceTime> glDoneFenceTime = std::make_shared<FenceTime>(glDoneFence); | |
| std::shared_ptr<FenceTime> presentFenceTime = std::make_shared<FenceTime>(presentFence); | |
| std::shared_ptr<FenceTime> releaseFenceTime = std::make_shared<FenceTime>(prevReleaseFence); | |
| mFrameEventHistory.addLatch(frameNumber, latchTime); | |
| if (flags::frametimestamps_previousrelease()) { | |
| if (previousFrameNumber > 0) { | |
| mFrameEventHistory.addRelease(previousFrameNumber, dequeueReadyTime, | |
| std::move(releaseFenceTime)); | |
| } | |
| } else { | |
| mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime)); | |
| } | |
| mFrameEventHistory.addPreComposition(frameNumber, refreshStartTime); | |
| mFrameEventHistory.addPostComposition(frameNumber, glDoneFenceTime, presentFenceTime, | |
| compositorTiming); | |
| } | |
| void BLASTBufferItemConsumer::getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect) { | |
| bool disconnect = false; | |
| Mutex::Autolock lock(mMutex); | |
| while (!mDisconnectEvents.empty() && mDisconnectEvents.front() <= frameNumber) { | |
| disconnect = true; | |
| mDisconnectEvents.pop(); | |
| } | |
| if (needsDisconnect != nullptr) *needsDisconnect = disconnect; | |
| } | |
| void BLASTBufferItemConsumer::onSidebandStreamChanged() { | |
| sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote(); | |
| if (bbq != nullptr) { | |
| sp<NativeHandle> stream = getSidebandStream(); | |
| bbq->setSidebandStream(stream); | |
| } | |
| } | |
| void BLASTBufferItemConsumer::onSetFrameRate(float frameRate, int8_t compatibility, | |
| int8_t changeFrameRateStrategy) { | |
| sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote(); | |
| if (bbq != nullptr) { | |
| bbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy); | |
| } | |
| } | |
| void BLASTBufferItemConsumer::resizeFrameEventHistory(size_t newSize) { | |
| Mutex::Autolock lock(mMutex); | |
| mFrameEventHistory.resize(newSize); | |
| } | |
| void BLASTBufferQueue::initialize() { | |
| std::lock_guard _lock{mMutex}; | |
| createBufferQueue(&mProducer, &mConsumer); | |
| mBufferItemConsumer = | |
| sp<BLASTBufferItemConsumer>::make(mProducer, mConsumer, | |
| GraphicBuffer::USAGE_HW_COMPOSER | | |
| GraphicBuffer::USAGE_HW_TEXTURE, | |
| 1, false, wp<BLASTBufferQueue>::fromExisting(this)); | |
| // since the adapter is in the client process, set dequeue timeout | |
| // explicitly so that dequeueBuffer will block | |
| mProducer->setDequeueTimeout(std::numeric_limits<int64_t>::max()); | |
| auto consumerName = mName + "(BLAST Consumer)" + std::to_string(mProducerId); | |
| mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(mProducerId); | |
| mBufferItemConsumer->setName(String8(consumerName.c_str())); | |
| mBufferItemConsumer->setFrameAvailableListener(wp<BLASTBufferQueue>::fromExisting(this)); | |
| ComposerServiceAIDL::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers); | |
| mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers); | |
| mCurrentMaxAcquiredBufferCount = mMaxAcquiredBuffers; | |
| TransactionCompletedListener::getInstance()->addQueueStallListener( | |
| [&](const std::string& reason) { | |
| std::function<void(const std::string&)> callbackCopy; | |
| { | |
| std::unique_lock _lock{mMutex}; | |
| callbackCopy = mTransactionHangCallback; | |
| } | |
| if (callbackCopy) callbackCopy(reason); | |
| }, | |
| this); | |
| // safe default, most producers are expected to override this | |
| mProducer->setMaxDequeuedBufferCount(2); | |
| BQA_LOGV("BLASTBufferQueue created"); | |
| } | |
| BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinationFrame) | |
| : mSurfaceControl(nullptr), | |
| mSize(1, 1), | |
| mRequestedSize(mSize), | |
| mFormat(PIXEL_FORMAT_RGBA_8888), | |
| mTransactionReadyCallback(nullptr), | |
| mSyncTransaction(nullptr), | |
| mUpdateDestinationFrame(updateDestinationFrame) { | |
| static std::atomic<uint32_t> nextId = 0; | |
| mProducerId = nextId++; | |
| mName = name + "#" + std::to_string(mProducerId); | |
| } | |
| BLASTBufferQueue::~BLASTBufferQueue() { | |
| TransactionCompletedListener::getInstance()->removeQueueStallListener(this); | |
| if (mPendingTransactions.empty()) { | |
| return; | |
| } | |
| BQA_LOGE("Applying pending transactions on dtor %d", | |
| static_cast<uint32_t>(mPendingTransactions.size())); | |
| SurfaceComposerClient::Transaction t; | |
| mergePendingTransactions(&t, std::numeric_limits<uint64_t>::max() /* frameNumber */); | |
| // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction | |
| t.setApplyToken(mApplyToken).apply(false, true); | |
| if (mTransactionReadyCallback) { | |
| mTransactionReadyCallback(mSyncTransaction); | |
| } | |
| } | |
| void BLASTBufferQueue::onFirstRef() { | |
| initialize(); | |
| } | |
| void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height, | |
| int32_t format) { | |
| LOG_ALWAYS_FATAL_IF(surface == nullptr, "BLASTBufferQueue: mSurfaceControl must not be NULL"); | |
| std::lock_guard _lock{mMutex}; | |
| if (mFormat != format) { | |
| mFormat = format; | |
| mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format)); | |
| } | |
| const bool surfaceControlChanged = !SurfaceControl::isSameSurface(mSurfaceControl, surface); | |
| if (surfaceControlChanged && mSurfaceControl != nullptr) { | |
| BQA_LOGD("Updating SurfaceControl without recreating BBQ"); | |
| mSetBufferBarrier = false; | |
| } | |
| // Always update the native object even though they might have the same layer handle, so we can | |
| // get the updated transform hint from WM. | |
| mSurfaceControl = surface; | |
| SurfaceComposerClient::Transaction t; | |
| bool applyTransaction = false; | |
| if (surfaceControlChanged) { | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| updateBufferReleaseProducer(); | |
| #endif | |
| t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure, | |
| layer_state_t::eEnableBackpressure); | |
| // Migrate the picture profile handle to the new surface control. | |
| if (com_android_graphics_libgui_flags_apply_picture_profiles() && | |
| mPictureProfileHandle.has_value()) { | |
| t.setPictureProfileHandle(mSurfaceControl, *mPictureProfileHandle); | |
| } | |
| applyTransaction = true; | |
| } | |
| mTransformHint = mSurfaceControl->getTransformHint(); | |
| mBufferItemConsumer->setTransformHint(mTransformHint); | |
| BQA_LOGV("update width=%d height=%d format=%d mTransformHint=%d", width, height, format, | |
| mTransformHint); | |
| ui::Size newSize(width, height); | |
| if (mRequestedSize != newSize) { | |
| mRequestedSize.set(newSize); | |
| mBufferItemConsumer->setDefaultBufferSize(mRequestedSize.width, mRequestedSize.height); | |
| if (mLastBufferInfo.scalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) { | |
| // If the buffer supports scaling, update the frame immediately since the client may | |
| // want to scale the existing buffer to the new size. | |
| mSize = mRequestedSize; | |
| if (mUpdateDestinationFrame) { | |
| t.setDestinationFrame(mSurfaceControl, Rect(newSize)); | |
| applyTransaction = true; | |
| } | |
| } | |
| } | |
| if (applyTransaction) { | |
| // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction | |
| t.setApplyToken(mApplyToken).apply(false /* synchronous */, true /* oneWay */); | |
| } | |
| } | |
| static std::optional<SurfaceControlStats> findMatchingStat( | |
| const std::vector<SurfaceControlStats>& stats, const sp<SurfaceControl>& sc) { | |
| for (auto stat : stats) { | |
| if (SurfaceControl::isSameSurface(sc, stat.surfaceControl)) { | |
| return stat; | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| TransactionCompletedCallbackTakesContext BLASTBufferQueue::makeTransactionCommittedCallbackThunk() { | |
| return [bbq = sp<BLASTBufferQueue>::fromExisting( | |
| this)](void* /*context*/, nsecs_t latchTime, const sp<Fence>& presentFence, | |
| const std::vector<SurfaceControlStats>& stats) { | |
| bbq->transactionCommittedCallback(latchTime, presentFence, stats); | |
| }; | |
| } | |
| void BLASTBufferQueue::transactionCommittedCallback(nsecs_t /*latchTime*/, | |
| const sp<Fence>& /*presentFence*/, | |
| const std::vector<SurfaceControlStats>& stats) { | |
| { | |
| std::lock_guard _lock{mMutex}; | |
| BBQ_TRACE(); | |
| BQA_LOGV("transactionCommittedCallback"); | |
| if (!mSurfaceControlsWithPendingCallback.empty()) { | |
| sp<SurfaceControl> pendingSC = mSurfaceControlsWithPendingCallback.front(); | |
| std::optional<SurfaceControlStats> stat = findMatchingStat(stats, pendingSC); | |
| if (stat) { | |
| uint64_t currFrameNumber = stat->frameEventStats.frameNumber; | |
| // We need to check if we were waiting for a transaction callback in order to | |
| // process any pending buffers and unblock. It's possible to get transaction | |
| // callbacks for previous requests so we need to ensure that there are no pending | |
| // frame numbers that were in a sync. We remove the frame from mSyncedFrameNumbers | |
| // set and then check if it's empty. If there are no more pending syncs, we can | |
| // proceed with flushing the shadow queue. | |
| mSyncedFrameNumbers.erase(currFrameNumber); | |
| if (mSyncedFrameNumbers.empty()) { | |
| flushShadowQueue(); | |
| } | |
| } else { | |
| BQA_LOGE("Failed to find matching SurfaceControl in transactionCommittedCallback"); | |
| } | |
| } else { | |
| BQA_LOGE("No matching SurfaceControls found: mSurfaceControlsWithPendingCallback was " | |
| "empty."); | |
| } | |
| } | |
| } | |
| TransactionCompletedCallbackTakesContext BLASTBufferQueue::makeTransactionCallbackThunk() { | |
| return [bbq = sp<BLASTBufferQueue>::fromExisting( | |
| this)](void* /*context*/, nsecs_t latchTime, const sp<Fence>& presentFence, | |
| const std::vector<SurfaceControlStats>& stats) { | |
| bbq->transactionCallback(latchTime, presentFence, stats); | |
| }; | |
| } | |
| void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence>& /*presentFence*/, | |
| const std::vector<SurfaceControlStats>& stats) { | |
| { | |
| std::lock_guard _lock{mMutex}; | |
| BBQ_TRACE(); | |
| BQA_LOGV("transactionCallback"); | |
| if (!mSurfaceControlsWithPendingCallback.empty()) { | |
| sp<SurfaceControl> pendingSC = mSurfaceControlsWithPendingCallback.front(); | |
| mSurfaceControlsWithPendingCallback.pop(); | |
| std::optional<SurfaceControlStats> statsOptional = findMatchingStat(stats, pendingSC); | |
| if (statsOptional) { | |
| SurfaceControlStats stat = *statsOptional; | |
| if (stat.transformHint) { | |
| mTransformHint = *stat.transformHint; | |
| mBufferItemConsumer->setTransformHint(mTransformHint); | |
| BQA_LOGV("updated mTransformHint=%d", mTransformHint); | |
| } | |
| // Update frametime stamps if the frame was latched and presented, indicated by a | |
| // valid latch time. | |
| if (stat.latchTime > 0) { | |
| mBufferItemConsumer | |
| ->updateFrameTimestamps(stat.frameEventStats.frameNumber, | |
| stat.frameEventStats.previousFrameNumber, | |
| stat.frameEventStats.refreshStartTime, | |
| stat.frameEventStats.gpuCompositionDoneFence, | |
| stat.presentFence, stat.previousReleaseFence, | |
| stat.frameEventStats.compositorTiming, | |
| stat.latchTime, | |
| stat.frameEventStats.dequeueReadyTime); | |
| } | |
| auto currFrameNumber = stat.frameEventStats.frameNumber; | |
| // Release stale buffers. | |
| for (const auto& [key, _] : mSubmitted) { | |
| if (currFrameNumber <= key.framenumber) { | |
| continue; // not stale. | |
| } | |
| releaseBufferCallbackLocked(key, | |
| stat.previousReleaseFence | |
| ? stat.previousReleaseFence | |
| : Fence::NO_FENCE, | |
| stat.currentMaxAcquiredBufferCount, | |
| true /* fakeRelease */); | |
| } | |
| } else { | |
| BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback"); | |
| } | |
| } else { | |
| BQA_LOGE("No matching SurfaceControls found: mSurfaceControlsWithPendingCallback was " | |
| "empty."); | |
| } | |
| } | |
| } | |
| void BLASTBufferQueue::flushShadowQueue() { | |
| BQA_LOGV("flushShadowQueue"); | |
| int32_t numFramesToFlush = mNumFrameAvailable; | |
| while (numFramesToFlush > 0) { | |
| acquireNextBufferLocked(std::nullopt); | |
| numFramesToFlush--; | |
| } | |
| } | |
| // Unlike transactionCallbackThunk the release buffer callback does not extend the life of the | |
| // BBQ. This is because if the BBQ is destroyed, then the buffers will be released by the client. | |
| // So we pass in a weak pointer to the BBQ and if it still alive, then we release the buffer. | |
| // Otherwise, this is a no-op. | |
| ReleaseBufferCallback BLASTBufferQueue::makeReleaseBufferCallbackThunk() { | |
| return [weakBbq = wp<BLASTBufferQueue>::fromExisting( | |
| this)](const ReleaseCallbackId& id, const sp<Fence>& releaseFence, | |
| std::optional<uint32_t> currentMaxAcquiredBufferCount) { | |
| sp<BLASTBufferQueue> bbq = weakBbq.promote(); | |
| if (!bbq) { | |
| ALOGV("releaseBufferCallbackThunk %s blastBufferQueue is dead", id.to_string().c_str()); | |
| return; | |
| } | |
| bbq->releaseBufferCallback(id, releaseFence, currentMaxAcquiredBufferCount); | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| bbq->drainBufferReleaseConsumer(); | |
| #endif | |
| }; | |
| } | |
| void BLASTBufferQueue::releaseBufferCallback( | |
| const ReleaseCallbackId& id, const sp<Fence>& releaseFence, | |
| std::optional<uint32_t> currentMaxAcquiredBufferCount) { | |
| std::lock_guard _lock{mMutex}; | |
| BBQ_TRACE(); | |
| releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount, | |
| false /* fakeRelease */); | |
| } | |
| void BLASTBufferQueue::releaseBufferCallbackLocked( | |
| const ReleaseCallbackId& id, const sp<Fence>& releaseFence, | |
| std::optional<uint32_t> currentMaxAcquiredBufferCount, bool fakeRelease) { | |
| ATRACE_CALL(); | |
| BQA_LOGV("releaseBufferCallback %s", id.to_string().c_str()); | |
| // Calculate how many buffers we need to hold before we release them back | |
| // to the buffer queue. This will prevent higher latency when we are running | |
| // on a lower refresh rate than the max supported. We only do that for EGL | |
| // clients as others don't care about latency | |
| const auto it = mSubmitted.find(id); | |
| const bool isEGL = it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL; | |
| if (currentMaxAcquiredBufferCount) { | |
| mCurrentMaxAcquiredBufferCount = *currentMaxAcquiredBufferCount; | |
| } | |
| const uint32_t numPendingBuffersToHold = | |
| isEGL ? std::max(0, mMaxAcquiredBuffers - (int32_t)mCurrentMaxAcquiredBufferCount) : 0; | |
| auto rb = ReleasedBuffer{id, releaseFence}; | |
| if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) { | |
| mPendingRelease.emplace_back(rb); | |
| if (fakeRelease) { | |
| BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback %" PRIu64, | |
| id.framenumber); | |
| BBQ_TRACE("FakeReleaseCallback"); | |
| } | |
| } | |
| // Release all buffers that are beyond the ones that we need to hold | |
| while (mPendingRelease.size() > numPendingBuffersToHold) { | |
| const auto releasedBuffer = mPendingRelease.front(); | |
| mPendingRelease.pop_front(); | |
| releaseBuffer(releasedBuffer.callbackId, releasedBuffer.releaseFence); | |
| // Don't process the transactions here if mSyncedFrameNumbers is not empty. That means | |
| // are still transactions that have sync buffers in them that have not been applied or | |
| // dropped. Instead, let onFrameAvailable handle processing them since it will merge with | |
| // the syncTransaction. | |
| if (mSyncedFrameNumbers.empty()) { | |
| acquireNextBufferLocked(std::nullopt); | |
| } | |
| } | |
| ATRACE_INT("PendingRelease", mPendingRelease.size()); | |
| ATRACE_INT(mQueuedBufferTrace.c_str(), | |
| mNumFrameAvailable + mNumAcquired - mPendingRelease.size()); | |
| mCallbackCV.notify_all(); | |
| } | |
| void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId, | |
| const sp<Fence>& releaseFence) { | |
| auto it = mSubmitted.find(callbackId); | |
| if (it == mSubmitted.end()) { | |
| return; | |
| } | |
| mNumAcquired--; | |
| BBQ_TRACE("frame=%" PRIu64, callbackId.framenumber); | |
| BQA_LOGV("released %s", callbackId.to_string().c_str()); | |
| mBufferItemConsumer->releaseBuffer(it->second, releaseFence); | |
| mSubmitted.erase(it); | |
| // Remove the frame number from mSyncedFrameNumbers since we can get a release callback | |
| // without getting a transaction committed if the buffer was dropped. | |
| mSyncedFrameNumbers.erase(callbackId.framenumber); | |
| } | |
| static ui::Size getBufferSize(const BufferItem& item) { | |
| uint32_t bufWidth = item.mGraphicBuffer->getWidth(); | |
| uint32_t bufHeight = item.mGraphicBuffer->getHeight(); | |
| // Take the buffer's orientation into account | |
| if (item.mTransform & ui::Transform::ROT_90) { | |
| std::swap(bufWidth, bufHeight); | |
| } | |
| return ui::Size(bufWidth, bufHeight); | |
| } | |
| status_t BLASTBufferQueue::acquireNextBufferLocked( | |
| const std::optional<SurfaceComposerClient::Transaction*> transaction) { | |
| // Check if we have frames available and we have not acquired the maximum number of buffers. | |
| // Even with this check, the consumer can fail to acquire an additional buffer if the consumer | |
| // has already acquired (mMaxAcquiredBuffers + 1) and the new buffer is not droppable. In this | |
| // case mBufferItemConsumer->acquireBuffer will return with NO_BUFFER_AVAILABLE. | |
| if (mNumFrameAvailable == 0) { | |
| BQA_LOGV("Can't acquire next buffer. No available frames"); | |
| return BufferQueue::NO_BUFFER_AVAILABLE; | |
| } | |
| if (mNumAcquired >= (mMaxAcquiredBuffers + 2)) { | |
| BQA_LOGV("Can't acquire next buffer. Already acquired max frames %d max:%d + 2", | |
| mNumAcquired, mMaxAcquiredBuffers); | |
| return BufferQueue::NO_BUFFER_AVAILABLE; | |
| } | |
| if (mSurfaceControl == nullptr) { | |
| BQA_LOGE("ERROR : surface control is null"); | |
| return NAME_NOT_FOUND; | |
| } | |
| SurfaceComposerClient::Transaction localTransaction; | |
| bool applyTransaction = true; | |
| SurfaceComposerClient::Transaction* t = &localTransaction; | |
| if (transaction) { | |
| t = *transaction; | |
| applyTransaction = false; | |
| } | |
| BufferItem bufferItem; | |
| status_t status = | |
| mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false); | |
| if (status == BufferQueue::NO_BUFFER_AVAILABLE) { | |
| BQA_LOGV("Failed to acquire a buffer, err=NO_BUFFER_AVAILABLE"); | |
| return status; | |
| } else if (status != OK) { | |
| BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str()); | |
| return status; | |
| } | |
| auto buffer = bufferItem.mGraphicBuffer; | |
| mNumFrameAvailable--; | |
| BBQ_TRACE("frame=%" PRIu64, bufferItem.mFrameNumber); | |
| if (buffer == nullptr) { | |
| mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE); | |
| BQA_LOGE("Buffer was empty"); | |
| return BAD_VALUE; | |
| } | |
| if (rejectBuffer(bufferItem)) { | |
| BQA_LOGE("rejecting buffer:active_size=%dx%d, requested_size=%dx%d " | |
| "buffer{size=%dx%d transform=%d}", | |
| mSize.width, mSize.height, mRequestedSize.width, mRequestedSize.height, | |
| buffer->getWidth(), buffer->getHeight(), bufferItem.mTransform); | |
| mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE); | |
| return acquireNextBufferLocked(transaction); | |
| } | |
| mNumAcquired++; | |
| mLastAcquiredFrameNumber = bufferItem.mFrameNumber; | |
| ReleaseCallbackId releaseCallbackId(buffer->getId(), mLastAcquiredFrameNumber); | |
| mSubmitted.emplace_or_replace(releaseCallbackId, bufferItem); | |
| bool needsDisconnect = false; | |
| mBufferItemConsumer->getConnectionEvents(bufferItem.mFrameNumber, &needsDisconnect); | |
| // if producer disconnected before, notify SurfaceFlinger | |
| if (needsDisconnect) { | |
| t->notifyProducerDisconnect(mSurfaceControl); | |
| } | |
| // Only update mSize for destination bounds if the incoming buffer matches the requested size. | |
| // Otherwise, it could cause stretching since the destination bounds will update before the | |
| // buffer with the new size is acquired. | |
| if (mRequestedSize == getBufferSize(bufferItem) || | |
| bufferItem.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) { | |
| mSize = mRequestedSize; | |
| } | |
| Rect crop = computeCrop(bufferItem); | |
| mLastBufferInfo.update(true /* hasBuffer */, bufferItem.mGraphicBuffer->getWidth(), | |
| bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform, | |
| bufferItem.mScalingMode, crop); | |
| auto releaseBufferCallback = makeReleaseBufferCallbackThunk(); | |
| sp<Fence> fence = | |
| bufferItem.mFence ? sp<Fence>::make(bufferItem.mFence->dup()) : Fence::NO_FENCE; | |
| nsecs_t dequeueTime = -1; | |
| { | |
| std::lock_guard _lock{mTimestampMutex}; | |
| auto dequeueTimeIt = mDequeueTimestamps.find(buffer->getId()); | |
| if (dequeueTimeIt != mDequeueTimestamps.end()) { | |
| dequeueTime = dequeueTimeIt->second; | |
| mDequeueTimestamps.erase(dequeueTimeIt); | |
| } | |
| } | |
| t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, mProducerId, | |
| releaseBufferCallback, dequeueTime); | |
| t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace)); | |
| t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata); | |
| t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage); | |
| t->addTransactionCompletedCallback(makeTransactionCallbackThunk(), nullptr); | |
| mSurfaceControlsWithPendingCallback.push(mSurfaceControl); | |
| if (mUpdateDestinationFrame) { | |
| t->setDestinationFrame(mSurfaceControl, Rect(mSize)); | |
| } else { | |
| const bool ignoreDestinationFrame = | |
| bufferItem.mScalingMode == NATIVE_WINDOW_SCALING_MODE_FREEZE; | |
| t->setFlags(mSurfaceControl, | |
| ignoreDestinationFrame ? layer_state_t::eIgnoreDestinationFrame : 0, | |
| layer_state_t::eIgnoreDestinationFrame); | |
| } | |
| t->setBufferCrop(mSurfaceControl, crop); | |
| t->setTransform(mSurfaceControl, bufferItem.mTransform); | |
| t->setTransformToDisplayInverse(mSurfaceControl, bufferItem.mTransformToDisplayInverse); | |
| t->setAutoRefresh(mSurfaceControl, bufferItem.mAutoRefresh); | |
| if (!bufferItem.mIsAutoTimestamp) { | |
| t->setDesiredPresentTime(bufferItem.mTimestamp); | |
| } | |
| if (com_android_graphics_libgui_flags_apply_picture_profiles() && | |
| bufferItem.mPictureProfileHandle.has_value()) { | |
| t->setPictureProfileHandle(mSurfaceControl, *bufferItem.mPictureProfileHandle); | |
| // The current picture profile must be maintained in case the BBQ gets its | |
| // SurfaceControl switched out. | |
| mPictureProfileHandle = bufferItem.mPictureProfileHandle; | |
| // Clear out the picture profile if the requestor has asked for it to be cleared | |
| if (mPictureProfileHandle == PictureProfileHandle::NONE) { | |
| mPictureProfileHandle = std::nullopt; | |
| } | |
| } | |
| // Drop stale frame timeline infos | |
| while (!mPendingFrameTimelines.empty() && | |
| mPendingFrameTimelines.front().first < bufferItem.mFrameNumber) { | |
| ATRACE_FORMAT_INSTANT("dropping stale frameNumber: %" PRIu64 " vsyncId: %" PRId64, | |
| mPendingFrameTimelines.front().first, | |
| mPendingFrameTimelines.front().second.vsyncId); | |
| mPendingFrameTimelines.pop(); | |
| } | |
| if (!mPendingFrameTimelines.empty() && | |
| mPendingFrameTimelines.front().first == bufferItem.mFrameNumber) { | |
| ATRACE_FORMAT_INSTANT("Transaction::setFrameTimelineInfo frameNumber: %" PRIu64 | |
| " vsyncId: %" PRId64, | |
| bufferItem.mFrameNumber, | |
| mPendingFrameTimelines.front().second.vsyncId); | |
| t->setFrameTimelineInfo(mPendingFrameTimelines.front().second); | |
| mPendingFrameTimelines.pop(); | |
| } | |
| mergePendingTransactions(t, bufferItem.mFrameNumber); | |
| if (applyTransaction) { | |
| // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction | |
| status_t status = t->setApplyToken(mApplyToken).apply(false, true); | |
| LOG_ALWAYS_FATAL_IF(status != OK, | |
| "[%s] acquireNextBufferLocked failed to apply transaction. status=%d", | |
| mName.c_str(), status); | |
| mAppliedLastTransaction = true; | |
| mLastAppliedFrameNumber = bufferItem.mFrameNumber; | |
| mSetBufferBarrier = true; | |
| } else { | |
| if (mSetBufferBarrier) { | |
| t->setBufferHasBarrier(mSurfaceControl, mLastAppliedFrameNumber); | |
| } | |
| mAppliedLastTransaction = false; | |
| } | |
| BQA_LOGV("acquireNextBufferLocked size=%dx%d mFrameNumber=%" PRIu64 | |
| " applyTransaction=%s mTimestamp=%" PRId64 "%s mPendingTransactions.size=%d" | |
| " graphicBufferId=%" PRIu64 "%s transform=%d", | |
| mSize.width, mSize.height, bufferItem.mFrameNumber, boolToString(applyTransaction), | |
| bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp ? "(auto)" : "", | |
| static_cast<uint32_t>(mPendingTransactions.size()), bufferItem.mGraphicBuffer->getId(), | |
| bufferItem.mAutoRefresh ? " mAutoRefresh" : "", bufferItem.mTransform); | |
| return OK; | |
| } | |
| Rect BLASTBufferQueue::computeCrop(const BufferItem& item) { | |
| if (item.mScalingMode == NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) { | |
| return GLConsumer::scaleDownCrop(item.mCrop, mSize.width, mSize.height); | |
| } | |
| return item.mCrop; | |
| } | |
| void BLASTBufferQueue::acquireAndReleaseBuffer() { | |
| BBQ_TRACE(); | |
| BufferItem bufferItem; | |
| status_t status = | |
| mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false); | |
| if (status != OK) { | |
| BQA_LOGE("Failed to acquire a buffer in acquireAndReleaseBuffer, err=%s", | |
| statusToString(status).c_str()); | |
| return; | |
| } | |
| mNumFrameAvailable--; | |
| mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence); | |
| } | |
| void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { | |
| std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr; | |
| SurfaceComposerClient::Transaction* prevTransaction = nullptr; | |
| { | |
| UNIQUE_LOCK_WITH_ASSERTION(mMutex); | |
| BBQ_TRACE(); | |
| bool waitForTransactionCallback = !mSyncedFrameNumbers.empty(); | |
| const bool syncTransactionSet = mTransactionReadyCallback != nullptr; | |
| BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet)); | |
| if (syncTransactionSet) { | |
| // If we are going to re-use the same mSyncTransaction, release the buffer that may | |
| // already be set in the Transaction. This is to allow us a free slot early to continue | |
| // processing a new buffer. | |
| if (!mAcquireSingleBuffer) { | |
| auto bufferData = mSyncTransaction->getAndClearBuffer(mSurfaceControl); | |
| if (bufferData) { | |
| BQA_LOGD("Releasing previous buffer when syncing: framenumber=%" PRIu64, | |
| bufferData->frameNumber); | |
| releaseBuffer(bufferData->generateReleaseCallbackId(), | |
| bufferData->acquireFence); | |
| } | |
| } | |
| if (waitForTransactionCallback) { | |
| // We are waiting on a previous sync's transaction callback so allow another sync | |
| // transaction to proceed. | |
| // | |
| // We need to first flush out the transactions that were in between the two syncs. | |
| // We do this by merging them into mSyncTransaction so any buffer merging will get | |
| // a release callback invoked. | |
| while (mNumFrameAvailable > 0) { | |
| // flush out the shadow queue | |
| acquireAndReleaseBuffer(); | |
| } | |
| } else { | |
| // Make sure the frame available count is 0 before proceeding with a sync to ensure | |
| // the correct frame is used for the sync. The only way mNumFrameAvailable would be | |
| // greater than 0 is if we already ran out of buffers previously. This means we | |
| // need to flush the buffers before proceeding with the sync. | |
| while (mNumFrameAvailable > 0) { | |
| BQA_LOGD("waiting until no queued buffers"); | |
| mCallbackCV.wait(_lock); | |
| } | |
| } | |
| } | |
| // add to shadow queue | |
| mNumFrameAvailable++; | |
| if (waitForTransactionCallback && mNumFrameAvailable >= 2) { | |
| acquireAndReleaseBuffer(); | |
| } | |
| ATRACE_INT(mQueuedBufferTrace.c_str(), | |
| mNumFrameAvailable + mNumAcquired - mPendingRelease.size()); | |
| BQA_LOGV("onFrameAvailable framenumber=%" PRIu64 " syncTransactionSet=%s", | |
| item.mFrameNumber, boolToString(syncTransactionSet)); | |
| if (syncTransactionSet) { | |
| // Add to mSyncedFrameNumbers before waiting in case any buffers are released | |
| // while waiting for a free buffer. The release and commit callback will try to | |
| // acquire buffers if there are any available, but we don't want it to acquire | |
| // in the case where a sync transaction wants the buffer. | |
| mSyncedFrameNumbers.emplace(item.mFrameNumber); | |
| // If there's no available buffer and we're in a sync transaction, we need to wait | |
| // instead of returning since we guarantee a buffer will be acquired for the sync. | |
| while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) { | |
| BQA_LOGD("waiting for available buffer"); | |
| mCallbackCV.wait(_lock); | |
| } | |
| // Only need a commit callback when syncing to ensure the buffer that's synced has been | |
| // sent to SF | |
| mSyncTransaction | |
| ->addTransactionCommittedCallback(makeTransactionCommittedCallbackThunk(), | |
| nullptr); | |
| if (mAcquireSingleBuffer) { | |
| prevCallback = mTransactionReadyCallback; | |
| prevTransaction = mSyncTransaction; | |
| mTransactionReadyCallback = nullptr; | |
| mSyncTransaction = nullptr; | |
| } | |
| } else if (!waitForTransactionCallback) { | |
| acquireNextBufferLocked(std::nullopt); | |
| } | |
| } | |
| if (prevCallback) { | |
| prevCallback(prevTransaction); | |
| } | |
| } | |
| void BLASTBufferQueue::onFrameReplaced(const BufferItem& item) { | |
| BQA_LOGV("onFrameReplaced framenumber=%" PRIu64, item.mFrameNumber); | |
| // Do nothing since we are not storing unacquired buffer items locally. | |
| } | |
| void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) { | |
| std::lock_guard _lock{mTimestampMutex}; | |
| mDequeueTimestamps.emplace_or_replace(bufferId, systemTime()); | |
| }; | |
| void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) { | |
| std::lock_guard _lock{mTimestampMutex}; | |
| mDequeueTimestamps.erase(bufferId); | |
| } | |
| bool BLASTBufferQueue::syncNextTransaction( | |
| std::function<void(SurfaceComposerClient::Transaction*)> callback, | |
| bool acquireSingleBuffer) { | |
| LOG_ALWAYS_FATAL_IF(!callback, | |
| "BLASTBufferQueue: callback passed in to syncNextTransaction must not be " | |
| "NULL"); | |
| std::lock_guard _lock{mMutex}; | |
| BBQ_TRACE(); | |
| if (mTransactionReadyCallback) { | |
| ALOGW("Attempting to overwrite transaction callback in syncNextTransaction"); | |
| return false; | |
| } | |
| mTransactionReadyCallback = callback; | |
| mSyncTransaction = new SurfaceComposerClient::Transaction(); | |
| mAcquireSingleBuffer = acquireSingleBuffer; | |
| return true; | |
| } | |
| void BLASTBufferQueue::stopContinuousSyncTransaction() { | |
| std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr; | |
| SurfaceComposerClient::Transaction* prevTransaction = nullptr; | |
| { | |
| std::lock_guard _lock{mMutex}; | |
| if (mAcquireSingleBuffer || !mTransactionReadyCallback) { | |
| ALOGW("Attempting to stop continuous sync when none are active"); | |
| return; | |
| } | |
| prevCallback = mTransactionReadyCallback; | |
| prevTransaction = mSyncTransaction; | |
| mTransactionReadyCallback = nullptr; | |
| mSyncTransaction = nullptr; | |
| mAcquireSingleBuffer = true; | |
| } | |
| if (prevCallback) { | |
| prevCallback(prevTransaction); | |
| } | |
| } | |
| void BLASTBufferQueue::clearSyncTransaction() { | |
| std::lock_guard _lock{mMutex}; | |
| if (!mAcquireSingleBuffer) { | |
| ALOGW("Attempting to clear sync transaction when none are active"); | |
| return; | |
| } | |
| mTransactionReadyCallback = nullptr; | |
| mSyncTransaction = nullptr; | |
| } | |
| bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) { | |
| if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) { | |
| // Only reject buffers if scaling mode is freeze. | |
| return false; | |
| } | |
| ui::Size bufferSize = getBufferSize(item); | |
| if (mRequestedSize != mSize && mRequestedSize == bufferSize) { | |
| return false; | |
| } | |
| // reject buffers if the buffer size doesn't match. | |
| return mSize != bufferSize; | |
| } | |
| class BBQSurface : public Surface { | |
| private: | |
| std::mutex mMutex; | |
| sp<BLASTBufferQueue> mBbq GUARDED_BY(mMutex); | |
| bool mDestroyed GUARDED_BY(mMutex) = false; | |
| public: | |
| BBQSurface(const sp<IGraphicBufferProducer>& igbp, bool controlledByApp, | |
| const sp<IBinder>& scHandle, const sp<BLASTBufferQueue>& bbq) | |
| : Surface(igbp, controlledByApp, scHandle), mBbq(bbq) {} | |
| void allocateBuffers() override { | |
| ATRACE_CALL(); | |
| uint32_t reqWidth = mReqWidth ? mReqWidth : mUserWidth; | |
| uint32_t reqHeight = mReqHeight ? mReqHeight : mUserHeight; | |
| auto gbp = getIGraphicBufferProducer(); | |
| std::thread allocateThread([reqWidth, reqHeight, gbp = getIGraphicBufferProducer(), | |
| reqFormat = mReqFormat, reqUsage = mReqUsage]() { | |
| androidSetThreadName("allocateBuffers"); | |
| pid_t tid = gettid(); | |
| androidSetThreadPriority(tid, ANDROID_PRIORITY_DISPLAY); | |
| gbp->allocateBuffers(reqWidth, reqHeight, | |
| reqFormat, reqUsage); | |
| }); | |
| allocateThread.detach(); | |
| } | |
| status_t setFrameTimelineInfo(uint64_t frameNumber, | |
| const FrameTimelineInfo& frameTimelineInfo) override { | |
| std::lock_guard _lock{mMutex}; | |
| if (mDestroyed) { | |
| return DEAD_OBJECT; | |
| } | |
| return mBbq->setFrameTimelineInfo(frameNumber, frameTimelineInfo); | |
| } | |
| void destroy() override { | |
| Surface::destroy(); | |
| std::lock_guard _lock{mMutex}; | |
| mDestroyed = true; | |
| mBbq = nullptr; | |
| } | |
| }; | |
| // TODO: Can we coalesce this with frame updates? Need to confirm | |
| // no timing issues. | |
| status_t BLASTBufferQueue::setFrameRate(float frameRate, int8_t compatibility, | |
| bool shouldBeSeamless) { | |
| std::lock_guard _lock{mMutex}; | |
| SurfaceComposerClient::Transaction t; | |
| return t.setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless).apply(); | |
| } | |
| status_t BLASTBufferQueue::setFrameTimelineInfo(uint64_t frameNumber, | |
| const FrameTimelineInfo& frameTimelineInfo) { | |
| ATRACE_FORMAT("%s(%s) frameNumber: %" PRIu64 " vsyncId: %" PRId64, __func__, mName.c_str(), | |
| frameNumber, frameTimelineInfo.vsyncId); | |
| std::lock_guard _lock{mMutex}; | |
| mPendingFrameTimelines.push({frameNumber, frameTimelineInfo}); | |
| return OK; | |
| } | |
| void BLASTBufferQueue::setSidebandStream(const sp<NativeHandle>& stream) { | |
| std::lock_guard _lock{mMutex}; | |
| SurfaceComposerClient::Transaction t; | |
| t.setSidebandStream(mSurfaceControl, stream).apply(); | |
| } | |
| sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) { | |
| std::lock_guard _lock{mMutex}; | |
| sp<IBinder> scHandle = nullptr; | |
| if (includeSurfaceControlHandle && mSurfaceControl) { | |
| scHandle = mSurfaceControl->getHandle(); | |
| } | |
| return sp<BBQSurface>::make(mProducer, true, scHandle, | |
| sp<BLASTBufferQueue>::fromExisting(this)); | |
| } | |
| void BLASTBufferQueue::mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, | |
| uint64_t frameNumber) { | |
| std::lock_guard _lock{mMutex}; | |
| if (mLastAcquiredFrameNumber >= frameNumber) { | |
| // Apply the transaction since we have already acquired the desired frame. | |
| t->setApplyToken(mApplyToken).apply(); | |
| } else { | |
| mPendingTransactions.emplace_back(frameNumber, *t); | |
| // Clear the transaction so it can't be applied elsewhere. | |
| t->clear(); | |
| } | |
| } | |
| void BLASTBufferQueue::applyPendingTransactions(uint64_t frameNumber) { | |
| std::lock_guard _lock{mMutex}; | |
| SurfaceComposerClient::Transaction t; | |
| mergePendingTransactions(&t, frameNumber); | |
| // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction | |
| t.setApplyToken(mApplyToken).apply(false, true); | |
| } | |
| void BLASTBufferQueue::mergePendingTransactions(SurfaceComposerClient::Transaction* t, | |
| uint64_t frameNumber) { | |
| auto mergeTransaction = | |
| [&t, currentFrameNumber = frameNumber]( | |
| std::tuple<uint64_t, SurfaceComposerClient::Transaction> pendingTransaction) { | |
| auto& [targetFrameNumber, transaction] = pendingTransaction; | |
| if (currentFrameNumber < targetFrameNumber) { | |
| return false; | |
| } | |
| t->merge(std::move(transaction)); | |
| return true; | |
| }; | |
| mPendingTransactions.erase(std::remove_if(mPendingTransactions.begin(), | |
| mPendingTransactions.end(), mergeTransaction), | |
| mPendingTransactions.end()); | |
| } | |
| SurfaceComposerClient::Transaction* BLASTBufferQueue::gatherPendingTransactions( | |
| uint64_t frameNumber) { | |
| std::lock_guard _lock{mMutex}; | |
| SurfaceComposerClient::Transaction* t = new SurfaceComposerClient::Transaction(); | |
| mergePendingTransactions(t, frameNumber); | |
| return t; | |
| } | |
| // Maintains a single worker thread per process that services a list of runnables. | |
| class AsyncWorker : public Singleton<AsyncWorker> { | |
| private: | |
| std::thread mThread; | |
| bool mDone = false; | |
| std::deque<std::function<void()>> mRunnables; | |
| std::mutex mMutex; | |
| std::condition_variable mCv; | |
| void run() { | |
| std::unique_lock<std::mutex> lock(mMutex); | |
| while (!mDone) { | |
| while (!mRunnables.empty()) { | |
| std::deque<std::function<void()>> runnables = std::move(mRunnables); | |
| mRunnables.clear(); | |
| lock.unlock(); | |
| // Run outside the lock since the runnable might trigger another | |
| // post to the async worker. | |
| execute(runnables); | |
| lock.lock(); | |
| } | |
| mCv.wait(lock); | |
| } | |
| } | |
| void execute(std::deque<std::function<void()>>& runnables) { | |
| while (!runnables.empty()) { | |
| std::function<void()> runnable = runnables.front(); | |
| runnables.pop_front(); | |
| runnable(); | |
| } | |
| } | |
| public: | |
| AsyncWorker() : Singleton<AsyncWorker>() { mThread = std::thread(&AsyncWorker::run, this); } | |
| ~AsyncWorker() { | |
| mDone = true; | |
| mCv.notify_all(); | |
| if (mThread.joinable()) { | |
| mThread.join(); | |
| } | |
| } | |
| void post(std::function<void()> runnable) { | |
| std::unique_lock<std::mutex> lock(mMutex); | |
| mRunnables.emplace_back(std::move(runnable)); | |
| mCv.notify_one(); | |
| } | |
| }; | |
| ANDROID_SINGLETON_STATIC_INSTANCE(AsyncWorker); | |
| // Asynchronously calls ProducerListener functions so we can emulate one way binder calls. | |
| class AsyncProducerListener : public BnProducerListener { | |
| private: | |
| const sp<IProducerListener> mListener; | |
| AsyncProducerListener(const sp<IProducerListener>& listener) : mListener(listener) {} | |
| friend class sp<AsyncProducerListener>; | |
| public: | |
| void onBufferReleased() override { | |
| AsyncWorker::getInstance().post([listener = mListener]() { listener->onBufferReleased(); }); | |
| } | |
| void onBuffersDiscarded(const std::vector<int32_t>& slots) override { | |
| AsyncWorker::getInstance().post( | |
| [listener = mListener, slots = slots]() { listener->onBuffersDiscarded(slots); }); | |
| } | |
| void onBufferDetached(int slot) override { | |
| AsyncWorker::getInstance().post( | |
| [listener = mListener, slot = slot]() { listener->onBufferDetached(slot); }); | |
| } | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK) | |
| void onBufferAttached() override { | |
| AsyncWorker::getInstance().post([listener = mListener]() { listener->onBufferAttached(); }); | |
| } | |
| #endif | |
| }; | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| // BufferReleaseReader is used to do blocking but interruptible reads from the buffer | |
| // release channel. To implement this, BufferReleaseReader owns an epoll file descriptor that | |
| // is configured to wake up when either the BufferReleaseReader::ConsumerEndpoint or an eventfd | |
| // becomes readable. Interrupts are necessary because a free buffer may become available for | |
| // reasons other than a buffer release from the producer. | |
| class BufferReleaseReader { | |
| public: | |
| explicit BufferReleaseReader(std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint>); | |
| BufferReleaseReader(const BufferReleaseReader&) = delete; | |
| BufferReleaseReader& operator=(const BufferReleaseReader&) = delete; | |
| // Block until we can read a buffer release message. | |
| // | |
| // Returns: | |
| // * OK if a ReleaseCallbackId and Fence were successfully read. | |
| // * WOULD_BLOCK if the blocking read was interrupted by interruptBlockingRead. | |
| // * TIMED_OUT if the blocking read timed out. | |
| // * UNKNOWN_ERROR if something went wrong. | |
| status_t readBlocking(ReleaseCallbackId& outId, sp<Fence>& outReleaseFence, | |
| uint32_t& outMaxAcquiredBufferCount, nsecs_t timeout); | |
| status_t readNonBlocking(ReleaseCallbackId& outId, sp<Fence>& outReleaseFence, | |
| uint32_t& outMaxAcquiredBufferCount); | |
| void interruptBlockingRead(); | |
| void clearInterrupts(); | |
| private: | |
| std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> mConsumerEndpoint; | |
| android::base::unique_fd mEpollFd; | |
| android::base::unique_fd mEventFd; | |
| }; | |
| class BBQBufferQueueCore : public BufferQueueCore { | |
| public: | |
| explicit BBQBufferQueueCore(std::shared_ptr<BufferReleaseReader> bufferReleaseReader) | |
| : mBufferReleaseReader{std::move(bufferReleaseReader)} {} | |
| void notifyBufferReleased() const override { mBufferReleaseReader->interruptBlockingRead(); } | |
| private: | |
| std::shared_ptr<BufferReleaseReader> mBufferReleaseReader; | |
| }; | |
| #endif | |
| // Extends the BufferQueueProducer to create a wrapper around the listener so the listener calls | |
| // can be non-blocking when the producer is in the client process. | |
| class BBQBufferQueueProducer : public BufferQueueProducer { | |
| public: | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| BBQBufferQueueProducer(const sp<BufferQueueCore>& core, const wp<BLASTBufferQueue>& bbq, | |
| std::shared_ptr<BufferReleaseReader> bufferReleaseReader) | |
| : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/), | |
| mBLASTBufferQueue(bbq), | |
| mBufferReleaseReader(std::move(bufferReleaseReader)) {} | |
| #else | |
| BBQBufferQueueProducer(const sp<BufferQueueCore>& core, const wp<BLASTBufferQueue>& bbq) | |
| : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/), | |
| mBLASTBufferQueue(bbq) {} | |
| #endif | |
| status_t connect(const sp<IProducerListener>& listener, int api, bool producerControlledByApp, | |
| QueueBufferOutput* output) override { | |
| if (!listener) { | |
| return BufferQueueProducer::connect(listener, api, producerControlledByApp, output); | |
| } | |
| return BufferQueueProducer::connect(sp<AsyncProducerListener>::make(listener), api, | |
| producerControlledByApp, output); | |
| } | |
| // We want to resize the frame history when changing the size of the buffer queue | |
| status_t setMaxDequeuedBufferCount(int maxDequeuedBufferCount) override { | |
| int maxBufferCount; | |
| if (status_t status = BufferQueueProducer::setMaxDequeuedBufferCount(maxDequeuedBufferCount, | |
| &maxBufferCount); | |
| status != OK) { | |
| return status; | |
| } | |
| sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote(); | |
| if (!bbq) { | |
| return OK; | |
| } | |
| // if we can't determine the max buffer count, then just skip growing the history size | |
| 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) { | |
| ALOGV("increasing frame history size to %zu", newFrameHistorySize); | |
| bbq->resizeFrameEventHistory(newFrameHistorySize); | |
| } | |
| return OK; | |
| } | |
| int query(int what, int* value) override { | |
| if (what == NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER) { | |
| *value = 1; | |
| return OK; | |
| } | |
| return BufferQueueProducer::query(what, value); | |
| } | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| status_t waitForBufferRelease(std::unique_lock<std::mutex>& bufferQueueLock, | |
| nsecs_t timeout) const override { | |
| ATRACE_CALL(); | |
| const auto startTime = std::chrono::steady_clock::now(); | |
| // BufferQueue has already checked if we have a free buffer. If there's an unread interrupt, | |
| // we want to ignore it. This must be done before unlocking the BufferQueue lock to ensure | |
| // we don't miss an interrupt. | |
| mBufferReleaseReader->clearInterrupts(); | |
| UnlockGuard unlockGuard{bufferQueueLock}; | |
| ReleaseCallbackId id; | |
| sp<Fence> fence; | |
| uint32_t maxAcquiredBufferCount; | |
| status_t status = | |
| mBufferReleaseReader->readBlocking(id, fence, maxAcquiredBufferCount, timeout); | |
| if (status == TIMED_OUT) { | |
| return TIMED_OUT; | |
| } else if (status != OK) { | |
| // Waiting was interrupted or an error occurred. BufferQueueProducer will check if we | |
| // have a free buffer and call this method again if not. | |
| return OK; | |
| } | |
| sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote(); | |
| if (!bbq) { | |
| return OK; | |
| } | |
| bbq->releaseBufferCallback(id, fence, maxAcquiredBufferCount); | |
| const nsecs_t durationNanos = std::chrono::duration_cast<std::chrono::nanoseconds>( | |
| std::chrono::steady_clock::now() - startTime) | |
| .count(); | |
| // Provide a callback for Choreographer to start buffer stuffing recovery when blocked | |
| // on buffer release. | |
| std::function<void(const nsecs_t)> callbackCopy = bbq->getWaitForBufferReleaseCallback(); | |
| if (callbackCopy) callbackCopy(durationNanos); | |
| return OK; | |
| } | |
| #endif | |
| private: | |
| const wp<BLASTBufferQueue> mBLASTBufferQueue; | |
| std::shared_ptr<BufferReleaseReader> mBufferReleaseReader; | |
| }; | |
| // Similar to BufferQueue::createBufferQueue but creates an adapter specific bufferqueue producer. | |
| // This BQP allows invoking client specified ProducerListeners and invoke them asynchronously, | |
| // emulating one way binder call behavior. Without this, if the listener calls back into the queue, | |
| // we can deadlock. | |
| void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer, | |
| sp<IGraphicBufferConsumer>* outConsumer) { | |
| LOG_ALWAYS_FATAL_IF(outProducer == nullptr, "BLASTBufferQueue: outProducer must not be NULL"); | |
| LOG_ALWAYS_FATAL_IF(outConsumer == nullptr, "BLASTBufferQueue: outConsumer must not be NULL"); | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> bufferReleaseConsumer; | |
| gui::BufferReleaseChannel::open(mName, bufferReleaseConsumer, mBufferReleaseProducer); | |
| mBufferReleaseReader = std::make_shared<BufferReleaseReader>(std::move(bufferReleaseConsumer)); | |
| auto core = sp<BBQBufferQueueCore>::make(mBufferReleaseReader); | |
| #else | |
| auto core = sp<BufferQueueCore>::make(); | |
| #endif | |
| LOG_ALWAYS_FATAL_IF(core == nullptr, "BLASTBufferQueue: failed to create BufferQueueCore"); | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| auto producer = sp<BBQBufferQueueProducer>::make(core, wp<BLASTBufferQueue>::fromExisting(this), | |
| mBufferReleaseReader); | |
| #else | |
| auto producer = | |
| sp<BBQBufferQueueProducer>::make(core, wp<BLASTBufferQueue>::fromExisting(this)); | |
| #endif | |
| LOG_ALWAYS_FATAL_IF(producer == nullptr, | |
| "BLASTBufferQueue: failed to create BBQBufferQueueProducer"); | |
| auto consumer = sp<BufferQueueConsumer>::make(core); | |
| consumer->setAllowExtraAcquire(true); | |
| LOG_ALWAYS_FATAL_IF(consumer == nullptr, | |
| "BLASTBufferQueue: failed to create BufferQueueConsumer"); | |
| *outProducer = producer; | |
| *outConsumer = consumer; | |
| } | |
| 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 | |
| // objects are cleaned up with a major refactor of BufferQueue as a whole. | |
| if (mBufferItemConsumer != nullptr) { | |
| std::unique_lock _lock{mMutex}; | |
| mBufferItemConsumer->resizeFrameEventHistory(newSize); | |
| } | |
| } | |
| PixelFormat BLASTBufferQueue::convertBufferFormat(PixelFormat& format) { | |
| PixelFormat convertedFormat = format; | |
| switch (format) { | |
| case PIXEL_FORMAT_TRANSPARENT: | |
| case PIXEL_FORMAT_TRANSLUCENT: | |
| convertedFormat = PIXEL_FORMAT_RGBA_8888; | |
| break; | |
| case PIXEL_FORMAT_OPAQUE: | |
| convertedFormat = PIXEL_FORMAT_RGBX_8888; | |
| break; | |
| } | |
| return convertedFormat; | |
| } | |
| uint32_t BLASTBufferQueue::getLastTransformHint() const { | |
| std::lock_guard _lock{mMutex}; | |
| if (mSurfaceControl != nullptr) { | |
| return mSurfaceControl->getTransformHint(); | |
| } else { | |
| return 0; | |
| } | |
| } | |
| uint64_t BLASTBufferQueue::getLastAcquiredFrameNum() { | |
| std::lock_guard _lock{mMutex}; | |
| return mLastAcquiredFrameNumber; | |
| } | |
| bool BLASTBufferQueue::isSameSurfaceControl(const sp<SurfaceControl>& surfaceControl) const { | |
| std::lock_guard _lock{mMutex}; | |
| return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl); | |
| } | |
| void BLASTBufferQueue::setTransactionHangCallback( | |
| std::function<void(const std::string&)> callback) { | |
| std::lock_guard _lock{mMutex}; | |
| mTransactionHangCallback = std::move(callback); | |
| } | |
| void BLASTBufferQueue::setApplyToken(sp<IBinder> applyToken) { | |
| std::lock_guard _lock{mMutex}; | |
| mApplyToken = std::move(applyToken); | |
| } | |
| void BLASTBufferQueue::setWaitForBufferReleaseCallback( | |
| std::function<void(const nsecs_t)> callback) { | |
| std::lock_guard _lock{mWaitForBufferReleaseMutex}; | |
| mWaitForBufferReleaseCallback = std::move(callback); | |
| } | |
| std::function<void(const nsecs_t)> BLASTBufferQueue::getWaitForBufferReleaseCallback() const { | |
| std::lock_guard _lock{mWaitForBufferReleaseMutex}; | |
| return mWaitForBufferReleaseCallback; | |
| } | |
| #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL) | |
| void BLASTBufferQueue::updateBufferReleaseProducer() { | |
| // SELinux policy may prevent this process from sending the BufferReleaseChannel's file | |
| // descriptor to SurfaceFlinger, causing the entire transaction to be dropped. We send this | |
| // transaction independently of any other updates to ensure those updates aren't lost. | |
| SurfaceComposerClient::Transaction t; | |
| status_t status = t.setApplyToken(mApplyToken) | |
| .setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer) | |
| .apply(false /* synchronous */, true /* oneWay */); | |
| if (status != OK) { | |
| ALOGW("[%s] %s - failed to set buffer release channel on %s", mName.c_str(), | |
| statusToString(status).c_str(), mSurfaceControl->getName().c_str()); | |
| } | |
| } | |
| void BLASTBufferQueue::drainBufferReleaseConsumer() { | |
| ATRACE_CALL(); | |
| while (true) { | |
| ReleaseCallbackId id; | |
| sp<Fence> fence; | |
| uint32_t maxAcquiredBufferCount; | |
| status_t status = mBufferReleaseReader->readNonBlocking(id, fence, maxAcquiredBufferCount); | |
| if (status != OK) { | |
| return; | |
| } | |
| releaseBufferCallback(id, fence, maxAcquiredBufferCount); | |
| } | |
| } | |
| BufferReleaseReader::BufferReleaseReader( | |
| std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> consumerEndpoint) | |
| : mConsumerEndpoint{std::move(consumerEndpoint)} { | |
| mEpollFd = android::base::unique_fd{epoll_create1(EPOLL_CLOEXEC)}; | |
| LOG_ALWAYS_FATAL_IF(!mEpollFd.ok(), | |
| "Failed to create buffer release epoll file descriptor. errno=%d " | |
| "message='%s'", | |
| errno, strerror(errno)); | |
| epoll_event registerEndpointFd{}; | |
| registerEndpointFd.events = EPOLLIN; | |
| registerEndpointFd.data.fd = mConsumerEndpoint->getFd(); | |
| status_t status = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mConsumerEndpoint->getFd(), | |
| ®isterEndpointFd); | |
| LOG_ALWAYS_FATAL_IF(status == -1, | |
| "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_CLOEXEC | EFD_NONBLOCK)); | |
| LOG_ALWAYS_FATAL_IF(!mEventFd.ok(), | |
| "Failed to create buffer release event file descriptor. errno=%d " | |
| "message='%s'", | |
| errno, strerror(errno)); | |
| epoll_event registerEventFd{}; | |
| registerEventFd.events = EPOLLIN; | |
| registerEventFd.data.fd = mEventFd.get(); | |
| status = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mEventFd.get(), ®isterEventFd); | |
| LOG_ALWAYS_FATAL_IF(status == -1, | |
| "Failed to register buffer release event file descriptor with epoll. " | |
| "errno=%d message='%s'", | |
| errno, strerror(errno)); | |
| } | |
| status_t BufferReleaseReader::readBlocking(ReleaseCallbackId& outId, sp<Fence>& outFence, | |
| uint32_t& outMaxAcquiredBufferCount, nsecs_t timeout) { | |
| // TODO(b/363290953) epoll_wait only has millisecond timeout precision. If timeout is less than | |
| // 1ms, then we round timeout up to 1ms. Otherwise, we round timeout to the nearest | |
| // millisecond. Once epoll_pwait2 can be used in libgui, we can specify timeout with nanosecond | |
| // precision. | |
| int timeoutMs = -1; | |
| if (timeout == 0) { | |
| timeoutMs = 0; | |
| } else if (timeout > 0) { | |
| const int nsPerMs = 1000000; | |
| if (timeout < nsPerMs) { | |
| timeoutMs = 1; | |
| } else { | |
| timeoutMs = static_cast<int>( | |
| std::chrono::round<std::chrono::milliseconds>(std::chrono::nanoseconds{timeout}) | |
| .count()); | |
| } | |
| } | |
| epoll_event event{}; | |
| int eventCount; | |
| do { | |
| eventCount = epoll_wait(mEpollFd.get(), &event, 1 /*maxevents*/, timeoutMs); | |
| } while (eventCount == -1 && errno != EINTR); | |
| 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()) { | |
| clearInterrupts(); | |
| return WOULD_BLOCK; | |
| } | |
| return mConsumerEndpoint->readReleaseFence(outId, outFence, outMaxAcquiredBufferCount); | |
| } | |
| status_t BufferReleaseReader::readNonBlocking(ReleaseCallbackId& outId, sp<Fence>& outFence, | |
| uint32_t& outMaxAcquiredBufferCount) { | |
| return mConsumerEndpoint->readReleaseFence(outId, outFence, outMaxAcquiredBufferCount); | |
| } | |
| void BufferReleaseReader::interruptBlockingRead() { | |
| if (eventfd_write(mEventFd.get(), 1) == -1) { | |
| ALOGE("failed to notify dequeue event. errno=%d message='%s'", errno, strerror(errno)); | |
| } | |
| } | |
| void BufferReleaseReader::clearInterrupts() { | |
| eventfd_t value; | |
| if (eventfd_read(mEventFd.get(), &value) == -1 && errno != EWOULDBLOCK) { | |
| ALOGE("error while reading from eventfd. errno=%d message='%s'", errno, strerror(errno)); | |
| } | |
| } | |
| #endif | |
| } // namespace android |