// 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 "ppapi/proxy/ppb_graphics_3d_proxy.h"

#include "gpu/command_buffer/client/gles2_implementation.h"
#include "gpu/command_buffer/common/command_buffer.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/proxy/enter_proxy.h"
#include "ppapi/proxy/plugin_dispatcher.h"
#include "ppapi/proxy/ppapi_command_buffer_proxy.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/ppapi_globals.h"
#include "ppapi/shared_impl/proxy_lock.h"
#include "ppapi/thunk/enter.h"
#include "ppapi/thunk/resource_creation_api.h"
#include "ppapi/thunk/thunk.h"

using ppapi::thunk::EnterResourceNoLock;
using ppapi::thunk::PPB_Graphics3D_API;
using ppapi::thunk::ResourceCreationAPI;

namespace ppapi {
namespace proxy {

namespace {

const int32 kCommandBufferSize = 1024 * 1024;
const int32 kTransferBufferSize = 1024 * 1024;

base::SharedMemoryHandle TransportSHMHandleFromInt(Dispatcher* dispatcher,
                                                   int shm_handle) {
  // TODO(piman): Change trusted interface to return a PP_FileHandle, those
  // casts are ugly.
  base::PlatformFile source =
#if defined(OS_WIN)
      reinterpret_cast<HANDLE>(static_cast<intptr_t>(shm_handle));
#elif defined(OS_POSIX)
      shm_handle;
#else
  #error Not implemented.
#endif
  // Don't close the handle, it doesn't belong to us.
  return dispatcher->ShareHandleWithRemote(source, false);
}

gpu::CommandBuffer::State GetErrorState() {
  gpu::CommandBuffer::State error_state;
  error_state.error = gpu::error::kGenericError;
  return error_state;
}

}  // namespace

// This class just wraps a CommandBuffer and optionally locks around every
// method. This is used to ensure that we have the Proxy lock any time we enter
// PpapiCommandBufferProxy.
//
// Note, for performance reasons, most of this code is not truly thread
// safe in the sense of multiple threads concurrently rendering to the same
// Graphics3D context; this isn't allowed, and will likely either crash or
// result in undefined behavior.  It is assumed that the thread which creates
// the Graphics3D context will be the thread on which subsequent gl rendering
// will be done. This is why it is okay to read need_to_lock_ without the lock;
// it should only ever be read and written on the same thread where the context
// was created.
//
// TODO(nfullagar): At some point, allow multiple threads to concurrently render
// each to its own context.  First step is to allow a single thread (either main
// thread or background thread) to render to a single Graphics3D context.
class Graphics3D::LockingCommandBuffer : public gpu::CommandBuffer {
 public:
  explicit LockingCommandBuffer(gpu::CommandBuffer* gpu_command_buffer)
      : gpu_command_buffer_(gpu_command_buffer), need_to_lock_(true) {
  }
  virtual ~LockingCommandBuffer() {
  }
  void set_need_to_lock(bool need_to_lock) { need_to_lock_ = need_to_lock; }
  bool need_to_lock() const { return need_to_lock_; }

 private:
  // MaybeLock acquires the proxy lock on construction if and only if
  // need_to_lock is true. If it acquired the lock, it releases it on
  // destruction. If need_to_lock is false, then the lock must already be held.
  struct MaybeLock {
    explicit MaybeLock(bool need_to_lock) : locked_(need_to_lock) {
      if (need_to_lock)
        ppapi::ProxyLock::Acquire();
      else
        ppapi::ProxyLock::AssertAcquired();
    }
    ~MaybeLock() {
      if (locked_)
        ppapi::ProxyLock::Release();
    }
   private:
    bool locked_;
  };

