Use OpenGL ES in a windowed vsync application

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

  1. Create the variables you'll need for your application:
    const int exit_area_size = 20;       /* the size of the area which will cause the
                                            exit of the application when a user makes
                                            contact with the display using a pointer. */
    const int barwidth = 32;             /* the 32-pixel width of the sliding vertical bar
    screen_context_t screen_ctx;         /* a connection to screen windowing system */
    screen_window_t screen_win;          /* a native handle for our window */
    screen_event_t screen_ev;            /* a handle used to pop events from our queue */
    int usage = SCREEN_USAGE_OPENGL_ES1; /* an indicator that OpenGL ES 1.X is used for rendering */
    int size[2] = { -1, -1 };            /* the width and height of your window,
                                            defaulted to invalid values so that you
                                            can verify if the size was in the command-line
                                            arguments, or to simply use the full display size */
    int pos[2] = { 0, 0 };               /* the x,y position of your window */
    int nbuffers = 2;                    /* the number of buffers backing the window */
    int format;                          /* the native visual type/screen format */
    int val;                             /* a variable used to set/get window properties */
    EGLint interval = 1;                 /* the EGL swap interval */
    int verbose = EGL_FALSE;             /* an indicator if "verbose" option was set */
    int vis = 1;                         /* an indicator if your window is visible */
    int pause = 0;                       /* an indicator if rendering is frozen*/
    const char *conf_str = NULL;         /* the EGL configuration string */
    const char *tok;                     /* the token used to process command-line arguments */
    int rval = EXIT_FAILURE;             /* the return value with which the application exits*/
    int rc;                              /* the return value from functions */
    int i;                               /* the loop/frame counter */
    GLshort points[20];                  /* the array where you'll store the vertices */
    EGLDisplay egl_disp;                 /* the abstract display on which graphics are drawn */
    EGLConfig egl_conf;                  /* the configuration describing the color and ancillary buffers */
    EGLSurface egl_surf;                 /* your window's rendering surface */
    EGLContext egl_ctx;                  /* a handle to a rendering context */
    EGLConfig egl_conf = (EGLConfig)0;   /* the resulting EGL config */
    EGLConfig *egl_configs;              /* describes the color and ancillary buffers */
    EGLint egl_num_configs;              /* number of configs that match our attributes */
    EGLint val;                          /* an EGL integer value */
    EGLBoolean eglrc;                    /* the return value of EGL functions */
    EGLint i;                            /* variable used to loop on matching configs */
     ** You'll use the surface attributes to choose between single-buffered
     ** and double-buffered rendering. To avoid having to keep track of
     ** indexes in a one-dimensional array of attribute/value pairs, you can use
     ** an aggregate of named attribute/value pairs of type EGLint (an integer of 32 bits).
    struct {
    	EGLint render_buffer[2];
    	EGLint none;
    } egl_surf_attr = {
    	.render_buffer = { EGL_RENDER_BUFFER, EGL_BACK_BUFFER }, /* double-buffering */
    	.none = EGL_NONE                                         /* End of list */
     ** Here is the list of attributes that will be passed to EGL to get you
     ** a pixel format configuration. An EGL configuration is required by
     ** EGL when creating surfaces and rendering contexts. Since you will
     ** modify certain values in this list when certain command-line arguments
     ** are provided, you will organize the attributes as an aggregate of named
     ** key/value pairs of EGL integers (EGLint). This way you
     ** won't have to track the index locations various attributes in a one-dimensional
     ** array.
    struct {
    	EGLint surface_type;
    	EGLint red_size;
    	EGLint green_size;
    	EGLint blue_size;
    	EGLint alpha_size;
    	EGLint samples;
    	EGLint config_id;
    } egl_conf_attr = {
    	.surface_type = EGL_WINDOW_BIT,   /* Ask for displayable and pbuffer surfaces */
    	.red_size = EGL_DONT_CARE,        /* Minimum number of red bits per pixel */
    	.green_size = EGL_DONT_CARE,      /* Minimum number of green bits per pixel */
    	.blue_size = EGL_DONT_CARE,       /* Minimum number of blue bits per pixel */
    	.alpha_size = EGL_DONT_CARE,      /* Minimum number of alpha bits per pixel */
    	.samples = EGL_DONT_CARE,         /* Minimum number of samples per pixel */
    	.config_id = EGL_DONT_CARE,       /* used to get a specific EGL config */
  2. Process the command-line arguments, if any.
    In this sample application, command-line arguments are accepted so that the user may specify some configuration values. The valid options include:
    • -single-buffer(rendering done to a single on-screen buffer)
    • -double-buffer(rendering done on alternating back buffers)
    • -interval=int (swap interval)
    • -config=string (comma-separated list of EGL configuration specifiers)
    • -size=widthxheight (size of the viewport)
    • -pos=x,y (position of the viewport)
    • -verbose(displays EGL configuration used by application)
    If no options are indicated as command-line arguments, this application will assume the following defaults:
    Option Default
    Number of buffers used for rendering 2
    Swap interval 1
    EGL configuration RGB pixel format with the smallest depth supported by the hardware
    Viewport size fullscreen
    Verbose off
    for (i = 1; i < argc; i++) {
    		if (strncmp(argv[i], "-config=", strlen("-config=")) == 0) {
    			/** EGL configuration **/
    			conf_str = argv[i] + strlen("-config=");
    		} else if (strncmp(argv[i], "-size=", strlen("-size=")) == 0) {
    			/** size of viewport **/
    			tok = argv[i] + strlen("-size=");
    			size[0] = atoi(tok);
    			while (*tok >= '0' && *tok <= '9') {
    			size[1] = atoi(tok+1);
    		} else if (strncmp(argv[i], "-pos=", strlen("-pos=")) == 0) {
    			/** position of viewport**/
    			tok = argv[i] + strlen("-pos=");
    			pos[0] = atoi(tok);
    			while (*tok >= '0' && *tok <= '9') {
    			pos[1] = atoi(tok+1);
    		} else if (strncmp(argv[i], "-interval=", strlen("-interval=")) == 0) {
    			/** swap interval **/
    			interval = atoi(argv[i] + strlen("-interval="));
    		} else if (strcmp(argv[i], "-single-buffer") == 0) {
    			/** single-buffer rendering **/
    			nbuffers = 1;
    		} else if (strcmp(argv[i], "-double-buffer") == 0) {
    			/** double-buffer rendering **/
    			nbuffers = 2;
    		} else if (strncmp(argv[i], "-verbose", strlen("-verbose")) == 0) {
    			/** verbose option selected **/
    			verbose = EGL_TRUE;
    		} else {
    			/** unsupported option **/
    			fprintf(stderr, "Invalid command-line option: %s\n", argv[i]);
  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 isn't necessary to check for the major and minor version numbers.

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

    Choosing an appropriate EGL config is an important part of the initialization procedure. This is especially true in embedded systems where the difference in performance between pixel formats can make or break an application.

    On desktop systems, a pixel format of RGB111 or better is usually sufficient because this pixel format returns the best configuration supported by the hardware.

    On embedded systems, RGBA8888 may not be an option. Even if RGBA8888 is supported by the rendering hardware, the system may not be able to handle the memory bandwidth required by the display controller to paint the display at 60 frames per second.

    Specifying an EGL configuration by its ID (EGL_CONFIG_ID) gives you the ability to get exactly what you want. However, this approach is far from being user-friendly when there are multiple platforms and windowing systems to consider.

    In this sample application the user can choose an appropriate EGL config by specifiying one of the following:
    • a pixel format
    • a pixel format and a number of per-pixel samples
    • an EGL configuration ID
    1. Parse the configuration string from the command-line argument

      You establish your EGL configuration attributes from the command-line arguments.

      if (str != NULL) {
      	tok = str;
      	while (*tok == ' ' || *tok == ',') {
      	while (*tok != '\0') {
      		if (strncmp(tok, "rgba8888", strlen("rgba8888")) == 0) {
      			egl_conf_attr.red_size = 8;
      			egl_conf_attr.green_size = 8;
      			egl_conf_attr.blue_size = 8;
      			egl_conf_attr.alpha_size = 8;
      			tok += strlen("rgba8888");
      		} else if (strncmp(tok, "rgba5551", strlen("rgba5551")) == 0) {
      			egl_conf_attr.red_size = 5;
      			egl_conf_attr.green_size = 5;
      			egl_conf_attr.blue_size = 5;
      			egl_conf_attr.alpha_size = 1;
      			tok += strlen("rgba5551");
      		} else if (strncmp(tok, "rgba4444", strlen("rgba4444")) == 0) {
      			egl_conf_attr.red_size = 4;
      			egl_conf_attr.green_size = 4;
      			egl_conf_attr.blue_size = 4;
      			egl_conf_attr.alpha_size = 4;
      			tok += strlen("rgba4444");
      		} else if (strncmp(tok, "rgb565", strlen("rgb565")) == 0) {
      			egl_conf_attr.red_size = 5;
      			egl_conf_attr.green_size = 6;
      			egl_conf_attr.blue_size = 5;
      			egl_conf_attr.alpha_size = 0;
      			tok += strlen("rgb565");
      		} else if (isdigit(*tok)) {
      			val = atoi(tok);
      			while (isdigit(*(++tok)));
      			if (*tok == 'x') {
      				egl_conf_attr.samples = val;
      			} else {
      				egl_conf_attr.config_id = val;
      		} else {
      			fprintf(stderr, "Invalid configuration specifier: ");
      			while (*tok != ' ' && *tok != ',' && *tok != '\0') {
      				fputc(*tok++, stderr);
      			fputc('\n', stderr);
      		 ** Skip any spaces and separators between this token and the next one.
      		while (*tok == ' ' || *tok == ',') {
    2. Use eglGetConfigs() to find an EGL conguration that matches attributes specified at the command line.

      Here, eglGetConfigs() is used instead of eglChooseConfigs(). 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. So instead of using eglChooseConfigs(), you will use eglGetConfigs() to get all the EGL configurations and search for one what matches your specified attributes.

      rc = eglGetConfigs(egl_disp, NULL, 0, &egl_num_configs);
    3. Allocate sufficient memory to hold all possible matching configurations.

      The total number of EGL configurations is stored in egl_num_configs after you've called the eglGetConfigs() function. The number is used to calculate how much memory needs to be allocated. You need enough memory to hold all the configurations so that you can traverse through the configurations to find a match.

      egl_configs = malloc(egl_num_configs * sizeof(*egl_configs));
    4. Call eglGetConfigs() a second time to store the configurations in the recently allocated memory.

      The list of EGL configurations is expected to be static. Therefore, your list of configurations for the purpose of matching should be static as well. As long as the call to eglGetConfigs() succeeds, it isn't necessary to check for egl_num_configs again.

      rc = eglGetConfigs(egl_disp, egl_configs, egl_num_configs, &egl_num_configs);
    5. Go through the list of EGL configurations to find one that has all the attributes specified on the command line.

      Some attributes such as surface type or the renderable type are masks, but all others are integers that you need to compare.

      for (i = 0; i < egl_num_configs; i++) {
      	/* EGL Configuration ID */
      	if (egl_conf_attr.config_id != EGL_DONT_CARE) {
      		eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_CONFIG_ID, &val);
      		if (val == egl_conf_attr.config_id) {
      			egl_conf = egl_configs[i];
      		} else {
      	/* Surface Type */
      	eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_SURFACE_TYPE, &val);
      	if ((val & egl_conf_attr.surface_type) != egl_conf_attr.surface_type) {
      	/ ** Renderable type has the OpenGL ES bit. */
      	eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_RENDERABLE_TYPE, &val);
      	if (!(val & EGL_OPENGL_ES_BIT)) {
          /* Red Bits */
      	if (egl_conf_attr.red_size != EGL_DONT_CARE) {
      		eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_RED_SIZE, &val);
      		if (val != egl_conf_attr.red_size) {
          /* Green Bits */
      	if (egl_conf_attr.green_size != EGL_DONT_CARE) {
      		eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_GREEN_SIZE, &val);
      		if (val != egl_conf_attr.green_size) {
          /* Blue Bits */
      	if (egl_conf_attr.blue_size != EGL_DONT_CARE) {
      		eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_BLUE_SIZE, &val);
      		if (val != egl_conf_attr.blue_size) {
          /* Alpha Bits */
      		if (egl_conf_attr.alpha_size != EGL_DONT_CARE) {
      			eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_ALPHA_SIZE, &val);
      			if (val != egl_conf_attr.alpha_size) {
          /* Number of Samples */
      	if (egl_conf_attr.samples != EGL_DONT_CARE) {
      		eglGetConfigAttrib(egl_disp, egl_configs[i], EGL_SAMPLES, &val);
      		if (val != egl_conf_attr.samples) {
      		/* This config has the pixel format we asked for, so we can keep it
      		     and stop looking.  */
      		egl_conf = egl_configs[i];
    6. Free the array that you allocated for the purpose of finding a matching EGL configuration.
  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 context.
    rc = screen_create_context(&screen_ctx, 0);
  8. Create your native window.
    rc = screen_create_window(&screen_win, screen_ctx);
  9. Set your native window properties based on the command-line arguments or defaults.
    EGLint buffer_bit_depth, alpha_bit_depth;
    eglGetConfigAttrib(egl_disp, egl_conf, EGL_BUFFER_SIZE, &buffer_bit_depth);
    eglGetConfigAttrib(egl_disp, egl_conf, EGL_ALPHA_SIZE, &alpha_bit_depth);
    switch (buffer_bit_depth) {
    	case 32: {
    		return SCREEN_FORMAT_RGBA8888;
    	case 24: {
    		return SCREEN_FORMAT_RGB888;
    	case 16: {
    		switch (alpha_bit_depth) {
    		  case 4: {
    		  	return SCREEN_FORMAT_RGBA4444;
    		  case 1: {
    			return SCREEN_FORMAT_RGBA5551;
    		  default: {
    			return SCREEN_FORMAT_RGB565;
    	default: {
    		return SCREEN_FORMAT_BYTE;
    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_FORMAT, &format);
    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage);
    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SWAP_INTERVAL, &interval);
    if (size[0] > 0 && size[1] > 0) {
    	rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, size);
    } else {
    	rc = screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, size);
    if (pos[0] != 0 || pos[1] != 0) {
    	rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_POSITION, pos);
  10. Create your window buffers for rendering.
    rc = screen_create_window_buffers(screen_win, nbuffers);
  11. Create a Screen and Windowing event so that the application can listen for and react to relevant events from the windowing system.
    rc = screen_create_event(&screen_ev);
  12. 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, (EGLint*)&egl_surf_attr);
  13. 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);
  14. 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);
  15. Initialize the viewport, the geometry, and the color for your application.

    This application is fairly simple, so you just need to initialize the OpenGL ES viewport and projection matrix. You need to compute the positions of vertices that will be used to do the rendering. The positions of these vertices are based on the window's dimensions instead of using scale and translation matrices. It's unlikely that the size will change often, so this method is more efficient than applying transformations every time a frame is rendered.

     ** The first four vertices take up 8 shorts. These vertices define a
     ** rectangle that goes from (0,0) to (barwidth,height). A translation
     ** matrix will be used to slide this rectangle across the viewport.
    points[0] = 0;
    points[1] = height;
    points[2] = barwidth;
    points[3] = height;
    points[4] = 0;
    points[5] = 0;
    points[6] = barwidth;
    points[7] = 0;
     ** The last six vertices take up 12 shorts. These vertices define two
     ** triangles that share a vertex. Because the OpenGL ES co-ordinate system
     ** starts at the bottom left instead of the top left corner, all y values
     ** need to be inverted. In other words, the hourglass needs to be
     ** translated up and down as the window height increases and decreases
     ** respectively.
    points[8] = 10;
    points[9] = height - 10;
    points[10] = 110;
    points[11] = height - 10;
    points[12] = 60;
    points[13] = height - 60;
    points[14] = 60;
    points[15] = height - 60;
    points[16] = 110;
    points[17] = height - 110;
    points[18] = 10;
    points[19] = height - 110;
    /* Update the viewport and projection matrix */
    glViewport(0, 0, width, height);
    glOrthof(0.0f, (GLfloat)width, 0.0f, (GLfloat)height, -1.0f, 1.0f);
    /* Set the clear color to yellow for the background*/
    glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
    /* You will use one vertex array for all of the rendering */
    glVertexPointer(2, GL_SHORT, 0, (const GLvoid *)points);
  16. Set up the main application loop to handle events and to perform the rendering.

    The main application loop runs continuously until either an error occurs or you receive a SCREEN_EVENT_CLOSE event from the windowing system.

    The main application loop consists of two main functions:
    • processing of relevant events
    • performing the rendering
    while (1) {
        /* Part 1: Process events */
        while (!screen_get_event(screen_ctx, screen_ev, vis ? 0 : ~0)){...}
        /* Part 2: Render if window is visible */
        if (vis && !pause) {...}
    1. Process relevant events.

      The first part of the main applcation loop processes any events that might be put on your context's queue. The only events that is of interest to you are the resize and close events. The timeout variable is set to 0 (no wait) or forever depending on whether the window is visible or not.

      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) {
      	switch (val) {
      		case SCREEN_EVENT_CLOSE:
      			 ** All you have to do when you receive the close event is
      			 ** exit the application loop. Because there is a loop
      			 ** within a loop, a simple break won't work - just use a goto
      			 ** to get out.
      			goto end;
      			 ** You are interested in visibility changes so you can pause
      			 ** or unpause the rendering.
      			screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_NAME, &val);
      			switch (val) {
      					 ** The visibility status is not included in the
      					 ** event, so you need to call screen_get_window_property_iv()
                           ** to retrieve the value.
      					screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_VISIBLE, &vis);
      			 ** To provide a way of gracefully terminating your application,
      			 ** exit if there is a pointer select event in the upper
      			 ** right corner of your window. This should happen if the mouse's
      			 ** left button is clicked or if a touch screen display is pressed.
      			 ** The event will come as a screen pointer event, with an (x,y)
      			 ** coordinate relative to the window's upper left corner and a
      			 ** select value. You have to verify ourselves that the co-ordinates
      			 ** of the pointer are in the upper right hand area.
      			screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_BUTTONS, &val);
      			if (val) {
      				screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_POSITION, pos);
      				if (pos[0] >= size[0] - exit_area_size &&
      					pos[1] < exit_area_size) {
      					goto end;
      			screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_KEY_FLAGS, &val);
      			if (val & KEY_DOWN) {
      				screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_KEY_SYM, &val);
      				switch (val) {
      					case KEYCODE_ESCAPE:
      						goto end;
      					case KEYCODE_F:
      						pause = !pause;
    1. Perform the rendering.

      The second part of the main application loop is the rendering. You want to skip the rendering part if your window isn't visible, so as to leave the CPU and GPU to other applications. This approach will enable the system to be more responsive while the window is invisible.

      if (vis && !pause) {
      	/* Start by clearing the window */
      	/** You could use glLoadIdentity or glPushMatrix here. If you use
      	 ** glLoadIdentity, you would need to call glLoadIdentity again when
      	 ** you draw the hourglass. The assumption is that glPushMatrix,
      	 ** glTranslatef, glPopMatrix are more efficient than
      	 ** glLoadIdentity, glTranslatef, and glLoadIdentity.
      	/* Use translation to animate the vertical bar */
      	glTranslatef((GLfloat)(i++ % (size[0] - barwidth)), 0.0f, 0.0f);
      	/* Make the animated vertical bar to be drawn in blue */
      	glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
      	/* Render the vertical bar */
      	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
      	/* You don't want the hourglass to be translated */
      	/* Make the hourglass to be drawn in gray */
      	glColor4f(0.62745f, 0.62745f, 0.62745f, 1.0f);
      	/* Render the hourglass */
      	glDrawArrays(GL_TRIANGLES, 4, 6);
      	 ** Posting of the new frame requires a call to eglSwapBuffers.
      	 ** For now, this is true even when using single buffering. If an
      	 ** event has occured that invalidates the surface we are currently
      	 ** using, eglSwapBuffers will return EGL_FALSE and set the error
      	 ** code to EGL_BAD_NATIVE_WINDOW. At this point, you could destroy
      	 ** the EGL surface, close the native window, and start again.
      	 ** This application will simply exit when any errors occur.
      	rc = eglSwapBuffers(egl_disp, egl_surf);
  17. Release resources.

    Before you can destroy any of the resources you've created for this application, you must deactivate the rendering context used and release the surfaces from where you were drawing and reading.

    This deactivation and release is done by calling eglMakeCurrent() with EGL_NO_SURFACE and EGL_NO_CONTEXT as arguments. Note that the call to eglMakeCurrent() will generate an error unless all arguments are EGL_NO_SURFACE and EGL_NO_CONTEXT, or all arguments are valid EGLSurface and EGLContext objects.

    You will also need to terminate the connection to the EGL display and release any resources that were allocated for this thread. On most systems, these resources would likely be released automatically when the program exits, but it's good practice to do so by calling eglReleaseThread().

    eglMakeCurrent(egl_disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroySurface(egl_disp, egl_surf);
    screen_destroy_event(screen_ev);        /* Destroy the Screen event */
    screen_destroy_window(screen_win);      /* Destroy the native window */
    screen_destroy_context(screen_ctx);     /* Destroy the Screen context */
    eglDestroyContext(egl_disp, egl_ctx);   /* Destroy the EGL render context */
    eglTerminate(egl_disp);                 /* Terminate connection to display */
    eglReleaseThread();                     /* Release resources for thread */