[RFC,4/4] libcamera: swstats_cpu: Add processFrame() method
diff mbox series

Message ID 20241009200110.275544-5-hdegoede@redhat.com
State Superseded
Headers show
Series
  • libcamera: swstats_cpu: Add processFrame() method
Related show

Commit Message

Hans de Goede Oct. 9, 2024, 8:01 p.m. UTC
Add a method to the SwstatsCpu class to process a whole Framebuffer in
one go, rather then line by line. This is useful for gathering stats
when debayering is not necessary or is not done on the CPU.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 src/libcamera/software_isp/swstats_cpu.cpp | 72 ++++++++++++++++++++++
 src/libcamera/software_isp/swstats_cpu.h   | 10 +++
 2 files changed, 82 insertions(+)

Comments

Kieran Bingham Oct. 9, 2024, 11:15 p.m. UTC | #1
Quoting Hans de Goede (2024-10-09 21:01:10)
> Add a method to the SwstatsCpu class to process a whole Framebuffer in
> one go, rather then line by line. This is useful for gathering stats
> when debayering is not necessary or is not done on the CPU.

I can't see how this is used yet. Is it just something that you would be
able to directly set up and call on a frame explicitly if desired?

I expect that's possible as it's RFC ... So I think I'll leave this one
to other soft-isp devs... But I can understand that it could be useful
for future development.

--
Kieran