  // gpu::CommandBuffer implementation:
  virtual bool Initialize() OVERRIDE {
    MaybeLock lock(need_to_lock_);
    return gpu_command_buffer_->Initialize();
  }
  virtual State GetState() OVERRIDE {
    MaybeLock lock(need_to_lock_);
    return gpu_command_buffer_->GetState();
  }
  virtual State GetLastState() OVERRIDE {
    // During a normal scene, the vast majority of calls are to GetLastState().
    // We don't allow multi-threaded rendering on the same contex, so for
    // performance reasons, avoid the global lock for this entry point.  We can
    // get away with this here because the underlying implementation of
    // GetLastState() is trivial and does not involve global or shared state
    // between other contexts.
    // TODO(nfullagar): We can probably skip MaybeLock for other methods, but
    // the performance gain may not be worth it.
    //
    // MaybeLock lock(need_to_lock_);
    return gpu_command_buffer_->GetLastState();
  }
  virtual int32 GetLastToken() OVERRIDE {
    return GetLastState().token;
  }
  virtual void Flush(int32 put_offset) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    gpu_command_buffer_->Flush(put_offset);
  }
  virtual State FlushSync(int32 put_offset, int32 last_known_get) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    return gpu_command_buffer_->FlushSync(put_offset, last_known_get);
  }
  virtual void SetGetBuffer(int32 transfer_buffer_id) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    gpu_command_buffer_->SetGetBuffer(transfer_buffer_id);
  }
  virtual void SetGetOffset(int32 get_offset) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    gpu_command_buffer_->SetGetOffset(get_offset);
  }
  virtual gpu::Buffer CreateTransferBuffer(size_t size,
                                           int32* id) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    return gpu_command_buffer_->CreateTransferBuffer(size, id);
  }
  virtual void DestroyTransferBuffer(int32 id) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    gpu_command_buffer_->DestroyTransferBuffer(id);
  }
  virtual gpu::Buffer GetTransferBuffer(int32 id) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    return gpu_command_buffer_->GetTransferBuffer(id);
  }
  virtual void SetToken(int32 token) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    gpu_command_buffer_->SetToken(token);
  }
  virtual void SetParseError(gpu::error::Error error) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    gpu_command_buffer_->SetParseError(error);
  }
  virtual void SetContextLostReason(
      gpu::error::ContextLostReason reason) OVERRIDE {
    MaybeLock lock(need_to_lock_);
    gpu_command_buffer_->SetContextLostReason(reason);
  }
  virtual uint32 InsertSyncPoint() OVERRIDE {
    MaybeLock lock(need_to_lock_);
    return gpu_command_buffer_->InsertSyncPoint();
  }

  // Weak pointer - see class Graphics3D for the scopted_ptr.
  gpu::CommandBuffer* gpu_command_buffer_;

  bool need_to_lock_;
};

Graphics3D::Graphics3D(const HostResource& resource)
    : PPB_Graphics3D_Shared(resource),
      num_already_locked_calls_(0) {
}

Graphics3D::~Graphics3D() {
  if (gles2_impl())
    DestroyGLES2Impl();
}

bool Graphics3D::Init(gpu::gles2::GLES2Implementation* share_gles2) {
  PluginDispatcher* dispatcher = PluginDispatcher::GetForResource(this);
  if (!dispatcher)
    return false;

  command_buffer_.reset(
      new PpapiCommandBufferProxy(host_resource(), dispatcher));
  locking_command_buffer_.reset(
      new LockingCommandBuffer(command_buffer_.get()));

  ScopedNoLocking already_locked(this);
  return CreateGLES2Impl(kCommandBufferSize, kTransferBufferSize,
                         share_gles2);
}

PP_Bool Graphics3D::SetGetBuffer(int32_t /* transfer_buffer_id */) {
  return PP_FALSE;
}

gpu::CommandBuffer::State Graphics3D::GetState() {
  return GetErrorState();
}

PP_Bool Graphics3D::Flush(int32_t put_offset) {
  return PP_FALSE;
}

gpu::CommandBuffer::State Graphics3D::FlushSync(int32_t put_offset) {
  return GetErrorState();
}

int32_t Graphics3D::CreateTransferBuffer(uint32_t size) {
  return PP_FALSE;
}

PP_Bool Graphics3D::DestroyTransferBuffer(int32_t id) {
  return PP_FALSE;
}

PP_Bool Graphics3D::GetTransferBuffer(int32_t id,
                                      int* shm_handle,
                                      uint32_t* shm_size) {
  return PP_FALSE;
}

gpu::CommandBuffer::State Graphics3D::FlushSyncFast(int32_t put_offset,
                                                    int32_t last_known_get) {
  return GetErrorState();
}

uint32_t Graphics3D::InsertSyncPoint() {
  NOTREACHED();
  return 0;
}

gpu::CommandBuffer* Graphics3D::GetCommandBuffer() {
  return locking_command_buffer_.get();
}

