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*)¤t_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