1/*
2 *  Copyright 2016 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#import "RTCShader.h"
12
13#if TARGET_OS_IPHONE
14#import <OpenGLES/ES3/gl.h>
15#else
16#import <OpenGL/gl3.h>
17#endif
18
19#include <algorithm>
20#include <array>
21#include <memory>
22
23#import "RTCOpenGLDefines.h"
24
25#include "rtc_base/checks.h"
26#include "rtc_base/logging.h"
27
28// Vertex shader doesn't do anything except pass coordinates through.
29const char kRTCVertexShaderSource[] =
30  SHADER_VERSION
31  VERTEX_SHADER_IN " vec2 position;\n"
32  VERTEX_SHADER_IN " vec2 texcoord;\n"
33  VERTEX_SHADER_OUT " vec2 v_texcoord;\n"
34  "void main() {\n"
35  "    gl_Position = vec4(position.x, position.y, 0.0, 1.0);\n"
36  "    v_texcoord = texcoord;\n"
37  "}\n";
38
39// Compiles a shader of the given |type| with GLSL source |source| and returns
40// the shader handle or 0 on error.
41GLuint RTCCreateShader(GLenum type, const GLchar *source) {
42  GLuint shader = glCreateShader(type);
43  if (!shader) {
44    return 0;
45  }
46  glShaderSource(shader, 1, &source, NULL);
47  glCompileShader(shader);
48  GLint compileStatus = GL_FALSE;
49  glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
50  if (compileStatus == GL_FALSE) {
51    GLint logLength = 0;
52    // The null termination character is included in the returned log length.
53    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
54    if (logLength > 0) {
55      std::unique_ptr<char[]> compileLog(new char[logLength]);
56      // The returned string is null terminated.
57      glGetShaderInfoLog(shader, logLength, NULL, compileLog.get());
58      RTC_LOG(LS_ERROR) << "Shader compile error: " << compileLog.get();
59    }
60    glDeleteShader(shader);
61    shader = 0;
62  }
63  return shader;
64}
65
66// Links a shader program with the given vertex and fragment shaders and
67// returns the program handle or 0 on error.
68GLuint RTCCreateProgram(GLuint vertexShader, GLuint fragmentShader) {
69  if (vertexShader == 0 || fragmentShader == 0) {
70    return 0;
71  }
72  GLuint program = glCreateProgram();
73  if (!program) {
74    return 0;
75  }
76  glAttachShader(program, vertexShader);
77  glAttachShader(program, fragmentShader);
78  glLinkProgram(program);
79  GLint linkStatus = GL_FALSE;
80  glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
81  if (linkStatus == GL_FALSE) {
82    glDeleteProgram(program);
83    program = 0;
84  }
85  return program;
86}
87
88// Creates and links a shader program with the given fragment shader source and
89// a plain vertex shader. Returns the program handle or 0 on error.
90GLuint RTCCreateProgramFromFragmentSource(const char fragmentShaderSource[]) {
91  GLuint vertexShader = RTCCreateShader(GL_VERTEX_SHADER, kRTCVertexShaderSource);
92  RTC_CHECK(vertexShader) << "failed to create vertex shader";
93  GLuint fragmentShader =
94      RTCCreateShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
95  RTC_CHECK(fragmentShader) << "failed to create fragment shader";
96  GLuint program = RTCCreateProgram(vertexShader, fragmentShader);
97  // Shaders are created only to generate program.
98  if (vertexShader) {
99    glDeleteShader(vertexShader);
100  }
101  if (fragmentShader) {
102    glDeleteShader(fragmentShader);
103  }
104
105  // Set vertex shader variables 'position' and 'texcoord' in program.
106  GLint position = glGetAttribLocation(program, "position");
107  GLint texcoord = glGetAttribLocation(program, "texcoord");
108  if (position < 0 || texcoord < 0) {
109    glDeleteProgram(program);
110    return 0;
111  }
112
113  // Read position attribute with size of 2 and stride of 4 beginning at the start of the array. The
114  // last argument indicates offset of data within the vertex buffer.
115  glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void *)0);
116  glEnableVertexAttribArray(position);
117
118  // Read texcoord attribute  with size of 2 and stride of 4 beginning at the first texcoord in the
119  // array. The last argument indicates offset of data within the vertex buffer.
120  glVertexAttribPointer(
121      texcoord, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void *)(2 * sizeof(GLfloat)));
122  glEnableVertexAttribArray(texcoord);
123
124  return program;
125}
126
127BOOL RTCCreateVertexBuffer(GLuint *vertexBuffer, GLuint *vertexArray) {
128#if !TARGET_OS_IPHONE
129  glGenVertexArrays(1, vertexArray);
130  if (*vertexArray == 0) {
131    return NO;
132  }
133  glBindVertexArray(*vertexArray);
134#endif
135  glGenBuffers(1, vertexBuffer);
136  if (*vertexBuffer == 0) {
137    glDeleteVertexArrays(1, vertexArray);
138    return NO;
139  }
140  glBindBuffer(GL_ARRAY_BUFFER, *vertexBuffer);
141  glBufferData(GL_ARRAY_BUFFER, 4 * 4 * sizeof(GLfloat), NULL, GL_DYNAMIC_DRAW);
142  return YES;
143}
144
145// Set vertex data to the currently bound vertex buffer.
146void RTCSetVertexData(RTCVideoRotation rotation) {
147  // When modelview and projection matrices are identity (default) the world is
148  // contained in the square around origin with unit size 2. Drawing to these
149  // coordinates is equivalent to drawing to the entire screen. The texture is
150  // stretched over that square using texture coordinates (u, v) that range
151  // from (0, 0) to (1, 1) inclusive. Texture coordinates are flipped vertically
152  // here because the incoming frame has origin in upper left hand corner but
153  // OpenGL expects origin in bottom left corner.
154  std::array<std::array<GLfloat, 2>, 4> UVCoords = {{
155      {{0, 1}},  // Lower left.
156      {{1, 1}},  // Lower right.
157      {{1, 0}},  // Upper right.
158      {{0, 0}},  // Upper left.
159  }};
160
161  // Rotate the UV coordinates.
162  int rotation_offset;
163  switch (rotation) {
164    case RTCVideoRotation_0:
165      rotation_offset = 0;
166      break;
167    case RTCVideoRotation_90:
168      rotation_offset = 1;
169      break;
170    case RTCVideoRotation_180:
171      rotation_offset = 2;
172      break;
173    case RTCVideoRotation_270:
174      rotation_offset = 3;
175      break;
176  }
177  std::rotate(UVCoords.begin(), UVCoords.begin() + rotation_offset,
178              UVCoords.end());
179
180  const GLfloat gVertices[] = {
181      // X, Y, U, V.
182      -1, -1, UVCoords[0][0], UVCoords[0][1],
183       1, -1, UVCoords[1][0], UVCoords[1][1],
184       1,  1, UVCoords[2][0], UVCoords[2][1],
185      -1,  1, UVCoords[3][0], UVCoords[3][1],
186  };
187
188  glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(gVertices), gVertices);
189}
190