// 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/common/gpu/gpu_channel_manager.h"

#include "base/bind.h"
#include "base/command_line.h"
#include "content/child/child_thread.h"
#include "content/common/gpu/gpu_channel.h"
#include "content/common/gpu/gpu_memory_manager.h"
#include "content/common/gpu/gpu_messages.h"
#include "content/common/gpu/sync_point_manager.h"
#include "gpu/command_buffer/service/feature_info.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/command_buffer/service/mailbox_manager.h"
#include "gpu/command_buffer/service/memory_program_cache.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_share_group.h"

namespace content {

GpuChannelManager::ImageOperation::ImageOperation(
    int32 sync_point, base::Closure callback)
    : sync_point(sync_point),
      callback(callback) {
}

GpuChannelManager::ImageOperation::~ImageOperation() {
}

GpuChannelManager::GpuChannelManager(ChildThread* gpu_child_thread,
                                     GpuWatchdog* watchdog,
                                     base::MessageLoopProxy* io_message_loop,
                                     base::WaitableEvent* shutdown_event)
    : weak_factory_(this),
      io_message_loop_(io_message_loop),
      shutdown_event_(shutdown_event),
      gpu_child_thread_(gpu_child_thread),
      gpu_memory_manager_(
          this,
          GpuMemoryManager::kDefaultMaxSurfacesWithFrontbufferSoftLimit),
      watchdog_(watchdog),
      sync_point_manager_(new SyncPointManager) {
  DCHECK(gpu_child_thread);
  DCHECK(io_message_loop);
  DCHECK(shutdown_event);
}

GpuChannelManager::~GpuChannelManager() {
  gpu_channels_.clear();
  if (default_offscreen_surface_.get()) {
    default_offscreen_surface_->Destroy();
    default_offscreen_surface_ = NULL;
  }
  DCHECK(image_operations_.empty());
}

gpu::gles2::ProgramCache* GpuChannelManager::program_cache() {
  if (!program_cache_.get() &&
      (gfx::g_driver_gl.ext.b_ARB_get_program_binary ||
       gfx::g_driver_gl.ext.b_OES_get_program_binary) &&
      !CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kDisableGpuProgramCache)) {
    program_cache_.reset(new gpu::gles2::MemoryProgramCache());
  }
  return program_cache_.get();
}

void GpuChannelManager::RemoveChannel(int client_id) {
  Send(new GpuHostMsg_DestroyChannel(client_id));
  gpu_channels_.erase(client_id);
}

int GpuChannelManager::GenerateRouteID() {
  static int last_id = 0;
  return ++last_id;
}

void GpuChannelManager::AddRoute(int32 routing_id, IPC::Listener* listener) {
  gpu_child_thread_->AddRoute(routing_id, listener);
}

void GpuChannelManager::RemoveRoute(int32 routing_id) {
  gpu_child_thread_->RemoveRoute(routing_id);
}

GpuChannel* GpuChannelManager::LookupChannel(int32 client_id) {
  GpuChannelMap::const_iterator iter = gpu_channels_.find(client_id);
  if (iter == gpu_channels_.end())
    return NULL;
  else
    return iter->second.get();
}

bool GpuChannelManager::OnMessageReceived(const IPC::Message& msg) {
  bool msg_is_ok = true;
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_EX(GpuChannelManager, msg, msg_is_ok)
    IPC_MESSAGE_HANDLER(GpuMsg_EstablishChannel, OnEstablishChannel)
    IPC_MESSAGE_HANDLER(GpuMsg_CloseChannel, OnCloseChannel)
    IPC_MESSAGE_HANDLER(GpuMsg_CreateViewCommandBuffer,
                        OnCreateViewCommandBuffer)
    IPC_MESSAGE_HANDLER(GpuMsg_CreateImage, OnCreateImage)
    IPC_MESSAGE_HANDLER(GpuMsg_DeleteImage, OnDeleteImage)
    IPC_MESSAGE_HANDLER(GpuMsg_LoadedShader, OnLoadedShader)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP_EX()
  return handled;
}

bool GpuChannelManager::Send(IPC::Message* msg) {
  return gpu_child_thread_->Send(msg);
}

void GpuChannelManager::OnEstablishChannel(int client_id, bool share_context) {
  IPC::ChannelHandle channel_handle;

  gfx::GLShareGroup* share_group = NULL;
  gpu::gles2::MailboxManager* mailbox_manager = NULL;
  if (share_context) {
    if (!share_group_.get()) {
      share_group_ = new gfx::GLShareGroup;
      DCHECK(!mailbox_manager_.get());
      mailbox_manager_ = new gpu::gles2::MailboxManager;
    }
    share_group = share_group_.get();
    mailbox_manager = mailbox_manager_.get();
  }

  scoped_refptr<GpuChannel> channel = new GpuChannel(this,
                                                     watchdog_,
                                                     share_group,
                                                     mailbox_manager,
                                                     client_id,
                                                     false);
  if (channel->Init(io_message_loop_.get(), shutdown_event_)) {
    gpu_channels_[client_id] = channel;
    channel_handle.name = channel->GetChannelName();

#if defined(OS_POSIX)
    // On POSIX, pass the renderer-side FD. Also mark it as auto-close so
    // that it gets closed after it has been sent.
    int renderer_fd = channel->TakeRendererFileDescriptor();
    DCHECK_NE(-1, renderer_fd);
    channel_handle.socket = base::FileDescriptor(renderer_fd, true);
#endif
  }

  Send(new GpuHostMsg_ChannelEstablished(channel_handle));
}

