| Message ID | 20260626113325.3218045-9-bryan.odonoghue@linaro.org |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
On 26.06.26 13:33, Bryan O'Donoghue wrote: > Implement a texture caching mechanism for both input and output frames and > for both types of input frame. > > The before/after on a Qualcomm x1e is: > > 9.737ms per frame > 5.691ms per frame > > The before/after on a Qualcomm sm8250 is: > > 21.710ms per frame > 17.336ms per frame > > for i in {1..20} do > cam -c /base/soc@0/cci@ac16000/i2c-bus@1/camera@10 -s width=1920,height=1080 --capture=60 > > Interestingly there appears to be an absolute ~ 4.x ms per frame uplift as > opposed to what intuition might suggest a proportional. > > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > src/libcamera/software_isp/debayer_egl.cpp | 87 +++++++++++++++++----- > src/libcamera/software_isp/debayer_egl.h | 10 ++- > 2 files changed, 75 insertions(+), 22 deletions(-) > > diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp > index 53bb67c17..fc37f0b75 100644 > --- a/src/libcamera/software_isp/debayer_egl.cpp > +++ b/src/libcamera/software_isp/debayer_egl.cpp > @@ -355,6 +355,9 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, > */ > stats_->setWindow(Rectangle(window_.size())); > > + inputBufferCount_ = inputCfg.bufferCount; > + outputBufferCount_ = outputCfg.bufferCount; > + > return 0; > } > > @@ -514,34 +517,84 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara > return; > } > > -int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) > +eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) > { > - /* eGL context switch */ > - egl_.makeCurrent(); > + auto [input_cache, cache_miss] = eglImageBayerIn_.try_emplace(input->planes()[0].fd.get()); > + if (cache_miss) { > + if (eglImageBayerIn_.size() > inputBufferCount_) { > + LOG(Debayer, Error) << "Input count " << inputBufferCount_ << " exhausted"; > + return nullptr; > + } > + input_cache->second = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, > + height_, inputConfig_.stride, GL_TEXTURE0, 0); > + } > + eGLImage *eglImageIn = input_cache->second.get(); > > /* Try to create texture for input buffer via dmabuf import */ > - if (use_dmabuf_) { > - if (egl_.createInputDMABufTexture2D(*eglImageBayerIn_, input->planes()[0].fd.get()) != 0) { > + if (use_dmabuf_ && cache_miss) { > + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { > use_dmabuf_ = false; > LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed, falling back to upload"; > } > } > > - /* Otherwise create texture for input buffer via upload from CPU */ > - if (!use_dmabuf_) { > + if (use_dmabuf_) { > + /* Cache hit using dmabuf activate and bind */ > + if (!cache_miss) > + egl_.activateBindTexture(*eglImageIn); > + } else { > + /* Otherwise create texture for input buffer via upload from CPU */ > inDmaSyncer->emplace(input->planes()[0].fd, DmaSyncer::SyncType::Read); > inMapped->emplace(input, MappedFrameBuffer::MapFlag::Read); > if (!inMapped->value().isValid()) { > LOG(Debayer, Error) << "mmap-ing buffer(s) failed"; > - return -ENODEV; > + return nullptr; > } > - egl_.createTexture2D(*eglImageBayerIn_, inMapped->value().planes()[0].data()); > + if (cache_miss) > + egl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); > + else > + egl_.updateTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); > } > > - /* Generate the output render framebuffer as render to texture */ > - egl_.createOutputDMABufTexture2D(*eglImageBayerOut_, output->planes()[0].fd.get()); > + return eglImageIn; > +} > + > +eGLImage *DebayerEGL::getCachedOutputFrameBuffer(FrameBuffer *output) > +{ > + auto [output_cache, cache_miss] = eglImageBayerOut_.try_emplace(output->planes()[0].fd.get()); > + if (cache_miss) { > + if (eglImageBayerOut_.size() > outputBufferCount_) { > + LOG(Debayer, Error) << "Output buffer count " << outputBufferCount_ << " exhaustion"; > + return nullptr; > + } > + output_cache->second = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, > + outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1); > + egl_.createOutputDMABufTexture2D(*output_cache->second, output->planes()[0].fd.get()); > + } Answering to my own question in the previous review: IIUC we indeed don't need to call "egl_.activateBindTexture(*eglImageBayerOut_);" in the "else" case here, it's apparently only required when setting up the texture. Hope that's correct. > + eGLImage *eglImageOut = output_cache->second.get(); > + > + return eglImageOut; > +} > + > +int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) > +{ > + eGLImage *eglImageIn; > + eGLImage *eglImageOut; > + > + /* eGL context switch */ > + egl_.makeCurrent(); > + > + eglImageIn = getCachedInputFrameBuffer(input, inMapped, inDmaSyncer); > + if (!eglImageIn) > + return -ENOMEM; > + > + eglImageOut = getCachedOutputFrameBuffer(output); > + if (!eglImageOut) > + return -ENOMEM; > + > + egl_.attachTextureToFBO(*eglImageOut); > + setShaderVariableValues(*eglImageIn, params); > > - setShaderVariableValues(*eglImageBayerIn_, params); > glViewport(0, 0, width_, height_); > glClear(GL_COLOR_BUFFER_BIT); > glDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS); > @@ -623,19 +676,13 @@ int DebayerEGL::start() > if (initBayerShaders(inputPixelFormat_, outputPixelFormat_)) > return -EINVAL; > > - /* Raw bayer input as texture */ > - eglImageBayerIn_ = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, height_, inputConfig_.stride, GL_TEXTURE0, 0); > - > - /* Texture we will render to */ > - eglImageBayerOut_ = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1); > - > return 0; > } > > void DebayerEGL::stop() > { > - eglImageBayerOut_.reset(); > - eglImageBayerIn_.reset(); > + eglImageBayerOut_.clear(); > + eglImageBayerIn_.clear(); Should we also reset inputBufferCount_ and outputBufferCount_ here, just to ensure the state is clean and can't accidentally be used in the future? > > if (programId_) > glDeleteProgram(programId_); > diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h > index d8509e9f2..ddb3ef378 100644 > --- a/src/libcamera/software_isp/debayer_egl.h > +++ b/src/libcamera/software_isp/debayer_egl.h > @@ -22,6 +22,7 @@ > #include "libcamera/internal/mapped_framebuffer.h" > #include "libcamera/internal/software_isp/benchmark.h" > #include "libcamera/internal/software_isp/swstats_cpu.h" > +#include "libcamera/internal/v4l2_videodevice.h" Should be removed again I think? > > #include <EGL/egl.h> > #include <EGL/eglext.h> > @@ -70,14 +71,19 @@ private: > > bool use_dmabuf_; > > + eGLImage *getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer); > + eGLImage *getCachedOutputFrameBuffer(FrameBuffer *output); > + > /* Shader program identifiers */ > GLuint vertexShaderId_ = 0; > GLuint fragmentShaderId_ = 0; > GLuint programId_ = 0; > > /* Pointer to object representing input texture */ > - std::unique_ptr<eGLImage> eglImageBayerIn_; > - std::unique_ptr<eGLImage> eglImageBayerOut_; > + std::unordered_map<int, std::unique_ptr<eGLImage>> eglImageBayerIn_; > + std::unordered_map<int, std::unique_ptr<eGLImage>> eglImageBayerOut_; > + unsigned int inputBufferCount_; > + unsigned int outputBufferCount_; > > /* Shader parameters */ > float firstRed_x_; This approach LGTM - with the two minor commits addressed: Reviewed-by: Robert Mader <robert.mader@collabora.com>
2026. 06. 26. 13:33 keltezéssel, Bryan O'Donoghue írta: > Implement a texture caching mechanism for both input and output frames and > for both types of input frame. > > The before/after on a Qualcomm x1e is: > > 9.737ms per frame > 5.691ms per frame > > The before/after on a Qualcomm sm8250 is: > > 21.710ms per frame > 17.336ms per frame > > for i in {1..20} do > cam -c /base/soc@0/cci@ac16000/i2c-bus@1/camera@10 -s width=1920,height=1080 --capture=60 > > Interestingly there appears to be an absolute ~ 4.x ms per frame uplift as > opposed to what intuition might suggest a proportional. > > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > src/libcamera/software_isp/debayer_egl.cpp | 87 +++++++++++++++++----- > src/libcamera/software_isp/debayer_egl.h | 10 ++- > 2 files changed, 75 insertions(+), 22 deletions(-) > > diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp > index 53bb67c17..fc37f0b75 100644 > --- a/src/libcamera/software_isp/debayer_egl.cpp > +++ b/src/libcamera/software_isp/debayer_egl.cpp > @@ -355,6 +355,9 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, > */ > stats_->setWindow(Rectangle(window_.size())); > > + inputBufferCount_ = inputCfg.bufferCount; > + outputBufferCount_ = outputCfg.bufferCount; > + > return 0; > } > > @@ -514,34 +517,84 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara > return; > } > > -int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) > +eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) > { > - /* eGL context switch */ > - egl_.makeCurrent(); > + auto [input_cache, cache_miss] = eglImageBayerIn_.try_emplace(input->planes()[0].fd.get()); > + if (cache_miss) { > + if (eglImageBayerIn_.size() > inputBufferCount_) { > + LOG(Debayer, Error) << "Input count " << inputBufferCount_ << " exhausted"; eglImageBayerIn_.erase(input_cache); otherwise the stale entry (nullptr) might cause issues later. > + return nullptr; > + } > + input_cache->second = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, > + height_, inputConfig_.stride, GL_TEXTURE0, 0); > + } > + eGLImage *eglImageIn = input_cache->second.get(); > > /* Try to create texture for input buffer via dmabuf import */ > - if (use_dmabuf_) { > - if (egl_.createInputDMABufTexture2D(*eglImageBayerIn_, input->planes()[0].fd.get()) != 0) { > + if (use_dmabuf_ && cache_miss) { > + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { > use_dmabuf_ = false; > LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed, falling back to upload"; > } > } > > - /* Otherwise create texture for input buffer via upload from CPU */ > - if (!use_dmabuf_) { > + if (use_dmabuf_) { > + /* Cache hit using dmabuf activate and bind */ > + if (!cache_miss) > + egl_.activateBindTexture(*eglImageIn); > + } else { > + /* Otherwise create texture for input buffer via upload from CPU */ > inDmaSyncer->emplace(input->planes()[0].fd, DmaSyncer::SyncType::Read); > inMapped->emplace(input, MappedFrameBuffer::MapFlag::Read); > if (!inMapped->value().isValid()) { > LOG(Debayer, Error) << "mmap-ing buffer(s) failed"; > - return -ENODEV; > + return nullptr; > } > - egl_.createTexture2D(*eglImageBayerIn_, inMapped->value().planes()[0].data()); > + if (cache_miss) > + egl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); > + else > + egl_.updateTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); > } > > - /* Generate the output render framebuffer as render to texture */ > - egl_.createOutputDMABufTexture2D(*eglImageBayerOut_, output->planes()[0].fd.get()); > + return eglImageIn; > +} > + > +eGLImage *DebayerEGL::getCachedOutputFrameBuffer(FrameBuffer *output) > +{ > + auto [output_cache, cache_miss] = eglImageBayerOut_.try_emplace(output->planes()[0].fd.get()); > + if (cache_miss) { > + if (eglImageBayerOut_.size() > outputBufferCount_) { > + LOG(Debayer, Error) << "Output buffer count " << outputBufferCount_ << " exhaustion"; eglImageBayerOut_.erase(output_cache); for the same reason as above. > + return nullptr; > + } > + output_cache->second = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, > + outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1); > + egl_.createOutputDMABufTexture2D(*output_cache->second, output->planes()[0].fd.get()); > + } > + eGLImage *eglImageOut = output_cache->second.get(); > + > + return eglImageOut; return output_cache->second.get(); ? > +} > + > +int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) > +{ > + eGLImage *eglImageIn; > + eGLImage *eglImageOut; > + > + /* eGL context switch */ > + egl_.makeCurrent(); > + > + eglImageIn = getCachedInputFrameBuffer(input, inMapped, inDmaSyncer); > + if (!eglImageIn) > + return -ENOMEM; > + > + eglImageOut = getCachedOutputFrameBuffer(output); > + if (!eglImageOut) > + return -ENOMEM; > + > + egl_.attachTextureToFBO(*eglImageOut); > + setShaderVariableValues(*eglImageIn, params); > > - setShaderVariableValues(*eglImageBayerIn_, params); > glViewport(0, 0, width_, height_); > glClear(GL_COLOR_BUFFER_BIT); > glDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS); > @@ -623,19 +676,13 @@ int DebayerEGL::start() > if (initBayerShaders(inputPixelFormat_, outputPixelFormat_)) > return -EINVAL; > > - /* Raw bayer input as texture */ > - eglImageBayerIn_ = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, height_, inputConfig_.stride, GL_TEXTURE0, 0); > - > - /* Texture we will render to */ > - eglImageBayerOut_ = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1); > - > return 0; > } > > void DebayerEGL::stop() > { > - eglImageBayerOut_.reset(); > - eglImageBayerIn_.reset(); > + eglImageBayerOut_.clear(); > + eglImageBayerIn_.clear(); > > if (programId_) > glDeleteProgram(programId_); > diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h > index d8509e9f2..ddb3ef378 100644 > --- a/src/libcamera/software_isp/debayer_egl.h > +++ b/src/libcamera/software_isp/debayer_egl.h > @@ -22,6 +22,7 @@ > #include "libcamera/internal/mapped_framebuffer.h" > #include "libcamera/internal/software_isp/benchmark.h" > #include "libcamera/internal/software_isp/swstats_cpu.h" > +#include "libcamera/internal/v4l2_videodevice.h" > > #include <EGL/egl.h> > #include <EGL/eglext.h> > @@ -70,14 +71,19 @@ private: > > bool use_dmabuf_; > > + eGLImage *getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer); > + eGLImage *getCachedOutputFrameBuffer(FrameBuffer *output); > + > /* Shader program identifiers */ > GLuint vertexShaderId_ = 0; > GLuint fragmentShaderId_ = 0; > GLuint programId_ = 0; > > /* Pointer to object representing input texture */ > - std::unique_ptr<eGLImage> eglImageBayerIn_; > - std::unique_ptr<eGLImage> eglImageBayerOut_; > + std::unordered_map<int, std::unique_ptr<eGLImage>> eglImageBayerIn_; > + std::unordered_map<int, std::unique_ptr<eGLImage>> eglImageBayerOut_; I think we should use `SharedFD` here to avoid any fd reuse issues. I have just realized that `V4l2BufferCache` does not use `SharedFD` either. And I think that is kind of an issue. Well, nowadays every user just uses `FrameBufferAllocator` with long lived `FrameBuffer`s, but the libcamera api does not require anything like that. Maybe this should be discussed more, what the exact schemantics should be, etc. In any case, this should be an easy change, just needs this as well: diff --git a/include/libcamera/base/shared_fd.h b/include/libcamera/base/shared_fd.h index 61fe11c1d6..cec1f6036c 100644 --- a/include/libcamera/base/shared_fd.h +++ b/include/libcamera/base/shared_fd.h @@ -7,6 +7,7 @@ #pragma once +#include <functional> #include <memory> namespace libcamera { @@ -57,3 +58,11 @@ static inline bool operator!=(const SharedFD &lhs, const SharedFD &rhs) } } /* namespace libcamera */ + +template<> +struct std::hash<libcamera::SharedFD> : private std::hash<int> { + auto operator()(const libcamera::SharedFD &x) const noexcept + { + return std::hash<int>::operator()(x.get()); + } +}; > + unsigned int inputBufferCount_; > + unsigned int outputBufferCount_; Something like `xyzBufferCacheMaxSize_` / `maxXyzBufferCacheSize_` is more to the point in my opinion. > > /* Shader parameters */ > float firstRed_x_; > -- > 2.54.0 >
2026. 06. 26. 15:19 keltezéssel, Barnabás Pőcze írta: > 2026. 06. 26. 13:33 keltezéssel, Bryan O'Donoghue írta: >> Implement a texture caching mechanism for both input and output frames and >> for both types of input frame. >> >> The before/after on a Qualcomm x1e is: >> >> 9.737ms per frame >> 5.691ms per frame >> >> The before/after on a Qualcomm sm8250 is: >> >> 21.710ms per frame >> 17.336ms per frame >> >> for i in {1..20} do >> cam -c /base/soc@0/cci@ac16000/i2c-bus@1/camera@10 -s width=1920,height=1080 --capture=60 >> >> Interestingly there appears to be an absolute ~ 4.x ms per frame uplift as >> opposed to what intuition might suggest a proportional. >> >> Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> >> --- >> src/libcamera/software_isp/debayer_egl.cpp | 87 +++++++++++++++++----- >> src/libcamera/software_isp/debayer_egl.h | 10 ++- >> 2 files changed, 75 insertions(+), 22 deletions(-) >> >> diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp >> index 53bb67c17..fc37f0b75 100644 >> --- a/src/libcamera/software_isp/debayer_egl.cpp >> +++ b/src/libcamera/software_isp/debayer_egl.cpp >> @@ -355,6 +355,9 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, > [...] >> +eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) >> { >> - /* eGL context switch */ >> - egl_.makeCurrent(); >> + auto [input_cache, cache_miss] = eglImageBayerIn_.try_emplace(input->planes()[0].fd.get()); >> + if (cache_miss) { >> + if (eglImageBayerIn_.size() > inputBufferCount_) { >> + LOG(Debayer, Error) << "Input count " << inputBufferCount_ << " exhausted"; And I am not sure this should be an error, maybe "debug" at most. > > eglImageBayerIn_.erase(input_cache); > > otherwise the stale entry (nullptr) might cause issues later. > > >> + return nullptr; >> + } >> + input_cache->second = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, >> + height_, inputConfig_.stride, GL_TEXTURE0, 0); >> + } >> + eGLImage *eglImageIn = input_cache->second.get(); >> >> /* Try to create texture for input buffer via dmabuf import */ >> - if (use_dmabuf_) { >> - if (egl_.createInputDMABufTexture2D(*eglImageBayerIn_, input->planes()[0].fd.get()) != 0) { >> + if (use_dmabuf_ && cache_miss) { >> + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { >> use_dmabuf_ = false; >> LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed, falling back to upload"; >> } >> } >> >> - /* Otherwise create texture for input buffer via upload from CPU */ >> - if (!use_dmabuf_) { >> + if (use_dmabuf_) { >> + /* Cache hit using dmabuf activate and bind */ >> + if (!cache_miss) >> + egl_.activateBindTexture(*eglImageIn); >> + } else { >> + /* Otherwise create texture for input buffer via upload from CPU */ >> inDmaSyncer->emplace(input->planes()[0].fd, DmaSyncer::SyncType::Read); >> inMapped->emplace(input, MappedFrameBuffer::MapFlag::Read); >> if (!inMapped->value().isValid()) { >> LOG(Debayer, Error) << "mmap-ing buffer(s) failed"; >> - return -ENODEV; >> + return nullptr; >> } >> - egl_.createTexture2D(*eglImageBayerIn_, inMapped->value().planes()[0].data()); >> + if (cache_miss) >> + egl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); >> + else >> + egl_.updateTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); >> } >> >> - /* Generate the output render framebuffer as render to texture */ >> - egl_.createOutputDMABufTexture2D(*eglImageBayerOut_, output->planes()[0].fd.get()); >> + return eglImageIn; >> +} >> [...]
On 26/06/2026 14:19, Barnabás Pőcze wrote: > diff --git a/include/libcamera/base/shared_fd.h b/include/libcamera/ > base/shared_fd.h > index 61fe11c1d6..cec1f6036c 100644 > --- a/include/libcamera/base/shared_fd.h > +++ b/include/libcamera/base/shared_fd.h > @@ -7,6 +7,7 @@ > > #pragma once > > +#include <functional> > #include <memory> > > namespace libcamera { > @@ -57,3 +58,11 @@ static inline bool operator!=(const SharedFD &lhs, > const SharedFD &rhs) > } > > } /* namespace libcamera */ > + > +template<> > +struct std::hash<libcamera::SharedFD> : private std::hash<int> { > + auto operator()(const libcamera::SharedFD &x) const noexcept > + { > + return std::hash<int>::operator()(x.get()); > + } > +}; Do you want to make that into a patch yourself or for me to pick it up for you ? --- bod
2026. 06. 26. 16:10 keltezéssel, Bryan O'Donoghue írta: > On 26/06/2026 14:19, Barnabás Pőcze wrote: >> diff --git a/include/libcamera/base/shared_fd.h b/include/libcamera/ base/shared_fd.h >> index 61fe11c1d6..cec1f6036c 100644 >> --- a/include/libcamera/base/shared_fd.h >> +++ b/include/libcamera/base/shared_fd.h >> @@ -7,6 +7,7 @@ >> >> #pragma once >> >> +#include <functional> >> #include <memory> >> >> namespace libcamera { >> @@ -57,3 +58,11 @@ static inline bool operator!=(const SharedFD &lhs, const SharedFD &rhs) >> } >> >> } /* namespace libcamera */ >> + >> +template<> >> +struct std::hash<libcamera::SharedFD> : private std::hash<int> { >> + auto operator()(const libcamera::SharedFD &x) const noexcept >> + { >> + return std::hash<int>::operator()(x.get()); >> + } >> +}; > > Do you want to make that into a patch yourself or for me to pick it up for you ? Please include it if you send a new version with `SharedFD` as the map key. > > --- > bod
2026. 06. 26. 13:33 keltezéssel, Bryan O'Donoghue írta: > Implement a texture caching mechanism for both input and output frames and > for both types of input frame. > > The before/after on a Qualcomm x1e is: > > 9.737ms per frame > 5.691ms per frame > > The before/after on a Qualcomm sm8250 is: > > 21.710ms per frame > 17.336ms per frame > > for i in {1..20} do > cam -c /base/soc@0/cci@ac16000/i2c-bus@1/camera@10 -s width=1920,height=1080 --capture=60 > > Interestingly there appears to be an absolute ~ 4.x ms per frame uplift as > opposed to what intuition might suggest a proportional. > > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > src/libcamera/software_isp/debayer_egl.cpp | 87 +++++++++++++++++----- > src/libcamera/software_isp/debayer_egl.h | 10 ++- > 2 files changed, 75 insertions(+), 22 deletions(-) > > diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp > index 53bb67c17..fc37f0b75 100644 > --- a/src/libcamera/software_isp/debayer_egl.cpp > +++ b/src/libcamera/software_isp/debayer_egl.cpp > @@ -355,6 +355,9 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, > [...] > -int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) > +eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) > { > - /* eGL context switch */ > - egl_.makeCurrent(); > + auto [input_cache, cache_miss] = eglImageBayerIn_.try_emplace(input->planes()[0].fd.get()); With a custom cache like here, there needs some kind of eviction policy. Just like `V4L2BufferCache` does a kind of LRU eviction. > + if (cache_miss) { > + if (eglImageBayerIn_.size() > inputBufferCount_) { > + LOG(Debayer, Error) << "Input count " << inputBufferCount_ << " exhausted"; > + return nullptr; > + } > + input_cache->second = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, > + height_, inputConfig_.stride, GL_TEXTURE0, 0); > + } > + eGLImage *eglImageIn = input_cache->second.get(); > > /* Try to create texture for input buffer via dmabuf import */ > - if (use_dmabuf_) { > - if (egl_.createInputDMABufTexture2D(*eglImageBayerIn_, input->planes()[0].fd.get()) != 0) { > + if (use_dmabuf_ && cache_miss) { > + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { > use_dmabuf_ = false; What if some buffers were imported successfully already? Can that cause issue later when `use_dmabuf_ == false`? > LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed, falling back to upload"; > } > } > > - /* Otherwise create texture for input buffer via upload from CPU */ > - if (!use_dmabuf_) { > + if (use_dmabuf_) { > + /* Cache hit using dmabuf activate and bind */ > + if (!cache_miss) > + egl_.activateBindTexture(*eglImageIn); > + } else { > + /* Otherwise create texture for input buffer via upload from CPU */ > inDmaSyncer->emplace(input->planes()[0].fd, DmaSyncer::SyncType::Read); > inMapped->emplace(input, MappedFrameBuffer::MapFlag::Read); > if (!inMapped->value().isValid()) { > LOG(Debayer, Error) << "mmap-ing buffer(s) failed"; > - return -ENODEV; > + return nullptr; > } > - egl_.createTexture2D(*eglImageBayerIn_, inMapped->value().planes()[0].data()); > + if (cache_miss) > + egl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); > + else > + egl_.updateTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); > } > > - /* Generate the output render framebuffer as render to texture */ > - egl_.createOutputDMABufTexture2D(*eglImageBayerOut_, output->planes()[0].fd.get()); > + return eglImageIn; > +} > [...]
On 30/06/2026 11:16, Barnabás Pőcze wrote: >> + if (use_dmabuf_ && cache_miss) { >> + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { >> use_dmabuf_ = false; > What if some buffers were imported successfully already? Can that cause > issue later when `use_dmabuf_ == false`? For this specific case - its all or nothing. There's no reason to support some input buffers using dma-buf handle and some not - that's indicative of a fundamental bug - all of the buffers come from the same place and pertain to framebuffers of the same size. I'll include this check if you _really_ want it but if encountered it is certain "error out and die screaming" not "be chill and accept" --- bod
On 01.07.26 10:35, Bryan O'Donoghue wrote: > On 30/06/2026 11:16, Barnabás Pőcze wrote: >>> + if (use_dmabuf_ && cache_miss) { >>> + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { >>> use_dmabuf_ = false; >> What if some buffers were imported successfully already? Can that cause >> issue later when `use_dmabuf_ == false`? > For this specific case - its all or nothing. > > There's no reason to support some input buffers using dma-buf handle and > some not - that's indicative of a fundamental bug - all of the buffers > come from the same place and pertain to framebuffers of the same size. > > I'll include this check if you _really_ want it but if encountered it is > certain "error out and die screaming" not "be chill and accept" > > --- > bod Fully agree, that's how things are usually handled in Wayland as well (if the compositor suddenly can't import a buffer from a dmabuf client any more).
2026. 07. 01. 10:35 keltezéssel, Bryan O'Donoghue írta: > On 30/06/2026 11:16, Barnabás Pőcze wrote: >>> + if (use_dmabuf_ && cache_miss) { >>> + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { >>> use_dmabuf_ = false; >> What if some buffers were imported successfully already? Can that cause >> issue later when `use_dmabuf_ == false`? > > For this specific case - its all or nothing. > > There's no reason to support some input buffers using dma-buf handle and > some not - that's indicative of a fundamental bug - all of the buffers > come from the same place and pertain to framebuffers of the same size. But my point is that this is not necessarily the case. An application can create and use its own `FrameBuffer`s, without ever touching a `FrameBufferAllocator`; so they don't necessarily come from the same place. Arguably this is not a commonly used thing today, but the API allows this, and it should be handled accordingly. E.g. if you look at the android component, you'll see it has its own `HALFrameBuffer` thing, it does not use `libcamera::FrameBufferAllocator` at all. > > I'll include this check if you _really_ want it but if encountered it is > certain "error out and die screaming" not "be chill and accept" > > --- > bod >
On 01/07/2026 10:06, Barnabás Pőcze wrote: >>> What if some buffers were imported successfully already? Can that cause >>> issue later when `use_dmabuf_ == false`? >> >> For this specific case - its all or nothing. >> >> There's no reason to support some input buffers using dma-buf handle and >> some not - that's indicative of a fundamental bug - all of the buffers >> come from the same place and pertain to framebuffers of the same size. > > But my point is that this is not necessarily the case. An application > can create and > use its own `FrameBuffer`s, without ever touching a > `FrameBufferAllocator`; so they > don't necessarily come from the same place. Arguably this is not a commonly > used thing today, but the API allows this, and it should be handled > accordingly. > E.g. if you look at the android component, you'll see it has its own > `HALFrameBuffer` thing, it does not use > `libcamera::FrameBufferAllocator` at all. Hmm. In this case we will need to stick to evaluating each eglImage instead of having a policy per session, my idea about promoting (demoting?) the logic to debayer_egl from eglImage is not a flier. --- bod
On 01.07.26 11:55, Bryan O'Donoghue wrote: > On 01/07/2026 10:06, Barnabás Pőcze wrote: >>>> What if some buffers were imported successfully already? Can that >>>> cause >>>> issue later when `use_dmabuf_ == false`? >>> >>> For this specific case - its all or nothing. >>> >>> There's no reason to support some input buffers using dma-buf handle >>> and >>> some not - that's indicative of a fundamental bug - all of the buffers >>> come from the same place and pertain to framebuffers of the same size. >> >> But my point is that this is not necessarily the case. An application >> can create and >> use its own `FrameBuffer`s, without ever touching a >> `FrameBufferAllocator`; so they >> don't necessarily come from the same place. Arguably this is not a >> commonly >> used thing today, but the API allows this, and it should be handled >> accordingly. >> E.g. if you look at the android component, you'll see it has its own >> `HALFrameBuffer` thing, it does not use >> `libcamera::FrameBufferAllocator` at all. > > Hmm. > > In this case we will need to stick to evaluating each eglImage instead > of having a policy per session, my idea about promoting (demoting?) > the logic to debayer_egl from eglImage is not a flier. > > --- > bod Even though the API allows it, would it be acceptable if we explicitly disallow it in debayer_egl? I.e. something along the lines of if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { + + if (eglImageBayerOut_.size() > 1) + LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed after previously succeeding. This is likely caused by mixing incompatible input buffers and not currently supported."; + return nullptr; + } + use_dmabuf_ = false; LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed, falling back to upload"; }
On Wed, Jul 01, 2026 at 01:40:38PM +0200, Robert Mader wrote: > > On 01.07.26 11:55, Bryan O'Donoghue wrote: > > On 01/07/2026 10:06, Barnabás Pőcze wrote: > >>>> What if some buffers were imported successfully already? Can that > >>>> cause > >>>> issue later when `use_dmabuf_ == false`? > >>> > >>> For this specific case - its all or nothing. > >>> > >>> There's no reason to support some input buffers using dma-buf handle > >>> and > >>> some not - that's indicative of a fundamental bug - all of the buffers > >>> come from the same place and pertain to framebuffers of the same size. > >> > >> But my point is that this is not necessarily the case. An application > >> can create and > >> use its own `FrameBuffer`s, without ever touching a > >> `FrameBufferAllocator`; so they > >> don't necessarily come from the same place. Arguably this is not a > >> commonly > >> used thing today, but the API allows this, and it should be handled > >> accordingly. > >> E.g. if you look at the android component, you'll see it has its own > >> `HALFrameBuffer` thing, it does not use > >> `libcamera::FrameBufferAllocator` at all. > > > > Hmm. > > > > In this case we will need to stick to evaluating each eglImage instead > > of having a policy per session, my idea about promoting (demoting?) > > the logic to debayer_egl from eglImage is not a flier. > > > > --- > > bod > > Even though the API allows it, would it be acceptable if we explicitly > disallow it in debayer_egl? I.e. something along the lines of Nice try, but sorry :-) We're accumulated a lot of technical debt in the soft ISP already, I'm increasingly feeling uncomfortable adding more without addressing the existing one. > if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { > + > + if (eglImageBayerOut_.size() > 1) > + LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed after previously succeeding. This is likely caused by mixing incompatible input buffers and not currently supported."; > + return nullptr; > + } > + > use_dmabuf_ = false; > LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed, falling back to upload"; > }
On 01.07.26 13:50, Laurent Pinchart wrote: > On Wed, Jul 01, 2026 at 01:40:38PM +0200, Robert Mader wrote: >> On 01.07.26 11:55, Bryan O'Donoghue wrote: >>> On 01/07/2026 10:06, Barnabás Pőcze wrote: >>>>>> What if some buffers were imported successfully already? Can that >>>>>> cause >>>>>> issue later when `use_dmabuf_ == false`? >>>>> For this specific case - its all or nothing. >>>>> >>>>> There's no reason to support some input buffers using dma-buf handle >>>>> and >>>>> some not - that's indicative of a fundamental bug - all of the buffers >>>>> come from the same place and pertain to framebuffers of the same size. >>>> But my point is that this is not necessarily the case. An application >>>> can create and >>>> use its own `FrameBuffer`s, without ever touching a >>>> `FrameBufferAllocator`; so they >>>> don't necessarily come from the same place. Arguably this is not a >>>> commonly >>>> used thing today, but the API allows this, and it should be handled >>>> accordingly. >>>> E.g. if you look at the android component, you'll see it has its own >>>> `HALFrameBuffer` thing, it does not use >>>> `libcamera::FrameBufferAllocator` at all. >>> Hmm. >>> >>> In this case we will need to stick to evaluating each eglImage instead >>> of having a policy per session, my idea about promoting (demoting?) >>> the logic to debayer_egl from eglImage is not a flier. >>> >>> --- >>> bod >> Even though the API allows it, would it be acceptable if we explicitly >> disallow it in debayer_egl? I.e. something along the lines of > Nice try, but sorry :-) We're accumulated a lot of technical debt in the > soft ISP already, I'm increasingly feeling uncomfortable adding more > without addressing the existing one. I see. In that case I guess we can simply keep using eglImageIn->dmabuf_import_failed_ - accepting the negligible overhead for the 3 additional failing imports in the common cases - and should probably ensure that the "Importing input buffer with DMABuf import failed, falling back to upload" log is only printed once. Shouldn't be a big deal AFAICS.
On 01/07/2026 12:56, Robert Mader wrote: >> Nice try, but sorry 🙂 We're accumulated a lot of technical debt in the >> soft ISP already, I'm increasingly feeling uncomfortable adding more >> without addressing the existing one. > I see. In that case I guess we can simply keep using > eglImageIn->dmabuf_import_failed_ - accepting the negligible overhead > for the 3 additional failing imports in the common cases - and should > probably ensure that the "Importing input buffer with DMABuf import > failed, falling back to upload" log is only printed once. Shouldn't be a > big deal AFAICS. Yes but - even though we are told @ configure() time of how many buffers to keep in the cache - I'm told those buffers can change. The dma-buf handle must be unique but the application can feed in its own buffer => import on a per-frame as opposed to per-session policy should be supported. We also agreed if we allocated space for four buffers and then we see a new fifth buffer to evict the oldest buffer in the cache. --- bod
On 01.07.26 17:20, Bryan O'Donoghue wrote: > On 01/07/2026 12:56, Robert Mader wrote: >>> Nice try, but sorry 🙂 We're accumulated a lot of technical debt in the >>> soft ISP already, I'm increasingly feeling uncomfortable adding more >>> without addressing the existing one. >> I see. In that case I guess we can simply keep using >> eglImageIn->dmabuf_import_failed_ - accepting the negligible overhead >> for the 3 additional failing imports in the common cases - and should >> probably ensure that the "Importing input buffer with DMABuf import >> failed, falling back to upload" log is only printed once. Shouldn't be a >> big deal AFAICS. > Yes but - even though we are told @ configure() time of how many buffers > to keep in the cache - I'm told those buffers can change. > > The dma-buf handle must be unique but the application can feed in its > own buffer => import on a per-frame as opposed to per-session policy > should be supported. > > We also agreed if we allocated space for four buffers and then we see a > new fifth buffer to evict the oldest buffer in the cache. Yes - AFAICS for each unique FrameBuffer (or rather: unique dmabuf handle within that FrameBuffer) we already create a unique eGLImage. So using eGLImage::dmabuf_import_failed_ ensures that for each FrameBuffer we try the dmabuf import exactly once.
Hey hey, I just took the freedom to try to address the feedback from the last series / apply all the requested changes and IMO it went quite well. I pushed it here: https://gitlab.freedesktop.org/rmader/libcamera/-/tree/swisp-dmabuf-import-v3-fixes Please feel free to pick that up for v4 if you agree with the changes or change things again as you prefer. Note that I also did a slight reordering in getCachedInputFrameBuffer(), IMO making it easier to follow. CI succeeds apart from one doxygen issue around the new SharedFD hash (need to investigate further, apparently enabling BUILTIN_STL_SUPPORT should fix it) On 01.07.26 17:34, Robert Mader wrote: > On 01.07.26 17:20, Bryan O'Donoghue wrote: >> On 01/07/2026 12:56, Robert Mader wrote: >>>> Nice try, but sorry 🙂 We're accumulated a lot of technical debt in >>>> the >>>> soft ISP already, I'm increasingly feeling uncomfortable adding more >>>> without addressing the existing one. >>> I see. In that case I guess we can simply keep using >>> eglImageIn->dmabuf_import_failed_ - accepting the negligible overhead >>> for the 3 additional failing imports in the common cases - and should >>> probably ensure that the "Importing input buffer with DMABuf import >>> failed, falling back to upload" log is only printed once. Shouldn't >>> be a >>> big deal AFAICS. >> Yes but - even though we are told @ configure() time of how many buffers >> to keep in the cache - I'm told those buffers can change. >> >> The dma-buf handle must be unique but the application can feed in its >> own buffer => import on a per-frame as opposed to per-session policy >> should be supported. >> >> We also agreed if we allocated space for four buffers and then we see a >> new fifth buffer to evict the oldest buffer in the cache. > Yes - AFAICS for each unique FrameBuffer (or rather: unique dmabuf > handle within that FrameBuffer) we already create a unique eGLImage. > So using eGLImage::dmabuf_import_failed_ ensures that for each > FrameBuffer we try the dmabuf import exactly once. >
On 04.07.26 11:46, Robert Mader wrote: > Hey hey, I just took the freedom to try to address the feedback from > the last series / apply all the requested changes and IMO it went > quite well. I pushed it here: > > https://gitlab.freedesktop.org/rmader/libcamera/-/tree/swisp-dmabuf-import-v3-fixes > > > Please feel free to pick that up for v4 if you agree with the changes > or change things again as you prefer. Note that I also did a slight > reordering in getCachedInputFrameBuffer(), IMO making it easier to > follow. > > CI succeeds apart from one doxygen issue around the new SharedFD hash > (need to investigate further, apparently enabling BUILTIN_STL_SUPPORT > should fix it) Yep, it does. Added and pushed, together with another small fix. CI succeeds now and I won't touch the branch any more.
diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 53bb67c17..fc37f0b75 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -355,6 +355,9 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, */ stats_->setWindow(Rectangle(window_.size())); + inputBufferCount_ = inputCfg.bufferCount; + outputBufferCount_ = outputCfg.bufferCount; + return 0; } @@ -514,34 +517,84 @@ void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerPara return; } -int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) +eGLImage *DebayerEGL::getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) { - /* eGL context switch */ - egl_.makeCurrent(); + auto [input_cache, cache_miss] = eglImageBayerIn_.try_emplace(input->planes()[0].fd.get()); + if (cache_miss) { + if (eglImageBayerIn_.size() > inputBufferCount_) { + LOG(Debayer, Error) << "Input count " << inputBufferCount_ << " exhausted"; + return nullptr; + } + input_cache->second = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, + height_, inputConfig_.stride, GL_TEXTURE0, 0); + } + eGLImage *eglImageIn = input_cache->second.get(); /* Try to create texture for input buffer via dmabuf import */ - if (use_dmabuf_) { - if (egl_.createInputDMABufTexture2D(*eglImageBayerIn_, input->planes()[0].fd.get()) != 0) { + if (use_dmabuf_ && cache_miss) { + if (egl_.createInputDMABufTexture2D(*eglImageIn, input->planes()[0].fd.get()) != 0) { use_dmabuf_ = false; LOG(Debayer, Info) << "Importing input buffer with DMABuf import failed, falling back to upload"; } } - /* Otherwise create texture for input buffer via upload from CPU */ - if (!use_dmabuf_) { + if (use_dmabuf_) { + /* Cache hit using dmabuf activate and bind */ + if (!cache_miss) + egl_.activateBindTexture(*eglImageIn); + } else { + /* Otherwise create texture for input buffer via upload from CPU */ inDmaSyncer->emplace(input->planes()[0].fd, DmaSyncer::SyncType::Read); inMapped->emplace(input, MappedFrameBuffer::MapFlag::Read); if (!inMapped->value().isValid()) { LOG(Debayer, Error) << "mmap-ing buffer(s) failed"; - return -ENODEV; + return nullptr; } - egl_.createTexture2D(*eglImageBayerIn_, inMapped->value().planes()[0].data()); + if (cache_miss) + egl_.createTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); + else + egl_.updateTexture2D(*eglImageIn, inMapped->value().planes()[0].data()); } - /* Generate the output render framebuffer as render to texture */ - egl_.createOutputDMABufTexture2D(*eglImageBayerOut_, output->planes()[0].fd.get()); + return eglImageIn; +} + +eGLImage *DebayerEGL::getCachedOutputFrameBuffer(FrameBuffer *output) +{ + auto [output_cache, cache_miss] = eglImageBayerOut_.try_emplace(output->planes()[0].fd.get()); + if (cache_miss) { + if (eglImageBayerOut_.size() > outputBufferCount_) { + LOG(Debayer, Error) << "Output buffer count " << outputBufferCount_ << " exhaustion"; + return nullptr; + } + output_cache->second = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, + outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1); + egl_.createOutputDMABufTexture2D(*output_cache->second, output->planes()[0].fd.get()); + } + eGLImage *eglImageOut = output_cache->second.get(); + + return eglImageOut; +} + +int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer) +{ + eGLImage *eglImageIn; + eGLImage *eglImageOut; + + /* eGL context switch */ + egl_.makeCurrent(); + + eglImageIn = getCachedInputFrameBuffer(input, inMapped, inDmaSyncer); + if (!eglImageIn) + return -ENOMEM; + + eglImageOut = getCachedOutputFrameBuffer(output); + if (!eglImageOut) + return -ENOMEM; + + egl_.attachTextureToFBO(*eglImageOut); + setShaderVariableValues(*eglImageIn, params); - setShaderVariableValues(*eglImageBayerIn_, params); glViewport(0, 0, width_, height_); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLE_FAN, 0, DEBAYER_OPENGL_COORDS); @@ -623,19 +676,13 @@ int DebayerEGL::start() if (initBayerShaders(inputPixelFormat_, outputPixelFormat_)) return -EINVAL; - /* Raw bayer input as texture */ - eglImageBayerIn_ = std::make_unique<eGLImage>(glFormat_, inputConfig_.stride / bytesPerPixel_, height_, inputConfig_.stride, GL_TEXTURE0, 0); - - /* Texture we will render to */ - eglImageBayerOut_ = std::make_unique<eGLImage>(GL_RGBA, outputSize_.width, outputSize_.height, outputConfig_.stride, GL_TEXTURE1, 1); - return 0; } void DebayerEGL::stop() { - eglImageBayerOut_.reset(); - eglImageBayerIn_.reset(); + eglImageBayerOut_.clear(); + eglImageBayerIn_.clear(); if (programId_) glDeleteProgram(programId_); diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index d8509e9f2..ddb3ef378 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -22,6 +22,7 @@ #include "libcamera/internal/mapped_framebuffer.h" #include "libcamera/internal/software_isp/benchmark.h" #include "libcamera/internal/software_isp/swstats_cpu.h" +#include "libcamera/internal/v4l2_videodevice.h" #include <EGL/egl.h> #include <EGL/eglext.h> @@ -70,14 +71,19 @@ private: bool use_dmabuf_; + eGLImage *getCachedInputFrameBuffer(FrameBuffer *input, std::optional<MappedFrameBuffer> *inMapped, std::optional<DmaSyncer> *inDmaSyncer); + eGLImage *getCachedOutputFrameBuffer(FrameBuffer *output); + /* Shader program identifiers */ GLuint vertexShaderId_ = 0; GLuint fragmentShaderId_ = 0; GLuint programId_ = 0; /* Pointer to object representing input texture */ - std::unique_ptr<eGLImage> eglImageBayerIn_; - std::unique_ptr<eGLImage> eglImageBayerOut_; + std::unordered_map<int, std::unique_ptr<eGLImage>> eglImageBayerIn_; + std::unordered_map<int, std::unique_ptr<eGLImage>> eglImageBayerOut_; + unsigned int inputBufferCount_; + unsigned int outputBufferCount_; /* Shader parameters */ float firstRed_x_;
Implement a texture caching mechanism for both input and output frames and for both types of input frame. The before/after on a Qualcomm x1e is: 9.737ms per frame 5.691ms per frame The before/after on a Qualcomm sm8250 is: 21.710ms per frame 17.336ms per frame for i in {1..20} do cam -c /base/soc@0/cci@ac16000/i2c-bus@1/camera@10 -s width=1920,height=1080 --capture=60 Interestingly there appears to be an absolute ~ 4.x ms per frame uplift as opposed to what intuition might suggest a proportional. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> --- src/libcamera/software_isp/debayer_egl.cpp | 87 +++++++++++++++++----- src/libcamera/software_isp/debayer_egl.h | 10 ++- 2 files changed, 75 insertions(+), 22 deletions(-)