opengl: move fragment shader creation to renderer
[vlc.git] / modules / video_output / opengl / renderer.c
blobae4179be4a915bd9736786648c7fd2633902d301
1 /*****************************************************************************
2 * renderer.c
3 *****************************************************************************
4 * Copyright (C) 2004-2020 VLC authors and VideoLAN
5 * Copyright (C) 2009, 2011 Laurent Aimar
7 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
8 * Ilkka Ollakka <ileoo@videolan.org>
9 * Rémi Denis-Courmont
10 * Adrien Maglo <magsoft at videolan dot org>
11 * Felix Paul Kühne <fkuehne at videolan dot org>
12 * Pierre d'Herbemont <pdherbemont at videolan dot org>
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU Lesser General Public License as published by
16 * the Free Software Foundation; either version 2.1 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Lesser General Public License for more details.
24 * You should have received a copy of the GNU Lesser General Public License
25 * along with this program; if not, write to the Free Software Foundation,
26 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27 *****************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
33 #include "renderer.h"
35 #include <assert.h>
36 #include <vlc_common.h>
37 #include <vlc_es.h>
38 #include <vlc_picture.h>
40 #include "gl_util.h"
41 #include "internal.h"
42 #include "interop.h"
43 #include "vout_helper.h"
45 #define SPHERE_RADIUS 1.f
47 static const GLfloat identity[] = {
48 1.0f, 0.0f, 0.0f, 0.0f,
49 0.0f, 1.0f, 0.0f, 0.0f,
50 0.0f, 0.0f, 1.0f, 0.0f,
51 0.0f, 0.0f, 0.0f, 1.0f
54 static void getZoomMatrix(float zoom, GLfloat matrix[static 16]) {
56 const GLfloat m[] = {
57 /* x y z w */
58 1.0f, 0.0f, 0.0f, 0.0f,
59 0.0f, 1.0f, 0.0f, 0.0f,
60 0.0f, 0.0f, 1.0f, 0.0f,
61 0.0f, 0.0f, zoom, 1.0f
64 memcpy(matrix, m, sizeof(m));
67 /* perspective matrix see https://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml */
68 static void getProjectionMatrix(float sar, float fovy, GLfloat matrix[static 16]) {
70 float zFar = 1000;
71 float zNear = 0.01;
73 float f = 1.f / tanf(fovy / 2.f);
75 const GLfloat m[] = {
76 f / sar, 0.f, 0.f, 0.f,
77 0.f, f, 0.f, 0.f,
78 0.f, 0.f, (zNear + zFar) / (zNear - zFar), -1.f,
79 0.f, 0.f, (2 * zNear * zFar) / (zNear - zFar), 0.f};
81 memcpy(matrix, m, sizeof(m));
84 static void getViewpointMatrixes(struct vlc_gl_renderer *renderer,
85 video_projection_mode_t projection_mode)
87 if (projection_mode == PROJECTION_MODE_EQUIRECTANGULAR
88 || projection_mode == PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD)
90 getProjectionMatrix(renderer->f_sar, renderer->f_fovy,
91 renderer->var.ProjectionMatrix);
92 getZoomMatrix(renderer->f_z, renderer->var.ZoomMatrix);
94 /* renderer->vp has been reversed and is a world transform */
95 vlc_viewpoint_to_4x4(&renderer->vp, renderer->var.ViewMatrix);
97 else
99 memcpy(renderer->var.ProjectionMatrix, identity, sizeof(identity));
100 memcpy(renderer->var.ZoomMatrix, identity, sizeof(identity));
101 memcpy(renderer->var.ViewMatrix, identity, sizeof(identity));
106 static void getOrientationTransformMatrix(video_orientation_t orientation,
107 GLfloat matrix[static 16])
109 memcpy(matrix, identity, sizeof(identity));
111 const int k_cos_pi = -1;
112 const int k_cos_pi_2 = 0;
113 const int k_cos_n_pi_2 = 0;
115 const int k_sin_pi = 0;
116 const int k_sin_pi_2 = 1;
117 const int k_sin_n_pi_2 = -1;
119 switch (orientation) {
121 case ORIENT_ROTATED_90:
122 matrix[0 * 4 + 0] = k_cos_pi_2;
123 matrix[0 * 4 + 1] = -k_sin_pi_2;
124 matrix[1 * 4 + 0] = k_sin_pi_2;
125 matrix[1 * 4 + 1] = k_cos_pi_2;
126 matrix[3 * 4 + 1] = 1;
127 break;
128 case ORIENT_ROTATED_180:
129 matrix[0 * 4 + 0] = k_cos_pi;
130 matrix[0 * 4 + 1] = -k_sin_pi;
131 matrix[1 * 4 + 0] = k_sin_pi;
132 matrix[1 * 4 + 1] = k_cos_pi;
133 matrix[3 * 4 + 0] = 1;
134 matrix[3 * 4 + 1] = 1;
135 break;
136 case ORIENT_ROTATED_270:
137 matrix[0 * 4 + 0] = k_cos_n_pi_2;
138 matrix[0 * 4 + 1] = -k_sin_n_pi_2;
139 matrix[1 * 4 + 0] = k_sin_n_pi_2;
140 matrix[1 * 4 + 1] = k_cos_n_pi_2;
141 matrix[3 * 4 + 0] = 1;
142 break;
143 case ORIENT_HFLIPPED:
144 matrix[0 * 4 + 0] = -1;
145 matrix[3 * 4 + 0] = 1;
146 break;
147 case ORIENT_VFLIPPED:
148 matrix[1 * 4 + 1] = -1;
149 matrix[3 * 4 + 1] = 1;
150 break;
151 case ORIENT_TRANSPOSED:
152 matrix[0 * 4 + 0] = 0;
153 matrix[1 * 4 + 1] = 0;
154 matrix[2 * 4 + 2] = -1;
155 matrix[0 * 4 + 1] = 1;
156 matrix[1 * 4 + 0] = 1;
157 break;
158 case ORIENT_ANTI_TRANSPOSED:
159 matrix[0 * 4 + 0] = 0;
160 matrix[1 * 4 + 1] = 0;
161 matrix[2 * 4 + 2] = -1;
162 matrix[0 * 4 + 1] = -1;
163 matrix[1 * 4 + 0] = -1;
164 matrix[3 * 4 + 0] = 1;
165 matrix[3 * 4 + 1] = 1;
166 break;
167 default:
168 break;
172 static void
173 InitStereoMatrix(GLfloat matrix_out[static 3*3],
174 video_multiview_mode_t multiview_mode)
177 * The stereo matrix transforms 2D pictures coordinates to crop the
178 * content, in order to view only one eye.
180 * This 2D transformation is affine, so the matrix is 3x3 and applies to 3D
181 * vectors in the form (x, y, 1).
183 * Note that since for now, we always crop the left eye, in practice the
184 * offset is always 0, so the transform is actually linear (a 2x2 matrix
185 * would be sufficient).
188 #define COL(x) (x*3)
189 #define ROW(x) (x)
191 /* Initialize to identity 3x3 */
192 memset(matrix_out, 0, 3 * 3 * sizeof(float));
193 matrix_out[COL(0) + ROW(0)] = 1;
194 matrix_out[COL(1) + ROW(1)] = 1;
195 matrix_out[COL(2) + ROW(2)] = 1;
197 switch (multiview_mode)
199 case MULTIVIEW_STEREO_SBS:
201 * +----------+----------+
202 * | . |
203 * | LEFT . RIGHT |
204 * | EYE . EYE |
205 * | . |
206 * +----------+----------+
208 * To crop the coordinates to the left eye, divide the x
209 * coordinates by 2:
211 * / 0.5 0 0 \
212 * matrix = | 0 1 0 |
213 * \ 0 0 1 /
215 matrix_out[COL(0) + ROW(0)] = 0.5;
216 break;
217 case MULTIVIEW_STEREO_TB:
219 * +----------+
220 * | |
221 * | LEFT |
222 * | EYE |
223 * | |
224 * +..........+
225 * | |
226 * | RIGHT |
227 * | EYE |
228 * | |
229 * +----------+
231 * To crop the coordinates to the left eye, divide the y
232 * coordinates by 2:
234 * / 1 0 0 \
235 * matrix = | 0 0.5 0 |
236 * \ 0 0 1 /
238 matrix_out[COL(1) + ROW(1)] = 0.5;
239 break;
240 default:
241 break;
243 #undef COL
244 #undef ROW
247 static char *
248 BuildVertexShader(const struct vlc_gl_renderer *renderer)
250 /* Basic vertex shader */
251 static const char *template =
252 "#version %u\n"
253 "attribute vec2 PicCoordsIn;\n"
254 "varying vec2 PicCoords;\n"
255 "attribute vec3 VertexPosition;\n"
256 "uniform mat3 StereoMatrix;\n"
257 "uniform mat4 ProjectionMatrix;\n"
258 "uniform mat4 ZoomMatrix;\n"
259 "uniform mat4 ViewMatrix;\n"
260 "void main() {\n"
261 " PicCoords = (StereoMatrix * vec3(PicCoordsIn, 1.0)).st;\n"
262 " gl_Position = ProjectionMatrix * ZoomMatrix * ViewMatrix\n"
263 " * vec4(VertexPosition, 1.0);\n"
264 "}";
266 char *code;
267 if (asprintf(&code, template, renderer->glsl_version) < 0)
268 return NULL;
270 if (renderer->b_dump_shaders)
271 msg_Dbg(renderer->gl, "\n=== Vertex shader for fourcc: %4.4s ===\n%s\n",
272 (const char *) &renderer->interop->fmt.i_chroma, code);
273 return code;
276 static char *
277 BuildFragmentShader(struct vlc_gl_renderer *renderer)
279 struct vlc_gl_interop *interop = renderer->interop;
280 char *vlc_texture =
281 opengl_fragment_shader_init(renderer, interop->tex_target,
282 interop->sw_fmt.i_chroma,
283 interop->sw_fmt.space);
284 if (!vlc_texture)
285 return NULL;
287 static const char *template =
288 "#version %u\n"
289 "%s" /* extensions */
290 "%s" /* precision header */
291 "%s" /* vlc_texture definition */
292 "varying vec2 PicCoords;\n"
293 "void main() {\n"
294 " gl_FragColor = vlc_texture(PicCoords);\n"
295 "}\n";
297 /* TODO move extensions back to fragment_shaders.c */
298 const char *extensions = interop->tex_target == GL_TEXTURE_EXTERNAL_OES
299 ? "#extension GL_OES_EGL_image_external : require\n"
300 : "";
302 char *code;
303 int ret = asprintf(&code, template, renderer->glsl_version, extensions,
304 renderer->glsl_precision_header, vlc_texture);
305 free(vlc_texture);
306 if (ret < 0)
307 return NULL;
309 if (renderer->b_dump_shaders)
310 msg_Dbg(renderer->gl, "\n=== Fragment shader for fourcc: %4.4s, colorspace: %d ===\n%s\n",
311 (const char *) &interop->sw_fmt.i_chroma,
312 interop->sw_fmt.space, code);
314 return code;
317 static int
318 opengl_link_program(struct vlc_gl_renderer *renderer)
320 struct vlc_gl_interop *interop = renderer->interop;
321 const opengl_vtable_t *vt = renderer->vt;
323 char *vertex_shader = BuildVertexShader(renderer);
324 if (!vertex_shader)
325 return VLC_EGENERIC;
327 char *fragment_shader = BuildFragmentShader(renderer);
328 if (!fragment_shader)
330 free(vertex_shader);
331 return VLC_EGENERIC;
334 assert(interop->tex_target != 0 &&
335 interop->tex_count > 0 &&
336 interop->ops->update_textures != NULL &&
337 renderer->pf_fetch_locations != NULL &&
338 renderer->pf_prepare_shader != NULL);
340 GLuint program_id =
341 vlc_gl_BuildProgram(VLC_OBJECT(renderer->gl), vt,
342 1, (const char **) &vertex_shader,
343 1, (const char **) &fragment_shader);
344 free(vertex_shader);
345 free(fragment_shader);
346 if (!program_id)
347 return VLC_EGENERIC;
349 /* Fetch UniformLocations and AttribLocations */
350 #define GET_LOC(type, x, str) do { \
351 x = vt->Get##type##Location(program_id, str); \
352 assert(x != -1); \
353 if (x == -1) { \
354 msg_Err(renderer->gl, "Unable to Get"#type"Location(%s)", str); \
355 goto error; \
357 } while (0)
358 #define GET_ULOC(x, str) GET_LOC(Uniform, renderer->uloc.x, str)
359 #define GET_ALOC(x, str) GET_LOC(Attrib, renderer->aloc.x, str)
360 GET_ULOC(TransformMatrix, "TransformMatrix");
361 GET_ULOC(OrientationMatrix, "OrientationMatrix");
362 GET_ULOC(StereoMatrix, "StereoMatrix");
363 GET_ULOC(ProjectionMatrix, "ProjectionMatrix");
364 GET_ULOC(ViewMatrix, "ViewMatrix");
365 GET_ULOC(ZoomMatrix, "ZoomMatrix");
367 GET_ALOC(PicCoordsIn, "PicCoordsIn");
368 GET_ALOC(VertexPosition, "VertexPosition");
370 GET_ULOC(TexCoordsMap[0], "TexCoordsMap0");
371 /* MultiTexCoord 1 and 2 can be optimized out if not used */
372 if (interop->tex_count > 1)
373 GET_ULOC(TexCoordsMap[1], "TexCoordsMap1");
374 else
375 renderer->uloc.TexCoordsMap[1] = -1;
376 if (interop->tex_count > 2)
377 GET_ULOC(TexCoordsMap[2], "TexCoordsMap2");
378 else
379 renderer->uloc.TexCoordsMap[2] = -1;
380 #undef GET_LOC
381 #undef GET_ULOC
382 #undef GET_ALOC
383 int ret = renderer->pf_fetch_locations(renderer, program_id);
384 assert(ret == VLC_SUCCESS);
385 if (ret != VLC_SUCCESS)
387 msg_Err(renderer->gl, "Unable to get locations from tex_conv");
388 goto error;
391 renderer->program_id = program_id;
393 return VLC_SUCCESS;
395 error:
396 vt->DeleteProgram(program_id);
397 renderer->program_id = 0;
398 return VLC_EGENERIC;
401 void
402 vlc_gl_renderer_Delete(struct vlc_gl_renderer *renderer)
404 struct vlc_gl_interop *interop = renderer->interop;
405 const opengl_vtable_t *vt = renderer->vt;
407 vt->DeleteBuffers(1, &renderer->vertex_buffer_object);
408 vt->DeleteBuffers(1, &renderer->index_buffer_object);
409 vt->DeleteBuffers(1, &renderer->texture_buffer_object);
411 if (!interop->handle_texs_gen)
412 vt->DeleteTextures(interop->tex_count, renderer->textures);
414 vlc_gl_interop_Delete(interop);
415 if (renderer->program_id != 0)
416 vt->DeleteProgram(renderer->program_id);
418 #ifdef HAVE_LIBPLACEBO
419 FREENULL(renderer->uloc.pl_vars);
420 if (renderer->pl_ctx)
421 pl_context_destroy(&renderer->pl_ctx);
422 #endif
424 free(renderer);
427 static int SetupCoords(struct vlc_gl_renderer *renderer);
429 struct vlc_gl_renderer *
430 vlc_gl_renderer_New(vlc_gl_t *gl, const struct vlc_gl_api *api,
431 vlc_video_context *context, const video_format_t *fmt,
432 bool b_dump_shaders)
434 const opengl_vtable_t *vt = &api->vt;
436 struct vlc_gl_renderer *renderer = calloc(1, sizeof(*renderer));
437 if (!renderer)
438 return NULL;
440 struct vlc_gl_interop *interop =
441 vlc_gl_interop_New(gl, api, context, fmt, false);
442 if (!interop)
444 free(renderer);
445 return NULL;
448 renderer->interop = interop;
450 renderer->gl = gl;
451 renderer->api = api;
452 renderer->vt = vt;
453 renderer->b_dump_shaders = b_dump_shaders;
454 #if defined(USE_OPENGL_ES2)
455 renderer->glsl_version = 100;
456 renderer->glsl_precision_header = "precision highp float;\n";
457 #else
458 renderer->glsl_version = 120;
459 renderer->glsl_precision_header = "";
460 #endif
462 #ifdef HAVE_LIBPLACEBO
463 // Create the main libplacebo context
464 renderer->pl_ctx = vlc_placebo_Create(VLC_OBJECT(gl));
465 if (renderer->pl_ctx) {
466 # if PL_API_VER >= 20
467 renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL);
468 # elif PL_API_VER >= 6
469 renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL, 0);
470 # else
471 renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL, 0, 0);
472 # endif
474 #endif
476 int ret = opengl_link_program(renderer);
477 if (ret != VLC_SUCCESS)
479 vlc_gl_renderer_Delete(renderer);
480 return NULL;
483 InitStereoMatrix(renderer->var.StereoMatrix, interop->fmt.multiview_mode);
485 getOrientationTransformMatrix(interop->fmt.orientation,
486 renderer->var.OrientationMatrix);
487 getViewpointMatrixes(renderer, interop->fmt.projection_mode);
489 /* Update the fmt to main program one */
490 renderer->fmt = interop->fmt;
491 /* The orientation is handled by the orientation matrix */
492 renderer->fmt.orientation = fmt->orientation;
494 /* Texture size */
495 for (unsigned j = 0; j < interop->tex_count; j++) {
496 const GLsizei w = renderer->fmt.i_visible_width * interop->texs[j].w.num
497 / interop->texs[j].w.den;
498 const GLsizei h = renderer->fmt.i_visible_height * interop->texs[j].h.num
499 / interop->texs[j].h.den;
500 if (api->supports_npot) {
501 renderer->tex_width[j] = w;
502 renderer->tex_height[j] = h;
503 } else {
504 renderer->tex_width[j] = vlc_align_pot(w);
505 renderer->tex_height[j] = vlc_align_pot(h);
509 if (!interop->handle_texs_gen)
511 ret = vlc_gl_interop_GenerateTextures(interop, renderer->tex_width,
512 renderer->tex_height,
513 renderer->textures);
514 if (ret != VLC_SUCCESS)
516 vlc_gl_renderer_Delete(renderer);
517 return NULL;
521 /* */
522 vt->Disable(GL_BLEND);
523 vt->Disable(GL_DEPTH_TEST);
524 vt->DepthMask(GL_FALSE);
525 vt->Enable(GL_CULL_FACE);
526 vt->ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
527 vt->Clear(GL_COLOR_BUFFER_BIT);
529 vt->GenBuffers(1, &renderer->vertex_buffer_object);
530 vt->GenBuffers(1, &renderer->index_buffer_object);
531 vt->GenBuffers(1, &renderer->texture_buffer_object);
533 ret = SetupCoords(renderer);
534 if (ret != VLC_SUCCESS)
536 vlc_gl_renderer_Delete(renderer);
537 return NULL;
540 return renderer;
543 static void UpdateZ(struct vlc_gl_renderer *renderer)
545 /* Do trigonometry to calculate the minimal z value
546 * that will allow us to zoom out without seeing the outside of the
547 * sphere (black borders). */
548 float tan_fovx_2 = tanf(renderer->f_fovx / 2);
549 float tan_fovy_2 = tanf(renderer->f_fovy / 2);
550 float z_min = - SPHERE_RADIUS / sinf(atanf(sqrtf(
551 tan_fovx_2 * tan_fovx_2 + tan_fovy_2 * tan_fovy_2)));
553 /* The FOV value above which z is dynamically calculated. */
554 const float z_thresh = 90.f;
556 if (renderer->f_fovx <= z_thresh * M_PI / 180)
557 renderer->f_z = 0;
558 else
560 float f = z_min / ((FIELD_OF_VIEW_DEGREES_MAX - z_thresh) * M_PI / 180);
561 renderer->f_z = f * renderer->f_fovx - f * z_thresh * M_PI / 180;
562 if (renderer->f_z < z_min)
563 renderer->f_z = z_min;
567 static void UpdateFOVy(struct vlc_gl_renderer *renderer)
569 renderer->f_fovy = 2 * atanf(tanf(renderer->f_fovx / 2) / renderer->f_sar);
573 vlc_gl_renderer_SetViewpoint(struct vlc_gl_renderer *renderer,
574 const vlc_viewpoint_t *p_vp)
576 if (p_vp->fov > FIELD_OF_VIEW_DEGREES_MAX
577 || p_vp->fov < FIELD_OF_VIEW_DEGREES_MIN)
578 return VLC_EBADVAR;
580 // Convert degree into radian
581 float f_fovx = p_vp->fov * (float)M_PI / 180.f;
583 /* vgl->vp needs to be converted into world transform */
584 vlc_viewpoint_reverse(&renderer->vp, p_vp);
586 if (fabsf(f_fovx - renderer->f_fovx) >= 0.001f)
588 /* FOVx has changed. */
589 renderer->f_fovx = f_fovx;
590 UpdateFOVy(renderer);
591 UpdateZ(renderer);
593 getViewpointMatrixes(renderer, renderer->fmt.projection_mode);
595 return VLC_SUCCESS;
598 void
599 vlc_gl_renderer_SetWindowAspectRatio(struct vlc_gl_renderer *renderer,
600 float f_sar)
602 /* Each time the window size changes, we must recompute the minimum zoom
603 * since the aspect ration changes.
604 * We must also set the new current zoom value. */
605 renderer->f_sar = f_sar;
606 UpdateFOVy(renderer);
607 UpdateZ(renderer);
608 getViewpointMatrixes(renderer, renderer->fmt.projection_mode);
611 static int BuildSphere(GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
612 GLushort **indices, unsigned *nbIndices)
614 unsigned nbLatBands = 128;
615 unsigned nbLonBands = 128;
617 *nbVertices = (nbLatBands + 1) * (nbLonBands + 1);
618 *nbIndices = nbLatBands * nbLonBands * 3 * 2;
620 *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
621 if (*vertexCoord == NULL)
622 return VLC_ENOMEM;
623 *textureCoord = vlc_alloc(*nbVertices * 2, sizeof(GLfloat));
624 if (*textureCoord == NULL)
626 free(*vertexCoord);
627 return VLC_ENOMEM;
629 *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
630 if (*indices == NULL)
632 free(*textureCoord);
633 free(*vertexCoord);
634 return VLC_ENOMEM;
637 for (unsigned lat = 0; lat <= nbLatBands; lat++) {
638 float theta = lat * (float) M_PI / nbLatBands;
639 float sinTheta, cosTheta;
641 sincosf(theta, &sinTheta, &cosTheta);
643 for (unsigned lon = 0; lon <= nbLonBands; lon++) {
644 float phi = lon * 2 * (float) M_PI / nbLonBands;
645 float sinPhi, cosPhi;
647 sincosf(phi, &sinPhi, &cosPhi);
649 float x = cosPhi * sinTheta;
650 float y = cosTheta;
651 float z = sinPhi * sinTheta;
653 unsigned off1 = (lat * (nbLonBands + 1) + lon) * 3;
654 (*vertexCoord)[off1] = SPHERE_RADIUS * x;
655 (*vertexCoord)[off1 + 1] = SPHERE_RADIUS * y;
656 (*vertexCoord)[off1 + 2] = SPHERE_RADIUS * z;
658 unsigned off2 = (lat * (nbLonBands + 1) + lon) * 2;
659 float u = (float)lon / nbLonBands;
660 float v = (float)lat / nbLatBands;
661 (*textureCoord)[off2] = u;
662 (*textureCoord)[off2 + 1] = v;
666 for (unsigned lat = 0; lat < nbLatBands; lat++) {
667 for (unsigned lon = 0; lon < nbLonBands; lon++) {
668 unsigned first = (lat * (nbLonBands + 1)) + lon;
669 unsigned second = first + nbLonBands + 1;
671 unsigned off = (lat * nbLatBands + lon) * 3 * 2;
673 (*indices)[off] = first;
674 (*indices)[off + 1] = second;
675 (*indices)[off + 2] = first + 1;
677 (*indices)[off + 3] = second;
678 (*indices)[off + 4] = second + 1;
679 (*indices)[off + 5] = first + 1;
683 return VLC_SUCCESS;
687 static int BuildCube(float padW, float padH,
688 GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
689 GLushort **indices, unsigned *nbIndices)
691 *nbVertices = 4 * 6;
692 *nbIndices = 6 * 6;
694 *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
695 if (*vertexCoord == NULL)
696 return VLC_ENOMEM;
697 *textureCoord = vlc_alloc(*nbVertices * 2, sizeof(GLfloat));
698 if (*textureCoord == NULL)
700 free(*vertexCoord);
701 return VLC_ENOMEM;
703 *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
704 if (*indices == NULL)
706 free(*textureCoord);
707 free(*vertexCoord);
708 return VLC_ENOMEM;
711 static const GLfloat coord[] = {
712 -1.0, 1.0, -1.0f, // front
713 -1.0, -1.0, -1.0f,
714 1.0, 1.0, -1.0f,
715 1.0, -1.0, -1.0f,
717 -1.0, 1.0, 1.0f, // back
718 -1.0, -1.0, 1.0f,
719 1.0, 1.0, 1.0f,
720 1.0, -1.0, 1.0f,
722 -1.0, 1.0, -1.0f, // left
723 -1.0, -1.0, -1.0f,
724 -1.0, 1.0, 1.0f,
725 -1.0, -1.0, 1.0f,
727 1.0f, 1.0, -1.0f, // right
728 1.0f, -1.0, -1.0f,
729 1.0f, 1.0, 1.0f,
730 1.0f, -1.0, 1.0f,
732 -1.0, -1.0, 1.0f, // bottom
733 -1.0, -1.0, -1.0f,
734 1.0, -1.0, 1.0f,
735 1.0, -1.0, -1.0f,
737 -1.0, 1.0, 1.0f, // top
738 -1.0, 1.0, -1.0f,
739 1.0, 1.0, 1.0f,
740 1.0, 1.0, -1.0f,
743 memcpy(*vertexCoord, coord, *nbVertices * 3 * sizeof(GLfloat));
745 float col[] = {0.f, 1.f/3, 2.f/3, 1.f};
746 float row[] = {0.f, 1.f/2, 1.0};
748 const GLfloat tex[] = {
749 col[1] + padW, row[1] + padH, // front
750 col[1] + padW, row[2] - padH,
751 col[2] - padW, row[1] + padH,
752 col[2] - padW, row[2] - padH,
754 col[3] - padW, row[1] + padH, // back
755 col[3] - padW, row[2] - padH,
756 col[2] + padW, row[1] + padH,
757 col[2] + padW, row[2] - padH,
759 col[2] - padW, row[0] + padH, // left
760 col[2] - padW, row[1] - padH,
761 col[1] + padW, row[0] + padH,
762 col[1] + padW, row[1] - padH,
764 col[0] + padW, row[0] + padH, // right
765 col[0] + padW, row[1] - padH,
766 col[1] - padW, row[0] + padH,
767 col[1] - padW, row[1] - padH,
769 col[0] + padW, row[2] - padH, // bottom
770 col[0] + padW, row[1] + padH,
771 col[1] - padW, row[2] - padH,
772 col[1] - padW, row[1] + padH,
774 col[2] + padW, row[0] + padH, // top
775 col[2] + padW, row[1] - padH,
776 col[3] - padW, row[0] + padH,
777 col[3] - padW, row[1] - padH,
780 memcpy(*textureCoord, tex,
781 *nbVertices * 2 * sizeof(GLfloat));
783 const GLushort ind[] = {
784 0, 1, 2, 2, 1, 3, // front
785 6, 7, 4, 4, 7, 5, // back
786 10, 11, 8, 8, 11, 9, // left
787 12, 13, 14, 14, 13, 15, // right
788 18, 19, 16, 16, 19, 17, // bottom
789 20, 21, 22, 22, 21, 23, // top
792 memcpy(*indices, ind, *nbIndices * sizeof(GLushort));
794 return VLC_SUCCESS;
797 static int BuildRectangle(GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
798 GLushort **indices, unsigned *nbIndices)
800 *nbVertices = 4;
801 *nbIndices = 6;
803 *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
804 if (*vertexCoord == NULL)
805 return VLC_ENOMEM;
806 *textureCoord = vlc_alloc(*nbVertices * 2, sizeof(GLfloat));
807 if (*textureCoord == NULL)
809 free(*vertexCoord);
810 return VLC_ENOMEM;
812 *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
813 if (*indices == NULL)
815 free(*textureCoord);
816 free(*vertexCoord);
817 return VLC_ENOMEM;
820 static const GLfloat coord[] = {
821 -1.0, 1.0, -1.0f,
822 -1.0, -1.0, -1.0f,
823 1.0, 1.0, -1.0f,
824 1.0, -1.0, -1.0f
827 memcpy(*vertexCoord, coord, *nbVertices * 3 * sizeof(GLfloat));
829 static const GLfloat tex[] = {
830 0.0, 0.0,
831 0.0, 1.0,
832 1.0, 0.0,
833 1.0, 1.0,
836 memcpy(*textureCoord, tex, *nbVertices * 2 * sizeof(GLfloat));
838 const GLushort ind[] = {
839 0, 1, 2,
840 2, 1, 3
843 memcpy(*indices, ind, *nbIndices * sizeof(GLushort));
845 return VLC_SUCCESS;
848 static int SetupCoords(struct vlc_gl_renderer *renderer)
850 const opengl_vtable_t *vt = renderer->vt;
852 GLfloat *vertexCoord, *textureCoord;
853 GLushort *indices;
854 unsigned nbVertices, nbIndices;
856 int i_ret;
857 switch (renderer->fmt.projection_mode)
859 case PROJECTION_MODE_RECTANGULAR:
860 i_ret = BuildRectangle(&vertexCoord, &textureCoord, &nbVertices,
861 &indices, &nbIndices);
862 break;
863 case PROJECTION_MODE_EQUIRECTANGULAR:
864 i_ret = BuildSphere(&vertexCoord, &textureCoord, &nbVertices,
865 &indices, &nbIndices);
866 break;
867 case PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD:
868 i_ret = BuildCube((float)renderer->fmt.i_cubemap_padding / renderer->fmt.i_width,
869 (float)renderer->fmt.i_cubemap_padding / renderer->fmt.i_height,
870 &vertexCoord, &textureCoord, &nbVertices,
871 &indices, &nbIndices);
872 break;
873 default:
874 i_ret = VLC_EGENERIC;
875 break;
878 if (i_ret != VLC_SUCCESS)
879 return i_ret;
881 vt->BindBuffer(GL_ARRAY_BUFFER, renderer->texture_buffer_object);
882 vt->BufferData(GL_ARRAY_BUFFER, nbVertices * 2 * sizeof(GLfloat),
883 textureCoord, GL_STATIC_DRAW);
885 vt->BindBuffer(GL_ARRAY_BUFFER, renderer->vertex_buffer_object);
886 vt->BufferData(GL_ARRAY_BUFFER, nbVertices * 3 * sizeof(GLfloat),
887 vertexCoord, GL_STATIC_DRAW);
889 vt->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->index_buffer_object);
890 vt->BufferData(GL_ELEMENT_ARRAY_BUFFER, nbIndices * sizeof(GLushort),
891 indices, GL_STATIC_DRAW);
893 free(textureCoord);
894 free(vertexCoord);
895 free(indices);
897 renderer->nb_indices = nbIndices;
899 return VLC_SUCCESS;
902 static void DrawWithShaders(struct vlc_gl_renderer *renderer)
904 const struct vlc_gl_interop *interop = renderer->interop;
905 const opengl_vtable_t *vt = renderer->vt;
906 renderer->pf_prepare_shader(renderer, renderer->tex_width,
907 renderer->tex_height, 1.0f);
909 for (unsigned j = 0; j < interop->tex_count; j++) {
910 assert(renderer->textures[j] != 0);
911 vt->ActiveTexture(GL_TEXTURE0+j);
912 vt->BindTexture(interop->tex_target, renderer->textures[j]);
914 vt->UniformMatrix3fv(renderer->uloc.TexCoordsMap[j], 1, GL_FALSE,
915 renderer->var.TexCoordsMap[j]);
918 vt->BindBuffer(GL_ARRAY_BUFFER, renderer->texture_buffer_object);
919 assert(renderer->aloc.PicCoordsIn != -1);
920 vt->EnableVertexAttribArray(renderer->aloc.PicCoordsIn);
921 vt->VertexAttribPointer(renderer->aloc.PicCoordsIn, 2, GL_FLOAT, 0, 0, 0);
923 vt->BindBuffer(GL_ARRAY_BUFFER, renderer->vertex_buffer_object);
924 vt->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->index_buffer_object);
925 vt->EnableVertexAttribArray(renderer->aloc.VertexPosition);
926 vt->VertexAttribPointer(renderer->aloc.VertexPosition, 3, GL_FLOAT, 0, 0, 0);
928 const GLfloat *tm = NULL;
929 if (interop->ops && interop->ops->get_transform_matrix)
930 tm = interop->ops->get_transform_matrix(interop);
931 if (!tm)
932 tm = identity;
934 vt->UniformMatrix4fv(renderer->uloc.TransformMatrix, 1, GL_FALSE, tm);
936 vt->UniformMatrix4fv(renderer->uloc.OrientationMatrix, 1, GL_FALSE,
937 renderer->var.OrientationMatrix);
938 vt->UniformMatrix3fv(renderer->uloc.StereoMatrix, 1, GL_FALSE,
939 renderer->var.StereoMatrix);
940 vt->UniformMatrix4fv(renderer->uloc.ProjectionMatrix, 1, GL_FALSE,
941 renderer->var.ProjectionMatrix);
942 vt->UniformMatrix4fv(renderer->uloc.ViewMatrix, 1, GL_FALSE,
943 renderer->var.ViewMatrix);
944 vt->UniformMatrix4fv(renderer->uloc.ZoomMatrix, 1, GL_FALSE,
945 renderer->var.ZoomMatrix);
947 vt->DrawElements(GL_TRIANGLES, renderer->nb_indices, GL_UNSIGNED_SHORT, 0);
951 vlc_gl_renderer_Prepare(struct vlc_gl_renderer *renderer, picture_t *picture)
953 const struct vlc_gl_interop *interop = renderer->interop;
954 const video_format_t *source = &picture->format;
956 if (source->i_x_offset != renderer->last_source.i_x_offset
957 || source->i_y_offset != renderer->last_source.i_y_offset
958 || source->i_visible_width != renderer->last_source.i_visible_width
959 || source->i_visible_height != renderer->last_source.i_visible_height)
961 memset(renderer->var.TexCoordsMap, 0,
962 sizeof(renderer->var.TexCoordsMap));
963 for (unsigned j = 0; j < interop->tex_count; j++)
965 float scale_w = (float)interop->texs[j].w.num / interop->texs[j].w.den
966 / renderer->tex_width[j];
967 float scale_h = (float)interop->texs[j].h.num / interop->texs[j].h.den
968 / renderer->tex_height[j];
970 /* Warning: if NPOT is not supported a larger texture is
971 allocated. This will cause right and bottom coordinates to
972 land on the edge of two texels with the texels to the
973 right/bottom uninitialized by the call to
974 glTexSubImage2D. This might cause a green line to appear on
975 the right/bottom of the display.
976 There are two possible solutions:
977 - Manually mirror the edges of the texture.
978 - Add a "-1" when computing right and bottom, however the
979 last row/column might not be displayed at all.
981 float left = (source->i_x_offset + 0 ) * scale_w;
982 float top = (source->i_y_offset + 0 ) * scale_h;
983 float right = (source->i_x_offset + source->i_visible_width ) * scale_w;
984 float bottom = (source->i_y_offset + source->i_visible_height) * scale_h;
987 * This matrix converts from picture coordinates (in range [0; 1])
988 * to textures coordinates where the picture is actually stored
989 * (removing paddings).
991 * texture (in texture coordinates)
992 * +----------------+--- 0.0
993 * | |
994 * | +---------+---|--- top
995 * | | picture | |
996 * | +---------+---|--- bottom
997 * | . . |
998 * | . . |
999 * +----------------+--- 1.0
1000 * | . . |
1001 * 0.0 left right 1.0 (in texture coordinates)
1003 * In particular:
1004 * - (0.0, 0.0) is mapped to (left, top)
1005 * - (1.0, 1.0) is mapped to (right, bottom)
1007 * This is an affine 2D transformation, so the input coordinates
1008 * are given as a 3D vector in the form (x, y, 1), and the output
1009 * is (x', y', 1).
1011 * The paddings are l (left), r (right), t (top) and b (bottom).
1013 * / (r-l) 0 l \
1014 * matrix = | 0 (b-t) t |
1015 * \ 0 0 1 /
1017 * It is stored in column-major order.
1019 GLfloat *matrix = renderer->var.TexCoordsMap[j];
1020 #define COL(x) (x*3)
1021 #define ROW(x) (x)
1022 matrix[COL(0) + ROW(0)] = right - left;
1023 matrix[COL(1) + ROW(1)] = bottom - top;
1024 matrix[COL(2) + ROW(0)] = left;
1025 matrix[COL(2) + ROW(1)] = top;
1026 #undef COL
1027 #undef ROW
1030 renderer->last_source.i_x_offset = source->i_x_offset;
1031 renderer->last_source.i_y_offset = source->i_y_offset;
1032 renderer->last_source.i_visible_width = source->i_visible_width;
1033 renderer->last_source.i_visible_height = source->i_visible_height;
1036 /* Update the texture */
1037 return interop->ops->update_textures(interop, renderer->textures,
1038 renderer->tex_width,
1039 renderer->tex_height, picture,
1040 NULL);
1044 vlc_gl_renderer_Draw(struct vlc_gl_renderer *renderer)
1046 const opengl_vtable_t *vt = renderer->vt;
1048 vt->Clear(GL_COLOR_BUFFER_BIT);
1050 vt->UseProgram(renderer->program_id);
1052 DrawWithShaders(renderer);
1054 return VLC_SUCCESS;