[v3] libcamera: software_isp: Probe EGL availability before creating DebayerEGL
diff mbox series

Message ID 20260612025641.3665225-1-qi.hou@nxp.com
State Superseded
Headers show
Series
  • [v3] libcamera: software_isp: Probe EGL availability before creating DebayerEGL
Related show

Commit Message

Qi Hou June 12, 2026, 2:56 a.m. UTC
When HAVE_DEBAYER_EGL is enabled, the SoftwareIsp constructor
unconditionally creates a DebayerEGL instance. If the platform lacks
EGL surfaceless support, DebayerEGL::start() fails with -ENODEV after
SwStatsCpu ownership has already been moved, making fallback to
DebayerCpu impossible.

Add a static eGL::isAvailable() method that probes whether EGL
surfaceless rendering can be initialised. Extract the common display
obtaining logic (eglBindAPI, eglGetPlatformDisplay, eglInitialize)
into a private eGL::probeDisplay() helper that is shared by both
isAvailable() and initEGLContext(), avoiding code duplication.

The SoftwareIsp constructor now calls eGL::isAvailable() before
creating DebayerEGL. If EGL is not available, the existing fallback
path creates a DebayerCpu instance instead.

Signed-off-by: Qi Hou <qi.hou@nxp.com>
---
 include/libcamera/internal/egl.h            |  2 +
 src/libcamera/egl.cpp                       | 60 ++++++++++++++++-----
 src/libcamera/software_isp/software_isp.cpp | 10 +++-
 3 files changed, 56 insertions(+), 16 deletions(-)

Comments

Milan Zamazal June 12, 2026, 10:34 a.m. UTC | #1
Qi Hou <qi.hou@nxp.com> writes:

> When HAVE_DEBAYER_EGL is enabled, the SoftwareIsp constructor
> unconditionally creates a DebayerEGL instance. If the platform lacks
> EGL surfaceless support, DebayerEGL::start() fails with -ENODEV after
> SwStatsCpu ownership has already been moved, making fallback to
> DebayerCpu impossible.
>
> Add a static eGL::isAvailable() method that probes whether EGL
> surfaceless rendering can be initialised. Extract the common display
> obtaining logic (eglBindAPI, eglGetPlatformDisplay, eglInitialize)
> into a private eGL::probeDisplay() helper that is shared by both
> isAvailable() and initEGLContext(), avoiding code duplication.
>
> The SoftwareIsp constructor now calls eGL::isAvailable() before
> creating DebayerEGL. If EGL is not available, the existing fallback
> path creates a DebayerCpu instance instead.
>
> Signed-off-by: Qi Hou <qi.hou@nxp.com>

Reviewed-by: Milan Zamazal <mzamazal@redhat.com>

