| Message ID | 20251217100138.82525-3-bryan.odonoghue@linaro.org |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
There are some possible clean-up left, but we can do that in a follow-up. Reviewed-by: Robert Mader <robert.mader@collabora.com> On 17.12.25 11:01, 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 | 185 ++++++++++ > src/libcamera/egl.cpp | 612 +++++++++++++++++++++++++++++++ > src/libcamera/meson.build | 23 ++ > 3 files changed, 820 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..1ed93f3dc > --- /dev/null > +++ b/include/libcamera/internal/egl.h > @@ -0,0 +1,185 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + */ > + > +#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); > + void 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..c6fde5e42 > --- /dev/null > +++ b/src/libcamera/egl.cpp > @@ -0,0 +1,612 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + */ > + > +#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. > + * > + */ > +void eGL::syncOutput() > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glFinish(); > +} > + > +/** > + * \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() > +{ > +} > + > +/** > + * \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() > +{ > + 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 fragment 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 e5b5330a8..60f036da4 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,
Hi Bryan, Quoting Bryan O'Donoghue (2025-12-17 10:01:14) > 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 | 185 ++++++++++ The linter spotted something here: -------------------------------------------------------------------------------------------------- e178c47f2d66b92005dcd2b44cb7e8e12bbbdb34 libcamera: software_isp: egl: Add a eGL base helper class -------------------------------------------------------------------------------------------------- Header include/libcamera/internal/egl.h added without corresponding update to include/libcamera/internal/meson.build Please make sure you add egl.h to include/libcamera/internal/meson.build. That's important as it ties in to the meson dependencies. With that, Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com> -- Kieran > src/libcamera/egl.cpp | 612 +++++++++++++++++++++++++++++++ > src/libcamera/meson.build | 23 ++ > 3 files changed, 820 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..1ed93f3dc > --- /dev/null > +++ b/include/libcamera/internal/egl.h > @@ -0,0 +1,185 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + */ > + > +#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); > + void 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..c6fde5e42 > --- /dev/null > +++ b/src/libcamera/egl.cpp > @@ -0,0 +1,612 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + */ > + > +#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. > + * > + */ > +void eGL::syncOutput() > +{ > + ASSERT(tid_ == Thread::currentId()); > + > + glFinish(); > +} > + > +/** > + * \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() > +{ > +} > + > +/** > + * \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() > +{ > + 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 fragment 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 e5b5330a8..60f036da4 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, > -- > 2.52.0 >
diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h new file mode 100644 index 000000000..1ed93f3dc --- /dev/null +++ b/include/libcamera/internal/egl.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Linaro Ltd. + * + * Authors: + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> + * + */ + +#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); + void 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..c6fde5e42 --- /dev/null +++ b/src/libcamera/egl.cpp @@ -0,0 +1,612 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Linaro Ltd. + * + * Authors: + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> + * + */ + +#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. + * + */ +void eGL::syncOutput() +{ + ASSERT(tid_ == Thread::currentId()); + + glFinish(); +} + +/** + * \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() +{ +} + +/** + * \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() +{ + 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 fragment 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 e5b5330a8..60f036da4 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,