This is an example of how to use the EGL API integration with a bare-bones X11 + EGL application.
This is an example of how to use the EGL API integration with a bare-bones X11 + EGL application.
#include <GLES2/gl2.h>
#include <asm-generic/errno-base.h>
#include <asm-generic/errno.h>
#include <assert.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
void check_egl_error(void) {
GLint eglerror = eglGetError();
if (eglerror != EGL_SUCCESS) {
fprintf(stderr, "EGL error: %d\n", eglerror);
}
assert(eglerror == EGL_SUCCESS);
}
void check_error(void) {
check_egl_error();
GLenum error = glGetError();
switch (error) {
case GL_NO_ERROR:
return;
case GL_INVALID_ENUM:
fprintf(stderr, "GL error: GL_INVALID_ENUM\n");
break;
case GL_INVALID_VALUE:
fprintf(stderr, "GL error: GL_INVALID_VALUE\n");
break;
case GL_INVALID_OPERATION:
fprintf(stderr, "GL error: GL_INVALID_OPERATION\n");
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
fprintf(stderr, "GL error: GL_INVALID_FRAMEBUFFER_OPERATION\n");
break;
case GL_OUT_OF_MEMORY:
fprintf(stderr, "GL error: GL_OUT_OF_MEMORY\n");
break;
case GL_STACK_UNDERFLOW:
fprintf(stderr, "GL error: GL_STACK_UNDERFLOW\n");
break;
case GL_STACK_OVERFLOW:
fprintf(stderr, "GL error: GL_STACK_OVERFLOW\n");
break;
default:
fprintf(stderr, "GL error: %d\n", error);
break;
}
assert(error == GL_NO_ERROR);
}
static void initialize_egl(Display *x11_display, Window x11_window,
EGLDisplay *egl_display, EGLContext *egl_context,
EGLSurface *egl_surface, int depth) {
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT =
(PFNEGLQUERYDEVICESTRINGEXTPROC)eglGetProcAddress(
"eglQueryDeviceStringEXT");
PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT =
(PFNEGLQUERYDISPLAYATTRIBEXTPROC)eglGetProcAddress(
"eglQueryDisplayAttribEXT");
PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT =
(PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress(
"eglQueryDmaBufModifiersEXT");
assert(eglQueryDeviceStringEXT);
assert(eglQueryDisplayAttribEXT);
assert(eglQueryDmaBufModifiersEXT);
eglBindAPI(EGL_OPENGL_API);
EGLDisplay display = eglGetDisplay(x11_display);
if (!eglInitialize(display, NULL, NULL)) {
fprintf(stderr, "eglInitialize failed\n");
check_egl_error();
}
EGLConfig config;
EGLint num_config;
EGLint const attribute_list_config[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, depth == 32 ? 8 : 0,
EGL_NONE};
if (!eglChooseConfig(display, attribute_list_config, &config, 1,
&num_config)) {
fprintf(stderr, "eglChooseConfig failed\n");
check_egl_error();
}
EGLint const attrib_list[] = {EGL_CONTEXT_MAJOR_VERSION, 3,
EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE};
EGLContext context =
eglCreateContext(display, config, EGL_NO_CONTEXT, attrib_list);
if (!context) {
fprintf(stderr, "eglCreateContext failed\n");
check_egl_error();
}
EGLSurface surface =
eglCreateWindowSurface(display, config, x11_window, NULL);
if (!surface) {
fprintf(stderr, "eglCreateWindowSurface failed\n");
check_egl_error();
}
if (!eglMakeCurrent(display, surface, surface, context)) {
fprintf(stderr, "eglMakeCurrent failed\n");
check_egl_error();
}
*egl_display = display;
*egl_context = context;
*egl_surface = surface;
}
int u_frame;
void gl_setup_scene(void) {
const char *vertex_shader_source =
"#version 330 core\n"
"uniform float frame;\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" float a = frame * 3.141592 / 4.;"
" mat4 rot = mat4(cos(a), -sin(a), 0., 0.,\n"
" sin(a), cos(a), 0., 0., \n"
" 0., 0., 1., 0., \n"
" 0., 0., 0., 1.);\n"
" vec4 pos = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
" pos = vec4(0.1,0.1,0.1,1.0) * pos;\n"
" pos += vec4(0.5,0.5,0.0,0.0);\n"
" gl_Position = rot * pos;\n"
"}";
const char *fragment_shader_source =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1., 0., 0., 1.);\n"
"}";
int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
glCompileShader(vertex_shader);
int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
glCompileShader(fragment_shader);
int shader_program = glCreateProgram();
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram(shader_program);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
u_frame = glGetUniformLocation(shader_program, "frame");
float vertices[] = {
0.f, 1.f, 0.0f, 1.f, -1.f, 0.0f, -1.f, -1.f, 0.0f,
};
unsigned int indices[] = {
0,
1,
2,
};
unsigned int VBO, VAO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
(void *)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glUseProgram(shader_program);
glBindVertexArray(VAO);
}
void gl_draw_scene(GLuint texture) {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
void gl_draw_triangle(void) {
static float frame = 0;
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUniform1f(u_frame, frame++);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
}
bool is_argb_visual(const XVisualInfo *visual) {
return visual->depth == 32 && visual->red_mask == 0xff0000 &&
visual->green_mask == 0x00ff00 && visual->blue_mask == 0x0000ff;
}
void create_x11_window(Display **x11_display, Window *x11_window, int w, int h,
int *depth) {
Display *display = XOpenDisplay(NULL);
int screen = DefaultScreen(display);
XVisualInfo vinfo;
int visual_depth = 32;
bool matched = !!XMatchVisualInfo(display, XDefaultScreen(display),
visual_depth, TrueColor, &vinfo);
if (!matched) {
visual_depth = 24;
matched = !!XMatchVisualInfo(display, XDefaultScreen(display),
visual_depth, TrueColor, &vinfo);
}
if (!matched) {
fprintf(stderr, "Failed to find window visual\n");
assert(0);
}
if (is_argb_visual(&vinfo))
*depth = 32;
else
*depth = 24;
fprintf(stderr, "Depth: %d\n", *depth);
XSetWindowAttributes attrs;
attrs.colormap = XCreateColormap(display, XDefaultRootWindow(display),
vinfo.visual, AllocNone);
attrs.background_pixel = BlackPixel(display, screen);
attrs.border_pixel = BlackPixel(display, screen);
Window window =
XCreateWindow(display, RootWindow(display, screen), 10, 10, w, h, 1,
visual_depth, InputOutput, vinfo.visual,
CWBackPixel | CWColormap | CWBorderPixel, &attrs);
XStoreName(display, window, "Client");
XMapWindow(display, window);
*x11_display = display;
*x11_window = window;
}
EGLDisplay egl_display;
EGLContext egl_context;
EGLSurface egl_surface;
int do_init(uint32_t width, uint32_t height) {
Display *x11_display;
Window x11_window;
int depth;
create_x11_window(&x11_display, &x11_window, width, height, &depth);
initialize_egl(x11_display, x11_window, &egl_display, &egl_context,
&egl_surface, depth);
gl_setup_scene();
return 0;
}
double timef(void) {
double val;
static double base = 0;
struct timeval tv;
gettimeofday(&tv, NULL);
val = tv.tv_sec + (double)tv.tv_usec / 1000000.f;
if (!base)
base = val;
return val - base;
}
int main(int argc, char **argv) {
int ret;
uint32_t width = 512;
uint32_t height = 512;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-async"))
else if (!strcmp(argv[i], "-single"))
else if (!strcmp(argv[i], "-double"))
else if (!strcmp(argv[i], "-synchronous"))
else if (!strcmp(argv[i], "-implicit_sync"))
else if (!strcmp(argv[i], "-explicit_sync_only"))
else if (!strcmp(argv[i], "-explicit_sync_hybrid"))
else if (!strcmp(argv[i], "-either_sync"))
}
do_init(width, height);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
ret =
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
assert(ret == 0);
check_error();
GLuint fb;
glGenFramebuffers(1, &fb);
while (1) {
check_error();
float t = timef();
assert(ret >= 0);
if (!buf) {
fprintf(stderr, "[%f] No buffers\n", t);
} else {
fprintf(stderr, "[%f] Got buffer\n", t);
}
check_error();
gl_draw_triangle();
check_error();
if (buf) {
EGLImage image;
assert(ret == 0);
fprintf(stderr, "[%f] Buffer has sync\n", t);
EGLSync acquire;
assert(ret == 0);
eglWaitSync(egl_display, acquire, 0);
eglDestroySync(egl_display, acquire);
check_error();
}
GLuint color_tex;
glGenTextures(1, &color_tex);
glBindTexture(GL_TEXTURE_2D, color_tex);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, fb);
glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
color_tex, 0);
glBlitFramebuffer(0, height, width, 0, 0, 0, width, height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
0, 0);
glDeleteTextures(1, &color_tex);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
EGLSync release = eglCreateSync(
egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL);
assert(ret == 0);
eglDestroySync(egl_display, release);
} else {
glFlush();
}
check_error();
}
eglSwapBuffers(egl_display, egl_surface);
if (buf) {
if (ret < 0)
fprintf(stderr, "Queue failed: %d\n", ret);
else if (ret != 1)
fprintf(stderr,
"Buffer dropped (stream renegotiated or paused)\n");
assert(ret >= 0);
}
}
assert(ret == 0);
}
libfunnel EGL API integration
int funnel_buffer_get_acquire_egl_sync(struct funnel_buffer *buf, EGLSync *psync)
Get the EGLSync for acquiring the buffer.
int funnel_stream_init_egl(struct funnel_stream *stream, EGLDisplay display)
Set up a stream for EGL integration.
int funnel_buffer_set_release_egl_sync(struct funnel_buffer *buf, EGLSync sync)
Set the EGLSync for releasing the buffer.
int funnel_stream_egl_add_format(struct funnel_stream *stream, enum funnel_egl_format format)
Add a supported EGL format.
@ FUNNEL_EGL_FORMAT_RGB888
8-bit RGB without alpha, sRGB (32bpp with padding)
Definition funnel-egl.h:32
@ FUNNEL_EGL_FORMAT_RGBA8888
8-bit RGBA with premultiplied alpha, sRGB (32bpp)
Definition funnel-egl.h:34
int funnel_buffer_get_egl_image(struct funnel_buffer *buf, EGLImage *pimage)
Get the EGLImage for a Funnel buffer.
struct funnel_stream funnel_stream
A PipeWire video stream.
Definition funnel.h:36
struct funnel_buffer funnel_buffer
A video buffer (frame).
Definition funnel.h:58
int funnel_stream_configure(struct funnel_stream *stream)
Apply the stream configuration and register the stream with PipeWire.
static struct funnel_fraction FUNNEL_FRACTION(uint32_t num, uint32_t den)
Helper to create a funnel_fraction.
Definition funnel.h:80
funnel_mode
Synchronization modes for the frame pacing.
Definition funnel.h:99
@ FUNNEL_SINGLE_BUFFERED
Produce frames synchronously to the consumer with single buffering.
Definition funnel.h:154
@ FUNNEL_SYNCHRONOUS
Produce frames synchronously with the PipeWire process cycle.
Definition funnel.h:170
@ FUNNEL_ASYNC
Produce frames asynchronously to the consumer.
Definition funnel.h:117
@ FUNNEL_DOUBLE_BUFFERED
Produce frames synchronously to the consumer with double buffering.
Definition funnel.h:137
int funnel_stream_enqueue(struct funnel_stream *stream, struct funnel_buffer *buf)
Enqueue a buffer to a stream.
int funnel_stream_stop(struct funnel_stream *stream)
Stop running a stream.
struct funnel_ctx funnel_ctx
A libfunnel context.
Definition funnel.h:24
int funnel_stream_set_sync(struct funnel_stream *stream, enum funnel_sync frontend, enum funnel_sync backend)
Configure the synchronization modes for the stream.
void funnel_shutdown(struct funnel_ctx *ctx)
Shut down a Funnel context.
int funnel_stream_set_size(struct funnel_stream *stream, uint32_t width, uint32_t height)
Set the frame dimensions for a stream.
int funnel_connect(struct funnel_ctx *ctx)
Connect to PipeWire.
int funnel_new(struct funnel_ctx **pctx)
Create a Funnel context.
int funnel_stream_create(struct funnel_ctx *ctx, const char *name, struct funnel_stream **pstream)
Create a new stream.
int funnel_set_app_version(struct funnel_ctx *ctx, const char *app_version)
Set the application version.
void funnel_stream_destroy(struct funnel_stream *stream)
Destroy a stream.
int funnel_set_app_id(struct funnel_ctx *ctx, const char *app_id)
Set the application ID.
int funnel_stream_set_mode(struct funnel_stream *stream, enum funnel_mode mode)
Configure the queueing mode for the stream.
funnel_sync
Buffer synchronization modes for frames.
Definition funnel.h:178
@ FUNNEL_SYNC_BOTH
Support both implicit and explicit sync.
Definition funnel.h:192
@ FUNNEL_SYNC_EXPLICIT
Use explicit sync only.
Definition funnel.h:187
@ FUNNEL_SYNC_IMPLICIT
Use implicit sync only.
Definition funnel.h:182
int funnel_stream_start(struct funnel_stream *stream)
Start running a stream.
bool funnel_buffer_has_sync(struct funnel_buffer *buf)
Check whether a buffer requires explicit synchronization.
int funnel_stream_set_rate(struct funnel_stream *stream, struct funnel_fraction def, struct funnel_fraction min, struct funnel_fraction max)
Set the frame rate of a stream.
int funnel_set_app_name(struct funnel_ctx *ctx, const char *app_name)
Set the user-friendly application name.
static const struct funnel_fraction FUNNEL_RATE_VARIABLE
Indicates that the frame rate is variable.
Definition funnel.h:71
int funnel_stream_dequeue(struct funnel_stream *stream, struct funnel_buffer **pbuf)
Dequeue a buffer from a stream.