> 
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> ---
>  src/libcamera/software_isp/swstats_cpu.cpp | 72 ++++++++++++++++++++++
>  src/libcamera/software_isp/swstats_cpu.h   | 10 +++
>  2 files changed, 82 insertions(+)
> 
> diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
> index 5e4246a9..117147c2 100644
> --- a/src/libcamera/software_isp/swstats_cpu.cpp
> +++ b/src/libcamera/software_isp/swstats_cpu.cpp
> @@ -16,6 +16,7 @@
>  #include <libcamera/stream.h>
>  
>  #include "libcamera/internal/bayer_format.h"
> +#include "libcamera/internal/mapped_framebuffer.h"
>  
>  namespace libcamera {
>  
> @@ -363,8 +364,11 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
>         BayerFormat bayerFormat =
>                 BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
>  
> +       stride_ = inputCfg.stride;
> +
>         if (bayerFormat.packing == BayerFormat::Packing::None &&
>             setupStandardBayerOrder(bayerFormat.order) == 0) {
> +               bpp_ = (bayerFormat.bitDepth + 7) & ~7;
>                 switch (bayerFormat.bitDepth) {
>                 case 8:
>                         stats0_ = &SwStatsCpu::statsBGGR8Line0;
> @@ -385,6 +389,7 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
>                 /* Skip every 3th and 4th line, sample every other 2x2 block */
>                 ySkipMask_ = 0x02;
>                 xShift_ = 0;
> +               bpp_ = 10;
>  
>                 switch (bayerFormat.order) {
>                 case BayerFormat::BGGR:
> @@ -425,4 +430,71 @@ void SwStatsCpu::setWindow(const Rectangle &window)
>         window_.height &= ~(patternSize_.height - 1);
>  }
>  
> +void SwStatsCpu::processFrame2(const uint8_t *src)
> +{
> +       unsigned int yEnd = window_.y + window_.height;
> +       const uint8_t *linePointers[3];
> +
> +       /* Adjust src to top left corner of the window */
> +       src += window_.y * stride_ + window_.x * bpp_ / 8;
> +
> +       for (unsigned int y = window_.y; y < yEnd; y += 2) {
> +               if (y & ySkipMask_) {
> +                       src += stride_ * 2;
> +                       continue;
> +               }
> +
> +               /* linePointers[0] is not used by any stats0_ functions */
> +               linePointers[1] = src;
> +               linePointers[2] = src + stride_;
> +               (this->*stats0_)(linePointers);
> +               src += stride_ * 2;
> +       }
> +}
> +
> +void SwStatsCpu::processFrame4(const uint8_t *src)
> +{
> +       const unsigned int yEnd = window_.y + window_.height;
> +       const uint8_t *linePointers[4];
> +
> +       /* Adjust src to top left corner of the window */
> +       src += window_.y * stride_ + window_.x * bpp_ / 8;
> +
> +       for (unsigned int y = window_.y; y < yEnd; y += 4) {
> +               if (y & ySkipMask_) {
> +                       src += stride_ * 4;
> +                       continue;
> +               }
> +
> +               /* linePointers[0] and [1] are not used by 4 line pattern stats0_ functions */
> +               linePointers[2] = src;
> +               linePointers[3] = src + stride_;
> +               (this->*stats0_)(linePointers);
> +               src += stride_ * 2;
> +
> +               linePointers[2] = src;
> +               linePointers[3] = src + stride_;
> +               (this->*stats2_)(linePointers);
> +               src += stride_ * 2;
> +       }
> +}
> +
> +void SwStatsCpu::processFrame(FrameBuffer *input)
> +{
> +       bench_.startFrame();
> +
> +       MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);
> +       if (!in.isValid()) {
> +               LOG(SwStatsCpu, Error) << "mmap-ing buffer(s) failed";
> +               return;
> +       }
> +
> +       if (patternSize_.height == 2)
> +               processFrame2(in.planes()[0].data());
> +       else
> +               processFrame4(in.planes()[0].data());
> +
> +       bench_.finishFrame();
> +}
> +
>  } /* namespace libcamera */
> diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
> index 26a2f462..bb178a04 100644
> --- a/src/libcamera/software_isp/swstats_cpu.h
> +++ b/src/libcamera/software_isp/swstats_cpu.h
> @@ -18,9 +18,12 @@
>  #include <libcamera/geometry.h>
>  
>  #include "libcamera/internal/bayer_format.h"
> +#include "libcamera/internal/framebuffer.h"
>  #include "libcamera/internal/shared_mem_object.h"
>  #include "libcamera/internal/software_isp/swisp_stats.h"
>  
> +#include "benchmark.h"
> +
>  namespace libcamera {
>  
>  class PixelFormat;
> @@ -42,6 +45,7 @@ public:
>         void setWindow(const Rectangle &window);
>         void startFrame();
>         void finishFrame(uint32_t frame, uint32_t bufferId);
> +       void processFrame(FrameBuffer *input);
>  
>         void processLine0(unsigned int y, const uint8_t *src[])
>         {
> @@ -77,6 +81,9 @@ private:
>         void statsBGGR10PLine0(const uint8_t *src[]);
>         void statsGBRG10PLine0(const uint8_t *src[]);
>  
> +       void processFrame2(const uint8_t *src);
> +       void processFrame4(const uint8_t *src);
> +
>         /* Variables set by configure(), used every line */
>         statsProcessFn stats0_;
>         statsProcessFn stats2_;
> @@ -89,9 +96,12 @@ private:
>         Size patternSize_;
>  
>         unsigned int xShift_;
> +       unsigned int bpp_; /* Memory used per pixel, not precision */
> +       unsigned int stride_;
>  
>         SharedMemObject<SwIspStats> sharedStats_;
>         SwIspStats stats_;
> +       Benchmark bench_;
>  };
>  
>  } /* namespace libcamera */
> -- 
> 2.46.2
>
Hans de Goede Oct. 10, 2024, 12:21 p.m. UTC | #2
Hi,

On 10-Oct-24 1:15 AM, Kieran Bingham wrote:
> Quoting Hans de Goede (2024-10-09 21:01:10)
>> Add a method to the SwstatsCpu class to process a whole Framebuffer in
>> one go, rather then line by line. This is useful for gathering stats
>> when debayering is not necessary or is not done on the CPU.
> 
> I can't see how this is used yet. Is it just something that you would be
> able to directly set up and call on a frame explicitly if desired?

Yes that is the idea. It could be e.g. used to do a raw-bayer pipeline
while still having 3A in place.

> I expect that's possible as it's RFC ... So I think I'll leave this one
> to other soft-isp devs... But I can understand that it could be useful
> for future development.

Regards,

Hans




>> ---
>>  src/libcamera/software_isp/swstats_cpu.cpp | 72 ++++++++++++++++++++++
>>  src/libcamera/software_isp/swstats_cpu.h   | 10 +++
>>  2 files changed, 82 insertions(+)
>>
>> diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
>> index 5e4246a9..117147c2 100644
>> --- a/src/libcamera/software_isp/swstats_cpu.cpp
>> +++ b/src/libcamera/software_isp/swstats_cpu.cpp
>> @@ -16,6 +16,7 @@
>>  #include <libcamera/stream.h>
>>  
>>  #include "libcamera/internal/bayer_format.h"
>> +#include "libcamera/internal/mapped_framebuffer.h"
>>  
>>  namespace libcamera {
>>  
>> @@ -363,8 +364,11 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
>>         BayerFormat bayerFormat =
>>                 BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
>>  
>> +       stride_ = inputCfg.stride;
>> +
>>         if (bayerFormat.packing == BayerFormat::Packing::None &&
>>             setupStandardBayerOrder(bayerFormat.order) == 0) {
>> +               bpp_ = (bayerFormat.bitDepth + 7) & ~7;
>>                 switch (bayerFormat.bitDepth) {
>>                 case 8:
>>                         stats0_ = &SwStatsCpu::statsBGGR8Line0;
>> @@ -385,6 +389,7 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
>>                 /* Skip every 3th and 4th line, sample every other 2x2 block */
>>                 ySkipMask_ = 0x02;
>>                 xShift_ = 0;
>> +               bpp_ = 10;
>>  
>>                 switch (bayerFormat.order) {
>>                 case BayerFormat::BGGR:
>> @@ -425,4 +430,71 @@ void SwStatsCpu::setWindow(const Rectangle &window)
>>         window_.height &= ~(patternSize_.height - 1);
>>  }
>>  
>> +void SwStatsCpu::processFrame2(const uint8_t *src)
>> +{
>> +       unsigned int yEnd = window_.y + window_.height;
>> +       const uint8_t *linePointers[3];
>> +
>> +       /* Adjust src to top left corner of the window */
>> +       src += window_.y * stride_ + window_.x * bpp_ / 8;
>> +
>> +       for (unsigned int y = window_.y; y < yEnd; y += 2) {
>> +               if (y & ySkipMask_) {
>> +                       src += stride_ * 2;
>> +                       continue;
>> +               }
>> +
>> +               /* linePointers[0] is not used by any stats0_ functions */
>> +               linePointers[1] = src;
>> +               linePointers[2] = src + stride_;
>> +               (this->*stats0_)(linePointers);
>> +               src += stride_ * 2;
>> +       }
>> +}
>> +
>> +void SwStatsCpu::processFrame4(const uint8_t *src)
>> +{
>> +       const unsigned int yEnd = window_.y + window_.height;
>> +       const uint8_t *linePointers[4];
>> +
>> +       /* Adjust src to top left corner of the window */
>> +       src += window_.y * stride_ + window_.x * bpp_ / 8;
>> +
>> +       for (unsigned int y = window_.y; y < yEnd; y += 4) {
>> +               if (y & ySkipMask_) {
>> +                       src += stride_ * 4;
>> +                       continue;
>> +               }
>> +
>> +               /* linePointers[0] and [1] are not used by 4 line pattern stats0_ functions */
>> +               linePointers[2] = src;
>> +               linePointers[3] = src + stride_;
>> +               (this->*stats0_)(linePointers);
>> +               src += stride_ * 2;
>> +
>> +               linePointers[2] = src;
>> +               linePointers[3] = src + stride_;
>> +               (this->*stats2_)(linePointers);
>> +               src += stride_ * 2;
>> +       }
>> +}
>> +
>> +void SwStatsCpu::processFrame(FrameBuffer *input)
>> +{
>> +       bench_.startFrame();
>> +
>> +       MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);
>> +       if (!in.isValid()) {
>> +               LOG(SwStatsCpu, Error) << "mmap-ing buffer(s) failed";
>> +               return;
>> +       }
>> +
>> +       if (patternSize_.height == 2)
>> +               processFrame2(in.planes()[0].data());
>> +       else
>> +               processFrame4(in.planes()[0].data());
>> +
>> +       bench_.finishFrame();
>> +}
>> +
>>  } /* namespace libcamera */
>> diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
>> index 26a2f462..bb178a04 100644
>> --- a/src/libcamera/software_isp/swstats_cpu.h
>> +++ b/src/libcamera/software_isp/swstats_cpu.h
>> @@ -18,9 +18,12 @@
>>  #include <libcamera/geometry.h>
>>  
>>  #include "libcamera/internal/bayer_format.h"
>> +#include "libcamera/internal/framebuffer.h"
>>  #include "libcamera/internal/shared_mem_object.h"
>>  #include "libcamera/internal/software_isp/swisp_stats.h"
>>  
>> +#include "benchmark.h"
>> +
>>  namespace libcamera {
>>  
>>  class PixelFormat;
>> @@ -42,6 +45,7 @@ public:
>>         void setWindow(const Rectangle &window);
>>         void startFrame();
>>         void finishFrame(uint32_t frame, uint32_t bufferId);
>> +       void processFrame(FrameBuffer *input);
>>  
>>         void processLine0(unsigned int y, const uint8_t *src[])
>>         {
>> @@ -77,6 +81,9 @@ private:
>>         void statsBGGR10PLine0(const uint8_t *src[]);
>>         void statsGBRG10PLine0(const uint8_t *src[]);
>>  
>> +       void processFrame2(const uint8_t *src);
>> +       void processFrame4(const uint8_t *src);
>> +
>>         /* Variables set by configure(), used every line */
>>         statsProcessFn stats0_;
>>         statsProcessFn stats2_;
>> @@ -89,9 +96,12 @@ private:
>>         Size patternSize_;
>>  
>>         unsigned int xShift_;
>> +       unsigned int bpp_; /* Memory used per pixel, not precision */
>> +       unsigned int stride_;
>>  
>>         SharedMemObject<SwIspStats> sharedStats_;
>>         SwIspStats stats_;
>> +       Benchmark bench_;
>>  };
>>  
>>  } /* namespace libcamera */
>> -- 
>> 2.46.2
>>
>
Milan Zamazal Oct. 16, 2024, 3:10 p.m. UTC | #3
Hans de Goede <hdegoede@redhat.com> writes:

> Add a method to the SwstatsCpu class to process a whole Framebuffer in
> one go, rather then line by line. This is useful for gathering stats
> when debayering is not necessary or is not done on the CPU.

Makes sense.  Although the actual use may be trickier, let's see.

Reviewed-by: Milan Zamazal <mzamazal@redhat.com>

> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
>
> ---
>  src/libcamera/software_isp/swstats_cpu.cpp | 72 ++++++++++++++++++++++
>  src/libcamera/software_isp/swstats_cpu.h   | 10 +++
>  2 files changed, 82 insertions(+)
>
> diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
> index 5e4246a9..117147c2 100644
> --- a/src/libcamera/software_isp/swstats_cpu.cpp
> +++ b/src/libcamera/software_isp/swstats_cpu.cpp
> @@ -16,6 +16,7 @@
>  #include <libcamera/stream.h>
>  
>  #include "libcamera/internal/bayer_format.h"
> +#include "libcamera/internal/mapped_framebuffer.h"
>  
>  namespace libcamera {
>  
> @@ -363,8 +364,11 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
>  	BayerFormat bayerFormat =
>  		BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
>  
> +	stride_ = inputCfg.stride;
> +
>  	if (bayerFormat.packing == BayerFormat::Packing::None &&
>  	    setupStandardBayerOrder(bayerFormat.order) == 0) {
> +		bpp_ = (bayerFormat.bitDepth + 7) & ~7;
>  		switch (bayerFormat.bitDepth) {
>  		case 8:
>  			stats0_ = &SwStatsCpu::statsBGGR8Line0;
> @@ -385,6 +389,7 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
>  		/* Skip every 3th and 4th line, sample every other 2x2 block */
>  		ySkipMask_ = 0x02;
>  		xShift_ = 0;
> +		bpp_ = 10;
>  
>  		switch (bayerFormat.order) {
>  		case BayerFormat::BGGR:
> @@ -425,4 +430,71 @@ void SwStatsCpu::setWindow(const Rectangle &window)
>  	window_.height &= ~(patternSize_.height - 1);
>  }
>  
> +void SwStatsCpu::processFrame2(const uint8_t *src)
> +{
> +	unsigned int yEnd = window_.y + window_.height;
> +	const uint8_t *linePointers[3];
> +
> +	/* Adjust src to top left corner of the window */
> +	src += window_.y * stride_ + window_.x * bpp_ / 8;
> +
> +	for (unsigned int y = window_.y; y < yEnd; y += 2) {
> +		if (y & ySkipMask_) {
> +			src += stride_ * 2;
> +			continue;
> +		}
> +
> +		/* linePointers[0] is not used by any stats0_ functions */
> +		linePointers[1] = src;
> +		linePointers[2] = src + stride_;
> +		(this->*stats0_)(linePointers);
> +		src += stride_ * 2;
> +	}
> +}
> +
> +void SwStatsCpu::processFrame4(const uint8_t *src)
> +{
> +	const unsigned int yEnd = window_.y + window_.height;
> +	const uint8_t *linePointers[4];
> +
> +	/* Adjust src to top left corner of the window */
> +	src += window_.y * stride_ + window_.x * bpp_ / 8;
> +
> +	for (unsigned int y = window_.y; y < yEnd; y += 4) {
> +		if (y & ySkipMask_) {
> +			src += stride_ * 4;
> +			continue;
> +		}
> +
> +		/* linePointers[0] and [1] are not used by 4 line pattern stats0_ functions */
> +		linePointers[2] = src;
> +		linePointers[3] = src + stride_;
> +		(this->*stats0_)(linePointers);
> +		src += stride_ * 2;
> +
> +		linePointers[2] = src;
> +		linePointers[3] = src + stride_;
> +		(this->*stats2_)(linePointers);
> +		src += stride_ * 2;
> +	}
> +}
> +
> +void SwStatsCpu::processFrame(FrameBuffer *input)
> +{
> +	bench_.startFrame();
> +
> +	MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);
> +	if (!in.isValid()) {
> +		LOG(SwStatsCpu, Error) << "mmap-ing buffer(s) failed";
> +		return;
> +	}
> +
> +	if (patternSize_.height == 2)
> +		processFrame2(in.planes()[0].data());
> +	else
> +		processFrame4(in.planes()[0].data());
> +
> +	bench_.finishFrame();
> +}
> +
>  } /* namespace libcamera */
> diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
> index 26a2f462..bb178a04 100644
> --- a/src/libcamera/software_isp/swstats_cpu.h
> +++ b/src/libcamera/software_isp/swstats_cpu.h
> @@ -18,9 +18,12 @@
>  #include <libcamera/geometry.h>
>  
>  #include "libcamera/internal/bayer_format.h"
> +#include "libcamera/internal/framebuffer.h"
>  #include "libcamera/internal/shared_mem_object.h"
>  #include "libcamera/internal/software_isp/swisp_stats.h"
>  
> +#include "benchmark.h"
> +
>  namespace libcamera {
>  
>  class PixelFormat;
> @@ -42,6 +45,7 @@ public:
>  	void setWindow(const Rectangle &window);
>  	void startFrame();
>  	void finishFrame(uint32_t frame, uint32_t bufferId);
> +	void processFrame(FrameBuffer *input);
>  
>  	void processLine0(unsigned int y, const uint8_t *src[])
>  	{
> @@ -77,6 +81,9 @@ private:
>  	void statsBGGR10PLine0(const uint8_t *src[]);
>  	void statsGBRG10PLine0(const uint8_t *src[]);
>  
> +	void processFrame2(const uint8_t *src);
> +	void processFrame4(const uint8_t *src);
> +
>  	/* Variables set by configure(), used every line */
>  	statsProcessFn stats0_;
>  	statsProcessFn stats2_;
> @@ -89,9 +96,12 @@ private:
>  	Size patternSize_;
>  
>  	unsigned int xShift_;
> +	unsigned int bpp_; /* Memory used per pixel, not precision */
> +	unsigned int stride_;
>  
>  	SharedMemObject<SwIspStats> sharedStats_;
>  	SwIspStats stats_;
> +	Benchmark bench_;
>  };
>  
>  } /* namespace libcamera */

Patch
diff mbox series

diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
index 5e4246a9..117147c2 100644
--- a/src/libcamera/software_isp/swstats_cpu.cpp
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -16,6 +16,7 @@ 
 #include <libcamera/stream.h>
 
 #include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/mapped_framebuffer.h"
 
 namespace libcamera {
 
@@ -363,8 +364,11 @@  int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
 	BayerFormat bayerFormat =
 		BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
 
+	stride_ = inputCfg.stride;
+
 	if (bayerFormat.packing == BayerFormat::Packing::None &&
 	    setupStandardBayerOrder(bayerFormat.order) == 0) {
+		bpp_ = (bayerFormat.bitDepth + 7) & ~7;
 		switch (bayerFormat.bitDepth) {
 		case 8:
 			stats0_ = &SwStatsCpu::statsBGGR8Line0;
@@ -385,6 +389,7 @@  int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
 		/* Skip every 3th and 4th line, sample every other 2x2 block */
 		ySkipMask_ = 0x02;
 		xShift_ = 0;
+		bpp_ = 10;
 
 		switch (bayerFormat.order) {
 		case BayerFormat::BGGR:
@@ -425,4 +430,71 @@  void SwStatsCpu::setWindow(const Rectangle &window)
 	window_.height &= ~(patternSize_.height - 1);
 }
 
+void SwStatsCpu::processFrame2(const uint8_t *src)
+{
+	unsigned int yEnd = window_.y + window_.height;
+	const uint8_t *linePointers[3];
+
+	/* Adjust src to top left corner of the window */
+	src += window_.y * stride_ + window_.x * bpp_ / 8;
+
+	for (unsigned int y = window_.y; y < yEnd; y += 2) {
+		if (y & ySkipMask_) {
+			src += stride_ * 2;
+			continue;
+		}
+
+		/* linePointers[0] is not used by any stats0_ functions */
+		linePointers[1] = src;
+		linePointers[2] = src + stride_;
+		(this->*stats0_)(linePointers);
+		src += stride_ * 2;
+	}
+}
+
+void SwStatsCpu::processFrame4(const uint8_t *src)
+{
+	const unsigned int yEnd = window_.y + window_.height;
+	const uint8_t *linePointers[4];
+
+	/* Adjust src to top left corner of the window */
+	src += window_.y * stride_ + window_.x * bpp_ / 8;
+
+	for (unsigned int y = window_.y; y < yEnd; y += 4) {
+		if (y & ySkipMask_) {
+			src += stride_ * 4;
+			continue;
+		}
+
+		/* linePointers[0] and [1] are not used by 4 line pattern stats0_ functions */
+		linePointers[2] = src;
+		linePointers[3] = src + stride_;
+		(this->*stats0_)(linePointers);
+		src += stride_ * 2;
+
+		linePointers[2] = src;
+		linePointers[3] = src + stride_;
+		(this->*stats2_)(linePointers);
+		src += stride_ * 2;
+	}
+}
+
+void SwStatsCpu::processFrame(FrameBuffer *input)
+{
+	bench_.startFrame();
+
+	MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);
+	if (!in.isValid()) {
+		LOG(SwStatsCpu, Error) << "mmap-ing buffer(s) failed";
+		return;
+	}
+
+	if (patternSize_.height == 2)
+		processFrame2(in.planes()[0].data());
+	else
+		processFrame4(in.planes()[0].data());
+
+	bench_.finishFrame();
+}
+
 } /* namespace libcamera */
diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
index 26a2f462..bb178a04 100644
--- a/src/libcamera/software_isp/swstats_cpu.h
+++ b/src/libcamera/software_isp/swstats_cpu.h
@@ -18,9 +18,12 @@ 
 #include <libcamera/geometry.h>
 
 #include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/framebuffer.h"
 #include "libcamera/internal/shared_mem_object.h"
 #include "libcamera/internal/software_isp/swisp_stats.h"
 
+#include "benchmark.h"
+
 namespace libcamera {
 
 class PixelFormat;
@@ -42,6 +45,7 @@  public:
 	void setWindow(const Rectangle &window);
 	void startFrame();
 	void finishFrame(uint32_t frame, uint32_t bufferId);
+	void processFrame(FrameBuffer *input);
 
 	void processLine0(unsigned int y, const uint8_t *src[])
 	{
@@ -77,6 +81,9 @@  private:
 	void statsBGGR10PLine0(const uint8_t *src[]);
 	void statsGBRG10PLine0(const uint8_t *src[]);
 
+	void processFrame2(const uint8_t *src);
+	void processFrame4(const uint8_t *src);
+
 	/* Variables set by configure(), used every line */
 	statsProcessFn stats0_;
 	statsProcessFn stats2_;
@@ -89,9 +96,12 @@  private:
 	Size patternSize_;
 
 	unsigned int xShift_;
+	unsigned int bpp_; /* Memory used per pixel, not precision */
+	unsigned int stride_;
 
 	SharedMemObject<SwIspStats> sharedStats_;
 	SwIspStats stats_;
+	Benchmark bench_;
 };
 
 } /* namespace libcamera */