Upto: Table of Contents of full book "Programming the Raspberry Pi GPU"

EGL on the Raspberry Pi

EGL provides a base for graphics programming, common to many systems such as OpenCL, OpenGL, OpenGL ES or OpenVG. It is system neutral and for the RPi is the layer above Dispmanx.

Resources

Files

Files used are here

Overview

From the EGL specification "EGL [is] an interface between rendering APIs such as OpenCL, OpenGL, OpenGL ES or OpenVG (referred to collectively as client APIs) and one or more underlying platforms (typically window systems such as X11)". It is not intended that application programmers write directly to EGL, instead they should use one of the APIs such as OpenGL.

Each underlying platform will have a means of rendering EGL surfaces. In this chapter we need to look at how EGL surfaces are linked to Dispmanx surfaces and how windows are built. We won't actually draw into any windows, because that is best done using e.g. OpenGL. That will be done in a later chapter.

Why EGL? From the Wayland FAQ : " EGL is the only GL binding API that lets us avoid dependencies on existing window systems, in particular X. GLX obviously pulls in X dependencies and only lets us set up GL on X drawables. The alternative is to write a Wayland specific GL binding API, say, WaylandGL. A more subtle point is that libGL.so includes the GLX symbols, so linking to that library will pull in all the X dependencies. This means that we can't link to full GL without pulling in the client side of X, so we're using GLES2 for now. Longer term, we'll need a way to use full GL under Wayland. "

From the RPi viewpoint, EGL forms the link between native Dispmanx windows and the OpenGL ES API.

Initialising EGL

EGL has a display that it writes on. The display is built on a native display, and is obtained by the call eglGetDisplay. The EGL platform is then initialised using eglInitialize.

Typically an EGL display will support a number of configurations. For example, a pixel may be 16 bits (5 red, 5 blue and 6 green), 24 bits (8 red, 8 green and 8 blue) or 32 bits (8 extra bits for alpha transparency). An application will specify certain parameters such as the minimum size of a red pixel, and can then access the array of matching configurations using eglChooseConfig. The attributes of a configuration can be queried using eglGetConfigAttrib. One configuration should be chosen before proceeding.

The program info.c lists the possible configurations that are available to EGL:

      
/*
 * code stolen from openGL-RPi-tutorial-master/encode_OGL/
 */

#include <stdio.h>
#include <assert.h>
#include <math.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>

typedef struct
{
    uint32_t screen_width;
    uint32_t screen_height;

    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig config;
} EGL_STATE_T;


EGL_STATE_T state, *p_state = &state;

void printConfigInfo(int n, EGLDisplay display, EGLConfig *config) {
    int size;

    printf("Configuration %d is\n", n);

    eglGetConfigAttrib(display,
		       *config, EGL_RED_SIZE, &size);
    printf("  Red size is %d\n", size);
    eglGetConfigAttrib(display,
		       *config, EGL_BLUE_SIZE, &size);
    printf("  Blue size is %d\n", size);
    eglGetConfigAttrib(display,
		       *config, EGL_GREEN_SIZE, &size);
    printf("  Green size is %d\n", size);
    eglGetConfigAttrib(display,
		       *config, EGL_BUFFER_SIZE, &size);
    printf("  Buffer size is %d\n", size);

   eglGetConfigAttrib(display,
		       *config,  EGL_BIND_TO_TEXTURE_RGB , &size);
   if (size == EGL_TRUE)
       printf("  Can be bound to RGB texture\n");
   else
       printf("  Can't be bound to RGB texture\n");

   eglGetConfigAttrib(display,
		       *config,  EGL_BIND_TO_TEXTURE_RGBA , &size);
   if (size == EGL_TRUE)
       printf("  Can be bound to RGBA texture\n");
   else
       printf("  Can't be bound to RGBA texture\n");
}

void init_egl(EGL_STATE_T *state)
{
    EGLBoolean result;
    EGLint num_configs;

    EGLConfig *configs;

    // get an EGL display connection
    state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    // initialize the EGL display connection
    result = eglInitialize(state->display, NULL, NULL);
    if (result == EGL_FALSE) {
	fprintf(stderr, "Can't initialise EGL\n");
	exit(1);
    }

    eglGetConfigs(state->display, NULL, 0, &num_configs);
    printf("EGL has %d configs\n", num_configs);

    configs = calloc(num_configs, sizeof *configs);
    eglGetConfigs(state->display, configs, num_configs, &num_configs);
    
    int i;
    for (i = 0; i < num_configs; i++) {
	printConfigInfo(i, state->display, &configs[i]);
    }
}

int
main(int argc, char *argv[])
{
    init_egl(p_state);

    return 0;
}

    

