Remove frame copy in RTCOpenGLVideoRenderer.

BUG=1128
R=magjed@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/44039004

Cr-Commit-Position: refs/heads/master@{#9036}
diff --git a/talk/app/webrtc/objc/RTCEAGLVideoView.m b/talk/app/webrtc/objc/RTCEAGLVideoView.m
index 9b089da..61040ba 100644
--- a/talk/app/webrtc/objc/RTCEAGLVideoView.m
+++ b/talk/app/webrtc/objc/RTCEAGLVideoView.m
@@ -122,7 +122,10 @@
 
 - (void)configure {
   EAGLContext* glContext =
-    [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+    [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
+  if (!glContext) {
+    glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+  }
   _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext];
 
   // GLKView manages a framebuffer for us.
diff --git a/talk/app/webrtc/objc/RTCOpenGLVideoRenderer.mm b/talk/app/webrtc/objc/RTCOpenGLVideoRenderer.mm
index 447a0a7..cfead91 100644
--- a/talk/app/webrtc/objc/RTCOpenGLVideoRenderer.mm
+++ b/talk/app/webrtc/objc/RTCOpenGLVideoRenderer.mm
@@ -36,7 +36,7 @@
 #include "webrtc/base/scoped_ptr.h"
 
 #if TARGET_OS_IPHONE
-#import <OpenGLES/ES2/gl.h>
+#import <OpenGLES/ES3/gl.h>
 #else
 #import <OpenGL/gl3.h>
 #endif
@@ -66,17 +66,6 @@
 #define FRAGMENT_SHADER_TEXTURE "texture"
 #endif
 
-void CopyPlane(uint8_t* dst,
-               int dst_stride,
-               const uint8_t* src,
-               int src_stride,
-               int width,
-               int height) {
-  for (int y = 0; y < height; ++y) {
-    memcpy(dst + y * dst_stride, src + y * src_stride, width);
-  }
-}
-
 // Vertex shader doesn't do anything except pass coordinates through.
 static const char kVertexShaderSource[] =
   SHADER_VERSION
@@ -193,6 +182,9 @@
   GLint _ySampler;
   GLint _uSampler;
   GLint _vSampler;
+  // Used to create a non-padded plane for GPU upload when we receive padded
+  // frames.
+  rtc::scoped_ptr<uint8_t[]> _planeBuffer;
 }
 
 + (void)initialize {
@@ -376,78 +368,91 @@
                  GL_UNSIGNED_BYTE,
                  0);
   }
+  if (frame.yPitch != frame.width || frame.uPitch != frame.chromaWidth ||
+      frame.vPitch != frame.chromaWidth) {
+    _planeBuffer.reset(new uint8_t[frame.width * frame.height]);
+  } else {
+    _planeBuffer.reset();
+  }
   return YES;
 }
 
