OLD | NEW |
---|---|
1 /* | 1 /* |
2 * libjingle | 2 * libjingle |
3 * Copyright 2014 Google Inc. | 3 * Copyright 2014 Google Inc. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions are met: | 6 * modification, are permitted provided that the following conditions are met: |
7 * | 7 * |
8 * 1. Redistributions of source code must retain the above copyright notice, | 8 * 1. Redistributions of source code must retain the above copyright notice, |
9 * this list of conditions and the following disclaimer. | 9 * this list of conditions and the following disclaimer. |
10 * 2. Redistributions in binary form must reproduce the above copyright notice, | 10 * 2. Redistributions in binary form must reproduce the above copyright notice, |
(...skipping 74 matching lines...) Loading... | |
85 - (void)invalidate { | 85 - (void)invalidate { |
86 [_displayLink invalidate]; | 86 [_displayLink invalidate]; |
87 } | 87 } |
88 | 88 |
89 - (void)displayLinkDidFire:(CADisplayLink*)displayLink { | 89 - (void)displayLinkDidFire:(CADisplayLink*)displayLink { |
90 _timerHandler(); | 90 _timerHandler(); |
91 } | 91 } |
92 | 92 |
93 @end | 93 @end |
94 | 94 |
95 // RTCEAGLVideoView wraps a GLKView which is setup with | |
96 // enableSetNeedsDisplay = NO for the purpose of gaining control of | |
97 // exactly when to call -[GLKView display]. This need for extra | |
98 // control is required to avoid triggering method calls on GLKView | |
99 // that results in attempting to bind the underlying render buffer | |
100 // when the drawable size would be empty which would result in the | |
101 // error GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. -[GLKView display] is | |
102 // the method that will trigger the binding of the render | |
103 // buffer. Because the standard behaviour of -[UIView setNeedsDisplay] | |
104 // is disabled for the reasons above, the RTCEAGLVideoView maintains | |
105 // its own |isDirty| flag. | |
106 | |
95 @interface RTCEAGLVideoView () <GLKViewDelegate> | 107 @interface RTCEAGLVideoView () <GLKViewDelegate> |
96 // |i420Frame| is set when we receive a frame from a worker thread and is read | 108 // |i420Frame| is set when we receive a frame from a worker thread and is read |
97 // from the display link callback so atomicity is required. | 109 // from the display link callback so atomicity is required. |
98 @property(atomic, strong) RTCI420Frame* i420Frame; | 110 @property(atomic, strong) RTCI420Frame* i420Frame; |
99 @property(nonatomic, readonly) GLKView* glkView; | 111 @property(nonatomic, readonly) GLKView* glkView; |
100 @property(nonatomic, readonly) RTCOpenGLVideoRenderer* glRenderer; | 112 @property(nonatomic, readonly) RTCOpenGLVideoRenderer* glRenderer; |
101 @end | 113 @end |
102 | 114 |
103 @implementation RTCEAGLVideoView { | 115 @implementation RTCEAGLVideoView { |
104 RTCDisplayLinkTimer* _timer; | 116 RTCDisplayLinkTimer* _timer; |
105 GLKView* _glkView; | 117 GLKView* _glkView; |
106 RTCOpenGLVideoRenderer* _glRenderer; | 118 RTCOpenGLVideoRenderer* _glRenderer; |
119 BOOL _isDirty; // this flag should only be set and read on the main thread (e. g. by | |
magjed_webrtc
2015/09/21 11:44:10
Put this comment above the variable instead. Also
| |
120 // setNeedsDisplay) | |
107 } | 121 } |
108 | 122 |
109 - (instancetype)initWithFrame:(CGRect)frame { | 123 - (instancetype)initWithFrame:(CGRect)frame { |
110 if (self = [super initWithFrame:frame]) { | 124 if (self = [super initWithFrame:frame]) { |
111 [self configure]; | 125 [self configure]; |
112 } | 126 } |
113 return self; | 127 return self; |
114 } | 128 } |
115 | 129 |
116 - (instancetype)initWithCoder:(NSCoder *)aDecoder { | 130 - (instancetype)initWithCoder:(NSCoder *)aDecoder { |
117 if (self = [super initWithCoder:aDecoder]) { | 131 if (self = [super initWithCoder:aDecoder]) { |
118 [self configure]; | 132 [self configure]; |
119 } | 133 } |
120 return self; | 134 return self; |
121 } | 135 } |
122 | 136 |
123 - (void)configure { | 137 - (void)configure { |
124 EAGLContext* glContext = | 138 EAGLContext* glContext = |
125 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; | 139 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; |
126 if (!glContext) { | 140 if (!glContext) { |
127 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; | 141 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; |
128 } | 142 } |
129 _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext]; | 143 _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext]; |
130 | 144 |
131 // GLKView manages a framebuffer for us. | 145 // GLKView manages a framebuffer for us. |
132 _glkView = [[GLKView alloc] initWithFrame:CGRectZero | 146 _glkView = [[GLKView alloc] initWithFrame:CGRectZero |
magjed_webrtc
2015/09/21 11:44:10
Would it solve all problems to simply replace CGRe
| |
133 context:glContext]; | 147 context:glContext]; |
134 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; | 148 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; |
135 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone; | 149 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone; |
136 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone; | 150 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone; |
137 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone; | 151 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone; |
138 _glkView.delegate = self; | 152 _glkView.delegate = self; |
139 _glkView.layer.masksToBounds = YES; | 153 _glkView.layer.masksToBounds = YES; |
154 _glkView.enableSetNeedsDisplay = NO; | |
140 [self addSubview:_glkView]; | 155 [self addSubview:_glkView]; |
141 | 156 |
142 // Listen to application state in order to clean up OpenGL before app goes | 157 // Listen to application state in order to clean up OpenGL before app goes |
143 // away. | 158 // away. |
144 NSNotificationCenter* notificationCenter = | 159 NSNotificationCenter* notificationCenter = |
145 [NSNotificationCenter defaultCenter]; | 160 [NSNotificationCenter defaultCenter]; |
146 [notificationCenter addObserver:self | 161 [notificationCenter addObserver:self |
147 selector:@selector(willResignActive) | 162 selector:@selector(willResignActive) |
148 name:UIApplicationWillResignActiveNotification | 163 name:UIApplicationWillResignActiveNotification |
149 object:nil]; | 164 object:nil]; |
150 [notificationCenter addObserver:self | 165 [notificationCenter addObserver:self |
151 selector:@selector(didBecomeActive) | 166 selector:@selector(didBecomeActive) |
152 name:UIApplicationDidBecomeActiveNotification | 167 name:UIApplicationDidBecomeActiveNotification |
153 object:nil]; | 168 object:nil]; |
154 | 169 |
155 // Frames are received on a separate thread, so we poll for current frame | 170 // Frames are received on a separate thread, so we poll for current frame |
156 // using a refresh rate proportional to screen refresh frequency. This | 171 // using a refresh rate proportional to screen refresh frequency. This |
157 // occurs on the main thread. | 172 // occurs on the main thread. |
158 __weak RTCEAGLVideoView* weakSelf = self; | 173 __weak RTCEAGLVideoView* weakSelf = self; |
159 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{ | 174 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{ |
160 RTCEAGLVideoView* strongSelf = weakSelf; | 175 RTCEAGLVideoView* strongSelf = weakSelf; |
161 // Don't render if frame hasn't changed. | 176 [strongSelf displayLinkTimerDidFire]; |
magjed_webrtc
2015/09/21 11:44:10
What if you keep this code unchanged, remove the v
| |
162 if (strongSelf.glRenderer.lastDrawnFrame == strongSelf.i420Frame) { | |
163 return; | |
164 } | |
165 // This tells the GLKView that it's dirty, which will then call the | |
166 // GLKViewDelegate method implemented below. | |
167 [strongSelf.glkView setNeedsDisplay]; | |
168 }]; | 177 }]; |
169 [self setupGL]; | 178 [self setupGL]; |
170 } | 179 } |
171 | 180 |
172 - (void)dealloc { | 181 - (void)dealloc { |
173 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 182 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
174 UIApplicationState appState = | 183 UIApplicationState appState = |
175 [UIApplication sharedApplication].applicationState; | 184 [UIApplication sharedApplication].applicationState; |
176 if (appState == UIApplicationStateActive) { | 185 if (appState == UIApplicationStateActive) { |
177 [self teardownGL]; | 186 [self teardownGL]; |
178 } | 187 } |
179 [_timer invalidate]; | 188 [_timer invalidate]; |
180 } | 189 } |
181 | 190 |
182 #pragma mark - UIView | 191 #pragma mark - UIView |
183 | 192 |
193 - (void)setNeedsDisplay { | |
194 [super setNeedsDisplay]; | |
195 _isDirty = YES; | |
magjed_webrtc
2015/09/21 11:44:10
You still need to execute the code in displayLinkT
| |
196 } | |
197 | |
198 - (void)setNeedsDisplayInRect:(CGRect)rect { | |
199 [super setNeedsDisplayInRect:rect]; | |
200 _isDirty = YES; | |
201 } | |
202 | |
184 - (void)layoutSubviews { | 203 - (void)layoutSubviews { |
185 [super layoutSubviews]; | 204 [super layoutSubviews]; |
186 _glkView.frame = self.bounds; | 205 _glkView.frame = self.bounds; |
187 } | 206 } |
188 | 207 |
189 #pragma mark - GLKViewDelegate | 208 #pragma mark - GLKViewDelegate |
190 | 209 |
191 // This method is called when the GLKView's content is dirty and needs to be | 210 // This method is called when the GLKView's content is dirty and needs to be |
192 // redrawn. This occurs on main thread. | 211 // redrawn. This occurs on main thread. |
193 - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { | 212 - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { |
(...skipping 12 matching lines...) Loading... | |
206 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size]; | 225 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size]; |
207 }); | 226 }); |
208 } | 227 } |
209 | 228 |
210 - (void)renderFrame:(RTCI420Frame*)frame { | 229 - (void)renderFrame:(RTCI420Frame*)frame { |
211 self.i420Frame = frame; | 230 self.i420Frame = frame; |
212 } | 231 } |
213 | 232 |
214 #pragma mark - Private | 233 #pragma mark - Private |
215 | 234 |
235 - (void)displayLinkTimerDidFire { | |
236 // Don't render unless video frame have changed or the view content | |
237 // has explicitly been marked dirty. | |
238 if (!_isDirty && _glRenderer.lastDrawnFrame == self.i420Frame) { | |
239 return; | |
240 } | |
241 | |
242 // Always reset isDirty at this point, even if -[GLKView display] | |
243 // won't be called in the case the drawable size is empty. | |
244 _isDirty = NO; | |
245 | |
246 // Only call -[GLKView display] if the drawable size is | |
247 // non-empty. Calling display will make the GLKView setup its | |
248 // render buffer if necessary, but that will fail with error | |
249 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty. | |
250 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) { | |
251 [_glkView display]; | |
magjed_webrtc
2015/09/21 11:44:10
Is there any way to override the display call and
| |
252 } | |
253 } | |
254 | |
216 - (void)setupGL { | 255 - (void)setupGL { |
217 self.i420Frame = nil; | 256 self.i420Frame = nil; |
218 [_glRenderer setupGL]; | 257 [_glRenderer setupGL]; |
219 _timer.isPaused = NO; | 258 _timer.isPaused = NO; |
220 } | 259 } |
221 | 260 |
222 - (void)teardownGL { | 261 - (void)teardownGL { |
223 self.i420Frame = nil; | 262 self.i420Frame = nil; |
224 _timer.isPaused = YES; | 263 _timer.isPaused = YES; |
225 [_glkView deleteDrawable]; | 264 [_glkView deleteDrawable]; |
226 [_glRenderer teardownGL]; | 265 [_glRenderer teardownGL]; |
227 } | 266 } |
228 | 267 |
229 - (void)didBecomeActive { | 268 - (void)didBecomeActive { |
230 [self setupGL]; | 269 [self setupGL]; |
231 } | 270 } |
232 | 271 |
233 - (void)willResignActive { | 272 - (void)willResignActive { |
234 [self teardownGL]; | 273 [self teardownGL]; |
235 } | 274 } |
236 | 275 |
237 @end | 276 @end |
OLD | NEW |