]> git.sesse.net Git - vlc/blob - modules/video_output/opengl.c
ce7d5ed3fc5d3d257287a3fe84fa6839bccc29bd
[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
28 #include <vlc_common.h>
29 #include <vlc_picture_pool.h>
30 #include <vlc_opengl.h>
31
32 #include "opengl.h"
33 // Define USE_OPENGL_ES to the GL ES Version you want to select
34
35 #if !defined (__APPLE__)
36 # if USE_OPENGL_ES == 2
37 #  include <GLES2/gl2ext.h>
38 # elif USE_OPENGL_ES == 1
39 #  include <GLES/glext.h>
40 //# else
41 //# include <GL/glext.h>
42 # endif
43 #else
44 # if USE_OPENGL_ES == 2
45 #  include <OpenGLES/ES2/gl.h>
46 # elif USE_OPENGL_ES == 1
47 #  include <OpenGLES/ES1/gl.h>
48 # else
49 #  define MACOS_OPENGL
50 #  include <OpenGL/glext.h>
51 # endif
52 #endif
53
54 /* RV16 */
55 #ifndef GL_UNSIGNED_SHORT_5_6_5
56 # define GL_UNSIGNED_SHORT_5_6_5 0x8363
57 #endif
58 #ifndef GL_CLAMP_TO_EDGE
59 # define GL_CLAMP_TO_EDGE 0x812F
60 #endif
61
62
63 #if USE_OPENGL_ES
64 # define VLCGL_TARGET GL_TEXTURE_2D
65
66 // Use RGB with OpenGLES
67 # define VLCGL_FORMAT GL_RGB
68 # define VLCGL_TYPE   GL_UNSIGNED_SHORT_5_6_5
69
70 # define VLCGL_TEXTURE_COUNT 1
71
72 #elif defined(MACOS_OPENGL)
73
74 /* On OS X, use GL_TEXTURE_RECTANGLE_EXT instead of GL_TEXTURE_2D.
75    This allows sizes which are not powers of 2 */
76 # define VLCGL_TARGET GL_TEXTURE_RECTANGLE_EXT
77
78 /* OS X OpenGL supports YUV. Hehe. */
79 # define VLCGL_FORMAT GL_YCBCR_422_APPLE
80 # define VLCGL_TYPE   GL_UNSIGNED_SHORT_8_8_APPLE
81
82 # define VLCGL_TEXTURE_COUNT 2
83
84 #else
85
86 # define VLCGL_TARGET GL_TEXTURE_2D
87
88 /* Use RGB on Win32/GLX */
89 # define VLCGL_FORMAT GL_RGBA
90 # define VLCGL_TYPE   GL_UNSIGNED_BYTE
91
92 # define VLCGL_TEXTURE_COUNT 1
93 #endif
94
95 struct vout_display_opengl_t {
96     vlc_gl_t   *gl;
97
98     video_format_t fmt;
99     const vlc_chroma_description_t *chroma;
100
101     int        tex_width;
102     int        tex_height;
103
104     GLuint     texture[VLCGL_TEXTURE_COUNT];
105     uint8_t    *buffer[VLCGL_TEXTURE_COUNT];
106     void       *buffer_base[VLCGL_TEXTURE_COUNT];
107
108     picture_pool_t *pool;
109 };
110
111 static inline int GetAlignedSize(unsigned size)
112 {
113     /* Return the smallest larger or equal power of 2 */
114     unsigned align = 1 << (8 * sizeof (unsigned) - clz(size));
115     return ((align >> 1) == size) ? size : align;
116 }
117
118 vout_display_opengl_t *vout_display_opengl_New(video_format_t *fmt,
119                                                vlc_gl_t *gl)
120 {
121     vout_display_opengl_t *vgl = malloc(sizeof(*vgl));
122     if (!vgl)
123         return NULL;
124
125     vgl->gl = gl;
126     if (vlc_gl_Lock(vgl->gl)) {
127         free(vgl);
128         return NULL;
129     }
130
131     const char *extensions = (const char *)glGetString(GL_EXTENSIONS);
132     if (!extensions)
133         extensions = "";
134
135     /* Find the chroma we will use and update fmt */
136     vgl->fmt = *fmt;
137     /* TODO: We use YCbCr on Mac which is Y422, but on OSX it seems to == YUY2. Verify */
138 #if defined(WORDS_BIGENDIAN) && VLCGL_FORMAT == GL_YCBCR_422_APPLE
139     vgl->fmt.i_chroma = VLC_CODEC_YUYV;
140 #elif defined(GL_YCBCR_422_APPLE) && (VLCGL_FORMAT == GL_YCBCR_422_APPLE)
141     vgl->fmt.i_chroma = VLC_CODEC_UYVY;
142 #elif VLCGL_FORMAT == GL_RGB
143 #   if VLCGL_TYPE == GL_UNSIGNED_BYTE
144     vgl->fmt.i_chroma = VLC_CODEC_RGB24;
145 #       if defined(WORDS_BIGENDIAN)
146     vgl->fmt.i_rmask = 0x00ff0000;
147     vgl->fmt.i_gmask = 0x0000ff00;
148     vgl->fmt.i_bmask = 0x000000ff;
149 #       else
150     vgl->fmt.i_rmask = 0x000000ff;
151     vgl->fmt.i_gmask = 0x0000ff00;
152     vgl->fmt.i_bmask = 0x00ff0000;
153 #       endif
154 #   else
155     vgl->fmt.i_chroma = VLC_CODEC_RGB16;
156 #       if defined(WORDS_BIGENDIAN)
157     vgl->fmt.i_rmask = 0x001f;
158     vgl->fmt.i_gmask = 0x07e0;
159     vgl->fmt.i_bmask = 0xf800;
160 #       else
161     vgl->fmt.i_rmask = 0xf800;
162     vgl->fmt.i_gmask = 0x07e0;
163     vgl->fmt.i_bmask = 0x001f;
164 #       endif
165 #   endif
166 #else
167     vgl->fmt.i_chroma = VLC_CODEC_RGB32;
168 #       if defined(WORDS_BIGENDIAN)
169     vgl->fmt.i_rmask = 0xff000000;
170     vgl->fmt.i_gmask = 0x00ff0000;
171     vgl->fmt.i_bmask = 0x0000ff00;
172 #       else
173     vgl->fmt.i_rmask = 0x000000ff;
174     vgl->fmt.i_gmask = 0x0000ff00;
175     vgl->fmt.i_bmask = 0x00ff0000;
176 #       endif
177 #endif
178
179     vgl->chroma = vlc_fourcc_GetChromaDescription(vgl->fmt.i_chroma);
180
181     bool supports_npot = false;
182 #if USE_OPENGL_ES == 2
183     supports_npot = true;
184 #elif defined(MACOS_OPENGL)
185     supports_npot = true;
186 #else
187     supports_npot |= strstr(extensions, "GL_APPLE_texture_2D_limited_npot") != NULL ||
188                      strstr(extensions, "GL_ARB_texture_non_power_of_two");
189 #endif
190
191     /* Texture size */
192     if (supports_npot) {
193         vgl->tex_width  = vgl->fmt.i_width;
194         vgl->tex_height = vgl->fmt.i_height;
195     }
196     else {
197         /* A texture must have a size aligned on a power of 2 */
198         vgl->tex_width  = GetAlignedSize(vgl->fmt.i_width);
199         vgl->tex_height = GetAlignedSize(vgl->fmt.i_height);
200     }
201
202     /* */
203     glDisable(GL_BLEND);
204     glDisable(GL_DEPTH_TEST);
205     glDepthMask(GL_FALSE);
206     glDisable(GL_CULL_FACE);
207     glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
208     glClear(GL_COLOR_BUFFER_BIT);
209
210     vlc_gl_Unlock(vgl->gl);
211
212     /* */
213     for (int i = 0; i < VLCGL_TEXTURE_COUNT; i++) {
214         vgl->texture[i] = 0;
215         vgl->buffer[i]  = NULL;
216         vgl->buffer_base[i]  = NULL;
217     }
218     vgl->pool = NULL;
219
220     *fmt = vgl->fmt;
221     return vgl;
222 }
223
224 void vout_display_opengl_Delete(vout_display_opengl_t *vgl)
225 {
226     /* */
227     if (!vlc_gl_Lock(vgl->gl)) {
228
229         glFinish();
230         glFlush();
231         glDeleteTextures(VLCGL_TEXTURE_COUNT, vgl->texture);
232
233         vlc_gl_Unlock(vgl->gl);
234     }
235     if (vgl->pool) {
236         picture_pool_Delete(vgl->pool);
237         for (int i = 0; i < VLCGL_TEXTURE_COUNT; i++)
238             free(vgl->buffer_base[i]);
239     }
240     free(vgl);
241 }
242
243 #ifdef MACOS_OPENGL
244 /* XXX See comment vout_display_opengl_Prepare */
245 struct picture_sys_t {
246     vout_display_opengl_t *vgl;
247     GLuint *texture;
248 };
249
250 /* Small helper */
251 static inline GLuint get_texture(picture_t *picture)
252 {
253     return *picture->p_sys->texture;
254 }
255
256 static int PictureLock(picture_t *picture)
257 {
258     if (!picture->p_sys)
259         return VLC_SUCCESS;
260
261     vout_display_opengl_t *vgl = picture->p_sys->vgl;
262     if (!vlc_gl_Lock(vgl->gl)) {
263         glBindTexture(VLCGL_TARGET, get_texture(picture));
264         glTexSubImage2D(VLCGL_TARGET, 0, 0, 0,
265                         picture->p[0].i_pitch / vgl->chroma->pixel_size,
266                         picture->p[0].i_lines,
267                         VLCGL_FORMAT, VLCGL_TYPE, picture->p[0].p_pixels);
268
269         vlc_gl_Unlock(vgl->gl);
270     }
271     return VLC_SUCCESS;
272 }
273
274 static void PictureUnlock(picture_t *picture)
275 {
276     VLC_UNUSED(picture);
277 }
278 #endif
279
280 picture_pool_t *vout_display_opengl_GetPool(vout_display_opengl_t *vgl)
281 {
282     if (vgl->pool)
283         return vgl->pool;
284
285     picture_t *picture[VLCGL_TEXTURE_COUNT];
286
287     int i;
288     for (i = 0; i < VLCGL_TEXTURE_COUNT; i++) {
289         vgl->buffer[i] = vlc_memalign(&vgl->buffer_base[i], 16,
290                                       vgl->tex_width * vgl->tex_height * vgl->chroma->pixel_size);
291         if (!vgl->buffer[i])
292             break;
293
294         picture_resource_t rsc;
295         memset(&rsc, 0, sizeof(rsc));
296 #ifdef MACOS_OPENGL
297         rsc.p_sys = malloc(sizeof(*rsc.p_sys));
298         if (rsc.p_sys)
299         {
300             rsc.p_sys->vgl = vgl;
301             rsc.p_sys->texture = &vgl->texture[i];
302         }
303 #endif
304         rsc.p[0].p_pixels = vgl->buffer[i];
305         rsc.p[0].i_pitch  = vgl->fmt.i_width * vgl->chroma->pixel_size;
306         rsc.p[0].i_lines  = vgl->fmt.i_height;
307
308         picture[i] = picture_NewFromResource(&vgl->fmt, &rsc);
309         if (!picture[i]) {
310             free(vgl->buffer[i]);
311             vgl->buffer[i] = NULL;
312             break;
313         }
314     }
315     if (i < VLCGL_TEXTURE_COUNT)
316         goto error;
317
318     /* */
319     picture_pool_configuration_t cfg;
320     memset(&cfg, 0, sizeof(cfg));
321     cfg.picture_count = i;
322     cfg.picture = picture;
323 #ifdef MACOS_OPENGL
324     cfg.lock = PictureLock;
325     cfg.unlock = PictureUnlock;
326 #endif
327     vgl->pool = picture_pool_NewExtended(&cfg);
328     if (!vgl->pool)
329         goto error;
330
331     if (vlc_gl_Lock(vgl->gl))
332         return vgl->pool;
333
334     glGenTextures(VLCGL_TEXTURE_COUNT, vgl->texture);
335     for (int i = 0; i < VLCGL_TEXTURE_COUNT; i++) {
336         glBindTexture(VLCGL_TARGET, vgl->texture[i]);
337
338 #if !USE_OPENGL_ES
339         /* Set the texture parameters */
340         glTexParameterf(VLCGL_TARGET, GL_TEXTURE_PRIORITY, 1.0);
341         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
342 #endif
343
344         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
345         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
346         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
347         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
348
349 #ifdef MACOS_OPENGL
350         /* Tell the driver not to make a copy of the texture but to use
351            our buffer */
352         glEnable(GL_UNPACK_CLIENT_STORAGE_APPLE);
353         glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
354
355 #if 0
356         /* Use VRAM texturing */
357         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
358                          GL_STORAGE_CACHED_APPLE);
359 #else
360         /* Use AGP texturing */
361         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
362                          GL_STORAGE_SHARED_APPLE);
363 #endif
364 #endif
365
366         /* Call glTexImage2D only once, and use glTexSubImage2D later */
367         if (vgl->buffer[i]) {
368             glTexImage2D(VLCGL_TARGET, 0, VLCGL_FORMAT, vgl->tex_width,
369                          vgl->tex_height, 0, VLCGL_FORMAT, VLCGL_TYPE,
370                          vgl->buffer[i]);
371         }
372     }
373
374     vlc_gl_Unlock(vgl->gl);
375
376     return vgl->pool;
377
378 error:
379     for (int j = 0; j < i; j++) {
380         picture_Delete(picture[j]);
381         vgl->buffer[j] = NULL;
382     }
383     return NULL;
384 }
385
386 int vout_display_opengl_Prepare(vout_display_opengl_t *vgl,
387                                 picture_t *picture)
388 {
389     /* On Win32/GLX, we do this the usual way:
390        + Fill the buffer with new content,
391        + Reload the texture,
392        + Use the texture.
393
394        On OS X with VRAM or AGP texturing, the order has to be:
395        + Reload the texture,
396        + Fill the buffer with new content,
397        + Use the texture.
398
399        (Thanks to gcc from the Arstechnica forums for the tip)
400
401        Therefore on OSX, we have to use two buffers and textures and use a
402        lock(/unlock) managed picture pool.
403      */
404
405     if (vlc_gl_Lock(vgl->gl))
406         return VLC_EGENERIC;
407
408 #ifdef MACOS_OPENGL
409     /* Bind to the texture for drawing */
410     glBindTexture(VLCGL_TARGET, get_texture(picture));
411 #else
412     /* Update the texture */
413     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
414                     picture->p[0].i_pitch / vgl->chroma->pixel_size,
415                     picture->p[0].i_lines,
416                     VLCGL_FORMAT, VLCGL_TYPE, picture->p[0].p_pixels);
417 #endif
418
419     vlc_gl_Unlock(vgl->gl);
420     return VLC_SUCCESS;
421 }
422
423 int vout_display_opengl_Display(vout_display_opengl_t *vgl,
424                                 const video_format_t *source)
425 {
426     if (vlc_gl_Lock(vgl->gl))
427         return VLC_EGENERIC;
428
429     /* glTexCoord works differently with GL_TEXTURE_2D and
430        GL_TEXTURE_RECTANGLE_EXT */
431 #if VLCGL_TARGET == GL_TEXTURE_2D
432     const float f_normw = vgl->tex_width;
433     const float f_normh = vgl->tex_height;
434 #elif defined (GL_TEXTURE_RECTANGLE_EXT) \
435    && (VLCGL_TARGET == GL_TEXTURE_RECTANGLE_EXT)
436     const float f_normw = 1.0;
437     const float f_normh = 1.0;
438 #else
439 # error Unknown texture type!
440 #endif
441
442     float f_x      = (source->i_x_offset +                       0 ) / f_normw;
443     float f_y      = (source->i_y_offset +                       0 ) / f_normh;
444     float f_width  = (source->i_x_offset + source->i_visible_width ) / f_normw;
445     float f_height = (source->i_y_offset + source->i_visible_height) / f_normh;
446
447     /* Why drawing here and not in Render()? Because this way, the
448        OpenGL providers can call vout_display_opengl_Display to force redraw.i
449        Currently, the OS X provider uses it to get a smooth window resizing */
450
451     glClear(GL_COLOR_BUFFER_BIT);
452
453     glEnable(VLCGL_TARGET);
454
455 #if USE_OPENGL_ES
456     static const GLfloat vertexCoord[] = {
457         -1.0f, -1.0f,
458          1.0f, -1.0f,
459         -1.0f,  1.0f,
460          1.0f,  1.0f,
461     };
462
463     const GLfloat textureCoord[8] = {
464         f_x,     f_height,
465         f_width, f_height,
466         f_x,     f_y,
467         f_width, f_y
468     };
469
470     glEnableClientState(GL_VERTEX_ARRAY);
471     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
472     glVertexPointer(2, GL_FLOAT, 0, vertexCoord);
473     glTexCoordPointer(2, GL_FLOAT, 0, textureCoord);
474
475     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
476 #else
477     glBegin(GL_POLYGON);
478     glTexCoord2f(f_x,      f_y);      glVertex2f(-1.0,  1.0);
479     glTexCoord2f(f_width,  f_y);      glVertex2f( 1.0,  1.0);
480     glTexCoord2f(f_width,  f_height); glVertex2f( 1.0, -1.0);
481     glTexCoord2f(f_x,      f_height); glVertex2f(-1.0, -1.0);
482     glEnd();
483 #endif
484
485     glDisable(VLCGL_TARGET);
486
487     vlc_gl_Swap(vgl->gl);
488
489     vlc_gl_Unlock(vgl->gl);
490     return VLC_SUCCESS;
491 }
492