blob: 08594c8d9095b6cb67832a271345a0d36f078ebb [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>
denicijad2088152017-04-28 02:14:54 -070032@property(nonatomic, strong) RTCMTLI420Renderer *rendererI420;
33@property(nonatomic, strong) RTCMTLNV12Renderer *rendererNV12;
Peter Hanspers1c62b982018-05-03 14:06:04 +020034@property(nonatomic, strong) RTCMTLRGBRenderer *rendererRGB;
denicija070d5e32017-02-26 11:44:13 -080035@property(nonatomic, strong) MTKView *metalView;
36@property(atomic, strong) RTCVideoFrame *videoFrame;
37@end
38
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +020039@implementation RTCMTLVideoView {
40 int64_t _lastFrameTimeNs;
JT Teh5c147252018-04-22 09:28:27 -070041 CGSize _videoFrameSize;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +020042}
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;
50
51- (instancetype)initWithFrame:(CGRect)frameRect {
52 self = [super initWithFrame:frameRect];
53 if (self) {
54 [self configure];
55 }
56 return self;
57}
58
59- (instancetype)initWithCoder:(NSCoder *)aCoder {
60 self = [super initWithCoder:aCoder];
61 if (self) {
62 [self configure];
63 }
64 return self;
65}
66
67#pragma mark - Private
68
69+ (BOOL)isMetalAvailable {
kthelgasona2fb30c2017-03-09 03:34:27 -080070#if defined(RTC_SUPPORTS_METAL)
Kári Tristan Helgasonbe806812018-04-18 17:04:02 +020071 return MTLCreateSystemDefaultDevice() != nil;
kthelgasona2fb30c2017-03-09 03:34:27 -080072#else
denicija070d5e32017-02-26 11:44:13 -080073 return NO;
74#endif
75}
76
kthelgason954d9b92017-03-09 03:36:58 -080077+ (MTKView *)createMetalView:(CGRect)frame {
78 MTKView *view = [[MTKViewClass alloc] initWithFrame:frame];
79 return view;
80}
81
denicijad2088152017-04-28 02:14:54 -070082+ (RTCMTLNV12Renderer *)createNV12Renderer {
83 return [[RTCMTLNV12RendererClass alloc] init];
84}
85
86+ (RTCMTLI420Renderer *)createI420Renderer {
87 return [[RTCMTLI420RendererClass alloc] init];
kthelgason954d9b92017-03-09 03:36:58 -080088}
89
Peter Hanspers1c62b982018-05-03 14:06:04 +020090+ (RTCMTLRGBRenderer *)createRGBRenderer {
91 return [[RTCMTLRGBRenderer alloc] init];
92}
93
denicija070d5e32017-02-26 11:44:13 -080094- (void)configure {
denicijad2088152017-04-28 02:14:54 -070095 NSAssert([RTCMTLVideoView isMetalAvailable], @"Metal not availiable on this device");
kthelgason954d9b92017-03-09 03:36:58 -080096
97 _metalView = [RTCMTLVideoView createMetalView:self.bounds];
denicijad2088152017-04-28 02:14:54 -070098 [self configureMetalView];
kthelgason954d9b92017-03-09 03:36:58 -080099}
100
101- (void)configureMetalView {
102 if (_metalView) {
kthelgason96d91522017-03-08 06:33:52 -0800103 _metalView.delegate = self;
kthelgason954d9b92017-03-09 03:36:58 -0800104 [self addSubview:_metalView];
JT Tehd5601322018-04-09 23:48:41 +0000105 _metalView.contentMode = UIViewContentModeScaleAspectFit;
JT Teh5c147252018-04-22 09:28:27 -0700106 _videoFrameSize = CGSizeZero;
denicija070d5e32017-02-26 11:44:13 -0800107 }
108}
kthelgason954d9b92017-03-09 03:36:58 -0800109
Kári Tristan Helgason4049a252018-04-20 17:33:32 +0200110- (void)setVideoContentMode:(UIViewContentMode)mode {
111 _metalView.contentMode = mode;
112}
113
Danielac4a14322017-10-31 11:19:38 +0100114#pragma mark - Private
115
116- (void)layoutSubviews {
117 [super layoutSubviews];
JT Teh5c147252018-04-22 09:28:27 -0700118 CGRect bounds = self.bounds;
119 _metalView.frame = bounds;
120 if (!CGSizeEqualToSize(_videoFrameSize, CGSizeZero)) {
121 _metalView.drawableSize = _videoFrameSize;
122 } else {
123 _metalView.drawableSize = bounds.size;
124 }
Danielac4a14322017-10-31 11:19:38 +0100125}
126
denicija070d5e32017-02-26 11:44:13 -0800127#pragma mark - MTKViewDelegate methods
128
129- (void)drawInMTKView:(nonnull MTKView *)view {
130 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200131 RTCVideoFrame *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200132 // Skip rendering if we've already rendered this frame.
133 if (!videoFrame || videoFrame.timeStampNs == _lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700134 return;
135 }
136
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200137 if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
Peter Hanspers1c62b982018-05-03 14:06:04 +0200138 RTCCVPixelBuffer *buffer = (RTCCVPixelBuffer*)videoFrame.buffer;
139 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
140 if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
141 if (!self.rendererRGB) {
142 self.rendererRGB = [RTCMTLVideoView createRGBRenderer];
143 if (![self.rendererRGB addRenderingDestination:self.metalView]) {
144 self.rendererRGB = nil;
145 RTCLogError(@"Failed to create RGB renderer");
146 return;
147 }
denicijad2088152017-04-28 02:14:54 -0700148 }
Peter Hanspers1c62b982018-05-03 14:06:04 +0200149 [self.rendererRGB drawFrame:videoFrame];
150 } else {
151 if (!self.rendererNV12) {
152 self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
153 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
154 self.rendererNV12 = nil;
155 RTCLogError(@"Failed to create NV12 renderer");
156 return;
157 }
158 }
159 [self.rendererNV12 drawFrame:videoFrame];
JT Teh6144fa52018-04-10 00:27:04 +0000160 }
denicijad2088152017-04-28 02:14:54 -0700161 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000162 if (!self.rendererI420) {
denicijad2088152017-04-28 02:14:54 -0700163 self.rendererI420 = [RTCMTLVideoView createI420Renderer];
164 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
165 self.rendererI420 = nil;
166 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200167 return;
denicijad2088152017-04-28 02:14:54 -0700168 }
JT Teh6144fa52018-04-10 00:27:04 +0000169 }
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200170 [self.rendererI420 drawFrame:videoFrame];
denicijad2088152017-04-28 02:14:54 -0700171 }
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200172 _lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800173}
174
175- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
176}
177
178#pragma mark - RTCVideoRenderer
179
180- (void)setSize:(CGSize)size {
kthelgason954d9b92017-03-09 03:36:58 -0800181 self.metalView.drawableSize = size;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200182 dispatch_async(dispatch_get_main_queue(), ^{
JT Tehe61125c2018-04-24 08:53:09 -0700183 _videoFrameSize = size;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200184 [self.delegate videoView:self didChangeVideoSize:size];
185 });
denicija070d5e32017-02-26 11:44:13 -0800186}
187
188- (void)renderFrame:(nullable RTCVideoFrame *)frame {
189 if (frame == nil) {
190 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
191 return;
192 }
193 self.videoFrame = frame;
194}
195
196@end