Commit 282049cc authored by ccameron's avatar ccameron Committed by Commit bot

Workaround to prevent crashes when destroying CGL contexts

Observation 1: It was discovered in crbug.com/410402 that deleting
the GL textures bound to IOSurfaces before releasing the CGL context
from the CAOpenGLLayer can result in crashes.

Observation 2: It was discovered in crbug.com/411782 that not deleting
the GL textures bounds to IOSurfaces before destroying the CGL context
can result in crashes.

As way speculative workaround for this, retain the CGL context when the
texture is created (when it is known to be valid), and the post a task to
- make that CGL context current
- delete the GL texture
- make no CGL context current
- release the CGL context
- release the IOSurface used by the GL texture

The theory here is that by doing this in a posted task, having a fresh stack
and having manually retained the CGL context when it was known-valid
will work around the crash in Observation 1, and manually destroying the
texture while its IOSurface is still retained and before the CGL context is
destroyed will work around the crash in Observation 2.

BUG=411782

Review URL: https://codereview.chromium.org/558803002

Cr-Commit-Position: refs/heads/master@{#294275}
parent b306b1e6
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "ui/gfx/size.h" #include "ui/gfx/size.h"
#include "ui/gl/scoped_cgl.h"
@class IOSurfaceLayer; @class IOSurfaceLayer;
...@@ -66,10 +67,12 @@ class IOSurfaceLayerClient { ...@@ -66,10 +67,12 @@ class IOSurfaceLayerClient {
// size of |io_surface_|. // size of |io_surface_|.
gfx::Size frame_pixel_size_; gfx::Size frame_pixel_size_;
// The GL texture that is bound to |io_surface_|. If |io_surface_| changes, // The GL texture that was bound to |io_surface_| during the last draw call,
// then this is marked as dirty by setting |io_surface_texture_dirty_|. // along with the context that was done in, and the value of |io_surface_| at
// the time of the bind.
GLuint io_surface_texture_; GLuint io_surface_texture_;
bool io_surface_texture_dirty_; base::ScopedTypeRef<CGLContextObj> io_surface_texture_context_;
base::ScopedCFTypeRef<IOSurfaceRef> io_surface_texture_io_surface_;
// The CGL renderer ID, captured at draw time. // The CGL renderer ID, captured at draw time.
GLint cgl_renderer_id_; GLint cgl_renderer_id_;
......
...@@ -27,20 +27,42 @@ ...@@ -27,20 +27,42 @@
LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \ LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error: " << gl_error; \
} while (0) } while (0)
// Helper function for logging error codes.
namespace { namespace {
// Helper function for logging error codes.
template<typename T> template<typename T>
std::string to_string(T value) { std::string to_string(T value) {
std::ostringstream stream; std::ostringstream stream;
stream << value; stream << value;
return stream.str(); return stream.str();
} }
// Helper function posted to destroy textures on a clean stack.
void DestroyTexture(
base::ScopedTypeRef<CGLContextObj> context,
base::ScopedCFTypeRef<IOSurfaceRef> io_surface,
GLuint texture) {
// Destroy the texture before tearing down the context, and while the
// IOSurface is still being kept around.
CGLSetCurrentContext(context);
glDeleteTextures(1, &texture);
CGLSetCurrentContext(NULL);
// Release (and likely destroy) the context.
context.reset();
// Finally release the IOSurface.
io_surface.reset();
}
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// IOSurfaceLayer(Private) // IOSurfaceLayer(Private)
@interface IOSurfaceLayer(Private) @interface IOSurfaceLayer(Private)
// Reset the texture and post a task to clean it up.
- (void)resetTextureAndPostDestroy;
// Force a draw immediately, but only if one was requested. // Force a draw immediately, but only if one was requested.
- (void)displayIfNeededAndAck; - (void)displayIfNeededAndAck;
...@@ -113,7 +135,6 @@ void IOSurfaceLayerHelper::TimerFired() { ...@@ -113,7 +135,6 @@ void IOSurfaceLayerHelper::TimerFired() {
did_not_draw_counter_ = 0; did_not_draw_counter_ = 0;
is_pumping_frames_ = false; is_pumping_frames_ = false;
io_surface_texture_ = 0; io_surface_texture_ = 0;
io_surface_texture_dirty_ = false;
cgl_renderer_id_ = 0; cgl_renderer_id_ = 0;
[self setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)]; [self setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
...@@ -170,8 +191,8 @@ void IOSurfaceLayerHelper::TimerFired() { ...@@ -170,8 +191,8 @@ void IOSurfaceLayerHelper::TimerFired() {
// GL texture needs to bind to the new surface. // GL texture needs to bind to the new surface.
if (!io_surface_ || io_surface_id != IOSurfaceGetID(io_surface_)) { if (!io_surface_ || io_surface_id != IOSurfaceGetID(io_surface_)) {
io_surface_.reset(IOSurfaceLookup(io_surface_id)); io_surface_.reset(IOSurfaceLookup(io_surface_id));
io_surface_texture_dirty_ = true;
if (!io_surface_) { if (!io_surface_) {
[self resetTextureAndPostDestroy];
content::GpuDataManagerImpl::GetInstance()->AddLogMessage( content::GpuDataManagerImpl::GetInstance()->AddLogMessage(
logging::LOG_ERROR, logging::LOG_ERROR,
"IOSurfaceLayer", "IOSurfaceLayer",
...@@ -306,9 +327,7 @@ void IOSurfaceLayerHelper::TimerFired() { ...@@ -306,9 +327,7 @@ void IOSurfaceLayerHelper::TimerFired() {
- (void)releaseCGLContext:(CGLContextObj)glContext { - (void)releaseCGLContext:(CGLContextObj)glContext {
// The GL context is being destroyed, so mark the resources as needing to be // The GL context is being destroyed, so mark the resources as needing to be
// recreated. // recreated.
io_surface_texture_ = 0; [self resetTextureAndPostDestroy];
io_surface_texture_dirty_ = true;
cgl_renderer_id_ = 0;
[super releaseCGLContext:glContext]; [super releaseCGLContext:glContext];
} }
...@@ -325,6 +344,8 @@ void IOSurfaceLayerHelper::TimerFired() { ...@@ -325,6 +344,8 @@ void IOSurfaceLayerHelper::TimerFired() {
// Create the texture if it has not been created in this context yet. // Create the texture if it has not been created in this context yet.
if (!io_surface_texture_) { if (!io_surface_texture_) {
DCHECK(!io_surface_texture_io_surface_);
DCHECK(!io_surface_texture_context_);
glGenTextures(1, &io_surface_texture_); glGenTextures(1, &io_surface_texture_);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, io_surface_texture_); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, io_surface_texture_);
glTexParameteri( glTexParameteri(
...@@ -332,12 +353,13 @@ void IOSurfaceLayerHelper::TimerFired() { ...@@ -332,12 +353,13 @@ void IOSurfaceLayerHelper::TimerFired() {
glTexParameteri( glTexParameteri(
GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
io_surface_texture_dirty_ = true; io_surface_texture_context_.reset(glContext, base::scoped_policy::RETAIN);
} }
DCHECK(io_surface_texture_context_ == glContext);
// Associate the IOSurface with this texture, if the underlying IOSurface has // Associate the IOSurface with this texture, if the underlying IOSurface has
// been changed. // been changed.
if (io_surface_texture_dirty_ && io_surface_) { if (io_surface_ != io_surface_texture_io_surface_) {
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, io_surface_texture_); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, io_surface_texture_);
CGLError cgl_error = CGLTexImageIOSurface2D( CGLError cgl_error = CGLTexImageIOSurface2D(
glContext, glContext,
...@@ -350,20 +372,20 @@ void IOSurfaceLayerHelper::TimerFired() { ...@@ -350,20 +372,20 @@ void IOSurfaceLayerHelper::TimerFired() {
io_surface_.get(), io_surface_.get(),
0 /* plane */); 0 /* plane */);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
if (cgl_error != kCGLNoError) { if (cgl_error == kCGLNoError) {
io_surface_texture_io_surface_ = io_surface_;
} else {
[self resetTextureAndPostDestroy];
content::GpuDataManagerImpl::GetInstance()->AddLogMessage( content::GpuDataManagerImpl::GetInstance()->AddLogMessage(
logging::LOG_ERROR, logging::LOG_ERROR,
"IOSurfaceLayer", "IOSurfaceLayer",
std::string("CGLTexImageIOSurface2D failed with CGL error ") + std::string("CGLTexImageIOSurface2D failed with CGL error ") +
to_string(cgl_error)); to_string(cgl_error));
glDeleteTextures(1, &io_surface_texture_);
io_surface_texture_ = 0;
if (client_) if (client_)
client_->IOSurfaceLayerHitError(); client_->IOSurfaceLayerHitError();
} }
} else if (io_surface_texture_) { } else if (!io_surface_) {
glDeleteTextures(1, &io_surface_texture_); [self resetTextureAndPostDestroy];
io_surface_texture_ = 0;
} }
// Fill the viewport with the texture. The viewport must be smaller or equal // Fill the viewport with the texture. The viewport must be smaller or equal
...@@ -452,4 +474,26 @@ void IOSurfaceLayerHelper::TimerFired() { ...@@ -452,4 +474,26 @@ void IOSurfaceLayerHelper::TimerFired() {
[self ackPendingFrame]; [self ackPendingFrame];
} }
- (void)resetTextureAndPostDestroy {
if (!io_surface_texture_) {
DCHECK(!io_surface_texture_context_);
DCHECK(!io_surface_texture_io_surface_);
return;
}
// Post a task to clean up the texture on a clean stack.
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&DestroyTexture,
io_surface_texture_context_,
io_surface_texture_io_surface_,
io_surface_texture_));
// Reset the texture state.
io_surface_texture_ = 0;
io_surface_texture_context_.reset();
io_surface_texture_io_surface_.reset();
cgl_renderer_id_ = 0;
}
@end @end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment