Raspberry Pi (Raspberry Pi) develops OpenGL ES applications based on the Raspbian operating system

Before the author developed OpenGL ES on the Raspberry Pi, I specially did some homework from the Internet. Currently, whether it is Raspberry Pi 3 or Zero, if you want to enable Broadcom Video Core GPU hardware acceleration, you can only use the official Raspbian OS system, and you need to use the system stored in /opt/ Private library under vc/. Therefore, we can only use OpenGL ES through EGL combined with the Raspberry Pi-specific DispManX runtime environment. Under the /opt/vc/src/hello_pi/ directory, there are various official demos, including OpenGL ES programs, OpenVG programs, and demos that use Video Core hardware video codec capabilities to process videos wait.

Since the /opt/vc/lib/ directory already contains all the basic libraries required by OpenGL ES, including the implementation of OpenGL ES and EGL, we don’t need to use apt-get to download and install other OpenGL ES related libraries. Among them, the bcm_host library is Broadcom’s private library for driving Video Core GPU, and the bcm_host_init function must be called before using the Video Core GPU.

For convenience, the author made a compiled shell file named build.sh. Its content is as follows:

gcc main.c -o rectangle -std=gnu11 -DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE -Wall - g -DHAVE_LIBOPENMAX=2 -DOMX -DOMX_SKIP64BIT -ftree-vectorize -pipe -DUSE_EXTERNAL_OMX -DHAVE_LIBBCM_HOST -DUSE_EXTERNAL_LIBBCM_HOST -DUSE_VCHIQ_ARM -Wno-psabi -L/opt/vc/lib/ -lbrcmGLESv2 -lbrcmEGL -lopenmaxil -lbcm_host -lvcos -lvchiq_arm -lpthread -lrt -lm -L/opt /vc/src/ hello_pi/libs/ilclient -L/opt/vc/src/hello_pi/libs/vgfont -I/opt/vc/include/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include /interface/vmcs_host/linux -I./ -I/opt/vc/src/hello_pi/libs/ilclient -I/opt/vc/src/hello_pi/libs/vgfont

When you use it, you only need to modify the source file name main.c and the output file name rectangle, and nothing else needs to be changed. After we edit the main.c source file, we can directly use bash build.sh to compile the script.

The code of main.c is given below:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <unistd.h>
#include <termio.h>
#include <fcntl.h>

#include <bcm_host.h>

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

#include <X11/Xlib.h>
#include <X11/Xutil.h>

static EGL_DISPMANX_WINDOW_T sNativewindow;
static DISPMANX_DISPLAY_HANDLE_T sDispmanDisplay;
static DISPMANX_ELEMENT_HANDLE_T sDispmanElement;
static EGLDisplay sDisplay;
static EGLSurface sSurface;
static EGLContext sContext;

static uint32_t screen_width = 0, screen_height = 0;

/// Object rotation angle
static int sRotateAngle = 0;

/// Indicate whether animation should be paused
static bool sShouldPauseAnimation = false;

/// Indicate whether the current program should exit the message run loop.
static bool sShouldTerminate = false;

/// Rectangle vertex coordinates
static const GLfloat sRectVertices[] = {<!-- -->
    // top left
    -0.4f, 0.4f,
    
    // bottom left
    -0.4f, -0.4f,
    
    // top right
    0.4f, 0.4f,
    
    // bottom right
    0.4f, -0.4f
};

/// Triangle vertex coordinates
static const GLfloat sTriangleVertices[] = {<!-- -->
    // top left
    0.0f, 0.4f,
    
    // bottom left
    -0.4f, -0.4f,
    
    // bottom right
    0.4f, -0.4f
};

static const GLfloat sColors[] = {<!-- -->
    //red
    1.0f, 0.0f, 0.0f, 1.0f,
    
    // green
    0.0f, 1.0f, 0.0f, 1.0f,
    
    //blue
    0.0f, 0.0f, 1.0f, 1.0f,
    
    //white
    1.0f, 1.0f, 1.0f, 1.0f
};

/// Setup OpenGL ES context and the models
static void SetupOGLStates(void)
{<!-- -->
    glViewport(0, 0, screen_width, screen_height);

    // Set background color and clear buffers
    glClearColor(0.15f, 0.25f, 0.35f, 1.0f);
    
    // Use smooth shade model, which is the default configuration
    glShadeModel(GL_SMOOTH);
    
    // Config the front face is in the counter clock-wise direction
    glFrontFace(GL_CCW);
    
    // Cull the back faces
    glCullFace(GL_BACK);

    // Enable back face culling.
    glEnable(GL_CULL_FACE);
    
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    
    // Setup projection transformation
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    float scale;
    if(screen_width < screen_height)
    {<!-- -->
        scale = (float)screen_width / (float)screen_height;
        glOrthof(-scale, scale, -1.0f, 1.0f, 1.0f, 3.0f);
    }
    else
    {<!-- -->
        scale = (float)screen_height / (float)screen_width;
        glOrthof(-1.0f, 1.0f, -scale, scale, 1.0f, 3.0f);
    }

    // The following steps are based on model view transformation
    glMatrixMode(GL_MODELVIEW);
}

/// EGL attribute list for context configuration
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,
    
    // In this demo, use OpenGL ES 1.x version
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
    
    // We will use multisample anti-aliasing
    EGL_SAMPLE_BUFFERS, 1,
    // Here specifies 4x samples
    EGL_SAMPLES, 4,

    //config complete
    EGL_NONE
};

/// Initialize the EGL context and do some other OpenGL ES configuration
static void InitializeEGLContext(void)
{<!-- -->
    // get an EGL display connection
    sDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    assert(sDisplay != EGL_NO_DISPLAY);

    // initialize the EGL display connection
    EGLBoolean result = eglInitialize(sDisplay, NULL, NULL);
    assert(EGL_FALSE != result);

    // get an appropriate EGL frame buffer configuration
    EGLConfig config;
    EGLint num_config;
    result = eglChooseConfig(sDisplay, attribute_list, &config, 1, &num_config);
    assert(EGL_FALSE != result);

    // create an EGL rendering context
    sContext = eglCreateContext(sDisplay, config, EGL_NO_CONTEXT, NULL);
    assert(sContext != EGL_NO_CONTEXT);

    // create an EGL window surface
    int success = graphics_get_display_size(0 /* LCD */, &screen_width, &screen_height);
    assert( success >= 0 );
   
    printf("Current screen resolution: %ux%u\\
", screen_width, screen_height);

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

    sDispmanDisplay = vc_dispmanx_display_open( 0 /* LCD */);
    DISPMANX_UPDATE_HANDLE_T dispman_update = vc_dispmanx_update_start( 0 );

    sDispmanElement = vc_dispmanx_element_add(dispman_update, sDispmanDisplay,
    0/*layer*/, &dst_rect, 0/*src*/,
     &src_rect, DISPMANX_PROTECTION_NONE, 0 /*alpha*/, 0/*clamp*/, 0/*transform*/);
      
    sNativewindow.element = sDispmanElement;
    sNativewindow.width = screen_width;
    sNativewindow.height = screen_height;
    vc_dispmanx_update_submit_sync( dispman_update );
      
    sSurface = eglCreateWindowSurface(sDisplay, config, & amp;sNativewindow, NULL);
    assert(sSurface != EGL_NO_SURFACE);

    // connect the context to the surface
    result = eglMakeCurrent(sDisplay, sSurface, sSurface, sContext);
    assert(EGL_FALSE != result);

    const char* vendor = (const char*)glGetString(GL_VENDOR);
    const char* renderer = (const char*)glGetString(GL_RENDERER);
    const char* version = (const char*)glGetString(GL_VERSION);
    printf("The vendor is: %s\\
", vendor);
    printf("The renderer is: %s\\
", renderer);
    printf("The GL version is: %s\\
", version);
    
    // Setup EGL swap interval as 60 FPS
    if(eglSwapInterval(sDisplay, 1))
        puts("Swap interval succeeded!");
}

static void redraw_scene(void)
{<!-- -->
    // Start with a clear screen
    glClear( GL_COLOR_BUFFER_BIT );

    // Draw rectangle
    glVertexPointer(2, GL_FLOAT, 0, sRectVertices);
    glColorPointer(4, GL_FLOAT, 0, sColors);
    
    glLoadIdentity();
    glTranslatef(-0.5f, 0.0f, -2.0f);
    glRotatef(sRotateAngle, 0.0f, 0.0f, 1.0f);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Draw triangle
    glVertexPointer(2, GL_FLOAT, 0, sTriangleVertices);
    glColorPointer(4, GL_FLOAT, 0, sColors);
    
    glLoadIdentity();
    glTranslatef(0.5f, 0.0f, -2.0f);
    glRotatef(-sRotateAngle, 0.0f, 0.0f, 1.0f);
    
    glDrawArrays(GL_TRIANGLES, 0, 3);
    
    // Update the rotation angle
    if(!sShouldPauseAnimation)
    {<!-- -->
        if(++sRotateAngle == 360)
            sRotateAngle = 0;
    }

    eglSwapBuffers(sDisplay, sSurface);
}

static void DestroyEGLContext(void)
{<!-- -->
    DISPMANX_UPDATE_HANDLE_T dispman_update;

    // clear screen
    glClear( GL_COLOR_BUFFER_BIT );
    eglSwapBuffers(sDisplay, sSurface);

    eglDestroySurface(sDisplay, sSurface);

    dispman_update = vc_dispmanx_update_start( 0 );
    int s = vc_dispmanx_element_remove(dispman_update, sDispmanElement);
    assert(s == 0);
    vc_dispmanx_update_submit_sync( dispman_update );
    s = vc_dispmanx_display_close(sDispmanDisplay);
    assert(s == 0);

    // Release OpenGL resources
    eglMakeCurrent(sDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
    eglDestroyContext(sDisplay, sContext);
    eglTerminate(sDisplay);
}

/// File handle for keyboard events
static int stdin_fd = -1;
static struct termios sTermOriginal;

/// Fetch the character if there's any key is pressed.
/// @return If any key is pressed, return the relevant character; Otherwise, return -1.
static int KeyPressed(void)
{<!-- -->
    // If this is the first time the function is called, change the stdin
    // stream so that we get each character when the keys are pressed and
    // and so that character aren't echoed to the screen when the keys are
    // pressed.
    if (stdin_fd == -1)
    {<!-- -->
        // Get the file descriptor associated with stdin stream.
        stdin_fd = fileno(stdin);

        // Get the terminal (termios) attritubets for stdin so we can
        // modify them and reset them before exiting the program.
        tcgetattr(stdin_fd, &sTermOriginal);

        // Copy the termios attributes so we can modify them.
        struct termios term;
        memcpy( &term, &sTermOriginal, sizeof(term));

        // Unset ICANON and ECHO for stdin. When ICANON is not set the
        // input is in noncanonical mode. In noncanonical mode input is
        // available as each key is pressed. In canonical mode input is
        // only available after the enter key is pressed. Unset ECHO so that
        // the characters aren't echoed to the screen when keys are pressed.
        // See the termios(3) man page for more information.
        term.c_lflag &= ~(ICANON|ECHO);
        tcsetattr(stdin_fd, TCSANOW, & term);

        // Turn off buffering for stdin. We want to get the characters
        // immediately. We don't want the characters to be buffered.
        setbuf(stdin, NULL);
    }

    int character = -1;

    // Get the number of characters that are waiting to be read.
    int characters_buffered = 0;
    ioctl(stdin_fd, FIONREAD, & characters_buffered);

    if (characters_buffered == 1)
    {<!-- -->
        // There is only one character to be read. Read it in.
        character = fgetc(stdin);
    }
    else if (characters_buffered > 1)
    {<!-- -->
        // There is more than one character to be read.
        // In this situation, just get the last read character
        while (characters_buffered != 0)
        {<!-- -->
            character = fgetc(stdin);
            --characters_buffered;
        }
    }

    return character;
}

/// If keyPressed() has been called,
/// the terminal input has been changed for the stdin stream.
/// Put the attributes back the way we found them.
static void KeyboardReset(void)
{<!-- -->
    if(stdin_fd != -1)
        tcsetattr(stdin_fd, TCSANOW, &sTermOriginal);
}

//================================================== ================================

int main (void)
{<!-- -->
    // Initialize VideoCore GPU
    bcm_host_init();

    InitializeEGLContext();
    
    SetupOGLStates();

    while (!sShouldTerminate)
    {<!-- -->
        redraw_scene();
        
        const int code = KeyPressed();
        if(code == 0x20)
            sShouldPauseAnimation = !sShouldPauseAnimation;
            
        sShouldTerminate = code == '\\
' || code == 0x1b;
    }
    
    KeyboardReset();

    DestroyEGLContext();
    
    puts("\\
Application closed");

    return 0;
}

In this demo, we must enable it through the command line, otherwise it cannot listen to keyboard key messages. We press Enter or ESC to exit the program, and press Space to pause the rotation animation.