// 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 "gpu/ipc/client/gpu_channel_host.h"

#include <algorithm>
#include <utility>

#include "base/atomic_sequence_num.h"
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
#include "gpu/ipc/client/client_shared_image_interface.h"
#include "gpu/ipc/common/command_buffer_id.h"
#include "gpu/ipc/common/gpu_messages.h"
#include "gpu/ipc/common/gpu_param_traits_macros.h"
#include "gpu/ipc/common/gpu_watchdog_timeout.h"
#include "ipc/ipc_channel_mojo.h"
#include "ipc/ipc_sync_message.h"
#include "ipc/trace_ipc_message.h"
#include "mojo/public/cpp/bindings/lib/message_quota_checker.h"
#include "url/gurl.h"

using base::AutoLock;

namespace gpu {

GpuChannelHost::GpuChannelHost(int channel_id,
                               const gpu::GPUInfo& gpu_info,
                               const gpu::GpuFeatureInfo& gpu_feature_info,
                               mojo::ScopedMessagePipeHandle handle)
    : io_thread_(base::ThreadTaskRunnerHandle::Get()),
      channel_id_(channel_id),
      gpu_info_(gpu_info),
      gpu_feature_info_(gpu_feature_info),
      listener_(new Listener(std::move(handle), io_thread_),
                base::OnTaskRunnerDeleter(io_thread_)),
      shared_image_interface_(
          this,
          static_cast<int32_t>(
              GpuChannelReservedRoutes::kSharedImageInterface)),
      image_decode_accelerator_proxy_(
          this,
          static_cast<int32_t>(
              GpuChannelReservedRoutes::kImageDecodeAccelerator)) {
  next_image_id_.GetNext();
  for (int32_t i = 0;
       i <= static_cast<int32_t>(GpuChannelReservedRoutes::kMaxValue); ++i)
    next_route_id_.GetNext();

#if defined(OS_MAC)
  gpu::SetMacOSSpecificTextureTarget(gpu_info.macos_specific_texture_target);
#endif  // defined(OS_MAC)
}

bool GpuChannelHost::Send(IPC::Message* msg) {
  TRACE_IPC_MESSAGE_SEND("ipc", "GpuChannelHost::Send", msg);

  auto message = base::WrapUnique(msg);

  DCHECK(!io_thread_->BelongsToCurrentThread());

  // The GPU process never sends synchronous IPCs so clear the unblock flag to
  // preserve order.
  message->set_unblock(false);

  if (!message->is_sync()) {
    io_thread_->PostTask(FROM_HERE,
                         base::BindOnce(&Listener::SendMessage,
                                        base::Unretained(listener_.get()),
                                        std::move(message), nullptr));
    return true;
  }

  base::WaitableEvent done_event(
      base::WaitableEvent::ResetPolicy::MANUAL,
      base::WaitableEvent::InitialState::NOT_SIGNALED);
  auto deserializer = base::WrapUnique(
      static_cast<IPC::SyncMessage*>(message.get())->GetReplyDeserializer());

  IPC::PendingSyncMsg pending_sync(IPC::SyncMessage::GetMessageId(*message),
                                   deserializer.get(), &done_event);
  io_thread_->PostTask(
      FROM_HERE,
      base::BindOnce(&Listener::SendMessage, base::Unretained(listener_.get()),
                     std::move(message), &pending_sync));

  // http://crbug.com/125264
  base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;

  // TODO(magchen): crbug.com/949839. Remove this histogram and do only one
  // done_event->Wait() after the GPU watchdog V2 is fully launched.
  base::TimeTicks start_time = base::TimeTicks::Now();

  // The wait for event is split into two phases so we can still record the
  // case in which the GPU hangs but not killed. Also all data should be
  // recorded in the range of max_wait_sec seconds for easier comparison.
  bool signaled =
      pending_sync.done_event->TimedWait(kGpuChannelHostMaxWaitTime);

  base::TimeDelta wait_duration = base::TimeTicks::Now() - start_time;

  // Histogram of wait-for-sync time, used for monitoring the GPU watchdog.
  UMA_HISTOGRAM_CUSTOM_TIMES("GPU.GPUChannelHostWaitTime2", wait_duration,
                             base::TimeDelta::FromSeconds(1),
                             kGpuChannelHostMaxWaitTime, 50);

  // Continue waiting for the event if not signaled
  if (!signaled)
    pending_sync.done_event->Wait();

  return pending_sync.send_result;
}

uint32_t GpuChannelHost::OrderingBarrier(
    int32_t route_id,
    int32_t put_offset,
    std::vector<SyncToken> sync_token_fences) {
  AutoLock lock(context_lock_);

  if (pending_ordering_barrier_ &&
      pending_ordering_barrier_->route_id != route_id)
    EnqueuePendingOrderingBarrier();
  if (!pending_ordering_barrier_)
    pending_ordering_barrier_.emplace();

  pending_ordering_barrier_->deferred_message_id = next_deferred_message_id_++;
  pending_ordering_barrier_->route_id = route_id;
  pending_ordering_barrier_->put_offset = put_offset;
  pending_ordering_barrier_->sync_token_fences.insert(
      pending_ordering_barrier_->sync_token_fences.end(),
      std::make_move_iterator(sync_token_fences.begin()),
      std::make_move_iterator(sync_token_fences.end()));
  return pending_ordering_barrier_->deferred_message_id;
}

uint32_t GpuChannelHost::EnqueueDeferredMessage(
    const IPC::Message& message,
    std::vector<SyncToken> sync_token_fences) {
  AutoLock lock(context_lock_);

  EnqueuePendingOrderingBarrier();
  enqueued_deferred_message_id_ = next_deferred_message_id_++;
  GpuDeferredMessage deferred_message;
  deferred_message.message = message;
  deferred_message.sync_token_fences = std::move(sync_token_fences);
  deferred_messages_.push_back(std::move(deferred_message));
  return enqueued_deferred_message_id_;
}

void GpuChannelHost::EnsureFlush(uint32_t deferred_message_id) {
  AutoLock lock(context_lock_);
  InternalFlush(deferred_message_id);
}

void GpuChannelHost::VerifyFlush(uint32_t deferred_message_id) {
  AutoLock lock(context_lock_);

  InternalFlush(deferred_message_id);

  if (deferred_message_id > verified_deferred_message_id_) {
    Send(new GpuChannelMsg_Nop());
    verified_deferred_message_id_ = flushed_deferred_message_id_;
  }
}

void GpuChannelHost::EnqueuePendingOrderingBarrier() {
  context_lock_.AssertAcquired();
  if (!pending_ordering_barrier_)
    return;
  DCHECK_LT(enqueued_deferred_message_id_,
            pending_ordering_barrier_->deferred_message_id);
  enqueued_deferred_message_id_ =
      pending_ordering_barrier_->deferred_message_id;
  GpuDeferredMessage deferred_message;
  deferred_message.message = GpuCommandBufferMsg_AsyncFlush(
      pending_ordering_barrier_->route_id,
      pending_ordering_barrier_->put_offset,
      pending_ordering_barrier_->deferred_message_id,
      pending_ordering_barrier_->sync_token_fences);
  deferred_message.sync_token_fences =
      std::move(pending_ordering_barrier_->sync_token_fences);
  deferred_messages_.push_back(std::move(deferred_message));
  pending_ordering_barrier_.reset();
}

void GpuChannelHost::InternalFlush(uint32_t deferred_message_id) {
  context_lock_.AssertAcquired();

  EnqueuePendingOrderingBarrier();
  if (!deferred_messages_.empty() &&
      deferred_message_id > flushed_deferred_message_id_) {
    DCHECK_EQ(enqueued_deferred_message_id_, next_deferred_message_id_ - 1);

    Send(
        new GpuChannelMsg_FlushDeferredMessages(std::move(deferred_messages_)));

    deferred_messages_.clear();
    flushed_deferred_message_id_ = next_deferred_message_id_ - 1;
  }
}

void GpuChannelHost::DestroyChannel() {
  io_thread_->PostTask(
      FROM_HERE,
      base::BindOnce(&Listener::Close, base::Unretained(listener_.get())));
}

void GpuChannelHost::AddRoute(int route_id,
                              base::WeakPtr<IPC::Listener> listener) {
  AddRouteWithTaskRunner(route_id, listener,
                         base::ThreadTaskRunnerHandle::Get());
}

void GpuChannelHost::AddRouteWithTaskRunner(
    int route_id,
    base::WeakPtr<IPC::Listener> listener,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  io_thread_->PostTask(
      FROM_HERE, base::BindOnce(&GpuChannelHost::Listener::AddRoute,
                                base::Unretained(listener_.get()), route_id,
                                listener, task_runner));
}

void GpuChannelHost::RemoveRoute(int route_id) {
  io_thread_->PostTask(
      FROM_HERE, base::BindOnce(&GpuChannelHost::Listener::RemoveRoute,
                                base::Unretained(listener_.get()), route_id));
}

int32_t GpuChannelHost::ReserveImageId() {
  return next_image_id_.GetNext();
}

int32_t GpuChannelHost::GenerateRouteID() {
  return next_route_id_.GetNext();
}

void GpuChannelHost::CrashGpuProcessForTesting() {
  Send(new GpuChannelMsg_CrashForTesting());
}

void GpuChannelHost::TerminateGpuProcessForTesting() {
  Send(new GpuChannelMsg_TerminateForTesting());
}

std::unique_ptr<ClientSharedImageInterface>
GpuChannelHost::CreateClientSharedImageInterface() {
  return std::make_unique<ClientSharedImageInterface>(&shared_image_interface_);
}

GpuChannelHost::~GpuChannelHost() = default;

GpuChannelHost::Listener::RouteInfo::RouteInfo() = default;

GpuChannelHost::Listener::RouteInfo::RouteInfo(const RouteInfo& other) =
    default;
GpuChannelHost::Listener::RouteInfo::RouteInfo(RouteInfo&& other) = default;
GpuChannelHost::Listener::RouteInfo::~RouteInfo() = default;

GpuChannelHost::Listener::RouteInfo& GpuChannelHost::Listener::RouteInfo::
operator=(const RouteInfo& other) = default;

GpuChannelHost::Listener::RouteInfo& GpuChannelHost::Listener::RouteInfo::
operator=(RouteInfo&& other) = default;

GpuChannelHost::OrderingBarrierInfo::OrderingBarrierInfo() = default;

GpuChannelHost::OrderingBarrierInfo::~OrderingBarrierInfo() = default;

GpuChannelHost::OrderingBarrierInfo::OrderingBarrierInfo(
    OrderingBarrierInfo&&) = default;

GpuChannelHost::OrderingBarrierInfo& GpuChannelHost::OrderingBarrierInfo::
operator=(OrderingBarrierInfo&&) = default;

GpuChannelHost::Listener::Listener(
    mojo::ScopedMessagePipeHandle handle,
    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
    : channel_(IPC::ChannelMojo::Create(
          std::move(handle),
          IPC::Channel::MODE_CLIENT,
          this,
          io_task_runner,
          base::ThreadTaskRunnerHandle::Get(),
          mojo::internal::MessageQuotaChecker::MaybeCreate())) {
  DCHECK(channel_);
  DCHECK(io_task_runner->BelongsToCurrentThread());
  bool result = channel_->Connect();
  DCHECK(result);
}

GpuChannelHost::Listener::~Listener() {
  DCHECK(pending_syncs_.empty());
}

void GpuChannelHost::Listener::Close() {
  OnChannelError();
}

void GpuChannelHost::Listener::AddRoute(
    int32_t route_id,
    base::WeakPtr<IPC::Listener> listener,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  DCHECK(routes_.find(route_id) == routes_.end());
  DCHECK(task_runner);
  RouteInfo info;
  info.listener = listener;
  info.task_runner = std::move(task_runner);
  routes_[route_id] = info;

  if (lost_) {
    info.task_runner->PostTask(
        FROM_HERE,
        base::BindOnce(&IPC::Listener::OnChannelError, info.listener));
  }
}

void GpuChannelHost::Listener::RemoveRoute(int32_t route_id) {
  routes_.erase(route_id);
}

bool GpuChannelHost::Listener::OnMessageReceived(const IPC::Message& message) {
  if (message.is_reply()) {
    int id = IPC::SyncMessage::GetMessageId(message);
    auto it = pending_syncs_.find(id);
    if (it == pending_syncs_.end())
      return false;
    auto* pending_sync = it->second;
    pending_syncs_.erase(it);
    if (!message.is_reply_error()) {
      pending_sync->send_result =
          pending_sync->deserializer->SerializeOutputParameters(message);
    }
    pending_sync->done_event->Signal();
    return true;
  }

  auto it = routes_.find(message.routing_id());
  if (it == routes_.end())
    return false;

  const RouteInfo& info = it->second;
  info.task_runner->PostTask(
      FROM_HERE,
      base::BindOnce(base::IgnoreResult(&IPC::Listener::OnMessageReceived),
                     info.listener, message));
  return true;
}

void GpuChannelHost::Listener::OnChannelError() {
  channel_ = nullptr;
  // Set the lost state before signalling the proxies. That way, if they
  // themselves post a task to recreate the context, they will not try to re-use
  // this channel host.
  {
    AutoLock lock(lock_);
    lost_ = true;
  }

  for (auto& kv : pending_syncs_) {
    IPC::PendingSyncMsg* pending_sync = kv.second;
    pending_sync->done_event->Signal();
  }
  pending_syncs_.clear();

  // Inform all the proxies that an error has occurred. This will be reported
  // via OpenGL as a lost context.
  for (const auto& kv : routes_) {
    const RouteInfo& info = kv.second;
    info.task_runner->PostTask(
        FROM_HERE,
        base::BindOnce(&IPC::Listener::OnChannelError, info.listener));
  }

  routes_.clear();
}

void GpuChannelHost::Listener::SendMessage(std::unique_ptr<IPC::Message> msg,
                                           IPC::PendingSyncMsg* pending_sync) {
  // Note: lost_ is only written on this thread, so it is safe to read here
  // without lock.
  if (pending_sync) {
    DCHECK(msg->is_sync());
    if (lost_) {
      pending_sync->done_event->Signal();
      return;
    }
    pending_syncs_.emplace(pending_sync->id, pending_sync);
  } else {
    if (lost_)
      return;
    DCHECK(!msg->is_sync());
  }
  DCHECK(!lost_);
  channel_->Send(msg.release());
}

bool GpuChannelHost::Listener::IsLost() const {
  AutoLock lock(lock_);
  return lost_;
}

}  // namespace gpu
