// 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.

#if defined(OS_WIN)
#include <windows.h>
#endif

#include "content/browser/renderer_host/gpu_message_filter.h"

#include "base/bind.h"
#include "base/command_line.h"
#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/browser/gpu/gpu_surface_tracker.h"
#include "content/browser/renderer_host/render_widget_helper.h"
#include "content/common/gpu/gpu_messages.h"
#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
#include "content/public/common/content_switches.h"
#include "gpu/command_buffer/service/gpu_switches.h"

namespace content {

struct GpuMessageFilter::CreateViewCommandBufferRequest {
  CreateViewCommandBufferRequest(
      int32 surface_id_,
      const GPUCreateCommandBufferConfig& init_params_,
      IPC::Message* reply_)
      : surface_id(surface_id_),
        init_params(init_params_),
        reply(reply_) {
  }
  int32 surface_id;
  GPUCreateCommandBufferConfig init_params;
  IPC::Message* reply;
};

struct GpuMessageFilter::FrameSubscription {
  FrameSubscription(
      int in_route_id,
      scoped_ptr<RenderWidgetHostViewFrameSubscriber> in_subscriber)
      : route_id(in_route_id),
        surface_id(0),
        subscriber(in_subscriber.Pass()),
        factory(subscriber.get()) {
  }

  int route_id;
  int surface_id;
  scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber;
  base::WeakPtrFactory<RenderWidgetHostViewFrameSubscriber> factory;
};

GpuMessageFilter::GpuMessageFilter(int render_process_id,
                                   RenderWidgetHelper* render_widget_helper)
    : gpu_process_id_(0),
      render_process_id_(render_process_id),
      share_contexts_(false),
      render_widget_helper_(render_widget_helper),
      weak_ptr_factory_(this) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

#if defined(USE_AURA) || defined(OS_ANDROID)
  // We use the GPU process for UI on Aura, and we need to share renderer GL
  // contexts with the compositor context.
  share_contexts_ = true;
#else
  // Share contexts when compositing webview plugin or using share groups
  // for asynchronous texture uploads.
  if (!CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kDisableBrowserPluginCompositing) ||
      CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kEnableShareGroupAsyncTextureUpload))
    share_contexts_ = true;
#endif
}

GpuMessageFilter::~GpuMessageFilter() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  EndAllFrameSubscriptions();
}

bool GpuMessageFilter::OnMessageReceived(
    const IPC::Message& message,
    bool* message_was_ok) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_EX(GpuMessageFilter, message, *message_was_ok)
    IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuHostMsg_EstablishGpuChannel,
                                    OnEstablishGpuChannel)
    IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuHostMsg_CreateViewCommandBuffer,
                                    OnCreateViewCommandBuffer)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP_EX()
  return handled;
}

void GpuMessageFilter::SurfaceUpdated(int32 surface_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  typedef std::vector<linked_ptr<CreateViewCommandBufferRequest> > RequestList;
  RequestList retry_requests;
  retry_requests.swap(pending_requests_);
  for (RequestList::iterator it = retry_requests.begin();
      it != retry_requests.end(); ++it) {
    if ((*it)->surface_id != surface_id) {
      pending_requests_.push_back(*it);
    } else {
      linked_ptr<CreateViewCommandBufferRequest> request = *it;
      OnCreateViewCommandBuffer(request->surface_id,
                                request->init_params,
                                request->reply);
    }
  }
}

void GpuMessageFilter::BeginFrameSubscription(
    int route_id,
    scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  linked_ptr<FrameSubscription> subscription(
      new FrameSubscription(route_id, subscriber.Pass()));
  BeginFrameSubscriptionInternal(subscription);
}

void GpuMessageFilter::EndFrameSubscription(int route_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  FrameSubscriptionList frame_subscription_list;
  frame_subscription_list.swap(frame_subscription_list_);
  for (FrameSubscriptionList::iterator it = frame_subscription_list.begin();
       it != frame_subscription_list.end(); ++it) {
    if ((*it)->route_id != route_id)
      frame_subscription_list_.push_back(*it);
    else
      EndFrameSubscriptionInternal(*it);
  }
}

void GpuMessageFilter::OnEstablishGpuChannel(
    CauseForGpuLaunch cause_for_gpu_launch,
    IPC::Message* reply) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // TODO(apatrick): Eventually, this will return the route ID of a
  // GpuProcessStub, from which the renderer process will create a
  // GpuProcessProxy. The renderer will use the proxy for all subsequent
  // communication with the GPU process. This means if the GPU process
  // terminates, the renderer process will not find itself unknowingly sending
  // IPCs to a newly launched GPU process. Also, I will rename this function
  // to something like OnCreateGpuProcess.
  GpuProcessHost* host = GpuProcessHost::FromID(gpu_process_id_);
  if (!host) {
    host = GpuProcessHost::Get(GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
                               cause_for_gpu_launch);
    if (!host) {
      reply->set_reply_error();
      Send(reply);
      return;
    }

    gpu_process_id_ = host->host_id();

    // Apply all frame subscriptions to the new GpuProcessHost.
    BeginAllFrameSubscriptions();
  }

  host->EstablishGpuChannel(
      render_process_id_,
      share_contexts_,
      base::Bind(&GpuMessageFilter::EstablishChannelCallback,
                 weak_ptr_factory_.GetWeakPtr(),
                 reply));
}