void GpuChannelManager::OnCloseChannel(
    const IPC::ChannelHandle& channel_handle) {
  for (GpuChannelMap::iterator iter = gpu_channels_.begin();
       iter != gpu_channels_.end(); ++iter) {
    if (iter->second->GetChannelName() == channel_handle.name) {
      gpu_channels_.erase(iter);
      return;
    }
  }
}

void GpuChannelManager::OnCreateViewCommandBuffer(
    const gfx::GLSurfaceHandle& window,
    int32 surface_id,
    int32 client_id,
    const GPUCreateCommandBufferConfig& init_params) {
  DCHECK(surface_id);
  int32 route_id = MSG_ROUTING_NONE;

  GpuChannelMap::const_iterator iter = gpu_channels_.find(client_id);
  if (iter != gpu_channels_.end()) {
    iter->second->CreateViewCommandBuffer(
        window, surface_id, init_params, &route_id);
  }

  Send(new GpuHostMsg_CommandBufferCreated(route_id));
}

void GpuChannelManager::CreateImage(
    gfx::PluginWindowHandle window, int32 client_id, int32 image_id) {
  gfx::Size size;

  GpuChannelMap::const_iterator iter = gpu_channels_.find(client_id);
  if (iter != gpu_channels_.end()) {
    iter->second->CreateImage(window, image_id, &size);
  }

  Send(new GpuHostMsg_ImageCreated(size));
}

void GpuChannelManager::OnCreateImage(
    gfx::PluginWindowHandle window, int32 client_id, int32 image_id) {
  DCHECK(image_id);

  if (image_operations_.empty()) {
    CreateImage(window, client_id, image_id);
  } else {
    image_operations_.push_back(
        new ImageOperation(0, base::Bind(&GpuChannelManager::CreateImage,
                                         base::Unretained(this),
                                         window,
                                         client_id,
                                         image_id)));
  }
}

void GpuChannelManager::DeleteImage(int32 client_id, int32 image_id) {
  GpuChannelMap::const_iterator iter = gpu_channels_.find(client_id);
  if (iter != gpu_channels_.end()) {
    iter->second->DeleteImage(image_id);
  }
}

void GpuChannelManager::OnDeleteImage(
    int32 client_id, int32 image_id, int32 sync_point) {
  DCHECK(image_id);

  if (!sync_point && image_operations_.empty()) {
    DeleteImage(client_id, image_id);
  } else {
    image_operations_.push_back(
        new ImageOperation(sync_point,
                           base::Bind(&GpuChannelManager::DeleteImage,
                                      base::Unretained(this),
                                      client_id,
                                      image_id)));
    if (sync_point) {
      sync_point_manager()->AddSyncPointCallback(
          sync_point,
          base::Bind(&GpuChannelManager::OnDeleteImageSyncPointRetired,
                     base::Unretained(this),
                     image_operations_.back()));
    }
  }
}

void GpuChannelManager::OnDeleteImageSyncPointRetired(
    ImageOperation* image_operation) {
  // Mark operation as no longer having a pending sync point.
  image_operation->sync_point = 0;

  // De-queue operations until we reach a pending sync point.
  while (!image_operations_.empty()) {
    // Check if operation has a pending sync point.
    if (image_operations_.front()->sync_point)
      return;

    image_operations_.front()->callback.Run();
    delete image_operations_.front();
    image_operations_.pop_front();
  }
}

void GpuChannelManager::OnLoadedShader(std::string program_proto) {
  if (program_cache())
    program_cache()->LoadProgram(program_proto);
}

bool GpuChannelManager::HandleMessagesScheduled() {
  for (GpuChannelMap::iterator iter = gpu_channels_.begin();
       iter != gpu_channels_.end(); ++iter) {
    if (iter->second->handle_messages_scheduled())
      return true;
  }
  return false;
}

uint64 GpuChannelManager::MessagesProcessed() {
  uint64 messages_processed = 0;

  for (GpuChannelMap::iterator iter = gpu_channels_.begin();
       iter != gpu_channels_.end(); ++iter) {
    messages_processed += iter->second->messages_processed();
  }
  return messages_processed;
}

void GpuChannelManager::LoseAllContexts() {
  for (GpuChannelMap::iterator iter = gpu_channels_.begin();
       iter != gpu_channels_.end(); ++iter) {
    iter->second->MarkAllContextsLost();
  }
  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&GpuChannelManager::OnLoseAllContexts,
                 weak_factory_.GetWeakPtr()));
}

void GpuChannelManager::OnLoseAllContexts() {
  gpu_channels_.clear();
}

gfx::GLSurface* GpuChannelManager::GetDefaultOffscreenSurface() {
  if (!default_offscreen_surface_.get()) {
    default_offscreen_surface_ =
        gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1));
  }
  return default_offscreen_surface_.get();
}

}  // namespace content