A portion of the output is

	
EGL has 28 configs
Configuration 0 is
  Red size is 8
  Blue size is 8
  Green size is 8
  Buffer size is 32
  Can't be bound to RGB texture
  Can be bound to RGBA texture
Configuration 1 is
  Red size is 8
  Blue size is 8
  Green size is 8
  Buffer size is 24
  Can be bound to RGB texture
  Can be bound to RGBA texture
	
      

Creating a rendering context

Each configuration will support one or more client APIs such as OpenGL. The API is usually requested through the configuration attribute EGL_RENDERABLE_TYPE which should have a value such as EGL_OPENGL_ES2_BIT.

In addition to a configuration, each application needs one or more contexts. Each context defines a level of the API that will be used for rendering. Examples typically use a level of 2, and a context is created using eglCreateContext.

Typical code to perform these steps is

	
init_egl() {
    EGLint major, minor, count, n, size;
    EGLConfig *configs;
    int i;
    EGLint config_attribs[] = {
	EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
	EGL_RED_SIZE, 8,
	EGL_GREEN_SIZE, 8,
	EGL_BLUE_SIZE, 8,
	EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
	EGL_NONE
    };

    static const EGLint context_attribs[] = {
	EGL_CONTEXT_CLIENT_VERSION, 2,
	EGL_NONE
    };

   ...    

    display->context =
	eglCreateContext(display->display,
			 display->config,
			 EGL_NO_CONTEXT, context_attribs);

}

	
      

The program context.c takes the first available configuration and then creates a context

      
/*
 * code stolen from openGL-RPi-tutorial-master/encode_OGL/
 */

#include <stdio.h>
#include <assert.h>
#include <math.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>

typedef struct
{
    uint32_t screen_width;
    uint32_t screen_height;

    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig config;
} EGL_STATE_T;


EGL_STATE_T state, *p_state = &state;

void init_egl(EGL_STATE_T *state)
{
    EGLBoolean result;
    EGLint num_configs;

    static const EGLint attribute_list[] =
	{
	    EGL_RED_SIZE, 8,
	    EGL_GREEN_SIZE, 8,
	    EGL_BLUE_SIZE, 8,
	    EGL_ALPHA_SIZE, 8,
	    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
	    EGL_NONE
	};

    static const EGLint context_attributes[] =
	{
	    EGL_CONTEXT_CLIENT_VERSION, 2,
	    EGL_NONE
	};

    EGLConfig *configs;

    // get an EGL display connection
    state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    // initialize the EGL display connection
    result = eglInitialize(state->display, NULL, NULL);

    eglGetConfigs(state->display, NULL, 0, &num_configs);
    printf("EGL has %d configs\n", num_configs);

    configs = calloc(num_configs, sizeof *configs);
    eglGetConfigs(state->display, configs, num_configs, &num_configs);
    
    // get an appropriate EGL configuration - just use the first available
    result = eglChooseConfig(state->display, attribute_list, 
			     &state->config, 1, &num_configs);
    assert(EGL_FALSE != result);

    // Choose the OpenGL ES API
    result = eglBindAPI(EGL_OPENGL_ES_API);
    assert(EGL_FALSE != result);

    // create an EGL rendering context
    state->context = eglCreateContext(state->display, 
				      state->config, EGL_NO_CONTEXT, 
				      context_attributes);
    assert(state->context!=EGL_NO_CONTEXT);
    printf("Got an EGL context\n");
}

int
main(int argc, char *argv[])
{
    init_egl(p_state);

    return 0;
}

    

Creating an EGL drawing surface with Dispmanx

An EGL window needs to be created from a native window or surface. This is different for each system. For the RPi, we use a Dispmanx window, as created in the previous chapter. This is used to create an EGL drawing surface by eglCreateWindowSurface.

We take the EGL context creation from the last program and the Dispmanx window creation from the previous chapter and build an EGL window surface by

	
void egl_from_dispmanx(EGL_STATE_T *state, 
		       EGL_DISPMANX_WINDOW_T *nativewindow) {
    EGLBoolean result;

    state->surface = eglCreateWindowSurface(state->display, 
					    state->config, 
					    nativewindow, NULL );
    assert(state->surface != EGL_NO_SURFACE);

    // connect the context to the surface
    result = eglMakeCurrent(state->display, state->surface, state->surface, state->context);
    assert(EGL_FALSE != result);
}
	
      

Drawing into this surface is generally done by an API such as OpenGL, and the choice of this has been set in the EGL context. This is set for the drawing surfaces by eglMakeCurrent.

Following this, drawing can take place. For now, we only draw a big red rectangle occupying the screen. Once drawing is complete, the EGL layer renders the surface by swapping the drawing buffer using eglSwapBuffers.

