libcamera: software_isp: Support 12-bit CSI2 packed raw bayer formats
diff mbox series

Message ID 20260522-swisp_12p-v1-1-e115c35387b7@ideasonboard.com
State Superseded, archived
Headers show
Series
  • libcamera: software_isp: Support 12-bit CSI2 packed raw bayer formats
Related show

Commit Message

Jai Luthra May 22, 2026, 2:37 p.m. UTC
Add support for debayering 12-bit CSI2 packed raw bayer formats.

Also add support for the same in SwStats, skipping one 2x2 block
horizontally and skipping every 3rd and 4th line vertically, like done
for other raw bayer formats.

GPUISP support was always present in the packed shaders (which already
handle skipping the pixel with LSBs), so enable it too.

Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
---
This is tested for RGGB12P using IMX678 on Arduino Uno Q. The sensor
doesn't change bayer layout on flips, so I couldn't verify other
layouts.
---
 .../libcamera/internal/software_isp/swstats_cpu.h  |   3 +
 src/libcamera/software_isp/debayer_cpu.cpp         | 107 +++++++++++++++++++++
 src/libcamera/software_isp/debayer_cpu.h           |   9 ++
 src/libcamera/software_isp/debayer_egl.cpp         |  13 +++
 src/libcamera/software_isp/swstats_cpu.cpp         |  78 +++++++++++++++
 5 files changed, 210 insertions(+)


---
base-commit: 1f97c59d276922d387f7bfcfaa6d54b4084e43c1
change-id: 20260522-swisp_12p-6ad22e4d08b0

Best regards,

Comments

Milan Zamazal May 26, 2026, 11:54 a.m. UTC | #1
Hi,

thank you for the patch.

Jai Luthra <jai.luthra@ideasonboard.com> writes:

> Add support for debayering 12-bit CSI2 packed raw bayer formats.
>
> Also add support for the same in SwStats, skipping one 2x2 block
> horizontally and skipping every 3rd and 4th line vertically, like done
> for other raw bayer formats.
>
> GPUISP support was always present in the packed shaders (which already
> handle skipping the pixel with LSBs), so enable it too.
>
> Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
> ---
> This is tested for RGGB12P using IMX678 on Arduino Uno Q. The sensor
> doesn't change bayer layout on flips, so I couldn't verify other
> layouts.
> ---
>  .../libcamera/internal/software_isp/swstats_cpu.h  |   3 +
>  src/libcamera/software_isp/debayer_cpu.cpp         | 107 +++++++++++++++++++++
>  src/libcamera/software_isp/debayer_cpu.h           |   9 ++
>  src/libcamera/software_isp/debayer_egl.cpp         |  13 +++
>  src/libcamera/software_isp/swstats_cpu.cpp         |  78 +++++++++++++++
>  5 files changed, 210 insertions(+)
>
> diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
> index b5348c6f..13fa7582 100644
> --- a/include/libcamera/internal/software_isp/swstats_cpu.h
> +++ b/include/libcamera/internal/software_isp/swstats_cpu.h
> @@ -98,6 +98,9 @@ private:
>  	/* Bayer 10 bpp packed */
>  	void statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats);
>  	void statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats);
> +	/* Bayer 12 bpp packed */
> +	void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats);
> +	void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats);
>  
>  	void processBayerFrame2(MappedFrameBuffer &in);
>  
> diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
> index 1f9b24da..2751dd30 100644
> --- a/src/libcamera/software_isp/debayer_cpu.cpp
> +++ b/src/libcamera/software_isp/debayer_cpu.cpp
> @@ -351,6 +351,78 @@ void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
>  	}
>  }
>  
> +template<bool addAlphaByte, bool ccmEnabled>
> +void DebayerCpu::debayer12P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
> +{
> +	const int widthInBytes = window_.width * 3 / 2;
> +	const uint8_t *prev = src[0];
> +	const uint8_t *curr = src[1];
> +	const uint8_t *next = src[2];
> +
> +	for (int x = 0; x < widthInBytes;) {
> +		/* Even pixel */
> +		BGGR_BGR888(2, 1, 1)
> +		/* Odd pixel RGGB -> GRBG */

BGGR -> GBRG

> +		GBRG_BGR888(1, 2, 1)
> +		/* Skip 3rd src byte with 2 x 4 least-significant-bits */
> +		x++;
> +	}
> +}
> +
> +template<bool addAlphaByte, bool ccmEnabled>
> +void DebayerCpu::debayer12P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
> +{
> +	const int widthInBytes = window_.width * 3 / 2;
> +	const uint8_t *prev = src[0];
> +	const uint8_t *curr = src[1];
> +	const uint8_t *next = src[2];
> +
> +	for (int x = 0; x < widthInBytes;) {
> +		/* Even pixel */
> +		GRBG_BGR888(2, 1, 1)
> +		/* Odd pixel RGGB -> GRBG */

GRBG -> RGGB

> +		RGGB_BGR888(1, 2, 1)
> +		/* Skip 3rd src byte with 2 x 4 least-significant-bits */
> +		x++;
> +	}
> +}
> +
> +template<bool addAlphaByte, bool ccmEnabled>
> +void DebayerCpu::debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
> +{
> +	const int widthInBytes = window_.width * 3 / 2;
> +	const uint8_t *prev = src[0];
> +	const uint8_t *curr = src[1];
> +	const uint8_t *next = src[2];
> +
> +	for (int x = 0; x < widthInBytes;) {
> +		/* Even pixel */
> +		GBRG_BGR888(2, 1, 1)
> +		/* Odd pixel RGGB -> GRBG */

GBRG -> BGGR

> +		BGGR_BGR888(1, 2, 1)
> +		/* Skip 3rd src byte with 2 x 4 least-significant-bits */
> +		x++;
> +	}
> +}
> +
> +template<bool addAlphaByte, bool ccmEnabled>
> +void DebayerCpu::debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
> +{
> +	const int widthInBytes = window_.width * 3 / 2;
> +	const uint8_t *prev = src[0];
> +	const uint8_t *curr = src[1];
> +	const uint8_t *next = src[2];
> +
> +	for (int x = 0; x < widthInBytes;) {
> +		/* Even pixel */
> +		RGGB_BGR888(2, 1, 1)
> +		/* Odd pixel RGGB -> GRBG */
> +		GRBG_BGR888(1, 2, 1)
> +		/* Skip 3rd src byte with 2 x 4 least-significant-bits */
> +		x++;
> +	}
> +}

