blob: f5c6ce3ec96001b5c27102e9122cd20f1d730594 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/gpu/client/gl_helper.h"
#include <queue>
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebCString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
#include "ui/gl/gl_bindings.h"
using WebKit::WebGLId;
using WebKit::WebGraphicsContext3D;
namespace {
const char kGLHelperThreadName[] = "GLHelperThread";
class GLHelperThread : public base::Thread {
public:
GLHelperThread() : base::Thread(kGLHelperThreadName) {
Start();
}
virtual ~GLHelperThread() {}
DISALLOW_COPY_AND_ASSIGN(GLHelperThread);
};
base::LazyInstance<GLHelperThread> g_gl_helper_thread =
LAZY_INSTANCE_INITIALIZER;
class ScopedWebGLId {
public:
typedef void (WebGraphicsContext3D::*DeleteFunc)(WebGLId);
ScopedWebGLId(WebGraphicsContext3D* context,
WebGLId id,
DeleteFunc delete_func)
: context_(context),
id_(id),
delete_func_(delete_func) {
}
operator WebGLId() const {
return id_;
}
WebGLId id() const { return id_; }
WebGLId Detach() {
WebGLId id = id_;
id_ = 0;
return id;
}
virtual ~ScopedWebGLId() {
if (id_ != 0)
(context_->*delete_func_)(id_);
}
private:
WebGraphicsContext3D* context_;
WebGLId id_;
DeleteFunc delete_func_;
DISALLOW_COPY_AND_ASSIGN(ScopedWebGLId);
};
class ScopedBuffer : public ScopedWebGLId {
public:
ScopedBuffer(WebGraphicsContext3D* context,
WebGLId id)
: ScopedWebGLId(context,
id,
&WebGraphicsContext3D::deleteBuffer) {}
};
class ScopedFramebuffer : public ScopedWebGLId {
public:
ScopedFramebuffer(WebGraphicsContext3D* context,
WebGLId id)
: ScopedWebGLId(context,
id,
&WebGraphicsContext3D::deleteFramebuffer) {}
};
class ScopedRenderbuffer : public ScopedWebGLId {
public:
ScopedRenderbuffer(WebGraphicsContext3D* context,
WebGLId id)
: ScopedWebGLId(context,
id,
&WebGraphicsContext3D::deleteRenderbuffer) {}
};
class ScopedProgram : public ScopedWebGLId {
public:
ScopedProgram(WebGraphicsContext3D* context,
WebGLId id)
: ScopedWebGLId(context,
id,
&WebGraphicsContext3D::deleteProgram) {}
};
class ScopedShader : public ScopedWebGLId {
public:
ScopedShader(WebGraphicsContext3D* context,
WebGLId id)
: ScopedWebGLId(context,
id,
&WebGraphicsContext3D::deleteShader) {}
};
class ScopedTexture : public ScopedWebGLId {
public:
ScopedTexture(WebGraphicsContext3D* context,
WebGLId id)
: ScopedWebGLId(context,
id,
&WebGraphicsContext3D::deleteTexture) {}
};
template <WebKit::WGC3Denum target>
class ScopedBinder {
public:
typedef void (WebGraphicsContext3D::*BindFunc)(WebKit::WGC3Denum,
WebGLId);
ScopedBinder(WebGraphicsContext3D* context,
WebGLId id,
BindFunc bind_func)
: context_(context),
id_(id),
bind_func_(bind_func) {
(context_->*bind_func_)(target, id_);
}
virtual ~ScopedBinder() {
if (id_ != 0)
(context_->*bind_func_)(target, 0);
}
private:
WebGraphicsContext3D* context_;
WebGLId id_;
BindFunc bind_func_;
DISALLOW_COPY_AND_ASSIGN(ScopedBinder);
};
template <WebKit::WGC3Denum target>
class ScopedBufferBinder : ScopedBinder<target> {
public:
ScopedBufferBinder(WebGraphicsContext3D* context,
WebGLId id)
: ScopedBinder<target>(
context,
id,
&WebGraphicsContext3D::bindBuffer) {}
};
template <WebKit::WGC3Denum target>
class ScopedFramebufferBinder : ScopedBinder<target> {
public:
ScopedFramebufferBinder(WebGraphicsContext3D* context,
WebGLId id)
: ScopedBinder<target>(
context,
id,
&WebGraphicsContext3D::bindFramebuffer) {}
};
template <WebKit::WGC3Denum target>
class ScopedRenderbufferBinder : ScopedBinder<target> {
public:
ScopedRenderbufferBinder(WebGraphicsContext3D* context,
WebGLId id)
: ScopedBinder<target>(
context,
id,
&WebGraphicsContext3D::bindRenderbuffer) {}
};
template <WebKit::WGC3Denum target>
class ScopedTextureBinder : ScopedBinder<target> {
public:
ScopedTextureBinder(WebGraphicsContext3D* context,
WebGLId id)
: ScopedBinder<target>(
context,
id,
&WebGraphicsContext3D::bindTexture) {}
};
class ScopedFlush {
public:
ScopedFlush(WebGraphicsContext3D* context)
: context_(context) {
}
virtual ~ScopedFlush() {
context_->flush();
}
private:
WebGraphicsContext3D* context_;
DISALLOW_COPY_AND_ASSIGN(ScopedFlush);
};
void DeleteContext(WebGraphicsContext3D* context) {
delete context;
}
void SignalWaitableEvent(base::WaitableEvent* event) {
event->Signal();
}
} // namespace
namespace content {
// Implements GLHelper::CopyTextureTo and encapsulates the data needed for it.
class GLHelper::CopyTextureToImpl {
public:
CopyTextureToImpl(WebGraphicsContext3D* context,
WebGraphicsContext3D* context_for_thread,
GLHelper* helper)
: context_(context),
context_for_thread_(context_for_thread),
helper_(helper),
flush_(context),
program_(context, context->createProgram()),
vertex_attributes_buffer_(context_, context_->createBuffer()) {
InitBuffer();
InitProgram();
}
~CopyTextureToImpl() {
CancelRequests();
DeleteContextForThread();
}
void InitBuffer();
void InitProgram();
void CopyTextureTo(WebGLId src_texture,
const gfx::Size& src_size,
const gfx::Rect& src_subrect,
const gfx::Size& dst_size,
unsigned char* out,
const base::Callback<void(bool)>& callback);
WebKit::WebGLId CopyTexture(WebGLId src_texture, const gfx::Size& size);
private:
// A single request to CopyTextureTo.
// Thread-safety notes: the main thread creates instances of this class. The
// main thread can cancel the request, before it's handled by the helper
// thread, by resetting the texture and pixels fields. Alternatively, the
// thread marks that it handles the request by resetting the pixels field
// (meaning it guarantees that the callback with be called).
// In either case, the callback must be called exactly once, and the texture
// must be deleted by the main thread context.
struct Request : public base::RefCountedThreadSafe<Request> {
Request(CopyTextureToImpl* impl,
WebGLId texture_,
const gfx::Size& size_,
unsigned char* pixels_,
const base::Callback<void(bool)>& callback_)
: copy_texture_impl(impl),
size(size_),
callback(callback_),
lock(),
texture(texture_),
pixels(pixels_) {
}
// These members are only accessed on the main thread.
GLHelper::CopyTextureToImpl* copy_texture_impl;
gfx::Size size;
base::Callback<void(bool)> callback;
// Locks access to below members, which can be accessed on any thread.
base::Lock lock;
WebGLId texture;
unsigned char* pixels;
private:
friend class base::RefCountedThreadSafe<Request>;
~Request() {}
};
// Copies the block of pixels specified with |src_subrect| from |src_texture|,
// scales it to |dst_size|, writes it into a texture, and returns its ID.
// |src_size| is the size of |src_texture|.
WebGLId ScaleTexture(WebGLId src_texture,
const gfx::Size& src_size,
const gfx::Rect& src_subrect,
const gfx::Size& dst_size);
// Deletes the context for GLHelperThread.
void DeleteContextForThread();
static void ReadBackFramebuffer(scoped_refptr<Request> request,
WebGraphicsContext3D* context,
scoped_refptr<base::TaskRunner> reply_loop);
static void ReadBackFramebufferComplete(scoped_refptr<Request> request,
bool result);
void FinishRequest(scoped_refptr<Request> request);
void CancelRequests();
// Interleaved array of 2-dimentional vertex positions (x, y) and
// 2-dimentional texture coordinates (s, t).
static const WebKit::WGC3Dfloat kVertexAttributes[];
// Shader sources used for GLHelper::CopyTextureTo
static const WebKit::WGC3Dchar kCopyVertexShader[];
static const WebKit::WGC3Dchar kCopyFragmentShader[];
WebGraphicsContext3D* context_;
WebGraphicsContext3D* context_for_thread_;
GLHelper* helper_;
// A scoped flush that will ensure all resource deletions are flushed when
// this object is destroyed. Must be declared before other Scoped* fields.
ScopedFlush flush_;
// A program for copying a source texture into a destination texture.
ScopedProgram program_;
// The buffer that holds the vertices and the texture coordinates data for
// drawing a quad.
ScopedBuffer vertex_attributes_buffer_;
// The location of the position in the program.
WebKit::WGC3Dint position_location_;
// The location of the texture coordinate in the program.
WebKit::WGC3Dint texcoord_location_;
// The location of the source texture in the program.
WebKit::WGC3Dint texture_location_;
// The location of the texture coordinate of the sub-rectangle in the program.
WebKit::WGC3Dint src_subrect_location_;
std::queue<scoped_refptr<Request> > request_queue_;
};
const WebKit::WGC3Dfloat GLHelper::CopyTextureToImpl::kVertexAttributes[] = {
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
};
const WebKit::WGC3Dchar GLHelper::CopyTextureToImpl::kCopyVertexShader[] =
"attribute vec2 a_position;"
"attribute vec2 a_texcoord;"
"varying vec2 v_texcoord;"
"uniform vec4 src_subrect;"
"void main() {"
" gl_Position = vec4(a_position, 0.0, 1.0);"
" v_texcoord = src_subrect.xy + a_texcoord * src_subrect.zw;"
"}";
const WebKit::WGC3Dchar GLHelper::CopyTextureToImpl::kCopyFragmentShader[] =
"precision mediump float;"
"varying vec2 v_texcoord;"
"uniform sampler2D s_texture;"
"void main() {"
" gl_FragColor = texture2D(s_texture, v_texcoord);"
"}";
void GLHelper::CopyTextureToImpl::InitBuffer() {
ScopedBufferBinder<GL_ARRAY_BUFFER> buffer_binder(
context_, vertex_attributes_buffer_);
context_->bufferData(GL_ARRAY_BUFFER,
sizeof(kVertexAttributes),
kVertexAttributes,
GL_STATIC_DRAW);
}
void GLHelper::CopyTextureToImpl::InitProgram() {
// Shaders to map the source texture to |dst_texture_|.
ScopedShader vertex_shader(context_, helper_->CompileShaderFromSource(
kCopyVertexShader, GL_VERTEX_SHADER));
DCHECK_NE(0U, vertex_shader.id());
context_->attachShader(program_, vertex_shader);
ScopedShader fragment_shader(context_, helper_->CompileShaderFromSource(
kCopyFragmentShader, GL_FRAGMENT_SHADER));
DCHECK_NE(0U, fragment_shader.id());
context_->attachShader(program_, fragment_shader);
context_->linkProgram(program_);
WebKit::WGC3Dint link_status = 0;
context_->getProgramiv(program_, GL_LINK_STATUS, &link_status);
if (!link_status) {
LOG(ERROR) << std::string(context_->getProgramInfoLog(program_).utf8());
return;
}
position_location_ = context_->getAttribLocation(program_, "a_position");
texcoord_location_ = context_->getAttribLocation(program_, "a_texcoord");
texture_location_ = context_->getUniformLocation(program_, "s_texture");
src_subrect_location_ = context_->getUniformLocation(program_, "src_subrect");
}
WebGLId GLHelper::CopyTextureToImpl::ScaleTexture(
WebGLId src_texture,
const gfx::Size& src_size,
const gfx::Rect& src_subrect,
const gfx::Size& dst_size) {
WebGLId dst_texture = context_->createTexture();
{
ScopedFramebuffer dst_framebuffer(context_, context_->createFramebuffer());
ScopedFramebufferBinder<GL_FRAMEBUFFER> framebuffer_binder(
context_, dst_framebuffer);
{
ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(
context_, dst_texture);
context_->texImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
dst_size.width(),
dst_size.height(),
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
NULL);
context_->framebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
dst_texture,
0);
}
ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_, src_texture);
ScopedBufferBinder<GL_ARRAY_BUFFER> buffer_binder(
context_, vertex_attributes_buffer_);
context_->viewport(0, 0, dst_size.width(), dst_size.height());
context_->useProgram(program_);
WebKit::WGC3Dintptr offset = 0;
context_->vertexAttribPointer(position_location_,
2,
GL_FLOAT,
GL_FALSE,
4 * sizeof(WebKit::WGC3Dfloat),
offset);
context_->enableVertexAttribArray(position_location_);
offset += 2 * sizeof(WebKit::WGC3Dfloat);
context_->vertexAttribPointer(texcoord_location_,
2,
GL_FLOAT,
GL_FALSE,
4 * sizeof(WebKit::WGC3Dfloat),
offset);
context_->enableVertexAttribArray(texcoord_location_);
context_->uniform1i(texture_location_, 0);
// Convert |src_subrect| to texture coordinates.
GLfloat src_subrect_texcoord[] = {
static_cast<float>(src_subrect.x()) / src_size.width(),
static_cast<float>(src_subrect.y()) / src_size.height(),
static_cast<float>(src_subrect.width()) / src_size.width(),
static_cast<float>(src_subrect.height()) / src_size.height(),
};
context_->uniform4fv(src_subrect_location_, 1, src_subrect_texcoord);
// Conduct texture mapping by drawing a quad composed of two triangles.
context_->drawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
return dst_texture;
}
void GLHelper::CopyTextureToImpl::DeleteContextForThread() {
if (!context_for_thread_)
return;
g_gl_helper_thread.Pointer()->message_loop_proxy()->PostTask(
FROM_HERE,
base::Bind(&DeleteContext,
context_for_thread_));
context_for_thread_ = NULL;
}
void GLHelper::CopyTextureToImpl::CopyTextureTo(
WebGLId src_texture,
const gfx::Size& src_size,
const gfx::Rect& src_subrect,
const gfx::Size& dst_size,
unsigned char* out,
const base::Callback<void(bool)>& callback) {
if (!context_for_thread_) {
callback.Run(false);
return;
}
WebGLId texture = ScaleTexture(src_texture, src_size, src_subrect, dst_size);
context_->flush();
scoped_refptr<Request> request =
new Request(this, texture, dst_size, out, callback);
request_queue_.push(request);
g_gl_helper_thread.Pointer()->message_loop_proxy()->PostTask(FROM_HERE,
base::Bind(&ReadBackFramebuffer,
request,
context_for_thread_,
base::MessageLoopProxy::current()));
}
WebKit::WebGLId GLHelper::CopyTextureToImpl::CopyTexture(
WebGLId src_texture,
const gfx::Size& size) {
if (!context_for_thread_)
return 0;
return ScaleTexture(src_texture, size, gfx::Rect(size), size);
}
void GLHelper::CopyTextureToImpl::ReadBackFramebuffer(
scoped_refptr<Request> request,
WebGraphicsContext3D* context,
scoped_refptr<base::TaskRunner> reply_loop) {
DCHECK(context);
if (!context->makeContextCurrent() || context->isContextLost()) {
base::AutoLock auto_lock(request->lock);
if (request->pixels) {
// Only report failure if the request wasn't canceled (otherwise the
// failure has already been reported).
request->pixels = NULL;
reply_loop->PostTask(
FROM_HERE, base::Bind(ReadBackFramebufferComplete, request, false));
}
return;
}
ScopedFlush flush(context);
ScopedFramebuffer dst_framebuffer(context, context->createFramebuffer());
unsigned char* pixels = NULL;
gfx::Size size;
{
// Note: We don't want to keep the lock while doing the readBack (since we
// don't want to block the UI thread). We rely on the fact that once the
// texture is bound to a FBO (that isn't current), deleting the texture is
// delayed until the FBO is deleted. We ensure ordering by flushing while
// the lock is held. Either the main thread cancelled before we get the
// lock, and we'll exit early, or we ensure that the texture is bound to the
// framebuffer before the main thread has a chance to delete it.
base::AutoLock auto_lock(request->lock);
if (!request->texture || !request->pixels)
return;
pixels = request->pixels;
request->pixels = NULL;
size = request->size;
{
ScopedFlush flush(context);
ScopedFramebufferBinder<GL_FRAMEBUFFER> framebuffer_binder(
context, dst_framebuffer);
ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(
context, request->texture);
context->framebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
request->texture,
0);
}
}
bool result = context->readBackFramebuffer(
pixels,
4 * size.GetArea(),
dst_framebuffer.id(),
size.width(),
size.height());
reply_loop->PostTask(
FROM_HERE, base::Bind(ReadBackFramebufferComplete, request, result));
}
void GLHelper::CopyTextureToImpl::ReadBackFramebufferComplete(
scoped_refptr<Request> request,
bool result) {
request->callback.Run(result);
if (request->copy_texture_impl)
request->copy_texture_impl->FinishRequest(request);
}
void GLHelper::CopyTextureToImpl::FinishRequest(
scoped_refptr<Request> request) {
CHECK(request_queue_.front() == request);
request_queue_.pop();
base::AutoLock auto_lock(request->lock);
if (request->texture != 0) {
context_->deleteTexture(request->texture);
request->texture = 0;
context_->flush();
}
}
void GLHelper::CopyTextureToImpl::CancelRequests() {
while (!request_queue_.empty()) {
scoped_refptr<Request> request = request_queue_.front();
request_queue_.pop();
request->copy_texture_impl = NULL;
bool cancelled = false;
{
base::AutoLock auto_lock(request->lock);
if (request->texture != 0) {
context_->deleteTexture(request->texture);
request->texture = 0;
}
if (request->pixels != NULL) {
request->pixels = NULL;
cancelled = true;
}
}
if (cancelled)
request->callback.Run(false);
}
}
base::subtle::Atomic32 GLHelper::count_ = 0;
GLHelper::GLHelper(WebGraphicsContext3D* context,
WebGraphicsContext3D* context_for_thread)
: context_(context),
context_for_thread_(context_for_thread) {
base::subtle::NoBarrier_AtomicIncrement(&count_, 1);
}
GLHelper::~GLHelper() {
DCHECK_NE(MessageLoop::current(),
g_gl_helper_thread.Pointer()->message_loop());
base::subtle::Atomic32 decremented_count =
base::subtle::NoBarrier_AtomicIncrement(&count_, -1);
if (decremented_count == 0) {
// When this is the last instance, we synchronize with the pending
// operations on GLHelperThread. Otherwise on shutdown we may kill the GPU
// process infrastructure (BrowserGpuChannelHostFactory) before they have
// a chance to complete, likely leading to a crash.
base::WaitableEvent event(false, false);
g_gl_helper_thread.Pointer()->message_loop_proxy()->PostTask(
FROM_HERE,
base::Bind(&SignalWaitableEvent,
&event));
// http://crbug.com/125415
base::ThreadRestrictions::ScopedAllowWait allow_wait;
event.Wait();
}
}
WebGraphicsContext3D* GLHelper::context() const {
return context_;
}
void GLHelper::CopyTextureTo(WebGLId src_texture,
const gfx::Size& src_size,
const gfx::Rect& src_subrect,
const gfx::Size& dst_size,
unsigned char* out,
const base::Callback<void(bool)>& callback) {
InitCopyTextToImpl();
copy_texture_to_impl_->CopyTextureTo(src_texture,
src_size,
src_subrect,
dst_size,
out,
callback);
}
WebKit::WebGLId GLHelper::CopyTexture(WebKit::WebGLId texture,
const gfx::Size& size) {
InitCopyTextToImpl();
return copy_texture_to_impl_->CopyTexture(texture, size);
}
WebGLId GLHelper::CompileShaderFromSource(
const WebKit::WGC3Dchar* source,
WebKit::WGC3Denum type) {
ScopedShader shader(context_, context_->createShader(type));
context_->shaderSource(shader, source);
context_->compileShader(shader);
WebKit::WGC3Dint compile_status = 0;
context_->getShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
if (!compile_status) {
LOG(ERROR) << std::string(context_->getShaderInfoLog(shader).utf8());
return 0;
}
return shader.Detach();
}
void GLHelper::InitCopyTextToImpl() {
// Lazily initialize |copy_texture_to_impl_|
if (!copy_texture_to_impl_.get())
copy_texture_to_impl_.reset(new CopyTextureToImpl(context_,
context_for_thread_,
this));
}
} // namespace content