| Message ID | 20251212002937.3118-3-bryan.odonoghue@linaro.org |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Hi! Looks mostly good to me, just some small comments. On 12.12.25 01:29, Bryan O'Donoghue wrote: > Introduce an eGL base helper class which provides an eGL context based on a > passed width and height. > > The initGLContext function could be overloaded to provide an interface to a > real display. > > A set of helper functions is provided to compile and link GLSL shaders. > linkShaderProgram currently compiles vertex/fragment pairs but could be > overloaded or passed a parameter to link a compute shader instead. > > Breaking the eGL interface away from debayering - allows to use the eGL > context inside of a dma-buf heap cleanly, reuse that context inside of a > debayer layer and conceivably reuse the context in a multi-stage shader > pass. > > Small note the image_attrs[] array doesn't pass checkstyle.py however the > elements of the array are in pairs. > > Acked-by: Kieran Bingham<kieran.bingham@ideasonboard.com> > [bod: Takes fix from Hans for constructor stride bpp] > [bod: Drops eglClientWaitSync in favour of glFinish Robert/Milan] > Co-developed-by: Hans de Goede<johannes.goede@oss.qualcomm.com> > Signed-off-by: Hans de Goede<johannes.goede@oss.qualcomm.com> > Co-developed-by: Milan Zamazal<mzamazal@redhat.com> > Signed-off-by: Milan Zamazal<mzamazal@redhat.com> > Signed-off-by: Bryan O'Donoghue<bryan.odonoghue@linaro.org> > --- > include/libcamera/internal/egl.h | 186 ++++++++++ > src/libcamera/egl.cpp | 619 +++++++++++++++++++++++++++++++ > src/libcamera/meson.build | 23 ++ > 3 files changed, 828 insertions(+) > create mode 100644 include/libcamera/internal/egl.h > create mode 100644 src/libcamera/egl.cpp > > diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h > new file mode 100644 > index 000000000..542787dc5 > --- /dev/null > +++ b/include/libcamera/internal/egl.h > @@ -0,0 +1,186 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue<bryan.odonoghue@linaro.org> > + * > + * egl_context.cpp - Helper class for managing eGL interactions. > + */ > + > +#pragma once > + > +#include <sys/types.h> > +#include <unistd.h> > + > +#include <libcamera/base/log.h> > +#include <libcamera/base/span.h> > + > +#include "libcamera/base/utils.h" > +#include "libcamera/internal/gbm.h" > + > +#define EGL_EGLEXT_PROTOTYPES > +#include <EGL/egl.h> > +#include <EGL/eglext.h> > +#define GL_GLEXT_PROTOTYPES > +#include <GLES2/gl2.h> > +#include <GLES2/gl2ext.h> > + > +namespace libcamera { > + > +LOG_DECLARE_CATEGORY(eGL) > + > +/** > + * \class eGLImage > + * \brief Helper class for managing EGL image resources > + * > + * The eGLImage class encapsulates OpenGL ES texture and framebuffer objects > + * along with their associated EGL image. It aggregates handles, descriptors, > + * and routines for managing textures that can be associated with shader > + * uniform IDs. > + * > + * This class is particularly useful for managing DMA-BUF backed textures > + * in zero-copy rendering pipelines, where textures are bound to specific > + * texture units and can be used as both input textures and render targets. > + */ > +class eGLImage > +{ > +public: > + /** > + * \brief Construct an eGLImage with explicit stride > + * \param[in] width Image width in pixels > + * \param[in] height Image height in pixels > + * \param[in] bpp Bytes per pixel > + * \param[in] stride Row stride in bytes > + * \param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0) > + * \param[in] texture_unit_uniform_id Shader uniform ID for this texture unit > + * > + * Creates an eGLImage with the specified dimensions and stride. The stride > + * may differ from width * bpp due to alignment. > + */ > + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id) > + { > + init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id); > + } > + /** > + * \brief Construct an eGLImage with automatic stride calculation > + * \param[in] width Image width in pixels > + * \param[in] height Image height in pixels > + * \param[in] bpp Bytes per pixel > + * \param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0) > + * \param[in] texture_unit_uniform_id Shader uniform ID for this texture unit > + * > + * Creates an eGLImage with automatic stride calculation. The stride is > + * aligned to 256 bytes because 256 byte alignment is a common baseline alignment for GPUs. > + */ > + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id) > + { > + uint32_t stride = libcamera::utils::alignUp(width * bpp / 8, 256); > + > + init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id); > + } Looks like this one isn't used any more and can be dropped. That's good, IMO, as we should only need this for buffer import - i.e. in cases where the stride is (and should be) known. > + > + /** > + * \brief Destroy the eGLImage > + * > + * Cleans up OpenGL resources by deleting the framebuffer object and > + * texture. > + */ > + ~eGLImage() > + { > + glDeleteFramebuffers(1, &fbo_); > + glDeleteTextures(1, &texture_); > + } > + > + uint32_t width_; /**< Image width in pixels */ > + uint32_t height_; /**< Image height in pixels */ > + uint32_t stride_; /**< Row stride in bytes */ > + uint32_t offset_; /**< Buffer offset (reserved for future use) */ > + uint32_t framesize_; /**< Total frame size in bytes (stride * height) */ > + uint32_t bpp_; /**< Bytes per pixel */ > + uint32_t texture_unit_uniform_id_; /**< Shader uniform id for texture unit */ > + GLenum texture_unit_; /**< Texture unit associated with this image eg (GL_TEXTURE0) */ > + GLuint texture_; /**< OpenGL texture object ID */ > + GLuint fbo_; /**< OpenGL frame buffer object ID */ > + EGLImageKHR image_; /**< EGL Image handle */ > + > +private: > + LIBCAMERA_DISABLE_COPY_AND_MOVE(eGLImage) > + > + /** > + * \brief Initialise eGLImage state > + * \param[in] width Image width in pixels > + * \param[in] height Image height in pixels > + * \param[in] bpp Bytes per pixel > + * \param[in] stride Row stride in bytes > + * \param[in] texture_unit OpenGL texture unit > + * \param[in] texture_unit_uniform_id Shader uniform ID > + * > + * Common initialisation routine called by both constructors. Sets up > + * member variables and generates OpenGL texture and framebuffer objects. > + */ > + void init(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id) > + { > + image_ = EGL_NO_IMAGE_KHR; > + width_ = width; > + height_ = height; > + bpp_ = bpp; > + stride_ = stride; > + framesize_ = stride_ * height_; > + texture_unit_ = texture_unit; > + texture_unit_uniform_id_ = texture_unit_uniform_id; > + > + glGenTextures(1, &texture_); > + glGenFramebuffers(1, &fbo_); > + } > +}; > + > +class eGL > +{ > +public: > + eGL(); > + ~eGL(); > + > + int initEGLContext(GBM *gbmContext); > + void cleanUp(); > + > + int createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd); > + int createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd); > + void destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage); > + void createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data); > + > + void pushEnv(std::vector<std::string> &shaderEnv, const char *str); > + void makeCurrent(); > + > + int compileVertexShader(GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv); > + int compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv); > + int linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId); > + void dumpShaderSource(GLuint shaderId); > + void useProgram(GLuint programId); > + void deleteProgram(GLuint programId); > + int syncOutput(); > + > +private: > + LIBCAMERA_DISABLE_COPY_AND_MOVE(eGL) > + > + pid_t tid_; > + > + EGLDisplay display_; > + EGLContext context_; > + EGLSurface surface_; > + > + int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv); > + > + int createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output); > + > + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; > + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; > + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; > +}; > +} //namespace libcamera > diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp > new file mode 100644 > index 000000000..2ef1156eb > --- /dev/null > +++ b/src/libcamera/egl.cpp > @@ -0,0 +1,619 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue<bryan.odonoghue@linaro.org> > + * > + * egl.cpp - Helper class for managing eGL interactions. > + */ > + > +#include "libcamera/internal/egl.h" > + > +#include <fcntl.h> > +#include <sys/ioctl.h> > +#include <sys/mman.h> > +#include <unistd.h> > + > +#include <linux/dma-buf.h> > +#include <linux/dma-heap.h> > + > +#include <libcamera/base/thread.h> > + > +#include <libdrm/drm_fourcc.h> > + > +namespace libcamera { > + > +LOG_DEFINE_CATEGORY(eGL) > + > +/** > + * \class eGL > + * \brief Helper class for managing OpenGL ES operations > + * > + * It provides: > + * > + * - EGL context setup and management > + * - Extension function pointer retrieval > + * - Shader compilation and program linking > + * - DMA-BUF texture creation and management > + * - Synchronisation primitives > + * > + * This class is designed to work with zero-copy buffers via DMA-BUF file > + * descriptors. > + */ > + > +/** > + *\var eGL::tid_ > + *\brief Thread ID of the thread associated with this EGL context > + */ > + > +/** > + *\var eGL::display_ > + *\brief EGL display handle > + */ > + > +/** > + *\var eGL::context_ > + *\brief EGL context handle > + */ > + > +/** > + *\var eGL::surface_ > + *\brief EGL sufrace handle > + */ > + > +/** > + * \brief Construct an EGL helper > + * > + * Creates an eGL instance with uninitialised context. Call initEGLContext() > + * to set up the EGL display, context, and load extension functions. > + */ > +eGL::eGL() > +{ > + context_ = EGL_NO_CONTEXT; > + surface_ = EGL_NO_SURFACE; > + display_ = EGL_NO_DISPLAY; > +} > + > +/** > + * \brief Destroy the EGL helper > + * > + * Destroys the EGL context and surface if they were successfully created. > + */ > +eGL::~eGL() > +{ > + if (context_ != EGL_NO_CONTEXT) > + eglDestroyContext(display_, context_); > + > + if (surface_ != EGL_NO_SURFACE) > + eglDestroySurface(display_, surface_); > +} > + > +/** > + * \brief Synchronise rendering output > + * > + * Sychronise here. Calls glFinish() right now. > + * > + * \return 0 on success > + */ > +int eGL::syncOutput(void) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glFinish(); > + > + return 0; > +} > + > +/** > + * \brief Create a DMA-BUF backed 2D texture > + * \param[in,out] eglImage EGL image to associate with the DMA-BUF > + * \param[in] fd DMA-BUF file descriptor > + * \param[in] output If true, create framebuffer for render target > + * > + * Internal implementation for creating DMA-BUF textures. Creates an EGL > + * image from the DMA-BUF and binds it to a 2D texture. If output is true, > + * also creates and attaches a framebuffer object. > + * > + * \return 0 on success, or -ENODEV on failure > + */ > +int eGL::createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output) > +{ > + int ret = 0; > + > + ASSERT(tid_ == Thread::currentId()); > + > + // clang-format off > + EGLint image_attrs[] = { > + EGL_WIDTH, (EGLint)eglImage->width_, > + EGL_HEIGHT, (EGLint)eglImage->height_, > + EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888, > + EGL_DMA_BUF_PLANE0_FD_EXT, fd, > + EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, > + EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)eglImage->stride_, > + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0, > + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0, > + EGL_NONE, > + }; > + // clang-format on > + > + eglImage->image_ = eglCreateImageKHR(display_, EGL_NO_CONTEXT, > + EGL_LINUX_DMA_BUF_EXT, > + NULL, image_attrs); > + > + if (eglImage->image_ == EGL_NO_IMAGE_KHR) { > + LOG(eGL, Error) << "eglCreateImageKHR fail"; > + ret = -ENODEV; > + goto done; > + } > + > + // Bind texture unit and texture > + glActiveTexture(eglImage->texture_unit_); > + glBindTexture(GL_TEXTURE_2D, eglImage->texture_); > + > + // Generate texture with filter semantics > + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_); > + > + // Nearest filtering > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); > + > + // Wrap to edge to avoid edge artifacts > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); > + > + if (output) { > + // Generate a framebuffer from our texture direct to dma-buf handle buffer > + glBindFramebuffer(GL_FRAMEBUFFER, eglImage->fbo_); > + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eglImage->texture_, 0); > + > + GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER); > + if (err != GL_FRAMEBUFFER_COMPLETE) { > + LOG(eGL, Error) << "glFrameBufferTexture2D error " << err; > + eglDestroyImageKHR(display_, eglImage->image_); > + ret = -ENODEV; > + goto done; > + } > + } > +done: > + return ret; > +} > + > +/** > + * \brief Create an input DMA-BUF backed texture > + * \param[in,out] eglImage EGL image to associate with the DMA-BUF > + * \param[in] fd DMA-BUF file descriptor > + * > + * Creates an EGL image from a DMA-BUF file descriptor and binds it to > + * a 2D texture for use as an input texture in shaders. The texture is > + * configured with nearest filtering and clamp-to-edge wrapping. > + * > + * \return 0 on success, or -ENODEV on failure > + */ > +int eGL::createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + return createDMABufTexture2D(eglImage, fd, false); > +} > + > +/** > + * \brief Create an output DMA-BUF backed texture > + * \param[in,out] eglImage EGL image to associate with the DMA-BUF > + * \param[in] fd DMA-BUF file descriptor > + * > + * Creates an EGL image from a DMA-BUF file descriptor and binds it to > + * a 2D texture, then attaches it to a framebuffer object for use as a > + * render target. This enables zero-copy rendering directly to the > + * DMA-BUF. > + * > + * \return 0 on success, or -ENODEV on failure > + */ > +int eGL::createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + return createDMABufTexture2D(eglImage, fd, true); > +} > + > +/** > + * \brief Destroy a DMA-BUF texture's EGL image > + * \param[in,out] eglImage EGL image to destroy > + * > + * Destroys the EGL image associated with a DMA-BUF texture. The OpenGL > + * texture and framebuffer objects are destroyed separately in the > + * eGLImage destructor. > + */ > +void eGL::destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage) > +{ > + eglDestroyImage(display_, eglImage->image_); > +} > + > + > +/** > + * \brief Create a 2D texture from a memory buffer > + * \param[in,out] eglImage EGL image to associate with the texture > + * \param[in] format OpenGL internal format (e.g., GL_RGB, GL_RGBA) > + * \param[in] width Texture width in pixels > + * \param[in] height Texture height in pixels > + * \param[in] data Pointer to pixel data, or nullptr for uninitialised texture > + * > + * Creates a 2D texture from a CPU-accessible memory buffer. The texture > + * is configured with nearest filtering and clamp-to-edge wrapping. This > + * is useful for uploading static data like lookup tables or uniform color > + * matrices to the GPU. > + */ > +void eGL::createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glActiveTexture(eglImage->texture_unit_); > + glBindTexture(GL_TEXTURE_2D, eglImage->texture_); > + > + // Generate texture, bind, associate image to texture, configure, unbind > + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); > + > + // Nearest filtering > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); > + > + // Wrap to edge to avoid edge artifacts > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); > +} > + > +/** > + * \brief Initialise the EGL context > + * \param[in] gbmContext Pointer to initialised GBM context > + * > + * Sets up the EGL display from the GBM device, creates an OpenGL ES 2.0 > + * context, and retrieves function pointers for required extensions > + * including: > + * - eglCreateImageKHR / eglDestroyImageKHR > + * - glEGLImageTargetTexture2DOES > + * > + * \return 0 on success, or -ENODEV on failure > + */ > +int eGL::initEGLContext(GBM *gbmContext) > +{ > + EGLint configAttribs[] = { > + EGL_RED_SIZE, 8, > + EGL_GREEN_SIZE, 8, > + EGL_BLUE_SIZE, 8, > + EGL_ALPHA_SIZE, 8, > + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, Note for the future: using EGL_WINDOW_BIT here instead of EGL_PBUFFER_BIT seems to be required by the GDM backend. As mentioned in my response to the "Add a GBM helper class for GPU surface access" patch IMO we should consider using the device (or surfaceless) EGL platform instead. If we do so, we'll have to use EGL_PBUFFER_BIT here. > + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, > + EGL_NONE > + }; > + > + EGLint contextAttribs[] = { > + EGL_CONTEXT_MAJOR_VERSION, 2, > + EGL_NONE > + }; > + > + EGLint numConfigs; > + EGLConfig config; > + EGLint major; > + EGLint minor; > + > + if (!eglBindAPI(EGL_OPENGL_ES_API)) { > + LOG(eGL, Error) << "API bind fail"; > + goto fail; > + } > + > + display_ = eglGetDisplay(gbmContext->getDevice()); Using "eglGetDisplay(EGL_DEFAULT_DISPLAY);" should be enough to get the surfaceless context here IIRC. > + if (display_ == EGL_NO_DISPLAY) { > + LOG(eGL, Error) << "Unable to get EGL display"; > + goto fail; > + } > + > + if (eglInitialize(display_, &major, &minor) != EGL_TRUE) { > + LOG(eGL, Error) << "eglInitialize fail"; > + goto fail; > + } > + > + LOG(eGL, Info) << "EGL: version " << major << "." << minor; > + LOG(eGL, Info) << "EGL: EGL_VERSION: " << eglQueryString(display_, EGL_VERSION); These two lines print the same - in case of semi-recent Mesa 1.5, on all drivers. I'd suggest to drop the later line, as we already get the version in `eglInitialize()`. P.S.: I think it would make sense to print the GLES context version in the future, so it's easy to check whether we're using 2.0 or e.g. 3.2. Again, that can be done in a follow-up. The following could be used as starting point: https://gitlab.freedesktop.org/rmader/libcamera/-/commit/a0f09c956bf4d0628d3b5041685f5856f196591c > + LOG(eGL, Info) << "EGL: EGL_VENDOR: " << eglQueryString(display_, EGL_VENDOR); > + LOG(eGL, Info) << "EGL: EGL_CLIENT_APIS: " << eglQueryString(display_, EGL_CLIENT_APIS); > + LOG(eGL, Info) << "EGL: EGL_EXTENSIONS: " << eglQueryString(display_, EGL_EXTENSIONS); > + > + eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); > + if (!eglCreateImageKHR) { > + LOG(eGL, Error) << "eglCreateImageKHR not found"; > + goto fail; > + } > + > + eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); > + if (!eglDestroyImageKHR) { > + LOG(eGL, Error) << "eglDestroyImageKHR not found"; > + goto fail; > + } > + > + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); > + if (!glEGLImageTargetTexture2DOES) { > + LOG(eGL, Error) << "glEGLImageTargetTexture2DOES not found"; > + goto fail; > + } > + > + if (eglChooseConfig(display_, configAttribs, &config, 1, &numConfigs) != EGL_TRUE) { > + LOG(eGL, Error) << "eglChooseConfig fail"; > + goto fail; > + } > + > + context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs); > + if (context_ == EGL_NO_CONTEXT) { > + LOG(eGL, Error) << "eglContext returned EGL_NO_CONTEXT"; > + goto fail; > + } > + > + tid_ = Thread::currentId(); > + > + makeCurrent(); > + > + return 0; > +fail: > + > + return -ENODEV; > +} > + > + > +/** > + * \brief Clean up EGL resources > + * > + * Destroys the EGL sync object. Must be called from the same thread > + * that created the EGL context. > + */ > +void eGL::cleanUp(void) {} This one can be removed now. > + > + > +/** > + * \brief Make the EGL context current for the calling thread > + * > + * Binds the EGL context to the current thread, allowing OpenGL ES > + * operations to be performed. Must be called from the thread that > + * will perform rendering operations. > + */ > +void eGL::makeCurrent(void) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + if (eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_) != EGL_TRUE) { > + LOG(eGL, Error) << "eglMakeCurrent fail"; > + } > +} > + > + > +/** > + * \brief Activate a shader program for rendering > + * \param[in] programId OpenGL program object ID > + * > + * Sets the specified program as the current rendering program. All > + * subsequent draw calls will use this program's shaders. > + */ > +void eGL::useProgram(GLuint programId) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glUseProgram(programId); > +} > + > +/** > + * \brief Delete a shader program > + * \param[in] programId OpenGL program object ID > + * > + * Deletes a shader program and frees associated resources. The program > + * must not be currently in use. > + */ > +void eGL::deleteProgram(GLuint programId) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glDeleteProgram(programId); > +} > + > +/** > + * \brief Add a preprocessor definition to shader environment > + * \param[in,out] shaderEnv Vector of shader environment strings > + * \param[in] str Preprocessor definition string (e.g., "#define APPLY_RGB_PARAMETERS") > + * > + * Appends a preprocessor definition to the shader environment vector. > + * These definitions are prepended to shader source code during compilation. > + */ > +void eGL::pushEnv(std::vector<std::string> &shaderEnv, const char *str) > +{ > + std::string addStr = str; > + > + addStr.push_back('\n'); > + shaderEnv.push_back(std::move(addStr)); > +} > + > +/** > + * \brief Compile a vertex shader > + * \param[out] shaderId OpenGL shader object ID > + * \param[in] shaderData Pointer to shader source code > + * \param[in] shaderDataLen Length of shader source in bytes > + * \param[in] shaderEnv Span of preprocessor definitions to prepend > + * > + * Compiles a vertex shader from source code with optional preprocessor > + * definitions. On compilation failure, logs the shader info log. > + * > + * \return 0 on success, or -EINVAL on compilation failure > + */ > +int eGL::compileVertexShader(GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv) > +{ > + return compileShader(GL_VERTEX_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv); > +} > + > +/** > + * \brief Compile a fragment shader > + * \param[out] shaderId OpenGL shader object ID > + * \param[in] shaderData Pointer to shader source code > + * \param[in] shaderDataLen Length of shader source in bytes > + * \param[in] shaderEnv Span of preprocessor definitions to prepend > + * > + * Compiles a fragment shader from source code with optional preprocessor > + * definitions. On compilation failure, logs the shader info log. > + * > + * \return 0 on success, or -EINVAL on compilation failure > + */ > +int eGL::compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv) > +{ > + return compileShader(GL_FRAGMENT_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv); > +} > + > +/** > + * \brief Compile a shader of specified type > + * \param[in] shaderType GL_VERTEX_SHADER or GL_FRAGMENT_SHADER > + * \param[out] shaderId OpenGL shader object ID > + * \param[in] shaderData Pointer to shader source code > + * \param[in] shaderDataLen Length of shader source in bytes > + * \param[in] shaderEnv Span of preprocessor definitions to prepend > + * > + * Internal helper function for shader compilation. Prepends environment > + * definitions to the shader source and compiles the shader. > + * > + * \return 0 on success, or -EINVAL on compilation failure > + */ > +int eGL::compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv) > +{ > + GLint success; > + size_t i; > + > + ASSERT(tid_ == Thread::currentId()); > + > + auto count = 1 + shaderEnv.size(); > + auto shaderSourceData = std::make_unique<const GLchar *[]>(count); > + auto shaderDataLengths = std::make_unique<GLint[]>(count); > + > + // Prefix defines before main body of shader > + for (i = 0; i < shaderEnv.size(); i++) { > + shaderSourceData[i] = shaderEnv[i].c_str(); > + shaderDataLengths[i] = shaderEnv[i].length(); > + } > + > + // Now the main body of the shader program > + shaderSourceData[i] = reinterpret_cast<const GLchar *>(shaderData); > + shaderDataLengths[i] = shaderDataLen; > + > + // And create the shader > + shaderId = glCreateShader(shaderType); > + glShaderSource(shaderId, count, shaderSourceData.get(), shaderDataLengths.get()); > + glCompileShader(shaderId); > + > + // Check status > + glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success); > + if (success == GL_FALSE) { > + GLint sizeLog = 0; > + > + glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog); > + auto infoLog = std::make_unique<GLchar []>(sizeLog); > + > + glGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog.get()); > + LOG(eGL, Error) << infoLog.get(); > + } > + > + return (success == GL_TRUE) ? 0 : -EINVAL; > +} > + > +/** > + * \brief Dump shader source code to the log > + * \param[in] shaderId OpenGL shader object ID > + * > + * Retrieves and logs the complete source code of a compiled shader. > + * Useful for debugging shader compilation issues. > + */ > +void eGL::dumpShaderSource(GLuint shaderId) > +{ > + GLint shaderLength = 0; > + > + ASSERT(tid_ == Thread::currentId()); > + > + glGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &shaderLength); > + > + LOG(eGL, Debug) << "Shader length is " << shaderLength; > + > + if (shaderLength > 0) { > + auto shaderSource = std::make_unique<GLchar []>(shaderLength); > + > + glGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource.get()); > + if (shaderLength) { > + LOG(eGL, Debug) << "Shader source = " << shaderSource.get(); > + } > + } > +} > + > + > +/** > + * \brief Link a shader program > + * \param[out] programId OpenGL program object ID > + * \param[in] fragmentshaderId Compiled fragment shader ID > + * \param[in] vertexshaderId Compiled vertex shader ID > + * > + * Links vertex and fragment shaders into an executable shader program. > + * On link failure, logs the program info log and deletes the program. > + * > + * \return 0 on success, or -ENODEV on link failure > + */ > +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId) > +{ > + GLint success; > + GLenum err; > + > + ASSERT(tid_ == Thread::currentId()); > + > + programId = glCreateProgram(); > + if (!programId) > + goto fail; > + > + glAttachShader(programId, vertexshaderId); > + if ((err = glGetError()) != GL_NO_ERROR) { > + LOG(eGL, Error) << "Attach compute vertex shader fail"; > + goto fail; > + } > + > + glAttachShader(programId, fragmentshaderId); > + if ((err = glGetError()) != GL_NO_ERROR) { > + LOG(eGL, Error) << "Attach compute vertex shader fail"; > + goto fail; > + } > + > + glLinkProgram(programId); > + if ((err = glGetError()) != GL_NO_ERROR) { > + LOG(eGL, Error) << "Link program fail"; > + goto fail; > + } > + > + glDetachShader(programId, fragmentshaderId); > + glDetachShader(programId, vertexshaderId); > + > + // Check status > + glGetProgramiv(programId, GL_LINK_STATUS, &success); > + if (success == GL_FALSE) { > + GLint sizeLog = 0; > + GLchar *infoLog; > + > + glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &sizeLog); > + infoLog = new GLchar[sizeLog]; > + > + glGetProgramInfoLog(programId, sizeLog, &sizeLog, infoLog); > + LOG(eGL, Error) << infoLog; > + > + delete[] infoLog; > + goto fail; > + } > + > + return 0; > +fail: > + if (programId) > + glDeleteProgram(programId); > + > + return -ENODEV; > +} > +} // namespace libcamera > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > index 685213c78..71ab39c25 100644 > --- a/src/libcamera/meson.build > +++ b/src/libcamera/meson.build > @@ -79,6 +79,27 @@ if libgbm.found() and gbm_works > ]) > endif > > +libegl = cc.find_library('EGL', required : false) > +libglesv2 = cc.find_library('GLESv2', required : false) > +mesa_works = cc.check_header('EGL/egl.h', required: false) > + > +if libegl.found() and mesa_works > + config_h.set('HAVE_LIBEGL', 1) > +endif > + > +if libglesv2.found() and mesa_works > + config_h.set('HAVE_GLESV2', 1) > +endif > + > +if mesa_works and gbm_works > + libcamera_internal_sources += files([ > + 'egl.cpp', > + ]) > + gles_headless_enabled = true > +else > + gles_headless_enabled = false > +endif > + > subdir('base') > subdir('converter') > subdir('ipa') > @@ -187,7 +208,9 @@ libcamera_deps += [ > libcamera_base_private, > libcrypto, > libdl, > + libegl, > libgbm, > + libglesv2, > liblttng, > libudev, > libyaml,
Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes: > Introduce an eGL base helper class which provides an eGL context based on a > passed width and height. > > The initGLContext function could be overloaded to provide an interface to a > real display. > > A set of helper functions is provided to compile and link GLSL shaders. > linkShaderProgram currently compiles vertex/fragment pairs but could be > overloaded or passed a parameter to link a compute shader instead. > > Breaking the eGL interface away from debayering - allows to use the eGL > context inside of a dma-buf heap cleanly, reuse that context inside of a > debayer layer and conceivably reuse the context in a multi-stage shader > pass. > > Small note the image_attrs[] array doesn't pass checkstyle.py however the > elements of the array are in pairs. > > Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > [bod: Takes fix from Hans for constructor stride bpp] > [bod: Drops eglClientWaitSync in favour of glFinish Robert/Milan] > Co-developed-by: Hans de Goede <johannes.goede@oss.qualcomm.com> > Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com> > Co-developed-by: Milan Zamazal <mzamazal@redhat.com> > Signed-off-by: Milan Zamazal <mzamazal@redhat.com> > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > include/libcamera/internal/egl.h | 186 ++++++++++ > src/libcamera/egl.cpp | 619 +++++++++++++++++++++++++++++++ > src/libcamera/meson.build | 23 ++ > 3 files changed, 828 insertions(+) > create mode 100644 include/libcamera/internal/egl.h > create mode 100644 src/libcamera/egl.cpp > > diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h > new file mode 100644 > index 000000000..542787dc5 > --- /dev/null > +++ b/include/libcamera/internal/egl.h > @@ -0,0 +1,186 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + * egl_context.cpp - Helper class for managing eGL interactions. No file name (even the correct one) at the beginning and no dot at the end. > + */ > + > +#pragma once > + > +#include <sys/types.h> > +#include <unistd.h> > + > +#include <libcamera/base/log.h> > +#include <libcamera/base/span.h> > + > +#include "libcamera/base/utils.h" I think it should be #include <libcamera/base/utils.h> and attached to the previous block. > +#include "libcamera/internal/gbm.h" > + > +#define EGL_EGLEXT_PROTOTYPES > +#include <EGL/egl.h> > +#include <EGL/eglext.h> > +#define GL_GLEXT_PROTOTYPES > +#include <GLES2/gl2.h> > +#include <GLES2/gl2ext.h> > + > +namespace libcamera { > + > +LOG_DECLARE_CATEGORY(eGL) > + > +/** > + * \class eGLImage > + * \brief Helper class for managing EGL image resources > + * > + * The eGLImage class encapsulates OpenGL ES texture and framebuffer objects > + * along with their associated EGL image. It aggregates handles, descriptors, > + * and routines for managing textures that can be associated with shader > + * uniform IDs. > + * > + * This class is particularly useful for managing DMA-BUF backed textures > + * in zero-copy rendering pipelines, where textures are bound to specific > + * texture units and can be used as both input textures and render targets. > + */ > +class eGLImage > +{ > +public: > + /** > + * \brief Construct an eGLImage with explicit stride > + * \param[in] width Image width in pixels > + * \param[in] height Image height in pixels > + * \param[in] bpp Bytes per pixel > + * \param[in] stride Row stride in bytes > + * \param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0) > + * \param[in] texture_unit_uniform_id Shader uniform ID for this texture unit > + * > + * Creates an eGLImage with the specified dimensions and stride. The stride > + * may differ from width * bpp due to alignment. > + */ > + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id) > + { > + init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id); > + } > + /** > + * \brief Construct an eGLImage with automatic stride calculation > + * \param[in] width Image width in pixels > + * \param[in] height Image height in pixels > + * \param[in] bpp Bytes per pixel > + * \param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0) > + * \param[in] texture_unit_uniform_id Shader uniform ID for this texture unit > + * > + * Creates an eGLImage with automatic stride calculation. The stride is > + * aligned to 256 bytes because 256 byte alignment is a common baseline alignment for GPUs. > + */ > + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id) > + { > + uint32_t stride = libcamera::utils::alignUp(width * bpp / 8, 256); > + > + init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id); > + } > + > + /** > + * \brief Destroy the eGLImage > + * > + * Cleans up OpenGL resources by deleting the framebuffer object and > + * texture. > + */ > + ~eGLImage() > + { > + glDeleteFramebuffers(1, &fbo_); > + glDeleteTextures(1, &texture_); > + } > + > + uint32_t width_; /**< Image width in pixels */ > + uint32_t height_; /**< Image height in pixels */ > + uint32_t stride_; /**< Row stride in bytes */ > + uint32_t offset_; /**< Buffer offset (reserved for future use) */ > + uint32_t framesize_; /**< Total frame size in bytes (stride * height) */ > + uint32_t bpp_; /**< Bytes per pixel */ > + uint32_t texture_unit_uniform_id_; /**< Shader uniform id for texture unit */ > + GLenum texture_unit_; /**< Texture unit associated with this image eg (GL_TEXTURE0) */ > + GLuint texture_; /**< OpenGL texture object ID */ > + GLuint fbo_; /**< OpenGL frame buffer object ID */ > + EGLImageKHR image_; /**< EGL Image handle */ s/\*<// Also, the autoformatter wants separating the comments just by a single space from the variable declarations. > + > +private: > + LIBCAMERA_DISABLE_COPY_AND_MOVE(eGLImage) > + > + /** > + * \brief Initialise eGLImage state > + * \param[in] width Image width in pixels > + * \param[in] height Image height in pixels > + * \param[in] bpp Bytes per pixel > + * \param[in] stride Row stride in bytes > + * \param[in] texture_unit OpenGL texture unit > + * \param[in] texture_unit_uniform_id Shader uniform ID > + * > + * Common initialisation routine called by both constructors. Sets up > + * member variables and generates OpenGL texture and framebuffer objects. > + */ > + void init(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id) > + { > + image_ = EGL_NO_IMAGE_KHR; > + width_ = width; > + height_ = height; > + bpp_ = bpp; > + stride_ = stride; > + framesize_ = stride_ * height_; > + texture_unit_ = texture_unit; > + texture_unit_uniform_id_ = texture_unit_uniform_id; > + > + glGenTextures(1, &texture_); > + glGenFramebuffers(1, &fbo_); > + } > +}; > + > +class eGL > +{ > +public: > + eGL(); > + ~eGL(); > + > + int initEGLContext(GBM *gbmContext); > + void cleanUp(); > + > + int createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd); > + int createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd); > + void destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage); > + void createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data); > + > + void pushEnv(std::vector<std::string> &shaderEnv, const char *str); > + void makeCurrent(); > + > + int compileVertexShader(GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv); > + int compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv); > + int linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId); > + void dumpShaderSource(GLuint shaderId); > + void useProgram(GLuint programId); > + void deleteProgram(GLuint programId); > + int syncOutput(); > + > +private: > + LIBCAMERA_DISABLE_COPY_AND_MOVE(eGL) > + > + pid_t tid_; > + > + EGLDisplay display_; > + EGLContext context_; > + EGLSurface surface_; > + > + int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv); > + > + int createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output); > + > + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; > + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; > + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; > +}; > +} //namespace libcamera > diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp > new file mode 100644 > index 000000000..2ef1156eb > --- /dev/null > +++ b/src/libcamera/egl.cpp > @@ -0,0 +1,619 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + * egl.cpp - Helper class for managing eGL interactions. No file name at the beginning and no dot at the end. > + */ > + > +#include "libcamera/internal/egl.h" > + > +#include <fcntl.h> > +#include <sys/ioctl.h> > +#include <sys/mman.h> > +#include <unistd.h> > + > +#include <linux/dma-buf.h> > +#include <linux/dma-heap.h> > + > +#include <libcamera/base/thread.h> > + > +#include <libdrm/drm_fourcc.h> > + > +namespace libcamera { > + > +LOG_DEFINE_CATEGORY(eGL) > + > +/** > + * \class eGL > + * \brief Helper class for managing OpenGL ES operations > + * > + * It provides: > + * > + * - EGL context setup and management > + * - Extension function pointer retrieval > + * - Shader compilation and program linking > + * - DMA-BUF texture creation and management > + * - Synchronisation primitives > + * > + * This class is designed to work with zero-copy buffers via DMA-BUF file > + * descriptors. > + */ > + > +/** > + *\var eGL::tid_ > + *\brief Thread ID of the thread associated with this EGL context > + */ > + > +/** > + *\var eGL::display_ > + *\brief EGL display handle > + */ > + > +/** > + *\var eGL::context_ > + *\brief EGL context handle > + */ > + > +/** > + *\var eGL::surface_ > + *\brief EGL sufrace handle > + */ > + > +/** > + * \brief Construct an EGL helper > + * > + * Creates an eGL instance with uninitialised context. Call initEGLContext() > + * to set up the EGL display, context, and load extension functions. > + */ > +eGL::eGL() > +{ > + context_ = EGL_NO_CONTEXT; > + surface_ = EGL_NO_SURFACE; > + display_ = EGL_NO_DISPLAY; > +} > + > +/** > + * \brief Destroy the EGL helper > + * > + * Destroys the EGL context and surface if they were successfully created. > + */ > +eGL::~eGL() > +{ > + if (context_ != EGL_NO_CONTEXT) > + eglDestroyContext(display_, context_); > + > + if (surface_ != EGL_NO_SURFACE) > + eglDestroySurface(display_, surface_); > +} > + > +/** > + * \brief Synchronise rendering output > + * > + * Sychronise here. Calls glFinish() right now. > + * > + * \return 0 on success > + */ > +int eGL::syncOutput(void) s/void// in C++. > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glFinish(); > + > + return 0; > +} > + > +/** > + * \brief Create a DMA-BUF backed 2D texture > + * \param[in,out] eglImage EGL image to associate with the DMA-BUF > + * \param[in] fd DMA-BUF file descriptor > + * \param[in] output If true, create framebuffer for render target > + * > + * Internal implementation for creating DMA-BUF textures. Creates an EGL > + * image from the DMA-BUF and binds it to a 2D texture. If output is true, > + * also creates and attaches a framebuffer object. > + * > + * \return 0 on success, or -ENODEV on failure > + */ > +int eGL::createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output) > +{ > + int ret = 0; > + > + ASSERT(tid_ == Thread::currentId()); > + > + // clang-format off > + EGLint image_attrs[] = { > + EGL_WIDTH, (EGLint)eglImage->width_, > + EGL_HEIGHT, (EGLint)eglImage->height_, > + EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888, > + EGL_DMA_BUF_PLANE0_FD_EXT, fd, > + EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, > + EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)eglImage->stride_, > + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0, > + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0, > + EGL_NONE, > + }; > + // clang-format on > + > + eglImage->image_ = eglCreateImageKHR(display_, EGL_NO_CONTEXT, > + EGL_LINUX_DMA_BUF_EXT, > + NULL, image_attrs); > + > + if (eglImage->image_ == EGL_NO_IMAGE_KHR) { > + LOG(eGL, Error) << "eglCreateImageKHR fail"; > + ret = -ENODEV; > + goto done; > + } > + > + // Bind texture unit and texture > + glActiveTexture(eglImage->texture_unit_); > + glBindTexture(GL_TEXTURE_2D, eglImage->texture_); > + > + // Generate texture with filter semantics > + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_); > + > + // Nearest filtering > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); > + > + // Wrap to edge to avoid edge artifacts > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); > + > + if (output) { > + // Generate a framebuffer from our texture direct to dma-buf handle buffer > + glBindFramebuffer(GL_FRAMEBUFFER, eglImage->fbo_); > + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eglImage->texture_, 0); > + > + GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER); > + if (err != GL_FRAMEBUFFER_COMPLETE) { > + LOG(eGL, Error) << "glFrameBufferTexture2D error " << err; > + eglDestroyImageKHR(display_, eglImage->image_); > + ret = -ENODEV; > + goto done; > + } > + } > +done: > + return ret; > +} > + > +/** > + * \brief Create an input DMA-BUF backed texture > + * \param[in,out] eglImage EGL image to associate with the DMA-BUF > + * \param[in] fd DMA-BUF file descriptor > + * > + * Creates an EGL image from a DMA-BUF file descriptor and binds it to > + * a 2D texture for use as an input texture in shaders. The texture is > + * configured with nearest filtering and clamp-to-edge wrapping. > + * > + * \return 0 on success, or -ENODEV on failure > + */ > +int eGL::createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + return createDMABufTexture2D(eglImage, fd, false); > +} > + > +/** > + * \brief Create an output DMA-BUF backed texture > + * \param[in,out] eglImage EGL image to associate with the DMA-BUF > + * \param[in] fd DMA-BUF file descriptor > + * > + * Creates an EGL image from a DMA-BUF file descriptor and binds it to > + * a 2D texture, then attaches it to a framebuffer object for use as a > + * render target. This enables zero-copy rendering directly to the > + * DMA-BUF. > + * > + * \return 0 on success, or -ENODEV on failure > + */ > +int eGL::createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + return createDMABufTexture2D(eglImage, fd, true); > +} > + > +/** > + * \brief Destroy a DMA-BUF texture's EGL image > + * \param[in,out] eglImage EGL image to destroy > + * > + * Destroys the EGL image associated with a DMA-BUF texture. The OpenGL > + * texture and framebuffer objects are destroyed separately in the > + * eGLImage destructor. > + */ > +void eGL::destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage) > +{ > + eglDestroyImage(display_, eglImage->image_); > +} > + > + There is an extra blank space here. > +/** > + * \brief Create a 2D texture from a memory buffer > + * \param[in,out] eglImage EGL image to associate with the texture > + * \param[in] format OpenGL internal format (e.g., GL_RGB, GL_RGBA) > + * \param[in] width Texture width in pixels > + * \param[in] height Texture height in pixels > + * \param[in] data Pointer to pixel data, or nullptr for uninitialised texture > + * > + * Creates a 2D texture from a CPU-accessible memory buffer. The texture > + * is configured with nearest filtering and clamp-to-edge wrapping. This > + * is useful for uploading static data like lookup tables or uniform color > + * matrices to the GPU. > + */ > +void eGL::createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glActiveTexture(eglImage->texture_unit_); > + glBindTexture(GL_TEXTURE_2D, eglImage->texture_); > + > + // Generate texture, bind, associate image to texture, configure, unbind > + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); > + > + // Nearest filtering > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); > + > + // Wrap to edge to avoid edge artifacts > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); > + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); > +} > + > +/** > + * \brief Initialise the EGL context > + * \param[in] gbmContext Pointer to initialised GBM context > + * > + * Sets up the EGL display from the GBM device, creates an OpenGL ES 2.0 > + * context, and retrieves function pointers for required extensions > + * including: > + * - eglCreateImageKHR / eglDestroyImageKHR > + * - glEGLImageTargetTexture2DOES > + * > + * \return 0 on success, or -ENODEV on failure > + */ > +int eGL::initEGLContext(GBM *gbmContext) > +{ > + EGLint configAttribs[] = { > + EGL_RED_SIZE, 8, > + EGL_GREEN_SIZE, 8, > + EGL_BLUE_SIZE, 8, > + EGL_ALPHA_SIZE, 8, > + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, > + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, > + EGL_NONE > + }; > + > + EGLint contextAttribs[] = { > + EGL_CONTEXT_MAJOR_VERSION, 2, > + EGL_NONE > + }; > + > + EGLint numConfigs; > + EGLConfig config; > + EGLint major; > + EGLint minor; > + > + if (!eglBindAPI(EGL_OPENGL_ES_API)) { > + LOG(eGL, Error) << "API bind fail"; > + goto fail; > + } > + > + display_ = eglGetDisplay(gbmContext->getDevice()); > + if (display_ == EGL_NO_DISPLAY) { > + LOG(eGL, Error) << "Unable to get EGL display"; > + goto fail; > + } > + > + if (eglInitialize(display_, &major, &minor) != EGL_TRUE) { > + LOG(eGL, Error) << "eglInitialize fail"; > + goto fail; > + } > + > + LOG(eGL, Info) << "EGL: version " << major << "." << minor; > + LOG(eGL, Info) << "EGL: EGL_VERSION: " << eglQueryString(display_, EGL_VERSION); > + LOG(eGL, Info) << "EGL: EGL_VENDOR: " << eglQueryString(display_, EGL_VENDOR); > + LOG(eGL, Info) << "EGL: EGL_CLIENT_APIS: " << eglQueryString(display_, EGL_CLIENT_APIS); > + LOG(eGL, Info) << "EGL: EGL_EXTENSIONS: " << eglQueryString(display_, EGL_EXTENSIONS); > + > + eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); > + if (!eglCreateImageKHR) { > + LOG(eGL, Error) << "eglCreateImageKHR not found"; > + goto fail; > + } > + > + eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); > + if (!eglDestroyImageKHR) { > + LOG(eGL, Error) << "eglDestroyImageKHR not found"; > + goto fail; > + } > + > + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); > + if (!glEGLImageTargetTexture2DOES) { > + LOG(eGL, Error) << "glEGLImageTargetTexture2DOES not found"; > + goto fail; > + } > + > + if (eglChooseConfig(display_, configAttribs, &config, 1, &numConfigs) != EGL_TRUE) { > + LOG(eGL, Error) << "eglChooseConfig fail"; > + goto fail; > + } > + > + context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs); > + if (context_ == EGL_NO_CONTEXT) { > + LOG(eGL, Error) << "eglContext returned EGL_NO_CONTEXT"; > + goto fail; > + } > + > + tid_ = Thread::currentId(); > + > + makeCurrent(); > + > + return 0; > +fail: > + > + return -ENODEV; > +} > + > + There is an extra blank space here. > +/** > + * \brief Clean up EGL resources > + * > + * Destroys the EGL sync object. Must be called from the same thread > + * that created the EGL context. > + */ > +void eGL::cleanUp(void) {} No `void' in the argument list. > + > + > +/** > + * \brief Make the EGL context current for the calling thread > + * > + * Binds the EGL context to the current thread, allowing OpenGL ES > + * operations to be performed. Must be called from the thread that > + * will perform rendering operations. > + */ > +void eGL::makeCurrent(void) No `void' in the argument list. > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + if (eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_) != EGL_TRUE) { > + LOG(eGL, Error) << "eglMakeCurrent fail"; > + } > +} > + > + There is an extra blank space here. > +/** > + * \brief Activate a shader program for rendering > + * \param[in] programId OpenGL program object ID > + * > + * Sets the specified program as the current rendering program. All > + * subsequent draw calls will use this program's shaders. > + */ > +void eGL::useProgram(GLuint programId) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glUseProgram(programId); > +} > + > +/** > + * \brief Delete a shader program > + * \param[in] programId OpenGL program object ID > + * > + * Deletes a shader program and frees associated resources. The program > + * must not be currently in use. > + */ > +void eGL::deleteProgram(GLuint programId) > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glDeleteProgram(programId); > +} > + > +/** > + * \brief Add a preprocessor definition to shader environment > + * \param[in,out] shaderEnv Vector of shader environment strings > + * \param[in] str Preprocessor definition string (e.g., "#define APPLY_RGB_PARAMETERS") > + * > + * Appends a preprocessor definition to the shader environment vector. > + * These definitions are prepended to shader source code during compilation. > + */ > +void eGL::pushEnv(std::vector<std::string> &shaderEnv, const char *str) > +{ > + std::string addStr = str; > + > + addStr.push_back('\n'); > + shaderEnv.push_back(std::move(addStr)); > +} > + > +/** > + * \brief Compile a vertex shader > + * \param[out] shaderId OpenGL shader object ID > + * \param[in] shaderData Pointer to shader source code > + * \param[in] shaderDataLen Length of shader source in bytes > + * \param[in] shaderEnv Span of preprocessor definitions to prepend > + * > + * Compiles a vertex shader from source code with optional preprocessor > + * definitions. On compilation failure, logs the shader info log. > + * > + * \return 0 on success, or -EINVAL on compilation failure > + */ > +int eGL::compileVertexShader(GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv) > +{ > + return compileShader(GL_VERTEX_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv); > +} > + > +/** > + * \brief Compile a fragment shader > + * \param[out] shaderId OpenGL shader object ID > + * \param[in] shaderData Pointer to shader source code > + * \param[in] shaderDataLen Length of shader source in bytes > + * \param[in] shaderEnv Span of preprocessor definitions to prepend > + * > + * Compiles a fragment shader from source code with optional preprocessor > + * definitions. On compilation failure, logs the shader info log. > + * > + * \return 0 on success, or -EINVAL on compilation failure > + */ > +int eGL::compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv) > +{ > + return compileShader(GL_FRAGMENT_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv); > +} > + > +/** > + * \brief Compile a shader of specified type > + * \param[in] shaderType GL_VERTEX_SHADER or GL_FRAGMENT_SHADER > + * \param[out] shaderId OpenGL shader object ID > + * \param[in] shaderData Pointer to shader source code > + * \param[in] shaderDataLen Length of shader source in bytes > + * \param[in] shaderEnv Span of preprocessor definitions to prepend > + * > + * Internal helper function for shader compilation. Prepends environment > + * definitions to the shader source and compiles the shader. > + * > + * \return 0 on success, or -EINVAL on compilation failure > + */ > +int eGL::compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData, > + unsigned int shaderDataLen, > + Span<const std::string> shaderEnv) > +{ > + GLint success; > + size_t i; > + > + ASSERT(tid_ == Thread::currentId()); > + > + auto count = 1 + shaderEnv.size(); > + auto shaderSourceData = std::make_unique<const GLchar *[]>(count); > + auto shaderDataLengths = std::make_unique<GLint[]>(count); > + > + // Prefix defines before main body of shader > + for (i = 0; i < shaderEnv.size(); i++) { > + shaderSourceData[i] = shaderEnv[i].c_str(); > + shaderDataLengths[i] = shaderEnv[i].length(); > + } > + > + // Now the main body of the shader program > + shaderSourceData[i] = reinterpret_cast<const GLchar *>(shaderData); > + shaderDataLengths[i] = shaderDataLen; > + > + // And create the shader > + shaderId = glCreateShader(shaderType); > + glShaderSource(shaderId, count, shaderSourceData.get(), shaderDataLengths.get()); > + glCompileShader(shaderId); > + > + // Check status > + glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success); > + if (success == GL_FALSE) { > + GLint sizeLog = 0; > + > + glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog); > + auto infoLog = std::make_unique<GLchar []>(sizeLog); There should be no space before []. > + > + glGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog.get()); > + LOG(eGL, Error) << infoLog.get(); > + } > + > + return (success == GL_TRUE) ? 0 : -EINVAL; > +} > + > +/** > + * \brief Dump shader source code to the log > + * \param[in] shaderId OpenGL shader object ID > + * > + * Retrieves and logs the complete source code of a compiled shader. > + * Useful for debugging shader compilation issues. > + */ > +void eGL::dumpShaderSource(GLuint shaderId) > +{ > + GLint shaderLength = 0; > + > + ASSERT(tid_ == Thread::currentId()); > + > + glGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &shaderLength); > + > + LOG(eGL, Debug) << "Shader length is " << shaderLength; > + > + if (shaderLength > 0) { > + auto shaderSource = std::make_unique<GLchar []>(shaderLength); There should be no space before []. > + > + glGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource.get()); > + if (shaderLength) { > + LOG(eGL, Debug) << "Shader source = " << shaderSource.get(); > + } > + } > +} > + > + There is an extra blank space here. > +/** > + * \brief Link a shader program > + * \param[out] programId OpenGL program object ID > + * \param[in] fragmentshaderId Compiled fragment shader ID > + * \param[in] vertexshaderId Compiled vertex shader ID > + * > + * Links vertex and fragment shaders into an executable shader program. > + * On link failure, logs the program info log and deletes the program. > + * > + * \return 0 on success, or -ENODEV on link failure > + */ > +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId) > +{ > + GLint success; > + GLenum err; > + > + ASSERT(tid_ == Thread::currentId()); > + > + programId = glCreateProgram(); > + if (!programId) > + goto fail; > + > + glAttachShader(programId, vertexshaderId); > + if ((err = glGetError()) != GL_NO_ERROR) { > + LOG(eGL, Error) << "Attach compute vertex shader fail"; > + goto fail; > + } > + > + glAttachShader(programId, fragmentshaderId); > + if ((err = glGetError()) != GL_NO_ERROR) { > + LOG(eGL, Error) << "Attach compute vertex shader fail"; s/vertex/fragment/ > + goto fail; > + } > + > + glLinkProgram(programId); > + if ((err = glGetError()) != GL_NO_ERROR) { > + LOG(eGL, Error) << "Link program fail"; > + goto fail; > + } > + > + glDetachShader(programId, fragmentshaderId); > + glDetachShader(programId, vertexshaderId); > + > + // Check status > + glGetProgramiv(programId, GL_LINK_STATUS, &success); > + if (success == GL_FALSE) { > + GLint sizeLog = 0; > + GLchar *infoLog; > + > + glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &sizeLog); > + infoLog = new GLchar[sizeLog]; > + > + glGetProgramInfoLog(programId, sizeLog, &sizeLog, infoLog); > + LOG(eGL, Error) << infoLog; > + > + delete[] infoLog; > + goto fail; > + } > + > + return 0; > +fail: > + if (programId) > + glDeleteProgram(programId); > + > + return -ENODEV; > +} > +} // namespace libcamera /* namespace libcamera */ > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > index 685213c78..71ab39c25 100644 > --- a/src/libcamera/meson.build > +++ b/src/libcamera/meson.build > @@ -79,6 +79,27 @@ if libgbm.found() and gbm_works > ]) > endif > > +libegl = cc.find_library('EGL', required : false) > +libglesv2 = cc.find_library('GLESv2', required : false) > +mesa_works = cc.check_header('EGL/egl.h', required: false) > + > +if libegl.found() and mesa_works > + config_h.set('HAVE_LIBEGL', 1) > +endif > + > +if libglesv2.found() and mesa_works > + config_h.set('HAVE_GLESV2', 1) > +endif > + > +if mesa_works and gbm_works > + libcamera_internal_sources += files([ > + 'egl.cpp', > + ]) > + gles_headless_enabled = true > +else > + gles_headless_enabled = false > +endif > + > subdir('base') > subdir('converter') > subdir('ipa') > @@ -187,7 +208,9 @@ libcamera_deps += [ > libcamera_base_private, > libcrypto, > libdl, > + libegl, > libgbm, > + libglesv2, > liblttng, > libudev, > libyaml,
On 15/12/2025 15:01, Milan Zamazal wrote: >> + EGLImageKHR image_; /**< EGL Image handle */ > s/\*<// I think this comment is a typo. You mean remove the tabs because the formatter wants just spaces - ick. So that's what I did. --- bod
diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h new file mode 100644 index 000000000..542787dc5 --- /dev/null +++ b/include/libcamera/internal/egl.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Linaro Ltd. + * + * Authors: + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> + * + * egl_context.cpp - Helper class for managing eGL interactions. + */ + +#pragma once + +#include <sys/types.h> +#include <unistd.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/span.h> + +#include "libcamera/base/utils.h" +#include "libcamera/internal/gbm.h" + +#define EGL_EGLEXT_PROTOTYPES +#include <EGL/egl.h> +#include <EGL/eglext.h> +#define GL_GLEXT_PROTOTYPES +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +namespace libcamera { + +LOG_DECLARE_CATEGORY(eGL) + +/** + * \class eGLImage + * \brief Helper class for managing EGL image resources + * + * The eGLImage class encapsulates OpenGL ES texture and framebuffer objects + * along with their associated EGL image. It aggregates handles, descriptors, + * and routines for managing textures that can be associated with shader + * uniform IDs. + * + * This class is particularly useful for managing DMA-BUF backed textures + * in zero-copy rendering pipelines, where textures are bound to specific + * texture units and can be used as both input textures and render targets. + */ +class eGLImage +{ +public: + /** + * \brief Construct an eGLImage with explicit stride + * \param[in] width Image width in pixels + * \param[in] height Image height in pixels + * \param[in] bpp Bytes per pixel + * \param[in] stride Row stride in bytes + * \param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0) + * \param[in] texture_unit_uniform_id Shader uniform ID for this texture unit + * + * Creates an eGLImage with the specified dimensions and stride. The stride + * may differ from width * bpp due to alignment. + */ + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id) + { + init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id); + } + /** + * \brief Construct an eGLImage with automatic stride calculation + * \param[in] width Image width in pixels + * \param[in] height Image height in pixels + * \param[in] bpp Bytes per pixel + * \param[in] texture_unit OpenGL texture unit (e.g., GL_TEXTURE0) + * \param[in] texture_unit_uniform_id Shader uniform ID for this texture unit + * + * Creates an eGLImage with automatic stride calculation. The stride is + * aligned to 256 bytes because 256 byte alignment is a common baseline alignment for GPUs. + */ + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id) + { + uint32_t stride = libcamera::utils::alignUp(width * bpp / 8, 256); + + init(width, height, bpp, stride, texture_unit, texture_unit_uniform_id); + } + + /** + * \brief Destroy the eGLImage + * + * Cleans up OpenGL resources by deleting the framebuffer object and + * texture. + */ + ~eGLImage() + { + glDeleteFramebuffers(1, &fbo_); + glDeleteTextures(1, &texture_); + } + + uint32_t width_; /**< Image width in pixels */ + uint32_t height_; /**< Image height in pixels */ + uint32_t stride_; /**< Row stride in bytes */ + uint32_t offset_; /**< Buffer offset (reserved for future use) */ + uint32_t framesize_; /**< Total frame size in bytes (stride * height) */ + uint32_t bpp_; /**< Bytes per pixel */ + uint32_t texture_unit_uniform_id_; /**< Shader uniform id for texture unit */ + GLenum texture_unit_; /**< Texture unit associated with this image eg (GL_TEXTURE0) */ + GLuint texture_; /**< OpenGL texture object ID */ + GLuint fbo_; /**< OpenGL frame buffer object ID */ + EGLImageKHR image_; /**< EGL Image handle */ + +private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(eGLImage) + + /** + * \brief Initialise eGLImage state + * \param[in] width Image width in pixels + * \param[in] height Image height in pixels + * \param[in] bpp Bytes per pixel + * \param[in] stride Row stride in bytes + * \param[in] texture_unit OpenGL texture unit + * \param[in] texture_unit_uniform_id Shader uniform ID + * + * Common initialisation routine called by both constructors. Sets up + * member variables and generates OpenGL texture and framebuffer objects. + */ + void init(uint32_t width, uint32_t height, uint32_t bpp, uint32_t stride, GLenum texture_unit, uint32_t texture_unit_uniform_id) + { + image_ = EGL_NO_IMAGE_KHR; + width_ = width; + height_ = height; + bpp_ = bpp; + stride_ = stride; + framesize_ = stride_ * height_; + texture_unit_ = texture_unit; + texture_unit_uniform_id_ = texture_unit_uniform_id; + + glGenTextures(1, &texture_); + glGenFramebuffers(1, &fbo_); + } +}; + +class eGL +{ +public: + eGL(); + ~eGL(); + + int initEGLContext(GBM *gbmContext); + void cleanUp(); + + int createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd); + int createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd); + void destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage); + void createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data); + + void pushEnv(std::vector<std::string> &shaderEnv, const char *str); + void makeCurrent(); + + int compileVertexShader(GLuint &shaderId, const unsigned char *shaderData, + unsigned int shaderDataLen, + Span<const std::string> shaderEnv); + int compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData, + unsigned int shaderDataLen, + Span<const std::string> shaderEnv); + int linkProgram(GLuint &programId, GLuint fragmentshaderId, GLuint vertexshaderId); + void dumpShaderSource(GLuint shaderId); + void useProgram(GLuint programId); + void deleteProgram(GLuint programId); + int syncOutput(); + +private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(eGL) + + pid_t tid_; + + EGLDisplay display_; + EGLContext context_; + EGLSurface surface_; + + int compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData, + unsigned int shaderDataLen, + Span<const std::string> shaderEnv); + + int createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output); + + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; +}; +} //namespace libcamera diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp new file mode 100644 index 000000000..2ef1156eb --- /dev/null +++ b/src/libcamera/egl.cpp @@ -0,0 +1,619 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Linaro Ltd. + * + * Authors: + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> + * + * egl.cpp - Helper class for managing eGL interactions. + */ + +#include "libcamera/internal/egl.h" + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <linux/dma-buf.h> +#include <linux/dma-heap.h> + +#include <libcamera/base/thread.h> + +#include <libdrm/drm_fourcc.h> + +namespace libcamera { + +LOG_DEFINE_CATEGORY(eGL) + +/** + * \class eGL + * \brief Helper class for managing OpenGL ES operations + * + * It provides: + * + * - EGL context setup and management + * - Extension function pointer retrieval + * - Shader compilation and program linking + * - DMA-BUF texture creation and management + * - Synchronisation primitives + * + * This class is designed to work with zero-copy buffers via DMA-BUF file + * descriptors. + */ + +/** + *\var eGL::tid_ + *\brief Thread ID of the thread associated with this EGL context + */ + +/** + *\var eGL::display_ + *\brief EGL display handle + */ + +/** + *\var eGL::context_ + *\brief EGL context handle + */ + +/** + *\var eGL::surface_ + *\brief EGL sufrace handle + */ + +/** + * \brief Construct an EGL helper + * + * Creates an eGL instance with uninitialised context. Call initEGLContext() + * to set up the EGL display, context, and load extension functions. + */ +eGL::eGL() +{ + context_ = EGL_NO_CONTEXT; + surface_ = EGL_NO_SURFACE; + display_ = EGL_NO_DISPLAY; +} + +/** + * \brief Destroy the EGL helper + * + * Destroys the EGL context and surface if they were successfully created. + */ +eGL::~eGL() +{ + if (context_ != EGL_NO_CONTEXT) + eglDestroyContext(display_, context_); + + if (surface_ != EGL_NO_SURFACE) + eglDestroySurface(display_, surface_); +} + +/** + * \brief Synchronise rendering output + * + * Sychronise here. Calls glFinish() right now. + * + * \return 0 on success + */ +int eGL::syncOutput(void) +{ + ASSERT(tid_ == Thread::currentId()); + + glFinish(); + + return 0; +} + +/** + * \brief Create a DMA-BUF backed 2D texture + * \param[in,out] eglImage EGL image to associate with the DMA-BUF + * \param[in] fd DMA-BUF file descriptor + * \param[in] output If true, create framebuffer for render target + * + * Internal implementation for creating DMA-BUF textures. Creates an EGL + * image from the DMA-BUF and binds it to a 2D texture. If output is true, + * also creates and attaches a framebuffer object. + * + * \return 0 on success, or -ENODEV on failure + */ +int eGL::createDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd, bool output) +{ + int ret = 0; + + ASSERT(tid_ == Thread::currentId()); + + // clang-format off + EGLint image_attrs[] = { + EGL_WIDTH, (EGLint)eglImage->width_, + EGL_HEIGHT, (EGLint)eglImage->height_, + EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888, + EGL_DMA_BUF_PLANE0_FD_EXT, fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, + EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)eglImage->stride_, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0, + EGL_NONE, + }; + // clang-format on + + eglImage->image_ = eglCreateImageKHR(display_, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + NULL, image_attrs); + + if (eglImage->image_ == EGL_NO_IMAGE_KHR) { + LOG(eGL, Error) << "eglCreateImageKHR fail"; + ret = -ENODEV; + goto done; + } + + // Bind texture unit and texture + glActiveTexture(eglImage->texture_unit_); + glBindTexture(GL_TEXTURE_2D, eglImage->texture_); + + // Generate texture with filter semantics + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_); + + // Nearest filtering + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Wrap to edge to avoid edge artifacts + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (output) { + // Generate a framebuffer from our texture direct to dma-buf handle buffer + glBindFramebuffer(GL_FRAMEBUFFER, eglImage->fbo_); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eglImage->texture_, 0); + + GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (err != GL_FRAMEBUFFER_COMPLETE) { + LOG(eGL, Error) << "glFrameBufferTexture2D error " << err; + eglDestroyImageKHR(display_, eglImage->image_); + ret = -ENODEV; + goto done; + } + } +done: + return ret; +} + +/** + * \brief Create an input DMA-BUF backed texture + * \param[in,out] eglImage EGL image to associate with the DMA-BUF + * \param[in] fd DMA-BUF file descriptor + * + * Creates an EGL image from a DMA-BUF file descriptor and binds it to + * a 2D texture for use as an input texture in shaders. The texture is + * configured with nearest filtering and clamp-to-edge wrapping. + * + * \return 0 on success, or -ENODEV on failure + */ +int eGL::createInputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd) +{ + ASSERT(tid_ == Thread::currentId()); + + return createDMABufTexture2D(eglImage, fd, false); +} + +/** + * \brief Create an output DMA-BUF backed texture + * \param[in,out] eglImage EGL image to associate with the DMA-BUF + * \param[in] fd DMA-BUF file descriptor + * + * Creates an EGL image from a DMA-BUF file descriptor and binds it to + * a 2D texture, then attaches it to a framebuffer object for use as a + * render target. This enables zero-copy rendering directly to the + * DMA-BUF. + * + * \return 0 on success, or -ENODEV on failure + */ +int eGL::createOutputDMABufTexture2D(std::unique_ptr<eGLImage> &eglImage, int fd) +{ + ASSERT(tid_ == Thread::currentId()); + + return createDMABufTexture2D(eglImage, fd, true); +} + +/** + * \brief Destroy a DMA-BUF texture's EGL image + * \param[in,out] eglImage EGL image to destroy + * + * Destroys the EGL image associated with a DMA-BUF texture. The OpenGL + * texture and framebuffer objects are destroyed separately in the + * eGLImage destructor. + */ +void eGL::destroyDMABufTexture(std::unique_ptr<eGLImage> &eglImage) +{ + eglDestroyImage(display_, eglImage->image_); +} + + +/** + * \brief Create a 2D texture from a memory buffer + * \param[in,out] eglImage EGL image to associate with the texture + * \param[in] format OpenGL internal format (e.g., GL_RGB, GL_RGBA) + * \param[in] width Texture width in pixels + * \param[in] height Texture height in pixels + * \param[in] data Pointer to pixel data, or nullptr for uninitialised texture + * + * Creates a 2D texture from a CPU-accessible memory buffer. The texture + * is configured with nearest filtering and clamp-to-edge wrapping. This + * is useful for uploading static data like lookup tables or uniform color + * matrices to the GPU. + */ +void eGL::createTexture2D(std::unique_ptr<eGLImage> &eglImage, GLint format, uint32_t width, uint32_t height, void *data) +{ + ASSERT(tid_ == Thread::currentId()); + + glActiveTexture(eglImage->texture_unit_); + glBindTexture(GL_TEXTURE_2D, eglImage->texture_); + + // Generate texture, bind, associate image to texture, configure, unbind + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + + // Nearest filtering + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Wrap to edge to avoid edge artifacts + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +/** + * \brief Initialise the EGL context + * \param[in] gbmContext Pointer to initialised GBM context + * + * Sets up the EGL display from the GBM device, creates an OpenGL ES 2.0 + * context, and retrieves function pointers for required extensions + * including: + * - eglCreateImageKHR / eglDestroyImageKHR + * - glEGLImageTargetTexture2DOES + * + * \return 0 on success, or -ENODEV on failure + */ +int eGL::initEGLContext(GBM *gbmContext) +{ + EGLint configAttribs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLint contextAttribs[] = { + EGL_CONTEXT_MAJOR_VERSION, 2, + EGL_NONE + }; + + EGLint numConfigs; + EGLConfig config; + EGLint major; + EGLint minor; + + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + LOG(eGL, Error) << "API bind fail"; + goto fail; + } + + display_ = eglGetDisplay(gbmContext->getDevice()); + if (display_ == EGL_NO_DISPLAY) { + LOG(eGL, Error) << "Unable to get EGL display"; + goto fail; + } + + if (eglInitialize(display_, &major, &minor) != EGL_TRUE) { + LOG(eGL, Error) << "eglInitialize fail"; + goto fail; + } + + LOG(eGL, Info) << "EGL: version " << major << "." << minor; + LOG(eGL, Info) << "EGL: EGL_VERSION: " << eglQueryString(display_, EGL_VERSION); + LOG(eGL, Info) << "EGL: EGL_VENDOR: " << eglQueryString(display_, EGL_VENDOR); + LOG(eGL, Info) << "EGL: EGL_CLIENT_APIS: " << eglQueryString(display_, EGL_CLIENT_APIS); + LOG(eGL, Info) << "EGL: EGL_EXTENSIONS: " << eglQueryString(display_, EGL_EXTENSIONS); + + eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); + if (!eglCreateImageKHR) { + LOG(eGL, Error) << "eglCreateImageKHR not found"; + goto fail; + } + + eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); + if (!eglDestroyImageKHR) { + LOG(eGL, Error) << "eglDestroyImageKHR not found"; + goto fail; + } + + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + if (!glEGLImageTargetTexture2DOES) { + LOG(eGL, Error) << "glEGLImageTargetTexture2DOES not found"; + goto fail; + } + + if (eglChooseConfig(display_, configAttribs, &config, 1, &numConfigs) != EGL_TRUE) { + LOG(eGL, Error) << "eglChooseConfig fail"; + goto fail; + } + + context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs); + if (context_ == EGL_NO_CONTEXT) { + LOG(eGL, Error) << "eglContext returned EGL_NO_CONTEXT"; + goto fail; + } + + tid_ = Thread::currentId(); + + makeCurrent(); + + return 0; +fail: + + return -ENODEV; +} + + +/** + * \brief Clean up EGL resources + * + * Destroys the EGL sync object. Must be called from the same thread + * that created the EGL context. + */ +void eGL::cleanUp(void) {} + + +/** + * \brief Make the EGL context current for the calling thread + * + * Binds the EGL context to the current thread, allowing OpenGL ES + * operations to be performed. Must be called from the thread that + * will perform rendering operations. + */ +void eGL::makeCurrent(void) +{ + ASSERT(tid_ == Thread::currentId()); + + if (eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_) != EGL_TRUE) { + LOG(eGL, Error) << "eglMakeCurrent fail"; + } +} + + +/** + * \brief Activate a shader program for rendering + * \param[in] programId OpenGL program object ID + * + * Sets the specified program as the current rendering program. All + * subsequent draw calls will use this program's shaders. + */ +void eGL::useProgram(GLuint programId) +{ + ASSERT(tid_ == Thread::currentId()); + + glUseProgram(programId); +} + +/** + * \brief Delete a shader program + * \param[in] programId OpenGL program object ID + * + * Deletes a shader program and frees associated resources. The program + * must not be currently in use. + */ +void eGL::deleteProgram(GLuint programId) +{ + ASSERT(tid_ == Thread::currentId()); + + glDeleteProgram(programId); +} + +/** + * \brief Add a preprocessor definition to shader environment + * \param[in,out] shaderEnv Vector of shader environment strings + * \param[in] str Preprocessor definition string (e.g., "#define APPLY_RGB_PARAMETERS") + * + * Appends a preprocessor definition to the shader environment vector. + * These definitions are prepended to shader source code during compilation. + */ +void eGL::pushEnv(std::vector<std::string> &shaderEnv, const char *str) +{ + std::string addStr = str; + + addStr.push_back('\n'); + shaderEnv.push_back(std::move(addStr)); +} + +/** + * \brief Compile a vertex shader + * \param[out] shaderId OpenGL shader object ID + * \param[in] shaderData Pointer to shader source code + * \param[in] shaderDataLen Length of shader source in bytes + * \param[in] shaderEnv Span of preprocessor definitions to prepend + * + * Compiles a vertex shader from source code with optional preprocessor + * definitions. On compilation failure, logs the shader info log. + * + * \return 0 on success, or -EINVAL on compilation failure + */ +int eGL::compileVertexShader(GLuint &shaderId, const unsigned char *shaderData, + unsigned int shaderDataLen, + Span<const std::string> shaderEnv) +{ + return compileShader(GL_VERTEX_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv); +} + +/** + * \brief Compile a fragment shader + * \param[out] shaderId OpenGL shader object ID + * \param[in] shaderData Pointer to shader source code + * \param[in] shaderDataLen Length of shader source in bytes + * \param[in] shaderEnv Span of preprocessor definitions to prepend + * + * Compiles a fragment shader from source code with optional preprocessor + * definitions. On compilation failure, logs the shader info log. + * + * \return 0 on success, or -EINVAL on compilation failure + */ +int eGL::compileFragmentShader(GLuint &shaderId, const unsigned char *shaderData, + unsigned int shaderDataLen, + Span<const std::string> shaderEnv) +{ + return compileShader(GL_FRAGMENT_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv); +} + +/** + * \brief Compile a shader of specified type + * \param[in] shaderType GL_VERTEX_SHADER or GL_FRAGMENT_SHADER + * \param[out] shaderId OpenGL shader object ID + * \param[in] shaderData Pointer to shader source code + * \param[in] shaderDataLen Length of shader source in bytes + * \param[in] shaderEnv Span of preprocessor definitions to prepend + * + * Internal helper function for shader compilation. Prepends environment + * definitions to the shader source and compiles the shader. + * + * \return 0 on success, or -EINVAL on compilation failure + */ +int eGL::compileShader(int shaderType, GLuint &shaderId, const unsigned char *shaderData, + unsigned int shaderDataLen, + Span<const std::string> shaderEnv) +{ + GLint success; + size_t i; + + ASSERT(tid_ == Thread::currentId()); + + auto count = 1 + shaderEnv.size(); + auto shaderSourceData = std::make_unique<const GLchar *[]>(count); + auto shaderDataLengths = std::make_unique<GLint[]>(count); + + // Prefix defines before main body of shader + for (i = 0; i < shaderEnv.size(); i++) { + shaderSourceData[i] = shaderEnv[i].c_str(); + shaderDataLengths[i] = shaderEnv[i].length(); + } + + // Now the main body of the shader program + shaderSourceData[i] = reinterpret_cast<const GLchar *>(shaderData); + shaderDataLengths[i] = shaderDataLen; + + // And create the shader + shaderId = glCreateShader(shaderType); + glShaderSource(shaderId, count, shaderSourceData.get(), shaderDataLengths.get()); + glCompileShader(shaderId); + + // Check status + glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success); + if (success == GL_FALSE) { + GLint sizeLog = 0; + + glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog); + auto infoLog = std::make_unique<GLchar []>(sizeLog); + + glGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog.get()); + LOG(eGL, Error) << infoLog.get(); + } + + return (success == GL_TRUE) ? 0 : -EINVAL; +} + +/** + * \brief Dump shader source code to the log + * \param[in] shaderId OpenGL shader object ID + * + * Retrieves and logs the complete source code of a compiled shader. + * Useful for debugging shader compilation issues. + */ +void eGL::dumpShaderSource(GLuint shaderId) +{ + GLint shaderLength = 0; + + ASSERT(tid_ == Thread::currentId()); + + glGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &shaderLength); + + LOG(eGL, Debug) << "Shader length is " << shaderLength; + + if (shaderLength > 0) { + auto shaderSource = std::make_unique<GLchar []>(shaderLength); + + glGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource.get()); + if (shaderLength) { + LOG(eGL, Debug) << "Shader source = " << shaderSource.get(); + } + } +} + + +/** + * \brief Link a shader program + * \param[out] programId OpenGL program object ID + * \param[in] fragmentshaderId Compiled fragment shader ID + * \param[in] vertexshaderId Compiled vertex shader ID + * + * Links vertex and fragment shaders into an executable shader program. + * On link failure, logs the program info log and deletes the program. + * + * \return 0 on success, or -ENODEV on link failure + */ +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId) +{ + GLint success; + GLenum err; + + ASSERT(tid_ == Thread::currentId()); + + programId = glCreateProgram(); + if (!programId) + goto fail; + + glAttachShader(programId, vertexshaderId); + if ((err = glGetError()) != GL_NO_ERROR) { + LOG(eGL, Error) << "Attach compute vertex shader fail"; + goto fail; + } + + glAttachShader(programId, fragmentshaderId); + if ((err = glGetError()) != GL_NO_ERROR) { + LOG(eGL, Error) << "Attach compute vertex shader fail"; + goto fail; + } + + glLinkProgram(programId); + if ((err = glGetError()) != GL_NO_ERROR) { + LOG(eGL, Error) << "Link program fail"; + goto fail; + } + + glDetachShader(programId, fragmentshaderId); + glDetachShader(programId, vertexshaderId); + + // Check status + glGetProgramiv(programId, GL_LINK_STATUS, &success); + if (success == GL_FALSE) { + GLint sizeLog = 0; + GLchar *infoLog; + + glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &sizeLog); + infoLog = new GLchar[sizeLog]; + + glGetProgramInfoLog(programId, sizeLog, &sizeLog, infoLog); + LOG(eGL, Error) << infoLog; + + delete[] infoLog; + goto fail; + } + + return 0; +fail: + if (programId) + glDeleteProgram(programId); + + return -ENODEV; +} +} // namespace libcamera diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 685213c78..71ab39c25 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -79,6 +79,27 @@ if libgbm.found() and gbm_works ]) endif +libegl = cc.find_library('EGL', required : false) +libglesv2 = cc.find_library('GLESv2', required : false) +mesa_works = cc.check_header('EGL/egl.h', required: false) + +if libegl.found() and mesa_works + config_h.set('HAVE_LIBEGL', 1) +endif + +if libglesv2.found() and mesa_works + config_h.set('HAVE_GLESV2', 1) +endif + +if mesa_works and gbm_works + libcamera_internal_sources += files([ + 'egl.cpp', + ]) + gles_headless_enabled = true +else + gles_headless_enabled = false +endif + subdir('base') subdir('converter') subdir('ipa') @@ -187,7 +208,9 @@ libcamera_deps += [ libcamera_base_private, libcrypto, libdl, + libegl, libgbm, + libglesv2, liblttng, libudev, libyaml,