(I wish we could get rid of all the duplication here one day; not in
this patch.)

> +
>  /*
>   * Setup the Debayer object according to the passed in parameters.
>   * Return 0 on success, a negative errno value on failure
> @@ -391,6 +463,21 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
>  		return 0;
>  	}
>  
> +	if (bayerFormat.bitDepth == 12 &&
> +	    bayerFormat.packing == BayerFormat::Packing::CSI2 &&
> +	    isStandardBayerOrder(bayerFormat.order)) {
> +		config.bpp = 12;
> +		config.patternSize.width = 2; /* 3 bytes per *2* pixels */
> +		config.patternSize.height = 2;
> +		config.outputFormats = std::vector<PixelFormat>({ formats::RGB888,
> +								  formats::XRGB8888,
> +								  formats::ARGB8888,
> +								  formats::BGR888,
> +								  formats::XBGR8888,
> +								  formats::ABGR8888 });

The output formats are all the same in all the cases, let's extract them
to a common variable?

> +		return 0;
> +	}
> +
>  	LOG(Debayer, Info)
>  		<< "Unsupported input format " << inputFormat.toString();
>  	return -EINVAL;
> @@ -538,6 +625,26 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat,
>  		}
>  	}
>  
> +	if (bayerFormat.bitDepth == 12 &&
> +	    bayerFormat.packing == BayerFormat::Packing::CSI2) {
> +		switch (bayerFormat.order) {
> +		case BayerFormat::BGGR:
> +			SET_DEBAYER_METHODS(debayer12P_BGBG_BGR888, debayer12P_GRGR_BGR888)
> +			return 0;
> +		case BayerFormat::GBRG:
> +			SET_DEBAYER_METHODS(debayer12P_GBGB_BGR888, debayer12P_RGRG_BGR888)
> +			return 0;
> +		case BayerFormat::GRBG:
> +			SET_DEBAYER_METHODS(debayer12P_GRGR_BGR888, debayer12P_BGBG_BGR888)
> +			return 0;
> +		case BayerFormat::RGGB:
> +			SET_DEBAYER_METHODS(debayer12P_RGRG_BGR888, debayer12P_GBGB_BGR888)
> +			return 0;
> +		default:
> +			break;
> +		}
> +	}
> +
>  	return invalidFmt();
>  }
>  
> diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
> index 68da9508..5281c65c 100644
> --- a/src/libcamera/software_isp/debayer_cpu.h
> +++ b/src/libcamera/software_isp/debayer_cpu.h
> @@ -110,6 +110,15 @@ private:
>  	void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
>  	template<bool addAlphaByte, bool ccmEnabled>
>  	void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
> +	/* CSI-2 packed 12-bit raw bayer format (all the 4 orders) */
> +	template<bool addAlphaByte, bool ccmEnabled>
> +	void debayer12P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
> +	template<bool addAlphaByte, bool ccmEnabled>
> +	void debayer12P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
> +	template<bool addAlphaByte, bool ccmEnabled>
> +	void debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
> +	template<bool addAlphaByte, bool ccmEnabled>
> +	void debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
>  
>  	static int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);
>  	static int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);
> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
> index eae4c57f..53adbdce 100644
> --- a/src/libcamera/software_isp/debayer_egl.cpp
> +++ b/src/libcamera/software_isp/debayer_egl.cpp
> @@ -78,6 +78,19 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
>  		return 0;
>  	}
>  
> +	if (bayerFormat.bitDepth == 12 &&
> +	    bayerFormat.packing == BayerFormat::Packing::CSI2 &&
> +	    isStandardBayerOrder(bayerFormat.order)) {
> +		config.bpp = 12;
> +		config.patternSize.width = 2; /* 3 bytes per *2* pixels */
> +		config.patternSize.height = 2;
> +		config.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,
> +								  formats::ARGB8888,
> +								  formats::XBGR8888,
> +								  formats::ABGR8888 });

Here too.