+- (void)uploadPlane:(const uint8_t*)plane
+            sampler:(GLint)sampler
+             offset:(NSUInteger)offset
+              width:(NSUInteger)width
+             height:(NSUInteger)height
+             stride:(NSInteger)stride {
+  glActiveTexture(GL_TEXTURE0 + offset);
+  // When setting texture sampler uniforms, the texture index is used not
+  // the texture handle.
+  glUniform1i(sampler, offset);
+#if TARGET_OS_IPHONE
+  BOOL hasUnpackRowLength = _context.API == kEAGLRenderingAPIOpenGLES3;
+#else
+  BOOL hasUnpackRowLength = YES;
+#endif
+  const uint8_t* uploadPlane = plane;
+  if (stride != width) {
+   if (hasUnpackRowLength) {
+      // GLES3 allows us to specify stride.
+      glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
+      glTexImage2D(GL_TEXTURE_2D,
+                   0,
+                   RTC_PIXEL_FORMAT,
+                   width,
+                   height,
+                   0,
+                   RTC_PIXEL_FORMAT,
+                   GL_UNSIGNED_BYTE,
+                   uploadPlane);
+      glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+      return;
+    } else {
+      // Make an unpadded copy and upload that instead. Quick profiling showed
+      // that this is faster than uploading row by row using glTexSubImage2D.
+      uint8_t* unpaddedPlane = _planeBuffer.get();
+      for (NSUInteger y = 0; y < height; ++y) {
+        memcpy(unpaddedPlane + y * width, plane + y * stride, width);
+      }
+      uploadPlane = unpaddedPlane;
+    }
+  }
+  glTexImage2D(GL_TEXTURE_2D,
+               0,
+               RTC_PIXEL_FORMAT,
+               width,
+               height,
+               0,
+               RTC_PIXEL_FORMAT,
+               GL_UNSIGNED_BYTE,
+               uploadPlane);
+}
+
 - (BOOL)updateTextureDataForFrame:(RTCI420Frame*)frame {
   NSUInteger textureOffset = _currentTextureSet * 3;
   NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset");
 
-  // TODO(magjed): Remove this frame copy, BUG=1128.
-  rtc::scoped_ptr<uint8_t[]> tmp;
-  if (frame.yPitch != frame.width || frame.uPitch != frame.chromaWidth ||
-      frame.vPitch != frame.chromaWidth) {
-    tmp.reset(new uint8_t[frame.width * frame.height]);
-  }
+  [self uploadPlane:frame.yPlane
+            sampler:_ySampler
+             offset:textureOffset
+              width:frame.width
+             height:frame.height
+             stride:frame.yPitch];
 
-  const uint8_t* yPlane = frame.yPlane;
-  if (frame.yPitch != frame.width) {
-    yPlane = tmp.get();
-    CopyPlane(tmp.get(), frame.width, frame.yPlane, frame.yPitch, frame.width,
-              frame.height);
-  }
+  [self uploadPlane:frame.uPlane
+            sampler:_uSampler
+             offset:textureOffset + 1
+              width:frame.chromaWidth
+             height:frame.chromaHeight
+             stride:frame.uPitch];
 
-  glActiveTexture(GL_TEXTURE0 + textureOffset);
-  // When setting texture sampler uniforms, the texture index is used not
-  // the texture handle.
-  glUniform1i(_ySampler, textureOffset);
-  glTexImage2D(GL_TEXTURE_2D,
-               0,
-               RTC_PIXEL_FORMAT,
-               frame.width,
-               frame.height,
-               0,
-               RTC_PIXEL_FORMAT,
-               GL_UNSIGNED_BYTE,
-               yPlane);
-
-  const uint8_t* uPlane = frame.uPlane;
-  if (frame.uPitch != frame.chromaWidth) {
-    uPlane = tmp.get();
-    CopyPlane(tmp.get(), frame.chromaWidth, frame.uPlane, frame.uPitch,
-              frame.chromaWidth, frame.chromaHeight);
-  }
-
-  glActiveTexture(GL_TEXTURE0 + textureOffset + 1);
-  glUniform1i(_uSampler, textureOffset + 1);
-  glTexImage2D(GL_TEXTURE_2D,
-               0,
-               RTC_PIXEL_FORMAT,
-               frame.chromaWidth,
-               frame.chromaHeight,
-               0,
-               RTC_PIXEL_FORMAT,
-               GL_UNSIGNED_BYTE,
-               uPlane);
-
-  const uint8_t* vPlane = frame.vPlane;
-  if (frame.vPitch != frame.chromaWidth) {
-    vPlane = tmp.get();
-    CopyPlane(tmp.get(), frame.chromaWidth, frame.vPlane, frame.vPitch,
-              frame.chromaWidth, frame.chromaHeight);
-  }
-
-  glActiveTexture(GL_TEXTURE0 + textureOffset + 2);
-  glUniform1i(_vSampler, textureOffset + 2);
-  glTexImage2D(GL_TEXTURE_2D,
-               0,
-               RTC_PIXEL_FORMAT,
-               frame.chromaWidth,
-               frame.chromaHeight,
-               0,
-               RTC_PIXEL_FORMAT,
-               GL_UNSIGNED_BYTE,
-               vPlane);
+  [self uploadPlane:frame.vPlane
+            sampler:_vSampler
+             offset:textureOffset + 2
+              width:frame.chromaWidth
+             height:frame.chromaHeight
+             stride:frame.vPitch];
 
   _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets;
   return YES;