Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Side by Side Diff: webrtc/sdk/objc/Framework/Classes/RTCOpenGLVideoRenderer.mm

Issue 2202823004: iOS: Add support for rendering native CVPixelBuffers directly (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Move guard. Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. 2 * Copyright 2015 The WebRTC project authors. All Rights Reserved.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license 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 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 6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may 7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree. 8 * be found in the AUTHORS file in the root of the source tree.
9 */ 9 */
10 10
11 #import "RTCOpenGLVideoRenderer.h" 11 #import "RTCOpenGLVideoRenderer.h"
12 12
13 #if TARGET_OS_IPHONE 13 #import "RTCShader+Private.h"
14 #import <OpenGLES/ES3/gl.h>
15 #else
16 #import <OpenGL/gl3.h>
17 #endif
18 #include <string.h>
19 #include <memory>
20
21 #import "WebRTC/RTCVideoFrame.h" 14 #import "WebRTC/RTCVideoFrame.h"
22 15
23
24 // TODO(tkchin): check and log openGL errors. Methods here return BOOLs in
25 // anticipation of that happening in the future.
26
27 #if TARGET_OS_IPHONE
28 #define RTC_PIXEL_FORMAT GL_LUMINANCE
29 #define SHADER_VERSION
30 #define VERTEX_SHADER_IN "attribute"
31 #define VERTEX_SHADER_OUT "varying"
32 #define FRAGMENT_SHADER_IN "varying"
33 #define FRAGMENT_SHADER_OUT
34 #define FRAGMENT_SHADER_COLOR "gl_FragColor"
35 #define FRAGMENT_SHADER_TEXTURE "texture2D"
36 #else
37 #define RTC_PIXEL_FORMAT GL_RED
38 #define SHADER_VERSION "#version 150\n"
39 #define VERTEX_SHADER_IN "in"
40 #define VERTEX_SHADER_OUT "out"
41 #define FRAGMENT_SHADER_IN "in"
42 #define FRAGMENT_SHADER_OUT "out vec4 fragColor;\n"
43 #define FRAGMENT_SHADER_COLOR "fragColor"
44 #define FRAGMENT_SHADER_TEXTURE "texture"
45 #endif
46
47 // Vertex shader doesn't do anything except pass coordinates through.
48 static const char kVertexShaderSource[] =
49 SHADER_VERSION
50 VERTEX_SHADER_IN " vec2 position;\n"
51 VERTEX_SHADER_IN " vec2 texcoord;\n"
52 VERTEX_SHADER_OUT " vec2 v_texcoord;\n"
53 "void main() {\n"
54 " gl_Position = vec4(position.x, position.y, 0.0, 1.0);\n"
55 " v_texcoord = texcoord;\n"
56 "}\n";
57
58 // Fragment shader converts YUV values from input textures into a final RGB
59 // pixel. The conversion formula is from http://www.fourcc.org/fccyvrgb.php.
60 static const char kFragmentShaderSource[] =
61 SHADER_VERSION
62 "precision highp float;"
63 FRAGMENT_SHADER_IN " vec2 v_texcoord;\n"
64 "uniform lowp sampler2D s_textureY;\n"
65 "uniform lowp sampler2D s_textureU;\n"
66 "uniform lowp sampler2D s_textureV;\n"
67 FRAGMENT_SHADER_OUT
68 "void main() {\n"
69 " float y, u, v, r, g, b;\n"
70 " y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n"
71 " u = " FRAGMENT_SHADER_TEXTURE "(s_textureU, v_texcoord).r;\n"
72 " v = " FRAGMENT_SHADER_TEXTURE "(s_textureV, v_texcoord).r;\n"
73 " u = u - 0.5;\n"
74 " v = v - 0.5;\n"
75 " r = y + 1.403 * v;\n"
76 " g = y - 0.344 * u - 0.714 * v;\n"
77 " b = y + 1.770 * u;\n"
78 " " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n"
79 " }\n";
80
81 // Compiles a shader of the given |type| with GLSL source |source| and returns
82 // the shader handle or 0 on error.
83 GLuint CreateShader(GLenum type, const GLchar *source) {
84 GLuint shader = glCreateShader(type);
85 if (!shader) {
86 return 0;
87 }
88 glShaderSource(shader, 1, &source, NULL);
89 glCompileShader(shader);
90 GLint compileStatus = GL_FALSE;
91 glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
92 if (compileStatus == GL_FALSE) {
93 glDeleteShader(shader);
94 shader = 0;
95 }
96 return shader;
97 }
98
99 // Links a shader program with the given vertex and fragment shaders and
100 // returns the program handle or 0 on error.
101 GLuint CreateProgram(GLuint vertexShader, GLuint fragmentShader) {
102 if (vertexShader == 0 || fragmentShader == 0) {
103 return 0;
104 }
105 GLuint program = glCreateProgram();
106 if (!program) {
107 return 0;
108 }
109 glAttachShader(program, vertexShader);
110 glAttachShader(program, fragmentShader);
111 glLinkProgram(program);
112 GLint linkStatus = GL_FALSE;
113 glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
114 if (linkStatus == GL_FALSE) {
115 glDeleteProgram(program);
116 program = 0;
117 }
118 return program;
119 }
120
121 // When modelview and projection matrices are identity (default) the world is
122 // contained in the square around origin with unit size 2. Drawing to these
123 // coordinates is equivalent to drawing to the entire screen. The texture is
124 // stretched over that square using texture coordinates (u, v) that range
125 // from (0, 0) to (1, 1) inclusive. Texture coordinates are flipped vertically
126 // here because the incoming frame has origin in upper left hand corner but
127 // OpenGL expects origin in bottom left corner.
128 const GLfloat gVertices[] = {
129 // X, Y, U, V.
130 -1, -1, 0, 1, // Bottom left.
131 1, -1, 1, 1, // Bottom right.
132 1, 1, 1, 0, // Top right.
133 -1, 1, 0, 0, // Top left.
134 };
135
136 // |kNumTextures| must not exceed 8, which is the limit in OpenGLES2. Two sets
137 // of 3 textures are used here, one for each of the Y, U and V planes. Having
138 // two sets alleviates CPU blockage in the event that the GPU is asked to render
139 // to a texture that is already in use.
140 static const GLsizei kNumTextureSets = 2;
141 static const GLsizei kNumTextures = 3 * kNumTextureSets;
142
143 @implementation RTCOpenGLVideoRenderer { 16 @implementation RTCOpenGLVideoRenderer {
144 #if TARGET_OS_IPHONE 17 GlContextType *_context;
145 EAGLContext *_context;
146 #else
147 NSOpenGLContext *_context;
148 #endif
149 BOOL _isInitialized; 18 BOOL _isInitialized;
150 GLint _currentTextureSet; 19 id<RTCShader> _i420Shader;
151 // Handles for OpenGL constructs. 20 id<RTCShader> _nv12Shader;
152 GLuint _textures[kNumTextures];
153 GLuint _program;
154 #if !TARGET_OS_IPHONE
155 GLuint _vertexArray;
156 #endif
157 GLuint _vertexBuffer;
158 GLint _position;
159 GLint _texcoord;
160 GLint _ySampler;
161 GLint _uSampler;
162 GLint _vSampler;
163 // Used to create a non-padded plane for GPU upload when we receive padded
164 // frames.
165 std::unique_ptr<uint8_t[]> _planeBuffer;
166 } 21 }
167 22
168 @synthesize lastDrawnFrame = _lastDrawnFrame; 23 @synthesize lastDrawnFrame = _lastDrawnFrame;
169 24
170 + (void)initialize { 25 + (void)initialize {
171 // Disable dithering for performance. 26 // Disable dithering for performance.
172 glDisable(GL_DITHER); 27 glDisable(GL_DITHER);
173 } 28 }
174 29
175 #if TARGET_OS_IPHONE 30 - (instancetype)initWithContext:(GlContextType *)context {
176 - (instancetype)initWithContext:(EAGLContext *)context {
177 #else
178 - (instancetype)initWithContext:(NSOpenGLContext *)context {
179 #endif
180 NSAssert(context != nil, @"context cannot be nil"); 31 NSAssert(context != nil, @"context cannot be nil");
181 if (self = [super init]) { 32 if (self = [super init]) {
182 _context = context; 33 _context = context;
183 } 34 }
184 return self; 35 return self;
185 } 36 }
186 37
187 - (BOOL)drawFrame:(RTCVideoFrame *)frame { 38 - (BOOL)drawFrame:(RTCVideoFrame *)frame {
188 if (!_isInitialized) { 39 if (!_isInitialized || !frame || frame == _lastDrawnFrame) {
189 return NO;
190 }
191 if (_lastDrawnFrame == frame) {
192 return NO; 40 return NO;
193 } 41 }
194 [self ensureGLContext]; 42 [self ensureGLContext];
195 glClear(GL_COLOR_BUFFER_BIT); 43 glClear(GL_COLOR_BUFFER_BIT);
196 if (frame) { 44 id<RTCShader> shader = nil;
197 if (![self updateTextureSizesForFrame:frame] || 45 #if TARGET_OS_IPHONE
198 ![self updateTextureDataForFrame:frame]) { 46 if (frame.nativeHandle) {
199 return NO; 47 if (!_nv12Shader) {
48 _nv12Shader = [[RTCNativeNV12Shader alloc] initWithContext:_context];
200 } 49 }
201 #if !TARGET_OS_IPHONE 50 shader = _nv12Shader;
202 glBindVertexArray(_vertexArray); 51 #else
52 // Rendering native CVPixelBuffer is not supported on OS X.
53 if (false) {
203 #endif 54 #endif
204 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); 55 } else {
205 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 56 if (!_i420Shader) {
57 _i420Shader = [[RTCI420Shader alloc] initWithContext:_context];
58 }
59 shader = _i420Shader;
206 } 60 }
61 if (!shader || ![shader drawFrame:frame]) {
62 return NO;
63 }
64
207 #if !TARGET_OS_IPHONE 65 #if !TARGET_OS_IPHONE
208 [_context flushBuffer]; 66 [_context flushBuffer];
209 #endif 67 #endif
210 _lastDrawnFrame = frame; 68 _lastDrawnFrame = frame;
69
211 return YES; 70 return YES;
212 } 71 }
213 72
214 - (void)setupGL { 73 - (void)setupGL {
215 if (_isInitialized) { 74 if (_isInitialized) {
216 return; 75 return;
217 } 76 }
218 [self ensureGLContext]; 77 [self ensureGLContext];
219 if (![self setupProgram]) {
220 return;
221 }
222 if (![self setupTextures]) {
223 return;
224 }
225 if (![self setupVertices]) {
226 return;
227 }
228 glUseProgram(_program);
229 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
230 _isInitialized = YES; 78 _isInitialized = YES;
231 } 79 }
232 80
233 - (void)teardownGL { 81 - (void)teardownGL {
234 if (!_isInitialized) { 82 if (!_isInitialized) {
235 return; 83 return;
236 } 84 }
237 [self ensureGLContext]; 85 [self ensureGLContext];
238 glDeleteProgram(_program); 86 _i420Shader = nil;
239 _program = 0; 87 _nv12Shader = nil;
240 glDeleteTextures(kNumTextures, _textures);
241 glDeleteBuffers(1, &_vertexBuffer);
242 _vertexBuffer = 0;
243 #if !TARGET_OS_IPHONE
244 glDeleteVertexArrays(1, &_vertexArray);
245 #endif
246 _isInitialized = NO; 88 _isInitialized = NO;
247 } 89 }
248 90
249 #pragma mark - Private 91 #pragma mark - Private
250 92
251 - (void)ensureGLContext { 93 - (void)ensureGLContext {
252 NSAssert(_context, @"context shouldn't be nil"); 94 NSAssert(_context, @"context shouldn't be nil");
253 #if TARGET_OS_IPHONE 95 #if TARGET_OS_IPHONE
254 if ([EAGLContext currentContext] != _context) { 96 if ([EAGLContext currentContext] != _context) {
255 [EAGLContext setCurrentContext:_context]; 97 [EAGLContext setCurrentContext:_context];
256 } 98 }
257 #else 99 #else
258 if ([NSOpenGLContext currentContext] != _context) { 100 if ([NSOpenGLContext currentContext] != _context) {
259 [_context makeCurrentContext]; 101 [_context makeCurrentContext];
260 } 102 }
261 #endif 103 #endif
262 } 104 }
263 105
264 - (BOOL)setupProgram {
265 NSAssert(!_program, @"program already set up");
266 GLuint vertexShader = CreateShader(GL_VERTEX_SHADER, kVertexShaderSource);
267 NSAssert(vertexShader, @"failed to create vertex shader");
268 GLuint fragmentShader =
269 CreateShader(GL_FRAGMENT_SHADER, kFragmentShaderSource);
270 NSAssert(fragmentShader, @"failed to create fragment shader");
271 _program = CreateProgram(vertexShader, fragmentShader);
272 // Shaders are created only to generate program.
273 if (vertexShader) {
274 glDeleteShader(vertexShader);
275 }
276 if (fragmentShader) {
277 glDeleteShader(fragmentShader);
278 }
279 if (!_program) {
280 return NO;
281 }
282 _position = glGetAttribLocation(_program, "position");
283 _texcoord = glGetAttribLocation(_program, "texcoord");
284 _ySampler = glGetUniformLocation(_program, "s_textureY");
285 _uSampler = glGetUniformLocation(_program, "s_textureU");
286 _vSampler = glGetUniformLocation(_program, "s_textureV");
287 if (_position < 0 || _texcoord < 0 || _ySampler < 0 || _uSampler < 0 ||
288 _vSampler < 0) {
289 return NO;
290 }
291 return YES;
292 }
293
294 - (BOOL)setupTextures {
295 glGenTextures(kNumTextures, _textures);
296 // Set parameters for each of the textures we created.
297 for (GLsizei i = 0; i < kNumTextures; i++) {
298 glActiveTexture(GL_TEXTURE0 + i);
299 glBindTexture(GL_TEXTURE_2D, _textures[i]);
300 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
301 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
302 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
303 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
304 }
305 return YES;
306 }
307
308 - (BOOL)updateTextureSizesForFrame:(RTCVideoFrame *)frame {
309 if (frame.height == _lastDrawnFrame.height &&
310 frame.width == _lastDrawnFrame.width &&
311 frame.chromaWidth == _lastDrawnFrame.chromaWidth &&
312 frame.chromaHeight == _lastDrawnFrame.chromaHeight) {
313 return YES;
314 }
315 GLsizei lumaWidth = static_cast<GLsizei>(frame.width);
316 GLsizei lumaHeight = static_cast<GLsizei>(frame.height);
317 GLsizei chromaWidth = static_cast<GLsizei>(frame.chromaWidth);
318 GLsizei chromaHeight = static_cast<GLsizei>(frame.chromaHeight);
319 for (GLint i = 0; i < kNumTextureSets; i++) {
320 glActiveTexture(GL_TEXTURE0 + i * 3);
321 glTexImage2D(GL_TEXTURE_2D,
322 0,
323 RTC_PIXEL_FORMAT,
324 lumaWidth,
325 lumaHeight,
326 0,
327 RTC_PIXEL_FORMAT,
328 GL_UNSIGNED_BYTE,
329 0);
330 glActiveTexture(GL_TEXTURE0 + i * 3 + 1);
331 glTexImage2D(GL_TEXTURE_2D,
332 0,
333 RTC_PIXEL_FORMAT,
334 chromaWidth,
335 chromaHeight,
336 0,
337 RTC_PIXEL_FORMAT,
338 GL_UNSIGNED_BYTE,
339 0);
340 glActiveTexture(GL_TEXTURE0 + i * 3 + 2);
341 glTexImage2D(GL_TEXTURE_2D,
342 0,
343 RTC_PIXEL_FORMAT,
344 chromaWidth,
345 chromaHeight,
346 0,
347 RTC_PIXEL_FORMAT,
348 GL_UNSIGNED_BYTE,
349 0);
350 }
351 if ((NSUInteger)frame.yPitch != frame.width ||
352 (NSUInteger)frame.uPitch != frame.chromaWidth ||
353 (NSUInteger)frame.vPitch != frame.chromaWidth) {
354 _planeBuffer.reset(new uint8_t[frame.width * frame.height]);
355 } else {
356 _planeBuffer.reset();
357 }
358 return YES;
359 }
360
361 - (void)uploadPlane:(const uint8_t *)plane
362 sampler:(GLint)sampler
363 offset:(GLint)offset
364 width:(size_t)width
365 height:(size_t)height
366 stride:(int32_t)stride {
367 glActiveTexture(static_cast<GLenum>(GL_TEXTURE0 + offset));
368 // When setting texture sampler uniforms, the texture index is used not
369 // the texture handle.
370 glUniform1i(sampler, offset);
371 #if TARGET_OS_IPHONE
372 BOOL hasUnpackRowLength = _context.API == kEAGLRenderingAPIOpenGLES3;
373 #else
374 BOOL hasUnpackRowLength = YES;
375 #endif
376 const uint8_t *uploadPlane = plane;
377 if ((size_t)stride != width) {
378 if (hasUnpackRowLength) {
379 // GLES3 allows us to specify stride.
380 glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
381 glTexImage2D(GL_TEXTURE_2D,
382 0,
383 RTC_PIXEL_FORMAT,
384 static_cast<GLsizei>(width),
385 static_cast<GLsizei>(height),
386 0,
387 RTC_PIXEL_FORMAT,
388 GL_UNSIGNED_BYTE,
389 uploadPlane);
390 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
391 return;
392 } else {
393 // Make an unpadded copy and upload that instead. Quick profiling showed
394 // that this is faster than uploading row by row using glTexSubImage2D.
395 uint8_t *unpaddedPlane = _planeBuffer.get();
396 for (size_t y = 0; y < height; ++y) {
397 memcpy(unpaddedPlane + y * width, plane + y * stride, width);
398 }
399 uploadPlane = unpaddedPlane;
400 }
401 }
402 glTexImage2D(GL_TEXTURE_2D,
403 0,
404 RTC_PIXEL_FORMAT,
405 static_cast<GLsizei>(width),
406 static_cast<GLsizei>(height),
407 0,
408 RTC_PIXEL_FORMAT,
409 GL_UNSIGNED_BYTE,
410 uploadPlane);
411 }
412
413 - (BOOL)updateTextureDataForFrame:(RTCVideoFrame *)frame {
414 GLint textureOffset = _currentTextureSet * 3;
415 NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset");
416
417 [self uploadPlane:frame.yPlane
418 sampler:_ySampler
419 offset:textureOffset
420 width:frame.width
421 height:frame.height
422 stride:frame.yPitch];
423
424 [self uploadPlane:frame.uPlane
425 sampler:_uSampler
426 offset:textureOffset + 1
427 width:frame.chromaWidth
428 height:frame.chromaHeight
429 stride:frame.uPitch];
430
431 [self uploadPlane:frame.vPlane
432 sampler:_vSampler
433 offset:textureOffset + 2
434 width:frame.chromaWidth
435 height:frame.chromaHeight
436 stride:frame.vPitch];
437
438 _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets;
439 return YES;
440 }
441
442 - (BOOL)setupVertices {
443 #if !TARGET_OS_IPHONE
444 NSAssert(!_vertexArray, @"vertex array already set up");
445 glGenVertexArrays(1, &_vertexArray);
446 if (!_vertexArray) {
447 return NO;
448 }
449 glBindVertexArray(_vertexArray);
450 #endif
451 NSAssert(!_vertexBuffer, @"vertex buffer already set up");
452 glGenBuffers(1, &_vertexBuffer);
453 if (!_vertexBuffer) {
454 #if !TARGET_OS_IPHONE
455 glDeleteVertexArrays(1, &_vertexArray);
456 _vertexArray = 0;
457 #endif
458 return NO;
459 }
460 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
461 glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_DYNAMIC_DRAW);
462
463 // Read position attribute from |gVertices| with size of 2 and stride of 4
464 // beginning at the start of the array. The last argument indicates offset
465 // of data within |gVertices| as supplied to the vertex buffer.
466 glVertexAttribPointer(
467 _position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void *)0);
468 glEnableVertexAttribArray(_position);
469
470 // Read texcoord attribute from |gVertices| with size of 2 and stride of 4
471 // beginning at the first texcoord in the array. The last argument indicates
472 // offset of data within |gVertices| as supplied to the vertex buffer.
473 glVertexAttribPointer(_texcoord,
474 2,
475 GL_FLOAT,
476 GL_FALSE,
477 4 * sizeof(GLfloat),
478 (void *)(2 * sizeof(GLfloat)));
479 glEnableVertexAttribArray(_texcoord);
480
481 return YES;
482 }
483
484 @end 106 @end
OLDNEW
« no previous file with comments | « webrtc/sdk/objc/Framework/Classes/RTCOpenGLDefines.h ('k') | webrtc/sdk/objc/Framework/Classes/RTCShader.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698