RTCEAGLVideoView: Fix GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT error.
Fix an issue where using setNeedsDisplay on a GLKView which has a frame
with size zero will make GLKView/iOS output the following error:
Failed to bind EAGLDrawable: <CAEAGLLayer: 0x1742282e0> to
GL_RENDERBUFFER 1 Failed to make complete framebuffer object 8cd6
(The error code 8cd6 corresponds to
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT.)
GLKView will internally setup it's render buffer when the delegate is
about to draw into it. Previously when enableSetNeedsDisplay was set to
YES (default), then GLKView would still attempt to setup it's internal
buffer even if it's frame size is zero and that would cause
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT.
By using enableSetNeedsDisplay = NO, RTCEAGLVideoView can guard against
calling -[GLKView display] if it's current frame size is empty.
Review URL: https://codereview.webrtc.org/1347013002
Cr-Commit-Position: refs/heads/master@{#10076}
This commit is contained in:
1
AUTHORS
1
AUTHORS
@ -39,6 +39,7 @@ Intel Corporation
|
||||
MIPS Technologies
|
||||
Mozilla Foundation
|
||||
Opera Software ASA
|
||||
Sinch AB
|
||||
struktur AG
|
||||
Telenor Digital AS
|
||||
Temasys Communications
|
||||
|
||||
@ -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 () <GLKViewDelegate>
|
||||
// |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];
|
||||
|
||||
Reference in New Issue
Block a user