> +		return 0;
> +	}
> +
>  	LOG(Debayer, Error)
>  		<< "Unsupported input format " << inputFormat;
>  
> diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
> index 0815ec9a..1deee942 100644
> --- a/src/libcamera/software_isp/swstats_cpu.cpp
> +++ b/src/libcamera/software_isp/swstats_cpu.cpp
> @@ -323,6 +323,58 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats)
>  	SWSTATS_FINISH_LINE_STATS()
>  }
>  
> +void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats)
> +{
> +	const uint8_t *src0 = src[1] + window_.x * 3 / 2;
> +	const uint8_t *src1 = src[2] + window_.x * 3 / 2;
> +	const unsigned int widthInBytes = window_.width * 3 / 2;
> +
> +	SWSTATS_START_LINE_STATS(uint8_t)
> +
> +	if (swapLines_)
> +		std::swap(src0, src1);
> +
> +	/* x += 6 sample every other 2x2 block */
> +	for (unsigned int x = 0; x < widthInBytes; x += 6) {
> +		b = src0[x];
> +		g = src0[x + 1];
> +		g2 = src1[x];
> +		r = src1[x + 1];
> +
> +		g = (g + g2) / 2;
> +
> +		SWSTATS_ACCUMULATE_LINE_STATS(1)
> +	}
> +
> +	SWSTATS_FINISH_LINE_STATS()
> +}
> +
> +void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats)
> +{
> +	const uint8_t *src0 = src[1] + window_.x * 3 / 2;
> +	const uint8_t *src1 = src[2] + window_.x * 3 / 2;
> +	const unsigned int widthInBytes = window_.width * 3 / 2;
> +
> +	SWSTATS_START_LINE_STATS(uint8_t)
> +
> +	if (swapLines_)
> +		std::swap(src0, src1);
> +
> +	/* x += 6 sample every other 2x2 block */
> +	for (unsigned int x = 0; x < widthInBytes; x += 6) {
> +		g = src0[x];
> +		b = src0[x + 1];
> +		r = src1[x];
> +		g2 = src1[x + 1];
> +
> +		g = (g + g2) / 2;
> +
> +		SWSTATS_ACCUMULATE_LINE_STATS(1)
> +	}
> +
> +	SWSTATS_FINISH_LINE_STATS()
> +}
> +
>  /**
>   * \brief Reset state to start statistics gathering for a new frame
>   * \param[in] frame The frame number
> @@ -466,6 +518,32 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat
>  		}
>  	}
>  
> +	if (bayerFormat.bitDepth == 12 &&
> +	    bayerFormat.packing == BayerFormat::Packing::CSI2) {
> +		patternSize_.height = 2;
> +		patternSize_.width = 2; /* 3 bytes for *2* pixels */
> +		/* Skip every 3th and 4th line, sample every other 2x2 block */
> +		ySkipMask_ = 0x02;
> +		xShift_ = 0;
> +		sumShift_ = 0;
> +		processFrame_ = &SwStatsCpu::processBayerFrame2;
> +
> +		switch (bayerFormat.order) {
> +		case BayerFormat::BGGR:
> +		case BayerFormat::GRBG:
> +			stats0_ = &SwStatsCpu::statsBGGR12PLine0;
> +			swapLines_ = bayerFormat.order == BayerFormat::GRBG;
> +			return 0;
> +		case BayerFormat::GBRG:
> +		case BayerFormat::RGGB:
> +			stats0_ = &SwStatsCpu::statsGBRG12PLine0;
> +			swapLines_ = bayerFormat.order == BayerFormat::RGGB;
> +			return 0;
> +		default:
> +			break;
> +		}
> +	}
> +

This is almost identical to the 10-bit case, it should be worth to deduplicate.

>  	LOG(SwStatsCpu, Info)
>  		<< "Unsupported input format " << inputCfg.pixelFormat.toString();
>  	return -EINVAL;
>
> ---
> base-commit: 1f97c59d276922d387f7bfcfaa6d54b4084e43c1
> change-id: 20260522-swisp_12p-6ad22e4d08b0
>
> Best regards,
Bryan O'Donoghue May 26, 2026, 5:52 p.m. UTC | #2
On 22/05/2026 15:37, Jai Luthra wrote:
> --- a/src/libcamera/software_isp/debayer_egl.cpp
> +++ b/src/libcamera/software_isp/debayer_egl.cpp
> @@ -78,6 +78,19 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
>   		return 0;
>   	}
> 
> +	if (bayerFormat.bitDepth == 12 &&
> +	    bayerFormat.packing == BayerFormat::Packing::CSI2 &&
> +	    isStandardBayerOrder(bayerFormat.order)) {
> +		config.bpp = 12;
> +		config.patternSize.width = 2; /* 3 bytes per *2* pixels */
> +		config.patternSize.height = 2;
> +		config.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,
> +								  formats::ARGB8888,
> +								  formats::XBGR8888,
> +								  formats::ABGR8888 });
> +		return 0;
> +	}
> +
>   	LOG(Debayer, Error)
>   		<< "Unsupported input format " << inputFormat;

A pleasingly small amount of change in debayer_egl.cpp to support this.

I assume you have put the data through the shaders to verify it performs 
as expected ?

---
bod
Jai Luthra May 28, 2026, 3:54 p.m. UTC | #3
Hi Bryan,

