// Copyright 2014 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/gpu/browser_gpu_memory_buffer_manager.h"

#include "base/atomic_sequence_num.h"
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/trace_event/trace_event.h"
#include "content/common/gpu/client/gpu_memory_buffer_factory_host.h"
#include "content/common/gpu/client/gpu_memory_buffer_impl.h"
#include "content/common/gpu/client/gpu_memory_buffer_impl_shared_memory.h"
#include "content/public/browser/browser_thread.h"

namespace content {
namespace {

BrowserGpuMemoryBufferManager* g_gpu_memory_buffer_manager = nullptr;

// Global atomic to generate gpu memory buffer unique IDs.
base::StaticAtomicSequenceNumber g_next_gpu_memory_buffer_id;

const char kMemoryAllocatorName[] = "gpumemorybuffer";

}  // namespace

struct BrowserGpuMemoryBufferManager::AllocateGpuMemoryBufferRequest {
  AllocateGpuMemoryBufferRequest(const gfx::Size& size,
                                 gfx::GpuMemoryBuffer::Format format,
                                 gfx::GpuMemoryBuffer::Usage usage,
                                 int client_id,
                                 int surface_id)
      : event(true, false),
        size(size),
        format(format),
        usage(usage),
        client_id(client_id),
        surface_id(surface_id) {}
  ~AllocateGpuMemoryBufferRequest() {}
  base::WaitableEvent event;
  gfx::Size size;
  gfx::GpuMemoryBuffer::Format format;
  gfx::GpuMemoryBuffer::Usage usage;
  int client_id;
  int surface_id;
  scoped_ptr<gfx::GpuMemoryBuffer> result;
};

BrowserGpuMemoryBufferManager::BrowserGpuMemoryBufferManager(
    GpuMemoryBufferFactoryHost* gpu_memory_buffer_factory_host,
    int gpu_client_id)
    : gpu_memory_buffer_factory_host_(gpu_memory_buffer_factory_host),
      gpu_client_id_(gpu_client_id),
      weak_ptr_factory_(this) {
  DCHECK(!g_gpu_memory_buffer_manager);
  g_gpu_memory_buffer_manager = this;
}

BrowserGpuMemoryBufferManager::~BrowserGpuMemoryBufferManager() {
  g_gpu_memory_buffer_manager = nullptr;
}

// static
BrowserGpuMemoryBufferManager* BrowserGpuMemoryBufferManager::current() {
  return g_gpu_memory_buffer_manager;
}

scoped_ptr<gfx::GpuMemoryBuffer>
BrowserGpuMemoryBufferManager::AllocateGpuMemoryBuffer(
    const gfx::Size& size,
    gfx::GpuMemoryBuffer::Format format,
    gfx::GpuMemoryBuffer::Usage usage) {
  return AllocateGpuMemoryBufferCommon(size, format, usage, 0);
}

scoped_ptr<gfx::GpuMemoryBuffer>
BrowserGpuMemoryBufferManager::AllocateGpuMemoryBufferForScanout(
    const gfx::Size& size,
    gfx::GpuMemoryBuffer::Format format,
    int32 surface_id) {
  DCHECK_GT(surface_id, 0);
  return AllocateGpuMemoryBufferCommon(
      size, format, gfx::GpuMemoryBuffer::SCANOUT, surface_id);
}

scoped_ptr<gfx::GpuMemoryBuffer>
BrowserGpuMemoryBufferManager::AllocateGpuMemoryBufferCommon(
    const gfx::Size& size,
    gfx::GpuMemoryBuffer::Format format,
    gfx::GpuMemoryBuffer::Usage usage,
    int32 surface_id) {
  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Fallback to shared memory buffer if |format| and |usage| are not supported
  // by factory.
  if (!gpu_memory_buffer_factory_host_->IsGpuMemoryBufferConfigurationSupported(
          format, usage)) {
    DCHECK(GpuMemoryBufferImplSharedMemory::IsFormatSupported(format));
    DCHECK_EQ(usage, gfx::GpuMemoryBuffer::MAP);
    return GpuMemoryBufferImplSharedMemory::Create(
        g_next_gpu_memory_buffer_id.GetNext(), size, format);
  }

  AllocateGpuMemoryBufferRequest request(size, format, usage, gpu_client_id_,
                                         surface_id);
  BrowserThread::PostTask(
      BrowserThread::IO,
      FROM_HERE,
      base::Bind(&BrowserGpuMemoryBufferManager::AllocateGpuMemoryBufferOnIO,
                 base::Unretained(this),  // Safe as we wait for result below.
                 base::Unretained(&request)));

  // We're blocking the UI thread, which is generally undesirable.
  TRACE_EVENT0("browser",
               "BrowserGpuMemoryBufferManager::AllocateGpuMemoryBuffer");
  base::ThreadRestrictions::ScopedAllowWait allow_wait;
  request.event.Wait();
  return request.result.Pass();
}

void BrowserGpuMemoryBufferManager::AllocateGpuMemoryBufferForChildProcess(
    const gfx::Size& size,
    gfx::GpuMemoryBuffer::Format format,
    gfx::GpuMemoryBuffer::Usage usage,
    base::ProcessHandle child_process_handle,
    int child_client_id,
    const AllocationCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  gfx::GpuMemoryBufferId new_id = g_next_gpu_memory_buffer_id.GetNext();

  BufferMap& buffers = clients_[child_client_id];
  DCHECK(buffers.find(new_id) == buffers.end());

  // Fallback to shared memory buffer if |format| and |usage| are not supported
  // by factory.
  if (!gpu_memory_buffer_factory_host_->IsGpuMemoryBufferConfigurationSupported(
          format, usage)) {
    // Early out if we cannot fallback to shared memory buffer.
    if (!GpuMemoryBufferImplSharedMemory::IsFormatSupported(format) ||
        !GpuMemoryBufferImplSharedMemory::IsSizeValidForFormat(size, format) ||
        usage != gfx::GpuMemoryBuffer::MAP) {
      callback.Run(gfx::GpuMemoryBufferHandle());
      return;
    }

    buffers[new_id] = BufferInfo(size, format, gfx::SHARED_MEMORY_BUFFER);
    callback.Run(GpuMemoryBufferImplSharedMemory::AllocateForChildProcess(
        new_id, size, format, child_process_handle));
    return;
  }

  // Note: Handling of cases where the child process is removed before the
  // allocation completes is less subtle if we set the buffer type to
  // EMPTY_BUFFER here and verify that this has not changed when allocation
  // completes.
  buffers[new_id] = BufferInfo(size, format, gfx::EMPTY_BUFFER);

  gpu_memory_buffer_factory_host_->CreateGpuMemoryBuffer(
      new_id, size, format, usage, child_client_id, 0,
      base::Bind(&BrowserGpuMemoryBufferManager::
                     GpuMemoryBufferAllocatedForChildProcess,
                 weak_ptr_factory_.GetWeakPtr(), child_client_id, callback));
}

gfx::GpuMemoryBuffer*
BrowserGpuMemoryBufferManager::GpuMemoryBufferFromClientBuffer(
    ClientBuffer buffer) {
  return GpuMemoryBufferImpl::FromClientBuffer(buffer);
}

void BrowserGpuMemoryBufferManager::SetDestructionSyncPoint(
    gfx::GpuMemoryBuffer* buffer,
    uint32 sync_point) {
  static_cast<GpuMemoryBufferImpl*>(buffer)
      ->set_destruction_sync_point(sync_point);
}

bool BrowserGpuMemoryBufferManager::OnMemoryDump(
    base::trace_event::ProcessMemoryDump* pmd) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  for (const auto& client : clients_) {
    for (const auto& buffer : client.second) {
      if (buffer.second.type == gfx::EMPTY_BUFFER)
        continue;

      base::trace_event::MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(
          base::StringPrintf("%s/%d", kMemoryAllocatorName, buffer.first));
      if (!dump)
        return false;

      size_t buffer_size_in_bytes = 0;
      // Note: BufferSizeInBytes returns an approximated size for the buffer
      // but the factory can be made to return the exact size if this
      // approximation is not good enough.
      bool valid_size = GpuMemoryBufferImpl::BufferSizeInBytes(
          buffer.second.size, buffer.second.format, &buffer_size_in_bytes);
      DCHECK(valid_size);

      dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameOuterSize,
                      base::trace_event::MemoryAllocatorDump::kUnitsBytes,
                      buffer_size_in_bytes);
    }
  }

  return true;
}

