[PATCH 14/27] libcamera: software_isp: egl: Introduce an eGL base helper class
Bryan O'Donoghue
bryan.odonoghue at linaro.org
Tue Apr 22 23:59:07 CEST 2025
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.
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue at linaro.org>
---
src/libcamera/software_isp/egl.cpp | 346 +++++++++++++++++++++++++++++
src/libcamera/software_isp/egl.h | 106 +++++++++
2 files changed, 452 insertions(+)
create mode 100644 src/libcamera/software_isp/egl.cpp
create mode 100644 src/libcamera/software_isp/egl.h
diff --git a/src/libcamera/software_isp/egl.cpp b/src/libcamera/software_isp/egl.cpp
new file mode 100644
index 00000000..c3eb8290
--- /dev/null
+++ b/src/libcamera/software_isp/egl.cpp
@@ -0,0 +1,346 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Linaro Ltd.
+ *
+ * Authors:
+ * Bryan O'Donoghue <bryan.odonoghue at linaro.org>
+ *
+ * egl.cpp - Helper class for managing eGL interactions.
+ */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <linux/dma-buf.h>
+#include <linux/dma-heap.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include "egl.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(eGL)
+
+eGL::eGL()
+{
+}
+
+eGL::~eGL()
+{
+}
+
+// Create linear image attached to previous BO object
+int eGL::createDMABufTexture2D(eGLImage *eglImage, int fd)
+{
+ int ret = 0;
+
+ eglImage->stride_ = eglImage->width_ * eglImage->height_;
+ eglImage->offset_ = 0;
+ eglImage->framesize_ = eglImage->height_ * eglImage->stride_;
+
+ LOG(eGL, Info) << __func__ << " stride " << eglImage->stride_ << " width " << eglImage->width_ <<
+ " height " << eglImage->height_ << " offset " << eglImage->offset_ << " framesize " <<
+ eglImage->framesize_;
+
+ // TODO: use the dma buf handle from udma heap here directly
+ // should work for both input and output with fencing
+ EGLint image_attrs[] = {
+ EGL_WIDTH, (EGLint)eglImage->width_,
+ EGL_HEIGHT, (EGLint)eglImage->height_,
+ EGL_LINUX_DRM_FOURCC_EXT, (int)GBM_FORMAT_ARGB8888,
+ EGL_DMA_BUF_PLANE0_FD_EXT, fd,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)eglImage->framesize_,
+ EGL_NONE, EGL_NONE, /* modifier lo */
+ EGL_NONE, EGL_NONE, /* modifier hi */
+ EGL_NONE,
+ };
+
+ 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;
+ }
+
+ // Generate texture, bind, associate image to texture, configure, unbind
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage->image_);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+done:
+ return ret;
+}
+
+void eGL::destroyDMABufTexture(eGLImage *eglImage)
+{
+ eglDestroyImage(display_, eglImage->image_);
+}
+
+//
+// Generate a 2D texture from an input buffer directly
+void eGL::createTexture2D(eGLImage *eglImage, uint32_t width, uint32_t height, void *data)
+{
+ glBindTexture(GL_TEXTURE_2D, eglImage->texture_);
+
+ // Generate texture, bind, associate image to texture, configure, unbind
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, 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);
+}
+
+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;
+ }
+
+ //TODO: use optional eglGetPlatformDisplayEXT ?
+ 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);
+
+ //TODO: interrogate strings to make sure we aren't hooking unsupported functions
+ // and remember to error out if a function we depend on isn't found.
+ eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
+ eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
+ eglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC)eglGetProcAddress("eglExportDMABUFImageMESA");
+ glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
+ eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR");
+ eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR");
+
+ 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;
+ }
+
+ surface_ = eglCreateWindowSurface(display_, config,
+ (EGLNativeWindowType)gbmContext->getSurface(),
+ NULL);
+ if (surface_ == EGL_NO_SURFACE) {
+ LOG(eGL, Error) << "eglCreateWindowSurface fail";
+ goto fail;
+ }
+
+ makeCurrent();
+ swapBuffers();
+
+ return 0;
+fail:
+
+ return -ENODEV;
+}
+
+void eGL::makeCurrent(void)
+{
+ if (eglMakeCurrent(display_, surface_, surface_, context_) != EGL_TRUE) {
+ LOG(eGL, Error) << "eglMakeCurrent fail";
+ }
+}
+
+void eGL::swapBuffers(void)
+{
+ if (eglSwapBuffers(display_, surface_) != EGL_TRUE) {
+ LOG(eGL, Error) << "eglSwapBuffers fail";
+ }
+}
+
+void eGL::useProgram(GLuint programId)
+{
+ glUseProgram(programId);
+}
+
+void eGL::pushEnv(std::vector<std::string>& shaderEnv, const char *str)
+{
+ std::string addStr = str;
+
+ addStr.push_back('\n');
+ shaderEnv.push_back(addStr);
+}
+
+int eGL::compileVertexShader(GLuint &shaderId, unsigned char *shaderData,
+ unsigned int shaderDataLen,
+ std::vector<std::string> shaderEnv)
+{
+ return compileShader(GL_VERTEX_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);
+}
+
+int eGL::compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,
+ unsigned int shaderDataLen,
+ std::vector<std::string> shaderEnv)
+{
+ return compileShader(GL_FRAGMENT_SHADER, shaderId, shaderData, shaderDataLen, shaderEnv);
+}
+
+int eGL::compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,
+ unsigned int shaderDataLen,
+ std::vector<std::string> shaderEnv)
+{
+ GLchar **shaderSourceData;
+ GLint *shaderDataLengths;
+ GLint success;
+ GLsizei count;
+ size_t i;
+
+ count = 1 + shaderEnv.size();
+ shaderSourceData = new GLchar*[count];
+ shaderDataLengths = new GLint[count];
+
+ // Prefix defines before main body of shader
+ for (i = 0; i < shaderEnv.size(); i++) {
+ shaderSourceData[i] = (GLchar*)shaderEnv[i].c_str();
+ shaderDataLengths[i] = shaderEnv[i].length();
+ }
+
+ // Now the main body of the shader program
+ shaderSourceData[i] = (GLchar*)shaderData;
+ shaderDataLengths[i] = shaderDataLen;
+
+ // And create the shader
+ shaderId = glCreateShader(shaderType);
+ glShaderSource(shaderId, count, shaderSourceData, shaderDataLengths);
+ glCompileShader(shaderId);
+
+ // Check status
+ glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);
+ if (success == GL_FALSE) {
+ GLint sizeLog = 0;
+ GLchar *infoLog;
+
+ glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &sizeLog);
+ infoLog = new GLchar[sizeLog];
+
+ glGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog);
+ LOG(eGL, Error) << infoLog;
+
+ delete [] infoLog;
+ }
+
+ delete [] shaderSourceData;
+ delete [] shaderDataLengths;
+
+ return !(success == GL_TRUE);
+}
+
+void eGL::dumpShaderSource(GLuint shaderId)
+{
+ GLint shaderLength = 0;
+ GLchar *shaderSource;
+
+ glGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &shaderLength);
+
+ LOG(eGL, Debug) <<"Shader length is " << shaderLength;
+
+ if (shaderLength > 0) {
+ shaderSource = new GLchar[shaderLength];
+ if (!shaderSource)
+ return;
+
+ glGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource);
+ if (shaderLength) {
+ LOG(eGL, Info) << "Shader source = " << shaderSource;
+ }
+ delete [] shaderSource;
+ }
+}
+
+int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId)
+{
+ GLint success;
+ GLenum err;
+
+ 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:
+ return -ENODEV;
+}
+}
diff --git a/src/libcamera/software_isp/egl.h b/src/libcamera/software_isp/egl.h
new file mode 100644
index 00000000..f3c5d50f
--- /dev/null
+++ b/src/libcamera/software_isp/egl.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Linaro Ltd.
+ *
+ * Authors:
+ * Bryan O'Donoghue <bryan.odonoghue at linaro.org>
+ *
+ * egl_context.cpp - Helper class for managing eGL interactions.
+ */
+
+#pragma once
+
+#define GL_GLEXT_PROTOTYPES
+#include <GL/gl.h>
+#define EGL_EGLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES3/gl32.h>
+
+#include <libcamera/base/log.h>
+
+#include <unistd.h> // close
+
+#include "gbm.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(eGL)
+
+class eGLImage {
+public:
+ eGLImage(uint32_t width, uint32_t height, uint32_t bpp) {
+ image_ = EGL_NO_IMAGE_KHR;
+ width_ = width;
+ height_ = height;
+ bpp_ = bpp;
+ stride_ = width_ * bpp_ / 4;
+ framesize_ = stride_ * height_;
+
+ glGenTextures(1, &texture_);
+ }
+
+ ~eGLImage() {
+ glDeleteTextures(1, &texture_);
+ };
+
+ GLuint texture_;
+ EGLImageKHR image_;
+ uint32_t width_;
+ uint32_t height_;
+ uint32_t stride_;
+ uint32_t offset_;
+ uint32_t framesize_;
+ uint32_t bpp_;
+};
+
+class eGL
+{
+public:
+ eGL();
+ ~eGL();
+
+ int initEGLContext(GBM *gbmContext);
+ int createDMABufTexture2D(eGLImage *eglImage, int fd);
+ void destroyDMABufTexture(eGLImage *eglImage);
+ void createTexture2D(eGLImage *eglImage, uint32_t width, uint32_t height, void *data);
+ void createTexture1D(eGLImage *eglImage, uint32_t width, void *data);
+
+ void pushEnv(std::vector<std::string> &shaderEnv, const char *str);
+ void makeCurrent();
+ void swapBuffers();
+
+ int compileVertexShader(GLuint &shaderId, unsigned char *shaderData,
+ unsigned int shaderDataLen,
+ std::vector<std::string> shaderEnv);
+ int compileFragmentShader(GLuint &shaderId, unsigned char *shaderData,
+ unsigned int shaderDataLen,
+ std::vector<std::string> shaderEnv);
+ int linkProgram(GLuint &programIdd, GLuint fragmentshaderId, GLuint vertexshaderId);
+ void dumpShaderSource(GLuint shaderId);
+ void useProgram(GLuint programId);
+
+private:
+ int fd_;
+
+ EGLDisplay display_;
+ EGLContext context_;
+ EGLSurface surface_;
+
+ int compileShader(int shaderType, GLuint &shaderId, unsigned char *shaderData,
+ unsigned int shaderDataLen,
+ std::vector<std::string> shaderEnv);
+
+ PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;
+ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
+
+ PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
+ PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
+
+ PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;
+ PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
+protected:
+
+};
+
+};
--
2.49.0
More information about the libcamera-devel
mailing list