[v3,1/2] libcamera: libipa: Add right-shift operators to Vector
diff mbox series

Message ID 20260511-libipa-vector-rshift-v3-1-a275f6e87ec4@jetm.me
State Accepted
Headers show
Series
  • libcamera: Vector right-shift operators and SwStatsCpu use site
Related show

Commit Message

Javier Tia May 11, 2026, 8:23 p.m. UTC
Add operator>> and operator>>= for right-shifting all elements by a
scalar shift amount. Both operate element-wise: operator>> returns a
new Vector with each element shifted, operator>>= shifts in place and
returns a reference to *this.

The motivating use case is SwStatsCpu::finishFrame(), which right-shifts
three individual components of the RGB sum by the same sumShift_ value.
With these operators that becomes a single sum_ >>= sumShift_ expression.

The private apply() helpers for non-mutating and mutating scalar
operations are templated on the scalar type so the existing operators
keep working with T while shift can pass an unsigned int. A
static_assert in each shift operator restricts them to integer element
types since right-shift is undefined on floating-point Vectors.

Suggested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Javier Tia <floss@jetm.me>
Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
---
 include/libcamera/internal/vector.h | 22 ++++++++++++++++++----
 src/libcamera/vector.cpp            | 14 ++++++++++++++
 test/vector.cpp                     |  5 +++++
 3 files changed, 37 insertions(+), 4 deletions(-)

Comments

Kieran Bingham May 12, 2026, 11:09 a.m. UTC | #1
Quoting Javier Tia (2026-05-11 21:23:28)
> Add operator>> and operator>>= for right-shifting all elements by a
> scalar shift amount. Both operate element-wise: operator>> returns a
> new Vector with each element shifted, operator>>= shifts in place and
> returns a reference to *this.
> 
> The motivating use case is SwStatsCpu::finishFrame(), which right-shifts
> three individual components of the RGB sum by the same sumShift_ value.
> With these operators that becomes a single sum_ >>= sumShift_ expression.
> 
> The private apply() helpers for non-mutating and mutating scalar
> operations are templated on the scalar type so the existing operators
> keep working with T while shift can pass an unsigned int. A
> static_assert in each shift operator restricts them to integer element
> types since right-shift is undefined on floating-point Vectors.
> 
> Suggested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Javier Tia <floss@jetm.me>
> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> ---
>  include/libcamera/internal/vector.h | 22 ++++++++++++++++++----
>  src/libcamera/vector.cpp            | 14 ++++++++++++++
>  test/vector.cpp                     |  5 +++++
>  3 files changed, 37 insertions(+), 4 deletions(-)
> 
> diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
> index ed7490e1..e3ea723d 100644
> --- a/include/libcamera/internal/vector.h
> +++ b/include/libcamera/internal/vector.h
> @@ -111,6 +111,13 @@ public:
>                 return apply(*this, scalar, std::divides<>{});
>         }
>  
> +       constexpr Vector operator>>(unsigned int shift) const
> +       {
> +               static_assert(std::is_integral_v<T>,
> +                             "Vector::operator>> requires an integer element type");
> +               return apply(*this, shift, [](T a, unsigned int b) { return a >> b; });
> +       }
> +
>         Vector &operator+=(const Vector &other)
>         {
>                 return apply(other, [](T a, T b) { return a + b; });
> @@ -151,6 +158,13 @@ public:
>                 return apply(scalar, [](T a, T b) { return a / b; });
>         }
>  
> +       Vector &operator>>=(unsigned int shift)
> +       {
> +               static_assert(std::is_integral_v<T>,
> +                             "Vector::operator>>= requires an integer element type");
> +               return apply(shift, [](T a, unsigned int b) { return a >> b; });
> +       }
> +
>         constexpr Vector min(const Vector &other) const
>         {
>                 return apply(*this, other, [](T a, T b) { return std::min(a, b); });
> @@ -260,8 +274,8 @@ private:
>                 return result;
>         }
>  
> -       template<class BinaryOp>
> -       static constexpr Vector apply(const Vector &lhs, T rhs, BinaryOp op)
> +       template<class U, class BinaryOp>
> +       static constexpr Vector apply(const Vector &lhs, U rhs, BinaryOp op)

I haven't understood why this needs to change, and does class U get used
implicitly ? I don't see anything changing the template parameters
elsewhere in this patch except...

