[{"id":39595,"web_url":"https://patchwork.libcamera.org/comment/39595/","msgid":"<3601dc0a-8369-46bc-9234-177c107476f6@collabora.com>","date":"2026-07-06T11:56:43","subject":"Re: [PATCH v5 7/7] libcamera: software_isp: debayer_egl: Implement\n\tinput/output frame caching mechanism","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"Hi,\n\nOn 06.07.26 13:39, Bryan O'Donoghue wrote:\n> Implement a texture caching mechanism for both input and output frames and\n> for both types of input frame.\n>\n> The before/after on a Qualcomm x1e is:\n>\n> 9.737ms per frame\n> 5.691ms per frame\n>\n> The before/after on a Qualcomm sm8250 is:\n>\n> 21.710ms per frame\n> 17.336ms per frame\n>\n> for i in {1..20} do\n> cam -c /base/soc@0/cci@ac16000/i2c-bus@1/camera@10 -s width=1920,height=1080 --capture=60\n>\n> Interestingly there appears to be an absolute ~ 4.x ms per frame uplift as\n> opposed to what intuition might suggest a proportional.\n>\n> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>\n> ---\n>   src/libcamera/software_isp/debayer_egl.cpp | 112 ++++++++++++++++-----\n>   src/libcamera/software_isp/debayer_egl.h   |  13 ++-\n>   2 files changed, 96 insertions(+), 29 deletions(-)\n>\n> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp\n> index c3a7b7952..ca3353d9f 100644\n> --- a/src/libcamera/software_isp/debayer_egl.cpp\n> +++ b/src/libcamera/software_isp/debayer_egl.cpp\n> @@ -345,6 +345,9 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg,\n>   \t */\n>   \tstats_->setWindow(Rectangle(window_.size()));\n>   \n> +\tinputBufferCount_ = inputCfg.bufferCount;\n> +\toutputBufferCount_ = outputCfg.bufferCount;\n> +\n>   \treturn 0;\n>   }\n>   \n> @@ -504,36 +507,95 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara\n>   \treturn;\n>   }\n>   \n> -int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams &params, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer)\n> +eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer)\n>   {\n> -\tbool dmabuf_import_succeeded = false;\n> -\n> -\t/* eGL context switch */\n> -\tegl_.makeCurrent();\n> -\n> -\t/* Try to create texture for input buffer via dmabuf import */\n> -\tif (!eglImageBayerIn_->dmabuf_import_failed_) {\n> -\t\tif (egl_.createInputDMABufTexture2D(*eglImageBayerIn_, input->planes()[0].fd.get()) == 0)\n> -\t\t\tdmabuf_import_succeeded = true;\n> -\t\telse\n> -\t\t\tLOG(Debayer, Info) << \"Importing input buffer with DMABuf import failed, falling back to upload\";\n> +\tconst SharedFD &fd = input->planes()[0].fd;\n> +\tbool use_dmabuf = false;\n> +\n> +\tauto [input_cache, cache_miss] = eglImageBayerIn_.try_emplace(fd);\n> +\tif (cache_miss) {\n> +\t\tif (eglImageBayerIn_.size() > inputBufferCount_) {\n> +\t\t\teglImageBayerIn_.erase(inputRing_.front());\n> +\t\t\tinputRing_.pop_front();\n> +\t\t\tLOG(Debayer, Error) << \"Input cache \" << inputBufferCount_ << \" exceeded evicted entry\";\n> +\t\t}\n> +\t\tinput_cache->second = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_,\n> +\t\t\t\t\t\t\t\t height_, inputConfig_.stride, GL_TEXTURE0, 0);\n> +\t\tinputRing_.push_back(fd);\n> +\t}\n> +\teGLImage *eglImageIn = input_cache->second.get();\n> +\n> +\t/* Try to create texture for input buffer via dmabuf import only on cache miss */\n> +\tif (cache_miss) {\n> +\t\tif (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) == 0)\n> +\t\t\tuse_dmabuf = true;\n> +\t} else if (!eglImageIn->dmabuf_import_failed_) {\n> +\t\tuse_dmabuf = true;\n\nStyle nit: the only use of \"use_dmabuf = true\" here and above is to skip \nthe upload block below now. Just \"return eglImageIn;\" would be easier to \nread IMO (which is why I reordered things in \nhttps://gitlab.freedesktop.org/rmader/libcamera/-/commit/01473931ddb8940f192448cfee897bf563972e91).\n\n> +\t\tegl_.activateBindTexture(*eglImageIn);\n>   \t}\n>   \n> -\t/* Otherwise create texture for input buffer via upload from CPU */\n> -\tif (!dmabuf_import_succeeded) {\n> +\tif (!use_dmabuf) {\n>   \t\tinDmaSyncer->emplace(input->planes()[0].fd, DmaSyncer::SyncType::Read);\n>   \t\tinMapped->emplace(input, MappedFrameBuffer::MapFlag::Read);\n>   \t\tif (!inMapped->value().isValid()) {\n>   \t\t\tLOG(Debayer, Error) << \"mmap-ing buffer(s) failed\";\n> -\t\t\treturn -ENODEV;\n> +\t\t\tif (cache_miss) {\n> +\t\t\t\teglImageBayerIn_.erase(input_cache);\n> +\t\t\t\tinputRing_.pop_back();\n> +\t\t\t}\n> +\t\t\treturn nullptr;\n> +\t\t}\n> +\t\tif (cache_miss)\n> +\t\t\tegl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data());\n> +\t\telse\n> +\t\t\tegl_.updateTexture2D(*eglImageIn, inMapped->value().planes()[0].data());\n> +\t}\n> +\n> +\treturn eglImageIn;\n> +}\n> +\n> +eGLImage *DebayerEGL::getCachedOutputFrameBuffer(FrameBuffer *output)\n> +{\n> +\tconst SharedFD &fd = output->planes()[0].fd;\n> +\n> +\tauto [output_cache, cache_miss] = eglImageBayerOut_.try_emplace(fd);\n> +\tif (cache_miss) {\n> +\t\tif (eglImageBayerOut_.size() > outputBufferCount_) {\n> +\t\t\teglImageBayerOut_.erase(outputRing_.front());\n> +\t\t\toutputRing_.pop_front();\n> +\t\t\tLOG(Debayer, Error) << \"Output cache \" << outputBufferCount_ << \" exceeded evicted entry\";\n>   \t\t}\n> -\t\tegl_.createTexture2D(*eglImageBayerIn_, inMapped->value().planes()[0].data());\n> +\t\toutput_cache->second = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width,\n> +\t\t\t\t\t\t\t\t  outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1);\n> +\t\tif (egl_.createOutputDMABufTexture2D(*output_cache->second, output->planes()[0].fd.get())) {\n> +\t\t\teglImageBayerOut_.erase(output_cache);\n> +\t\t\treturn nullptr;\n> +\t\t}\n> +\t\toutputRing_.push_back(fd);\n>   \t}\n> +\teGLImage *eglImageOut = output_cache->second.get();\n> +\n> +\treturn eglImageOut;\n> +}\n> +\n> +int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams &params, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer)\n> +{\n> +\teGLImage *eglImageIn;\n> +\teGLImage *eglImageOut;\n> +\n> +\t/* eGL context switch */\n> +\tegl_.makeCurrent();\n>   \n> -\t/* Generate the output render framebuffer as render to texture */\n> -\tegl_.createOutputDMABufTexture2D(*eglImageBayerOut_, output->planes()[0].fd.get());\n> +\teglImageIn = getCachedInputFrameBuffer(input, inMapped, inDmaSyncer);\n> +\tif (!eglImageIn)\n> +\t\treturn -ENOMEM;\n> +\teglImageOut = getCachedOutputFrameBuffer(output);\n> +\tif (!eglImageOut)\n> +\t\treturn -ENOMEM;\n> +\n> +\tegl_.attachTextureToFBO(*eglImageOut);\n> +\tsetShaderVariableValues(*eglImageIn, params);\n>   \n> -\tsetShaderVariableValues(*eglImageBayerIn_, params);\n>   \tglViewport(0, 0, width_, height_);\n>   \tglClear(GL_COLOR_BUFFER_BIT);\n>   \tglDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS);\n> @@ -615,19 +677,15 @@ int DebayerEGL::start()\n>   \tif (initBayerShaders(inputPixelFormat_, outputPixelFormat_))\n>   \t\treturn -EINVAL;\n>   \n> -\t/* Raw bayer input as texture */\n> -\teglImageBayerIn_ = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, height_, inputConfig_.stride, GL_TEXTURE0, 0);\n> -\n> -\t/* Texture we will render to */\n> -\teglImageBayerOut_ = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1);\n> -\n>   \treturn 0;\n>   }\n>   \n>   void DebayerEGL::stop()\n>   {\n> -\teglImageBayerOut_.reset();\n> -\teglImageBayerIn_.reset();\n> +\teglImageBayerOut_.clear();\n> +\teglImageBayerIn_.clear();\n> +\toutputRing_.clear();\n> +\tinputRing_.clear();\n>   \n>   \tif (programId_)\n>   \t\tglDeleteProgram(programId_);\n> diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h\n> index 348d7305b..dde6ba9bf 100644\n> --- a/src/libcamera/software_isp/debayer_egl.h\n> +++ b/src/libcamera/software_isp/debayer_egl.h\n> @@ -9,9 +9,11 @@\n>   \n>   #pragma once\n>   \n> +#include <deque>\n>   #include <memory>\n>   #include <stdint.h>\n>   #include <tuple>\n> +#include <unordered_map>\n>   #include <vector>\n>   \n>   #define GL_GLEXT_PROTOTYPES\n> @@ -68,14 +70,21 @@ private:\n>   \tvoid setShaderVariableValues(eGLImage &eGLImageIn, const DebayerParams &params);\n>   \tint debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams &params, std::optional<MappedFrameBuffer> *mappedInputBuffer, std::optional<DmaSyncer> *inputBufferDmaSyncer);\n>   \n> +\teGLImage *getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer);\n> +\teGLImage *getCachedOutputFrameBuffer(FrameBuffer *output);\n> +\n>   \t/* Shader program identifiers */\n>   \tGLuint vertexShaderId_ = 0;\n>   \tGLuint fragmentShaderId_ = 0;\n>   \tGLuint programId_ = 0;\n>   \n>   \t/* Pointer to object representing input texture */\n> -\tstd::unique_ptr<eGLImage> eglImageBayerIn_;\n> -\tstd::unique_ptr<eGLImage> eglImageBayerOut_;\n> +\tstd::unordered_map<SharedFD, std::unique_ptr<eGLImage>> eglImageBayerIn_;\n> +\tstd::unordered_map<SharedFD, std::unique_ptr<eGLImage>> eglImageBayerOut_;\n> +\tstd::deque<SharedFD> inputRing_;\n> +\tstd::deque<SharedFD> outputRing_;\n> +\tunsigned int inputBufferCount_;\n> +\tunsigned int outputBufferCount_;\n>   \n>   \t/* Shader parameters */\n>   \tfloat firstRed_x_;","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 38DD0C330A\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  6 Jul 2026 11:56:52 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7A51D66040;\n\tMon,  6 Jul 2026 13:56:51 +0200 (CEST)","from sender4-op-o11.zoho.com (sender4-op-o11.zoho.com\n\t[136.143.188.11])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id F0B2D66022\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  6 Jul 2026 13:56:49 +0200 (CEST)","by mx.zohomail.com with SMTPS id 1783339006171918.1161639019327;\n\tMon, 6 Jul 2026 04:56:46 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"BwIZ0Nc2\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1783339007; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=lIL48Vtul3y4X28EshGtB+HwXmQ/FM13ctToYXJSCgcu64TkKiqymZnw6XQEN6UjxaGpdCsyNNNc516VnjgrHqzMrmoblTOfu0jPHElyW1b35zTVcI00YfValzZdRIhv0scw7jfyBbRMaotQJ3i/w7bjN885t+DbO6Z+jsoNQbM=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1783339007;\n\th=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To;\n\tbh=pimGA5iMrfflP4MPr1DNAiWjblFrKvcCDMEm3ODoYrY=; \n\tb=anU+a62aSZ2NUHTZOuskZ0n9sXiyZ+CMiqf9W/wBKAQ+3beeZ7RBwGw4rgjO1yMR1D5rGrshGlyRKs2j+n/QLTvZvuPGLE2p1h7N7h7eLV3liID469iYkrgskWMA8HDXqh2j+W58d/bbzVNgauTM10HTqxD4w5/Od39LheMsJ7w=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1783339007;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:Cc:Cc:References:From:From:In-Reply-To:Content-Type:Content-Transfer-Encoding:Message-Id:Reply-To;\n\tbh=pimGA5iMrfflP4MPr1DNAiWjblFrKvcCDMEm3ODoYrY=;\n\tb=BwIZ0Nc2+ZOCNF5grJphOd9ppZk4vr5IYcwlRsy2Dr9SieYO6M101fG4wQTgq/BP\n\tAGqnCQVodqyiE92ZrM+qGZjQeHz2EOd8jRRkUK8dGEZJpXCuYjOM2IStk3xGrh7u02o\n\tYAeYxKW/RYZ0VXAlQOxzrH8GUkTA2jpGTAY/QQgk=","Message-ID":"<3601dc0a-8369-46bc-9234-177c107476f6@collabora.com>","Date":"Mon, 6 Jul 2026 13:56:43 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v5 7/7] libcamera: software_isp: debayer_egl: Implement\n\tinput/output frame caching mechanism","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tlibcamera-devel@lists.libcamera.org","Cc":"pavel@ucw.cz","References":"<20260706113900.2530549-1-bryan.odonoghue@linaro.org>\n\t<20260706113900.2530549-8-bryan.odonoghue@linaro.org>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<20260706113900.2530549-8-bryan.odonoghue@linaro.org>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","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>"}}]