blob: a3435a781571e2815c67c4067c2724dfdc64a082 [file] [log] [blame]
Jon Hjellee799bad2016-01-11 13:47:11 -08001/*
2 * Copyright 2015 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 "RTCEAGLVideoView.h"
Jon Hjellee799bad2016-01-11 13:47:11 -080012
13#import <GLKit/GLKit.h>
14
magjed13941912017-05-30 06:11:58 -070015#import "RTCDefaultShader.h"
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020016#import "RTCDisplayLinkTimer.h"
magjed13941912017-05-30 06:11:58 -070017#import "RTCI420TextureCache.h"
18#import "RTCNV12TextureCache.h"
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020019#import "base/RTCLogging.h"
20#import "base/RTCVideoFrame.h"
21#import "base/RTCVideoFrameBuffer.h"
22#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
Jon Hjellee799bad2016-01-11 13:47:11 -080023
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020024// RTC_OBJC_TYPE(RTCEAGLVideoView) wraps a GLKView which is setup with
Jon Hjellee799bad2016-01-11 13:47:11 -080025// enableSetNeedsDisplay = NO for the purpose of gaining control of
26// exactly when to call -[GLKView display]. This need for extra
27// control is required to avoid triggering method calls on GLKView
28// that results in attempting to bind the underlying render buffer
29// when the drawable size would be empty which would result in the
30// error GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. -[GLKView display] is
31// the method that will trigger the binding of the render
32// buffer. Because the standard behaviour of -[UIView setNeedsDisplay]
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020033// is disabled for the reasons above, the RTC_OBJC_TYPE(RTCEAGLVideoView) maintains
Jon Hjellee799bad2016-01-11 13:47:11 -080034// its own |isDirty| flag.
35
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020036@interface RTC_OBJC_TYPE (RTCEAGLVideoView)
37()<GLKViewDelegate>
38 // |videoFrame| is set when we receive a frame from a worker thread and is read
39 // from the display link callback so atomicity is required.
40 @property(atomic, strong) RTC_OBJC_TYPE(RTCVideoFrame) * videoFrame;
Jon Hjellee799bad2016-01-11 13:47:11 -080041@property(nonatomic, readonly) GLKView *glkView;
Jon Hjellee799bad2016-01-11 13:47:11 -080042@end
43
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020044@implementation RTC_OBJC_TYPE (RTCEAGLVideoView) {
Jon Hjellee799bad2016-01-11 13:47:11 -080045 RTCDisplayLinkTimer *_timer;
tkchin41a32872016-08-17 16:02:58 -070046 EAGLContext *_glContext;
Jon Hjellee799bad2016-01-11 13:47:11 -080047 // This flag should only be set and read on the main thread (e.g. by
48 // setNeedsDisplay)
49 BOOL _isDirty;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020050 id<RTC_OBJC_TYPE(RTCVideoViewShading)> _shader;
magjed13941912017-05-30 06:11:58 -070051 RTCNV12TextureCache *_nv12TextureCache;
52 RTCI420TextureCache *_i420TextureCache;
Maxim Pavlova72b7fc2018-04-10 16:57:43 +030053 // As timestamps should be unique between frames, will store last
54 // drawn frame timestamp instead of the whole frame to reduce memory usage.
55 int64_t _lastDrawnFrameTimeStampNs;
Jon Hjellee799bad2016-01-11 13:47:11 -080056}
57
58@synthesize delegate = _delegate;
59@synthesize videoFrame = _videoFrame;
60@synthesize glkView = _glkView;
CZ Theng41875aa2019-12-20 11:33:21 +080061@synthesize rotationOverride = _rotationOverride;
Jon Hjellee799bad2016-01-11 13:47:11 -080062
63- (instancetype)initWithFrame:(CGRect)frame {
magjed13941912017-05-30 06:11:58 -070064 return [self initWithFrame:frame shader:[[RTCDefaultShader alloc] init]];
65}
66
67- (instancetype)initWithCoder:(NSCoder *)aDecoder {
68 return [self initWithCoder:aDecoder shader:[[RTCDefaultShader alloc] init]];
69}
70
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020071- (instancetype)initWithFrame:(CGRect)frame shader:(id<RTC_OBJC_TYPE(RTCVideoViewShading)>)shader {
Jon Hjellee799bad2016-01-11 13:47:11 -080072 if (self = [super initWithFrame:frame]) {
magjed13941912017-05-30 06:11:58 -070073 _shader = shader;
magjed1c12b812017-07-31 09:11:46 -070074 if (![self configure]) {
75 return nil;
76 }
Jon Hjellee799bad2016-01-11 13:47:11 -080077 }
78 return self;
79}
80
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020081- (instancetype)initWithCoder:(NSCoder *)aDecoder
82 shader:(id<RTC_OBJC_TYPE(RTCVideoViewShading)>)shader {
Jon Hjellee799bad2016-01-11 13:47:11 -080083 if (self = [super initWithCoder:aDecoder]) {
magjed13941912017-05-30 06:11:58 -070084 _shader = shader;
magjed1c12b812017-07-31 09:11:46 -070085 if (![self configure]) {
86 return nil;
87 }
Jon Hjellee799bad2016-01-11 13:47:11 -080088 }
89 return self;
90}
91
magjed1c12b812017-07-31 09:11:46 -070092- (BOOL)configure {
Jon Hjellee799bad2016-01-11 13:47:11 -080093 EAGLContext *glContext =
94 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
95 if (!glContext) {
96 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
97 }
magjed1c12b812017-07-31 09:11:46 -070098 if (!glContext) {
99 RTCLogError(@"Failed to create EAGLContext");
100 return NO;
101 }
tkchin41a32872016-08-17 16:02:58 -0700102 _glContext = glContext;
Jon Hjellee799bad2016-01-11 13:47:11 -0800103
104 // GLKView manages a framebuffer for us.
105 _glkView = [[GLKView alloc] initWithFrame:CGRectZero
tkchin41a32872016-08-17 16:02:58 -0700106 context:_glContext];
Jon Hjellee799bad2016-01-11 13:47:11 -0800107 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
108 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone;
109 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone;
110 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone;
111 _glkView.delegate = self;
112 _glkView.layer.masksToBounds = YES;
113 _glkView.enableSetNeedsDisplay = NO;
114 [self addSubview:_glkView];
115
116 // Listen to application state in order to clean up OpenGL before app goes
117 // away.
118 NSNotificationCenter *notificationCenter =
119 [NSNotificationCenter defaultCenter];
120 [notificationCenter addObserver:self
121 selector:@selector(willResignActive)
122 name:UIApplicationWillResignActiveNotification
123 object:nil];
124 [notificationCenter addObserver:self
125 selector:@selector(didBecomeActive)
126 name:UIApplicationDidBecomeActiveNotification
127 object:nil];
128
129 // Frames are received on a separate thread, so we poll for current frame
130 // using a refresh rate proportional to screen refresh frequency. This
131 // occurs on the main thread.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200132 __weak RTC_OBJC_TYPE(RTCEAGLVideoView) *weakSelf = self;
Jon Hjellee799bad2016-01-11 13:47:11 -0800133 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200134 RTC_OBJC_TYPE(RTCEAGLVideoView) *strongSelf = weakSelf;
135 [strongSelf displayLinkTimerDidFire];
136 }];
Gustavo Garcia7281f922017-11-07 17:54:03 +0100137 if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) {
138 [self setupGL];
139 }
magjed1c12b812017-07-31 09:11:46 -0700140 return YES;
Jon Hjellee799bad2016-01-11 13:47:11 -0800141}
142
CZ Theng0ff7c022019-10-22 20:36:40 +0800143- (void)setMultipleTouchEnabled:(BOOL)multipleTouchEnabled {
144 [super setMultipleTouchEnabled:multipleTouchEnabled];
145 _glkView.multipleTouchEnabled = multipleTouchEnabled;
146}
147
Jon Hjellee799bad2016-01-11 13:47:11 -0800148- (void)dealloc {
149 [[NSNotificationCenter defaultCenter] removeObserver:self];
150 UIApplicationState appState =
151 [UIApplication sharedApplication].applicationState;
152 if (appState == UIApplicationStateActive) {
153 [self teardownGL];
154 }
155 [_timer invalidate];
Gustavo Garcia730add82018-01-04 02:45:38 +0100156 [self ensureGLContext];
157 _shader = nil;
tkchin41a32872016-08-17 16:02:58 -0700158 if (_glContext && [EAGLContext currentContext] == _glContext) {
159 [EAGLContext setCurrentContext:nil];
160 }
Jon Hjellee799bad2016-01-11 13:47:11 -0800161}
162
163#pragma mark - UIView
164
165- (void)setNeedsDisplay {
166 [super setNeedsDisplay];
167 _isDirty = YES;
168}
169
170- (void)setNeedsDisplayInRect:(CGRect)rect {
171 [super setNeedsDisplayInRect:rect];
172 _isDirty = YES;
173}
174
175- (void)layoutSubviews {
176 [super layoutSubviews];
177 _glkView.frame = self.bounds;
178}
179
180#pragma mark - GLKViewDelegate
181
182// This method is called when the GLKView's content is dirty and needs to be
183// redrawn. This occurs on main thread.
184- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
185 // The renderer will draw the frame to the framebuffer corresponding to the
186 // one used by |view|.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200187 RTC_OBJC_TYPE(RTCVideoFrame) *frame = self.videoFrame;
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300188 if (!frame || frame.timeStampNs == _lastDrawnFrameTimeStampNs) {
magjed2f7f9b82017-04-13 04:15:53 -0700189 return;
190 }
CZ Theng41875aa2019-12-20 11:33:21 +0800191 RTCVideoRotation rotation = frame.rotation;
192 if(_rotationOverride != nil) {
193 [_rotationOverride getValue: &rotation];
194 }
magjed2f7f9b82017-04-13 04:15:53 -0700195 [self ensureGLContext];
196 glClear(GL_COLOR_BUFFER_BIT);
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200197 if ([frame.buffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]) {
magjed13941912017-05-30 06:11:58 -0700198 if (!_nv12TextureCache) {
199 _nv12TextureCache = [[RTCNV12TextureCache alloc] initWithContext:_glContext];
magjed2f7f9b82017-04-13 04:15:53 -0700200 }
magjed13941912017-05-30 06:11:58 -0700201 if (_nv12TextureCache) {
202 [_nv12TextureCache uploadFrameToTextures:frame];
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200203 [_shader applyShadingForFrameWithWidth:frame.width
204 height:frame.height
CZ Theng41875aa2019-12-20 11:33:21 +0800205 rotation:rotation
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200206 yPlane:_nv12TextureCache.yTexture
207 uvPlane:_nv12TextureCache.uvTexture];
magjed13941912017-05-30 06:11:58 -0700208 [_nv12TextureCache releaseTextures];
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300209
210 _lastDrawnFrameTimeStampNs = self.videoFrame.timeStampNs;
magjed2f7f9b82017-04-13 04:15:53 -0700211 }
magjed2f7f9b82017-04-13 04:15:53 -0700212 } else {
magjed13941912017-05-30 06:11:58 -0700213 if (!_i420TextureCache) {
214 _i420TextureCache = [[RTCI420TextureCache alloc] initWithContext:_glContext];
215 }
216 [_i420TextureCache uploadFrameToTextures:frame];
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200217 [_shader applyShadingForFrameWithWidth:frame.width
218 height:frame.height
CZ Theng41875aa2019-12-20 11:33:21 +0800219 rotation:rotation
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200220 yPlane:_i420TextureCache.yTexture
221 uPlane:_i420TextureCache.uTexture
222 vPlane:_i420TextureCache.vTexture];
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300223
224 _lastDrawnFrameTimeStampNs = self.videoFrame.timeStampNs;
magjed2f7f9b82017-04-13 04:15:53 -0700225 }
Jon Hjellee799bad2016-01-11 13:47:11 -0800226}
227
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200228#pragma mark - RTC_OBJC_TYPE(RTCVideoRenderer)
Jon Hjellee799bad2016-01-11 13:47:11 -0800229
230// These methods may be called on non-main thread.
231- (void)setSize:(CGSize)size {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200232 __weak RTC_OBJC_TYPE(RTCEAGLVideoView) *weakSelf = self;
Jon Hjellee799bad2016-01-11 13:47:11 -0800233 dispatch_async(dispatch_get_main_queue(), ^{
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200234 RTC_OBJC_TYPE(RTCEAGLVideoView) *strongSelf = weakSelf;
Jon Hjellee799bad2016-01-11 13:47:11 -0800235 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size];
236 });
237}
238
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200239- (void)renderFrame:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame {
Jon Hjellee799bad2016-01-11 13:47:11 -0800240 self.videoFrame = frame;
241}
242
243#pragma mark - Private
244
245- (void)displayLinkTimerDidFire {
246 // Don't render unless video frame have changed or the view content
247 // has explicitly been marked dirty.
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300248 if (!_isDirty && _lastDrawnFrameTimeStampNs == self.videoFrame.timeStampNs) {
Jon Hjellee799bad2016-01-11 13:47:11 -0800249 return;
250 }
251
252 // Always reset isDirty at this point, even if -[GLKView display]
253 // won't be called in the case the drawable size is empty.
254 _isDirty = NO;
255
256 // Only call -[GLKView display] if the drawable size is
257 // non-empty. Calling display will make the GLKView setup its
258 // render buffer if necessary, but that will fail with error
259 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty.
260 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) {
261 [_glkView display];
262 }
263}
264
265- (void)setupGL {
magjed2f7f9b82017-04-13 04:15:53 -0700266 [self ensureGLContext];
267 glDisable(GL_DITHER);
Jon Hjellee799bad2016-01-11 13:47:11 -0800268 _timer.isPaused = NO;
269}
270
271- (void)teardownGL {
272 self.videoFrame = nil;
273 _timer.isPaused = YES;
274 [_glkView deleteDrawable];
magjed2f7f9b82017-04-13 04:15:53 -0700275 [self ensureGLContext];
magjed13941912017-05-30 06:11:58 -0700276 _nv12TextureCache = nil;
277 _i420TextureCache = nil;
Jon Hjellee799bad2016-01-11 13:47:11 -0800278}
279
280- (void)didBecomeActive {
281 [self setupGL];
282}
283
284- (void)willResignActive {
285 [self teardownGL];
286}
287
magjed2f7f9b82017-04-13 04:15:53 -0700288- (void)ensureGLContext {
289 NSAssert(_glContext, @"context shouldn't be nil");
290 if ([EAGLContext currentContext] != _glContext) {
291 [EAGLContext setCurrentContext:_glContext];
292 }
293}
294
Jon Hjellee799bad2016-01-11 13:47:11 -0800295@end