Quoting Bryan O'Donoghue (2026-05-26 23:22:36)
> On 22/05/2026 15:37, Jai Luthra wrote:
> > --- a/src/libcamera/software_isp/debayer_egl.cpp
> > +++ b/src/libcamera/software_isp/debayer_egl.cpp
> > @@ -78,6 +78,19 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
> >               return 0;
> >       }
> > 
> > +     if (bayerFormat.bitDepth == 12 &&
> > +         bayerFormat.packing == BayerFormat::Packing::CSI2 &&
> > +         isStandardBayerOrder(bayerFormat.order)) {
> > +             config.bpp = 12;
> > +             config.patternSize.width = 2; /* 3 bytes per *2* pixels */
> > +             config.patternSize.height = 2;
> > +             config.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,
> > +                                                               formats::ARGB8888,
> > +                                                               formats::XBGR8888,
> > +                                                               formats::ABGR8888 });
> > +             return 0;
> > +     }
> > +
> >       LOG(Debayer, Error)
> >               << "Unsupported input format " << inputFormat;
> 
> A pleasingly small amount of change in debayer_egl.cpp to support this.
> 

Indeed, I was pleasantly surprised as well :-)


> I assume you have put the data through the shaders to verify it performs 
> as expected ?
> 

I tested with IMX678 (RAW12, RGGB), the output looked close enough visually
to the CPU-based debayering.

I couldn't test other bayer orderings though as the sensor shifts-by-1 when
flipping to maintain the RGGB layout.

Is there some other test source you usually use to thoroughly test the shader?
I wonder if it would be easy enough to use pixpat [1] to generate packed 12
bit test patterns. I can give that a try.

[1] https://github.com/tomba/pixpat

> ---
> bod
> 

Thanks,
    Jai
Jai Luthra May 28, 2026, 3:55 p.m. UTC | #4
Hi Milan,

Thanks for the review.

Quoting Milan Zamazal (2026-05-26 17:24:01)
> Hi,
> 
> thank you for the patch.
> 
> Jai Luthra <jai.luthra@ideasonboard.com> writes:
> 
> > Add support for debayering 12-bit CSI2 packed raw bayer formats.
> >
> > Also add support for the same in SwStats, skipping one 2x2 block
> > horizontally and skipping every 3rd and 4th line vertically, like done
> > for other raw bayer formats.
> >
> > GPUISP support was always present in the packed shaders (which already
> > handle skipping the pixel with LSBs), so enable it too.
> >
> > Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
> > ---
> > This is tested for RGGB12P using IMX678 on Arduino Uno Q. The sensor
> > doesn't change bayer layout on flips, so I couldn't verify other
> > layouts.
> > ---
> >  .../libcamera/internal/software_isp/swstats_cpu.h  |   3 +
> >  src/libcamera/software_isp/debayer_cpu.cpp         | 107 +++++++++++++++++++++
> >  src/libcamera/software_isp/debayer_cpu.h           |   9 ++
> >  src/libcamera/software_isp/debayer_egl.cpp         |  13 +++
> >  src/libcamera/software_isp/swstats_cpu.cpp         |  78 +++++++++++++++
> >  5 files changed, 210 insertions(+)
> >
> > diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
> > index b5348c6f..13fa7582 100644
> > --- a/include/libcamera/internal/software_isp/swstats_cpu.h
> > +++ b/include/libcamera/internal/software_isp/swstats_cpu.h
> > @@ -98,6 +98,9 @@ private:
> >       /* Bayer 10 bpp packed */
> >       void statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats);
> >       void statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats);
> > +     /* Bayer 12 bpp packed */
> > +     void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats);
> > +     void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats);
> >  
> >       void processBayerFrame2(MappedFrameBuffer &in);
> >  
> > diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
> > index 1f9b24da..2751dd30 100644
> > --- a/src/libcamera/software_isp/debayer_cpu.cpp
> > +++ b/src/libcamera/software_isp/debayer_cpu.cpp
> > @@ -351,6 +351,78 @@ void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
> >       }
> >  }
> >  
> > +template<bool addAlphaByte, bool ccmEnabled>
> > +void DebayerCpu::debayer12P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
> > +{
> > +     const int widthInBytes = window_.width * 3 / 2;
> > +     const uint8_t *prev = src[0];
> > +     const uint8_t *curr = src[1];
> > +     const uint8_t *next = src[2];
> > +
> > +     for (int x = 0; x < widthInBytes;) {
> > +             /* Even pixel */
> > +             BGGR_BGR888(2, 1, 1)
> > +             /* Odd pixel RGGB -> GRBG */
> 
> BGGR -> GBRG
> 

Oops, will fix.

> > +             GBRG_BGR888(1, 2, 1)
> > +             /* Skip 3rd src byte with 2 x 4 least-significant-bits */
> > +             x++;
> > +     }
> > +}
> > +
> > +template<bool addAlphaByte, bool ccmEnabled>
> > +void DebayerCpu::debayer12P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
> > +{
> > +     const int widthInBytes = window_.width * 3 / 2;
> > +     const uint8_t *prev = src[0];
> > +     const uint8_t *curr = src[1];
> > +     const uint8_t *next = src[2];
> > +
> > +     for (int x = 0; x < widthInBytes;) {
> > +             /* Even pixel */
> > +             GRBG_BGR888(2, 1, 1)
> > +             /* Odd pixel RGGB -> GRBG */
> 
> GRBG -> RGGB
> 
> > +             RGGB_BGR888(1, 2, 1)
> > +             /* Skip 3rd src byte with 2 x 4 least-significant-bits */
> > +             x++;
> > +     }
> > +}
> > +
> > +template<bool addAlphaByte, bool ccmEnabled>
> > +void DebayerCpu::debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
> > +{
> > +     const int widthInBytes = window_.width * 3 / 2;
> > +     const uint8_t *prev = src[0];
> > +     const uint8_t *curr = src[1];
> > +     const uint8_t *next = src[2];
> > +
> > +     for (int x = 0; x < widthInBytes;) {
> > +             /* Even pixel */
> > +             GBRG_BGR888(2, 1, 1)
> > +             /* Odd pixel RGGB -> GRBG */
> 
> GBRG -> BGGR
> 
> > +             BGGR_BGR888(1, 2, 1)
> > +             /* Skip 3rd src byte with 2 x 4 least-significant-bits */
> > +             x++;
> > +     }
> > +}
> > +
> > +template<bool addAlphaByte, bool ccmEnabled>
> > +void DebayerCpu::debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
> > +{
> > +     const int widthInBytes = window_.width * 3 / 2;
> > +     const uint8_t *prev = src[0];
> > +     const uint8_t *curr = src[1];
> > +     const uint8_t *next = src[2];
> > +
> > +     for (int x = 0; x < widthInBytes;) {
> > +             /* Even pixel */
> > +             RGGB_BGR888(2, 1, 1)
> > +             /* Odd pixel RGGB -> GRBG */
> > +             GRBG_BGR888(1, 2, 1)
> > +             /* Skip 3rd src byte with 2 x 4 least-significant-bits */
> > +             x++;
> > +     }
> > +}
> 
> (I wish we could get rid of all the duplication here one day; not in
> this patch.)
> 
> > +
> >  /*
> >   * Setup the Debayer object according to the passed in parameters.
> >   * Return 0 on success, a negative errno value on failure
> > @@ -391,6 +463,21 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
> >               return 0;
> >       }
> >  
> > +     if (bayerFormat.bitDepth == 12 &&
> > +         bayerFormat.packing == BayerFormat::Packing::CSI2 &&
> > +         isStandardBayerOrder(bayerFormat.order)) {
> > +             config.bpp = 12;
> > +             config.patternSize.width = 2; /* 3 bytes per *2* pixels */
> > +             config.patternSize.height = 2;
> > +             config.outputFormats = std::vector<PixelFormat>({ formats::RGB888,
> > +                                                               formats::XRGB8888,
> > +                                                               formats::ARGB8888,
> > +                                                               formats::BGR888,
> > +                                                               formats::XBGR8888,
> > +                                                               formats::ABGR8888 });
> 
> The output formats are all the same in all the cases, let's extract them
> to a common variable?
> 

