Showing posts with label OpenGL ES 2. Show all posts
Showing posts with label OpenGL ES 2. Show all posts

Wednesday, 20 June 2012

OpenGL ES on the raspberry pi Pt 3 Creating a window

In the previous post I discussed the EGLWindow class, this class is designed as a framework for the user of the library to create consistent windows. This post will look at how we can use the EGLWindow class and extend it.

MyEGLWindow

This class is going to inherit from the main EGLWindow class then implement the two methods initializeGL and paintGL.
#ifndef MYGLWINDOW_H__
#define MYGLWINDOW_H__

#include "EGLWindow.h"
/// @brief this class create our window by inheriting the features of the EGL Window
class MyGLWindow : public EGLWindow
{
  public :
   /// @brief ctor
   /// @param[in] _config an optional configuration for the buffers etc
   MyGLWindow(EGLconfig *_config=0);
   /// @brief dtor will close down the vc and re-set EGL
   ~MyGLWindow();
   /// @brief the is the main drawing function should only be called once initalizeGL has
   /// been called
   virtual void paintGL();
  protected :
   /// @brief one time OpenGL initialisation
   virtual void initializeGL();
};
When using the class the constructor must always call the initializeGL method, as the EGLWindow class is called first this will mean that we have a valid OpenGL context and any GL calls are going to be associated with this context. In this example we just print out that the ctor has been called and then init gl.
MyGLWindow::MyGLWindow(EGLconfig *_config) : EGLWindow(_config)
{
std::cout<<"My GL Window Ctor\n";
srand(time(NULL));
// init GL in this case we are going to create some render buffers
// for colour and depth
initializeGL();
}
The next stage is to implement the initializeGL function, this is where you should do any one off configuration for OpenGL, in this case I'm just going to set the clear colour (which will change later in the draw function)
void MyGLWindow::initializeGL()
{
 // set the clear colour
 glClearColor(1,1,1,1);
}
Next the we will create the paintGL method, this is designed to be called within our main look each time the screen needs to be updated. In this case I'm going to set the screen clear colour and clear the screen.
void MyGLWindow::paintGL()
{
 // scale the colour based on the width
 float r=(float)rand()/(float)RAND_MAX;
 float g=(float)rand()/(float)RAND_MAX;
 float b=(float)rand()/(float)RAND_MAX;
 // set the clear colour
 glClearColor(r,g,b,1);
 // clear screen
 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 // to update we need to swap the buffers
 swapBuffers();
}
Once drawing is complete the swapBuffers() method must be called to tell OpenGL to swap the back buffer with the front to show the re-drawn surface.

Using the window

The following code will create an instance of the MyGLWindow class and execute a loop calling draw.
#include <iostream>
#include "MyGLWindow.h"
#include "bcm_host.h"


int main()
{
 atexit( bcm_host_deinit); 
  
 std::cout<<"starting GL test\n";
 bcm_host_init();
 std::cout<<"done bcm init\n";
 // here I create a config with RGB bit size 5,6,5 and no alpha
 EGLconfig *config = new EGLconfig();
 config->setRGBA(5,6,5,0);
 // set the depth buffer
 config->setDepth(16);
 // now create a new window using the default config
 MyGLWindow win(config);
 // now set the size of the screen in this case I'm going to do a
 // rectangle in the middle (if you don't call this you would get a full
 // screen rect by default)
 uint32_t w=win.getMaxWidth();
 uint32_t h=win.getMaxHeight();
 // set this to true to upscale the dst rect
 win.setUpscale(false);
 win.setScreen(w/4,h/4,w/2,h/2);
 int x,y;
 while(1)
 {
  win.paintGL();
  sleep(1);
 }
}
In this example we create a custom config with R/B of bit depth 5 and green bit depth 6 and a depth buffer of 16 bits. This is then passed to the MyGLWindow ctor and used as the config when creating the EGL window. Next we re-size the screen so that is is half the screen dimensions and centred. (This will call the destroySurface method mentioned in the previous post). Finally we loop calling paintGL for each update and sleeping for 1ms (use ctrl + C to exit the program). We need a Makefile to build this program and it needs to include several libraries to get it working, for more details on this see the blog post here For the full code grab this

OpenGL ES on the raspberry pi Pt 2 EGLWindow Class

In the previous post I created an EGLconfig class to allow the creation of an eglConfig for raspberry pi. In this post I will talk about the design and implementation of an EGLWindow class which allows the user to create a window and then extend the basic window for their own drawing.

EGLWindow 

This class will implement various functions to setup and create an OpenGL drawing context for the user. It is then the users responsibility to implement certain methods in the sub-class to do the basic initialisation of the OpenGL functions, then a drawing class which will be called each frame in the client program.
You will notice from the class diagram there are a number of methods and attributes which are either protected or private, along with several methods which are "pure virtual" this is to force the user of the class to implement them. Full source code for the .h file is here

