{"id":26182,"url":"https://patchwork.libcamera.org/api/patches/26182/?format=json","web_url":"https://patchwork.libcamera.org/patch/26182/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260216203034.27558-14-mzamazal@redhat.com>","date":"2026-02-16T20:30:32","name":"[RFC,v2,13/14] libcamera: software_isp: Share statistics buffers with IPA","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"c951f24ef473797a7a1ce55bd8eba7429e51e793","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/?format=json","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/26182/mbox/","series":[{"id":5795,"url":"https://patchwork.libcamera.org/api/series/5795/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5795","date":"2026-02-16T20:30:19","name":"Software ISP: Share params and stats buffers","version":2,"mbox":"https://patchwork.libcamera.org/series/5795/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/26182/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/26182/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 1510FC0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 16 Feb 2026 20:31:10 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C6FE462237;\n\tMon, 16 Feb 2026 21:31:09 +0100 (CET)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.133.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9313E6220C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Feb 2026 21:31:07 +0100 (CET)","from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com\n\t(ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97])\n\tby relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n\tcipher=TLS_AES_256_GCM_SHA384) id us-mta-650-uzbJVJ2oMmuPK684sTuFxA-1;\n\tMon, 16 Feb 2026 15:31:04 -0500","from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com\n\t(mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com\n\t[10.30.177.93])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (2048 bits)\n\tserver-digest SHA256) (No client certificate requested)\n\tby mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTPS\n\tid 289F31800370 for <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Feb 2026 20:31:04 +0000 (UTC)","from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.25])\n\tby mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTP id 208A91800464; Mon, 16 Feb 2026 20:31:02 +0000 (UTC)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"WqYKzWbp\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1771273866;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tcontent-transfer-encoding:content-transfer-encoding:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=6GpWSVOCYfk2sKMuYL9BWe2Bb/IVbC1yntBaT9i9skE=;\n\tb=WqYKzWbpWZLbNew0QYDpSJma5RpCKMQCELWi4Q6EFchMBXlQL8OvUXWbdCEuRY2gsv/rLt\n\tSitZfhu3lmMuAQ2ozwRXbGA43Y+53yD1nUEcqyAXUL1wkgmiKPhtuPNdJ/7VH32hCeMGRK\n\t2kBNS79AUufLSFunF0dCNXbzC6tPwa4=","X-MC-Unique":"uzbJVJ2oMmuPK684sTuFxA-1","X-Mimecast-MFC-AGG-ID":"uzbJVJ2oMmuPK684sTuFxA_1771273864","From":"Milan Zamazal <mzamazal@redhat.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Milan Zamazal <mzamazal@redhat.com>","Subject":"[RFC PATCH v2 13/14] libcamera: software_isp: Share statistics\n\tbuffers with IPA","Date":"Mon, 16 Feb 2026 21:30:32 +0100","Message-ID":"<20260216203034.27558-14-mzamazal@redhat.com>","In-Reply-To":"<20260216203034.27558-1-mzamazal@redhat.com>","References":"<20260216203034.27558-1-mzamazal@redhat.com>","MIME-Version":"1.0","X-Scanned-By":"MIMEDefang 3.4.1 on 10.30.177.93","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"keXBiYsICvHqsTSqpy-E0hFRyOf02nWjMaYjIy7PiU8_1771273864","X-Mimecast-Originator":"redhat.com","Content-Transfer-Encoding":"8bit","content-type":"text/plain; charset=\"US-ASCII\"; x-default=true","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"The last step to complete statistics buffer sharing is to pass all the\nallocated statistics buffers to the IPA and refer to them using their ids.\n\nThis allows to remove the buffer copying in SwStatsCpu::finishFrame.\nIn order to track the current statistics buffer in SwStatsCpu instead,\nwe change SwStatsCpu::stats_ to a wrapper structure.  This is because we\nneed a reference to a shared mem object but a class attribute cannot be\na dynamically assigned reference.  This hack works around the problem.\n\nWe can also remove now the methods that served for handling the former\nsingle buffer shared between debayering and IPA.\n\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\n---\n .../internal/software_isp/swstats_cpu.h       | 12 +++---\n include/libcamera/ipa/soft.mojom              |  2 +-\n src/ipa/simple/soft_simple.cpp                | 43 ++++++++++---------\n src/libcamera/software_isp/debayer.cpp        | 10 -----\n src/libcamera/software_isp/debayer.h          |  2 -\n src/libcamera/software_isp/debayer_cpu.h      |  1 -\n src/libcamera/software_isp/debayer_egl.h      |  1 -\n src/libcamera/software_isp/software_isp.cpp   |  2 +-\n src/libcamera/software_isp/swstats_cpu.cpp    | 35 +++++----------\n 9 files changed, 42 insertions(+), 66 deletions(-)","diff":"diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h\nindex 050b13ec1..2592da94a 100644\n--- a/include/libcamera/internal/software_isp/swstats_cpu.h\n+++ b/include/libcamera/internal/software_isp/swstats_cpu.h\n@@ -12,6 +12,7 @@\n #pragma once\n \n #include <map>\n+#include <memory>\n #include <stdint.h>\n \n #include <libcamera/base/signal.h>\n@@ -46,10 +47,6 @@ public:\n \t */\n \tstatic constexpr uint32_t kStatPerNumFrames = 4;\n \n-\tbool isValid() const { return sharedStats_->begin()->second.fd().isValid(); }\n-\n-\tconst SharedFD &getStatsFD() { return sharedStats_->begin()->second.fd(); }\n-\n \tconst Size &patternSize() { return patternSize_; }\n \n \tint configure(const StreamConfiguration &inputCfg);\n@@ -118,7 +115,12 @@ private:\n \tunsigned int stride_;\n \n \tstd::unique_ptr<std::map<uint32_t, SharedMemObject<SwIspStats>>> sharedStats_;\n-\tSwIspStats stats_;\n+\tstruct SwIspStatsRef {\n+\t\tSharedMemObject<SwIspStats> &stats;\n+\t\tSwIspStatsRef(SharedMemObject<SwIspStats> &_stats)\n+\t\t\t: stats(_stats) {};\n+\t};\n+\tstd::unique_ptr<SwIspStatsRef> stats_;\n \tBenchmark bench_;\n };\n \ndiff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom\nindex 5ebe2864c..129f2f6b4 100644\n--- a/include/libcamera/ipa/soft.mojom\n+++ b/include/libcamera/ipa/soft.mojom\n@@ -14,7 +14,7 @@ struct IPAConfigInfo {\n \n interface IPASoftInterface {\n \tinit(libcamera.IPASettings settings,\n-\t     libcamera.SharedFD fdStats,\n+\t     array<libcamera.SharedFD> fdStats,\n \t     array<libcamera.SharedFD> fdParams,\n \t     libcamera.IPACameraSensorInfo sensorInfo,\n \t     libcamera.ControlInfoMap sensorControls)\ndiff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\nindex 4a681babe..b36894d5d 100644\n--- a/src/ipa/simple/soft_simple.cpp\n+++ b/src/ipa/simple/soft_simple.cpp\n@@ -52,7 +52,7 @@ public:\n \t~IPASoftSimple();\n \n \tint init(const IPASettings &settings,\n-\t\t const SharedFD &fdStats,\n+\t\t const std::vector<SharedFD> &fdStats,\n \t\t const std::vector<SharedFD> &fdParams,\n \t\t const IPACameraSensorInfo &sensorInfo,\n \t\t const ControlInfoMap &sensorControls,\n@@ -77,7 +77,7 @@ private:\n \tvoid updateExposure(double exposureMSV);\n \n \tstd::map<unsigned int, DebayerParams *> paramsBuffers_;\n-\tSwIspStats *stats_;\n+\tstd::map<unsigned int, SwIspStats *> statsBuffers_;\n \tstd::unique_ptr<CameraSensorHelper> camHelper_;\n \tControlInfoMap sensorInfoMap_;\n \n@@ -87,14 +87,14 @@ private:\n \n IPASoftSimple::~IPASoftSimple()\n {\n-\tif (stats_)\n-\t\tmunmap(stats_, sizeof(SwIspStats));\n+\tfor (auto &item : statsBuffers_)\n+\t\tmunmap(item.second, sizeof(SwIspStats));\n \tfor (auto &item : paramsBuffers_)\n \t\tmunmap(item.second, sizeof(DebayerParams));\n }\n \n int IPASoftSimple::init(const IPASettings &settings,\n-\t\t\tconst SharedFD &fdStats,\n+\t\t\tconst std::vector<SharedFD> &fdStats,\n \t\t\tconst std::vector<SharedFD> &fdParams,\n \t\t\tconst IPACameraSensorInfo &sensorInfo,\n \t\t\tconst ControlInfoMap &sensorControls,\n@@ -138,11 +138,21 @@ int IPASoftSimple::init(const IPASettings &settings,\n \t\treturn ret;\n \n \t*ccmEnabled = context_.ccmEnabled;\n-\tstats_ = nullptr;\n+\tfor (auto &sharedFd : fdStats) {\n+\t\tif (!sharedFd.isValid()) {\n+\t\t\tLOG(IPASoft, Error) << \"Invalid Statistics handle\";\n+\t\t\treturn -ENODEV;\n+\t\t}\n \n-\tif (!fdStats.isValid()) {\n-\t\tLOG(IPASoft, Error) << \"Invalid Statistics handle\";\n-\t\treturn -ENODEV;\n+\t\tvoid *mem = mmap(nullptr, sizeof(SwIspStats), PROT_READ,\n+\t\t\t\t MAP_SHARED, sharedFd.get(), 0);\n+\t\tif (mem == MAP_FAILED) {\n+\t\t\tLOG(IPASoft, Error) << \"Unable to map Statistics\";\n+\t\t\treturn -errno;\n+\t\t}\n+\n+\t\tASSERT(sharedFd.get() >= 0);\n+\t\tstatsBuffers_[sharedFd.get()] = static_cast<SwIspStats *>(mem);\n \t}\n \n \tfor (auto &sharedFd : fdParams) {\n@@ -168,17 +178,6 @@ int IPASoftSimple::init(const IPASettings &settings,\n \t\tparamsBuffers_[sharedFd.get()] = params;\n \t}\n \n-\t{\n-\t\tvoid *mem = mmap(nullptr, sizeof(SwIspStats), PROT_READ,\n-\t\t\t\t MAP_SHARED, fdStats.get(), 0);\n-\t\tif (mem == MAP_FAILED) {\n-\t\t\tLOG(IPASoft, Error) << \"Unable to map Statistics\";\n-\t\t\treturn -errno;\n-\t\t}\n-\n-\t\tstats_ = static_cast<SwIspStats *>(mem);\n-\t}\n-\n \tControlInfoMap::Map ctrlMap = context_.ctrlMap;\n \t*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);\n \n@@ -305,6 +304,8 @@ void IPASoftSimple::processStats(const uint32_t frame,\n {\n \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n \n+\tconst SwIspStats *stats = statsBuffers_.at(statsBufferId);\n+\n \tframeContext.sensor.exposure =\n \t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n \tint32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n@@ -312,7 +313,7 @@ void IPASoftSimple::processStats(const uint32_t frame,\n \n \tControlList metadata(controls::controls);\n \tfor (auto const &algo : algorithms())\n-\t\talgo->process(context_, frame, frameContext, stats_, metadata);\n+\t\talgo->process(context_, frame, frameContext, stats, metadata);\n \tmetadataReady.emit(frame, metadata);\n \tstatsProcessed.emit(statsBufferId);\n \ndiff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp\nindex f3f241e14..3a9049d0b 100644\n--- a/src/libcamera/software_isp/debayer.cpp\n+++ b/src/libcamera/software_isp/debayer.cpp\n@@ -143,16 +143,6 @@ Debayer::~Debayer()\n  * \\return The valid size ranges or an empty range if there are none\n  */\n \n-/**\n- * \\fn const SharedFD &Debayer::getStatsFD()\n- * \\brief Get the file descriptor for the statistics\n- *\n- * This file descriptor provides access to the output statistics buffer\n- * associated with the current debayering process.\n- *\n- * \\return The file descriptor pointing to the statistics data\n- */\n-\n /**\n  * \\fn unsigned int Debayer::frameSize()\n  * \\brief Get the output frame size\ndiff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h\nindex 9f08a7f4e..e640c95a0 100644\n--- a/src/libcamera/software_isp/debayer.h\n+++ b/src/libcamera/software_isp/debayer.h\n@@ -57,8 +57,6 @@ public:\n \n \tvirtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0;\n \n-\tvirtual const SharedFD &getStatsFD() = 0;\n-\n \tunsigned int frameSize() { return outputConfig_.frameSize; }\n \n \tSignal<FrameBuffer *> inputBufferReady;\ndiff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h\nindex 8d38fa43f..7f4428bc0 100644\n--- a/src/libcamera/software_isp/debayer_cpu.h\n+++ b/src/libcamera/software_isp/debayer_cpu.h\n@@ -48,7 +48,6 @@ public:\n \t\t     FrameBuffer *input,\n \t\t     FrameBuffer *output);\n \tSizeRange sizes(PixelFormat inputFormat, const Size &inputSize);\n-\tconst SharedFD &getStatsFD() { return stats_->getStatsFD(); }\n \n private:\n \t/**\ndiff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h\nindex 3504c0d4f..344c4a4d2 100644\n--- a/src/libcamera/software_isp/debayer_egl.h\n+++ b/src/libcamera/software_isp/debayer_egl.h\n@@ -60,7 +60,6 @@ public:\n \tint start();\n \tvoid stop();\n \n-\tconst SharedFD &getStatsFD() { return stats_->getStatsFD(); }\n \tunsigned int frameSize();\n \n \tSizeRange sizes(PixelFormat inputFormat, const Size &inputSize);\ndiff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp\nindex f3ee5773b..047d0fb22 100644\n--- a/src/libcamera/software_isp/software_isp.cpp\n+++ b/src/libcamera/software_isp/software_isp.cpp\n@@ -154,7 +154,7 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,\n \t}\n \n \tret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },\n-\t\t\t debayer_->getStatsFD(),\n+\t\t\t fdStats,\n \t\t\t fdParams,\n \t\t\t sensorInfo,\n \t\t\t sensor->controls(),\ndiff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp\nindex b9f36b383..22164e33d 100644\n--- a/src/libcamera/software_isp/swstats_cpu.cpp\n+++ b/src/libcamera/software_isp/swstats_cpu.cpp\n@@ -48,20 +48,6 @@ namespace libcamera {\n  * exchange.\n  */\n \n-/**\n- * \\fn bool SwStatsCpu::isValid() const\n- * \\brief Gets whether the statistics object is valid\n- *\n- * \\return True if it's valid, false otherwise\n- */\n-\n-/**\n- * \\fn const SharedFD &SwStatsCpu::getStatsFD()\n- * \\brief Get the file descriptor for the statistics\n- *\n- * \\return The file descriptor\n- */\n-\n /**\n  * \\fn const Size &SwStatsCpu::patternSize()\n  * \\brief Get the pattern size\n@@ -183,12 +169,12 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */\n \tyVal = r * kRedYMul;               \\\n \tyVal += g * kGreenYMul;            \\\n \tyVal += b * kBlueYMul;             \\\n-\tstats_.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;\n+\tstats_->stats->yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;\n \n-#define SWSTATS_FINISH_LINE_STATS() \\\n-\tstats_.sum_.r() += sumR;    \\\n-\tstats_.sum_.g() += sumG;    \\\n-\tstats_.sum_.b() += sumB;\n+#define SWSTATS_FINISH_LINE_STATS()      \\\n+\tstats_->stats->sum_.r() += sumR; \\\n+\tstats_->stats->sum_.g() += sumG; \\\n+\tstats_->stats->sum_.b() += sumB;\n \n void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[])\n {\n@@ -326,7 +312,7 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[])\n  *\n  * This may only be called after a successful setWindow() call.\n  */\n-void SwStatsCpu::startFrame(uint32_t frame, [[maybe_unused]] const uint32_t statsBufferId)\n+void SwStatsCpu::startFrame(uint32_t frame, const uint32_t statsBufferId)\n {\n \tif (frame % kStatPerNumFrames)\n \t\treturn;\n@@ -334,8 +320,10 @@ void SwStatsCpu::startFrame(uint32_t frame, [[maybe_unused]] const uint32_t stat\n \tif (window_.width == 0)\n \t\tLOG(SwStatsCpu, Error) << \"Calling startFrame() without setWindow()\";\n \n-\tstats_.sum_ = RGB<uint64_t>({ 0, 0, 0 });\n-\tstats_.yHistogram.fill(0);\n+\tauto &s = sharedStats_->at(statsBufferId);\n+\tstats_ = std::make_unique<SwIspStatsRef>(s);\n+\tstats_->stats->sum_ = RGB<uint64_t>({ 0, 0, 0 });\n+\tstats_->stats->yHistogram.fill(0);\n }\n \n /**\n@@ -348,8 +336,7 @@ void SwStatsCpu::startFrame(uint32_t frame, [[maybe_unused]] const uint32_t stat\n void SwStatsCpu::finishFrame(uint32_t frame,\n \t\t\t     const uint32_t statsBufferId)\n {\n-\tstats_.valid = frame % kStatPerNumFrames == 0;\n-\t*(sharedStats_->at(statsBufferId)) = stats_;\n+\tstats_->stats->valid = frame % kStatPerNumFrames == 0;\n \tstatsReady.emit(frame, statsBufferId);\n }\n \n","prefixes":["RFC","v2","13/14"]}