> ---
>  include/libcamera/internal/egl.h            |  2 +
>  src/libcamera/egl.cpp                       | 60 ++++++++++++++++-----
>  src/libcamera/software_isp/software_isp.cpp | 10 +++-
>  3 files changed, 56 insertions(+), 16 deletions(-)
>
> diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h
> index 57f90d93..f7bfb28d 100644
> --- a/include/libcamera/internal/egl.h
> +++ b/include/libcamera/internal/egl.h
> @@ -103,6 +103,7 @@ public:
>  	~eGL();
>  
>  	int initEGLContext();
> +	static bool isAvailable();
>  
>  	int createInputDMABufTexture2D(eGLImage &eglImage, int fd);
>  	int createOutputDMABufTexture2D(eGLImage &eglImage, int fd);
> @@ -133,6 +134,7 @@ private:
>  	EGLContext context_ = EGL_NO_CONTEXT;
>  	EGLSurface surface_ = EGL_NO_SURFACE;
>  
> +	static EGLDisplay probeDisplay();
>  	int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,
>  			  unsigned int shaderDataLen,
>  			  Span<const std::string> shaderEnv);
> diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp
> index c185bb7a..860f18dd 100644
> --- a/src/libcamera/egl.cpp
> +++ b/src/libcamera/egl.cpp
> @@ -267,6 +267,50 @@ void eGL::createTexture2D(eGLImage &eglImage, void *data)
>  	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
>  }
>  
> +EGLDisplay eGL::probeDisplay()
> +{
> +	EGLDisplay display;
> +
> +	if (!eglBindAPI(EGL_OPENGL_ES_API)) {
> +		LOG(eGL, Info) << "API bind fail";
> +		return EGL_NO_DISPLAY;
> +	}
> +
> +	display = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,
> +					 EGL_DEFAULT_DISPLAY,
> +					 nullptr);
> +
> +	if (display == EGL_NO_DISPLAY) {
> +		LOG(eGL, Info) << "Unable to get EGL display";
> +		return EGL_NO_DISPLAY;
> +	}
> +
> +	if (eglInitialize(display, nullptr, nullptr) != EGL_TRUE) {
> +		LOG(eGL, Error) << "eglInitialize fail";
> +		return EGL_NO_DISPLAY;
> +	}
> +
> +	return display;
> +}
> +
> +/**
> + * \brief Probe whether EGL surfaceless rendering is available
> + *
> + * Checks if an EGL surfaceless display can be obtained and initialised.
> + * The display is immediately terminated so that no resources are leaked.
> + *
> + * \return true if EGL surfaceless rendering is available, false otherwise
> + */
> +bool eGL::isAvailable()
> +{
> +	EGLDisplay display = probeDisplay();
> +	if (display == EGL_NO_DISPLAY)
> +		return false;
> +
> +	eglTerminate(display);
> +	return true;
> +}
> +
>  /**
>   * \brief Initialise the EGL context
>   *
> @@ -297,21 +341,9 @@ int eGL::initEGLContext()
>  	EGLint numConfigs;
>  	EGLConfig config;
>  
> -	if (!eglBindAPI(EGL_OPENGL_ES_API)) {
> -		LOG(eGL, Error) << "API bind fail";
> -		goto fail;
> -	}
> -
> -	display_ = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,
> -					 EGL_DEFAULT_DISPLAY,
> -					 nullptr);
> +	display_ = probeDisplay();
>  	if (display_ == EGL_NO_DISPLAY) {
> -		LOG(eGL, Error) << "Unable to get EGL display";
> -		goto fail;
> -	}
> -
> -	if (eglInitialize(display_, nullptr, nullptr) != EGL_TRUE) {
> -		LOG(eGL, Error) << "eglInitialize fail";
> +		LOG(eGL, Error) << "Unable to probe display";
>  		goto fail;
>  	}
>  
> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
> index 781cf02f..ff8c3465 100644
> --- a/src/libcamera/software_isp/software_isp.cpp
> +++ b/src/libcamera/software_isp/software_isp.cpp
> @@ -119,8 +119,14 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
>  		}
>  	}
>  
> -	if (!softISPMode || softISPMode == "gpu")
> -		debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);
> +	if (!softISPMode || softISPMode == "gpu") {
> +		if (eGL::isAvailable()) {
> +			debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);
> +		} else {
> +			LOG(SoftwareIsp, Info)
> +				<< "EGL not available, falling back to CPU debayer";
> +		}
> +	}
>  
>  #endif
>  	if (!debayer_)
Bryan O'Donoghue June 13, 2026, 12:42 p.m. UTC | #2
On 12/06/2026 03:56, Qi Hou wrote:
> When HAVE_DEBAYER_EGL is enabled, the SoftwareIsp constructor
> unconditionally creates a DebayerEGL instance. If the platform lacks
> EGL surfaceless support, DebayerEGL::start() fails with -ENODEV after
> SwStatsCpu ownership has already been moved, making fallback to
> DebayerCpu impossible.
> 
> Add a static eGL::isAvailable() method that probes whether EGL
> surfaceless rendering can be initialised. Extract the common display
> obtaining logic (eglBindAPI, eglGetPlatformDisplay, eglInitialize)
> into a private eGL::probeDisplay() helper that is shared by both
> isAvailable() and initEGLContext(), avoiding code duplication.
> 
> The SoftwareIsp constructor now calls eGL::isAvailable() before
> creating DebayerEGL. If EGL is not available, the existing fallback
> path creates a DebayerCpu instance instead.
> 
> Signed-off-by: Qi Hou <qi.hou@nxp.com>
> ---
>   include/libcamera/internal/egl.h            |  2 +
>   src/libcamera/egl.cpp                       | 60 ++++++++++++++++-----
>   src/libcamera/software_isp/software_isp.cpp | 10 +++-
>   3 files changed, 56 insertions(+), 16 deletions(-)
> 
> diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h
> index 57f90d93..f7bfb28d 100644
> --- a/include/libcamera/internal/egl.h
> +++ b/include/libcamera/internal/egl.h
> @@ -103,6 +103,7 @@ public:
>   	~eGL();
> 
>   	int initEGLContext();
> +	static bool isAvailable();
> 
>   	int createInputDMABufTexture2D(eGLImage &eglImage, int fd);
>   	int createOutputDMABufTexture2D(eGLImage &eglImage, int fd);
> @@ -133,6 +134,7 @@ private:
>   	EGLContext context_ = EGL_NO_CONTEXT;
>   	EGLSurface surface_ = EGL_NO_SURFACE;
> 
> +	static EGLDisplay probeDisplay();
>   	int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,
>   			  unsigned int shaderDataLen,
>   			  Span<const std::string> shaderEnv);
> diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp
> index c185bb7a..860f18dd 100644
> --- a/src/libcamera/egl.cpp
> +++ b/src/libcamera/egl.cpp
> @@ -267,6 +267,50 @@ void eGL::createTexture2D(eGLImage &eglImage, void *data)
>   	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
>   }
> 
> +EGLDisplay eGL::probeDisplay()
> +{
> +	EGLDisplay display;
> +
> +	if (!eglBindAPI(EGL_OPENGL_ES_API)) {
> +		LOG(eGL, Info) << "API bind fail";
> +		return EGL_NO_DISPLAY;
> +	}
> +
> +	display = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,
> +					 EGL_DEFAULT_DISPLAY,
> +					 nullptr);
> +
> +	if (display == EGL_NO_DISPLAY) {
> +		LOG(eGL, Info) << "Unable to get EGL display";
> +		return EGL_NO_DISPLAY;
> +	}
> +
> +	if (eglInitialize(display, nullptr, nullptr) != EGL_TRUE) {
> +		LOG(eGL, Error) << "eglInitialize fail";
> +		return EGL_NO_DISPLAY;
> +	}
> +
> +	return display;
> +}
> +
> +/**
> + * \brief Probe whether EGL surfaceless rendering is available
> + *
> + * Checks if an EGL surfaceless display can be obtained and initialised.
> + * The display is immediately terminated so that no resources are leaked.
> + *
> + * \return true if EGL surfaceless rendering is available, false otherwise
> + */
> +bool eGL::isAvailable()
> +{
> +	EGLDisplay display = probeDisplay();
> +	if (display == EGL_NO_DISPLAY)
> +		return false;
> +
> +	eglTerminate(display);
> +	return true;
> +}
> +
>   /**
>    * \brief Initialise the EGL context
>    *
> @@ -297,21 +341,9 @@ int eGL::initEGLContext()
>   	EGLint numConfigs;
>   	EGLConfig config;
> 
> -	if (!eglBindAPI(EGL_OPENGL_ES_API)) {
> -		LOG(eGL, Error) << "API bind fail";
> -		goto fail;
> -	}
> -
> -	display_ = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,
> -					 EGL_DEFAULT_DISPLAY,
> -					 nullptr);
> +	display_ = probeDisplay();
>   	if (display_ == EGL_NO_DISPLAY) {
> -		LOG(eGL, Error) << "Unable to get EGL display";
> -		goto fail;
> -	}
> -
> -	if (eglInitialize(display_, nullptr, nullptr) != EGL_TRUE) {
> -		LOG(eGL, Error) << "eglInitialize fail";
> +		LOG(eGL, Error) << "Unable to probe display";
>   		goto fail;
>   	}
> 
> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
> index 781cf02f..ff8c3465 100644
> --- a/src/libcamera/software_isp/software_isp.cpp
> +++ b/src/libcamera/software_isp/software_isp.cpp
> @@ -119,8 +119,14 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
>   		}
>   	}
> 
> -	if (!softISPMode || softISPMode == "gpu")
> -		debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);
> +	if (!softISPMode || softISPMode == "gpu") {
> +		if (eGL::isAvailable()) {
> +			debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);
> +		} else {
> +			LOG(SoftwareIsp, Info)
> +				<< "EGL not available, falling back to CPU debayer";
> +		}
> +	}
> 
>   #endif
>   	if (!debayer_)
> --
> 2.34.1
> 

Perhaps a further optimisation of this would be to bring back GBM 
surface support ?

i.e. our first version of GPUISP used a GBM surface which then 
transitioned to MESA surfaceless.

I wonder if you have tried GBM ? It should be easy enough to revert the 
change and test ?

---
bod
Qi Hou June 15, 2026, 8:23 a.m. UTC | #3
Hi Bryan O'Donoghue,



Thanks for your comment. I gave a try to revert below two patches that moved from GBM surface to surfaceless and tested on my i.MX8MM board.

The good news is that DebayerEGL::start() no longer fails. However, the output display is completely black.



I’m not sure if this is a GBM surface configuration issue on i.MX8MM or something else. Since I’m not very familiar with the GBM surface path.

I think it might be better handled by someone like you with professional expertise.



My patch (the isAvailable() probe + CPU fallback) at least provides a working path on platforms without surfaceless support. Would it make sense

to merge this as a basic fix, and then add GBM surface fallback as a follow-up ?





  1.  commit 0c2ed9ebf99e3328efe7e480d194d333a8dc5568

Author: Robert Mader <robert.mader@collabora.com<mailto:robert.mader@collabora.com>>

Date:   Wed Jan 21 10:08:52 2026 +0100



    egl: Use the Mesa surfaceless platform instead of GBM



    Mesa surfaceless platform appears to be a better fit for the use-case at hand:

    1. Like GBM it is Mesa specific, so no change in supported setups is

       expected. If ever required, a fallback to the generic device platform

       could be added on top.

    2. It leaves the complexity of selecting a renderer device to the

       driver, reducing code and dependencies.

    3. It allows to use llvmpipe / software drivers without dri device,

       which can be useful on CI or debugging (with LIBGL_ALWAYS_SOFTWARE=1).



    Signed-off-by: Robert Mader <robert.mader@collabora.com<mailto:robert.mader@collabora.com>>

    Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org<mailto:bryan.odonoghue@linaro.org>>

    Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org<mailto:bryan.odonoghue@linaro.org>> # sm8250/rb5, x1e/Dell Insprion14p

    Reviewed-by: Milan Zamazal <mzamazal@redhat.com<mailto:mzamazal@redhat.com>>

    Tested-by: Milan Zamazal <mzamazal@redhat.com<mailto:mzamazal@redhat.com>> # TI AM69

    Tested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com<mailto:barnabas.pocze@ideasonboard.com>> # ThinkPad X1 Yoga Gen 7 + ov2740

    Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com<mailto:laurent.pinchart@ideasonboard.com>>

    Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com<mailto:kieran.bingham@ideasonboard.com>>



  1.  commit 7350d6cc5dd1b5bc724749d21744c07879deb803

Author: Robert Mader <robert.mader@collabora.com<mailto:robert.mader@collabora.com>>

Date:   Wed Jan 21 10:08:54 2026 +0100



    Revert "libcamera: software_isp: gbm: Add a GBM helper class for GPU surface access"



    GBM is not used any more - remove the helper class.



    This reverts commit c60b1ce8193841c2742b655097bb39ccbcb417c2.



    Signed-off-by: Robert Mader <robert.mader@collabora.com<mailto:robert.mader@collabora.com>>

    Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org<mailto:bryan.odonoghue@linaro.org>>

    Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org<mailto:bryan.odonoghue@linaro.org>> # sm8250/rb5, x1e/Dell Insprion14p

    Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com<mailto:barnabas.pocze@ideasonboard.com>>

    Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com<mailto:laurent.pinchart@ideasonboard.com>>

    Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com<mailto:kieran.bingham@ideasonboard.com>>



Regards,

Qi Hou



-----Original Message-----
From: Bryan O'Donoghue <bod.linux@nxsw.ie>
Sent: Saturday, June 13, 2026 8:43 PM
To: Qi Hou <qi.hou@nxp.com>; libcamera-devel@lists.libcamera.org
Cc: Jared Hu <jared.hu@nxp.com>; Julien Vuillaumier <julien.vuillaumier@nxp.com>
Subject: [EXT] Re: [PATCH v3] libcamera: software_isp: Probe EGL availability before creating DebayerEGL



[Some people who received this message don't often get email from bod.linux@nxsw.ie<mailto:bod.linux@nxsw.ie>. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]



Caution: This is an external email. Please take care when clicking links or opening attachments. When in doubt, report the message using the 'Report this email' button





On 12/06/2026 03:56, Qi Hou wrote:

> When HAVE_DEBAYER_EGL is enabled, the SoftwareIsp constructor

> unconditionally creates a DebayerEGL instance. If the platform lacks

> EGL surfaceless support, DebayerEGL::start() fails with -ENODEV after

> SwStatsCpu ownership has already been moved, making fallback to

> DebayerCpu impossible.

>

> Add a static eGL::isAvailable() method that probes whether EGL

> surfaceless rendering can be initialised. Extract the common display

> obtaining logic (eglBindAPI, eglGetPlatformDisplay, eglInitialize)

> into a private eGL::probeDisplay() helper that is shared by both

> isAvailable() and initEGLContext(), avoiding code duplication.

>

> The SoftwareIsp constructor now calls eGL::isAvailable() before

> creating DebayerEGL. If EGL is not available, the existing fallback

> path creates a DebayerCpu instance instead.

>

> Signed-off-by: Qi Hou <qi.hou@nxp.com<mailto:qi.hou@nxp.com>>

> ---

>   include/libcamera/internal/egl.h            |  2 +

>   src/libcamera/egl.cpp                       | 60 ++++++++++++++++-----

>   src/libcamera/software_isp/software_isp.cpp | 10 +++-

>   3 files changed, 56 insertions(+), 16 deletions(-)

>

> diff --git a/include/libcamera/internal/egl.h

> b/include/libcamera/internal/egl.h

> index 57f90d93..f7bfb28d 100644

> --- a/include/libcamera/internal/egl.h

> +++ b/include/libcamera/internal/egl.h

> @@ -103,6 +103,7 @@ public:

>       ~eGL();

>

>       int initEGLContext();

> +     static bool isAvailable();

>

>       int createInputDMABufTexture2D(eGLImage &eglImage, int fd);

>       int createOutputDMABufTexture2D(eGLImage &eglImage, int fd); @@

> -133,6 +134,7 @@ private:

>       EGLContext context_ = EGL_NO_CONTEXT;

>       EGLSurface surface_ = EGL_NO_SURFACE;

>

> +     static EGLDisplay probeDisplay();

>       int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,

>                         unsigned int shaderDataLen,

>                         Span<const std::string> shaderEnv); diff --git

> a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp index

> c185bb7a..860f18dd 100644

> --- a/src/libcamera/egl.cpp

> +++ b/src/libcamera/egl.cpp

> @@ -267,6 +267,50 @@ void eGL::createTexture2D(eGLImage &eglImage, void *data)

>       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

>   }

>

> +EGLDisplay eGL::probeDisplay()

> +{

> +     EGLDisplay display;

> +

> +     if (!eglBindAPI(EGL_OPENGL_ES_API)) {

> +             LOG(eGL, Info) << "API bind fail";

> +             return EGL_NO_DISPLAY;

> +     }

> +

> +     display = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,

> +                                      EGL_DEFAULT_DISPLAY,

> +                                      nullptr);

> +

> +     if (display == EGL_NO_DISPLAY) {

> +             LOG(eGL, Info) << "Unable to get EGL display";

> +             return EGL_NO_DISPLAY;

> +     }

> +

> +     if (eglInitialize(display, nullptr, nullptr) != EGL_TRUE) {

> +             LOG(eGL, Error) << "eglInitialize fail";

> +             return EGL_NO_DISPLAY;

> +     }

> +

> +     return display;

> +}

> +

> +/**

> + * \brief Probe whether EGL surfaceless rendering is available

> + *

> + * Checks if an EGL surfaceless display can be obtained and initialised.

> + * The display is immediately terminated so that no resources are leaked.

> + *

> + * \return true if EGL surfaceless rendering is available, false

> +otherwise  */ bool eGL::isAvailable() {

> +     EGLDisplay display = probeDisplay();

> +     if (display == EGL_NO_DISPLAY)

> +             return false;

> +

> +     eglTerminate(display);

> +     return true;

> +}

