diff --git a/AUTHORS b/AUTHORS index 2398b88b44..0d9ff5abd2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,6 +39,7 @@ Intel Corporation MIPS Technologies Mozilla Foundation Opera Software ASA +Sinch AB struktur AG Telenor Digital AS Temasys Communications diff --git a/talk/app/webrtc/objc/RTCEAGLVideoView.m b/talk/app/webrtc/objc/RTCEAGLVideoView.m index 61040ba102..d19462c9d9 100644 --- a/talk/app/webrtc/objc/RTCEAGLVideoView.m +++ b/talk/app/webrtc/objc/RTCEAGLVideoView.m @@ -92,6 +92,18 @@ @end +// RTCEAGLVideoView wraps a GLKView which is setup with +// enableSetNeedsDisplay = NO for the purpose of gaining control of +// exactly when to call -[GLKView display]. This need for extra +// control is required to avoid triggering method calls on GLKView +// that results in attempting to bind the underlying render buffer +// when the drawable size would be empty which would result in the +// error GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. -[GLKView display] is +// the method that will trigger the binding of the render +// buffer. Because the standard behaviour of -[UIView setNeedsDisplay] +// is disabled for the reasons above, the RTCEAGLVideoView maintains +// its own |isDirty| flag. + @interface RTCEAGLVideoView () // |i420Frame| is set when we receive a frame from a worker thread and is read // from the display link callback so atomicity is required. @@ -104,6 +116,9 @@ RTCDisplayLinkTimer* _timer; GLKView* _glkView; RTCOpenGLVideoRenderer* _glRenderer; + // This flag should only be set and read on the main thread (e.g. by + // setNeedsDisplay) + BOOL _isDirty; } - (instancetype)initWithFrame:(CGRect)frame { @@ -137,6 +152,7 @@ _glkView.drawableMultisample = GLKViewDrawableMultisampleNone; _glkView.delegate = self; _glkView.layer.masksToBounds = YES; + _glkView.enableSetNeedsDisplay = NO; [self addSubview:_glkView]; // Listen to application state in order to clean up OpenGL before app goes @@ -158,13 +174,7 @@ __weak RTCEAGLVideoView* weakSelf = self; _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{ RTCEAGLVideoView* strongSelf = weakSelf; - // Don't render if frame hasn't changed. - if (strongSelf.glRenderer.lastDrawnFrame == strongSelf.i420Frame) { - return; - } - // This tells the GLKView that it's dirty, which will then call the - // GLKViewDelegate method implemented below. - [strongSelf.glkView setNeedsDisplay]; + [strongSelf displayLinkTimerDidFire]; }]; [self setupGL]; } @@ -181,6 +191,16 @@ #pragma mark - UIView +- (void)setNeedsDisplay { + [super setNeedsDisplay]; + _isDirty = YES; +} + +- (void)setNeedsDisplayInRect:(CGRect)rect { + [super setNeedsDisplayInRect:rect]; + _isDirty = YES; +} + - (void)layoutSubviews { [super layoutSubviews]; _glkView.frame = self.bounds; @@ -213,6 +233,26 @@ #pragma mark - Private +- (void)displayLinkTimerDidFire { + // Don't render unless video frame have changed or the view content + // has explicitly been marked dirty. + if (!_isDirty && _glRenderer.lastDrawnFrame == self.i420Frame) { + return; + } + + // Always reset isDirty at this point, even if -[GLKView display] + // won't be called in the case the drawable size is empty. + _isDirty = NO; + + // Only call -[GLKView display] if the drawable size is + // non-empty. Calling display will make the GLKView setup its + // render buffer if necessary, but that will fail with error + // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty. + if (self.bounds.size.width > 0 && self.bounds.size.height > 0) { + [_glkView display]; + } +} + - (void)setupGL { self.i420Frame = nil; [_glRenderer setupGL];