From patchwork Thu Mar 12 14:27:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 26276 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 3DEB7BDCC1 for ; Thu, 12 Mar 2026 14:27:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0500E6262B; Thu, 12 Mar 2026 15:27:15 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qt9g7Ygu"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BAB1762010 for ; Thu, 12 Mar 2026 15:27:12 +0100 (CET) Received: from pb-laptop.local (185.182.214.153.nat.pool.zt.hu [185.182.214.153]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2CCE550A for ; Thu, 12 Mar 2026 15:26:04 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1773325564; bh=pCnNgE9Ko596XIc9NppV1vJGOVChCVEAuWX93hQKyBI=; h=From:To:Subject:Date:From; b=qt9g7Ygu3xpNv+O9So9bnXzyCm2/heYNn6wB2ywSIl+ZwLA8Plub+k/Xl73rMShur sR/RJ9fO3DY+3MuRCxeJ3a2avW396QKNIH3JlpmuQXLuq5jDwt+12LR9/c0jZTFYFe CIn93YkLwo9O1E/h1xNrfDq++6Ws3wK2wDBjBBfY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v1] ipa: libipa: fc_queue: Support non-contiguous frame numbers Date: Thu, 12 Mar 2026 15:27:08 +0100 Message-ID: <20260312142708.169901-1-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.53.0 MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" At the moment non-contiguous frame numbers are not really supported. They may cause assertion failures. Consider the situation where an `FCQueue` with a capacity of 32 is created, and then `alloc(45)` and `alloc(77)` are called. In this case, since 45 == 77 (mod 32), the same slot (13) is reused for both. A subsequent `get(45)` call would then log a fatal error as it detects that the slot has been overwritten. This issue affects the rkisp1 pipeline handler in particular as it does not use the request sequence number but instead manages a separate counter, which can have jumps when adjusted in `PipelineHandlerRkISP1::statBufferReady()`. It is easily visible in non-optimized sanitized builds with significant amount of log output. Other pipeline handlers employing `FCQueue` use the request sequence number, which is always incremented by 1, with no jumps. Address this by storing the sequence number next to the data, and using binary search instead of direct indexing. As a consequence of this, `get(x)` can no longer initialize the item in every case if `alloc(x)` has not been called, thus it has to abort to provide the same interface. The `FrameContext` type is also removed, no inheritance is necessary anymore. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/issues/250 Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/ipa_context.h | 2 +- src/ipa/libipa/fc_queue.cpp | 35 ++----- src/ipa/libipa/fc_queue.h | 178 ++++++++++++++++++--------------- src/ipa/mali-c55/ipa_context.h | 2 +- src/ipa/rkisp1/ipa_context.h | 2 +- src/ipa/simple/ipa_context.h | 2 +- 6 files changed, 107 insertions(+), 114 deletions(-) diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h index 97fcf06cd..4159b8605 100644 --- a/src/ipa/ipu3/ipa_context.h +++ b/src/ipa/ipu3/ipa_context.h @@ -76,7 +76,7 @@ struct IPAActiveState { } toneMapping; }; -struct IPAFrameContext : public FrameContext { +struct IPAFrameContext { struct { uint32_t exposure; double gain; diff --git a/src/ipa/libipa/fc_queue.cpp b/src/ipa/libipa/fc_queue.cpp index 0365e9197..ddcaca26f 100644 --- a/src/ipa/libipa/fc_queue.cpp +++ b/src/ipa/libipa/fc_queue.cpp @@ -20,28 +20,10 @@ namespace ipa { * \brief Queue of per-frame contexts */ -/** - * \struct FrameContext - * \brief Context for a frame - * - * The frame context stores data specific to a single frame processed by the - * IPA module. Each frame processed by the IPA module has a context associated - * with it, accessible through the Frame Context Queue. - * - * Fields in the frame context should reflect values and controls associated - * with the specific frame as requested by the application, and as configured by - * the hardware. Fields can be read by algorithms to determine if they should - * update any specific action for this frame, and finally to update the metadata - * control lists when the frame is fully completed. - * - * \var FrameContext::frame - * \brief The frame number - */ - /** * \class FCQueue * \brief A support class for managing FrameContext instances in IPA modules - * \tparam FrameContext The IPA module-specific FrameContext derived class type + * \tparam FrameContext The IPA module-specific frame context type * * Along with the Module and Algorithm classes, the frame context queue is a * core component of the libipa infrastructure. It stores per-frame contexts @@ -83,16 +65,12 @@ namespace ipa { * allowed to overflow, which must be ensured by pipeline handlers never * queuing more in-flight requests to the IPA module than the queue size. If an * overflow condition is detected, the queue will log a fatal error. - * - * IPA module-specific frame context implementations shall inherit from the - * FrameContext base class to support the minimum required features for a - * FrameContext. */ /** - * \fn FCQueue::FCQueue(unsigned int size) + * \fn FCQueue::FCQueue(std::size_t capacity) * \brief Construct a frame contexts queue of a specified size - * \param[in] size The number of contexts in the queue + * \param[in] capacity The number of contexts in the queue */ /** @@ -116,7 +94,8 @@ namespace ipa { * initialised already, and returned to the caller. * * If the FrameContext was already initialized for this \a frame, a warning will - * be reported and the previously initialized FrameContext is returned. + * be reported and the previously initialized FrameContext is returned. Otherwise, + * \a frame must be greater than the last allocated frame number. * * Frame contexts are expected to be initialised when a Request is first passed * to the IPA module in IPAModule::queueRequest(). @@ -129,8 +108,8 @@ namespace ipa { * \brief Obtain the FrameContext for the \a frame * \param[in] frame The frame context sequence number * - * If the FrameContext is not correctly initialised for the \a frame, it will be - * initialised. + * If the FrameContext is not correctly initialised for the \a frame, initialisation + * will be tried as if by calling \a alloc(frame). Note, that this may fail, and abort. * * \return A reference to the FrameContext for sequence \a frame */ diff --git a/src/ipa/libipa/fc_queue.h b/src/ipa/libipa/fc_queue.h index a1d136521..86ae6ea1d 100644 --- a/src/ipa/libipa/fc_queue.h +++ b/src/ipa/libipa/fc_queue.h @@ -7,8 +7,11 @@ #pragma once +#include +#include +#include #include -#include +#include #include @@ -18,118 +21,129 @@ LOG_DECLARE_CATEGORY(FCQueue) namespace ipa { -template -class FCQueue; - -struct FrameContext { -private: - template friend class FCQueue; - uint32_t frame; - bool initialised = false; -}; - -template +template class FCQueue { + static_assert(std::is_default_constructible_v); + public: - FCQueue(unsigned int size) - : contexts_(size) + FCQueue(std::size_t capacity) + : entries_(std::make_unique[]>(capacity)), + capacity_(capacity) { + ASSERT(capacity > 0); } void clear() { - for (FrameContext &ctx : contexts_) { - ctx.initialised = false; - ctx.frame = 0; - } + next_ = 0; + lastFrame_.reset(); + + for (size_t i = 0; i < capacity_; i++) + entries_[i].reset(); } - FrameContext &alloc(const uint32_t frame) + T &get(uint32_t frame) { - FrameContext &frameContext = contexts_[frame % contexts_.size()]; + LOG(FCQueue, Debug) << "get(" << frame << ")"; - /* - * Do not re-initialise if a get() call has already fetched this - * frame context to preseve the context. - * - * \todo If the the sequence number of the context to initialise - * is smaller than the sequence number of the queue slot to use, - * it means that we had a serious request underrun and more - * frames than the queue size has been produced since the last - * time the application has queued a request. Does this deserve - * an error condition ? - */ - if (frame != 0 && frame <= frameContext.frame) - LOG(FCQueue, Warning) - << "Frame " << frame << " already initialised"; - else - init(frameContext, frame); + if (auto *d = find(frame)) + return *d; + + LOG(FCQueue, Warning) + << "Frame " << frame << " not found, trying to allocate"; - return frameContext; + return allocNext(frame); } - FrameContext &get(uint32_t frame) + T &alloc(uint32_t frame) { - FrameContext &frameContext = contexts_[frame % contexts_.size()]; + LOG(FCQueue, Debug) << "alloc(" << frame << ")"; + + if (frame <= lastFrame_) { + if (auto *d = find(frame)) { + LOG(FCQueue, Warning) + << "Frame " << frame << " already initialised"; + return *d; + } + } - /* - * If the IPA algorithms try to access a frame context slot which - * has been already overwritten by a newer context, it means the - * frame context queue has overflowed and the desired context - * has been forever lost. The pipeline handler shall avoid - * queueing more requests to the IPA than the frame context - * queue size. - */ - if (frame < frameContext.frame) - LOG(FCQueue, Fatal) << "Frame context for " << frame - << " has been overwritten by " - << frameContext.frame; + return allocNext(frame); + } - if (frame == 0 && !frameContext.initialised) { - /* - * If the IPA calls get() at start() time it will get an - * un-intialized FrameContext as the below "frame == - * frameContext.frame" check will return success because - * FrameContexts are zeroed at creation time. - * - * Make sure the FrameContext gets initialised if get() - * is called before alloc() by the IPA for frame#0. - */ - init(frameContext, frame); +private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(FCQueue) - return frameContext; + struct Entry { + uint32_t frame; + T data; + + Entry(uint32_t f) + : frame(f), data() + { } + }; + + T *find(uint32_t frame) + { + const auto findInRange = [&](auto first, auto last) -> T * { + auto it = std::partition_point(first, last, [&](const auto &e) { + ASSERT(e); + return e->frame < frame; + }); + if (it != last && (*it)->frame == frame) + return &(*it)->data; + + return nullptr; + }; - if (frame == frameContext.frame) - return frameContext; + const auto first = entries_.get(); + const auto mid = first + next_; + const auto last = first + capacity_; /* - * The frame context has been retrieved before it was - * initialised through the initialise() call. This indicates an - * algorithm attempted to access a Frame context before it was - * queued to the IPA. Controls applied for this request may be - * left unhandled. - * - * \todo Set an error flag for per-frame control errors. + * Search the more recent half: [0; next), + * the optionals in this range must always be non-empty. */ - LOG(FCQueue, Warning) - << "Obtained an uninitialised FrameContext for " << frame; + if (auto *d = findInRange(first, mid)) + return d; - init(frameContext, frame); + /* Search the less recent half: [next_; capacity_) */ + if (mid != last && mid->has_value()) { + /* + * If `next_` has wrapped around at least once, then all the optionals + * in [next_; capacity_) are non-empty. So if `*mid` is not empty, + * then all of them should be non-empty. + */ + if (auto *d = findInRange(mid, last)) + return d; + } - return frameContext; + return nullptr; } -private: - void init(FrameContext &frameContext, const uint32_t frame) + T &allocNext(uint32_t frame) { - frameContext = {}; - frameContext.frame = frame; - frameContext.initialised = true; + if (!(lastFrame_ < frame)) { + LOG(FCQueue, Fatal) + << "Tried to allocate frame context for frame " << frame + << " after having already allocated one for a later frame " + << *lastFrame_; + } + + auto &e = entries_[next_].emplace(frame); + LOG(FCQueue, Debug) << "frame " << frame << " slot " << next_; + + next_ = (next_ + 1) % capacity_; + lastFrame_ = frame; + + return e.data; } - std::vector contexts_; + std::unique_ptr[]> entries_; + std::size_t capacity_; + std::size_t next_ = 0; + std::optional lastFrame_; }; } /* namespace ipa */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ac4b83773..156889dfe 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -60,7 +60,7 @@ struct IPAActiveState { } awb; }; -struct IPAFrameContext : public FrameContext { +struct IPAFrameContext { struct { uint32_t exposure; double sensorGain; diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index e61391bb1..e1aeac7fc 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -153,7 +153,7 @@ struct IPAActiveState { } lsc; }; -struct IPAFrameContext : public FrameContext { +struct IPAFrameContext { struct { uint32_t exposure; double gain; diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 34f7403a4..7e5e94904 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -63,7 +63,7 @@ struct IPAActiveState { } knobs; }; -struct IPAFrameContext : public FrameContext { +struct IPAFrameContext { Matrix ccm; struct {