1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      OpenGL drawing routines
12  *
13  *      By Elias Pschernig.
14  */
15 
16 #include "allegro5/allegro.h"
17 #include "allegro5/allegro_opengl.h"
18 #include "allegro5/internal/aintern_display.h"
19 #include "allegro5/internal/aintern_memdraw.h"
20 #include "allegro5/internal/aintern_opengl.h"
21 
22 #ifdef ALLEGRO_ANDROID
23 #include "allegro5/internal/aintern_android.h"
24 #endif
25 
26 #include "ogl_helpers.h"
27 
28 ALLEGRO_DEBUG_CHANNEL("opengl")
29 
30 /* FIXME: For some reason x86_64 Android crashes for me when calling
31  * glBlendColor - so adding this hack to disable it.
32  */
33 #ifdef ALLEGRO_ANDROID
34 #if defined(__x86_64__) || defined(__i686__)
35 #define ALLEGRO_ANDROID_HACK_X86_64
36 #endif
37 #endif
38 
try_const_color(ALLEGRO_DISPLAY * ogl_disp,ALLEGRO_COLOR * c)39 static void try_const_color(ALLEGRO_DISPLAY *ogl_disp, ALLEGRO_COLOR *c)
40 {
41    #ifdef ALLEGRO_CFG_OPENGLES
42       #ifndef ALLEGRO_CFG_OPENGLES2
43          return;
44       #endif
45       // Only OpenGL ES 2.0 has glBlendColor
46       if (ogl_disp->ogl_extras->ogl_info.version < _ALLEGRO_OPENGL_VERSION_2_0) {
47          return;
48       }
49    #else
50    (void)ogl_disp;
51    #endif
52    glBlendColor(c->r, c->g, c->b, c->a);
53 }
54 
_al_opengl_set_blender(ALLEGRO_DISPLAY * ogl_disp)55 bool _al_opengl_set_blender(ALLEGRO_DISPLAY *ogl_disp)
56 {
57    int op, src_color, dst_color, op_alpha, src_alpha, dst_alpha;
58    ALLEGRO_COLOR const_color;
59    const int blend_modes[10] = {
60       GL_ZERO, GL_ONE, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
61       GL_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_SRC_COLOR,
62       GL_ONE_MINUS_DST_COLOR,
63 #if defined(ALLEGRO_CFG_OPENGLES2) || !defined(ALLEGRO_CFG_OPENGLES)
64       GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR
65 #else
66       GL_ONE, GL_ONE
67 #endif
68    };
69    const int blend_equations[3] = {
70       GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT
71    };
72 
73    (void)ogl_disp;
74 
75    al_get_separate_bitmap_blender(&op, &src_color, &dst_color,
76       &op_alpha, &src_alpha, &dst_alpha);
77    const_color = al_get_bitmap_blend_color();
78    /* glBlendFuncSeparate was only included with OpenGL 1.4 */
79 #if !defined ALLEGRO_CFG_OPENGLES
80    if (ogl_disp->ogl_extras->ogl_info.version >= _ALLEGRO_OPENGL_VERSION_1_4) {
81 #else
82    /* FIXME: At this time (09/2014) there are a lot of Android phones that
83     * don't support glBlendFuncSeparate even though they claim OpenGL ES 2.0
84     * support. Rather than not work on 20-25% of phones, we just don't support
85     * separate blending on Android for now.
86     */
87 #if defined ALLEGRO_ANDROID && !defined ALLEGRO_CFG_OPENGLES3
88    if (false) {
89 #else
90    if (ogl_disp->ogl_extras->ogl_info.version >= _ALLEGRO_OPENGL_VERSION_2_0) {
91 #endif
92 #endif
93       glEnable(GL_BLEND);
94       try_const_color(ogl_disp, &const_color);
95       glBlendFuncSeparate(blend_modes[src_color], blend_modes[dst_color],
96          blend_modes[src_alpha], blend_modes[dst_alpha]);
97       if (ogl_disp->ogl_extras->ogl_info.version >= _ALLEGRO_OPENGL_VERSION_2_0) {
98          glBlendEquationSeparate(
99             blend_equations[op],
100             blend_equations[op_alpha]);
101       }
102       else {
103          glBlendEquation(blend_equations[op]);
104       }
105    }
106    else {
107       if (src_color == src_alpha && dst_color == dst_alpha) {
108          glEnable(GL_BLEND);
109          try_const_color(ogl_disp, &const_color);
110          glBlendFunc(blend_modes[src_color], blend_modes[dst_color]);
111       }
112       else {
113          ALLEGRO_ERROR("Blender unsupported with this OpenGL version (%d %d %d %d %d %d)\n",
114             op, src_color, dst_color, op_alpha, src_alpha, dst_alpha);
115          return false;
116       }
117    }
118    return true;
119 }
120 
121 /* These functions make drawing calls use shaders or the fixed pipeline
122  * based on what the user has set up. FIXME: OpenGL only right now.
123  */
124 
125 static void vert_ptr_on(ALLEGRO_DISPLAY *display, int n, GLint t, int stride, void *v)
126 {
127 /* Only use this shader stuff with GLES2+ or equivalent */
128    if (display->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
129 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
130       if (display->ogl_extras->varlocs.pos_loc >= 0) {
131          glVertexAttribPointer(display->ogl_extras->varlocs.pos_loc, n, t, false, stride, v);
132          glEnableVertexAttribArray(display->ogl_extras->varlocs.pos_loc);
133       }
134 #endif
135    }
136    else {
137 #ifdef ALLEGRO_CFG_OPENGL_FIXED_FUNCTION
138       glEnableClientState(GL_VERTEX_ARRAY);
139       glVertexPointer(n, t, stride, v);
140 #endif
141    }
142 }
143 
144 static void vert_ptr_off(ALLEGRO_DISPLAY *display)
145 {
146    if (display->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
147 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
148       if (display->ogl_extras->varlocs.pos_loc >= 0) {
149          glDisableVertexAttribArray(display->ogl_extras->varlocs.pos_loc);
150       }
151 #endif
152    }
153    else {
154 #ifdef ALLEGRO_CFG_OPENGL_FIXED_FUNCTION
155       glDisableClientState(GL_VERTEX_ARRAY);
156 #endif
157    }
158 }
159 
160 static void color_ptr_on(ALLEGRO_DISPLAY *display, int n, GLint t, int stride, void *v)
161 {
162    if (display->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
163 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
164       if (display->ogl_extras->varlocs.color_loc >= 0) {
165          glVertexAttribPointer(display->ogl_extras->varlocs.color_loc, n, t, false, stride, v);
166          glEnableVertexAttribArray(display->ogl_extras->varlocs.color_loc);
167       }
168 #endif
169    }
170    else {
171 #ifdef ALLEGRO_CFG_OPENGL_FIXED_FUNCTION
172       glEnableClientState(GL_COLOR_ARRAY);
173       glColorPointer(n, t, stride, v);
174 #endif
175    }
176 }
177 
178 static void color_ptr_off(ALLEGRO_DISPLAY *display)
179 {
180    if (display->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
181 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
182       if (display->ogl_extras->varlocs.color_loc >= 0) {
183          glDisableVertexAttribArray(display->ogl_extras->varlocs.color_loc);
184       }
185 #endif
186    }
187    else {
188 #ifdef ALLEGRO_CFG_OPENGL_FIXED_FUNCTION
189       glDisableClientState(GL_COLOR_ARRAY);
190 #endif
191    }
192 }
193 
194 static void tex_ptr_on(ALLEGRO_DISPLAY *display, int n, GLint t, int stride, void *v)
195 {
196    if (display->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
197 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
198       if (display->ogl_extras->varlocs.texcoord_loc >= 0) {
199          glVertexAttribPointer(display->ogl_extras->varlocs.texcoord_loc, n, t, false, stride, v);
200          glEnableVertexAttribArray(display->ogl_extras->varlocs.texcoord_loc);
201       }
202 #endif
203    }
204    else {
205 #ifdef ALLEGRO_CFG_OPENGL_FIXED_FUNCTION
206       glEnableClientState(GL_TEXTURE_COORD_ARRAY);
207       glTexCoordPointer(n, t, stride, v);
208 #endif
209    }
210 }
211 
212 static void tex_ptr_off(ALLEGRO_DISPLAY *display)
213 {
214    if (display->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
215 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
216       if (display->ogl_extras->varlocs.texcoord_loc >= 0) {
217          glDisableVertexAttribArray(display->ogl_extras->varlocs.texcoord_loc);
218       }
219 #endif
220    }
221    else {
222 #ifdef ALLEGRO_CFG_OPENGL_FIXED_FUNCTION
223       glDisableClientState(GL_TEXTURE_COORD_ARRAY);
224 #endif
225    }
226 }
227 
228 static void ogl_clear(ALLEGRO_DISPLAY *d, ALLEGRO_COLOR *color)
229 {
230    ALLEGRO_DISPLAY *ogl_disp = (void *)d;
231    ALLEGRO_BITMAP *target = al_get_target_bitmap();
232    ALLEGRO_BITMAP_EXTRA_OPENGL *ogl_target;
233    float r, g, b, a;
234 
235    if (target->parent)
236       target = target->parent;
237 
238    ogl_target = target->extra;
239 
240    if ((!ogl_target->is_backbuffer &&
241          ogl_disp->ogl_extras->opengl_target != target)
242       || target->locked)
243    {
244       _al_clear_bitmap_by_locking(target, color);
245       return;
246    }
247 
248    al_unmap_rgba_f(*color, &r, &g, &b, &a);
249 
250    glClearColor(r, g, b, a);
251    glClear(GL_COLOR_BUFFER_BIT);
252 }
253 
254 
255 static void ogl_draw_pixel(ALLEGRO_DISPLAY *d, float x, float y,
256    ALLEGRO_COLOR *color)
257 {
258    ALLEGRO_BITMAP *target = al_get_target_bitmap();
259    ALLEGRO_BITMAP_EXTRA_OPENGL *ogl_target;
260    GLfloat vert[2];
261    GLfloat color_array[4];
262 
263    /* For sub-bitmaps */
264    if (target->parent) {
265       target = target->parent;
266    }
267 
268    ogl_target = target->extra;
269 
270    if ((!ogl_target->is_backbuffer &&
271       d->ogl_extras->opengl_target != target) ||
272       target->locked || !_al_opengl_set_blender(d))  {
273       _al_draw_pixel_memory(target, x, y, color);
274       return;
275    }
276 
277    vert[0] = x;
278    vert[1] = y;
279 
280    color_array[0] = color->r;
281    color_array[1] = color->g;
282    color_array[2] = color->b;
283    color_array[3] = color->a;
284 
285    vert_ptr_on(d, 2, GL_FLOAT, 2*sizeof(float), vert);
286    color_ptr_on(d, 4, GL_FLOAT, 4*sizeof(float), color_array);
287 
288    // Should this be here if it's in the if above?
289    if (!_al_opengl_set_blender(d)) {
290       return;
291    }
292 
293    glDrawArrays(GL_POINTS, 0, 1);
294 
295    vert_ptr_off(d);
296    color_ptr_off(d);
297 }
298 
299 static void* ogl_prepare_vertex_cache(ALLEGRO_DISPLAY* disp,
300                                       int num_new_vertices)
301 {
302    disp->num_cache_vertices += num_new_vertices;
303    if (!disp->vertex_cache) {
304       disp->vertex_cache = al_malloc(num_new_vertices * sizeof(ALLEGRO_OGL_BITMAP_VERTEX));
305 
306       disp->vertex_cache_size = num_new_vertices;
307    } else if (disp->num_cache_vertices > disp->vertex_cache_size) {
308       disp->vertex_cache = al_realloc(disp->vertex_cache,
309                               2 * disp->num_cache_vertices * sizeof(ALLEGRO_OGL_BITMAP_VERTEX));
310 
311       disp->vertex_cache_size = 2 * disp->num_cache_vertices;
312    }
313    return (ALLEGRO_OGL_BITMAP_VERTEX*)disp->vertex_cache +
314          (disp->num_cache_vertices - num_new_vertices);
315 }
316 
317 static void ogl_flush_vertex_cache(ALLEGRO_DISPLAY *disp)
318 {
319    GLuint current_texture;
320    ALLEGRO_OGL_EXTRAS *o = disp->ogl_extras;
321    (void)o; /* not used in all ports */
322 
323    if (!disp->vertex_cache)
324       return;
325    if (disp->num_cache_vertices == 0)
326       return;
327 
328    if (!_al_opengl_set_blender(disp)) {
329       disp->num_cache_vertices = 0;
330       return;
331    }
332 
333    if (disp->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
334 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
335       if (disp->ogl_extras->varlocs.use_tex_loc >= 0) {
336          glUniform1i(disp->ogl_extras->varlocs.use_tex_loc, 1);
337       }
338       if (disp->ogl_extras->varlocs.use_tex_matrix_loc >= 0) {
339          glUniform1i(disp->ogl_extras->varlocs.use_tex_matrix_loc, 0);
340       }
341 #endif
342    }
343    else {
344       glEnable(GL_TEXTURE_2D);
345    }
346 
347    glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&current_texture);
348    if (current_texture != disp->cache_texture) {
349       if (disp->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
350 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
351          /* Use texture unit 0 */
352          glActiveTexture(GL_TEXTURE0);
353          if (disp->ogl_extras->varlocs.tex_loc >= 0)
354             glUniform1i(disp->ogl_extras->varlocs.tex_loc, 0);
355 #endif
356       }
357       glBindTexture(GL_TEXTURE_2D, disp->cache_texture);
358    }
359 
360 #if !defined(ALLEGRO_CFG_OPENGLES) && !defined(ALLEGRO_MACOSX)
361    if (disp->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
362       int stride = sizeof(ALLEGRO_OGL_BITMAP_VERTEX);
363       int bytes = disp->num_cache_vertices * stride;
364 
365       /* We create the VAO and VBO on first use. */
366       if (o->vao == 0) {
367          glGenVertexArrays(1, &o->vao);
368          ALLEGRO_DEBUG("new VAO: %u\n", o->vao);
369       }
370       glBindVertexArray(o->vao);
371 
372       if (o->vbo == 0) {
373          glGenBuffers(1, &o->vbo);
374          ALLEGRO_DEBUG("new VBO: %u\n", o->vbo);
375       }
376       glBindBuffer(GL_ARRAY_BUFFER, o->vbo);
377 
378       /* Then we upload data into it. */
379       glBufferData(GL_ARRAY_BUFFER, bytes, disp->vertex_cache, GL_STREAM_DRAW);
380 
381       /* Finally set the "pos", "texccord" and "color" attributes used by our
382        * shader and enable them.
383        */
384       if (o->varlocs.pos_loc >= 0)  {
385          glVertexAttribPointer(o->varlocs.pos_loc, 3, GL_FLOAT, false, stride,
386             (void *)offsetof(ALLEGRO_OGL_BITMAP_VERTEX, x));
387          glEnableVertexAttribArray(o->varlocs.pos_loc);
388       }
389 
390       if (o->varlocs.texcoord_loc >= 0) {
391          glVertexAttribPointer(o->varlocs.texcoord_loc, 2, GL_FLOAT, false, stride,
392             (void *)offsetof(ALLEGRO_OGL_BITMAP_VERTEX, tx));
393          glEnableVertexAttribArray(o->varlocs.texcoord_loc);
394       }
395 
396       if (o->varlocs.color_loc >= 0) {
397          glVertexAttribPointer(o->varlocs.color_loc, 4, GL_FLOAT, false, stride,
398             (void *)offsetof(ALLEGRO_OGL_BITMAP_VERTEX, r));
399          glEnableVertexAttribArray(o->varlocs.color_loc);
400       }
401    }
402    else
403 #endif
404    {
405       vert_ptr_on(disp, 3, GL_FLOAT, sizeof(ALLEGRO_OGL_BITMAP_VERTEX),
406          (char *)(disp->vertex_cache) + offsetof(ALLEGRO_OGL_BITMAP_VERTEX, x));
407       tex_ptr_on(disp, 2, GL_FLOAT, sizeof(ALLEGRO_OGL_BITMAP_VERTEX),
408          (char*)(disp->vertex_cache) + offsetof(ALLEGRO_OGL_BITMAP_VERTEX, tx));
409       color_ptr_on(disp, 4, GL_FLOAT, sizeof(ALLEGRO_OGL_BITMAP_VERTEX),
410          (char*)(disp->vertex_cache) + offsetof(ALLEGRO_OGL_BITMAP_VERTEX, r));
411 
412 #ifdef ALLEGRO_CFG_OPENGL_FIXED_FUNCTION
413       if (!(disp->flags & ALLEGRO_PROGRAMMABLE_PIPELINE))
414          glDisableClientState(GL_NORMAL_ARRAY);
415 #endif
416    }
417 
418    glGetError(); /* clear error */
419    glDrawArrays(GL_TRIANGLES, 0, disp->num_cache_vertices);
420 
421 #ifdef DEBUGMODE
422    {
423       int e = glGetError();
424       if (e) {
425          ALLEGRO_WARN("glDrawArrays failed: %s\n", _al_gl_error_string(e));
426       }
427    }
428 #endif
429 
430 #if !defined ALLEGRO_CFG_OPENGLES && !defined ALLEGRO_MACOSX
431    if (disp->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
432       if (o->varlocs.pos_loc >= 0)
433          glDisableVertexAttribArray(o->varlocs.pos_loc);
434       if (o->varlocs.texcoord_loc >= 0)
435          glDisableVertexAttribArray(o->varlocs.texcoord_loc);
436       if (o->varlocs.color_loc >= 0)
437          glDisableVertexAttribArray(o->varlocs.color_loc);
438       glBindBuffer(GL_ARRAY_BUFFER, 0);
439       glBindVertexArray(0);
440    }
441    else
442 #endif
443    {
444       vert_ptr_off(disp);
445       tex_ptr_off(disp);
446       color_ptr_off(disp);
447    }
448 
449    disp->num_cache_vertices = 0;
450 
451    if (disp->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
452 #ifdef ALLEGRO_CFG_OPENGL_PROGRAMMABLE_PIPELINE
453       if (disp->ogl_extras->varlocs.use_tex_loc >= 0)
454          glUniform1i(disp->ogl_extras->varlocs.use_tex_loc, 0);
455 #endif
456    }
457    else {
458       glDisable(GL_TEXTURE_2D);
459    }
460 }
461 
462 static void ogl_update_transformation(ALLEGRO_DISPLAY* disp,
463    ALLEGRO_BITMAP *target)
464 {
465    if (disp->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
466 #ifdef ALLEGRO_CFG_SHADER_GLSL
467       GLint loc = disp->ogl_extras->varlocs.projview_matrix_loc;
468       ALLEGRO_TRANSFORM projview;
469       al_copy_transform(&projview, &target->transform);
470       al_compose_transform(&projview, &target->proj_transform);
471       al_copy_transform(&disp->projview_transform, &projview);
472 
473       if (disp->ogl_extras->program_object > 0 && loc >= 0) {
474          _al_glsl_set_projview_matrix(loc, &disp->projview_transform);
475       }
476 #endif
477    } else {
478 #ifdef ALLEGRO_CFG_OPENGL_FIXED_FUNCTION
479       glMatrixMode(GL_PROJECTION);
480       glLoadMatrixf((float *)target->proj_transform.m);
481       glMatrixMode(GL_MODELVIEW);
482       glLoadMatrixf((float *)target->transform.m);
483 #endif
484    }
485 
486    if (target->parent) {
487       ALLEGRO_BITMAP_EXTRA_OPENGL *ogl_extra = target->parent->extra;
488       /* glViewport requires the bottom-left coordinate of the corner. */
489       glViewport(target->xofs, ogl_extra->true_h - (target->yofs + target->h), target->w, target->h);
490    } else {
491       glViewport(0, 0, target->w, target->h);
492    }
493 }
494 
495 static void ogl_clear_depth_buffer(ALLEGRO_DISPLAY *display, float x)
496 {
497    (void)display;
498 
499 #if defined(ALLEGRO_CFG_OPENGLES)
500    glClearDepthf(x);
501 #else
502    glClearDepth(x);
503 #endif
504 
505    /* We may want to defer this to the next glClear call as a combined
506     * color/depth clear may be faster.
507     */
508    glClear(GL_DEPTH_BUFFER_BIT);
509 }
510 
511 /* Add drawing commands to the vtable. */
512 void _al_ogl_add_drawing_functions(ALLEGRO_DISPLAY_INTERFACE *vt)
513 {
514    vt->clear = ogl_clear;
515    vt->draw_pixel = ogl_draw_pixel;
516    vt->clear_depth_buffer = ogl_clear_depth_buffer;
517 
518    vt->flush_vertex_cache = ogl_flush_vertex_cache;
519    vt->prepare_vertex_cache = ogl_prepare_vertex_cache;
520    vt->update_transformation = ogl_update_transformation;
521 }
522 
523 /* vim: set sts=3 sw=3 et: */
524