[06/10] ipa: ipu3: ToneMapping: Convert to use GammaAlgorithm
diff mbox series

Message ID 20260616-ipu3-libipa-rework-v1-6-d4448b54f1d8@ideasonboard.com
State Superseded
Headers show
Series
  • libipa: Re-work IPU3 IPA to use libipa algorithms
Related show

Commit Message

Dan Scally June 16, 2026, 6:41 a.m. UTC
Convert the IPU3 ToneMapping algorithm to use the new GammaAlgorithm
base class. This gives us configurable gamma via the tuning files and
at runtime using the Gamma control.

Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
---
 src/ipa/ipu3/algorithms/tone_mapping.cpp | 73 +++++++++++++++++---------------
 src/ipa/ipu3/algorithms/tone_mapping.h   |  9 +++-
 src/ipa/ipu3/ipa_context.cpp             | 24 +++++------
 src/ipa/ipu3/ipa_context.h               |  8 ++--
 4 files changed, 60 insertions(+), 54 deletions(-)

Comments

Kieran Bingham June 16, 2026, 6:39 p.m. UTC | #1
Quoting Daniel Scally (2026-06-16 07:41:40)
> Convert the IPU3 ToneMapping algorithm to use the new GammaAlgorithm
> base class. This gives us configurable gamma via the tuning files and
> at runtime using the Gamma control.

I'm curious that we're handling gamma in a tone mapping block, but
that's where it was so lets not change that in this patch anyway.

