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:
content/browser/renderer_host
@ -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, ©_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, ©_context_.frame_buffer_texture); CHECK_GL_ERROR();
|
||||
glBindTexture(kTarget, copy_context_.frame_buffer_texture); CHECK_GL_ERROR();
|
||||
glGenFramebuffersEXT(1, ©_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, ©_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, ©_context_.frame_buffer); CHECK_GL_ERROR();
|
||||
glDeleteTextures(1, ©_context_.frame_buffer_texture); CHECK_GL_ERROR();
|
||||
glDeleteBuffers(1, ©_context_.pixel_buffer); CHECK_GL_ERROR();
|
||||
if (copy_context_.use_fence) {
|
||||
glDeleteFencesAPPLE(1, ©_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.
|
||||
|
Reference in New Issue
Block a user