blob: 6a01d48f323de985becb5dac6cc4610b226d695f [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;
CZ Theng41875aa2019-12-20 11:33:21 +080060@synthesize rotationOverride = _rotationOverride;
Jon Hjellee799bad2016-01-11 13:47:11 -080061
62- (instancetype)initWithFrame:(CGRect)frame {
magjed13941912017-05-30 06:11:58 -070063 return [self initWithFrame:frame shader:[[RTCDefaultShader alloc] init]];
64}
65
66- (instancetype)initWithCoder:(NSCoder *)aDecoder {
67 return [self initWithCoder:aDecoder shader:[[RTCDefaultShader alloc] init]];
68}
69
70- (instancetype)initWithFrame:(CGRect)frame shader:(id<RTCVideoViewShading>)shader {
Jon Hjellee799bad2016-01-11 13:47:11 -080071 if (self = [super initWithFrame:frame]) {
magjed13941912017-05-30 06:11:58 -070072 _shader = shader;
magjed1c12b812017-07-31 09:11:46 -070073 if (![self configure]) {
74 return nil;
75 }
Jon Hjellee799bad2016-01-11 13:47:11 -080076 }
77 return self;
78}
79
magjed13941912017-05-30 06:11:58 -070080- (instancetype)initWithCoder:(NSCoder *)aDecoder shader:(id<RTCVideoViewShading>)shader {
Jon Hjellee799bad2016-01-11 13:47:11 -080081 if (self = [super initWithCoder:aDecoder]) {
magjed13941912017-05-30 06:11:58 -070082 _shader = shader;
magjed1c12b812017-07-31 09:11:46 -070083 if (![self configure]) {
84 return nil;
85 }
Jon Hjellee799bad2016-01-11 13:47:11 -080086 }
87 return self;
88}
89
magjed1c12b812017-07-31 09:11:46 -070090- (BOOL)configure {
Jon Hjellee799bad2016-01-11 13:47:11 -080091 EAGLContext *glContext =
92 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
93 if (!glContext) {
94 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
95 }
magjed1c12b812017-07-31 09:11:46 -070096 if (!glContext) {
97 RTCLogError(@"Failed to create EAGLContext");
98 return NO;
99 }
tkchin41a32872016-08-17 16:02:58 -0700100 _glContext = glContext;
Jon Hjellee799bad2016-01-11 13:47:11 -0800101
102 // GLKView manages a framebuffer for us.
103 _glkView = [[GLKView alloc] initWithFrame:CGRectZero
tkchin41a32872016-08-17 16:02:58 -0700104 context:_glContext];
Jon Hjellee799bad2016-01-11 13:47:11 -0800105 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
106 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone;
107 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone;
108 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone;
109 _glkView.delegate = self;
110 _glkView.layer.masksToBounds = YES;
111 _glkView.enableSetNeedsDisplay = NO;
112 [self addSubview:_glkView];
113
114 // Listen to application state in order to clean up OpenGL before app goes
115 // away.
116 NSNotificationCenter *notificationCenter =
117 [NSNotificationCenter defaultCenter];
118 [notificationCenter addObserver:self
119 selector:@selector(willResignActive)
120 name:UIApplicationWillResignActiveNotification
121 object:nil];
122 [notificationCenter addObserver:self
123 selector:@selector(didBecomeActive)
124 name:UIApplicationDidBecomeActiveNotification
125 object:nil];
126
127 // Frames are received on a separate thread, so we poll for current frame
128 // using a refresh rate proportional to screen refresh frequency. This
129 // occurs on the main thread.
130 __weak RTCEAGLVideoView *weakSelf = self;
131 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{
132 RTCEAGLVideoView *strongSelf = weakSelf;
133 [strongSelf displayLinkTimerDidFire];
134 }];
Gustavo Garcia7281f922017-11-07 17:54:03 +0100135 if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) {
136 [self setupGL];
137 }
magjed1c12b812017-07-31 09:11:46 -0700138 return YES;
Jon Hjellee799bad2016-01-11 13:47:11 -0800139}
140
CZ Theng0ff7c022019-10-22 20:36:40 +0800141- (void)setMultipleTouchEnabled:(BOOL)multipleTouchEnabled {
142 [super setMultipleTouchEnabled:multipleTouchEnabled];
143 _glkView.multipleTouchEnabled = multipleTouchEnabled;
144}
145
Jon Hjellee799bad2016-01-11 13:47:11 -0800146- (void)dealloc {
147 [[NSNotificationCenter defaultCenter] removeObserver:self];
148 UIApplicationState appState =
149 [UIApplication sharedApplication].applicationState;
150 if (appState == UIApplicationStateActive) {
151 [self teardownGL];
152 }
153 [_timer invalidate];
Gustavo Garcia730add82018-01-04 02:45:38 +0100154 [self ensureGLContext];
155 _shader = nil;
tkchin41a32872016-08-17 16:02:58 -0700156 if (_glContext && [EAGLContext currentContext] == _glContext) {
157 [EAGLContext setCurrentContext:nil];
158 }
Jon Hjellee799bad2016-01-11 13:47:11 -0800159}
160
161#pragma mark - UIView
162
163- (void)setNeedsDisplay {
164 [super setNeedsDisplay];
165 _isDirty = YES;
166}
167
168- (void)setNeedsDisplayInRect:(CGRect)rect {
169 [super setNeedsDisplayInRect:rect];
170 _isDirty = YES;
171}
172
173- (void)layoutSubviews {
174 [super layoutSubviews];
175 _glkView.frame = self.bounds;
176}
177
178#pragma mark - GLKViewDelegate
179
180// This method is called when the GLKView's content is dirty and needs to be
181// redrawn. This occurs on main thread.
182- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
183 // The renderer will draw the frame to the framebuffer corresponding to the
184 // one used by |view|.
magjed2f7f9b82017-04-13 04:15:53 -0700185 RTCVideoFrame *frame = self.videoFrame;
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300186 if (!frame || frame.timeStampNs == _lastDrawnFrameTimeStampNs) {
magjed2f7f9b82017-04-13 04:15:53 -0700187 return;
188 }
CZ Theng41875aa2019-12-20 11:33:21 +0800189 RTCVideoRotation rotation = frame.rotation;
190 if(_rotationOverride != nil) {
191 [_rotationOverride getValue: &rotation];
192 }
magjed2f7f9b82017-04-13 04:15:53 -0700193 [self ensureGLContext];
194 glClear(GL_COLOR_BUFFER_BIT);
Anders Carlssone5960ce2017-06-22 15:26:30 +0200195 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
magjed13941912017-05-30 06:11:58 -0700196 if (!_nv12TextureCache) {
197 _nv12TextureCache = [[RTCNV12TextureCache alloc] initWithContext:_glContext];
magjed2f7f9b82017-04-13 04:15:53 -0700198 }
magjed13941912017-05-30 06:11:58 -0700199 if (_nv12TextureCache) {
200 [_nv12TextureCache uploadFrameToTextures:frame];
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200201 [_shader applyShadingForFrameWithWidth:frame.width
202 height:frame.height
CZ Theng41875aa2019-12-20 11:33:21 +0800203 rotation:rotation
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200204 yPlane:_nv12TextureCache.yTexture
205 uvPlane:_nv12TextureCache.uvTexture];
magjed13941912017-05-30 06:11:58 -0700206 [_nv12TextureCache releaseTextures];
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300207
208 _lastDrawnFrameTimeStampNs = self.videoFrame.timeStampNs;
magjed2f7f9b82017-04-13 04:15:53 -0700209 }
magjed2f7f9b82017-04-13 04:15:53 -0700210 } else {
magjed13941912017-05-30 06:11:58 -0700211 if (!_i420TextureCache) {
212 _i420TextureCache = [[RTCI420TextureCache alloc] initWithContext:_glContext];
213 }
214 [_i420TextureCache uploadFrameToTextures:frame];
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200215 [_shader applyShadingForFrameWithWidth:frame.width
216 height:frame.height
CZ Theng41875aa2019-12-20 11:33:21 +0800217 rotation:rotation
Magnus Jedvert6b9653e2017-06-05 17:58:34 +0200218 yPlane:_i420TextureCache.yTexture
219 uPlane:_i420TextureCache.uTexture
220 vPlane:_i420TextureCache.vTexture];
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300221
222 _lastDrawnFrameTimeStampNs = self.videoFrame.timeStampNs;
magjed2f7f9b82017-04-13 04:15:53 -0700223 }
Jon Hjellee799bad2016-01-11 13:47:11 -0800224}
225
226#pragma mark - RTCVideoRenderer
227
228// These methods may be called on non-main thread.
229- (void)setSize:(CGSize)size {
230 __weak RTCEAGLVideoView *weakSelf = self;
231 dispatch_async(dispatch_get_main_queue(), ^{
232 RTCEAGLVideoView *strongSelf = weakSelf;
233 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size];
234 });
235}
236
237- (void)renderFrame:(RTCVideoFrame *)frame {
Jon Hjellee799bad2016-01-11 13:47:11 -0800238 self.videoFrame = frame;
239}
240
241#pragma mark - Private
242
243- (void)displayLinkTimerDidFire {
244 // Don't render unless video frame have changed or the view content
245 // has explicitly been marked dirty.
Maxim Pavlova72b7fc2018-04-10 16:57:43 +0300246 if (!_isDirty && _lastDrawnFrameTimeStampNs == self.videoFrame.timeStampNs) {
Jon Hjellee799bad2016-01-11 13:47:11 -0800247 return;
248 }
249
250 // Always reset isDirty at this point, even if -[GLKView display]
251 // won't be called in the case the drawable size is empty.
252 _isDirty = NO;
253
254 // Only call -[GLKView display] if the drawable size is
255 // non-empty. Calling display will make the GLKView setup its
256 // render buffer if necessary, but that will fail with error
257 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty.
258 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) {
259 [_glkView display];
260 }
261}
262
263- (void)setupGL {
magjed2f7f9b82017-04-13 04:15:53 -0700264 [self ensureGLContext];
265 glDisable(GL_DITHER);
Jon Hjellee799bad2016-01-11 13:47:11 -0800266 _timer.isPaused = NO;
267}
268
269- (void)teardownGL {
270 self.videoFrame = nil;
271 _timer.isPaused = YES;
272 [_glkView deleteDrawable];
magjed2f7f9b82017-04-13 04:15:53 -0700273 [self ensureGLContext];
magjed13941912017-05-30 06:11:58 -0700274 _nv12TextureCache = nil;
275 _i420TextureCache = nil;
Jon Hjellee799bad2016-01-11 13:47:11 -0800276}
277
278- (void)didBecomeActive {
279 [self setupGL];
280}
281
282- (void)willResignActive {
283 [self teardownGL];
284}
285
magjed2f7f9b82017-04-13 04:15:53 -0700286- (void)ensureGLContext {
287 NSAssert(_glContext, @"context shouldn't be nil");
288 if ([EAGLContext currentContext] != _glContext) {
289 [EAGLContext setCurrentContext:_glContext];
290 }
291}
292
Jon Hjellee799bad2016-01-11 13:47:11 -0800293@end