Adding a Metal RGB renderer.

The new RTCMTLRGBRenderer dynamically handles both the kCVPixelFormatType_32BGRA
and the kCVPixelFormatType_32ARGB pixel formats.

Change-Id: I935532f762eff74c4b84fea9b855191f4c321fb7
Bug: webrtc:9200
Reviewed-on: https://webrtc-review.googlesource.com/72482
Reviewed-by: Kári Helgason <kthelgason@webrtc.org>
Reviewed-by: Anders Carlsson <andersc@webrtc.org>
Commit-Queue: Peter Hanspers <peterhanspers@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23100}
diff --git a/sdk/objc/Framework/Classes/Metal/RTCMTLI420Renderer.mm b/sdk/objc/Framework/Classes/Metal/RTCMTLI420Renderer.mm
index 027db05..ae55258 100644
--- a/sdk/objc/Framework/Classes/Metal/RTCMTLI420Renderer.mm
+++ b/sdk/objc/Framework/Classes/Metal/RTCMTLI420Renderer.mm
@@ -19,10 +19,10 @@
 
 #import "RTCMTLRenderer+Private.h"
 
-#define MTL_STRINGIFY(s) @ #s
-
 static NSString *const shaderSource = MTL_STRINGIFY(
-    using namespace metal; typedef struct {
+    using namespace metal;
+
+    typedef struct {
       packed_float2 position;
       packed_float2 texcoord;
     } Vertex;
diff --git a/sdk/objc/Framework/Classes/Metal/RTCMTLNV12Renderer.mm b/sdk/objc/Framework/Classes/Metal/RTCMTLNV12Renderer.mm
index 6de9c34..d8dd7e7 100644
--- a/sdk/objc/Framework/Classes/Metal/RTCMTLNV12Renderer.mm
+++ b/sdk/objc/Framework/Classes/Metal/RTCMTLNV12Renderer.mm
@@ -20,10 +20,10 @@
 #import "RTCMTLRenderer+Private.h"
 #include "rtc_base/checks.h"
 
-#define MTL_STRINGIFY(s) @ #s
-
 static NSString *const shaderSource = MTL_STRINGIFY(
-    using namespace metal; typedef struct {
+    using namespace metal;
+
+    typedef struct {
       packed_float2 position;
       packed_float2 texcoord;
     } Vertex;
@@ -67,18 +67,20 @@
 
 - (BOOL)addRenderingDestination:(__kindof MTKView *)view {
   if ([super addRenderingDestination:view]) {
-    [self initializeTextureCache];
-    return YES;
+    return [self initializeTextureCache];
   }
   return NO;
 }
 
-- (void)initializeTextureCache {
+- (BOOL)initializeTextureCache {
   CVReturn status = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, [self currentMetalDevice],
                                               nil, &_textureCache);
   if (status != kCVReturnSuccess) {
     RTCLogError(@"Metal: Failed to initialize metal texture cache. Return status is %d", status);
+    return NO;
   }
+
+  return YES;
 }
 
 - (NSString *)shaderSource {
diff --git a/sdk/objc/Framework/Classes/Metal/RTCMTLRGBRenderer.h b/sdk/objc/Framework/Classes/Metal/RTCMTLRGBRenderer.h
new file mode 100644
index 0000000..de27055
--- /dev/null
+++ b/sdk/objc/Framework/Classes/Metal/RTCMTLRGBRenderer.h
@@ -0,0 +1,21 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "RTCMTLRenderer.h"
+
+/** @abstract RGB/BGR renderer.
+ *  @discussion This renderer handles both kCVPixelFormatType_32BGRA and kCVPixelFormatType_32ARGB.
+ */
+NS_AVAILABLE(10_11, 9_0)
+@interface RTCMTLRGBRenderer : RTCMTLRenderer
+
+@end
diff --git a/sdk/objc/Framework/Classes/Metal/RTCMTLRGBRenderer.mm b/sdk/objc/Framework/Classes/Metal/RTCMTLRGBRenderer.mm
new file mode 100644
index 0000000..e735879
--- /dev/null
+++ b/sdk/objc/Framework/Classes/Metal/RTCMTLRGBRenderer.mm
@@ -0,0 +1,145 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#import "RTCMTLRGBRenderer.h"
+
+#import <Metal/Metal.h>
+#import <MetalKit/MetalKit.h>
+
+#import "WebRTC/RTCLogging.h"
+#import "WebRTC/RTCVideoFrame.h"
+#import "WebRTC/RTCVideoFrameBuffer.h"
+
+#import "RTCMTLRenderer+Private.h"
+#include "rtc_base/checks.h"
+
+static NSString *const shaderSource = MTL_STRINGIFY(
+    using namespace metal;
+
+    typedef struct {
+      packed_float2 position;
+      packed_float2 texcoord;
+    } Vertex;
+
+    typedef struct {
+      float4 position[[position]];
+      float2 texcoord;
+    } VertexIO;
+
+    vertex VertexIO vertexPassthrough(device Vertex * verticies[[buffer(0)]],
+                                      uint vid[[vertex_id]]) {
+      VertexIO out;
+      device Vertex &v = verticies[vid];
+      out.position = float4(float2(v.position), 0.0, 1.0);
+      out.texcoord = v.texcoord;
+      return out;
+    }
+
+    fragment half4 fragmentColorConversion(
+        VertexIO in[[stage_in]], texture2d<half, access::sample> texture[[texture(0)]],
+                                           constant bool &isARGB[[buffer(0)]]) {
+      constexpr sampler s(address::clamp_to_edge, filter::linear);
+
+      half4 out = texture.sample(s, in.texcoord);
+      if (isARGB) {
+        out = half4(out.g, out.b, out.a, out.r);
+      }
+
+      return out;
+    });
+
+@implementation RTCMTLRGBRenderer {
+  // Textures.
+  CVMetalTextureCacheRef _textureCache;
+  id<MTLTexture> _texture;
+
+  // Uniforms.
+  id<MTLBuffer> _uniformsBuffer;
+}
+
+- (BOOL)addRenderingDestination:(__kindof MTKView *)view {
+  if ([super addRenderingDestination:view]) {
+    return [self initializeTextureCache];
+  }
+  return NO;
+}
+
+- (BOOL)initializeTextureCache {
+  CVReturn status = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, [self currentMetalDevice],
+                                              nil, &_textureCache);
+  if (status != kCVReturnSuccess) {
+    RTCLogError(@"Metal: Failed to initialize metal texture cache. Return status is %d", status);
+    return NO;
+  }
+
+  return YES;
+}
+
+- (NSString *)shaderSource {
+  return shaderSource;
+}
+
+- (BOOL)setupTexturesForFrame:(nonnull RTCVideoFrame *)frame {
+  RTC_DCHECK([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]);
+  [super setupTexturesForFrame:frame];
+  CVPixelBufferRef pixelBuffer = ((RTCCVPixelBuffer *)frame.buffer).pixelBuffer;
+
+  id<MTLTexture> gpuTexture = nil;
+  CVMetalTextureRef textureOut = nullptr;
+  bool isARGB;
+
+  int width = CVPixelBufferGetWidth(pixelBuffer);
+  int height = CVPixelBufferGetHeight(pixelBuffer);
+  OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
+
+  MTLPixelFormat mtlPixelFormat;
+  if (pixelFormat == kCVPixelFormatType_32BGRA) {
+    mtlPixelFormat = MTLPixelFormatBGRA8Unorm;
+    isARGB = false;
+  } else if (pixelFormat == kCVPixelFormatType_32ARGB) {
+    mtlPixelFormat = MTLPixelFormatRGBA8Unorm;
+    isARGB = true;
+  } else {
+    RTC_NOTREACHED();
+    return NO;
+  }
+
+  CVReturn result = CVMetalTextureCacheCreateTextureFromImage(
+                kCFAllocatorDefault, _textureCache, pixelBuffer, nil, mtlPixelFormat,
+                width, height, 0, &textureOut);
+  if (result == kCVReturnSuccess) {
+    gpuTexture = CVMetalTextureGetTexture(textureOut);
+  }
+  CVBufferRelease(textureOut);
+
+  if (gpuTexture != nil) {
+    _texture = gpuTexture;
+    _uniformsBuffer =
+        [[self currentMetalDevice] newBufferWithBytes:&isARGB
+                                               length:sizeof(isARGB)
+                                              options:MTLResourceCPUCacheModeDefaultCache];
+    return YES;
+  }
+
+  return NO;
+}
+
+- (void)uploadTexturesToRenderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
+  [renderEncoder setFragmentTexture:_texture atIndex:0];
+  [renderEncoder setFragmentBuffer:_uniformsBuffer offset:0 atIndex:0];
+}
+
+- (void)dealloc {
+  if (_textureCache) {
+    CFRelease(_textureCache);
+  }
+}
+
+@end
diff --git a/sdk/objc/Framework/Classes/Metal/RTCMTLRenderer+Private.h b/sdk/objc/Framework/Classes/Metal/RTCMTLRenderer+Private.h
index dfeb87b..c9ea3cc 100644
--- a/sdk/objc/Framework/Classes/Metal/RTCMTLRenderer+Private.h
+++ b/sdk/objc/Framework/Classes/Metal/RTCMTLRenderer+Private.h
@@ -11,6 +11,8 @@
 #import <Metal/Metal.h>
 #import "RTCMTLRenderer.h"
 
+#define MTL_STRINGIFY(s) @ #s
+
 NS_ASSUME_NONNULL_BEGIN
 @interface RTCMTLRenderer (Private)
 - (nullable id<MTLDevice>)currentMetalDevice;
diff --git a/sdk/objc/Framework/Classes/Metal/RTCMTLRenderer.mm b/sdk/objc/Framework/Classes/Metal/RTCMTLRenderer.mm
index 7fcba49..68486dc 100644
--- a/sdk/objc/Framework/Classes/Metal/RTCMTLRenderer.mm
+++ b/sdk/objc/Framework/Classes/Metal/RTCMTLRenderer.mm
@@ -139,9 +139,10 @@
 
   // Load metal library from source.
   NSError *libraryError = nil;
+  NSString *shaderSource = [self shaderSource];
 
   id<MTLLibrary> sourceLibrary =
-      [_device newLibraryWithSource:[self shaderSource] options:NULL error:&libraryError];
+      [_device newLibraryWithSource:shaderSource options:NULL error:&libraryError];
 
   if (libraryError) {
     RTCLogError(@"Metal: Library with source failed\n%@", libraryError);
diff --git a/sdk/objc/Framework/Classes/Metal/RTCMTLVideoView.m b/sdk/objc/Framework/Classes/Metal/RTCMTLVideoView.m
index 9ec2583..08594c8 100644
--- a/sdk/objc/Framework/Classes/Metal/RTCMTLVideoView.m
+++ b/sdk/objc/Framework/Classes/Metal/RTCMTLVideoView.m
@@ -19,16 +19,19 @@
 
 #import "RTCMTLI420Renderer.h"
 #import "RTCMTLNV12Renderer.h"
+#import "RTCMTLRGBRenderer.h"
 
 // To avoid unreconized symbol linker errors, we're taking advantage of the objc runtime.
 // Linking errors occur when compiling for architectures that don't support Metal.
 #define MTKViewClass NSClassFromString(@"MTKView")
 #define RTCMTLNV12RendererClass NSClassFromString(@"RTCMTLNV12Renderer")
 #define RTCMTLI420RendererClass NSClassFromString(@"RTCMTLI420Renderer")
+#define RTCMTLRGBRendererClass NSClassFromString(@"RTCMTLRGBRenderer")
 
 @interface RTCMTLVideoView () <MTKViewDelegate>
 @property(nonatomic, strong) RTCMTLI420Renderer *rendererI420;
 @property(nonatomic, strong) RTCMTLNV12Renderer *rendererNV12;
+@property(nonatomic, strong) RTCMTLRGBRenderer *rendererRGB;
 @property(nonatomic, strong) MTKView *metalView;
 @property(atomic, strong) RTCVideoFrame *videoFrame;
 @end
@@ -41,6 +44,7 @@
 @synthesize delegate = _delegate;
 @synthesize rendererI420 = _rendererI420;
 @synthesize rendererNV12 = _rendererNV12;
+@synthesize rendererRGB = _rendererRGB;
 @synthesize metalView = _metalView;
 @synthesize videoFrame = _videoFrame;
 
@@ -83,6 +87,10 @@
   return [[RTCMTLI420RendererClass alloc] init];
 }
 
++ (RTCMTLRGBRenderer *)createRGBRenderer {
+  return [[RTCMTLRGBRenderer alloc] init];
+}
+
 - (void)configure {
   NSAssert([RTCMTLVideoView isMetalAvailable], @"Metal not availiable on this device");
 
@@ -127,15 +135,29 @@
   }
 
   if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
-    if (!self.rendererNV12) {
-      self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
-      if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
-        self.rendererNV12 = nil;
-        RTCLogError(@"Failed to create NV12 renderer");
-        return;
+    RTCCVPixelBuffer *buffer = (RTCCVPixelBuffer*)videoFrame.buffer;
+    const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
+    if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
+      if (!self.rendererRGB) {
+        self.rendererRGB = [RTCMTLVideoView createRGBRenderer];
+        if (![self.rendererRGB addRenderingDestination:self.metalView]) {
+          self.rendererRGB = nil;
+          RTCLogError(@"Failed to create RGB renderer");
+          return;
+        }
       }
+      [self.rendererRGB drawFrame:videoFrame];
+    } else {
+      if (!self.rendererNV12) {
+        self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
+        if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
+          self.rendererNV12 = nil;
+          RTCLogError(@"Failed to create NV12 renderer");
+          return;
+        }
+      }
+      [self.rendererNV12 drawFrame:videoFrame];
     }
-    [self.rendererNV12 drawFrame:videoFrame];
   } else {
     if (!self.rendererI420) {
       self.rendererI420 = [RTCMTLVideoView createI420Renderer];