blob: f8575c0cfed52fc4a55b8f6215fe8b3cb5865665 [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
Danielac4a14322017-10-31 11:19:38 +0100122- (void)layoutSubviews {
123 [super layoutSubviews];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200124
JT Teh5c147252018-04-22 09:28:27 -0700125 CGRect bounds = self.bounds;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200126 self.metalView.frame = bounds;
127 if (!CGSizeEqualToSize(self.videoFrameSize, CGSizeZero)) {
128 self.metalView.drawableSize = [self drawableSize];
JT Teh5c147252018-04-22 09:28:27 -0700129 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200130 self.metalView.drawableSize = bounds.size;
JT Teh5c147252018-04-22 09:28:27 -0700131 }
Danielac4a14322017-10-31 11:19:38 +0100132}
133
denicija070d5e32017-02-26 11:44:13 -0800134#pragma mark - MTKViewDelegate methods
135
136- (void)drawInMTKView:(nonnull MTKView *)view {
137 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200138 RTCVideoFrame *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200139 // Skip rendering if we've already rendered this frame.
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200140 if (!videoFrame || videoFrame.timeStampNs == self.lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700141 return;
142 }
143
Peter Hanspers3955a502018-11-22 16:29:56 +0100144 if (CGRectIsEmpty(view.bounds)) {
145 return;
146 }
147
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200148 RTCMTLRenderer *renderer;
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200149 if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
Peter Hanspers1c62b982018-05-03 14:06:04 +0200150 RTCCVPixelBuffer *buffer = (RTCCVPixelBuffer*)videoFrame.buffer;
151 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
152 if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
153 if (!self.rendererRGB) {
154 self.rendererRGB = [RTCMTLVideoView createRGBRenderer];
155 if (![self.rendererRGB addRenderingDestination:self.metalView]) {
156 self.rendererRGB = nil;
157 RTCLogError(@"Failed to create RGB renderer");
158 return;
159 }
denicijad2088152017-04-28 02:14:54 -0700160 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200161 renderer = self.rendererRGB;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200162 } else {
163 if (!self.rendererNV12) {
164 self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
165 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
166 self.rendererNV12 = nil;
167 RTCLogError(@"Failed to create NV12 renderer");
168 return;
169 }
170 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200171 renderer = self.rendererNV12;
JT Teh6144fa52018-04-10 00:27:04 +0000172 }
denicijad2088152017-04-28 02:14:54 -0700173 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000174 if (!self.rendererI420) {
denicijad2088152017-04-28 02:14:54 -0700175 self.rendererI420 = [RTCMTLVideoView createI420Renderer];
176 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
177 self.rendererI420 = nil;
178 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200179 return;
denicijad2088152017-04-28 02:14:54 -0700180 }
JT Teh6144fa52018-04-10 00:27:04 +0000181 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200182 renderer = self.rendererI420;
denicijad2088152017-04-28 02:14:54 -0700183 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200184
185 renderer.rotationOverride = self.rotationOverride;
186
187 [renderer drawFrame:videoFrame];
188 self.lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800189}
190
191- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
192}
193
Peter Hanspers7c32c862018-06-14 14:12:45 +0200194#pragma mark -
195
196- (void)setRotationOverride:(NSValue *)rotationOverride {
197 _rotationOverride = rotationOverride;
198
199 self.metalView.drawableSize = [self drawableSize];
200 [self setNeedsLayout];
201}
202
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200203- (RTCVideoRotation)frameRotation {
204 if (self.rotationOverride) {
205 RTCVideoRotation rotation;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200206 if (@available(iOS 11, *)) {
207 [self.rotationOverride getValue:&rotation size:sizeof(rotation)];
Kári Tristan Helgason90143242018-07-27 12:34:54 +0200208 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200209 [self.rotationOverride getValue:&rotation];
210 }
211 return rotation;
212 }
213
214 return self.videoFrame.rotation;
215}
216
217- (CGSize)drawableSize {
218 // Flip width/height if the rotations are not the same.
219 CGSize videoFrameSize = self.videoFrameSize;
220 RTCVideoRotation frameRotation = [self frameRotation];
221
222 BOOL useLandscape =
223 (frameRotation == RTCVideoRotation_0) || (frameRotation == RTCVideoRotation_180);
224 BOOL sizeIsLandscape = (self.videoFrame.rotation == RTCVideoRotation_0) ||
225 (self.videoFrame.rotation == RTCVideoRotation_180);
226
227 if (useLandscape == sizeIsLandscape) {
228 return videoFrameSize;
229 } else {
230 return CGSizeMake(videoFrameSize.height, videoFrameSize.width);
231 }
232}
233
denicija070d5e32017-02-26 11:44:13 -0800234#pragma mark - RTCVideoRenderer
235
236- (void)setSize:(CGSize)size {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200237 __weak RTCMTLVideoView *weakSelf = self;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200238 dispatch_async(dispatch_get_main_queue(), ^{
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200239 RTCMTLVideoView *strongSelf = weakSelf;
240
241 strongSelf.videoFrameSize = size;
242 CGSize drawableSize = [strongSelf drawableSize];
243
244 strongSelf.metalView.drawableSize = drawableSize;
245 [strongSelf setNeedsLayout];
246 [strongSelf.delegate videoView:self didChangeVideoSize:size];
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200247 });
denicija070d5e32017-02-26 11:44:13 -0800248}
249
250- (void)renderFrame:(nullable RTCVideoFrame *)frame {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200251 if (!self.isEnabled) {
252 return;
253 }
254
denicija070d5e32017-02-26 11:44:13 -0800255 if (frame == nil) {
256 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
257 return;
258 }
259 self.videoFrame = frame;
denicija070d5e32017-02-26 11:44:13 -0800260}
261
262@end