| // 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 |