The program is window.c

      
/*
 * code stolen from openGL-RPi-tutorial-master/encode_OGL/
 */

#include <stdio.h>
#include <assert.h>
#include <math.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>

#include <bcm_host.h>

typedef struct
{
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig config;

    EGL_DISPMANX_WINDOW_T nativewindow;
    DISPMANX_DISPLAY_HANDLE_T dispman_display;
} EGL_STATE_T;

EGL_STATE_T state = {
    .display = EGL_NO_DISPLAY,
    .surface = EGL_NO_SURFACE,
    .context = EGL_NO_CONTEXT
};
EGL_STATE_T *p_state = &state;

void init_egl(EGL_STATE_T *state)
{
    EGLint num_configs;
    EGLBoolean result;

    bcm_host_init();

    static const EGLint attribute_list[] =
	{
	    EGL_RED_SIZE, 8,
	    EGL_GREEN_SIZE, 8,
	    EGL_BLUE_SIZE, 8,
	    EGL_ALPHA_SIZE, 8,
	    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
	    EGL_NONE
	};

    static const EGLint context_attributes[] =
	{
	    EGL_CONTEXT_CLIENT_VERSION, 2,
	    EGL_NONE
	};

    // get an EGL display connection
    state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    // initialize the EGL display connection
    result = eglInitialize(state->display, NULL, NULL);

    // get an appropriate EGL frame buffer configuration
    result = eglChooseConfig(state->display, attribute_list, &state->config, 1, &num_configs);
    assert(EGL_FALSE != result);
    
    // Choose the OpenGL ES API
    result = eglBindAPI(EGL_OPENGL_ES_API);
    assert(EGL_FALSE != result);

    // create an EGL rendering context
    state->context = eglCreateContext(state->display, 
				      state->config, EGL_NO_CONTEXT, 
				      context_attributes);
    assert(state->context!=EGL_NO_CONTEXT);
}

void init_dispmanx(EGL_STATE_T *state) {
    EGL_DISPMANX_WINDOW_T *nativewindow = &p_state->nativewindow;  
    int32_t success = 0;   
    uint32_t screen_width;
    uint32_t screen_height;

    DISPMANX_ELEMENT_HANDLE_T dispman_element;
    DISPMANX_DISPLAY_HANDLE_T dispman_display;
    DISPMANX_UPDATE_HANDLE_T dispman_update;
    VC_RECT_T dst_rect;
    VC_RECT_T src_rect;

    bcm_host_init();

    // create an EGL window surface
    success = graphics_get_display_size(0 /* LCD */, 
					&screen_width, 
					&screen_height);
    assert( success >= 0 );

    dst_rect.x = 0;
    dst_rect.y = 0;
    dst_rect.width = screen_width;
    dst_rect.height = screen_height;

    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = screen_width << 16;
    src_rect.height = screen_height << 16;        

    dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
    dispman_update = vc_dispmanx_update_start( 0 );
    state->dispman_display = dispman_display;

    dispman_element = 
	vc_dispmanx_element_add(dispman_update, dispman_display,
				0/*layer*/, &dst_rect, 0/*src*/,
				&src_rect, DISPMANX_PROTECTION_NONE, 
				0 /*alpha*/, 0/*clamp*/, 0/*transform*/);

    // Build an EGL_DISPMANX_WINDOW_T from the Dispmanx window
    nativewindow->element = dispman_element;
    nativewindow->width = screen_width;
    nativewindow->height = screen_height;
    vc_dispmanx_update_submit_sync(dispman_update);
    assert(vc_dispmanx_element_remove(dispman_update, dispman_element) == 0);

    printf("Got a Dispmanx window\n");
}

void egl_from_dispmanx(EGL_STATE_T *state) { 
    EGLBoolean result;

    state->surface = eglCreateWindowSurface(state->display, 
					    state->config, 
					    &p_state->nativewindow, NULL );
    assert(state->surface != EGL_NO_SURFACE);

    // connect the context to the surface
    result = eglMakeCurrent(state->display, state->surface, state->surface, state->context);
    assert(EGL_FALSE != result);
}

void cleanup(int s) {
    if (p_state->surface != EGL_NO_SURFACE &&
	eglDestroySurface(p_state->display, p_state->surface)) {
	printf("Surface destroyed ok\n");
    }
    if (p_state->context !=  EGL_NO_CONTEXT &&
	eglDestroyContext(p_state->display, p_state->context)) {
        printf("Main context destroyed ok\n");
    }
    if (p_state->display != EGL_NO_DISPLAY &&
	eglTerminate(p_state->display)) {
        printf("Display terminated ok\n");
    }
    if (eglReleaseThread()) {
        printf("EGL thread resources released ok\n");
    }
    if (vc_dispmanx_display_close(p_state->dispman_display) == 0) {
	printf("Dispmanx display rleased ok\n");
    }
    bcm_host_deinit();
    exit(s);
}

