From patchwork Mon Oct 26 08:25:59 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28605 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 E8837C3261 for ; Mon, 26 Oct 2026 08:27:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 19E5F68FDA; Mon, 26 Oct 2026 09:27:02 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="c4jUbtKP"; dkim-atps=neutral Received: from smtp-outgoing-1901.laposte.net (smtp-outgoing-1901.laposte.net [160.92.124.105]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1A0C668E58 for ; Mon, 26 Oct 2026 09:26:58 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmv02vBgzgY7m", "contextId": "d198212d-53c9-48de-ab52-90e770aca1f7"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0104.laposte.net (SMTP Server) with ESMTP id 4jCmv02vBgzgY7m; Mon, 26 Oct 2026 09:26:52 +0100 (CET) X-mail-filterd: {"version":"1.9.7", "queueID":"4jCmtz3XFkzgY7R", "contextId": "f40a76b7-a85f-4e9f-9b43-10fadbe0acf2"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian (unknown [212.133.41.21]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mlpnf0104.laposte.net (SMTP Server) with ESMTPSA id 4jCmtz3XFkzgY7R; Mon, 26 Oct 2026 09:26:51 +0100 (CET) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: bryan.odonoghue@linaro.org, barnabas.pocze@ideasonboard.com, mzamazal@redhat.com, johannes.goede@oss.qualcomm.com, laurent.pinchart@ideasonboard.com, jai.luthra@ideasonboard.com, =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v7 2/5] libcamera: software_isp: Add support for monochrome formats - Stats Date: Mon, 26 Oct 2026 09:25:59 +0100 Message-ID: <20261026082602.60417-3-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261026082602.60417-1-alain.cousinie@laposte.net> References: <20261026082602.60417-1-alain.cousinie@laposte.net> MIME-Version: 1.0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=laposte.net; s=lpn-wlmd; t=1793003217; bh=rfKnxJrV6+Fn+SwdkJupoQ/ztQDFxFpkcXp7zmETsDw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=c4jUbtKP0JrK9RBN2Ci69Xlqm34c6znZaD1NddwZ5RGM3uWnNxO8brpURi4EdMjbognxqCPuQTdZ0tTD6u2KPnb1LrsL3oeVVjTlkqg0D2LmaPitmiUDvt13nYgASorNNPBNEtYG52aWdSLX7olFeNTXu94S2yb86S8FoYBWVSR93jrYeG8uvqmm69jfX/SV9YVe4/prFZxyxPvoZy/el8IWZtsrZxZKWjj7CKl01il+5wxqefjR7Sy4l7d9Q1dWCUSFNm10wQGQehEzcZC+XvWd+3doQ7EhOz6DS9ysmvrigQFKhauf0hFDijlvJDZDMuroDVR/SzRe/ZNhzKhLIw==; 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" Signed-off-by: Alain CousiniƩ --- .../internal/software_isp/swstats_cpu.h | 17 ++ src/libcamera/software_isp/swstats_cpu.cpp | 225 ++++++++++++++++++ 2 files changed, 242 insertions(+) -- 2.55.0 diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h index 551870921..2e03c6a55 100644 --- a/include/libcamera/internal/software_isp/swstats_cpu.h +++ b/include/libcamera/internal/software_isp/swstats_cpu.h @@ -101,8 +101,25 @@ private: /* Bayer 12 bpp packed */ void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats); void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats); + /* Mono 8 bpp unpacked */ + void statsMono8Line0(const uint8_t *src[], SwIspStats &stats); + /* Mono 10 bpp unpacked */ + void statsMono10Line0(const uint8_t *src[], SwIspStats &stats); + /* Mono 10 bpp packed */ + void statsMono10PLine0(const uint8_t *src[], SwIspStats &stats); + /* Mono 12 bpp unpacked */ + void statsMono12Line0(const uint8_t *src[], SwIspStats &stats); + /* Mono 12 bpp packed */ + void statsMono12PLine0(const uint8_t *src[], SwIspStats &stats); + /* Mono 14 bpp unpacked */ + void statsMono14Line0(const uint8_t *src[], SwIspStats &stats); + /* Mono 14 bpp packed */ + void statsMono14PLine0(const uint8_t *src[], SwIspStats &stats); + /* Mono 16 bpp unpacked */ + void statsMono16Line0(const uint8_t *src[], SwIspStats &stats); void processBayerFrame2(MappedFrameBuffer &in); + void processMonoFrame2(MappedFrameBuffer &in); processFrameFn processFrame_; diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp index 7fb77ce7d..0ce08a01b 100644 --- a/src/libcamera/software_isp/swstats_cpu.cpp +++ b/src/libcamera/software_isp/swstats_cpu.cpp @@ -375,6 +375,162 @@ void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats) SWSTATS_FINISH_LINE_STATS() } +/* + * Horizontal sub-sampling optimization: evaluates 1 pixel out of 4 (x += 4) + * to reduce CPU overhead. Each sample gets a weight of +4 to preserve global + * frame statistical density required by the Auto Exposure (AE) algorithm. + */ +inline void storeMonoAccumulation(SwIspStats &stats, uint64_t value) +{ + stats.sum_.r() += value; + stats.sum_.g() += value; + stats.sum_.b() += value; +} + +void SwStatsCpu::statsMono8Line0(const uint8_t *src[], SwIspStats &stats) +{ + const uint8_t *cur = src[1] + window_.x; + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + sum += *cur; + stats.yHistogram[*cur >> 2] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : x << 2 */ + storeMonoAccumulation(stats, sum << 2); +} + +void SwStatsCpu::statsMono10Line0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * Little-Endian optimization: Unpacked 10-bit format stores data + * right-aligned in a 2-byte container. Reading via a 16-bit pointer + * allows clean data access before applying the bit-shift. + */ + const uint16_t *cur = reinterpret_cast(src[1] + window_.x * 2); + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint16_t val = *cur; + sum += val; + stats.yHistogram[(val >> 4) & 0x3F] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : x 10 >> 2 to 8bits and x << 2 */ + storeMonoAccumulation(stats, sum); +} + +void SwStatsCpu::statsMono12Line0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * 12-bit unpacked processing. Data is read via a native 16-bit pointer + * and right-shifted to fit the histogram range. + */ + const uint16_t *cur = reinterpret_cast(src[1] + window_.x * 2); + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint16_t val = *cur; + sum += val; + stats.yHistogram[(val >> 6) & 0x3F] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : 12 >> 4 to 8bits and x << 2 */ + storeMonoAccumulation(stats, sum >> 2); +} + +void SwStatsCpu::statsMono14Line0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * 14-bit unpacked processing. Data is read via a native 16-bit pointer + * and right-shifted to fit the histogram range. + */ + const uint16_t *cur = reinterpret_cast(src[1] + window_.x * 2); + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint16_t val = *cur; + sum += val; + stats.yHistogram[(val >> 8) & 0x3F] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : 14 >> 6 to 8bits and x << 2 */ + storeMonoAccumulation(stats, sum >> 4); +} + +void SwStatsCpu::statsMono10PLine0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * MIPI CSI-2 10-bit packed: 4 pixels in 5 bytes. Stepping 5 bytes + * evaluates the head MSB pixel of each block as a native 8-bit value. + */ + const uint8_t *cur = src[1] + window_.x * 5 / 4; + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint8_t val = *cur; + sum += val; + stats.yHistogram[val >> 2] += 4; + cur += 5; + } + /* Process 4 pixels per iteration : first byte x << 2 */ + storeMonoAccumulation(stats, sum << 2); +} + +void SwStatsCpu::statsMono12PLine0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * MIPI CSI-2 12-bit packed: 2 pixels in 3 bytes. Stepping 6 bytes + * advances by two full blocks for optimal data alignment. + */ + const uint8_t *cur = src[1] + window_.x * 3 / 2; + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint8_t val = *cur; + sum += val; + stats.yHistogram[val >> 2] += 4; + cur += 6; + } + /* Process 4 pixels per iteration : first byte x << 2 */ + storeMonoAccumulation(stats, sum << 2); +} + +void SwStatsCpu::statsMono14PLine0(const uint8_t *src[], SwIspStats &stats) +{ + /* + * MIPI CSI-2 14-bit packed: 4 pixels in 6 bytes. Stepping 6 bytes + * advances by two full blocks for optimal data alignment. + */ + const uint8_t *cur = src[1] + window_.x * 7 / 4; + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint8_t val = *cur; + sum += val; + stats.yHistogram[val >> 2] += 4; + cur += 7; + } + /* Process 4 pixels per iteration : first byte x << 2 */ + storeMonoAccumulation(stats, sum << 2); +} + +void SwStatsCpu::statsMono16Line0(const uint8_t *src[], SwIspStats &stats) +{ + const uint16_t *cur = reinterpret_cast(src[1] + window_.x * 2); + uint64_t sum = 0; + + for (unsigned int x = 0; x < window_.width; x += 4) { + uint16_t val = *cur; + sum += val; + stats.yHistogram[val >> 10] += 4; + cur += 4; + } + /* Process 4 pixels per iteration : 16 >> 8 to 8bits and x << 2 */ + storeMonoAccumulation(stats, sum >> 6); +} + /** * \brief Reset state to start statistics gathering for a new frame * \param[in] frame The frame number @@ -494,6 +650,56 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat uint8_t bitDepth = bayerFormat.bitDepth; + if (bayerFormat.order == BayerFormat::Order::MONO) { + /* No skip line for mono */ + ySkipMask_ = 0; + xShift_ = 0; + sumShift_ = 0; + processFrame_ = &SwStatsCpu::processMonoFrame2; + + if (bayerFormat.packing == BayerFormat::Packing::None) { + patternSize_.width = 2; + patternSize_.height = 2; + + switch (bitDepth) { + case 8: + stats0_ = &SwStatsCpu::statsMono8Line0; + return 0; + case 10: + stats0_ = &SwStatsCpu::statsMono10Line0; + return 0; + case 12: + stats0_ = &SwStatsCpu::statsMono12Line0; + return 0; + case 14: + stats0_ = &SwStatsCpu::statsMono14Line0; + return 0; + case 16: + stats0_ = &SwStatsCpu::statsMono16Line0; + return 0; + } + } + if (bayerFormat.packing == BayerFormat::Packing::CSI2) { + patternSize_.height = 2; + + switch (bitDepth) { + case 10: + patternSize_.width = 4; + stats0_ = &SwStatsCpu::statsMono10PLine0; + return 0; + case 12: + patternSize_.width = 2; + stats0_ = &SwStatsCpu::statsMono12PLine0; + return 0; + case 14: + patternSize_.width = 4; + stats0_ = &SwStatsCpu::statsMono14PLine0; + return 0; + } + } + return -EINVAL; + } + if ((bitDepth == 10 || bitDepth == 12) && bayerFormat.packing == BayerFormat::Packing::CSI2) { if (bitDepth == 10) @@ -594,6 +800,25 @@ void SwStatsCpu::processBayerFrame2(MappedFrameBuffer &in) } } +void SwStatsCpu::processMonoFrame2(MappedFrameBuffer &in) +{ + const uint8_t *src = in.planes()[0].data(); + const uint8_t *linePointers[3]; + + src += window_.y * stride_; + + for (unsigned int y = 0; y < window_.height; y++) { + if (y & ySkipMask_) { + src += stride_; + continue; + } + + linePointers[1] = src; + (this->*stats0_)(linePointers, stats_[0]); + src += stride_; + } +} + /** * \brief Calculate statistics for a frame in one go * \param[in] frame The frame number