blob: 9ee0216cb858e7ce51ab94837e773e1a3e3f0ac7 [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];
208 if (![self updateTextureSizesForFrame:frame] ||
209 ![self updateTextureDataForFrame:frame]) {
210 return NO;
211 }
212 glClear(GL_COLOR_BUFFER_BIT);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000213#if !TARGET_OS_IPHONE
214 glBindVertexArray(_vertexArray);
215#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000216 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
217 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000218#if !TARGET_OS_IPHONE
219 [_context flushBuffer];
220#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000221 _lastDrawnFrame = frame;
222 return YES;
223}
224
225- (void)setupGL {
226 if (_isInitialized) {
227 return;
228 }
229 [self ensureGLContext];
230 if (![self setupProgram]) {
231 return;
232 }
233 if (![self setupTextures]) {
234 return;
235 }
236 if (![self setupVertices]) {
237 return;
238 }
239 glUseProgram(_program);
240 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
241 glClearColor(0, 0, 0, 1);
242 _isInitialized = YES;
243}
244
245- (void)teardownGL {
246 if (!_isInitialized) {
247 return;
248 }
249 [self ensureGLContext];
250 glDeleteProgram(_program);
251 _program = 0;
252 glDeleteTextures(kNumTextures, _textures);
253 glDeleteBuffers(1, &_vertexBuffer);
254 _vertexBuffer = 0;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000255#if !TARGET_OS_IPHONE
256 glDeleteVertexArrays(1, &_vertexArray);
257#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000258 _isInitialized = NO;
259}
260
261#pragma mark - Private
262
263- (void)ensureGLContext {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000264 NSAssert(_context, @"context shouldn't be nil");
265#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000266 if ([EAGLContext currentContext] != _context) {
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000267 [EAGLContext setCurrentContext:_context];
268 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000269#else
270 if ([NSOpenGLContext currentContext] != _context) {
271 [_context makeCurrentContext];
272 }
273#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000274}
275
276- (BOOL)setupProgram {
277 NSAssert(!_program, @"program already set up");
278 GLuint vertexShader = CreateShader(GL_VERTEX_SHADER, kVertexShaderSource);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000279 NSAssert(vertexShader, @"failed to create vertex shader");
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000280 GLuint fragmentShader =
281 CreateShader(GL_FRAGMENT_SHADER, kFragmentShaderSource);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000282 NSAssert(fragmentShader, @"failed to create fragment shader");
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000283 _program = CreateProgram(vertexShader, fragmentShader);
284 // Shaders are created only to generate program.
285 if (vertexShader) {
286 glDeleteShader(vertexShader);
287 }
288 if (fragmentShader) {
289 glDeleteShader(fragmentShader);
290 }
291 if (!_program) {
292 return NO;
293 }
294 _position = glGetAttribLocation(_program, "position");
295 _texcoord = glGetAttribLocation(_program, "texcoord");
296 _ySampler = glGetUniformLocation(_program, "s_textureY");
297 _uSampler = glGetUniformLocation(_program, "s_textureU");
298 _vSampler = glGetUniformLocation(_program, "s_textureV");
299 if (_position < 0 || _texcoord < 0 || _ySampler < 0 || _uSampler < 0 ||
300 _vSampler < 0) {
301 return NO;
302 }
303 return YES;
304}
305
306- (BOOL)setupTextures {
307 glGenTextures(kNumTextures, _textures);
308 // Set parameters for each of the textures we created.
309 for (GLsizei i = 0; i < kNumTextures; i++) {
310 glActiveTexture(GL_TEXTURE0 + i);
311 glBindTexture(GL_TEXTURE_2D, _textures[i]);
312 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
313 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
314 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
315 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
316 }
317 return YES;
318}
319
320- (BOOL)updateTextureSizesForFrame:(RTCI420Frame*)frame {
321 if (frame.height == _lastDrawnFrame.height &&
322 frame.width == _lastDrawnFrame.width &&
323 frame.chromaWidth == _lastDrawnFrame.chromaWidth &&
324 frame.chromaHeight == _lastDrawnFrame.chromaHeight) {
325 return YES;
326 }
327 GLsizei lumaWidth = frame.width;
328 GLsizei lumaHeight = frame.height;
329 GLsizei chromaWidth = frame.chromaWidth;
330 GLsizei chromaHeight = frame.chromaHeight;
331 for (GLint i = 0; i < kNumTextureSets; i++) {
332 glActiveTexture(GL_TEXTURE0 + i * 3);
333 glTexImage2D(GL_TEXTURE_2D,
334 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000335 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000336 lumaWidth,
337 lumaHeight,
338 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000339 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000340 GL_UNSIGNED_BYTE,
341 0);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000342 glActiveTexture(GL_TEXTURE0 + i * 3 + 1);
343 glTexImage2D(GL_TEXTURE_2D,
344 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000345 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000346 chromaWidth,
347 chromaHeight,
348 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000349 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000350 GL_UNSIGNED_BYTE,
351 0);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000352 glActiveTexture(GL_TEXTURE0 + i * 3 + 2);
353 glTexImage2D(GL_TEXTURE_2D,
354 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000355 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000356 chromaWidth,
357 chromaHeight,
358 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000359 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000360 GL_UNSIGNED_BYTE,
361 0);
362 }
363 return YES;
364}
365
366- (BOOL)updateTextureDataForFrame:(RTCI420Frame*)frame {
367 NSUInteger textureOffset = _currentTextureSet * 3;
368 NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset");
369 NSParameterAssert(frame.yPitch == frame.width);
370 NSParameterAssert(frame.uPitch == frame.chromaWidth);
371 NSParameterAssert(frame.vPitch == frame.chromaWidth);
372
373 glActiveTexture(GL_TEXTURE0 + textureOffset);
374 // When setting texture sampler uniforms, the texture index is used not
375 // the texture handle.
376 glUniform1i(_ySampler, textureOffset);
377 glTexImage2D(GL_TEXTURE_2D,
378 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000379 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000380 frame.width,
381 frame.height,
382 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000383 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000384 GL_UNSIGNED_BYTE,
385 frame.yPlane);
386
387 glActiveTexture(GL_TEXTURE0 + textureOffset + 1);
388 glUniform1i(_uSampler, textureOffset + 1);
389 glTexImage2D(GL_TEXTURE_2D,
390 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000391 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000392 frame.chromaWidth,
393 frame.chromaHeight,
394 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000395 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000396 GL_UNSIGNED_BYTE,
397 frame.uPlane);
398
399 glActiveTexture(GL_TEXTURE0 + textureOffset + 2);
400 glUniform1i(_vSampler, textureOffset + 2);
401 glTexImage2D(GL_TEXTURE_2D,
402 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000403 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000404 frame.chromaWidth,
405 frame.chromaHeight,
406 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000407 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000408 GL_UNSIGNED_BYTE,
409 frame.vPlane);
410
411 _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets;
412 return YES;
413}
414
415- (BOOL)setupVertices {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000416#if !TARGET_OS_IPHONE
417 NSAssert(!_vertexArray, @"vertex array already set up");
418 glGenVertexArrays(1, &_vertexArray);
419 if (!_vertexArray) {
420 return NO;
421 }
422 glBindVertexArray(_vertexArray);
423#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000424 NSAssert(!_vertexBuffer, @"vertex buffer already set up");
425 glGenBuffers(1, &_vertexBuffer);
426 if (!_vertexBuffer) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000427#if !TARGET_OS_IPHONE
428 glDeleteVertexArrays(1, &_vertexArray);
429 _vertexArray = 0;
430#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000431 return NO;
432 }
433 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
434 glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_DYNAMIC_DRAW);
435
436 // Read position attribute from |gVertices| with size of 2 and stride of 4
437 // beginning at the start of the array. The last argument indicates offset
438 // of data within |gVertices| as supplied to the vertex buffer.
439 glVertexAttribPointer(
440 _position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0);
441 glEnableVertexAttribArray(_position);
442
443 // Read texcoord attribute from |gVertices| with size of 2 and stride of 4
444 // beginning at the first texcoord in the array. The last argument indicates
445 // offset of data within |gVertices| as supplied to the vertex buffer.
446 glVertexAttribPointer(_texcoord,
447 2,
448 GL_FLOAT,
449 GL_FALSE,
450 4 * sizeof(GLfloat),
451 (void*)(2 * sizeof(GLfloat)));
452 glEnableVertexAttribArray(_texcoord);
453
454 return YES;
455}
456
457@end