> 
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
>  src/ipa/ipu3/algorithms/tone_mapping.cpp | 73 +++++++++++++++++---------------
>  src/ipa/ipu3/algorithms/tone_mapping.h   |  9 +++-
>  src/ipa/ipu3/ipa_context.cpp             | 24 +++++------
>  src/ipa/ipu3/ipa_context.h               |  8 ++--
>  4 files changed, 60 insertions(+), 54 deletions(-)
> 
> diff --git a/src/ipa/ipu3/algorithms/tone_mapping.cpp b/src/ipa/ipu3/algorithms/tone_mapping.cpp
> index 160338c139448cc9a0bc1fe2400c335a96f68f73..2bc29bb9124dd8bd327ca3064b52c55637f56e7b 100644
> --- a/src/ipa/ipu3/algorithms/tone_mapping.cpp
> +++ b/src/ipa/ipu3/algorithms/tone_mapping.cpp
> @@ -10,6 +10,8 @@
>  #include <cmath>
>  #include <string.h>
>  
> +#include <libcamera/base/span.h>
> +
>  /**
>   * \file tone_mapping.h
>   */
> @@ -27,10 +29,17 @@ namespace ipa::ipu3::algorithms {
>   */
>  
>  ToneMapping::ToneMapping()
> -       : gamma_(1.0)
>  {
>  }
>  
> +/**
> + * \copydoc libcamera::ipa::Algorithm::init
> + */
> +int ToneMapping::init(IPAContext &context, const ValueNode &tuningData)
> +{
> +       return gammaAlgo_.init(context.ctrlMap, tuningData);
> +}
> +
>  /**
>   * \brief Configure the tone mapping given a configInfo
>   * \param[in] context The shared IPA context
> @@ -41,12 +50,21 @@ ToneMapping::ToneMapping()
>  int ToneMapping::configure(IPAContext &context,
>                            [[maybe_unused]] const IPAConfigInfo &configInfo)
>  {
> -       /* Initialise tone mapping gamma value. */
> -       context.activeState.toneMapping.gamma = 0.0;
> -
> +       gammaAlgo_.configure(context.activeState.gamma);
>         return 0;
>  }
>  
> +/**
> + * \copydoc libcamera::ipa::Algorithm::queueRequest
> + */
> +void ToneMapping::queueRequest(IPAContext &context, const uint32_t frame,
> +                              IPAFrameContext &frameContext,
> +                              const ControlList &controls)
> +{
> +       gammaAlgo_.queueRequest(context.activeState.gamma, frame,
> +                               frameContext.gamma, controls);
> +}
> +
>  /**
>   * \brief Fill in the parameter structure, and enable gamma control
>   * \param[in] context The shared IPA context
> @@ -59,14 +77,21 @@ int ToneMapping::configure(IPAContext &context,
>   */
>  void ToneMapping::prepare([[maybe_unused]] IPAContext &context,
>                           [[maybe_unused]] const uint32_t frame,
> -                         [[maybe_unused]] IPAFrameContext &frameContext,
> +                         IPAFrameContext &frameContext,
>                           ipu3_uapi_params *params)
>  {
> -       /* Copy the calculated LUT into the parameters buffer. */
> -       memcpy(params->acc_param.gamma.gc_lut.lut,
> -              context.activeState.toneMapping.gammaCorrection.lut,
> -              IPU3_UAPI_GAMMA_CORR_LUT_ENTRIES *
> -              sizeof(params->acc_param.gamma.gc_lut.lut[0]));
> +       if (!frameContext.gamma.update)
> +               return;
> +
> +       /*
> +        * Unfortunately necessary given the IPU3's gamma uAPI struct has the
> +        * __packed attribute.
> +        */
> +       uint16_t *lutData = reinterpret_cast<uint16_t *>(
> +               __builtin_assume_aligned(params->acc_param.gamma.gc_lut.lut, 16));
> +       Span<uint16_t> lut {lutData, kNumLutNodes};
> +
> +       gammaAlgo_.prepare(frameContext.gamma, lut);
>  
>         /* Enable the custom gamma table. */
>         params->use.acc_gamma = 1;
> @@ -84,33 +109,13 @@ void ToneMapping::prepare([[maybe_unused]] IPAContext &context,
>   * The tone mapping look up table is generated as an inverse power curve from
>   * our gamma setting.
>   */
> -void ToneMapping::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> -                         [[maybe_unused]] IPAFrameContext &frameContext,
> +void ToneMapping::process([[maybe_unused]] IPAContext &context,
> +                         [[maybe_unused]] const uint32_t frame,
> +                         IPAFrameContext &frameContext,
>                           [[maybe_unused]] const ipu3_uapi_stats_3a *stats,
>                           [[maybe_unused]] ControlList &metadata)
>  {
> -       /*
> -        * Hardcode gamma to 1.1 as a default for now.
> -        *
> -        * \todo Expose gamma control setting through the libcamera control API
> -        */
> -       gamma_ = 1.1;

Sooooooo good to drop all this.

> -
> -       if (context.activeState.toneMapping.gamma == gamma_)
> -               return;
> -
> -       struct ipu3_uapi_gamma_corr_lut &lut =
> -               context.activeState.toneMapping.gammaCorrection;
> -
> -       for (uint32_t i = 0; i < std::size(lut.lut); i++) {
> -               double j = static_cast<double>(i) / (std::size(lut.lut) - 1);
> -               double gamma = std::pow(j, 1.0 / gamma_);
> -
> -               /* The output value is expressed on 13 bits. */
> -               lut.lut[i] = gamma * 8191;
> -       }
> -
> -       context.activeState.toneMapping.gamma = gamma_;
> +       gammaAlgo_.process(frameContext.gamma, metadata);
>  }
>  
>  REGISTER_IPA_ALGORITHM(ToneMapping, "ToneMapping")
> diff --git a/src/ipa/ipu3/algorithms/tone_mapping.h b/src/ipa/ipu3/algorithms/tone_mapping.h
> index b2b380108e014b3d5ee7b93bcdd948dea4d2302d..db351a32b4383c4d607a2d6ee6a8fa3994a1d436 100644
> --- a/src/ipa/ipu3/algorithms/tone_mapping.h
> +++ b/src/ipa/ipu3/algorithms/tone_mapping.h
> @@ -7,6 +7,8 @@
>  
>  #pragma once
>  
> +#include <libipa/gamma.h>
> +
>  #include "algorithm.h"
>  
>  namespace libcamera {
> @@ -18,7 +20,11 @@ class ToneMapping : public Algorithm
>  public:
>         ToneMapping();
>  
> +       int init(IPAContext &context, const ValueNode &tuningData) override;
>         int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
> +       void queueRequest(IPAContext &context, const uint32_t frame,
> +                         IPAFrameContext &frameContext,
> +                         const ControlList &controls) override;
>         void prepare(IPAContext &context, const uint32_t frame,
>                      IPAFrameContext &frameContext, ipu3_uapi_params *params) override;
>         void process(IPAContext &context, const uint32_t frame,
> @@ -27,7 +33,8 @@ public:
>                      ControlList &metadata) override;
>  
>  private:
> -       double gamma_;
> +       static constexpr unsigned int kNumLutNodes = IPU3_UAPI_GAMMA_CORR_LUT_ENTRIES;
> +       GammaAlgorithm<kNumLutNodes, UQ<0, 13>> gammaAlgo_;

Well, that all looks pretty clean to me.

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

>  };
>  
>  } /* namespace ipa::ipu3::algorithms */
> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
> index dc43bc0877ed5c2e7287414e12667374f5ee1c80..7152d070d3ab1bc463fdaad437d5e1c1b87ce25c 100644
> --- a/src/ipa/ipu3/ipa_context.cpp
> +++ b/src/ipa/ipu3/ipa_context.cpp
> @@ -119,6 +119,11 @@ namespace libcamera::ipa::ipu3 {
>   * \brief Active colour Correction Matrix parameters for the IPA
>   */
>  
> +/**
> + * \var IPAActiveState::gamma
> + * \brief Active gamma correction parameters for the IPA
> + */
> +
>  /**
>   * \var IPASessionConfiguration::sensor
>   * \brief Sensor-specific configuration of the IPA
> @@ -154,20 +159,6 @@ namespace libcamera::ipa::ipu3 {
>   * The gain should be adapted to the sensor specific gain code before applying.
>   */
>  
> -/**
> - * \var IPAActiveState::toneMapping
> - * \brief Context for ToneMapping and Gamma control
> - *
> - * \var IPAActiveState::toneMapping.gamma
> - * \brief Gamma value for the LUT
> - *
> - * \var IPAActiveState::toneMapping.gammaCorrection
> - * \brief Per-pixel tone mapping implemented as a LUT
> - *
> - * The LUT structure is defined by the IPU3 kernel interface. See
> - * <linux/intel-ipu3.h> struct ipu3_uapi_gamma_corr_lut for further details.
> - */
> -
>  /**
>   * \struct IPAFrameContext
>   * \brief IPU3-specific FrameContext
> @@ -192,4 +183,9 @@ namespace libcamera::ipa::ipu3 {
>   * \brief Per-frame colour Correction Matrix parameters for the IPA
>   */
>  
> +/**
> + * \var IPAFrameContext::gamma
> + * \brief Per-frame gamma correction parameters for the IPA
> + */
> +
>  } /* namespace libcamera::ipa::ipu3 */
> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
> index be626d30d966b1bdaa322e5154f95f745f799976..1eaaac82da0e3ad5bed0749c39d9dad8c585cab0 100644
> --- a/src/ipa/ipu3/ipa_context.h
> +++ b/src/ipa/ipu3/ipa_context.h
> @@ -18,6 +18,7 @@
>  #include <libipa/awb.h>
>  #include <libipa/ccm.h>
>  #include <libipa/fc_queue.h>
> +#include <libipa/gamma.h>
>  
>  namespace libcamera {
>  
> @@ -66,11 +67,7 @@ struct IPAActiveState {
>  
>         ipa::awb::ActiveState awb;
>         ipa::ccm::ActiveState ccm;
> -
> -       struct {
> -               double gamma;
> -               struct ipu3_uapi_gamma_corr_lut gammaCorrection;
> -       } toneMapping;
> +       ipa::gamma::ActiveState gamma;
>  };
>  
>  struct IPAFrameContext : public FrameContext {
> @@ -81,6 +78,7 @@ struct IPAFrameContext : public FrameContext {
>  
>         ipa::awb::FrameContext awb;
>         ipa::ccm::FrameContext ccm;
> +       ipa::gamma::FrameContext gamma;
>  };
>  
>  struct IPAContext {
> 
> -- 
> 2.43.0
>
Jacopo Mondi June 17, 2026, 9:49 a.m. UTC | #2
Hi Dan

On Tue, Jun 16, 2026 at 07:41:40AM +0100, Daniel Scally wrote:
> Convert the IPU3 ToneMapping algorithm to use the new GammaAlgorithm
> base class. This gives us configurable gamma via the tuning files and
> at runtime using the Gamma control.
>
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
>  src/ipa/ipu3/algorithms/tone_mapping.cpp | 73 +++++++++++++++++---------------
>  src/ipa/ipu3/algorithms/tone_mapping.h   |  9 +++-
>  src/ipa/ipu3/ipa_context.cpp             | 24 +++++------
>  src/ipa/ipu3/ipa_context.h               |  8 ++--
>  4 files changed, 60 insertions(+), 54 deletions(-)
>
> diff --git a/src/ipa/ipu3/algorithms/tone_mapping.cpp b/src/ipa/ipu3/algorithms/tone_mapping.cpp
> index 160338c139448cc9a0bc1fe2400c335a96f68f73..2bc29bb9124dd8bd327ca3064b52c55637f56e7b 100644
> --- a/src/ipa/ipu3/algorithms/tone_mapping.cpp
> +++ b/src/ipa/ipu3/algorithms/tone_mapping.cpp
> @@ -10,6 +10,8 @@
>  #include <cmath>
>  #include <string.h>

Please check if headers inclusion has to be adjusted here and in the
header

Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

>
> +#include <libcamera/base/span.h>
> +
>  /**
>   * \file tone_mapping.h
>   */
> @@ -27,10 +29,17 @@ namespace ipa::ipu3::algorithms {
>   */
>
>  ToneMapping::ToneMapping()
> -	: gamma_(1.0)
>  {
>  }
>
> +/**
> + * \copydoc libcamera::ipa::Algorithm::init
> + */
> +int ToneMapping::init(IPAContext &context, const ValueNode &tuningData)
> +{
> +	return gammaAlgo_.init(context.ctrlMap, tuningData);
> +}
> +
>  /**
>   * \brief Configure the tone mapping given a configInfo
>   * \param[in] context The shared IPA context
> @@ -41,12 +50,21 @@ ToneMapping::ToneMapping()
>  int ToneMapping::configure(IPAContext &context,
>  			   [[maybe_unused]] const IPAConfigInfo &configInfo)
>  {
> -	/* Initialise tone mapping gamma value. */
> -	context.activeState.toneMapping.gamma = 0.0;
> -
> +	gammaAlgo_.configure(context.activeState.gamma);
>  	return 0;
>  }
>
> +/**
> + * \copydoc libcamera::ipa::Algorithm::queueRequest
> + */
> +void ToneMapping::queueRequest(IPAContext &context, const uint32_t frame,
> +			       IPAFrameContext &frameContext,
> +			       const ControlList &controls)
> +{
> +	gammaAlgo_.queueRequest(context.activeState.gamma, frame,
> +				frameContext.gamma, controls);
> +}
> +
>  /**
>   * \brief Fill in the parameter structure, and enable gamma control
>   * \param[in] context The shared IPA context
> @@ -59,14 +77,21 @@ int ToneMapping::configure(IPAContext &context,
>   */
>  void ToneMapping::prepare([[maybe_unused]] IPAContext &context,
>  			  [[maybe_unused]] const uint32_t frame,
> -			  [[maybe_unused]] IPAFrameContext &frameContext,
> +			  IPAFrameContext &frameContext,
>  			  ipu3_uapi_params *params)
>  {
> -	/* Copy the calculated LUT into the parameters buffer. */
> -	memcpy(params->acc_param.gamma.gc_lut.lut,
> -	       context.activeState.toneMapping.gammaCorrection.lut,
> -	       IPU3_UAPI_GAMMA_CORR_LUT_ENTRIES *
> -	       sizeof(params->acc_param.gamma.gc_lut.lut[0]));
> +	if (!frameContext.gamma.update)
> +		return;
> +
> +	/*
> +	 * Unfortunately necessary given the IPU3's gamma uAPI struct has the
> +	 * __packed attribute.
> +	 */
> +	uint16_t *lutData = reinterpret_cast<uint16_t *>(
> +		__builtin_assume_aligned(params->acc_param.gamma.gc_lut.lut, 16));
> +	Span<uint16_t> lut {lutData, kNumLutNodes};
> +
> +	gammaAlgo_.prepare(frameContext.gamma, lut);
>
>  	/* Enable the custom gamma table. */
>  	params->use.acc_gamma = 1;
> @@ -84,33 +109,13 @@ void ToneMapping::prepare([[maybe_unused]] IPAContext &context,
>   * The tone mapping look up table is generated as an inverse power curve from
>   * our gamma setting.
>   */
> -void ToneMapping::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> -			  [[maybe_unused]] IPAFrameContext &frameContext,
> +void ToneMapping::process([[maybe_unused]] IPAContext &context,
> +			  [[maybe_unused]] const uint32_t frame,
> +			  IPAFrameContext &frameContext,
>  			  [[maybe_unused]] const ipu3_uapi_stats_3a *stats,
>  			  [[maybe_unused]] ControlList &metadata)
>  {
> -	/*
> -	 * Hardcode gamma to 1.1 as a default for now.
> -	 *
> -	 * \todo Expose gamma control setting through the libcamera control API
> -	 */
> -	gamma_ = 1.1;
> -
> -	if (context.activeState.toneMapping.gamma == gamma_)
> -		return;
> -
> -	struct ipu3_uapi_gamma_corr_lut &lut =
> -		context.activeState.toneMapping.gammaCorrection;
> -
> -	for (uint32_t i = 0; i < std::size(lut.lut); i++) {
> -		double j = static_cast<double>(i) / (std::size(lut.lut) - 1);
> -		double gamma = std::pow(j, 1.0 / gamma_);
> -
> -		/* The output value is expressed on 13 bits. */
> -		lut.lut[i] = gamma * 8191;
> -	}
> -
> -	context.activeState.toneMapping.gamma = gamma_;
> +	gammaAlgo_.process(frameContext.gamma, metadata);
>  }
>
>  REGISTER_IPA_ALGORITHM(ToneMapping, "ToneMapping")
> diff --git a/src/ipa/ipu3/algorithms/tone_mapping.h b/src/ipa/ipu3/algorithms/tone_mapping.h
> index b2b380108e014b3d5ee7b93bcdd948dea4d2302d..db351a32b4383c4d607a2d6ee6a8fa3994a1d436 100644
> --- a/src/ipa/ipu3/algorithms/tone_mapping.h
> +++ b/src/ipa/ipu3/algorithms/tone_mapping.h
> @@ -7,6 +7,8 @@
>
>  #pragma once
>
> +#include <libipa/gamma.h>
> +
>  #include "algorithm.h"
>
>  namespace libcamera {
> @@ -18,7 +20,11 @@ class ToneMapping : public Algorithm
>  public:
>  	ToneMapping();
>
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>  	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
> +	void queueRequest(IPAContext &context, const uint32_t frame,
> +			  IPAFrameContext &frameContext,
> +			  const ControlList &controls) override;
>  	void prepare(IPAContext &context, const uint32_t frame,
>  		     IPAFrameContext &frameContext, ipu3_uapi_params *params) override;
>  	void process(IPAContext &context, const uint32_t frame,
> @@ -27,7 +33,8 @@ public:
>  		     ControlList &metadata) override;
>
>  private:
> -	double gamma_;
> +	static constexpr unsigned int kNumLutNodes = IPU3_UAPI_GAMMA_CORR_LUT_ENTRIES;
> +	GammaAlgorithm<kNumLutNodes, UQ<0, 13>> gammaAlgo_;
>  };
>
>  } /* namespace ipa::ipu3::algorithms */
> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
> index dc43bc0877ed5c2e7287414e12667374f5ee1c80..7152d070d3ab1bc463fdaad437d5e1c1b87ce25c 100644
> --- a/src/ipa/ipu3/ipa_context.cpp
> +++ b/src/ipa/ipu3/ipa_context.cpp
> @@ -119,6 +119,11 @@ namespace libcamera::ipa::ipu3 {
>   * \brief Active colour Correction Matrix parameters for the IPA
>   */
>
> +/**
> + * \var IPAActiveState::gamma
> + * \brief Active gamma correction parameters for the IPA
> + */
> +
>  /**
>   * \var IPASessionConfiguration::sensor
>   * \brief Sensor-specific configuration of the IPA
> @@ -154,20 +159,6 @@ namespace libcamera::ipa::ipu3 {
>   * The gain should be adapted to the sensor specific gain code before applying.
>   */
>
> -/**
> - * \var IPAActiveState::toneMapping
> - * \brief Context for ToneMapping and Gamma control
> - *
> - * \var IPAActiveState::toneMapping.gamma
> - * \brief Gamma value for the LUT
> - *
> - * \var IPAActiveState::toneMapping.gammaCorrection
> - * \brief Per-pixel tone mapping implemented as a LUT
> - *
> - * The LUT structure is defined by the IPU3 kernel interface. See
> - * <linux/intel-ipu3.h> struct ipu3_uapi_gamma_corr_lut for further details.
> - */
> -
>  /**
>   * \struct IPAFrameContext
>   * \brief IPU3-specific FrameContext
> @@ -192,4 +183,9 @@ namespace libcamera::ipa::ipu3 {
>   * \brief Per-frame colour Correction Matrix parameters for the IPA
>   */
>
> +/**
> + * \var IPAFrameContext::gamma
> + * \brief Per-frame gamma correction parameters for the IPA
> + */
> +
>  } /* namespace libcamera::ipa::ipu3 */
> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
> index be626d30d966b1bdaa322e5154f95f745f799976..1eaaac82da0e3ad5bed0749c39d9dad8c585cab0 100644
> --- a/src/ipa/ipu3/ipa_context.h
> +++ b/src/ipa/ipu3/ipa_context.h
> @@ -18,6 +18,7 @@
>  #include <libipa/awb.h>
>  #include <libipa/ccm.h>
>  #include <libipa/fc_queue.h>
> +#include <libipa/gamma.h>
>
>  namespace libcamera {
>
> @@ -66,11 +67,7 @@ struct IPAActiveState {
>
>  	ipa::awb::ActiveState awb;
>  	ipa::ccm::ActiveState ccm;
> -
> -	struct {
> -		double gamma;
> -		struct ipu3_uapi_gamma_corr_lut gammaCorrection;
> -	} toneMapping;
> +	ipa::gamma::ActiveState gamma;
>  };
>
>  struct IPAFrameContext : public FrameContext {
> @@ -81,6 +78,7 @@ struct IPAFrameContext : public FrameContext {
>
>  	ipa::awb::FrameContext awb;
>  	ipa::ccm::FrameContext ccm;
> +	ipa::gamma::FrameContext gamma;
>  };
>
>  struct IPAContext {
>
> --
> 2.43.0
>

Patch
diff mbox series

diff --git a/src/ipa/ipu3/algorithms/tone_mapping.cpp b/src/ipa/ipu3/algorithms/tone_mapping.cpp
index 160338c139448cc9a0bc1fe2400c335a96f68f73..2bc29bb9124dd8bd327ca3064b52c55637f56e7b 100644
--- a/src/ipa/ipu3/algorithms/tone_mapping.cpp
+++ b/src/ipa/ipu3/algorithms/tone_mapping.cpp
@@ -10,6 +10,8 @@ 
 #include <cmath>
 #include <string.h>
 
+#include <libcamera/base/span.h>
+
 /**
  * \file tone_mapping.h
  */
@@ -27,10 +29,17 @@  namespace ipa::ipu3::algorithms {
  */
 
 ToneMapping::ToneMapping()
-	: gamma_(1.0)
 {
 }
 
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int ToneMapping::init(IPAContext &context, const ValueNode &tuningData)
+{
+	return gammaAlgo_.init(context.ctrlMap, tuningData);
+}
+
 /**
  * \brief Configure the tone mapping given a configInfo
  * \param[in] context The shared IPA context
@@ -41,12 +50,21 @@  ToneMapping::ToneMapping()
 int ToneMapping::configure(IPAContext &context,
 			   [[maybe_unused]] const IPAConfigInfo &configInfo)
 {
-	/* Initialise tone mapping gamma value. */
-	context.activeState.toneMapping.gamma = 0.0;
-
+	gammaAlgo_.configure(context.activeState.gamma);
 	return 0;
 }
 
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void ToneMapping::queueRequest(IPAContext &context, const uint32_t frame,
+			       IPAFrameContext &frameContext,
+			       const ControlList &controls)
+{
+	gammaAlgo_.queueRequest(context.activeState.gamma, frame,
+				frameContext.gamma, controls);
+}
+
 /**
  * \brief Fill in the parameter structure, and enable gamma control
  * \param[in] context The shared IPA context
@@ -59,14 +77,21 @@  int ToneMapping::configure(IPAContext &context,
  */
 void ToneMapping::prepare([[maybe_unused]] IPAContext &context,
 			  [[maybe_unused]] const uint32_t frame,
-			  [[maybe_unused]] IPAFrameContext &frameContext,
+			  IPAFrameContext &frameContext,
 			  ipu3_uapi_params *params)
 {
-	/* Copy the calculated LUT into the parameters buffer. */
-	memcpy(params->acc_param.gamma.gc_lut.lut,
-	       context.activeState.toneMapping.gammaCorrection.lut,
-	       IPU3_UAPI_GAMMA_CORR_LUT_ENTRIES *
-	       sizeof(params->acc_param.gamma.gc_lut.lut[0]));
+	if (!frameContext.gamma.update)
+		return;
+
+	/*
+	 * Unfortunately necessary given the IPU3's gamma uAPI struct has the
+	 * __packed attribute.
+	 */
+	uint16_t *lutData = reinterpret_cast<uint16_t *>(
+		__builtin_assume_aligned(params->acc_param.gamma.gc_lut.lut, 16));
+	Span<uint16_t> lut {lutData, kNumLutNodes};
+
+	gammaAlgo_.prepare(frameContext.gamma, lut);
 
 	/* Enable the custom gamma table. */
 	params->use.acc_gamma = 1;
@@ -84,33 +109,13 @@  void ToneMapping::prepare([[maybe_unused]] IPAContext &context,
  * The tone mapping look up table is generated as an inverse power curve from
  * our gamma setting.
  */
-void ToneMapping::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
-			  [[maybe_unused]] IPAFrameContext &frameContext,
+void ToneMapping::process([[maybe_unused]] IPAContext &context,
+			  [[maybe_unused]] const uint32_t frame,
+			  IPAFrameContext &frameContext,
 			  [[maybe_unused]] const ipu3_uapi_stats_3a *stats,
 			  [[maybe_unused]] ControlList &metadata)
 {
-	/*
-	 * Hardcode gamma to 1.1 as a default for now.
-	 *
-	 * \todo Expose gamma control setting through the libcamera control API
-	 */
-	gamma_ = 1.1;
-
-	if (context.activeState.toneMapping.gamma == gamma_)
-		return;
-
-	struct ipu3_uapi_gamma_corr_lut &lut =
-		context.activeState.toneMapping.gammaCorrection;
-
-	for (uint32_t i = 0; i < std::size(lut.lut); i++) {
-		double j = static_cast<double>(i) / (std::size(lut.lut) - 1);
-		double gamma = std::pow(j, 1.0 / gamma_);
-
-		/* The output value is expressed on 13 bits. */
-		lut.lut[i] = gamma * 8191;
-	}
-
-	context.activeState.toneMapping.gamma = gamma_;
+	gammaAlgo_.process(frameContext.gamma, metadata);
 }
 
 REGISTER_IPA_ALGORITHM(ToneMapping, "ToneMapping")
diff --git a/src/ipa/ipu3/algorithms/tone_mapping.h b/src/ipa/ipu3/algorithms/tone_mapping.h
index b2b380108e014b3d5ee7b93bcdd948dea4d2302d..db351a32b4383c4d607a2d6ee6a8fa3994a1d436 100644
--- a/src/ipa/ipu3/algorithms/tone_mapping.h
+++ b/src/ipa/ipu3/algorithms/tone_mapping.h
@@ -7,6 +7,8 @@ 
 
 #pragma once
 
+#include <libipa/gamma.h>
+
 #include "algorithm.h"
 
 namespace libcamera {
@@ -18,7 +20,11 @@  class ToneMapping : public Algorithm
 public:
 	ToneMapping();
 
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
+	void queueRequest(IPAContext &context, const uint32_t frame,
+			  IPAFrameContext &frameContext,
+			  const ControlList &controls) override;
 	void prepare(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext, ipu3_uapi_params *params) override;
 	void process(IPAContext &context, const uint32_t frame,
@@ -27,7 +33,8 @@  public:
 		     ControlList &metadata) override;
 
 private:
-	double gamma_;
+	static constexpr unsigned int kNumLutNodes = IPU3_UAPI_GAMMA_CORR_LUT_ENTRIES;
+	GammaAlgorithm<kNumLutNodes, UQ<0, 13>> gammaAlgo_;
 };
 
 } /* namespace ipa::ipu3::algorithms */
diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
index dc43bc0877ed5c2e7287414e12667374f5ee1c80..7152d070d3ab1bc463fdaad437d5e1c1b87ce25c 100644
--- a/src/ipa/ipu3/ipa_context.cpp
+++ b/src/ipa/ipu3/ipa_context.cpp
@@ -119,6 +119,11 @@  namespace libcamera::ipa::ipu3 {
  * \brief Active colour Correction Matrix parameters for the IPA
  */
 
+/**
+ * \var IPAActiveState::gamma
+ * \brief Active gamma correction parameters for the IPA
+ */
+
 /**
  * \var IPASessionConfiguration::sensor
  * \brief Sensor-specific configuration of the IPA
@@ -154,20 +159,6 @@  namespace libcamera::ipa::ipu3 {
  * The gain should be adapted to the sensor specific gain code before applying.
  */
 
-/**
- * \var IPAActiveState::toneMapping
- * \brief Context for ToneMapping and Gamma control
- *
- * \var IPAActiveState::toneMapping.gamma
- * \brief Gamma value for the LUT
- *
- * \var IPAActiveState::toneMapping.gammaCorrection
- * \brief Per-pixel tone mapping implemented as a LUT
- *
- * The LUT structure is defined by the IPU3 kernel interface. See
- * <linux/intel-ipu3.h> struct ipu3_uapi_gamma_corr_lut for further details.
- */
-
 /**
  * \struct IPAFrameContext
  * \brief IPU3-specific FrameContext
@@ -192,4 +183,9 @@  namespace libcamera::ipa::ipu3 {
  * \brief Per-frame colour Correction Matrix parameters for the IPA
  */
 
+/**
+ * \var IPAFrameContext::gamma
+ * \brief Per-frame gamma correction parameters for the IPA
+ */
+
 } /* namespace libcamera::ipa::ipu3 */
diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
index be626d30d966b1bdaa322e5154f95f745f799976..1eaaac82da0e3ad5bed0749c39d9dad8c585cab0 100644
--- a/src/ipa/ipu3/ipa_context.h
+++ b/src/ipa/ipu3/ipa_context.h
@@ -18,6 +18,7 @@ 
 #include <libipa/awb.h>
 #include <libipa/ccm.h>
 #include <libipa/fc_queue.h>
+#include <libipa/gamma.h>
 
 namespace libcamera {
 
@@ -66,11 +67,7 @@  struct IPAActiveState {
 
 	ipa::awb::ActiveState awb;
 	ipa::ccm::ActiveState ccm;
-
-	struct {
-		double gamma;
-		struct ipu3_uapi_gamma_corr_lut gammaCorrection;
-	} toneMapping;
+	ipa::gamma::ActiveState gamma;
 };
 
 struct IPAFrameContext : public FrameContext {
@@ -81,6 +78,7 @@  struct IPAFrameContext : public FrameContext {
 
 	ipa::awb::FrameContext awb;
 	ipa::ccm::FrameContext ccm;
+	ipa::gamma::FrameContext gamma;
 };
 
 struct IPAContext {