]> git.sesse.net Git - vlc/blob - modules/video_output/opengl.c
Prepare for fragment program support in opengl.
[vlc] / modules / video_output / opengl.c
1 /*****************************************************************************
2  * opengl.c: OpenGL and OpenGL ES output common code
3  *****************************************************************************
4  * Copyright (C) 2004 the VideoLAN team
5  * Copyright (C) 2009 Laurent Aimar
6  *
7  * Authors: Cyril Deguet <asmax@videolan.org>
8  *          Gildas Bazin <gbazin@videolan.org>
9  *          Eric Petit <titer@m0k.org>
10  *          Cedric Cocquebert <cedric.cocquebert@supelec.fr>
11  *          Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_common.h>
32 #include <vlc_picture_pool.h>
33 #include <vlc_opengl.h>
34
35 #include "opengl.h"
36 // Define USE_OPENGL_ES to the GL ES Version you want to select
37
38 #if !defined (__APPLE__)
39 # if USE_OPENGL_ES == 2
40 #  include <GLES2/gl2ext.h>
41 # elif USE_OPENGL_ES == 1
42 #  include <GLES/glext.h>
43 //# else
44 //# include <GL/glext.h>
45 # endif
46 #else
47 # if USE_OPENGL_ES == 2
48 #  include <OpenGLES/ES2/gl.h>
49 # elif USE_OPENGL_ES == 1
50 #  include <OpenGLES/ES1/gl.h>
51 # else
52 #  define MACOS_OPENGL
53 #  include <OpenGL/glext.h>
54 # endif
55 #endif
56
57 /* RV16 */
58 #ifndef GL_UNSIGNED_SHORT_5_6_5
59 # define GL_UNSIGNED_SHORT_5_6_5 0x8363
60 #endif
61 #ifndef GL_CLAMP_TO_EDGE
62 # define GL_CLAMP_TO_EDGE 0x812F
63 #endif
64
65
66 #if USE_OPENGL_ES
67 # define VLCGL_TARGET GL_TEXTURE_2D
68
69 // Use RGB with OpenGLES
70 # define VLCGL_FORMAT GL_RGB
71 # define VLCGL_TYPE   GL_UNSIGNED_SHORT_5_6_5
72
73 # define VLCGL_TEXTURE_COUNT 1
74
75 #elif defined(MACOS_OPENGL)
76
77 /* On OS X, use GL_TEXTURE_RECTANGLE_EXT instead of GL_TEXTURE_2D.
78    This allows sizes which are not powers of 2 */
79 # define VLCGL_TARGET GL_TEXTURE_RECTANGLE_EXT
80
81 /* OS X OpenGL supports YUV. Hehe. */
82 # define VLCGL_FORMAT GL_YCBCR_422_APPLE
83 # define VLCGL_TYPE   GL_UNSIGNED_SHORT_8_8_APPLE
84
85 # define VLCGL_TEXTURE_COUNT 2
86
87 #else
88
89 # define VLCGL_TARGET GL_TEXTURE_2D
90
91 /* Use RGB on Win32/GLX */
92 # define VLCGL_FORMAT GL_RGBA
93 # define VLCGL_TYPE   GL_UNSIGNED_BYTE
94
95 # define VLCGL_TEXTURE_COUNT 1
96 #endif
97
98 struct vout_display_opengl_t {
99     vlc_gl_t   *gl;
100
101     video_format_t fmt;
102     const vlc_chroma_description_t *chroma;
103
104     int        tex_width;
105     int        tex_height;
106
107     GLuint     texture[VLCGL_TEXTURE_COUNT];
108     uint8_t    *buffer[VLCGL_TEXTURE_COUNT];
109     void       *buffer_base[VLCGL_TEXTURE_COUNT];
110
111     picture_pool_t *pool;
112
113     GLuint     program;
114
115     /* fragment_program */
116     void (*GenProgramsARB)(GLuint, GLuint *);
117     void (*BindProgramARB)(GLuint, GLuint);
118     void (*ProgramStringARB)(GLuint, GLuint, GLint, const GLbyte *);
119     void (*DeleteProgramsARB)(GLuint, GLuint *);
120 };
121
122 static inline int GetAlignedSize(unsigned size)
123 {
124     /* Return the smallest larger or equal power of 2 */
125     unsigned align = 1 << (8 * sizeof (unsigned) - clz(size));
126     return ((align >> 1) == size) ? size : align;
127 }
128
129 vout_display_opengl_t *vout_display_opengl_New(video_format_t *fmt,
130                                                vlc_gl_t *gl)
131 {
132     vout_display_opengl_t *vgl = calloc(1, sizeof(*vgl));
133     if (!vgl)
134         return NULL;
135
136     vgl->gl = gl;
137     if (vlc_gl_Lock(vgl->gl)) {
138         free(vgl);
139         return NULL;
140     }
141
142     const char *extensions = (const char *)glGetString(GL_EXTENSIONS);
143     if (!extensions)
144         extensions = "";
145
146     /* Load extensions */
147     bool supports_fp = false;
148     if (strstr(extensions, "GL_ARB_fragment_program")) {
149         vgl->GenProgramsARB    = (void (*)(GLuint, GLuint *))vlc_gl_GetProcAddress(vgl->gl, "glGenProgramsARB");
150         vgl->BindProgramARB    = (void (*)(GLuint, GLuint))vlc_gl_GetProcAddress(vgl->gl, "glBindProgramARB");
151         vgl->ProgramStringARB  = (void (*)(GLuint, GLuint, GLint, const GLbyte *))vlc_gl_GetProcAddress(vgl->gl, "glProgramStringARB");
152         vgl->DeleteProgramsARB = (void (*)(GLuint, GLuint *))vlc_gl_GetProcAddress(vgl->gl, "glDeleteProgramsARB");
153
154         supports_fp = vgl->GenProgramsARB &&
155                       vgl->BindProgramARB &&
156                       vgl->ProgramStringARB &&
157                       vgl->DeleteProgramsARB;
158     }
159
160     /* Find the chroma we will use and update fmt */
161     vgl->fmt = *fmt;
162     /* TODO: We use YCbCr on Mac which is Y422, but on OSX it seems to == YUY2. Verify */
163 #if defined(WORDS_BIGENDIAN) && VLCGL_FORMAT == GL_YCBCR_422_APPLE
164     vgl->fmt.i_chroma = VLC_CODEC_YUYV;
165 #elif defined(GL_YCBCR_422_APPLE) && (VLCGL_FORMAT == GL_YCBCR_422_APPLE)
166     vgl->fmt.i_chroma = VLC_CODEC_UYVY;
167 #elif VLCGL_FORMAT == GL_RGB
168 #   if VLCGL_TYPE == GL_UNSIGNED_BYTE
169     vgl->fmt.i_chroma = VLC_CODEC_RGB24;
170 #       if defined(WORDS_BIGENDIAN)
171     vgl->fmt.i_rmask = 0x00ff0000;
172     vgl->fmt.i_gmask = 0x0000ff00;
173     vgl->fmt.i_bmask = 0x000000ff;
174 #       else
175     vgl->fmt.i_rmask = 0x000000ff;
176     vgl->fmt.i_gmask = 0x0000ff00;
177     vgl->fmt.i_bmask = 0x00ff0000;
178 #       endif
179 #   else
180     vgl->fmt.i_chroma = VLC_CODEC_RGB16;
181 #       if defined(WORDS_BIGENDIAN)
182     vgl->fmt.i_rmask = 0x001f;
183     vgl->fmt.i_gmask = 0x07e0;
184     vgl->fmt.i_bmask = 0xf800;
185 #       else
186     vgl->fmt.i_rmask = 0xf800;
187     vgl->fmt.i_gmask = 0x07e0;
188     vgl->fmt.i_bmask = 0x001f;
189 #       endif
190 #   endif
191 #else
192     vgl->fmt.i_chroma = VLC_CODEC_RGB32;
193 #       if defined(WORDS_BIGENDIAN)
194     vgl->fmt.i_rmask = 0xff000000;
195     vgl->fmt.i_gmask = 0x00ff0000;
196     vgl->fmt.i_bmask = 0x0000ff00;
197 #       else
198     vgl->fmt.i_rmask = 0x000000ff;
199     vgl->fmt.i_gmask = 0x0000ff00;
200     vgl->fmt.i_bmask = 0x00ff0000;
201 #       endif
202 #endif
203
204     vgl->chroma = vlc_fourcc_GetChromaDescription(vgl->fmt.i_chroma);
205
206     bool supports_npot = false;
207 #if USE_OPENGL_ES == 2
208     supports_npot = true;
209 #elif defined(MACOS_OPENGL)
210     supports_npot = true;
211 #else
212     supports_npot |= strstr(extensions, "GL_APPLE_texture_2D_limited_npot") != NULL ||
213                      strstr(extensions, "GL_ARB_texture_non_power_of_two");
214 #endif
215
216     /* Texture size */
217     if (supports_npot) {
218         vgl->tex_width  = vgl->fmt.i_width;
219         vgl->tex_height = vgl->fmt.i_height;
220     }
221     else {
222         /* A texture must have a size aligned on a power of 2 */
223         vgl->tex_width  = GetAlignedSize(vgl->fmt.i_width);
224         vgl->tex_height = GetAlignedSize(vgl->fmt.i_height);
225     }
226
227     /* Build fragment program if needed */
228     vgl->program = 0;
229     if (supports_fp) {
230         char *code = NULL;
231         if (code) {
232             vgl->GenProgramsARB(1, &vgl->program);
233             vgl->BindProgramARB(GL_FRAGMENT_PROGRAM_ARB, vgl->program);
234             vgl->ProgramStringARB(GL_FRAGMENT_PROGRAM_ARB,
235                                   GL_PROGRAM_FORMAT_ASCII_ARB,
236                                   strlen(code), (const GLbyte*)code);
237             if (glGetError() == GL_INVALID_OPERATION) {
238                 /* FIXME if the program was needed for YUV, the video will be broken */
239 #if 1
240                 GLint position;
241                 glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &position);
242
243                 const char *msg = (const char *)glGetString(GL_PROGRAM_ERROR_STRING_ARB);
244                 fprintf(stderr, "GL_INVALID_OPERATION: error at %d: %s\n", position, msg);
245 #endif
246                 vgl->DeleteProgramsARB(1, &vgl->program);
247                 vgl->program = 0;
248             }
249             free(code);
250         }
251     }
252
253     /* */
254     glDisable(GL_BLEND);
255     glDisable(GL_DEPTH_TEST);
256     glDepthMask(GL_FALSE);
257     glDisable(GL_CULL_FACE);
258     glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
259     glClear(GL_COLOR_BUFFER_BIT);
260
261     vlc_gl_Unlock(vgl->gl);
262
263     /* */
264     for (int i = 0; i < VLCGL_TEXTURE_COUNT; i++) {
265         vgl->texture[i] = 0;
266         vgl->buffer[i]  = NULL;
267         vgl->buffer_base[i]  = NULL;
268     }
269     vgl->pool = NULL;
270
271     *fmt = vgl->fmt;
272     return vgl;
273 }
274
275 void vout_display_opengl_Delete(vout_display_opengl_t *vgl)
276 {
277     /* */
278     if (!vlc_gl_Lock(vgl->gl)) {
279
280         glFinish();
281         glFlush();
282         glDeleteTextures(VLCGL_TEXTURE_COUNT, vgl->texture);
283
284         if (vgl->program)
285             vgl->DeleteProgramsARB(1, &vgl->program);
286
287         vlc_gl_Unlock(vgl->gl);
288     }
289     if (vgl->pool) {
290         picture_pool_Delete(vgl->pool);
291         for (int i = 0; i < VLCGL_TEXTURE_COUNT; i++)
292             free(vgl->buffer_base[i]);
293     }
294     free(vgl);
295 }
296
297 #ifdef MACOS_OPENGL
298 /* XXX See comment vout_display_opengl_Prepare */
299 struct picture_sys_t {
300     vout_display_opengl_t *vgl;
301     GLuint *texture;
302 };
303
304 /* Small helper */
305 static inline GLuint get_texture(picture_t *picture)
306 {
307     return *picture->p_sys->texture;
308 }
309
310 static int PictureLock(picture_t *picture)
311 {
312     if (!picture->p_sys)
313         return VLC_SUCCESS;
314
315     vout_display_opengl_t *vgl = picture->p_sys->vgl;
316     if (!vlc_gl_Lock(vgl->gl)) {
317         glBindTexture(VLCGL_TARGET, get_texture(picture));
318         glTexSubImage2D(VLCGL_TARGET, 0, 0, 0,
319                         picture->p[0].i_pitch / vgl->chroma->pixel_size,
320                         picture->p[0].i_lines,
321                         VLCGL_FORMAT, VLCGL_TYPE, picture->p[0].p_pixels);
322
323         vlc_gl_Unlock(vgl->gl);
324     }
325     return VLC_SUCCESS;
326 }
327
328 static void PictureUnlock(picture_t *picture)
329 {
330     VLC_UNUSED(picture);
331 }
332 #endif
333
334 picture_pool_t *vout_display_opengl_GetPool(vout_display_opengl_t *vgl)
335 {
336     if (vgl->pool)
337         return vgl->pool;
338
339     picture_t *picture[VLCGL_TEXTURE_COUNT];
340
341     int i;
342     for (i = 0; i < VLCGL_TEXTURE_COUNT; i++) {
343         vgl->buffer[i] = vlc_memalign(&vgl->buffer_base[i], 16,
344                                       vgl->tex_width * vgl->tex_height * vgl->chroma->pixel_size);
345         if (!vgl->buffer[i])
346             break;
347
348         picture_resource_t rsc;
349         memset(&rsc, 0, sizeof(rsc));
350 #ifdef MACOS_OPENGL
351         rsc.p_sys = malloc(sizeof(*rsc.p_sys));
352         if (rsc.p_sys)
353         {
354             rsc.p_sys->vgl = vgl;
355             rsc.p_sys->texture = &vgl->texture[i];
356         }
357 #endif
358         rsc.p[0].p_pixels = vgl->buffer[i];
359         rsc.p[0].i_pitch  = vgl->fmt.i_width * vgl->chroma->pixel_size;
360         rsc.p[0].i_lines  = vgl->fmt.i_height;
361
362         picture[i] = picture_NewFromResource(&vgl->fmt, &rsc);
363         if (!picture[i]) {
364             free(vgl->buffer[i]);
365             vgl->buffer[i] = NULL;
366             break;
367         }
368     }
369     if (i < VLCGL_TEXTURE_COUNT)
370         goto error;
371
372     /* */
373     picture_pool_configuration_t cfg;
374     memset(&cfg, 0, sizeof(cfg));
375     cfg.picture_count = i;
376     cfg.picture = picture;
377 #ifdef MACOS_OPENGL
378     cfg.lock = PictureLock;
379     cfg.unlock = PictureUnlock;
380 #endif
381     vgl->pool = picture_pool_NewExtended(&cfg);
382     if (!vgl->pool)
383         goto error;
384
385     if (vlc_gl_Lock(vgl->gl))
386         return vgl->pool;
387
388     glGenTextures(VLCGL_TEXTURE_COUNT, vgl->texture);
389     for (int i = 0; i < VLCGL_TEXTURE_COUNT; i++) {
390         glBindTexture(VLCGL_TARGET, vgl->texture[i]);
391
392 #if !USE_OPENGL_ES
393         /* Set the texture parameters */
394         glTexParameterf(VLCGL_TARGET, GL_TEXTURE_PRIORITY, 1.0);
395         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
396 #endif
397
398         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
399         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
400         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
401         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
402
403 #ifdef MACOS_OPENGL
404         /* Tell the driver not to make a copy of the texture but to use
405            our buffer */
406         glEnable(GL_UNPACK_CLIENT_STORAGE_APPLE);
407         glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
408
409 #if 0
410         /* Use VRAM texturing */
411         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
412                          GL_STORAGE_CACHED_APPLE);
413 #else
414         /* Use AGP texturing */
415         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
416                          GL_STORAGE_SHARED_APPLE);
417 #endif
418 #endif
419
420         /* Call glTexImage2D only once, and use glTexSubImage2D later */
421         if (vgl->buffer[i]) {
422             glTexImage2D(VLCGL_TARGET, 0, VLCGL_FORMAT, vgl->tex_width,
423                          vgl->tex_height, 0, VLCGL_FORMAT, VLCGL_TYPE,
424                          vgl->buffer[i]);
425         }
426     }
427
428     vlc_gl_Unlock(vgl->gl);
429
430     return vgl->pool;
431
432 error:
433     for (int j = 0; j < i; j++) {
434         picture_Delete(picture[j]);
435         vgl->buffer[j] = NULL;
436     }
437     return NULL;
438 }
439
440 int vout_display_opengl_Prepare(vout_display_opengl_t *vgl,
441                                 picture_t *picture)
442 {
443     /* On Win32/GLX, we do this the usual way:
444        + Fill the buffer with new content,
445        + Reload the texture,
446        + Use the texture.
447
448        On OS X with VRAM or AGP texturing, the order has to be:
449        + Reload the texture,
450        + Fill the buffer with new content,
451        + Use the texture.
452
453        (Thanks to gcc from the Arstechnica forums for the tip)
454
455        Therefore on OSX, we have to use two buffers and textures and use a
456        lock(/unlock) managed picture pool.
457      */
458
459     if (vlc_gl_Lock(vgl->gl))
460         return VLC_EGENERIC;
461
462 #ifdef MACOS_OPENGL
463     /* Bind to the texture for drawing */
464     glBindTexture(VLCGL_TARGET, get_texture(picture));
465 #else
466     /* Update the texture */
467     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
468                     picture->p[0].i_pitch / vgl->chroma->pixel_size,
469                     picture->p[0].i_lines,
470                     VLCGL_FORMAT, VLCGL_TYPE, picture->p[0].p_pixels);
471 #endif
472
473     vlc_gl_Unlock(vgl->gl);
474     return VLC_SUCCESS;
475 }
476
477 int vout_display_opengl_Display(vout_display_opengl_t *vgl,
478                                 const video_format_t *source)
479 {
480     if (vlc_gl_Lock(vgl->gl))
481         return VLC_EGENERIC;
482
483     /* glTexCoord works differently with GL_TEXTURE_2D and
484        GL_TEXTURE_RECTANGLE_EXT */
485 #if VLCGL_TARGET == GL_TEXTURE_2D
486     const float f_normw = vgl->tex_width;
487     const float f_normh = vgl->tex_height;
488 #elif defined (GL_TEXTURE_RECTANGLE_EXT) \
489    && (VLCGL_TARGET == GL_TEXTURE_RECTANGLE_EXT)
490     const float f_normw = 1.0;
491     const float f_normh = 1.0;
492 #else
493 # error Unknown texture type!
494 #endif
495
496     float f_x      = (source->i_x_offset +                       0 ) / f_normw;
497     float f_y      = (source->i_y_offset +                       0 ) / f_normh;
498     float f_width  = (source->i_x_offset + source->i_visible_width ) / f_normw;
499     float f_height = (source->i_y_offset + source->i_visible_height) / f_normh;
500
501     /* Why drawing here and not in Render()? Because this way, the
502        OpenGL providers can call vout_display_opengl_Display to force redraw.i
503        Currently, the OS X provider uses it to get a smooth window resizing */
504
505     glClear(GL_COLOR_BUFFER_BIT);
506
507     if (vgl->program)
508         glEnable(GL_FRAGMENT_PROGRAM_ARB);
509     else
510         glEnable(VLCGL_TARGET);
511
512 #if USE_OPENGL_ES
513     static const GLfloat vertexCoord[] = {
514         -1.0f, -1.0f,
515          1.0f, -1.0f,
516         -1.0f,  1.0f,
517          1.0f,  1.0f,
518     };
519
520     const GLfloat textureCoord[8] = {
521         f_x,     f_height,
522         f_width, f_height,
523         f_x,     f_y,
524         f_width, f_y
525     };
526
527     glEnableClientState(GL_VERTEX_ARRAY);
528     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
529     glVertexPointer(2, GL_FLOAT, 0, vertexCoord);
530     glTexCoordPointer(2, GL_FLOAT, 0, textureCoord);
531
532     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
533 #else
534     glBegin(GL_POLYGON);
535     glTexCoord2f(f_x,      f_y);      glVertex2f(-1.0,  1.0);
536     glTexCoord2f(f_width,  f_y);      glVertex2f( 1.0,  1.0);
537     glTexCoord2f(f_width,  f_height); glVertex2f( 1.0, -1.0);
538     glTexCoord2f(f_x,      f_height); glVertex2f(-1.0, -1.0);
539     glEnd();
540 #endif
541
542     if (vgl->program)
543         glDisable(GL_FRAGMENT_PROGRAM_ARB);
544     else
545         glDisable(VLCGL_TARGET);
546
547     vlc_gl_Swap(vgl->gl);
548
549     vlc_gl_Unlock(vgl->gl);
550     return VLC_SUCCESS;
551 }
552