blob: 5a24cf0eb379e0de716e102aab57df5f659e7b43 [file] [log] [blame]
tkchin@webrtc.org1732a592014-05-19 23:26:01 +00001/*
2 * libjingle
3 * Copyright 2014, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#if !defined(__has_feature) || !__has_feature(objc_arc)
29#error "This file requires ARC support."
30#endif
31
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000032#import "RTCOpenGLVideoRenderer.h"
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000033
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000034#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000035#import <OpenGLES/ES2/gl.h>
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000036#else
37#import <OpenGL/gl3.h>
38#endif
39
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000040#import "RTCI420Frame.h"
41
42// TODO(tkchin): check and log openGL errors. Methods here return BOOLs in
43// anticipation of that happening in the future.
44
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000045#if TARGET_OS_IPHONE
46#define RTC_PIXEL_FORMAT GL_LUMINANCE
47#define SHADER_VERSION
48#define VERTEX_SHADER_IN "attribute"
49#define VERTEX_SHADER_OUT "varying"
50#define FRAGMENT_SHADER_IN "varying"
51#define FRAGMENT_SHADER_OUT
52#define FRAGMENT_SHADER_COLOR "gl_FragColor"
53#define FRAGMENT_SHADER_TEXTURE "texture2D"
54#else
55#define RTC_PIXEL_FORMAT GL_RED
56#define SHADER_VERSION "#version 150\n"
57#define VERTEX_SHADER_IN "in"
58#define VERTEX_SHADER_OUT "out"
59#define FRAGMENT_SHADER_IN "in"
60#define FRAGMENT_SHADER_OUT "out vec4 fragColor;\n"
61#define FRAGMENT_SHADER_COLOR "fragColor"
62#define FRAGMENT_SHADER_TEXTURE "texture"
63#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000064
65// Vertex shader doesn't do anything except pass coordinates through.
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000066static const char kVertexShaderSource[] =
67 SHADER_VERSION
68 VERTEX_SHADER_IN " vec2 position;\n"
69 VERTEX_SHADER_IN " vec2 texcoord;\n"
70 VERTEX_SHADER_OUT " vec2 v_texcoord;\n"
71 "void main() {\n"
72 " gl_Position = vec4(position.x, position.y, 0.0, 1.0);\n"
73 " v_texcoord = texcoord;\n"
74 "}\n";
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000075
76// Fragment shader converts YUV values from input textures into a final RGB
77// pixel. The conversion formula is from http://www.fourcc.org/fccyvrgb.php.
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000078static const char kFragmentShaderSource[] =
79 SHADER_VERSION
80 "precision highp float;"
81 FRAGMENT_SHADER_IN " vec2 v_texcoord;\n"
82 "uniform lowp sampler2D s_textureY;\n"
83 "uniform lowp sampler2D s_textureU;\n"
84 "uniform lowp sampler2D s_textureV;\n"
85 FRAGMENT_SHADER_OUT
86 "void main() {\n"
87 " float y, u, v, r, g, b;\n"
88 " y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n"
89 " u = " FRAGMENT_SHADER_TEXTURE "(s_textureU, v_texcoord).r;\n"
90 " v = " FRAGMENT_SHADER_TEXTURE "(s_textureV, v_texcoord).r;\n"
91 " u = u - 0.5;\n"
92 " v = v - 0.5;\n"
93 " r = y + 1.403 * v;\n"
94 " g = y - 0.344 * u - 0.714 * v;\n"
95 " b = y + 1.770 * u;\n"
96 " " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n"
97 " }\n";
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000098
99// Compiles a shader of the given |type| with GLSL source |source| and returns
100// the shader handle or 0 on error.
101GLuint CreateShader(GLenum type, const GLchar* source) {
102 GLuint shader = glCreateShader(type);
103 if (!shader) {
104 return 0;
105 }
106 glShaderSource(shader, 1, &source, NULL);
107 glCompileShader(shader);
108 GLint compileStatus = GL_FALSE;
109 glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
110 if (compileStatus == GL_FALSE) {
111 glDeleteShader(shader);
112 shader = 0;
113 }
114 return shader;
115}
116
117// Links a shader program with the given vertex and fragment shaders and
118// returns the program handle or 0 on error.
119GLuint CreateProgram(GLuint vertexShader, GLuint fragmentShader) {
120 if (vertexShader == 0 || fragmentShader == 0) {
121 return 0;
122 }
123 GLuint program = glCreateProgram();
124 if (!program) {
125 return 0;
126 }
127 glAttachShader(program, vertexShader);
128 glAttachShader(program, fragmentShader);
129 glLinkProgram(program);
130 GLint linkStatus = GL_FALSE;
131 glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
132 if (linkStatus == GL_FALSE) {
133 glDeleteProgram(program);
134 program = 0;
135 }
136 return program;
137}
138
139// When modelview and projection matrices are identity (default) the world is
140// contained in the square around origin with unit size 2. Drawing to these
141// coordinates is equivalent to drawing to the entire screen. The texture is
142// stretched over that square using texture coordinates (u, v) that range
143// from (0, 0) to (1, 1) inclusive. Texture coordinates are flipped vertically
144// here because the incoming frame has origin in upper left hand corner but
145// OpenGL expects origin in bottom left corner.
146const GLfloat gVertices[] = {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000147 // X, Y, U, V.
148 -1, -1, 0, 1, // Bottom left.
149 1, -1, 1, 1, // Bottom right.
150 1, 1, 1, 0, // Top right.
151 -1, 1, 0, 0, // Top left.
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000152};
153
154// |kNumTextures| must not exceed 8, which is the limit in OpenGLES2. Two sets
155// of 3 textures are used here, one for each of the Y, U and V planes. Having
156// two sets alleviates CPU blockage in the event that the GPU is asked to render
157// to a texture that is already in use.
158static const GLsizei kNumTextureSets = 2;
159static const GLsizei kNumTextures = 3 * kNumTextureSets;
160
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000161@implementation RTCOpenGLVideoRenderer {
162#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000163 EAGLContext* _context;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000164#else
165 NSOpenGLContext* _context;
166#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000167 BOOL _isInitialized;
168 NSUInteger _currentTextureSet;
169 // Handles for OpenGL constructs.
170 GLuint _textures[kNumTextures];
171 GLuint _program;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000172#if !TARGET_OS_IPHONE
173 GLuint _vertexArray;
174#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000175 GLuint _vertexBuffer;
176 GLint _position;
177 GLint _texcoord;
178 GLint _ySampler;
179 GLint _uSampler;
180 GLint _vSampler;
181}
182
183+ (void)initialize {
184 // Disable dithering for performance.
185 glDisable(GL_DITHER);
186}
187
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000188#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000189- (instancetype)initWithContext:(EAGLContext*)context {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000190#else
191- (instancetype)initWithContext:(NSOpenGLContext*)context {
192#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000193 NSAssert(context != nil, @"context cannot be nil");
194 if (self = [super init]) {
195 _context = context;
196 }
197 return self;
198}
199
200- (BOOL)drawFrame:(RTCI420Frame*)frame {
201 if (!_isInitialized) {
202 return NO;
203 }
204 if (_lastDrawnFrame == frame) {
205 return NO;
206 }
207 [self ensureGLContext];
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000208 glClear(GL_COLOR_BUFFER_BIT);
tkchin@webrtc.org90750482014-09-02 20:50:00 +0000209 if (frame) {
210 if (![self updateTextureSizesForFrame:frame] ||
211 ![self updateTextureDataForFrame:frame]) {
212 return NO;
213 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000214#if !TARGET_OS_IPHONE
tkchin@webrtc.org90750482014-09-02 20:50:00 +0000215 glBindVertexArray(_vertexArray);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000216#endif
tkchin@webrtc.org90750482014-09-02 20:50:00 +0000217 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
218 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
219 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000220#if !TARGET_OS_IPHONE
221 [_context flushBuffer];
222#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000223 _lastDrawnFrame = frame;
224 return YES;
225}
226
227- (void)setupGL {
228 if (_isInitialized) {
229 return;
230 }
231 [self ensureGLContext];
232 if (![self setupProgram]) {
233 return;
234 }
235 if (![self setupTextures]) {
236 return;
237 }
238 if (![self setupVertices]) {
239 return;
240 }
241 glUseProgram(_program);
242 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000243 _isInitialized = YES;
244}
245
246- (void)teardownGL {
247 if (!_isInitialized) {
248 return;
249 }
250 [self ensureGLContext];
251 glDeleteProgram(_program);
252 _program = 0;
253 glDeleteTextures(kNumTextures, _textures);
254 glDeleteBuffers(1, &_vertexBuffer);
255 _vertexBuffer = 0;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000256#if !TARGET_OS_IPHONE
257 glDeleteVertexArrays(1, &_vertexArray);
258#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000259 _isInitialized = NO;
260}
261
262#pragma mark - Private
263
264- (void)ensureGLContext {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000265 NSAssert(_context, @"context shouldn't be nil");
266#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000267 if ([EAGLContext currentContext] != _context) {
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000268 [EAGLContext setCurrentContext:_context];
269 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000270#else
271 if ([NSOpenGLContext currentContext] != _context) {
272 [_context makeCurrentContext];
273 }
274#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000275}
276
277- (BOOL)setupProgram {
278 NSAssert(!_program, @"program already set up");
279 GLuint vertexShader = CreateShader(GL_VERTEX_SHADER, kVertexShaderSource);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000280 NSAssert(vertexShader, @"failed to create vertex shader");
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000281 GLuint fragmentShader =
282 CreateShader(GL_FRAGMENT_SHADER, kFragmentShaderSource);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000283 NSAssert(fragmentShader, @"failed to create fragment shader");
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000284 _program = CreateProgram(vertexShader, fragmentShader);
285 // Shaders are created only to generate program.
286 if (vertexShader) {
287 glDeleteShader(vertexShader);
288 }
289 if (fragmentShader) {
290 glDeleteShader(fragmentShader);
291 }
292 if (!_program) {
293 return NO;
294 }
295 _position = glGetAttribLocation(_program, "position");
296 _texcoord = glGetAttribLocation(_program, "texcoord");
297 _ySampler = glGetUniformLocation(_program, "s_textureY");
298 _uSampler = glGetUniformLocation(_program, "s_textureU");
299 _vSampler = glGetUniformLocation(_program, "s_textureV");
300 if (_position < 0 || _texcoord < 0 || _ySampler < 0 || _uSampler < 0 ||
301 _vSampler < 0) {
302 return NO;
303 }
304 return YES;
305}
306
307- (BOOL)setupTextures {
308 glGenTextures(kNumTextures, _textures);
309 // Set parameters for each of the textures we created.
310 for (GLsizei i = 0; i < kNumTextures; i++) {
311 glActiveTexture(GL_TEXTURE0 + i);
312 glBindTexture(GL_TEXTURE_2D, _textures[i]);
313 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
314 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
315 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
316 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
317 }
318 return YES;
319}
320
321- (BOOL)updateTextureSizesForFrame:(RTCI420Frame*)frame {
322 if (frame.height == _lastDrawnFrame.height &&
323 frame.width == _lastDrawnFrame.width &&
324 frame.chromaWidth == _lastDrawnFrame.chromaWidth &&
325 frame.chromaHeight == _lastDrawnFrame.chromaHeight) {
326 return YES;
327 }
328 GLsizei lumaWidth = frame.width;
329 GLsizei lumaHeight = frame.height;
330 GLsizei chromaWidth = frame.chromaWidth;
331 GLsizei chromaHeight = frame.chromaHeight;
332 for (GLint i = 0; i < kNumTextureSets; i++) {
333 glActiveTexture(GL_TEXTURE0 + i * 3);
334 glTexImage2D(GL_TEXTURE_2D,
335 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000336 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000337 lumaWidth,
338 lumaHeight,
339 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000340 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000341 GL_UNSIGNED_BYTE,
342 0);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000343 glActiveTexture(GL_TEXTURE0 + i * 3 + 1);
344 glTexImage2D(GL_TEXTURE_2D,
345 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000346 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000347 chromaWidth,
348 chromaHeight,
349 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000350 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000351 GL_UNSIGNED_BYTE,
352 0);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000353 glActiveTexture(GL_TEXTURE0 + i * 3 + 2);
354 glTexImage2D(GL_TEXTURE_2D,
355 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000356 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000357 chromaWidth,
358 chromaHeight,
359 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000360 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000361 GL_UNSIGNED_BYTE,
362 0);
363 }
364 return YES;
365}
366
367- (BOOL)updateTextureDataForFrame:(RTCI420Frame*)frame {
368 NSUInteger textureOffset = _currentTextureSet * 3;
369 NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset");
370 NSParameterAssert(frame.yPitch == frame.width);
371 NSParameterAssert(frame.uPitch == frame.chromaWidth);
372 NSParameterAssert(frame.vPitch == frame.chromaWidth);
373
374 glActiveTexture(GL_TEXTURE0 + textureOffset);
375 // When setting texture sampler uniforms, the texture index is used not
376 // the texture handle.
377 glUniform1i(_ySampler, textureOffset);
378 glTexImage2D(GL_TEXTURE_2D,
379 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000380 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000381 frame.width,
382 frame.height,
383 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000384 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000385 GL_UNSIGNED_BYTE,
386 frame.yPlane);
387
388 glActiveTexture(GL_TEXTURE0 + textureOffset + 1);
389 glUniform1i(_uSampler, textureOffset + 1);
390 glTexImage2D(GL_TEXTURE_2D,
391 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000392 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000393 frame.chromaWidth,
394 frame.chromaHeight,
395 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000396 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000397 GL_UNSIGNED_BYTE,
398 frame.uPlane);
399
400 glActiveTexture(GL_TEXTURE0 + textureOffset + 2);
401 glUniform1i(_vSampler, textureOffset + 2);
402 glTexImage2D(GL_TEXTURE_2D,
403 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000404 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000405 frame.chromaWidth,
406 frame.chromaHeight,
407 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000408 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000409 GL_UNSIGNED_BYTE,
410 frame.vPlane);
411
412 _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets;
413 return YES;
414}
415
416- (BOOL)setupVertices {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000417#if !TARGET_OS_IPHONE
418 NSAssert(!_vertexArray, @"vertex array already set up");
419 glGenVertexArrays(1, &_vertexArray);
420 if (!_vertexArray) {
421 return NO;
422 }
423 glBindVertexArray(_vertexArray);
424#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000425 NSAssert(!_vertexBuffer, @"vertex buffer already set up");
426 glGenBuffers(1, &_vertexBuffer);
427 if (!_vertexBuffer) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000428#if !TARGET_OS_IPHONE
429 glDeleteVertexArrays(1, &_vertexArray);
430 _vertexArray = 0;
431#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000432 return NO;
433 }
434 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
435 glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_DYNAMIC_DRAW);
436
437 // Read position attribute from |gVertices| with size of 2 and stride of 4
438 // beginning at the start of the array. The last argument indicates offset
439 // of data within |gVertices| as supplied to the vertex buffer.
440 glVertexAttribPointer(
441 _position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0);
442 glEnableVertexAttribArray(_position);
443
444 // Read texcoord attribute from |gVertices| with size of 2 and stride of 4
445 // beginning at the first texcoord in the array. The last argument indicates
446 // offset of data within |gVertices| as supplied to the vertex buffer.
447 glVertexAttribPointer(_texcoord,
448 2,
449 GL_FLOAT,
450 GL_FALSE,
451 4 * sizeof(GLfloat),
452 (void*)(2 * sizeof(GLfloat)));
453 glEnableVertexAttribArray(_texcoord);
454
455 return YES;
456}
457
458@end