void BrowserGpuMemoryBufferManager::ChildProcessDeletedGpuMemoryBuffer(
    gfx::GpuMemoryBufferId id,
    base::ProcessHandle child_process_handle,
    int child_client_id,
    uint32 sync_point) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(clients_.find(child_client_id) != clients_.end());

  BufferMap& buffers = clients_[child_client_id];

  BufferMap::iterator buffer_it = buffers.find(id);
  if (buffer_it == buffers.end()) {
    LOG(ERROR) << "Invalid GpuMemoryBuffer ID for child process.";
    return;
  }

  // This can happen if a child process managed to trigger a call to this while
  // a buffer is in the process of being allocated.
  if (buffer_it->second.type == gfx::EMPTY_BUFFER) {
    LOG(ERROR) << "Invalid GpuMemoryBuffer type.";
    return;
  }

  // Buffers allocated using the factory need to be destroyed through the
  // factory.
  if (buffer_it->second.type != gfx::SHARED_MEMORY_BUFFER) {
    gpu_memory_buffer_factory_host_->DestroyGpuMemoryBuffer(id,
                                                            child_client_id,
                                                            sync_point);
  }

  buffers.erase(buffer_it);
}

void BrowserGpuMemoryBufferManager::ProcessRemoved(
    base::ProcessHandle process_handle,
    int client_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  ClientMap::iterator client_it = clients_.find(client_id);
  if (client_it == clients_.end())
    return;

  for (const auto& buffer : client_it->second) {
    // This might happen if buffer is currenlty in the process of being
    // allocated. The buffer will in that case be cleaned up when allocation
    // completes.
    if (buffer.second.type == gfx::EMPTY_BUFFER)
      continue;

    // Skip shared memory buffers as they were not allocated using the factory.
    if (buffer.second.type == gfx::SHARED_MEMORY_BUFFER)
      continue;

    gpu_memory_buffer_factory_host_->DestroyGpuMemoryBuffer(buffer.first,
                                                            client_id, 0);
  }

  clients_.erase(client_it);
}

