comparison src/render/opengles2/SDL_render_gles2.c @ 5204:523409574510

Added an OpenGL ES 2.0 renderer, contributed by itsnotabigtruck This compiles, but it untested.
author Sam Lantinga <slouken@libsdl.org>
date Sun, 06 Feb 2011 00:00:13 -0800
parents
children 1f2b17f42fd0
comparison
equal deleted inserted replaced
5203:01bced9a4cc1 5204:523409574510
1 /*
2 SDL - Simple DirectMedia Layer
3 Copyright (C) 2010 itsnotabigtruck.
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 DEALINGS IN THE SOFTWARE.
22 */
23
24 #include "SDL_config.h"
25
26 #if SDL_VIDEO_RENDER_OGL_ES2
27
28 #ifdef __IPHONEOS__
29 #include <OpenGLES/ES2/gl.h>
30 #include <OpenGLES/ES2/glext.h>
31 #else
32 #include <GLES2/gl2.h>
33 #include <GLES2/gl2ext.h>
34 #endif
35 #include "../SDL_sysrender.h"
36 #include "SDL_shaders_gles2.h"
37
38 /*************************************************************************************************
39 * Bootstrap data *
40 *************************************************************************************************/
41
42 static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, Uint32 flags);
43
44 SDL_RenderDriver GLES2_RenderDriver = {
45 GLES2_CreateRenderer,
46 {
47 "opengles2",
48 (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
49 #if GLES2_ASSUME_BGRA
50 11,
51 {
52 SDL_PIXELFORMAT_ABGR8888,
53 SDL_PIXELFORMAT_ABGR4444,
54 SDL_PIXELFORMAT_ABGR1555,
55 SDL_PIXELFORMAT_BGR565,
56 SDL_PIXELFORMAT_BGR24,
57 SDL_PIXELFORMAT_ARGB8888,
58 SDL_PIXELFORMAT_ARGB4444,
59 SDL_PIXELFORMAT_ARGB1555,
60 SDL_PIXELFORMAT_RGB565,
61 SDL_PIXELFORMAT_RGB24
62 },
63 #elif GLES2_ASSUME_BGRA8888
64 7,
65 {
66 SDL_PIXELFORMAT_ABGR8888,
67 SDL_PIXELFORMAT_ABGR4444,
68 SDL_PIXELFORMAT_ABGR1555,
69 SDL_PIXELFORMAT_BGR565,
70 SDL_PIXELFORMAT_BGR24,
71 SDL_PIXELFORMAT_ARGB8888
72 },
73 #else
74 6,
75 {
76 SDL_PIXELFORMAT_ABGR8888,
77 SDL_PIXELFORMAT_ABGR4444,
78 SDL_PIXELFORMAT_ABGR1555,
79 SDL_PIXELFORMAT_BGR565,
80 SDL_PIXELFORMAT_BGR24
81 },
82 #endif
83 0,
84 0
85 }
86 };
87
88 /*************************************************************************************************
89 * Context structures *
90 *************************************************************************************************/
91
92 typedef struct GLES2_TextureData
93 {
94 GLenum texture;
95 GLenum texture_type;
96 GLenum pixel_format;
97 GLenum pixel_type;
98 void *pixel_data;
99 size_t pitch;
100 } GLES2_TextureData;
101
102 typedef struct GLES2_ShaderCacheEntry
103 {
104 GLuint id;
105 GLES2_ShaderType type;
106 const GLES2_ShaderInstance *instance;
107 int references;
108 struct GLES2_ShaderCacheEntry *prev;
109 struct GLES2_ShaderCacheEntry *next;
110 } GLES2_ShaderCacheEntry;
111
112 typedef struct GLES2_ShaderCache
113 {
114 int count;
115 GLES2_ShaderCacheEntry *head;
116 } GLES2_ShaderCache;
117
118 typedef struct GLES2_ProgramCacheEntry
119 {
120 GLuint id;
121 SDL_BlendMode blend_mode;
122 GLES2_ShaderCacheEntry *vertex_shader;
123 GLES2_ShaderCacheEntry *fragment_shader;
124 GLuint uniform_locations[16];
125 struct GLES2_ProgramCacheEntry *prev;
126 struct GLES2_ProgramCacheEntry *next;
127 } GLES2_ProgramCacheEntry;
128
129 typedef struct GLES2_ProgramCache
130 {
131 int count;
132 GLES2_ProgramCacheEntry *head;
133 GLES2_ProgramCacheEntry *tail;
134 } GLES2_ProgramCache;
135
136 typedef enum
137 {
138 GLES2_ATTRIBUTE_POSITION = 0,
139 GLES2_ATTRIBUTE_TEXCOORD = 1
140 } GLES2_Attribute;
141
142 typedef enum
143 {
144 GLES2_UNIFORM_PROJECTION,
145 GLES2_UNIFORM_TEXTURE,
146 GLES2_UNIFORM_MODULATION,
147 GLES2_UNIFORM_COLOR,
148 GLES2_UNIFORM_COLORTABLE
149 } GLES2_Uniform;
150
151 typedef enum
152 {
153 GLES2_IMAGESOURCE_SOLID,
154 GLES2_IMAGESOURCE_TEXTURE
155 } GLES2_ImageSource;
156
157 typedef struct GLES2_DriverContext
158 {
159 SDL_GLContext *context;
160 int shader_format_count;
161 GLenum *shader_formats;
162 GLES2_ShaderCache shader_cache;
163 GLES2_ProgramCache program_cache;
164 GLES2_ProgramCacheEntry *current_program;
165 SDL_bool updateSize;
166 } GLES2_DriverContext;
167
168 #define GLES2_MAX_CACHED_PROGRAMS 8
169
170 /*************************************************************************************************
171 * Renderer state APIs *
172 *************************************************************************************************/
173
174 static void GLES2_WindowEvent(SDL_Renderer * renderer,
175 const SDL_WindowEvent *event);
176 static int GLES2_ActivateRenderer(SDL_Renderer *renderer);
177 static int GLES2_DisplayModeChanged(SDL_Renderer *renderer);
178 static void GLES2_DestroyRenderer(SDL_Renderer *renderer);
179
180 static SDL_GLContext SDL_CurrentContext = NULL;
181
182 static int
183 GLES2_ActivateRenderer(SDL_Renderer * renderer)
184 {
185 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
186 SDL_Window *window = renderer->window;
187
188 if (SDL_CurrentContext != rdata->context) {
189 /* Null out the current program to ensure we set it again */
190 rdata->current_program = NULL;
191
192 if (SDL_GL_MakeCurrent(window, rdata->context) < 0) {
193 return -1;
194 }
195 SDL_CurrentContext = rdata->context;
196 }
197 if (rdata->updateSize) {
198 int w, h;
199
200 SDL_GetWindowSize(window, &w, &h);
201 glViewport(0, 0, w, h);
202 rdata->updateSize = SDL_FALSE;
203 }
204 return 0;
205 }
206
207 static void
208 GLES2_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
209 {
210 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
211
212 if (event->event == SDL_WINDOWEVENT_RESIZED) {
213 /* Rebind the context to the window area */
214 SDL_CurrentContext = NULL;
215 rdata->updateSize = SDL_TRUE;
216 }
217 }
218
219 static void
220 GLES2_DestroyRenderer(SDL_Renderer *renderer)
221 {
222 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
223 GLES2_ProgramCacheEntry *entry;
224 GLES2_ProgramCacheEntry *next;
225
226 GLES2_ActivateRenderer(renderer);
227
228 /* Deallocate everything */
229 entry = rdata->program_cache.head;
230 while (entry)
231 {
232 glDeleteShader(entry->vertex_shader->id);
233 glDeleteShader(entry->fragment_shader->id);
234 SDL_free(entry->vertex_shader);
235 SDL_free(entry->fragment_shader);
236 glDeleteProgram(entry->id);
237 next = entry->next;
238 SDL_free(entry);
239 entry = next;
240 }
241 SDL_GL_DeleteContext(rdata->context);
242 SDL_free(rdata->shader_formats);
243 SDL_free(renderer->driverdata);
244 SDL_free(renderer);
245 }
246
247 /*************************************************************************************************
248 * Texture APIs *
249 *************************************************************************************************/
250
251 #define GL_BGR_EXT 0x80E0
252 #define GL_BGRA_EXT 0x80E1
253
254 static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture);
255 static void GLES2_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture);
256 static int GLES2_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect,
257 void **pixels, int *pitch);
258 static void GLES2_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture);
259 static int GLES2_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect,
260 const void *pixels, int pitch);
261
262 static int
263 GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture)
264 {
265 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
266 GLES2_TextureData *tdata;
267 GLenum format;
268 GLenum type;
269
270 GLES2_ActivateRenderer(renderer);
271
272 /* Determine the corresponding GLES texture format params */
273 switch (texture->format)
274 {
275 case SDL_PIXELFORMAT_BGR24:
276 format = GL_RGB;
277 type = GL_UNSIGNED_BYTE;
278 break;
279 case SDL_PIXELFORMAT_ABGR8888:
280 format = GL_RGBA;
281 type = GL_UNSIGNED_BYTE;
282 break;
283 case SDL_PIXELFORMAT_BGR565:
284 format = GL_RGB;
285 type = GL_UNSIGNED_SHORT_5_6_5;
286 break;
287 case SDL_PIXELFORMAT_ABGR1555:
288 format = GL_RGBA;
289 type = GL_UNSIGNED_SHORT_5_5_5_1;
290 break;
291 case SDL_PIXELFORMAT_ABGR4444:
292 format = GL_RGBA;
293 type = GL_UNSIGNED_SHORT_4_4_4_4;
294 break;
295 #if GLES2_ASSUME_BGRA || GLES2_ASSUME_BGRA8888
296 case SDL_PIXELFORMAT_ARGB8888:
297 format = GL_BGRA_EXT;
298 type = GL_UNSIGNED_BYTE;
299 break;
300 #endif /* GLES2_ASSUME_BGRA || GLES2_ASSUME_BGRA8888 */
301 #if GLES2_ASSUME_BGRA
302 case SDL_PIXELFORMAT_RGB24:
303 format = GL_BGR_EXT;
304 type = GL_UNSIGNED_BYTE;
305 break;
306 case SDL_PIXELFORMAT_RGB565:
307 format = GL_BGR_EXT;
308 type = GL_UNSIGNED_SHORT_5_6_5;
309 break;
310 case SDL_PIXELFORMAT_ARGB1555:
311 format = GL_BGRA_EXT;
312 type = GL_UNSIGNED_SHORT_5_5_5_1;
313 break;
314 case SDL_PIXELFORMAT_ARGB4444:
315 format = GL_BGRA_EXT;
316 type = GL_UNSIGNED_SHORT_4_4_4_4;
317 break;
318 #endif /* GLES2_ASSUME_BGRA */
319 default:
320 SDL_SetError("Texture format not supported");
321 return -1;
322 }
323
324 /* Allocate a texture struct */
325 tdata = (GLES2_TextureData *)SDL_calloc(1, sizeof(GLES2_TextureData));
326 if (!tdata)
327 {
328 SDL_OutOfMemory();
329 return -1;
330 }
331 tdata->texture = 0;
332 tdata->texture_type = GL_TEXTURE_2D;
333 tdata->pixel_format = format;
334 tdata->pixel_type = type;
335
336 /* Allocate a blob for image data */
337 if (texture->access == SDL_TEXTUREACCESS_STREAMING)
338 {
339 tdata->pitch = texture->w * SDL_BYTESPERPIXEL(texture->format);
340 tdata->pixel_data = SDL_malloc(tdata->pitch * texture->h);
341 if (!tdata->pixel_data)
342 {
343 SDL_OutOfMemory();
344 SDL_free(tdata);
345 return -1;
346 }
347 }
348
349 /* Allocate the texture */
350 glGetError();
351 glGenTextures(1, &tdata->texture);
352 glActiveTexture(GL_TEXTURE0);
353 glBindTexture(tdata->texture_type, tdata->texture);
354 glTexParameteri(tdata->texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
355 glTexParameteri(tdata->texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
356 glTexParameteri(tdata->texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
357 glTexParameteri(tdata->texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
358 glTexImage2D(tdata->texture_type, 0, format, texture->w, texture->h, 0, format, type, NULL);
359 if (glGetError() != GL_NO_ERROR)
360 {
361 SDL_SetError("Texture creation failed");
362 glDeleteTextures(1, &tdata->texture);
363 SDL_free(tdata);
364 return -1;
365 }
366 texture->driverdata = tdata;
367 return 0;
368 }
369
370 static void
371 GLES2_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture)
372 {
373 GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
374
375 GLES2_ActivateRenderer(renderer);
376
377 /* Destroy the texture */
378 if (tdata)
379 {
380 glDeleteTextures(1, &tdata->texture);
381 SDL_free(tdata->pixel_data);
382 SDL_free(tdata);
383 texture->driverdata = NULL;
384 }
385 }
386
387 static int
388 GLES2_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect,
389 void **pixels, int *pitch)
390 {
391 GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
392
393 /* Retrieve the buffer/pitch for the specified region */
394 *pixels = (Uint8 *)tdata->pixel_data +
395 (tdata->pitch * rect->y) +
396 (rect->x * SDL_BYTESPERPIXEL(texture->format));
397 *pitch = tdata->pitch;
398
399 return 0;
400 }
401
402 static void
403 GLES2_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
404 {
405 GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
406
407 GLES2_ActivateRenderer(renderer);
408
409 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
410 glActiveTexture(GL_TEXTURE0);
411 glBindTexture(tdata->texture_type, tdata->texture);
412 glTexSubImage2D(tdata->texture_type, 0, 0, 0, texture->w, texture->h,
413 tdata->pixel_format, tdata->pixel_type, tdata->pixel_data);
414 }
415
416 static int
417 GLES2_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect,
418 const void *pixels, int pitch)
419 {
420 GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
421 Uint8 *blob = NULL;
422 Uint8 *src;
423 int srcPitch;
424 Uint8 *dest;
425 int y;
426
427 GLES2_ActivateRenderer(renderer);
428
429 /* Bail out if we're supposed to update an empty rectangle */
430 if (rect->w <= 0 || rect->h <= 0)
431 return 0;
432
433 /* Reformat the texture data into a tightly packed array */
434 srcPitch = rect->w * SDL_BYTESPERPIXEL(texture->format);
435 src = (Uint8 *)pixels;
436 if (pitch != srcPitch)
437 {
438 blob = (Uint8 *)SDL_malloc(srcPitch * rect->h);
439 if (!blob)
440 {
441 SDL_OutOfMemory();
442 return -1;
443 }
444 src = blob;
445 for (y = 0; y < rect->h; ++y)
446 {
447 SDL_memcpy(src, pixels, srcPitch);
448 src += srcPitch;
449 pixels = (Uint8 *)pixels + pitch;
450 }
451 src = blob;
452 }
453
454 /* Create a texture subimage with the supplied data */
455 glGetError();
456 glActiveTexture(GL_TEXTURE0);
457 glBindTexture(tdata->texture_type, tdata->texture);
458 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
459 glTexSubImage2D(tdata->texture_type,
460 0,
461 rect->x,
462 rect->y,
463 rect->w,
464 rect->h,
465 tdata->pixel_format,
466 tdata->pixel_type,
467 src);
468 if (glGetError() != GL_NO_ERROR)
469 {
470 SDL_SetError("Failed to update texture");
471 return -1;
472 }
473
474 /* Update the (streaming) texture buffer, in one pass if possible */
475 if (tdata->pixel_data)
476 {
477 dest = (Uint8 *)tdata->pixel_data +
478 (tdata->pitch * rect->y) +
479 (SDL_BYTESPERPIXEL(texture->format) * rect->x);
480 if (rect->w == texture->w)
481 {
482 SDL_memcpy(dest, src, srcPitch * rect->h);
483 }
484 else
485 {
486 for (y = 0; y < rect->h; ++y)
487 {
488 SDL_memcpy(dest, src, srcPitch);
489 src += srcPitch;
490 dest += tdata->pitch;
491 }
492 }
493 }
494
495 /* Clean up and return */
496 SDL_free(blob);
497 return 0;
498 }
499
500 /*************************************************************************************************
501 * Shader management functions *
502 *************************************************************************************************/
503
504 static GLES2_ShaderCacheEntry *GLES2_CacheShader(SDL_Renderer *renderer, GLES2_ShaderType type,
505 SDL_BlendMode blendMode);
506 static void GLES2_EvictShader(SDL_Renderer *renderer, GLES2_ShaderCacheEntry *entry);
507 static GLES2_ProgramCacheEntry *GLES2_CacheProgram(SDL_Renderer *renderer,
508 GLES2_ShaderCacheEntry *vertex,
509 GLES2_ShaderCacheEntry *fragment,
510 SDL_BlendMode blendMode);
511 static int GLES2_SelectProgram(SDL_Renderer *renderer, GLES2_ImageSource source,
512 SDL_BlendMode blendMode);
513 static int GLES2_SetOrthographicProjection(SDL_Renderer *renderer);
514
515 static GLES2_ProgramCacheEntry *
516 GLES2_CacheProgram(SDL_Renderer *renderer, GLES2_ShaderCacheEntry *vertex,
517 GLES2_ShaderCacheEntry *fragment, SDL_BlendMode blendMode)
518 {
519 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
520 GLES2_ProgramCacheEntry *entry;
521 GLES2_ShaderCacheEntry *shaderEntry;
522 GLint linkSuccessful;
523
524 /* Check if we've already cached this program */
525 entry = rdata->program_cache.head;
526 while (entry)
527 {
528 if (entry->vertex_shader == vertex && entry->fragment_shader == fragment)
529 break;
530 entry = entry->next;
531 }
532 if (entry)
533 {
534 if (rdata->program_cache.count > 1)
535 {
536 if (entry->next)
537 entry->next->prev = entry->prev;
538 if (entry->prev)
539 entry->prev->next = entry->next;
540 entry->prev = NULL;
541 entry->next = rdata->program_cache.head;
542 rdata->program_cache.head->prev = entry;
543 rdata->program_cache.head = entry;
544 }
545 return entry;
546 }
547
548 /* Create a program cache entry */
549 entry = (GLES2_ProgramCacheEntry *)SDL_calloc(1, sizeof(GLES2_ProgramCacheEntry));
550 if (!entry)
551 {
552 SDL_OutOfMemory();
553 return NULL;
554 }
555 entry->vertex_shader = vertex;
556 entry->fragment_shader = fragment;
557 entry->blend_mode = blendMode;
558
559 /* Create the program and link it */
560 glGetError();
561 entry->id = glCreateProgram();
562 glAttachShader(entry->id, vertex->id);
563 glAttachShader(entry->id, fragment->id);
564 glBindAttribLocation(entry->id, GLES2_ATTRIBUTE_POSITION, "a_position");
565 glBindAttribLocation(entry->id, GLES2_ATTRIBUTE_TEXCOORD, "a_texCoord");
566 glLinkProgram(entry->id);
567 glGetProgramiv(entry->id, GL_LINK_STATUS, &linkSuccessful);
568 if (glGetError() != GL_NO_ERROR || !linkSuccessful)
569 {
570 SDL_SetError("Failed to link shader program");
571 glDeleteProgram(entry->id);
572 SDL_free(entry);
573 return NULL;
574 }
575
576 /* Predetermine locations of uniform variables */
577 entry->uniform_locations[GLES2_UNIFORM_PROJECTION] =
578 glGetUniformLocation(entry->id, "u_projection");
579 entry->uniform_locations[GLES2_UNIFORM_TEXTURE] =
580 glGetUniformLocation(entry->id, "u_texture");
581 entry->uniform_locations[GLES2_UNIFORM_MODULATION] =
582 glGetUniformLocation(entry->id, "u_modulation");
583 entry->uniform_locations[GLES2_UNIFORM_COLOR] =
584 glGetUniformLocation(entry->id, "u_color");
585 entry->uniform_locations[GLES2_UNIFORM_COLORTABLE] =
586 glGetUniformLocation(entry->id, "u_colorTable");
587
588 /* Cache the linked program */
589 if (rdata->program_cache.head)
590 {
591 entry->next = rdata->program_cache.head;
592 rdata->program_cache.head->prev = entry;
593 }
594 else
595 {
596 rdata->program_cache.tail = entry;
597 }
598 rdata->program_cache.head = entry;
599 ++rdata->program_cache.count;
600
601 /* Increment the refcount of the shaders we're using */
602 ++vertex->references;
603 ++fragment->references;
604
605 /* Evict the last entry from the cache if we exceed the limit */
606 if (rdata->program_cache.count > GLES2_MAX_CACHED_PROGRAMS)
607 {
608 shaderEntry = rdata->program_cache.tail->vertex_shader;
609 if (--shaderEntry->references <= 0)
610 GLES2_EvictShader(renderer, shaderEntry);
611 shaderEntry = rdata->program_cache.tail->fragment_shader;
612 if (--shaderEntry->references <= 0)
613 GLES2_EvictShader(renderer, shaderEntry);
614 glDeleteProgram(rdata->program_cache.tail->id);
615 rdata->program_cache.tail = rdata->program_cache.tail->prev;
616 SDL_free(rdata->program_cache.tail->next);
617 rdata->program_cache.tail->next = NULL;
618 --rdata->program_cache.count;
619 }
620 return entry;
621 }
622
623 static GLES2_ShaderCacheEntry *
624 GLES2_CacheShader(SDL_Renderer *renderer, GLES2_ShaderType type, SDL_BlendMode blendMode)
625 {
626 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
627 const GLES2_Shader *shader;
628 const GLES2_ShaderInstance *instance = NULL;
629 GLES2_ShaderCacheEntry *entry = NULL;
630 GLint compileSuccessful = GL_FALSE;
631 int i, j;
632
633 /* Find the corresponding shader */
634 shader = GLES2_GetShader(type, blendMode);
635 if (!shader)
636 {
637 SDL_SetError("No shader matching the requested characteristics was found");
638 return NULL;
639 }
640
641 /* Find a matching shader instance that's supported on this hardware */
642 for (i = 0; i < shader->instance_count; ++i)
643 {
644 for (j = 0; j < rdata->shader_format_count; ++j)
645 {
646 if (!shader->instances)
647 continue;
648 if (shader->instances[i]->format != rdata->shader_formats[j])
649 continue;
650 instance = shader->instances[i];
651 break;
652 }
653 }
654 if (!instance)
655 {
656 SDL_SetError("The specified shader cannot be loaded on the current platform");
657 return NULL;
658 }
659
660 /* Check if we've already cached this shader */
661 entry = rdata->shader_cache.head;
662 while (entry)
663 {
664 if (entry->instance == instance)
665 break;
666 entry = entry->next;
667 }
668 if (entry)
669 return entry;
670
671 /* Create a shader cache entry */
672 entry = (GLES2_ShaderCacheEntry *)SDL_calloc(1, sizeof(GLES2_ShaderCacheEntry));
673 if (!entry)
674 {
675 SDL_OutOfMemory();
676 return NULL;
677 }
678 entry->type = type;
679 entry->instance = instance;
680
681 /* Compile or load the selected shader instance */
682 glGetError();
683 entry->id = glCreateShader(instance->type);
684 if (instance->format == (GLenum)-1)
685 {
686 glShaderSource(entry->id, 1, (const char **)&instance->data, &instance->length);
687 glCompileShader(entry->id);
688 glGetShaderiv(entry->id, GL_COMPILE_STATUS, &compileSuccessful);
689 }
690 else
691 {
692 glShaderBinary(1, &entry->id, instance->format, instance->data, instance->length);
693 compileSuccessful = GL_TRUE;
694 }
695 if (glGetError() != GL_NO_ERROR || !compileSuccessful)
696 {
697 SDL_SetError("Failed to load the specified shader");
698 glDeleteShader(entry->id);
699 SDL_free(entry);
700 return NULL;
701 }
702
703 /* Link the shader entry in at the front of the cache */
704 if (rdata->shader_cache.head)
705 {
706 entry->next = rdata->shader_cache.head;
707 rdata->shader_cache.head->prev = entry;
708 }
709 rdata->shader_cache.head = entry;
710 ++rdata->shader_cache.count;
711 return entry;
712 }
713
714 static void
715 GLES2_EvictShader(SDL_Renderer *renderer, GLES2_ShaderCacheEntry *entry)
716 {
717 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
718
719 /* Unlink the shader from the cache */
720 if (entry->next)
721 entry->next->prev = entry->prev;
722 if (entry->prev)
723 entry->prev->next = entry->next;
724 if (rdata->shader_cache.head == entry)
725 rdata->shader_cache.head = entry->next;
726 --rdata->shader_cache.count;
727
728 /* Deallocate the shader */
729 glDeleteShader(entry->id);
730 SDL_free(entry);
731 }
732
733 static int
734 GLES2_SelectProgram(SDL_Renderer *renderer, GLES2_ImageSource source, SDL_BlendMode blendMode)
735 {
736 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
737 GLES2_ShaderCacheEntry *vertex = NULL;
738 GLES2_ShaderCacheEntry *fragment = NULL;
739 GLES2_ShaderType vtype, ftype;
740 GLES2_ProgramCacheEntry *program;
741
742 /* Select an appropriate shader pair for the specified modes */
743 vtype = GLES2_SHADER_VERTEX_DEFAULT;
744 switch (source)
745 {
746 case GLES2_IMAGESOURCE_SOLID:
747 ftype = GLES2_SHADER_FRAGMENT_SOLID_SRC;
748 break;
749 case GLES2_IMAGESOURCE_TEXTURE:
750 ftype = GLES2_SHADER_FRAGMENT_TEXTURE_SRC;
751 break;
752 }
753
754 /* Load the requested shaders */
755 vertex = GLES2_CacheShader(renderer, vtype, blendMode);
756 if (!vertex)
757 goto fault;
758 fragment = GLES2_CacheShader(renderer, ftype, blendMode);
759 if (!fragment)
760 goto fault;
761
762 /* Check if we need to change programs at all */
763 if (rdata->current_program &&
764 rdata->current_program->vertex_shader == vertex &&
765 rdata->current_program->fragment_shader == fragment)
766 return 0;
767
768 /* Generate a matching program */
769 program = GLES2_CacheProgram(renderer, vertex, fragment, blendMode);
770 if (!program)
771 goto fault;
772
773 /* Select that program in OpenGL */
774 glGetError();
775 glUseProgram(program->id);
776 if (glGetError() != GL_NO_ERROR)
777 {
778 SDL_SetError("Failed to select program");
779 goto fault;
780 }
781
782 /* Set the current program */
783 rdata->current_program = program;
784
785 /* Activate an orthographic projection */
786 if (GLES2_SetOrthographicProjection(renderer) < 0)
787 goto fault;
788
789 /* Clean up and return */
790 return 0;
791 fault:
792 if (vertex && vertex->references <= 0)
793 GLES2_EvictShader(renderer, vertex);
794 if (fragment && fragment->references <= 0)
795 GLES2_EvictShader(renderer, fragment);
796 rdata->current_program = NULL;
797 return -1;
798 }
799
800 static int
801 GLES2_SetOrthographicProjection(SDL_Renderer *renderer)
802 {
803 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
804 SDL_Window *window = renderer->window;
805 int w, h;
806 GLfloat projection[4][4];
807 GLuint locProjection;
808
809 /* Get the window width and height */
810 SDL_GetWindowSize(window, &w, &h);
811
812 /* Prepare an orthographic projection */
813 projection[0][0] = 2.0f / w;
814 projection[0][1] = 0.0f;
815 projection[0][2] = 0.0f;
816 projection[0][3] = 0.0f;
817 projection[1][0] = 0.0f;
818 projection[1][1] = -2.0f / h;
819 projection[1][2] = 0.0f;
820 projection[1][3] = 0.0f;
821 projection[2][0] = 0.0f;
822 projection[2][1] = 0.0f;
823 projection[2][2] = 1.0f;
824 projection[2][3] = 0.0f;
825 projection[3][0] = -1.0f;
826 projection[3][1] = 1.0f;
827 projection[3][2] = 0.0f;
828 projection[3][3] = 1.0f;
829
830 /* Set the projection matrix */
831 locProjection = rdata->current_program->uniform_locations[GLES2_UNIFORM_PROJECTION];
832 glGetError();
833 glUniformMatrix4fv(locProjection, 1, GL_FALSE, (GLfloat *)projection);
834 if (glGetError() != GL_NO_ERROR)
835 {
836 SDL_SetError("Failed to set orthographic projection");
837 return -1;
838 }
839 return 0;
840 }
841
842 /*************************************************************************************************
843 * Rendering functions *
844 *************************************************************************************************/
845
846 static int GLES2_RenderClear(SDL_Renderer *renderer);
847 static int GLES2_RenderDrawPoints(SDL_Renderer *renderer, const SDL_Point *points, int count);
848 static int GLES2_RenderDrawLines(SDL_Renderer *renderer, const SDL_Point *points, int count);
849 static int GLES2_RenderDrawRects(SDL_Renderer *renderer, const SDL_Rect **rects, int count);
850 static int GLES2_RenderFillRects(SDL_Renderer *renderer, const SDL_Rect **rects, int count);
851 static int GLES2_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect,
852 const SDL_Rect *dstrect);
853 static void GLES2_RenderPresent(SDL_Renderer *renderer);
854
855 static int
856 GLES2_RenderClear(SDL_Renderer *renderer)
857 {
858 float r = (float)renderer->r / 255.0f;
859 float g = (float)renderer->g / 255.0f;
860 float b = (float)renderer->b / 255.0f;
861 float a = (float)renderer->a / 255.0f;
862
863 GLES2_ActivateRenderer(renderer);
864
865 /* Clear the backbuffer with the selected color */
866 glClearColor(r, g, b, a);
867 glClear(GL_COLOR_BUFFER_BIT);
868 return 0;
869 }
870
871 static void
872 GLES2_SetBlendMode(int blendMode)
873 {
874 switch (blendMode)
875 {
876 case SDL_BLENDMODE_NONE:
877 default:
878 glDisable(GL_BLEND);
879 break;
880 case SDL_BLENDMODE_BLEND:
881 glEnable(GL_BLEND);
882 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
883 break;
884 case SDL_BLENDMODE_ADD:
885 glEnable(GL_BLEND);
886 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
887 break;
888 case SDL_BLENDMODE_MOD:
889 glEnable(GL_BLEND);
890 glBlendFunc(GL_ZERO, GL_SRC_COLOR);
891 break;
892 }
893 }
894
895 static int
896 GLES2_RenderDrawPoints(SDL_Renderer *renderer, const SDL_Point *points, int count)
897 {
898 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
899 GLfloat *vertices;
900 SDL_BlendMode blendMode;
901 int alpha;
902 GLuint locColor;
903 int idx;
904
905 GLES2_ActivateRenderer(renderer);
906
907 blendMode = renderer->blendMode;
908 alpha = renderer->a;
909
910 /* Activate an appropriate shader and set the projection matrix */
911 if (GLES2_SelectProgram(renderer, GLES2_IMAGESOURCE_SOLID, blendMode) < 0)
912 return -1;
913
914 /* Select the color to draw with */
915 locColor = rdata->current_program->uniform_locations[GLES2_UNIFORM_COLOR];
916 glGetError();
917 glUniform4f(locColor,
918 renderer->r / 255.0f,
919 renderer->g / 255.0f,
920 renderer->b / 255.0f,
921 alpha / 255.0f);
922
923 /* Configure the correct blend mode */
924 GLES2_SetBlendMode(blendMode);
925
926 /* Emit the specified vertices as points */
927 vertices = SDL_stack_alloc(GLfloat, count * 2);
928 for (idx = 0; idx < count; ++idx)
929 {
930 GLfloat x = (GLfloat)points[idx].x + 0.5f;
931 GLfloat y = (GLfloat)points[idx].y + 0.5f;
932
933 vertices[idx * 2] = x;
934 vertices[(idx * 2) + 1] = y;
935 }
936 glEnableVertexAttribArray(GLES2_ATTRIBUTE_POSITION);
937 glVertexAttribPointer(GLES2_ATTRIBUTE_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
938 glDrawArrays(GL_POINTS, 0, count);
939 glDisableVertexAttribArray(GLES2_ATTRIBUTE_POSITION);
940 SDL_stack_free(vertices);
941 if (glGetError() != GL_NO_ERROR)
942 {
943 SDL_SetError("Failed to render lines");
944 return -1;
945 }
946 return 0;
947 }
948
949 static int
950 GLES2_RenderDrawLines(SDL_Renderer *renderer, const SDL_Point *points, int count)
951 {
952 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
953 GLfloat *vertices;
954 SDL_BlendMode blendMode;
955 int alpha;
956 GLuint locColor;
957 int idx;
958
959 GLES2_ActivateRenderer(renderer);
960
961 blendMode = renderer->blendMode;
962 alpha = renderer->a;
963
964 /* Activate an appropriate shader and set the projection matrix */
965 if (GLES2_SelectProgram(renderer, GLES2_IMAGESOURCE_SOLID, blendMode) < 0)
966 return -1;
967
968 /* Select the color to draw with */
969 locColor = rdata->current_program->uniform_locations[GLES2_UNIFORM_COLOR];
970 glGetError();
971 glUniform4f(locColor,
972 renderer->r / 255.0f,
973 renderer->g / 255.0f,
974 renderer->b / 255.0f,
975 alpha / 255.0f);
976
977 /* Configure the correct blend mode */
978 GLES2_SetBlendMode(blendMode);
979
980 /* Emit a line strip including the specified vertices */
981 vertices = SDL_stack_alloc(GLfloat, count * 2);
982 for (idx = 0; idx < count; ++idx)
983 {
984 GLfloat x = (GLfloat)points[idx].x + 0.5f;
985 GLfloat y = (GLfloat)points[idx].y + 0.5f;
986
987 vertices[idx * 2] = x;
988 vertices[(idx * 2) + 1] = y;
989 }
990 glEnableVertexAttribArray(GLES2_ATTRIBUTE_POSITION);
991 glVertexAttribPointer(GLES2_ATTRIBUTE_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
992 glDrawArrays(GL_LINE_STRIP, 0, count);
993 glDisableVertexAttribArray(GLES2_ATTRIBUTE_POSITION);
994 SDL_stack_free(vertices);
995 if (glGetError() != GL_NO_ERROR)
996 {
997 SDL_SetError("Failed to render lines");
998 return -1;
999 }
1000 return 0;
1001 }
1002
1003 static int
1004 GLES2_RenderFillRects(SDL_Renderer *renderer, const SDL_Rect **rects, int count)
1005 {
1006 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
1007 GLfloat vertices[8];
1008 SDL_BlendMode blendMode;
1009 int alpha;
1010 GLuint locColor;
1011 int idx;
1012
1013 GLES2_ActivateRenderer(renderer);
1014
1015 blendMode = renderer->blendMode;
1016 alpha = renderer->a;
1017
1018 /* Activate an appropriate shader and set the projection matrix */
1019 if (GLES2_SelectProgram(renderer, GLES2_IMAGESOURCE_SOLID, blendMode) < 0)
1020 return -1;
1021
1022 /* Select the color to draw with */
1023 locColor = rdata->current_program->uniform_locations[GLES2_UNIFORM_COLOR];
1024 glGetError();
1025 glUniform4f(locColor,
1026 renderer->r / 255.0f,
1027 renderer->g / 255.0f,
1028 renderer->b / 255.0f,
1029 alpha / 255.0f);
1030
1031 /* Configure the correct blend mode */
1032 GLES2_SetBlendMode(blendMode);
1033
1034 /* Emit a line loop for each rectangle */
1035 glEnableVertexAttribArray(GLES2_ATTRIBUTE_POSITION);
1036 for (idx = 0; idx < count; ++idx)
1037 {
1038 GLfloat xMin = (GLfloat)rects[idx]->x;
1039 GLfloat xMax = (GLfloat)(rects[idx]->x + rects[idx]->w);
1040 GLfloat yMin = (GLfloat)rects[idx]->y;
1041 GLfloat yMax = (GLfloat)(rects[idx]->y + rects[idx]->h);
1042
1043 vertices[0] = xMin;
1044 vertices[1] = yMin;
1045 vertices[2] = xMax;
1046 vertices[3] = yMin;
1047 vertices[4] = xMin;
1048 vertices[5] = yMax;
1049 vertices[6] = xMax;
1050 vertices[7] = yMax;
1051 glVertexAttribPointer(GLES2_ATTRIBUTE_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
1052 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
1053 }
1054 glDisableVertexAttribArray(GLES2_ATTRIBUTE_POSITION);
1055 if (glGetError() != GL_NO_ERROR)
1056 {
1057 SDL_SetError("Failed to render lines");
1058 return -1;
1059 }
1060 return 0;
1061 }
1062
1063 static int
1064 GLES2_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect,
1065 const SDL_Rect *dstrect)
1066 {
1067 GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
1068 GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
1069 GLES2_ImageSource sourceType;
1070 SDL_BlendMode blendMode;
1071 int alpha;
1072 GLfloat vertices[8];
1073 GLfloat texCoords[8];
1074 GLuint locTexture;
1075 GLuint locModulation;
1076 GLuint locColorTable;
1077
1078 GLES2_ActivateRenderer(renderer);
1079
1080 /* Activate an appropriate shader and set the projection matrix */
1081 blendMode = texture->blendMode;
1082 alpha = texture->a;
1083 sourceType = GLES2_IMAGESOURCE_TEXTURE;
1084 if (GLES2_SelectProgram(renderer, sourceType, blendMode) < 0)
1085 return -1;
1086
1087 /* Select the target texture */
1088 locTexture = rdata->current_program->uniform_locations[GLES2_UNIFORM_TEXTURE];
1089 glGetError();
1090 glActiveTexture(GL_TEXTURE0);
1091 glBindTexture(tdata->texture_type, tdata->texture);
1092 glUniform1i(locTexture, 0);
1093
1094 /* Configure texture blending */
1095 GLES2_SetBlendMode(blendMode);
1096
1097 /* Configure color modulation */
1098 locModulation = rdata->current_program->uniform_locations[GLES2_UNIFORM_MODULATION];
1099 glUniform4f(locModulation,
1100 texture->r / 255.0f,
1101 texture->g / 255.0f,
1102 texture->b / 255.0f,
1103 alpha / 255.0f);
1104
1105 /* Emit the textured quad */
1106 glEnableVertexAttribArray(GLES2_ATTRIBUTE_TEXCOORD);
1107 glEnableVertexAttribArray(GLES2_ATTRIBUTE_POSITION);
1108 vertices[0] = (GLfloat)dstrect->x;
1109 vertices[1] = (GLfloat)dstrect->y;
1110 vertices[2] = (GLfloat)(dstrect->x + dstrect->w);
1111 vertices[3] = (GLfloat)dstrect->y;
1112 vertices[4] = (GLfloat)dstrect->x;
1113 vertices[5] = (GLfloat)(dstrect->y + dstrect->h);
1114 vertices[6] = (GLfloat)(dstrect->x + dstrect->w);
1115 vertices[7] = (GLfloat)(dstrect->y + dstrect->h);
1116 glVertexAttribPointer(GLES2_ATTRIBUTE_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
1117 texCoords[0] = srcrect->x / (GLfloat)texture->w;
1118 texCoords[1] = srcrect->y / (GLfloat)texture->h;
1119 texCoords[2] = (srcrect->x + srcrect->w) / (GLfloat)texture->w;
1120 texCoords[3] = srcrect->y / (GLfloat)texture->h;
1121 texCoords[4] = srcrect->x / (GLfloat)texture->w;
1122 texCoords[5] = (srcrect->y + srcrect->h) / (GLfloat)texture->h;
1123 texCoords[6] = (srcrect->x + srcrect->w) / (GLfloat)texture->w;
1124 texCoords[7] = (srcrect->y + srcrect->h) / (GLfloat)texture->h;
1125 glVertexAttribPointer(GLES2_ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
1126 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
1127 glDisableVertexAttribArray(GLES2_ATTRIBUTE_POSITION);
1128 glDisableVertexAttribArray(GLES2_ATTRIBUTE_TEXCOORD);
1129 if (glGetError() != GL_NO_ERROR)
1130 {
1131 SDL_SetError("Failed to render texture");
1132 return -1;
1133 }
1134 return 0;
1135 }
1136
1137 static void
1138 GLES2_RenderPresent(SDL_Renderer *renderer)
1139 {
1140 GLES2_ActivateRenderer(renderer);
1141
1142 /* Tell the video driver to swap buffers */
1143 SDL_GL_SwapWindow(renderer->window);
1144 }
1145
1146 /*************************************************************************************************
1147 * Renderer instantiation *
1148 *************************************************************************************************/
1149
1150 #define GL_NVIDIA_PLATFORM_BINARY_NV 0x890B
1151
1152 static SDL_Renderer *
1153 GLES2_CreateRenderer(SDL_Window *window, Uint32 flags)
1154 {
1155 SDL_Renderer *renderer;
1156 GLES2_DriverContext *rdata;
1157 GLint nFormats;
1158 #ifndef ZUNE_HD
1159 GLboolean hasCompiler;
1160 #endif
1161
1162 /* Create the renderer struct */
1163 renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(SDL_Renderer));
1164 rdata = (GLES2_DriverContext *)SDL_calloc(1, sizeof(GLES2_DriverContext));
1165 if (!renderer)
1166 {
1167 SDL_OutOfMemory();
1168 SDL_free(renderer);
1169 SDL_free(rdata);
1170 return NULL;
1171 }
1172 renderer->info = GLES2_RenderDriver.info;
1173 renderer->window = window;
1174 renderer->driverdata = rdata;
1175
1176 /* Create the GL context */
1177 rdata->context = SDL_GL_CreateContext(window);
1178 if (!rdata->context)
1179 {
1180 SDL_free(renderer);
1181 SDL_free(rdata);
1182 return NULL;
1183 }
1184
1185 /* Determine supported shader formats */
1186 /* HACK: glGetInteger is broken on the Zune HD's compositor, so we just hardcode this */
1187 glGetError();
1188 #ifdef ZUNE_HD
1189 nFormats = 1;
1190 #else /* !ZUNE_HD */
1191 glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &nFormats);
1192 glGetBooleanv(GL_SHADER_COMPILER, &hasCompiler);
1193 if (hasCompiler)
1194 ++nFormats;
1195 #endif /* ZUNE_HD */
1196 rdata->shader_formats = (GLenum *)SDL_calloc(nFormats, sizeof(GLenum));
1197 if (!rdata->shader_formats)
1198 {
1199 SDL_OutOfMemory();
1200 SDL_free(renderer);
1201 SDL_free(rdata);
1202 return NULL;
1203 }
1204 rdata->shader_format_count = nFormats;
1205 #ifdef ZUNE_HD
1206 rdata->shader_formats[0] = GL_NVIDIA_PLATFORM_BINARY_NV;
1207 #else /* !ZUNE_HD */
1208 glGetIntegerv(GL_SHADER_BINARY_FORMATS, (GLint *)rdata->shader_formats);
1209 if (glGetError() != GL_NO_ERROR)
1210 {
1211 SDL_SetError("Failed to query supported shader formats");
1212 SDL_free(renderer);
1213 SDL_free(rdata->shader_formats);
1214 SDL_free(rdata);
1215 return NULL;
1216 }
1217 if (hasCompiler)
1218 rdata->shader_formats[nFormats - 1] = (GLenum)-1;
1219 #endif /* ZUNE_HD */
1220
1221 /* Populate the function pointers for the module */
1222 renderer->WindowEvent = &GLES2_WindowEvent;
1223 renderer->CreateTexture = &GLES2_CreateTexture;
1224 renderer->UpdateTexture = &GLES2_UpdateTexture;
1225 renderer->LockTexture = &GLES2_LockTexture;
1226 renderer->UnlockTexture = &GLES2_UnlockTexture;
1227 renderer->RenderClear = &GLES2_RenderClear;
1228 renderer->RenderDrawPoints = &GLES2_RenderDrawPoints;
1229 renderer->RenderDrawLines = &GLES2_RenderDrawLines;
1230 renderer->RenderFillRects = &GLES2_RenderFillRects;
1231 renderer->RenderCopy = &GLES2_RenderCopy;
1232 renderer->RenderPresent = &GLES2_RenderPresent;
1233 renderer->DestroyTexture = &GLES2_DestroyTexture;
1234 renderer->DestroyRenderer = &GLES2_DestroyRenderer;
1235 return renderer;
1236 }
1237
1238 #endif /* SDL_VIDEO_RENDER_OGL_ES2 */
1239
1240 /* vi: set ts=4 sw=4 expandtab: */