From patchwork Sat Oct 17 11:10:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alain Cousinie X-Patchwork-Id: 28493 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 42C2BC32F4 for ; Sat, 17 Oct 2026 11:11:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 11E7F68E64; Sat, 17 Oct 2026 13:11:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=laposte.net header.i=@laposte.net header.b="J+/bDJ1b"; dkim-atps=neutral Received: from smtp-outgoing-2002.laposte.net (smtp-outgoing-2002.laposte.net [160.92.124.109]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5EB0068E43 for ; Sat, 17 Oct 2026 13:11:49 +0200 (CEST) X-mail-filterd: {"version":"1.9.7", "queueID":"4j6JzP0VdBzdZLZ", "contextId": "afd5e26c-bfa9-4917-8d85-c46b1177b630"} Received: from outgoing-mail.laposte.net (localhost.localdomain [127.0.0.1]) by mlpnf0106.laposte.net (SMTP Server) with ESMTP id 4j6JzP0VdBzdZLZ; Sat, 17 Oct 2026 13:11:45 +0200 (CEST) X-mail-filterd: {"version":"1.9.7", "queueID":"4j6JzN5vvhzdZLg", "contextId": "f5805657-7ca2-4ec9-aa9b-08a2e4e16e5f"} X-lpn-mailing: LEGIT X-lpn-spamrating: 36 X-lpn-spamlevel: not-spam Received: from debian.home (m-dj.hd.free.fr [82.67.82.126]) (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 mlpnf0106.laposte.net (SMTP Server) with ESMTPSA id 4j6JzN5vvhzdZLg; Sat, 17 Oct 2026 13:11:44 +0200 (CEST) From: alain.cousinie@laposte.net To: libcamera-devel@lists.libcamera.org Cc: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Milan Zamazal , Hans de Goede , Bryan O'Donoghue , =?utf-8?q?Alain_Cousini=C3=A9?= Subject: [PATCH v6 2/4] libcamera: software_isp: Add support for monochrome formats - Stats Date: Sat, 17 Oct 2026 13:10:29 +0200 Message-ID: <20261017111036.700859-3-alain.cousinie@laposte.net> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261017111036.700859-1-alain.cousinie@laposte.net> References: <10d396ff-2270-46ef-a641-967df76917be@laposte.net> <20261017111036.700859-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=1792235509; bh=yBriJ1xTeOKbSD+jhg6r8vHshXuwheh86h4Rq8JgPTk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Content-Type:Content-Transfer-Encoding; b=J+/bDJ1bqsBi3hREpnjdK5Cmm1URXRbu9WSbcHeUcZviDYL66AxM7iWlzmUN+WL0bigobo7jp8oz+xN94ZLNAkJau1ht8DKRshXVkgTJJF8Cqq3etXF4xmPaWsko1BjINdTXGbgweeOIJZcRaGIMSINlS8SMn2YqyJ2YmlMxDMN1dROyWx5XqU3E3Vem3zKmic2sw7CAkZhghe6OL+QWV0gbFU0X8qvbw3D7W1JBuXguEkl04MOk3/C4PMSLBjDhTQcele5Tbxg04jSa60Vrk1oJasAZdNXkU5ZCKYMHAmSaBZzUi1titEJvOf/E138XJBSehKYQznS0oBj+RLeJTQ==; 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 | 13 ++ src/libcamera/software_isp/swstats_cpu.cpp | 180 ++++++++++++++++++ 2 files changed, 193 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..f702cf126 100644 --- a/include/libcamera/internal/software_isp/swstats_cpu.h +++ b/include/libcamera/internal/software_isp/swstats_cpu.h @@ -101,8 +101,21 @@ 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 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..f023563f1 100644 --- a/src/libcamera/software_isp/swstats_cpu.cpp +++ b/src/libcamera/software_isp/swstats_cpu.cpp @@ -375,6 +375,124 @@ 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::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::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 +612,49 @@ 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 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; + } + } + return -EINVAL; + } + if ((bitDepth == 10 || bitDepth == 12) && bayerFormat.packing == BayerFormat::Packing::CSI2) { if (bitDepth == 10) @@ -594,6 +755,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