1/*
2 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#if !defined(__has_feature) || !__has_feature(objc_arc)
12#error "This file requires ARC support."
13#endif
14
15// This files is mostly copied from
16// webrtc/modules/video_render/android/video_render_opengles20.h
17
18// TODO(sjlee): unify this copy with the android one.
19#include "webrtc/modules/video_render/ios/open_gles20.h"
20#include "webrtc/system_wrappers/include/trace.h"
21
22using namespace webrtc;
23
24const char OpenGles20::indices_[] = {0, 3, 2, 0, 2, 1};
25
26const char OpenGles20::vertext_shader_[] = {
27    "attribute vec4 aPosition;\n"
28    "attribute vec2 aTextureCoord;\n"
29    "varying vec2 vTextureCoord;\n"
30    "void main() {\n"
31    "  gl_Position = aPosition;\n"
32    "  vTextureCoord = aTextureCoord;\n"
33    "}\n"};
34
35// The fragment shader.
36// Do YUV to RGB565 conversion.
37const char OpenGles20::fragment_shader_[] = {
38    "precision mediump float;\n"
39    "uniform sampler2D Ytex;\n"
40    "uniform sampler2D Utex,Vtex;\n"
41    "varying vec2 vTextureCoord;\n"
42    "void main(void) {\n"
43    "  float nx,ny,r,g,b,y,u,v;\n"
44    "  mediump vec4 txl,ux,vx;"
45    "  nx=vTextureCoord[0];\n"
46    "  ny=vTextureCoord[1];\n"
47    "  y=texture2D(Ytex,vec2(nx,ny)).r;\n"
48    "  u=texture2D(Utex,vec2(nx,ny)).r;\n"
49    "  v=texture2D(Vtex,vec2(nx,ny)).r;\n"
50    "  y=1.1643*(y-0.0625);\n"
51    "  u=u-0.5;\n"
52    "  v=v-0.5;\n"
53    "  r=y+1.5958*v;\n"
54    "  g=y-0.39173*u-0.81290*v;\n"
55    "  b=y+2.017*u;\n"
56    "  gl_FragColor=vec4(r,g,b,1.0);\n"
57    "}\n"};
58
59OpenGles20::OpenGles20() : texture_width_(-1), texture_height_(-1) {
60  texture_ids_[0] = 0;
61  texture_ids_[1] = 0;
62  texture_ids_[2] = 0;
63
64  program_ = 0;
65
66  const GLfloat vertices[20] = {
67      // X, Y, Z, U, V
68      -1, -1, 0, 0, 1,   // Bottom Left
69      1,  -1, 0, 1, 1,   // Bottom Right
70      1,  1,  0, 1, 0,   // Top Right
71      -1, 1,  0, 0, 0};  // Top Left
72
73  memcpy(vertices_, vertices, sizeof(vertices_));
74}
75
76OpenGles20::~OpenGles20() {
77  if (program_) {
78    glDeleteTextures(3, texture_ids_);
79    glDeleteProgram(program_);
80  }
81}
82
83bool OpenGles20::Setup(int32_t width, int32_t height) {
84  program_ = CreateProgram(vertext_shader_, fragment_shader_);
85  if (!program_) {
86    return false;
87  }
88
89  int position_handle = glGetAttribLocation(program_, "aPosition");
90  int texture_handle = glGetAttribLocation(program_, "aTextureCoord");
91
92  // set the vertices array in the shader
93  // vertices_ contains 4 vertices with 5 coordinates.
94  // 3 for (xyz) for the vertices and 2 for the texture
95  glVertexAttribPointer(
96      position_handle, 3, GL_FLOAT, false, 5 * sizeof(GLfloat), vertices_);
97
98  glEnableVertexAttribArray(position_handle);
99
100  // set the texture coordinate array in the shader
101  // vertices_ contains 4 vertices with 5 coordinates.
102  // 3 for (xyz) for the vertices and 2 for the texture
103  glVertexAttribPointer(
104      texture_handle, 2, GL_FLOAT, false, 5 * sizeof(GLfloat), &vertices_[3]);
105  glEnableVertexAttribArray(texture_handle);
106
107  glUseProgram(program_);
108  int i = glGetUniformLocation(program_, "Ytex");
109  glUniform1i(i, 0); /* Bind Ytex to texture unit 0 */
110
111  i = glGetUniformLocation(program_, "Utex");
112  glUniform1i(i, 1); /* Bind Utex to texture unit 1 */
113
114  i = glGetUniformLocation(program_, "Vtex");
115  glUniform1i(i, 2); /* Bind Vtex to texture unit 2 */
116
117  glViewport(0, 0, width, height);
118  return true;
119}
120
121bool OpenGles20::SetCoordinates(const float z_order,
122                                const float left,
123                                const float top,
124                                const float right,
125                                const float bottom) {
126  if (top > 1 || top < 0 || right > 1 || right < 0 || bottom > 1 ||
127      bottom < 0 || left > 1 || left < 0) {
128    return false;
129  }
130
131  // Bottom Left
132  vertices_[0] = (left * 2) - 1;
133  vertices_[1] = -1 * (2 * bottom) + 1;
134  vertices_[2] = z_order;
135
136  // Bottom Right
137  vertices_[5] = (right * 2) - 1;
138  vertices_[6] = -1 * (2 * bottom) + 1;
139  vertices_[7] = z_order;
140
141  // Top Right
142  vertices_[10] = (right * 2) - 1;
143  vertices_[11] = -1 * (2 * top) + 1;
144  vertices_[12] = z_order;
145
146  // Top Left
147  vertices_[15] = (left * 2) - 1;
148  vertices_[16] = -1 * (2 * top) + 1;
149  vertices_[17] = z_order;
150
151  return true;
152}
153
154bool OpenGles20::Render(const VideoFrame& frame) {
155  if (texture_width_ != (GLsizei)frame.width() ||
156      texture_height_ != (GLsizei)frame.height()) {
157    SetupTextures(frame);
158  }
159  UpdateTextures(frame);
160
161  glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices_);
162
163  return true;
164}
165
166GLuint OpenGles20::LoadShader(GLenum shader_type, const char* shader_source) {
167  GLuint shader = glCreateShader(shader_type);
168  if (shader) {
169    glShaderSource(shader, 1, &shader_source, NULL);
170    glCompileShader(shader);
171
172    GLint compiled = 0;
173    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
174    if (!compiled) {
175      GLint info_len = 0;
176      glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
177      if (info_len) {
178        char* buf = (char*)malloc(info_len);
179        glGetShaderInfoLog(shader, info_len, NULL, buf);
180        WEBRTC_TRACE(kTraceError,
181                     kTraceVideoRenderer,
182                     0,
183                     "%s: Could not compile shader %d: %s",
184                     __FUNCTION__,
185                     shader_type,
186                     buf);
187        free(buf);
188      }
189      glDeleteShader(shader);
190      shader = 0;
191    }
192  }
193  return shader;
194}
195
196GLuint OpenGles20::CreateProgram(const char* vertex_source,
197                                 const char* fragment_source) {
198  GLuint vertex_shader = LoadShader(GL_VERTEX_SHADER, vertex_source);
199  if (!vertex_shader) {
200    return -1;
201  }
202
203  GLuint fragment_shader = LoadShader(GL_FRAGMENT_SHADER, fragment_source);
204  if (!fragment_shader) {
205    return -1;
206  }
207
208  GLuint program = glCreateProgram();
209  if (program) {
210    glAttachShader(program, vertex_shader);
211    glAttachShader(program, fragment_shader);
212    glLinkProgram(program);
213    GLint link_status = GL_FALSE;
214    glGetProgramiv(program, GL_LINK_STATUS, &link_status);
215    if (link_status != GL_TRUE) {
216      GLint info_len = 0;
217      glGetProgramiv(program, GL_INFO_LOG_LENGTH, &info_len);
218      if (info_len) {
219        char* buf = (char*)malloc(info_len);
220        glGetProgramInfoLog(program, info_len, NULL, buf);
221        WEBRTC_TRACE(kTraceError,
222                     kTraceVideoRenderer,
223                     0,
224                     "%s: Could not link program: %s",
225                     __FUNCTION__,
226                     buf);
227        free(buf);
228      }
229      glDeleteProgram(program);
230      program = 0;
231    }
232  }
233
234  if (vertex_shader) {
235    glDeleteShader(vertex_shader);
236  }
237
238  if (fragment_shader) {
239    glDeleteShader(fragment_shader);
240  }
241
242  return program;
243}
244
245static void InitializeTexture(int name, int id, int width, int height) {
246  glActiveTexture(name);
247  glBindTexture(GL_TEXTURE_2D, id);
248  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
249  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
250  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
251  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
252  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
253  glTexImage2D(GL_TEXTURE_2D,
254               0,
255               GL_LUMINANCE,
256               width,
257               height,
258               0,
259               GL_LUMINANCE,
260               GL_UNSIGNED_BYTE,
261               NULL);
262}
263
264void OpenGles20::SetupTextures(const VideoFrame& frame) {
265  const GLsizei width = frame.width();
266  const GLsizei height = frame.height();
267
268  if (!texture_ids_[0]) {
269    glGenTextures(3, texture_ids_);  // Generate  the Y, U and V texture
270  }
271
272  InitializeTexture(GL_TEXTURE0, texture_ids_[0], width, height);
273  InitializeTexture(GL_TEXTURE1, texture_ids_[1], width / 2, height / 2);
274  InitializeTexture(GL_TEXTURE2, texture_ids_[2], width / 2, height / 2);
275
276  texture_width_ = width;
277  texture_height_ = height;
278}
279
280// Uploads a plane of pixel data, accounting for stride != width*bpp.
281static void GlTexSubImage2D(GLsizei width,
282                            GLsizei height,
283                            int stride,
284                            const uint8_t* plane) {
285  if (stride == width) {
286    // Yay!  We can upload the entire plane in a single GL call.
287    glTexSubImage2D(GL_TEXTURE_2D,
288                    0,
289                    0,
290                    0,
291                    width,
292                    height,
293                    GL_LUMINANCE,
294                    GL_UNSIGNED_BYTE,
295                    static_cast<const GLvoid*>(plane));
296  } else {
297    // Boo!  Since GLES2 doesn't have GL_UNPACK_ROW_LENGTH and iOS doesn't
298    // have GL_EXT_unpack_subimage we have to upload a row at a time.  Ick.
299    for (int row = 0; row < height; ++row) {
300      glTexSubImage2D(GL_TEXTURE_2D,
301                      0,
302                      0,
303                      row,
304                      width,
305                      1,
306                      GL_LUMINANCE,
307                      GL_UNSIGNED_BYTE,
308                      static_cast<const GLvoid*>(plane + (row * stride)));
309    }
310  }
311}
312
313void OpenGles20::UpdateTextures(const VideoFrame& frame) {
314  const GLsizei width = frame.width();
315  const GLsizei height = frame.height();
316
317  glActiveTexture(GL_TEXTURE0);
318  glBindTexture(GL_TEXTURE_2D, texture_ids_[0]);
319  GlTexSubImage2D(width, height, frame.stride(kYPlane), frame.buffer(kYPlane));
320
321  glActiveTexture(GL_TEXTURE1);
322  glBindTexture(GL_TEXTURE_2D, texture_ids_[1]);
323  GlTexSubImage2D(
324      width / 2, height / 2, frame.stride(kUPlane), frame.buffer(kUPlane));
325
326  glActiveTexture(GL_TEXTURE2);
327  glBindTexture(GL_TEXTURE_2D, texture_ids_[2]);
328  GlTexSubImage2D(
329      width / 2, height / 2, frame.stride(kVPlane), frame.buffer(kVPlane));
330}
331