Ack.

> > +             return 0;
> > +     }
> > +
> >       LOG(Debayer, Info)
> >               << "Unsupported input format " << inputFormat.toString();
> >       return -EINVAL;
> > @@ -538,6 +625,26 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat,
> >               }
> >       }
> >  
> > +     if (bayerFormat.bitDepth == 12 &&
> > +         bayerFormat.packing == BayerFormat::Packing::CSI2) {
> > +             switch (bayerFormat.order) {
> > +             case BayerFormat::BGGR:
> > +                     SET_DEBAYER_METHODS(debayer12P_BGBG_BGR888, debayer12P_GRGR_BGR888)
> > +                     return 0;
> > +             case BayerFormat::GBRG:
> > +                     SET_DEBAYER_METHODS(debayer12P_GBGB_BGR888, debayer12P_RGRG_BGR888)
> > +                     return 0;
> > +             case BayerFormat::GRBG:
> > +                     SET_DEBAYER_METHODS(debayer12P_GRGR_BGR888, debayer12P_BGBG_BGR888)
> > +                     return 0;
> > +             case BayerFormat::RGGB:
> > +                     SET_DEBAYER_METHODS(debayer12P_RGRG_BGR888, debayer12P_GBGB_BGR888)
> > +                     return 0;
> > +             default:
> > +                     break;
> > +             }
> > +     }
> > +
> >       return invalidFmt();
> >  }
> >  
> > diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
> > index 68da9508..5281c65c 100644
> > --- a/src/libcamera/software_isp/debayer_cpu.h
> > +++ b/src/libcamera/software_isp/debayer_cpu.h
> > @@ -110,6 +110,15 @@ private:
> >       void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
> >       template<bool addAlphaByte, bool ccmEnabled>
> >       void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
> > +     /* CSI-2 packed 12-bit raw bayer format (all the 4 orders) */
> > +     template<bool addAlphaByte, bool ccmEnabled>
> > +     void debayer12P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
> > +     template<bool addAlphaByte, bool ccmEnabled>
> > +     void debayer12P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
> > +     template<bool addAlphaByte, bool ccmEnabled>
> > +     void debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
> > +     template<bool addAlphaByte, bool ccmEnabled>
> > +     void debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
> >  
> >       static int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);
> >       static int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);
> > diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
> > index eae4c57f..53adbdce 100644
> > --- a/src/libcamera/software_isp/debayer_egl.cpp
> > +++ b/src/libcamera/software_isp/debayer_egl.cpp
> > @@ -78,6 +78,19 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
> >               return 0;
> >       }
> >  
> > +     if (bayerFormat.bitDepth == 12 &&
> > +         bayerFormat.packing == BayerFormat::Packing::CSI2 &&
> > +         isStandardBayerOrder(bayerFormat.order)) {
> > +             config.bpp = 12;
> > +             config.patternSize.width = 2; /* 3 bytes per *2* pixels */
> > +             config.patternSize.height = 2;
> > +             config.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,
> > +                                                               formats::ARGB8888,
> > +                                                               formats::XBGR8888,
> > +                                                               formats::ABGR8888 });
> 
> Here too.
> 
> > +             return 0;
> > +     }
> > +
> >       LOG(Debayer, Error)
> >               << "Unsupported input format " << inputFormat;
> >  
> > diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
> > index 0815ec9a..1deee942 100644
> > --- a/src/libcamera/software_isp/swstats_cpu.cpp
> > +++ b/src/libcamera/software_isp/swstats_cpu.cpp
> > @@ -323,6 +323,58 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats)
> >       SWSTATS_FINISH_LINE_STATS()
> >  }
> >  
> > +void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats)
> > +{
> > +     const uint8_t *src0 = src[1] + window_.x * 3 / 2;
> > +     const uint8_t *src1 = src[2] + window_.x * 3 / 2;
> > +     const unsigned int widthInBytes = window_.width * 3 / 2;
> > +
> > +     SWSTATS_START_LINE_STATS(uint8_t)
> > +
> > +     if (swapLines_)
> > +             std::swap(src0, src1);
> > +
> > +     /* x += 6 sample every other 2x2 block */
> > +     for (unsigned int x = 0; x < widthInBytes; x += 6) {
> > +             b = src0[x];
> > +             g = src0[x + 1];
> > +             g2 = src1[x];
> > +             r = src1[x + 1];
> > +
> > +             g = (g + g2) / 2;
> > +
> > +             SWSTATS_ACCUMULATE_LINE_STATS(1)
> > +     }
> > +
> > +     SWSTATS_FINISH_LINE_STATS()
> > +}
> > +
> > +void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats)
> > +{
> > +     const uint8_t *src0 = src[1] + window_.x * 3 / 2;
> > +     const uint8_t *src1 = src[2] + window_.x * 3 / 2;
> > +     const unsigned int widthInBytes = window_.width * 3 / 2;
> > +
> > +     SWSTATS_START_LINE_STATS(uint8_t)
> > +
> > +     if (swapLines_)
> > +             std::swap(src0, src1);
> > +
> > +     /* x += 6 sample every other 2x2 block */
> > +     for (unsigned int x = 0; x < widthInBytes; x += 6) {
> > +             g = src0[x];
> > +             b = src0[x + 1];
> > +             r = src1[x];
> > +             g2 = src1[x + 1];
> > +
> > +             g = (g + g2) / 2;
> > +
> > +             SWSTATS_ACCUMULATE_LINE_STATS(1)
> > +     }
> > +
> > +     SWSTATS_FINISH_LINE_STATS()
> > +}
> > +
> >  /**
> >   * \brief Reset state to start statistics gathering for a new frame
> >   * \param[in] frame The frame number
> > @@ -466,6 +518,32 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat
> >               }
> >       }
> >  
> > +     if (bayerFormat.bitDepth == 12 &&
> > +         bayerFormat.packing == BayerFormat::Packing::CSI2) {
> > +             patternSize_.height = 2;
> > +             patternSize_.width = 2; /* 3 bytes for *2* pixels */
> > +             /* Skip every 3th and 4th line, sample every other 2x2 block */
> > +             ySkipMask_ = 0x02;
> > +             xShift_ = 0;
> > +             sumShift_ = 0;
> > +             processFrame_ = &SwStatsCpu::processBayerFrame2;
> > +
> > +             switch (bayerFormat.order) {
> > +             case BayerFormat::BGGR:
> > +             case BayerFormat::GRBG:
> > +                     stats0_ = &SwStatsCpu::statsBGGR12PLine0;
> > +                     swapLines_ = bayerFormat.order == BayerFormat::GRBG;
> > +                     return 0;
> > +             case BayerFormat::GBRG:
> > +             case BayerFormat::RGGB:
> > +                     stats0_ = &SwStatsCpu::statsGBRG12PLine0;
> > +                     swapLines_ = bayerFormat.order == BayerFormat::RGGB;
> > +                     return 0;
> > +             default:
> > +                     break;
> > +             }
> > +     }
> > +
> 
> This is almost identical to the 10-bit case, it should be worth to deduplicate.
> 