void GpuMessageFilter::OnCreateViewCommandBuffer(
    int32 surface_id,
    const GPUCreateCommandBufferConfig& init_params,
    IPC::Message* reply) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  GpuSurfaceTracker* surface_tracker = GpuSurfaceTracker::Get();
  gfx::GLSurfaceHandle compositing_surface;

  int renderer_id = 0;
  int render_widget_id = 0;
  bool result = surface_tracker->GetRenderWidgetIDForSurface(
      surface_id, &renderer_id, &render_widget_id);
  if (result && renderer_id == render_process_id_) {
    compositing_surface = surface_tracker->GetSurfaceHandle(surface_id);
  } else {
    DLOG(ERROR) << "Renderer " << render_process_id_
                << " tried to access a surface for renderer " << renderer_id;
  }

  if (compositing_surface.parent_gpu_process_id &&
      compositing_surface.parent_gpu_process_id != gpu_process_id_) {
    // If the current handle for the surface is using a different (older) gpu
    // host, it means the GPU process died and we need to wait until the UI
    // re-allocates the surface in the new process.
    linked_ptr<CreateViewCommandBufferRequest> request(
        new CreateViewCommandBufferRequest(surface_id, init_params, reply));
    pending_requests_.push_back(request);
    return;
  }

  GpuProcessHost* host = GpuProcessHost::FromID(gpu_process_id_);
  if (!host || compositing_surface.is_null()) {
    // TODO(apatrick): Eventually, this IPC message will be routed to a
    // GpuProcessStub with a particular routing ID. The error will be set if
    // the GpuProcessStub with that routing ID is not in the MessageRouter.
    reply->set_reply_error();
    Send(reply);
    return;
  }

  host->CreateViewCommandBuffer(
      compositing_surface,
      surface_id,
      render_process_id_,
      init_params,
      base::Bind(&GpuMessageFilter::CreateCommandBufferCallback,
                 weak_ptr_factory_.GetWeakPtr(),
                 reply));
}

void GpuMessageFilter::EstablishChannelCallback(
    IPC::Message* reply,
    const IPC::ChannelHandle& channel,
    const gpu::GPUInfo& gpu_info) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  GpuHostMsg_EstablishGpuChannel::WriteReplyParams(
      reply, render_process_id_, channel, gpu_info);
  Send(reply);
}

void GpuMessageFilter::CreateCommandBufferCallback(
    IPC::Message* reply, int32 route_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  GpuHostMsg_CreateViewCommandBuffer::WriteReplyParams(reply, route_id);
  Send(reply);
}

void GpuMessageFilter::BeginAllFrameSubscriptions() {
  FrameSubscriptionList frame_subscription_list;
  frame_subscription_list.swap(frame_subscription_list_);
  for (FrameSubscriptionList::iterator it = frame_subscription_list.begin();
       it != frame_subscription_list.end(); ++it) {
    BeginFrameSubscriptionInternal(*it);
  }
}

void GpuMessageFilter::EndAllFrameSubscriptions() {
  for (FrameSubscriptionList::iterator it = frame_subscription_list_.begin();
       it != frame_subscription_list_.end(); ++it) {
    EndFrameSubscriptionInternal(*it);
  }
  frame_subscription_list_.clear();
}

void GpuMessageFilter::BeginFrameSubscriptionInternal(
    linked_ptr<FrameSubscription> subscription) {
  if (!subscription->surface_id) {
    GpuSurfaceTracker* surface_tracker = GpuSurfaceTracker::Get();
    subscription->surface_id = surface_tracker->LookupSurfaceForRenderer(
        render_process_id_, subscription->route_id);

    // If the surface ID cannot be found this subscription is dropped.
    if (!subscription->surface_id)
      return;
  }
  frame_subscription_list_.push_back(subscription);

  // Frame subscriber is owned by this object, but it is shared with
  // GpuProcessHost. GpuProcessHost can be destroyed in the case of crashing
  // and we do not get a signal. This object can also be destroyed independent
  // of GpuProcessHost. To ensure that GpuProcessHost does not reference a
  // deleted frame subscriber, a weak reference is shared.
  GpuProcessHost* host = GpuProcessHost::FromID(gpu_process_id_);
  if (!host)
    return;
  host->BeginFrameSubscription(subscription->surface_id,
                               subscription->factory.GetWeakPtr());
}

void GpuMessageFilter::EndFrameSubscriptionInternal(
    linked_ptr<FrameSubscription> subscription) {
  GpuProcessHost* host = GpuProcessHost::FromID(gpu_process_id_);

  // An empty surface ID means subscription has never started in GpuProcessHost
  // so it is not necessary to end it.
  if (!host || !subscription->surface_id)
    return;

  // Note that GpuProcessHost here might not be the same one that frame
  // subscription has applied.
  host->EndFrameSubscription(subscription->surface_id);
}

}  // namespace content
