[{"id":33695,"web_url":"https://patchwork.libcamera.org/comment/33695/","msgid":"<20250324210026.GA20035@pendragon.ideasonboard.com>","date":"2025-03-24T21:00:26","subject":"Re: [PATCH v7 10/10] libcamera: software_isp: Apply CCM in\n\tdebayering","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Milan,\n\nThank you for the patch.\n\nOn Fri, Feb 07, 2025 at 11:17:02AM +0100, Milan Zamazal wrote:\n> This patch applies color correction matrix (CCM) in debayering if the\n> CCM is specified.  Not using CCM must still be supported for performance\n> reasons.\n> \n> The CCM is applied as follows:\n> \n>   [r1 g1 b1]   [r]\n>   [r2 g2 b2] * [g]\n>   [r3 g3 b3]   [b]\n> \n> The CCM matrix (the left side of the multiplication) is constant during\n> single frame processing, while the input pixel (the right side) changes.\n> Because each of the color channels is only 8-bit in software ISP, we can\n> make 9 lookup tables with 256 input values for multiplications of each\n> of the r_i, g_i, b_i values.  This way we don't have to multiply each\n> pixel, we can use table lookups and additions instead.  Gamma (which is\n> non-linear and thus cannot be a part of the 9 lookup tables values) is\n> applied on the final values rounded to integers using another lookup\n> table.\n> \n> Because the changing part is the pixel value with three color elements,\n> only three dynamic table lookups are needed.  We use three lookup tables\n> to represent the multiplied matrix values, each of the tables\n> corresponding to the given matrix column and pixel color.\n> \n> We use int16_t to store the precomputed multiplications.  This seems to\n> be noticeably (>10%) faster than `float' for the price of slightly less\n> accuracy and it covers the range of values that sane CCMs produce.  The\n> selection and structure of data is performance critical, for example\n> using bytes would add significant (>10%) speedup but would be too short\n> to cover the value range.\n> \n> The color lookup tables can be represented either as unions,\n> accommodating tables for both the CCM and non-CCM cases, or as separate\n> tables for each of the cases, leaving the tables for the other case\n> unused.  The latter is selected as a matter of preference.\n> \n> The tables are copied (as before), which is not elegant but also not a\n> big problem.  There are patches posted that use shared buffers for\n> parameters passing in software ISP (see software ISP TODO #5) and they\n> can be adjusted for the new parameter format.\n> \n> Color gains from white balance are supposed not to be a part of the\n> specified CCM.  They are applied on it using matrix multiplication,\n> which is simple and in correspondence with future additions in the form\n> of matrix multiplication, like saturation adjustment.\n> \n> With this patch, the reported per-frame slowdown when applying CCM is\n> about 45% on Debix Model A and about 75% on TI AM69 SK.\n> \n> Using std::clamp in debayering adds some performance penalty (a few\n> percent).  The clamping is necessary to eliminate out of range values\n> possibly produced by the CCM.  If it could be avoided by adjusting the\n> precomputed tables some way then performance could be improved a bit.\n> \n> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n> ---\n>  .../internal/software_isp/debayer_params.h    | 34 ++++++++++--\n>  src/ipa/simple/algorithms/lut.cpp             | 53 ++++++++++++++-----\n>  src/ipa/simple/algorithms/lut.h               |  1 +\n>  src/libcamera/software_isp/debayer.cpp        | 51 ++++++++++++++++--\n>  src/libcamera/software_isp/debayer_cpu.cpp    | 53 +++++++++++++++----\n>  src/libcamera/software_isp/debayer_cpu.h      | 10 ++--\n>  6 files changed, 169 insertions(+), 33 deletions(-)\n> \n> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\n> index 7d8fdd48..e85b5c60 100644\n> --- a/include/libcamera/internal/software_isp/debayer_params.h\n> +++ b/include/libcamera/internal/software_isp/debayer_params.h\n> @@ -1,6 +1,6 @@\n>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>  /*\n> - * Copyright (C) 2023, 2024 Red Hat Inc.\n> + * Copyright (C) 2023-2025 Red Hat Inc.\n>   *\n>   * Authors:\n>   * Hans de Goede <hdegoede@redhat.com>\n> @@ -18,11 +18,35 @@ namespace libcamera {\n>  struct DebayerParams {\n>  \tstatic constexpr unsigned int kRGBLookupSize = 256;\n>  \n> -\tusing ColorLookupTable = std::array<uint8_t, kRGBLookupSize>;\n> +\tstruct CcmColumn {\n> +\t\tint16_t r;\n> +\t\tint16_t g;\n> +\t\tint16_t b;\n> +\t};\n>  \n> -\tColorLookupTable red;\n> -\tColorLookupTable green;\n> -\tColorLookupTable blue;\n> +\tusing LookupTable = std::array<uint8_t, kRGBLookupSize>;\n> +\tusing CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n> +\n> +\t/*\n> +\t * Color lookup tables when CCM is not used.\n\nAdd a blank line, or remove the line break.\n\n> +\t * Each color of a debayered pixel is amended by the corresponding\n> +\t * value in the given table.\n> +\t */\n> +\tLookupTable red;\n> +\tLookupTable green;\n> +\tLookupTable blue;\n> +\n> +\t/*\n> +\t * Color and gamma lookup tables when CCM is used.\n\nSame here.\n\n> +\t * Each of the CcmLookupTable's corresponds to a CCM column; together they\n> +\t * make a complete 3x3 CCM lookup table. The CCM is applied on debayered\n> +\t * pixels and then the gamma lookup table is used to set the resulting\n> +\t * values of all the three colors.\n> +\t */\n> +\tCcmLookupTable redCcm;\n> +\tCcmLookupTable greenCcm;\n> +\tCcmLookupTable blueCcm;\n> +\tLookupTable gammaLut;\n>  };\n>  \n>  } /* namespace libcamera */\n> diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp\n> index 352bbf57..1eaa2b5e 100644\n> --- a/src/ipa/simple/algorithms/lut.cpp\n> +++ b/src/ipa/simple/algorithms/lut.cpp\n> @@ -1,6 +1,6 @@\n>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>  /*\n> - * Copyright (C) 2024, Red Hat Inc.\n> + * Copyright (C) 2024-2025, Red Hat Inc.\n>   *\n>   * Color lookup tables construction\n>   */\n> @@ -80,6 +80,11 @@ void Lut::updateGammaTable(IPAContext &context)\n>  \tcontext.activeState.gamma.contrast = contrast;\n>  }\n>  \n> +int16_t Lut::ccmValue(unsigned int i, float ccm) const\n> +{\n> +\treturn std::round(i * ccm);\n> +}\n> +\n>  void Lut::prepare(IPAContext &context,\n>  \t\t  [[maybe_unused]] const uint32_t frame,\n>  \t\t  [[maybe_unused]] IPAFrameContext &frameContext,\n> @@ -91,22 +96,46 @@ void Lut::prepare(IPAContext &context,\n>  \t * observed, it's not permanently prone to minor fluctuations or\n>  \t * rounding errors.\n>  \t */\n> -\tif (context.activeState.gamma.blackLevel != context.activeState.blc.level ||\n> -\t    context.activeState.gamma.contrast != context.activeState.knobs.contrast)\n> +\tconst bool gammaUpdateNeeded =\n> +\t\tcontext.activeState.gamma.blackLevel != context.activeState.blc.level ||\n> +\t\tcontext.activeState.gamma.contrast != context.activeState.knobs.contrast;\n> +\tif (gammaUpdateNeeded)\n>  \t\tupdateGammaTable(context);\n>  \n>  \tauto &gains = context.activeState.awb.gains;\n>  \tauto &gammaTable = context.activeState.gamma.gammaTable;\n>  \tconst unsigned int gammaTableSize = gammaTable.size();\n> -\n> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> -\t\tconst double div = static_cast<double>(DebayerParams::kRGBLookupSize) /\n> -\t\t\t\t   gammaTableSize;\n> -\t\t/* Apply gamma after gain! */\n> -\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n> -\t\tparams->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];\n> -\t\tparams->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];\n> -\t\tparams->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];\n> +\tconst double div = static_cast<double>(DebayerParams::kRGBLookupSize) /\n> +\t\t\t   gammaTableSize;\n> +\n> +\tif (!context.ccmEnabled) {\n> +\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> +\t\t\t/* Apply gamma after gain! */\n> +\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n> +\t\t\tparams->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];\n> +\t\t\tparams->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];\n> +\t\t\tparams->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];\n> +\t\t}\n> +\t} else if (context.activeState.ccm.changed || gammaUpdateNeeded) {\n> +\t\tMatrix<float, 3, 3> gainCcm = { { gains.r(), 0, 0,\n> +\t\t\t\t\t\t  0, gains.g(), 0,\n> +\t\t\t\t\t\t  0, 0, gains.b() } };\n> +\t\tauto ccm = gainCcm * context.activeState.ccm.ccm;\n> +\t\tauto &red = params->redCcm;\n> +\t\tauto &green = params->greenCcm;\n> +\t\tauto &blue = params->blueCcm;\n> +\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n> +\t\t\tred[i].r = ccmValue(i, ccm[0][0]);\n> +\t\t\tred[i].g = ccmValue(i, ccm[1][0]);\n> +\t\t\tred[i].b = ccmValue(i, ccm[2][0]);\n> +\t\t\tgreen[i].r = ccmValue(i, ccm[0][1]);\n> +\t\t\tgreen[i].g = ccmValue(i, ccm[1][1]);\n> +\t\t\tgreen[i].b = ccmValue(i, ccm[2][1]);\n> +\t\t\tblue[i].r = ccmValue(i, ccm[0][2]);\n> +\t\t\tblue[i].g = ccmValue(i, ccm[1][2]);\n> +\t\t\tblue[i].b = ccmValue(i, ccm[2][2]);\n> +\t\t\tparams->gammaLut[i] = gammaTable[i / div];\n> +\t\t}\n>  \t}\n>  }\n>  \n> diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h\n> index 889f864b..77324800 100644\n> --- a/src/ipa/simple/algorithms/lut.h\n> +++ b/src/ipa/simple/algorithms/lut.h\n> @@ -33,6 +33,7 @@ public:\n>  \n>  private:\n>  \tvoid updateGammaTable(IPAContext &context);\n> +\tint16_t ccmValue(unsigned int i, float ccm) const;\n>  };\n>  \n>  } /* namespace ipa::soft::algorithms */\n> diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp\n> index 34e42201..ca81d2e4 100644\n> --- a/src/libcamera/software_isp/debayer.cpp\n> +++ b/src/libcamera/software_isp/debayer.cpp\n> @@ -1,7 +1,7 @@\n>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>  /*\n>   * Copyright (C) 2023, Linaro Ltd\n> - * Copyright (C) 2023, 2024 Red Hat Inc.\n> + * Copyright (C) 2023-2025 Red Hat Inc.\n>   *\n>   * Authors:\n>   * Hans de Goede <hdegoede@redhat.com>\n> @@ -24,8 +24,33 @@ namespace libcamera {\n>   */\n>  \n>  /**\n> - * \\typedef DebayerParams::ColorLookupTable\n> - * \\brief Type of the lookup tables for red, green, blue values\n> + * \\struct DebayerParams::CcmColumn\n> + * \\brief Type of a single column of a color correction matrix (CCM)\n> + */\n> +\n> +/**\n> + * \\var DebayerParams::CcmColumn::r\n> + * \\brief Red (first) component of a CCM column\n> + */\n> +\n> +/**\n> + * \\var DebayerParams::CcmColumn::g\n> + * \\brief Green (second) component of a CCM column\n> + */\n> +\n> +/**\n> + * \\var DebayerParams::CcmColumn::b\n> + * \\brief Blue (third) component of a CCM column\n\nThis is slightly confusing, given the structure of the CCM described in\nthe commit message. I understand that r, g and b here refer to the rows\nin the CCM that produce the red, green and blue components of the output\nvalue, not the columns that are multiplied by the red, green and blue\ncomponents of the input value. Maybe expanding the documentation of\nDebayerParams::CcmColumn would make it clearer:\n\n * \\struct DebayerParams::CcmColumn\n * \\brief Type of a single column of a color correction matrix (CCM)\n *\n * When multiplying an input pixel, columns in the CCM corresponds to the red,\n * green or blue component of input pixel values, while rows corresponds to the\n * red, green or blue components of the output pixel values. The members of the\n * CcmColumn structure are named after the colour components of the output pixel\n * values they correspond to.\n\n> + */\n> +\n> +/**\n> + * \\typedef DebayerParams::LookupTable\n> + * \\brief Type of the lookup tables for single lookup values\n> + */\n> +\n> +/**\n> + * \\typedef DebayerParams::CcmLookupTable\n> + * \\brief Type of the CCM lookup tables for red, green, blue values\n>   */\n>  \n>  /**\n> @@ -43,6 +68,26 @@ namespace libcamera {\n>   * \\brief Lookup table for blue color, mapping input values to output values\n>   */\n>  \n> +/**\n> + * \\var DebayerParams::redCcm\n> + * \\brief CCM lookup table for red color, mapping input values to output values\n\nFor the same reason, I would write here\n\n * \\brief Lookup table for the CCM red column, mapping input values to output values\n\nSame below.\n\n> + */\n> +\n> +/**\n> + * \\var DebayerParams::greenCcm\n> + * \\brief CCM lookup table for green color, mapping input values to output values\n> + */\n> +\n> +/**\n> + * \\var DebayerParams::blueCcm\n> + * \\brief CCM lookup table for blue color, mapping input values to output values\n> + */\n> +\n> +/**\n> + * \\var DebayerParams::gammaLut\n> + * \\brief Gamma lookup table used with color correction matrix\n> + */\n> +\n>  /**\n>   * \\class Debayer\n>   * \\brief Base debayering class\n> diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\n> index 0cd03a8f..83a171d8 100644\n> --- a/src/libcamera/software_isp/debayer_cpu.cpp\n> +++ b/src/libcamera/software_isp/debayer_cpu.cpp\n> @@ -1,7 +1,7 @@\n>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>  /*\n>   * Copyright (C) 2023, Linaro Ltd\n> - * Copyright (C) 2023, Red Hat Inc.\n> + * Copyright (C) 2023-2025 Red Hat Inc.\n>   *\n>   * Authors:\n>   * Hans de Goede <hdegoede@redhat.com>\n> @@ -11,9 +11,11 @@\n>  \n>  #include \"debayer_cpu.h\"\n>  \n> +#include <algorithm>\n>  #include <stdlib.h>\n>  #include <sys/ioctl.h>\n>  #include <time.h>\n> +#include <utility>\n>  \n>  #include <linux/dma-buf.h>\n>  \n> @@ -51,8 +53,12 @@ DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)\n>  \tenableInputMemcpy_ = true;\n>  \n>  \t/* Initialize color lookup tables */\n> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++)\n> +\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>  \t\tred_[i] = green_[i] = blue_[i] = i;\n> +\t\tredCcm_[i] = { 1, 0, 0 };\n> +\t\tgreenCcm_[i] = { 0, 1, 0 };\n> +\t\tblueCcm_[i] = { 0, 0, 1 };\n\nShouldn't the diagonal elements be 'i', not '1' ?\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\n> +\t}\n>  }\n>  \n>  DebayerCpu::~DebayerCpu() = default;\n> @@ -62,12 +68,24 @@ DebayerCpu::~DebayerCpu() = default;\n>  \tconst pixel_t *curr = (const pixel_t *)src[1] + xShift_; \\\n>  \tconst pixel_t *next = (const pixel_t *)src[2] + xShift_;\n>  \n> -#define STORE_PIXEL(b, g, r)        \\\n> -\t*dst++ = blue_[b];          \\\n> -\t*dst++ = green_[g];         \\\n> -\t*dst++ = red_[r];           \\\n> -\tif constexpr (addAlphaByte) \\\n> -\t\t*dst++ = 255;       \\\n> +#define GAMMA(value) \\\n> +\t*dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)]\n> +\n> +#define STORE_PIXEL(b_, g_, r_)                                        \\\n> +\tif constexpr (ccmEnabled) {                                    \\\n> +\t\tconst DebayerParams::CcmColumn &blue = blueCcm_[b_];   \\\n> +\t\tconst DebayerParams::CcmColumn &green = greenCcm_[g_]; \\\n> +\t\tconst DebayerParams::CcmColumn &red = redCcm_[r_];     \\\n> +\t\tGAMMA(blue.b + green.b + red.b);                       \\\n> +\t\tGAMMA(blue.g + green.g + red.g);                       \\\n> +\t\tGAMMA(blue.r + green.r + red.r);                       \\\n> +\t} else {                                                       \\\n> +\t\t*dst++ = blue_[b_];                                    \\\n> +\t\t*dst++ = green_[g_];                                   \\\n> +\t\t*dst++ = red_[r_];                                     \\\n> +\t}                                                              \\\n> +\tif constexpr (addAlphaByte)                                    \\\n> +\t\t*dst++ = 255;                                          \\\n>  \tx++;\n>  \n>  /*\n> @@ -755,8 +773,23 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n>  \t\tdmaSyncers.emplace_back(plane.fd, DmaSyncer::SyncType::Write);\n>  \n>  \tgreen_ = params.green;\n> -\tred_ = swapRedBlueGains_ ? params.blue : params.red;\n> -\tblue_ = swapRedBlueGains_ ? params.red : params.blue;\n> +\tgreenCcm_ = params.greenCcm;\n> +\tif (swapRedBlueGains_) {\n> +\t\tred_ = params.blue;\n> +\t\tblue_ = params.red;\n> +\t\tredCcm_ = params.blueCcm;\n> +\t\tblueCcm_ = params.redCcm;\n> +\t\tfor (unsigned int i = 0; i < 256; i++) {\n> +\t\t\tstd::swap(redCcm_[i].r, redCcm_[i].b);\n> +\t\t\tstd::swap(blueCcm_[i].r, blueCcm_[i].b);\n> +\t\t}\n> +\t} else {\n> +\t\tred_ = params.red;\n> +\t\tblue_ = params.blue;\n> +\t\tredCcm_ = params.redCcm;\n> +\t\tblueCcm_ = params.blueCcm;\n> +\t}\n> +\tgammaLut_ = params.gammaLut;\n>  \n>  \t/* Copy metadata from the input buffer */\n>  \tFrameMetadata &metadata = output->_d()->metadata();\n> diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h\n> index 21c08a2d..926195e9 100644\n> --- a/src/libcamera/software_isp/debayer_cpu.h\n> +++ b/src/libcamera/software_isp/debayer_cpu.h\n> @@ -138,9 +138,13 @@ private:\n>  \t/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */\n>  \tstatic constexpr unsigned int kMaxLineBuffers = 5;\n>  \n> -\tDebayerParams::ColorLookupTable red_;\n> -\tDebayerParams::ColorLookupTable green_;\n> -\tDebayerParams::ColorLookupTable blue_;\n> +\tDebayerParams::LookupTable red_;\n> +\tDebayerParams::LookupTable green_;\n> +\tDebayerParams::LookupTable blue_;\n> +\tDebayerParams::CcmLookupTable redCcm_;\n> +\tDebayerParams::CcmLookupTable greenCcm_;\n> +\tDebayerParams::CcmLookupTable blueCcm_;\n> +\tDebayerParams::LookupTable gammaLut_;\n>  \tdebayerFn debayer0_;\n>  \tdebayerFn debayer1_;\n>  \tdebayerFn debayer2_;","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 602D9C3213\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 24 Mar 2025 21:00:52 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6282168951;\n\tMon, 24 Mar 2025 22:00:51 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9F4966893F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 24 Mar 2025 22:00:49 +0100 (CET)","from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi\n\t[81.175.209.231])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 89E3763F;\n\tMon, 24 Mar 2025 21:59:02 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"Hr3PXSCi\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1742849942;\n\tbh=qgqf/VkfhkZRYpXUyt233ogQTQifSQm5GKKtOgIDJTY=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=Hr3PXSCiWnnWHJPedCAN0/lj1itN+uEz0SQw4Y887Wclhhdm1knvp4qGEMbFfe2Yi\n\ttgz3YzpGLVkDrsfMj5qruuC9JZ6hDvDdj5Wmldk7CdUvny4TKLwZsh4KBNCs+LVkox\n\tthsQWdrRFerj07B5Ank6pzyBPOuD7fDE+A1DZ4qk=","Date":"Mon, 24 Mar 2025 23:00:26 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Milan Zamazal <mzamazal@redhat.com>","Cc":"libcamera-devel@lists.libcamera.org,\n\tRobert Mader <robert.mader@collabora.com>,\n\tHans de Goede <hdegoede@redhat.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>","Subject":"Re: [PATCH v7 10/10] libcamera: software_isp: Apply CCM in\n\tdebayering","Message-ID":"<20250324210026.GA20035@pendragon.ideasonboard.com>","References":"<20250207101703.21149-1-mzamazal@redhat.com>\n\t<20250207101703.21149-11-mzamazal@redhat.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20250207101703.21149-11-mzamazal@redhat.com>","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>"}},{"id":33710,"web_url":"https://patchwork.libcamera.org/comment/33710/","msgid":"<85cye440mx.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-03-26T09:11:34","subject":"Re: [PATCH v7 10/10] libcamera: software_isp: Apply CCM in\n\tdebayering","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Laurent,\n\nthank you for review.\n\nLaurent Pinchart <laurent.pinchart@ideasonboard.com> writes:\n\n> Hi Milan,\n>\n> Thank you for the patch.\n>\n> On Fri, Feb 07, 2025 at 11:17:02AM +0100, Milan Zamazal wrote:\n>> This patch applies color correction matrix (CCM) in debayering if the\n>> CCM is specified.  Not using CCM must still be supported for performance\n>> reasons.\n>> \n>> The CCM is applied as follows:\n>> \n>>   [r1 g1 b1]   [r]\n>>   [r2 g2 b2] * [g]\n>>   [r3 g3 b3]   [b]\n>> \n>> The CCM matrix (the left side of the multiplication) is constant during\n>> single frame processing, while the input pixel (the right side) changes.\n>> Because each of the color channels is only 8-bit in software ISP, we can\n>> make 9 lookup tables with 256 input values for multiplications of each\n>> of the r_i, g_i, b_i values.  This way we don't have to multiply each\n>> pixel, we can use table lookups and additions instead.  Gamma (which is\n>> non-linear and thus cannot be a part of the 9 lookup tables values) is\n>> applied on the final values rounded to integers using another lookup\n>> table.\n>> \n>> Because the changing part is the pixel value with three color elements,\n>> only three dynamic table lookups are needed.  We use three lookup tables\n>> to represent the multiplied matrix values, each of the tables\n>> corresponding to the given matrix column and pixel color.\n>> \n>> We use int16_t to store the precomputed multiplications.  This seems to\n>> be noticeably (>10%) faster than `float' for the price of slightly less\n>> accuracy and it covers the range of values that sane CCMs produce.  The\n>> selection and structure of data is performance critical, for example\n>> using bytes would add significant (>10%) speedup but would be too short\n>> to cover the value range.\n>> \n>> The color lookup tables can be represented either as unions,\n>> accommodating tables for both the CCM and non-CCM cases, or as separate\n>> tables for each of the cases, leaving the tables for the other case\n>> unused.  The latter is selected as a matter of preference.\n>> \n>> The tables are copied (as before), which is not elegant but also not a\n>> big problem.  There are patches posted that use shared buffers for\n>> parameters passing in software ISP (see software ISP TODO #5) and they\n>> can be adjusted for the new parameter format.\n>> \n>> Color gains from white balance are supposed not to be a part of the\n>> specified CCM.  They are applied on it using matrix multiplication,\n>> which is simple and in correspondence with future additions in the form\n>> of matrix multiplication, like saturation adjustment.\n>> \n>> With this patch, the reported per-frame slowdown when applying CCM is\n>> about 45% on Debix Model A and about 75% on TI AM69 SK.\n>> \n>> Using std::clamp in debayering adds some performance penalty (a few\n>> percent).  The clamping is necessary to eliminate out of range values\n>> possibly produced by the CCM.  If it could be avoided by adjusting the\n>> precomputed tables some way then performance could be improved a bit.\n>> \n>> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n>> ---\n>>  .../internal/software_isp/debayer_params.h    | 34 ++++++++++--\n>>  src/ipa/simple/algorithms/lut.cpp             | 53 ++++++++++++++-----\n>>  src/ipa/simple/algorithms/lut.h               |  1 +\n>>  src/libcamera/software_isp/debayer.cpp        | 51 ++++++++++++++++--\n>>  src/libcamera/software_isp/debayer_cpu.cpp    | 53 +++++++++++++++----\n>>  src/libcamera/software_isp/debayer_cpu.h      | 10 ++--\n>>  6 files changed, 169 insertions(+), 33 deletions(-)\n>> \n>> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\n>> index 7d8fdd48..e85b5c60 100644\n>> --- a/include/libcamera/internal/software_isp/debayer_params.h\n>> +++ b/include/libcamera/internal/software_isp/debayer_params.h\n>> @@ -1,6 +1,6 @@\n>>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>  /*\n>> - * Copyright (C) 2023, 2024 Red Hat Inc.\n>> + * Copyright (C) 2023-2025 Red Hat Inc.\n>>   *\n>>   * Authors:\n>>   * Hans de Goede <hdegoede@redhat.com>\n>> @@ -18,11 +18,35 @@ namespace libcamera {\n>>  struct DebayerParams {\n>>  \tstatic constexpr unsigned int kRGBLookupSize = 256;\n>>  \n>> -\tusing ColorLookupTable = std::array<uint8_t, kRGBLookupSize>;\n>> +\tstruct CcmColumn {\n>> +\t\tint16_t r;\n>> +\t\tint16_t g;\n>> +\t\tint16_t b;\n>> +\t};\n>>  \n>> -\tColorLookupTable red;\n>> -\tColorLookupTable green;\n>> -\tColorLookupTable blue;\n>> +\tusing LookupTable = std::array<uint8_t, kRGBLookupSize>;\n>> +\tusing CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>;\n>> +\n>> +\t/*\n>> +\t * Color lookup tables when CCM is not used.\n>\n> Add a blank line, or remove the line break.\n>\n>> +\t * Each color of a debayered pixel is amended by the corresponding\n>> +\t * value in the given table.\n>> +\t */\n>> +\tLookupTable red;\n>> +\tLookupTable green;\n>> +\tLookupTable blue;\n>> +\n>> +\t/*\n>> +\t * Color and gamma lookup tables when CCM is used.\n>\n> Same here.\n>\n>> +\t * Each of the CcmLookupTable's corresponds to a CCM column; together they\n>> +\t * make a complete 3x3 CCM lookup table. The CCM is applied on debayered\n>> +\t * pixels and then the gamma lookup table is used to set the resulting\n>> +\t * values of all the three colors.\n>> +\t */\n>> +\tCcmLookupTable redCcm;\n>> +\tCcmLookupTable greenCcm;\n>> +\tCcmLookupTable blueCcm;\n>> +\tLookupTable gammaLut;\n>>  };\n>>  \n>>  } /* namespace libcamera */\n>> diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp\n>> index 352bbf57..1eaa2b5e 100644\n>> --- a/src/ipa/simple/algorithms/lut.cpp\n>> +++ b/src/ipa/simple/algorithms/lut.cpp\n>> @@ -1,6 +1,6 @@\n>>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>  /*\n>> - * Copyright (C) 2024, Red Hat Inc.\n>> + * Copyright (C) 2024-2025, Red Hat Inc.\n>>   *\n>>   * Color lookup tables construction\n>>   */\n>> @@ -80,6 +80,11 @@ void Lut::updateGammaTable(IPAContext &context)\n>>  \tcontext.activeState.gamma.contrast = contrast;\n>>  }\n>>  \n>> +int16_t Lut::ccmValue(unsigned int i, float ccm) const\n>> +{\n>> +\treturn std::round(i * ccm);\n>> +}\n>> +\n>>  void Lut::prepare(IPAContext &context,\n>>  \t\t  [[maybe_unused]] const uint32_t frame,\n>>  \t\t  [[maybe_unused]] IPAFrameContext &frameContext,\n>> @@ -91,22 +96,46 @@ void Lut::prepare(IPAContext &context,\n>>  \t * observed, it's not permanently prone to minor fluctuations or\n>>  \t * rounding errors.\n>>  \t */\n>> -\tif (context.activeState.gamma.blackLevel != context.activeState.blc.level ||\n>> -\t    context.activeState.gamma.contrast != context.activeState.knobs.contrast)\n>> +\tconst bool gammaUpdateNeeded =\n>> +\t\tcontext.activeState.gamma.blackLevel != context.activeState.blc.level ||\n>> +\t\tcontext.activeState.gamma.contrast != context.activeState.knobs.contrast;\n>> +\tif (gammaUpdateNeeded)\n>>  \t\tupdateGammaTable(context);\n>>  \n>>  \tauto &gains = context.activeState.awb.gains;\n>>  \tauto &gammaTable = context.activeState.gamma.gammaTable;\n>>  \tconst unsigned int gammaTableSize = gammaTable.size();\n>> -\n>> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>> -\t\tconst double div = static_cast<double>(DebayerParams::kRGBLookupSize) /\n>> -\t\t\t\t   gammaTableSize;\n>> -\t\t/* Apply gamma after gain! */\n>> -\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n>> -\t\tparams->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];\n>> -\t\tparams->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];\n>> -\t\tparams->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];\n>> +\tconst double div = static_cast<double>(DebayerParams::kRGBLookupSize) /\n>> +\t\t\t   gammaTableSize;\n>> +\n>> +\tif (!context.ccmEnabled) {\n>> +\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>> +\t\t\t/* Apply gamma after gain! */\n>> +\t\t\tconst RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);\n>> +\t\t\tparams->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];\n>> +\t\t\tparams->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];\n>> +\t\t\tparams->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];\n>> +\t\t}\n>> +\t} else if (context.activeState.ccm.changed || gammaUpdateNeeded) {\n>> +\t\tMatrix<float, 3, 3> gainCcm = { { gains.r(), 0, 0,\n>> +\t\t\t\t\t\t  0, gains.g(), 0,\n>> +\t\t\t\t\t\t  0, 0, gains.b() } };\n>> +\t\tauto ccm = gainCcm * context.activeState.ccm.ccm;\n>> +\t\tauto &red = params->redCcm;\n>> +\t\tauto &green = params->greenCcm;\n>> +\t\tauto &blue = params->blueCcm;\n>> +\t\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>> +\t\t\tred[i].r = ccmValue(i, ccm[0][0]);\n>> +\t\t\tred[i].g = ccmValue(i, ccm[1][0]);\n>> +\t\t\tred[i].b = ccmValue(i, ccm[2][0]);\n>> +\t\t\tgreen[i].r = ccmValue(i, ccm[0][1]);\n>> +\t\t\tgreen[i].g = ccmValue(i, ccm[1][1]);\n>> +\t\t\tgreen[i].b = ccmValue(i, ccm[2][1]);\n>> +\t\t\tblue[i].r = ccmValue(i, ccm[0][2]);\n>> +\t\t\tblue[i].g = ccmValue(i, ccm[1][2]);\n>> +\t\t\tblue[i].b = ccmValue(i, ccm[2][2]);\n>> +\t\t\tparams->gammaLut[i] = gammaTable[i / div];\n>> +\t\t}\n>>  \t}\n>>  }\n>>  \n>> diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h\n>> index 889f864b..77324800 100644\n>> --- a/src/ipa/simple/algorithms/lut.h\n>> +++ b/src/ipa/simple/algorithms/lut.h\n>> @@ -33,6 +33,7 @@ public:\n>>  \n>>  private:\n>>  \tvoid updateGammaTable(IPAContext &context);\n>> +\tint16_t ccmValue(unsigned int i, float ccm) const;\n>>  };\n>>  \n>>  } /* namespace ipa::soft::algorithms */\n>> diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp\n>> index 34e42201..ca81d2e4 100644\n>> --- a/src/libcamera/software_isp/debayer.cpp\n>> +++ b/src/libcamera/software_isp/debayer.cpp\n>> @@ -1,7 +1,7 @@\n>>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>  /*\n>>   * Copyright (C) 2023, Linaro Ltd\n>> - * Copyright (C) 2023, 2024 Red Hat Inc.\n>> + * Copyright (C) 2023-2025 Red Hat Inc.\n>>   *\n>>   * Authors:\n>>   * Hans de Goede <hdegoede@redhat.com>\n>> @@ -24,8 +24,33 @@ namespace libcamera {\n>>   */\n>>  \n>>  /**\n>> - * \\typedef DebayerParams::ColorLookupTable\n>> - * \\brief Type of the lookup tables for red, green, blue values\n>> + * \\struct DebayerParams::CcmColumn\n>> + * \\brief Type of a single column of a color correction matrix (CCM)\n>> + */\n>> +\n>> +/**\n>> + * \\var DebayerParams::CcmColumn::r\n>> + * \\brief Red (first) component of a CCM column\n>> + */\n>> +\n>> +/**\n>> + * \\var DebayerParams::CcmColumn::g\n>> + * \\brief Green (second) component of a CCM column\n>> + */\n>> +\n>> +/**\n>> + * \\var DebayerParams::CcmColumn::b\n>> + * \\brief Blue (third) component of a CCM column\n>\n> This is slightly confusing, given the structure of the CCM described in\n> the commit message. I understand that r, g and b here refer to the rows\n> in the CCM that produce the red, green and blue components of the output\n> value, not the columns that are multiplied by the red, green and blue\n> components of the input value. Maybe expanding the documentation of\n> DebayerParams::CcmColumn would make it clearer:\n>\n>  * \\struct DebayerParams::CcmColumn\n>  * \\brief Type of a single column of a color correction matrix (CCM)\n>  *\n>  * When multiplying an input pixel, columns in the CCM corresponds to the red,\n>  * green or blue component of input pixel values, while rows corresponds to the\n>  * red, green or blue components of the output pixel values. The members of the\n>  * CcmColumn structure are named after the colour components of the output pixel\n>  * values they correspond to.\n>\n>> + */\n>> +\n>> +/**\n>> + * \\typedef DebayerParams::LookupTable\n>> + * \\brief Type of the lookup tables for single lookup values\n>> + */\n>> +\n>> +/**\n>> + * \\typedef DebayerParams::CcmLookupTable\n>> + * \\brief Type of the CCM lookup tables for red, green, blue values\n>>   */\n>>  \n>>  /**\n>> @@ -43,6 +68,26 @@ namespace libcamera {\n>>   * \\brief Lookup table for blue color, mapping input values to output values\n>>   */\n>>  \n>> +/**\n>> + * \\var DebayerParams::redCcm\n>> + * \\brief CCM lookup table for red color, mapping input values to output values\n>\n> For the same reason, I would write here\n>\n>  * \\brief Lookup table for the CCM red column, mapping input values to output values\n>\n> Same below.\n>\n>> + */\n>> +\n>> +/**\n>> + * \\var DebayerParams::greenCcm\n>> + * \\brief CCM lookup table for green color, mapping input values to output values\n>> + */\n>> +\n>> +/**\n>> + * \\var DebayerParams::blueCcm\n>> + * \\brief CCM lookup table for blue color, mapping input values to output values\n>> + */\n>> +\n>> +/**\n>> + * \\var DebayerParams::gammaLut\n>> + * \\brief Gamma lookup table used with color correction matrix\n>> + */\n>> +\n>>  /**\n>>   * \\class Debayer\n>>   * \\brief Base debayering class\n>> diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\n>> index 0cd03a8f..83a171d8 100644\n>> --- a/src/libcamera/software_isp/debayer_cpu.cpp\n>> +++ b/src/libcamera/software_isp/debayer_cpu.cpp\n>> @@ -1,7 +1,7 @@\n>>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>  /*\n>>   * Copyright (C) 2023, Linaro Ltd\n>> - * Copyright (C) 2023, Red Hat Inc.\n>> + * Copyright (C) 2023-2025 Red Hat Inc.\n>>   *\n>>   * Authors:\n>>   * Hans de Goede <hdegoede@redhat.com>\n>> @@ -11,9 +11,11 @@\n>>  \n>>  #include \"debayer_cpu.h\"\n>>  \n>> +#include <algorithm>\n>>  #include <stdlib.h>\n>>  #include <sys/ioctl.h>\n>>  #include <time.h>\n>> +#include <utility>\n>>  \n>>  #include <linux/dma-buf.h>\n>>  \n>> @@ -51,8 +53,12 @@ DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)\n>>  \tenableInputMemcpy_ = true;\n>>  \n>>  \t/* Initialize color lookup tables */\n>> -\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++)\n>> +\tfor (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {\n>>  \t\tred_[i] = green_[i] = blue_[i] = i;\n>> +\t\tredCcm_[i] = { 1, 0, 0 };\n>> +\t\tgreenCcm_[i] = { 0, 1, 0 };\n>> +\t\tblueCcm_[i] = { 0, 0, 1 };\n>\n> Shouldn't the diagonal elements be 'i', not '1' ?\n\nIndeed, they should.\n\nPosted v8 with all these changes.\n\n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n>\n>> +\t}\n>>  }\n>>  \n>>  DebayerCpu::~DebayerCpu() = default;\n>> @@ -62,12 +68,24 @@ DebayerCpu::~DebayerCpu() = default;\n>>  \tconst pixel_t *curr = (const pixel_t *)src[1] + xShift_; \\\n>>  \tconst pixel_t *next = (const pixel_t *)src[2] + xShift_;\n>>  \n>> -#define STORE_PIXEL(b, g, r)        \\\n>> -\t*dst++ = blue_[b];          \\\n>> -\t*dst++ = green_[g];         \\\n>> -\t*dst++ = red_[r];           \\\n>> -\tif constexpr (addAlphaByte) \\\n>> -\t\t*dst++ = 255;       \\\n>> +#define GAMMA(value) \\\n>> +\t*dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)]\n>> +\n>> +#define STORE_PIXEL(b_, g_, r_)                                        \\\n>> +\tif constexpr (ccmEnabled) {                                    \\\n>> +\t\tconst DebayerParams::CcmColumn &blue = blueCcm_[b_];   \\\n>> +\t\tconst DebayerParams::CcmColumn &green = greenCcm_[g_]; \\\n>> +\t\tconst DebayerParams::CcmColumn &red = redCcm_[r_];     \\\n>> +\t\tGAMMA(blue.b + green.b + red.b);                       \\\n>> +\t\tGAMMA(blue.g + green.g + red.g);                       \\\n>> +\t\tGAMMA(blue.r + green.r + red.r);                       \\\n>> +\t} else {                                                       \\\n>> +\t\t*dst++ = blue_[b_];                                    \\\n>> +\t\t*dst++ = green_[g_];                                   \\\n>> +\t\t*dst++ = red_[r_];                                     \\\n>> +\t}                                                              \\\n>> +\tif constexpr (addAlphaByte)                                    \\\n>> +\t\t*dst++ = 255;                                          \\\n>>  \tx++;\n>>  \n>>  /*\n>> @@ -755,8 +773,23 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n>>  \t\tdmaSyncers.emplace_back(plane.fd, DmaSyncer::SyncType::Write);\n>>  \n>>  \tgreen_ = params.green;\n>> -\tred_ = swapRedBlueGains_ ? params.blue : params.red;\n>> -\tblue_ = swapRedBlueGains_ ? params.red : params.blue;\n>> +\tgreenCcm_ = params.greenCcm;\n>> +\tif (swapRedBlueGains_) {\n>> +\t\tred_ = params.blue;\n>> +\t\tblue_ = params.red;\n>> +\t\tredCcm_ = params.blueCcm;\n>> +\t\tblueCcm_ = params.redCcm;\n>> +\t\tfor (unsigned int i = 0; i < 256; i++) {\n>> +\t\t\tstd::swap(redCcm_[i].r, redCcm_[i].b);\n>> +\t\t\tstd::swap(blueCcm_[i].r, blueCcm_[i].b);\n>> +\t\t}\n>> +\t} else {\n>> +\t\tred_ = params.red;\n>> +\t\tblue_ = params.blue;\n>> +\t\tredCcm_ = params.redCcm;\n>> +\t\tblueCcm_ = params.blueCcm;\n>> +\t}\n>> +\tgammaLut_ = params.gammaLut;\n>>  \n>>  \t/* Copy metadata from the input buffer */\n>>  \tFrameMetadata &metadata = output->_d()->metadata();\n>> diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h\n>> index 21c08a2d..926195e9 100644\n>> --- a/src/libcamera/software_isp/debayer_cpu.h\n>> +++ b/src/libcamera/software_isp/debayer_cpu.h\n>> @@ -138,9 +138,13 @@ private:\n>>  \t/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */\n>>  \tstatic constexpr unsigned int kMaxLineBuffers = 5;\n>>  \n>> -\tDebayerParams::ColorLookupTable red_;\n>> -\tDebayerParams::ColorLookupTable green_;\n>> -\tDebayerParams::ColorLookupTable blue_;\n>> +\tDebayerParams::LookupTable red_;\n>> +\tDebayerParams::LookupTable green_;\n>> +\tDebayerParams::LookupTable blue_;\n>> +\tDebayerParams::CcmLookupTable redCcm_;\n>> +\tDebayerParams::CcmLookupTable greenCcm_;\n>> +\tDebayerParams::CcmLookupTable blueCcm_;\n>> +\tDebayerParams::LookupTable gammaLut_;\n>>  \tdebayerFn debayer0_;\n>>  \tdebayerFn debayer1_;\n>>  \tdebayerFn debayer2_;","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 C5672C323E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 26 Mar 2025 09:11:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 590DC68955;\n\tWed, 26 Mar 2025 10:11:42 +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 53C1968947\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 26 Mar 2025 10:11:40 +0100 (CET)","from mail-wm1-f72.google.com (mail-wm1-f72.google.com\n\t[209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-438-nLMZgqzSMmCBYwMrq6nP4g-1; Wed, 26 Mar 2025 05:11:37 -0400","by mail-wm1-f72.google.com with SMTP id\n\t5b1f17b1804b1-43d08915f61so39412055e9.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 26 Mar 2025 02:11:37 -0700 (PDT)","from mzamazal-thinkpadp1gen7.tpbc.csb\n\t(ip-77-48-47-2.net.vodafone.cz. [77.48.47.2])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-43d43f43ed6sm225097185e9.13.2025.03.26.02.11.34\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 26 Mar 2025 02:11:35 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"VofP8zjW\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1742980299;\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\tin-reply-to:in-reply-to:references:references;\n\tbh=gPyXeMLRU+PdYSq0BCrfuUUQe3nLmmxcxzhSLwpu5eI=;\n\tb=VofP8zjW77LemkAwoYl5Lx9zotMUY5sLJ2y06lxJLoI3JdEH1GHBz5WRtxZ0fH/wVuX+Hr\n\th5WOoaRD8wELtqdmAPBYPo9et8+eT3tgemDbUWFNsMk14gHj6UL16SZuw/o5+IawUbXzVW\n\t9q/IPL1fCwZ4O7bdZjRfpFivnvSESUs=","X-MC-Unique":"nLMZgqzSMmCBYwMrq6nP4g-1","X-Mimecast-MFC-AGG-ID":"nLMZgqzSMmCBYwMrq6nP4g_1742980297","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1742980296; x=1743585096;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=gPyXeMLRU+PdYSq0BCrfuUUQe3nLmmxcxzhSLwpu5eI=;\n\tb=csNDOBoQd4lgdSAxcc1XhS0ETrlpMzNVg9fLVb8ffoanEAPQ+PBj3oX79nsSdI8W7D\n\tWp+H+J6Rg2TSOBXb5ViJ0qxK/KorBIHijLQU5pM0V2/ZBgpRsZ/4vgA7xOCXVYvCLkrr\n\tZ1v4/i1S0WB84oDlYikEo70ACiF3V6xvAUoudcj+LTKZ9M1uVjl79VXNrlkC3DDivuGR\n\t53eRiVj4ZB9EpcOoOkbrw+lsrdbhQtaoopJNM3OfeFuQFU9cVTnd+fh+2E43M/+7qkHi\n\tbAkFc4k08yefhXgdcc3O0xELsfJpBHFiNd0w9qeWhq9hMFXAnRVwod2Cy2mjm8KYE9s2\n\tNMBQ==","X-Gm-Message-State":"AOJu0Yxq6svz0RtfdmgpF/lIT0W9H/Y2zg4p/Rq0HWv/UgBUkuvVxhjN\n\tdTzaMOu4ZFsD5KO+pd+Vs3O2HuhZ/qcKGaLyNYl7eat5k1Q2UOrArVj6KElloMw4d8CLSTSjk0S\n\t4W5g6egWHAXGRomKKrzlvuCpbRL9sdUe0YO0mHMIQTcIwJmC/VSxSvX+tuM+SlxC96zLyMus=","X-Gm-Gg":"ASbGnct+MrQv8NdSm5b5JRJvNwrnJ3Phf0ynXeBFSsECsekz4m6hyn1aq4IiT/0qy9C\n\tEyrMiQwM3OTFX1MXIO8iLXMJzwqhjZZALNfZs31eqsr3MMlcxlFMLjZ52d28izJ0eoyMQTOSWXg\n\tmspAczbwQlw/yDJWvYq7YM5xtOAdZ68B8AY2F+9neHquiJS64VBL225K/UeXB2QLiVoL9HhGz+Q\n\tXFLnAoOS6gS5x2X0dOjluPY9XR/K778a2EQOzodIwcVcL/4MGt1vqobUKgB7ZB8XtcDKH4I7zdZ\n\tvHCN9IeATBFvCFnmkGMrtUP0e3Y6qUU4zn0M8BjWA3PFL7JJ4blfYMCtUwzhlw3gOpW0","X-Received":["by 2002:a05:600c:3c9b:b0:43c:fffc:786c with SMTP id\n\t5b1f17b1804b1-43d50a319a3mr185560495e9.19.1742980296389; \n\tWed, 26 Mar 2025 02:11:36 -0700 (PDT)","by 2002:a05:600c:3c9b:b0:43c:fffc:786c with SMTP id\n\t5b1f17b1804b1-43d50a319a3mr185560055e9.19.1742980295726; \n\tWed, 26 Mar 2025 02:11:35 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IGWS1W3uQs+QfaH2wEAgWj1lBJsezGkmTH5G7zg2wwLLZ4p7G3HK1mq/+nI5KbquFJJqFzc8w==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org,  Robert Mader\n\t<robert.mader@collabora.com>,  Hans de Goede <hdegoede@redhat.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>","Subject":"Re: [PATCH v7 10/10] libcamera: software_isp: Apply CCM in\n\tdebayering","In-Reply-To":"<20250324210026.GA20035@pendragon.ideasonboard.com> (Laurent\n\tPinchart's message of \"Mon, 24 Mar 2025 23:00:26 +0200\")","References":"<20250207101703.21149-1-mzamazal@redhat.com>\n\t<20250207101703.21149-11-mzamazal@redhat.com>\n\t<20250324210026.GA20035@pendragon.ideasonboard.com>","Date":"Wed, 26 Mar 2025 10:11:34 +0100","Message-ID":"<85cye440mx.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"mYiBKUnixnZYoVxF0TG4LtNOAijIaBX3GDgZu9VulYQ_1742980297","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","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>"}}]