blob: 100b2f077c023acf5d5f3e66cfd77b02d0abed0b [file] [log] [blame]
denicija070d5e32017-02-26 11:44:13 -08001/*
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 "WebRTC/RTCMTLVideoView.h"
12
13#import <Metal/Metal.h>
14#import <MetalKit/MetalKit.h>
15
16#import "WebRTC/RTCLogging.h"
17#import "WebRTC/RTCVideoFrame.h"
Anders Carlssone5960ce2017-06-22 15:26:30 +020018#import "WebRTC/RTCVideoFrameBuffer.h"
denicija070d5e32017-02-26 11:44:13 -080019
denicijad2088152017-04-28 02:14:54 -070020#import "RTCMTLI420Renderer.h"
denicija070d5e32017-02-26 11:44:13 -080021#import "RTCMTLNV12Renderer.h"
Peter Hanspers1c62b982018-05-03 14:06:04 +020022#import "RTCMTLRGBRenderer.h"
denicija070d5e32017-02-26 11:44:13 -080023
kthelgason954d9b92017-03-09 03:36:58 -080024// To avoid unreconized symbol linker errors, we're taking advantage of the objc runtime.
25// Linking errors occur when compiling for architectures that don't support Metal.
26#define MTKViewClass NSClassFromString(@"MTKView")
27#define RTCMTLNV12RendererClass NSClassFromString(@"RTCMTLNV12Renderer")
denicijad2088152017-04-28 02:14:54 -070028#define RTCMTLI420RendererClass NSClassFromString(@"RTCMTLI420Renderer")
Peter Hanspers1c62b982018-05-03 14:06:04 +020029#define RTCMTLRGBRendererClass NSClassFromString(@"RTCMTLRGBRenderer")
kthelgason954d9b92017-03-09 03:36:58 -080030
denicija070d5e32017-02-26 11:44:13 -080031@interface RTCMTLVideoView () <MTKViewDelegate>
Peter Hanspersfc4a9c92018-05-23 16:30:00 +020032@property(nonatomic) RTCMTLI420Renderer *rendererI420;
33@property(nonatomic) RTCMTLNV12Renderer *rendererNV12;
34@property(nonatomic) RTCMTLRGBRenderer *rendererRGB;
35@property(nonatomic) MTKView *metalView;
36@property(atomic) RTCVideoFrame *videoFrame;
37@property(nonatomic) CGSize videoFrameSize;
38@property(nonatomic) int64_t lastFrameTimeNs;
denicija070d5e32017-02-26 11:44:13 -080039@end
40
Peter Hanspersfc4a9c92018-05-23 16:30:00 +020041@implementation RTCMTLVideoView
denicija070d5e32017-02-26 11:44:13 -080042
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +020043@synthesize delegate = _delegate;
denicijad2088152017-04-28 02:14:54 -070044@synthesize rendererI420 = _rendererI420;
45@synthesize rendererNV12 = _rendererNV12;
Peter Hanspers1c62b982018-05-03 14:06:04 +020046@synthesize rendererRGB = _rendererRGB;
denicija070d5e32017-02-26 11:44:13 -080047@synthesize metalView = _metalView;
48@synthesize videoFrame = _videoFrame;
Peter Hanspersfc4a9c92018-05-23 16:30:00 +020049@synthesize useDisplayLink = _useDisplayLink;
50@synthesize videoFrameSize = _videoFrameSize;
51@synthesize lastFrameTimeNs = _lastFrameTimeNs;
52@synthesize enabled = _enabled;
53@synthesize rotationOverride = _rotationOverride;
denicija070d5e32017-02-26 11:44:13 -080054
55- (instancetype)initWithFrame:(CGRect)frameRect {
56 self = [super initWithFrame:frameRect];
57 if (self) {
58 [self configure];
59 }
60 return self;
61}
62
63- (instancetype)initWithCoder:(NSCoder *)aCoder {
64 self = [super initWithCoder:aCoder];
65 if (self) {
66 [self configure];
67 }
68 return self;
69}
70
Peter Hanspersfc4a9c92018-05-23 16:30:00 +020071- (void)setUseDisplayLink:(BOOL)useDisplayLink {
72 _useDisplayLink = useDisplayLink;
73 [self updateRunningState];
74}
75
76- (void)setEnabled:(BOOL)enabled {
77 _enabled = enabled;
78 [self updateRunningState];
79}
80
81- (UIViewContentMode)videoContentMode {
82 return self.metalView.contentMode;
83}
84
85- (void)setVideoContentMode:(UIViewContentMode)mode {
86 self.metalView.contentMode = mode;
87}
88
denicija070d5e32017-02-26 11:44:13 -080089#pragma mark - Private
90
Peter Hanspersfc4a9c92018-05-23 16:30:00 +020091- (void)updateRunningState {
92 if (self.useDisplayLink) {
93 self.metalView.paused = !self.enabled;
94 self.metalView.enableSetNeedsDisplay = YES;
95 } else {
96 self.metalView.paused = YES;
97 self.metalView.enableSetNeedsDisplay = NO;
98 }
99}
100
denicija070d5e32017-02-26 11:44:13 -0800101+ (BOOL)isMetalAvailable {
kthelgasona2fb30c2017-03-09 03:34:27 -0800102#if defined(RTC_SUPPORTS_METAL)
Kári Tristan Helgasonbe806812018-04-18 17:04:02 +0200103 return MTLCreateSystemDefaultDevice() != nil;
kthelgasona2fb30c2017-03-09 03:34:27 -0800104#else
denicija070d5e32017-02-26 11:44:13 -0800105 return NO;
106#endif
107}
108
denicijad2088152017-04-28 02:14:54 -0700109+ (RTCMTLNV12Renderer *)createNV12Renderer {
110 return [[RTCMTLNV12RendererClass alloc] init];
111}
112
113+ (RTCMTLI420Renderer *)createI420Renderer {
114 return [[RTCMTLI420RendererClass alloc] init];
kthelgason954d9b92017-03-09 03:36:58 -0800115}
116
Peter Hanspers1c62b982018-05-03 14:06:04 +0200117+ (RTCMTLRGBRenderer *)createRGBRenderer {
118 return [[RTCMTLRGBRenderer alloc] init];
119}
120
denicija070d5e32017-02-26 11:44:13 -0800121- (void)configure {
denicijad2088152017-04-28 02:14:54 -0700122 NSAssert([RTCMTLVideoView isMetalAvailable], @"Metal not availiable on this device");
kthelgason954d9b92017-03-09 03:36:58 -0800123
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200124 _enabled = YES;
125 _useDisplayLink = YES;
126 [self updateRunningState];
kthelgason954d9b92017-03-09 03:36:58 -0800127
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200128 self.metalView = [[MTKViewClass alloc] initWithFrame:self.bounds];
129 self.metalView.delegate = self;
130 self.metalView.paused = YES;
131 self.metalView.enableSetNeedsDisplay = NO;
132 self.metalView.contentMode = UIViewContentModeScaleAspectFill;
133 [self addSubview:self.metalView];
134 self.videoFrameSize = CGSizeZero;
denicija070d5e32017-02-26 11:44:13 -0800135}
kthelgason954d9b92017-03-09 03:36:58 -0800136
Danielac4a14322017-10-31 11:19:38 +0100137- (void)layoutSubviews {
138 [super layoutSubviews];
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200139
JT Teh5c147252018-04-22 09:28:27 -0700140 CGRect bounds = self.bounds;
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200141 self.metalView.frame = bounds;
142 if (!CGSizeEqualToSize(self.videoFrameSize, CGSizeZero)) {
143 self.metalView.drawableSize = [self drawableSize];
JT Teh5c147252018-04-22 09:28:27 -0700144 } else {
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200145 self.metalView.drawableSize = bounds.size;
JT Teh5c147252018-04-22 09:28:27 -0700146 }
Danielac4a14322017-10-31 11:19:38 +0100147}
148
denicija070d5e32017-02-26 11:44:13 -0800149#pragma mark - MTKViewDelegate methods
150
151- (void)drawInMTKView:(nonnull MTKView *)view {
152 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200153 RTCVideoFrame *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200154 // Skip rendering if we've already rendered this frame.
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200155 if (!videoFrame || videoFrame.timeStampNs == self.lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700156 return;
157 }
158
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200159 RTCMTLRenderer *renderer;
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200160 if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
Peter Hanspers1c62b982018-05-03 14:06:04 +0200161 RTCCVPixelBuffer *buffer = (RTCCVPixelBuffer*)videoFrame.buffer;
162 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
163 if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
164 if (!self.rendererRGB) {
165 self.rendererRGB = [RTCMTLVideoView createRGBRenderer];
166 if (![self.rendererRGB addRenderingDestination:self.metalView]) {
167 self.rendererRGB = nil;
168 RTCLogError(@"Failed to create RGB renderer");
169 return;
170 }
denicijad2088152017-04-28 02:14:54 -0700171 }
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200172 renderer = self.rendererRGB;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200173 } else {
174 if (!self.rendererNV12) {
175 self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
176 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
177 self.rendererNV12 = nil;
178 RTCLogError(@"Failed to create NV12 renderer");
179 return;
180 }
181 }
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200182 renderer = self.rendererNV12;
JT Teh6144fa52018-04-10 00:27:04 +0000183 }
denicijad2088152017-04-28 02:14:54 -0700184 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000185 if (!self.rendererI420) {
denicijad2088152017-04-28 02:14:54 -0700186 self.rendererI420 = [RTCMTLVideoView createI420Renderer];
187 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
188 self.rendererI420 = nil;
189 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200190 return;
denicijad2088152017-04-28 02:14:54 -0700191 }
JT Teh6144fa52018-04-10 00:27:04 +0000192 }
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200193 renderer = self.rendererI420;
denicijad2088152017-04-28 02:14:54 -0700194 }
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200195
196 renderer.rotationOverride = self.rotationOverride;
197
198 [renderer drawFrame:videoFrame];
199 self.lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800200}
201
202- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
203}
204
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200205- (RTCVideoRotation)frameRotation {
206 if (self.rotationOverride) {
207 RTCVideoRotation rotation;
208#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
209 if (@available(iOS 11, *)) {
210 [self.rotationOverride getValue:&rotation size:sizeof(rotation)];
211 } else
212#endif
213 {
214 [self.rotationOverride getValue:&rotation];
215 }
216 return rotation;
217 }
218
219 return self.videoFrame.rotation;
220}
221
222- (CGSize)drawableSize {
223 // Flip width/height if the rotations are not the same.
224 CGSize videoFrameSize = self.videoFrameSize;
225
226 BOOL useLandscape = ([self frameRotation] == RTCVideoRotation_0) ||
227 ([self frameRotation] == RTCVideoRotation_180);
228 BOOL sizeIsLandscape = (self.videoFrame.rotation == RTCVideoRotation_0) ||
229 (self.videoFrame.rotation == RTCVideoRotation_180);
230
231 if (useLandscape == sizeIsLandscape) {
232 return videoFrameSize;
233 } else {
234 return CGSizeMake(videoFrameSize.height, videoFrameSize.width);
235 }
236}
237
denicija070d5e32017-02-26 11:44:13 -0800238#pragma mark - RTCVideoRenderer
239
240- (void)setSize:(CGSize)size {
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200241 __weak RTCMTLVideoView *weakSelf = self;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200242 dispatch_async(dispatch_get_main_queue(), ^{
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200243 RTCMTLVideoView *strongSelf = weakSelf;
244
245 strongSelf.videoFrameSize = size;
246 CGSize drawableSize = [strongSelf drawableSize];
247
248 strongSelf.metalView.drawableSize = drawableSize;
249 [strongSelf setNeedsLayout];
250 [strongSelf.delegate videoView:self didChangeVideoSize:size];
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200251 });
denicija070d5e32017-02-26 11:44:13 -0800252}
253
254- (void)renderFrame:(nullable RTCVideoFrame *)frame {
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200255 if (!self.isEnabled) {
256 return;
257 }
258
denicija070d5e32017-02-26 11:44:13 -0800259 if (frame == nil) {
260 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
261 return;
262 }
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200263
denicija070d5e32017-02-26 11:44:13 -0800264 self.videoFrame = frame;
Peter Hanspersfc4a9c92018-05-23 16:30:00 +0200265
266 if (!self.useDisplayLink) {
267 [self.metalView draw];
268 }
denicija070d5e32017-02-26 11:44:13 -0800269}
270
271@end