1/*
2 *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
3 *
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
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#import "RTCMTLRGBRenderer.h"
12
13#import <Metal/Metal.h>
14#import <MetalKit/MetalKit.h>
15
16#import "RTCMTLRenderer+Private.h"
17#import "base/RTCLogging.h"
18#import "base/RTCVideoFrame.h"
19#import "base/RTCVideoFrameBuffer.h"
20#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
21
22#include "rtc_base/checks.h"
23
24static NSString *const shaderSource = MTL_STRINGIFY(
25    using namespace metal;
26
27    typedef struct {
28      packed_float2 position;
29      packed_float2 texcoord;
30    } Vertex;
31
32    typedef struct {
33      float4 position[[position]];
34      float2 texcoord;
35    } VertexIO;
36
37    vertex VertexIO vertexPassthrough(constant Vertex *verticies[[buffer(0)]],
38                                      uint vid[[vertex_id]]) {
39      VertexIO out;
40      constant Vertex &v = verticies[vid];
41      out.position = float4(float2(v.position), 0.0, 1.0);
42      out.texcoord = v.texcoord;
43      return out;
44    }
45
46    fragment half4 fragmentColorConversion(VertexIO in[[stage_in]],
47                                           texture2d<half, access::sample> texture[[texture(0)]],
48                                           constant bool &isARGB[[buffer(0)]]) {
49      constexpr sampler s(address::clamp_to_edge, filter::linear);
50
51      half4 out = texture.sample(s, in.texcoord);
52      if (isARGB) {
53        out = half4(out.g, out.b, out.a, out.r);
54      }
55
56      return out;
57    });
58
59@implementation RTCMTLRGBRenderer {
60  // Textures.
61  CVMetalTextureCacheRef _textureCache;
62  id<MTLTexture> _texture;
63
64  // Uniforms.
65  id<MTLBuffer> _uniformsBuffer;
66}
67
68- (BOOL)addRenderingDestination:(__kindof MTKView *)view {
69  if ([super addRenderingDestination:view]) {
70    return [self initializeTextureCache];
71  }
72  return NO;
73}
74
75- (BOOL)initializeTextureCache {
76  CVReturn status = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, [self currentMetalDevice],
77                                              nil, &_textureCache);
78  if (status != kCVReturnSuccess) {
79    RTCLogError(@"Metal: Failed to initialize metal texture cache. Return status is %d", status);
80    return NO;
81  }
82
83  return YES;
84}
85
86- (NSString *)shaderSource {
87  return shaderSource;
88}
89
90- (void)getWidth:(nonnull int *)width
91          height:(nonnull int *)height
92       cropWidth:(nonnull int *)cropWidth
93      cropHeight:(nonnull int *)cropHeight
94           cropX:(nonnull int *)cropX
95           cropY:(nonnull int *)cropY
96         ofFrame:(nonnull RTC_OBJC_TYPE(RTCVideoFrame) *)frame {
97  RTC_OBJC_TYPE(RTCCVPixelBuffer) *pixelBuffer = (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)frame.buffer;
98  *width = CVPixelBufferGetWidth(pixelBuffer.pixelBuffer);
99  *height = CVPixelBufferGetHeight(pixelBuffer.pixelBuffer);
100  *cropWidth = pixelBuffer.cropWidth;
101  *cropHeight = pixelBuffer.cropHeight;
102  *cropX = pixelBuffer.cropX;
103  *cropY = pixelBuffer.cropY;
104}
105
106- (BOOL)setupTexturesForFrame:(nonnull RTC_OBJC_TYPE(RTCVideoFrame) *)frame {
107  RTC_DCHECK([frame.buffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]);
108  if (![super setupTexturesForFrame:frame]) {
109    return NO;
110  }
111  CVPixelBufferRef pixelBuffer = ((RTC_OBJC_TYPE(RTCCVPixelBuffer) *)frame.buffer).pixelBuffer;
112
113  id<MTLTexture> gpuTexture = nil;
114  CVMetalTextureRef textureOut = nullptr;
115  bool isARGB;
116
117  int width = CVPixelBufferGetWidth(pixelBuffer);
118  int height = CVPixelBufferGetHeight(pixelBuffer);
119  OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
120
121  MTLPixelFormat mtlPixelFormat;
122  if (pixelFormat == kCVPixelFormatType_32BGRA) {
123    mtlPixelFormat = MTLPixelFormatBGRA8Unorm;
124    isARGB = false;
125  } else if (pixelFormat == kCVPixelFormatType_32ARGB) {
126    mtlPixelFormat = MTLPixelFormatRGBA8Unorm;
127    isARGB = true;
128  } else {
129    RTC_NOTREACHED();
130    return NO;
131  }
132
133  CVReturn result = CVMetalTextureCacheCreateTextureFromImage(
134                kCFAllocatorDefault, _textureCache, pixelBuffer, nil, mtlPixelFormat,
135                width, height, 0, &textureOut);
136  if (result == kCVReturnSuccess) {
137    gpuTexture = CVMetalTextureGetTexture(textureOut);
138  }
139  CVBufferRelease(textureOut);
140
141  if (gpuTexture != nil) {
142    _texture = gpuTexture;
143    _uniformsBuffer =
144        [[self currentMetalDevice] newBufferWithBytes:&isARGB
145                                               length:sizeof(isARGB)
146                                              options:MTLResourceCPUCacheModeDefaultCache];
147    return YES;
148  }
149
150  return NO;
151}
152
153- (void)uploadTexturesToRenderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
154  [renderEncoder setFragmentTexture:_texture atIndex:0];
155  [renderEncoder setFragmentBuffer:_uniformsBuffer offset:0 atIndex:0];
156}
157
158- (void)dealloc {
159  if (_textureCache) {
160    CFRelease(_textureCache);
161  }
162}
163
164@end
165