Chromium Code Reviews| Index: ui/gl/async_pixel_transfer_delegate_android.cc |
| diff --git a/ui/gl/async_pixel_transfer_delegate_android.cc b/ui/gl/async_pixel_transfer_delegate_android.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..9d931b6af4efa7b99c71941f02acd3b65cdbdb97 |
| --- /dev/null |
| +++ b/ui/gl/async_pixel_transfer_delegate_android.cc |
| @@ -0,0 +1,467 @@ |
| +// 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 "ui/gl/async_pixel_transfer_delegate_android.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/cancelable_callback.h" |
| +#include "base/debug/trace_event.h" |
| +#include "base/lazy_instance.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/threading/thread.h" |
| +#include "build/build_config.h" |
| +#include "third_party/angle/include/EGL/egl.h" |
| +#include "third_party/angle/include/EGL/eglext.h" |
| +#include "ui/gfx/point.h" |
| +#include "ui/gfx/size.h" |
| +#include "ui/gl/async_pixel_transfer_delegate.h" |
| +#include "ui/gl/egl_util.h" |
| +#include "ui/gl/gl_bindings.h" |
| +#include "ui/gl/gl_context.h" |
| +#include "ui/gl/gl_surface_egl.h" |
| + |
| +namespace gfx { |
| + |
| +namespace { |
| +bool CheckErrors(const char* file, int line) { |
| + EGLint eglerror; |
| + GLenum glerror; |
| + bool success = true; |
| + while ((eglerror = eglGetError()) != EGL_SUCCESS) { |
| + LOG(ERROR) << "Async transfer eglerror at " |
| + << file << ":" << line << " " << eglerror; |
| + success = false; |
| + } |
| + while ((glerror = glGetError()) != GL_NO_ERROR) { |
| + LOG(ERROR) << "Async transfer openglerror at " |
| + << file << ":" << line << " " << glerror; |
| + success = false; |
| + } |
| + return success; |
| +} |
| +#define CHK() CheckErrors(__FILE__, __LINE__) |
| +} // namespace |
| + |
| +// Class which holds async pixel transfers state (EGLImage). |
| +// The EGLImage is accessed by either thread, but the everything |
|
apatrick_chromium
2012/12/04 20:59:22
nit: fix comment
epenner
2012/12/08 03:15:04
Is this regarding thread-safe ref counting? Let me
|
| +// else (and the lifetime of this object) is access only by the main |
| +// GPU thread. |
| +class AsyncTransferStateAndroid : public AsyncPixelTransferState { |
| + public: |
| + AsyncTransferStateAndroid(GLuint texture_id) |
| + : texture_id_(texture_id), |
| + needs_bind_(false), |
| + transfer_in_progress_(false), |
| + egl_image_(0) {} |
| + |
| + virtual ~AsyncTransferStateAndroid() { |
| + if (egl_image_) { |
|
apatrick_chromium
2012/12/04 20:59:22
nit: indentation out by 1 space
epenner
2012/12/08 03:15:04
Done.
|
| + EGLDisplay display = eglGetCurrentDisplay(); |
| + eglDestroyImageKHR(display, egl_image_); |
| + } |
| + } |
| + |
| + // implement AsyncPixelTransferState: |
| + virtual bool TransferIsInProgress() { |
| + return transfer_in_progress_; |
| + } |
| + |
| + virtual void BindTexture(GLenum target) { |
| + glBindTexture(target, texture_id_); |
| + if (needs_bind_) |
| + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image_); |
| + needs_bind_ = false; |
| + } |
| + |
| + // Completion callbacks. |
| + void TexImage2DCompleted() { |
| + needs_bind_ = true; |
| + transfer_in_progress_ = false; |
| + } |
| + void TexSubImage2DCompleted() { |
| + transfer_in_progress_ = false; |
| + } |
| + |
| + // The 'real' texture. |
| + GLuint texture_id_; |
| + |
| + // Indicates there is a new EGLImage and the 'real' |
| + // texture needs to be bound to it. |
| + bool needs_bind_; |
| + |
| + // Indicates that an async transfer is in progress. |
| + bool transfer_in_progress_; |
| + |
| + // It would be nice if we could just create a new EGLImage for |
| + // every upload, but I found that didn't work, so this stores |
| + // one for the lifetime of the texture. |
| + EGLImageKHR egl_image_; |
| +}; |
| + |
| +// Class which handles async pixel transfers on Android (using |
| +// EGLImageKHR and another upload thread) |
| +class AsyncPixelTransferDelegateAndroid : public AsyncPixelTransferDelegate { |
| + public: |
| + AsyncPixelTransferDelegateAndroid(); |
| + virtual ~AsyncPixelTransferDelegateAndroid(); |
| + |
| + // implement AsyncPixelTransferDelegate: |
| + virtual scoped_refptr<AsyncPixelTransferState> |
| + CreatePixelTransferState(GLuint); |
| + |
| + virtual void AsyncNotifyCompletion( |
| + const base::Closure& task); |
| + |
| + virtual void AsyncTexImage2D( |
| + AsyncPixelTransferState*, |
| + GLenum target, |
| + GLint level, |
| + GLenum internal_format, |
| + GLsizei width, |
| + GLsizei height, |
| + GLint border, |
| + GLenum format, |
| + GLenum type, |
| + const void* data); |
| + |
| + virtual void AsyncTexSubImage2D( |
| + AsyncPixelTransferState*, |
| + GLenum target, |
| + GLint level, |
| + GLint xoffset, |
| + GLint yoffset, |
| + GLsizei width, |
| + GLsizei height, |
| + GLenum format, |
| + GLenum type, |
| + const void* data); |
| + |
| + private: |
| + void Initialize(); |
| + void Shutdown(); |
| + void PerformInitialize(); |
| + void PerformShutdown(); |
| + |
| + void PerformAsyncTexImage2D( |
| + EGLImageKHR* egl_image, |
| + std::pair<GLenum, GLenum> formats, |
| + gfx::Size size, |
| + GLint border, |
| + GLenum type, |
| + const void* data); |
| + |
| + void PerformAsyncTexSubImage2D( |
| + EGLImageKHR egl_image, |
| + gfx::Point offset, |
| + gfx::Size size, |
| + GLenum format, |
| + GLenum type, |
| + const void* data); |
| + |
| + scoped_ptr<base::Thread> thread_; |
| + scoped_refptr<gfx::GLContext> thread_context_; |
| + scoped_refptr<gfx::GLSurface> thread_surface_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(AsyncPixelTransferDelegateAndroid); |
| +}; |
| + |
| +// Lazy instance creation. |
| +base::LazyInstance<AsyncPixelTransferDelegateAndroid> |
| + g_async_pixel_transfer_delegate_ = LAZY_INSTANCE_INITIALIZER; |
| + |
| +AsyncPixelTransferDelegate* AsyncPixelTransferDelegate::Get() { |
| + return g_async_pixel_transfer_delegate_.Pointer(); |
| +} |
| + |
| +scoped_refptr<AsyncPixelTransferState> |
| + AsyncPixelTransferDelegateAndroid:: |
| + CreatePixelTransferState(GLuint texture_id) { |
| + return make_scoped_refptr(static_cast<AsyncPixelTransferState*>( |
| + new AsyncTransferStateAndroid(texture_id))); |
| +} |
| + |
| +AsyncPixelTransferDelegateAndroid::AsyncPixelTransferDelegateAndroid() |
| + : thread_(new base::Thread("GPUAsyncTransferThread")) |
| +{ |
| + Initialize(); |
| +} |
| + |
| +AsyncPixelTransferDelegateAndroid::~AsyncPixelTransferDelegateAndroid() |
| +{ |
| + Shutdown(); |
| +} |
| + |
| +void AsyncPixelTransferDelegateAndroid::Initialize() { |
| + // Start the thread and initialize on the thread. |
| + thread_->Start(); |
| + thread_->message_loop()->PostTask(FROM_HERE, base::Bind( |
| + &AsyncPixelTransferDelegateAndroid::PerformInitialize, |
| + base::Unretained(this))); |
| +} |
| + |
| +void AsyncPixelTransferDelegateAndroid::Shutdown() { |
| + // Shutdown and wait for the thread to finish. |
| + thread_->message_loop()->PostTask(FROM_HERE, base::Bind( |
| + &AsyncPixelTransferDelegateAndroid::PerformShutdown, |
| + base::Unretained(this))); |
| + thread_->Stop(); |
| +} |
| + |
| + |
| +namespace { |
| +// Dummy function to measure completion on |
| +// the upload thread. |
| +void NoOp() {} |
| +} // namespace |
| + |
| +void AsyncPixelTransferDelegateAndroid::AsyncNotifyCompletion( |
| + const base::Closure& task) { |
| + // Post a no-op task to the upload thread followed |
| + // by a reply to the callback. The reply will then occur after |
| + // all async transfers are complete. |
| + thread_->message_loop_proxy()->PostTaskAndReply(FROM_HERE, |
| + base::Bind(&NoOp), task); |
| +} |
| + |
| +void AsyncPixelTransferDelegateAndroid::AsyncTexImage2D( |
| + AsyncPixelTransferState* transfer_state, |
| + GLenum target, |
| + GLint level, |
| + GLenum internal_format, |
| + GLsizei width, |
| + GLsizei height, |
| + GLint border, |
| + GLenum format, |
| + GLenum type, |
| + const void* data) { |
| + AsyncTransferStateAndroid* state = |
| + static_cast<AsyncTransferStateAndroid*>(transfer_state); |
| + DCHECK(state); |
| + DCHECK(state->texture_id_); |
| + DCHECK(!state->transfer_in_progress_); |
| + |
| + // TODO: Implement other targets/levels if needed. |
| + DCHECK(target == GL_TEXTURE_2D); |
| + DCHECK(level == 0); |
| + |
| + state->transfer_in_progress_ = true; |
| + |
| + // Any existing EGLImage is made an 'orphan' by a call to |
| + // texImage2D. We can delete the existing one safely since |
| + // the client texture is not effected. |
|
apatrick_chromium
2012/12/04 20:59:22
nit: effected -> affected
epenner
2012/12/08 03:15:04
Done.
|
| + if (state->egl_image_) { |
| + EGLDisplay display = eglGetCurrentDisplay(); |
| + eglDestroyImageKHR(display, state->egl_image_); |
| + state->egl_image_ = 0; |
| + } |
| + |
| + // Post the upload task. The reply also keeps the texture |
| + // state referenced until the reply executes on the main thread. |
| + thread_->message_loop_proxy()->PostTaskAndReply(FROM_HERE, |
| + base::Bind( |
| + &AsyncPixelTransferDelegateAndroid::PerformAsyncTexImage2D, |
| + base::Unretained(this), |
| + &state->egl_image_, |
| + std::pair<GLenum, GLenum>(internal_format, format), |
| + gfx::Size(width, height), |
| + border, type, data), |
| + base::Bind( |
| + &AsyncTransferStateAndroid::TexImage2DCompleted, |
| + state)); |
| +} |
| + |
| +void AsyncPixelTransferDelegateAndroid::AsyncTexSubImage2D( |
| + AsyncPixelTransferState* transfer_state, |
| + GLenum target, |
| + GLint level, |
| + GLint xoffset, |
| + GLint yoffset, |
| + GLsizei width, |
| + GLsizei height, |
| + GLenum format, |
| + GLenum type, |
| + const void* data) { |
| + TRACE_EVENT2("gpu", "AsyncTexSubImage2D", |
| + "width", width, |
| + "height", height); |
| + AsyncTransferStateAndroid* state = |
| + static_cast<AsyncTransferStateAndroid*>(transfer_state); |
| + DCHECK(state); |
| + DCHECK(state->texture_id_); |
| + DCHECK(!state->transfer_in_progress_); |
| + |
| + // TODO: Implement other targets/levels if needed. |
| + DCHECK(target == GL_TEXTURE_2D); |
| + DCHECK(level == 0); |
| + |
| + state->transfer_in_progress_ = true; |
| + |
| + // Create the EGLImage if it hasn't already been created. |
| + if (!state->egl_image_) { |
| + EGLDisplay egl_display = eglGetCurrentDisplay(); |
| + EGLContext egl_context = eglGetCurrentContext(); |
| + EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR; |
| + EGLClientBuffer egl_buffer = |
| + reinterpret_cast<EGLClientBuffer>(state->texture_id_); |
| + EGLint egl_attrib_list[] = { |
| + EGL_GL_TEXTURE_LEVEL_KHR, level, // mip-map level to reference. |
| + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, // preserve the data in the texture. |
| + EGL_NONE |
| + }; |
| + state->egl_image_ = eglCreateImageKHR( |
| + egl_display, |
| + egl_context, |
| + egl_target, |
| + egl_buffer, |
| + egl_attrib_list); |
| + } |
| + |
| + // Post the upload task. The reply also keeps the texture |
| + // state referenced until the reply executes on the main thread. |
| + thread_->message_loop_proxy()->PostTaskAndReply(FROM_HERE, |
| + base::Bind( |
| + &AsyncPixelTransferDelegateAndroid::PerformAsyncTexSubImage2D, |
| + base::Unretained(this), |
| + state->egl_image_, |
| + gfx::Point(xoffset, yoffset), |
| + gfx::Size(width, height), |
| + format, type, data), |
| + base::Bind( |
| + &AsyncTransferStateAndroid::TexSubImage2DCompleted, |
| + state)); |
| +} |
| + |
| +void AsyncPixelTransferDelegateAndroid::PerformInitialize() { |
| + DCHECK(!thread_surface_); |
| + DCHECK(!thread_context_); |
| + GLShareGroup* share_group = NULL; |
| + bool software = false; |
| + thread_surface_ = new gfx::PbufferGLSurfaceEGL(software, gfx::Size(1,1)); |
| + thread_surface_->Initialize(); |
|
apatrick_chromium
2012/12/04 20:59:22
What if this fails?
epenner
2012/12/08 03:15:04
Hmm, is this normally treated as a recoverable err
|
| + thread_context_ = gfx::GLContext::CreateGLContext(share_group, |
|
apatrick_chromium
2012/12/04 20:59:22
And this.
epenner
2012/12/08 03:15:04
See above.
|
| + thread_surface_, |
| + gfx::PreferDiscreteGpu); |
| + bool is_current = thread_context_->MakeCurrent(thread_surface_); |
|
apatrick_chromium
2012/12/04 20:59:22
Ditto.
epenner
2012/12/08 03:15:04
See above.
|
| + DCHECK(thread_surface_); |
| + DCHECK(thread_context_); |
| + DCHECK(is_current); |
| +} |
| + |
| +void AsyncPixelTransferDelegateAndroid::PerformShutdown() { |
| + DCHECK(thread_surface_); |
| + DCHECK(thread_context_); |
| + thread_surface_ = NULL; |
| + thread_context_->ReleaseCurrent(thread_surface_); |
| + thread_context_ = NULL; |
| +} |
| + |
| +namespace { |
| +void WaitForGlFence() { |
| + // TODO: Fix bindings (link errors) to enable the code below. |
| + // TODO: Should we only sync just before we report completion? |
| + |
| + // Uploads usually finish on the CPU, but just in case add a fence |
| + // and guarantee the upload has completed. Flush bit is set to |
| + // insure we don't wait forever. |
| + // EGLSyncKHR fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL); |
| + // EGLint flags = EGL_SYNC_FLUSH_COMMANDS_BIT_KHR; |
| + // EGLTimeKHR time = EGL_FOREVER_KHR; |
| + // eglClientWaitSyncKHR(display, fence, flags, time); |
| + glFinish(); |
| +} |
| +} // namespace |
| + |
| +void AsyncPixelTransferDelegateAndroid::PerformAsyncTexImage2D( |
| + EGLImageKHR* egl_image, |
| + std::pair<GLenum, GLenum> formats, |
| + gfx::Size size, |
| + GLint border, |
| + GLenum type, |
| + const void* data) { |
| + // In texImage2D, we do everything on the upload thread. |
| + // This is because texImage2D can incur the allocation cost, and |
| + // it also 'orphans' any previous EGLImage bound to the texture. |
| + DCHECK(egl_image); |
| + DCHECK(!(*egl_image)); |
| + TRACE_EVENT2("gpu", "performAsyncTexImage2D", |
| + "width", size.width(), |
| + "height", size.height()); |
| + |
| + // Create a texture from the image and upload to it. |
| + GLenum internal_format = formats.first; |
| + GLenum format = formats.second; |
| + GLuint temp_texture = 0; |
| + glGenTextures(1, &temp_texture); |
| + glActiveTexture(GL_TEXTURE0); |
| + glBindTexture(GL_TEXTURE_2D, temp_texture); |
| + { |
| + TRACE_EVENT0("gpu", "performAsyncTexSubImage2D glTexImage2D"); |
| + glTexImage2D(GL_TEXTURE_2D, 0, |
| + internal_format, |
| + size.width(), size.height(), |
| + border, format, type, data); |
| + } |
| + |
| + // Create the EGLImage, as texSubImage always 'orphan's a previous EGLImage. |
| + GLuint level = 0; |
| + EGLDisplay egl_display = eglGetCurrentDisplay(); |
| + EGLContext egl_context = eglGetCurrentContext(); |
| + EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR; |
| + EGLClientBuffer egl_buffer = (EGLClientBuffer) temp_texture; |
| + EGLint egl_attrib_list[] = { |
| + EGL_GL_TEXTURE_LEVEL_KHR, level, // mip-map level to reference. |
| + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, // preserve the data in the texture. |
| + EGL_NONE |
| + }; |
| + (*egl_image) = eglCreateImageKHR( |
| + egl_display, |
| + egl_context, |
| + egl_target, |
| + egl_buffer, |
| + egl_attrib_list); |
| + |
| + WaitForGlFence(); |
| + |
| + // We can delete this thread's texture as the real texture |
| + // now contains the data. |
| + glDeleteTextures(1, &temp_texture); |
| +} |
| + |
| +void AsyncPixelTransferDelegateAndroid::PerformAsyncTexSubImage2D( |
| + EGLImageKHR egl_image, |
| + gfx::Point offset, |
| + gfx::Size size, |
| + GLenum format, |
| + GLenum type, |
| + const void* data) { |
| + // For a texSubImage, the texture must already have been |
| + // created on the main thread, along with EGLImageKHR. |
| + DCHECK(egl_image); |
| + TRACE_EVENT2("gpu", "performAsyncTexSubImage2D", |
| + "width", size.width(), |
| + "height", size.height()); |
| + |
| + // Create a texture from the image and upload to it. |
| + GLuint temp_texture = 0; |
| + glGenTextures(1, &temp_texture); |
| + glActiveTexture(GL_TEXTURE0); |
| + glBindTexture(GL_TEXTURE_2D, temp_texture); |
| + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image); |
| + { |
| + TRACE_EVENT0("gpu", "performAsyncTexSubImage2D glTexSubImage2D"); |
| + glTexSubImage2D(GL_TEXTURE_2D, 0, |
| + offset.x(), offset.y(), |
| + size.width(), size.height(), |
| + format, type, data); |
| + } |
| + |
| + WaitForGlFence(); |
| + |
| + // We can delete this thread's texture as the real texture |
| + // now contains the data. |
| + glDeleteTextures(1, &temp_texture); |
| +} |
| + |
| +} // namespace gfx |