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 |