From patchwork Tue Jun 2 11:55:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jai Luthra X-Patchwork-Id: 26816 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 68337BDCBC for ; Tue, 2 Jun 2026 11:55:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 772606303C; Tue, 2 Jun 2026 13:55:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nGAq80kX"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BB77861754 for ; Tue, 2 Jun 2026 13:55:50 +0200 (CEST) Received: from mail.ideasonboard.com (unknown [IPv6:2401:4900:1c66:476d:c684:fe78:389f:7375]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EE3A997F; Tue, 2 Jun 2026 13:55:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1780401327; bh=bdaFtEyren7j3fafwqirYFkk/BVWAj+iH6JaSZsxoSY=; h=From:Date:Subject:To:Cc:From; b=nGAq80kXCeYyNhoyw1RrimN6UDTDpsEDHh/Fpotdn+PilxfNQ1XrA/CU1cJzk73Hs HblhHlk6gBS7+EFu1RG4+5Q4MtWuY67wGQ+diPVcjhf5hyR6Hu44XvbvBioa1nq9v+ NiSBjX5Rb4uToLOse4xHn8DdWHVsE/9qL3aalK4k= From: Jai Luthra Date: Tue, 02 Jun 2026 17:25:30 +0530 Subject: [PATCH v2] libcamera: software_isp: Support 12-bit CSI2 packed raw bayer formats MIME-Version: 1.0 Message-Id: <20260602-swisp_12p-v2-1-2e6d25db7081@ideasonboard.com> X-B4-Tracking: v=1; b=H4sIAAAAAAAC/23MQQ6CMBCF4auQWVvTDhaIK+5hiCntKLOQNh2DG sLdraxd/i8v3wpCmUngXK2QaWHhOJfAQwV+cvOdFIfSgBobbRGVvFjS1WBSjQuIdAq6GzWUf8p 04/duXYbSE8sz5s9OL+a3/lMWo4wiY6yvbd21Y9tzICdxHqPL4ejjA4Zt275H1mb+qQAAAA== X-Change-ID: 20260522-swisp_12p-6ad22e4d08b0 To: Hans de Goede , Milan Zamazal , Bryan O'Donoghue Cc: libcamera-devel@lists.libcamera.org, Jai Luthra X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=13495; i=jai.luthra@ideasonboard.com; h=from:subject:message-id; bh=bdaFtEyren7j3fafwqirYFkk/BVWAj+iH6JaSZsxoSY=; b=owEBbQKS/ZANAwAKAUPekfkkmnFFAcsmYgBqHsS2PXbMLoG5PPgiClkQnTtOfNv02yJBGsO4/ C9wdeXKggKJAjMEAAEKAB0WIQRN4NgY5dV16NRar8VD3pH5JJpxRQUCah7EtgAKCRBD3pH5JJpx RTFDD/sHSuyAYN3Asqm6p0HUVu6drpM0N+u4qnUokHXJVA9Lkm6c6uvmdqh/LGU4yX8tlhf73pn 4KbbYUgR2eXlLPgbDq7Otx3NT9XiE8HohAzDwXyjuxxwWqvvYQWpiqroo8aunMBhygI7kpIW/q2 5i1ND/JFFb7UnBmnbLmCdRW7XmVWQCLV3DCnITv+dNPvjnchkv+SOLM1jL6y3bUnejpNCO4lra7 +mIDgbUvr9bg8J+N58brweXtHM9ABCUApB+CnKJgHxgWuWflzWGjA4FmXQIgwcWvQXJK1OA8wSz yH8QZO+p9S52LMR7v04tKgr5hgkupqF12oDNmCVjfdop5l3026rwxEMZIl7Rlz7UG1lksseckyB E8JIynS4ndND5ibUyERj64TjLm7fEYs3kvu9qDC66abT6mMNZIFuCILkXlOqUreoj929Nkh3TCs Ahmj6fDh3tIN8UE7qnXAvr0THtlWzGLVLGFiB60qndWIaM99NBa4l7q3EmdaJ/JVhQRa/VsFwyC pG9ZInehZSa+sTJ5QayJc0t9+AkucL5nap/Nasp1hDUD2AqruBHe+ra8RdvXBuv5H92jVGPdteC 0yKjsVSRZnCMO2XRSlKXbFElfwrbMJMJPgoXchEVF5tE7dbrTCs8QiWQQwCEDobvjd/4MgiKc6j nVUlylgb/HybEyQ== X-Developer-Key: i=jai.luthra@ideasonboard.com; a=openpgp; fpr=4DE0D818E5D575E8D45AAFC543DE91F9249A7145 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" 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 --- 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 --- Changes in v2: - Handle 12bit/10bit packed stats together - Reuse output format definition across all input formats for each (CPU/GPU) pipeline - Fix wrong odd pixel bayer order comments in debayer12P_xxx functions - Link to v1: https://lore.kernel.org/r/20260522-swisp_12p-v1-1-e115c35387b7@ideasonboard.com --- .../libcamera/internal/software_isp/swstats_cpu.h | 3 + src/libcamera/software_isp/debayer_cpu.cpp | 122 +++++++++++++++++++-- src/libcamera/software_isp/debayer_cpu.h | 9 ++ src/libcamera/software_isp/debayer_egl.cpp | 25 +++-- src/libcamera/software_isp/swstats_cpu.cpp | 67 ++++++++++- 5 files changed, 202 insertions(+), 24 deletions(-) --- base-commit: ad5506544b798539e1d094ec2eac0b2a1ecbdc4d change-id: 20260522-swisp_12p-6ad22e4d08b0 Best regards, diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h index b5348c6fe..13fa75826 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 1f9b24da0..d2596d32b 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 +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 BGGR -> GBRG */ + GBRG_BGR888(1, 2, 1) + /* Skip 3rd src byte with 2 x 4 least-significant-bits */ + x++; + } +} + +template +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 GRBG -> RGGB */ + RGGB_BGR888(1, 2, 1) + /* Skip 3rd src byte with 2 x 4 least-significant-bits */ + x++; + } +} + +template +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 GBRG -> BGGR */ + BGGR_BGR888(1, 2, 1) + /* Skip 3rd src byte with 2 x 4 least-significant-bits */ + x++; + } +} + +template +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 @@ -360,6 +432,12 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf { BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + std::vector outputFormats = { formats::RGB888, + formats::XRGB8888, + formats::ARGB8888, + formats::BGR888, + formats::XBGR8888, + formats::ABGR8888 }; if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && bayerFormat.packing == BayerFormat::Packing::None && @@ -367,12 +445,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf config.bpp = (bayerFormat.bitDepth + 7) & ~7; config.patternSize.width = 2; config.patternSize.height = 2; - config.outputFormats = std::vector({ formats::RGB888, - formats::XRGB8888, - formats::ARGB8888, - formats::BGR888, - formats::XBGR8888, - formats::ABGR8888 }); + config.outputFormats = outputFormats; return 0; } @@ -382,12 +455,17 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf config.bpp = 10; config.patternSize.width = 4; /* 5 bytes per *4* pixels */ config.patternSize.height = 2; - config.outputFormats = std::vector({ formats::RGB888, - formats::XRGB8888, - formats::ARGB8888, - formats::BGR888, - formats::XBGR8888, - formats::ABGR8888 }); + config.outputFormats = outputFormats; + 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 = outputFormats; return 0; } @@ -538,6 +616,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 68da95083..5281c65cb 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 void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 12-bit raw bayer format (all the 4 orders) */ + template + void debayer12P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); + template + void debayer12P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); + template + void debayer12P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]); + template + 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 7b9e02d90..9ea892f11 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -52,16 +52,18 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); + std::vector outputFormats = { formats::XRGB8888, + formats::ARGB8888, + formats::XBGR8888, + formats::ABGR8888 }; + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10) && bayerFormat.packing == BayerFormat::Packing::None && isStandardBayerOrder(bayerFormat.order)) { config.bpp = (bayerFormat.bitDepth + 7) & ~7; config.patternSize.width = 2; config.patternSize.height = 2; - config.outputFormats = std::vector({ formats::XRGB8888, - formats::ARGB8888, - formats::XBGR8888, - formats::ABGR8888 }); + config.outputFormats = outputFormats; return 0; } @@ -71,10 +73,17 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf config.bpp = 10; config.patternSize.width = 4; /* 5 bytes per *4* pixels */ config.patternSize.height = 2; - config.outputFormats = std::vector({ formats::XRGB8888, - formats::ARGB8888, - formats::XBGR8888, - formats::ABGR8888 }); + config.outputFormats = outputFormats; + 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 = outputFormats; return 0; } diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp index 0815ec9a3..2f57a5f33 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 @@ -440,10 +492,17 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat } } - if (bayerFormat.bitDepth == 10 && + uint8_t bitDepth = bayerFormat.bitDepth; + + if ((bitDepth == 10 || bitDepth == 12) && bayerFormat.packing == BayerFormat::Packing::CSI2) { + if (bitDepth == 10) + patternSize_.width = 4; /* 5 bytes per *4* pixels */ + else + patternSize_.width = 2; /* 3 bytes for *2* pixels */ + patternSize_.height = 2; - patternSize_.width = 4; /* 5 bytes per *4* pixels */ + /* Skip every 3th and 4th line, sample every other 2x2 block */ ySkipMask_ = 0x02; xShift_ = 0; @@ -453,12 +512,12 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat switch (bayerFormat.order) { case BayerFormat::BGGR: case BayerFormat::GRBG: - stats0_ = &SwStatsCpu::statsBGGR10PLine0; + stats0_ = (bitDepth == 10) ? &SwStatsCpu::statsBGGR10PLine0 : &SwStatsCpu::statsBGGR12PLine0; swapLines_ = bayerFormat.order == BayerFormat::GRBG; return 0; case BayerFormat::GBRG: case BayerFormat::RGGB: - stats0_ = &SwStatsCpu::statsGBRG10PLine0; + stats0_ = (bitDepth == 10) ? &SwStatsCpu::statsGBRG10PLine0 : &SwStatsCpu::statsGBRG12PLine0; swapLines_ = bayerFormat.order == BayerFormat::RGGB; return 0; default: