Using OpenGL ES 2.0 on the Raspberry Pi without X windows.

Posted on April 27, 2012

29


[EDIT: This post contains information from the early days of the RasPi and may not be that relevant to its current software build. I don’t actually have anything to plug my RasPi into anymore so I cannot check or update any of the video/OpenGLES work. (I have laptops but no TV – go figure.) Please see the comments for some helpful updates and potential links if this no longer works.]

The Raspberry Pi has a surprisingly sophisticated video core with a nice implementation of the OpenGL ES 2.0 interface for 3D graphics. It will be exciting to see what people do with it, but there is a small hurdle in the way at the moment for casual coders. The traditional way (on Linux) is to use the X window system to provide a surface on which the OpenGL/OpenGL ES surface can be rendered. At this moment, the implementation of X for the Raspberry Pi is unable to do this, and so you can’t use X to provide your surface. I am very sure that this support will be added in time, but until then, people wanting to play with this will have to connect up the OpenGL ES side of things to the display by another method.

A big thanks to ‘friggle'(IRC) aka ‘asb’ on github and the forums for helping me out and showing me what I might be doing wrong!

Using the display directly (technical)

Before I go any further, I want to point out that for the vast majority of people, this will be a temporary solution and in time, you’ll be able to use the conventional Linux/X11 method for getting a surface to draw to. For others, they may wish to skip X11 entirely, and I’m sure that the following method will be improved in time. See below this part for link to the opengles-book examples ported with this method to run on the Raspberry Pi.

I won’t be going into the intricacies of OpenGL ES 2.0 and how to write those. I recommend the OpenGL ES 2.0 programming guide as I cut my OpenGL teeth on the Red book and my brain is already wired up this way! Please leave links to good learning resources in the  comments.

First step: setting up a connection to the hardware ‘pipe’, a connection to VCHIQ in your C source code.

import "bcm_host.h"
...
// early on, perhaps at the start of your 'main.c':
bcm_host_init();

The bcm_host.h file is within the ‘/opt/vc/include‘ directory on the Pi, so you will need to add that to your Makefile. The correct EGL libraries are also in a similar location ( ‘/opt/vc/lib‘) so be sure to add that into your linker options or LD path too.

Next, create a display surface that you can use to render the EGL surface onto.  This is the key setup to make! If the settings here are off then your might find that your EGL code will run, and you’ll get console output, but you won’t get any 3D visuals!

   static EGL_DISPMANX_WINDOW_T nativewindow;

   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;

   int display_width;
   int display_height;

   // create an EGL window surface, passing context width/height
   success = graphics_get_display_size(0 /* LCD */, 
                        &display_width, &display_height);
   if ( success < 0 )
   {
      return EGL_FALSE;
   }

   // You can hardcode the resolution here:
   display_width = 640;
   display_height = 480;

   dst_rect.x = 0;
   dst_rect.y = 0;
   dst_rect.width = display_width;
   dst_rect.height = display_height;

   src_rect.x = 0;
   src_rect.y = 0;
   src_rect.width = display_width << 16;
   src_rect.height = display_height << 16;

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

   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*/);

   nativewindow.element = dispman_element;
   nativewindow.width = display_width;
   nativewindow.height = display_height;
   vc_dispmanx_update_submit_sync( dispman_update );

...
   // Pass the window to the display that have been created 
   // to the esContext:
   esContext->hWnd = &nativewindow;

The vc_*, dispman_*, and other unusual symbols are taken from the bcm_host.h header so make sure that that is included in the source file that you use to create the context.

Final tasks: alter eglGetDisplay and eglCreateWindowSurface initialisation calls.

Instead of casting an X display or something similar in eglGetDisplay, you can simply do the following:

display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if ( display == EGL_NO_DISPLAY )
{
  return EGL_FALSE;
}

The next change is to eglCreateWindowSurface. Remember the esContext->hWnd pointer we set earlier? We’ll use that now:

   surface = eglCreateWindowSurface(display, config, 
                         (EGLNativeWindowType)hWnd, NULL);
   if ( surface == EGL_NO_SURFACE )
   {
      return EGL_FALSE;
   }

...
   // 'context' is returned by a "eglCreateContext" call

   // Make the context current
   if ( !eglMakeCurrent(display, surface, surface, context) )
   {
      return EGL_FALSE;
   }

That seems to be all you need to access and display OpenGL ES visuals directly on the Raspberry Pi without using X. Why some of the steps are the way they are I can only guess, but this at least works for me!

A port of the OpenGL ES 2.0 Programming guide examples

https://github.com/benosteen/opengles-book-samples/tree/master/Raspi

Most of the key changes are in the Common/esUtil.c file where the display surface and so on are set up. This is currently hardcoded to be a 640×480 resolution window, that will appear at the bottom-left of the screen.

To build this on your Raspberry Pi:

Get the source files onto your Pi. Either:

  • wget --no-check-certificate https://github.com/benosteen/opengles-book-samples/tarball/master; tar -xvzf master Or,
  • sudo apt-get install git-core; git clone git://github.com/benosteen/opengles-book-samples.git

Then:

pi@raspberrypi:~$ cd opengles-book-samples/

pi@raspberrypi:~/opengles-book-samples$ cd Raspi/

pi@raspberrypi:~/opengles-book-samples/Raspi$ make

gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_2/Hello_Triangle/Hello_Triangle.c -o Chapter_2/Hello_Triangle/CH02_HelloTriangle -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_8/Simple_VertexShader/Simple_VertexShader.c -o ./Chapter_8/Simple_VertexShader/CH08_SimpleVertexShader -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_9/Simple_Texture2D/Simple_Texture2D.c -o ./Chapter_9/Simple_Texture2D/CH09_SimpleTexture2D -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_9/MipMap2D/MipMap2D.c -o ./Chapter_9/MipMap2D/CH09_MipMap2D -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_9/Simple_TextureCubemap/Simple_TextureCubemap.c -o ./Chapter_9/Simple_TextureCubemap/CH09_TextureCubemap -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_9/TextureWrap/TextureWrap.c -o ./Chapter_9/TextureWrap/CH09_TextureWrap -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_10/MultiTexture/MultiTexture.c -o ./Chapter_10/MultiTexture/CH10_MultiTexture -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_11/Multisample/Multisample.c -o ./Chapter_11/Multisample/CH11_Multisample -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_11/Stencil_Test/Stencil_Test.c -o ./Chapter_11/Stencil_Test/CH11_Stencil_Test -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
gcc -DRPI_NO_X ./Common/esShader.c ./Common/esTransform.c ./Common/esShapes.c ./Common/esUtil.c ./Chapter_13/ParticleSystem/ParticleSystem.c -o ./Chapter_13/ParticleSystem/CH13_ParticleSystem -I./Common -I/opt/vc/include -lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib

Now, to test it:

pi@raspberrypi:~/opengles-book-samples/Raspi$ cd Chapter_2/Hello_Triangle
pi@raspberrypi:~/opengles-book-samples/Raspi/Chapter_2/Hello_Triangle$ 
       ./CH02_HelloTriangle 
 123 frames rendered in 2.0036 seconds -> FPS=61.3894

NB As no X server is involved, you can actually invoke the examples from an ssh connection and have them render out to whatever video device is connected to the Pi 🙂

Advertisement
Posted in: linux