>         {
>                 Vector result;
>                 std::transform(lhs.data_.begin(), lhs.data_.end(),
> @@ -281,8 +295,8 @@ private:
>                 return *this;
>         }
>  
> -       template<class BinaryOp>
> -       Vector &apply(T scalar, BinaryOp op)
> +       template<class U, class BinaryOp>
> +       Vector &apply(U scalar, BinaryOp op)

here ... which I also can't understand how this is used ?

I removed these two hunks and the code still compiled and ran the tests.

What am I missing ? Does U mean unsigned version of T perhaps? or
something else implicit?



>         {
>                 std::for_each(data_.begin(), data_.end(),
>                               [&op, scalar](T &v) { v = op(v, scalar); });
> diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp
> index 86b9f9bb..94fbf61e 100644
> --- a/src/libcamera/vector.cpp
> +++ b/src/libcamera/vector.cpp
> @@ -126,6 +126,13 @@ LOG_DEFINE_CATEGORY(Vector)
>   * \return The element-wise division of this vector by \a scalar
>   */
>  
> +/**
> + * \fn Vector::operator>>(unsigned int shift) const
> + * \brief Right-shift each element of this vector by \a shift bits
> + * \param[in] shift The shift amount
> + * \return A new vector with each element right-shifted by \a shift
> + */
> +
>  /**
>   * \fn Vector::operator+=(Vector const &other)
>   * \brief Add \a other element-wise to this vector
> @@ -182,6 +189,13 @@ LOG_DEFINE_CATEGORY(Vector)
>   * \return This vector
>   */
>  
> +/**
> + * \fn Vector::operator>>=(unsigned int shift)
> + * \brief Right-shift each element of this vector by \a shift bits in place
> + * \param[in] shift The shift amount
> + * \return This vector
> + */
> +
>  /**
>   * \fn Vector::min(const Vector &other) const
>   * \brief Calculate the minimum of this vector and \a other element-wise
> diff --git a/test/vector.cpp b/test/vector.cpp
> index 4fae960d..4ff908e8 100644
> --- a/test/vector.cpp
> +++ b/test/vector.cpp
> @@ -93,6 +93,11 @@ protected:
>                 v2 /= 4.0;
>                 ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
>  
> +               Vector<int, 3> vi{{ 8, 16, 32 }};
> +               ASSERT_EQ(vi >> 2, (Vector<int, 3>{{ 2, 4, 8 }}));
> +               vi >>= 1;
> +               ASSERT_EQ(vi, (Vector<int, 3>{{ 4, 8, 16 }}));
> +

This upsets the checkstyle script / clang format - but I think we should
ignore it - as it's matching the style used for the tests above, and if
we fix checkstyle it should be applied to the whole file, not just this
patch hunk.


>                 return TestPass;
>         }
>  };
> 
> -- 
> 2.54.0
>
Barnabás Pőcze May 12, 2026, 11:59 a.m. UTC | #2
2026. 05. 12. 13:09 keltezéssel, Kieran Bingham írta:
> Quoting Javier Tia (2026-05-11 21:23:28)
>> Add operator>> and operator>>= for right-shifting all elements by a
>> scalar shift amount. Both operate element-wise: operator>> returns a
>> new Vector with each element shifted, operator>>= shifts in place and
>> returns a reference to *this.
>>
>> The motivating use case is SwStatsCpu::finishFrame(), which right-shifts
>> three individual components of the RGB sum by the same sumShift_ value.
>> With these operators that becomes a single sum_ >>= sumShift_ expression.
>>
>> The private apply() helpers for non-mutating and mutating scalar
>> operations are templated on the scalar type so the existing operators
>> keep working with T while shift can pass an unsigned int. A
>> static_assert in each shift operator restricts them to integer element
>> types since right-shift is undefined on floating-point Vectors.
>>
>> Suggested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
>> Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>> Signed-off-by: Javier Tia <floss@jetm.me>
>> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
>> ---
>>   include/libcamera/internal/vector.h | 22 ++++++++++++++++++----
>>   src/libcamera/vector.cpp            | 14 ++++++++++++++
>>   test/vector.cpp                     |  5 +++++
>>   3 files changed, 37 insertions(+), 4 deletions(-)
>>
>> diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
>> index ed7490e1..e3ea723d 100644
>> --- a/include/libcamera/internal/vector.h
>> +++ b/include/libcamera/internal/vector.h
>> @@ -111,6 +111,13 @@ public:
>>                  return apply(*this, scalar, std::divides<>{});
>>          }
>>
>> +       constexpr Vector operator>>(unsigned int shift) const
>> +       {
>> +               static_assert(std::is_integral_v<T>,
>> +                             "Vector::operator>> requires an integer element type");
>> +               return apply(*this, shift, [](T a, unsigned int b) { return a >> b; });
>> +       }
>> +
>>          Vector &operator+=(const Vector &other)
>>          {
>>                  return apply(other, [](T a, T b) { return a + b; });
>> @@ -151,6 +158,13 @@ public:
>>                  return apply(scalar, [](T a, T b) { return a / b; });
>>          }
>>
>> +       Vector &operator>>=(unsigned int shift)
>> +       {
>> +               static_assert(std::is_integral_v<T>,
>> +                             "Vector::operator>>= requires an integer element type");
>> +               return apply(shift, [](T a, unsigned int b) { return a >> b; });
>> +       }
>> +
>>          constexpr Vector min(const Vector &other) const
>>          {
>>                  return apply(*this, other, [](T a, T b) { return std::min(a, b); });
>> @@ -260,8 +274,8 @@ private:
>>                  return result;
>>          }
>>
>> -       template<class BinaryOp>
>> -       static constexpr Vector apply(const Vector &lhs, T rhs, BinaryOp op)
>> +       template<class U, class BinaryOp>
>> +       static constexpr Vector apply(const Vector &lhs, U rhs, BinaryOp op)
> 
> I haven't understood why this needs to change, and does class U get used
> implicitly ? I don't see anything changing the template parameters
> elsewhere in this patch except...

It's so that the `unsigned int shift` parameter is not converted to `T`
in the `apply()` call (i.e. `U=unsigned int` is deduced). Since the range
of `shift` is limited, I'm not sure it makes a lot of practical difference,
but I think it's better to make it possible to preserve the type.


> 
>>          {
>>                  Vector result;
>>                  std::transform(lhs.data_.begin(), lhs.data_.end(),
>> @@ -281,8 +295,8 @@ private:
>>                  return *this;
>>          }
>>
>> -       template<class BinaryOp>
>> -       Vector &apply(T scalar, BinaryOp op)
>> +       template<class U, class BinaryOp>
>> +       Vector &apply(U scalar, BinaryOp op)
> 
> here ... which I also can't understand how this is used ?
> 
> I removed these two hunks and the code still compiled and ran the tests.
> 
> What am I missing ? Does U mean unsigned version of T perhaps? or
> something else implicit?
> 
> 
> 
>>          {
>>                  std::for_each(data_.begin(), data_.end(),
>>                                [&op, scalar](T &v) { v = op(v, scalar); });
>> diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp
>> index 86b9f9bb..94fbf61e 100644
>> --- a/src/libcamera/vector.cpp
>> +++ b/src/libcamera/vector.cpp
>> @@ -126,6 +126,13 @@ LOG_DEFINE_CATEGORY(Vector)
>>    * \return The element-wise division of this vector by \a scalar
>>    */
>>
>> +/**
>> + * \fn Vector::operator>>(unsigned int shift) const
>> + * \brief Right-shift each element of this vector by \a shift bits
>> + * \param[in] shift The shift amount
>> + * \return A new vector with each element right-shifted by \a shift
>> + */
>> +
>>   /**
>>    * \fn Vector::operator+=(Vector const &other)
>>    * \brief Add \a other element-wise to this vector
>> @@ -182,6 +189,13 @@ LOG_DEFINE_CATEGORY(Vector)
>>    * \return This vector
>>    */
>>
>> +/**
>> + * \fn Vector::operator>>=(unsigned int shift)
>> + * \brief Right-shift each element of this vector by \a shift bits in place
>> + * \param[in] shift The shift amount
>> + * \return This vector
>> + */
>> +
>>   /**
>>    * \fn Vector::min(const Vector &other) const
>>    * \brief Calculate the minimum of this vector and \a other element-wise
>> diff --git a/test/vector.cpp b/test/vector.cpp
>> index 4fae960d..4ff908e8 100644
>> --- a/test/vector.cpp
>> +++ b/test/vector.cpp
>> @@ -93,6 +93,11 @@ protected:
>>                  v2 /= 4.0;
>>                  ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
>>
>> +               Vector<int, 3> vi{{ 8, 16, 32 }};
>> +               ASSERT_EQ(vi >> 2, (Vector<int, 3>{{ 2, 4, 8 }}));
>> +               vi >>= 1;
>> +               ASSERT_EQ(vi, (Vector<int, 3>{{ 4, 8, 16 }}));
>> +
> 
> This upsets the checkstyle script / clang format - but I think we should
> ignore it - as it's matching the style used for the tests above, and if
> we fix checkstyle it should be applied to the whole file, not just this
> patch hunk.
> 
> 
>>                  return TestPass;
>>          }
>>   };
>>
>> --
>> 2.54.0
>>
Kieran Bingham May 12, 2026, 12:53 p.m. UTC | #3
Quoting Barnabás Pőcze (2026-05-12 12:59:12)
> 2026. 05. 12. 13:09 keltezéssel, Kieran Bingham írta:
> > Quoting Javier Tia (2026-05-11 21:23:28)
> >> Add operator>> and operator>>= for right-shifting all elements by a
> >> scalar shift amount. Both operate element-wise: operator>> returns a
> >> new Vector with each element shifted, operator>>= shifts in place and
> >> returns a reference to *this.
> >>
> >> The motivating use case is SwStatsCpu::finishFrame(), which right-shifts
> >> three individual components of the RGB sum by the same sumShift_ value.
> >> With these operators that becomes a single sum_ >>= sumShift_ expression.
> >>
> >> The private apply() helpers for non-mutating and mutating scalar
> >> operations are templated on the scalar type so the existing operators
> >> keep working with T while shift can pass an unsigned int. A
> >> static_assert in each shift operator restricts them to integer element
> >> types since right-shift is undefined on floating-point Vectors.
> >>
> >> Suggested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> >> Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >> Signed-off-by: Javier Tia <floss@jetm.me>
> >> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> >> ---
> >>   include/libcamera/internal/vector.h | 22 ++++++++++++++++++----
> >>   src/libcamera/vector.cpp            | 14 ++++++++++++++
> >>   test/vector.cpp                     |  5 +++++
> >>   3 files changed, 37 insertions(+), 4 deletions(-)
> >>
> >> diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
> >> index ed7490e1..e3ea723d 100644
> >> --- a/include/libcamera/internal/vector.h
> >> +++ b/include/libcamera/internal/vector.h
> >> @@ -111,6 +111,13 @@ public:
> >>                  return apply(*this, scalar, std::divides<>{});
> >>          }
> >>
> >> +       constexpr Vector operator>>(unsigned int shift) const
> >> +       {
> >> +               static_assert(std::is_integral_v<T>,
> >> +                             "Vector::operator>> requires an integer element type");
> >> +               return apply(*this, shift, [](T a, unsigned int b) { return a >> b; });
> >> +       }
> >> +
> >>          Vector &operator+=(const Vector &other)
> >>          {
> >>                  return apply(other, [](T a, T b) { return a + b; });
> >> @@ -151,6 +158,13 @@ public:
> >>                  return apply(scalar, [](T a, T b) { return a / b; });
> >>          }
> >>
> >> +       Vector &operator>>=(unsigned int shift)
> >> +       {
> >> +               static_assert(std::is_integral_v<T>,
> >> +                             "Vector::operator>>= requires an integer element type");
> >> +               return apply(shift, [](T a, unsigned int b) { return a >> b; });
> >> +       }
> >> +
> >>          constexpr Vector min(const Vector &other) const
> >>          {
> >>                  return apply(*this, other, [](T a, T b) { return std::min(a, b); });
> >> @@ -260,8 +274,8 @@ private:
> >>                  return result;
> >>          }
> >>
> >> -       template<class BinaryOp>
> >> -       static constexpr Vector apply(const Vector &lhs, T rhs, BinaryOp op)
> >> +       template<class U, class BinaryOp>
> >> +       static constexpr Vector apply(const Vector &lhs, U rhs, BinaryOp op)
> > 
> > I haven't understood why this needs to change, and does class U get used
> > implicitly ? I don't see anything changing the template parameters
> > elsewhere in this patch except...
> 
> It's so that the `unsigned int shift` parameter is not converted to `T`
> in the `apply()` call (i.e. `U=unsigned int` is deduced). Since the range
> of `shift` is limited, I'm not sure it makes a lot of practical difference,
> but I think it's better to make it possible to preserve the type.


Thanks. Templates are hard, but I'm happy with the rest of this, and
it's passing the tests so:

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

Patch
diff mbox series

diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
index ed7490e1..e3ea723d 100644
--- a/include/libcamera/internal/vector.h
+++ b/include/libcamera/internal/vector.h
@@ -111,6 +111,13 @@  public:
 		return apply(*this, scalar, std::divides<>{});
 	}
 
+	constexpr Vector operator>>(unsigned int shift) const
+	{
+		static_assert(std::is_integral_v<T>,
+			      "Vector::operator>> requires an integer element type");
+		return apply(*this, shift, [](T a, unsigned int b) { return a >> b; });
+	}
+
 	Vector &operator+=(const Vector &other)
 	{
 		return apply(other, [](T a, T b) { return a + b; });
@@ -151,6 +158,13 @@  public:
 		return apply(scalar, [](T a, T b) { return a / b; });
 	}
 
+	Vector &operator>>=(unsigned int shift)
+	{
+		static_assert(std::is_integral_v<T>,
+			      "Vector::operator>>= requires an integer element type");
+		return apply(shift, [](T a, unsigned int b) { return a >> b; });
+	}
+
 	constexpr Vector min(const Vector &other) const
 	{
 		return apply(*this, other, [](T a, T b) { return std::min(a, b); });
@@ -260,8 +274,8 @@  private:
 		return result;
 	}
 
-	template<class BinaryOp>
-	static constexpr Vector apply(const Vector &lhs, T rhs, BinaryOp op)
+	template<class U, class BinaryOp>
+	static constexpr Vector apply(const Vector &lhs, U rhs, BinaryOp op)
 	{
 		Vector result;
 		std::transform(lhs.data_.begin(), lhs.data_.end(),
@@ -281,8 +295,8 @@  private:
 		return *this;
 	}
 
-	template<class BinaryOp>
-	Vector &apply(T scalar, BinaryOp op)
+	template<class U, class BinaryOp>
+	Vector &apply(U scalar, BinaryOp op)
 	{
 		std::for_each(data_.begin(), data_.end(),
 			      [&op, scalar](T &v) { v = op(v, scalar); });
diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp
index 86b9f9bb..94fbf61e 100644
--- a/src/libcamera/vector.cpp
+++ b/src/libcamera/vector.cpp
@@ -126,6 +126,13 @@  LOG_DEFINE_CATEGORY(Vector)
  * \return The element-wise division of this vector by \a scalar
  */
 
+/**
+ * \fn Vector::operator>>(unsigned int shift) const
+ * \brief Right-shift each element of this vector by \a shift bits
+ * \param[in] shift The shift amount
+ * \return A new vector with each element right-shifted by \a shift
+ */
+
 /**
  * \fn Vector::operator+=(Vector const &other)
  * \brief Add \a other element-wise to this vector
@@ -182,6 +189,13 @@  LOG_DEFINE_CATEGORY(Vector)
  * \return This vector
  */
 
+/**
+ * \fn Vector::operator>>=(unsigned int shift)
+ * \brief Right-shift each element of this vector by \a shift bits in place
+ * \param[in] shift The shift amount
+ * \return This vector
+ */
+
 /**
  * \fn Vector::min(const Vector &other) const
  * \brief Calculate the minimum of this vector and \a other element-wise
diff --git a/test/vector.cpp b/test/vector.cpp
index 4fae960d..4ff908e8 100644
--- a/test/vector.cpp
+++ b/test/vector.cpp
@@ -93,6 +93,11 @@  protected:
 		v2 /= 4.0;
 		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
 
+		Vector<int, 3> vi{{ 8, 16, 32 }};
+		ASSERT_EQ(vi >> 2, (Vector<int, 3>{{ 2, 4, 8 }}));
+		vi >>= 1;
+		ASSERT_EQ(vi, (Vector<int, 3>{{ 4, 8, 16 }}));
+
 		return TestPass;
 	}
 };