From d9587b2fea57cfffd58b070a6b7f86319aeaafd4 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Fri, 28 Aug 2026 23:53:33 +0200 Subject: [PATCH] temp/libcamera: Add some patches and tuning files Notably: 1. Add the in-review quad-debayer patch required for the imx371 - the OP6 front camera. 2. Fix GLES 2.0 devices like the Librem5 and PinePhone 3. Small tuning file adjustments, including CCMS for the imx371 and imx800 (FP5 main rear camera). 4. A small patch allowing to set maximum resolutions via config file. Part-of: --- ...ple-Enable-softISP-for-the-Pinephone.patch | 4 +- ...-Skip-hwISP-formats-if-swISP-is-acti.patch | 4 +- ...ayer_egl-Re-add-log-for-failing-DMAB.patch | 39 + ...e-AGC-minimum-gain-step-from-sensor-.patch | 85 ++ ...-demote-Unsupported-input-format-log.patch | 45 + ...-untransposed-matrix-to-fix-GLES-2.0.patch | 63 + ...0007-simple-Limit-swISP-output-sizes.patch | 43 + ...ra-Add-quad-Bayer-CFA-layout-support.patch | 1115 +++++++++++++++++ temp/libcamera/APKBUILD | 28 +- temp/libcamera/imx355.yaml | 45 +- temp/libcamera/imx371.yaml | 27 + temp/libcamera/imx376.yaml | 54 +- temp/libcamera/imx519.yaml | 27 + temp/libcamera/imx800.yaml | 42 + 14 files changed, 1552 insertions(+), 69 deletions(-) create mode 100644 temp/libcamera/0003-software_isp-debayer_egl-Re-add-log-for-failing-DMAB.patch create mode 100644 temp/libcamera/0004-ipa-simple-Derive-AGC-minimum-gain-step-from-sensor-.patch create mode 100644 temp/libcamera/0005-debayer-Further-demote-Unsupported-input-format-log.patch create mode 100644 temp/libcamera/0006-debayer_egl-Use-untransposed-matrix-to-fix-GLES-2.0.patch create mode 100644 temp/libcamera/0007-simple-Limit-swISP-output-sizes.patch create mode 100644 temp/libcamera/0008-libcamera-Add-quad-Bayer-CFA-layout-support.patch create mode 100644 temp/libcamera/imx800.yaml diff --git a/temp/libcamera/0001-libcamera-simple-Enable-softISP-for-the-Pinephone.patch b/temp/libcamera/0001-libcamera-simple-Enable-softISP-for-the-Pinephone.patch index 2814618c3..afe74daa4 100644 --- a/temp/libcamera/0001-libcamera-simple-Enable-softISP-for-the-Pinephone.patch +++ b/temp/libcamera/0001-libcamera-simple-Enable-softISP-for-the-Pinephone.patch @@ -1,7 +1,7 @@ From be67f67c4ac44b571d1740c1da10669fe6a5c75b Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Wed, 1 May 2024 18:12:02 +0200 -Subject: [PATCH 1/2] libcamera: simple: Enable softISP for the Pinephone +Subject: [PATCH 1/8] libcamera: simple: Enable softISP for the Pinephone In theory the PP should be able to use the actual HW ISP, however in practice this does not work well yet - especially as the driver for the @@ -17,7 +17,7 @@ Signed-off-by: Robert Mader 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp -index e26f438d9..24538649c 100644 +index e26f438d9a77..24538649cb85 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -264,7 +264,7 @@ static const SimplePipelineInfo supportedDevices[] = { diff --git a/temp/libcamera/0002-libcamera-simple-Skip-hwISP-formats-if-swISP-is-acti.patch b/temp/libcamera/0002-libcamera-simple-Skip-hwISP-formats-if-swISP-is-acti.patch index 34f4b132f..94a9f0bb8 100644 --- a/temp/libcamera/0002-libcamera-simple-Skip-hwISP-formats-if-swISP-is-acti.patch +++ b/temp/libcamera/0002-libcamera-simple-Skip-hwISP-formats-if-swISP-is-acti.patch @@ -1,7 +1,7 @@ From dc471142932b1f2b0bf596c3ae83dba8bd4b4021 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Mon, 6 May 2024 21:21:57 +0200 -Subject: [PATCH 2/2] libcamera: simple: Skip hwISP formats if swISP is active +Subject: [PATCH 2/8] libcamera: simple: Skip hwISP formats if swISP is active On devices like the Pinephone libcamera will advertise support for formats provided by the HW ISP and the SW ISP at the same time. The @@ -15,7 +15,7 @@ Signed-off-by: Robert Mader 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp -index 24538649c..b96b8b529 100644 +index 24538649cb85..b96b8b529642 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -745,9 +745,7 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size) diff --git a/temp/libcamera/0003-software_isp-debayer_egl-Re-add-log-for-failing-DMAB.patch b/temp/libcamera/0003-software_isp-debayer_egl-Re-add-log-for-failing-DMAB.patch new file mode 100644 index 000000000..76d239dcc --- /dev/null +++ b/temp/libcamera/0003-software_isp-debayer_egl-Re-add-log-for-failing-DMAB.patch @@ -0,0 +1,39 @@ +From 929eac400d4aa9ce248bba12e4fc9779bcfb5ef2 Mon Sep 17 00:00:00 2001 +From: Robert Mader +Date: Thu, 23 Jul 2026 09:19:33 +0200 +Subject: [PATCH 3/8] software_isp: debayer_egl: Re-add log for failing DMABuf + import + +The log was introduced in commit +93f0db167a8d ("debayer_egl: Implement dmabuf import for input buffers") +and dropped in the commit mentioned below. Let's readd it as failing +imports majorly impact performance and indicate actionable issues in the +V4L2 or GPU drivers. Ensure the log is only printed once, now that we use +multiple eGLImages. + +Fixes: 13f7b921acdf ("libcamera: software_isp: debayer_egl: Implement input/output frame caching mechanism") +Signed-off-by: Robert Mader +Reviewed-by: Milan Zamazal +Reviewed-by: Kieran Bingham +Signed-off-by: Kieran Bingham +--- + src/libcamera/software_isp/debayer_egl.cpp | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp +index 02651fe87922..09ab5a58c994 100644 +--- a/src/libcamera/software_isp/debayer_egl.cpp ++++ b/src/libcamera/software_isp/debayer_egl.cpp +@@ -533,6 +533,9 @@ eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optiona + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) == 0) + return eglImageIn; + ++ if (eglImageInCache_.size() == 1) ++ LOG(Debayer, Info) ++ << "Importing input DMABuf failed, falling back to upload"; + } else if (!eglImageIn->dmabuf_import_failed_) { + egl_.activateBindTexture(*eglImageIn); + return eglImageIn; +-- +2.55.0 + diff --git a/temp/libcamera/0004-ipa-simple-Derive-AGC-minimum-gain-step-from-sensor-.patch b/temp/libcamera/0004-ipa-simple-Derive-AGC-minimum-gain-step-from-sensor-.patch new file mode 100644 index 000000000..4351ae3e0 --- /dev/null +++ b/temp/libcamera/0004-ipa-simple-Derive-AGC-minimum-gain-step-from-sensor-.patch @@ -0,0 +1,85 @@ +From b0728a7d0ad5462a7e7e39d28e209ede9cbd63ef Mon Sep 17 00:00:00 2001 +From: Emilio Miranda +Date: Tue, 25 Aug 2026 14:06:30 +0200 +Subject: [PATCH 4/8] ipa: simple: Derive AGC minimum gain step from sensor + granularity +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The minimum gain step is computed as 1% of the sensor's gain range. For a +sensor with a wide gain range that is far larger than the hardware can +resolve: the OV01A10 spans 1-63.9961, giving a step of 0.629961, which is +63% at unity gain where the sensor resolves 1/256 = 0.0039. + +updateExposure() substitutes againMinStep whenever the proportional +correction is smaller than it. Once the exposure saturates and gain is +the only remaining control, the AGC therefore applies a 63% change every +time it computes a 2% one, and never settles: + + again 1.00000 exposureMSV 1.99 error +0.51 factor 1.020 -> 1.62996 + again 1.62996 exposureMSV 2.90 error -0.41 factor 0.984 -> 1.00000 + +That is 0.7 EV of brightness flicker at frame rate, observed on a Dell +XPS 13 9315 (IPU6, simple pipeline and software ISP). + +This is not specific to one sensor. Without a CameraSensorHelper the gain +values are raw register codes, where a step of 1.0 is by definition the +smallest possible change in gain; adding a helper switches the units to +real multipliers, and 1% of the range then bears no relation to what the +sensor can resolve. Any sensor with a wide gain range is exposed to this +once it gains a helper. + +Sample the step at both ends of the gain range and take the smaller one, +so that non-uniform gain models are covered as well. This yields +0.00390625 for the OV01A10, and the gain then tracks continuously +instead of alternating between two values. + +Link: https://bugzilla.redhat.com/show_bug.cgi?id=2483190 +Suggested-by: Barnabás Pőcze +Signed-off-by: Emilio Miranda +--- + src/ipa/simple/soft_simple.cpp | 20 ++++++++++++++++---- + 1 file changed, 16 insertions(+), 4 deletions(-) + +diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp +index 629e1a32de8a..8bffd2a441fd 100644 +--- a/src/ipa/simple/soft_simple.cpp ++++ b/src/ipa/simple/soft_simple.cpp +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + #include + +@@ -228,10 +229,21 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) + context_.configuration.agc.againMin = camHelper_->gain(againMin); + context_.configuration.agc.againMax = camHelper_->gain(againMax); + context_.configuration.agc.again10 = std::max(context_.configuration.agc.againMin, 1.0); +- context_.configuration.agc.againMinStep = +- (context_.configuration.agc.againMax - +- context_.configuration.agc.againMin) / +- 100.0; ++ /* ++ * The minimum gain step must reflect what the sensor can ++ * actually resolve. Deriving it from a fraction of the gain ++ * range yields a huge step for sensors with a wide range: the ++ * OV01A10 spans 1-63.9961, giving 0.63 at unity gain where the ++ * hardware resolves 1/256. The AGC can then make no correction ++ * smaller than 63%, and oscillates around the target once the ++ * exposure saturates and gain is the only remaining control. ++ * ++ * Sample the step at both ends of the range and take the ++ * smaller one, so that non-uniform gain models are covered. ++ */ ++ context_.configuration.agc.againMinStep = std::min( ++ std::abs(camHelper_->gain(againMin + 1) - camHelper_->gain(againMin)), ++ std::abs(camHelper_->gain(againMax) - camHelper_->gain(againMax - 1))); + if (camHelper_->blackLevel().has_value()) { + /* + * The black level from camHelper_ is a 16 bit value, software ISP +-- +2.55.0 + diff --git a/temp/libcamera/0005-debayer-Further-demote-Unsupported-input-format-log.patch b/temp/libcamera/0005-debayer-Further-demote-Unsupported-input-format-log.patch new file mode 100644 index 000000000..285d28541 --- /dev/null +++ b/temp/libcamera/0005-debayer-Further-demote-Unsupported-input-format-log.patch @@ -0,0 +1,45 @@ +From 2c72bce471a5098f7bd3a23997e03bd163b89093 Mon Sep 17 00:00:00 2001 +From: Robert Mader +Date: Fri, 28 Aug 2026 18:26:58 +0200 +Subject: [PATCH 5/8] debayer: Further demote 'Unsupported input format' log + +On some platforms such as the PinePhone these logs clutter the output of +tools and the journal a lot. Let's silence them for normal usage. + +Signed-off-by: Robert Mader +--- + src/libcamera/software_isp/debayer_cpu.cpp | 4 ++-- + src/libcamera/software_isp/debayer_egl.cpp | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp +index 49382b4c2719..2c2c1dd4912d 100644 +--- a/src/libcamera/software_isp/debayer_cpu.cpp ++++ b/src/libcamera/software_isp/debayer_cpu.cpp +@@ -469,8 +469,8 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf + return 0; + } + +- LOG(Debayer, Info) +- << "Unsupported input format " << inputFormat.toString(); ++ LOG(Debayer, Debug) ++ << "Unsupported input format " << inputFormat; + return -EINVAL; + } + +diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp +index 09ab5a58c994..5c0508239f9c 100644 +--- a/src/libcamera/software_isp/debayer_egl.cpp ++++ b/src/libcamera/software_isp/debayer_egl.cpp +@@ -90,7 +90,7 @@ int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf + return 0; + } + +- LOG(Debayer, Info) ++ LOG(Debayer, Debug) + << "Unsupported input format " << inputFormat; + + return -EINVAL; +-- +2.55.0 + diff --git a/temp/libcamera/0006-debayer_egl-Use-untransposed-matrix-to-fix-GLES-2.0.patch b/temp/libcamera/0006-debayer_egl-Use-untransposed-matrix-to-fix-GLES-2.0.patch new file mode 100644 index 000000000..ea6bf6a73 --- /dev/null +++ b/temp/libcamera/0006-debayer_egl-Use-untransposed-matrix-to-fix-GLES-2.0.patch @@ -0,0 +1,63 @@ +From 2e2d412965dc84cc0bdcd770b76674a8da0cf419 Mon Sep 17 00:00:00 2001 +From: Robert Mader +Date: Sat, 29 Aug 2026 01:38:26 +0200 +Subject: [PATCH 6/8] debayer_egl: Use untransposed matrix to fix GLES 2.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The softISP curently fails on Mali-400 on Mesa. It turns out that +transposed matrices are not allowed by the spec: +> The UniformMatrix{234}fv commands will load count 2 × 2, 3 × 3, or 4 × 4 +> matrices (corresponding to 2, 3, or 4 in the command name) of floating-point values +> into a uniform location defined as a matrix or an array of matrices. The matrix is +> specified in column-major order. transpose must be FALSE. + +Thus partially revert the commit mentioned below and manually transpose +the matrix. + +Fixes: d780e285b593 ("shaders: bayer: Use native matrix multiplication") +Closes: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/342 +Signed-off-by: Robert Mader + +--- + +Changes in v2: + - Actually transpose the matrix to not re-introduce issues with CCMs + - Add comment about GLES 2.0 + - Add "Closes:" link +--- + src/libcamera/software_isp/debayer_egl.cpp | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp +index 5c0508239f9c..ae06dc0d2a3d 100644 +--- a/src/libcamera/software_isp/debayer_egl.cpp ++++ b/src/libcamera/software_isp/debayer_egl.cpp +@@ -479,8 +479,21 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara + << " textureUniformStep_.y " << Step[1] + << " textureUniformStrideFactor_ " << Stride + << " textureUniformProjMatrix_ " << textureUniformProjMatrix_; +- +- glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_TRUE, params.combinedMatrix.data().data()); ++ /* ++ * Pre-transpose matrix for GLES 2.0 ++ */ ++ GLfloat ccm[9] = { ++ params.combinedMatrix[0][0], ++ params.combinedMatrix[1][0], ++ params.combinedMatrix[2][0], ++ params.combinedMatrix[0][1], ++ params.combinedMatrix[1][1], ++ params.combinedMatrix[2][1], ++ params.combinedMatrix[0][2], ++ params.combinedMatrix[1][2], ++ params.combinedMatrix[2][2], ++ }; ++ glUniformMatrix3fv(ccmUniformDataIn_, 1, GL_FALSE, ccm); + LOG(Debayer, Debug) << " ccmUniformDataIn_ " << ccmUniformDataIn_ << " data " << params.combinedMatrix; + + /* +-- +2.55.0 + diff --git a/temp/libcamera/0007-simple-Limit-swISP-output-sizes.patch b/temp/libcamera/0007-simple-Limit-swISP-output-sizes.patch new file mode 100644 index 000000000..30256df62 --- /dev/null +++ b/temp/libcamera/0007-simple-Limit-swISP-output-sizes.patch @@ -0,0 +1,43 @@ +From 80b6b8bf4735c1b2042b36635a8963b3a0a199be Mon Sep 17 00:00:00 2001 +From: Robert Mader +Date: Sat, 8 Aug 2026 13:42:36 +0200 +Subject: [PATCH 7/8] simple: Limit swISP output sizes + +~/.config/libcamera/configuration.yaml +--- +version: 1 +configuration: + pipelines: + simple: + max_output_width: 1280 + max_output_height: 720 + +Signed-off-by: Robert Mader +--- + src/libcamera/pipeline/simple/simple.cpp | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp +index b96b8b529642..fa55d764f32e 100644 +--- a/src/libcamera/pipeline/simple/simple.cpp ++++ b/src/libcamera/pipeline/simple/simple.cpp +@@ -752,6 +752,16 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size) + config.outputSizes = config.captureSize; + } + ++ const GlobalConfiguration &configuration = pipe()->cameraManager()->_d()->configuration(); ++ if (auto maxOutputWidth = configuration.configuration()["pipelines"]["simple"]["max_output_width"].get()) { ++ if (config.outputSizes.max.width > maxOutputWidth.value()) ++ config.outputSizes.max.width = maxOutputWidth.value(); ++ } ++ if (auto maxOutputHeight = configuration.configuration()["pipelines"]["simple"]["max_output_height"].get()) { ++ if (config.outputSizes.max.height > maxOutputHeight.value()) ++ config.outputSizes.max.height = maxOutputHeight.value(); ++ } ++ + configs_.push_back(config); + } + } +-- +2.55.0 + diff --git a/temp/libcamera/0008-libcamera-Add-quad-Bayer-CFA-layout-support.patch b/temp/libcamera/0008-libcamera-Add-quad-Bayer-CFA-layout-support.patch new file mode 100644 index 000000000..22757ac74 --- /dev/null +++ b/temp/libcamera/0008-libcamera-Add-quad-Bayer-CFA-layout-support.patch @@ -0,0 +1,1115 @@ +From 5f350d71db338ac8d363762231d5111e87e511e0 Mon Sep 17 00:00:00 2001 +From: Frederic Laing +Date: Tue, 25 Aug 2026 18:16:06 +0000 +Subject: [PATCH 8/8] libcamera: Add quad-Bayer CFA layout support + +Quad-Bayer sensors arrange pixels in 2x2 same-colour cells. Treating +the IMX371 full-resolution stream as a conventional Bayer pattern +produces incorrect colours and exposes physical instead of logical +output geometry. + +Describe native and software ISP CFA cell sizes per sensor. Use the +metadata for the Raspberry Pi minimum debinning factor and enable cell +collapse only for IMX371. Extend the EGL shader and statistics path to +average cells, preserve Bayer order, crop in physical coordinates, and +expose half-resolution logical sizes. Reject unsupported formats, sizes, +and the CPU fallback. + +Add unit coverage for sensor metadata, format and size validation, +geometry, Bayer order normalization, and cell averaging. + +Signed-off-by: Frederic Laing +--- + include/libcamera/internal/meson.build | 1 + + .../libcamera/internal/sensor_cfa_layout.h | 46 ++++ + .../internal/software_isp/meson.build | 1 + + .../internal/software_isp/quad_bayer.h | 158 ++++++++++++ + .../internal/software_isp/swstats_cpu.h | 6 +- + src/ipa/rpi/cam_helper/cam_helper.cpp | 8 +- + src/ipa/rpi/cam_helper/cam_helper.h | 8 +- + src/ipa/rpi/cam_helper/cam_helper_imx708.cpp | 7 - + src/ipa/rpi/common/ipa_base.cpp | 1 + + src/libcamera/shaders/bayer_1x_packed.frag | 49 +++- + src/libcamera/software_isp/debayer.cpp | 4 +- + src/libcamera/software_isp/debayer.h | 3 +- + src/libcamera/software_isp/debayer_egl.cpp | 61 +++-- + src/libcamera/software_isp/debayer_egl.h | 5 +- + src/libcamera/software_isp/software_isp.cpp | 13 +- + src/libcamera/software_isp/swstats_cpu.cpp | 58 ++++- + test/meson.build | 1 + + test/quad-bayer.cpp | 224 ++++++++++++++++++ + 18 files changed, 613 insertions(+), 41 deletions(-) + create mode 100644 include/libcamera/internal/sensor_cfa_layout.h + create mode 100644 include/libcamera/internal/software_isp/quad_bayer.h + create mode 100644 test/quad-bayer.cpp + +diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build +index fd375134a5c4..f928d241f770 100644 +--- a/include/libcamera/internal/meson.build ++++ b/include/libcamera/internal/meson.build +@@ -40,6 +40,7 @@ libcamera_internal_headers = files([ + 'process.h', + 'pub_key.h', + 'request.h', ++ 'sensor_cfa_layout.h', + 'shared_mem_object.h', + 'source_paths.h', + 'sysfs.h', +diff --git a/include/libcamera/internal/sensor_cfa_layout.h b/include/libcamera/internal/sensor_cfa_layout.h +new file mode 100644 +index 000000000000..2ab95e2bfc76 +--- /dev/null ++++ b/include/libcamera/internal/sensor_cfa_layout.h +@@ -0,0 +1,46 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2026 Frederic Laing ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#include ++ ++namespace libcamera { ++ ++struct SensorCfaLayout { ++ Size nativeCellSize; ++ Size softwareIspInputCellSize; ++ ++ unsigned int minimumDebinFactor() const ++ { ++ return 2 * std::max(nativeCellSize.width, nativeCellSize.height); ++ } ++ ++ bool softwareIspNeedsCellCollapse() const ++ { ++ return softwareIspInputCellSize != Size(1, 1); ++ } ++}; ++ ++inline SensorCfaLayout sensorCfaLayout(std::string_view model) ++{ ++ /* ++ * Native cell size describes the sensor construction. The software ISP ++ * input size separately describes whether that pipeline receives physical ++ * same-colour cells or an ordinary Bayer stream produced by the sensor. ++ */ ++ if (model == "imx371") ++ return { { 2, 2 }, { 2, 2 } }; ++ if (model == "imx708" || model == "imx708_wide" || ++ model == "imx708_noir" || model == "imx708_wide_noir") ++ return { { 2, 2 }, { 1, 1 } }; ++ ++ return { { 1, 1 }, { 1, 1 } }; ++} ++ ++} /* namespace libcamera */ +diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build +index df7c3b97dbca..598b44873296 100644 +--- a/include/libcamera/internal/software_isp/meson.build ++++ b/include/libcamera/internal/software_isp/meson.build +@@ -3,6 +3,7 @@ + libcamera_internal_headers += files([ + 'benchmark.h', + 'debayer_params.h', ++ 'quad_bayer.h', + 'software_isp.h', + 'swisp_stats.h', + 'swstats_cpu.h', +diff --git a/include/libcamera/internal/software_isp/quad_bayer.h b/include/libcamera/internal/software_isp/quad_bayer.h +new file mode 100644 +index 000000000000..9971e6def5ee +--- /dev/null ++++ b/include/libcamera/internal/software_isp/quad_bayer.h +@@ -0,0 +1,158 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2026 Frederic Laing ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "libcamera/internal/bayer_format.h" ++#include "libcamera/internal/sensor_cfa_layout.h" ++ ++namespace libcamera { ++ ++inline bool softwareIspNeedsCellCollapse(std::string_view model) ++{ ++ return sensorCfaLayout(model).softwareIspNeedsCellCollapse(); ++} ++ ++inline Size quadBayerLogicalSize(const Size &physicalSize) ++{ ++ return { physicalSize.width / 2, physicalSize.height / 2 }; ++} ++ ++inline bool isQuadBayerInputSizeSupported(const Size &size) ++{ ++ return size.width >= 4 && size.height >= 4 && ++ size.width % 4 == 0 && size.height % 4 == 0; ++} ++ ++inline Rectangle softwareIspViewport(const Size &physicalSize, ++ const Size &outputSize, ++ bool quadBayer) ++{ ++ const Size &viewportSize = quadBayer ? outputSize : physicalSize; ++ return { 0, 0, viewportSize.width, viewportSize.height }; ++} ++ ++inline Rectangle quadBayerStatsWindow(const Size &physicalSize, ++ const Size &logicalOutputSize) ++{ ++ Size statsSize = physicalSize; ++ if (static_cast(physicalSize.width) * logicalOutputSize.height > ++ static_cast(physicalSize.height) * logicalOutputSize.width) { ++ statsSize.width = static_cast(physicalSize.height) * ++ logicalOutputSize.width / logicalOutputSize.height; ++ statsSize.width &= ~3U; ++ } else { ++ statsSize.height = static_cast(physicalSize.width) * ++ logicalOutputSize.height / logicalOutputSize.width; ++ statsSize.height &= ~3U; ++ } ++ return { ++ static_cast(((physicalSize.width - statsSize.width) / 2) & ~3U), ++ static_cast(((physicalSize.height - statsSize.height) / 2) & ~3U), ++ statsSize.width, ++ statsSize.height, ++ }; ++} ++ ++inline std::array softwareIspTextureCoordinates(const Size &physicalSize, ++ const Rectangle &window, ++ bool quadBayer) ++{ ++ if (!quadBayer) { ++ return { ++ 0.0f, ++ 0.0f, ++ 0.0f, ++ 1.0f, ++ 1.0f, ++ 1.0f, ++ 1.0f, ++ 0.0f, ++ }; ++ } ++ ++ const float left = static_cast(window.x) / physicalSize.width; ++ const float top = static_cast(window.y) / physicalSize.height; ++ const float right = static_cast(window.x + window.width) / physicalSize.width; ++ const float bottom = static_cast(window.y + window.height) / physicalSize.height; ++ return { ++ left, ++ top, ++ left, ++ bottom, ++ right, ++ bottom, ++ right, ++ top, ++ }; ++} ++ ++inline Point quadBayerCellOrigin(const Size &physicalSize, const Point &pixel) ++{ ++ return { ++ std::clamp(pixel.x & ~1, 0, static_cast(physicalSize.width) - 2), ++ std::clamp(pixel.y & ~1, 0, static_cast(physicalSize.height) - 2), ++ }; ++} ++ ++inline Point quadBayerOrderShifts(BayerFormat::Order order) ++{ ++ switch (order) { ++ case BayerFormat::RGGB: ++ return { 0, 0 }; ++ case BayerFormat::GRBG: ++ return { 2, 0 }; ++ case BayerFormat::GBRG: ++ return { 0, 2 }; ++ case BayerFormat::BGGR: ++ return { 2, 2 }; ++ default: ++ return { -1, -1 }; ++ } ++} ++ ++inline bool isQuadBayerInputFormatSupported(PixelFormat inputFormat) ++{ ++ const BayerFormat bayerFormat = BayerFormat::fromPixelFormat(inputFormat); ++ return bayerFormat.bitDepth == 10 && ++ bayerFormat.packing == BayerFormat::Packing::CSI2 && ++ quadBayerOrderShifts(bayerFormat.order).x >= 0; ++} ++ ++template ++inline auto normalizeQuadBayerTile(Sample sample, BayerFormat::Order order) ++{ ++ using Value = decltype(sample(0, 0)); ++ const Point redShift = quadBayerOrderShifts(order); ++ const Point red(redShift.x / 2, redShift.y / 2); ++ const Point blue(1 - red.x, 1 - red.y); ++ const Value green0 = sample(1 - red.x, red.y); ++ const Value green1 = sample(red.x, 1 - red.y); ++ return std::array{ ++ sample(red.x, red.y), ++ static_cast((green0 + green1) / 2), ++ sample(blue.x, blue.y), ++ }; ++} ++ ++template ++inline auto averageQuadBayerCell(Sample sample, unsigned int x, unsigned int y) ++{ ++ const unsigned int physicalX = x * 2; ++ const unsigned int physicalY = y * 2; ++ return (sample(physicalX, physicalY) + ++ sample(physicalX + 1, physicalY) + ++ sample(physicalX, physicalY + 1) + ++ sample(physicalX + 1, physicalY + 1) + 2) / ++ 4; ++} ++ ++} /* namespace libcamera */ +diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h +index 551870921862..6fe60e6a0bb2 100644 +--- a/include/libcamera/internal/software_isp/swstats_cpu.h ++++ b/include/libcamera/internal/software_isp/swstats_cpu.h +@@ -35,7 +35,7 @@ struct StreamConfiguration; + class SwStatsCpu + { + public: +- SwStatsCpu(const CameraManager &cm); ++ SwStatsCpu(const CameraManager &cm, bool quadBayer = false); + ~SwStatsCpu() = default; + + /* +@@ -98,11 +98,13 @@ private: + /* Bayer 10 bpp packed */ + void statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats); + void statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats); ++ void statsQuadRGGB10P(const uint8_t *src[], SwIspStats &stats); + /* Bayer 12 bpp packed */ + void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats); + void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats); + + void processBayerFrame2(MappedFrameBuffer &in); ++ void processQuadBayerFrame(MappedFrameBuffer &in); + + processFrameFn processFrame_; + +@@ -120,6 +122,8 @@ private: + unsigned int xShift_; + unsigned int stride_; + unsigned int sumShift_; ++ bool quadBayer_; ++ BayerFormat::Order quadBayerOrder_ = BayerFormat::RGGB; + + std::vector stats_; + SharedMemObject sharedStats_; +diff --git a/src/ipa/rpi/cam_helper/cam_helper.cpp b/src/ipa/rpi/cam_helper/cam_helper.cpp +index dfdcd167839a..6c7cf3a29e30 100644 +--- a/src/ipa/rpi/cam_helper/cam_helper.cpp ++++ b/src/ipa/rpi/cam_helper/cam_helper.cpp +@@ -206,8 +206,12 @@ unsigned int CamHelper::mistrustFramesModeSwitch() const + + unsigned int CamHelper::getMinDebinFactor() const + { +- /* Most cameras require debinning from 2x2 binning upwards. */ +- return 2; ++ return cfaLayout_.minimumDebinFactor(); ++} ++ ++void CamHelper::setCfaLayout(const libcamera::SensorCfaLayout &layout) ++{ ++ cfaLayout_ = layout; + } + + void CamHelper::parseEmbeddedData(Span buffer, +diff --git a/src/ipa/rpi/cam_helper/cam_helper.h b/src/ipa/rpi/cam_helper/cam_helper.h +index bd101895f23e..6dac8c7a7b62 100644 +--- a/src/ipa/rpi/cam_helper/cam_helper.h ++++ b/src/ipa/rpi/cam_helper/cam_helper.h +@@ -13,12 +13,14 @@ + #include + #include + ++#include "libcamera/internal/sensor_cfa_layout.h" ++#include "libcamera/internal/v4l2_videodevice.h" ++ + #include "controller/camera_mode.h" + #include "controller/controller.h" + #include "controller/metadata.h" +-#include "md_parser.h" + +-#include "libcamera/internal/v4l2_videodevice.h" ++#include "md_parser.h" + + namespace RPiController { + +@@ -99,6 +101,7 @@ public: + virtual unsigned int mistrustFramesStartup() const; + virtual unsigned int mistrustFramesModeSwitch() const; + virtual unsigned int getMinDebinFactor() const; ++ void setCfaLayout(const libcamera::SensorCfaLayout &layout); + + protected: + void parseEmbeddedData(libcamera::Span buffer, +@@ -109,6 +112,7 @@ protected: + std::unique_ptr parser_; + CameraMode mode_; + Controller::HardwareConfig hwConfig_; ++ libcamera::SensorCfaLayout cfaLayout_{ { 1, 1 }, { 1, 1 } }; + + private: + /* +diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp +index e7b8d6712185..6150909ca34f 100644 +--- a/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp ++++ b/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp +@@ -58,7 +58,6 @@ public: + double getModeSensitivity(const CameraMode &mode) const override; + unsigned int hideFramesModeSwitch() const override; + unsigned int hideFramesStartup() const override; +- unsigned int getMinDebinFactor() const override; + + private: + /* +@@ -238,12 +237,6 @@ unsigned int CamHelperImx708::hideFramesStartup() const + return hideFramesModeSwitch(); + } + +-unsigned int CamHelperImx708::getMinDebinFactor() const +-{ +- /* Quad-Bayer sensor, so debinning required only at 4x4 binning. */ +- return 4; +-} +- + void CamHelperImx708::populateMetadata(const MdParser::RegisterMap ®isters, + Metadata &metadata) const + { +diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp +index dacafa578c98..8f6b987edcd3 100644 +--- a/src/ipa/rpi/common/ipa_base.cpp ++++ b/src/ipa/rpi/common/ipa_base.cpp +@@ -144,6 +144,7 @@ int32_t IpaBase::init(const IPASettings &settings, const InitParams ¶ms, Ini + << settings.sensorModel; + return -EINVAL; + } ++ helper_->setCfaLayout(sensorCfaLayout(settings.sensorModel)); + + /* Pass out the sensor metadata to the pipeline handler */ + int sensorMetadata = helper_->sensorEmbeddedDataPresent(); +diff --git a/src/libcamera/shaders/bayer_1x_packed.frag b/src/libcamera/shaders/bayer_1x_packed.frag +index 0b641a5f35c5..d20d9d6fb694 100644 +--- a/src/libcamera/shaders/bayer_1x_packed.frag ++++ b/src/libcamera/shaders/bayer_1x_packed.frag +@@ -71,6 +71,26 @@ uniform vec3 blacklevel; + uniform float gamma; + uniform float contrastExp; + ++#if defined (QUAD_BAYER) ++vec2 quad_cell(vec2 pixel) ++{ ++ return clamp(floor(pixel / 2.0) * 2.0, vec2(0.0), tex_size - vec2(2.0)); ++} ++ ++float quad_fetch(vec2 pixel) ++{ ++ vec2 cell = quad_cell(pixel); ++ vec2 byte_pos = vec2(floor(BPP * cell.x + 0.02), cell.y) * tex_step; ++ vec2 byte_pos_x1 = vec2(floor(BPP * (cell.x + 1.0) + 0.02), cell.y) * tex_step; ++ vec2 byte_pos_y1 = byte_pos + vec2(0.0, tex_step.y); ++ vec2 byte_pos_xy1 = vec2(byte_pos_x1.x, byte_pos_y1.y); ++ return (texture2D(tex_y, byte_pos).r + ++ texture2D(tex_y, byte_pos_x1).r + ++ texture2D(tex_y, byte_pos_y1).r + ++ texture2D(tex_y, byte_pos_xy1).r) * 0.25; ++} ++#endif ++ + float apply_contrast(float value) + { + // Apply simple S-curve +@@ -108,6 +128,9 @@ void main(void) + * by hand. + */ + center_pixel = floor(textureOut * tex_size); ++#if defined (QUAD_BAYER) ++ center_pixel = quad_cell(center_pixel); ++#endif + center_bytes.y = center_pixel.y; + + /* +@@ -127,6 +150,10 @@ void main(void) + center_bytes.x = floor(center_bytes.x); + center_bytes *= tex_step; + ++#if defined (QUAD_BAYER) ++ xcoords = center_pixel.x + vec2(-2.0, 2.0); ++ ycoords = center_pixel.y + vec2(-2.0, 2.0); ++#else + xcoords = center_bytes.x + vec2(-tex_step.x, tex_step.x); + ycoords = center_bytes.y + vec2(-tex_step.y, tex_step.y); + +@@ -143,8 +170,12 @@ void main(void) + * byte forward. + */ + xcoords[1] += (fract_x > THRESHOLD_H) ? tex_step.x : 0.0; ++#endif + +- vec2 alternate = mod(center_pixel.xy + tex_bayer_first_red, 2.0); ++ vec2 alternate = mod(center_pixel.xy / 2.0 + tex_bayer_first_red, 2.0); ++#if !defined (QUAD_BAYER) ++ alternate = mod(center_pixel.xy + tex_bayer_first_red, 2.0); ++#endif + bool even_col = alternate.x < 1.0; + bool even_row = alternate.y < 1.0; + +@@ -199,17 +230,31 @@ void main(void) + * patterns.z = (A0 + A1 + B0 + B1) / 4.0 + * patterns.w = (D0 + D1 + D2 + D3) / 4.0 + */ ++#if defined (QUAD_BAYER) ++ #define fetch(x, y) quad_fetch(vec2(x, y)) ++ float C = quad_fetch(center_pixel); ++#else + #define fetch(x, y) texture2D(tex_y, vec2(x, y)).r +- + float C = texture2D(tex_y, center_bytes).r; ++#endif + vec4 patterns = vec4( ++#if defined (QUAD_BAYER) ++ fetch(center_pixel.x, ycoords[0]), /* A0: (0,-1) */ ++ fetch(xcoords[0], center_pixel.y), /* B0: (-1,0) */ ++#else + fetch(center_bytes.x, ycoords[0]), /* A0: (0,-1) */ + fetch(xcoords[0], center_bytes.y), /* B0: (-1,0) */ ++#endif + fetch(xcoords[0], ycoords[0]), /* D0: (-1,-1) */ + fetch(xcoords[1], ycoords[0])); /* D1: (1,-1) */ + vec4 temp = vec4( ++#if defined (QUAD_BAYER) ++ fetch(center_pixel.x, ycoords[1]), /* A1: (0,1) */ ++ fetch(xcoords[1], center_pixel.y), /* B1: (1,0) */ ++#else + fetch(center_bytes.x, ycoords[1]), /* A1: (0,1) */ + fetch(xcoords[1], center_bytes.y), /* B1: (1,0) */ ++#endif + fetch(xcoords[1], ycoords[1]), /* D3: (1,1) */ + fetch(xcoords[0], ycoords[1])); /* D2: (-1,1) */ + patterns = (patterns + temp) * 0.5; +diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp +index 56446f55d44f..d60b2a2318a8 100644 +--- a/src/libcamera/software_isp/debayer.cpp ++++ b/src/libcamera/software_isp/debayer.cpp +@@ -56,8 +56,8 @@ LOG_DEFINE_CATEGORY(Debayer) + * \brief Construct a Debayer object + * \param[in] cm The camera manager + */ +-Debayer::Debayer(const CameraManager &cm) +- : bench_(cm, "Debayer") ++Debayer::Debayer(const CameraManager &cm, bool quadBayer) ++ : bench_(cm, "Debayer"), quadBayer_(quadBayer) + { + } + +diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h +index 28f1b857b388..6c52ed0249a4 100644 +--- a/src/libcamera/software_isp/debayer.h ++++ b/src/libcamera/software_isp/debayer.h +@@ -35,7 +35,7 @@ LOG_DECLARE_CATEGORY(Debayer) + class Debayer : public Object + { + public: +- Debayer(const CameraManager &cm); ++ Debayer(const CameraManager &cm, bool quadBayer = false); + virtual ~Debayer() = 0; + + virtual int configure(const StreamConfiguration &inputCfg, +@@ -81,6 +81,7 @@ public: + PixelFormat outputPixelFormat_; + bool swapRedBlueGains_; + Benchmark bench_; ++ bool quadBayer_; + + private: + virtual Size patternSize(PixelFormat inputFormat) = 0; +diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp +index ae06dc0d2a3d..f8cf81dad172 100644 +--- a/src/libcamera/software_isp/debayer_egl.cpp ++++ b/src/libcamera/software_isp/debayer_egl.cpp +@@ -24,6 +24,7 @@ + + #include "libcamera/internal/formats.h" + #include "libcamera/internal/framebuffer.h" ++#include "libcamera/internal/software_isp/quad_bayer.h" + + #include "../shaders/glsl_shaders.h" + +@@ -40,9 +41,11 @@ namespace libcamera { + * \brief Construct a DebayerEGL object + * \param[in] stats Statistics processing object + * \param[in] cm The camera manager ++ * \param[in] quadBayer Whether the sensor uses 2x2 same-colour cells + */ +-DebayerEGL::DebayerEGL(std::unique_ptr stats, const CameraManager &cm) +- : Debayer(cm), stats_(std::move(stats)) ++DebayerEGL::DebayerEGL(std::unique_ptr stats, const CameraManager &cm, ++ bool quadBayer) ++ : Debayer(cm, quadBayer), stats_(std::move(stats)) + { + } + +@@ -152,6 +155,8 @@ int DebayerEGL::initBayerShaders(PixelFormat inputFormat, PixelFormat outputForm + + /* Target gles 100 glsl requires "#version x" as first directive in shader */ + egl_.pushEnv(shaderEnv, "#version 100"); ++ if (quadBayer_) ++ egl_.pushEnv(shaderEnv, "#define QUAD_BAYER"); + + /* Specify GL_OES_EGL_image_external */ + egl_.pushEnv(shaderEnv, "#extension GL_OES_EGL_image_external: enable"); +@@ -290,6 +295,8 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, + { + if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) + return -EINVAL; ++ if (quadBayer_) ++ inputConfig_.patternSize = { 4, 4 }; + + if (stats_->configure(inputCfg) != 0) + return -EINVAL; +@@ -332,18 +339,22 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, + outputSize_ = outputCfg.size; + nativeOutputSize_ = outSizeRange.max; + +- window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & +- ~(inputConfig_.patternSize.width - 1); +- window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & +- ~(inputConfig_.patternSize.height - 1); +- window_.width = outputCfg.size.width; +- window_.height = outputCfg.size.height; ++ if (quadBayer_) { ++ window_ = quadBayerStatsWindow(inputCfg.size, outputCfg.size); ++ } else { ++ window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & ++ ~(inputConfig_.patternSize.width - 1); ++ window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & ++ ~(inputConfig_.patternSize.height - 1); ++ window_.width = outputCfg.size.width; ++ window_.height = outputCfg.size.height; ++ } + + /* + * Don't pass x,y from window_ since process() already adjusts for it. + * But crop the window to 2/3 of its width and height for speedup. + */ +- stats_->setWindow(Rectangle(window_.size())); ++ stats_->setWindow(quadBayer_ ? window_ : Rectangle(window_.size())); + + inputBufferCount_ = inputCfg.bufferCount; + outputBufferCount_ = outputCfg.bufferCount; +@@ -363,6 +374,9 @@ Size DebayerEGL::patternSize(PixelFormat inputFormat) + + std::vector DebayerEGL::formats(PixelFormat inputFormat) + { ++ if (quadBayer_ && !isQuadBayerInputFormatSupported(inputFormat)) ++ return {}; ++ + DebayerEGL::DebayerInputConfig config; + + if (getInputConfig(inputFormat, config) != 0) +@@ -409,8 +423,10 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara + * the input size. Keep the aspect ratio and prefer cropping over black + * bars. + */ +- GLfloat scale = std::max((GLfloat)outputSize_.width / nativeOutputSize_.width, +- (GLfloat)outputSize_.height / nativeOutputSize_.height); ++ GLfloat scale = quadBayer_ ++ ? 1.0f ++ : std::max((GLfloat)outputSize_.width / nativeOutputSize_.width, ++ (GLfloat)outputSize_.height / nativeOutputSize_.height); + GLfloat trans = -(1.0f - scale); + GLfloat projMatrix[] = { + scale, 0, 0, 0, +@@ -425,12 +441,8 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara + { +1.0f, +1.0f }, + { +1.0f, -1.0f }, + }; +- static const GLfloat tcoordinates[4][2] = { +- { 0.0f, 0.0f }, +- { 0.0f, 1.0f }, +- { 1.0f, 1.0f }, +- { 1.0f, 0.0f }, +- }; ++ textureCoordinates_ = softwareIspTextureCoordinates({ width_, height_ }, window_, ++ quadBayer_); + + /* vertexIn - bayer_8.vert */ + glEnableVertexAttribArray(attributeVertex_); +@@ -440,7 +452,7 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara + /* textureIn - bayer_8.vert */ + glEnableVertexAttribArray(attributeTexture_); + glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE, +- 2 * sizeof(GLfloat), tcoordinates); ++ 2 * sizeof(GLfloat), textureCoordinates_.data()); + + /* + * Set the sampler2D to the respective texture unit for each texutre +@@ -623,7 +635,9 @@ int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const Debaye + egl_.attachTextureToFBO(*eglImageOut); + setShaderVariableValues(*eglImageIn, params); + +- glViewport(0, 0, width_, height_); ++ const Rectangle viewport = softwareIspViewport({ width_, height_ }, outputSize_, ++ quadBayer_); ++ glViewport(viewport.x, viewport.y, viewport.width, viewport.height); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS); + +@@ -718,6 +732,15 @@ void DebayerEGL::stop() + + SizeRange DebayerEGL::sizes(PixelFormat inputFormat, const Size &inputSize) + { ++ if (quadBayer_) { ++ if (!isQuadBayerInputFormatSupported(inputFormat) || ++ !isQuadBayerInputSizeSupported(inputSize)) ++ return {}; ++ ++ Size logicalSize = quadBayerLogicalSize(inputSize); ++ return SizeRange(Size(2, 2), logicalSize, 2, 2); ++ } ++ + Size patternSize = this->patternSize(inputFormat); + unsigned int borderHeight = patternSize.height; + +diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h +index d6223f3a9724..8e6b4af91476 100644 +--- a/src/libcamera/software_isp/debayer_egl.h ++++ b/src/libcamera/software_isp/debayer_egl.h +@@ -9,6 +9,7 @@ + + #pragma once + ++#include + #include + #include + #include +@@ -40,7 +41,8 @@ class CameraManager; + class DebayerEGL : public Debayer + { + public: +- DebayerEGL(std::unique_ptr stats, const CameraManager &cm); ++ DebayerEGL(std::unique_ptr stats, const CameraManager &cm, ++ bool quadBayer = false); + ~DebayerEGL(); + + int configure(const StreamConfiguration &inputCfg, +@@ -93,6 +95,7 @@ private: + GLint textureUniformStrideFactor_; + GLint textureUniformBayerFirstRed_; + GLint textureUniformProjMatrix_; ++ std::array textureCoordinates_; + + GLint textureUniformBayerDataIn_; + +diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp +index c73a16ce0a29..74973d9ef99e 100644 +--- a/src/libcamera/software_isp/software_isp.cpp ++++ b/src/libcamera/software_isp/software_isp.cpp +@@ -26,6 +26,7 @@ + #include "libcamera/internal/bayer_format.h" + #include "libcamera/internal/framebuffer.h" + #include "libcamera/internal/software_isp/debayer_params.h" ++#include "libcamera/internal/software_isp/quad_bayer.h" + + #include "debayer_cpu.h" + #if HAVE_DEBAYER_EGL +@@ -98,8 +99,9 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, + } + + const CameraManager &cm = *pipe->cameraManager(); ++ const bool quadBayer = softwareIspNeedsCellCollapse(sensor->model()); + +- auto stats = std::make_unique(cm); ++ auto stats = std::make_unique(cm, quadBayer); + if (!stats->isValid()) { + LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object"; + return; +@@ -121,7 +123,7 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, + + if (!softISPMode || softISPMode == "gpu") { + if (eGL::isAvailable()) { +- debayer_ = std::make_unique(std::move(stats), cm); ++ debayer_ = std::make_unique(std::move(stats), cm, quadBayer); + } else { + LOG(SoftwareIsp, Info) + << "EGL not available, falling back to CPU debayer"; +@@ -129,6 +131,13 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, + } + + #endif ++ if (!debayer_ && quadBayer) { ++ LOG(SoftwareIsp, Error) ++ << "Quad Bayer sensor " << sensor->model() ++ << " requires the EGL software ISP"; ++ return; ++ } ++ + if (!debayer_) + debayer_ = std::make_unique(std::move(stats), cm); + +diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp +index 7fb77ce7ddd6..0d3359422caf 100644 +--- a/src/libcamera/software_isp/swstats_cpu.cpp ++++ b/src/libcamera/software_isp/swstats_cpu.cpp +@@ -17,6 +17,7 @@ + + #include "libcamera/internal/bayer_format.h" + #include "libcamera/internal/mapped_framebuffer.h" ++#include "libcamera/internal/software_isp/quad_bayer.h" + + namespace libcamera { + +@@ -159,8 +160,8 @@ namespace libcamera { + + LOG_DEFINE_CATEGORY(SwStatsCpu) + +-SwStatsCpu::SwStatsCpu(const CameraManager &cm) +- : sharedStats_("softIsp_stats"), bench_(cm, "CPU stats") ++SwStatsCpu::SwStatsCpu(const CameraManager &cm, bool quadBayer) ++ : quadBayer_(quadBayer), sharedStats_("softIsp_stats"), bench_(cm, "CPU stats") + { + if (!sharedStats_) + LOG(SwStatsCpu, Error) +@@ -323,6 +324,34 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats) + SWSTATS_FINISH_LINE_STATS() + } + ++void SwStatsCpu::statsQuadRGGB10P(const uint8_t *src[], SwIspStats &stats) ++{ ++ const unsigned int widthInBytes = window_.width * 5 / 4; ++ ++ SWSTATS_START_LINE_STATS(uint8_t) ++ (void)g2; ++ ++ /* One logical RGGB tile occupies a 4x4 physical quad-cell block. */ ++ for (unsigned int x = 0; x + 4 < widthInBytes; x += 10) { ++ auto rgb = normalizeQuadBayerTile( ++ [&](unsigned int cellX, unsigned int cellY) { ++ const unsigned int row = cellY * 2; ++ const unsigned int column = x + cellX * 2; ++ return static_cast( ++ (src[row][column] + src[row][column + 1] + ++ src[row + 1][column] + src[row + 1][column + 1]) / ++ 4); ++ }, ++ quadBayerOrder_); ++ r = rgb[0]; ++ g = rgb[1]; ++ b = rgb[2]; ++ SWSTATS_ACCUMULATE_LINE_STATS(1) ++ } ++ ++ SWSTATS_FINISH_LINE_STATS() ++} ++ + void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats) + { + const uint8_t *src0 = src[1] + window_.x * 3 / 2; +@@ -473,6 +502,18 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + ++ if (quadBayer_) { ++ if (!isQuadBayerInputFormatSupported(inputCfg.pixelFormat) || ++ !isQuadBayerInputSizeSupported(inputCfg.size)) ++ return -EINVAL; ++ quadBayerOrder_ = bayerFormat.order; ++ patternSize_ = { 4, 4 }; ++ xShift_ = 0; ++ sumShift_ = 0; ++ processFrame_ = &SwStatsCpu::processQuadBayerFrame; ++ return 0; ++ } ++ + if (bayerFormat.packing == BayerFormat::Packing::None && + setupStandardBayerOrder(bayerFormat.order) == 0) { + processFrame_ = &SwStatsCpu::processBayerFrame2; +@@ -594,6 +635,19 @@ void SwStatsCpu::processBayerFrame2(MappedFrameBuffer &in) + } + } + ++void SwStatsCpu::processQuadBayerFrame(MappedFrameBuffer &in) ++{ ++ const uint8_t *src = in.planes()[0].data() + window_.y * stride_; ++ const uint8_t *rows[4]; ++ ++ for (unsigned int y = 0; y + 3 < window_.height; y += 8) { ++ for (unsigned int row = 0; row < 4; ++row) ++ rows[row] = src + row * stride_ + window_.x * 5 / 4; ++ statsQuadRGGB10P(rows, stats_[0]); ++ src += stride_ * 8; ++ } ++} ++ + /** + * \brief Calculate statistics for a frame in one go + * \param[in] frame The frame number +diff --git a/test/meson.build b/test/meson.build +index e4450625ee4c..d41ce23c0fd3 100644 +--- a/test/meson.build ++++ b/test/meson.build +@@ -51,6 +51,7 @@ public_tests = [ + + internal_tests = [ + {'name': 'bayer-format', 'sources': ['bayer-format.cpp']}, ++ {'name': 'quad-bayer', 'sources': ['quad-bayer.cpp']}, + {'name': 'byte-stream-buffer', 'sources': ['byte-stream-buffer.cpp']}, + {'name': 'camera-sensor', 'sources': ['camera-sensor.cpp']}, + {'name': 'delayed_controls', 'sources': ['delayed_controls.cpp']}, +diff --git a/test/quad-bayer.cpp b/test/quad-bayer.cpp +new file mode 100644 +index 000000000000..ddbee01c3f12 +--- /dev/null ++++ b/test/quad-bayer.cpp +@@ -0,0 +1,224 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (C) 2026 Frederic Laing ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "libcamera/internal/bayer_format.h" ++#include "libcamera/internal/sensor_cfa_layout.h" ++#include "libcamera/internal/software_isp/quad_bayer.h" ++ ++#include "test.h" ++ ++using namespace libcamera; ++ ++class QuadBayerTest : public Test ++{ ++protected: ++ int run() ++ { ++ const SensorCfaLayout imx371Layout = sensorCfaLayout("imx371"); ++ const SensorCfaLayout ordinaryLayout = sensorCfaLayout("imx519"); ++ constexpr std::array imx708Models = { ++ "imx708", "imx708_wide", "imx708_noir", "imx708_wide_noir" ++ }; ++ ++ if (imx371Layout.nativeCellSize != Size(2, 2) || ++ imx371Layout.softwareIspInputCellSize != Size(2, 2) || ++ imx371Layout.minimumDebinFactor() != 4) { ++ std::cerr << "unexpected IMX371 CFA layout" << std::endl; ++ return TestFail; ++ } ++ ++ for (std::string_view model : imx708Models) { ++ const SensorCfaLayout layout = sensorCfaLayout(model); ++ if (layout.nativeCellSize != Size(2, 2) || ++ layout.softwareIspInputCellSize != Size(1, 1) || ++ layout.minimumDebinFactor() != 4 || ++ softwareIspNeedsCellCollapse(model)) { ++ std::cerr << "unexpected IMX708 CFA layout for " << model ++ << std::endl; ++ return TestFail; ++ } ++ } ++ ++ if (ordinaryLayout.nativeCellSize != Size(1, 1) || ++ ordinaryLayout.softwareIspInputCellSize != Size(1, 1) || ++ ordinaryLayout.minimumDebinFactor() != 2) { ++ std::cerr << "unexpected ordinary Bayer CFA layout" << std::endl; ++ return TestFail; ++ } ++ ++ if (!softwareIspNeedsCellCollapse("imx371") || ++ softwareIspNeedsCellCollapse("imx519") || ++ softwareIspNeedsCellCollapse("")) { ++ std::cerr << "software ISP quad-cell collapse selection is not narrow" ++ << std::endl; ++ return TestFail; ++ } ++ ++ if (!isQuadBayerInputFormatSupported(formats::SRGGB10_CSI2P) || ++ isQuadBayerInputFormatSupported(formats::SRGGB8) || ++ isQuadBayerInputFormatSupported(formats::SRGGB10) || ++ isQuadBayerInputFormatSupported(formats::SRGGB12_CSI2P)) { ++ std::cerr << "unexpected quad Bayer input format support" << std::endl; ++ return TestFail; ++ } ++ ++ if (!isQuadBayerInputSizeSupported({ 4656, 3456 }) || ++ isQuadBayerInputSizeSupported({ 4655, 3456 }) || ++ isQuadBayerInputSizeSupported({ 4656, 3454 })) { ++ std::cerr << "unexpected quad Bayer input size support" << std::endl; ++ return TestFail; ++ } ++ ++ if (quadBayerLogicalSize({ 4656, 3456 }) != Size(2328, 1728)) { ++ std::cerr << "unexpected IMX371 logical size" << std::endl; ++ return TestFail; ++ } ++ ++ if (softwareIspViewport({ 4656, 3456 }, { 2328, 1728 }, true) != ++ Rectangle(0, 0, 2328, 1728) || ++ softwareIspViewport({ 4656, 3456 }, { 1600, 1200 }, true) != ++ Rectangle(0, 0, 1600, 1200)) { ++ std::cerr << "quad Bayer viewport does not match the logical FBO" << std::endl; ++ return TestFail; ++ } ++ if (softwareIspViewport({ 4656, 3456 }, { 1600, 1200 }, false) != ++ Rectangle(0, 0, 4656, 3456)) { ++ std::cerr << "ordinary Bayer viewport no longer uses physical geometry" << std::endl; ++ return TestFail; ++ } ++ ++ if (quadBayerStatsWindow({ 4656, 3456 }, { 1600, 1200 }) != ++ Rectangle(24, 0, 4608, 3456)) { ++ std::cerr << "quad Bayer statistics window is not centered physically" << std::endl; ++ return TestFail; ++ } ++ ++ const auto ordinaryCoordinates = softwareIspTextureCoordinates( ++ { 4656, 3456 }, { 0, 0, 4656, 3456 }, false); ++ constexpr std::array fullTexture = { ++ 0.0f, ++ 0.0f, ++ 0.0f, ++ 1.0f, ++ 1.0f, ++ 1.0f, ++ 1.0f, ++ 0.0f, ++ }; ++ if (ordinaryCoordinates != fullTexture) { ++ std::cerr << "ordinary Bayer texture coordinates changed" << std::endl; ++ return TestFail; ++ } ++ ++ const Rectangle previewWindow = ++ quadBayerStatsWindow({ 4656, 3456 }, { 1454, 1080 }); ++ const auto quadCoordinates = softwareIspTextureCoordinates( ++ { 4656, 3456 }, previewWindow, true); ++ constexpr float tolerance = 0.000001f; ++ const std::array expectedQuad = { ++ 0.0f, ++ 0.0f, ++ 0.0f, ++ 1.0f, ++ 4652.0f / 4656.0f, ++ 1.0f, ++ 4652.0f / 4656.0f, ++ 0.0f, ++ }; ++ for (unsigned int i = 0; i < quadCoordinates.size(); ++i) { ++ if (std::abs(quadCoordinates[i] - expectedQuad[i]) > tolerance) { ++ std::cerr << "quad Bayer texture coordinate " << i ++ << " is incorrect" << std::endl; ++ return TestFail; ++ } ++ } ++ ++ if (quadBayerCellOrigin({ 4656, 3456 }, { 4656, 3456 }) != Point(4654, 3454) || ++ quadBayerCellOrigin({ 4656, 3456 }, { -2, -2 }) != Point(0, 0)) { ++ std::cerr << "quad Bayer edge sampling is not clamped to complete cells" << std::endl; ++ return TestFail; ++ } ++ ++ if (quadBayerOrderShifts(BayerFormat::RGGB) != Point(0, 0) || ++ quadBayerOrderShifts(BayerFormat::GRBG) != Point(2, 0) || ++ quadBayerOrderShifts(BayerFormat::GBRG) != Point(0, 2) || ++ quadBayerOrderShifts(BayerFormat::BGGR) != Point(2, 2)) { ++ std::cerr << "quad Bayer order shifts are incorrect" << std::endl; ++ return TestFail; ++ } ++ ++ constexpr std::array, 4> orderTiles = { { ++ { 100, 200, 204, 300 }, /* RGGB */ ++ { 200, 100, 300, 204 }, /* GRBG */ ++ { 200, 300, 100, 204 }, /* GBRG */ ++ { 300, 200, 204, 100 }, /* BGGR */ ++ } }; ++ constexpr std::array orders = { ++ BayerFormat::RGGB, ++ BayerFormat::GRBG, ++ BayerFormat::GBRG, ++ BayerFormat::BGGR, ++ }; ++ for (unsigned int index = 0; index < orders.size(); ++index) { ++ auto rgb = normalizeQuadBayerTile( ++ [&](unsigned int x, unsigned int y) { ++ return orderTiles[index][y * 2 + x]; ++ }, ++ orders[index]); ++ if (rgb != std::array{ 100, 202, 300 }) { ++ std::cerr << "quad Bayer order normalization failed" << std::endl; ++ return TestFail; ++ } ++ } ++ ++ /* Physical quad-RGGB tile: 2x2 samples for each logical colour. */ ++ constexpr std::array physical = { ++ 100, ++ 104, ++ 200, ++ 204, ++ 108, ++ 112, ++ 208, ++ 212, ++ 300, ++ 304, ++ 400, ++ 404, ++ 308, ++ 312, ++ 408, ++ 412, ++ }; ++ constexpr std::array expected = { 106, 206, 306, 406 }; ++ ++ for (unsigned int y = 0; y < 2; ++y) { ++ for (unsigned int x = 0; x < 2; ++x) { ++ uint16_t value = averageQuadBayerCell( ++ [&](unsigned int px, unsigned int py) { ++ return physical[py * 4 + px]; ++ }, ++ x, y); ++ if (value != expected[y * 2 + x]) { ++ std::cerr << "unexpected logical sample at " << x << ',' << y ++ << ": " << value << std::endl; ++ return TestFail; ++ } ++ } ++ } ++ ++ return TestPass; ++ } ++}; ++ ++TEST_REGISTER(QuadBayerTest) +-- +2.55.0 + diff --git a/temp/libcamera/APKBUILD b/temp/libcamera/APKBUILD index 3857ef249..d9f66afc4 100644 --- a/temp/libcamera/APKBUILD +++ b/temp/libcamera/APKBUILD @@ -3,7 +3,7 @@ maintainer="Robert Mader " pkgname=libcamera _pkgver=0.7.2 pkgver=9999$_pkgver -pkgrel=2 +pkgrel=3 pkgdesc="Linux camera framework" url="https://libcamera.org/" arch="all" @@ -52,6 +52,7 @@ _tuning_files=" imx371.yaml imx376.yaml imx519.yaml + imx800.yaml imx858.yaml s5k3l6xx.yaml s5kjn1.yaml @@ -59,6 +60,12 @@ _tuning_files=" source="https://gitlab.freedesktop.org/camera/libcamera/-/archive/v$_pkgver/libcamera-v$_pkgver.tar.gz 0001-libcamera-simple-Enable-softISP-for-the-Pinephone.patch 0002-libcamera-simple-Skip-hwISP-formats-if-swISP-is-acti.patch + 0003-software_isp-debayer_egl-Re-add-log-for-failing-DMAB.patch + 0004-ipa-simple-Derive-AGC-minimum-gain-step-from-sensor-.patch + 0005-debayer-Further-demote-Unsupported-input-format-log.patch + 0006-debayer_egl-Use-untransposed-matrix-to-fix-GLES-2.0.patch + 0007-simple-Limit-swISP-output-sizes.patch + 0008-libcamera-Add-quad-Bayer-CFA-layout-support.patch qcam.desktop $_tuning_files " @@ -173,15 +180,22 @@ py3() { sha512sums=" b317cdc96f61682ee0bf26dfb7c4d6441db9a9c5b93ab53528121a8cd44862b3d59cbaa3881bb0837e3e381564c184c7b742124aef4267eea754c6083de353c1 libcamera-v0.7.2.tar.gz -eef5e38efc874f2049d17f08b04d9f21c3e4616f66873358cd2c446e651f78d6952ae25ee13a6ef917e3f85bc7b6a6991152e42c8efd8c7f5bff69829a518826 0001-libcamera-simple-Enable-softISP-for-the-Pinephone.patch -191dfa09cfd08bcdf0d0d07410d17af590cf78b8bc8f8b7587fd2dc1c645a9ca07f2926b5d98381c011319b1f4e1d6195dd0d6dba65fd8c723882e6be0d6117d 0002-libcamera-simple-Skip-hwISP-formats-if-swISP-is-acti.patch +c6e4d44758bc3afa639d5dfa30113e92270094f1dca8505982e85629ff88fab890ec3f14098600bdf7498259becea4bdadbff117aa96b8bc0a043ffeb4fb2f4f 0001-libcamera-simple-Enable-softISP-for-the-Pinephone.patch +1a84267c138192f3999b870985030b0cf9bf0b61b911481bdef73d6a377bea4159f438a21180db87c3ff97c9da9f00fb0885cfedde828f4237d0ce03196d6ed7 0002-libcamera-simple-Skip-hwISP-formats-if-swISP-is-acti.patch +e90c6c742a594e6af2efec8b9ce7cf133ed294645840f72a07408eabc010e50192462a653ef6e43b4558c9390ac516d0eefbc606691da55e604f5d20f871a0db 0003-software_isp-debayer_egl-Re-add-log-for-failing-DMAB.patch +6159dc60692382a712c683fa63bb131bd1d4978e1e639b90940ae5f340ac396d1f9b1abfb9b450cf29c0e484018af3f80e87cc9a4b7b4e856038bd4154bdab2c 0004-ipa-simple-Derive-AGC-minimum-gain-step-from-sensor-.patch +de5fd287fea2c71754beb7d72a6fd739a94ec7a47ed3ea09a60130bee1c08efdcf94e12b1830914e7e498b0d80a0cb4550a614aeb899129fba9d4f06918f5c63 0005-debayer-Further-demote-Unsupported-input-format-log.patch +875788a4571ec8485f17b0abf9681238acdf3c92a1152363d4416d68e171eb7a9231dd51b59b9f56e2815f6e81bbb33fbde5419e3b9b15e1963800ebe6eddb14 0006-debayer_egl-Use-untransposed-matrix-to-fix-GLES-2.0.patch +ac4e3c742fde4f8e4775bd2afbaaecb18fa49b869eb4309032bae985680f97443353eb384de19a9b588b632bb1711f82c318e0431214afe09068910b2e901252 0007-simple-Limit-swISP-output-sizes.patch +2b8ee82c974a0a54279700c70dc8973038f5e1b517cade87872319d5f0b5aa9be6df0a827a9098da3fb498238b1fe080419e9950e34def0edadca0247794f269 0008-libcamera-Add-quad-Bayer-CFA-layout-support.patch 22167a4eceb6d1b40b0b7c45fdf116c71684f5340de7f767535cb8e160ad9d2ae0f00cb3d461f73a344520a48a4641cf46226841d78bee06bfbfd2a91337f754 qcam.desktop 2ee566653b17d565d2c0de67cbe6ebad25df5aac6051cad78e6bae52016fa2ca0247e964d735ac776b3e0bd447ead9c4fcb26cd7629228cdba3ceb91b46f378f hi846.yaml -55dab9dcb9b1982143b9d63e4e51138c5bffd7c8f4a8e4e122750b3ad9d475446956bc297d9f564819ade4be75a575d06633a064e14f8ba3ef77986783c5f067 imx355.yaml +4942edd551dcf51630246f71e600c17760d3574d5d45e5530d6cfeefe2c9ab1a812fbe220f8e89d92e19a499c592b2507c801ca4eb74cd15fcecb60bff5c650b imx355.yaml 285c6d0d712df341c0117c3762ddc139f53f44e6be36c3388d1a62696429175e2916f392163131e21f410c3676f02f5d125f375171f45f8d82ad278aa5076c62 imx363.yaml -2ee566653b17d565d2c0de67cbe6ebad25df5aac6051cad78e6bae52016fa2ca0247e964d735ac776b3e0bd447ead9c4fcb26cd7629228cdba3ceb91b46f378f imx371.yaml -55dab9dcb9b1982143b9d63e4e51138c5bffd7c8f4a8e4e122750b3ad9d475446956bc297d9f564819ade4be75a575d06633a064e14f8ba3ef77986783c5f067 imx376.yaml -2ee566653b17d565d2c0de67cbe6ebad25df5aac6051cad78e6bae52016fa2ca0247e964d735ac776b3e0bd447ead9c4fcb26cd7629228cdba3ceb91b46f378f imx519.yaml +1b1f6d36311bee38006eaa1e380571fc9fbd5625fd72f63309426d3651483cef52a176d56cdebd2ce46cbb264684ede186740df169027d2bf27d0f3057da2f86 imx371.yaml +1b1f6d36311bee38006eaa1e380571fc9fbd5625fd72f63309426d3651483cef52a176d56cdebd2ce46cbb264684ede186740df169027d2bf27d0f3057da2f86 imx376.yaml +1b1f6d36311bee38006eaa1e380571fc9fbd5625fd72f63309426d3651483cef52a176d56cdebd2ce46cbb264684ede186740df169027d2bf27d0f3057da2f86 imx519.yaml +55dab9dcb9b1982143b9d63e4e51138c5bffd7c8f4a8e4e122750b3ad9d475446956bc297d9f564819ade4be75a575d06633a064e14f8ba3ef77986783c5f067 imx800.yaml 55dab9dcb9b1982143b9d63e4e51138c5bffd7c8f4a8e4e122750b3ad9d475446956bc297d9f564819ade4be75a575d06633a064e14f8ba3ef77986783c5f067 imx858.yaml 2ee566653b17d565d2c0de67cbe6ebad25df5aac6051cad78e6bae52016fa2ca0247e964d735ac776b3e0bd447ead9c4fcb26cd7629228cdba3ceb91b46f378f s5k3l6xx.yaml d59020e62520ee622282c85ce7bcdfe453c99becf48cc1005c5f8a4bb8b2511b19c89f38fb3c994e7c6a79d1f84e2ed5f5a09b4c5bdc1dc4a44b071dbccf0d44 s5kjn1.yaml diff --git a/temp/libcamera/imx355.yaml b/temp/libcamera/imx355.yaml index 2c5ea9e51..d5dd068d4 100644 --- a/temp/libcamera/imx355.yaml +++ b/temp/libcamera/imx355.yaml @@ -7,36 +7,23 @@ algorithms: blackLevel: 4096 - Awb: - Ccm: - # copied from s5kjn1.yaml ccms: - - ct: 2676 - ccm: [ 2.02152, -0.41859, -0.60292, - -0.59481, 2.21509, -0.62029, - -0.44029, -1.16865, 2.60894 ] - - ct: 2710 - ccm: [ 2.01597, -0.46478, -0.55119, - -0.59047, 2.14717, -0.55671, - -0.40793, -1.19946, 2.60739 ] - - ct: 2719 - ccm: [ 1.99894, -0.41626, -0.58268, - -0.59321, 2.18167, -0.58846, - -0.42518, -1.21384, 2.63903 ] - - ct: 2867 - ccm: [ 1.95177, -0.35904, -0.59274, - -0.44282, 1.97362, -0.53079, - -0.49059, -1.35379, 2.84439 ] - - ct: 3474 - ccm: [ 1.97712, -0.60854, -0.36858, - -0.47723, 1.91142, -0.43418, - -0.23092, -0.72246, 1.95339 ] - - ct: 3761 - ccm: [ 2.29903, -0.94652, -0.35251, - -0.51661, 1.90311, -0.38651, - -0.25114, -0.67501, 1.92615 ] - - ct: 6890 - ccm: [ 1.93867, -0.67486, -0.26381, - -0.43049, 2.00702, -0.57652, - -0.15696, -0.51345, 1.67041 ] + - ct: 2920 + ccm: [ 1.70452, -0.28006, -0.42446, + -0.66546, 2.27544, -0.60998, + -0.16166, -0.89357, 2.05524 ] + - ct: 3630 + ccm: [ 1.82077, -0.47914, -0.34163, + -0.58711, 2.13712, -0.55002, + -0.12653, -0.74572, 1.87226 ] + - ct: 4550 + ccm: [ 1.80579, -0.47998, -0.32582, + -0.52116, 2.23331, -0.71214, + -0.11717, -0.64942, 1.76659 ] + - ct: 5580 + ccm: [ 1.84933, -0.52533, -0.32401, + -0.45888, 2.07922, -0.62033, + -0.12065, -0.56643, 1.68708 ] - Adjust: - Agc: ... diff --git a/temp/libcamera/imx371.yaml b/temp/libcamera/imx371.yaml index e903d96a8..826440372 100644 --- a/temp/libcamera/imx371.yaml +++ b/temp/libcamera/imx371.yaml @@ -6,6 +6,33 @@ algorithms: - BlackLevel: blackLevel: 4096 - Awb: + - Ccm: + # copied from imx363.yaml + ccms: + - ct: 2701 + ccm: [ 1.66951, -0.35271, -0.31679, + -0.33594, 1.69731, -0.36137, + 0.07843, -1.25185, 2.17341 ] + - ct: 2789 + ccm: [ 1.74401, -0.44379, -0.30022, + -0.33322, 1.69889, -0.36567, + 0.09074, -1.27168, 2.18093 ] + - ct: 3025 + ccm: [ 1.71956, -0.37493, -0.34462, + -0.46994, 1.83453, -0.36459, + 0.01838, -1.03863, 2.02024 ] + - ct: 4021 + ccm: [ 1.65286, -0.42348, -0.22938, + -0.34752, 1.63472, -0.28719, + -0.02119, -0.65164, 1.67283 ] + - ct: 4125 + ccm: [ 1.89736, -0.65796, -0.23941, + -0.36603, 1.62015, -0.25412, + 0.01202, -0.66114, 1.64912 ] + - ct: 6521 + ccm: [ 1.64464, -0.50901, -0.13564, + -0.28189, 1.73169, -0.44979, + -0.02601, -0.51457, 1.54058 ] - Adjust: - Agc: ... diff --git a/temp/libcamera/imx376.yaml b/temp/libcamera/imx376.yaml index 2c5ea9e51..826440372 100644 --- a/temp/libcamera/imx376.yaml +++ b/temp/libcamera/imx376.yaml @@ -7,36 +7,32 @@ algorithms: blackLevel: 4096 - Awb: - Ccm: - # copied from s5kjn1.yaml + # copied from imx363.yaml ccms: - - ct: 2676 - ccm: [ 2.02152, -0.41859, -0.60292, - -0.59481, 2.21509, -0.62029, - -0.44029, -1.16865, 2.60894 ] - - ct: 2710 - ccm: [ 2.01597, -0.46478, -0.55119, - -0.59047, 2.14717, -0.55671, - -0.40793, -1.19946, 2.60739 ] - - ct: 2719 - ccm: [ 1.99894, -0.41626, -0.58268, - -0.59321, 2.18167, -0.58846, - -0.42518, -1.21384, 2.63903 ] - - ct: 2867 - ccm: [ 1.95177, -0.35904, -0.59274, - -0.44282, 1.97362, -0.53079, - -0.49059, -1.35379, 2.84439 ] - - ct: 3474 - ccm: [ 1.97712, -0.60854, -0.36858, - -0.47723, 1.91142, -0.43418, - -0.23092, -0.72246, 1.95339 ] - - ct: 3761 - ccm: [ 2.29903, -0.94652, -0.35251, - -0.51661, 1.90311, -0.38651, - -0.25114, -0.67501, 1.92615 ] - - ct: 6890 - ccm: [ 1.93867, -0.67486, -0.26381, - -0.43049, 2.00702, -0.57652, - -0.15696, -0.51345, 1.67041 ] + - ct: 2701 + ccm: [ 1.66951, -0.35271, -0.31679, + -0.33594, 1.69731, -0.36137, + 0.07843, -1.25185, 2.17341 ] + - ct: 2789 + ccm: [ 1.74401, -0.44379, -0.30022, + -0.33322, 1.69889, -0.36567, + 0.09074, -1.27168, 2.18093 ] + - ct: 3025 + ccm: [ 1.71956, -0.37493, -0.34462, + -0.46994, 1.83453, -0.36459, + 0.01838, -1.03863, 2.02024 ] + - ct: 4021 + ccm: [ 1.65286, -0.42348, -0.22938, + -0.34752, 1.63472, -0.28719, + -0.02119, -0.65164, 1.67283 ] + - ct: 4125 + ccm: [ 1.89736, -0.65796, -0.23941, + -0.36603, 1.62015, -0.25412, + 0.01202, -0.66114, 1.64912 ] + - ct: 6521 + ccm: [ 1.64464, -0.50901, -0.13564, + -0.28189, 1.73169, -0.44979, + -0.02601, -0.51457, 1.54058 ] - Adjust: - Agc: ... diff --git a/temp/libcamera/imx519.yaml b/temp/libcamera/imx519.yaml index e903d96a8..826440372 100644 --- a/temp/libcamera/imx519.yaml +++ b/temp/libcamera/imx519.yaml @@ -6,6 +6,33 @@ algorithms: - BlackLevel: blackLevel: 4096 - Awb: + - Ccm: + # copied from imx363.yaml + ccms: + - ct: 2701 + ccm: [ 1.66951, -0.35271, -0.31679, + -0.33594, 1.69731, -0.36137, + 0.07843, -1.25185, 2.17341 ] + - ct: 2789 + ccm: [ 1.74401, -0.44379, -0.30022, + -0.33322, 1.69889, -0.36567, + 0.09074, -1.27168, 2.18093 ] + - ct: 3025 + ccm: [ 1.71956, -0.37493, -0.34462, + -0.46994, 1.83453, -0.36459, + 0.01838, -1.03863, 2.02024 ] + - ct: 4021 + ccm: [ 1.65286, -0.42348, -0.22938, + -0.34752, 1.63472, -0.28719, + -0.02119, -0.65164, 1.67283 ] + - ct: 4125 + ccm: [ 1.89736, -0.65796, -0.23941, + -0.36603, 1.62015, -0.25412, + 0.01202, -0.66114, 1.64912 ] + - ct: 6521 + ccm: [ 1.64464, -0.50901, -0.13564, + -0.28189, 1.73169, -0.44979, + -0.02601, -0.51457, 1.54058 ] - Adjust: - Agc: ... diff --git a/temp/libcamera/imx800.yaml b/temp/libcamera/imx800.yaml new file mode 100644 index 000000000..2c5ea9e51 --- /dev/null +++ b/temp/libcamera/imx800.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: CC0-1.0 +%YAML 1.1 +--- +version: 1 +algorithms: + - BlackLevel: + blackLevel: 4096 + - Awb: + - Ccm: + # copied from s5kjn1.yaml + ccms: + - ct: 2676 + ccm: [ 2.02152, -0.41859, -0.60292, + -0.59481, 2.21509, -0.62029, + -0.44029, -1.16865, 2.60894 ] + - ct: 2710 + ccm: [ 2.01597, -0.46478, -0.55119, + -0.59047, 2.14717, -0.55671, + -0.40793, -1.19946, 2.60739 ] + - ct: 2719 + ccm: [ 1.99894, -0.41626, -0.58268, + -0.59321, 2.18167, -0.58846, + -0.42518, -1.21384, 2.63903 ] + - ct: 2867 + ccm: [ 1.95177, -0.35904, -0.59274, + -0.44282, 1.97362, -0.53079, + -0.49059, -1.35379, 2.84439 ] + - ct: 3474 + ccm: [ 1.97712, -0.60854, -0.36858, + -0.47723, 1.91142, -0.43418, + -0.23092, -0.72246, 1.95339 ] + - ct: 3761 + ccm: [ 2.29903, -0.94652, -0.35251, + -0.51661, 1.90311, -0.38651, + -0.25114, -0.67501, 1.92615 ] + - ct: 6890 + ccm: [ 1.93867, -0.67486, -0.26381, + -0.43049, 2.00702, -0.57652, + -0.15696, -0.51345, 1.67041 ] + - Adjust: + - Agc: +...