void BrowserGpuMemoryBufferManager::AllocateGpuMemoryBufferOnIO(
    AllocateGpuMemoryBufferRequest* request) {
  // Note: Unretained is safe as this is only used for synchronous allocation
  // from a non-IO thread.
  gpu_memory_buffer_factory_host_->CreateGpuMemoryBuffer(
      g_next_gpu_memory_buffer_id.GetNext(), request->size, request->format,
      request->usage, request->client_id, request->surface_id,
      base::Bind(&BrowserGpuMemoryBufferManager::GpuMemoryBufferAllocatedOnIO,
                 base::Unretained(this), base::Unretained(request)));
}

void BrowserGpuMemoryBufferManager::GpuMemoryBufferAllocatedOnIO(
    AllocateGpuMemoryBufferRequest* request,
    const gfx::GpuMemoryBufferHandle& handle) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // Early out if factory failed to allocate the buffer.
  if (handle.is_null()) {
    request->event.Signal();
    return;
  }

  DCHECK_NE(handle.type, gfx::SHARED_MEMORY_BUFFER);
  request->result = GpuMemoryBufferImpl::CreateFromHandle(
      handle,
      request->size,
      request->format,
      base::Bind(&BrowserGpuMemoryBufferManager::GpuMemoryBufferDeleted,
                 weak_ptr_factory_.GetWeakPtr(),
                 handle.id,
                 request->client_id));
  request->event.Signal();
}

void BrowserGpuMemoryBufferManager::GpuMemoryBufferDeleted(
    gfx::GpuMemoryBufferId id,
    int client_id,
    uint32 sync_point) {
  gpu_memory_buffer_factory_host_->DestroyGpuMemoryBuffer(id,
                                                          client_id,
                                                          sync_point);
}

void BrowserGpuMemoryBufferManager::GpuMemoryBufferAllocatedForChildProcess(
    int child_client_id,
    const AllocationCallback& callback,
    const gfx::GpuMemoryBufferHandle& handle) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  ClientMap::iterator client_it = clients_.find(child_client_id);

  // This can happen if the child process is removed while the buffer is being
  // allocated.
  if (client_it == clients_.end()) {
    if (!handle.is_null()) {
      gpu_memory_buffer_factory_host_->DestroyGpuMemoryBuffer(
          handle.id, child_client_id, 0);
    }
    callback.Run(gfx::GpuMemoryBufferHandle());
    return;
  }

  BufferMap& buffers = client_it->second;

  BufferMap::iterator buffer_it = buffers.find(handle.id);
  DCHECK(buffer_it != buffers.end());
  DCHECK_EQ(buffer_it->second.type, gfx::EMPTY_BUFFER);

  if (handle.is_null()) {
    buffers.erase(buffer_it);
    callback.Run(gfx::GpuMemoryBufferHandle());
    return;
  }

  // The factory should never return a shared memory backed buffer.
  DCHECK_NE(handle.type, gfx::SHARED_MEMORY_BUFFER);

  // Store the type of this buffer so it can be cleaned up if the child
  // process is removed.
  buffer_it->second.type = handle.type;

  callback.Run(handle);
}

}  // namespace content