Using FreeType library and OpenGL ES to render text

The following walkthrough takes you through the process of writing a native application that uses the FreeType library and OpenGL ES for text rendering.

To use FreeType and OpenGL ES for your text rendering in a native application:
  1. Create some basic variables you'll need for your application:
    int rc;                         /* a return code */
    screen_event_t screen_ev;       /* a screen event to handle */
    screen_context_t screen_ctx;    /* a connection to the screen windowing system */
    int vis = 1;                    /* an indicator if our window is visible */
    int pause = 0;                  /* an indicator if rendering is frozen */
    const int exit_area_size = 20;  /* a size of area on the window where a user can
                                     * contact (using a pointer) to exit this application */
    int size[2] = { 0, 0 };         /* the width and height of your window; will default to
                                     * the size of display since the window property wasn't
                                     * explicitly set */
    int val;                        /* a variable used to set/get window properties */
    int pos[2] = { 0, 0 };          /* the x,y position of your pointer */
                        
  2. Create your native context.
    rc = screen_create_context(&screen_ctx, 0);
                        
  3. Establish a connection to the EGL display.

    Before you can do any kind of rendering, you must establish a connection to a display.

    In this sample application, you will use the default display.

    egl_disp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
                        
  4. Initialize the EGL display.

    You will be able to do little with the EGL display until it's been initialized. The second and third arguments of eglInitialize() are both set to NULL because OpenGL ES 1.X is supported by all versions of EGL; therefore it isn't necessary to check for the major and minor version numbers.

    rc = eglInitialize(egl_disp, NULL, NULL);
                        
  5. Choose an EGL configuration.

    First establish your EGL configuration attributes:

    static EGLConfig egl_conf;      /* An aray of framebuffer configurations */
    int num_configs;                /* The number of framebuffer configurations from eglChooseConfig() */
    
    EGLint attrib_list[]= { EGL_RED_SIZE,        8,
                            EGL_GREEN_SIZE,      8,
                            EGL_BLUE_SIZE,       8,
                            EGL_BLUE_SIZE,       8,
                            EGL_SURFACE_TYPE,    EGL_WINDOW_BIT,
                            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
                            EGL_NONE};
                        

    Then you can use eglChooseConfigs() to choose your EGL configuration. The function eglChooseConfigs() is probably the most complicated function of EGL; there are many attributes that can be specified, each with its own matching rules, default value, and sorting order. It's easy to get confused with all the special rules ending up with the wrong configuration, or no configuration, without understanding why. Be aware of this fact when you are specifying your EGL configuration attributes.

    rc = eglChooseConfig(egl_disp, attrib_list, &egl_conf, 1, &num_configs))
                        
  6. Create an OpenGL ES rendering context.

    Now, create an OpenGL ES rendering context. Among other things, this context keeps track of the OpenGL ES state. You don't need to specify the current rendering API with the eglBindApi() function because OpenGL ES is the default rendering API.

    The third argument to eglCreateContext() is another EGL rendering context with which you wish to share data. Pass EGL_NO_CONTEXT to indicate that you won't need any of the textures or vertex buffer objects created in another EGL rendering context.

    The last argument to eglCreateContext() is an attribute list that you can use to specify an API version number. You would use it to override the EGL_CONTEXT_CLIENT_VERSION value from 1 to 2 if you were writing an OpenGL ES 2.X application.

    egl_ctx = eglCreateContext(egl_disp, egl_conf, EGL_NO_CONTEXT, NULL);
                        
  7. Create your native window.
    rc = screen_create_window(&screen_win, screen_ctx);
                        
  8. Set your native window properties.
    1. Set your window format and usage.
      int format = SCREEN_FORMAT_RGBX8888;
      usage = SCREEN_USAGE_OPENGL_ES1 | SCREEN_USAGE_ROTATION;
      
      rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_FORMAT, &format);
      rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage);
                                  
    2. Set your window buffer size.

      In this case, you are going to set the buffer size of your window based on the orientation of the display.

      int angle = atoi(getenv("ORIENTATION"));
      
      screen_display_mode_t screen_mode;
      rc = screen_get_display_property_pv(screen_disp, SCREEN_PROPERTY_MODE, (void**)&screen_mode);
      
      int size[2];
      rc = screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, size);
      
      int buffer_size[2] = {size[0], size[1]};
      
      if ((angle == 0) || (angle == 180)){
      	if (((screen_mode.width > screen_mode.height) && (size[0] < size[1])) ||
      		((screen_mode.width < screen_mode.height) && (size[0] > size[1])))
      		{
      			buffer_size[1] = size[0];
      			buffer_size[0] = size[1];
      	    }
      } else if ((angle == 90) || (angle == 270)){
      	if (((screen_mode.width > screen_mode.height) && (size[0] > size[1])) ||
      		((screen_mode.width < screen_mode.height && size[0] < size[1])))
      		{
      			buffer_size[1] = size[0];
      			buffer_size[0] = size[1];
      	    }
      }
      
      rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, buffer_size);
                          
    3. Set your window rotation.
      rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_ROTATION, &angle);
                                  
  9. Create your window buffers for rendering.
    rc = screen_create_window_buffers(screen_win, nbuffers);
                        
  10. Create the EGL on-screen rendering surface.

    Now that you've created a native platform window, you can use it to create an EGL on-screen rendering surface. You'll be able to use this surface as the target of your OpenGL ES rendering. You'll use the same EGL display and EGL configuration to create the EGL surface as you used to set the properties on your native window. The EGL configuration needs to be compatible with the one used to create the window.

    egl_surf = eglCreateWindowSurface(egl_disp, egl_conf, screen_win, NULL);
                        
  11. Bind the EGL context to the current rendering thread and to a draw-and-read surface.

    In this application, you want to draw to the EGL surface and not really care about where you read from. Since EGL doesn't allow specifying EGL_NO_SURFACE for only the read surface, you will use egl_surf for both drawing and reading. Once eglMakeCurrent() completes successfully, all OpenGL ES calls will be executed on the context and the surface you provided as arguments.

    rc = eglMakeCurrent(egl_disp, egl_surf, egl_surf, egl_ctx);
                        
  12. Set the EGL swap interval.

    The eglSwapInterval() function specifies the minimum number of video frame periods per buffer swap for the window associated with the current context. So, if the interval is 0, the application renders as fast as it can. Interval values of 1 or more limit the rendering to fractions of the display's refresh rate. (For example, 60, 30, 20, 15, etc. frames per second in the case of a display with a refresh rate of 60 Hz.)

    rc = eglSwapInterval(egl_disp, interval);
                        
  13. Calculate the display resolution based on the display size.
    int screen_phys_size[2] = { 0, 0 };
    
    screen_get_display_property_iv(screen_disp, SCREEN_PROPERTY_PHYSICAL_SIZE, screen_phys_size);
    
    /* If using a simulator, {0,0} is returned for physical size of the screen,
       so use 170 as the default dpi when this is the case. */
    if ((screen_phys_size[0] == 0) && (screen_phys_size[1] == 0)){
            return 170;
    } else{
            int screen_resolution[2] = { 0, 0 };
            screen_get_display_property_iv(screen_disp, SCREEN_PROPERTY_SIZE, screen_resolution);
    
            int diagonal_pixels = sqrt(screen_resolution[0] * screen_resolution[0]
                                  + screen_resolution[1] * screen_resolution[1]);
            int diagonal_inches = 0.0393700787 * sqrt(screen_phys_size[0] * screen_phys_size[0]
                                  + screen_phys_size[1] * screen_phys_size[1]);
            return (int)(diagonal_pixels / diagonal_inches);
    }
                        
  14. Load your font.

    Pick an appropriate font and ensure that you have the correct directory path to access that font.

    In this example, the function load_font() is a simple utility function. It is written to facilitate the steps to load and ready the font for use by OpenGL.

    font = load_font("/usr/fonts/font_repository/monotype/georgiab.ttf", 8, dpi);
                        
  15. Load your background texture.

    In this example, the function load_texture() is a simple utility function.

    load_texture("app/native/HelloWorld_smaller_bubble.png", NULL, NULL, &tex_x, &tex_y, &background)
                        
  16. Initialize the Graphics Library for 2D rendering.
    /* Query width and height of the window surface created by utility code */
    eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
    eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);
    
    width = (float) surface_width;
    height = (float) surface_height;
    
    glViewport(0, 0, (int) width, (int) height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof(0.0f, width / height, 0.0f, 1.0f, -1.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    /* Set world coordinates to coincide with screen pixels */
    glScalef(1.0f / height, 1.0f / height, 1.0f);
    
    float text_width, text_height;
    measure_text(font, "Hello world", &text_width, &text_height);
    pos_x = (width - text_width) / 2;
    pos_y = height / 2;
    
    /* Setup background polygon */
    vertices[0] = 0.0f;
    vertices[1] = 0.0f;
    vertices[2] = width;
    vertices[3] = 0.0f;
    vertices[4] = 0.0f;
    vertices[5] = height;
    vertices[6] = width;
    vertices[7] = height;
    
    tex_coord[0] = 0.0f;
    tex_coord[1] = 0.0f;
    tex_coord[2] = tex_x;
    tex_coord[3] = 0.0f;
    tex_coord[4] = 0.0f;
    tex_coord[5] = tex_y;
    tex_coord[6] = tex_x;
    tex_coord[7] = tex_y;
                        
  17. Create a screen event.

    This screen event you create will be used to retrieve event information so that each event can be handled.

    rc = screen_create_event(&screen_ev);
                        
  18. Create a main application loop that continues running until an explicit event to close the application (or a system error) occurs.

    This main application loop consists of two parts:

    The first part of the loop processes screen events.

    while (!screen_get_event(screen_ctx, screen_ev, vis ? 0 : ~0))
    {
    	rc = screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_TYPE, &val);
    	if (rc || val == SCREEN_EVENT_NONE)
    	{
    		break;
    	}
    	switch (val) {
    	   case SCREEN_EVENT_CLOSE:
    		  goto end;
    	   case SCREEN_EVENT_PROPERTY:
                screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_NAME, &val);
    			 switch (val) {
    			 	case SCREEN_PROPERTY_VISIBLE:
    			 		screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_VISIBLE, &vis);
    					break;
    			 }
    			 break;
    	   case SCREEN_EVENT_POINTER:
    	       screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_BUTTONS, &val);
    		   if (val) {
    		      screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_POSITION, pos);
    			  screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, size);
    			  fprintf(stderr, "window width: %d, window height: %d\n", size[0], size[1]);
    			  fprintf(stderr, "pointer x: %d, pointer y: %d\n", pos[0], pos[1]);
    			  if (pos[0] >= size[0] - exit_area_size &&
    			   	  pos[1] < exit_area_size) {
    				  goto end;
    			  }
    		   }
    		   break;
    	}
    }
                                

    The second part of the loop performs the rendering.

    Perform the rendering only if your window is visible. This will leave the CPU and GPU available to other applications and make the system more responsive while your window is invisible.

    In this example, the function render() is a utility function that renders the background, sets the color to use for text rendering, renders the text onto the screen, and updates the screen (posts the new frame).

    if (vis && !pause)
    {
        rc = render();
    }
                           
  19. Perform the appropriate cleanup.
    /* Destroy the font */
    if (font) {
    	glDeleteTextures(1, &(font->font_texture));
        free(font);
    }
    /* Terminate EGL setup */
    if (egl_disp != EGL_NO_DISPLAY) {
       eglMakeCurrent(egl_disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
       if (egl_surf != EGL_NO_SURFACE) {
           eglDestroySurface(egl_disp, egl_surf);
           egl_surf = EGL_NO_SURFACE;
       }
       if (egl_ctx != EGL_NO_CONTEXT) {
           eglDestroyContext(egl_disp, egl_ctx);
           egl_ctx = EGL_NO_CONTEXT;
       }
       eglTerminate(egl_disp);
       egl_disp = EGL_NO_DISPLAY;
    }
    eglReleaseThread();
    
    /* Clean up screen */
    if (screen_win != NULL) {
        screen_destroy_window(screen_win);
        screen_win = NULL;
    }
    screen_destroy_event(screen_ev);
    screen_destroy_context(screen_ctx);