blob: 0ccbf9dd6f709befef8484c35eb36ef67c29073b [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
24// RTCEAGLVideoView wraps a GLKView which is setup with
25// 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]
33// is disabled for the reasons above, the RTCEAGLVideoView maintains
34// its own |isDirty| flag.
35
36@interface RTCEAGLVideoView () <GLKViewDelegate>
37// |videoFrame| is set when we receive a frame from a worker thread and is read
38// from the display link callback so atomicity is required.
39@property(atomic, strong) RTCVideoFrame *videoFrame;
40@property(nonatomic, readonly) GLKView *glkView;
Jon Hjellee799bad2016-01-11 13:47:11 -080041@end
42
43@implementation RTCEAGLVideoView {
44 RTCDisplayLinkTimer *_timer;
tkchin41a32872016-08-17 16:02:58 -070045 EAGLContext *_glContext;
Jon Hjellee799bad2016-01-11 13:47:11 -080046 // This flag should only be set and read on the main thread (e.g. by
47 // setNeedsDisplay)
48 BOOL _isDirty;
magjed13941912017-05-30 06:11:58 -070049 id<RTCVideoViewShading> _shader;
50 RTCNV12TextureCache *_nv12TextureCache;
51 RTCI420TextureCache *_i420TextureCache;
Maxim Pavlova72b7fc2018-04-10 16:57:43 +030052 // As timestamps should be unique between frames, will store last
53 // drawn frame timestamp instead of the whole frame to reduce memory usage.
54 int64_t _lastDrawnFrameTimeStampNs;
Jon Hjellee799bad2016-01-11 13:47:11 -080055}
56
57@synthesize delegate = _delegate;
58@synthesize videoFrame = _videoFrame;
59@synthesize glkView = _glkView;
Jon Hjellee799bad2016-01-11 13:47:11 -080060
61- (instancetype)initWithFrame:(CGRect)frame {
magjed13941912017-05-30 06:11:58 -070062 return [self initWithFrame:frame shader:[[RTCDefaultShader alloc] init]];
63}
64
65- (instancetype)initWithCoder:(NSCoder *)aDecoder {
66 return [self initWithCoder:aDecoder shader:[[RTCDefaultShader alloc] init]];
67}
68
69- (instancetype)initWithFrame:(CGRect)frame shader:(id<RTCVideoViewShading>)shader {
Jon Hjellee799bad2016-01-11 13:47:11 -080070 if (self = [super initWithFrame:frame]) {
magjed13941912017-05-30 06:11:58 -070071 _shader = shader;
magjed1c12b812017-07-31 09:11:46 -070072 if (![self configure]) {
73 return nil;
74 }
Jon Hjellee799bad2016-01-11 13:47:11 -080075 }
76 return self;
77}
78
magjed13941912017-05-30 06:11:58 -070079- (instancetype)initWithCoder:(NSCoder *)aDecoder shader:(id<RTCVideoViewShading>)shader {
Jon Hjellee799bad2016-01-11 13:47:11 -080080 if (self = [super initWithCoder:aDecoder]) {
magjed13941912017-05-30 06:11:58 -070081 _shader = shader;
magjed1c12b812017-07-31 09:11:46 -070082 if (![self configure]) {
83 return nil;
84 }
Jon Hjellee799bad2016-01-11 13:47:11 -080085 }
86 return self;
87}
88
magjed1c12b812017-07-31 09:11:46 -070089- (BOOL)configure {
Jon Hjellee799bad2016-01-11 13:47:11 -080090 EAGLContext *glContext =
91 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
92 if (!glContext) {
93 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
94 }
magjed1c12b812017-07-31 09:11:46 -070095 if (!glContext) {
96 RTCLogError(@"Failed to create EAGLContext");
97 return NO;
98 }
tkchin41a32872016-08-17 16:02:58 -070099 _glContext = glContext;
Jon Hjellee799bad2016-01-11 13:47:11 -0800100
101 // GLKView manages a framebuffer for us.
102 _glkView = [[GLKView alloc] initWithFrame:CGRectZero
tkchin41a32872016-08-17 16:02:58 -0700103 context:_glContext];
Jon Hjellee799bad2016-01-11 13:47:11 -0800104 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
105 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone;
106 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone;
107 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone;
108 _glkView.delegate = self;
109 _glkView.layer.masksToBounds = YES;
110 _glkView.enableSetNeedsDisplay = NO;
111 [self addSubview:_glkView];
112
113 // Listen to application state in order to clean up OpenGL before app goes
114 // away.
115 NSNotificationCenter *notificationCenter =
116 [NSNotificationCenter defaultCenter];
117 [notificationCenter addObserver:self
118 selector:@selector(willResignActive)
119 name:UIApplicationWillResignActiveNotification
120 object:nil];
121 [notificationCenter addObserver:self
122 selector:@selector(didBecomeActive)
123 name:UIApplicationDidBecomeActiveNotification
124 object:nil];
125
126 // Frames are received on a separate thread, so we poll for current frame
127 // using a refresh rate proportional to screen refresh frequency. This
128 // occurs on the main thread.
129 __weak RTCEAGLVideoView *weakSelf = self;
130 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{
131 RTCEAGLVideoView *strongSelf = weakSelf;
132 [strongSelf displayLinkTimerDidFire];
133 }];
Gustavo Garcia7281f922017-11-07 17:54:03 +0100134 if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) {
135 [self setupGL];
136 }
magjed1c12b812017-07-31 09:11:46 -0700137 return YES;
Jon Hjellee799bad2016-01-11 13:47:11 -0800138}
139
CZ Theng0ff7c022019-10-22 20:36:40 +0800140- (void)setMultipleTouchEnabled:(BOOL)multipleTouchEnabled {
141 [super setMultipleTouchEnabled:multipleTouchEnabled];
142 _glkView.multipleTouchEnabled = multipleTouchEnabled;
143}
144
Jon Hjellee799bad2016-01-11 13:47:11 -0800145- (void)dealloc {
146 [[NSNotificationCenter defaultCenter] removeObserver:self];
147 UIApplicationState appState =
148 [UIApplication sharedApplication].applicationState;
149 if (appState == UIApplicationStateActive) {
150 [self teardownGL];
151 }
152 [_timer invalidate];
Gustavo Garcia730add82018-01-04 02:45:38 +0100153 [self ensureGLContext];
154 _shader = nil;
tkchin41a32872016-08-17 16:02:58 -0700155 if (_glContext && [EAGLContext currentContext] == _glContext) {
156 [EAGLContext setCurrentContext:nil];
157 }
Jon Hjellee799bad2016-01-11 13:47:11 -0800158}
159
160#pragma mark - UIView
161
162- (void)setNeedsDisplay {
163 [super setNeedsDisplay];
164 _isDirty = YES;
165}
166
167- (void)setNeedsDisplayInRect:(CGRect)rect {
168 [super setNeedsDisplayInRect:rect];
169 _isDirty = YES;
170}
171
172- (void)layoutSubviews {
173 [super layoutSubviews];
174 _glkView.frame = self.bounds;
175}
176
177#pragma mark - GLKViewDelegate
178
179// This method is called when the GLKView's content is dirty and needs to be
180// redrawn. This occurs on main thread.
181- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
182 // The renderer will draw the frame to the framebuffer corresponding to the
183 // one used by |view|.
magjed2f7f9b82017-04-13 04:15:53 -0700184 RTCVideoFrame *frame = self.videoFrame;
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300185 if (!frame || frame.timeStampNs == _lastDrawnFrameTimeStampNs) {
magjed2f7f9b82017-04-13 04:15:53 -0700186 return;
187 }
188 [self ensureGLContext];
189 glClear(GL_COLOR_BUFFER_BIT);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200190 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
magjed13941912017-05-30 06:11:58 -0700191 if (!_nv12TextureCache) {
192 _nv12TextureCache = [[RTCNV12TextureCache alloc] initWithContext:_glContext];
magjed2f7f9b82017-04-13 04:15:53 -0700193 }
magjed13941912017-05-30 06:11:58 -0700194 if (_nv12TextureCache) {
195 [_nv12TextureCache uploadFrameToTextures:frame];
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200196 [_shader applyShadingForFrameWithWidth:frame.width
197 height:frame.height
198 rotation:frame.rotation
199 yPlane:_nv12TextureCache.yTexture
200 uvPlane:_nv12TextureCache.uvTexture];
magjed13941912017-05-30 06:11:58 -0700201 [_nv12TextureCache releaseTextures];
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300202
203 _lastDrawnFrameTimeStampNs = self.videoFrame.timeStampNs;
magjed2f7f9b82017-04-13 04:15:53 -0700204 }
magjed2f7f9b82017-04-13 04:15:53 -0700205 } else {
magjed13941912017-05-30 06:11:58 -0700206 if (!_i420TextureCache) {
207 _i420TextureCache = [[RTCI420TextureCache alloc] initWithContext:_glContext];
208 }
209 [_i420TextureCache uploadFrameToTextures:frame];
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200210 [_shader applyShadingForFrameWithWidth:frame.width
211 height:frame.height
212 rotation:frame.rotation
213 yPlane:_i420TextureCache.yTexture
214 uPlane:_i420TextureCache.uTexture
215 vPlane:_i420TextureCache.vTexture];
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300216
217 _lastDrawnFrameTimeStampNs = self.videoFrame.timeStampNs;
magjed2f7f9b82017-04-13 04:15:53 -0700218 }
Jon Hjellee799bad2016-01-11 13:47:11 -0800219}
220
221#pragma mark - RTCVideoRenderer
222
223// These methods may be called on non-main thread.
224- (void)setSize:(CGSize)size {
225 __weak RTCEAGLVideoView *weakSelf = self;
226 dispatch_async(dispatch_get_main_queue(), ^{
227 RTCEAGLVideoView *strongSelf = weakSelf;
228 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size];
229 });
230}
231
232- (void)renderFrame:(RTCVideoFrame *)frame {
Jon Hjellee799bad2016-01-11 13:47:11 -0800233 self.videoFrame = frame;
234}
235
236#pragma mark - Private
237
238- (void)displayLinkTimerDidFire {
239 // Don't render unless video frame have changed or the view content
240 // has explicitly been marked dirty.
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300241 if (!_isDirty && _lastDrawnFrameTimeStampNs == self.videoFrame.timeStampNs) {
Jon Hjellee799bad2016-01-11 13:47:11 -0800242 return;
243 }
244
245 // Always reset isDirty at this point, even if -[GLKView display]
246 // won't be called in the case the drawable size is empty.
247 _isDirty = NO;
248
249 // Only call -[GLKView display] if the drawable size is
250 // non-empty. Calling display will make the GLKView setup its
251 // render buffer if necessary, but that will fail with error
252 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty.
253 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) {
254 [_glkView display];
255 }
256}
257
258- (void)setupGL {
magjed2f7f9b82017-04-13 04:15:53 -0700259 [self ensureGLContext];
260 glDisable(GL_DITHER);
Jon Hjellee799bad2016-01-11 13:47:11 -0800261 _timer.isPaused = NO;
262}
263
264- (void)teardownGL {
265 self.videoFrame = nil;
266 _timer.isPaused = YES;
267 [_glkView deleteDrawable];
magjed2f7f9b82017-04-13 04:15:53 -0700268 [self ensureGLContext];
magjed13941912017-05-30 06:11:58 -0700269 _nv12TextureCache = nil;
270 _i420TextureCache = nil;
Jon Hjellee799bad2016-01-11 13:47:11 -0800271}
272
273- (void)didBecomeActive {
274 [self setupGL];
275}
276
277- (void)willResignActive {
278 [self teardownGL];
279}
280
magjed2f7f9b82017-04-13 04:15:53 -0700281- (void)ensureGLContext {
282 NSAssert(_glContext, @"context shouldn't be nil");
283 if ([EAGLContext currentContext] != _glContext) {
284 [EAGLContext setCurrentContext:_glContext];
285 }
286}
287
Jon Hjellee799bad2016-01-11 13:47:11 -0800288@end