1/*
2 *  Copyright 2017 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 "RTCMTLNSVideoView.h"
12
13#import <Metal/Metal.h>
14#import <MetalKit/MetalKit.h>
15
16#import "base/RTCVideoFrame.h"
17
18#import "RTCMTLI420Renderer.h"
19
20@interface RTC_OBJC_TYPE (RTCMTLNSVideoView)
21()<MTKViewDelegate> @property(nonatomic) id<RTCMTLRenderer> renderer;
22@property(nonatomic, strong) MTKView *metalView;
23@property(atomic, strong) RTC_OBJC_TYPE(RTCVideoFrame) * videoFrame;
24@end
25
26@implementation RTC_OBJC_TYPE (RTCMTLNSVideoView) {
27  id<RTCMTLRenderer> _renderer;
28}
29
30@synthesize delegate = _delegate;
31@synthesize renderer = _renderer;
32@synthesize metalView = _metalView;
33@synthesize videoFrame = _videoFrame;
34
35- (instancetype)initWithFrame:(CGRect)frameRect {
36  self = [super initWithFrame:frameRect];
37  if (self) {
38    [self configure];
39  }
40  return self;
41}
42
43- (instancetype)initWithCoder:(NSCoder *)aCoder {
44  self = [super initWithCoder:aCoder];
45  if (self) {
46    [self configure];
47  }
48  return self;
49}
50
51#pragma mark - Private
52
53+ (BOOL)isMetalAvailable {
54  return [MTLCopyAllDevices() count] > 0;
55}
56
57- (void)configure {
58  if ([[self class] isMetalAvailable]) {
59    _metalView = [[MTKView alloc] initWithFrame:self.bounds];
60    [self addSubview:_metalView];
61    _metalView.layerContentsPlacement = NSViewLayerContentsPlacementScaleProportionallyToFit;
62    _metalView.translatesAutoresizingMaskIntoConstraints = NO;
63    _metalView.framebufferOnly = YES;
64    _metalView.delegate = self;
65
66    _renderer = [[RTCMTLI420Renderer alloc] init];
67    if (![(RTCMTLI420Renderer *)_renderer addRenderingDestination:_metalView]) {
68      _renderer = nil;
69    };
70  }
71}
72
73- (void)updateConstraints {
74  NSDictionary *views = NSDictionaryOfVariableBindings(_metalView);
75
76  NSArray *constraintsHorizontal =
77      [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[_metalView]-0-|"
78                                              options:0
79                                              metrics:nil
80                                                views:views];
81  [self addConstraints:constraintsHorizontal];
82
83  NSArray *constraintsVertical =
84      [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[_metalView]-0-|"
85                                              options:0
86                                              metrics:nil
87                                                views:views];
88  [self addConstraints:constraintsVertical];
89  [super updateConstraints];
90}
91
92#pragma mark - MTKViewDelegate methods
93- (void)drawInMTKView:(nonnull MTKView *)view {
94  if (self.videoFrame == nil) {
95    return;
96  }
97  if (view == self.metalView) {
98    [_renderer drawFrame:self.videoFrame];
99  }
100}
101
102- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
103}
104
105#pragma mark - RTC_OBJC_TYPE(RTCVideoRenderer)
106
107- (void)setSize:(CGSize)size {
108  _metalView.drawableSize = size;
109  dispatch_async(dispatch_get_main_queue(), ^{
110    [self.delegate videoView:self didChangeVideoSize:size];
111  });
112  [_metalView draw];
113}
114
115- (void)renderFrame:(nullable RTC_OBJC_TYPE(RTCVideoFrame) *)frame {
116  if (frame == nil) {
117    return;
118  }
119  self.videoFrame = [frame newI420VideoFrame];
120}
121
122@end
123