0

Implement asynchronous operation for RWHVP::CopyFromCompositingSurface on Mac

Current implementation blocks the UI thread for a long period of time. 
This change uses asynchronous operation to copy the frame buffer.

I have tested this manually with two cases:

1. Thumbnail generation

Tested thumbnails are generated successfully with --force-compositing-mode.
I have also verified that CopyTo() now completes almost instantaneously while
FinishCopy() completes in a relatively short time. Total time that UI thread
is blocked is about 1/4 of previous implementation.

2. Resource destruction

Manually tested that if CompositingIOSurface is destroyed before
asynchronous copy is finished then GL resources associated with the copy
is destroyed.

BUG=145587


Review URL: https://chromiumcodereview.appspot.com/10917307

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@158395 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
hclam@chromium.org
2012-09-24 21:32:41 +00:00
parent 712c3daa6d
commit a02f64e4b9
3 changed files with 212 additions and 51 deletions

@ -9,12 +9,14 @@
#import <QuartzCore/CVDisplayLink.h>
#include <QuartzCore/QuartzCore.h>
#include "base/callback.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/memory/scoped_nsobject.h"
#include "base/synchronization/lock.h"
#include "base/time.h"
#include "base/timer.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
class IOSurfaceSupport;
@ -49,9 +51,12 @@ class CompositingIOSurfaceMac {
// |src_pixel_subrect| and |dst_pixel_size| are not in DIP but in pixel.
// Caller must ensure that |out| is allocated with the size no less than
// |4 * dst_pixel_size.width() * dst_pixel_size.height()| bytes.
bool CopyTo(const gfx::Rect& src_pixel_subrect,
// |callback| is invoked when the operation is completed or failed.
// Do no call this method again before |callback| is invoked.
void CopyTo(const gfx::Rect& src_pixel_subrect,
const gfx::Size& dst_pixel_size,
void* out);
void* out,
const base::Callback<void(bool)>& callback);
// Unref the IOSurface and delete the associated GL texture. If the GPU
// process is no longer referencing it, this will delete the IOSurface.
@ -143,6 +148,36 @@ class CompositingIOSurfaceMac {
SurfaceVertex verts_[4];
};
// Keeps track of states and buffers for asynchronous readback of IOSurface.
struct CopyContext {
CopyContext();
~CopyContext();
void Reset() {
started = false;
cycles_elapsed = 0;
frame_buffer = 0;
frame_buffer_texture = 0;
pixel_buffer = 0;
use_fence = false;
fence = 0;
out_buf = NULL;
callback.Reset();
}
bool started;
int cycles_elapsed;
GLuint frame_buffer;
GLuint frame_buffer_texture;
GLuint pixel_buffer;
bool use_fence;
GLuint fence;
gfx::Rect src_rect;
gfx::Size dest_size;
void* out_buf;
base::Callback<void(bool)> callback;
};
CompositingIOSurfaceMac(IOSurfaceSupport* io_surface_support,
NSOpenGLContext* glContext,
CGLContextObj cglContext,
@ -172,6 +207,9 @@ class CompositingIOSurfaceMac {
void StartOrContinueDisplayLink();
void StopDisplayLink();
void FinishCopy();
void CleanupResourcesForCopy();
// Cached pointer to IOSurfaceSupport Singleton.
IOSurfaceSupport* io_surface_support_;
@ -194,6 +232,11 @@ class CompositingIOSurfaceMac {
// with it.
GLuint texture_;
CopyContext copy_context_;
// Timer for finishing a copy operation.
base::RepeatingTimer<CompositingIOSurfaceMac> copy_timer_;
// Shader parameters.
GLuint shader_program_blit_rgb_;
GLint blit_rgb_sampler_location_;

@ -9,6 +9,7 @@
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/message_loop.h"
#include "base/threading/platform_thread.h"
#include "content/common/content_constants_internal.h"
#include "content/public/browser/browser_thread.h"
@ -105,6 +106,19 @@ GLuint CreateProgramGLSL(const char* vertex_shader_str,
return program;
}
bool HasAppleFenceExtension() {
static bool initialized_has_fence = false;
static bool has_fence = false;
if (!initialized_has_fence) {
has_fence =
strstr(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)),
"GL_APPLE_fence") != NULL;
initialized_has_fence = true;
}
return has_fence;
}
} // namespace
CVReturn DisplayLinkCallback(CVDisplayLinkRef display_link,
@ -119,6 +133,13 @@ CVReturn DisplayLinkCallback(CVDisplayLinkRef display_link,
return kCVReturnSuccess;
}
CompositingIOSurfaceMac::CopyContext::CopyContext() {
Reset();
}
CompositingIOSurfaceMac::CopyContext::~CopyContext() {
}
CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create() {
TRACE_EVENT0("browser", "CompositingIOSurfaceMac::Create");
IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
@ -269,7 +290,10 @@ void CompositingIOSurfaceMac::GetVSyncParameters(base::TimeTicks* timebase,
CompositingIOSurfaceMac::~CompositingIOSurfaceMac() {
CVDisplayLinkRelease(display_link_);
UnrefIOSurface();
CGLSetCurrentContext(cglContext_);
CleanupResourcesForCopy();
UnrefIOSurfaceWithContextCurrent();
CGLSetCurrentContext(0);
}
void CompositingIOSurfaceMac::SetIOSurface(uint64 io_surface_handle,
@ -385,79 +409,120 @@ void CompositingIOSurfaceMac::DrawIOSurface(NSView* view, float scale_factor) {
RateLimitDraws();
}
bool CompositingIOSurfaceMac::CopyTo(
void CompositingIOSurfaceMac::CopyTo(
const gfx::Rect& src_pixel_subrect,
const gfx::Size& dst_pixel_size,
void* out) {
if (!MapIOSurfaceToTexture(io_surface_handle_))
return false;
void* out,
const base::Callback<void(bool)>& callback) {
if (copy_context_.started) {
callback.Run(false);
return;
}
CGLSetCurrentContext(cglContext_);
GLuint target = GL_TEXTURE_RECTANGLE_ARB;
if (!MapIOSurfaceToTexture(io_surface_handle_)) {
CGLSetCurrentContext(0);
callback.Run(false);
return;
}
GLuint dst_texture = 0;
glGenTextures(1, &dst_texture); CHECK_GL_ERROR();
glBindTexture(target, dst_texture); CHECK_GL_ERROR();
TRACE_EVENT0("browser", "CompositingIOSurfaceMac::CopyTo()");
GLuint dst_framebuffer = 0;
glGenFramebuffersEXT(1, &dst_framebuffer); CHECK_GL_ERROR();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dst_framebuffer); CHECK_GL_ERROR();
copy_context_.started = true;
copy_context_.src_rect = src_pixel_subrect;
copy_context_.dest_size = dst_pixel_size;
copy_context_.out_buf = out;
copy_context_.callback = callback;
glTexImage2D(target,
const bool use_fence = HasAppleFenceExtension();
if (use_fence) {
glGenFencesAPPLE(1, &copy_context_.fence); CHECK_GL_ERROR();
copy_context_.use_fence = true;
copy_context_.cycles_elapsed = 0;
}
// Create an offscreen framebuffer.
// This is used to render and scale a subrect of IOSurface.
const GLenum kTarget = GL_TEXTURE_RECTANGLE_ARB;
const int dest_width = copy_context_.dest_size.width();
const int dest_height = copy_context_.dest_size.height();
glGenTextures(1, &copy_context_.frame_buffer_texture); CHECK_GL_ERROR();
glBindTexture(kTarget, copy_context_.frame_buffer_texture); CHECK_GL_ERROR();
glGenFramebuffersEXT(1, &copy_context_.frame_buffer); CHECK_GL_ERROR();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, copy_context_.frame_buffer);
CHECK_GL_ERROR();
glTexImage2D(kTarget,
0,
GL_RGBA,
dst_pixel_size.width(),
dst_pixel_size.height(),
dest_width,
dest_height,
0,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
NULL); CHECK_GL_ERROR();
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
target,
dst_texture,
kTarget,
copy_context_.frame_buffer_texture,
0); CHECK_GL_ERROR();
glBindTexture(target, 0); CHECK_GL_ERROR();
glViewport(0, 0, dst_pixel_size.width(), dst_pixel_size.height());
glViewport(0, 0, dest_width, dest_height); CHECK_GL_ERROR();
glMatrixMode(GL_PROJECTION); CHECK_GL_ERROR();
glLoadIdentity(); CHECK_GL_ERROR();
glOrtho(0, dest_width, 0, dest_height, -1, 1); CHECK_GL_ERROR();
glMatrixMode(GL_MODELVIEW); CHECK_GL_ERROR();
glLoadIdentity(); CHECK_GL_ERROR();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, dst_pixel_size.width(), 0, dst_pixel_size.height(), -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST); CHECK_GL_ERROR();
glDisable(GL_BLEND); CHECK_GL_ERROR();
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glUseProgram(shader_program_blit_rgb_); CHECK_GL_ERROR();
glUseProgram(shader_program_blit_rgb_);
int texture_unit = 0;
glUniform1i(blit_rgb_sampler_location_, texture_unit);
glActiveTexture(GL_TEXTURE0 + texture_unit);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
const int kTextureUnit = 0;
glUniform1i(blit_rgb_sampler_location_, kTextureUnit); CHECK_GL_ERROR();
glActiveTexture(GL_TEXTURE0 + kTextureUnit); CHECK_GL_ERROR();
glBindTexture(kTarget, texture_); CHECK_GL_ERROR();
glTexParameterf(kTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); CHECK_GL_ERROR();
glTexParameterf(kTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); CHECK_GL_ERROR();
SurfaceQuad quad;
quad.set_rect(0.0f, 0.0f, dst_pixel_size.width(), dst_pixel_size.height());
quad.set_texcoord_rect(src_pixel_subrect.x(), src_pixel_subrect.y(),
src_pixel_subrect.right(), src_pixel_subrect.bottom());
quad.set_rect(0.0f, 0.0f, dest_width, dest_height); CHECK_GL_ERROR();
quad.set_texcoord_rect(
copy_context_.src_rect.x(), copy_context_.src_rect.y(),
copy_context_.src_rect.right(), copy_context_.src_rect.bottom());
DrawQuad(quad);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); CHECK_GL_ERROR();
glUseProgram(0);
glBindTexture(kTarget, 0); CHECK_GL_ERROR();
glUseProgram(0); CHECK_GL_ERROR();
CGLFlushDrawable(cglContext_);
glReadPixels(0, 0, dst_pixel_size.width(), dst_pixel_size.height(),
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, out);
// Copy the offscreen framebuffer to a PBO.
glGenBuffersARB(1, &copy_context_.pixel_buffer); CHECK_GL_ERROR();
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context_.pixel_buffer);
CHECK_GL_ERROR();
glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB,
dest_width * dest_height * 4,
NULL, GL_STREAM_READ_ARB); CHECK_GL_ERROR();
glReadPixels(0, 0, dest_width, dest_height, GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV, 0); CHECK_GL_ERROR();
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_GL_ERROR();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_GL_ERROR();
glDeleteFramebuffersEXT(1, &dst_framebuffer);
glDeleteTextures(1, &dst_texture);
if (use_fence) {
glSetFenceAPPLE(copy_context_.fence); CHECK_GL_ERROR();
}
glFlush(); CHECK_GL_ERROR();
CGLSetCurrentContext(0);
return true;
// 20ms is an estimate assuming most hardware can complete asynchronous
// readback within this time limit. The timer will keep running until
// operation is completed.
const int kIntervalMilliseconds = 20;
copy_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kIntervalMilliseconds),
this, &CompositingIOSurfaceMac::FinishCopy);
}
bool CompositingIOSurfaceMac::MapIOSurfaceToTexture(
@ -617,4 +682,57 @@ void CompositingIOSurfaceMac::StopDisplayLink() {
CVDisplayLinkStop(display_link_);
}
void CompositingIOSurfaceMac::FinishCopy() {
CHECK(copy_context_.started);
TRACE_EVENT0("browser", "CompositingIOSurfaceMac::FinishCopy()");
CGLSetCurrentContext(cglContext_);
if (copy_context_.use_fence) {
bool copy_completed = glTestFenceAPPLE(copy_context_.fence);
CHECK_GL_ERROR();
// Allow 1s for the operation to complete.
const int kRetryCycles = 50;
if (!copy_completed && copy_context_.cycles_elapsed < kRetryCycles) {
++copy_context_.cycles_elapsed;
CGLSetCurrentContext(0);
return;
}
}
copy_timer_.Stop();
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context_.pixel_buffer);
CHECK_GL_ERROR();
void* buf = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
CHECK_GL_ERROR();
if (buf) {
memcpy(copy_context_.out_buf, buf, copy_context_.dest_size.GetArea() * 4);
glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); CHECK_GL_ERROR();
}
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_GL_ERROR();
base::Callback<void(bool)> callback = copy_context_.callback;
CleanupResourcesForCopy();
CGLSetCurrentContext(0);
callback.Run(buf != NULL);
}
void CompositingIOSurfaceMac::CleanupResourcesForCopy() {
if (!copy_context_.started)
return;
glDeleteFramebuffersEXT(1, &copy_context_.frame_buffer); CHECK_GL_ERROR();
glDeleteTextures(1, &copy_context_.frame_buffer_texture); CHECK_GL_ERROR();
glDeleteBuffers(1, &copy_context_.pixel_buffer); CHECK_GL_ERROR();
if (copy_context_.use_fence) {
glDeleteFencesAPPLE(1, &copy_context_.fence); CHECK_GL_ERROR();
}
copy_context_.Reset();
}
} // namespace content

@ -843,6 +843,7 @@ void RenderWidgetHostViewMac::CopyFromCompositingSurface(
if (!output->initialize(
dst_pixel_size.width(), dst_pixel_size.height(), true))
return;
scoped_callback_runner.Release();
// Convert |src_subrect| from the views coordinate (upper-left origin) into
// the OpenGL coordinate (lower-left origin).
@ -850,12 +851,11 @@ void RenderWidgetHostViewMac::CopyFromCompositingSurface(
src_gl_subrect.set_y(GetViewBounds().height() - src_subrect.bottom());
gfx::Rect src_pixel_gl_subrect = src_gl_subrect.Scale(scale);
const bool result = compositing_iosurface_->CopyTo(
compositing_iosurface_->CopyTo(
src_pixel_gl_subrect,
dst_pixel_size,
output->getTopDevice()->accessBitmap(true).getPixels());
scoped_callback_runner.Release();
callback.Run(result);
output->getTopDevice()->accessBitmap(true).getPixels(),
callback);
}
// Sets whether or not to accept first responder status.