{"id":19876,"url":"https://patchwork.libcamera.org/api/patches/19876/?format=json","web_url":"https://patchwork.libcamera.org/patch/19876/","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":"<20240416091357.211951-8-mzamazal@redhat.com>","date":"2024-04-16T09:13:43","name":"[v8,07/18] libcamera: software_isp: Add SwStatsCpu class","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"29f568c366fd8791557a7514c71abbdcbad986bb","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/19876/mbox/","series":[{"id":4257,"url":"https://patchwork.libcamera.org/api/series/4257/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=4257","date":"2024-04-16T09:13:36","name":"libcamera: introduce Software ISP and Software IPA","version":8,"mbox":"https://patchwork.libcamera.org/series/4257/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/19876/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/19876/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 62585C3285\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 16 Apr 2024 09:14:45 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id ECBA863368;\n\tTue, 16 Apr 2024 11:14:44 +0200 (CEST)","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 385DA63365\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 16 Apr 2024 11:14:43 +0200 (CEST)","from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com\n\t[66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-340-pLzG1HltNJuOyQqrWktsgg-1; Tue, 16 Apr 2024 05:14:39 -0400","from smtp.corp.redhat.com\n\t(int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4])\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 mimecast-mx02.redhat.com (Postfix) with ESMTPS id 774478DED80;\n\tTue, 16 Apr 2024 09:14:38 +0000 (UTC)","from nuthatch.redhat.com (unknown [10.45.225.245])\n\tby smtp.corp.redhat.com (Postfix) with ESMTP id 7EBDE2026962;\n\tTue, 16 Apr 2024 09:14:35 +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=\"dIuZt9Ng\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1713258882;\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=LjIqynNgSDs3GRwX6ATA3Dw/9yfJuEA4Miaj0/zMS8g=;\n\tb=dIuZt9Ng4YMahV6DlDOWnxcI1oyoSRYl/6JmJ8Q7ZZKcHptmySjnERoVj0AF+xPrwR53kE\n\t4tojqAigaEub3P/qMQeXZfW6V0WhvuQ1N7cNLLZH0epqeGSCdd0NqLplExeoV6v3oOc+sy\n\tY0CyOxSHliCgvWL8SgM16bDsFVwddOI=","X-MC-Unique":"pLzG1HltNJuOyQqrWktsgg-1","From":"Milan Zamazal <mzamazal@redhat.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Hans de Goede <hdegoede@redhat.com>, Milan Zamazal <mzamazal@redhat.com>,\n\tAndrei Konovalov <andrey.konovalov.ynk@gmail.com>,\n\tBryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tMaxime Ripard <mripard@redhat.com>, Pavel Machek <pavel@ucw.cz>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tAndrey Konovalov <andrey.konovalov@linaro.org>,\n\tDennis Bonke <admin@dennisbonke.com>, Marttico <g.martti@gmail.com>, \n\tToon Langendam <t.langendam@gmail.com>","Subject":"[PATCH v8 07/18] libcamera: software_isp: Add SwStatsCpu class","Date":"Tue, 16 Apr 2024 11:13:43 +0200","Message-ID":"<20240416091357.211951-8-mzamazal@redhat.com>","In-Reply-To":"<20240416091357.211951-1-mzamazal@redhat.com>","References":"<20240416091357.211951-1-mzamazal@redhat.com>","MIME-Version":"1.0","X-Scanned-By":"MIMEDefang 3.4.1 on 10.11.54.4","X-Mimecast-Spam-Score":"0","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":"From: Hans de Goede <hdegoede@redhat.com>\n\nAdd a CPU based SwStats implementation for SoftwareISP / SoftIPA use.\n\nThis implementation offers a configure function + functions to gather\nstatistics on a line by line basis. This allows CPU based software\ndebayering to call into interleave debayering and statistics gathering\non a line by line basis while the input data is still hot in the cache.\n\nThis implementation also allows specifying a window over which to gather\nstatistics instead of processing the whole frame.\n\nDoxygen documentation by Dennis Bonke.\n\nTested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s\nTested-by: Pavel Machek <pavel@ucw.cz>\nReviewed-by: Pavel Machek <pavel@ucw.cz>\nReviewed-by: Milan Zamazal <mzamazal@redhat.com>\nCo-developed-by: Andrey Konovalov <andrey.konovalov@linaro.org>\nSigned-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>\nCo-developed-by: Pavel Machek <pavel@ucw.cz>\nSigned-off-by: Pavel Machek <pavel@ucw.cz>\nCo-developed-by: Dennis Bonke <admin@dennisbonke.com>\nSigned-off-by: Dennis Bonke <admin@dennisbonke.com>\nCo-developed-by: Marttico <g.martti@gmail.com>\nSigned-off-by: Marttico <g.martti@gmail.com>\nCo-developed-by: Toon Langendam <t.langendam@gmail.com>\nSigned-off-by: Toon Langendam <t.langendam@gmail.com>\nSigned-off-by: Hans de Goede <hdegoede@redhat.com>\n---\n include/libcamera/internal/meson.build        |   1 +\n .../internal/software_isp/meson.build         |   5 +\n .../internal/software_isp/swisp_stats.h       |  45 +++\n src/libcamera/meson.build                     |   1 +\n src/libcamera/software_isp/TODO               |  71 ++++\n src/libcamera/software_isp/meson.build        |  12 +\n src/libcamera/software_isp/swstats_cpu.cpp    | 304 ++++++++++++++++++\n src/libcamera/software_isp/swstats_cpu.h      |  88 +++++\n 8 files changed, 527 insertions(+)\n create mode 100644 include/libcamera/internal/software_isp/meson.build\n create mode 100644 include/libcamera/internal/software_isp/swisp_stats.h\n create mode 100644 src/libcamera/software_isp/TODO\n create mode 100644 src/libcamera/software_isp/meson.build\n create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp\n create mode 100644 src/libcamera/software_isp/swstats_cpu.h","diff":"diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\nindex 5807dfd9..160fdc37 100644\n--- a/include/libcamera/internal/meson.build\n+++ b/include/libcamera/internal/meson.build\n@@ -50,3 +50,4 @@ libcamera_internal_headers = files([\n ])\n \n subdir('converter')\n+subdir('software_isp')\ndiff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build\nnew file mode 100644\nindex 00000000..66c9c3fb\n--- /dev/null\n+++ b/include/libcamera/internal/software_isp/meson.build\n@@ -0,0 +1,5 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libcamera_internal_headers += files([\n+    'swisp_stats.h',\n+])\ndiff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h\nnew file mode 100644\nindex 00000000..dd5e207d\n--- /dev/null\n+++ b/include/libcamera/internal/software_isp/swisp_stats.h\n@@ -0,0 +1,45 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Linaro Ltd\n+ *\n+ * swisp_stats.h - Statistics data format used by the software ISP and software IPA\n+ */\n+\n+#pragma once\n+\n+#include <array>\n+#include <stdint.h>\n+\n+namespace libcamera {\n+\n+/**\n+ * \\brief Struct that holds the statistics for the Software ISP\n+ *\n+ * The struct value types are large enough to not overflow.\n+ * Should they still overflow for some reason, no check is performed and they\n+ * wrap around.\n+ */\n+struct SwIspStats {\n+\t/**\n+\t * \\brief Holds the sum of all sampled red pixels\n+\t */\n+\tuint64_t sumR_;\n+\t/**\n+\t * \\brief Holds the sum of all sampled green pixels\n+\t */\n+\tuint64_t sumG_;\n+\t/**\n+\t * \\brief Holds the sum of all sampled blue pixels\n+\t */\n+\tuint64_t sumB_;\n+\t/**\n+\t * \\brief Number of bins in the yHistogram\n+\t */\n+\tstatic constexpr unsigned int kYHistogramSize = 16;\n+\t/**\n+\t * \\brief A histogram of luminance values\n+\t */\n+\tstd::array<uint32_t, kYHistogramSize> yHistogram;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex ce31180b..a3b12bc1 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -70,6 +70,7 @@ subdir('ipa')\n subdir('pipeline')\n subdir('proxy')\n subdir('sensor')\n+subdir('software_isp')\n \n null_dep = dependency('', required : false)\n \ndiff --git a/src/libcamera/software_isp/TODO b/src/libcamera/software_isp/TODO\nnew file mode 100644\nindex 00000000..1c28fc36\n--- /dev/null\n+++ b/src/libcamera/software_isp/TODO\n@@ -0,0 +1,71 @@\n+1. Setting F_SEAL_SHRINK and F_SEAL_GROW after ftruncate()\n+\n+>> SharedMem::SharedMem(const std::string &name, std::size_t size)\n+>> \t: name_(name), size_(size), mem_(nullptr)\n+>>\n+>> ...\n+>>\n+>> \tif (ftruncate(fd_.get(), size_) < 0)\n+>> \t\treturn;\n+>\n+> Should we set the GROW and SHRINK seals (in a separate patch) ?\n+\n+Yes, this can be done.\n+Setting F_SEAL_SHRINK and F_SEAL_GROW after the ftruncate() call above could catch\n+some potential errors related to improper access to the shared memory allocated by\n+the SharedMemObject.\n+\n+---\n+\n+2. Reconsider stats sharing\n+\n+>>> +void SwStatsCpu::finishFrame(void)\n+>>> +{\n+>>> +\t*sharedStats_ = stats_;\n+>> \n+>> Is it more efficient to copy the stats instead of operating directly on\n+>> the shared memory ?\n+>\n+> I inherited doing things this way from Andrey. I kept this because\n+> we don't really have any synchronization with the IPA reading this.\n+>\n+> So the idea is to only touch this when the next set of statistics\n+> is ready since we don't know when the IPA is done with accessing\n+> the previous set of statistics ...\n+>\n+> This is both something which seems mostly a theoretic problem,\n+> yet also definitely something which I think we need to fix.\n+>\n+> Maybe use a ringbuffer of stats buffers and pass the index into\n+> the ringbuffer to the emit signal ?\n+\n+That would match how we deal with hardware ISPs, and I think that's a\n+good idea. It will help decoupling the processing side from the IPA.\n+\n+---\n+\n+3. Remove statsReady signal\n+\n+> class SwStatsCpu\n+> {\n+> \t/**\n+> \t * \\brief Signals that the statistics are ready\n+> \t */\n+> \tSignal<> statsReady;\n+\n+But better, I wonder if the signal could be dropped completely. The\n+SwStatsCpu class does not operate asynchronously. Shouldn't whoever\n+calls the finishFrame() function then handle emitting the signal ?\n+\n+Now, the trouble is that this would be the DebayerCpu class, whose name\n+doesn't indicate as a prime candidate to handle stats. However, it\n+already exposes a getStatsFD() function, so we're already calling for\n+trouble :-) Either that should be moved to somewhere else, or the class\n+should be renamed. Considering that the class applies colour gains in\n+addition to performing the interpolation, it may be more of a naming\n+issue.\n+\n+Removing the signal and refactoring those classes doesn't have to be\n+addressed now, I think it would be part of a larger refactoring\n+(possibly also considering platforms that have no ISP but can produce\n+stats in hardware, such as the i.MX7), but please keep it on your radar.\ndiff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build\nnew file mode 100644\nindex 00000000..281bbf0e\n--- /dev/null\n+++ b/src/libcamera/software_isp/meson.build\n@@ -0,0 +1,12 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+softisp_enabled = pipelines.contains('simple')\n+summary({'SoftISP support' : softisp_enabled}, section : 'Configuration')\n+\n+if not softisp_enabled\n+    subdir_done()\n+endif\n+\n+libcamera_sources += files([\n+    'swstats_cpu.cpp',\n+])\ndiff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp\nnew file mode 100644\nindex 00000000..24ae0b16\n--- /dev/null\n+++ b/src/libcamera/software_isp/swstats_cpu.cpp\n@@ -0,0 +1,304 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Linaro Ltd\n+ * Copyright (C) 2023, Red Hat Inc.\n+ *\n+ * Authors:\n+ * Hans de Goede <hdegoede@redhat.com>\n+ *\n+ * swstats_cpu.cpp - CPU based software statistics implementation\n+ */\n+\n+#include \"swstats_cpu.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/bayer_format.h\"\n+\n+namespace libcamera {\n+\n+/**\n+ * \\class SwStatsCpu\n+ * \\brief Class for gathering statistics on the CPU\n+ *\n+ * CPU based software ISP statistics implementation.\n+ *\n+ * This class offers a configure function + functions to gather statistics on a\n+ * line by line basis. This allows CPU based software debayering to interleave\n+ * debayering and statistics gathering on a line by line basis while the input\n+ * data is still hot in the cache.\n+ *\n+ * It is also possible to specify a window over which to gather statistics\n+ * instead of processing the whole frame.\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+ *\n+ * For some input-formats, e.g. Bayer data, processing is done multiple lines\n+ * and/or columns at a time. Get width and height at which the (bayer) pattern\n+ * repeats. Window values are rounded down to a multiple of this and the height\n+ * also indicates if processLine2() should be called or not.\n+ * This may only be called after a successful configure() call.\n+ *\n+ * \\return The pattern size\n+ */\n+\n+/**\n+ * \\fn void SwStatsCpu::processLine0(unsigned int y, const uint8_t *src[])\n+ * \\brief Process line 0\n+ * \\param[in] y The y coordinate.\n+ * \\param[in] src The input data.\n+ *\n+ * This function processes line 0 for input formats with\n+ * patternSize height == 1.\n+ * It'll process line 0 and 1 for input formats with patternSize height >= 2.\n+ * This function may only be called after a successful setWindow() call.\n+ */\n+\n+/**\n+ * \\fn void SwStatsCpu::processLine2(unsigned int y, const uint8_t *src[])\n+ * \\brief Process line 2 and 3\n+ * \\param[in] y The y coordinate.\n+ * \\param[in] src The input data.\n+ *\n+ * This function processes line 2 and 3 for input formats with\n+ * patternSize height == 4.\n+ * This function may only be called after a successful setWindow() call.\n+ */\n+\n+/**\n+ * \\var Signal<> SwStatsCpu::statsReady\n+ * \\brief Signals that the statistics are ready\n+ */\n+\n+/**\n+ * \\typedef SwStatsCpu::statsProcessFn\n+ * \\brief Called when there is data to get statistics from\n+ * \\param[in] src The input data\n+ *\n+ * These functions take an array of (patternSize_.height + 1) src\n+ * pointers each pointing to a line in the source image. The middle\n+ * element of the array will point to the actual line being processed.\n+ * Earlier element(s) will point to the previous line(s) and later\n+ * element(s) to the next line(s).\n+ *\n+ * See the documentation of DebayerCpu::debayerFn for more details.\n+ */\n+\n+/**\n+ * \\var unsigned int SwStatsCpu::ySkipMask_\n+ * \\brief Skip lines where this bitmask is set in y\n+ */\n+\n+/**\n+ * \\var Rectangle SwStatsCpu::window_\n+ * \\brief Statistics window, set by setWindow(), used every line\n+ */\n+\n+/**\n+ * \\var Size SwStatsCpu::patternSize_\n+ * \\brief The size of the bayer pattern\n+ *\n+ * Valid sizes are: 2x2, 4x2 or 4x4.\n+ */\n+\n+/**\n+ * \\var unsigned int SwStatsCpu::xShift_\n+ * \\brief The offset of x, applied to window_.x for bayer variants\n+ *\n+ * This can either be 0 or 1.\n+ */\n+\n+LOG_DEFINE_CATEGORY(SwStatsCpu)\n+\n+SwStatsCpu::SwStatsCpu()\n+\t: sharedStats_(\"softIsp_stats\")\n+{\n+\tif (!sharedStats_)\n+\t\tLOG(SwStatsCpu, Error)\n+\t\t\t<< \"Failed to create shared memory for statistics\";\n+}\n+\n+static constexpr unsigned int kRedYMul = 77; /* 0.299 * 256 */\n+static constexpr unsigned int kGreenYMul = 150; /* 0.587 * 256 */\n+static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */\n+\n+#define SWSTATS_START_LINE_STATS(pixel_t) \\\n+\tpixel_t r, g, g2, b;              \\\n+\tuint64_t yVal;                    \\\n+                                          \\\n+\tuint64_t sumR = 0;                \\\n+\tuint64_t sumG = 0;                \\\n+\tuint64_t sumB = 0;\n+\n+#define SWSTATS_ACCUMULATE_LINE_STATS(div) \\\n+\tsumR += r;                         \\\n+\tsumG += g;                         \\\n+\tsumB += b;                         \\\n+                                           \\\n+\tyVal = r * kRedYMul;               \\\n+\tyVal += g * kGreenYMul;            \\\n+\tyVal += b * kBlueYMul;             \\\n+\tstats_.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;\n+\n+#define SWSTATS_FINISH_LINE_STATS() \\\n+\tstats_.sumR_ += sumR;       \\\n+\tstats_.sumG_ += sumG;       \\\n+\tstats_.sumB_ += sumB;\n+\n+void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[])\n+{\n+\tconst uint8_t *src0 = src[1] + window_.x * 5 / 4;\n+\tconst uint8_t *src1 = src[2] + window_.x * 5 / 4;\n+\tconst int widthInBytes = window_.width * 5 / 4;\n+\n+\tif (swapLines_)\n+\t\tstd::swap(src0, src1);\n+\n+\tSWSTATS_START_LINE_STATS(uint8_t)\n+\n+\t/* x += 5 sample every other 2x2 block */\n+\tfor (int x = 0; x < widthInBytes; x += 5) {\n+\t\t/* BGGR */\n+\t\tb = src0[x];\n+\t\tg = src0[x + 1];\n+\t\tg2 = src1[x];\n+\t\tr = src1[x + 1];\n+\t\tg = (g + g2) / 2;\n+\t\t/* Data is already 8 bits, divide by 1 */\n+\t\tSWSTATS_ACCUMULATE_LINE_STATS(1)\n+\t}\n+\n+\tSWSTATS_FINISH_LINE_STATS()\n+}\n+\n+void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[])\n+{\n+\tconst uint8_t *src0 = src[1] + window_.x * 5 / 4;\n+\tconst uint8_t *src1 = src[2] + window_.x * 5 / 4;\n+\tconst int widthInBytes = window_.width * 5 / 4;\n+\n+\tif (swapLines_)\n+\t\tstd::swap(src0, src1);\n+\n+\tSWSTATS_START_LINE_STATS(uint8_t)\n+\n+\t/* x += 5 sample every other 2x2 block */\n+\tfor (int x = 0; x < widthInBytes; x += 5) {\n+\t\t/* GBRG */\n+\t\tg = src0[x];\n+\t\tb = src0[x + 1];\n+\t\tr = src1[x];\n+\t\tg2 = src1[x + 1];\n+\t\tg = (g + g2) / 2;\n+\t\t/* Data is already 8 bits, divide by 1 */\n+\t\tSWSTATS_ACCUMULATE_LINE_STATS(1)\n+\t}\n+\n+\tSWSTATS_FINISH_LINE_STATS()\n+}\n+\n+/**\n+ * \\brief Reset state to start statistics gathering for a new frame\n+ *\n+ * This may only be called after a successful setWindow() call.\n+ */\n+void SwStatsCpu::startFrame(void)\n+{\n+\tif (window_.width == 0)\n+\t\tLOG(SwStatsCpu, Error) << \"Calling startFrame() without setWindow()\";\n+\n+\tstats_.sumR_ = 0;\n+\tstats_.sumB_ = 0;\n+\tstats_.sumG_ = 0;\n+\tstats_.yHistogram.fill(0);\n+}\n+\n+/**\n+ * \\brief Finish statistics calculation for the current frame\n+ *\n+ * This may only be called after a successful setWindow() call.\n+ */\n+void SwStatsCpu::finishFrame(void)\n+{\n+\t*sharedStats_ = stats_;\n+\tstatsReady.emit();\n+}\n+\n+/**\n+ * \\brief Configure the statistics object for the passed in input format\n+ * \\param[in] inputCfg The input format\n+ *\n+ * \\return 0 on success, a negative errno value on failure\n+ */\n+int SwStatsCpu::configure(const StreamConfiguration &inputCfg)\n+{\n+\tBayerFormat bayerFormat =\n+\t\tBayerFormat::fromPixelFormat(inputCfg.pixelFormat);\n+\n+\tif (bayerFormat.bitDepth == 10 &&\n+\t    bayerFormat.packing == BayerFormat::Packing::CSI2) {\n+\t\tpatternSize_.height = 2;\n+\t\tpatternSize_.width = 4; /* 5 bytes per *4* pixels */\n+\t\t/* Skip every 3th and 4th line, sample every other 2x2 block */\n+\t\tySkipMask_ = 0x02;\n+\t\txShift_ = 0;\n+\n+\t\tswitch (bayerFormat.order) {\n+\t\tcase BayerFormat::BGGR:\n+\t\tcase BayerFormat::GRBG:\n+\t\t\tstats0_ = &SwStatsCpu::statsBGGR10PLine0;\n+\t\t\tswapLines_ = bayerFormat.order == BayerFormat::GRBG;\n+\t\t\treturn 0;\n+\t\tcase BayerFormat::GBRG:\n+\t\tcase BayerFormat::RGGB:\n+\t\t\tstats0_ = &SwStatsCpu::statsGBRG10PLine0;\n+\t\t\tswapLines_ = bayerFormat.order == BayerFormat::RGGB;\n+\t\t\treturn 0;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tLOG(SwStatsCpu, Info)\n+\t\t<< \"Unsupported input format \" << inputCfg.pixelFormat.toString();\n+\treturn -EINVAL;\n+}\n+\n+/**\n+ * \\brief Specify window coordinates over which to gather statistics\n+ * \\param[in] window The window object.\n+ */\n+void SwStatsCpu::setWindow(const Rectangle &window)\n+{\n+\twindow_ = window;\n+\n+\twindow_.x &= ~(patternSize_.width - 1);\n+\twindow_.x += xShift_;\n+\twindow_.y &= ~(patternSize_.height - 1);\n+\n+\t/* width_ - xShift_ to make sure the window fits */\n+\twindow_.width -= xShift_;\n+\twindow_.width &= ~(patternSize_.width - 1);\n+\twindow_.height &= ~(patternSize_.height - 1);\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h\nnew file mode 100644\nindex 00000000..27b15756\n--- /dev/null\n+++ b/src/libcamera/software_isp/swstats_cpu.h\n@@ -0,0 +1,88 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Linaro Ltd\n+ * Copyright (C) 2023, Red Hat Inc.\n+ *\n+ * Authors:\n+ * Hans de Goede <hdegoede@redhat.com>\n+ *\n+ * swstats_cpu.h - CPU based software statistics implementation\n+ */\n+\n+#pragma once\n+\n+#include <stdint.h>\n+\n+#include <libcamera/base/signal.h>\n+\n+#include <libcamera/geometry.h>\n+\n+#include \"libcamera/internal/shared_mem_object.h\"\n+#include \"libcamera/internal/software_isp/swisp_stats.h\"\n+\n+namespace libcamera {\n+\n+class PixelFormat;\n+struct StreamConfiguration;\n+\n+class SwStatsCpu\n+{\n+public:\n+\tSwStatsCpu();\n+\t~SwStatsCpu() = default;\n+\n+\tbool isValid() const { return sharedStats_.fd().isValid(); }\n+\n+\tconst SharedFD &getStatsFD() { return sharedStats_.fd(); }\n+\n+\tconst Size &patternSize() { return patternSize_; }\n+\n+\tint configure(const StreamConfiguration &inputCfg);\n+\tvoid setWindow(const Rectangle &window);\n+\tvoid startFrame();\n+\tvoid finishFrame();\n+\n+\tvoid processLine0(unsigned int y, const uint8_t *src[])\n+\t{\n+\t\tif ((y & ySkipMask_) || y < static_cast<unsigned int>(window_.y) ||\n+\t\t    y >= (window_.y + window_.height))\n+\t\t\treturn;\n+\n+\t\t(this->*stats0_)(src);\n+\t}\n+\n+\tvoid processLine2(unsigned int y, const uint8_t *src[])\n+\t{\n+\t\tif ((y & ySkipMask_) || y < static_cast<unsigned int>(window_.y) ||\n+\t\t    y >= (window_.y + window_.height))\n+\t\t\treturn;\n+\n+\t\t(this->*stats2_)(src);\n+\t}\n+\n+\tSignal<> statsReady;\n+\n+private:\n+\tusing statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[]);\n+\n+\tvoid statsBGGR10PLine0(const uint8_t *src[]);\n+\tvoid statsGBRG10PLine0(const uint8_t *src[]);\n+\n+\t/* Variables set by configure(), used every line */\n+\tstatsProcessFn stats0_;\n+\tstatsProcessFn stats2_;\n+\tbool swapLines_;\n+\n+\tunsigned int ySkipMask_;\n+\n+\tRectangle window_;\n+\n+\tSize patternSize_;\n+\n+\tunsigned int xShift_;\n+\n+\tSharedMemObject<SwIspStats> sharedStats_;\n+\tSwIspStats stats_;\n+};\n+\n+} /* namespace libcamera */\n","prefixes":["v8","07/18"]}