Showing posts with label pingl. Show all posts
Showing posts with label pingl. 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.