added sdl_gles_get/setattribute calls
[sdlhildon] / sdlgles / src / SDL_gles.c
1 /* This file is part of SDL_gles - SDL addon for OpenGL|ES
2  * Copyright (C) 2010 Javier S. Pedro
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 3 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA or see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <dlfcn.h>
24 #include <assert.h>
25
26 #include <EGL/egl.h>
27
28 #include <SDL.h>
29 #include <SDL_syswm.h>
30
31 #include "SDL_gles.h"
32
33 typedef struct SDL_GLES_ContextPriv
34 {
35         SDL_GLES_Context p;
36
37         EGLConfig egl_config;
38         EGLContext egl_context;
39 } SDL_GLES_ContextPriv;
40
41 static const char * default_libgl[] = {
42         [SDL_GLES_VERSION_1_1] = "/usr/lib/libGLES_CM.so",
43         [SDL_GLES_VERSION_2_0] = "/usr/lib/libGLESv2.so"
44 };
45
46 /** SDL GFX display */
47 static Display *display = NULL;
48 /** EGLDisplay for the above X11 display */
49 static EGLDisplay *egl_display = EGL_NO_DISPLAY;
50 /** The current surface. Recreated by SDL_GLES_SetVideoMode(). */
51 static EGLSurface egl_surface = EGL_NO_SURFACE;
52 /** A pointer to the current active context. */
53 static SDL_GLES_ContextPriv *cur_context = NULL;
54
55 /** The desired GLES version, as selected by the SDL_GLES_Init() call. */
56 static SDL_GLES_Version gl_version = SDL_GLES_VERSION_NONE;
57 /** A handle to the dynamically loaded GL library. */
58 static void* gl_handle = NULL;
59 /** EGL version. */
60 static EGLint egl_major, egl_minor;
61
62 /** Your average countof() macro. */
63 #define countof(a) (sizeof(a)/sizeof(a[0]))
64
65 /** List of EGLConfig attributes we care about;
66   * Used for filtering; modified by SDL_GLES_Get/SetAttribute(). */
67 static EGLint attrib_list[] = {
68 #define A(number, attrib, default_value) \
69         attrib, default_value,
70 #include "attribs.inc"
71 #undef A
72         EGL_NONE
73 };
74 /** A enum which maps A_EGL_* attrib constants to attrib_list positions. */
75 typedef enum {
76 #define A(number, attrib, default_value) \
77         A_ ## attrib = (number * 2),
78 #include "attribs.inc"
79 #undef A
80 } attrib_enum;
81 static EGLint context_attrib_list[] = {
82         EGL_CONTEXT_CLIENT_VERSION,     1,
83         EGL_NONE
84 };
85
86 static const char * get_error_string(int error) {
87         switch (error) {
88                 case EGL_SUCCESS:
89                         return "EGL_SUCCESS";
90                 case EGL_NOT_INITIALIZED:
91                         return "EGL_NOT_INITIALIZED";
92                 case EGL_BAD_ACCESS:
93                         return "EGL_BAD_ACCESS";
94                 case EGL_BAD_ALLOC:
95                         return "EGL_BAD_ALLOC";
96                 case EGL_BAD_ATTRIBUTE:
97                         return "EGL_BAD_ATTRIBUTE";
98                 case EGL_BAD_CONFIG:
99                         return "EGL_BAD_CONFIG";
100                 case EGL_BAD_CONTEXT:
101                         return "EGL_BAD_CONTEXT";
102                 case EGL_BAD_CURRENT_SURFACE:
103                         return "EGL_BAD_CURRENT_SURFACE";
104                 case EGL_BAD_DISPLAY:
105                         return "EGL_BAD_DISPLAY";
106                 case EGL_BAD_MATCH:
107                         return "EGL_BAD_MATCH";
108                 case EGL_BAD_NATIVE_PIXMAP:
109                         return "EGL_BAD_NATIVE_PIXMAP";
110                 case EGL_BAD_NATIVE_WINDOW:
111                         return "EGL_BAD_NATIVE_WINDOW";
112                 case EGL_BAD_PARAMETER:
113                         return "EGL_BAD_PARAMETER";
114                 case EGL_BAD_SURFACE:
115                         return "EGL_BAD_SURFACE";
116                 case EGL_CONTEXT_LOST:
117                         return "EGL_CONTEXT_LOST";
118                 default:
119                         return "EGL_UNKNOWN_ERROR";
120     }
121 }
122
123 static inline void set_egl_attrib(attrib_enum attrib, EGLint value)
124 {
125         const unsigned int i = (unsigned int)attrib + 1;
126         assert(i < countof(attrib_list));
127         attrib_list[i] = value;
128 }
129
130 static inline EGLint get_egl_attrib(attrib_enum attrib)
131 {
132         const unsigned int i = (unsigned int)attrib + 1;
133         assert(i < countof(attrib_list));
134         return attrib_list[i];
135 }
136
137 static inline void set_egl_context_attrib(EGLenum attrib, EGLint value)
138 {
139         /* Only one attribute supported here. */
140         assert(attrib == EGL_CONTEXT_CLIENT_VERSION);
141         context_attrib_list[1] = value;
142 }
143
144 int SDL_GLES_LoadLibrary(const char *path)
145 {
146         /* If path is NULL, try first to use path from SDL_VIDEO_GL_DRIVER,
147          * otherwise use a sane default depending on selected GLES version. */
148         if (!path) {
149                 path = getenv("SDL_VIDEO_GL_DRIVER");
150                 if (!path) {
151                         switch (gl_version) {
152                                 case SDL_GLES_VERSION_1_1:
153                                 case SDL_GLES_VERSION_2_0:
154                                         path = default_libgl[gl_version];
155                                 break;
156                                 default:
157                                         SDL_SetError("No GL version specific and SDL_VIDEO_GL_DRIVER set");
158                                         return -1;
159                         }
160                 }
161         }
162
163         /* Dynamically load the desired GL library */
164         gl_handle = dlopen(path, RTLD_LAZY|RTLD_GLOBAL);
165         if (!gl_handle) {
166                 SDL_SetError("Failed to open GL library: %s (%s)", path, dlerror());
167                 return -2;
168         }
169
170         return 0;
171 }
172
173 void* SDL_GLES_GetProcAddress(const char *proc)
174 {
175         if (!gl_handle) return NULL;
176         return dlsym(gl_handle, proc);
177 }
178
179 int SDL_GLES_Init(SDL_GLES_Version version)
180 {
181         SDL_SysWMinfo info;
182         EGLBoolean res;
183
184         SDL_VERSION(&info.version);
185         if (SDL_GetWMInfo(&info) != 1) {
186                 SDL_SetError("SDL_gles is incompatible with this SDL version");
187                 return -1;
188         }
189
190         /* We use the SDL GFX display (we're using the GFX window too after all) */
191         display = info.info.x11.gfxdisplay;
192
193         egl_display = eglGetDisplay((EGLNativeDisplayType)display);
194         if (egl_display == EGL_NO_DISPLAY) {
195                 SDL_SetError("EGL found no available displays");
196                 return -2;
197         }
198
199         res = eglInitialize(egl_display, &egl_major, &egl_minor);
200         if (!res) {
201                 SDL_SetError("EGL failed to initialize: %s",
202                         get_error_string(eglGetError()));
203                 return -2;
204         }
205
206         /* Configure some context attributes and bind the required API now. */
207         EGLenum api_to_bind = EGL_OPENGL_ES_API;
208         gl_version = version;
209         switch (gl_version) {
210                 case SDL_GLES_VERSION_1_1:
211                         /* OpenGL|ES 1.1 */
212                         api_to_bind = EGL_OPENGL_ES_API;
213                         /* filter non ES 1.0 renderable configurations */
214                         set_egl_attrib(A_EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT);
215                         /* default egl_context_client_version is OK */
216                         break;
217                 case SDL_GLES_VERSION_2_0:
218                         /* OpenGL|ES 2.0 */
219                         api_to_bind = EGL_OPENGL_ES_API; /* Note: no EGL_OPENGL_ES2_API */
220                         /* filter non ES 2.0 renderable configurations */
221                         set_egl_attrib(A_EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT);
222                         /* and request GL ES 2.0 contexts */
223                         set_egl_context_attrib(EGL_CONTEXT_CLIENT_VERSION, 2);
224                         break;
225                 default:
226                         SDL_SetError("Unsupported API version");
227                         return -1;
228         }
229
230         res = eglBindAPI(api_to_bind);
231         if (!res) {
232                 SDL_SetError("EGL failed to bind the required API");
233                 return -2;
234         }
235
236         return 0;
237 }
238
239 void SDL_GLES_Quit()
240 {
241         /* Close the loaded GL library (if any) */
242         if (gl_handle) {
243                 dlclose(gl_handle);
244                 gl_handle = NULL;
245         }
246         /* Unallocate most stuff we can unallocate. */
247         if (egl_display != EGL_NO_DISPLAY) {
248                 eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
249                         EGL_NO_CONTEXT);
250
251                 if (cur_context) {
252                         eglDestroyContext(egl_display, cur_context->egl_context);
253                         free(cur_context);
254                         cur_context = 0;
255                 }
256                 if (egl_surface != EGL_NO_SURFACE) {
257                         eglDestroySurface(egl_display, egl_surface);
258                         egl_surface = EGL_NO_SURFACE;
259                 }
260
261                 eglTerminate(egl_display);
262                 egl_display = EGL_NO_DISPLAY;
263         }
264 }
265
266 int SDL_GLES_SetVideoMode()
267 {
268         SDL_SysWMinfo info;
269         EGLBoolean res;
270
271         SDL_VERSION(&info.version);
272         if (SDL_GetWMInfo(&info) != 1) {
273                 SDL_SetError("SDL_gles is incompatible with this SDL version");
274                 return -1;
275         }
276
277         /* Destroy previous surface, if any. */
278         if (egl_surface != EGL_NO_SURFACE) {
279                 /* Ensure the surface is not the current one,
280                  * thus freeing memory earlier. */
281                 eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
282                          EGL_NO_CONTEXT);
283                 eglDestroySurface(egl_display, egl_surface);
284                 egl_surface = EGL_NO_SURFACE;
285         }
286
287         /* No current context? Quietly defer surface creation.
288          * Surface will be created on the call to MakeCurrent. */
289         if (!cur_context) {
290                 return 0;
291         }
292
293         /* Create the new window surface. */
294         egl_surface = eglCreateWindowSurface(egl_display, cur_context->egl_config,
295                 (EGLNativeWindowType)info.info.x11.window, NULL);
296         if (egl_surface == EGL_NO_SURFACE) {
297                 SDL_SetError("EGL failed to create a window surface: %s",
298                         get_error_string(eglGetError()));
299                 return -2;
300         }
301
302         /* New surface created. Make it active. */
303         assert(cur_context && cur_context->egl_context != EGL_NO_CONTEXT);
304         res = eglMakeCurrent(egl_display, egl_surface, egl_surface,
305                 cur_context->egl_context);
306
307         if (!res) {
308                 SDL_SetError("EGL failed to change current surface: %s",
309                         get_error_string(eglGetError()));
310                 cur_context = NULL;
311                 return -2;
312         }
313
314         return 0;
315 }
316
317 SDL_GLES_Context* SDL_GLES_CreateContext(void)
318 {
319         SDL_GLES_ContextPriv *context = malloc(sizeof(SDL_GLES_ContextPriv));
320         if (!context) {
321                 SDL_Error(SDL_ENOMEM);
322                 return NULL;
323         }
324
325         EGLBoolean res;
326         EGLConfig configs[1];
327         EGLint num_config;
328
329         res = eglChooseConfig(egl_display, attrib_list, configs, 1, &num_config);
330         if (!res || num_config < 1) {
331                 SDL_SetError("EGL failed to find any valid config with required attributes: %s",
332                         get_error_string(eglGetError()));
333                 free(context);
334                 return NULL;
335         }
336
337         context->egl_config = configs[0];
338         context->egl_context = eglCreateContext(egl_display, configs[0],
339                 EGL_NO_CONTEXT, context_attrib_list);
340         if (context->egl_context == EGL_NO_CONTEXT) {
341                 SDL_SetError("EGL failed to create context: %s",
342                         get_error_string(eglGetError()));
343                 free(context);
344                 return NULL;
345         }
346
347         return (SDL_GLES_Context*) context;
348 }
349
350 void SDL_GLES_DeleteContext(SDL_GLES_Context* c)
351 {
352         SDL_GLES_ContextPriv *context = (SDL_GLES_ContextPriv*)c;
353         if (!context) return;
354
355         if (cur_context == context) {
356                 /* Deleting the active context */
357                 SDL_GLES_MakeCurrent(NULL);
358         }
359
360         eglDestroyContext(egl_display, context->egl_context);
361         free(context);
362 }
363
364 int SDL_GLES_MakeCurrent(SDL_GLES_Context* c)
365 {
366         SDL_GLES_ContextPriv *context = (SDL_GLES_ContextPriv*)c;
367         int res;
368
369         cur_context = context;
370
371         /* SDL_GLES_SetVideoMode() will appropiately clear the current context
372          * (and surface), then create a new surface matching the selected context
373          * config and make both the surface and the context the active ones. */
374         res = SDL_GLES_SetVideoMode();
375         if (res != 0) return res; /* Surface (re-)creation failed. */
376
377         return 0;
378 }
379
380 void SDL_GLES_SwapBuffers()
381 {
382         eglSwapBuffers(egl_display, egl_surface);
383 }
384
385 /** A simple map between SDL_GLES_* attributes and EGL ones.
386   * More abstraction layers is always good.
387   */
388 static const attrib_enum attrib_map[] = {
389         [SDL_GLES_BUFFER_SIZE]          = A_EGL_BUFFER_SIZE,
390         [SDL_GLES_RED_SIZE]                     = A_EGL_RED_SIZE,
391         [SDL_GLES_GREEN_SIZE]           = A_EGL_GREEN_SIZE,
392         [SDL_GLES_BLUE_SIZE]            = A_EGL_BLUE_SIZE,
393         [SDL_GLES_ALPHA_SIZE]           = A_EGL_ALPHA_SIZE,
394         [SDL_GLES_LUMINANCE_SIZE]       = A_EGL_LUMINANCE_SIZE,
395         [SDL_GLES_DEPTH_SIZE]           = A_EGL_DEPTH_SIZE,
396         [SDL_GLES_STENCIL_SIZE]         = A_EGL_STENCIL_SIZE,
397 };
398
399 int SDL_GLES_SetAttribute(SDL_GLES_Attr attr, int value)
400 {
401         if (attr >= countof(attrib_map)) return -1;
402         attrib_enum list_attr = attrib_map[attr];
403         set_egl_attrib(list_attr, value);
404         return 0;
405 }
406
407 int SDL_GLES_GetAttribute(SDL_GLES_Attr attr, int *value)
408 {
409         if (attr >= countof(attrib_map)) return -1;
410         attrib_enum list_attr = attrib_map[attr];
411         if (cur_context) {
412                 EGLenum egl_attr = attrib_list[list_attr];
413                 EGLint egl_value = 0;
414                 EGLBoolean res = eglGetConfigAttrib(egl_display,
415                         cur_context->egl_config, egl_attr, &egl_value);
416                 if (res) {
417                         *value = egl_value;
418                         return 0;
419                 } else {
420                         printf("Failed: %s\n", get_error_string(eglGetError()));
421                         return -1;
422                 }
423         } else {
424                 *value = get_egl_attrib(list_attr);
425                 return 0;
426         }
427 }
428