int32 Graphics3D::DoSwapBuffers() {
  // gles2_impl()->SwapBuffers() results in CommandBuffer calls, and we already
  // have the proxy lock.
  ScopedNoLocking already_locked(this);

  gles2_impl()->SwapBuffers();
  IPC::Message* msg = new PpapiHostMsg_PPBGraphics3D_SwapBuffers(
      API_ID_PPB_GRAPHICS_3D, host_resource());
  msg->set_unblock(true);
  PluginDispatcher::GetForResource(this)->Send(msg);

  return PP_OK_COMPLETIONPENDING;
}

void Graphics3D::PushAlreadyLocked() {
  ppapi::ProxyLock::AssertAcquired();
  if (!locking_command_buffer_) {
    NOTREACHED();
    return;
  }
  if (num_already_locked_calls_ == 0)
    locking_command_buffer_->set_need_to_lock(false);
  ++num_already_locked_calls_;
}

void Graphics3D::PopAlreadyLocked() {
  // We must have Pushed before we can Pop.
  DCHECK(!locking_command_buffer_->need_to_lock());
  DCHECK_GT(num_already_locked_calls_, 0);
  ppapi::ProxyLock::AssertAcquired();
  if (!locking_command_buffer_) {
    NOTREACHED();
    return;
  }
  --num_already_locked_calls_;
  if (num_already_locked_calls_ == 0)
    locking_command_buffer_->set_need_to_lock(true);
}

PPB_Graphics3D_Proxy::PPB_Graphics3D_Proxy(Dispatcher* dispatcher)
    : InterfaceProxy(dispatcher),
      callback_factory_(this) {
}

PPB_Graphics3D_Proxy::~PPB_Graphics3D_Proxy() {
}

// static
PP_Resource PPB_Graphics3D_Proxy::CreateProxyResource(
    PP_Instance instance,
    PP_Resource share_context,
    const int32_t* attrib_list) {
  PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
  if (!dispatcher)
    return PP_ERROR_BADARGUMENT;

  HostResource share_host;
  gpu::gles2::GLES2Implementation* share_gles2 = NULL;
  if (share_context != 0) {
    EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true);
    if (enter.failed())
      return PP_ERROR_BADARGUMENT;

    PPB_Graphics3D_Shared* share_graphics =
        static_cast<PPB_Graphics3D_Shared*>(enter.object());
    share_host = share_graphics->host_resource();
    share_gles2 = share_graphics->gles2_impl();
  }

  std::vector<int32_t> attribs;
  if (attrib_list) {
    for (const int32_t* attr = attrib_list;
         attr[0] != PP_GRAPHICS3DATTRIB_NONE;
         attr += 2) {
      attribs.push_back(attr[0]);
      attribs.push_back(attr[1]);
    }
  }
  attribs.push_back(PP_GRAPHICS3DATTRIB_NONE);

  HostResource result;
  dispatcher->Send(new PpapiHostMsg_PPBGraphics3D_Create(
      API_ID_PPB_GRAPHICS_3D, instance, share_host, attribs, &result));
  if (result.is_null())
    return 0;

  scoped_refptr<Graphics3D> graphics_3d(new Graphics3D(result));
  if (!graphics_3d->Init(share_gles2))
    return 0;
  return graphics_3d->GetReference();
}

bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg)
#if !defined(OS_NACL)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_Create,
                        OnMsgCreate)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_SetGetBuffer,
                        OnMsgSetGetBuffer)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_GetState,
                        OnMsgGetState)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_Flush,
                        OnMsgFlush)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_AsyncFlush,
                        OnMsgAsyncFlush)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_CreateTransferBuffer,
                        OnMsgCreateTransferBuffer)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_DestroyTransferBuffer,
                        OnMsgDestroyTransferBuffer)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_GetTransferBuffer,
                        OnMsgGetTransferBuffer)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_SwapBuffers,
                        OnMsgSwapBuffers)
    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_InsertSyncPoint,
                        OnMsgInsertSyncPoint)
#endif  // !defined(OS_NACL)

    IPC_MESSAGE_HANDLER(PpapiMsg_PPBGraphics3D_SwapBuffersACK,
                        OnMsgSwapBuffersACK)
    IPC_MESSAGE_UNHANDLED(handled = false)

  IPC_END_MESSAGE_MAP()
  // FIXME(brettw) handle bad messages!
  return handled;
}

