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...) Expand 10 before | Expand all | Expand 10 after 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 // This flag should only be set and read on the main thread (e.g. by |
| 120 // setNeedsDisplay) |
| 121 BOOL _isDirty; |
107 } | 122 } |
108 | 123 |
109 - (instancetype)initWithFrame:(CGRect)frame { | 124 - (instancetype)initWithFrame:(CGRect)frame { |
110 if (self = [super initWithFrame:frame]) { | 125 if (self = [super initWithFrame:frame]) { |
111 [self configure]; | 126 [self configure]; |
112 } | 127 } |
113 return self; | 128 return self; |
114 } | 129 } |
115 | 130 |
116 - (instancetype)initWithCoder:(NSCoder *)aDecoder { | 131 - (instancetype)initWithCoder:(NSCoder *)aDecoder { |
(...skipping 13 matching lines...) Expand all Loading... |
130 | 145 |
131 // GLKView manages a framebuffer for us. | 146 // GLKView manages a framebuffer for us. |
132 _glkView = [[GLKView alloc] initWithFrame:CGRectZero | 147 _glkView = [[GLKView alloc] initWithFrame:CGRectZero |
133 context:glContext]; | 148 context:glContext]; |
134 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; | 149 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; |
135 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone; | 150 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone; |
136 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone; | 151 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone; |
137 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone; | 152 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone; |
138 _glkView.delegate = self; | 153 _glkView.delegate = self; |
139 _glkView.layer.masksToBounds = YES; | 154 _glkView.layer.masksToBounds = YES; |
| 155 _glkView.enableSetNeedsDisplay = NO; |
140 [self addSubview:_glkView]; | 156 [self addSubview:_glkView]; |
141 | 157 |
142 // Listen to application state in order to clean up OpenGL before app goes | 158 // Listen to application state in order to clean up OpenGL before app goes |
143 // away. | 159 // away. |
144 NSNotificationCenter* notificationCenter = | 160 NSNotificationCenter* notificationCenter = |
145 [NSNotificationCenter defaultCenter]; | 161 [NSNotificationCenter defaultCenter]; |
146 [notificationCenter addObserver:self | 162 [notificationCenter addObserver:self |
147 selector:@selector(willResignActive) | 163 selector:@selector(willResignActive) |
148 name:UIApplicationWillResignActiveNotification | 164 name:UIApplicationWillResignActiveNotification |
149 object:nil]; | 165 object:nil]; |
150 [notificationCenter addObserver:self | 166 [notificationCenter addObserver:self |
151 selector:@selector(didBecomeActive) | 167 selector:@selector(didBecomeActive) |
152 name:UIApplicationDidBecomeActiveNotification | 168 name:UIApplicationDidBecomeActiveNotification |
153 object:nil]; | 169 object:nil]; |
154 | 170 |
155 // Frames are received on a separate thread, so we poll for current frame | 171 // Frames are received on a separate thread, so we poll for current frame |
156 // using a refresh rate proportional to screen refresh frequency. This | 172 // using a refresh rate proportional to screen refresh frequency. This |
157 // occurs on the main thread. | 173 // occurs on the main thread. |
158 __weak RTCEAGLVideoView* weakSelf = self; | 174 __weak RTCEAGLVideoView* weakSelf = self; |
159 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{ | 175 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{ |
160 RTCEAGLVideoView* strongSelf = weakSelf; | 176 RTCEAGLVideoView* strongSelf = weakSelf; |
161 // Don't render if frame hasn't changed. | 177 [strongSelf displayLinkTimerDidFire]; |
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 }]; | 178 }]; |
169 [self setupGL]; | 179 [self setupGL]; |
170 } | 180 } |
171 | 181 |
172 - (void)dealloc { | 182 - (void)dealloc { |
173 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 183 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
174 UIApplicationState appState = | 184 UIApplicationState appState = |
175 [UIApplication sharedApplication].applicationState; | 185 [UIApplication sharedApplication].applicationState; |
176 if (appState == UIApplicationStateActive) { | 186 if (appState == UIApplicationStateActive) { |
177 [self teardownGL]; | 187 [self teardownGL]; |
178 } | 188 } |
179 [_timer invalidate]; | 189 [_timer invalidate]; |
180 } | 190 } |
181 | 191 |
182 #pragma mark - UIView | 192 #pragma mark - UIView |
183 | 193 |
| 194 - (void)setNeedsDisplay { |
| 195 [super setNeedsDisplay]; |
| 196 _isDirty = YES; |
| 197 } |
| 198 |
| 199 - (void)setNeedsDisplayInRect:(CGRect)rect { |
| 200 [super setNeedsDisplayInRect:rect]; |
| 201 _isDirty = YES; |
| 202 } |
| 203 |
184 - (void)layoutSubviews { | 204 - (void)layoutSubviews { |
185 [super layoutSubviews]; | 205 [super layoutSubviews]; |
186 _glkView.frame = self.bounds; | 206 _glkView.frame = self.bounds; |
187 } | 207 } |
188 | 208 |
189 #pragma mark - GLKViewDelegate | 209 #pragma mark - GLKViewDelegate |
190 | 210 |
191 // This method is called when the GLKView's content is dirty and needs to be | 211 // This method is called when the GLKView's content is dirty and needs to be |
192 // redrawn. This occurs on main thread. | 212 // redrawn. This occurs on main thread. |
193 - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { | 213 - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { |
(...skipping 12 matching lines...) Expand all Loading... |
206 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size]; | 226 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size]; |
207 }); | 227 }); |
208 } | 228 } |
209 | 229 |
210 - (void)renderFrame:(RTCI420Frame*)frame { | 230 - (void)renderFrame:(RTCI420Frame*)frame { |
211 self.i420Frame = frame; | 231 self.i420Frame = frame; |
212 } | 232 } |
213 | 233 |
214 #pragma mark - Private | 234 #pragma mark - Private |
215 | 235 |
| 236 - (void)displayLinkTimerDidFire { |
| 237 // Don't render unless video frame have changed or the view content |
| 238 // has explicitly been marked dirty. |
| 239 if (!_isDirty && _glRenderer.lastDrawnFrame == self.i420Frame) { |
| 240 return; |
| 241 } |
| 242 |
| 243 // Always reset isDirty at this point, even if -[GLKView display] |
| 244 // won't be called in the case the drawable size is empty. |
| 245 _isDirty = NO; |
| 246 |
| 247 // Only call -[GLKView display] if the drawable size is |
| 248 // non-empty. Calling display will make the GLKView setup its |
| 249 // render buffer if necessary, but that will fail with error |
| 250 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty. |
| 251 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) { |
| 252 [_glkView display]; |
| 253 } |
| 254 } |
| 255 |
216 - (void)setupGL { | 256 - (void)setupGL { |
217 self.i420Frame = nil; | 257 self.i420Frame = nil; |
218 [_glRenderer setupGL]; | 258 [_glRenderer setupGL]; |
219 _timer.isPaused = NO; | 259 _timer.isPaused = NO; |
220 } | 260 } |
221 | 261 |
222 - (void)teardownGL { | 262 - (void)teardownGL { |
223 self.i420Frame = nil; | 263 self.i420Frame = nil; |
224 _timer.isPaused = YES; | 264 _timer.isPaused = YES; |
225 [_glkView deleteDrawable]; | 265 [_glkView deleteDrawable]; |
226 [_glRenderer teardownGL]; | 266 [_glRenderer teardownGL]; |
227 } | 267 } |
228 | 268 |
229 - (void)didBecomeActive { | 269 - (void)didBecomeActive { |
230 [self setupGL]; | 270 [self setupGL]; |
231 } | 271 } |
232 | 272 |
233 - (void)willResignActive { | 273 - (void)willResignActive { |
234 [self teardownGL]; | 274 [self teardownGL]; |
235 } | 275 } |
236 | 276 |
237 @end | 277 @end |
OLD | NEW |