blob: 46e5f21fe2ab7943902d86a4a4c0dcf3bac8c6af [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"
22
kthelgason954d9b92017-03-09 03:36:58 -080023// To avoid unreconized symbol linker errors, we're taking advantage of the objc runtime.
24// Linking errors occur when compiling for architectures that don't support Metal.
25#define MTKViewClass NSClassFromString(@"MTKView")
26#define RTCMTLNV12RendererClass NSClassFromString(@"RTCMTLNV12Renderer")
denicijad2088152017-04-28 02:14:54 -070027#define RTCMTLI420RendererClass NSClassFromString(@"RTCMTLI420Renderer")
kthelgason954d9b92017-03-09 03:36:58 -080028
denicija070d5e32017-02-26 11:44:13 -080029@interface RTCMTLVideoView () <MTKViewDelegate>
denicijad2088152017-04-28 02:14:54 -070030@property(nonatomic, strong) RTCMTLI420Renderer *rendererI420;
31@property(nonatomic, strong) RTCMTLNV12Renderer *rendererNV12;
denicija070d5e32017-02-26 11:44:13 -080032@property(nonatomic, strong) MTKView *metalView;
33@property(atomic, strong) RTCVideoFrame *videoFrame;
34@end
35
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +020036@implementation RTCMTLVideoView {
37 int64_t _lastFrameTimeNs;
JT Teh5c147252018-04-22 09:28:27 -070038 CGSize _videoFrameSize;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +020039}
denicija070d5e32017-02-26 11:44:13 -080040
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +020041@synthesize delegate = _delegate;
denicijad2088152017-04-28 02:14:54 -070042@synthesize rendererI420 = _rendererI420;
43@synthesize rendererNV12 = _rendererNV12;
denicija070d5e32017-02-26 11:44:13 -080044@synthesize metalView = _metalView;
45@synthesize videoFrame = _videoFrame;
46
47- (instancetype)initWithFrame:(CGRect)frameRect {
48 self = [super initWithFrame:frameRect];
49 if (self) {
50 [self configure];
51 }
52 return self;
53}
54
55- (instancetype)initWithCoder:(NSCoder *)aCoder {
56 self = [super initWithCoder:aCoder];
57 if (self) {
58 [self configure];
59 }
60 return self;
61}
62
63#pragma mark - Private
64
65+ (BOOL)isMetalAvailable {
kthelgasona2fb30c2017-03-09 03:34:27 -080066#if defined(RTC_SUPPORTS_METAL)
denicija070d5e32017-02-26 11:44:13 -080067 return YES;
kthelgasona2fb30c2017-03-09 03:34:27 -080068#else
denicija070d5e32017-02-26 11:44:13 -080069 return NO;
70#endif
71}
72
kthelgason954d9b92017-03-09 03:36:58 -080073+ (MTKView *)createMetalView:(CGRect)frame {
74 MTKView *view = [[MTKViewClass alloc] initWithFrame:frame];
75 return view;
76}
77
denicijad2088152017-04-28 02:14:54 -070078+ (RTCMTLNV12Renderer *)createNV12Renderer {
79 return [[RTCMTLNV12RendererClass alloc] init];
80}
81
82+ (RTCMTLI420Renderer *)createI420Renderer {
83 return [[RTCMTLI420RendererClass alloc] init];
kthelgason954d9b92017-03-09 03:36:58 -080084}
85
denicija070d5e32017-02-26 11:44:13 -080086- (void)configure {
denicijad2088152017-04-28 02:14:54 -070087 NSAssert([RTCMTLVideoView isMetalAvailable], @"Metal not availiable on this device");
kthelgason954d9b92017-03-09 03:36:58 -080088
89 _metalView = [RTCMTLVideoView createMetalView:self.bounds];
denicijad2088152017-04-28 02:14:54 -070090 [self configureMetalView];
kthelgason954d9b92017-03-09 03:36:58 -080091}
92
93- (void)configureMetalView {
94 if (_metalView) {
kthelgason96d91522017-03-08 06:33:52 -080095 _metalView.delegate = self;
kthelgason954d9b92017-03-09 03:36:58 -080096 [self addSubview:_metalView];
JT Tehd5601322018-04-09 23:48:41 +000097 _metalView.contentMode = UIViewContentModeScaleAspectFit;
JT Teh5c147252018-04-22 09:28:27 -070098 _videoFrameSize = CGSizeZero;
denicija070d5e32017-02-26 11:44:13 -080099 }
100}
kthelgason954d9b92017-03-09 03:36:58 -0800101
Kári Tristan Helgason4049a252018-04-20 17:33:32 +0200102- (void)setVideoContentMode:(UIViewContentMode)mode {
103 _metalView.contentMode = mode;
104}
105
Danielac4a14322017-10-31 11:19:38 +0100106#pragma mark - Private
107
108- (void)layoutSubviews {
109 [super layoutSubviews];
JT Teh5c147252018-04-22 09:28:27 -0700110 CGRect bounds = self.bounds;
111 _metalView.frame = bounds;
112 if (!CGSizeEqualToSize(_videoFrameSize, CGSizeZero)) {
113 _metalView.drawableSize = _videoFrameSize;
114 } else {
115 _metalView.drawableSize = bounds.size;
116 }
Danielac4a14322017-10-31 11:19:38 +0100117}
118
denicija070d5e32017-02-26 11:44:13 -0800119#pragma mark - MTKViewDelegate methods
120
121- (void)drawInMTKView:(nonnull MTKView *)view {
122 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200123 RTCVideoFrame *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200124 // Skip rendering if we've already rendered this frame.
125 if (!videoFrame || videoFrame.timeStampNs == _lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700126 return;
127 }
128
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200129 if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
JT Teh6144fa52018-04-10 00:27:04 +0000130 if (!self.rendererNV12) {
denicijad2088152017-04-28 02:14:54 -0700131 self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
132 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
133 self.rendererNV12 = nil;
134 RTCLogError(@"Failed to create NV12 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200135 return;
denicijad2088152017-04-28 02:14:54 -0700136 }
JT Teh6144fa52018-04-10 00:27:04 +0000137 }
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200138 [self.rendererNV12 drawFrame:videoFrame];
denicijad2088152017-04-28 02:14:54 -0700139 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000140 if (!self.rendererI420) {
denicijad2088152017-04-28 02:14:54 -0700141 self.rendererI420 = [RTCMTLVideoView createI420Renderer];
142 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
143 self.rendererI420 = nil;
144 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200145 return;
denicijad2088152017-04-28 02:14:54 -0700146 }
JT Teh6144fa52018-04-10 00:27:04 +0000147 }
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200148 [self.rendererI420 drawFrame:videoFrame];
denicijad2088152017-04-28 02:14:54 -0700149 }
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200150 _lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800151}
152
153- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
154}
155
156#pragma mark - RTCVideoRenderer
157
158- (void)setSize:(CGSize)size {
kthelgason954d9b92017-03-09 03:36:58 -0800159 self.metalView.drawableSize = size;
JT Teh5c147252018-04-22 09:28:27 -0700160 _videoFrameSize = size;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200161 dispatch_async(dispatch_get_main_queue(), ^{
162 [self.delegate videoView:self didChangeVideoSize:size];
163 });
denicija070d5e32017-02-26 11:44:13 -0800164}
165
166- (void)renderFrame:(nullable RTCVideoFrame *)frame {
167 if (frame == nil) {
168 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
169 return;
170 }
171 self.videoFrame = frame;
172}
173
174@end