Will do in v2.

Thanks,
    Jai

> >       LOG(SwStatsCpu, Info)
> >               << "Unsupported input format " << inputCfg.pixelFormat.toString();
> >       return -EINVAL;
> >
> > ---
> > base-commit: 1f97c59d276922d387f7bfcfaa6d54b4084e43c1
> > change-id: 20260522-swisp_12p-6ad22e4d08b0
> >
> > Best regards,
>
Bryan O'Donoghue June 1, 2026, 11:35 p.m. UTC | #5
On 28/05/2026 16:54, Jai Luthra wrote:
> Is there some other test source you usually use to thoroughly test the shader?

Some test ?

Run it once, if there is no blue smoke, ship it !

---
bod

Patch
diff mbox series

diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
index b5348c6f..13fa7582 100644
--- a/include/libcamera/internal/software_isp/swstats_cpu.h
+++ b/include/libcamera/internal/software_isp/swstats_cpu.h
@@ -98,6 +98,9 @@  private:
 	/* Bayer 10 bpp packed */
 	void statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats);
 	void statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats);
+	/* Bayer 12 bpp packed */
+	void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats);
+	void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats);
 
 	void processBayerFrame2(MappedFrameBuffer &in);
 
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
index 1f9b24da..2751dd30 100644
--- a/src/libcamera/software_isp/debayer_cpu.cpp
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -351,6 +351,78 @@  void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
 	}
 }
 