int
main(int argc, char *argv[])
{ 
    signal(SIGINT, cleanup);
    
    init_egl(p_state);

    init_dispmanx(p_state);
    
    egl_from_dispmanx(p_state);
    
    
    glClearColor(1.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glFlush();
    
    eglSwapBuffers(p_state->display, p_state->surface);
    
    sleep(4);
    
    cleanup(0);
    return 0;
}

    

Makefile

A Makefile to build these programs is

	
DMX_INC =  -I/opt/vc/include/ -I /opt/vc/include/interface/vmcs_host/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux
EGL_INC = 
INCLUDES = $(DMX_INC) $(EGL_INC)

CFLAGS = $(INCLUDES)
CPPFLAGS = -march=armv7-a -mtune=cortex-a7

DMX_LIBS =  -L/opt/vc/lib/ -lbcm_host -lvcos -lvchiq_arm -lpthread
EGL_LIBS = -L/opt/vc/lib -lEGL -lGLESv2 

LDFLAGS = $(DMX_LIBS) $(EGL_LIBS)

all: context info window
	
      

Garbage collection

C does not have automatic garbage collection like Java or other languages have. You have to do it yourself. The function cleanup manages this. It is also attached to a SIG_INT interrupt handler to clean up after ctl-C interrupts

	
void cleanup(int s) {
    if (p_state->surface != EGL_NO_SURFACE &&
	eglDestroySurface(p_state->display, p_state->surface)) {
	printf("Surface destroyed ok\n");
    }
    if (p_state->context !=  EGL_NO_CONTEXT &&
	eglDestroyContext(p_state->display, p_state->context)) {
        printf("Main context destroyed ok\n");
    }
    if (p_state->display != EGL_NO_DISPLAY &&
	eglTerminate(p_state->display)) {
        printf("Display terminated ok\n");
    }
    if (eglReleaseThread()) {
        printf("EGL thread resources released ok\n");
    }
    if (vc_dispmanx_display_close(p_state->dispman_display) == 0) {
	printf("Dispmanx display released ok\n");
    }
    bcm_host_deinit();
    exit(s);
}
	
      

Garbage collection is not complete: there seem to be a number of Khronos calls which don't go away:

	
$/opt/vc/bin/vcdbg reloc

Relocatable heap version 4 found at 0x1f000000
total space allocated is 492M, with 490M relocatable, 0 legacy and 2.3M offline
0 legacy blocks of size 2359296

free list at 0x3bf78d20
461M free memory in 1 free block(s)
largest free block is 461M bytes

0x1f000000: offline 2.3M
0x1f240000: free 461M
[  31] 0x3bf78d40: used  96K (refcount 1 lock count 1, size    98304, align  256, data 0x3bf78e00, D1rual) 'khrn_hw_bin_mem'
[  30] 0x3bf90e60: used  96K (refcount 1 lock count 1, size    98304, align  256, data 0x3bf90f00, D1rual) 'khrn_hw_bin_mem'
0x3bf90e60: corrupt trailer (space 98592 != 98593)
[  29] 0x3bfa8f80: used  96K (refcount 0 lock count 0, size    98304, align  256, data 0x3bfa9000, D1ruAl) 'khrn_hw_bin_mem'
0x3bfc10a0: free 25M
[  11] 0x3d885700: used   96 (refcount 0 lock count 0, size       22, align    1, data 0x3d885720, d1rual) 'khrn_hw_null_render'
0x3d885760: free 3.5K
[   4] 0x3d886540: used  576 (refcount 1 lock count 0, size      512, align    4, data 0x3d886560, d0rual) 'ILCS VC buffer pool'
[   3] 0x3d886780: used 3.5M (refcount 1 lock count 8, size  3618816, align 4096, data 0x3d887000, d1rual) 'ARM FB'
[   2] 0x3dbfafa0: used  16K (refcount 1 lock count 0, size    16384, align   32, data 0x3dbfafc0, d0ruAl) 'audioplus_tmp_buf'
[   1] 0x3dbfefe0: used 4.0K (refcount 1 lock count 0, size        0, align 4096, data 0x3dbff000, d1rual) 'camera fast alloc arena'
heap corruption detected
small allocs not requested
	
      

This appears to be a fixed allocation and does not increase over multiple runs of programs.

Conclusion

This chapter has looked at EGL which is the layer above Dispmanx and the layer below the major APIs of OpenGL ES, OpenVG, etc.

      
    

Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
" Programming AudioVideo on the Raspberry Pi GPU " by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/RPi/ .

If you like this book, please contribute using PayPal

Or Flattr me:
Flattr this book