From patchwork Fri May 22 14:37:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jai Luthra X-Patchwork-Id: 26797 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 14103BDCBD for ; Fri, 22 May 2026 14:39:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 757A562FEC; Fri, 22 May 2026 16:39:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="bQpBWEbY"; 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 DB0EE62DC4 for ; Fri, 22 May 2026 16:39:32 +0200 (CEST) Received: from mail.ideasonboard.com (unknown [IPv6:2a01:cb1d:8f2:800:ad48:920a:da6f:a034]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 29C60244; Fri, 22 May 2026 16:39:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1779460757; bh=AaufH8LsxLwYYBszCyHm+I+WeV93Vdbv6vVxDEqJoZQ=; h=From:Date:Subject:To:Cc:From; b=bQpBWEbYZZGqHWN4RxVWhb/q01dSKchIv8JWrHimtiLq+2TQOwXWLQ9J06yAnWlKX 8RCpmI1QVlYFgf7hgocdGirQhGhgQ3gXQ9Wx7w3X0STMmNYvziumi3Z3H9I9hFX1bT 9Q2YImb2byh7WrrFIRi8PpDEpCdfHaSfkMjkUix8= From: Jai Luthra Date: Fri, 22 May 2026 16:37:56 +0200 Subject: [PATCH] libcamera: software_isp: Support 12-bit CSI2 packed raw bayer formats MIME-Version: 1.0 Message-Id: <20260522-swisp_12p-v1-1-e115c35387b7@ideasonboard.com> X-B4-Tracking: v=1; b=H4sIAAAAAAAC/6tWKk4tykwtVrJSqFYqSi3LLM7MzwNyDHUUlJIzE vPSU3UzU4B8JSMDIzMDUyMj3eLyzOKCeEOjAl2zxBQjo1STFAOLJAMloPqCotS0zAqwWdGxtbU APurGZFsAAAA= X-Change-ID: 20260522-swisp_12p-6ad22e4d08b0 To: Hans de Goede , Milan Zamazal Cc: libcamera-devel@lists.libcamera.org, Jai Luthra X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=10647; i=jai.luthra@ideasonboard.com; h=from:subject:message-id; bh=AaufH8LsxLwYYBszCyHm+I+WeV93Vdbv6vVxDEqJoZQ=; b=owEBbQKS/ZANAwAKAUPekfkkmnFFAcsmYgBqEGqbib0p/2tH0QfL1jr2vf2mzPPfKQ0UqNYFK +6he6c6CJ6JAjMEAAEKAB0WIQRN4NgY5dV16NRar8VD3pH5JJpxRQUCahBqmwAKCRBD3pH5JJpx RapAEAChkVQwCf+U+cbDQ6Q/yRW14hzvQaULAAYAE021y7EgLrCp2uaPY3MiEdInMXYVQ7Dtj5i EiZKPb0Ssiwea8+VZJ4yWsNot1Zt0ytUyx3G/2uIoBOCc2eY4ArBRpPEr9z/VaGRlYe24xNf4dz eM9BDBSRkCvMfeKtPDAlap20NzyhuvW8LpgCsxWWW3hlRn6nP2u/ZgM5jDK4YPLMgOl8MoKCG6f k/mQ3RhrKD5xdBrKw47C9V1AcPeyKnem8/0FLionncOq/BOlfjpH6rOTp1qCA3UIitsngfiOQ8n bn3d634q5GJ+8BJRi+EwIOItv1+UPnCcGMu/OF48CoUE2ayNlz8jNPyJrqltKbM1h1JXtqp01Jy Rsug+U9zKvPFdLMqogAArKsjr3+PeIYNq2Lx9gtLikKCOg9MbZF7/6PC7DIe2s0wLDFxlWwKpx6 uy9OaGBX3NA5l8wLbl62kfZoQO7FPR5crcbH/l1Kgmelxv8aQoOoY6yJeb7j6wQOJMDY3DF2Ga0 nqtcHEQFH9KNm79XfHXjRrmZt+xWdvsm0BenKLJ1i03p9LHMpibr5cDSfCMdYN4M4duVahkn8o1 0NJvOf9CDCZ/j5npHS/1b3OqrD1atFqOdfu0My6+yEJJNmTkyNVA1GFadJOiNNsa2KfjZp6388C 9FJSO4+PLRXywkw== 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. --- .../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, 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 +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 +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 +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 +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({ 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 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 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({ 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;