[libcamera-devel] [PATCH v1 2/4] ipa: ipu3: ipa_context: Extend FCQueue::get()

Jacopo Mondi jacopo at jmondi.org
Wed Jun 8 15:56:10 CEST 2022


Hi Umang,

On Fri, Jun 03, 2022 at 03:22:57PM +0200, Umang Jain via libcamera-devel wrote:
> Extend the FCQueue::get() to return a IPAFrameContext for a
> non-existent frame. The .get() should be able to figure out if a
> existent frame context is asked for, or to create a new frame context
> based on the next available position. To keep track of next available
> position in the queue, use of head and tail pointer are used.
>
> We specifically want to have access to the queue through the
> .get() function hence operator[] is deleted for FCQueue as
> part of this patch as well.
>
> Signed-off-by: Umang Jain <umang.jain at ideasonboard.com>
> ---
>  src/ipa/ipu3/ipa_context.cpp | 95 +++++++++++++++++++++++++++++++++---
>  src/ipa/ipu3/ipa_context.h   |  9 ++++
>  src/ipa/ipu3/ipu3.cpp        |  4 +-
>  3 files changed, 100 insertions(+), 8 deletions(-)
>
> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
> index 9f95a61c..2438d68d 100644
> --- a/src/ipa/ipu3/ipa_context.cpp
> +++ b/src/ipa/ipu3/ipa_context.cpp
> @@ -222,13 +222,33 @@ IPAFrameContext::IPAFrameContext(uint32_t id, const ControlList &reqControls)
>   * \brief A FIFO circular queue holding IPAFrameContext(s)
>   *
>   * FCQueue holds all the IPAFrameContext(s) related to frames required
> - * to be processed by the IPA at a given point.
> + * to be processed by the IPA at a given point. An IPAFrameContext is created
> + * on the first call FCQueue::get(frame) for that frame. Subsequent calls to
> + * FCQueue::get() with the same frame number shall return the IPAFrameContext
> + * previously created until the frame is marked as complete through
> + * FCQueue::completeFrame(frame).
> + */
> +
> +/**
> + * \var FCQueue::head_
> + * \brief A pointer to the a IPAFrameContext next expected to complete
> + */
> +
> +/**
> + * \var FCQueue::tail_
> + * \brief A pointer to the latest IPAFrameContext created
> + */
> +
> +/**
> + * \var FCQueue::isFull_
> + * \brief Flag set when the FCQueue is full
>   */
>
>  /**
>   * \brief FCQueue constructor
>   */
>  FCQueue::FCQueue()
> +	: head_(nullptr), tail_(nullptr), isFull_(false)
>  {
>  	clear();
>  }
> @@ -238,21 +258,75 @@ FCQueue::FCQueue()
>   * \param[in] frame Frame number for which the IPAFrameContext needs to
>   * retrieved
>   *
> - * \return Pointer to the IPAFrameContext
> + * This function returns the IPAFrameContext for the desired frame. If the
> + * frame context does not exist in the queue, the next available slot in the
> + * queue is returned. It is the responsibility of the caller to fill the correct
> + * IPAFrameContext parameters of newly returned IPAFrameContext.
> + *
> + * \return Pointer to the IPAFrameContext or nullptr if frame context couldn't
> + * be created
>   */
>  IPAFrameContext *FCQueue::get(uint32_t frame)
>  {
> -	IPAFrameContext &frameContext = this->at(frame % kMaxFrameContexts);
> +	if (frame <= tail_->frame) {

How does this work ? The first get() you call after construction has tail_
== nullptr. How come this doesn't segfault ? Is it because there's a
call to clear() that initializes the pointers before usage ?
Shouldn't the constructor take care of creating a usable object
without requiring clear() to be called ?

Also, are we sure using the frame number in tail_ and head_ is the best
way to ensure that we actually track where we are in the queue ?

I have a few  worries:

1) are frame numbers always starting from 0 ?

2) frame numbers are monotonically increasing, but can have gaps.
   When you create a new frame context you increase by one to get the
   next slot, but when you get an existing frame you compute the index
   by doing frame % kMaxFrameContexts

        IPAFrameContext *FCQueue::get(uint32_t frame) {

                if (frame <= tail_->frame)
                        /* GET */
                        IPAFrameContext &frameContext = this->at(frame % kMaxFrameContexts);
                else
                        /* PUSH */
                        tail_ = &this->at((tail_->frame + 1) % kMaxFrameContexts);
        		tail_->frame = frame;

    Isn't there a risk you get the wrong frame ?

    (also being this a LIFO, isn't the head the most recent and the
    tail the oldest entry ? you seem to advance tail when you push a
    new frame)

2 ) tail_ gets initialized with an empty IPAFrameContext whose constructor
    is
        IPAFrameContext::IPAFrameContext() = default;

    hence its frame id is not intialized, or are POD types default
    initialized in C++ ?

Anyway, isn't it simpler to use plain counters for head and tail instead
of pointers to the contained objects ? (This would maybe make
complicated to generalize the libcamera::RingBuffer implementation
maybe), but head_ and tail_ mainly serve for two purpose:
- underflow detection (trying to complete a frame not yet queued)
- overflow detection (trying to queue a frame overwrites a
  not-yet-consumed one)

