blob: c9a622e484e57df7dcff82c91d48b422f9f43384 [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
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020011#import "RTCMTLVideoView.h"
denicija070d5e32017-02-26 11:44:13 -080012
13#import <Metal/Metal.h>
14#import <MetalKit/MetalKit.h>
15
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020016#import "base/RTCLogging.h"
17#import "base/RTCVideoFrame.h"
18#import "base/RTCVideoFrameBuffer.h"
19#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
denicija070d5e32017-02-26 11:44:13 -080020
denicijad2088152017-04-28 02:14:54 -070021#import "RTCMTLI420Renderer.h"
denicija070d5e32017-02-26 11:44:13 -080022#import "RTCMTLNV12Renderer.h"
Peter Hanspers1c62b982018-05-03 14:06:04 +020023#import "RTCMTLRGBRenderer.h"
denicija070d5e32017-02-26 11:44:13 -080024
kthelgason954d9b92017-03-09 03:36:58 -080025// To avoid unreconized symbol linker errors, we're taking advantage of the objc runtime.
26// Linking errors occur when compiling for architectures that don't support Metal.
27#define MTKViewClass NSClassFromString(@"MTKView")
28#define RTCMTLNV12RendererClass NSClassFromString(@"RTCMTLNV12Renderer")
denicijad2088152017-04-28 02:14:54 -070029#define RTCMTLI420RendererClass NSClassFromString(@"RTCMTLI420Renderer")
Peter Hanspers1c62b982018-05-03 14:06:04 +020030#define RTCMTLRGBRendererClass NSClassFromString(@"RTCMTLRGBRenderer")
kthelgason954d9b92017-03-09 03:36:58 -080031
denicija070d5e32017-02-26 11:44:13 -080032@interface RTCMTLVideoView () <MTKViewDelegate>
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020033@property(nonatomic) RTCMTLI420Renderer *rendererI420;
34@property(nonatomic) RTCMTLNV12Renderer *rendererNV12;
35@property(nonatomic) RTCMTLRGBRenderer *rendererRGB;
36@property(nonatomic) MTKView *metalView;
37@property(atomic) RTCVideoFrame *videoFrame;
38@property(nonatomic) CGSize videoFrameSize;
39@property(nonatomic) int64_t lastFrameTimeNs;
denicija070d5e32017-02-26 11:44:13 -080040@end
41
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020042@implementation RTCMTLVideoView
denicija070d5e32017-02-26 11:44:13 -080043
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +020044@synthesize delegate = _delegate;
denicijad2088152017-04-28 02:14:54 -070045@synthesize rendererI420 = _rendererI420;
46@synthesize rendererNV12 = _rendererNV12;
Peter Hanspers1c62b982018-05-03 14:06:04 +020047@synthesize rendererRGB = _rendererRGB;
denicija070d5e32017-02-26 11:44:13 -080048@synthesize metalView = _metalView;
49@synthesize videoFrame = _videoFrame;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020050@synthesize videoFrameSize = _videoFrameSize;
51@synthesize lastFrameTimeNs = _lastFrameTimeNs;
52@synthesize rotationOverride = _rotationOverride;
denicija070d5e32017-02-26 11:44:13 -080053
54- (instancetype)initWithFrame:(CGRect)frameRect {
55 self = [super initWithFrame:frameRect];
56 if (self) {
57 [self configure];
58 }
59 return self;
60}
61
62- (instancetype)initWithCoder:(NSCoder *)aCoder {
63 self = [super initWithCoder:aCoder];
64 if (self) {
65 [self configure];
66 }
67 return self;
68}
69
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020070- (BOOL)isEnabled {
71 return !self.metalView.paused;
72}
73
74- (void)setEnabled:(BOOL)enabled {
75 self.metalView.paused = !enabled;
76}
77
78- (UIViewContentMode)videoContentMode {
79 return self.metalView.contentMode;
80}
81
82- (void)setVideoContentMode:(UIViewContentMode)mode {
83 self.metalView.contentMode = mode;
84}
85
denicija070d5e32017-02-26 11:44:13 -080086#pragma mark - Private
87
88+ (BOOL)isMetalAvailable {
kthelgasona2fb30c2017-03-09 03:34:27 -080089#if defined(RTC_SUPPORTS_METAL)
Kári Tristan Helgasonbe806812018-04-18 17:04:02 +020090 return MTLCreateSystemDefaultDevice() != nil;
kthelgasona2fb30c2017-03-09 03:34:27 -080091#else
denicija070d5e32017-02-26 11:44:13 -080092 return NO;
93#endif
94}
95
Kári Tristan Helgason90143242018-07-27 12:34:54 +020096+ (MTKView *)createMetalView:(CGRect)frame {
97 return [[MTKViewClass alloc] initWithFrame:frame];
98}
99
denicijad2088152017-04-28 02:14:54 -0700100+ (RTCMTLNV12Renderer *)createNV12Renderer {
101 return [[RTCMTLNV12RendererClass alloc] init];
102}
103
104+ (RTCMTLI420Renderer *)createI420Renderer {
105 return [[RTCMTLI420RendererClass alloc] init];
kthelgason954d9b92017-03-09 03:36:58 -0800106}
107
Peter Hanspers1c62b982018-05-03 14:06:04 +0200108+ (RTCMTLRGBRenderer *)createRGBRenderer {
109 return [[RTCMTLRGBRenderer alloc] init];
110}
111
denicija070d5e32017-02-26 11:44:13 -0800112- (void)configure {
denicijad2088152017-04-28 02:14:54 -0700113 NSAssert([RTCMTLVideoView isMetalAvailable], @"Metal not availiable on this device");
kthelgason954d9b92017-03-09 03:36:58 -0800114
Kári Tristan Helgason90143242018-07-27 12:34:54 +0200115 self.metalView = [RTCMTLVideoView createMetalView:self.bounds];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200116 self.metalView.delegate = self;
117 self.metalView.contentMode = UIViewContentModeScaleAspectFill;
118 [self addSubview:self.metalView];
119 self.videoFrameSize = CGSizeZero;
denicija070d5e32017-02-26 11:44:13 -0800120}
kthelgason954d9b92017-03-09 03:36:58 -0800121
CZ Theng0ff7c022019-10-22 20:36:40 +0800122- (void)setMultipleTouchEnabled:(BOOL)multipleTouchEnabled {
123 [super setMultipleTouchEnabled:multipleTouchEnabled];
124 self.metalView.multipleTouchEnabled = multipleTouchEnabled;
125}
126
Danielac4a14322017-10-31 11:19:38 +0100127- (void)layoutSubviews {
128 [super layoutSubviews];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200129
JT Teh5c147252018-04-22 09:28:27 -0700130 CGRect bounds = self.bounds;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200131 self.metalView.frame = bounds;
132 if (!CGSizeEqualToSize(self.videoFrameSize, CGSizeZero)) {
133 self.metalView.drawableSize = [self drawableSize];
JT Teh5c147252018-04-22 09:28:27 -0700134 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200135 self.metalView.drawableSize = bounds.size;
JT Teh5c147252018-04-22 09:28:27 -0700136 }
Danielac4a14322017-10-31 11:19:38 +0100137}
138
denicija070d5e32017-02-26 11:44:13 -0800139#pragma mark - MTKViewDelegate methods
140
141- (void)drawInMTKView:(nonnull MTKView *)view {
142 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200143 RTCVideoFrame *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200144 // Skip rendering if we've already rendered this frame.
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200145 if (!videoFrame || videoFrame.timeStampNs == self.lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700146 return;
147 }
148
Peter Hanspers3955a502018-11-22 16:29:56 +0100149 if (CGRectIsEmpty(view.bounds)) {
150 return;
151 }
152
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200153 RTCMTLRenderer *renderer;
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200154 if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
Peter Hanspers1c62b982018-05-03 14:06:04 +0200155 RTCCVPixelBuffer *buffer = (RTCCVPixelBuffer*)videoFrame.buffer;
156 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
157 if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
158 if (!self.rendererRGB) {
159 self.rendererRGB = [RTCMTLVideoView createRGBRenderer];
160 if (![self.rendererRGB addRenderingDestination:self.metalView]) {
161 self.rendererRGB = nil;
162 RTCLogError(@"Failed to create RGB renderer");
163 return;
164 }
denicijad2088152017-04-28 02:14:54 -0700165 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200166 renderer = self.rendererRGB;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200167 } else {
168 if (!self.rendererNV12) {
169 self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
170 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
171 self.rendererNV12 = nil;
172 RTCLogError(@"Failed to create NV12 renderer");
173 return;
174 }
175 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200176 renderer = self.rendererNV12;
JT Teh6144fa52018-04-10 00:27:04 +0000177 }
denicijad2088152017-04-28 02:14:54 -0700178 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000179 if (!self.rendererI420) {
denicijad2088152017-04-28 02:14:54 -0700180 self.rendererI420 = [RTCMTLVideoView createI420Renderer];
181 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
182 self.rendererI420 = nil;
183 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200184 return;
denicijad2088152017-04-28 02:14:54 -0700185 }
JT Teh6144fa52018-04-10 00:27:04 +0000186 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200187 renderer = self.rendererI420;
denicijad2088152017-04-28 02:14:54 -0700188 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200189
190 renderer.rotationOverride = self.rotationOverride;
191
192 [renderer drawFrame:videoFrame];
193 self.lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800194}
195
196- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
197}
198
Peter Hanspers7c32c862018-06-14 14:12:45 +0200199#pragma mark -
200
201- (void)setRotationOverride:(NSValue *)rotationOverride {
202 _rotationOverride = rotationOverride;
203
204 self.metalView.drawableSize = [self drawableSize];
205 [self setNeedsLayout];
206}
207
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200208- (RTCVideoRotation)frameRotation {
209 if (self.rotationOverride) {
210 RTCVideoRotation rotation;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200211 if (@available(iOS 11, *)) {
212 [self.rotationOverride getValue:&rotation size:sizeof(rotation)];
Kári Tristan Helgason90143242018-07-27 12:34:54 +0200213 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200214 [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 RTCVideoRotation frameRotation = [self frameRotation];
226
227 BOOL useLandscape =
228 (frameRotation == RTCVideoRotation_0) || (frameRotation == RTCVideoRotation_180);
229 BOOL sizeIsLandscape = (self.videoFrame.rotation == RTCVideoRotation_0) ||
230 (self.videoFrame.rotation == RTCVideoRotation_180);
231
232 if (useLandscape == sizeIsLandscape) {
233 return videoFrameSize;
234 } else {
235 return CGSizeMake(videoFrameSize.height, videoFrameSize.width);
236 }
237}
238
denicija070d5e32017-02-26 11:44:13 -0800239#pragma mark - RTCVideoRenderer
240
241- (void)setSize:(CGSize)size {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200242 __weak RTCMTLVideoView *weakSelf = self;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200243 dispatch_async(dispatch_get_main_queue(), ^{
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200244 RTCMTLVideoView *strongSelf = weakSelf;
245
246 strongSelf.videoFrameSize = size;
247 CGSize drawableSize = [strongSelf drawableSize];
248
249 strongSelf.metalView.drawableSize = drawableSize;
250 [strongSelf setNeedsLayout];
251 [strongSelf.delegate videoView:self didChangeVideoSize:size];
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200252 });
denicija070d5e32017-02-26 11:44:13 -0800253}
254
255- (void)renderFrame:(nullable RTCVideoFrame *)frame {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200256 if (!self.isEnabled) {
257 return;
258 }
259
denicija070d5e32017-02-26 11:44:13 -0800260 if (frame == nil) {
261 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
262 return;
263 }
264 self.videoFrame = frame;
denicija070d5e32017-02-26 11:44:13 -0800265}
266
267@end