+template<bool addAlphaByte, bool ccmEnabled>
+void DebayerCpu::debayer12P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+	const int widthInBytes = window_.width * 3 / 2;
+	const uint8_t *prev = src[0];
+	const uint8_t *curr = src[1];
+	const uint8_t *next = src[2];
+
+	for (int x = 0; x < widthInBytes;) {
+		/* Even pixel */
+		BGGR_BGR888(2, 1, 1)
+		/* Odd pixel RGGB -> GRBG */
+		GBRG_BGR888(1, 2, 1)
+		/* Skip 3rd src byte with 2 x 4 least-significant-bits */
+		x++;
+	}
+}
+
+template<bool addAlphaByte, bool ccmEnabled>
+void DebayerCpu::debayer12P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+	const int widthInBytes = window_.width * 3 / 2;
+	const uint8_t *prev = src[0];
+	const uint8_t *curr = src[1];
+	const uint8_t *next = src[2];
+
+	for (int x = 0; x < widthInBytes;) {
+		/* Even pixel */
+		GRBG_BGR888(2, 1, 1)
+		/* Odd pixel RGGB -> GRBG */
+		RGGB_BGR888(1, 2, 1)
+		/* Skip 3rd src byte with 2 x 4 least-significant-bits */
+		x++;
+	}
+}
+
+template<bool addAlphaByte, bool ccmEnabled>
+void DebayerCpu::debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+	const int widthInBytes = window_.width * 3 / 2;
+	const uint8_t *prev = src[0];
+	const uint8_t *curr = src[1];
+	const uint8_t *next = src[2];
+
+	for (int x = 0; x < widthInBytes;) {
+		/* Even pixel */
+		GBRG_BGR888(2, 1, 1)
+		/* Odd pixel RGGB -> GRBG */
+		BGGR_BGR888(1, 2, 1)
+		/* Skip 3rd src byte with 2 x 4 least-significant-bits */
+		x++;
+	}
+}
+
+template<bool addAlphaByte, bool ccmEnabled>
+void DebayerCpu::debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
+{
+	const int widthInBytes = window_.width * 3 / 2;
+	const uint8_t *prev = src[0];
+	const uint8_t *curr = src[1];
+	const uint8_t *next = src[2];
+
+	for (int x = 0; x < widthInBytes;) {
+		/* Even pixel */
+		RGGB_BGR888(2, 1, 1)
+		/* Odd pixel RGGB -> GRBG */
+		GRBG_BGR888(1, 2, 1)
+		/* Skip 3rd src byte with 2 x 4 least-significant-bits */
+		x++;
+	}
+}
+
 /*
  * Setup the Debayer object according to the passed in parameters.
  * Return 0 on success, a negative errno value on failure
@@ -391,6 +463,21 @@  int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
 		return 0;
 	}
 
+	if (bayerFormat.bitDepth == 12 &&
+	    bayerFormat.packing == BayerFormat::Packing::CSI2 &&
+	    isStandardBayerOrder(bayerFormat.order)) {
+		config.bpp = 12;
+		config.patternSize.width = 2; /* 3 bytes per *2* pixels */
+		config.patternSize.height = 2;
+		config.outputFormats = std::vector<PixelFormat>({ formats::RGB888,
+								  formats::XRGB8888,
+								  formats::ARGB8888,
+								  formats::BGR888,
+								  formats::XBGR8888,
+								  formats::ABGR8888 });
+		return 0;
+	}
+
 	LOG(Debayer, Info)
 		<< "Unsupported input format " << inputFormat.toString();
 	return -EINVAL;
@@ -538,6 +625,26 @@  int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat,
 		}
 	}
 
+	if (bayerFormat.bitDepth == 12 &&
+	    bayerFormat.packing == BayerFormat::Packing::CSI2) {
+		switch (bayerFormat.order) {
+		case BayerFormat::BGGR:
+			SET_DEBAYER_METHODS(debayer12P_BGBG_BGR888, debayer12P_GRGR_BGR888)
+			return 0;
+		case BayerFormat::GBRG:
+			SET_DEBAYER_METHODS(debayer12P_GBGB_BGR888, debayer12P_RGRG_BGR888)
+			return 0;
+		case BayerFormat::GRBG:
+			SET_DEBAYER_METHODS(debayer12P_GRGR_BGR888, debayer12P_BGBG_BGR888)
+			return 0;
+		case BayerFormat::RGGB:
+			SET_DEBAYER_METHODS(debayer12P_RGRG_BGR888, debayer12P_GBGB_BGR888)
+			return 0;
+		default:
+			break;
+		}
+	}
+
 	return invalidFmt();
 }
 
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
index 68da9508..5281c65c 100644
--- a/src/libcamera/software_isp/debayer_cpu.h
+++ b/src/libcamera/software_isp/debayer_cpu.h
@@ -110,6 +110,15 @@  private:
 	void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
 	template<bool addAlphaByte, bool ccmEnabled>
 	void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