#if !defined(OS_NACL)
void PPB_Graphics3D_Proxy::OnMsgCreate(PP_Instance instance,
                                       HostResource share_context,
                                       const std::vector<int32_t>& attribs,
                                       HostResource* result) {
  if (attribs.empty() ||
      attribs.back() != PP_GRAPHICS3DATTRIB_NONE ||
      !(attribs.size() & 1))
    return;  // Bad message.

  thunk::EnterResourceCreation enter(instance);

  if (enter.succeeded()) {
    result->SetHostResource(
      instance,
      enter.functions()->CreateGraphics3DRaw(instance,
                                             share_context.host_resource(),
                                             &attribs.front()));
  }
}

void PPB_Graphics3D_Proxy::OnMsgSetGetBuffer(
    const HostResource& context,
    int32 transfer_buffer_id) {
  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  if (enter.succeeded())
    enter.object()->SetGetBuffer(transfer_buffer_id);
}

void PPB_Graphics3D_Proxy::OnMsgGetState(const HostResource& context,
                                         gpu::CommandBuffer::State* state,
                                         bool* success) {
  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  if (enter.failed()) {
    *success = false;
    return;
  }
  *state = enter.object()->GetState();
  *success = true;
}

void PPB_Graphics3D_Proxy::OnMsgFlush(const HostResource& context,
                                      int32 put_offset,
                                      int32 last_known_get,
                                      gpu::CommandBuffer::State* state,
                                      bool* success) {
  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  if (enter.failed()) {
    *success = false;
    return;
  }
  *state = enter.object()->FlushSyncFast(put_offset, last_known_get);
  *success = true;
}

void PPB_Graphics3D_Proxy::OnMsgAsyncFlush(const HostResource& context,
                                           int32 put_offset) {
  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  if (enter.succeeded())
    enter.object()->Flush(put_offset);
}

void PPB_Graphics3D_Proxy::OnMsgCreateTransferBuffer(
    const HostResource& context,
    uint32 size,
    int32* id) {
  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  if (enter.succeeded())
    *id = enter.object()->CreateTransferBuffer(size);
  else
    *id = -1;
}

void PPB_Graphics3D_Proxy::OnMsgDestroyTransferBuffer(
    const HostResource& context,
    int32 id) {
  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  if (enter.succeeded())
    enter.object()->DestroyTransferBuffer(id);
}

void PPB_Graphics3D_Proxy::OnMsgGetTransferBuffer(
    const HostResource& context,
    int32 id,
    ppapi::proxy::SerializedHandle* transfer_buffer) {
  transfer_buffer->set_null_shmem();

  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  int shm_handle = 0;
  uint32_t shm_size = 0;
  if (enter.succeeded() &&
      enter.object()->GetTransferBuffer(id, &shm_handle, &shm_size)) {
    transfer_buffer->set_shmem(
        TransportSHMHandleFromInt(dispatcher(), shm_handle),
        shm_size);
  }
}

void PPB_Graphics3D_Proxy::OnMsgSwapBuffers(const HostResource& context) {
  EnterHostFromHostResourceForceCallback<PPB_Graphics3D_API> enter(
      context, callback_factory_,
      &PPB_Graphics3D_Proxy::SendSwapBuffersACKToPlugin, context);
  if (enter.succeeded())
    enter.SetResult(enter.object()->SwapBuffers(enter.callback()));
}

void PPB_Graphics3D_Proxy::OnMsgInsertSyncPoint(const HostResource& context,
                                                uint32* sync_point) {
  *sync_point = 0;
  EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
  if (enter.succeeded())
    *sync_point = enter.object()->InsertSyncPoint();
}
#endif  // !defined(OS_NACL)

void PPB_Graphics3D_Proxy::OnMsgSwapBuffersACK(const HostResource& resource,
                                              int32_t pp_error) {
  EnterPluginFromHostResource<PPB_Graphics3D_API> enter(resource);
  if (enter.succeeded())
    static_cast<Graphics3D*>(enter.object())->SwapBuffersACK(pp_error);
}

#if !defined(OS_NACL)
void PPB_Graphics3D_Proxy::SendSwapBuffersACKToPlugin(
    int32_t result,
    const HostResource& context) {
  dispatcher()->Send(new PpapiMsg_PPBGraphics3D_SwapBuffersACK(
      API_ID_PPB_GRAPHICS_3D, context, result));
}
#endif  // !defined(OS_NACL)

}  // namespace proxy
}  // namespace ppapi