| 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 |