Can't we have head and tail simply follow the latest frame number that
have been queued and the lastest frame number that has been consumed
respectively ?

Collision detection will simply be
- undeflow: tail + 1 == head
- overflow (queue frame): head - tail == array size

The actual position on the array can always be computed as (frame %
kMaxFrameContexts)

This doesn't however work well with gaps, as one frame gap means we're
actually not using one slot, so a little space is wasted. Ofc as the
number of gaps approaches the number of available slots, the risk of
overflow increases. But gaps should be an exceptional events and with
large enough buffers this shouldn't be a problem ?

Also I wonder if a push/get interface wouldn't be simpler, with new
reuests being pushed() and frames consumed with get(), but that's more
an implementation detail maybe..

IPA components cannot have tests right ? It would be nice to have a
unit test for this component to make sure it behaves as intended
instead of having to debug it "live"

sorry, a lot of questions actually..

> +		IPAFrameContext &frameContext = this->at(frame % kMaxFrameContexts);
> +		if (frame != frameContext.frame)
> +			LOG(IPAIPU3, Warning)
> +				<< "Got wrong frame context for frame " << frame;
> +
> +		return &frameContext;
> +	} else {
> +		if (isFull_) {
> +			LOG(IPAIPU3, Warning)
> +				<< "Cannot create frame context for frame " << frame
> +				<< " since FCQueue is full";
> +
> +			return nullptr;
> +		}
> +
> +		/*
> +		 * Frame context doesn't exist yet so get the next available
> +		 * position in the queue.
> +		 */
> +		tail_ = &this->at((tail_->frame + 1) % kMaxFrameContexts);
> +		tail_->frame = frame;
> +
> +		/* Check if the queue is full to avoid over-queueing later */
> +		if (tail_->frame - head_->frame >= kMaxFrameContexts - 1)
> +			isFull_ = true;
>
> -	if (frame != frameContext.frame) {
> +		return tail_;
> +	}
> +}
> +
> +/**
> + * \brief Notifies the FCQueue that a frame has been completed
> + * \param[in] frame The completed frame number
> + */
> +void FCQueue::completeFrame(uint32_t frame)
> +{
> +	if (head_->frame != frame)
>  		LOG(IPAIPU3, Warning)
> -			<< "Got wrong frame context for frame" << frame
> -			<< " or frame context doesn't exist yet";
> +			<< "Frame " << frame << " completed out-of-sync?";
> +
> +	if (frame > tail_->frame) {
> +		LOG(IPAIPU3, Error)
> +			<< "Completing a frame " << frame
> +			<< " not present in the queue is disallowed";
> +		return;
>  	}
>
> -	return &frameContext;
> +	head_ = this->get(frame + 1);
> +
> +	if (isFull_)
> +		isFull_ = false;
>  }
>
> +/**
> + * \fn FCQueue::isFull()
> + * \brief Checks whether the frame context queue is full
> + */
> +
>  /**
>   * \brief Clear the FCQueue by resetting all the entries in the ring-buffer
>   */
> @@ -260,6 +334,13 @@ void FCQueue::clear()
>  {
>  	IPAFrameContext initFrameContext;
>  	this->fill(initFrameContext);
> +
> +	isFull_ = false;
> +
> +	/* Intialise 0th index to frame 0 */
> +	this->at(0).frame = 0;
> +	tail_ = &this->at(0);
> +	head_ = tail_;
>  }
>
>  } /* namespace ipa::ipu3 */
> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
> index 56d281f6..475855da 100644
> --- a/src/ipa/ipu3/ipa_context.h
> +++ b/src/ipa/ipu3/ipa_context.h
> @@ -93,9 +93,18 @@ class FCQueue : public std::array<IPAFrameContext, kMaxFrameContexts>
>  {
>  public:
>  	FCQueue();
> +	FCQueue &operator[](const FCQueue &) = delete;
>
>  	void clear();
> +	void completeFrame(uint32_t frame);
>  	IPAFrameContext *get(uint32_t frame);
> +	bool isFull() { return isFull_; }
> +
> +private:
> +	IPAFrameContext *head_;
> +	IPAFrameContext *tail_;
> +
> +	bool isFull_;
>  };
>
>  struct IPAContext {
> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
> index 0843d882..1d6ee515 100644
> --- a/src/ipa/ipu3/ipu3.cpp
> +++ b/src/ipa/ipu3/ipu3.cpp
> @@ -605,6 +605,8 @@ void IPAIPU3::processStatsBuffer(const uint32_t frame,
>  	 */
>
>  	metadataReady.emit(frame, ctrls);
> +
> +	context_.frameContexts.completeFrame(frame);
>  }
>
>  /**
> @@ -618,7 +620,7 @@ void IPAIPU3::processStatsBuffer(const uint32_t frame,
>  void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls)
>  {
>  	/* \todo Start processing for 'frame' based on 'controls'. */
> -	context_.frameContexts[frame % kMaxFrameContexts] = { frame, controls };
> +	*context_.frameContexts.get(frame) = { frame, controls };
>  }
>
>  /**
> --
> 2.31.1
>


More information about the libcamera-devel mailing list