Message ID | 20250611013245.133785-23-bryan.odonoghue@linaro.org |
---|---|
State | New |
Headers | show |
Series |
|
Related | show |
On 11/06/2025 02:32, 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. > > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > include/libcamera/internal/egl.h | 110 +++++++++ > src/libcamera/egl.cpp | 369 +++++++++++++++++++++++++++++++ > src/libcamera/meson.build | 23 ++ > 3 files changed, 502 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 00000000..04d637d8 > --- /dev/null > +++ b/include/libcamera/internal/egl.h > @@ -0,0 +1,110 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + * egl_context.cpp - Helper class for managing eGL interactions. > + */ > + > +#pragma once > + > +#include <unistd.h> > + > +#include <libcamera/base/log.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 > +{ > +public: > + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id) > + { > + image_ = EGL_NO_IMAGE_KHR; > + width_ = width; > + height_ = height; > + bpp_ = bpp; > + stride_ = width_ * bpp_ / 4; > + framesize_ = stride_ * height_; > + texture_unit_ = texture_unit; > + texture_unit_uniform_id_ = texture_unit_uniform_id; > + > + glGenTextures(1, &texture_); > + } > + > + ~eGLImage() > + { > + glDeleteTextures(1, &texture_); > + } > + > + uint32_t width_; > + uint32_t height_; > + uint32_t stride_; > + uint32_t offset_; > + uint32_t framesize_; > + uint32_t bpp_; > + uint32_t texture_unit_uniform_id_; > + GLenum texture_unit_; > + GLuint texture_; > + EGLImageKHR image_; > +}; > + > +class eGL > +{ > +public: > + eGL(); > + ~eGL(); > + > + int initEGLContext(GBM *gbmContext); > + int createDMABufTexture2D(eGLImage *eglImage, int fd); > + void destroyDMABufTexture(eGLImage *eglImage); > + void createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void *data); > + void createTexture1D(eGLImage *eglImage, GLint format, 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; > +}; > +} //namespace libcamera > diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp > new file mode 100644 > index 00000000..89ece148 > --- /dev/null > +++ b/src/libcamera/egl.cpp > @@ -0,0 +1,369 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + * egl.cpp - Helper class for managing eGL interactions. > + */ > + > +#include "libcamera/internal/egl.h" > + > +#include <fcntl.h> > +#include <sys/ioctl.h> > +#include <sys/mman.h> > +#include <unistd.h> > + > +#include <linux/dma-buf.h> > +#include <linux/dma-heap.h> > + > +namespace libcamera { > + > +LOG_DEFINE_CATEGORY(eGL) > + > +eGL::eGL() > +{ > +} > + > +eGL::~eGL() > +{ > +} Defaults can be dropped. > +// 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) > + << " 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); Not to self don't forget to set these parameters for GL_TEXTURE_1D. > + > +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) > +{ > + glActiveTexture(eglImage->texture_unit_); > + 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); Looks like squashing Milan's change for the format - GL_LUMINANCE hasn't made it into this patch. > + // 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. > + // we don't use these functions right now but expect to for DMA backed > + // texture generation and render-to-texture. One thing we can do is differentiate > + // between DMA and non-DMA texture generation based on the presence of these functions > + // In reality most - all ? - mesa implementations have these extensions so > + // probably no fallback will be required > + eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); > + if (!eglCreateImageKHR) > + LOG(eGL, Warning) << "eglCreateImageKHR not found"; > + > + eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); > + if (!eglDestroyImageKHR) > + LOG(eGL, Warning) << "eglDestroyImageKHR not found"; > + > + eglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC)eglGetProcAddress("eglExportDMABUFImageMESA"); > + if (!eglExportDMABUFImageMESA) > + LOG(eGL, Warning) << "eglExportDMABUFImageMESA not found"; > + > + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); > + if (!glEGLImageTargetTexture2DOES) > + LOG(eGL, Warning) << "glEGLImageTargetTexture2DOES not found"; > + > + eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR"); > + if (!eglClientWaitSyncKHR) > + LOG(eGL, Warning) << "eglClientWaitSyncKHR not found"; > + > + eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR"); > + if (!eglCreateSyncKHR) > + LOG(eGL, Warning) << "eglCreateSyncKHR not found"; > + > + 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, Debug) << "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; > +} > +} // namespace libcamera > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > index 0d004694..491eb734 100644 > --- a/src/libcamera/meson.build > +++ b/src/libcamera/meson.build > @@ -77,6 +77,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) Recall Laurent said dependency() was the preferred way. --- bod
Hi 2025. 06. 11. 3:32 keltezéssel, Bryan O'Donoghue írta: > 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. > > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > include/libcamera/internal/egl.h | 110 +++++++++ > src/libcamera/egl.cpp | 369 +++++++++++++++++++++++++++++++ > src/libcamera/meson.build | 23 ++ > 3 files changed, 502 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 00000000..04d637d8 > --- /dev/null > +++ b/include/libcamera/internal/egl.h > @@ -0,0 +1,110 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + * egl_context.cpp - Helper class for managing eGL interactions. > + */ > + > +#pragma once > + > +#include <unistd.h> > + > +#include <libcamera/base/log.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 > +{ > +public: > + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id) Could you use the member init list? > + { > + image_ = EGL_NO_IMAGE_KHR; > + width_ = width; > + height_ = height; > + bpp_ = bpp; > + stride_ = width_ * bpp_ / 4; > + framesize_ = stride_ * height_; > + texture_unit_ = texture_unit; > + texture_unit_uniform_id_ = texture_unit_uniform_id; > + > + glGenTextures(1, &texture_); > + } > + > + ~eGLImage() > + { > + glDeleteTextures(1, &texture_); > + } > + > + uint32_t width_; > + uint32_t height_; > + uint32_t stride_; > + uint32_t offset_; > + uint32_t framesize_; > + uint32_t bpp_; > + uint32_t texture_unit_uniform_id_; > + GLenum texture_unit_; > + GLuint texture_; > + EGLImageKHR image_; = EGL_NO_IMAGE_KHR; > +}; Copy ctor/assignment should be deleted; move ctor/assignment should be implemented or deleted (if not needed). > + > +class eGL > +{ > +public: > + eGL(); > + ~eGL(); > + > + int initEGLContext(GBM *gbmContext); > + int createDMABufTexture2D(eGLImage *eglImage, int fd); > + void destroyDMABufTexture(eGLImage *eglImage); > + void createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void *data); > + void createTexture1D(eGLImage *eglImage, GLint format, 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); Span<const std::string> ? > + int compileFragmentShader(GLuint &shaderId, unsigned char *shaderData, > + unsigned int shaderDataLen, > + std::vector<std::string> shaderEnv); And here? > + int linkProgram(GLuint &programIdd, GLuint fragmentshaderId, GLuint vertexshaderId); programIdd -> programId > + 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); Span<const std::string> here as well? > + > + PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA; > + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; > + > + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; > + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; > + > + PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR; > + PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR; > +}; Copy ctor/assignment should be deleted; move ctor/assignment should be implemented or deleted (if not needed). > +} //namespace libcamera > diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp > new file mode 100644 > index 00000000..89ece148 > --- /dev/null > +++ b/src/libcamera/egl.cpp > @@ -0,0 +1,369 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Linaro Ltd. > + * > + * Authors: > + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> > + * > + * egl.cpp - Helper class for managing eGL interactions. > + */ > + > +#include "libcamera/internal/egl.h" > + > +#include <fcntl.h> > +#include <sys/ioctl.h> > +#include <sys/mman.h> > +#include <unistd.h> > + > +#include <linux/dma-buf.h> > +#include <linux/dma-heap.h> > + > +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) > + << " 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, > + }; I think if you add `// clang-format off` and `// clang-format on` around it and then checkstyle.py should not complain. > + > + 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) > +{ > + glActiveTexture(eglImage->texture_unit_); > + 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. > + // we don't use these functions right now but expect to for DMA backed > + // texture generation and render-to-texture. One thing we can do is differentiate > + // between DMA and non-DMA texture generation based on the presence of these functions > + // In reality most - all ? - mesa implementations have these extensions so > + // probably no fallback will be required > + eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); > + if (!eglCreateImageKHR) > + LOG(eGL, Warning) << "eglCreateImageKHR not found"; > + > + eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); > + if (!eglDestroyImageKHR) > + LOG(eGL, Warning) << "eglDestroyImageKHR not found"; > + > + eglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC)eglGetProcAddress("eglExportDMABUFImageMESA"); > + if (!eglExportDMABUFImageMESA) > + LOG(eGL, Warning) << "eglExportDMABUFImageMESA not found"; > + > + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); > + if (!glEGLImageTargetTexture2DOES) > + LOG(eGL, Warning) << "glEGLImageTargetTexture2DOES not found"; > + > + eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR"); > + if (!eglClientWaitSyncKHR) > + LOG(eGL, Warning) << "eglClientWaitSyncKHR not found"; > + > + eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR"); > + if (!eglCreateSyncKHR) > + LOG(eGL, Warning) << "eglCreateSyncKHR not found"; > + > + if (eglChooseConfig(display_, configAttribs, &config, 1, &numConfigs) != EGL_TRUE) { > + LOG(eGL, Error) << "eglChooseConfig fail"; > + goto fail; > + } > + > + context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs); Does this not need `eglDestroyContext()` or similar in `~eGL()`? > + if (context_ == EGL_NO_CONTEXT) { > + LOG(eGL, Error) << "eglContext returned EGL_NO_CONTEXT"; > + goto fail; > + } > + > + surface_ = eglCreateWindowSurface(display_, config, > + (EGLNativeWindowType)gbmContext->getSurface(), > + NULL); Same question here with `eglDestroySurface()`? > + 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); std::move(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]; auto shaderSourceData = std::make_unique<const GLchar *[]>(count); etc. > + > + // Prefix defines before main body of shader > + for (i = 0; i < shaderEnv.size(); i++) { > + shaderSourceData[i] = (GLchar *)shaderEnv[i].c_str(); The above cast shouldn't be needed if you change the type. `glShaderSource()` should take `const GLchar * const *`, so if you have `const GLchar **`, that should work. > + 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]; Same here. > + > + glGetShaderInfoLog(shaderId, sizeLog, &sizeLog, infoLog); > + LOG(eGL, Error) << infoLog; > + > + delete[] infoLog; > + } > + > + delete[] shaderSourceData; > + delete[] shaderDataLengths; > + > + return !(success == GL_TRUE); Should `glDeleteShader()` be called in case of failure? > +} > + > +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]; Same here. > + if (!shaderSource) > + return; > + > + glGetShaderSource(shaderId, shaderLength, &shaderLength, shaderSource); > + if (shaderLength) { > + LOG(eGL, Debug) << "Shader source = " << shaderSource; > + } > + delete[] shaderSource; > + } > +} > + > +int eGL::linkProgram(GLuint &programId, GLuint vertexshaderId, GLuint fragmentshaderId) > +{ > + GLint success; > + GLenum err; > + > + programId = glCreateProgram(); > + if (!programId) > + goto fail; I think there should be another failure label that calls `glDeleteProgram()`. > + > + 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]; std::unique_ptr<GLchar[]> here as well. > + > + glGetProgramInfoLog(programId, sizeLog, &sizeLog, infoLog); > + LOG(eGL, Error) << infoLog; > + > + delete[] infoLog; > + goto fail; > + } > + > + return 0; > +fail: > + return -ENODEV; > +} > +} // namespace libcamera > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > index 0d004694..491eb734 100644 > --- a/src/libcamera/meson.build > +++ b/src/libcamera/meson.build > @@ -77,6 +77,27 @@ if libgbm.found() and gbm_works > ]) > endif > > +libegl = cc.find_library('EGL', required : false) egl_dep = dependency('egl', required: false) ? > +libglesv2 = cc.find_library('GLESv2', required : false) > +mesa_works = cc.check_header('EGL/egl.h', required: false) cc.check_header(..., dependency: egl_dep) ? > + > +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') > @@ -198,7 +219,9 @@ libcamera_deps += [ > libcamera_base_private, > libcrypto, > libdl, > + libegl, > libgbm, > + libglesv2, > liblttng, > libudev, > libyaml, Regards, Barnabás Pőcze
diff --git a/include/libcamera/internal/egl.h b/include/libcamera/internal/egl.h new file mode 100644 index 00000000..04d637d8 --- /dev/null +++ b/include/libcamera/internal/egl.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Linaro Ltd. + * + * Authors: + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> + * + * egl_context.cpp - Helper class for managing eGL interactions. + */ + +#pragma once + +#include <unistd.h> + +#include <libcamera/base/log.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 +{ +public: + eGLImage(uint32_t width, uint32_t height, uint32_t bpp, GLenum texture_unit, uint32_t texture_unit_uniform_id) + { + image_ = EGL_NO_IMAGE_KHR; + width_ = width; + height_ = height; + bpp_ = bpp; + stride_ = width_ * bpp_ / 4; + framesize_ = stride_ * height_; + texture_unit_ = texture_unit; + texture_unit_uniform_id_ = texture_unit_uniform_id; + + glGenTextures(1, &texture_); + } + + ~eGLImage() + { + glDeleteTextures(1, &texture_); + } + + uint32_t width_; + uint32_t height_; + uint32_t stride_; + uint32_t offset_; + uint32_t framesize_; + uint32_t bpp_; + uint32_t texture_unit_uniform_id_; + GLenum texture_unit_; + GLuint texture_; + EGLImageKHR image_; +}; + +class eGL +{ +public: + eGL(); + ~eGL(); + + int initEGLContext(GBM *gbmContext); + int createDMABufTexture2D(eGLImage *eglImage, int fd); + void destroyDMABufTexture(eGLImage *eglImage); + void createTexture2D(eGLImage *eglImage, GLint format, uint32_t width, uint32_t height, void *data); + void createTexture1D(eGLImage *eglImage, GLint format, 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; +}; +} //namespace libcamera diff --git a/src/libcamera/egl.cpp b/src/libcamera/egl.cpp new file mode 100644 index 00000000..89ece148 --- /dev/null +++ b/src/libcamera/egl.cpp @@ -0,0 +1,369 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Linaro Ltd. + * + * Authors: + * Bryan O'Donoghue <bryan.odonoghue@linaro.org> + * + * egl.cpp - Helper class for managing eGL interactions. + */ + +#include "libcamera/internal/egl.h" + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <linux/dma-buf.h> +#include <linux/dma-heap.h> + +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) + << " 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) +{ + glActiveTexture(eglImage->texture_unit_); + 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. + // we don't use these functions right now but expect to for DMA backed + // texture generation and render-to-texture. One thing we can do is differentiate + // between DMA and non-DMA texture generation based on the presence of these functions + // In reality most - all ? - mesa implementations have these extensions so + // probably no fallback will be required + eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); + if (!eglCreateImageKHR) + LOG(eGL, Warning) << "eglCreateImageKHR not found"; + + eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); + if (!eglDestroyImageKHR) + LOG(eGL, Warning) << "eglDestroyImageKHR not found"; + + eglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC)eglGetProcAddress("eglExportDMABUFImageMESA"); + if (!eglExportDMABUFImageMESA) + LOG(eGL, Warning) << "eglExportDMABUFImageMESA not found"; + + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + if (!glEGLImageTargetTexture2DOES) + LOG(eGL, Warning) << "glEGLImageTargetTexture2DOES not found"; + + eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR"); + if (!eglClientWaitSyncKHR) + LOG(eGL, Warning) << "eglClientWaitSyncKHR not found"; + + eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR"); + if (!eglCreateSyncKHR) + LOG(eGL, Warning) << "eglCreateSyncKHR not found"; + + 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, Debug) << "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; +} +} // namespace libcamera diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 0d004694..491eb734 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -77,6 +77,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') @@ -198,7 +219,9 @@ libcamera_deps += [ libcamera_base_private, libcrypto, libdl, + libegl, libgbm, + libglesv2, liblttng, libudev, libyaml,
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. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> --- include/libcamera/internal/egl.h | 110 +++++++++ src/libcamera/egl.cpp | 369 +++++++++++++++++++++++++++++++ src/libcamera/meson.build | 23 ++ 3 files changed, 502 insertions(+) create mode 100644 include/libcamera/internal/egl.h create mode 100644 src/libcamera/egl.cpp