+	/* CSI-2 packed 12-bit raw bayer format (all the 4 orders) */
+	template<bool addAlphaByte, bool ccmEnabled>
+	void debayer12P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+	template<bool addAlphaByte, bool ccmEnabled>
+	void debayer12P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+	template<bool addAlphaByte, bool ccmEnabled>
+	void debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
+	template<bool addAlphaByte, bool ccmEnabled>
+	void debayer12P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
 
 	static int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);
 	static int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);
diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index eae4c57f..53adbdce 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -78,6 +78,19 @@  int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
 		return 0;
 	}
 
+	if (bayerFormat.bitDepth == 12 &&
+	    bayerFormat.packing == BayerFormat::Packing::CSI2 &&
+	    isStandardBayerOrder(bayerFormat.order)) {
+		config.bpp = 12;
+		config.patternSize.width = 2; /* 3 bytes per *2* pixels */
+		config.patternSize.height = 2;
+		config.outputFormats = std::vector<PixelFormat>({ formats::XRGB8888,
+								  formats::ARGB8888,
+								  formats::XBGR8888,
+								  formats::ABGR8888 });
+		return 0;
+	}
+
 	LOG(Debayer, Error)
 		<< "Unsupported input format " << inputFormat;
 
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
index 0815ec9a..1deee942 100644
--- a/src/libcamera/software_isp/swstats_cpu.cpp
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -323,6 +323,58 @@  void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats)
 	SWSTATS_FINISH_LINE_STATS()
 }
 
+void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats)
+{
+	const uint8_t *src0 = src[1] + window_.x * 3 / 2;
+	const uint8_t *src1 = src[2] + window_.x * 3 / 2;
+	const unsigned int widthInBytes = window_.width * 3 / 2;
+
+	SWSTATS_START_LINE_STATS(uint8_t)
+
+	if (swapLines_)
+		std::swap(src0, src1);
+
+	/* x += 6 sample every other 2x2 block */
+	for (unsigned int x = 0; x < widthInBytes; x += 6) {
+		b = src0[x];
+		g = src0[x + 1];
+		g2 = src1[x];
+		r = src1[x + 1];
+
+		g = (g + g2) / 2;
+
+		SWSTATS_ACCUMULATE_LINE_STATS(1)
+	}
+
+	SWSTATS_FINISH_LINE_STATS()
+}
+
+void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats)
+{
+	const uint8_t *src0 = src[1] + window_.x * 3 / 2;
+	const uint8_t *src1 = src[2] + window_.x * 3 / 2;
+	const unsigned int widthInBytes = window_.width * 3 / 2;
+
+	SWSTATS_START_LINE_STATS(uint8_t)
+
+	if (swapLines_)
+		std::swap(src0, src1);
+
+	/* x += 6 sample every other 2x2 block */
+	for (unsigned int x = 0; x < widthInBytes; x += 6) {
+		g = src0[x];
+		b = src0[x + 1];
+		r = src1[x];
+		g2 = src1[x + 1];
+
+		g = (g + g2) / 2;
+
+		SWSTATS_ACCUMULATE_LINE_STATS(1)
+	}
+
+	SWSTATS_FINISH_LINE_STATS()
+}
+
 /**
  * \brief Reset state to start statistics gathering for a new frame
  * \param[in] frame The frame number
@@ -466,6 +518,32 @@  int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat
 		}
 	}
 
+	if (bayerFormat.bitDepth == 12 &&
+	    bayerFormat.packing == BayerFormat::Packing::CSI2) {
+		patternSize_.height = 2;
+		patternSize_.width = 2; /* 3 bytes for *2* pixels */
+		/* Skip every 3th and 4th line, sample every other 2x2 block */
+		ySkipMask_ = 0x02;
+		xShift_ = 0;
+		sumShift_ = 0;
+		processFrame_ = &SwStatsCpu::processBayerFrame2;
+
+		switch (bayerFormat.order) {
+		case BayerFormat::BGGR:
+		case BayerFormat::GRBG:
+			stats0_ = &SwStatsCpu::statsBGGR12PLine0;
+			swapLines_ = bayerFormat.order == BayerFormat::GRBG;
+			return 0;
+		case BayerFormat::GBRG:
+		case BayerFormat::RGGB:
+			stats0_ = &SwStatsCpu::statsGBRG12PLine0;
+			swapLines_ = bayerFormat.order == BayerFormat::RGGB;
+			return 0;
+		default:
+			break;
+		}
+	}
+
 	LOG(SwStatsCpu, Info)
 		<< "Unsupported input format " << inputCfg.pixelFormat.toString();
 	return -EINVAL;