blob: 85a4374ae93cadbeaade24ca5c0780d25e6547d3 [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 "ui/gl/async_pixel_transfer_delegate_android.h"
#include <string>
#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/process_util.h"
#include "base/shared_memory.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "ui/gl/async_pixel_transfer_delegate.h"
#include "ui/gl/async_pixel_transfer_delegate_stub.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"
#include "ui/gl/safe_shared_memory_pool.h"
// TODO(epenner): Move thread priorities to base. (crbug.com/170549)
#include <sys/resource.h>
using base::SharedMemory;
using base::SharedMemoryHandle;
namespace gfx {
namespace {
class TextureUploadStats
: public base::RefCountedThreadSafe<TextureUploadStats> {
public:
TextureUploadStats() : texture_upload_count_(0) {}
void AddUpload(base::TimeDelta transfer_time) {
base::AutoLock scoped_lock(lock_);
texture_upload_count_++;
total_texture_upload_time_ += transfer_time;
}
int GetStats(base::TimeDelta* total_texture_upload_time) {
base::AutoLock scoped_lock(lock_);
if (total_texture_upload_time)
*total_texture_upload_time = total_texture_upload_time_;
return texture_upload_count_;
}
private:
friend class RefCountedThreadSafe<TextureUploadStats>;
~TextureUploadStats() {}
int texture_upload_count_;
base::TimeDelta total_texture_upload_time_;
base::Lock lock_;
DISALLOW_COPY_AND_ASSIGN(TextureUploadStats);
};
const char kAsyncTransferThreadName[] = "AsyncTransferThread";
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__)
// 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);
}
// Gets the address of the data from shared memory.
void* GetAddress(SharedMemory* shared_memory, uint32 shm_data_offset) {
// Memory bounds have already been validated, so there
// is just DCHECKS here.
CHECK(shared_memory);
CHECK(shared_memory->memory());
return static_cast<int8*>(shared_memory->memory()) + shm_data_offset;
}
class TransferThread : public base::Thread {
public:
TransferThread() : base::Thread(kAsyncTransferThreadName) {
Start();
}
virtual ~TransferThread() {
Stop();
}
virtual void Init() OVERRIDE {
GLShareGroup* share_group = NULL;
bool software = false;
surface_ = new gfx::PbufferGLSurfaceEGL(software, gfx::Size(1,1));
surface_->Initialize();
context_ = gfx::GLContext::CreateGLContext(share_group,
surface_,
gfx::PreferDiscreteGpu);
bool is_current = context_->MakeCurrent(surface_);
DCHECK(is_current);
// TODO(epenner): Move thread priorities to base. (crbug.com/170549)
int nice_value = 10; // Idle priority.
setpriority(PRIO_PROCESS, base::PlatformThread::CurrentId(), nice_value);
}
virtual void CleanUp() OVERRIDE {
surface_ = NULL;
context_->ReleaseCurrent(surface_);
context_ = NULL;
}
SafeSharedMemoryPool* safe_shared_memory_pool() {
return &safe_shared_memory_pool_;
}
private:
scoped_refptr<gfx::GLContext> context_;
scoped_refptr<gfx::GLSurface> surface_;
SafeSharedMemoryPool safe_shared_memory_pool_;
DISALLOW_COPY_AND_ASSIGN(TransferThread);
};
base::LazyInstance<TransferThread>
g_transfer_thread = LAZY_INSTANCE_INITIALIZER;
base::MessageLoopProxy* transfer_message_loop_proxy() {
return g_transfer_thread.Pointer()->message_loop_proxy();
}
SafeSharedMemoryPool* safe_shared_memory_pool() {
return g_transfer_thread.Pointer()->safe_shared_memory_pool();
}
} // namespace
// 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<TransferStateInternal> {
public:
explicit TransferStateInternal(GLuint texture_id,
bool wait_for_uploads,
bool wait_for_creation,
bool use_image_preserved)
: texture_id_(texture_id),
thread_texture_id_(0),
needs_late_bind_(false),
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) {
static const AsyncTexImage2DParams zero_params = {0, 0, 0, 0, 0, 0, 0, 0};
late_bind_define_params_ = zero_params;
}
// Implement AsyncPixelTransferState:
bool TransferIsInProgress() {
return !transfer_completion_.IsSignaled();
}
void BindTransfer(AsyncTexImage2DParams* bound_params) {
TRACE_EVENT2("gpu", "BindAsyncTransfer glEGLImageTargetTexture2DOES",
"width", late_bind_define_params_.width,
"height", late_bind_define_params_.height);
DCHECK(bound_params);
DCHECK(texture_id_);
*bound_params = late_bind_define_params_;
if (!needs_late_bind_)
return;
DCHECK_NE(EGL_NO_IMAGE_KHR, egl_image_);
// We can only change the active texture and unit 0,
// as that is all that will be restored.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_id_);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image_);
needs_late_bind_ = false;
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<EGLClientBuffer>(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);
DCHECK_NE(EGL_NO_IMAGE_KHR, egl_image_);
}
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() {
transfer_completion_.Reset();
}
void MarkAsCompleted() {
transfer_completion_.Signal();
}
void WaitForTransferCompletion() {
transfer_completion_.Wait();
}
protected:
friend class base::RefCountedThreadSafe<TransferStateInternal>;
friend class AsyncPixelTransferDelegateAndroid;
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_;
// Indicates there is a new EGLImage and the 'real'
// texture needs to be bound to it as an EGLImage target.
bool needs_late_bind_;
// Definition params for texture that needs binding.
AsyncTexImage2DParams late_bind_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_;
// Customize when we block on fences (these are work-arounds).
bool wait_for_uploads_;
bool wait_for_creation_;
bool use_image_preserved_;
};
// Android needs thread-safe ref-counting, so this just wraps
// an internal thread-safe ref-counted state object.
class AsyncTransferStateAndroid : public AsyncPixelTransferState {
public:
explicit AsyncTransferStateAndroid(GLuint texture_id,
bool wait_for_uploads,
bool wait_for_creation,
bool use_image_preserved)
: internal_(new TransferStateInternal(texture_id,
wait_for_uploads,
wait_for_creation,
use_image_preserved)) {
}
virtual ~AsyncTransferStateAndroid() {}
virtual bool TransferIsInProgress() OVERRIDE {
return internal_->TransferIsInProgress();
}
virtual void BindTransfer(AsyncTexImage2DParams* bound_params) OVERRIDE {
internal_->BindTransfer(bound_params);
}
scoped_refptr<TransferStateInternal> internal_;
};
// Class which handles async pixel transfers on Android (using
// EGLImageKHR and another upload thread)
class AsyncPixelTransferDelegateAndroid
: public AsyncPixelTransferDelegate,
public base::SupportsWeakPtr<AsyncPixelTransferDelegateAndroid> {
public:
AsyncPixelTransferDelegateAndroid();
virtual ~AsyncPixelTransferDelegateAndroid();
// implement AsyncPixelTransferDelegate:
virtual void AsyncNotifyCompletion(
const AsyncMemoryParams& mem_params,
const CompletionCallback& callback) OVERRIDE;
virtual void AsyncTexImage2D(
AsyncPixelTransferState* state,
const AsyncTexImage2DParams& tex_params,
const AsyncMemoryParams& mem_params) OVERRIDE;
virtual void AsyncTexSubImage2D(
AsyncPixelTransferState* state,
const AsyncTexSubImage2DParams& tex_params,
const AsyncMemoryParams& mem_params) OVERRIDE;
virtual void WaitForTransferCompletion(
AsyncPixelTransferState* state) OVERRIDE;
virtual uint32 GetTextureUploadCount() OVERRIDE;
virtual base::TimeDelta GetTotalTextureUploadTime() OVERRIDE;
private:
// implement AsyncPixelTransferDelegate:
virtual AsyncPixelTransferState*
CreateRawPixelTransferState(GLuint texture_id) OVERRIDE;
static void PerformNotifyCompletion(
AsyncMemoryParams mem_params,
ScopedSafeSharedMemory* safe_shared_memory,
const CompletionCallback& callback);
static void PerformAsyncTexImage2D(
TransferStateInternal* state,
AsyncTexImage2DParams tex_params,
AsyncMemoryParams mem_params,
ScopedSafeSharedMemory* safe_shared_memory);
static void PerformAsyncTexSubImage2D(
TransferStateInternal* state,
AsyncTexSubImage2DParams tex_params,
AsyncMemoryParams mem_params,
ScopedSafeSharedMemory* safe_shared_memory,
scoped_refptr<TextureUploadStats> texture_upload_stats);
// Returns true if a work-around was used.
bool WorkAroundAsyncTexImage2D(
TransferStateInternal* state,
const AsyncTexImage2DParams& tex_params,
const AsyncMemoryParams& mem_params);
bool WorkAroundAsyncTexSubImage2D(
TransferStateInternal* state,
const AsyncTexSubImage2DParams& tex_params,
const AsyncMemoryParams& mem_params);
scoped_refptr<TextureUploadStats> texture_upload_stats_;
bool is_imagination_;
bool is_qualcomm_;
DISALLOW_COPY_AND_ASSIGN(AsyncPixelTransferDelegateAndroid);
};
// We only used threaded uploads when we can:
// - Create EGLImages out of OpenGL textures (EGL_KHR_gl_texture_2D_image)
// - Bind EGLImages to OpenGL textures (GL_OES_EGL_image)
// - Use fences (to test for upload completion).
scoped_ptr<AsyncPixelTransferDelegate>
AsyncPixelTransferDelegate::Create(gfx::GLContext* context) {
DCHECK(context);
if (context->HasExtension("EGL_KHR_fence_sync") &&
context->HasExtension("EGL_KHR_image") &&
context->HasExtension("EGL_KHR_image_base") &&
context->HasExtension("EGL_KHR_gl_texture_2D_image") &&
context->HasExtension("GL_OES_EGL_image")) {
return make_scoped_ptr(
static_cast<AsyncPixelTransferDelegate*>(
new AsyncPixelTransferDelegateAndroid()));
} else {
LOG(INFO) << "Async pixel transfers not supported";
return make_scoped_ptr(
static_cast<AsyncPixelTransferDelegate*>(
new AsyncPixelTransferDelegateStub()));
}
}
AsyncPixelTransferDelegateAndroid::AsyncPixelTransferDelegateAndroid() {
std::string vendor;
vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
is_imagination_ = vendor.find("Imagination") != std::string::npos;
is_qualcomm_ = vendor.find("Qualcomm") != std::string::npos;
// TODO(reveman): Skip this if --enable-gpu-benchmarking is not present.
texture_upload_stats_ = make_scoped_refptr(new TextureUploadStats);
}
AsyncPixelTransferDelegateAndroid::~AsyncPixelTransferDelegateAndroid() {
}
AsyncPixelTransferState*
AsyncPixelTransferDelegateAndroid::
CreateRawPixelTransferState(GLuint texture_id) {
// 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 = !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 = 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 = is_qualcomm_ || is_imagination_;
return static_cast<AsyncPixelTransferState*>(
new AsyncTransferStateAndroid(texture_id,
wait_for_uploads,
wait_for_creation,
use_image_preserved));
}
void AsyncPixelTransferDelegateAndroid::AsyncNotifyCompletion(
const AsyncMemoryParams& mem_params,
const CompletionCallback& callback) {
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(&AsyncPixelTransferDelegateAndroid::PerformNotifyCompletion,
mem_params,
base::Owned(
new ScopedSafeSharedMemory(safe_shared_memory_pool(),
mem_params.shared_memory,
mem_params.shm_size)),
callback));
}
void AsyncPixelTransferDelegateAndroid::WaitForTransferCompletion(
AsyncPixelTransferState* transfer_state) {
TRACE_EVENT0("gpu", "WaitForTransferCompletion");
scoped_refptr<TransferStateInternal> state =
static_cast<AsyncTransferStateAndroid*>(transfer_state)->internal_.get();
DCHECK(state);
DCHECK(state->texture_id_);
if (state->TransferIsInProgress()) {
// TODO(epenner): Move thread priorities to base. (crbug.com/170549)
int default_nice_value = 0; // Default priority.
int idle_nice_value = 10; // Idle priority.
setpriority(PRIO_PROCESS,
g_transfer_thread.Pointer()->thread_id(),
default_nice_value);
state->WaitForTransferCompletion();
DCHECK(!state->TransferIsInProgress());
// TODO(epenner): Move thread priorities to base. (crbug.com/170549)
setpriority(PRIO_PROCESS,
g_transfer_thread.Pointer()->thread_id(),
idle_nice_value);
}
}
void AsyncPixelTransferDelegateAndroid::AsyncTexImage2D(
AsyncPixelTransferState* transfer_state,
const AsyncTexImage2DParams& tex_params,
const AsyncMemoryParams& mem_params) {
scoped_refptr<TransferStateInternal> state =
static_cast<AsyncTransferStateAndroid*>(transfer_state)->internal_.get();
DCHECK(mem_params.shared_memory);
DCHECK_LE(mem_params.shm_data_offset + mem_params.shm_data_size,
mem_params.shm_size);
DCHECK(state);
DCHECK(state->texture_id_);
DCHECK(!state->needs_late_bind_);
DCHECK(!state->TransferIsInProgress());
DCHECK_EQ(state->egl_image_, EGL_NO_IMAGE_KHR);
DCHECK_EQ(static_cast<GLenum>(GL_TEXTURE_2D), tex_params.target);
DCHECK_EQ(tex_params.level, 0);
if (WorkAroundAsyncTexImage2D(state, tex_params, mem_params))
return;
// Mark the transfer in progress and save define params for lazy binding.
state->needs_late_bind_ = true;
state->late_bind_define_params_ = tex_params;
// Mark the transfer in progress.
state->MarkAsTransferIsInProgress();
// 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(
&AsyncPixelTransferDelegateAndroid::PerformAsyncTexImage2D,
state,
tex_params,
mem_params,
base::Owned(new ScopedSafeSharedMemory(safe_shared_memory_pool(),
mem_params.shared_memory,
mem_params.shm_size))));
DCHECK(CHECK_GL());
}
void AsyncPixelTransferDelegateAndroid::AsyncTexSubImage2D(
AsyncPixelTransferState* transfer_state,
const AsyncTexSubImage2DParams& tex_params,
const AsyncMemoryParams& mem_params) {
TRACE_EVENT2("gpu", "AsyncTexSubImage2D",
"width", tex_params.width,
"height", tex_params.height);
scoped_refptr<TransferStateInternal> state =
static_cast<AsyncTransferStateAndroid*>(transfer_state)->internal_.get();
DCHECK(state->texture_id_);
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<GLenum>(GL_TEXTURE_2D), tex_params.target);
DCHECK_EQ(tex_params.level, 0);
if (WorkAroundAsyncTexSubImage2D(state, tex_params, mem_params))
return;
// 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(
&AsyncPixelTransferDelegateAndroid::PerformAsyncTexSubImage2D,
state,
tex_params,
mem_params,
base::Owned(new ScopedSafeSharedMemory(safe_shared_memory_pool(),
mem_params.shared_memory,
mem_params.shm_size)),
texture_upload_stats_));
DCHECK(CHECK_GL());
}
uint32 AsyncPixelTransferDelegateAndroid::GetTextureUploadCount() {
CHECK(texture_upload_stats_);
return texture_upload_stats_->GetStats(NULL);
}
base::TimeDelta AsyncPixelTransferDelegateAndroid::GetTotalTextureUploadTime() {
CHECK(texture_upload_stats_);
base::TimeDelta total_texture_upload_time;
texture_upload_stats_->GetStats(&total_texture_upload_time);
return total_texture_upload_time;
}
namespace {
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);
}
} // namespace
void AsyncPixelTransferDelegateAndroid::PerformNotifyCompletion(
AsyncMemoryParams mem_params,
ScopedSafeSharedMemory* safe_shared_memory,
const CompletionCallback& callback) {
TRACE_EVENT0("gpu", "PerformNotifyCompletion");
gfx::AsyncMemoryParams safe_mem_params = mem_params;
safe_mem_params.shared_memory = safe_shared_memory->shared_memory();
callback.Run(safe_mem_params);
}
void AsyncPixelTransferDelegateAndroid::PerformAsyncTexImage2D(
TransferStateInternal* state,
AsyncTexImage2DParams tex_params,
AsyncMemoryParams mem_params,
ScopedSafeSharedMemory* safe_shared_memory) {
TRACE_EVENT2("gpu", "PerformAsyncTexImage",
"width", tex_params.width,
"height", tex_params.height);
DCHECK(state);
DCHECK(!state->thread_texture_id_);
DCHECK_EQ(0, tex_params.level);
DCHECK_EQ(EGL_NO_IMAGE_KHR, state->egl_image_);
void* data = GetAddress(safe_shared_memory->shared_memory(),
mem_params.shm_data_offset);
{
TRACE_EVENT0("gpu", "glTexImage2D no data");
glGenTextures(1, &state->thread_texture_id_);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, state->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 (state->use_image_preserved_)
DoTexImage2D(tex_params, data);
else
DoTexImage2D(tex_params, NULL);
}
state->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 (!state->use_image_preserved_)
DoFullTexSubImage2D(tex_params, data);
}
state->WaitForLastUpload();
state->MarkAsCompleted();
DCHECK(CHECK_GL());
}
void AsyncPixelTransferDelegateAndroid::PerformAsyncTexSubImage2D(
TransferStateInternal* state,
AsyncTexSubImage2DParams tex_params,
AsyncMemoryParams mem_params,
ScopedSafeSharedMemory* safe_shared_memory,
scoped_refptr<TextureUploadStats> texture_upload_stats) {
TRACE_EVENT2("gpu", "PerformAsyncTexSubImage2D",
"width", tex_params.width,
"height", tex_params.height);
DCHECK(state);
DCHECK_NE(EGL_NO_IMAGE_KHR, state->egl_image_);
DCHECK_EQ(0, tex_params.level);
void* data = GetAddress(safe_shared_memory->shared_memory(),
mem_params.shm_data_offset);
base::TimeTicks begin_time;
if (texture_upload_stats)
begin_time = base::TimeTicks::HighResNow();
if (!state->thread_texture_id_) {
TRACE_EVENT0("gpu", "glEGLImageTargetTexture2DOES");
glGenTextures(1, &state->thread_texture_id_);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, state->thread_texture_id_);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, state->egl_image_);
} else {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, state->thread_texture_id_);
}
{
TRACE_EVENT0("gpu", "glTexSubImage2D");
DoTexSubImage2D(tex_params, data);
}
state->WaitForLastUpload();
state->MarkAsCompleted();
DCHECK(CHECK_GL());
if (texture_upload_stats) {
texture_upload_stats->AddUpload(
base::TimeTicks::HighResNow() - begin_time);
}
}
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 AsyncPixelTransferDelegateAndroid::WorkAroundAsyncTexImage2D(
TransferStateInternal* state,
const AsyncTexImage2DParams& tex_params,
const AsyncMemoryParams& mem_params) {
if (!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.shared_memory,
mem_params.shm_data_offset);
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.
state->needs_late_bind_ = false;
state->late_bind_define_params_ = tex_params;
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();
state->needs_late_bind_ = true;
}
DCHECK(CHECK_GL());
return true;
}
bool AsyncPixelTransferDelegateAndroid::WorkAroundAsyncTexSubImage2D(
TransferStateInternal* state,
const AsyncTexSubImage2DParams& tex_params,
const AsyncMemoryParams& mem_params) {
if (!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->late_bind_define_params_.width, tex_params.width);
DCHECK_EQ(state->late_bind_define_params_.height, tex_params.height);
DCHECK_EQ(state->late_bind_define_params_.level, tex_params.level);
DCHECK_EQ(state->late_bind_define_params_.format, tex_params.format);
DCHECK_EQ(state->late_bind_define_params_.type, tex_params.type);
void* data = GetAddress(mem_params.shared_memory,
mem_params.shm_data_offset);
base::TimeTicks begin_time;
if (texture_upload_stats_)
begin_time = base::TimeTicks::HighResNow();
{
TRACE_EVENT0("gpu", "glTexSubImage2D");
// Note we use late_bind_define_params_ instead of tex_params.
// The DCHECKs above verify this is always the same.
DoTexImage2D(state->late_bind_define_params_, data);
}
if (texture_upload_stats_) {
texture_upload_stats_->AddUpload(
base::TimeTicks::HighResNow() - begin_time);
}
DCHECK(CHECK_GL());
return true;
}
} // namespace gfx