blob: 662e4c61ee54c66d49fdf30c9d98cd99cfaee2c3 [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
140- (void)dealloc {
141 [[NSNotificationCenter defaultCenter] removeObserver:self];
142 UIApplicationState appState =
143 [UIApplication sharedApplication].applicationState;
144 if (appState == UIApplicationStateActive) {
145 [self teardownGL];
146 }
147 [_timer invalidate];
Gustavo Garcia730add82018-01-04 02:45:38 +0100148 [self ensureGLContext];
149 _shader = nil;
tkchin41a32872016-08-17 16:02:58 -0700150 if (_glContext && [EAGLContext currentContext] == _glContext) {
151 [EAGLContext setCurrentContext:nil];
152 }
Jon Hjellee799bad2016-01-11 13:47:11 -0800153}
154
155#pragma mark - UIView
156
157- (void)setNeedsDisplay {
158 [super setNeedsDisplay];
159 _isDirty = YES;
160}
161
162- (void)setNeedsDisplayInRect:(CGRect)rect {
163 [super setNeedsDisplayInRect:rect];
164 _isDirty = YES;
165}
166
167- (void)layoutSubviews {
168 [super layoutSubviews];
169 _glkView.frame = self.bounds;
170}
171
172#pragma mark - GLKViewDelegate
173
174// This method is called when the GLKView's content is dirty and needs to be
175// redrawn. This occurs on main thread.
176- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
177 // The renderer will draw the frame to the framebuffer corresponding to the
178 // one used by |view|.
magjed2f7f9b82017-04-13 04:15:53 -0700179 RTCVideoFrame *frame = self.videoFrame;
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300180 if (!frame || frame.timeStampNs == _lastDrawnFrameTimeStampNs) {
magjed2f7f9b82017-04-13 04:15:53 -0700181 return;
182 }
183 [self ensureGLContext];
184 glClear(GL_COLOR_BUFFER_BIT);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200185 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
magjed13941912017-05-30 06:11:58 -0700186 if (!_nv12TextureCache) {
187 _nv12TextureCache = [[RTCNV12TextureCache alloc] initWithContext:_glContext];
magjed2f7f9b82017-04-13 04:15:53 -0700188 }
magjed13941912017-05-30 06:11:58 -0700189 if (_nv12TextureCache) {
190 [_nv12TextureCache uploadFrameToTextures:frame];
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200191 [_shader applyShadingForFrameWithWidth:frame.width
192 height:frame.height
193 rotation:frame.rotation
194 yPlane:_nv12TextureCache.yTexture
195 uvPlane:_nv12TextureCache.uvTexture];
magjed13941912017-05-30 06:11:58 -0700196 [_nv12TextureCache releaseTextures];
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300197
198 _lastDrawnFrameTimeStampNs = self.videoFrame.timeStampNs;
magjed2f7f9b82017-04-13 04:15:53 -0700199 }
magjed2f7f9b82017-04-13 04:15:53 -0700200 } else {
magjed13941912017-05-30 06:11:58 -0700201 if (!_i420TextureCache) {
202 _i420TextureCache = [[RTCI420TextureCache alloc] initWithContext:_glContext];
203 }
204 [_i420TextureCache uploadFrameToTextures:frame];
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200205 [_shader applyShadingForFrameWithWidth:frame.width
206 height:frame.height
207 rotation:frame.rotation
208 yPlane:_i420TextureCache.yTexture
209 uPlane:_i420TextureCache.uTexture
210 vPlane:_i420TextureCache.vTexture];
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300211
212 _lastDrawnFrameTimeStampNs = self.videoFrame.timeStampNs;
magjed2f7f9b82017-04-13 04:15:53 -0700213 }
Jon Hjellee799bad2016-01-11 13:47:11 -0800214}
215
216#pragma mark - RTCVideoRenderer
217
218// These methods may be called on non-main thread.
219- (void)setSize:(CGSize)size {
220 __weak RTCEAGLVideoView *weakSelf = self;
221 dispatch_async(dispatch_get_main_queue(), ^{
222 RTCEAGLVideoView *strongSelf = weakSelf;
223 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size];
224 });
225}
226
227- (void)renderFrame:(RTCVideoFrame *)frame {
Jon Hjellee799bad2016-01-11 13:47:11 -0800228 self.videoFrame = frame;
229}
230
231#pragma mark - Private
232
233- (void)displayLinkTimerDidFire {
234 // Don't render unless video frame have changed or the view content
235 // has explicitly been marked dirty.
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300236 if (!_isDirty && _lastDrawnFrameTimeStampNs == self.videoFrame.timeStampNs) {
Jon Hjellee799bad2016-01-11 13:47:11 -0800237 return;
238 }
239
240 // Always reset isDirty at this point, even if -[GLKView display]
241 // won't be called in the case the drawable size is empty.
242 _isDirty = NO;
243
244 // Only call -[GLKView display] if the drawable size is
245 // non-empty. Calling display will make the GLKView setup its
246 // render buffer if necessary, but that will fail with error
247 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty.
248 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) {
249 [_glkView display];
250 }
251}
252
253- (void)setupGL {
254 self.videoFrame = nil;
magjed2f7f9b82017-04-13 04:15:53 -0700255 [self ensureGLContext];
256 glDisable(GL_DITHER);
Jon Hjellee799bad2016-01-11 13:47:11 -0800257 _timer.isPaused = NO;
258}
259
260- (void)teardownGL {
261 self.videoFrame = nil;
262 _timer.isPaused = YES;
263 [_glkView deleteDrawable];
magjed2f7f9b82017-04-13 04:15:53 -0700264 [self ensureGLContext];
magjed13941912017-05-30 06:11:58 -0700265 _nv12TextureCache = nil;
266 _i420TextureCache = nil;
Jon Hjellee799bad2016-01-11 13:47:11 -0800267}
268
269- (void)didBecomeActive {
270 [self setupGL];
271}
272
273- (void)willResignActive {
274 [self teardownGL];
275}
276
magjed2f7f9b82017-04-13 04:15:53 -0700277- (void)ensureGLContext {
278 NSAssert(_glContext, @"context shouldn't be nil");
279 if ([EAGLContext currentContext] != _glContext) {
280 [EAGLContext setCurrentContext:_glContext];
281 }
282}
283
Jon Hjellee799bad2016-01-11 13:47:11 -0800284@end