wegen/FrOSCon26/examples/egl-display.hpp
Ichthyostega a7c57e98ce Compat: Check for successful GLEW initialisation fails on my PC / Trixie
I observed same strange behaviour already last year on my »Video Output« talk.
The return value indicates an unkown error, but the subsequent usage seemingly works...

On my Laptop, I don not see this error; it might be due to the very old graphics card on my PC...
2026-08-15 02:19:09 +02:00

377 lines
16 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
egl-display.hpp - output video from a GTK application, using EGL - OpenGL binding
Copyright (C)
2025, Benny Lyons <benny.lyons@gmx.net>
2025, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU GPL version 2+ See the LICENSE file for details.
* ************************************************************************/
#include "commons.hpp"
#include "gtk-uring-app.hpp"
#include <functional>
#include <algorithm>
#include <cassert>
#include <string>
#include <array>
#include <set>
// for low-level access -> X-Window
#include <gdk/gdkx.h>
// X11, EGL and GLEW for OpenGL
#include <X11/Xlib.h>
#include <EGL/egl.h>
#include <EGL/eglext.h> // defines plattform-specific config and extensions
#include <GL/glew.h> // provides system-specific bindings for OpenGL extensions
using std::string;
/**
* Connection and drawing context used for EGL based video display.
*/
template<typename PIX, uint WIDTH, uint HEIGHT>
class EglDisplay
: NonCopyable
{
public:
using Buffer = std::array<PIX, WIDTH*HEIGHT * 3 >;
/**
* Interface: retrieve pixel data for video frames to display.
* - this is a Glib::Dispatcher, and the attached slot shall be
* triggered whenever data for a new frame is available
* - pixel data is accessed (threadsafe) by invoking #retrieveFrame()
* - when processing is complete and the data will no longer be needed,
* access to the pixel buffer is yielded by invoking #completedFrame()
*/
class PixelFeed
: public Glib::Dispatcher
{
public:
virtual ~PixelFeed() { }; ///< this is an interface
virtual Buffer* retrieveFrame() =0;
virtual void completedFrame() =0;
};
EglDisplay (Gtk::Window& appWindow, PixelFeed& pixelFeed)
: pixelFeed_{pixelFeed}
{
openDisplay (appWindow);
pixelFeed_.connect([this]{ displayFrame(); });
}
void cleanUp();
private:
/** EGL / X11 connection. */
EGLDisplay display_{nullptr};
EGLSurface surface_{nullptr};
EGLContext egl_{nullptr};
uint texID_{0};
uint vboID_{0};
uint vaoID_{0};
float scaleX_{1};
float scaleY_{1};
/** Callback to retrieve pixel data */
PixelFeed& pixelFeed_{};
void openDisplay (Gtk::Window&);
void displayFrame();
};
template<typename PIX, uint WIDTH, uint HEIGHT>
void
EglDisplay<PIX,WIDTH,HEIGHT>::openDisplay (Gtk::Window& appWindow)
{
std::cout << "Open display-connection through EGL..." << std::endl;
// use the X-Window as anchor to build an OpenGL context via EGL
Glib::RefPtr<Gdk::Window> gdkWindow = appWindow.get_window();
Window xWindow = GDK_WINDOW_XID (gdkWindow->gobj());
Display* xDisplay = GDK_WINDOW_XDISPLAY (gdkWindow->gobj());
long xScreen;
XWindowAttributes xWinAttrs;
if (XGetWindowAttributes (xDisplay, xWindow, &xWinAttrs))
xScreen = XScreenNumberOfScreen (xWinAttrs.screen);
else
__FAIL ("unable to retrieve screen number from the X11 window attributes.");
long SCREEN_SPEC[]
{EGL_PLATFORM_X11_SCREEN_EXT, xScreen
,EGL_NONE};
display_ = eglGetPlatformDisplay (EGL_PLATFORM_X11_EXT, xDisplay, SCREEN_SPEC);
if (EGL_NO_DISPLAY == display_
or not eglInitialize(display_, NULL,NULL))
__FAIL ("could not establish EGL Display connection.");
EGLint DESIRED_ATTRIBS[]
{EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER
,EGL_RED_SIZE, 4
,EGL_GREEN_SIZE, 4
,EGL_BLUE_SIZE, 4
,EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT // config must support creating an OpenGL context
,EGL_SURFACE_TYPE, EGL_WINDOW_BIT // want to create a window surface
,EGL_DEPTH_SIZE, 0 // prefer config without depth buffer (occlusion testing not needed)
,EGL_STENCIL_SIZE, 0 // prefer config without stencil buffer (no advanced visual effects)
,EGL_NONE};
EGLint _cnt;
EGLConfig config;
if (not eglChooseConfig (display_
,DESIRED_ATTRIBS
,& config
,1,&_cnt)
or not config)
__FAIL ("unable to select a EGL display config with the required attributes");
surface_ = eglCreateWindowSurface (display_,config,xWindow,nullptr);
if (not surface_)
switch (eglGetError())
{
case EGL_BAD_CONFIG:
__FAIL ("selected display configuration is not valid");
break;
case EGL_BAD_ALLOC:
__FAIL ("unable to allocate resources or collision with existing resources");
break;
case EGL_BAD_MATCH:
__FAIL ("mismatch between X11 window visuals and the capabilities of the selected config");
break;
default:
__FAIL ("unable to create EGL window surface, for unknown reasons");
break;
}
if (not eglBindAPI( EGL_OPENGL_API))
__FAIL ("unable to configure EGL for usage with the OpenGL API.");
EGLint CTX_ATTRIBS[] // Note: require modern OpenGL
{EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT
// ,EGL_CONTEXT_MAJOR_VERSION, 3
// ,EGL_CONTEXT_MINOR_VERSION, 3
,EGL_NONE};
egl_ = eglCreateContext (display_, config
,EGL_NO_CONTEXT // do not share definitions and data with another context
,CTX_ATTRIBS);
if (not egl_)
__FAIL ("failed to create OpenGL context for this display with desired visuals");
// create a binding for the current thread to use this context on this window
if (not eglMakeCurrent (display_, surface_,surface_, egl_))
__FAIL ("failed to attach an OpenGL context to the application X-Window");
// use lib-GLEW to manage OpenGL extensions (notably GL Shader Language)
GLenum glewErr = glewInit();
////////////////////////////////////////////////////////////////////////////////////TODO for some reason I get glewErr == 4 ("unknown error"), but it works non the less
//if (GLEW_OK != glewErr)
// __FAIL ("could not bind OpenGL extensions through lib-GLEW:\n"
// +string{reinterpret_cast<const char*> (glewGetErrorString(glewErr))});
auto compileShader = [](GLenum kind, string code)
{
uint shaderID = glCreateShader (kind);
const GLchar* codeSegment = code.c_str();
glShaderSource (shaderID, 1, &codeSegment, NULL);
glCompileShader(shaderID);
// diagnostics...
int success;
glGetShaderiv (shaderID, GL_COMPILE_STATUS, &success);
if (not success)
{
int logSiz{0};
glGetShaderiv (shaderID, GL_INFO_LOG_LENGTH, &logSiz);
string report(logSiz,'.');
glGetShaderInfoLog (shaderID, logSiz, NULL, & report[0]);
std::cout << "\n■□■□■□■□■----Shader-Code-------------------------\n"
<< code
<< "\n■□■□■□■□■----Failure-Report----------------------\n"
<< report.c_str()
<< std::endl;
__FAIL ("shader compilation failure");
}
return shaderID;
};
auto checkLinkError = [](uint programID)
{
int success;
glGetProgramiv (programID, GL_LINK_STATUS, &success);
if (not success)
{
int logSiz{0};
glGetProgramiv (programID, GL_INFO_LOG_LENGTH, &logSiz);
string report(logSiz,'.');
glGetProgramInfoLog (programID, logSiz, NULL, & report[0]);
std::cout << "\n■□■□■□■□■----Failure-Report----------------------\n"
<< report.c_str()
<< std::endl;
__FAIL ("unable to link shader code");
}
};
uint vertexShader
= compileShader (GL_VERTEX_SHADER,
R"-(#version 330 core
layout(location = 0) in vec2 position;
layout(location = 1) in vec2 texCoord;
out vec2 fragTexCoord;
void main() {
gl_Position = vec4 (position, 0.0, 1.0);
fragTexCoord = texCoord;
}
)-");
uint fragmentShader
= compileShader (GL_FRAGMENT_SHADER,
R"-(#version 330 core
in vec2 fragTexCoord;
out vec4 colour;
uniform sampler2D textureSampler;
void main() {
colour = texture (textureSampler, fragTexCoord);
}
)-");
uint gpuProgram = glCreateProgram();
glAttachShader (gpuProgram, vertexShader);
glAttachShader (gpuProgram, fragmentShader);
glLinkProgram (gpuProgram);
checkLinkError (gpuProgram);
glUseProgram (gpuProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
/* === setup a 2D texture, to be mapped into the viewport === */
glActiveTexture(GL_TEXTURE0); // activate texture-unit #0 on the GPU
glGenTextures (1, &texID_); // allocate 1 new texture ID
glBindTexture (GL_TEXTURE_2D, texID_); // use this textureID as "the" TEXTURE_2D
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // configure image scaling filter
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // clamp, don't wrap at the texture edges
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// configure the `textureSampler` parameter (»uniform« is a global parameter visible in all shaders)
GLint samplerParam = glGetUniformLocation(gpuProgram, "textureSampler");
glUniform1i(samplerParam, 0); // configure the shader to use texture-unit #0 of the GPU
/* === setup a fixed geometry to hold that texture === */
glDisable (GL_DEPTH_TEST); // we do not need 3D layering / positioning
glViewport (0, 0, WIDTH, HEIGHT); // Origin in the middle of the window (note Y points upwards)
// Note: this demo uses a fixed-size window and hard-coded video size;
// a real-world implementation would have to place the video frame
// dynamically into the available screen space, possibly scaling up/down
GLfloat sX{scaleX_};
GLfloat sY{scaleY_}; // Note: positions use »normalised device coordinates« (NDC)
GLfloat w{1}; // TEXTURE_2D points cover the range (0,0) ... (1,1)
GLfloat h{1};
GLfloat vertexData[] // Geometry and mapping data to be sent to the GPU...
//-point-+--texture // We define the 4 edges of a rectangular »projection screen«
{-sX,-sY , 0,h // For each edge, we define the position in NDC [-1 ... +1]
, sX,-sY , w,h // and we specify which point in the texture to map there
, sX, sY , w,0 // Note: the rows of the video image run top-down, and thus we
,-sX, sY , 0,0 // have to map the texture flipped, since OpenGL orients Y upwards.
};
glGenBuffers (1, &vboID_); // allocate 1 new vertex-buffer-object
glGenVertexArrays(1, &vaoID_); // allocate 1 new vertex-array-object (to attach all definitions)
glBindVertexArray(vaoID_); // activate the VAO to pick up the following definitions...
glBindBuffer (GL_ARRAY_BUFFER, vboID_);
// copy the vertexData into the vertex-buffer-object currently bound to GL_ARRAY_BUFFER
glBufferData (GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
auto SIZ = sizeof(float); // parameters are transferred to the GPU as float data
auto attrib = 2; // we use 2 input attributes for the shader pipeline
auto coords = 2; // our data vectors have 2 coordinates (2D)
auto stride = attrib * coords * SIZ; // stride is the offset in bytes between full data points
auto offset = [&](uint elm){ return (void*) (elm * SIZ); }; // offset of one attribute relative to the start of a data point (packaged into a void*)
auto normalise = GL_FALSE; // do not auto-normalise, positions are already [-1 ... +1]
// define how two two attributes are packed into the data sequence we send to the GPU
glVertexAttribPointer (0, coords, GL_FLOAT, normalise, stride, offset(0) );
glVertexAttribPointer (1, coords, GL_FLOAT, normalise, stride, offset(2) );
glEnableVertexAttribArray (0); // activate attribute #1 ≙ input argument `position` in vertexShader
glEnableVertexAttribArray (1); // activate attribute #2 ≙ input argument `texCoord` in vertexShader
std::cout << "Successfully established OpenGL display ("<<WIDTH<<"x"<<HEIGHT<<"px) within GTK-Window." << std::endl;
}
template<typename PIX, uint WIDTH, uint HEIGHT>
void
EglDisplay<PIX,WIDTH,HEIGHT>::displayFrame()
{
auto* currFrame = pixelFeed_.retrieveFrame();
if (not currFrame) return;
// pick up current buffer with RGB data
// and send it to the GPU (into the prepared texture)...
glTexImage2D (GL_TEXTURE_2D // the target texture store to work on, here "the" TEXTURE_2D
,0 // detail level (when using mipmap reduction, which we don't)
,GL_RGB // internal format or features to use for this texture
,WIDTH,HEIGHT, /*border*/ 0
,GL_RGB // data layout of the provided pixels
,GL_UNSIGNED_BYTE // data format / size of the pixels
,currFrame
);
// Draw the quadrilateral
glDrawArrays(GL_QUADS, 0, 4);
// double-buffer flip, automatically invokes glFlush()
eglSwapBuffers (display_, surface_);
// signal that processing of this frame is complete
pixelFeed_.completedFrame();
}
template<typename PIX, uint WIDTH, uint HEIGHT>
void
EglDisplay<PIX,WIDTH,HEIGHT>::cleanUp()
{
// std::cout << "STOP " << imgGen_.getFrameNr() << " frames displayed." << std::endl;
// detach binding with OpenGL context
eglMakeCurrent (display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface (display_, surface_);
eglDestroyContext (display_, egl_);
eglTerminate(display_); // detach EGL from display
}