> +

>   /**

>    * \brief Initialise the EGL context

>    *

> @@ -297,21 +341,9 @@ int eGL::initEGLContext()

>       EGLint numConfigs;

>       EGLConfig config;

>

> -     if (!eglBindAPI(EGL_OPENGL_ES_API)) {

> -             LOG(eGL, Error) << "API bind fail";

> -             goto fail;

> -     }

> -

> -     display_ = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,

> -                                      EGL_DEFAULT_DISPLAY,

> -                                      nullptr);

> +     display_ = probeDisplay();

>       if (display_ == EGL_NO_DISPLAY) {

> -             LOG(eGL, Error) << "Unable to get EGL display";

> -             goto fail;

> -     }

> -

> -     if (eglInitialize(display_, nullptr, nullptr) != EGL_TRUE) {

> -             LOG(eGL, Error) << "eglInitialize fail";

> +             LOG(eGL, Error) << "Unable to probe display";

>               goto fail;

>       }

>

> diff --git a/src/libcamera/software_isp/software_isp.cpp

> b/src/libcamera/software_isp/software_isp.cpp

> index 781cf02f..ff8c3465 100644

> --- a/src/libcamera/software_isp/software_isp.cpp

> +++ b/src/libcamera/software_isp/software_isp.cpp

> @@ -119,8 +119,14 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,

>               }

>       }

>

> -     if (!softISPMode || softISPMode == "gpu")

> -             debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);

> +     if (!softISPMode || softISPMode == "gpu") {

> +             if (eGL::isAvailable()) {

> +                     debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);

> +             } else {

> +                     LOG(SoftwareIsp, Info)

> +                             << "EGL not available, falling back to CPU debayer";

> +             }

> +     }

>

>   #endif

>       if (!debayer_)

> --

> 2.34.1

>



Perhaps a further optimisation of this would be to bring back GBM surface support ?



i.e. our first version of GPUISP used a GBM surface which then transitioned to MESA surfaceless.



I wonder if you have tried GBM ? It should be easy enough to revert the change and test ?



---

bod
Bryan O'Donoghue June 15, 2026, 10:54 a.m. UTC | #4
On 15/06/2026 09:23, Qi Hou wrote:
> Hi Bryan O'Donoghue,
> 
> Thanks for your comment. I gave a try to revert below two patches that 
> moved from GBM surface to surfaceless and tested on my i.MX8MM board.
> 
> The good news is that DebayerEGL::start() no longer fails. However, the 
> output display is completely black.
> 
> I’m not sure if this is a GBM surface configuration issue on i.MX8MM or 
> something else. Since I’m not very familiar with the GBM surface path.
> 
> I think it might be better handled by someone like you with professional 
> expertise.
> 
> My patch (the isAvailable() probe + CPU fallback) at least provides a 
> working path on platforms without surfaceless support. Would it make sense
> 
> to merge this as a basic fix, and then add GBM surface fallback as a 
> follow-up ?
Sure.

Could you add something to the gpuisp todo log about adding GBM back in 
as a progressive fallback ?

I'm sure I can get a GBM surface going again, unless you do it first.

---
bod
Qi Hou June 16, 2026, 9:45 a.m. UTC | #5
Hi Bryan O'Donoghue,



I don’t know why I did not receive the mail about your latest comment in https://patchwork.libcamera.org/patch/26874/ that stated:



“Sure.



Could you add something to the gpuisp todo log about adding GBM back in

as a progressive fallback ?



I'm sure I can get a GBM surface going again, unless you do it first.

“

Just now I sent out v4 to add this entry to the file src/libcamera/software_isp/TODO.md.
I’m not sure if I placed it to the right place. Please help review.

Regards,
Qi Hou

From: Qi Hou
Sent: Monday, June 15, 2026 4:24 PM
To: Bryan O'Donoghue <bod.linux@nxsw.ie>; libcamera-devel@lists.libcamera.org
Cc: Jared Hu <jared.hu@nxp.com>; Julien Vuillaumier <julien.vuillaumier@nxp.com>
Subject: RE: [EXT] Re: [PATCH v3] libcamera: software_isp: Probe EGL availability before creating DebayerEGL


Hi Bryan O'Donoghue,



Thanks for your comment. I gave a try to revert below two patches that moved from GBM surface to surfaceless and tested on my i.MX8MM board.

The good news is that DebayerEGL::start() no longer fails. However, the output display is completely black.



I’m not sure if this is a GBM surface configuration issue on i.MX8MM or something else. Since I’m not very familiar with the GBM surface path.

I think it might be better handled by someone like you with professional expertise.



My patch (the isAvailable() probe + CPU fallback) at least provides a working path on platforms without surfaceless support. Would it make sense

to merge this as a basic fix, and then add GBM surface fallback as a follow-up ?





  1.  commit 0c2ed9ebf99e3328efe7e480d194d333a8dc5568

Author: Robert Mader <robert.mader@collabora.com<mailto:robert.mader@collabora.com>>

Date:   Wed Jan 21 10:08:52 2026 +0100



    egl: Use the Mesa surfaceless platform instead of GBM



    Mesa surfaceless platform appears to be a better fit for the use-case at hand:

    1. Like GBM it is Mesa specific, so no change in supported setups is

       expected. If ever required, a fallback to the generic device platform

       could be added on top.

    2. It leaves the complexity of selecting a renderer device to the

       driver, reducing code and dependencies.

    3. It allows to use llvmpipe / software drivers without dri device,

       which can be useful on CI or debugging (with LIBGL_ALWAYS_SOFTWARE=1).



    Signed-off-by: Robert Mader <robert.mader@collabora.com<mailto:robert.mader@collabora.com>>

    Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org<mailto:bryan.odonoghue@linaro.org>>

    Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org<mailto:bryan.odonoghue@linaro.org>> # sm8250/rb5, x1e/Dell Insprion14p

    Reviewed-by: Milan Zamazal <mzamazal@redhat.com<mailto:mzamazal@redhat.com>>

    Tested-by: Milan Zamazal <mzamazal@redhat.com<mailto:mzamazal@redhat.com>> # TI AM69

    Tested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com<mailto:barnabas.pocze@ideasonboard.com>> # ThinkPad X1 Yoga Gen 7 + ov2740

    Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com<mailto:laurent.pinchart@ideasonboard.com>>

    Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com<mailto:kieran.bingham@ideasonboard.com>>



  1.  commit 7350d6cc5dd1b5bc724749d21744c07879deb803

Author: Robert Mader <robert.mader@collabora.com<mailto:robert.mader@collabora.com>>

Date:   Wed Jan 21 10:08:54 2026 +0100



    Revert "libcamera: software_isp: gbm: Add a GBM helper class for GPU surface access"



    GBM is not used any more - remove the helper class.



    This reverts commit c60b1ce8193841c2742b655097bb39ccbcb417c2.



    Signed-off-by: Robert Mader <robert.mader@collabora.com<mailto:robert.mader@collabora.com>>

    Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org<mailto:bryan.odonoghue@linaro.org>>

    Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org<mailto:bryan.odonoghue@linaro.org>> # sm8250/rb5, x1e/Dell Insprion14p

    Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com<mailto:barnabas.pocze@ideasonboard.com>>

    Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com<mailto:laurent.pinchart@ideasonboard.com>>

    Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com<mailto:kieran.bingham@ideasonboard.com>>



Regards,

Qi Hou



-----Original Message-----
From: Bryan O'Donoghue <bod.linux@nxsw.ie<mailto:bod.linux@nxsw.ie>>
Sent: Saturday, June 13, 2026 8:43 PM
To: Qi Hou <qi.hou@nxp.com<mailto:qi.hou@nxp.com>>; libcamera-devel@lists.libcamera.org<mailto:libcamera-devel@lists.libcamera.org>
Cc: Jared Hu <jared.hu@nxp.com<mailto:jared.hu@nxp.com>>; Julien Vuillaumier <julien.vuillaumier@nxp.com<mailto:julien.vuillaumier@nxp.com>>
Subject: [EXT] Re: [PATCH v3] libcamera: software_isp: Probe EGL availability before creating DebayerEGL



[Some people who received this message don't often get email from bod.linux@nxsw.ie<mailto:bod.linux@nxsw.ie>. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]



Caution: This is an external email. Please take care when clicking links or opening attachments. When in doubt, report the message using the 'Report this email' button





On 12/06/2026 03:56, Qi Hou wrote:

> When HAVE_DEBAYER_EGL is enabled, the SoftwareIsp constructor

> unconditionally creates a DebayerEGL instance. If the platform lacks

> EGL surfaceless support, DebayerEGL::start() fails with -ENODEV after

> SwStatsCpu ownership has already been moved, making fallback to

> DebayerCpu impossible.

>

> Add a static eGL::isAvailable() method that probes whether EGL

> surfaceless rendering can be initialised. Extract the common display

> obtaining logic (eglBindAPI, eglGetPlatformDisplay, eglInitialize)

> into a private eGL::probeDisplay() helper that is shared by both

> isAvailable() and initEGLContext(), avoiding code duplication.

>

> The SoftwareIsp constructor now calls eGL::isAvailable() before

> creating DebayerEGL. If EGL is not available, the existing fallback

> path creates a DebayerCpu instance instead.

>

> Signed-off-by: Qi Hou <qi.hou@nxp.com<mailto:qi.hou@nxp.com>>

> ---

>   include/libcamera/internal/egl.h            |  2 +

>   src/libcamera/egl.cpp                       | 60 ++++++++++++++++-----

>   src/libcamera/software_isp/software_isp.cpp | 10 +++-

>   3 files changed, 56 insertions(+), 16 deletions(-)

>

> diff --git a/include/libcamera/internal/egl.h

> b/include/libcamera/internal/egl.h

> index 57f90d93..f7bfb28d 100644

> --- a/include/libcamera/internal/egl.h

> +++ b/include/libcamera/internal/egl.h

> @@ -103,6 +103,7 @@ public:

>       ~eGL();

>

>       int initEGLContext();

> +     static bool isAvailable();

>

>       int createInputDMABufTexture2D(eGLImage &eglImage, int fd);

>       int createOutputDMABufTexture2D(eGLImage &eglImage, int fd); @@

> -133,6 +134,7 @@ private:

>       EGLContext context_ = EGL_NO_CONTEXT;

>       EGLSurface surface_ = EGL_NO_SURFACE;

>

> +     static EGLDisplay probeDisplay();

>       int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,

>                         unsigned int shaderDataLen,

>                         Span<const std::string> shaderEnv); diff --git

> a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp index

> c185bb7a..860f18dd 100644

> --- a/src/libcamera/egl.cpp

> +++ b/src/libcamera/egl.cpp

> @@ -267,6 +267,50 @@ void eGL::createTexture2D(eGLImage &eglImage, void *data)

>       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

>   }

>

> +EGLDisplay eGL::probeDisplay()

> +{

> +     EGLDisplay display;

> +

> +     if (!eglBindAPI(EGL_OPENGL_ES_API)) {

> +             LOG(eGL, Info) << "API bind fail";

> +             return EGL_NO_DISPLAY;

> +     }

> +

> +     display = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,

> +                                      EGL_DEFAULT_DISPLAY,

> +                                      nullptr);

> +

> +     if (display == EGL_NO_DISPLAY) {

> +             LOG(eGL, Info) << "Unable to get EGL display";

> +             return EGL_NO_DISPLAY;

> +     }

> +

> +     if (eglInitialize(display, nullptr, nullptr) != EGL_TRUE) {

> +             LOG(eGL, Error) << "eglInitialize fail";

> +             return EGL_NO_DISPLAY;

> +     }

> +

> +     return display;

> +}

> +

> +/**

> + * \brief Probe whether EGL surfaceless rendering is available

> + *

> + * Checks if an EGL surfaceless display can be obtained and initialised.

> + * The display is immediately terminated so that no resources are leaked.

> + *

> + * \return true if EGL surfaceless rendering is available, false

> +otherwise  */ bool eGL::isAvailable() {

> +     EGLDisplay display = probeDisplay();

> +     if (display == EGL_NO_DISPLAY)

> +             return false;

> +

> +     eglTerminate(display);

> +     return true;

> +}

