]> git.sesse.net Git - vlc/blob - modules/video_output/opengl.c
274e8fe6ed9803b90f0166d2d805b331ab3cce52
[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                         vgl->fmt.i_width, vgl->fmt.i_height,
266                         VLCGL_FORMAT, VLCGL_TYPE, picture->p[0].p_pixels);
267
268         vlc_gl_Unlock(vgl->gl);
269     }
270     return VLC_SUCCESS;
271 }
272
273 static void PictureUnlock(picture_t *picture)
274 {
275     VLC_UNUSED(picture);
276 }
277 #endif
278
279 picture_pool_t *vout_display_opengl_GetPool(vout_display_opengl_t *vgl)
280 {
281     if (vgl->pool)
282         return vgl->pool;
283
284     picture_t *picture[VLCGL_TEXTURE_COUNT];
285
286     int i;
287     for (i = 0; i < VLCGL_TEXTURE_COUNT; i++) {
288         vgl->buffer[i] = vlc_memalign(&vgl->buffer_base[i], 16,
289                                       vgl->tex_width * vgl->tex_height * vgl->chroma->pixel_size);
290         if (!vgl->buffer[i])
291             break;
292
293         picture_resource_t rsc;
294         memset(&rsc, 0, sizeof(rsc));
295 #ifdef MACOS_OPENGL
296         rsc.p_sys = malloc(sizeof(*rsc.p_sys));
297         if (rsc.p_sys)
298         {
299             rsc.p_sys->vgl = vgl;
300             rsc.p_sys->texture = &vgl->texture[i];
301         }
302 #endif
303         rsc.p[0].p_pixels = vgl->buffer[i];
304         rsc.p[0].i_pitch  = vgl->fmt.i_width * vgl->chroma->pixel_size;
305         rsc.p[0].i_lines  = vgl->fmt.i_height;
306
307         picture[i] = picture_NewFromResource(&vgl->fmt, &rsc);
308         if (!picture[i]) {
309             free(vgl->buffer[i]);
310             vgl->buffer[i] = NULL;
311             break;
312         }
313     }
314     if (i < VLCGL_TEXTURE_COUNT)
315         goto error;
316
317     /* */
318     picture_pool_configuration_t cfg;
319     memset(&cfg, 0, sizeof(cfg));
320     cfg.picture_count = i;
321     cfg.picture = picture;
322 #ifdef MACOS_OPENGL
323     cfg.lock = PictureLock;
324     cfg.unlock = PictureUnlock;
325 #endif
326     vgl->pool = picture_pool_NewExtended(&cfg);
327     if (!vgl->pool)
328         goto error;
329
330     if (vlc_gl_Lock(vgl->gl))
331         return vgl->pool;
332
333     glGenTextures(VLCGL_TEXTURE_COUNT, vgl->texture);
334     for (int i = 0; i < VLCGL_TEXTURE_COUNT; i++) {
335         glBindTexture(VLCGL_TARGET, vgl->texture[i]);
336
337 #if !USE_OPENGL_ES
338         /* Set the texture parameters */
339         glTexParameterf(VLCGL_TARGET, GL_TEXTURE_PRIORITY, 1.0);
340         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
341 #endif
342
343         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
344         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
345         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
346         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
347
348 #ifdef MACOS_OPENGL
349         /* Tell the driver not to make a copy of the texture but to use
350            our buffer */
351         glEnable(GL_UNPACK_CLIENT_STORAGE_APPLE);
352         glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
353
354 #if 0
355         /* Use VRAM texturing */
356         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
357                          GL_STORAGE_CACHED_APPLE);
358 #else
359         /* Use AGP texturing */
360         glTexParameteri(VLCGL_TARGET, GL_TEXTURE_STORAGE_HINT_APPLE,
361                          GL_STORAGE_SHARED_APPLE);
362 #endif
363 #endif
364
365         /* Call glTexImage2D only once, and use glTexSubImage2D later */
366         if (vgl->buffer[i]) {
367             glTexImage2D(VLCGL_TARGET, 0, VLCGL_FORMAT, vgl->tex_width,
368                          vgl->tex_height, 0, VLCGL_FORMAT, VLCGL_TYPE,
369                          vgl->buffer[i]);
370         }
371     }
372
373     vlc_gl_Unlock(vgl->gl);
374
375     return vgl->pool;
376
377 error:
378     for (int j = 0; j < i; j++) {
379         picture_Delete(picture[j]);
380         vgl->buffer[j] = NULL;
381     }
382     return NULL;
383 }
384
385 int vout_display_opengl_Prepare(vout_display_opengl_t *vgl,
386                                 picture_t *picture)
387 {
388     /* On Win32/GLX, we do this the usual way:
389        + Fill the buffer with new content,
390        + Reload the texture,
391        + Use the texture.
392
393        On OS X with VRAM or AGP texturing, the order has to be:
394        + Reload the texture,
395        + Fill the buffer with new content,
396        + Use the texture.
397
398        (Thanks to gcc from the Arstechnica forums for the tip)
399
400        Therefore on OSX, we have to use two buffers and textures and use a
401        lock(/unlock) managed picture pool.
402      */
403
404     if (vlc_gl_Lock(vgl->gl))
405         return VLC_EGENERIC;
406
407 #ifdef MACOS_OPENGL
408     /* Bind to the texture for drawing */
409     glBindTexture(VLCGL_TARGET, get_texture(picture));
410 #else
411     /* Update the texture */
412     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
413                     vgl->fmt.i_width, vgl->fmt.i_height,
414                     VLCGL_FORMAT, VLCGL_TYPE, picture->p[0].p_pixels);
415 #endif
416
417     vlc_gl_Unlock(vgl->gl);
418     return VLC_SUCCESS;
419 }
420
421 int vout_display_opengl_Display(vout_display_opengl_t *vgl,
422                                 const video_format_t *source)
423 {
424     if (vlc_gl_Lock(vgl->gl))
425         return VLC_EGENERIC;
426
427     /* glTexCoord works differently with GL_TEXTURE_2D and
428        GL_TEXTURE_RECTANGLE_EXT */
429 #if VLCGL_TARGET == GL_TEXTURE_2D
430     const float f_normw = vgl->tex_width;
431     const float f_normh = vgl->tex_height;
432 #elif defined (GL_TEXTURE_RECTANGLE_EXT) \
433    && (VLCGL_TARGET == GL_TEXTURE_RECTANGLE_EXT)
434     const float f_normw = 1.0;
435     const float f_normh = 1.0;
436 #else
437 # error Unknown texture type!
438 #endif
439
440     float f_x      = (source->i_x_offset +                       0 ) / f_normw;
441     float f_y      = (source->i_y_offset +                       0 ) / f_normh;
442     float f_width  = (source->i_x_offset + source->i_visible_width ) / f_normw;
443     float f_height = (source->i_y_offset + source->i_visible_height) / f_normh;
444
445     /* Why drawing here and not in Render()? Because this way, the
446        OpenGL providers can call vout_display_opengl_Display to force redraw.i
447        Currently, the OS X provider uses it to get a smooth window resizing */
448
449     glClear(GL_COLOR_BUFFER_BIT);
450
451     glEnable(VLCGL_TARGET);
452
453 #if USE_OPENGL_ES
454     static const GLfloat vertexCoord[] = {
455         -1.0f, -1.0f,
456          1.0f, -1.0f,
457         -1.0f,  1.0f,
458          1.0f,  1.0f,
459     };
460
461     const GLfloat textureCoord[8] = {
462         f_x,     f_height,
463         f_width, f_height,
464         f_x,     f_y,
465         f_width, f_y
466     };
467
468     glEnableClientState(GL_VERTEX_ARRAY);
469     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
470     glVertexPointer(2, GL_FLOAT, 0, vertexCoord);
471     glTexCoordPointer(2, GL_FLOAT, 0, textureCoord);
472
473     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
474 #else
475     glBegin(GL_POLYGON);
476     glTexCoord2f(f_x,      f_y);      glVertex2f(-1.0,  1.0);
477     glTexCoord2f(f_width,  f_y);      glVertex2f( 1.0,  1.0);
478     glTexCoord2f(f_width,  f_height); glVertex2f( 1.0, -1.0);
479     glTexCoord2f(f_x,      f_height); glVertex2f(-1.0, -1.0);
480     glEnd();
481 #endif
482
483     glDisable(VLCGL_TARGET);
484
485     vlc_gl_Swap(vgl->gl);
486
487     vlc_gl_Unlock(vgl->gl);
488     return VLC_SUCCESS;
489 }
490