// 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/browser/renderer_host/image_transport_factory_android.h"

#include "base/lazy_instance.h"
#include "base/strings/stringprintf.h"
#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
#include "content/common/gpu/client/gl_helper.h"
#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
#include "content/common/gpu/gpu_process_launch_causes.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "ui/gfx/android/device_display_info.h"

namespace content {

base::LazyInstance<ObserverList<ImageTransportFactoryAndroidObserver> >::Leaky
    g_factory_observers = LAZY_INSTANCE_INITIALIZER;

class GLContextLostListener
    : public blink::WebGraphicsContext3D::WebGraphicsContextLostCallback {
 public:
  // WebGraphicsContextLostCallback implementation.
  virtual void onContextLost() OVERRIDE;
 private:
  static void DidLoseContext();
};

namespace {

static ImageTransportFactoryAndroid* g_factory = NULL;

class CmdBufferImageTransportFactory : public ImageTransportFactoryAndroid {
 public:
  CmdBufferImageTransportFactory();
  virtual ~CmdBufferImageTransportFactory();

  virtual GLHelper* GetGLHelper() OVERRIDE;
  virtual uint32 GetChannelID() OVERRIDE {
    return BrowserGpuChannelHostFactory::instance()->GetGpuChannelId();
  }

 private:
  scoped_ptr<WebGraphicsContext3DCommandBufferImpl> context_;
  scoped_ptr<GLHelper> gl_helper_;

  DISALLOW_COPY_AND_ASSIGN(CmdBufferImageTransportFactory);
};

CmdBufferImageTransportFactory::CmdBufferImageTransportFactory() {
  BrowserGpuChannelHostFactory* factory =
      BrowserGpuChannelHostFactory::instance();
  scoped_refptr<GpuChannelHost> gpu_channel_host(factory->EstablishGpuChannelSync(
      CAUSE_FOR_GPU_LAUNCH_WEBGRAPHICSCONTEXT3DCOMMANDBUFFERIMPL_INITIALIZE));
  DCHECK(gpu_channel_host);

  blink::WebGraphicsContext3D::Attributes attrs;
  attrs.shareResources = true;
  GURL url("chrome://gpu/ImageTransportFactoryAndroid");
  static const size_t kBytesPerPixel = 4;
  gfx::DeviceDisplayInfo display_info;
  size_t full_screen_texture_size_in_bytes = display_info.GetDisplayHeight() *
                                             display_info.GetDisplayWidth() *
                                             kBytesPerPixel;
  WebGraphicsContext3DCommandBufferImpl::SharedMemoryLimits limits;
  limits.command_buffer_size = 64 * 1024;
  limits.start_transfer_buffer_size = 64 * 1024;
  limits.min_transfer_buffer_size = 64 * 1024;
  limits.max_transfer_buffer_size = std::min(
      3 * full_screen_texture_size_in_bytes, kDefaultMaxTransferBufferSize);
  limits.mapped_memory_reclaim_limit =
      WebGraphicsContext3DCommandBufferImpl::kNoLimit;
  bool lose_context_when_out_of_memory = false;
  context_.reset(
      new WebGraphicsContext3DCommandBufferImpl(0,  // offscreen
                                                url,
                                                gpu_channel_host.get(),
                                                attrs,
                                                lose_context_when_out_of_memory,
                                                limits,
                                                NULL));
  context_->setContextLostCallback(context_lost_listener_.get());
  if (context_->makeContextCurrent())
    context_->pushGroupMarkerEXT(
        base::StringPrintf("CmdBufferImageTransportFactory-%p",
                           context_.get()).c_str());
}

CmdBufferImageTransportFactory::~CmdBufferImageTransportFactory() {
  context_->setContextLostCallback(NULL);
}

GLHelper* CmdBufferImageTransportFactory::GetGLHelper() {
  if (!gl_helper_)
    gl_helper_.reset(new GLHelper(context_->GetImplementation(),
                                  context_->GetContextSupport()));

  return gl_helper_.get();
}

}  // anonymous namespace

// static
void ImageTransportFactoryAndroid::InitializeForUnitTests(
    scoped_ptr<ImageTransportFactoryAndroid> test_factory) {
  DCHECK(!g_factory);
  g_factory = test_factory.release();
}

// static
void ImageTransportFactoryAndroid::TerminateForUnitTests() {
  DCHECK(g_factory);
  delete g_factory;
  g_factory = NULL;
}

// static
ImageTransportFactoryAndroid* ImageTransportFactoryAndroid::GetInstance() {
  if (!g_factory)
    g_factory = new CmdBufferImageTransportFactory();

  return g_factory;
}

ImageTransportFactoryAndroid::ImageTransportFactoryAndroid()
    : context_lost_listener_(new GLContextLostListener()) {}

ImageTransportFactoryAndroid::~ImageTransportFactoryAndroid() {}

void ImageTransportFactoryAndroid::AddObserver(
    ImageTransportFactoryAndroidObserver* observer) {
  g_factory_observers.Get().AddObserver(observer);
}

void ImageTransportFactoryAndroid::RemoveObserver(
    ImageTransportFactoryAndroidObserver* observer) {
  g_factory_observers.Get().RemoveObserver(observer);
}

void GLContextLostListener::onContextLost() {
  // Need to post a task because the command buffer client cannot be deleted
  // from within this callback.
  LOG(ERROR) << "Context lost.";
  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&GLContextLostListener::DidLoseContext));
}

void GLContextLostListener::DidLoseContext() {
  delete g_factory;
  g_factory = NULL;
  FOR_EACH_OBSERVER(ImageTransportFactoryAndroidObserver,
      g_factory_observers.Get(),
      OnLostResources());
}

} // namespace content
