Show a patch.

GET /api/1.1/patches/26168/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 26168,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/26168/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/26168/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20260216190204.106922-3-johannes.goede@oss.qualcomm.com>",
    "date": "2026-02-16T19:02:01",
    "name": "[2/5] software_isp: debayer_cpu: Add per render thread data",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "d247763ca53cea09a857a2c338996046f4e3ae77",
    "submitter": {
        "id": 242,
        "url": "https://patchwork.libcamera.org/api/1.1/people/242/?format=api",
        "name": "Hans de Goede",
        "email": "johannes.goede@oss.qualcomm.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/26168/mbox/",
    "series": [
        {
            "id": 5794,
            "url": "https://patchwork.libcamera.org/api/1.1/series/5794/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5794",
            "date": "2026-02-16T19:01:59",
            "name": "software_isp: debayer_cpu: Add multi-threading support",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/5794/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/26168/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/26168/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>",
        "X-Original-To": "parsemail@patchwork.libcamera.org",
        "Delivered-To": "parsemail@patchwork.libcamera.org",
        "Received": [
            "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 6932BC3240\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 16 Feb 2026 19:02:19 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1BB9F6220D;\n\tMon, 16 Feb 2026 20:02:19 +0100 (CET)",
            "from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com\n\t[205.220.168.131])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 45D0962217\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Feb 2026 20:02:17 +0100 (CET)",
            "from pps.filterd (m0279865.ppops.net [127.0.0.1])\n\tby mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id\n\t61GC5oZc2987647 for <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Feb 2026 19:02:09 GMT",
            "from mail-vk1-f197.google.com (mail-vk1-f197.google.com\n\t[209.85.221.197])\n\tby mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4cbfuw340s-1\n\t(version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT)\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Feb 2026 19:02:09 +0000 (GMT)",
            "by mail-vk1-f197.google.com with SMTP id\n\t71dfb90a1353d-5662a8e87a0so6082184e0c.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 16 Feb 2026 11:02:09 -0800 (PST)",
            "from shalem\n\t(2001-1c00-0c32-7800-5bfa-a036-83f0-f9ec.cable.dynamic.v6.ziggo.nl.\n\t[2001:1c00:c32:7800:5bfa:a036:83f0:f9ec])\n\tby smtp.gmail.com with ESMTPSA id\n\ta640c23a62f3a-b8fc735e587sm276698966b.2.2026.02.16.11.02.06\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 16 Feb 2026 11:02:07 -0800 (PST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=qualcomm.com header.i=@qualcomm.com\n\theader.b=\"pifdwMZV\"; dkim=pass (2048-bit key;\n\tunprotected) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com\n\theader.b=\"GcySVJCA\"; dkim-atps=neutral",
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h=\n\tcc:content-transfer-encoding:date:from:in-reply-to:message-id\n\t:mime-version:references:subject:to; s=qcppdkim1; bh=KGJwpNxvV2U\n\twvsIFRaK7R3GhgzvleU0SLTfhWhMvPDM=; b=pifdwMZVHisoO8+2+m81C716Q1F\n\teMDo1KAwQw4u2KZozc3yt4QJ+3t9hTbrxg0qfWgnKOViPmSkn55bJSRZvfu8AMKc\n\t0PJwj97ZjoiuVidhnjszPnsQZ6rOhPihjiBQHsFsbymokT1B5RZ0WUznCFTGZnam\n\tDwwVLbXy6lwx19sON0CpRr313WrzEPR/KBIS7fgBrNySEZZOXNT7jgd8QbeGrX+D\n\tJ/iJIJIz6josPy9VZ6mpvRuMpmw8IkaW6frCO3JMMsLNbxN8KBrRlyB7tkingA58\n\tqpQq75Z++aTW0BDNnyPauauhkpALiXm4EcZvcvtm7u/+AR2f2Uy3wz5U0zw==",
            "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=oss.qualcomm.com; s=google; t=1771268528; x=1771873328;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=KGJwpNxvV2UwvsIFRaK7R3GhgzvleU0SLTfhWhMvPDM=;\n\tb=GcySVJCA3eKDNP1Fsb1yQoj9JGpgGIhRlMp9mYT/A/OVu5/bQX/GOdEkzCaWYf/HFE\n\t+3ECL5VJY4niOSTa3kiDi+bTuo20P3WQ7E4IsSM3+3FMUx17VdjG3dwt6mOHTWZb4F2U\n\t0Muqr2ACeFEaZp9zhGbKNVnsRy4u99TuBXx88dFyR39h7b5AJytPZvSnHqkGs/XvNO5r\n\ty5XrVcvpTTTr1mu9+8lqm3drnYKD5njjrkjpoPVuuvpo5vBcJVc5+8ny45FN7RzALGc/\n\tT/6LddlnO+6pMJXr43knwblHlAng8FVzMbvRwWVDPsPdfQpbicCnl2sFv1Bf+QvxaV0R\n\tsgXQ=="
        ],
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1771268528; x=1771873328;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to;\n\tbh=KGJwpNxvV2UwvsIFRaK7R3GhgzvleU0SLTfhWhMvPDM=;\n\tb=B+1gGs/S8Ik7wTf10EHAf8bLeiFFYQLNx7/M0OsziwS7MJKFEtKtg3biZvM9F2n3rc\n\tcNx2T26P6UGf5yYyBc69qlH6FJC9BTtOSSMXFoIwSGLeD+OrQBIMviWNxBqwIpKyre6p\n\tIj184Fw0upco9IdZkGkHRFmW1iAnNJxE9ZYESyO6Uxbb+neS+paHAbfNyvub3i2rBukm\n\ta0qpWD/DpP7SU5kOK043+wopx2YqoiHTKARBJK81iZJ596Kk35yjO8fLnOXPwHP+B5NI\n\tPhjpCtnzw1x2jdri6Q6ob7dKHQTLjscOCL2mtDOPn3HaryHx5KxHJjNig+cQ2O6vBgh2\n\t3Y1Q==",
        "X-Gm-Message-State": "AOJu0Yw7bfQg4Oc72Drf2Wiq68kDAmHefElHT7nbavu27gTeVb3MWEf1\n\t+R9D6NzTXwoMLZzcigM37aoZXj0TvaDGaoebKjbDKM9X3TCDLR1Fm3/Wk1bHeZIuY5yM/9Yha7w\n\tvGN2qQX0LCZgZgaXMGvLspvxwuGL69wPtVRM4ZKrwsoZD/KuoDmr8bd++D5lKS59bFIihQtU9eA\n\tGafSD0MXw0",
        "X-Gm-Gg": "AZuq6aKSWziFe/jszY3vPzFxIJcY1nCumBVstlizzo5IGRRs3zEkqV4/2Li6OuTyKWh\n\twtLzEpQFf8LutBT0lMrQywm/aja7lfa3lHzqL8TEbi02bbneg0TJN0nJJlo0zT2otn77eA14QWI\n\twIUFJtaeY7MiHFktWvulcRu8eeno/BwU85jWqOfH+avBTLxP9xBd4hhN7oCFOCNyLTvi2cO1j5C\n\tYP/Y6Hsivsp6ieorkqCdHl/Ke3i8JnmYi9zmETbJAwZEoI4Cn0Kc7ImaVs9Rcph8CbCYNcgBTiH\n\tBWJ7A77mfYsfIMimkpcI4re3hi+1DWX6nl/ImK9UXIdLZiuHp1TDG7sC46OvyKnQOrQWR3UNwLY\n\tC3QuDsGdKRYZuhyY1GUfuhegYDVmJSiw17jiuopHiNhzQUH/JYNrijxMlrW/FX5GJLgoWHaVldW\n\t1OeHEq9YQoZVa37spi8D6LAiiLyMPvUxOlahi6",
        "X-Received": [
            "by 2002:a05:6122:2a4c:b0:567:d87:e18c with SMTP id\n\t71dfb90a1353d-56889b8e750mr2305622e0c.9.1771268528179; \n\tMon, 16 Feb 2026 11:02:08 -0800 (PST)",
            "by 2002:a05:6122:2a4c:b0:567:d87:e18c with SMTP id\n\t71dfb90a1353d-56889b8e750mr2305572e0c.9.1771268527605; \n\tMon, 16 Feb 2026 11:02:07 -0800 (PST)"
        ],
        "From": "Hans de Goede <johannes.goede@oss.qualcomm.com>",
        "To": "libcamera-devel@lists.libcamera.org, Milan Zamazal <mzamazal@redhat.com>",
        "Cc": "Hans de Goede <johannes.goede@oss.qualcomm.com>",
        "Subject": "[PATCH 2/5] software_isp: debayer_cpu: Add per render thread data",
        "Date": "Mon, 16 Feb 2026 20:02:01 +0100",
        "Message-ID": "<20260216190204.106922-3-johannes.goede@oss.qualcomm.com>",
        "X-Mailer": "git-send-email 2.52.0",
        "In-Reply-To": "<20260216190204.106922-1-johannes.goede@oss.qualcomm.com>",
        "References": "<20260216190204.106922-1-johannes.goede@oss.qualcomm.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-Authority-Analysis": "v=2.4 cv=Jb+xbEKV c=1 sm=1 tr=0 ts=699369b1 cx=c_pps\n\ta=JIY1xp/sjQ9K5JH4t62bdg==:117 a=xqWC_Br6kY4A:10 a=HzLeVaNsDn8A:10\n\ta=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=Mpw57Om8IfrbqaoTuvik:22\n\ta=GgsMoib0sEa3-_RKJdDe:22 a=EUspDBNiAAAA:8 a=LZmg44-WHxdkjKT3r1sA:9\n\ta=tNoRWFLymzeba-QzToBc:22",
        "X-Proofpoint-ORIG-GUID": "Tnf95s2-LoQSF_CWipS46Gc7eyAHIkwq",
        "X-Proofpoint-GUID": "Tnf95s2-LoQSF_CWipS46Gc7eyAHIkwq",
        "X-Proofpoint-Spam-Details-Enc": "AW1haW4tMjYwMjE2MDE2MyBTYWx0ZWRfX9veWaIY+hMaM\n\tG50kJPbPUGs3IKJ7yu/3E4op1GiFqSQYG4b1QIJnTsY/H/10rfdkJnKsJKVccah6wp97Y1en0e7\n\tiobp5HywP+eZ/sfDLhJym1RiROoER4iNhAlcBRArcRX8ZDd563YAZDqrV4fuNXCy6ntTJBnHC1A\n\te6hZH/l0H+gGUKjah1lXDMI2S5tIL5rrY3pOI4BqSqG4zetMg7cVIr0iSAvfsDKlprQ6j5XgiD6\n\taRWDQyVP3JzU6teCQmT+UFQJxJKX7Ok7IBWzOwgxgdbZmWWL5jCEtxzKH9K8qA0QDpriSJKLiFN\n\tJfwjWXkiPAfwoqZZy3n+bL2iFjIE9UogkehHUkxiFuJc4RXxJOxzxkc9ePTVYaGeqiycyNUq/Yh\n\t5NOrh5d3V1INAkhw/DAmGk58eNZ7PljJPMz/b63yw453oQY0XdLAmgMq2eYQZoZb47jCFmzUZqf\n\tFByQqatzTEPd8CWm2Lg==",
        "X-Proofpoint-Virus-Version": "vendor=baseguard\n\tengine=ICAP:2.0.293, Aquarius:18.0.1121, Hydra:6.1.51,\n\tFMLib:17.12.100.49\n\tdefinitions=2026-02-16_06,2026-02-16_04,2025-10-01_01",
        "X-Proofpoint-Spam-Details": "rule=outbound_notspam policy=outbound score=0\n\tbulkscore=0 impostorscore=0 adultscore=0 suspectscore=0 spamscore=0\n\tlowpriorityscore=0 priorityscore=1501 phishscore=0 clxscore=1015\n\tmalwarescore=0 classifier=typeunknown authscore=0 authtc= authcc=\n\troute=outbound adjust=0 reason=mlx scancount=1\n\tengine=8.22.0-2601150000\n\tdefinitions=main-2602160163",
        "X-BeenThere": "libcamera-devel@lists.libcamera.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Add a DebayerCpuThreadData data struct and use this in the inner render\nloop. This contains data which needs to be separate per thread.\n\nThis is a preparation patch for making DebayerCpu support multi-threading.\n\nNote this passed the DebayerCpuThreadData with a pointer rather then by\nreference, because passing by reference is not supported for functions\npassed as the thread function to std::thread().\n\nBenchmarking on the Uno-Q with a weak CPU which is good for performance\ntesting, shows 146-147ms per 3272x2464 frame both before and after this\nchange, with things maybe being 0.5 ms slower after this change.\n\nSigned-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>\n---\n src/libcamera/software_isp/debayer_cpu.cpp | 90 ++++++++++++++--------\n src/libcamera/software_isp/debayer_cpu.h   | 30 +++++---\n 2 files changed, 77 insertions(+), 43 deletions(-)",
    "diff": "diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\nindex 97c1959a..e1d3c164 100644\n--- a/src/libcamera/software_isp/debayer_cpu.cpp\n+++ b/src/libcamera/software_isp/debayer_cpu.cpp\n@@ -41,7 +41,7 @@ namespace libcamera {\n  * \\param[in] configuration The global configuration\n  */\n DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats, const GlobalConfiguration &configuration)\n-\t: Debayer(configuration), stats_(std::move(stats))\n+\t: Debayer(configuration), stats_(std::move(stats)), threadCount_(1)\n {\n \t/*\n \t * Reading from uncached buffers may be very slow.\n@@ -555,8 +555,9 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg,\n \t\t\t    2 * lineBufferPadding_;\n \n \tif (enableInputMemcpy_) {\n-\t\tfor (unsigned int i = 0; i <= inputConfig_.patternSize.height; i++)\n-\t\t\tlineBuffers_[i].resize(lineBufferLength_);\n+\t\tfor (unsigned int i = 0; i < threadCount_; i++)\n+\t\t\tfor (unsigned int j = 0; j <= inputConfig_.patternSize.height; j++)\n+\t\t\t\tthreadData_[i].lineBuffers[j].resize(lineBufferLength_);\n \t}\n \n \treturn 0;\n@@ -600,7 +601,8 @@ DebayerCpu::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size\n \treturn std::make_tuple(stride, stride * size.height);\n }\n \n-void DebayerCpu::setupInputMemcpy(const uint8_t *linePointers[])\n+void DebayerCpu::setupInputMemcpy(const uint8_t *linePointers[],\n+\t\t\t\t  DebayerCpuThreadData *threadData)\n {\n \tconst unsigned int patternHeight = inputConfig_.patternSize.height;\n \n@@ -608,14 +610,14 @@ void DebayerCpu::setupInputMemcpy(const uint8_t *linePointers[])\n \t\treturn;\n \n \tfor (unsigned int i = 0; i < patternHeight; i++) {\n-\t\tmemcpy(lineBuffers_[i].data(),\n+\t\tmemcpy(threadData->lineBuffers[i].data(),\n \t\t       linePointers[i + 1] - lineBufferPadding_,\n \t\t       lineBufferLength_);\n-\t\tlinePointers[i + 1] = lineBuffers_[i].data() + lineBufferPadding_;\n+\t\tlinePointers[i + 1] = threadData->lineBuffers[i].data() + lineBufferPadding_;\n \t}\n \n \t/* Point lineBufferIndex_ to first unused lineBuffer */\n-\tlineBufferIndex_ = patternHeight;\n+\tthreadData->lineBufferIndex = patternHeight;\n }\n \n void DebayerCpu::shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src)\n@@ -629,66 +631,78 @@ void DebayerCpu::shiftLinePointers(const uint8_t *linePointers[], const uint8_t\n \t\t\t\t      (patternHeight / 2) * (int)inputConfig_.stride;\n }\n \n-void DebayerCpu::memcpyNextLine(const uint8_t *linePointers[])\n+void DebayerCpu::memcpyNextLine(const uint8_t *linePointers[],\n+\t\t\t\tDebayerCpuThreadData *threadData)\n {\n \tconst unsigned int patternHeight = inputConfig_.patternSize.height;\n \n \tif (!enableInputMemcpy_)\n \t\treturn;\n \n-\tmemcpy(lineBuffers_[lineBufferIndex_].data(),\n+\tmemcpy(threadData->lineBuffers[threadData->lineBufferIndex].data(),\n \t       linePointers[patternHeight] - lineBufferPadding_,\n \t       lineBufferLength_);\n-\tlinePointers[patternHeight] = lineBuffers_[lineBufferIndex_].data() + lineBufferPadding_;\n+\tlinePointers[patternHeight] = threadData->lineBuffers[threadData->lineBufferIndex].data() + lineBufferPadding_;\n \n-\tlineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1);\n+\tthreadData->lineBufferIndex = (threadData->lineBufferIndex + 1) % (patternHeight + 1);\n }\n \n-void DebayerCpu::process2(uint32_t frame, const uint8_t *src, uint8_t *dst)\n+void DebayerCpu::process2(uint32_t frame, const uint8_t *src, uint8_t *dst,\n+\t\t\t  DebayerCpuThreadData *threadData)\n {\n-\tunsigned int yEnd = window_.height;\n \t/* Holds [0] previous- [1] current- [2] next-line */\n \tconst uint8_t *linePointers[3];\n \n \t/* Adjust src to top left corner of the window */\n-\tsrc += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;\n+\tsrc += (window_.y + threadData->yStart) * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;\n \n \t/* [x] becomes [x - 1] after initial shiftLinePointers() call */\n-\tif (window_.y) {\n+\tif (window_.y + threadData->yStart) {\n \t\tlinePointers[1] = src - inputConfig_.stride; /* previous-line */\n \t\tlinePointers[2] = src;\n \t} else {\n-\t\t/* window_.y == 0, use the next line as prev line */\n+\t\t/* Top line, use the next line as prev line */\n \t\tlinePointers[1] = src + inputConfig_.stride;\n \t\tlinePointers[2] = src;\n+\t}\n+\n+\tif (window_.y == 0 && threadData->yEnd == window_.height) {\n \t\t/*\n \t\t * Last 2 lines also need special handling.\n \t\t * (And configure() ensures that yEnd >= 2.)\n \t\t */\n-\t\tyEnd -= 2;\n+\t\tthreadData->yEnd -= 2;\n+\t\tthreadData->processLastLinesSeperately = true;\n+\t} else {\n+\t\tthreadData->processLastLinesSeperately = false;\n \t}\n \n-\tsetupInputMemcpy(linePointers);\n+\tsetupInputMemcpy(linePointers, threadData);\n \n-\tfor (unsigned int y = 0; y < yEnd; y += 2) {\n+\t/*\n+\t * Note y is the line-number *inside* the window, since stats_' window\n+\t * is the stats window inside/relative to the debayer window. IOW for\n+\t * single thread rendering y goes from 0 - window_.height.\n+\t */\n+\tfor (unsigned int y = threadData->yStart; y < threadData->yEnd; y += 2) {\n \t\tshiftLinePointers(linePointers, src);\n-\t\tmemcpyNextLine(linePointers);\n+\t\tmemcpyNextLine(linePointers, threadData);\n \t\tstats_->processLine0(frame, y, linePointers, &statsBuffer_);\n \t\t(this->*debayer0_)(dst, linePointers);\n \t\tsrc += inputConfig_.stride;\n \t\tdst += outputConfig_.stride;\n \n \t\tshiftLinePointers(linePointers, src);\n-\t\tmemcpyNextLine(linePointers);\n+\t\tmemcpyNextLine(linePointers, threadData);\n \t\t(this->*debayer1_)(dst, linePointers);\n \t\tsrc += inputConfig_.stride;\n \t\tdst += outputConfig_.stride;\n \t}\n \n-\tif (window_.y == 0) {\n+\tif (threadData->processLastLinesSeperately) {\n \t\tshiftLinePointers(linePointers, src);\n-\t\tmemcpyNextLine(linePointers);\n-\t\tstats_->processLine0(frame, yEnd, linePointers, &statsBuffer_);\n+\t\tmemcpyNextLine(linePointers, threadData);\n+\t\tstats_->processLine0(frame, threadData->yEnd, linePointers, &statsBuffer_);\n \t\t(this->*debayer0_)(dst, linePointers);\n \t\tsrc += inputConfig_.stride;\n \t\tdst += outputConfig_.stride;\n@@ -702,7 +716,8 @@ void DebayerCpu::process2(uint32_t frame, const uint8_t *src, uint8_t *dst)\n \t}\n }\n \n-void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst)\n+void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst,\n+\t\t\t  DebayerCpuThreadData *threadData)\n {\n \t/*\n \t * This holds pointers to [0] 2-lines-up [1] 1-line-up [2] current-line\n@@ -711,7 +726,7 @@ void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst)\n \tconst uint8_t *linePointers[5];\n \n \t/* Adjust src to top left corner of the window */\n-\tsrc += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;\n+\tsrc += (window_.y + threadData->yStart) * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;\n \n \t/* [x] becomes [x - 1] after initial shiftLinePointers() call */\n \tlinePointers[1] = src - 2 * inputConfig_.stride;\n@@ -719,31 +734,36 @@ void DebayerCpu::process4(uint32_t frame, const uint8_t *src, uint8_t *dst)\n \tlinePointers[3] = src;\n \tlinePointers[4] = src + inputConfig_.stride;\n \n-\tsetupInputMemcpy(linePointers);\n+\tsetupInputMemcpy(linePointers, threadData);\n \n-\tfor (unsigned int y = 0; y < window_.height; y += 4) {\n+\t/*\n+\t * Note y is the line-number *inside* the window, since stats_' window\n+\t * is the stats window inside/relative to the debayer window. IOW for\n+\t * single thread rendering y goes from 0 - window_.height.\n+\t */\n+\tfor (unsigned int y = threadData->yStart; y < threadData->yEnd; y += 4) {\n \t\tshiftLinePointers(linePointers, src);\n-\t\tmemcpyNextLine(linePointers);\n+\t\tmemcpyNextLine(linePointers, threadData);\n \t\tstats_->processLine0(frame, y, linePointers, &statsBuffer_);\n \t\t(this->*debayer0_)(dst, linePointers);\n \t\tsrc += inputConfig_.stride;\n \t\tdst += outputConfig_.stride;\n \n \t\tshiftLinePointers(linePointers, src);\n-\t\tmemcpyNextLine(linePointers);\n+\t\tmemcpyNextLine(linePointers, threadData);\n \t\t(this->*debayer1_)(dst, linePointers);\n \t\tsrc += inputConfig_.stride;\n \t\tdst += outputConfig_.stride;\n \n \t\tshiftLinePointers(linePointers, src);\n-\t\tmemcpyNextLine(linePointers);\n+\t\tmemcpyNextLine(linePointers, threadData);\n \t\tstats_->processLine2(frame, y, linePointers, &statsBuffer_);\n \t\t(this->*debayer2_)(dst, linePointers);\n \t\tsrc += inputConfig_.stride;\n \t\tdst += outputConfig_.stride;\n \n \t\tshiftLinePointers(linePointers, src);\n-\t\tmemcpyNextLine(linePointers);\n+\t\tmemcpyNextLine(linePointers, threadData);\n \t\t(this->*debayer3_)(dst, linePointers);\n \t\tsrc += inputConfig_.stride;\n \t\tdst += outputConfig_.stride;\n@@ -868,10 +888,12 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output\n \n \tstats_->startFrame(frame, &statsBuffer_, 1);\n \n+\tthreadData_[0].yStart = 0;\n+\tthreadData_[0].yEnd = window_.height;\n \tif (inputConfig_.patternSize.height == 2)\n-\t\tprocess2(frame, in.planes()[0].data(), out.planes()[0].data());\n+\t\tprocess2(frame, in.planes()[0].data(), out.planes()[0].data(), &threadData_[0]);\n \telse\n-\t\tprocess4(frame, in.planes()[0].data(), out.planes()[0].data());\n+\t\tprocess4(frame, in.planes()[0].data(), out.planes()[0].data(), &threadData_[0]);\n \n \tmetadata.planes()[0].bytesused = out.planes()[0].size();\n \ndiff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h\nindex 8abf5168..800b018c 100644\n--- a/src/libcamera/software_isp/debayer_cpu.h\n+++ b/src/libcamera/software_isp/debayer_cpu.h\n@@ -74,6 +74,19 @@ private:\n \t */\n \tusing debayerFn = void (DebayerCpu::*)(uint8_t *dst, const uint8_t *src[]);\n \n+\t/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */\n+\tstatic constexpr unsigned int kMaxLineBuffers = 5;\n+\n+\t/* Per render thread data */\n+\tstruct DebayerCpuThreadData {\n+\t\tunsigned int yStart;\n+\t\tunsigned int yEnd;\n+\t\tstd::vector<uint8_t> lineBuffers[kMaxLineBuffers];\n+\t\tunsigned int lineBufferIndex;\n+\t\t/* Stored here to avoid causing register pressure in inner loop */\n+\t\tbool processLastLinesSeperately;\n+\t};\n+\n \t/* 8-bit raw bayer format */\n \ttemplate<bool addAlphaByte, bool ccmEnabled>\n \tvoid debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);\n@@ -105,17 +118,14 @@ private:\n \tint setDebayerFunctions(PixelFormat inputFormat,\n \t\t\t\tPixelFormat outputFormat,\n \t\t\t\tbool ccmEnabled);\n-\tvoid setupInputMemcpy(const uint8_t *linePointers[]);\n+\tvoid setupInputMemcpy(const uint8_t *linePointers[], DebayerCpuThreadData *threadData);\n \tvoid shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src);\n-\tvoid memcpyNextLine(const uint8_t *linePointers[]);\n-\tvoid process2(uint32_t frame, const uint8_t *src, uint8_t *dst);\n-\tvoid process4(uint32_t frame, const uint8_t *src, uint8_t *dst);\n+\tvoid memcpyNextLine(const uint8_t *linePointers[], DebayerCpuThreadData *threadData);\n+\tvoid process2(uint32_t frame, const uint8_t *src, uint8_t *dst, DebayerCpuThreadData *threadData);\n+\tvoid process4(uint32_t frame, const uint8_t *src, uint8_t *dst, DebayerCpuThreadData *threadData);\n \tvoid updateGammaTable(const DebayerParams &params);\n \tvoid updateLookupTables(const DebayerParams &params);\n \n-\t/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */\n-\tstatic constexpr unsigned int kMaxLineBuffers = 5;\n-\n \tstatic constexpr unsigned int kRGBLookupSize = 256;\n \tstatic constexpr unsigned int kGammaLookupSize = 1024;\n \tstruct CcmColumn {\n@@ -143,12 +153,14 @@ private:\n \tdebayerFn debayer3_;\n \tRectangle window_;\n \tstd::unique_ptr<SwStatsCpu> stats_;\n-\tstd::vector<uint8_t> lineBuffers_[kMaxLineBuffers];\n \tunsigned int lineBufferLength_;\n \tunsigned int lineBufferPadding_;\n-\tunsigned int lineBufferIndex_;\n \tunsigned int xShift_; /* Offset of 0/1 applied to window_.x */\n \tbool enableInputMemcpy_;\n+\n+\tstatic constexpr unsigned int kMaxThreads = 4;\n+\tstruct DebayerCpuThreadData threadData_[kMaxThreads];\n+\tunsigned int threadCount_;\n };\n \n } /* namespace libcamera */\n",
    "prefixes": [
        "2/5"
    ]
}