> +

>   /**

>    * \brief Initialise the EGL context

>    *

> @@ -297,21 +341,9 @@ int eGL::initEGLContext()

>       EGLint numConfigs;

>       EGLConfig config;

>

> -     if (!eglBindAPI(EGL_OPENGL_ES_API)) {

> -             LOG(eGL, Error) << "API bind fail";

> -             goto fail;

> -     }

> -

> -     display_ = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,

> -                                      EGL_DEFAULT_DISPLAY,

> -                                      nullptr);

> +     display_ = probeDisplay();

>       if (display_ == EGL_NO_DISPLAY) {

> -             LOG(eGL, Error) << "Unable to get EGL display";

> -             goto fail;

> -     }

> -

> -     if (eglInitialize(display_, nullptr, nullptr) != EGL_TRUE) {

> -             LOG(eGL, Error) << "eglInitialize fail";

> +             LOG(eGL, Error) << "Unable to probe display";

>               goto fail;

>       }

>

> diff --git a/src/libcamera/software_isp/software_isp.cpp

> b/src/libcamera/software_isp/software_isp.cpp

> index 781cf02f..ff8c3465 100644

> --- a/src/libcamera/software_isp/software_isp.cpp

> +++ b/src/libcamera/software_isp/software_isp.cpp

> @@ -119,8 +119,14 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,

>               }

>       }

>

> -     if (!softISPMode || softISPMode == "gpu")

> -             debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);

> +     if (!softISPMode || softISPMode == "gpu") {

> +             if (eGL::isAvailable()) {

> +                     debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);

> +             } else {

> +                     LOG(SoftwareIsp, Info)

> +                             << "EGL not available, falling back to CPU debayer";

> +             }

> +     }

>

>   #endif

>       if (!debayer_)

> --

> 2.34.1

>



Perhaps a further optimisation of this would be to bring back GBM surface support ?



i.e. our first version of GPUISP used a GBM surface which then transitioned to MESA surfaceless.



I wonder if you have tried GBM ? It should be easy enough to revert the change and test ?



---

bod

Patch
diff mbox series

diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h
index 57f90d93..f7bfb28d 100644
--- a/include/libcamera/internal/egl.h
+++ b/include/libcamera/internal/egl.h
@@ -103,6 +103,7 @@  public:
 	~eGL();
 
 	int initEGLContext();
+	static bool isAvailable();
 
 	int createInputDMABufTexture2D(eGLImage &eglImage, int fd);
 	int createOutputDMABufTexture2D(eGLImage &eglImage, int fd);
@@ -133,6 +134,7 @@  private:
 	EGLContext context_ = EGL_NO_CONTEXT;
 	EGLSurface surface_ = EGL_NO_SURFACE;
 
+	static EGLDisplay probeDisplay();
 	int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData,
 			  unsigned int shaderDataLen,
 			  Span<const std::string> shaderEnv);
diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp
index c185bb7a..860f18dd 100644
--- a/src/libcamera/egl.cpp
+++ b/src/libcamera/egl.cpp
@@ -267,6 +267,50 @@  void eGL::createTexture2D(eGLImage &eglImage, void *data)
 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 }
 
+EGLDisplay eGL::probeDisplay()
+{
+	EGLDisplay display;
+
+	if (!eglBindAPI(EGL_OPENGL_ES_API)) {
+		LOG(eGL, Info) << "API bind fail";
+		return EGL_NO_DISPLAY;
+	}
+
+	display = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,
+					 EGL_DEFAULT_DISPLAY,
+					 nullptr);
+
+	if (display == EGL_NO_DISPLAY) {
+		LOG(eGL, Info) << "Unable to get EGL display";
+		return EGL_NO_DISPLAY;
+	}
+
+	if (eglInitialize(display, nullptr, nullptr) != EGL_TRUE) {
+		LOG(eGL, Error) << "eglInitialize fail";
+		return EGL_NO_DISPLAY;
+	}
+
+	return display;
+}
+
+/**
+ * \brief Probe whether EGL surfaceless rendering is available
+ *
+ * Checks if an EGL surfaceless display can be obtained and initialised.
+ * The display is immediately terminated so that no resources are leaked.
+ *
+ * \return true if EGL surfaceless rendering is available, false otherwise
+ */
+bool eGL::isAvailable()
+{
+	EGLDisplay display = probeDisplay();
+	if (display == EGL_NO_DISPLAY)
+		return false;
+
+	eglTerminate(display);
+	return true;
+}
+
 /**
  * \brief Initialise the EGL context
  *
@@ -297,21 +341,9 @@  int eGL::initEGLContext()
 	EGLint numConfigs;
 	EGLConfig config;
 
-	if (!eglBindAPI(EGL_OPENGL_ES_API)) {
-		LOG(eGL, Error) << "API bind fail";
-		goto fail;
-	}
-
-	display_ = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,
-					 EGL_DEFAULT_DISPLAY,
-					 nullptr);
+	display_ = probeDisplay();
 	if (display_ == EGL_NO_DISPLAY) {
-		LOG(eGL, Error) << "Unable to get EGL display";
-		goto fail;
-	}
-
-	if (eglInitialize(display_, nullptr, nullptr) != EGL_TRUE) {
-		LOG(eGL, Error) << "eglInitialize fail";
+		LOG(eGL, Error) << "Unable to probe display";
 		goto fail;
 	}
 
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 781cf02f..ff8c3465 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -119,8 +119,14 @@  SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
 		}
 	}
 
-	if (!softISPMode || softISPMode == "gpu")
-		debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);
+	if (!softISPMode || softISPMode == "gpu") {
+		if (eGL::isAvailable()) {
+			debayer_ = std::make_unique<DebayerEGL>(std::move(stats), cm);
+		} else {
+			LOG(SoftwareIsp, Info)
+				<< "EGL not available, falling back to CPU debayer";
+		}
+	}
 
 #endif
 	if (!debayer_)