The constructor takes an EGLConfig class as the main parameter, this by default is set to 0 so if one is not passed a default one will be created. This is shown in the following code
EGLWindow::EGLWindow(EGLconfig *_config)
{
 // toggle we don't yet have an active surface
 m_activeSurface=false;
 // set default to not upscale the screen resolution
 m_upscale=false;
 // set our display values to 0 (not once ported to cx11 will use nullptr but
 // current pi default compiler doesn't support it yet
 m_display=0;
 m_context=0;
 m_surface=0;

 // now find the max display size (we will use this later to assert if the user
 // defined sizes are in the correct bounds
 int32_t success = 0;
 success = graphics_get_display_size(0 , &m_width, &m_height);
 assert( success >= 0 );
 std::cout<<"max width and height "<<m_width<<" "<<m_height<<"\n";
 m_maxWidth=m_width;
 m_maxHeight=m_height;
 // if we have a user defined config we will use that else we need to create one
 if (_config == 0)
 {
  std::cout<<"making new config\n";
  m_config= new EGLconfig();
 }
 else
 {
  m_config=_config;
 }

}

The core method to this class is the makeSurface method. It will create our surface and configure internal class attributes to hold values needed for the drawing etc. It also calls the initializeGL method once the surface has been created to do one off configuration of OpenGL / class attributes.
void EGLWindow::makeSurface(uint32_t _x, uint32_t _y, uint32_t _w, uint32_t _h)
{
// this code does the main window creation
EGLBoolean result;

static EGL_DISPMANX_WINDOW_T nativeWindow;
// our source and destination rect for the screen
VC_RECT_T dstRect;
VC_RECT_T srcRect;

// config you use OpenGL ES2.0 by default
static const EGLint contextAttributes[] =
{
 EGL_CONTEXT_CLIENT_VERSION, 2,
 EGL_NONE
};


// get an EGL display connection
m_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(m_display == EGL_NO_DISPLAY)
{
 std::cerr<<"error getting display\n";
 exit(EXIT_FAILURE);
}
// initialize the EGL display connection
int major,minor;

result = eglInitialize(m_display, &major, &minor);
std::cout<<"EGL init version "<<major<<"."<<minor<<"\n";
if(result == EGL_FALSE)
{
 std::cerr<<"error initialising display\n";
 exit(EXIT_FAILURE);
}
// get our config from the config class
m_config->chooseConfig(m_display);
EGLConfig config=m_config->getConfig();
// bind the OpenGL API to the EGL
result = eglBindAPI(EGL_OPENGL_ES_API);
if(result ==EGL_FALSE)
{
 std::cerr<<"error binding API\n";
 exit(EXIT_FAILURE);
}
// create an EGL rendering context
m_context = eglCreateContext(m_display, config, EGL_NO_CONTEXT, contextAttributes);
if(m_context ==EGL_NO_CONTEXT)
{
 std::cerr<<"couldn't get a valid context\n";
 exit(EXIT_FAILURE);
}
// create an EGL window surface the way this works is we set the dimensions of the srec
// and destination rectangles.
// if these are the same size there is no scaling, else the window will auto scale

dstRect.x = _x;
dstRect.y = _y;
if(m_upscale == false)
{
 dstRect.width = _w;
 dstRect.height = _h;
}
else
{
 dstRect.width = m_maxWidth;
 dstRect.height = m_maxHeight;
}
srcRect.x = 0;
srcRect.y = 0;
srcRect.width = _w << 16;
srcRect.height = _h << 16;
// whilst this is mostly taken from demos I will try to explain what it does
// there are very few documents on this ;-0
// open our display with 0 being the first display, there are also some other versions
// of this function where we can pass in a mode however the mode is not documented as
// far as I can see
m_dispmanDisplay = vc_dispmanx_display_open(0);
// now we signal to the video core we are going to start updating the config
m_dispmanUpdate = vc_dispmanx_update_start(0);
// this is the main setup function where we add an element to the display, this is filled in
// to the src / dst rectangles
m_dispmanElement = vc_dispmanx_element_add ( m_dispmanUpdate, m_dispmanDisplay,
 0, &dstRect, 0,&srcRect, DISPMANX_PROTECTION_NONE, 0 ,0,DISPMANX_NO_ROTATE);
// now we have created this element we pass it to the native window structure ready
// no create our new EGL surface
nativeWindow.element = m_dispmanElement;
nativeWindow.width =_w;
nativeWindow.height =_h;
// we now tell the vc we have finished our update
vc_dispmanx_update_submit_sync( m_dispmanUpdate );

// finally we can create a new surface using this config and window
m_surface = eglCreateWindowSurface( m_display, config, &nativeWindow, NULL );
assert(m_surface != EGL_NO_SURFACE);
// connect the context to the surface
result = eglMakeCurrent(m_display, m_surface, m_surface, m_context);
assert(EGL_FALSE != result);
m_activeSurface=true;
initializeGL();
}
The rest of the class is fairy straight forward, however it is worth mentioning the destroySurface method as it is used to allow re-creation / re-size of the window created. This is a private method and is used by the destructor and the resizeScreen method
void EGLWindow::destroySurface()
{
 if(m_activeSurface == true)
 {
  eglSwapBuffers(m_display, m_surface);
  // here we free up the context and display we made earlier
  eglMakeCurrent( m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
  eglDestroySurface( m_display, m_surface );
  eglDestroyContext( m_display, m_context );
  eglTerminate( m_display );
  m_activeSurface=false;
 }
}
The next post will show how these classes can be used to create a simple OpenGL window.

OpenGL ES on the raspberry pi Pt 1 EGLconfig Class

I've finally ported my graphics library ngl to the raspberry pi and I've released the source on google code. This post is going to be an introduction on getting started with the EGL window and the basic design behind the classes that have been added to pingl to facilitate this window creation.

Initial Design

As most of my desktop NGL demos are based on using Qt, I decided to make the config and running on NGL on the pi to be similar. To that ends I decided to design the EGLWindow class along the same lines as the QGLWidget class in Qt, this would require the splitting of the configuration and the window creation, as well as using inheritance to allow the user a common interface when writing client programs.

I also initially decided to write my own Mouse and Keyboard events stack but in the end decided against this as there are many pre-existing libraries around that allow the user to access this information. An in my case I will be using SDL.

EGLconfig class

The EGLconfig class is used to store the different attributes that may be set for an EGLConfig structure. (for more details see this blog post) The class will by default setup some useable parameters and also allow the user to set their own.

As you can see from the class diagram I use a std::map to store key value pairs for the attributes which will be turned into the appropriate structure when required. The full source for the header file can be seen here

By default the constructor creates the following attributes
EGLconfig::EGLconfig()
{
  m_attributes[EGL_RED_SIZE]=8;
  m_attributes[EGL_GREEN_SIZE]=8;
  m_attributes[EGL_BLUE_SIZE]=8;
  m_attributes[EGL_ALPHA_SIZE]=8;
  m_attributes[EGL_SURFACE_TYPE]=EGL_WINDOW_BIT;
}
To set one of the attributes by value the following code is used
void EGLconfig::setAttribute(EGLint _attrib,EGLint _value)
{
  assert(_attrib >= 0x3020 && _attrib <=0x3042);
  m_attributes[_attrib]=_value;
}
The assert is based on the values of the attribute defines in the egl.h file. This is a hard coded value and will need to be checked from time to time if the egl.h file changes and new attributes are added / removed (this is why enums should be used!) For convenience methods have been added for the most used attributes as shown below
void EGLconfig::setRGBA(EGLint _r,EGLint _g, EGLint _b, EGLint _a)
{
  m_attributes[EGL_RED_SIZE]=_r;
  m_attributes[EGL_GREEN_SIZE]=_g;
  m_attributes[EGL_BLUE_SIZE]=_b;
  m_attributes[EGL_ALPHA_SIZE]=_a;
}
void EGLconfig::setDepth(EGLint _d)
{
  m_attributes[EGL_DEPTH_SIZE]=_d;
}

void EGLconfig::setSurface(EGLint _s)
{
  m_attributes[EGL_SURFACE_TYPE]=_s;
}
Finally to choose the correct config we need to pass a constructed EGLDisplay to the class. We then build from our attribute list an array of key value pairs into a std::vector terminating the list with an EGL_NONE token. This is then passed to the eglChooseConfig function to setup the correct values.

void EGLconfig::chooseConfig(EGLDisplay _display)
{
  std::map<EGLint,EGLint>::const_iterator it;

  std::vector <EGLint> attribs;
  for ( it=m_attributes.begin() ; it != m_attributes.end(); it++ )
  {
    attribs.push_back((*it).first);
    attribs.push_back((*it).second);
  }
  attribs.push_back(EGL_NONE);

  EGLBoolean result;
  EGLint numConfig;

  // get an appropriate EGL frame buffer configuration
  result = eglChooseConfig(_display, &attribs[0], &m_config, 1, &numConfig);
  std::cout<<"got numCofig ="<<numConfig<<"\n";
  if( result==EGL_FALSE )
  {
  std::cerr<<"error setting config check your setting or if you have a valid display\n";
  exit(EXIT_FAILURE);
  }
  std::cout<<"choosing config\n";
}
The full source for the cpp file can be seen here In the next post we will create an EGLWindow class to use this.

Tuesday, 5 June 2012

An EGLWindow class

I've just written a simple wrapper for the Raspberry Pi EGL Window / Config and the video core functions, it is a simple set of C++ classes which allows the creation of windows and setting of size etc. I will write a fuller post soon, but I thought I would upload the code and a simple video now.

The design is based in part on how the Qt QGLWidget works.

You can grab the code from here


Thursday, 10 November 2011

iNGL

I've just release the initial version and a demo of the iNGL library for iOS devices (it's designed for iPhone 3Gs and above and both iPads as it uses OpenGLES 2.0)

You can see more here and I will do many a blog post about it soon

http://nccastaff.bournemouth.ac.uk/jmacey/GraphicsLib/iNGL/index.html