Last active
December 26, 2023 08:01
-
-
Save jfuerth/82b816510bb2cc063c9945baf1093fd9 to your computer and use it in GitHub Desktop.
Self-contained example of an EGL + X11 OpenGL ES 2.0 rectangle moving across the screen
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// egl_bar.c - self-contained example of a vertical bar moving across the screen. | |
// compile with gcc -Wall -O0 -g -o egl_bar egl_bar.c log.c -lX11 -lEGL -lGLESv2 -lm | |
#include <stdio.h> | |
#include <math.h> | |
#include <malloc.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <sys/mman.h> | |
#include <X11/Xlib.h> | |
#include <X11/Xatom.h> | |
#include <X11/Xutil.h> | |
#include <GLES2/gl2.h> | |
#include <EGL/egl.h> | |
#include "log.h" | |
static void log_egl_details(EGLDisplay egl_display, EGLConfig egl_conf) { | |
log_info("EGL Client APIs: %s", eglQueryString(egl_display, EGL_CLIENT_APIS)); | |
log_info("EGL Vendor: %s", eglQueryString(egl_display, EGL_VENDOR)); | |
log_info("EGL Version: %s", eglQueryString(egl_display, EGL_VERSION)); | |
log_info("EGL Extensions: %s", eglQueryString(egl_display, EGL_EXTENSIONS)); | |
int i = -1; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_CONFIG_ID, &i); | |
log_debug("EGL_CONFIG_ID = %d", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_RED_SIZE, &i); | |
log_debug("EGL_RED_SIZE = %d", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_GREEN_SIZE, &i); | |
log_debug("EGL_GREEN_SIZE = %d", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_BLUE_SIZE, &i); | |
log_debug("EGL_BLUE_SIZE = %d", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_ALPHA_SIZE, &i); | |
log_debug("EGL_ALPHA_SIZE = %d", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_DEPTH_SIZE, &i); | |
log_debug("EGL_DEPTH_SIZE = %d", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_LEVEL, &i); | |
log_debug("EGL_LEVEL = %d", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_NATIVE_RENDERABLE, &i); | |
log_debug("EGL_NATIVE_RENDERABLE = %s", i ? "EGL_TRUE" : "EGL_FALSE"); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_NATIVE_VISUAL_TYPE, &i); | |
log_debug("EGL_NATIVE_VISUAL_TYPE = %d", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_RENDERABLE_TYPE, &i); | |
log_debug("EGL_RENDERABLE_TYPE = 0x%04x", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_SURFACE_TYPE, &i); | |
log_debug("EGL_SURFACE_TYPE = 0x%04x", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_TRANSPARENT_TYPE, &i); | |
if (i == EGL_TRANSPARENT_RGB) { | |
log_debug("EGL_TRANSPARENT_TYPE = EGL_TRANSPARENT_RGB"); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_TRANSPARENT_RED_VALUE, &i); | |
log_debug("EGL_TRANSPARENT_RED = 0x%02x", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_TRANSPARENT_GREEN_VALUE, &i); | |
log_debug("EGL_TRANSPARENT_GREEN = 0x%02x", i); | |
i = 0; | |
eglGetConfigAttrib(egl_display, egl_conf, EGL_TRANSPARENT_BLUE_VALUE, &i); | |
log_debug("EGL_TRANSPARENT_BLUE = 0x%02x", i); | |
} else { | |
log_debug("EGL_TRANSPARENT_TYPE = EGL_NONE"); | |
} | |
} | |
void print_shader_info_log(GLuint shader) { | |
GLint length; | |
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); | |
if (length) { | |
char* buffer = malloc(length); | |
glGetShaderInfoLog(shader, length, NULL, buffer); | |
log_info("%s", buffer); | |
free(buffer); | |
GLint success; | |
glGetShaderiv(shader, GL_COMPILE_STATUS, &success); | |
if (success != GL_TRUE) { | |
log_fatal("aborting because shader compilation failed."); | |
exit(1); | |
} | |
} | |
} | |
GLuint load_shader(const char *shaderSource, GLenum type) { | |
GLuint shader = glCreateShader(type); | |
glShaderSource(shader, 1, &shaderSource, NULL); | |
glCompileShader(shader); | |
print_shader_info_log(shader); | |
return shader; | |
} | |
static GLuint getUniformLocationOrDie(GLuint shaderProgram, const char *name) { | |
GLuint loc = glGetUniformLocation(shaderProgram, name); | |
if (loc < 0) { | |
log_fatal("Couldn't find uniform %s", name); | |
exit(1); | |
} | |
return loc; | |
} | |
static GLuint getAttribLocationOrDie(GLuint shaderProgram, const char *name) { | |
GLuint loc = glGetAttribLocation(shaderProgram, name); | |
if (loc < 0) { | |
log_fatal("Couldn't find attribute %s", name); | |
exit(1); | |
} | |
return loc; | |
} | |
int main(int argc, char **argv) { | |
Display *x_display = XOpenDisplay(NULL); | |
if (x_display == NULL) { | |
log_fatal("cannot connect to X server"); | |
return 1; | |
} | |
Window root = DefaultRootWindow(x_display); // get the root window (usually the whole screen) | |
int root_x, root_y; | |
unsigned int root_w, root_h, root_border_width, root_depth; | |
Window root_again; | |
XGetGeometry(x_display, root, &root_again, &root_x, &root_y, &root_w, &root_h, &root_border_width, &root_depth); | |
log_info("Matching X11 root window geometry: +%d,%d %dx%d border %d, %dbpp\n", | |
root_x, root_y, root_w, root_h, root_border_width, root_depth); | |
XSetWindowAttributes swa; | |
swa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask; | |
Window win = XCreateWindow(x_display, root, | |
0, 0, root_w, root_h, 0, | |
CopyFromParent, InputOutput, | |
CopyFromParent, CWEventMask, | |
&swa); | |
XSetWindowAttributes xattr; | |
xattr.override_redirect = False; | |
XChangeWindowAttributes(x_display, win, CWOverrideRedirect, &xattr); | |
/* This fails when there is no window manager running | |
Atom atom; | |
atom = XInternAtom(x_display, "_NET_WM_STATE_FULLSCREEN", True); | |
XChangeProperty( | |
x_display, win, | |
XInternAtom(x_display, "_NET_WM_STATE", True), | |
XA_ATOM, 32, PropModeReplace, | |
(unsigned char*) &atom, 1); | |
*/ | |
int one = 1; | |
XChangeProperty( | |
x_display, win, | |
XInternAtom ( x_display, "_HILDON_NON_COMPOSITED_WINDOW", False ), | |
XA_INTEGER, 32, PropModeReplace, | |
(unsigned char*) &one, 1); | |
XWMHints hints; | |
hints.input = True; | |
hints.flags = InputHint; | |
XSetWMHints(x_display, win, &hints); | |
XMapWindow(x_display, win); // makes the window visible on the screen | |
XStoreName(x_display, win, argv[0]); | |
//// get identifiers for the provided atom name strings | |
Atom wm_state = XInternAtom(x_display, "_NET_WM_STATE", False); | |
Atom fullscreen = XInternAtom(x_display, "_NET_WM_STATE_FULLSCREEN", False); | |
XEvent xev; | |
memset(&xev, 0, sizeof(xev)); | |
xev.type = ClientMessage; | |
xev.xclient.window = win; | |
xev.xclient.message_type = wm_state; | |
xev.xclient.format = 32; | |
xev.xclient.data.l[0] = 1; | |
xev.xclient.data.l[1] = fullscreen; | |
XSendEvent( // set up event mask (which events we want to receive) | |
x_display, | |
DefaultRootWindow(x_display), | |
False, | |
SubstructureNotifyMask, | |
&xev); | |
/////// the egl part ////////////////////////////////////////////////////////////////// | |
// egl provides an interface to connect the graphics related functionality of openGL ES | |
// with the windowing interface and functionality of the native operation system (X11 | |
// in our case.) | |
EGLDisplay egl_display = eglGetDisplay((EGLNativeDisplayType) x_display); | |
if (egl_display == EGL_NO_DISPLAY) { | |
log_fatal("Got no EGL display."); | |
return 1; | |
} | |
EGLint egl_version_major, egl_version_minor; | |
if (!eglInitialize(egl_display, &egl_version_major, &egl_version_minor)) { | |
log_fatal("Unable to initialize EGL"); | |
return 1; | |
} | |
log_info("Initialized EGL version %d.%d", egl_version_major, egl_version_minor); | |
EGLint egl_config_constraints[] = { | |
EGL_RED_SIZE, 8, | |
EGL_GREEN_SIZE, 8, | |
EGL_BLUE_SIZE, 8, | |
EGL_ALPHA_SIZE, 0, | |
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, | |
EGL_CONFIG_CAVEAT, EGL_NONE, | |
EGL_NONE | |
}; | |
EGLConfig egl_conf; | |
EGLint num_config; | |
if (!eglChooseConfig(egl_display, egl_config_constraints, &egl_conf, 1, &num_config)) { | |
log_fatal("Failed to choose config (eglError: %s)", eglGetError()); | |
return 1; | |
} | |
if (num_config != 1) { | |
log_fatal("Didn't get exactly one config, but %d", num_config); | |
return 1; | |
} | |
EGLSurface egl_surface = eglCreateWindowSurface(egl_display, egl_conf, win, NULL); | |
if (egl_surface == EGL_NO_SURFACE) { | |
log_fatal("Unable to create EGL surface (eglError: %s)", eglGetError()); | |
return 1; | |
} | |
//// egl-contexts collect all state descriptions needed required for operation | |
EGLint ctxattr[] = { | |
EGL_CONTEXT_CLIENT_VERSION, 2, | |
EGL_NONE | |
}; | |
EGLContext egl_context = eglCreateContext(egl_display, egl_conf, EGL_NO_CONTEXT, ctxattr); | |
if (egl_context == EGL_NO_CONTEXT) { | |
log_fatal("Unable to create EGL context (eglError: %s)", eglGetError()); | |
return 1; | |
} | |
//// associate the egl-context with the egl-surface | |
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); | |
log_egl_details(egl_display, egl_conf); | |
EGLint queriedRenderBuffer; | |
if (eglQueryContext(egl_display, egl_context, EGL_RENDER_BUFFER, &queriedRenderBuffer)) { | |
switch (queriedRenderBuffer) { | |
case EGL_SINGLE_BUFFER: log_info("Render Buffer: EGL_SINGLE_BUFFER"); break; | |
case EGL_BACK_BUFFER: log_info("Render Buffer: EGL_BACK_BUFFER"); break; | |
case EGL_NONE: log_info("Render Buffer: EGL_NONE"); break; | |
default: log_info("Render Buffer: unknown value %d", queriedRenderBuffer); break; | |
} | |
} else { | |
log_error("Failed to query EGL_RENDER_BUFFER: %d", eglGetError()); | |
} | |
if (!eglSwapInterval(egl_display, 1)) { | |
log_warn("eglSwapInterval failed: %d", eglGetError()); | |
} else { | |
log_info("Set swap interval"); | |
} | |
/////// the openGL part /////////////////////////////////////////////////////////////// | |
GLuint vertexShader = load_shader | |
(" precision mediump float;" | |
"\n " | |
"\n // Model-view-projection transformation matrix" | |
"\n uniform mat4 mvp_matrix;" | |
"\n " | |
"\n // Incoming" | |
"\n attribute vec2 a_position; // vertex position in 2d Model Space" | |
"\n attribute vec3 a_color; // color of the rectangular segment this vertex is part of" | |
"\n " | |
"\n // Outgoing interpolated values for the fragment shader" | |
"\n varying vec3 color;" | |
"\n " | |
"\n void main() {" | |
"\n gl_Position = mvp_matrix * vec4(a_position, 0, 1);" | |
"\n color = a_color;" | |
"\n }", GL_VERTEX_SHADER); | |
GLuint fragmentShader = load_shader | |
(" precision mediump float;" | |
"\n " | |
"\n varying vec3 color;" | |
"\n " | |
"\n void main() {" | |
"\n gl_FragColor = vec4(color, 1.0);" | |
"\n }", GL_FRAGMENT_SHADER); | |
GLuint shaderProgram = glCreateProgram(); | |
glAttachShader(shaderProgram, vertexShader); | |
glAttachShader(shaderProgram, fragmentShader); | |
glLinkProgram(shaderProgram); | |
glUseProgram(shaderProgram); | |
// OpenGL Rendering Parameters | |
glEnable(GL_BLEND); | |
glBlendFunc(GL_ONE, GL_ONE); | |
XWindowAttributes gwa; | |
XGetWindowAttributes(x_display, win, &gwa); | |
glViewport(0, 0, gwa.width, gwa.height); | |
glClearColor(0.0, 0.0, 0.0, 1.0); | |
float attribute_array[] = | |
// x y r g b | |
{ 0.0, -1, 1, 0, 0, | |
0.0, 1, 0, 1, 1, | |
0.2, -1, 0, 0, 1, | |
0.2, 1, 0, 1, 0 | |
}; | |
size_t attribute_stride = 5 * sizeof(float); | |
// figure out a display matrix that doesn't stretch the image | |
float display_aspect = ((float) gwa.width) / ((float) gwa.height); | |
float scale = 1.5; | |
float xscale = scale / display_aspect; | |
float yscale = scale; | |
float display_transform_cols[4*4] = | |
{ // WARNING: THIS IS A COLUMN MATRIX SO BE CAREFUL IF YOU GO OFF THE DIAGONAL! | |
xscale, 0, 0, 0, | |
0, yscale, 0, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 1 | |
}; | |
GLuint mvp_matrix_loc = getUniformLocationOrDie(shaderProgram, "mvp_matrix"); | |
glUniformMatrix4fv(mvp_matrix_loc, 1, GL_FALSE, display_transform_cols); | |
GLuint position_loc = getAttribLocationOrDie(shaderProgram, "a_position"); | |
GLuint color_loc = getAttribLocationOrDie(shaderProgram, "a_color"); | |
glVertexAttribPointer(position_loc, 2, GL_FLOAT, GL_FALSE, attribute_stride, (void *) &attribute_array); | |
glVertexAttribPointer(color_loc, 3, GL_FLOAT, GL_FALSE, attribute_stride, ((void *) &attribute_array) + 2 * sizeof(float)); | |
glEnableVertexAttribArray(position_loc); | |
glEnableVertexAttribArray(color_loc); | |
float speed = 0.01; | |
float bar_width = 0.2; | |
// render loop | |
for (;;) { | |
display_transform_cols[12] += speed; | |
if (display_transform_cols[12] < -1 || display_transform_cols[12] > (1.0 - bar_width)) { | |
speed *= -1; | |
} | |
glUniformMatrix4fv(mvp_matrix_loc, 1, GL_FALSE, display_transform_cols); | |
glClear(GL_COLOR_BUFFER_BIT); | |
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
eglSwapBuffers(egl_display, egl_surface); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2017 rxi | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to | |
* deal in the Software without restriction, including without limitation the | |
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
* sell copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
* IN THE SOFTWARE. | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdarg.h> | |
#include <string.h> | |
#include <time.h> | |
#include "log.h" | |
static struct { | |
void *udata; | |
log_LockFn lock; | |
FILE *fp; | |
int level; | |
int quiet; | |
} L; | |
static const char *level_names[] = { | |
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" | |
}; | |
#ifdef LOG_USE_COLOR | |
static const char *level_colors[] = { | |
"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m" | |
}; | |
#endif | |
static void lock(void) { | |
if (L.lock) { | |
L.lock(L.udata, 1); | |
} | |
} | |
static void unlock(void) { | |
if (L.lock) { | |
L.lock(L.udata, 0); | |
} | |
} | |
void log_set_udata(void *udata) { | |
L.udata = udata; | |
} | |
void log_set_lock(log_LockFn fn) { | |
L.lock = fn; | |
} | |
void log_set_fp(FILE *fp) { | |
L.fp = fp; | |
} | |
void log_set_level(int level) { | |
L.level = level; | |
} | |
void log_set_quiet(int enable) { | |
L.quiet = enable ? 1 : 0; | |
} | |
void log_log(int level, const char *file, int line, const char *fmt, ...) { | |
if (level < L.level) { | |
return; | |
} | |
/* Acquire lock */ | |
lock(); | |
/* Get current time */ | |
time_t t = time(NULL); | |
struct tm *lt = localtime(&t); | |
/* Log to stderr */ | |
if (!L.quiet) { | |
va_list args; | |
char buf[16]; | |
buf[strftime(buf, sizeof(buf), "%H:%M:%S", lt)] = '\0'; | |
#ifdef LOG_USE_COLOR | |
fprintf( | |
stderr, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ", | |
buf, level_colors[level], level_names[level], file, line); | |
#else | |
fprintf(stderr, "%s %-5s %s:%d: ", buf, level_names[level], file, line); | |
#endif | |
va_start(args, fmt); | |
vfprintf(stderr, fmt, args); | |
va_end(args); | |
fprintf(stderr, "\n"); | |
fflush(stderr); | |
} | |
/* Log to file */ | |
if (L.fp) { | |
va_list args; | |
char buf[32]; | |
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", lt)] = '\0'; | |
fprintf(L.fp, "%s %-5s %s:%d: ", buf, level_names[level], file, line); | |
va_start(args, fmt); | |
vfprintf(L.fp, fmt, args); | |
va_end(args); | |
fprintf(L.fp, "\n"); | |
fflush(L.fp); | |
} | |
/* Release lock */ | |
unlock(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (c) 2017 rxi | |
* | |
* This library is free software; you can redistribute it and/or modify it | |
* under the terms of the MIT license. See `log.c` for details. | |
*/ | |
#ifndef LOG_H | |
#define LOG_H | |
#include <stdio.h> | |
#include <stdarg.h> | |
#define LOG_VERSION "0.1.0" | |
typedef void (*log_LockFn)(void *udata, int lock); | |
enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL }; | |
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) | |
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) | |
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) | |
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) | |
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) | |
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) | |
void log_set_udata(void *udata); | |
void log_set_lock(log_LockFn fn); | |
void log_set_fp(FILE *fp); | |
void log_set_level(int level); | |
void log_set_quiet(int enable); | |
void log_log(int level, const char *file, int line, const char *fmt, ...); | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment