// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //#define LOG_NDEBUG 0 #define LOG_TAG "C2VdaBqBlockPool" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace { // The wait time for acquire fence in milliseconds. constexpr int kFenceWaitTimeMs = 10; // The timeout limit of acquiring lock of timed_mutex in milliseconds. constexpr std::chrono::milliseconds kTimedMutexTimeoutMs = std::chrono::milliseconds(500); } // namespace using namespace std::chrono_literals; using ::android::C2AndroidMemoryUsage; using ::android::Fence; using ::android::GraphicBuffer; using ::android::sp; using ::android::status_t; using ::android::BufferQueueDefs::BUFFER_NEEDS_REALLOCATION; using ::android::BufferQueueDefs::NUM_BUFFER_SLOTS; using ::android::BufferQueueDefs::RELEASE_ALL_BUFFERS; using ::android::hardware::hidl_handle; using ::android::hardware::Return; using HBuffer = ::android::hardware::graphics::common::V1_2::HardwareBuffer; using HStatus = ::android::hardware::graphics::bufferqueue::V2_0::Status; using HGraphicBufferProducer = ::android::hardware::graphics::bufferqueue::V2_0::IGraphicBufferProducer; using HProducerListener = ::android::hardware::graphics::bufferqueue::V2_0::IProducerListener; using HConnectionType = hardware::graphics::bufferqueue::V2_0::ConnectionType; using HQueueBufferOutput = ::android::hardware::graphics::bufferqueue::V2_0::IGraphicBufferProducer::QueueBufferOutput; using ::android::hardware::graphics::bufferqueue::V2_0::utils::b2h; using ::android::hardware::graphics::bufferqueue::V2_0::utils::h2b; using ::android::hardware::graphics::bufferqueue::V2_0::utils::HFenceWrapper; static c2_status_t asC2Error(int32_t err) { switch (err) { case android::NO_ERROR: return C2_OK; case android::NO_INIT: return C2_NO_INIT; case android::BAD_VALUE: return C2_BAD_VALUE; case android::TIMED_OUT: return C2_TIMED_OUT; case android::WOULD_BLOCK: return C2_BLOCKING; case android::NO_MEMORY: return C2_NO_MEMORY; } return C2_CORRUPTED; } class H2BGraphicBufferProducer { public: explicit H2BGraphicBufferProducer(sp base) : mBase(base) {} ~H2BGraphicBufferProducer() = default; status_t requestBuffer(int slot, sp* buf) { bool converted = false; status_t status = UNKNOWN_ERROR; Return transResult = mBase->requestBuffer( slot, [&converted, &status, buf](HStatus hStatus, HBuffer const& hBuffer, uint32_t generationNumber) { converted = h2b(hStatus, &status) && h2b(hBuffer, buf); if (*buf) { (*buf)->setGenerationNumber(generationNumber); } }); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!converted) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } if (status != android::NO_ERROR) { ALOGE("%s() failed: %d", __func__, status); } return status; } status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers) { status_t status = UNKNOWN_ERROR; Return transResult = mBase->setMaxDequeuedBufferCount(static_cast(maxDequeuedBuffers)); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!h2b(static_cast(transResult), &status)) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } if (status != android::NO_ERROR) { ALOGE("%s() failed: %d", __func__, status); } return status; } status_t dequeueBuffer(uint32_t width, uint32_t height, uint32_t pixelFormat, C2AndroidMemoryUsage androidUsage, int* slot, sp* fence) { using Input = HGraphicBufferProducer::DequeueBufferInput; using Output = HGraphicBufferProducer::DequeueBufferOutput; Input input{width, height, pixelFormat, androidUsage.asGrallocUsage()}; bool converted = false; status_t status = UNKNOWN_ERROR; Return transResult = mBase->dequeueBuffer( input, [&converted, &status, &slot, &fence](HStatus hStatus, int32_t hSlot, Output const& hOutput) { converted = h2b(hStatus, &status); if (!converted || status != android::NO_ERROR) { return; } *slot = hSlot; if (hOutput.bufferNeedsReallocation) { status = BUFFER_NEEDS_REALLOCATION; } converted = h2b(hOutput.fence, fence); }); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!converted) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } if (status != android::NO_ERROR && status != BUFFER_NEEDS_REALLOCATION && status != android::TIMED_OUT) { ALOGE("%s() failed: %d", __func__, status); } return status; } status_t detachBuffer(int slot) { status_t status = UNKNOWN_ERROR; Return transResult = mBase->detachBuffer(static_cast(slot)); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!h2b(static_cast(transResult), &status)) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } if (status != android::NO_ERROR) { ALOGE("%s() failed: %d", __func__, status); } return status; } status_t attachBuffer(const sp& buffer, int* outSlot) { HBuffer hBuffer; uint32_t hGenerationNumber; if (!b2h(buffer, &hBuffer, &hGenerationNumber)) { ALOGE("%s: invalid input buffer.", __func__); return BAD_VALUE; } bool converted = false; status_t status = UNKNOWN_ERROR; Return transResult = mBase->attachBuffer( hBuffer, hGenerationNumber, [&converted, &status, outSlot](HStatus hStatus, int32_t hSlot, bool releaseAllBuffers) { converted = h2b(hStatus, &status); *outSlot = static_cast(hSlot); if (converted && releaseAllBuffers && status == android::NO_ERROR) { status = RELEASE_ALL_BUFFERS; } }); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!converted) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } if (status != android::NO_ERROR) { ALOGE("%s() failed: %d", __func__, status); } return status; } status_t cancelBuffer(int slot, const sp& fence) { HFenceWrapper hFenceWrapper; if (!b2h(fence, &hFenceWrapper)) { ALOGE("%s(): corrupted input fence.", __func__); return UNKNOWN_ERROR; } status_t status = UNKNOWN_ERROR; Return transResult = mBase->cancelBuffer(static_cast(slot), hFenceWrapper.getHandle()); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!h2b(static_cast(transResult), &status)) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } if (status != android::NO_ERROR) { ALOGE("%s() failed: %d", __func__, status); } return status; } int query(int what, int* value) { int result = 0; Return transResult = mBase->query(static_cast(what), [&result, value](int32_t r, int32_t v) { result = static_cast(r); *value = static_cast(v); }); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } return result; } status_t allowAllocation(bool allow) { status_t status = UNKNOWN_ERROR; Return transResult = mBase->allowAllocation(allow); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!h2b(static_cast(transResult), &status)) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } if (status != android::NO_ERROR) { ALOGW("%s() failed: %d", __func__, status); } return status; } status_t getUniqueId(uint64_t* outId) const { Return transResult = mBase->getUniqueId(); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } *outId = static_cast(transResult); return android::NO_ERROR; } // android::IProducerListener cannot be depended by vendor library, so we use HProducerListener // directly. status_t connect(sp const& hListener, int32_t api, bool producerControlledByApp) { bool converted = false; status_t status = UNKNOWN_ERROR; // hack(b/146409777): We pass self-defined api, so we don't use b2h() here. Return transResult = mBase->connect( hListener, static_cast(api), producerControlledByApp, [&converted, &status](HStatus hStatus, HQueueBufferOutput const& /* hOutput */) { converted = h2b(hStatus, &status); }); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!converted) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } return status; } status_t setDequeueTimeout(nsecs_t timeout) { status_t status = UNKNOWN_ERROR; Return transResult = mBase->setDequeueTimeout(static_cast(timeout)); if (!transResult.isOk()) { ALOGE("%s(): transaction failed: %s", __func__, transResult.description().c_str()); return FAILED_TRANSACTION; } if (!h2b(static_cast(transResult), &status)) { ALOGE("%s(): corrupted transaction.", __func__); return FAILED_TRANSACTION; } return status; } private: const sp mBase; }; // This class is used to notify the listener when a certain event happens. class EventNotifier : public virtual android::RefBase { public: class Listener { public: virtual ~Listener() = default; // Called by EventNotifier when a certain event happens. virtual void onEventNotified() = 0; }; explicit EventNotifier(const std::shared_ptr& listener) : mListener(listener) {} virtual ~EventNotifier() = default; protected: void notify() { ALOGV("%s()", __func__); std::shared_ptr listener = mListener.lock(); if (listener) { listener->onEventNotified(); } } std::weak_ptr mListener; }; // Notifies the listener when the connected IGBP releases buffers. class BufferReleasedNotifier : public EventNotifier, public HProducerListener { public: using EventNotifier::EventNotifier; ~BufferReleasedNotifier() override = default; // HProducerListener implementation Return onBuffersReleased(uint32_t count) override { ALOGV("%s(%u)", __func__, count); if (count > 0) { notify(); } return {}; } }; /** * BlockPoolData implementation for C2VdaBqBlockPool. The life cycle of this object should be as * long as its accompanied C2GraphicBlock. * * When C2VdaBqBlockPoolData is created, |mShared| is false, and the owner of the accompanied * C2GraphicBlock is the component that called fetchGraphicBlock(). If this is released before * sharing, the destructor will call detachBuffer() to BufferQueue to free the slot. * * When the accompanied C2GraphicBlock is going to share to client from component, component should * call MarkBlockPoolDataAsShared() to set |mShared| to true, and then this will be released after * the transition of C2GraphicBlock across HIDL interface. At this time, the destructor will not * call detachBuffer(). */ struct C2VdaBqBlockPoolData : public _C2BlockPoolData { // This type should be a different value than what _C2BlockPoolData::type_t has defined. static constexpr int kTypeVdaBufferQueue = TYPE_BUFFERQUEUE + 256; C2VdaBqBlockPoolData(uint64_t producerId, int32_t slotId, const std::shared_ptr& pool); C2VdaBqBlockPoolData() = delete; // If |mShared| is false, call detach buffer to BufferQueue via |mPool| virtual ~C2VdaBqBlockPoolData() override; type_t getType() const override { return static_cast(kTypeVdaBufferQueue); } bool mShared = false; // whether is shared from component to client. const uint64_t mProducerId; const int32_t mSlotId; const std::shared_ptr mPool; }; c2_status_t MarkBlockPoolDataAsShared(const C2ConstGraphicBlock& sharedBlock) { std::shared_ptr<_C2BlockPoolData> data = _C2BlockFactory::GetGraphicBlockPoolData(sharedBlock); if (!data || data->getType() != C2VdaBqBlockPoolData::kTypeVdaBufferQueue) { // Skip this functtion if |sharedBlock| is not fetched from C2VdaBqBlockPool. return C2_OMITTED; } const std::shared_ptr poolData = std::static_pointer_cast(data); if (poolData->mShared) { ALOGE("C2VdaBqBlockPoolData(id=%" PRIu64 ", slot=%d) is already marked as shared...", poolData->mProducerId, poolData->mSlotId); return C2_BAD_STATE; } poolData->mShared = true; return C2_OK; } // static std::optional C2VdaBqBlockPool::getBufferIdFromGraphicBlock(const C2Block2D& block) { uint32_t width, height, format, stride, igbp_slot, generation; uint64_t usage, igbp_id; android::_UnwrapNativeCodec2GrallocMetadata(block.handle(), &width, &height, &format, &usage, &stride, &generation, &igbp_id, &igbp_slot); ALOGV("Unwrap Metadata: igbp[%" PRIu64 ", %u] (%u*%u, fmt %#x, usage %" PRIx64 ", stride %u)", igbp_id, igbp_slot, width, height, format, usage, stride); return igbp_slot; } class C2VdaBqBlockPool::Impl : public std::enable_shared_from_this, public EventNotifier::Listener { public: using HGraphicBufferProducer = C2VdaBqBlockPool::HGraphicBufferProducer; explicit Impl(const std::shared_ptr& allocator); // TODO: should we detach buffers on producer if any on destructor? ~Impl() = default; // EventNotifier::Listener implementation. void onEventNotified() override; c2_status_t fetchGraphicBlock(uint32_t width, uint32_t height, uint32_t format, C2MemoryUsage usage, std::shared_ptr* block /* nonnull */); void setRenderCallback(const C2BufferQueueBlockPool::OnRenderCallback& renderCallback); void configureProducer(const sp& producer); c2_status_t requestNewBufferSet(int32_t bufferCount); c2_status_t updateGraphicBlock(bool willCancel, uint32_t oldSlot, uint32_t* newSlot, std::shared_ptr* block /* nonnull */); c2_status_t getMinBuffersForDisplay(size_t* bufferCount); bool setNotifyBlockAvailableCb(::base::OnceClosure cb); private: friend struct C2VdaBqBlockPoolData; // Requested buffer formats. struct BufferFormat { BufferFormat(uint32_t width, uint32_t height, uint32_t pixelFormat, C2AndroidMemoryUsage androidUsage) : mWidth(width), mHeight(height), mPixelFormat(pixelFormat), mUsage(androidUsage) {} BufferFormat() = default; uint32_t mWidth = 0; uint32_t mHeight = 0; uint32_t mPixelFormat = 0; C2AndroidMemoryUsage mUsage = C2MemoryUsage(0); }; // For C2VdaBqBlockPoolData to detach corresponding slot buffer from BufferQueue. void detachBuffer(uint64_t producerId, int32_t slotId); // Queries the generation and usage flags from the given producer by dequeuing and requesting a // buffer (the buffer is then detached and freed). c2_status_t queryGenerationAndUsage(H2BGraphicBufferProducer* const producer, uint32_t width, uint32_t height, uint32_t pixelFormat, C2AndroidMemoryUsage androidUsage, uint32_t* generation, uint64_t* usage); // Switches producer and transfers allocated buffers from old producer to the new one. bool switchProducer(H2BGraphicBufferProducer* const newProducer, uint64_t newProducerId); const std::shared_ptr mAllocator; std::unique_ptr mProducer; uint64_t mProducerId; C2BufferQueueBlockPool::OnRenderCallback mRenderCallback; // Function mutex to lock at the start of each API function call for protecting the // synchronization of all member variables. std::mutex mMutex; // The mutex of excluding the procedures of configuring producer and allocating buffers. They // should be blocked mutually. Set the timeout for acquiring lock in case of any deadlock. // Configuring producer: configureProducer() called by CCodec. // Allocating buffers: requestNewBufferSet(), then a loop of fetchGraphicBlock() called by // compoenent until |mSlotAllocations|.size() equals |mBuffersRequested|. std::timed_mutex mConfigureProducerAndAllocateBuffersMutex; // The unique lock of the procedure of allocating buffers. It should be locked in the beginning // of requestNewBufferSet() and unlock in the end of the loop of fetchGraphicBlock(). Note that // all calls should be in the same thread. std::unique_lock mAllocateBuffersLock; // The map restored C2GraphicAllocation from corresponding slot index. std::map> mSlotAllocations; // Number of buffers requested on requestNewBufferSet() call. size_t mBuffersRequested; // Currently requested buffer formats. BufferFormat mBufferFormat; // The map recorded the slot indices from old producer to new producer. std::map mProducerChangeSlotMap; // The counter for representing the buffer count in client. Only used in producer switching // case. It will be reset in switchProducer(), and accumulated in updateGraphicBlock() routine. uint32_t mBuffersInClient = 0u; // The indicator to record if producer has been switched. Set to true when producer is switched. // Toggle off when requestNewBufferSet() is called. We forcedly detach all slots to make sure // all slots are available, except the ones owned by client. bool mProducerSwitched = false; // Listener for buffer release events. sp mFetchBufferNotifier; std::mutex mBufferReleaseMutex; // Set to true when the buffer release event is triggered after dequeueing // buffer from IGBP times out. bool mBufferReleasedAfterTimedOut GUARDED_BY(mBufferReleaseMutex) = false; // The callback to notify the caller the buffer is available. ::base::OnceClosure mNotifyBlockAvailableCb GUARDED_BY(mBufferReleaseMutex); }; C2VdaBqBlockPool::Impl::Impl(const std::shared_ptr& allocator) : mAllocator(allocator), mAllocateBuffersLock(mConfigureProducerAndAllocateBuffersMutex, std::defer_lock), mBuffersRequested(0u) {} c2_status_t C2VdaBqBlockPool::Impl::fetchGraphicBlock( uint32_t width, uint32_t height, uint32_t format, C2MemoryUsage usage, std::shared_ptr* block /* nonnull */) { ALOGV("%s()", __func__); std::lock_guard lock(mMutex); if (!mProducer) { // Producer will not be configured in byte-buffer mode. Allocate buffers from allocator // directly as a basic graphic block pool. std::shared_ptr alloc; c2_status_t err = mAllocator->newGraphicAllocation(width, height, format, usage, &alloc); if (err != C2_OK) { return err; } *block = _C2BlockFactory::CreateGraphicBlock(alloc); return C2_OK; } // The existence of |mProducerChangeSlotMap| indicates producer is just switched. Use return // code C2_BAD_STATE to inform the component to handle the procedure of producer change. // TODO(johnylin): consider to inform producer change to component in an active way. if (!mProducerChangeSlotMap.empty()) { return C2_BAD_STATE; } C2AndroidMemoryUsage androidUsage = usage; uint32_t pixelFormat = format; int32_t slot; sp fence = new Fence(); status_t status = mProducer->dequeueBuffer(width, height, pixelFormat, androidUsage, &slot, &fence); // The C2VdaBqBlockPool does not fully own the bufferqueue. After buffers are dequeued here, // they are passed into the codec2 framework, processed, and eventually queued into the // bufferqueue. The C2VdaBqBlockPool cannot determine exactly when a buffer gets queued. // However, if every buffer is being processed by the codec2 framework, then dequeueBuffer() // will return INVALID_OPERATION because of an attempt to dequeue too many buffers. // The C2VdaBqBlockPool cannot prevent this from happening, so just map it to TIMED_OUT // and let the C2VdaBqBlockPool's caller's timeout retry logic handle the failure. if (status == android::INVALID_OPERATION) { status = android::TIMED_OUT; } if (status == android::TIMED_OUT) { std::lock_guard lock(mBufferReleaseMutex); mBufferReleasedAfterTimedOut = false; } if (status != android::NO_ERROR && status != BUFFER_NEEDS_REALLOCATION) { return asC2Error(status); } // Wait for acquire fence if we get one. if (fence) { status_t fenceStatus = fence->wait(kFenceWaitTimeMs); if (fenceStatus != android::NO_ERROR) { if (mProducer->cancelBuffer(slot, fence) != android::NO_ERROR) { return C2_CORRUPTED; } if (fenceStatus == -ETIME) { // fence wait timed out ALOGV("%s(): buffer (slot=%d) fence wait timed out", __func__, slot); return C2_TIMED_OUT; } ALOGE("buffer fence wait error: %d", fenceStatus); return asC2Error(fenceStatus); } if (mRenderCallback) { nsecs_t signalTime = fence->getSignalTime(); if (signalTime >= 0 && signalTime < INT64_MAX) { mRenderCallback(mProducerId, slot, signalTime); } else { ALOGV("got fence signal time of %" PRId64 " nsec", signalTime); } } } auto iter = mSlotAllocations.find(slot); if (iter == mSlotAllocations.end()) { if (mSlotAllocations.size() >= mBuffersRequested) { // The dequeued slot has a pre-allocated buffer whose size and format is as same as // currently requested (but was not dequeued during allocation cycle). Just detach it to // free this slot. And try dequeueBuffer again. ALOGD("dequeued a new slot index but already allocated enough buffers. Detach it."); if (mProducer->detachBuffer(slot) != android::NO_ERROR) { return C2_CORRUPTED; } return C2_TIMED_OUT; } if (status != BUFFER_NEEDS_REALLOCATION) { // The dequeued slot has a pre-allocated buffer whose size and format is as same as // currently requested, so there is no BUFFER_NEEDS_REALLOCATION flag. However since the // buffer reference is already dropped, still call requestBuffer to re-allocate then. // Add a debug note here for tracking. ALOGD("dequeued a new slot index without BUFFER_NEEDS_REALLOCATION flag."); } // Call requestBuffer to allocate buffer for the slot and obtain the reference. sp slotBuffer = new GraphicBuffer(); status = mProducer->requestBuffer(slot, &slotBuffer); if (status != android::NO_ERROR) { if (mProducer->cancelBuffer(slot, fence) != android::NO_ERROR) { return C2_CORRUPTED; } return asC2Error(status); } // Convert GraphicBuffer to C2GraphicAllocation and wrap producer id and slot index ALOGV("buffer wraps { producer id: %" PRIu64 ", slot: %d }", mProducerId, slot); C2Handle* c2Handle = android::WrapNativeCodec2GrallocHandle( slotBuffer->handle, slotBuffer->width, slotBuffer->height, slotBuffer->format, slotBuffer->usage, slotBuffer->stride, slotBuffer->getGenerationNumber(), mProducerId, slot); if (!c2Handle) { ALOGE("WrapNativeCodec2GrallocHandle failed"); return C2_NO_MEMORY; } std::shared_ptr alloc; c2_status_t err = mAllocator->priorGraphicAllocation(c2Handle, &alloc); if (err != C2_OK) { ALOGE("priorGraphicAllocation failed: %d", err); return err; } mSlotAllocations[slot] = std::move(alloc); if (mSlotAllocations.size() == mBuffersRequested) { // Already allocated enough buffers, set allowAllocation to false to restrict the // eligible slots to allocated ones for future dequeue. status = mProducer->allowAllocation(false); if (status != android::NO_ERROR) { return asC2Error(status); } // Store buffer formats for future usage. mBufferFormat = BufferFormat(width, height, pixelFormat, androidUsage); ALOG_ASSERT(mAllocateBuffersLock.owns_lock()); mAllocateBuffersLock.unlock(); } } auto poolData = std::make_shared(mProducerId, slot, shared_from_this()); *block = _C2BlockFactory::CreateGraphicBlock(mSlotAllocations[slot], std::move(poolData)); return C2_OK; } void C2VdaBqBlockPool::Impl::onEventNotified() { ALOGV("%s()", __func__); ::base::OnceClosure outputCb; { std::lock_guard lock(mBufferReleaseMutex); mBufferReleasedAfterTimedOut = true; if (mNotifyBlockAvailableCb) { outputCb = std::move(mNotifyBlockAvailableCb); } } // Calling the callback outside the lock to avoid the deadlock. if (outputCb) { std::move(outputCb).Run(); } } c2_status_t C2VdaBqBlockPool::Impl::queryGenerationAndUsage( H2BGraphicBufferProducer* const producer, uint32_t width, uint32_t height, uint32_t pixelFormat, C2AndroidMemoryUsage androidUsage, uint32_t* generation, uint64_t* usage) { ALOGV("queryGenerationAndUsage"); sp fence = new Fence(); int32_t status; int32_t slot; status = producer->dequeueBuffer(width, height, pixelFormat, androidUsage, &slot, &fence); if (status != android::NO_ERROR && status != BUFFER_NEEDS_REALLOCATION) { return asC2Error(status); } // Wait for acquire fence if we get one. if (fence) { status_t fenceStatus = fence->wait(kFenceWaitTimeMs); if (fenceStatus != android::NO_ERROR) { if (producer->cancelBuffer(slot, fence) != android::NO_ERROR) { return C2_CORRUPTED; } if (fenceStatus == -ETIME) { // fence wait timed out ALOGV("%s(): buffer (slot=%d) fence wait timed out", __func__, slot); return C2_TIMED_OUT; } ALOGE("buffer fence wait error: %d", fenceStatus); return asC2Error(fenceStatus); } } // Call requestBuffer to allocate buffer for the slot and obtain the reference. // Get generation number here. sp slotBuffer = new GraphicBuffer(); status = producer->requestBuffer(slot, &slotBuffer); // Detach and delete the temporary buffer. if (producer->detachBuffer(slot) != android::NO_ERROR) { return C2_CORRUPTED; } // Check requestBuffer return flag. if (status != android::NO_ERROR) { return asC2Error(status); } // Get generation number and usage from the slot buffer. *usage = slotBuffer->getUsage(); *generation = slotBuffer->getGenerationNumber(); ALOGV("Obtained from temp buffer: generation = %u, usage = %" PRIu64 "", *generation, *usage); return C2_OK; } void C2VdaBqBlockPool::Impl::setRenderCallback( const C2BufferQueueBlockPool::OnRenderCallback& renderCallback) { ALOGV("setRenderCallback"); std::lock_guard lock(mMutex); mRenderCallback = renderCallback; } c2_status_t C2VdaBqBlockPool::Impl::requestNewBufferSet(int32_t bufferCount) { if (bufferCount <= 0) { ALOGE("Invalid requested buffer count = %d", bufferCount); return C2_BAD_VALUE; } if (!mAllocateBuffersLock.try_lock_for(kTimedMutexTimeoutMs)) { ALOGE("Cannot acquire allocate buffers / configure producer lock over %" PRId64 " ms...", static_cast(kTimedMutexTimeoutMs.count())); return C2_BLOCKING; } std::lock_guard lock(mMutex); if (!mProducer) { ALOGD("No HGraphicBufferProducer is configured..."); return C2_NO_INIT; } if (mProducerSwitched) { // Some slots can be occupied by buffers transferred from the old producer. They will not // used in the current producer. Free the slots of the buffers here. But we cannot find a // slot is associated with the staled buffer. We free all slots whose associated buffers // are not owned by client. ALOGI("requestNewBufferSet: detachBuffer all slots forcedly"); for (int32_t slot = 0; slot < static_cast(NUM_BUFFER_SLOTS); ++slot) { if (mSlotAllocations.find(slot) != mSlotAllocations.end()) { // Skip detaching the buffer which is owned by client now. continue; } status_t status = mProducer->detachBuffer(slot); if (status == android::NO_INIT) { // No more active buffer slot. Break the loop now. break; } else if (status != android::NO_ERROR) { return C2_CORRUPTED; } } mProducerSwitched = false; } ALOGV("Requested new buffer count: %d, still dequeued buffer count: %zu", bufferCount, mSlotAllocations.size()); // The remained slot indices in |mSlotAllocations| now are still dequeued (un-available). // maxDequeuedBufferCount should be set to "new requested buffer count" + "still dequeued buffer // count" to make sure it has enough available slots to request buffer from. status_t status = mProducer->setMaxDequeuedBufferCount(bufferCount + mSlotAllocations.size()); if (status != android::NO_ERROR) { return asC2Error(status); } // Release all remained slot buffer references here. CCodec should either cancel or queue its // owned buffers from this set before the next resolution change. mSlotAllocations.clear(); mProducerChangeSlotMap.clear(); mBuffersRequested = static_cast(bufferCount); status = mProducer->allowAllocation(true); if (status != android::NO_ERROR) { return asC2Error(status); } return C2_OK; } void C2VdaBqBlockPool::Impl::configureProducer(const sp& producer) { ALOGV("configureProducer"); if (producer == nullptr) { ALOGE("input producer is nullptr..."); return; } std::unique_lock configureProducerLock( mConfigureProducerAndAllocateBuffersMutex, std::defer_lock); if (!configureProducerLock.try_lock_for(kTimedMutexTimeoutMs)) { ALOGE("Cannot acquire configure producer / allocate buffers lock over %" PRId64 " ms...", static_cast(kTimedMutexTimeoutMs.count())); return; } std::lock_guard lock(mMutex); auto newProducer = std::make_unique(producer); uint64_t producerId; if (newProducer->getUniqueId(&producerId) != android::NO_ERROR) { return; } if (mProducer && mProducerId != producerId) { ALOGI("Producer (Surface) is going to switch... ( %" PRIu64 " -> %" PRIu64 " )", mProducerId, producerId); if (!switchProducer(newProducer.get(), producerId)) { mProducerChangeSlotMap.clear(); return; } } else { mSlotAllocations.clear(); } if (newProducer->setDequeueTimeout(0) != android::NO_ERROR) { ALOGE("%s(): failed to setDequeueTimeout(0)", __func__); return; } // hack(b/146409777): Try to connect ARC-specific listener first. sp listener = new BufferReleasedNotifier(shared_from_this()); if (newProducer->connect(listener, 'ARC\0', false) == android::NO_ERROR) { ALOGI("connected to ARC-specific IGBP listener."); mFetchBufferNotifier = listener; } // HGraphicBufferProducer could (and should) be replaced if the client has set a new generation // number to producer. The old HGraphicBufferProducer will be disconnected and deprecated then. mProducer = std::move(newProducer); mProducerId = producerId; } bool C2VdaBqBlockPool::Impl::switchProducer(H2BGraphicBufferProducer* const newProducer, uint64_t newProducerId) { if (mAllocator->getId() == android::V4L2AllocatorId::SECURE_GRAPHIC) { // TODO(johnylin): support this when we meet the use case in the future. ALOGE("Switch producer for secure buffer is not supported..."); return false; } // Set maxDequeuedBufferCount to new producer. // Just like requestNewBufferSet(), maxDequeuedBufferCount should be set to "requested buffer // count" + "buffer count in client" to make sure it has enough available slots to request // buffers from. // "Requested buffer count" could be obtained by the size of |mSlotAllocations|. However, it is // not able to know "buffer count in client" in blockpool's aspect. The alternative solution is // to set the worse case first, which is equal to the size of |mSlotAllocations|. And in the end // of updateGraphicBlock() routine, we could get the arbitrary "buffer count in client" by // counting the calls of updateGraphicBlock(willCancel=true). Then we set maxDequeuedBufferCount // again to the correct value. if (newProducer->setMaxDequeuedBufferCount(mSlotAllocations.size() * 2) != android::NO_ERROR) { return false; } // Reset "buffer count in client". It will be accumulated in updateGraphicBlock() routine. mBuffersInClient = 0; // Set allowAllocation to new producer. if (newProducer->allowAllocation(true) != android::NO_ERROR) { return false; } // Get a buffer from the new producer to get the generation number and usage of new producer. // While attaching buffers, generation number and usage must be aligned to the producer. uint32_t newGeneration; uint64_t newUsage; c2_status_t err = queryGenerationAndUsage(newProducer, mBufferFormat.mWidth, mBufferFormat.mHeight, mBufferFormat.mPixelFormat, mBufferFormat.mUsage, &newGeneration, &newUsage); if (err != C2_OK) { ALOGE("queryGenerationAndUsage failed: %d", err); return false; } // Attach all buffers to new producer. mProducerChangeSlotMap.clear(); int32_t slot; std::map> newSlotAllocations; for (auto iter = mSlotAllocations.begin(); iter != mSlotAllocations.end(); ++iter) { // Convert C2GraphicAllocation to GraphicBuffer. uint32_t width, height, format, stride, igbp_slot, generation; uint64_t usage, igbp_id; android::_UnwrapNativeCodec2GrallocMetadata(iter->second->handle(), &width, &height, &format, &usage, &stride, &generation, &igbp_id, &igbp_slot); native_handle_t* grallocHandle = android::UnwrapNativeCodec2GrallocHandle(iter->second->handle()); // Update generation number and usage. sp graphicBuffer = new GraphicBuffer(grallocHandle, GraphicBuffer::CLONE_HANDLE, width, height, format, 1, newUsage, stride); if (graphicBuffer->initCheck() != android::NO_ERROR) { ALOGE("Failed to create GraphicBuffer: %d", graphicBuffer->initCheck()); return false; } graphicBuffer->setGenerationNumber(newGeneration); native_handle_delete(grallocHandle); if (newProducer->attachBuffer(graphicBuffer, &slot) != android::NO_ERROR) { return false; } // Convert back to C2GraphicAllocation wrapping new producer id, generation number, usage // and slot index. ALOGV("buffer wraps { producer id: %" PRIu64 ", slot: %d }", newProducerId, slot); C2Handle* c2Handle = android::WrapNativeCodec2GrallocHandle( graphicBuffer->handle, width, height, format, newUsage, stride, newGeneration, newProducerId, slot); if (!c2Handle) { ALOGE("WrapNativeCodec2GrallocHandle failed"); return false; } std::shared_ptr alloc; c2_status_t err = mAllocator->priorGraphicAllocation(c2Handle, &alloc); if (err != C2_OK) { ALOGE("priorGraphicAllocation failed: %d", err); return false; } // Store to |newSlotAllocations| and also store old-to-new producer slot map. ALOGV("Transfered buffer from old producer to new, slot prev: %d -> new %d", iter->first, slot); newSlotAllocations[slot] = std::move(alloc); mProducerChangeSlotMap[iter->first] = slot; } // Set allowAllocation to false so producer could not allocate new buffers. if (newProducer->allowAllocation(false) != android::NO_ERROR) { ALOGE("allowAllocation(false) failed"); return false; } // Try to detach all buffers from old producer. for (const auto& slotAllocation : mSlotAllocations) { status_t status = mProducer->detachBuffer(slotAllocation.first); if (status != android::NO_ERROR) { ALOGW("detachBuffer slot=%d from old producer failed: %d", slotAllocation.first, status); } } mSlotAllocations = std::move(newSlotAllocations); return true; } c2_status_t C2VdaBqBlockPool::Impl::updateGraphicBlock( bool willCancel, uint32_t oldSlot, uint32_t* newSlot, std::shared_ptr* block /* nonnull */) { std::lock_guard lock(mMutex); if (mProducerChangeSlotMap.empty()) { ALOGD("A new buffer set is requested right after producer change, no more update needed."); return C2_CANCELED; } auto it = mProducerChangeSlotMap.find(static_cast(oldSlot)); if (it == mProducerChangeSlotMap.end()) { ALOGE("Cannot find old slot = %u in map...", oldSlot); return C2_NOT_FOUND; } int32_t slot = it->second; *newSlot = static_cast(slot); mProducerChangeSlotMap.erase(it); if (willCancel) { sp fence = new Fence(); // The old C2GraphicBlock might be owned by client. Cancel this slot. if (mProducer->cancelBuffer(slot, fence) != android::NO_ERROR) { return C2_CORRUPTED; } // Client might try to attach the old buffer to the current producer on client's end, // although it is useless for us anymore. However it will still occupy an available slot. mBuffersInClient++; } else { // The old C2GraphicBlock is still owned by component, replace by the new one and keep this // slot dequeued. auto poolData = std::make_shared(mProducerId, slot, shared_from_this()); *block = _C2BlockFactory::CreateGraphicBlock(mSlotAllocations[slot], std::move(poolData)); } if (mProducerChangeSlotMap.empty()) { // The updateGraphicBlock() routine is about to finish. // Set the correct maxDequeuedBufferCount to producer, which is "requested buffer count" + // "buffer count in client". ALOGV("Requested buffer count: %zu, buffer count in client: %u", mSlotAllocations.size(), mBuffersInClient); if (mProducer->setMaxDequeuedBufferCount(mSlotAllocations.size() + mBuffersInClient) != android::NO_ERROR) { return C2_CORRUPTED; } mProducerSwitched = true; } return C2_OK; } c2_status_t C2VdaBqBlockPool::Impl::getMinBuffersForDisplay(size_t* bufferCount) { std::lock_guard lock(mMutex); if (!mProducer) { ALOGD("No HGraphicBufferProducer is configured..."); return C2_NO_INIT; } int32_t status, value; status = mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &value); if (status != android::NO_ERROR) { ALOGE("query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS) failed: %d", status); return asC2Error(status); } if (value <= 0) { ALOGE("Illegal value of NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS = %d", value); return C2_BAD_VALUE; } *bufferCount = static_cast(value); return C2_OK; } void C2VdaBqBlockPool::Impl::detachBuffer(uint64_t producerId, int32_t slotId) { ALOGV("detachBuffer: producer id = %" PRIu64 ", slot = %d", producerId, slotId); std::lock_guard lock(mMutex); if (producerId == mProducerId && mProducer) { if (mProducer->detachBuffer(slotId) != android::NO_ERROR) { return; } auto it = mSlotAllocations.find(slotId); // It may happen that the slot is not included in |mSlotAllocations|, which means it is // released after resolution change. if (it != mSlotAllocations.end()) { mSlotAllocations.erase(it); } } } bool C2VdaBqBlockPool::Impl::setNotifyBlockAvailableCb(::base::OnceClosure cb) { ALOGV("%s()", __func__); if (mFetchBufferNotifier == nullptr) { return false; } ::base::OnceClosure outputCb; { std::lock_guard lock(mBufferReleaseMutex); // If there is any buffer released after dequeueBuffer() timed out, then we could notify the // caller directly. if (mBufferReleasedAfterTimedOut) { outputCb = std::move(cb); } else { mNotifyBlockAvailableCb = std::move(cb); } } // Calling the callback outside the lock to avoid the deadlock. if (outputCb) { std::move(outputCb).Run(); } return true; } C2VdaBqBlockPool::C2VdaBqBlockPool(const std::shared_ptr& allocator, const local_id_t localId) : C2BufferQueueBlockPool(allocator, localId), mLocalId(localId), mImpl(new Impl(allocator)) {} c2_status_t C2VdaBqBlockPool::fetchGraphicBlock( uint32_t width, uint32_t height, uint32_t format, C2MemoryUsage usage, std::shared_ptr* block /* nonnull */) { if (mImpl) { return mImpl->fetchGraphicBlock(width, height, format, usage, block); } return C2_NO_INIT; } void C2VdaBqBlockPool::setRenderCallback( const C2BufferQueueBlockPool::OnRenderCallback& renderCallback) { if (mImpl) { mImpl->setRenderCallback(renderCallback); } } c2_status_t C2VdaBqBlockPool::requestNewBufferSet(int32_t bufferCount) { if (mImpl) { return mImpl->requestNewBufferSet(bufferCount); } return C2_NO_INIT; } void C2VdaBqBlockPool::configureProducer(const sp& producer) { if (mImpl) { mImpl->configureProducer(producer); } } c2_status_t C2VdaBqBlockPool::updateGraphicBlock( bool willCancel, uint32_t oldSlot, uint32_t* newSlot, std::shared_ptr* block /* nonnull */) { if (mImpl) { return mImpl->updateGraphicBlock(willCancel, oldSlot, newSlot, block); } return C2_NO_INIT; } c2_status_t C2VdaBqBlockPool::getMinBuffersForDisplay(size_t* bufferCount) { if (mImpl) { return mImpl->getMinBuffersForDisplay(bufferCount); } return C2_NO_INIT; } bool C2VdaBqBlockPool::setNotifyBlockAvailableCb(::base::OnceClosure cb) { if (mImpl) { return mImpl->setNotifyBlockAvailableCb(std::move(cb)); } return false; } C2VdaBqBlockPoolData::C2VdaBqBlockPoolData(uint64_t producerId, int32_t slotId, const std::shared_ptr& pool) : mProducerId(producerId), mSlotId(slotId), mPool(pool) {} C2VdaBqBlockPoolData::~C2VdaBqBlockPoolData() { if (mShared || !mPool) { return; } mPool->detachBuffer(mProducerId, mSlotId); } } // namespace android