// Copyright 2013 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 "gpu/command_buffer/service/async_pixel_transfer_manager_egl.h" #include #include #include "base/bind.h" #include "base/debug/trace_event.h" #include "base/debug/trace_event_synthetic_delay.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "gpu/command_buffer/service/async_pixel_transfer_delegate.h" #include "gpu/command_buffer/service/safe_shared_memory_pool.h" #include "ui/gl/gl_context.h" #include "ui/gl/gl_surface_egl.h" #include "ui/gl/scoped_binders.h" namespace gpu { namespace { bool CheckErrors(const char* file, int line) { EGLint eglerror; GLenum glerror; bool success = true; while ((eglerror = eglGetError()) != EGL_SUCCESS) { LOG(ERROR) << "Async transfer EGL error at " << file << ":" << line << " " << eglerror; success = false; } while ((glerror = glGetError()) != GL_NO_ERROR) { LOG(ERROR) << "Async transfer OpenGL error at " << file << ":" << line << " " << glerror; success = false; } return success; } #define CHECK_GL() CheckErrors(__FILE__, __LINE__) const char kAsyncTransferThreadName[] = "AsyncTransferThread"; // Regular glTexImage2D call. void DoTexImage2D(const AsyncTexImage2DParams& tex_params, void* data) { glTexImage2D( GL_TEXTURE_2D, tex_params.level, tex_params.internal_format, tex_params.width, tex_params.height, tex_params.border, tex_params.format, tex_params.type, data); } // Regular glTexSubImage2D call. void DoTexSubImage2D(const AsyncTexSubImage2DParams& tex_params, void* data) { glTexSubImage2D( GL_TEXTURE_2D, tex_params.level, tex_params.xoffset, tex_params.yoffset, tex_params.width, tex_params.height, tex_params.format, tex_params.type, data); } // Full glTexSubImage2D call, from glTexImage2D params. void DoFullTexSubImage2D(const AsyncTexImage2DParams& tex_params, void* data) { glTexSubImage2D( GL_TEXTURE_2D, tex_params.level, 0, 0, tex_params.width, tex_params.height, tex_params.format, tex_params.type, data); } void SetGlParametersForEglImageTexture() { // These params are needed for EGLImage creation to succeed on several // Android devices. I couldn't find this requirement in the EGLImage // extension spec, but several devices fail without it. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } void PerformNotifyCompletion( AsyncMemoryParams mem_params, ScopedSafeSharedMemory* safe_shared_memory, scoped_refptr observer) { TRACE_EVENT0("gpu", "PerformNotifyCompletion"); AsyncMemoryParams safe_mem_params = mem_params; safe_mem_params.shared_memory = safe_shared_memory->shared_memory(); observer->DidComplete(safe_mem_params); } class TransferThread : public base::Thread { public: TransferThread() : base::Thread(kAsyncTransferThreadName) { Start(); #if defined(OS_ANDROID) || defined(OS_LINUX) SetPriority(base::kThreadPriority_Background); #endif } virtual ~TransferThread() { Stop(); } virtual void Init() OVERRIDE { gfx::GLShareGroup* share_group = NULL; surface_ = new gfx::PbufferGLSurfaceEGL(gfx::Size(1, 1)); surface_->Initialize(); context_ = gfx::GLContext::CreateGLContext( share_group, surface_.get(), gfx::PreferDiscreteGpu); bool is_current = context_->MakeCurrent(surface_.get()); DCHECK(is_current); } virtual void CleanUp() OVERRIDE { surface_ = NULL; context_->ReleaseCurrent(surface_.get()); context_ = NULL; } SafeSharedMemoryPool* safe_shared_memory_pool() { return &safe_shared_memory_pool_; } private: scoped_refptr context_; scoped_refptr surface_; SafeSharedMemoryPool safe_shared_memory_pool_; DISALLOW_COPY_AND_ASSIGN(TransferThread); }; base::LazyInstance g_transfer_thread = LAZY_INSTANCE_INITIALIZER; base::MessageLoopProxy* transfer_message_loop_proxy() { return g_transfer_thread.Pointer()->message_loop_proxy().get(); } SafeSharedMemoryPool* safe_shared_memory_pool() { return g_transfer_thread.Pointer()->safe_shared_memory_pool(); } // Class which holds async pixel transfers state (EGLImage). // The EGLImage is accessed by either thread, but everything // else accessed only on the main thread. class TransferStateInternal : public base::RefCountedThreadSafe { public: TransferStateInternal(GLuint texture_id, const AsyncTexImage2DParams& define_params, bool wait_for_uploads, bool wait_for_creation, bool use_image_preserved) : texture_id_(texture_id), thread_texture_id_(0), transfer_completion_(true, true), egl_image_(EGL_NO_IMAGE_KHR), wait_for_uploads_(wait_for_uploads), wait_for_creation_(wait_for_creation), use_image_preserved_(use_image_preserved) { define_params_ = define_params; } bool TransferIsInProgress() { return !transfer_completion_.IsSignaled(); } void BindTransfer() { TRACE_EVENT2("gpu", "BindAsyncTransfer glEGLImageTargetTexture2DOES", "width", define_params_.width, "height", define_params_.height); DCHECK(texture_id_); if (EGL_NO_IMAGE_KHR == egl_image_) return; glBindTexture(GL_TEXTURE_2D, texture_id_); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image_); bind_callback_.Run(); DCHECK(CHECK_GL()); } void CreateEglImage(GLuint texture_id) { TRACE_EVENT0("gpu", "eglCreateImageKHR"); DCHECK(texture_id); DCHECK_EQ(egl_image_, EGL_NO_IMAGE_KHR); EGLDisplay egl_display = eglGetCurrentDisplay(); EGLContext egl_context = eglGetCurrentContext(); EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR; EGLClientBuffer egl_buffer = reinterpret_cast(texture_id); EGLint image_preserved = use_image_preserved_ ? EGL_TRUE : EGL_FALSE; EGLint egl_attrib_list[] = { EGL_GL_TEXTURE_LEVEL_KHR, 0, // mip-level. EGL_IMAGE_PRESERVED_KHR, image_preserved, EGL_NONE }; egl_image_ = eglCreateImageKHR( egl_display, egl_context, egl_target, egl_buffer, egl_attrib_list); DLOG_IF(ERROR, EGL_NO_IMAGE_KHR == egl_image_) << "eglCreateImageKHR failed"; } void CreateEglImageOnUploadThread() { CreateEglImage(thread_texture_id_); } void CreateEglImageOnMainThreadIfNeeded() { if (egl_image_ == EGL_NO_IMAGE_KHR) { CreateEglImage(texture_id_); if (wait_for_creation_) { TRACE_EVENT0("gpu", "glFinish creation"); glFinish(); } } } void WaitForLastUpload() { // This glFinish is just a safe-guard for if uploads have some // GPU action that needs to occur. We could use fences and try // to do this less often. However, on older drivers fences are // not always reliable (eg. Mali-400 just blocks forever). if (wait_for_uploads_) { TRACE_EVENT0("gpu", "glFinish"); glFinish(); } } void MarkAsTransferIsInProgress() { TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("gpu.AsyncTexImage"); transfer_completion_.Reset(); } void MarkAsCompleted() { TRACE_EVENT_SYNTHETIC_DELAY_END("gpu.AsyncTexImage"); transfer_completion_.Signal(); } void WaitForTransferCompletion() { TRACE_EVENT0("gpu", "WaitForTransferCompletion"); // TODO(backer): Deschedule the channel rather than blocking the main GPU // thread (crbug.com/240265). transfer_completion_.Wait(); } void PerformAsyncTexImage2D( AsyncTexImage2DParams tex_params, AsyncMemoryParams mem_params, ScopedSafeSharedMemory* safe_shared_memory, scoped_refptr texture_upload_stats) { TRACE_EVENT2("gpu", "PerformAsyncTexImage", "width", tex_params.width, "height", tex_params.height); DCHECK(!thread_texture_id_); DCHECK_EQ(0, tex_params.level); if (EGL_NO_IMAGE_KHR != egl_image_) { MarkAsCompleted(); return; } void* data = AsyncPixelTransferDelegate::GetAddress(safe_shared_memory, mem_params); base::TimeTicks begin_time; if (texture_upload_stats.get()) begin_time = base::TimeTicks::HighResNow(); { TRACE_EVENT0("gpu", "glTexImage2D no data"); glGenTextures(1, &thread_texture_id_); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, thread_texture_id_); SetGlParametersForEglImageTexture(); // If we need to use image_preserved, we pass the data with // the allocation. Otherwise we use a NULL allocation to // try to avoid any costs associated with creating the EGLImage. if (use_image_preserved_) DoTexImage2D(tex_params, data); else DoTexImage2D(tex_params, NULL); } CreateEglImageOnUploadThread(); { TRACE_EVENT0("gpu", "glTexSubImage2D with data"); // If we didn't use image_preserved, we haven't uploaded // the data yet, so we do this with a full texSubImage. if (!use_image_preserved_) DoFullTexSubImage2D(tex_params, data); } WaitForLastUpload(); MarkAsCompleted(); DCHECK(CHECK_GL()); if (texture_upload_stats.get()) { texture_upload_stats->AddUpload(base::TimeTicks::HighResNow() - begin_time); } } void PerformAsyncTexSubImage2D( AsyncTexSubImage2DParams tex_params, AsyncMemoryParams mem_params, ScopedSafeSharedMemory* safe_shared_memory, scoped_refptr texture_upload_stats) { TRACE_EVENT2("gpu", "PerformAsyncTexSubImage2D", "width", tex_params.width, "height", tex_params.height); DCHECK_NE(EGL_NO_IMAGE_KHR, egl_image_); DCHECK_EQ(0, tex_params.level); void* data = AsyncPixelTransferDelegate::GetAddress(safe_shared_memory, mem_params); base::TimeTicks begin_time; if (texture_upload_stats.get()) begin_time = base::TimeTicks::HighResNow(); if (!thread_texture_id_) { TRACE_EVENT0("gpu", "glEGLImageTargetTexture2DOES"); glGenTextures(1, &thread_texture_id_); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, thread_texture_id_); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image_); } else { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, thread_texture_id_); } { TRACE_EVENT0("gpu", "glTexSubImage2D"); DoTexSubImage2D(tex_params, data); } WaitForLastUpload(); MarkAsCompleted(); DCHECK(CHECK_GL()); if (texture_upload_stats.get()) { texture_upload_stats->AddUpload(base::TimeTicks::HighResNow() - begin_time); } } protected: friend class base::RefCountedThreadSafe; friend class gpu::AsyncPixelTransferDelegateEGL; static void DeleteTexture(GLuint id) { glDeleteTextures(1, &id); } virtual ~TransferStateInternal() { if (egl_image_ != EGL_NO_IMAGE_KHR) { EGLDisplay display = eglGetCurrentDisplay(); eglDestroyImageKHR(display, egl_image_); } if (thread_texture_id_) { transfer_message_loop_proxy()->PostTask(FROM_HERE, base::Bind(&DeleteTexture, thread_texture_id_)); } } // The 'real' texture. GLuint texture_id_; // The EGLImage sibling on the upload thread. GLuint thread_texture_id_; // Definition params for texture that needs binding. AsyncTexImage2DParams define_params_; // Indicates that an async transfer is in progress. base::WaitableEvent transfer_completion_; // 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_; // Callback to invoke when AsyncTexImage2D is complete // and the client can safely use the texture. This occurs // during BindCompletedAsyncTransfers(). base::Closure bind_callback_; // Customize when we block on fences (these are work-arounds). bool wait_for_uploads_; bool wait_for_creation_; bool use_image_preserved_; }; } // namespace // Class which handles async pixel transfers using EGLImageKHR and another // upload thread class AsyncPixelTransferDelegateEGL : public AsyncPixelTransferDelegate, public base::SupportsWeakPtr { public: AsyncPixelTransferDelegateEGL( AsyncPixelTransferManagerEGL::SharedState* shared_state, GLuint texture_id, const AsyncTexImage2DParams& define_params); virtual ~AsyncPixelTransferDelegateEGL(); void BindTransfer() { state_->BindTransfer(); } // Implement AsyncPixelTransferDelegate: virtual void AsyncTexImage2D( const AsyncTexImage2DParams& tex_params, const AsyncMemoryParams& mem_params, const base::Closure& bind_callback) OVERRIDE; virtual void AsyncTexSubImage2D( const AsyncTexSubImage2DParams& tex_params, const AsyncMemoryParams& mem_params) OVERRIDE; virtual bool TransferIsInProgress() OVERRIDE; virtual void WaitForTransferCompletion() OVERRIDE; private: // Returns true if a work-around was used. bool WorkAroundAsyncTexImage2D( const AsyncTexImage2DParams& tex_params, const AsyncMemoryParams& mem_params, const base::Closure& bind_callback); bool WorkAroundAsyncTexSubImage2D( const AsyncTexSubImage2DParams& tex_params, const AsyncMemoryParams& mem_params); // A raw pointer is safe because the SharedState is owned by the Manager, // which owns this Delegate. AsyncPixelTransferManagerEGL::SharedState* shared_state_; scoped_refptr state_; DISALLOW_COPY_AND_ASSIGN(AsyncPixelTransferDelegateEGL); }; AsyncPixelTransferDelegateEGL::AsyncPixelTransferDelegateEGL( AsyncPixelTransferManagerEGL::SharedState* shared_state, GLuint texture_id, const AsyncTexImage2DParams& define_params) : shared_state_(shared_state) { // We can't wait on uploads on imagination (it can take 200ms+). // In practice, they are complete when the CPU glTexSubImage2D completes. bool wait_for_uploads = !shared_state_->is_imagination; // Qualcomm runs into texture corruption problems if the same texture is // uploaded to with both async and normal uploads. Synchronize after EGLImage // creation on the main thread as a work-around. bool wait_for_creation = shared_state_->is_qualcomm; // Qualcomm has a race when using image_preserved=FALSE, // which can result in black textures even after the first upload. // Since using FALSE is mainly for performance (to avoid layout changes), // but Qualcomm itself doesn't seem to get any performance benefit, // we just using image_preservedd=TRUE on Qualcomm as a work-around. bool use_image_preserved = shared_state_->is_qualcomm || shared_state_->is_imagination; state_ = new TransferStateInternal(texture_id, define_params, wait_for_uploads, wait_for_creation, use_image_preserved); } AsyncPixelTransferDelegateEGL::~AsyncPixelTransferDelegateEGL() {} bool AsyncPixelTransferDelegateEGL::TransferIsInProgress() { return state_->TransferIsInProgress(); } void AsyncPixelTransferDelegateEGL::WaitForTransferCompletion() { if (state_->TransferIsInProgress()) { #if defined(OS_ANDROID) || defined(OS_LINUX) g_transfer_thread.Pointer()->SetPriority(base::kThreadPriority_Display); #endif state_->WaitForTransferCompletion(); DCHECK(!state_->TransferIsInProgress()); #if defined(OS_ANDROID) || defined(OS_LINUX) g_transfer_thread.Pointer()->SetPriority(base::kThreadPriority_Background); #endif } } void AsyncPixelTransferDelegateEGL::AsyncTexImage2D( const AsyncTexImage2DParams& tex_params, const AsyncMemoryParams& mem_params, const base::Closure& bind_callback) { if (WorkAroundAsyncTexImage2D(tex_params, mem_params, bind_callback)) return; DCHECK(mem_params.shared_memory); DCHECK_LE(mem_params.shm_data_offset + mem_params.shm_data_size, mem_params.shm_size); DCHECK(!state_->TransferIsInProgress()); DCHECK_EQ(state_->egl_image_, EGL_NO_IMAGE_KHR); DCHECK_EQ(static_cast(GL_TEXTURE_2D), tex_params.target); DCHECK_EQ(tex_params.level, 0); // Mark the transfer in progress and save the late bind // callback, so we can notify the client when it is bound. shared_state_->pending_allocations.push_back(AsWeakPtr()); state_->bind_callback_ = bind_callback; // Mark the transfer in progress. state_->MarkAsTransferIsInProgress(); // Duplicate the shared memory so there is no way we can get // a use-after-free of the raw pixels. transfer_message_loop_proxy()->PostTask(FROM_HERE, base::Bind( &TransferStateInternal::PerformAsyncTexImage2D, state_, tex_params, mem_params, base::Owned(new ScopedSafeSharedMemory(safe_shared_memory_pool(), mem_params.shared_memory, mem_params.shm_size)), shared_state_->texture_upload_stats)); DCHECK(CHECK_GL()); } void AsyncPixelTransferDelegateEGL::AsyncTexSubImage2D( const AsyncTexSubImage2DParams& tex_params, const AsyncMemoryParams& mem_params) { TRACE_EVENT2("gpu", "AsyncTexSubImage2D", "width", tex_params.width, "height", tex_params.height); if (WorkAroundAsyncTexSubImage2D(tex_params, mem_params)) return; DCHECK(!state_->TransferIsInProgress()); DCHECK(mem_params.shared_memory); DCHECK_LE(mem_params.shm_data_offset + mem_params.shm_data_size, mem_params.shm_size); DCHECK_EQ(static_cast(GL_TEXTURE_2D), tex_params.target); DCHECK_EQ(tex_params.level, 0); // Mark the transfer in progress. state_->MarkAsTransferIsInProgress(); // If this wasn't async allocated, we don't have an EGLImage yet. // Create the EGLImage if it hasn't already been created. state_->CreateEglImageOnMainThreadIfNeeded(); // Duplicate the shared memory so there are no way we can get // a use-after-free of the raw pixels. transfer_message_loop_proxy()->PostTask(FROM_HERE, base::Bind( &TransferStateInternal::PerformAsyncTexSubImage2D, state_, tex_params, mem_params, base::Owned(new ScopedSafeSharedMemory(safe_shared_memory_pool(), mem_params.shared_memory, mem_params.shm_size)), shared_state_->texture_upload_stats)); DCHECK(CHECK_GL()); } namespace { bool IsPowerOfTwo (unsigned int x) { return ((x != 0) && !(x & (x - 1))); } bool IsMultipleOfEight(unsigned int x) { return (x & 7) == 0; } bool DimensionsSupportImgFastPath(int width, int height) { // Multiple of eight, but not a power of two. return IsMultipleOfEight(width) && IsMultipleOfEight(height) && !(IsPowerOfTwo(width) && IsPowerOfTwo(height)); } } // namespace // It is very difficult to stream uploads on Imagination GPUs: // - glTexImage2D defers a swizzle/stall until draw-time // - glTexSubImage2D will sleep for 16ms on a good day, and 100ms // or longer if OpenGL is in heavy use by another thread. // The one combination that avoids these problems requires: // a.) Allocations/Uploads must occur on different threads/contexts. // b.) Texture size must be non-power-of-two. // When using a+b, uploads will be incorrect/corrupt unless: // c.) Texture size must be a multiple-of-eight. // // To achieve a.) we allocate synchronously on the main thread followed // by uploading on the upload thread. When b/c are not true we fall back // on purely synchronous allocation/upload on the main thread. bool AsyncPixelTransferDelegateEGL::WorkAroundAsyncTexImage2D( const AsyncTexImage2DParams& tex_params, const AsyncMemoryParams& mem_params, const base::Closure& bind_callback) { if (!shared_state_->is_imagination) return false; // On imagination we allocate synchronously all the time, even // if the dimensions support fast uploads. This is for part a.) // above, so allocations occur on a different thread/context as uploads. void* data = GetAddress(mem_params); SetGlParametersForEglImageTexture(); { TRACE_EVENT0("gpu", "glTexImage2D with data"); DoTexImage2D(tex_params, data); } // The allocation has already occured, so mark it as finished // and ready for binding. CHECK(!state_->TransferIsInProgress()); // If the dimensions support fast async uploads, create the // EGLImage for future uploads. The late bind should not // be needed since the EGLImage was created from the main thread // texture, but this is required to prevent an imagination driver crash. if (DimensionsSupportImgFastPath(tex_params.width, tex_params.height)) { state_->CreateEglImageOnMainThreadIfNeeded(); shared_state_->pending_allocations.push_back(AsWeakPtr()); state_->bind_callback_ = bind_callback; } DCHECK(CHECK_GL()); return true; } bool AsyncPixelTransferDelegateEGL::WorkAroundAsyncTexSubImage2D( const AsyncTexSubImage2DParams& tex_params, const AsyncMemoryParams& mem_params) { if (!shared_state_->is_imagination) return false; // If the dimensions support fast async uploads, we can use the // normal async upload path for uploads. if (DimensionsSupportImgFastPath(tex_params.width, tex_params.height)) return false; // Fall back on a synchronous stub as we don't have a known fast path. // Also, older ICS drivers crash when we do any glTexSubImage2D on the // same thread. To work around this we do glTexImage2D instead. Since // we didn't create an EGLImage for this texture (see above), this is // okay, but it limits this API to full updates for now. DCHECK(!state_->egl_image_); DCHECK_EQ(tex_params.xoffset, 0); DCHECK_EQ(tex_params.yoffset, 0); DCHECK_EQ(state_->define_params_.width, tex_params.width); DCHECK_EQ(state_->define_params_.height, tex_params.height); DCHECK_EQ(state_->define_params_.level, tex_params.level); DCHECK_EQ(state_->define_params_.format, tex_params.format); DCHECK_EQ(state_->define_params_.type, tex_params.type); void* data = GetAddress(mem_params); base::TimeTicks begin_time; if (shared_state_->texture_upload_stats.get()) begin_time = base::TimeTicks::HighResNow(); { TRACE_EVENT0("gpu", "glTexSubImage2D"); // Note we use define_params_ instead of tex_params. // The DCHECKs above verify this is always the same. DoTexImage2D(state_->define_params_, data); } if (shared_state_->texture_upload_stats.get()) { shared_state_->texture_upload_stats ->AddUpload(base::TimeTicks::HighResNow() - begin_time); } DCHECK(CHECK_GL()); return true; } AsyncPixelTransferManagerEGL::SharedState::SharedState() // TODO(reveman): Skip this if --enable-gpu-benchmarking is not present. : texture_upload_stats(new AsyncPixelTransferUploadStats) { std::string vendor; vendor = reinterpret_cast(glGetString(GL_VENDOR)); is_imagination = vendor.find("Imagination") != std::string::npos; is_qualcomm = vendor.find("Qualcomm") != std::string::npos; } AsyncPixelTransferManagerEGL::SharedState::~SharedState() {} AsyncPixelTransferManagerEGL::AsyncPixelTransferManagerEGL() {} AsyncPixelTransferManagerEGL::~AsyncPixelTransferManagerEGL() {} void AsyncPixelTransferManagerEGL::BindCompletedAsyncTransfers() { scoped_ptr texture_binder; while(!shared_state_.pending_allocations.empty()) { if (!shared_state_.pending_allocations.front().get()) { shared_state_.pending_allocations.pop_front(); continue; } AsyncPixelTransferDelegateEGL* delegate = shared_state_.pending_allocations.front().get(); // Terminate early, as all transfers finish in order, currently. if (delegate->TransferIsInProgress()) break; if (!texture_binder) texture_binder.reset(new gfx::ScopedTextureBinder(GL_TEXTURE_2D, 0)); // If the transfer is finished, bind it to the texture // and remove it from pending list. delegate->BindTransfer(); shared_state_.pending_allocations.pop_front(); } } void AsyncPixelTransferManagerEGL::AsyncNotifyCompletion( const AsyncMemoryParams& mem_params, AsyncPixelTransferCompletionObserver* observer) { DCHECK(mem_params.shared_memory); DCHECK_LE(mem_params.shm_data_offset + mem_params.shm_data_size, mem_params.shm_size); // Post a PerformNotifyCompletion task to the upload thread. This task // will run after all async transfers are complete. transfer_message_loop_proxy()->PostTask( FROM_HERE, base::Bind(&PerformNotifyCompletion, mem_params, base::Owned( new ScopedSafeSharedMemory(safe_shared_memory_pool(), mem_params.shared_memory, mem_params.shm_size)), make_scoped_refptr(observer))); } uint32 AsyncPixelTransferManagerEGL::GetTextureUploadCount() { return shared_state_.texture_upload_stats->GetStats(NULL); } base::TimeDelta AsyncPixelTransferManagerEGL::GetTotalTextureUploadTime() { base::TimeDelta total_texture_upload_time; shared_state_.texture_upload_stats->GetStats(&total_texture_upload_time); return total_texture_upload_time; } void AsyncPixelTransferManagerEGL::ProcessMorePendingTransfers() { } bool AsyncPixelTransferManagerEGL::NeedsProcessMorePendingTransfers() { return false; } AsyncPixelTransferDelegate* AsyncPixelTransferManagerEGL::CreatePixelTransferDelegateImpl( gles2::TextureRef* ref, const AsyncTexImage2DParams& define_params) { return new AsyncPixelTransferDelegateEGL( &shared_state_, ref->service_id(), define_params); } } // namespace gpu