libcamera: software_isp: egl: Introduce an eGL base helper class

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>
This commit is contained in:
Bryan O'Donoghue 2024-02-25 16:23:26 +00:00
parent 234849b2b9
commit 94f30456aa
3 changed files with 502 additions and 0 deletions

View file

@ -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

369
src/libcamera/egl.cpp Normal file
View file

@ -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

View file

@ -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,